Merge pull request #14469 from FakeFishGames/master
Separatist jobgear icons
This commit is contained in:
6
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
6
.github/DISCUSSION_TEMPLATE/bug-reports.yml
vendored
@@ -72,9 +72,9 @@ body:
|
|||||||
attributes:
|
attributes:
|
||||||
label: Version
|
label: Version
|
||||||
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
|
||||||
options:
|
options:
|
||||||
- v1.3.0.4
|
- v1.5.9.1 (Summer Update Hotfix 2)
|
||||||
- v1.4.3.0 (unstable)
|
- v1.6.101.0 (unstable)
|
||||||
- Other
|
- Other
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
@@ -66,6 +66,8 @@ namespace Barotrauma
|
|||||||
public Vector2 ShakePosition { get; private set; }
|
public Vector2 ShakePosition { get; private set; }
|
||||||
private float shakeTimer;
|
private float shakeTimer;
|
||||||
|
|
||||||
|
public float MovementLockTimer;
|
||||||
|
|
||||||
private float globalZoomScale = 1.0f;
|
private float globalZoomScale = 1.0f;
|
||||||
|
|
||||||
//used to smooth out the movement when in freecam
|
//used to smooth out the movement when in freecam
|
||||||
@@ -257,13 +259,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
float moveSpeed = 20.0f / zoom;
|
float moveSpeed = 20.0f / zoom;
|
||||||
|
|
||||||
|
MovementLockTimer -= deltaTime;
|
||||||
|
|
||||||
Vector2 moveCam = Vector2.Zero;
|
Vector2 moveCam = Vector2.Zero;
|
||||||
if (TargetPos == Vector2.Zero)
|
if (TargetPos == Vector2.Zero)
|
||||||
{
|
{
|
||||||
Vector2 moveInput = Vector2.Zero;
|
Vector2 moveInput = Vector2.Zero;
|
||||||
if (allowMove && !Freeze)
|
if (allowMove && !Freeze)
|
||||||
{
|
{
|
||||||
if (GUI.KeyboardDispatcher.Subscriber == null && allowInput)
|
if (GUI.KeyboardDispatcher.Subscriber == null && allowInput && MovementLockTimer <= 0.0f)
|
||||||
{
|
{
|
||||||
if (PlayerInput.KeyDown(Keys.LeftShift)) { moveSpeed *= 2.0f; }
|
if (PlayerInput.KeyDown(Keys.LeftShift)) { moveSpeed *= 2.0f; }
|
||||||
if (PlayerInput.KeyDown(Keys.LeftControl)) { moveSpeed *= 0.5f; }
|
if (PlayerInput.KeyDown(Keys.LeftControl)) { moveSpeed *= 0.5f; }
|
||||||
|
|||||||
@@ -558,14 +558,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (GameMain.Client != null) { chatMessage += " " + TextManager.Get("DeathChatNotification"); }
|
if (GameMain.Client != null) { chatMessage += " " + TextManager.Get("DeathChatNotification"); }
|
||||||
|
|
||||||
GameMain.NetworkMember.RespawnManager?.ShowRespawnPromptIfNeeded();
|
RespawnManager.ShowDeathPromptIfNeeded();
|
||||||
|
|
||||||
GameMain.NetworkMember.AddChatMessage(chatMessage.Value, ChatMessageType.Dead);
|
GameMain.NetworkMember.AddChatMessage(chatMessage.Value, ChatMessageType.Dead);
|
||||||
GameMain.LightManager.LosEnabled = false;
|
GameMain.LightManager.LosEnabled = false;
|
||||||
controlled = null;
|
controlled = null;
|
||||||
if (!(Screen.Selected?.Cam is null))
|
if (Screen.Selected?.Cam is Camera cam)
|
||||||
{
|
{
|
||||||
Screen.Selected.Cam.TargetPos = Vector2.Zero;
|
cam.TargetPos = Vector2.Zero;
|
||||||
|
//briefly lock moving the camera with arrow keys
|
||||||
|
//(it's annoying to have the camera fly off when you die while trying to move to safety)
|
||||||
|
cam.MovementLockTimer = 2.0f;
|
||||||
Lights.LightManager.ViewTarget = null;
|
Lights.LightManager.ViewTarget = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,7 +289,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||||
{
|
{
|
||||||
if (character.SelectedCharacter.CanInventoryBeAccessed)
|
if (character.SelectedCharacter.IsInventoryAccessibleTo(character))
|
||||||
{
|
{
|
||||||
character.SelectedCharacter.Inventory.Update(deltaTime, cam);
|
character.SelectedCharacter.Inventory.Update(deltaTime, cam);
|
||||||
}
|
}
|
||||||
@@ -677,7 +677,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
if (character.Params.CanInteract && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
|
||||||
{
|
{
|
||||||
if (character.SelectedCharacter.CanInventoryBeAccessed)
|
if (character.SelectedCharacter.IsInventoryAccessibleTo(character))
|
||||||
{
|
{
|
||||||
character.SelectedCharacter.Inventory.Locked = false;
|
character.SelectedCharacter.Inventory.Locked = false;
|
||||||
character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
|
character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
|
||||||
@@ -759,7 +759,7 @@ namespace Barotrauma
|
|||||||
textPos.Y += largeTextSize.Y;
|
textPos.Y += largeTextSize.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (character.FocusedCharacter.CanBeDragged)
|
if (character.FocusedCharacter.CanBeDraggedBy(character))
|
||||||
{
|
{
|
||||||
string text = character.CanEat ? "EatHint" : "GrabHint";
|
string text = character.CanEat ? "EatHint" : "GrabHint";
|
||||||
GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, InputType.Grab),
|
GUI.DrawString(spriteBatch, textPos, GetCachedHudText(text, InputType.Grab),
|
||||||
@@ -767,11 +767,7 @@ namespace Barotrauma
|
|||||||
textPos.Y += largeTextSize.Y;
|
textPos.Y += largeTextSize.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!character.DisableHealthWindow &&
|
if (character.FocusedCharacter.CanBeHealedBy(character))
|
||||||
character.IsFriendly(character.FocusedCharacter) &&
|
|
||||||
character.FocusedCharacter.CharacterHealth.UseHealthWindow &&
|
|
||||||
character.CanInteractWith(character.FocusedCharacter, 160f, false) &&
|
|
||||||
!character.IsClimbing)
|
|
||||||
{
|
{
|
||||||
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health),
|
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("HealHint", InputType.Health),
|
||||||
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
|
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
|
||||||
|
|||||||
@@ -141,7 +141,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
||||||
|
|
||||||
var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font) { Padding = Vector4.Zero };
|
var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), skill.DisplayName, textColor: textColor, font: font) { Padding = Vector4.Zero };
|
||||||
|
|
||||||
float modifiedSkillLevel = skill.Level;
|
float modifiedSkillLevel = skill.Level;
|
||||||
if (Character != null)
|
if (Character != null)
|
||||||
@@ -525,6 +525,7 @@ namespace Barotrauma
|
|||||||
ushort infoID = inc.ReadUInt16();
|
ushort infoID = inc.ReadUInt16();
|
||||||
string newName = inc.ReadString();
|
string newName = inc.ReadString();
|
||||||
string originalName = inc.ReadString();
|
string originalName = inc.ReadString();
|
||||||
|
bool renamingEnabled = inc.ReadBoolean();
|
||||||
int tagCount = inc.ReadByte();
|
int tagCount = inc.ReadByte();
|
||||||
HashSet<Identifier> tagSet = new HashSet<Identifier>();
|
HashSet<Identifier> tagSet = new HashSet<Identifier>();
|
||||||
for (int i = 0; i < tagCount; i++)
|
for (int i = 0; i < tagCount; i++)
|
||||||
@@ -538,7 +539,8 @@ namespace Barotrauma
|
|||||||
Color skinColor = inc.ReadColorR8G8B8();
|
Color skinColor = inc.ReadColorR8G8B8();
|
||||||
Color hairColor = inc.ReadColorR8G8B8();
|
Color hairColor = inc.ReadColorR8G8B8();
|
||||||
Color facialHairColor = inc.ReadColorR8G8B8();
|
Color facialHairColor = inc.ReadColorR8G8B8();
|
||||||
|
|
||||||
|
|
||||||
Identifier npcId = inc.ReadIdentifier();
|
Identifier npcId = inc.ReadIdentifier();
|
||||||
|
|
||||||
Identifier factionId = inc.ReadIdentifier();
|
Identifier factionId = inc.ReadIdentifier();
|
||||||
@@ -571,7 +573,8 @@ namespace Barotrauma
|
|||||||
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, variant, npcIdentifier: npcId)
|
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, variant, npcIdentifier: npcId)
|
||||||
{
|
{
|
||||||
ID = infoID,
|
ID = infoID,
|
||||||
MinReputationToHire = (factionId, minReputationToHire)
|
MinReputationToHire = (factionId, minReputationToHire),
|
||||||
|
RenamingEnabled = renamingEnabled
|
||||||
};
|
};
|
||||||
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
||||||
ch.Head.SkinColor = skinColor;
|
ch.Head.SkinColor = skinColor;
|
||||||
@@ -582,6 +585,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
ch.ExperiencePoints = inc.ReadInt32();
|
ch.ExperiencePoints = inc.ReadInt32();
|
||||||
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
|
ch.AdditionalTalentPoints = inc.ReadRangedInteger(0, MaxAdditionalTalentPoints);
|
||||||
|
ch.PermanentlyDead = inc.ReadBoolean();
|
||||||
return ch;
|
return ch;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -357,6 +357,7 @@ namespace Barotrauma
|
|||||||
case EventType.Control:
|
case EventType.Control:
|
||||||
bool myCharacter = msg.ReadBoolean();
|
bool myCharacter = msg.ReadBoolean();
|
||||||
byte ownerID = msg.ReadByte();
|
byte ownerID = msg.ReadByte();
|
||||||
|
bool renamingEnabled = msg.ReadBoolean();
|
||||||
ResetNetState();
|
ResetNetState();
|
||||||
if (myCharacter)
|
if (myCharacter)
|
||||||
{
|
{
|
||||||
@@ -385,6 +386,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
IsRemotePlayer = ownerID > 0;
|
IsRemotePlayer = ownerID > 0;
|
||||||
}
|
}
|
||||||
|
if (info != null)
|
||||||
|
{
|
||||||
|
info.RenamingEnabled = renamingEnabled;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case EventType.Status:
|
case EventType.Status:
|
||||||
ReadStatus(msg);
|
ReadStatus(msg);
|
||||||
@@ -723,11 +728,19 @@ namespace Barotrauma
|
|||||||
character.ReadStatus(inc);
|
character.ReadStatus(inc);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (character.IsHuman && character.TeamID != CharacterTeamType.FriendlyNPC && character.TeamID != CharacterTeamType.None && !character.IsDead)
|
if (character.IsHuman && character.TeamID != CharacterTeamType.FriendlyNPC && character.TeamID != CharacterTeamType.None)
|
||||||
{
|
{
|
||||||
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
|
CharacterInfo duplicateCharacterInfo = GameMain.GameSession.CrewManager.GetCharacterInfos().FirstOrDefault(c => c.ID == info.ID);
|
||||||
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
|
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
|
||||||
GameMain.GameSession.CrewManager.AddCharacter(character);
|
if (character.isDead)
|
||||||
|
{
|
||||||
|
//just add the info if dead (displayed in the round summary, and crew list if the character is revived)
|
||||||
|
GameMain.GameSession.CrewManager.AddCharacterInfo(character.info);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameMain.GameSession.CrewManager.AddCharacter(character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameMain.Client.SessionId == ownerId)
|
if (GameMain.Client.SessionId == ownerId)
|
||||||
|
|||||||
@@ -152,8 +152,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (value == null &&
|
if (value == null &&
|
||||||
Character.Controlled?.SelectedCharacter?.CharacterHealth != null &&
|
Character.Controlled?.SelectedCharacter?.CharacterHealth != null &&
|
||||||
Character.Controlled.SelectedCharacter.CharacterHealth == prevOpenHealthWindow/* &&
|
Character.Controlled.SelectedCharacter.CharacterHealth == prevOpenHealthWindow)
|
||||||
!Character.Controlled.SelectedCharacter.CanInventoryBeAccessed*/)
|
|
||||||
{
|
{
|
||||||
Character.Controlled.DeselectCharacter();
|
Character.Controlled.DeselectCharacter();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -289,18 +289,19 @@ namespace Barotrauma
|
|||||||
spriteAnimState.Add(decorativeSprite, new SpriteState());
|
spriteAnimState.Add(decorativeSprite, new SpriteState());
|
||||||
}
|
}
|
||||||
TintMask = null;
|
TintMask = null;
|
||||||
|
float sourceRectScale = ragdoll.RagdollParams.SourceRectScale;
|
||||||
foreach (var subElement in element.Elements())
|
foreach (var subElement in element.Elements())
|
||||||
{
|
{
|
||||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||||
{
|
{
|
||||||
case "sprite":
|
case "sprite":
|
||||||
Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams, ref _texturePath));
|
Sprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.normalSpriteParams, ref _texturePath), sourceRectScale: sourceRectScale);
|
||||||
break;
|
break;
|
||||||
case "damagedsprite":
|
case "damagedsprite":
|
||||||
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath));
|
DamagedSprite = new Sprite(subElement, file: GetSpritePath(subElement, Params.damagedSpriteParams, ref _damagedTexturePath), sourceRectScale: sourceRectScale);
|
||||||
break;
|
break;
|
||||||
case "conditionalsprite":
|
case "conditionalsprite":
|
||||||
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath));
|
var conditionalSprite = new ConditionalSprite(subElement, GetConditionalTarget(), file: GetSpritePath(subElement, null, ref _texturePath), sourceRectScale: sourceRectScale);
|
||||||
ConditionalSprites.Add(conditionalSprite);
|
ConditionalSprites.Add(conditionalSprite);
|
||||||
if (conditionalSprite.DeformableSprite != null)
|
if (conditionalSprite.DeformableSprite != null)
|
||||||
{
|
{
|
||||||
@@ -310,7 +311,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "deformablesprite":
|
case "deformablesprite":
|
||||||
_deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams, ref _texturePath));
|
_deformSprite = new DeformableSprite(subElement, filePath: GetSpritePath(subElement, Params.deformSpriteParams, ref _texturePath), sourceRectScale: sourceRectScale);
|
||||||
var deformations = CreateDeformations(subElement);
|
var deformations = CreateDeformations(subElement);
|
||||||
Deformations.AddRange(deformations);
|
Deformations.AddRange(deformations);
|
||||||
NonConditionalDeformations.AddRange(deformations);
|
NonConditionalDeformations.AddRange(deformations);
|
||||||
@@ -339,7 +340,7 @@ namespace Barotrauma
|
|||||||
ContentPath tintMaskPath = subElement.GetAttributeContentPath("texture");
|
ContentPath tintMaskPath = subElement.GetAttributeContentPath("texture");
|
||||||
if (!tintMaskPath.IsNullOrWhiteSpace())
|
if (!tintMaskPath.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
TintMask = new Sprite(subElement, file: GetSpritePath(tintMaskPath));
|
TintMask = new Sprite(subElement, file: GetSpritePath(tintMaskPath), sourceRectScale: sourceRectScale);
|
||||||
TintHighlightThreshold = subElement.GetAttributeFloat("highlightthreshold", 0.6f);
|
TintHighlightThreshold = subElement.GetAttributeFloat("highlightthreshold", 0.6f);
|
||||||
TintHighlightMultiplier = subElement.GetAttributeFloat("highlightmultiplier", 0.8f);
|
TintHighlightMultiplier = subElement.GetAttributeFloat("highlightmultiplier", 0.8f);
|
||||||
}
|
}
|
||||||
@@ -348,7 +349,7 @@ namespace Barotrauma
|
|||||||
ContentPath huskMaskPath = subElement.GetAttributeContentPath("texture");
|
ContentPath huskMaskPath = subElement.GetAttributeContentPath("texture");
|
||||||
if (!huskMaskPath.IsNullOrWhiteSpace())
|
if (!huskMaskPath.IsNullOrWhiteSpace())
|
||||||
{
|
{
|
||||||
HuskMask = new Sprite(subElement, file: GetSpritePath(huskMaskPath));
|
HuskMask = new Sprite(subElement, file: GetSpritePath(huskMaskPath), sourceRectScale: sourceRectScale);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -700,31 +701,34 @@ namespace Barotrauma
|
|||||||
if (spriteParams == null || Alpha <= 0) { return; }
|
if (spriteParams == null || Alpha <= 0) { return; }
|
||||||
float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
|
float burn = spriteParams.IgnoreTint ? 0 : burnOverLayStrength;
|
||||||
float brightness = Math.Max(1.0f - burn, 0.2f);
|
float brightness = Math.Max(1.0f - burn, 0.2f);
|
||||||
Color clr = spriteParams.Color;
|
Color tintedColor = spriteParams.Color;
|
||||||
if (!spriteParams.IgnoreTint)
|
if (!spriteParams.IgnoreTint)
|
||||||
{
|
{
|
||||||
clr = clr.Multiply(ragdoll.RagdollParams.Color);
|
tintedColor = tintedColor.Multiply(ragdoll.RagdollParams.Color);
|
||||||
if (character.Info != null)
|
if (character.Info != null)
|
||||||
{
|
{
|
||||||
clr = clr.Multiply(character.Info.Head.SkinColor);
|
tintedColor = tintedColor.Multiply(character.Info.Head.SkinColor);
|
||||||
}
|
}
|
||||||
if (character.CharacterHealth.FaceTint.A > 0 && type == LimbType.Head)
|
if (character.CharacterHealth.FaceTint.A > 0 && type == LimbType.Head)
|
||||||
{
|
{
|
||||||
clr = Color.Lerp(clr, character.CharacterHealth.FaceTint.Opaque(), character.CharacterHealth.FaceTint.A / 255.0f);
|
tintedColor = Color.Lerp(tintedColor, character.CharacterHealth.FaceTint.Opaque(), character.CharacterHealth.FaceTint.A / 255.0f);
|
||||||
}
|
}
|
||||||
if (character.CharacterHealth.BodyTint.A > 0)
|
if (character.CharacterHealth.BodyTint.A > 0)
|
||||||
{
|
{
|
||||||
clr = Color.Lerp(clr, character.CharacterHealth.BodyTint.Opaque(), character.CharacterHealth.BodyTint.A / 255.0f);
|
tintedColor = Color.Lerp(tintedColor, character.CharacterHealth.BodyTint.Opaque(), character.CharacterHealth.BodyTint.A / 255.0f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Color color = new Color((byte)(clr.R * brightness), (byte)(clr.G * brightness), (byte)(clr.B * brightness), clr.A);
|
Color color = new Color(tintedColor.Multiply(brightness), tintedColor.A);
|
||||||
|
Color colorWithoutTint = new Color(spriteParams.Color.Multiply(brightness), spriteParams.Color.A);
|
||||||
Color blankColor = new Color(brightness, brightness, brightness, 1);
|
Color blankColor = new Color(brightness, brightness, brightness, 1);
|
||||||
if (deadTimer > 0)
|
if (deadTimer > 0)
|
||||||
{
|
{
|
||||||
color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
|
color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
|
||||||
|
colorWithoutTint = Color.Lerp(colorWithoutTint, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
|
||||||
}
|
}
|
||||||
|
|
||||||
color = overrideColor ?? color;
|
color = overrideColor ?? color;
|
||||||
|
colorWithoutTint = overrideColor ?? colorWithoutTint;
|
||||||
blankColor = overrideColor ?? blankColor;
|
blankColor = overrideColor ?? blankColor;
|
||||||
color *= Alpha;
|
color *= Alpha;
|
||||||
blankColor *= Alpha;
|
blankColor *= Alpha;
|
||||||
@@ -739,6 +743,7 @@ namespace Barotrauma
|
|||||||
else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f)
|
else if (severedFadeOutTimer > SeveredFadeOutTime - 1.0f)
|
||||||
{
|
{
|
||||||
color *= SeveredFadeOutTime - severedFadeOutTimer;
|
color *= SeveredFadeOutTime - severedFadeOutTimer;
|
||||||
|
colorWithoutTint *= SeveredFadeOutTime - severedFadeOutTimer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -956,7 +961,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
DamagedSprite.Draw(spriteBatch,
|
DamagedSprite.Draw(spriteBatch,
|
||||||
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
new Vector2(body.DrawPosition.X, -body.DrawPosition.Y),
|
||||||
color * damageOverlayStrength, activeSprite.Origin,
|
colorWithoutTint * damageOverlayStrength, activeSprite.Origin,
|
||||||
-body.DrawRotation,
|
-body.DrawRotation,
|
||||||
Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
|
Scale, spriteEffect, activeSprite.Depth - depthStep * Math.Max(1, WearingItems.Count * 2)); // Multiply by 2 to get rid of z-fighting with some clothing combos
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -75,7 +75,7 @@ namespace Barotrauma
|
|||||||
foreach (ItemComponent ic in Item.Components)
|
foreach (ItemComponent ic in Item.Components)
|
||||||
{
|
{
|
||||||
if (ic is Holdable) { continue; }
|
if (ic is Holdable) { continue; }
|
||||||
if (!ic.AllowInGameEditing) { continue; }
|
if (!ic.AllowInGameEditing && Screen.Selected is not { IsEditor: true }) { continue; }
|
||||||
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 &&
|
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 &&
|
||||||
!SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable(ic)))
|
!SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable(ic)))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ namespace Barotrauma
|
|||||||
Length = Rect.Width + Padding + Label.Size.X;
|
Length = Rect.Width + Padding + Label.Size.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetLabel(LocalizedString label, CircuitBoxNode node)
|
||||||
|
{
|
||||||
|
Label = new CircuitBoxLabel(label, GUIStyle.SubHeadingFont);
|
||||||
|
Length = Rect.Width + Padding + Label.Size.X;
|
||||||
|
}
|
||||||
|
|
||||||
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Vector2 parentPos, Color color)
|
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, Vector2 parentPos, Color color)
|
||||||
{
|
{
|
||||||
if (CircuitBox.UI is not { } circuitBoxUi) { return; }
|
if (CircuitBox.UI is not { } circuitBoxUi) { return; }
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal sealed partial class CircuitBoxInputOutputNode
|
||||||
|
{
|
||||||
|
private const string PromptUserData = "InputOutputEditPrompt";
|
||||||
|
|
||||||
|
public void PromptEdit(GUIComponent parent)
|
||||||
|
{
|
||||||
|
CircuitBox.UI?.SetMenuVisibility(false);
|
||||||
|
GUIFrame backgroundBlocker = new(new RectTransform(Vector2.One, parent.RectTransform), style: "GUIBackgroundBlocker")
|
||||||
|
{
|
||||||
|
UserData = PromptUserData
|
||||||
|
};
|
||||||
|
|
||||||
|
GUILayoutGroup mainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.8f), backgroundBlocker.RectTransform, Anchor.Center), isHorizontal: false, childAnchor: Anchor.TopCenter);
|
||||||
|
GUIFrame labelArea = new(new RectTransform(new Vector2(1f, 0.8f), mainLayout.RectTransform, Anchor.Center));
|
||||||
|
|
||||||
|
GUILayoutGroup labelLayout = new GUILayoutGroup(new RectTransform(Vector2.One, labelArea.RectTransform), childAnchor: Anchor.Center);
|
||||||
|
GUIListBox labelList = new GUIListBox(new RectTransform(ToolBox.PaddingSizeParentRelative(labelLayout.RectTransform, 0.9f), labelLayout.RectTransform));
|
||||||
|
|
||||||
|
Dictionary<string, GUITextBox> textBoxes = new();
|
||||||
|
|
||||||
|
foreach (var conn in Connectors)
|
||||||
|
{
|
||||||
|
bool found = ConnectionLabelOverrides.TryGetValue(conn.Name, out string labelOverride);
|
||||||
|
|
||||||
|
GUILayoutGroup connLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), labelList.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), connLayout.RectTransform), text: conn.Connection.DisplayName, font: GUIStyle.SubHeadingFont);
|
||||||
|
GUITextBox box = GUI.CreateTextBoxWithPlaceholder(new RectTransform(new Vector2(0.6f, 1f), connLayout.RectTransform), text: found ? labelOverride : string.Empty, conn.Connection.DisplayName.Value);
|
||||||
|
box.MaxTextLength = MaxConnectionLabelLength;
|
||||||
|
|
||||||
|
textBoxes.Add(conn.Name, box);
|
||||||
|
}
|
||||||
|
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), mainLayout.RectTransform), text: TextManager.Get("confirm"))
|
||||||
|
{
|
||||||
|
OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
var newOverrides = textBoxes.ToDictionary(
|
||||||
|
static pair => pair.Key,
|
||||||
|
static pair => pair.Value.Text);
|
||||||
|
|
||||||
|
foreach (var (key, value) in newOverrides.ToImmutableDictionary())
|
||||||
|
{
|
||||||
|
if (ConnectionLabelOverrides.TryGetValue(key, out string newValue))
|
||||||
|
{
|
||||||
|
if (newValue == value)
|
||||||
|
{
|
||||||
|
newOverrides.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrWhiteSpace(value))
|
||||||
|
{
|
||||||
|
newOverrides.Remove(key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CircuitBox.SetConnectionLabelOverrides(this, newOverrides);
|
||||||
|
RemoveEditPrompt(parent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 0.1f), mainLayout.RectTransform), text: TextManager.Get("cancel"))
|
||||||
|
{
|
||||||
|
OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
RemoveEditPrompt(parent);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveEditPrompt(GUIComponent parent)
|
||||||
|
{
|
||||||
|
if (parent.FindChild(PromptUserData) is not { } promptParent) { return; }
|
||||||
|
parent.RemoveChild(promptParent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
@@ -80,6 +81,18 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var characterLimit = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), frame.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.03f, 0.02f) }, text: $"{bodyTextBox.Text.Length}/{NetLimitedString.MaxLength}", font: GUIStyle.SmallFont, textAlignment: Alignment.Right);
|
||||||
|
|
||||||
|
bodyTextBox.OnTextChanged += (textBox, _) =>
|
||||||
|
{
|
||||||
|
textBox.TextColor = textBox.TextBlock.SelectedTextColor = textBox.Text.Length > NetLimitedString.MaxLength
|
||||||
|
? GUIStyle.Red
|
||||||
|
: GUIStyle.TextColorNormal;
|
||||||
|
|
||||||
|
characterLimit.Text = $"{textBox.Text.Length}/{NetLimitedString.MaxLength}";
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
static void UpdateLabelColor(GUITextBox box)
|
static void UpdateLabelColor(GUITextBox box)
|
||||||
{
|
{
|
||||||
bool found = TextManager.ContainsTag(box.Text);
|
bool found = TextManager.ContainsTag(box.Text);
|
||||||
@@ -97,8 +110,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bodyTextBox.OnDeselected += (textBox, _) => UpdateLabelColor(textBox);
|
bodyTextBox.OnDeselected += static (textBox, _) => UpdateLabelColor(textBox);
|
||||||
headerTextBox.OnDeselected += (textBox, _) => UpdateLabelColor(textBox);
|
headerTextBox.OnDeselected += static (textBox, _) => UpdateLabelColor(textBox);
|
||||||
UpdateLabelColor(bodyTextBox);
|
UpdateLabelColor(bodyTextBox);
|
||||||
UpdateLabelColor(headerTextBox);
|
UpdateLabelColor(headerTextBox);
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,16 @@ namespace Barotrauma
|
|||||||
SnapshotMoveAffectedNodes();
|
SnapshotMoveAffectedNodes();
|
||||||
startClick = cursorPos;
|
startClick = cursorPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ClearSnapshot()
|
||||||
|
{
|
||||||
|
lastNodesUnderCursor = ImmutableHashSet<CircuitBoxNode>.Empty;
|
||||||
|
lastSelectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
|
||||||
|
moveAffectedComponents = ImmutableHashSet<CircuitBoxNode>.Empty;
|
||||||
|
LastConnectorUnderCursor = Option.None;
|
||||||
|
LastWireUnderCursor = Option.None;
|
||||||
|
LastResizeAffectedNode = Option.None;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds all connections and gathers them into a single list for easier iteration.
|
/// Finds all connections and gathers them into a single list for easier iteration.
|
||||||
@@ -168,38 +178,36 @@ namespace Barotrauma
|
|||||||
LastResizeAffectedNode = FindResizeBorderUnderCursor(lastNodesUnderCursor, cursorPos);
|
LastResizeAffectedNode = FindResizeBorderUnderCursor(lastNodesUnderCursor, cursorPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Option<(CircuitBoxResizeDirection, CircuitBoxNode)> FindResizeBorderUnderCursor(ImmutableHashSet<CircuitBoxNode> nodes, Vector2 cursorPos)
|
private Option<(CircuitBoxResizeDirection, CircuitBoxNode)> FindResizeBorderUnderCursor(ImmutableHashSet<CircuitBoxNode> nodes, Vector2 cursorPos)
|
||||||
{
|
{
|
||||||
foreach (var node in nodes)
|
if (!nodes.Any()) { return Option.None; }
|
||||||
|
|
||||||
|
var node = circuitBoxUi.GetTopmostNode(nodes);
|
||||||
|
if (node is null || !node.IsResizable) { return Option.None; }
|
||||||
|
|
||||||
|
const float borderSize = 32f;
|
||||||
|
|
||||||
|
var rect = node.Rect;
|
||||||
|
RectangleF bottomBorder = new(rect.X, rect.Top, rect.Width, borderSize);
|
||||||
|
RectangleF rightBorder = new(rect.Right - borderSize, rect.Y, borderSize, rect.Height);
|
||||||
|
RectangleF leftBorder = new(rect.X, rect.Y, borderSize, rect.Height);
|
||||||
|
|
||||||
|
bool hoverBottom = bottomBorder.Contains(cursorPos),
|
||||||
|
hoverRight = rightBorder.Contains(cursorPos),
|
||||||
|
hoverLeft = leftBorder.Contains(cursorPos);
|
||||||
|
|
||||||
|
var dir = CircuitBoxResizeDirection.None;
|
||||||
|
|
||||||
|
if (hoverBottom) { dir |= CircuitBoxResizeDirection.Down; }
|
||||||
|
if (hoverRight) { dir |= CircuitBoxResizeDirection.Right; }
|
||||||
|
if (hoverLeft) { dir |= CircuitBoxResizeDirection.Left; }
|
||||||
|
|
||||||
|
if (dir is CircuitBoxResizeDirection.None)
|
||||||
{
|
{
|
||||||
if (!node.IsResizable) { continue; }
|
return Option.None;
|
||||||
|
|
||||||
const float borderSize = 32f;
|
|
||||||
|
|
||||||
var rect = node.Rect;
|
|
||||||
RectangleF bottomBorder = new(rect.X, rect.Top, rect.Width, borderSize);
|
|
||||||
RectangleF rightBorder = new(rect.Right - borderSize, rect.Y, borderSize, rect.Height);
|
|
||||||
RectangleF leftBorder = new(rect.X, rect.Y, borderSize, rect.Height);
|
|
||||||
|
|
||||||
bool hoverBottom = bottomBorder.Contains(cursorPos),
|
|
||||||
hoverRight = rightBorder.Contains(cursorPos),
|
|
||||||
hoverLeft = leftBorder.Contains(cursorPos);
|
|
||||||
|
|
||||||
var dir = CircuitBoxResizeDirection.None;
|
|
||||||
|
|
||||||
if (hoverBottom) { dir |= CircuitBoxResizeDirection.Down; }
|
|
||||||
if (hoverRight) { dir |= CircuitBoxResizeDirection.Right; }
|
|
||||||
if (hoverLeft) { dir |= CircuitBoxResizeDirection.Left; }
|
|
||||||
|
|
||||||
if (dir is CircuitBoxResizeDirection.None)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Option.Some((dir, node));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Option.None;
|
return Option.Some((dir, node));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -281,14 +289,14 @@ namespace Barotrauma
|
|||||||
if (circuitBoxUi.Locked) { return; }
|
if (circuitBoxUi.Locked) { return; }
|
||||||
bool isDragThresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold;
|
bool isDragThresholdExceeded = Vector2.DistanceSquared(startClick, cursorPos) > dragTreshold * dragTreshold;
|
||||||
|
|
||||||
if (LastResizeAffectedNode.IsSome())
|
if (LastConnectorUnderCursor.IsSome())
|
||||||
{
|
|
||||||
IsResizing |= isDragThresholdExceeded;
|
|
||||||
}
|
|
||||||
else if (LastConnectorUnderCursor.IsSome())
|
|
||||||
{
|
{
|
||||||
IsWiring |= isDragThresholdExceeded;
|
IsWiring |= isDragThresholdExceeded;
|
||||||
}
|
}
|
||||||
|
else if (LastResizeAffectedNode.IsSome())
|
||||||
|
{
|
||||||
|
IsResizing |= isDragThresholdExceeded;
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IsDragging |= isDragThresholdExceeded;
|
IsDragging |= isDragThresholdExceeded;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
@@ -350,6 +350,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
component.Sprite.Draw(spriteBatch, PlayerInput.MousePosition);
|
component.Sprite.Draw(spriteBatch, PlayerInput.MousePosition);
|
||||||
}
|
}
|
||||||
|
if (PlayerInput.PrimaryMouseButtonHeld() && MouseSnapshotHandler.LastConnectorUnderCursor.IsSome())
|
||||||
|
{
|
||||||
|
CircuitBoxWire.SelectedWirePrefab.Sprite.Draw(spriteBatch, PlayerInput.MousePosition, CircuitBoxWire.SelectedWirePrefab.SpriteColor, scale: camera.Zoom);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var c in CircuitBox.Components)
|
foreach (var c in CircuitBox.Components)
|
||||||
{
|
{
|
||||||
@@ -360,11 +364,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
n.DrawHUD(spriteBatch, camera);
|
n.DrawHUD(spriteBatch, camera);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Locked)
|
if (Locked)
|
||||||
{
|
{
|
||||||
LocalizedString lockedText = TextManager.Get("CircuitBoxLocked")
|
LocalizedString lockedText = TextManager.Get("CircuitBoxLocked")
|
||||||
.Fallback(TextManager.Get("ConnectionLocked"));
|
.Fallback(TextManager.Get("ConnectionLocked"), useDefaultLanguageIfFound: false);
|
||||||
|
|
||||||
Vector2 size = GUIStyle.LargeFont.MeasureString(lockedText);
|
Vector2 size = GUIStyle.LargeFont.MeasureString(lockedText);
|
||||||
Vector2 pos = new Vector2(screenRect.Center.X - size.X / 2, screenRect.Top + screenRect.Height * 0.05f);
|
Vector2 pos = new Vector2(screenRect.Center.X - size.X / 2, screenRect.Top + screenRect.Height * 0.05f);
|
||||||
@@ -579,9 +583,25 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (isMouseOn)
|
if (isMouseOn)
|
||||||
{
|
{
|
||||||
if (CircuitBox.HeldComponent.IsNone() && PlayerInput.PrimaryMouseButtonDown())
|
if (PlayerInput.PrimaryMouseButtonDown())
|
||||||
{
|
{
|
||||||
MouseSnapshotHandler.StartDragging();
|
if (CircuitBox.HeldComponent.IsNone())
|
||||||
|
{
|
||||||
|
MouseSnapshotHandler.StartDragging();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MouseSnapshotHandler.ClearSnapshot();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PlayerInput.DoubleClicked() && MouseSnapshotHandler.FindWireUnderCursor(cursorPos).IsNone())
|
||||||
|
{
|
||||||
|
var topmostNode = GetTopmostNode(MouseSnapshotHandler.FindNodesUnderCursor(cursorPos));
|
||||||
|
if (topmostNode is CircuitBoxLabelNode label && circuitComponent is not null)
|
||||||
|
{
|
||||||
|
label.PromptEditText(circuitComponent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayerInput.MidButtonHeld() || (PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonHeld()))
|
if (PlayerInput.MidButtonHeld() || (PlayerInput.IsAltDown() && PlayerInput.PrimaryMouseButtonHeld()))
|
||||||
@@ -629,6 +649,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||||
{
|
{
|
||||||
|
bool selectedNode = false;
|
||||||
if (MouseSnapshotHandler.IsResizing && MouseSnapshotHandler.LastResizeAffectedNode.TryUnwrap(out var r))
|
if (MouseSnapshotHandler.IsResizing && MouseSnapshotHandler.LastResizeAffectedNode.TryUnwrap(out var r))
|
||||||
{
|
{
|
||||||
var (dir, node) = r;
|
var (dir, node) = r;
|
||||||
@@ -647,7 +668,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (!MouseSnapshotHandler.IsWiring)
|
else if (!MouseSnapshotHandler.IsWiring)
|
||||||
{
|
{
|
||||||
TrySelectComponentsUnderCursor();
|
selectedNode = TrySelectComponentsUnderCursor();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -658,8 +679,15 @@ namespace Barotrauma
|
|||||||
CircuitBox.AddWire(one, two);
|
CircuitBox.AddWire(one, two);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
CircuitBox.SelectWires(MouseSnapshotHandler.LastWireUnderCursor.TryUnwrap(out var wire) ? ImmutableArray.Create(wire) : ImmutableArray<CircuitBoxWire>.Empty, !PlayerInput.IsShiftDown());
|
if (MouseSnapshotHandler.LastWireUnderCursor.TryUnwrap(out var wire) && !MouseSnapshotHandler.IsDragging && !selectedNode)
|
||||||
|
{
|
||||||
|
CircuitBox.SelectWires(ImmutableArray.Create(wire), !PlayerInput.IsShiftDown());
|
||||||
|
}
|
||||||
|
else if (CircuitBox.Wires.Any(static wire => wire.IsSelectedByMe))
|
||||||
|
{
|
||||||
|
CircuitBox.SelectWires(ImmutableArray<CircuitBoxWire>.Empty, !PlayerInput.IsShiftDown());
|
||||||
|
}
|
||||||
|
|
||||||
CircuitBox.HeldComponent = Option.None;
|
CircuitBox.HeldComponent = Option.None;
|
||||||
MouseSnapshotHandler.EndDragging();
|
MouseSnapshotHandler.EndDragging();
|
||||||
@@ -732,11 +760,17 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TrySelectComponentsUnderCursor()
|
private bool TrySelectComponentsUnderCursor()
|
||||||
{
|
{
|
||||||
CircuitBoxNode? foundNode = GetTopmostNode(MouseSnapshotHandler.GetLastComponentsUnderCursor());
|
CircuitBoxNode? foundNode = GetTopmostNode(MouseSnapshotHandler.GetLastComponentsUnderCursor());
|
||||||
|
|
||||||
|
if (foundNode is CircuitBoxLabelNode && MouseSnapshotHandler.LastWireUnderCursor.IsSome())
|
||||||
|
{
|
||||||
|
foundNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
CircuitBox.SelectComponents(foundNode is null ? ImmutableArray<CircuitBoxNode>.Empty : ImmutableArray.Create(foundNode), !PlayerInput.IsShiftDown());
|
CircuitBox.SelectComponents(foundNode is null ? ImmutableArray<CircuitBoxNode>.Empty : ImmutableArray.Create(foundNode), !PlayerInput.IsShiftDown());
|
||||||
|
return foundNode is not null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OpenContextMenu()
|
private void OpenContextMenu()
|
||||||
@@ -767,17 +801,26 @@ namespace Barotrauma
|
|||||||
|
|
||||||
var editLabel = new ContextMenuOption(TextManager.Get("circuitboxeditlabel"), isEnabled: nodeOption is CircuitBoxLabelNode && !Locked, () =>
|
var editLabel = new ContextMenuOption(TextManager.Get("circuitboxeditlabel"), isEnabled: nodeOption is CircuitBoxLabelNode && !Locked, () =>
|
||||||
{
|
{
|
||||||
if (nodeOption is not CircuitBoxLabelNode label || circuitComponent is null) { return; }
|
if (circuitComponent is null) { return; }
|
||||||
|
if (nodeOption is not CircuitBoxLabelNode label) { return; }
|
||||||
|
|
||||||
label.PromptEditText(circuitComponent);
|
label.PromptEditText(circuitComponent);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var editConnections = new ContextMenuOption(TextManager.Get("circuitboxrenameconnections"), isEnabled: nodeOption is CircuitBoxInputOutputNode && !Locked, () =>
|
||||||
|
{
|
||||||
|
if (circuitComponent is null) { return; }
|
||||||
|
if (nodeOption is not CircuitBoxInputOutputNode io) { return; }
|
||||||
|
|
||||||
|
io.PromptEdit(circuitComponent);
|
||||||
|
});
|
||||||
|
|
||||||
var addLabelOption = new ContextMenuOption(TextManager.Get("circuitboxaddlabel"), isEnabled: !Locked, () =>
|
var addLabelOption = new ContextMenuOption(TextManager.Get("circuitboxaddlabel"), isEnabled: !Locked, () =>
|
||||||
{
|
{
|
||||||
CircuitBox.AddLabel(cursorPos);
|
CircuitBox.AddLabel(cursorPos);
|
||||||
});
|
});
|
||||||
|
|
||||||
ContextMenuOption[] allOptions = { addLabelOption, editLabel, option };
|
ContextMenuOption[] allOptions = { addLabelOption, editLabel, editConnections, option };
|
||||||
|
|
||||||
// show component name in the header to better indicate what is about to be deleted
|
// show component name in the header to better indicate what is about to be deleted
|
||||||
if (nodeOption is CircuitBoxComponent comp)
|
if (nodeOption is CircuitBoxComponent comp)
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ namespace Barotrauma
|
|||||||
textBox.MaxTextLength = maxLength;
|
textBox.MaxTextLength = maxLength;
|
||||||
textBox.OnKeyHit += (sender, key) =>
|
textBox.OnKeyHit += (sender, key) =>
|
||||||
{
|
{
|
||||||
if (key != Keys.Tab)
|
if (key != Keys.Tab && key != Keys.LeftShift)
|
||||||
{
|
{
|
||||||
ResetAutoComplete();
|
ResetAutoComplete();
|
||||||
}
|
}
|
||||||
@@ -181,7 +181,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive)
|
if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive)
|
||||||
{
|
{
|
||||||
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
|
int increment = PlayerInput.KeyDown(Keys.LeftShift) ? -1 : 1;
|
||||||
|
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : increment );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
||||||
@@ -634,6 +635,20 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
NewMessage("Ready checks can only be commenced in multiplayer.", Color.Red);
|
NewMessage("Ready checks can only be commenced in multiplayer.", Color.Red);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
commands.Add(new Command("setsalary", "setsalary [0-100] [character/default]: Sets the salary of a certain character or the default salary to a percentage.", (string[] args) =>
|
||||||
|
{
|
||||||
|
ThrowError("This command can only be used in multiplayer campaign.");
|
||||||
|
}, isCheat: true, getValidArgs: () =>
|
||||||
|
{
|
||||||
|
return new[]
|
||||||
|
{
|
||||||
|
new[]{ "0", "100" },
|
||||||
|
Enumerable.Union(
|
||||||
|
new string[] { "default" },
|
||||||
|
Character.CharacterList.Select(c => c.Name).Distinct().OrderBy(n => n)).ToArray(),
|
||||||
|
};
|
||||||
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("bindkey", "bindkey [key] [command]: Binds a key to a command.", (string[] args) =>
|
commands.Add(new Command("bindkey", "bindkey [key] [command]: Binds a key to a command.", (string[] args) =>
|
||||||
{
|
{
|
||||||
@@ -793,6 +808,7 @@ namespace Barotrauma
|
|||||||
AssignRelayToServer("money", true);
|
AssignRelayToServer("money", true);
|
||||||
AssignRelayToServer("showmoney", true);
|
AssignRelayToServer("showmoney", true);
|
||||||
AssignRelayToServer("setskill", true);
|
AssignRelayToServer("setskill", true);
|
||||||
|
AssignRelayToServer("setsalary", true);
|
||||||
AssignRelayToServer("readycheck", true);
|
AssignRelayToServer("readycheck", true);
|
||||||
commands.Add(new Command("debugjobassignment", "", (string[] args) => { }));
|
commands.Add(new Command("debugjobassignment", "", (string[] args) => { }));
|
||||||
AssignRelayToServer("debugjobassignment", true);
|
AssignRelayToServer("debugjobassignment", true);
|
||||||
@@ -838,11 +854,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
|
AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
|
||||||
{
|
{
|
||||||
Character tpCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false);
|
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||||
if (tpCharacter != null)
|
TeleportCharacter(cursorWorldPos, Character.Controlled, args);
|
||||||
{
|
|
||||||
tpCharacter.TeleportTo(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AssignOnExecute("spawn|spawncharacter", (string[] args) =>
|
AssignOnExecute("spawn|spawncharacter", (string[] args) =>
|
||||||
@@ -1425,6 +1438,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
AssignRelayToServer("water|editwater", false);
|
AssignRelayToServer("water|editwater", false);
|
||||||
AssignRelayToServer("fire|editfire", false);
|
AssignRelayToServer("fire|editfire", false);
|
||||||
|
#if DEBUG
|
||||||
|
AssignRelayToServer("debugvoip", true);
|
||||||
|
#endif
|
||||||
|
|
||||||
commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.",
|
commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.",
|
||||||
null,
|
null,
|
||||||
@@ -2345,6 +2361,11 @@ namespace Barotrauma
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
|
commands.Add(new Command("deathprompt", "Shows the death prompt for testing purposes.", (string[] args) =>
|
||||||
|
{
|
||||||
|
DeathPrompt.Create(delay: 1.0f);
|
||||||
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("listspamfilters", "Lists filters that are in the global spam filter.", (string[] args) =>
|
commands.Add(new Command("listspamfilters", "Lists filters that are in the global spam filter.", (string[] args) =>
|
||||||
{
|
{
|
||||||
if (!SpamServerFilters.GlobalSpamFilter.TryUnwrap(out var filter))
|
if (!SpamServerFilters.GlobalSpamFilter.TryUnwrap(out var filter))
|
||||||
@@ -3093,12 +3114,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (Screen.Selected == GameMain.GameScreen)
|
if (Screen.Selected == GameMain.GameScreen)
|
||||||
{
|
{
|
||||||
ThrowError("Reloading the package while in GameScreen may break things; to do it anyway, type 'reloadcorepackage [name] force'");
|
ThrowError("Reloading the package while in GameScreen may break things; to do it anyway, type 'reloadpackage [name] force'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||||
{
|
{
|
||||||
ThrowError("Reloading the core package while in sub editor may break thingg; to do it anyway, type 'reloadcorepackage [name] force'");
|
ThrowError("Reloading the core package while in sub editor may break things; to do it anyway, type 'reloadpackage [name] force'");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Barotrauma.Networking;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class AlienRuinMission : Mission
|
partial class EliminateTargetsMission : Mission
|
||||||
{
|
{
|
||||||
public override bool DisplayAsCompleted => State > 0;
|
public override bool DisplayAsCompleted => State > 0;
|
||||||
public override bool DisplayAsFailed => false;
|
public override bool DisplayAsFailed => false;
|
||||||
@@ -403,9 +403,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
|
senderName = (message.Type == ChatMessageType.Private ? "[PM] " : "") + message.SenderName;
|
||||||
}
|
}
|
||||||
if (message.Sender?.Info?.Job != null)
|
if (message.SenderCharacter?.Info?.Job != null)
|
||||||
{
|
{
|
||||||
senderColor = Color.Lerp(message.Sender.Info.Job.Prefab.UIColor, Color.White, 0.25f);
|
senderColor = Color.Lerp(message.SenderCharacter.Info.Job.Prefab.UIColor, Color.White, 0.25f);
|
||||||
}
|
}
|
||||||
|
|
||||||
var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
|
var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
|
||||||
|
|||||||
529
Barotrauma/BarotraumaClient/ClientSource/GUI/DeathPrompt.cs
Normal file
529
Barotrauma/BarotraumaClient/ClientSource/GUI/DeathPrompt.cs
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
#nullable enable
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Barotrauma;
|
||||||
|
|
||||||
|
internal class DeathPrompt
|
||||||
|
{
|
||||||
|
private static CoroutineHandle? createPromptCoroutine;
|
||||||
|
|
||||||
|
private GUIComponent? skillPanel;
|
||||||
|
private GUIComponent? newCharacterPanel;
|
||||||
|
private GUIComponent? takeOverBotPanel;
|
||||||
|
|
||||||
|
private GUIComponent? content;
|
||||||
|
|
||||||
|
public static GUIComponent? takeOverBotPanelFrame;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Private constructor, because these should only be created using the Show method
|
||||||
|
/// </summary>
|
||||||
|
private DeathPrompt() { }
|
||||||
|
|
||||||
|
public static void Create(float delay)
|
||||||
|
{
|
||||||
|
if (!RespawnManager.UseDeathPrompt) { return; }
|
||||||
|
if (GameMain.GameSession.DeathPrompt != null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (createPromptCoroutine != null && CoroutineManager.IsCoroutineRunning(createPromptCoroutine)) { return; }
|
||||||
|
if ((GameMain.GameSession is not { IsRunning: true })) { return; }
|
||||||
|
|
||||||
|
createPromptCoroutine = CoroutineManager.Invoke(() =>
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession != null)
|
||||||
|
{
|
||||||
|
GameMain.GameSession.DeathPrompt = new DeathPrompt();
|
||||||
|
GameMain.GameSession.DeathPrompt.CreatePrompt();
|
||||||
|
SoundPlayer.OverrideMusicType = "crewdead".ToIdentifier();
|
||||||
|
SoundPlayer.OverrideMusicDuration = 25.0f;
|
||||||
|
}
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddToGUIUpdateList()
|
||||||
|
{
|
||||||
|
content?.AddToGUIUpdateList();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreatePrompt()
|
||||||
|
{
|
||||||
|
const float FadeInInterval = 1.0f;
|
||||||
|
const float FadeInDuration = 1.0f;
|
||||||
|
|
||||||
|
bool permadeath = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.Permadeath };
|
||||||
|
bool ironman = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
|
||||||
|
|
||||||
|
var background = new GUICustomComponent(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), onDraw: DrawBackground)
|
||||||
|
{
|
||||||
|
UserData = this
|
||||||
|
};
|
||||||
|
background.FadeIn(wait: 0, duration: 5.0f);
|
||||||
|
|
||||||
|
var foreground = new GUIImage(new RectTransform(new Vector2(1.0f, GUI.RelativeHorizontalAspectRatio), background.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(-20)) }, "DeathScreenForeground")
|
||||||
|
{
|
||||||
|
Color = Color.White
|
||||||
|
};
|
||||||
|
foreground.FadeIn(wait: 0, duration: 5.0f);
|
||||||
|
foreground.Pulsate(startScale: Vector2.One, Vector2.One * 0.8f, duration: 25.0f);
|
||||||
|
|
||||||
|
var frame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), background.RectTransform, Anchor.Center))
|
||||||
|
{
|
||||||
|
UserData = this
|
||||||
|
};
|
||||||
|
frame.FadeIn(wait: 0, duration: FadeInDuration);
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), background.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.2f) }, string.Empty, font: GUIStyle.LargeFont, textAlignment: Alignment.TopCenter)
|
||||||
|
{
|
||||||
|
TextGetter = () =>
|
||||||
|
{
|
||||||
|
return GameMain.Client.EndRoundTimeRemaining > 0.0f ?
|
||||||
|
TextManager.GetWithVariable("endinground", "[time]", ToolBox.SecondsToReadableTime(GameMain.Client.EndRoundTimeRemaining))
|
||||||
|
.Fallback(ToolBox.SecondsToReadableTime(GameMain.Client.EndRoundTimeRemaining), useDefaultLanguageIfFound: false) :
|
||||||
|
string.Empty;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center))
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
|
||||||
|
//"you have died" header
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("deathprompt.header"), font: GUIStyle.LargeFont, textAlignment: Alignment.Center)
|
||||||
|
.FadeIn(wait: 0, duration: FadeInDuration);
|
||||||
|
|
||||||
|
var causeOfDeath = GameMain.Client?.Character?.CauseOfDeath;
|
||||||
|
if (causeOfDeath != null && causeOfDeath.Type != CauseOfDeathType.Unknown)
|
||||||
|
{
|
||||||
|
var causeOfDeathDescription = causeOfDeath.Affliction != null ?
|
||||||
|
causeOfDeath.Affliction.SelfCauseOfDeathDescription :
|
||||||
|
TextManager.Get("Self_CauseOfDeathDescription." + causeOfDeath.Type.ToString(), "Self_CauseOfDeathDescription.Damage");
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), causeOfDeathDescription)
|
||||||
|
.FadeIn(wait: FadeInInterval * 2, duration: FadeInDuration);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (permadeath)
|
||||||
|
{
|
||||||
|
if (ironman)
|
||||||
|
{
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||||
|
TextManager.Get("deathprompt.permadeathnotification") + "\n\n" + TextManager.Get("deathprompt.ironmanexplanation"), wrap: true)
|
||||||
|
.FadeIn(wait: FadeInInterval * 3, duration: FadeInDuration);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||||
|
TextManager.Get("deathprompt.permadeathnotification") + '\n' + TextManager.Get("deathprompt.takeoverbotexplanation"), wrap: true)
|
||||||
|
.FadeIn(wait: FadeInInterval * 3, duration: FadeInDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (RespawnManager.SkillLossPercentageOnDeath > 0)
|
||||||
|
{
|
||||||
|
string skillLossAmount = ((int)RespawnManager.SkillLossPercentageOnDeath).ToString();
|
||||||
|
string skillLossText = $"‖color: { XMLExtensions.ToStringHex(GUIStyle.Red)}‖{skillLossAmount}‖end‖";
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform),
|
||||||
|
RichString.Rich(TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", skillLossText)))
|
||||||
|
.FadeIn(wait: FadeInInterval * 3, duration: FadeInDuration);
|
||||||
|
};
|
||||||
|
|
||||||
|
//"what do you want to do" buttons in the middle
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var decisionButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
|
||||||
|
if (ironman)
|
||||||
|
{
|
||||||
|
// The only option is to spectate
|
||||||
|
var buttonContainerMiddle = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), decisionButtonContainer.RectTransform), childAnchor: Anchor.Center);
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainerMiddle.RectTransform), TextManager.Get("spectatebutton"))
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: true);
|
||||||
|
Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var buttonContainerLeft = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), decisionButtonContainer.RectTransform));
|
||||||
|
var buttonContainerRight = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), decisionButtonContainer.RectTransform));
|
||||||
|
|
||||||
|
// The default "I'll wait" button
|
||||||
|
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerLeft.RectTransform), TextManager.Get("respawnquestionpromptwait"))
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: true);
|
||||||
|
Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
|
||||||
|
if (permadeath)
|
||||||
|
{
|
||||||
|
if (GameMain.Client != null && GameMain.Client.ServerSettings.AllowBotTakeoverOnPermadeath)
|
||||||
|
{
|
||||||
|
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.takeoverbot"))
|
||||||
|
{
|
||||||
|
Enabled = false,
|
||||||
|
OnAddedToGUIUpdateList = (component) =>
|
||||||
|
{
|
||||||
|
component.Enabled = GetAvailableBots().Any();
|
||||||
|
},
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
if (takeOverBotPanel == null)
|
||||||
|
{
|
||||||
|
CreateTakeOverBotPanel(frame, this);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
takeOverBotPanel.Parent?.RemoveChild(takeOverBotPanel);
|
||||||
|
takeOverBotPanel = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false);
|
||||||
|
Close();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
Enabled = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.MidRound }
|
||||||
|
}.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//"info buttons" at the bottom
|
||||||
|
//-------------------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
var infoButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), content.RectTransform), childAnchor: Anchor.TopRight)
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
RelativeSpacing = 0.025f
|
||||||
|
};
|
||||||
|
if (permadeath)
|
||||||
|
{
|
||||||
|
if (Level.IsLoadedFriendlyOutpost)
|
||||||
|
{
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f), infoButtonContainer.RectTransform), TextManager.Get("npctitle.hrmanager"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession?.Campaign is { } campaign)
|
||||||
|
{
|
||||||
|
campaign.ShowCampaignUI = true;
|
||||||
|
campaign.CampaignUI?.SelectTab(CampaignMode.InteractionType.Crew);
|
||||||
|
}
|
||||||
|
Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 5, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f), infoButtonContainer.RectTransform), TextManager.Get("deathprompt.showskills"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
if (skillPanel == null)
|
||||||
|
{
|
||||||
|
CreateSkillPanel(frame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
skillPanel.Parent?.RemoveChild(skillPanel);
|
||||||
|
skillPanel = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 5, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f), infoButtonContainer.RectTransform), TextManager.Get("deathprompt.newcharacter"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
if (newCharacterPanel == null)
|
||||||
|
{
|
||||||
|
CreateNewCharacterPanel(frame);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newCharacterPanel.Parent?.RemoveChild(newCharacterPanel);
|
||||||
|
newCharacterPanel = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 5, duration: FadeInDuration, alsoChildren: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO
|
||||||
|
/*new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), infoButtonContainer.RectTransform), "Respawn settings", style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}.FadeIn(wait: FadeInInterval * 5, duration: FadeInDuration, alsoChildren: true);*/
|
||||||
|
|
||||||
|
this.content = background;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateSkillPanel(GUIComponent parent, CharacterInfo? characterInfo)
|
||||||
|
{
|
||||||
|
if (characterInfo == null) { return; }
|
||||||
|
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent.RectTransform, Anchor.CenterRight, Pivot.CenterLeft));
|
||||||
|
|
||||||
|
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.8f), frame.RectTransform, Anchor.Center), isHorizontal: true)
|
||||||
|
{
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), content.RectTransform))
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
var middleColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1.0f), content.RectTransform))
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
var rightColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1.0f), content.RectTransform))
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
|
||||||
|
var leftHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), leftColumn.RectTransform), TextManager.Get("Skills"), font: GUIStyle.SubHeadingFont, textColor: GUIStyle.TextColorBright);
|
||||||
|
var middleHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), middleColumn.RectTransform), TextManager.Get("deathprompt.SkillsLostHeader"), font: GUIStyle.SubHeadingFont, textColor: GUIStyle.TextColorBright);
|
||||||
|
var rightHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform), TextManager.Get("deathprompt.respawnnow"), font: GUIStyle.SubHeadingFont, textColor: GUIStyle.TextColorBright);
|
||||||
|
|
||||||
|
GUITextBlock.AutoScaleAndNormalize(leftHeader, middleHeader, rightHeader);
|
||||||
|
|
||||||
|
foreach (var skill in characterInfo.Job.GetSkills().OrderByDescending(s => s.Level))
|
||||||
|
{
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), leftColumn.RectTransform), skill.DisplayName);
|
||||||
|
|
||||||
|
int previousSkill = (int)skill.HighestLevelDuringRound;
|
||||||
|
int reducedSkill = (int)RespawnManager.GetReducedSkill(characterInfo, skill, RespawnManager.SkillLossPercentageOnDeath);
|
||||||
|
int reducedSkillOnImmediateRespawn = (int)RespawnManager.GetReducedSkill(characterInfo, skill, RespawnManager.SkillLossPercentageOnImmediateRespawn, currentSkillLevel: reducedSkill);
|
||||||
|
|
||||||
|
int skillLoss = reducedSkill - previousSkill;
|
||||||
|
int skillLossOnImmediateRespawn = reducedSkillOnImmediateRespawn - previousSkill;
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), middleColumn.RectTransform),
|
||||||
|
RichString.Rich($"{reducedSkill} (‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{skillLoss}‖end‖)"));
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), rightColumn.RectTransform),
|
||||||
|
RichString.Rich($"{reducedSkillOnImmediateRespawn} (‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{skillLossOnImmediateRespawn}‖end‖)"));
|
||||||
|
}
|
||||||
|
|
||||||
|
new GUIButton(new RectTransform(new Vector2(1.0f, 0.15f), leftColumn.RectTransform, Anchor.BottomLeft), TextManager.Get("Close"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
IgnoreLayoutGroups = true,
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
frame.Parent?.RemoveChild(frame);
|
||||||
|
skillPanel = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
skillPanel = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateNewCharacterPanel(GUIComponent parent)
|
||||||
|
{
|
||||||
|
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.5f), parent.RectTransform, Anchor.CenterRight, Pivot.CenterLeft));
|
||||||
|
|
||||||
|
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: false)
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
GameMain.NetLobbyScreen.CreatePlayerFrame(content, alwaysAllowEditing: true, createPendingText: false);
|
||||||
|
|
||||||
|
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.05f,
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform, Anchor.BottomLeft), TextManager.Get("Cancel"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
frame.Parent?.RemoveChild(frame);
|
||||||
|
newCharacterPanel = null;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform, Anchor.BottomLeft), TextManager.Get("ApplySettingsYes"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
|
||||||
|
{
|
||||||
|
frame.Parent?.RemoveChild(frame);
|
||||||
|
newCharacterPanel = null;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
newCharacterPanel = frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CreateTakeOverBotPanel()
|
||||||
|
{
|
||||||
|
var panelHolder = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), GUI.Canvas, Anchor.Center));
|
||||||
|
var takeOverBotPanel = CreateTakeOverBotPanel(panelHolder, deathPrompt: null);
|
||||||
|
if (takeOverBotPanel != null)
|
||||||
|
{
|
||||||
|
takeOverBotPanel.RectTransform.SetPosition(Anchor.Center);
|
||||||
|
GUIMessageBox.MessageBoxes.Add(panelHolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Static because the "take over bot" panel can be accessed outside the death prompt too
|
||||||
|
/// </summary>
|
||||||
|
private static GUIComponent? CreateTakeOverBotPanel(GUIComponent parent, DeathPrompt? deathPrompt)
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession?.CrewManager == null) { return null; }
|
||||||
|
if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign campaign) { return null; }
|
||||||
|
|
||||||
|
if (campaign.CampaignUI == null) { campaign.InitCampaignUI(); }
|
||||||
|
|
||||||
|
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 1.0f), parent.RectTransform, Anchor.CenterRight, Pivot.CenterLeft));
|
||||||
|
takeOverBotPanelFrame = frame;
|
||||||
|
|
||||||
|
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: false)
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
|
||||||
|
var botList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.9f), content.RectTransform));
|
||||||
|
foreach (CharacterInfo c in GetAvailableBots())
|
||||||
|
{
|
||||||
|
var characterFrame = campaign.CampaignUI?.HRManagerUI.CreateCharacterFrame(c, botList, hideSalary: true);
|
||||||
|
if (characterFrame != null)
|
||||||
|
{
|
||||||
|
characterFrame.UserData = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
botList.UpdateScrollBarSize();
|
||||||
|
|
||||||
|
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.15f), content.RectTransform), isHorizontal: true)
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.05f,
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform, Anchor.BottomLeft), TextManager.Get("Cancel"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
GUIMessageBox.MessageBoxes.Remove(frame.Parent);
|
||||||
|
frame.Parent?.RemoveChild(frame);
|
||||||
|
if (deathPrompt != null)
|
||||||
|
{
|
||||||
|
deathPrompt.takeOverBotPanel = null;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform, Anchor.BottomLeft), TextManager.Get("inputtype.select"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
Enabled = false,
|
||||||
|
OnAddedToGUIUpdateList = (component) =>
|
||||||
|
{
|
||||||
|
component.Enabled = botList.SelectedData is CharacterInfo;
|
||||||
|
},
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
if (botList.SelectedData is CharacterInfo selectedCharacter && GameMain.Client is GameClient client)
|
||||||
|
{
|
||||||
|
client.SendTakeOverBotRequest(selectedCharacter);
|
||||||
|
GUIMessageBox.MessageBoxes.Remove(frame.Parent);
|
||||||
|
deathPrompt?.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Conditions for sending bot takeover request not met");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if (deathPrompt != null)
|
||||||
|
{
|
||||||
|
deathPrompt.takeOverBotPanel = frame;
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static IEnumerable<CharacterInfo> GetAvailableBots()
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession?.CrewManager is { } crewManager)
|
||||||
|
{
|
||||||
|
return crewManager.GetCharacterInfos().Where(c =>
|
||||||
|
/*either an alive bot */
|
||||||
|
c is { Character.IsBot: true, Character.IsDead: false } ||
|
||||||
|
/* or a newly hired bot that hasn't spawned yet */
|
||||||
|
(c.IsNewHire && c.Character == null));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return Enumerable.Empty<CharacterInfo>();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void DrawBackground(SpriteBatch spriteBatch, GUICustomComponent guiCustomComponent)
|
||||||
|
{
|
||||||
|
var background = GUIStyle.GetComponentStyle("DeathScreenBackground");
|
||||||
|
if (background != null)
|
||||||
|
{
|
||||||
|
GUI.DrawBackgroundSprite(spriteBatch, background.GetDefaultSprite(), Color.White * (guiCustomComponent.Color.A / 255.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Close()
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession != null)
|
||||||
|
{
|
||||||
|
GameMain.GameSession.DeathPrompt = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CloseBotPanel()
|
||||||
|
{
|
||||||
|
if (takeOverBotPanelFrame is GUIComponent frame)
|
||||||
|
{
|
||||||
|
GUIMessageBox.MessageBoxes.Remove(frame.Parent);
|
||||||
|
frame.Parent?.RemoveChild(frame);
|
||||||
|
}
|
||||||
|
takeOverBotPanelFrame = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -717,9 +717,9 @@ namespace Barotrauma
|
|||||||
private static readonly Queue<GUIComponent> removals = new Queue<GUIComponent>();
|
private static readonly Queue<GUIComponent> removals = new Queue<GUIComponent>();
|
||||||
private static readonly Queue<GUIComponent> additions = new Queue<GUIComponent>();
|
private static readonly Queue<GUIComponent> additions = new Queue<GUIComponent>();
|
||||||
// A helpers list for all elements that have a draw order less than 0.
|
// A helpers list for all elements that have a draw order less than 0.
|
||||||
private static readonly List<GUIComponent> first = new List<GUIComponent>();
|
private static readonly List<GUIComponent> firstAdditions = new List<GUIComponent>();
|
||||||
// A helper list for all elements that have a draw order greater than 0.
|
// A helper list for all elements that have a draw order greater than 0.
|
||||||
private static readonly List<GUIComponent> last = new List<GUIComponent>();
|
private static readonly List<GUIComponent> lastAdditions = new List<GUIComponent>();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the component on the addition queue.
|
/// Adds the component on the addition queue.
|
||||||
@@ -737,11 +737,11 @@ namespace Barotrauma
|
|||||||
if (!component.Visible) { return; }
|
if (!component.Visible) { return; }
|
||||||
if (component.UpdateOrder < 0)
|
if (component.UpdateOrder < 0)
|
||||||
{
|
{
|
||||||
first.Add(component);
|
firstAdditions.Add(component);
|
||||||
}
|
}
|
||||||
else if (component.UpdateOrder > 0)
|
else if (component.UpdateOrder > 0)
|
||||||
{
|
{
|
||||||
last.Add(component);
|
lastAdditions.Add(component);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -800,9 +800,9 @@ namespace Barotrauma
|
|||||||
RemoveFromUpdateList(component);
|
RemoveFromUpdateList(component);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ProcessHelperList(first);
|
ProcessHelperList(firstAdditions);
|
||||||
ProcessAdditions();
|
ProcessAdditions();
|
||||||
ProcessHelperList(last);
|
ProcessHelperList(lastAdditions);
|
||||||
ProcessRemovals();
|
ProcessRemovals();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -897,7 +897,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static IEnumerable<GUIComponent> GetAdditions()
|
public static IEnumerable<GUIComponent> GetAdditions()
|
||||||
{
|
{
|
||||||
return additions;
|
return additions.Union(firstAdditions).Union(lastAdditions);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -2171,6 +2171,28 @@ namespace Barotrauma
|
|||||||
return frame;
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static GUITextBox CreateTextBoxWithPlaceholder(RectTransform rectT, string text, LocalizedString placeholder)
|
||||||
|
{
|
||||||
|
var holder = new GUIFrame(rectT, style: null);
|
||||||
|
var textBox = new GUITextBox(new RectTransform(Vector2.One, holder.RectTransform, Anchor.CenterLeft), text, createClearButton: false);
|
||||||
|
var placeholderElement = new GUITextBlock(new RectTransform(Vector2.One, holder.RectTransform, Anchor.CenterLeft),
|
||||||
|
textColor: Color.DarkGray * 0.6f,
|
||||||
|
text: placeholder,
|
||||||
|
textAlignment: Alignment.CenterLeft)
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
|
||||||
|
new GUICustomComponent(new RectTransform(Vector2.Zero, holder.RectTransform),
|
||||||
|
onUpdate: delegate { placeholderElement.RectTransform.NonScaledSize = textBox.Frame.RectTransform.NonScaledSize; });
|
||||||
|
|
||||||
|
textBox.OnSelected += delegate { placeholderElement.Visible = false; };
|
||||||
|
textBox.OnDeselected += delegate { placeholderElement.Visible = textBox.Text.IsNullOrWhiteSpace(); };
|
||||||
|
|
||||||
|
placeholderElement.Visible = string.IsNullOrWhiteSpace(text);
|
||||||
|
return textBox;
|
||||||
|
}
|
||||||
|
|
||||||
public static void NotifyPrompt(LocalizedString header, LocalizedString body)
|
public static void NotifyPrompt(LocalizedString header, LocalizedString body)
|
||||||
{
|
{
|
||||||
GUIMessageBox msgBox = new GUIMessageBox(header, body, new[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
|
GUIMessageBox msgBox = new GUIMessageBox(header, body, new[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
|
||||||
|
|||||||
@@ -815,7 +815,8 @@ namespace Barotrauma
|
|||||||
protected virtual void SetAlpha(float a)
|
protected virtual void SetAlpha(float a)
|
||||||
{
|
{
|
||||||
color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a);
|
color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a);
|
||||||
hoverColor = new Color(hoverColor.R / 255.0f, hoverColor.G / 255.0f, hoverColor.B / 255.0f, a);;
|
hoverColor = new Color(hoverColor.R / 255.0f, hoverColor.G / 255.0f, hoverColor.B / 255.0f, a);
|
||||||
|
disabledColor = new Color(disabledColor.R / 255.0f, disabledColor.G / 255.0f, disabledColor.B / 255.0f, a);
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)
|
public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)
|
||||||
@@ -835,15 +836,29 @@ namespace Barotrauma
|
|||||||
flashColor = (color == null) ? GUIStyle.Red : (Color)color;
|
flashColor = (color == null) ? GUIStyle.Red : (Color)color;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FadeOut(float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null)
|
public void FadeOut(float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null, bool alsoChildren = false)
|
||||||
{
|
{
|
||||||
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait, onRemove));
|
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait, onRemove));
|
||||||
|
if (alsoChildren)
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.FadeOut(duration, removeAfter, wait, onRemove, alsoChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void FadeIn(float wait, float duration)
|
public void FadeIn(float wait, float duration, bool alsoChildren = false)
|
||||||
{
|
{
|
||||||
SetAlpha(0.0f);
|
SetAlpha(0.0f);
|
||||||
CoroutineManager.StartCoroutine(LerpAlpha(1.0f, duration, false, wait));
|
CoroutineManager.StartCoroutine(LerpAlpha(1.0f, duration, false, wait));
|
||||||
|
if (alsoChildren)
|
||||||
|
{
|
||||||
|
foreach (var child in Children)
|
||||||
|
{
|
||||||
|
child.FadeIn(wait, duration, alsoChildren);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SlideIn(float wait, float duration, int amount, SlideDirection direction)
|
public void SlideIn(float wait, float duration, int amount, SlideDirection direction)
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (sprite?.Texture is { IsDisposed: false })
|
else if (sprite?.Texture is { IsDisposed: false })
|
||||||
{
|
{
|
||||||
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, origin,
|
spriteBatch.Draw(sprite.Texture, new Vector2(Rect.X + Rect.Width / 2.0f, Rect.Y + Rect.Height / 2.0f), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, origin,
|
||||||
Scale, SpriteEffects, 0.0f);
|
Scale, SpriteEffects, 0.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public class GUIMessageBox : GUIFrame
|
public class GUIMessageBox : GUIFrame
|
||||||
{
|
{
|
||||||
#warning TODO: change this to List<GUIMessageBox> and fix incorrect uses of this list
|
|
||||||
public readonly static List<GUIComponent> MessageBoxes = new List<GUIComponent>();
|
public readonly static List<GUIComponent> MessageBoxes = new List<GUIComponent>();
|
||||||
private static int DefaultWidth
|
private static int DefaultWidth
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (MathUtils.NearlyEqual(value, floatValue)) { return; }
|
if (Math.Abs(value - floatValue) < 0.0001f && MathUtils.NearlyEqual(value, floatValue)) { return; }
|
||||||
floatValue = value;
|
floatValue = value;
|
||||||
ClampFloatValue();
|
ClampFloatValue();
|
||||||
float newValue = floatValue;
|
float newValue = floatValue;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -25,6 +26,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public delegate void OnValueChangedHandler(GUISelectionCarousel<T> carousel);
|
public delegate void OnValueChangedHandler(GUISelectionCarousel<T> carousel);
|
||||||
public OnValueChangedHandler? OnValueChanged;
|
public OnValueChangedHandler? OnValueChanged;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Are there some conditions for selecting a particular element?
|
||||||
|
/// </summary>
|
||||||
|
public Func<T, bool>? ElementSelectionCondition { get; set; }
|
||||||
|
|
||||||
public GUITextBlock TextBlock { get; private set; }
|
public GUITextBlock TextBlock { get; private set; }
|
||||||
|
|
||||||
@@ -89,35 +95,9 @@ namespace Barotrauma
|
|||||||
GUIStyle.Apply(TextBlock, "TextBlock", this);
|
GUIStyle.Apply(TextBlock, "TextBlock", this);
|
||||||
RightButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), layoutGroup.RectTransform), style: "GUIButtonToggleRight");
|
RightButton = new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), layoutGroup.RectTransform), style: "GUIButtonToggleRight");
|
||||||
GUIStyle.Apply(RightButton, "RightButton", this);
|
GUIStyle.Apply(RightButton, "RightButton", this);
|
||||||
|
|
||||||
RightButton.OnClicked += (btn, userData) =>
|
RightButton.OnClicked += (_, _) => SelectNextValidElement();
|
||||||
{
|
LeftButton.OnClicked += (_, _) => SelectNextValidElement(directionLeft: true);
|
||||||
if (elements.Count < 2) { return false; }
|
|
||||||
if (SelectedElement == null)
|
|
||||||
{
|
|
||||||
SelectElement(elements.First());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int newIndex = (elements.IndexOf(SelectedElement) + 1) % elements.Count;
|
|
||||||
SelectElement(elements[newIndex]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
LeftButton.OnClicked += (btn, userData) =>
|
|
||||||
{
|
|
||||||
if (elements.Count < 2) { return false; }
|
|
||||||
if (SelectedElement == null)
|
|
||||||
{
|
|
||||||
SelectElement(elements.First());
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
int newIndex = MathUtils.PositiveModulo((elements.IndexOf(SelectedElement) - 1), elements.Count);
|
|
||||||
SelectElement(elements[newIndex]);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
|
|
||||||
if (newElements != null && newElements.Any())
|
if (newElements != null && newElements.Any())
|
||||||
{
|
{
|
||||||
@@ -140,9 +120,11 @@ namespace Barotrauma
|
|||||||
SelectElement(null);
|
SelectElement(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (elements.FirstOrDefault(e => value.Equals(e.value)) is { } element)
|
var matchingElement = elements.Where(e => value.Equals(e.value)) // selection is in the set of possible values
|
||||||
|
.FirstOrDefault(e => ElementSelectionCondition == null || ElementSelectionCondition(e.value)); // selection matches extra conditions, if any
|
||||||
|
if (matchingElement != null)
|
||||||
{
|
{
|
||||||
SelectElement(element);
|
SelectElement(matchingElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,5 +169,42 @@ namespace Barotrauma
|
|||||||
SelectElement(newElement);
|
SelectElement(newElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// Refresh the current selection, for example if there are conditions for which elements are valid, and those might have changed
|
||||||
|
/// </summary>
|
||||||
|
public void Refresh()
|
||||||
|
{
|
||||||
|
if (SelectedElement != null)
|
||||||
|
{
|
||||||
|
if (ElementSelectionCondition == null || ElementSelectionCondition(SelectedElement.value))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectElement(elements.FirstOrDefault(e => ElementSelectionCondition == null || ElementSelectionCondition(e.value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool SelectNextValidElement(bool directionLeft = false)
|
||||||
|
{
|
||||||
|
if (elements.Count < 2) { return false; }
|
||||||
|
|
||||||
|
// Try to find a valid next/previous element
|
||||||
|
int currentIndex = SelectedElement == null ? -1 : elements.IndexOf(SelectedElement);
|
||||||
|
int newIndex = currentIndex;
|
||||||
|
for (int i = 0; i < elements.Count; i++)
|
||||||
|
{
|
||||||
|
newIndex = directionLeft ? MathUtils.PositiveModulo((newIndex - 1), elements.Count) : (newIndex + 1) % elements.Count;
|
||||||
|
if (ElementSelectionCondition == null || ElementSelectionCondition(elements[newIndex].value))
|
||||||
|
{
|
||||||
|
SelectElement(elements[newIndex]);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// No valid elements found
|
||||||
|
SelectElement(null);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -438,8 +438,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void SetAlpha(float a)
|
protected override void SetAlpha(float a)
|
||||||
{
|
{
|
||||||
// base.SetAlpha(a);
|
textColor = new Color(TextColor, a);
|
||||||
textColor = new Color(TextColor.R / 255.0f, TextColor.G / 255.0f, TextColor.B / 255.0f, a);
|
if (hoverTextColor.HasValue)
|
||||||
|
{
|
||||||
|
hoverTextColor = new Color(hoverTextColor.Value, a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -8,12 +8,16 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class CrewManagement
|
/// <summary>
|
||||||
|
/// The "HR manager" UI, which is used to hire/fire characters and rename crewmates.
|
||||||
|
/// </summary>
|
||||||
|
class HRManagerUI
|
||||||
{
|
{
|
||||||
private CampaignMode campaign => campaignUI.Campaign;
|
private CampaignMode campaign => campaignUI.Campaign;
|
||||||
private readonly CampaignUI campaignUI;
|
private readonly CampaignUI campaignUI;
|
||||||
private readonly GUIComponent parentComponent;
|
private readonly GUIComponent parentComponent;
|
||||||
|
|
||||||
|
private GUIComponent pendingAndCrewPanel;
|
||||||
private GUIListBox hireableList, pendingList, crewList;
|
private GUIListBox hireableList, pendingList, crewList;
|
||||||
private GUIFrame characterPreviewFrame;
|
private GUIFrame characterPreviewFrame;
|
||||||
private GUIDropDown sortingDropDown;
|
private GUIDropDown sortingDropDown;
|
||||||
@@ -24,7 +28,21 @@ namespace Barotrauma
|
|||||||
private PlayerBalanceElement? playerBalanceElement;
|
private PlayerBalanceElement? playerBalanceElement;
|
||||||
|
|
||||||
private List<CharacterInfo> PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires;
|
private List<CharacterInfo> PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires;
|
||||||
private bool HasPermission => CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageHires);
|
|
||||||
|
|
||||||
|
private bool wasReplacingPermanentlyDeadCharacter;
|
||||||
|
/// <summary>
|
||||||
|
/// Is the player hiring a new character for themselves instead of bots for the crew?
|
||||||
|
/// The window can only be used for one of these purposes at the same time.
|
||||||
|
/// </summary>
|
||||||
|
private static bool ReplacingPermanentlyDeadCharacter =>
|
||||||
|
GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } &&
|
||||||
|
GameMain.Client?.CharacterInfo is { PermanentlyDead: true };
|
||||||
|
|
||||||
|
private bool hadPermissionToHire;
|
||||||
|
private static bool HasPermissionToHire => ReplacingPermanentlyDeadCharacter ?
|
||||||
|
GameMain.NetworkMember?.ServerSettings.ReplaceCostPercentage <= 0 || CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMoney) || CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageHires) :
|
||||||
|
CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageHires);
|
||||||
|
|
||||||
private Point resolutionWhenCreated;
|
private Point resolutionWhenCreated;
|
||||||
|
|
||||||
@@ -40,7 +58,7 @@ namespace Barotrauma
|
|||||||
SkillDesc
|
SkillDesc
|
||||||
}
|
}
|
||||||
|
|
||||||
public CrewManagement(CampaignUI campaignUI, GUIComponent parentComponent)
|
public HRManagerUI(CampaignUI campaignUI, GUIComponent parentComponent)
|
||||||
{
|
{
|
||||||
this.campaignUI = campaignUI;
|
this.campaignUI = campaignUI;
|
||||||
this.parentComponent = parentComponent;
|
this.parentComponent = parentComponent;
|
||||||
@@ -53,27 +71,35 @@ namespace Barotrauma
|
|||||||
(locationChangeInfo) => UpdateLocationView(locationChangeInfo.NewLocation, true, locationChangeInfo.PrevLocation));
|
(locationChangeInfo) => UpdateLocationView(locationChangeInfo.NewLocation, true, locationChangeInfo.PrevLocation));
|
||||||
Reputation.OnAnyReputationValueChanged.RegisterOverwriteExisting(
|
Reputation.OnAnyReputationValueChanged.RegisterOverwriteExisting(
|
||||||
"CrewManagement.UpdateLocationView".ToIdentifier(), _ => needsHireableRefresh = true);
|
"CrewManagement.UpdateLocationView".ToIdentifier(), _ => needsHireableRefresh = true);
|
||||||
|
|
||||||
|
hadPermissionToHire = HasPermissionToHire;
|
||||||
|
wasReplacingPermanentlyDeadCharacter = ReplacingPermanentlyDeadCharacter;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RefreshPermissions()
|
public void RefreshUI()
|
||||||
{
|
{
|
||||||
RefreshCrewFrames(hireableList);
|
RefreshCrewFrames(hireableList);
|
||||||
RefreshCrewFrames(crewList);
|
RefreshCrewFrames(crewList);
|
||||||
RefreshCrewFrames(pendingList);
|
RefreshCrewFrames(pendingList);
|
||||||
if (clearAllButton != null) { clearAllButton.Enabled = HasPermission; }
|
if (clearAllButton != null) { clearAllButton.Enabled = HasPermissionToHire; }
|
||||||
|
hadPermissionToHire = HasPermissionToHire;
|
||||||
|
wasReplacingPermanentlyDeadCharacter = ReplacingPermanentlyDeadCharacter;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshCrewFrames(GUIListBox listBox)
|
private void RefreshCrewFrames(GUIListBox listBox)
|
||||||
{
|
{
|
||||||
if (listBox == null) { return; }
|
if (listBox == null) { return; }
|
||||||
listBox.CanBeFocused = HasPermission;
|
listBox.CanBeFocused = HasPermissionToHire;
|
||||||
foreach (GUIComponent child in listBox.Content.Children)
|
foreach (GUIComponent child in listBox.Content.Children)
|
||||||
{
|
{
|
||||||
if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo, true) is GUIButton buyButton)
|
if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo, true) is GUIButton buyButton)
|
||||||
{
|
{
|
||||||
CharacterInfo characterInfo = buyButton.UserData as CharacterInfo;
|
CharacterInfo characterInfo = buyButton.UserData as CharacterInfo;
|
||||||
bool enoughReputationToHire = EnoughReputationToHire(characterInfo);
|
buyButton.Enabled =
|
||||||
buyButton.Enabled = HasPermission && enoughReputationToHire;
|
//"normal buying" is disabled when replacing a dead character
|
||||||
|
!ReplacingPermanentlyDeadCharacter &&
|
||||||
|
HasPermissionToHire &&
|
||||||
|
EnoughReputationToHire(characterInfo) && campaign.CanAffordNewCharacter(characterInfo);
|
||||||
foreach (GUITextBlock text in child.GetAllChildren<GUITextBlock>())
|
foreach (GUITextBlock text in child.GetAllChildren<GUITextBlock>())
|
||||||
{
|
{
|
||||||
text.TextColor = new Color(text.TextColor, buyButton.Enabled ? 1.0f : 0.6f);
|
text.TextColor = new Color(text.TextColor, buyButton.Enabled ? 1.0f : 0.6f);
|
||||||
@@ -174,11 +200,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
playerBalanceElement = CampaignUI.AddBalanceElement(pendingAndCrewMainGroup, new Vector2(1.0f, 0.75f / 14.0f));
|
playerBalanceElement = CampaignUI.AddBalanceElement(pendingAndCrewMainGroup, new Vector2(1.0f, 0.75f / 14.0f));
|
||||||
|
|
||||||
|
pendingAndCrewPanel = new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform)
|
||||||
|
{
|
||||||
|
MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
|
||||||
|
});
|
||||||
|
|
||||||
var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center,
|
var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center,
|
||||||
parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform)
|
parent: pendingAndCrewPanel.RectTransform));
|
||||||
{
|
|
||||||
MaxSize = new Point(panelMaxWidth, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
|
|
||||||
}).RectTransform));
|
|
||||||
|
|
||||||
float height = 0.05f;
|
float height = 0.05f;
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaigncrew.pending"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaigncrew.pending"), font: GUIStyle.SubHeadingFont);
|
||||||
@@ -222,7 +250,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
ClickSound = GUISoundType.Cart,
|
ClickSound = GUISoundType.Cart,
|
||||||
ForceUpperCase = ForceUpperCase.Yes,
|
ForceUpperCase = ForceUpperCase.Yes,
|
||||||
Enabled = HasPermission,
|
Enabled = HasPermissionToHire,
|
||||||
OnClicked = (b, o) => RemoveAllPendingHires()
|
OnClicked = (b, o) => RemoveAllPendingHires()
|
||||||
};
|
};
|
||||||
GUITextBlock.AutoScaleAndNormalize(validateHiresButton.TextBlock, clearAllButton.TextBlock);
|
GUITextBlock.AutoScaleAndNormalize(validateHiresButton.TextBlock, clearAllButton.TextBlock);
|
||||||
@@ -277,7 +305,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (CharacterInfo c in hireableCharacters)
|
foreach (CharacterInfo c in hireableCharacters)
|
||||||
{
|
{
|
||||||
if (c == null) { continue; }
|
if (c == null || PendingHires.Contains(c)) { continue; }
|
||||||
CreateCharacterFrame(c, hireableList);
|
CreateCharacterFrame(c, hireableList);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -289,8 +317,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
HireManager hireManager = location.HireManager;
|
HireManager hireManager = location.HireManager;
|
||||||
if (hireManager == null) { return; }
|
if (hireManager == null) { return; }
|
||||||
int hireVal = hireManager.AvailableCharacters.Aggregate(0, (curr, hire) => curr + hire.GetIdentifier());
|
int hireVal = hireManager.AvailableCharacters.Aggregate(0, (curr, hire) => curr + hire.ID);
|
||||||
int newVal = availableHires.Aggregate(0, (curr, hire) => curr + hire.GetIdentifier());
|
int newVal = availableHires.Aggregate(0, (curr, hire) => curr + hire.ID);
|
||||||
if (hireVal != newVal)
|
if (hireVal != newVal)
|
||||||
{
|
{
|
||||||
location.HireManager.AvailableCharacters = availableHires;
|
location.HireManager.AvailableCharacters = availableHires;
|
||||||
@@ -371,7 +399,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox)
|
public GUIComponent CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox, bool hideSalary = false)
|
||||||
{
|
{
|
||||||
Skill skill = null;
|
Skill skill = null;
|
||||||
Color? jobColor = null;
|
Color? jobColor = null;
|
||||||
@@ -442,33 +470,41 @@ namespace Barotrauma
|
|||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listBox != crewList)
|
if (!hideSalary)
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
|
if (listBox != crewList)
|
||||||
TextManager.FormatCurrency(HireManager.GetSalaryFor(characterInfo)),
|
|
||||||
textAlignment: Alignment.Center)
|
|
||||||
{
|
{
|
||||||
CanBeFocused = false
|
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
|
||||||
};
|
TextManager.FormatCurrency(ReplacingPermanentlyDeadCharacter ? campaign.NewCharacterCost(characterInfo) : HireManager.GetSalaryFor(characterInfo)),
|
||||||
}
|
textAlignment: Alignment.Center)
|
||||||
else
|
{
|
||||||
{
|
CanBeFocused = false
|
||||||
// Just a bit of padding to make list layouts similar
|
};
|
||||||
new GUIFrame(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Just a bit of padding to make list layouts similar
|
||||||
|
new GUIFrame(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (listBox == hireableList)
|
if (listBox == hireableList)
|
||||||
{
|
{
|
||||||
var hireButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
|
var hireButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
|
||||||
{
|
{
|
||||||
|
ToolTip = TextManager.Get("hirebutton"),
|
||||||
ClickSound = GUISoundType.Cart,
|
ClickSound = GUISoundType.Cart,
|
||||||
UserData = characterInfo,
|
UserData = characterInfo,
|
||||||
Enabled = CanHire(characterInfo),
|
Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter,
|
||||||
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
|
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
|
||||||
};
|
};
|
||||||
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
|
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
|
||||||
{
|
{
|
||||||
|
if (ReplacingPermanentlyDeadCharacter)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
|
if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
|
||||||
{
|
{
|
||||||
if (btn.Enabled)
|
if (btn.Enabled)
|
||||||
@@ -483,6 +519,41 @@ namespace Barotrauma
|
|||||||
btn.Enabled = CanHire(characterInfo);
|
btn.Enabled = CanHire(characterInfo);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (ReplacingPermanentlyDeadCharacter)
|
||||||
|
{
|
||||||
|
bool canHire = CanHire(characterInfo) && campaign.CanAffordNewCharacter(characterInfo);
|
||||||
|
var takeoverButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
|
||||||
|
{
|
||||||
|
ToolTip = canHire ? TextManager.Get("hireandtakecontrol") : TextManager.Get("hireandtakecontroldisabled"),
|
||||||
|
ClickSound = GUISoundType.ConfirmTransaction,
|
||||||
|
UserData = characterInfo,
|
||||||
|
Enabled = canHire,
|
||||||
|
OnClicked = (b, o) =>
|
||||||
|
{
|
||||||
|
if (GameMain.Client is not GameClient gameClient)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Client client = gameClient.ConnectedClients.FirstOrDefault(c => c.SessionId == gameClient.SessionId);
|
||||||
|
if (!campaign.TryPurchase(client, campaign.NewCharacterCost(characterInfo)))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
gameClient.SendTakeOverBotRequest(characterInfo);
|
||||||
|
needsHireableRefresh = true;
|
||||||
|
campaign.ShowCampaignUI = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
takeoverButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
|
||||||
|
{
|
||||||
|
bool canHireCurrently = ReplacingPermanentlyDeadCharacter && CanHire(characterInfo) && campaign.CanAffordNewCharacter(characterInfo);
|
||||||
|
btn.ToolTip = TextManager.Get(canHireCurrently ? "hireandtakecontrol" : "hireandtakecontroldisabled");
|
||||||
|
btn.Visible = GameMain.GameSession is { AllowHrManagerBotTakeover: true };
|
||||||
|
btn.Enabled = canHireCurrently;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (listBox == pendingList)
|
else if (listBox == pendingList)
|
||||||
{
|
{
|
||||||
@@ -501,7 +572,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
UserData = characterInfo,
|
UserData = characterInfo,
|
||||||
//can't fire if there's only one character in the crew
|
//can't fire if there's only one character in the crew
|
||||||
Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermission,
|
Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermissionToHire,
|
||||||
OnClicked = (btn, obj) =>
|
OnClicked = (btn, obj) =>
|
||||||
{
|
{
|
||||||
var confirmDialog = new GUIMessageBox(
|
var confirmDialog = new GUIMessageBox(
|
||||||
@@ -534,11 +605,13 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CanHire(CharacterInfo characterInfo)
|
bool CanHire(CharacterInfo thisCharacterInfo)
|
||||||
{
|
{
|
||||||
if (!HasPermission) { return false; }
|
if (!HasPermissionToHire) { return false; }
|
||||||
return EnoughReputationToHire(characterInfo);
|
return EnoughReputationToHire(thisCharacterInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return frame;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool EnoughReputationToHire(CharacterInfo characterInfo)
|
private bool EnoughReputationToHire(CharacterInfo characterInfo)
|
||||||
@@ -709,10 +782,10 @@ namespace Barotrauma
|
|||||||
totalBlock.Text = TextManager.FormatCurrency(total);
|
totalBlock.Text = TextManager.FormatCurrency(total);
|
||||||
bool enoughMoney = campaign == null || campaign.CanAfford(total);
|
bool enoughMoney = campaign == null || campaign.CanAfford(total);
|
||||||
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
|
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
|
||||||
validateHiresButton.Enabled = enoughMoney && HasPermission && pendingList.Content.RectTransform.Children.Any();
|
validateHiresButton.Enabled = enoughMoney && HasPermissionToHire && pendingList.Content.RectTransform.Children.Any();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool ValidateHires(List<CharacterInfo> hires, bool takeMoney = true, bool createNetworkEvent = false)
|
public bool ValidateHires(List<CharacterInfo> hires, bool takeMoney = true, bool createNetworkEvent = false, bool createNotification = true)
|
||||||
{
|
{
|
||||||
if (hires == null || hires.None()) { return false; }
|
if (hires == null || hires.None()) { return false; }
|
||||||
|
|
||||||
@@ -750,11 +823,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
UpdateLocationView(campaign.Map.CurrentLocation, true);
|
UpdateLocationView(campaign.Map.CurrentLocation, true);
|
||||||
SelectCharacter(null, null, null);
|
SelectCharacter(null, null, null);
|
||||||
var dialog = new GUIMessageBox(
|
if (createNotification)
|
||||||
TextManager.Get("newcrewmembers"),
|
{
|
||||||
TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
|
var dialog = new GUIMessageBox(
|
||||||
new LocalizedString[] { TextManager.Get("Ok") });
|
TextManager.Get("newcrewmembers"),
|
||||||
dialog.Buttons[0].OnClicked += dialog.Close;
|
TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
|
||||||
|
new LocalizedString[] { TextManager.Get("Ok") });
|
||||||
|
dialog.Buttons[0].OnClicked += dialog.Close;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (createNetworkEvent)
|
if (createNetworkEvent)
|
||||||
@@ -767,7 +843,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool CreateRenamingComponent(GUIButton button, object userData)
|
private bool CreateRenamingComponent(GUIButton button, object userData)
|
||||||
{
|
{
|
||||||
if (!HasPermission || userData is not CharacterInfo characterInfo) { return false; }
|
if (!HasPermissionToHire || userData is not CharacterInfo characterInfo) { return false; }
|
||||||
var outerGlowFrame = new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
|
var outerGlowFrame = new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
|
||||||
style: "OuterGlow", color: Color.Black * 0.7f);
|
style: "OuterGlow", color: Color.Black * 0.7f);
|
||||||
var frame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor: Anchor.Center)
|
var frame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.4f), outerGlowFrame.RectTransform, anchor: Anchor.Center)
|
||||||
@@ -876,6 +952,15 @@ namespace Barotrauma
|
|||||||
playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
|
playerBalanceElement = CampaignUI.UpdateBalanceElement(playerBalanceElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When showing this window to someone hiring a new character, the right side panels aren't needed
|
||||||
|
pendingAndCrewPanel.Visible = !ReplacingPermanentlyDeadCharacter;
|
||||||
|
|
||||||
|
if (hadPermissionToHire != HasPermissionToHire ||
|
||||||
|
wasReplacingPermanentlyDeadCharacter != ReplacingPermanentlyDeadCharacter)
|
||||||
|
{
|
||||||
|
RefreshUI();
|
||||||
|
}
|
||||||
|
|
||||||
if (needsHireableRefresh)
|
if (needsHireableRefresh)
|
||||||
{
|
{
|
||||||
RefreshCrewFrames(hireableList);
|
RefreshCrewFrames(hireableList);
|
||||||
@@ -949,7 +1034,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetPendingHires(List<int> characterInfos, Location location)
|
public void SetPendingHires(List<UInt16> characterInfos, Location location)
|
||||||
{
|
{
|
||||||
List<CharacterInfo> oldHires = PendingHires.ToList();
|
List<CharacterInfo> oldHires = PendingHires.ToList();
|
||||||
foreach (CharacterInfo pendingHire in oldHires)
|
foreach (CharacterInfo pendingHire in oldHires)
|
||||||
@@ -957,9 +1042,9 @@ namespace Barotrauma
|
|||||||
RemovePendingHire(pendingHire, createNetworkMessage: false);
|
RemovePendingHire(pendingHire, createNetworkMessage: false);
|
||||||
}
|
}
|
||||||
PendingHires.Clear();
|
PendingHires.Clear();
|
||||||
foreach (int identifier in characterInfos)
|
foreach (UInt16 identifier in characterInfos)
|
||||||
{
|
{
|
||||||
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifierUsingOriginalName() == identifier);
|
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.ID == identifier);
|
||||||
if (match != null)
|
if (match != null)
|
||||||
{
|
{
|
||||||
AddPendingHire(match, createNetworkMessage: false);
|
AddPendingHire(match, createNetworkMessage: false);
|
||||||
@@ -992,7 +1077,7 @@ namespace Barotrauma
|
|||||||
msg.WriteUInt16((ushort)PendingHires.Count);
|
msg.WriteUInt16((ushort)PendingHires.Count);
|
||||||
foreach (CharacterInfo pendingHire in PendingHires)
|
foreach (CharacterInfo pendingHire in PendingHires)
|
||||||
{
|
{
|
||||||
msg.WriteInt32(pendingHire.GetIdentifierUsingOriginalName());
|
msg.WriteUInt16(pendingHire.ID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1002,17 +1087,16 @@ namespace Barotrauma
|
|||||||
msg.WriteBoolean(validRenaming);
|
msg.WriteBoolean(validRenaming);
|
||||||
if (validRenaming)
|
if (validRenaming)
|
||||||
{
|
{
|
||||||
int identifier = renameCharacter.info.GetIdentifierUsingOriginalName();
|
msg.WriteUInt16(renameCharacter.info.ID);
|
||||||
msg.WriteInt32(identifier);
|
|
||||||
msg.WriteString(renameCharacter.newName);
|
msg.WriteString(renameCharacter.newName);
|
||||||
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false;
|
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.ID == renameCharacter.info.ID) ?? false;
|
||||||
msg.WriteBoolean(existingCrewMember);
|
msg.WriteBoolean(existingCrewMember);
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.WriteBoolean(firedCharacter != null);
|
msg.WriteBoolean(firedCharacter != null);
|
||||||
if (firedCharacter != null)
|
if (firedCharacter != null)
|
||||||
{
|
{
|
||||||
msg.WriteInt32(firedCharacter.GetIdentifier());
|
msg.WriteUInt16(firedCharacter.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
@@ -2108,13 +2108,13 @@ namespace Barotrauma
|
|||||||
deliveryPrompt.Buttons[0].OnClicked = (btn, userdata) =>
|
deliveryPrompt.Buttons[0].OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
ConfirmPurchase(deliverImmediately: true);
|
ConfirmPurchase(deliverImmediately: true);
|
||||||
deliveryPrompt.Close();
|
deliveryPrompt?.Close();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
deliveryPrompt.Buttons[1].OnClicked = (btn, userdata) =>
|
deliveryPrompt.Buttons[1].OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
ConfirmPurchase(deliverImmediately: false);
|
ConfirmPurchase(deliverImmediately: false);
|
||||||
deliveryPrompt.Close();
|
deliveryPrompt?.Close();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -742,8 +742,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private (LocalizedString header, LocalizedString body) GetItemTransferWarningText()
|
private (LocalizedString header, LocalizedString body) GetItemTransferWarningText()
|
||||||
{
|
{
|
||||||
var header = TextManager.Get("itemtransferheader").Fallback("lowfuelheader");
|
var header = TextManager.Get("itemtransferheader").Fallback("lowfuelheader", useDefaultLanguageIfFound: false);
|
||||||
var body = TextManager.Get("itemtransferwarning").Fallback("lowfuelwarning");
|
var body = TextManager.Get("itemtransferwarning").Fallback("lowfuelwarning", useDefaultLanguageIfFound: false);
|
||||||
return (header, body);
|
return (header, body);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -320,7 +320,61 @@ namespace Barotrauma
|
|||||||
var reputationButton = createTabButton(InfoFrameTab.Reputation, "reputation");
|
var reputationButton = createTabButton(InfoFrameTab.Reputation, "reputation");
|
||||||
|
|
||||||
var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame");
|
var balanceFrame = new GUIFrame(new RectTransform(new Point(innerLayoutGroup.Rect.Width, innerLayoutGroup.Rect.Height - infoFrameHolderHeight), parent: innerLayoutGroup.RectTransform), style: "InnerFrame");
|
||||||
GUITextBlock balanceText = new GUITextBlock(new RectTransform(Vector2.One, balanceFrame.RectTransform), string.Empty, textAlignment: Alignment.Right);
|
GUILayoutGroup salaryFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.66f, 1f), balanceFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
|
|
||||||
|
GUIScrollBar salaryScrollBar = null;
|
||||||
|
GUITextBlock salaryPercentage = null;
|
||||||
|
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign)
|
||||||
|
{
|
||||||
|
float value = campaignMode.Bank.RewardDistribution;
|
||||||
|
GUITextBlock salaryText = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), salaryFrame.RectTransform), TextManager.Get("defaultsalary"), textAlignment: Alignment.Center)
|
||||||
|
{
|
||||||
|
AutoScaleHorizontal = true
|
||||||
|
};
|
||||||
|
salaryScrollBar = new GUIScrollBar(new RectTransform(new Vector2(0.4f, 1f), salaryFrame.RectTransform), barSize: 0.1f, style: "GUISlider")
|
||||||
|
{
|
||||||
|
Range = new Vector2(0, 1),
|
||||||
|
BarScrollValue = value / 100f,
|
||||||
|
Step = 0.01f,
|
||||||
|
BarSize = 0.1f,
|
||||||
|
};
|
||||||
|
|
||||||
|
salaryPercentage = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1f), salaryFrame.RectTransform), "0", textAlignment: Alignment.Center)
|
||||||
|
{
|
||||||
|
Text = ValueToPercentage(RoundRewardDistribution(salaryScrollBar.BarScroll, salaryScrollBar.Step))
|
||||||
|
};
|
||||||
|
|
||||||
|
salaryScrollBar.OnMoved = (scrollBar, value) =>
|
||||||
|
{
|
||||||
|
salaryPercentage.Text = ValueToPercentage(RoundRewardDistribution(value, scrollBar.Step));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
salaryScrollBar.OnReleased = (bar, scroll) =>
|
||||||
|
{
|
||||||
|
int newRewardDistribution = RoundRewardDistribution(scroll, bar.Step);
|
||||||
|
SetRewardDistribution(Option.None, newRewardDistribution);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
var resetButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), salaryFrame.RectTransform), TextManager.Get("ResetSalaries"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
TextBlock = { AutoScaleHorizontal = true },
|
||||||
|
ToolTip = TextManager.Get("resetsalaries.tooltip"),
|
||||||
|
OnClicked = (button, userData) =>
|
||||||
|
{
|
||||||
|
GUI.AskForConfirmation(TextManager.Get("ResetSalaries"), TextManager.Get("ResetSalaries.Warning"), onConfirm: ResetRewardDistributions);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void UpdateSliderEnabled()
|
||||||
|
=> salaryScrollBar.Enabled = resetButton.Enabled = CampaignMode.AllowedToManageWallets();
|
||||||
|
UpdateSliderEnabled();
|
||||||
|
|
||||||
|
Identifier defaultSalaryEventIdentifier = "DefaultSalarySlider".ToIdentifier();
|
||||||
|
GameMain.Client?.OnPermissionChanged?.RegisterOverwriteExisting(defaultSalaryEventIdentifier, _ => UpdateSliderEnabled());
|
||||||
|
}
|
||||||
|
GUITextBlock balanceText = new GUITextBlock(new RectTransform(new Vector2(0.33f, 1f), balanceFrame.RectTransform, Anchor.TopRight), string.Empty, textAlignment: Alignment.Right);
|
||||||
if (GameMain.IsMultiplayer)
|
if (GameMain.IsMultiplayer)
|
||||||
{
|
{
|
||||||
balanceText.ToolTip = TextManager.Get("bankdescription");
|
balanceText.ToolTip = TextManager.Get("bankdescription");
|
||||||
@@ -343,6 +397,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!e.Owner.IsNone()) { return; }
|
if (!e.Owner.IsNone()) { return; }
|
||||||
SetBalanceText(balanceText, e.Wallet.Balance);
|
SetBalanceText(balanceText, e.Wallet.Balance);
|
||||||
|
|
||||||
|
if (salaryPercentage is not null && salaryScrollBar is not null)
|
||||||
|
{
|
||||||
|
float rewardDistribution = e.Wallet.RewardDistribution;
|
||||||
|
salaryScrollBar.BarScrollValue = rewardDistribution / 100f;
|
||||||
|
salaryPercentage.Text = ValueToPercentage(rewardDistribution);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
registeredEvents.Add(eventIdentifier);
|
registeredEvents.Add(eventIdentifier);
|
||||||
|
|
||||||
@@ -350,6 +411,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
text.Text = TextManager.GetWithVariable("bankbalanceformat", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", balance));
|
text.Text = TextManager.GetWithVariable("bankbalanceformat", "[money]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", balance));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LocalizedString ValueToPercentage(float value)
|
||||||
|
=> TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)MathF.Round(value)}");
|
||||||
}
|
}
|
||||||
|
|
||||||
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
|
var submarineButton = createTabButton(InfoFrameTab.Submarine, "submarine");
|
||||||
@@ -1037,11 +1101,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
int newRewardDistribution = RoundRewardDistribution(scroll, bar.Step);
|
int newRewardDistribution = RoundRewardDistribution(scroll, bar.Step);
|
||||||
if (newRewardDistribution == targetWallet.RewardDistribution) { return false; }
|
if (newRewardDistribution == targetWallet.RewardDistribution) { return false; }
|
||||||
SetRewardDistribution(character, newRewardDistribution);
|
SetRewardDistribution(Option.Some(character), newRewardDistribution);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
int RoundRewardDistribution(float scroll, float step) => (int)MathUtils.RoundTowardsClosest(scroll * 100, step * 100);
|
|
||||||
|
|
||||||
SetRewardText(targetWallet.RewardDistribution, rewardBlock);
|
SetRewardText(targetWallet.RewardDistribution, rewardBlock);
|
||||||
|
|
||||||
@@ -1201,6 +1264,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance);
|
moneyBlock.Text = TextManager.FormatCurrency(e.Info.Balance);
|
||||||
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
|
salarySlider.BarScrollValue = e.Info.RewardDistribution / 100f;
|
||||||
|
SetRewardText(e.Info.RewardDistribution, rewardBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateAllInputs();
|
UpdateAllInputs();
|
||||||
@@ -1311,20 +1375,29 @@ namespace Barotrauma
|
|||||||
transfer.Write(msg);
|
transfer.Write(msg);
|
||||||
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void SetRewardDistribution(Character character, int newValue)
|
|
||||||
{
|
|
||||||
INetSerializableStruct transfer = new NetWalletSetSalaryUpdate
|
|
||||||
{
|
|
||||||
Target = character.ID,
|
|
||||||
NewRewardDistribution = newValue
|
|
||||||
};
|
|
||||||
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.REWARD_DISTRIBUTION);
|
|
||||||
transfer.Write(msg);
|
|
||||||
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SetRewardDistribution(Option<Character> character, int newValue)
|
||||||
|
{
|
||||||
|
INetSerializableStruct transfer = new NetWalletSetSalaryUpdate
|
||||||
|
{
|
||||||
|
Target = character.Select(c => c.ID),
|
||||||
|
NewRewardDistribution = newValue
|
||||||
|
};
|
||||||
|
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.REWARD_DISTRIBUTION);
|
||||||
|
transfer.Write(msg);
|
||||||
|
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ResetRewardDistributions()
|
||||||
|
{
|
||||||
|
IWriteMessage msg = new WriteOnlyMessage().WithHeader(ClientPacketHeader.RESET_REWARD_DISTRIBUTION);
|
||||||
|
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int RoundRewardDistribution(float scroll, float step)
|
||||||
|
=> (int)MathUtils.RoundTowardsClosest(scroll * 100, step * 100);
|
||||||
|
|
||||||
private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null)
|
private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null)
|
||||||
{
|
{
|
||||||
GUIComponent paddedFrame;
|
GUIComponent paddedFrame;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -71,7 +71,9 @@ namespace Barotrauma
|
|||||||
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
|
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
|
||||||
|
|
||||||
private readonly Queue<Identifier> showCaseClosureQueue = new();
|
private readonly Queue<Identifier> showCaseClosureQueue = new();
|
||||||
|
|
||||||
|
private GUITextBlock? nameBlock;
|
||||||
|
private GUIButton? renameButton;
|
||||||
private GUIListBox? skillListBox;
|
private GUIListBox? skillListBox;
|
||||||
private GUITextBlock? talentPointText;
|
private GUITextBlock? talentPointText;
|
||||||
private GUIProgressBar? experienceBar;
|
private GUIProgressBar? experienceBar;
|
||||||
@@ -133,43 +135,65 @@ namespace Barotrauma
|
|||||||
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
|
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
|
||||||
GUILayoutGroup playerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), containerFrame.RectTransform, Anchor.TopCenter));
|
GUILayoutGroup playerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), containerFrame.RectTransform, Anchor.TopCenter));
|
||||||
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
|
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
|
||||||
|
|
||||||
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
|
if (!GameMain.NetLobbyScreen.PermadeathMode)
|
||||||
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
|
|
||||||
{
|
{
|
||||||
IgnoreLayoutGroups = false,
|
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
|
||||||
TextBlock =
|
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
|
||||||
{
|
{
|
||||||
AutoScaleHorizontal = true
|
IgnoreLayoutGroups = false,
|
||||||
}
|
TextBlock =
|
||||||
};
|
|
||||||
|
|
||||||
newCharacterBox.OnClicked = (button, o) =>
|
|
||||||
{
|
|
||||||
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
|
|
||||||
{
|
|
||||||
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
|
|
||||||
{
|
{
|
||||||
newCharacterBox.Text = TextManager.Get("settings");
|
AutoScaleHorizontal = true
|
||||||
if (TabMenu.PendingChangesFrame != null)
|
}
|
||||||
{
|
};
|
||||||
NetLobbyScreen.CreateChangesPendingFrame(TabMenu.PendingChangesFrame);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenMenu();
|
newCharacterBox.OnClicked = (button, o) =>
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenMenu();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
void OpenMenu()
|
|
||||||
{
|
{
|
||||||
characterSettingsFrame!.Visible = true;
|
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
|
||||||
content.Visible = false;
|
{
|
||||||
}
|
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
|
||||||
};
|
{
|
||||||
|
newCharacterBox.Text = TextManager.Get("settings");
|
||||||
|
if (TabMenu.PendingChangesFrame != null)
|
||||||
|
{
|
||||||
|
NetLobbyScreen.CreateChangesPendingFrame(TabMenu.PendingChangesFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenMenu();
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenMenu();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
void OpenMenu()
|
||||||
|
{
|
||||||
|
characterSettingsFrame!.Visible = true;
|
||||||
|
content.Visible = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (characterInfo != null)
|
||||||
|
{
|
||||||
|
renameButton = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
|
||||||
|
text: TextManager.Get("button.RenameCharacter"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
Enabled = characterInfo.RenamingEnabled,
|
||||||
|
ToolTip = TextManager.Get("permadeath.rename.description"),
|
||||||
|
IgnoreLayoutGroups = false,
|
||||||
|
TextBlock =
|
||||||
|
{
|
||||||
|
AutoScaleHorizontal = true
|
||||||
|
},
|
||||||
|
OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
CreateRenamePopup();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
|
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
|
||||||
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
|
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
|
||||||
@@ -177,6 +201,7 @@ namespace Barotrauma
|
|||||||
OnClicked = (button, o) =>
|
OnClicked = (button, o) =>
|
||||||
{
|
{
|
||||||
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
|
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
|
||||||
|
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
|
||||||
characterSettingsFrame.Visible = false;
|
characterSettingsFrame.Visible = false;
|
||||||
content.Visible = true;
|
content.Visible = true;
|
||||||
return true;
|
return true;
|
||||||
@@ -184,6 +209,57 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CreateRenamePopup()
|
||||||
|
{
|
||||||
|
GUIMessageBox renamePopup = new(
|
||||||
|
TextManager.Get("button.RenameCharacter"), TextManager.Get("permadeath.rename.description"),
|
||||||
|
new LocalizedString[] { TextManager.Get("Confirm"), TextManager.Get("Cancel") }, minSize: new Point(0, GUI.IntScale(230)));
|
||||||
|
GUITextBox newNameBox = new(new(Vector2.One, renamePopup.Content.RectTransform), "")
|
||||||
|
{
|
||||||
|
OnEnterPressed = (textBox, text) =>
|
||||||
|
{
|
||||||
|
textBox.Text = text.Trim();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
renamePopup.Buttons[0].OnClicked += (_, _) =>
|
||||||
|
{
|
||||||
|
if (newNameBox.Text?.Trim() is string newName && newName != "")
|
||||||
|
{
|
||||||
|
if (characterInfo != null)
|
||||||
|
{
|
||||||
|
if (newNameBox.Text == characterInfo.Name)
|
||||||
|
{
|
||||||
|
renamePopup.Close();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (GameMain.GameSession?.Campaign?.CampaignUI?.HRManagerUI is { } crewManagement)
|
||||||
|
{
|
||||||
|
crewManagement.RenameCharacter(characterInfo, newName);
|
||||||
|
if (nameBlock != null)
|
||||||
|
{
|
||||||
|
nameBlock.Text = newName;
|
||||||
|
}
|
||||||
|
if (renameButton != null)
|
||||||
|
{
|
||||||
|
renameButton.Enabled = false;
|
||||||
|
}
|
||||||
|
renamePopup.Close();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DebugConsole.ThrowError("Tried to rename character, but CharacterInfo completely missing!");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newNameBox.Flash();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
renamePopup.Buttons[1].OnClicked += renamePopup.Close;
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateStatPanel(GUIComponent parent, CharacterInfo info)
|
private void CreateStatPanel(GUIComponent parent, CharacterInfo info)
|
||||||
{
|
{
|
||||||
Job job = info.Job;
|
Job job = info.Job;
|
||||||
@@ -201,7 +277,7 @@ namespace Barotrauma
|
|||||||
CanBeFocused = true
|
CanBeFocused = true
|
||||||
};
|
};
|
||||||
|
|
||||||
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
|
nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
if (!info.OmitJobInMenus)
|
if (!info.OmitJobInMenus)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -773,7 +773,7 @@ namespace Barotrauma
|
|||||||
subItems ??= GetSubItems();
|
subItems ??= GetSubItems();
|
||||||
return subItems.Any(i =>
|
return subItems.Any(i =>
|
||||||
i.Prefab.SwappableItem != null &&
|
i.Prefab.SwappableItem != null &&
|
||||||
!i.HiddenInGame && i.AllowSwapping &&
|
!i.IsHidden && i.AllowSwapping &&
|
||||||
(i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) &&
|
(i.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == i.Prefab.Identifier)) &&
|
||||||
Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t)));
|
Submarine.MainSub.IsEntityFoundOnThisSub(i, true) && category.ItemTags.Any(t => i.HasTag(t)));
|
||||||
}
|
}
|
||||||
@@ -876,7 +876,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
parent.Content.ClearChildren();
|
parent.Content.ClearChildren();
|
||||||
currentUpgradeCategory = category;
|
currentUpgradeCategory = category;
|
||||||
var entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true) && !i.HiddenInGame && i.AllowSwapping && i.Prefab.SwappableItem != null && category.ItemTags.Any(t => i.HasTag(t))).ToList();
|
var entitiesOnSub = submarine.GetItems(true).Where(i => submarine.IsEntityFoundOnThisSub(i, true) && !i.IsHidden && i.AllowSwapping && i.Prefab.SwappableItem != null && category.ItemTags.Any(t => i.HasTag(t))).ToList();
|
||||||
|
|
||||||
foreach (Item item in entitiesOnSub)
|
foreach (Item item in entitiesOnSub)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -115,7 +115,6 @@ namespace Barotrauma
|
|||||||
if (IsMouseOver || (!RequireMouseOn && SelectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
|
if (IsMouseOver || (!RequireMouseOn && SelectedWidgets.Contains(this) && PlayerInput.PrimaryMouseButtonHeld()))
|
||||||
{
|
{
|
||||||
Hovered?.Invoke();
|
Hovered?.Invoke();
|
||||||
System.Diagnostics.Debug.WriteLine("hovered");
|
|
||||||
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
|
if (RequireMouseOn || PlayerInput.PrimaryMouseButtonDown())
|
||||||
{
|
{
|
||||||
if ((multiselect && !SelectedWidgets.Contains(this)) || SelectedWidgets.None())
|
if ((multiselect && !SelectedWidgets.Contains(this)) || SelectedWidgets.None())
|
||||||
|
|||||||
@@ -1137,19 +1137,14 @@ namespace Barotrauma
|
|||||||
if (save)
|
if (save)
|
||||||
{
|
{
|
||||||
GUI.SetSavingIndicatorState(true);
|
GUI.SetSavingIndicatorState(true);
|
||||||
|
|
||||||
|
GameSession.Campaign?.HandleSaveAndQuit();
|
||||||
if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
|
if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
|
||||||
{
|
{
|
||||||
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
|
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
|
||||||
}
|
}
|
||||||
if (GameSession.Campaign is CampaignMode campaign)
|
GameSession.Campaign?.End();
|
||||||
{
|
|
||||||
if (campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
|
|
||||||
{
|
|
||||||
spCampaign.UpdateStoreStock();
|
|
||||||
}
|
|
||||||
GameSession.EventManager?.RegisterEventHistory(registerFinishedOnly: true);
|
|
||||||
campaign.End();
|
|
||||||
}
|
|
||||||
SaveUtil.SaveGame(GameSession.SavePath);
|
SaveUtil.SaveGame(GameSession.SavePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -522,6 +522,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GameMain.GameSession?.Campaign?.CampaignUI?.HRManagerUI is { } crewManagement)
|
||||||
|
{
|
||||||
|
crewManagement.RefreshUI();
|
||||||
|
}
|
||||||
|
|
||||||
return background;
|
return background;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -532,6 +537,10 @@ namespace Barotrauma
|
|||||||
crewList.RemoveChild(component);
|
crewList.RemoveChild(component);
|
||||||
traitorButtons.RemoveAll(t => t.IsChildOf(component, recursive: true));
|
traitorButtons.RemoveAll(t => t.IsChildOf(component, recursive: true));
|
||||||
}
|
}
|
||||||
|
if (GameMain.GameSession?.Campaign?.CampaignUI?.HRManagerUI is { } crewManagement)
|
||||||
|
{
|
||||||
|
crewManagement.RefreshUI();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetCharacterComponentTooltip(GUIComponent characterComponent)
|
private static void SetCharacterComponentTooltip(GUIComponent characterComponent)
|
||||||
@@ -678,12 +687,12 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds the message to the single player chatbox.
|
/// Adds the message to the single player chatbox.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void AddSinglePlayerChatMessage(LocalizedString senderName, LocalizedString text, ChatMessageType messageType, Character sender)
|
public void AddSinglePlayerChatMessage(LocalizedString senderName, LocalizedString text, ChatMessageType messageType, Entity sender)
|
||||||
{
|
{
|
||||||
AddSinglePlayerChatMessage(senderName.Value, text.Value, messageType, sender);
|
AddSinglePlayerChatMessage(senderName.Value, text.Value, messageType, sender);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSinglePlayerChatMessage(string senderName, string text, ChatMessageType messageType, Character sender)
|
public void AddSinglePlayerChatMessage(string senderName, string text, ChatMessageType messageType, Entity sender)
|
||||||
{
|
{
|
||||||
if (!IsSinglePlayer)
|
if (!IsSinglePlayer)
|
||||||
{
|
{
|
||||||
@@ -692,9 +701,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(text)) { return; }
|
if (string.IsNullOrEmpty(text)) { return; }
|
||||||
|
|
||||||
if (sender != null)
|
if (sender is Character character)
|
||||||
{
|
{
|
||||||
GameMain.GameSession.CrewManager.SetCharacterSpeaking(sender);
|
GameMain.GameSession.CrewManager?.SetCharacterSpeaking(character);
|
||||||
|
if (!character.IsBot)
|
||||||
|
{
|
||||||
|
character.TextChatVolume = 1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
|
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
|
||||||
}
|
}
|
||||||
@@ -708,9 +721,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (string.IsNullOrEmpty(message.Text)) { return; }
|
if (string.IsNullOrEmpty(message.Text)) { return; }
|
||||||
|
|
||||||
if (message.Sender != null)
|
if (message.SenderCharacter != null)
|
||||||
{
|
{
|
||||||
GameMain.GameSession.CrewManager.SetCharacterSpeaking(message.Sender);
|
GameMain.GameSession.CrewManager?.SetCharacterSpeaking(message.SenderCharacter);
|
||||||
}
|
}
|
||||||
ChatBox.AddMessage(message);
|
ChatBox.AddMessage(message);
|
||||||
}
|
}
|
||||||
@@ -3688,6 +3701,9 @@ namespace Barotrauma
|
|||||||
crewList.ClearChildren();
|
crewList.ClearChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves the current crew. Note that this is client-only code (only used in the single player campaign) - saving in multiplayer is handled in the server-side code of <see cref="MultiPlayerCampaign"/>.
|
||||||
|
/// </summary>
|
||||||
public XElement Save(XElement parentElement)
|
public XElement Save(XElement parentElement)
|
||||||
{
|
{
|
||||||
var element = new XElement("crew");
|
var element = new XElement("crew");
|
||||||
|
|||||||
@@ -254,7 +254,7 @@ namespace Barotrauma
|
|||||||
buttonText = TextManager.Get("map");
|
buttonText = TextManager.Get("map");
|
||||||
}
|
}
|
||||||
else if (prevCampaignUIAutoOpenType != availableTransition &&
|
else if (prevCampaignUIAutoOpenType != availableTransition &&
|
||||||
(availableTransition == TransitionType.ProgressToNextEmptyLocation || availableTransition == TransitionType.ReturnToPreviousEmptyLocation))
|
availableTransition == TransitionType.ProgressToNextEmptyLocation)
|
||||||
{
|
{
|
||||||
HintManager.OnAvailableTransition(availableTransition);
|
HintManager.OnAvailableTransition(availableTransition);
|
||||||
//opening the campaign map pauses the game and prevents HintManager from running -> update it manually to get the hint to show up immediately
|
//opening the campaign map pauses the game and prevents HintManager from running -> update it manually to get the hint to show up immediately
|
||||||
@@ -344,18 +344,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected SubmarineInfo GetPredefinedStartOutpost()
|
|
||||||
{
|
|
||||||
if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
return new SubmarineInfo(parameters.OutpostFilePath.Value)
|
|
||||||
{
|
|
||||||
OutpostGenerationParams = parameters
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
partial void NPCInteractProjSpecific(Character npc, Character interactor)
|
partial void NPCInteractProjSpecific(Character npc, Character interactor)
|
||||||
{
|
{
|
||||||
if (npc == null || interactor == null) { return; }
|
if (npc == null || interactor == null) { return; }
|
||||||
@@ -370,7 +358,7 @@ namespace Barotrauma
|
|||||||
UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade").Value, IsSinglePlayer, npc);
|
UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade").Value, IsSinglePlayer, npc);
|
||||||
return;
|
return;
|
||||||
case InteractionType.Crew when GameMain.NetworkMember != null:
|
case InteractionType.Crew when GameMain.NetworkMember != null:
|
||||||
CampaignUI.CrewManagement.SendCrewState(false);
|
CampaignUI.HRManagerUI.SendCrewState(false);
|
||||||
goto default;
|
goto default;
|
||||||
case InteractionType.MedicalClinic:
|
case InteractionType.MedicalClinic:
|
||||||
CampaignUI.MedicalClinic.RequestLatestPending();
|
CampaignUI.MedicalClinic.RequestLatestPending();
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitCampaignUI()
|
public void InitCampaignUI()
|
||||||
{
|
{
|
||||||
campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
|
campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
|
||||||
CampaignUI = new CampaignUI(this, campaignUIContainer)
|
CampaignUI = new CampaignUI(this, campaignUIContainer)
|
||||||
@@ -720,7 +720,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
campaign.UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, force: true);
|
campaign.UpgradeManager.PurchaseItemSwap(purchasedItemSwap.ItemToRemove, purchasedItemSwap.ItemToInstall, isNetworkMessage: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (Item item in Item.ItemList.ToList())
|
foreach (Item item in Item.ItemList.ToList())
|
||||||
@@ -906,6 +906,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void ClientReadCrew(IReadMessage msg)
|
public void ClientReadCrew(IReadMessage msg)
|
||||||
{
|
{
|
||||||
|
bool createNotification = msg.ReadBoolean();
|
||||||
|
|
||||||
ushort availableHireLength = msg.ReadUInt16();
|
ushort availableHireLength = msg.ReadUInt16();
|
||||||
List<CharacterInfo> availableHires = new List<CharacterInfo>();
|
List<CharacterInfo> availableHires = new List<CharacterInfo>();
|
||||||
for (int i = 0; i < availableHireLength; i++)
|
for (int i = 0; i < availableHireLength; i++)
|
||||||
@@ -916,10 +918,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
ushort pendingHireLength = msg.ReadUInt16();
|
ushort pendingHireLength = msg.ReadUInt16();
|
||||||
List<int> pendingHires = new List<int>();
|
List<UInt16> pendingHires = new List<UInt16>();
|
||||||
for (int i = 0; i < pendingHireLength; i++)
|
for (int i = 0; i < pendingHireLength; i++)
|
||||||
{
|
{
|
||||||
pendingHires.Add(msg.ReadInt32());
|
pendingHires.Add(msg.ReadUInt16());
|
||||||
}
|
}
|
||||||
|
|
||||||
ushort hiredLength = msg.ReadUInt16();
|
ushort hiredLength = msg.ReadUInt16();
|
||||||
@@ -934,30 +936,49 @@ namespace Barotrauma
|
|||||||
bool renameCrewMember = msg.ReadBoolean();
|
bool renameCrewMember = msg.ReadBoolean();
|
||||||
if (renameCrewMember)
|
if (renameCrewMember)
|
||||||
{
|
{
|
||||||
int renamedIdentifier = msg.ReadInt32();
|
UInt16 renamedIdentifier = msg.ReadUInt16();
|
||||||
string newName = msg.ReadString();
|
string newName = msg.ReadString();
|
||||||
CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
|
CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == renamedIdentifier);
|
||||||
if (renamedCharacter != null) { CrewManager.RenameCharacter(renamedCharacter, newName); }
|
if (renamedCharacter != null)
|
||||||
|
{
|
||||||
|
CrewManager.RenameCharacter(renamedCharacter, newName);
|
||||||
|
// Since renaming can only be done once in permadeath, we can safely set this to false to disable the renaming in the UI.
|
||||||
|
renamedCharacter.RenamingEnabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Could not find a character to rename with the ID {renamedIdentifier}.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fireCharacter = msg.ReadBoolean();
|
bool fireCharacter = msg.ReadBoolean();
|
||||||
if (fireCharacter)
|
if (fireCharacter)
|
||||||
{
|
{
|
||||||
int firedIdentifier = msg.ReadInt32();
|
UInt16 firedIdentifier = msg.ReadUInt16();
|
||||||
CharacterInfo firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.GetIdentifier() == firedIdentifier);
|
CharacterInfo firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == firedIdentifier);
|
||||||
// this one might and is allowed to be null since the character is already fired on the original sender's game
|
// this one might and is allowed to be null since the character is already fired on the original sender's game
|
||||||
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
|
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null &&
|
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.HRManagerUI != null)
|
||||||
/*can't apply until we have the latest save file*/
|
|
||||||
!NetIdUtils.IdMoreRecent(pendingSaveID, LastSaveID))
|
|
||||||
{
|
{
|
||||||
CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
|
//can't apply until we have the latest save file
|
||||||
if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters, takeMoney: false); }
|
if (!NetIdUtils.IdMoreRecent(pendingSaveID, LastSaveID))
|
||||||
CampaignUI.CrewManagement.SetPendingHires(pendingHires, map.CurrentLocation);
|
{
|
||||||
if (renameCrewMember || fireCharacter) { CampaignUI.CrewManagement.UpdateCrew(); }
|
CampaignUI.HRManagerUI.SetHireables(map.CurrentLocation, availableHires);
|
||||||
|
if (hiredCharacters.Any()) { CampaignUI.HRManagerUI.ValidateHires(hiredCharacters, takeMoney: false, createNotification: createNotification); }
|
||||||
|
CampaignUI.HRManagerUI.SetPendingHires(pendingHires, map.CurrentLocation);
|
||||||
|
if (renameCrewMember || fireCharacter) { CampaignUI.HRManagerUI.UpdateCrew(); }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//This is pretty nasty: setting hireables is handled through CrewManagement,
|
||||||
|
//which is part of the Campaign UI that might not exist when the client is still initializing the round.
|
||||||
|
//If that's the case, let's force the available hires here so they're available when the UI is created
|
||||||
|
CurrentLocation?.ForceHireableCharacters(availableHires);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientReadMoney(IReadMessage inc)
|
public void ClientReadMoney(IReadMessage inc)
|
||||||
@@ -979,6 +1000,7 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
Bank.Balance = info.Balance;
|
Bank.Balance = info.Balance;
|
||||||
|
Bank.RewardDistribution = info.RewardDistribution;
|
||||||
TryInvokeEvent(Bank, transaction.ChangedData, info);
|
TryInvokeEvent(Bank, transaction.ChangedData, info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -994,6 +1016,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool TryPurchase(Client client, int price)
|
public override bool TryPurchase(Client client, int price)
|
||||||
{
|
{
|
||||||
|
if (price == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
if (!AllowedToManageCampaign(ClientPermissions.ManageMoney))
|
if (!AllowedToManageCampaign(ClientPermissions.ManageMoney))
|
||||||
{
|
{
|
||||||
return PersonalWallet.TryDeduct(price);
|
return PersonalWallet.TryDeduct(price);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
using System;
|
using Barotrauma.Abilities;
|
||||||
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -43,13 +45,19 @@ namespace Barotrauma
|
|||||||
private GUIButton crewListButton, commandButton, tabMenuButton;
|
private GUIButton crewListButton, commandButton, tabMenuButton;
|
||||||
private GUIImage talentPointNotification;
|
private GUIImage talentPointNotification;
|
||||||
|
|
||||||
private GUIComponent respawnInfoFrame, respawnButtonContainer;
|
private GUIComponent deathChoiceInfoFrame, deathChoiceButtonContainer;
|
||||||
private GUITextBlock respawnInfoText;
|
private GUITextBlock respawnInfoText;
|
||||||
private GUITickBox respawnTickBox;
|
private GUITickBox deathChoiceTickBox;
|
||||||
|
private GUIButton takeOverBotButton;
|
||||||
|
private GUIButton hrManagerButton;
|
||||||
|
public DeathPrompt DeathPrompt;
|
||||||
|
|
||||||
private GUIImage eventLogNotification;
|
private GUIImage eventLogNotification;
|
||||||
|
|
||||||
private Point prevTopLeftButtonsResolution;
|
private Point prevTopLeftButtonsResolution;
|
||||||
|
|
||||||
|
public bool AllowHrManagerBotTakeover => GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false }
|
||||||
|
&& Level.IsLoadedFriendlyOutpost;
|
||||||
|
|
||||||
private void CreateTopLeftButtons()
|
private void CreateTopLeftButtons()
|
||||||
{
|
{
|
||||||
@@ -96,30 +104,63 @@ namespace Barotrauma
|
|||||||
|
|
||||||
talentPointNotification = CreateNotificationIcon(tabMenuButton);
|
talentPointNotification = CreateNotificationIcon(tabMenuButton);
|
||||||
eventLogNotification = CreateNotificationIcon(tabMenuButton);
|
eventLogNotification = CreateNotificationIcon(tabMenuButton);
|
||||||
|
|
||||||
respawnInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
|
// The visibility of the following contents of deathChoiceInfoFrame is controlled by SetRespawnInfo()
|
||||||
{ MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null)
|
|
||||||
|
deathChoiceInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 1.0f), parent: topLeftButtonGroup.RectTransform)
|
||||||
|
{ MaxSize = new Point(HUDLayoutSettings.ButtonAreaTop.Width / 3, int.MaxValue) }, style: null)
|
||||||
{
|
{
|
||||||
Visible = false
|
Visible = false
|
||||||
};
|
};
|
||||||
respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform), "", wrap: true);
|
respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform), "", wrap: true);
|
||||||
respawnButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
deathChoiceButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||||
{
|
{
|
||||||
AbsoluteSpacing = HUDLayoutSettings.Padding,
|
AbsoluteSpacing = HUDLayoutSettings.Padding,
|
||||||
Stretch = true,
|
Stretch = true,
|
||||||
Visible = false
|
Visible = false
|
||||||
};
|
};
|
||||||
respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn"))
|
|
||||||
|
takeOverBotButton = new GUIButton(new RectTransform(Vector2.One * 0.9f, deathChoiceButtonContainer.RectTransform, Anchor.Center),
|
||||||
|
TextManager.Get("takeoverbotquestionprompttakeoverbot"), style: "GUIButtonSmall")
|
||||||
{
|
{
|
||||||
ToolTip = TextManager.GetWithVariable(
|
OnClicked = (btn, userdata) =>
|
||||||
"respawnquestionprompt", "[percentage]",
|
|
||||||
(Math.Round(Networking.RespawnManager.SkillLossPercentageOnImmediateRespawn).ToString())),
|
|
||||||
OnSelected = (tickbox) =>
|
|
||||||
{
|
{
|
||||||
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: !tickbox.Selected);
|
DeathPrompt.CreateTakeOverBotPanel();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
takeOverBotButton.TextBlock.AutoScaleHorizontal = true;
|
||||||
|
|
||||||
|
hrManagerButton = new GUIButton(new RectTransform(Vector2.One * 0.9f, deathChoiceButtonContainer.RectTransform, Anchor.Center),
|
||||||
|
TextManager.Get("npctitle.hrmanager"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession?.Campaign is { } campaign)
|
||||||
|
{
|
||||||
|
campaign.ShowCampaignUI = true;
|
||||||
|
campaign.CampaignUI?.SelectTab(CampaignMode.InteractionType.Crew);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
hrManagerButton.TextBlock.AutoScaleHorizontal = true;
|
||||||
|
|
||||||
|
var questionText =
|
||||||
|
TextManager.GetWithVariable(
|
||||||
|
"respawnquestionprompt", "[percentage]",
|
||||||
|
((int)Math.Round(RespawnManager.SkillLossPercentageOnImmediateRespawn)).ToString());
|
||||||
|
deathChoiceTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, deathChoiceButtonContainer.RectTransform, Anchor.Center),
|
||||||
|
TextManager.Get("respawnquestionpromptrespawn"))
|
||||||
|
{
|
||||||
|
ToolTip = questionText,
|
||||||
|
OnSelected = (tickbox) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: !tickbox.Selected);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
prevTopLeftButtonsResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
prevTopLeftButtonsResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,6 +191,8 @@ namespace Barotrauma
|
|||||||
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
|
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
|
||||||
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
|
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DeathPrompt?.AddToGUIUpdateList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static GUIImage CreateNotificationIcon(GUIComponent parent, bool offset = true)
|
public static GUIImage CreateNotificationIcon(GUIComponent parent, bool offset = true)
|
||||||
@@ -230,16 +273,67 @@ namespace Barotrauma
|
|||||||
HintManager.Update();
|
HintManager.Update();
|
||||||
ObjectiveManager.VideoPlayer.Update();
|
ObjectiveManager.VideoPlayer.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn)
|
/// <summary>
|
||||||
|
/// This method controls the content and visibility logic of the respawn-related GUI elements at the top left of the game screen.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="waitForNextRoundRespawn">Has the player chosen to wait until next round</param>
|
||||||
|
/// <param name="hideButtons">Hide the respawn buttons even if they would otherwise be visible</param>
|
||||||
|
public void SetRespawnInfo(string text, Color textColor, bool waitForNextRoundRespawn, bool hideButtons = false)
|
||||||
{
|
{
|
||||||
if (topLeftButtonGroup == null) { return; }
|
if (topLeftButtonGroup == null) { return; }
|
||||||
respawnInfoFrame.Visible = visible;
|
|
||||||
if (!visible) { return; }
|
bool permadeathMode = GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath };
|
||||||
|
bool ironmanMode = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
|
||||||
|
|
||||||
|
bool hasRespawnOptions;
|
||||||
|
if (permadeathMode)
|
||||||
|
{
|
||||||
|
// In permadeath mode you can (in ironman, must) always at least wait, and possibly buy a new character from HR or take control of a bot
|
||||||
|
hasRespawnOptions = !ironmanMode &&
|
||||||
|
GameMain.Client is GameClient client && (client.CharacterInfo == null || client.CharacterInfo.PermanentlyDead);
|
||||||
|
}
|
||||||
|
else // "classic" respawn modes
|
||||||
|
{
|
||||||
|
//can choose between midround respawning with a penalty or waiting
|
||||||
|
//if we're in a non-outpost level, and either don't have an existing character or have already spawned during the round
|
||||||
|
//(otherwise, e.g. when joining a campaign in which we have an existing character, we can respawn mid-round "for free" and there's no reason to make a choice)
|
||||||
|
hasRespawnOptions = Level.Loaded?.Type != LevelData.LevelType.Outpost &&
|
||||||
|
(GameMain.Client is GameClient client && (client.CharacterInfo == null || client.HasSpawned));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Are the death choice elements shown at all, at least with the text?
|
||||||
|
deathChoiceInfoFrame.Visible = !text.IsNullOrEmpty() || hasRespawnOptions;
|
||||||
|
if (!deathChoiceInfoFrame.Visible) { return; }
|
||||||
respawnInfoText.Text = text;
|
respawnInfoText.Text = text;
|
||||||
respawnInfoText.TextColor = textColor;
|
respawnInfoText.TextColor = textColor;
|
||||||
respawnButtonContainer.Visible = buttonsVisible;
|
|
||||||
respawnTickBox.Selected = !waitForNextRoundRespawn;
|
// Determine if we even bother considering showing the buttons
|
||||||
|
if (GameMain.GameSession.GameMode is not CampaignMode || Character.Controlled != null)
|
||||||
|
{
|
||||||
|
// Disable the button container in case it was left visible earlier
|
||||||
|
deathChoiceButtonContainer.Visible = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
deathChoiceButtonContainer.Visible = hasRespawnOptions && !hideButtons;
|
||||||
|
if (deathChoiceButtonContainer.Visible)
|
||||||
|
{
|
||||||
|
hrManagerButton.Visible = AllowHrManagerBotTakeover;
|
||||||
|
|
||||||
|
if (permadeathMode && ironmanMode)
|
||||||
|
{
|
||||||
|
takeOverBotButton.Visible = false;
|
||||||
|
deathChoiceTickBox.Visible = false;
|
||||||
|
deathChoiceTickBox.Selected = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
takeOverBotButton.Visible = permadeathMode && GameMain.NetworkMember?.ServerSettings is { AllowBotTakeoverOnPermadeath: true };
|
||||||
|
deathChoiceTickBox.Visible = !permadeathMode;
|
||||||
|
deathChoiceTickBox.Selected = !waitForNextRoundRespawn;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(SpriteBatch spriteBatch)
|
public void Draw(SpriteBatch spriteBatch)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -81,6 +81,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void Update(float deltaTime)
|
public void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
|
processAfflictionChangesTimer -= deltaTime;
|
||||||
|
if (processAfflictionChangesTimer <= 0.0f)
|
||||||
|
{
|
||||||
|
foreach (var character in charactersWithAfflictionChanges)
|
||||||
|
{
|
||||||
|
if (GameMain.NetworkMember is null)
|
||||||
|
{
|
||||||
|
ImmutableArray<NetAffliction> afflictions = GetAllAfflictions(character.CharacterHealth);
|
||||||
|
ui?.UpdateAfflictions(new NetCrewMember(character.Info, afflictions));
|
||||||
|
}
|
||||||
|
ui?.UpdateCrewPanel();
|
||||||
|
}
|
||||||
|
charactersWithAfflictionChanges.Clear();
|
||||||
|
processAfflictionChangesTimer = ProcessAfflictionChangesInterval;
|
||||||
|
}
|
||||||
|
|
||||||
DateTimeOffset now = DateTimeOffset.Now;
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
UpdateQueue(afflictionRequests, now, onTimeout: static callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); });
|
UpdateQueue(afflictionRequests, now, onTimeout: static callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); });
|
||||||
UpdateQueue(pendingHealRequests, now, onTimeout: static callback => { callback(new PendingRequest(RequestResult.Timeout, NetCollection<NetCrewMember>.Empty)); });
|
UpdateQueue(pendingHealRequests, now, onTimeout: static callback => { callback(new PendingRequest(RequestResult.Timeout, NetCollection<NetCrewMember>.Empty)); });
|
||||||
|
|||||||
@@ -785,7 +785,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (item.Container == null || character.Inventory.FindIndex(item.Container) == -1) // Not a subinventory in the character's inventory
|
if (item.Container == null || character.Inventory.FindIndex(item.Container) == -1) // Not a subinventory in the character's inventory
|
||||||
{
|
{
|
||||||
if (character.HeldItems.Any(i => i.OwnInventory != null && i.OwnInventory.CanBePut(item)))
|
if (character.HeldItems.Any(i => i.OwnInventory != null && i.OwnInventory.CanBePut(item) && character.CanAccessInventory(i.OwnInventory)))
|
||||||
{
|
{
|
||||||
return QuickUseAction.PutToEquippedItem;
|
return QuickUseAction.PutToEquippedItem;
|
||||||
}
|
}
|
||||||
@@ -843,13 +843,14 @@ namespace Barotrauma
|
|||||||
else if (character.HeldItems.FirstOrDefault(i =>
|
else if (character.HeldItems.FirstOrDefault(i =>
|
||||||
i.OwnInventory != null &&
|
i.OwnInventory != null &&
|
||||||
i.OwnInventory.Container.DrawInventory &&
|
i.OwnInventory.Container.DrawInventory &&
|
||||||
|
character.CanAccessInventory(i.OwnInventory) &&
|
||||||
(i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))) is { } equippedContainer)
|
(i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))) is { } equippedContainer)
|
||||||
{
|
{
|
||||||
if (allowEquip)
|
if (allowEquip)
|
||||||
{
|
{
|
||||||
if (!character.HasEquippedItem(item))
|
if (!character.HasEquippedItem(item))
|
||||||
{
|
{
|
||||||
if (equippedContainer.GetComponent<ItemContainer>() is { QuickUseMovesItemsInside: false})
|
if (equippedContainer.GetComponent<ItemContainer>() is { QuickUseMovesItemsInside: false })
|
||||||
{
|
{
|
||||||
//put the item in a hand slot if that hand is free
|
//put the item in a hand slot if that hand is free
|
||||||
if ((item.AllowedSlots.Contains(InvSlotType.RightHand) && character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) == null) ||
|
if ((item.AllowedSlots.Contains(InvSlotType.RightHand) && character.Inventory.GetItemInLimbSlot(InvSlotType.RightHand) == null) ||
|
||||||
|
|||||||
@@ -79,7 +79,8 @@ namespace Barotrauma.Items.Components
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serialize(false, IsPropertySaveable.No, description: "If true, the contained state indicator calculates how full the item is based on the total amount of items that can be stacked inside it, as opposed to how many of the inventory slots are occupied.")]
|
[Serialize(false, IsPropertySaveable.No, description: "If true, the contained state indicator calculates how full the item is based on the total amount of items that can be stacked inside it, as opposed to how many of the inventory slots are occupied." +
|
||||||
|
" Note that only items in the main container or in the subcontainer are counted, depending on which container the first containable item match is found in. The item determining this can be defined with ContainedStateIndicatorSlot")]
|
||||||
public bool ShowTotalStackCapacityInContainedStateIndicator { get; set; }
|
public bool ShowTotalStackCapacityInContainedStateIndicator { get; set; }
|
||||||
|
|
||||||
[Serialize(false, IsPropertySaveable.No, description: "Should the inventory of this item be kept open when the item is equipped by a character.")]
|
[Serialize(false, IsPropertySaveable.No, description: "Should the inventory of this item be kept open when the item is equipped by a character.")]
|
||||||
@@ -274,8 +275,14 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
itemsPerSlot.Sort((i1, i2) => i1.First().Name.CompareTo(i2.First().Name));
|
var sortedItems = itemsPerSlot
|
||||||
foreach (var items in itemsPerSlot)
|
.OrderBy(i => i.First().Name)
|
||||||
|
//if there's multiple items with the same name, sort largest stacks first
|
||||||
|
.ThenByDescending(i => i.Count)
|
||||||
|
//same name and stack size, sort items with most items inside first
|
||||||
|
.ThenByDescending(i => i.First().ContainedItems.Count());
|
||||||
|
|
||||||
|
foreach (var items in sortedItems)
|
||||||
{
|
{
|
||||||
int firstFreeSlot = -1;
|
int firstFreeSlot = -1;
|
||||||
for (int i = 0; i < Inventory.Capacity; i++)
|
for (int i = 0; i < Inventory.Capacity; i++)
|
||||||
@@ -591,7 +598,8 @@ namespace Barotrauma.Items.Components
|
|||||||
contained.Item.Scale,
|
contained.Item.Scale,
|
||||||
spriteEffects,
|
spriteEffects,
|
||||||
depth: containedSpriteDepth);
|
depth: containedSpriteDepth);
|
||||||
contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX,flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation), containedSpriteDepth);
|
contained.Item.DrawDecorativeSprites(spriteBatch, itemPos, flipX,flipY, (contained.Item.body == null ? 0.0f : contained.Item.body.DrawRotation),
|
||||||
|
containedSpriteDepth, overrideColor);
|
||||||
|
|
||||||
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
|
foreach (ItemContainer ic in contained.Item.GetComponents<ItemContainer>())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
|
|||||||
partial void SetLightSourceState(bool enabled, float brightness)
|
partial void SetLightSourceState(bool enabled, float brightness)
|
||||||
{
|
{
|
||||||
if (Light == null) { return; }
|
if (Light == null) { return; }
|
||||||
if (item.HiddenInGame) { enabled = false; }
|
if (item.IsHidden) { enabled = false; }
|
||||||
Light.Enabled = enabled;
|
Light.Enabled = enabled;
|
||||||
lightColorMultiplier = brightness;
|
lightColorMultiplier = brightness;
|
||||||
if (enabled)
|
if (enabled)
|
||||||
|
|||||||
@@ -429,7 +429,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (it?.Submarine == null) { return false; }
|
if (it?.Submarine == null) { return false; }
|
||||||
if (item.Submarine == null || !item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
|
if (item.Submarine == null || !item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
|
||||||
if (it.NonInteractable || it.HiddenInGame) { return false; }
|
if (it.NonInteractable || it.IsHidden) { return false; }
|
||||||
if (it.GetComponent<Pickable>() == null) { return false; }
|
if (it.GetComponent<Pickable>() == null) { return false; }
|
||||||
|
|
||||||
var holdable = it.GetComponent<Holdable>();
|
var holdable = it.GetComponent<Holdable>();
|
||||||
@@ -470,10 +470,10 @@ namespace Barotrauma.Items.Components
|
|||||||
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
|
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
|
||||||
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
|
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
|
||||||
|
|
||||||
ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() != null || it.GetComponent<Turret>() != null)).ToImmutableHashSet();
|
ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.IsHidden && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() != null || it.GetComponent<Turret>() != null)).ToImmutableHashSet();
|
||||||
miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents);
|
miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents);
|
||||||
|
|
||||||
IEnumerable<Item> electricalPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
|
IEnumerable<Item> electricalPointsOfInterest = Item.ItemList.Where(it => item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true) && !it.IsHidden && !it.NonInteractable && it.GetComponent<Repairable>() != null);
|
||||||
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electricalPointsOfInterest, out electricalMapComponents);
|
electricalFrame = CreateMiniMap(item.Submarine, miniMapContainer, new MiniMapSettings(createHullElements: false), electricalPointsOfInterest, out electricalMapComponents);
|
||||||
|
|
||||||
Dictionary<MiniMapGUIComponent, GUIComponent> electricChildren = new Dictionary<MiniMapGUIComponent, GUIComponent>();
|
Dictionary<MiniMapGUIComponent, GUIComponent> electricChildren = new Dictionary<MiniMapGUIComponent, GUIComponent>();
|
||||||
@@ -566,7 +566,7 @@ namespace Barotrauma.Items.Components
|
|||||||
displayedSubs.Add(item.Submarine);
|
displayedSubs.Add(item.Submarine);
|
||||||
displayedSubs.AddRange(item.Submarine.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID));
|
displayedSubs.AddRange(item.Submarine.DockedTo.Where(s => s.TeamID == item.Submarine.TeamID));
|
||||||
|
|
||||||
subEntities = MapEntity.MapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.HiddenInGame).OrderByDescending(w => w.SpriteDepth).ToList();
|
subEntities = MapEntity.MapEntityList.Where(me => (item.Submarine is { } sub && sub.IsEntityFoundOnThisSub(me, includingConnectedSubs: true, allowDifferentType: false)) && !me.IsHidden).OrderByDescending(w => w.SpriteDepth).ToList();
|
||||||
|
|
||||||
BakeSubmarine(item.Submarine, parentRect);
|
BakeSubmarine(item.Submarine, parentRect);
|
||||||
elementSize = GuiFrame.Rect.Size;
|
elementSize = GuiFrame.Rect.Size;
|
||||||
@@ -763,7 +763,7 @@ namespace Barotrauma.Items.Components
|
|||||||
worldBorders.Location += item.Submarine.WorldPosition.ToPoint();
|
worldBorders.Location += item.Submarine.WorldPosition.ToPoint();
|
||||||
foreach (Gap gap in Gap.GapList)
|
foreach (Gap gap in Gap.GapList)
|
||||||
{
|
{
|
||||||
if (gap.IsRoomToRoom || gap.linkedTo.Count == 0 || gap.Submarine != item.Submarine || gap.ConnectedDoor != null || gap.HiddenInGame) { continue; }
|
if (gap.IsRoomToRoom || gap.linkedTo.Count == 0 || gap.Submarine != item.Submarine || gap.ConnectedDoor != null || gap.IsHidden) { continue; }
|
||||||
RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders);
|
RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders);
|
||||||
|
|
||||||
Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f;
|
Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f;
|
||||||
@@ -930,7 +930,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (DisplayAsSameItem(it.Prefab, searchedPrefab))
|
if (DisplayAsSameItem(it.Prefab, searchedPrefab))
|
||||||
{
|
{
|
||||||
// ignore items on players and hidden inventories
|
// ignore items on players and hidden inventories
|
||||||
if (it.FindParentInventory(inv => inv is CharacterInventory || inv is ItemInventory { Owner: Item { HiddenInGame: true }}) is { }) { continue; }
|
if (it.FindParentInventory(inv => inv is CharacterInventory || inv is ItemInventory { Owner: Item { IsHidden: true }}) is { }) { continue; }
|
||||||
|
|
||||||
if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent)
|
if (it.FindParentInventory(inventory => inventory is ItemInventory { Owner: Item { ParentInventory: null } }) is ItemInventory parent)
|
||||||
{
|
{
|
||||||
@@ -1112,7 +1112,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (ShowHullIntegrity)
|
if (ShowHullIntegrity)
|
||||||
{
|
{
|
||||||
float amount = 1f + hullData.LinkedHulls.Count;
|
float amount = 1f + hullData.LinkedHulls.Count;
|
||||||
gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => g.linkedTo.Count == 1 && !g.HiddenInGame).Sum(g => g.Open) / amount;
|
gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => g.linkedTo.Count == 1 && !g.IsHidden).Sum(g => g.Open) / amount;
|
||||||
borderColor = Color.Lerp(neutralColor, GUIStyle.Red, Math.Min(gapOpenSum, 1.0f));
|
borderColor = Color.Lerp(neutralColor, GUIStyle.Red, Math.Min(gapOpenSum, 1.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1557,7 +1557,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (linkedEntity is Hull linkedHull)
|
if (linkedEntity is Hull linkedHull)
|
||||||
{
|
{
|
||||||
if (linkedHulls.Contains(linkedHull) || linkedHull.HiddenInGame) { continue; }
|
if (linkedHulls.Contains(linkedHull) || linkedHull.IsHidden) { continue; }
|
||||||
linkedHulls.Add(linkedHull);
|
linkedHulls.Add(linkedHull);
|
||||||
GetLinkedHulls(linkedHull, linkedHulls);
|
GetLinkedHulls(linkedHull, linkedHulls);
|
||||||
}
|
}
|
||||||
@@ -1737,7 +1737,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
bool IsPartofSub(MapEntity entity)
|
bool IsPartofSub(MapEntity entity)
|
||||||
{
|
{
|
||||||
if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine) || entity.HiddenInGame) { return false; }
|
if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine) || entity.IsHidden) { return false; }
|
||||||
return sub.IsEntityFoundOnThisSub(entity, true);
|
return sub.IsEntityFoundOnThisSub(entity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1186,7 +1186,7 @@ namespace Barotrauma.Items.Components
|
|||||||
foreach (DockingPort dockingPort in DockingPort.List)
|
foreach (DockingPort dockingPort in DockingPort.List)
|
||||||
{
|
{
|
||||||
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
|
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
|
||||||
if (dockingPort.Item.HiddenInGame) { continue; }
|
if (dockingPort.Item.IsHidden) { continue; }
|
||||||
if (dockingPort.Item.Submarine == null) { continue; }
|
if (dockingPort.Item.Submarine == null) { continue; }
|
||||||
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
|
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
|
||||||
// docking ports should be shown even if defined as not, if the submarine is the same as the sonar's
|
// docking ports should be shown even if defined as not, if the submarine is the same as the sonar's
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override bool ShouldDrawHUD(Character character)
|
public override bool ShouldDrawHUD(Character character)
|
||||||
{
|
{
|
||||||
if (item.HiddenInGame) { return false; }
|
if (item.IsHidden) { return false; }
|
||||||
if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; }
|
if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; }
|
||||||
if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
|
if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
|
||||||
if (item.ConditionPercentageRelativeToDefaultMaxCondition < RepairThreshold) { return true; }
|
if (item.ConditionPercentageRelativeToDefaultMaxCondition < RepairThreshold) { return true; }
|
||||||
@@ -224,7 +224,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime)
|
partial void UpdateProjSpecific(float deltaTime)
|
||||||
{
|
{
|
||||||
if (item.HiddenInGame) { return; }
|
if (item.IsHidden) { return; }
|
||||||
if (FakeBrokenTimer > 0.0f)
|
if (FakeBrokenTimer > 0.0f)
|
||||||
{
|
{
|
||||||
item.FakeBroken = true;
|
item.FakeBroken = true;
|
||||||
@@ -397,6 +397,12 @@ namespace Barotrauma.Items.Components
|
|||||||
GUI.DrawString(spriteBatch,
|
GUI.DrawString(spriteBatch,
|
||||||
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition,
|
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 20), "Condition: " + (int)item.Condition + "/" + (int)item.MaxCondition,
|
||||||
GUIStyle.Orange);
|
GUIStyle.Orange);
|
||||||
|
if (MaxStressDeteriorationMultiplier > 1.0f)
|
||||||
|
{
|
||||||
|
GUI.DrawString(spriteBatch,
|
||||||
|
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + 40), "Stress multiplier: " + StressDeteriorationMultiplier.ToString("0.00"),
|
||||||
|
GUIStyle.Red);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -303,6 +303,17 @@ namespace Barotrauma.Items.Components
|
|||||||
CreateClientEvent(new CircuitBoxRenameLabelEvent(label.ID, color, header, body));
|
CreateClientEvent(new CircuitBoxRenameLabelEvent(label.ID, color, header, body));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetConnectionLabelOverrides(CircuitBoxInputOutputNode node, Dictionary<string, string> newOverrides)
|
||||||
|
{
|
||||||
|
if (GameMain.NetworkMember is null)
|
||||||
|
{
|
||||||
|
node.ReplaceAllConnectionLabelOverrides(newOverrides);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateClientEvent(new CircuitBoxRenameConnectionLabelsEvent(node.NodeType, newOverrides.ToNetDictionary()));
|
||||||
|
}
|
||||||
|
|
||||||
public void ResizeNode(CircuitBoxNode node, CircuitBoxResizeDirection dir, Vector2 amount)
|
public void ResizeNode(CircuitBoxNode node, CircuitBoxResizeDirection dir, Vector2 amount)
|
||||||
{
|
{
|
||||||
if (Locked) { return; }
|
if (Locked) { return; }
|
||||||
@@ -528,6 +539,12 @@ namespace Barotrauma.Items.Components
|
|||||||
_ => node.Position
|
_ => node.Position
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (var labelOverride in data.LabelOverrides)
|
||||||
|
{
|
||||||
|
RenameConnectionLabelsInternal(labelOverride.Type, labelOverride.Override.ToDictionary());
|
||||||
|
}
|
||||||
|
|
||||||
wasInitializedByServer = true;
|
wasInitializedByServer = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -556,6 +573,12 @@ namespace Barotrauma.Items.Components
|
|||||||
ResizeLabelInternal(data.ID, data.Position, data.Size);
|
ResizeLabelInternal(data.ID, data.Position, data.Size);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CircuitBoxOpcode.RenameConnections:
|
||||||
|
{
|
||||||
|
var data = INetSerializableStruct.Read<CircuitBoxRenameConnectionLabelsEvent>(msg);
|
||||||
|
RenameConnectionLabelsInternal(data.Type, data.Override.ToDictionary());
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
|
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -292,8 +292,17 @@ namespace Barotrauma.Items.Components
|
|||||||
if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
|
if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
|
||||||
|
|
||||||
Connection recipient = wire.OtherConnection(this);
|
Connection recipient = wire.OtherConnection(this);
|
||||||
LocalizedString label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
LocalizedString label;
|
||||||
if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
if (wire.Item.IsLayerHidden)
|
||||||
|
{
|
||||||
|
label = TextManager.Get("ConnectionLocked");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
|
||||||
|
if (wire.Locked) { label += "\n" + TextManager.Get("ConnectionLocked"); }
|
||||||
|
}
|
||||||
|
|
||||||
DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label);
|
DrawWire(spriteBatch, wire, position, wirePosition, equippedWire, panel, label);
|
||||||
|
|
||||||
wirePosition.Y += wireInterval;
|
wirePosition.Y += wireInterval;
|
||||||
@@ -494,7 +503,7 @@ namespace Barotrauma.Items.Components
|
|||||||
ConnectionPanel.HighlightedWire = wire;
|
ConnectionPanel.HighlightedWire = wire;
|
||||||
|
|
||||||
bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring || panel.AlwaysAllowRewiring;
|
bool allowRewiring = GameMain.NetworkMember?.ServerSettings == null || GameMain.NetworkMember.ServerSettings.AllowRewiring || panel.AlwaysAllowRewiring;
|
||||||
if (allowRewiring && (!wire.Locked && !panel.Locked && !panel.TemporarilyLocked || Screen.Selected == GameMain.SubEditorScreen))
|
if (allowRewiring && (!wire.Locked && !wire.Item.IsLayerHidden && !panel.Locked && !panel.TemporarilyLocked || Screen.Selected == GameMain.SubEditorScreen))
|
||||||
{
|
{
|
||||||
//start dragging the wire
|
//start dragging the wire
|
||||||
if (PlayerInput.PrimaryMouseButtonHeld()) { DraggingConnected = wire; }
|
if (PlayerInput.PrimaryMouseButtonHeld()) { DraggingConnected = wire; }
|
||||||
|
|||||||
@@ -283,12 +283,12 @@ namespace Barotrauma.Items.Components
|
|||||||
texts.Add(CharacterHUD.GetCachedHudText("PlayHint", InputType.Use));
|
texts.Add(CharacterHUD.GetCachedHudText("PlayHint", InputType.Use));
|
||||||
textColors.Add(GUIStyle.Green);
|
textColors.Add(GUIStyle.Green);
|
||||||
}
|
}
|
||||||
if (target.CharacterHealth.UseHealthWindow && !target.DisableHealthWindow && equipper?.FocusedCharacter == target && equipper.CanInteractWith(target, 160f, false))
|
if (equipper?.FocusedCharacter == target && target.CanBeHealedBy(equipper, checkFriendlyTeam: false))
|
||||||
{
|
{
|
||||||
texts.Add(CharacterHUD.GetCachedHudText("HealHint", InputType.Health));
|
texts.Add(CharacterHUD.GetCachedHudText("HealHint", InputType.Health));
|
||||||
textColors.Add(GUIStyle.Green);
|
textColors.Add(GUIStyle.Green);
|
||||||
}
|
}
|
||||||
if (target.CanBeDragged)
|
if (target.CanBeDraggedBy(Character.Controlled))
|
||||||
{
|
{
|
||||||
texts.Add(CharacterHUD.GetCachedHudText("GrabHint", InputType.Grab));
|
texts.Add(CharacterHUD.GetCachedHudText("GrabHint", InputType.Grab));
|
||||||
textColors.Add(GUIStyle.Green);
|
textColors.Add(GUIStyle.Green);
|
||||||
|
|||||||
@@ -196,7 +196,7 @@ namespace Barotrauma.Items.Components
|
|||||||
Vector2 particlePos = GetRelativeFiringPosition();
|
Vector2 particlePos = GetRelativeFiringPosition();
|
||||||
foreach (ParticleEmitter emitter in particleEmitters)
|
foreach (ParticleEmitter emitter in particleEmitters)
|
||||||
{
|
{
|
||||||
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation);
|
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -Rotation, particleRotation: Rotation);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,7 +213,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (crosshairSprite != null)
|
if (crosshairSprite != null)
|
||||||
{
|
{
|
||||||
Vector2 itemPos = cam.WorldToScreen(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y));
|
Vector2 itemPos = cam.WorldToScreen(new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y));
|
||||||
Vector2 turretDir = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
Vector2 turretDir = new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation));
|
||||||
|
|
||||||
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
||||||
crosshairPos = new Vector2(
|
crosshairPos = new Vector2(
|
||||||
@@ -268,7 +268,7 @@ namespace Barotrauma.Items.Components
|
|||||||
foreach (ParticleEmitter emitter in particleEmitterCharges)
|
foreach (ParticleEmitter emitter in particleEmitterCharges)
|
||||||
{
|
{
|
||||||
// color is currently not connected to ammo type, should be updated when ammo is changed
|
// color is currently not connected to ammo type, should be updated when ammo is changed
|
||||||
emitter.Emit(deltaTime, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier);
|
emitter.Emit(deltaTime, particlePos, hullGuess: null, angle: -Rotation, particleRotation: Rotation, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying)
|
if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying)
|
||||||
@@ -339,7 +339,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (crosshairSprite != null)
|
if (crosshairSprite != null)
|
||||||
{
|
{
|
||||||
Vector2 itemPos = cam.WorldToScreen(item.WorldPosition);
|
Vector2 itemPos = cam.WorldToScreen(item.WorldPosition);
|
||||||
Vector2 turretDir = new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
|
Vector2 turretDir = new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation));
|
||||||
|
|
||||||
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
|
||||||
crosshairPos = new Vector2(
|
crosshairPos = new Vector2(
|
||||||
@@ -372,7 +372,7 @@ namespace Barotrauma.Items.Components
|
|||||||
recoilOffset = RecoilDistance;
|
recoilOffset = RecoilDistance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)) * recoilOffset;
|
return new Vector2((float)Math.Cos(Rotation), (float)Math.Sin(Rotation)) * recoilOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
|
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1, Color? overrideColor = null)
|
||||||
@@ -388,13 +388,13 @@ namespace Barotrauma.Items.Components
|
|||||||
railSprite?.Draw(spriteBatch,
|
railSprite?.Draw(spriteBatch,
|
||||||
drawPos,
|
drawPos,
|
||||||
overrideColor ?? item.SpriteColor,
|
overrideColor ?? item.SpriteColor,
|
||||||
rotation + MathHelper.PiOver2, item.Scale,
|
Rotation + MathHelper.PiOver2, item.Scale,
|
||||||
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
|
SpriteEffects.None, item.SpriteDepth + (railSprite.Depth - item.Sprite.Depth));
|
||||||
|
|
||||||
barrelSprite?.Draw(spriteBatch,
|
barrelSprite?.Draw(spriteBatch,
|
||||||
drawPos - GetRecoilOffset() * item.Scale,
|
drawPos - GetRecoilOffset() * item.Scale,
|
||||||
overrideColor ?? item.SpriteColor,
|
overrideColor ?? item.SpriteColor,
|
||||||
rotation + MathHelper.PiOver2, item.Scale,
|
Rotation + MathHelper.PiOver2, item.Scale,
|
||||||
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
|
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
|
||||||
|
|
||||||
float chargeRatio = currentChargeTime / MaxChargeTime;
|
float chargeRatio = currentChargeTime / MaxChargeTime;
|
||||||
@@ -402,9 +402,9 @@ namespace Barotrauma.Items.Components
|
|||||||
foreach ((Sprite chargeSprite, Vector2 position) in chargeSprites)
|
foreach ((Sprite chargeSprite, Vector2 position) in chargeSprites)
|
||||||
{
|
{
|
||||||
chargeSprite?.Draw(spriteBatch,
|
chargeSprite?.Draw(spriteBatch,
|
||||||
drawPos - MathUtils.RotatePoint(new Vector2(position.X * chargeRatio, position.Y * chargeRatio) * item.Scale, rotation + MathHelper.PiOver2),
|
drawPos - MathUtils.RotatePoint(new Vector2(position.X * chargeRatio, position.Y * chargeRatio) * item.Scale, Rotation + MathHelper.PiOver2),
|
||||||
item.SpriteColor,
|
item.SpriteColor,
|
||||||
rotation + MathHelper.PiOver2, item.Scale,
|
Rotation + MathHelper.PiOver2, item.Scale,
|
||||||
SpriteEffects.None, item.SpriteDepth + (chargeSprite.Depth - item.Sprite.Depth));
|
SpriteEffects.None, item.SpriteDepth + (chargeSprite.Depth - item.Sprite.Depth));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -427,9 +427,9 @@ namespace Barotrauma.Items.Components
|
|||||||
float newPositionOffset = barrelPositionModifier * SpinningBarrelDistance;
|
float newPositionOffset = barrelPositionModifier * SpinningBarrelDistance;
|
||||||
|
|
||||||
spinningBarrel.Draw(spriteBatch,
|
spinningBarrel.Draw(spriteBatch,
|
||||||
drawPos - MathUtils.RotatePoint(new Vector2(newPositionOffset, 0f) * item.Scale, rotation + MathHelper.PiOver2),
|
drawPos - MathUtils.RotatePoint(new Vector2(newPositionOffset, 0f) * item.Scale, Rotation + MathHelper.PiOver2),
|
||||||
Color.Lerp(overrideColor ?? item.SpriteColor, newColorModifier, 0.8f),
|
Color.Lerp(overrideColor ?? item.SpriteColor, newColorModifier, 0.8f),
|
||||||
rotation + MathHelper.PiOver2, item.Scale,
|
Rotation + MathHelper.PiOver2, item.Scale,
|
||||||
SpriteEffects.None, newDepth);
|
SpriteEffects.None, newDepth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -475,9 +475,9 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
spriteBatch.DrawLine(drawPos, drawPos + center * circleRadius, GUIStyle.Green, thickness: lineThickness);
|
spriteBatch.DrawLine(drawPos, drawPos + center * circleRadius, GUIStyle.Green, thickness: lineThickness);
|
||||||
}
|
}
|
||||||
else if (radians > Math.PI * 2)
|
else if (radians >= MathHelper.TwoPi)
|
||||||
{
|
{
|
||||||
spriteBatch.DrawCircle(drawPos, circleRadius, 180, GUIStyle.Red, thickness: lineThickness);
|
spriteBatch.DrawCircle(drawPos, circleRadius, 180, GUIStyle.Green, thickness: lineThickness);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -510,7 +510,12 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
widget.MouseHeld += (deltaTime) =>
|
widget.MouseHeld += (deltaTime) =>
|
||||||
{
|
{
|
||||||
minRotation = GetRotationAngle(GetDrawPos());
|
float newMinRotation = GetRotationAngle(GetDrawPos());
|
||||||
|
AngleWrapAdjustment(minRotation, newMinRotation, ref maxRotation);
|
||||||
|
|
||||||
|
// clamp value here to keep widget movement within max range
|
||||||
|
minRotation = MathHelper.Clamp(newMinRotation, maxRotation - MathHelper.TwoPi, maxRotation);
|
||||||
|
|
||||||
UpdateBarrel();
|
UpdateBarrel();
|
||||||
MapEntity.DisableSelect = true;
|
MapEntity.DisableSelect = true;
|
||||||
};
|
};
|
||||||
@@ -554,7 +559,12 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
widget.MouseHeld += (deltaTime) =>
|
widget.MouseHeld += (deltaTime) =>
|
||||||
{
|
{
|
||||||
maxRotation = GetRotationAngle(GetDrawPos());
|
float newMaxRotation = GetRotationAngle(GetDrawPos());
|
||||||
|
AngleWrapAdjustment(maxRotation, newMaxRotation, ref minRotation);
|
||||||
|
|
||||||
|
// clamp value here to keep widget movement within max range
|
||||||
|
maxRotation = MathHelper.Clamp(newMaxRotation, minRotation, minRotation + MathHelper.TwoPi);
|
||||||
|
|
||||||
UpdateBarrel();
|
UpdateBarrel();
|
||||||
MapEntity.DisableSelect = true;
|
MapEntity.DisableSelect = true;
|
||||||
};
|
};
|
||||||
@@ -580,10 +590,44 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
void UpdateBarrel()
|
void UpdateBarrel()
|
||||||
{
|
{
|
||||||
rotation = (minRotation + maxRotation) / 2;
|
Rotation = (minRotation + maxRotation) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AngleWrapAdjustment(float currentRotation, float newRotation, ref float rangeLockedRotation)
|
||||||
|
{
|
||||||
|
if (DetectAngleWrapAround(currentRotation, newRotation))
|
||||||
|
{
|
||||||
|
// if there's a wrap-around, also wrap the other rotation limit to keep range
|
||||||
|
if (newRotation < currentRotation)
|
||||||
|
{
|
||||||
|
rangeLockedRotation -= MathHelper.TwoPi;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rangeLockedRotation += MathHelper.TwoPi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool DetectAngleWrapAround(float rotation, float newRotation)
|
||||||
|
{
|
||||||
|
float deltaRotation = MathF.Abs(rotation - newRotation);
|
||||||
|
|
||||||
|
// turret angle wraps around to 0 from -2Pi and 2Pi.
|
||||||
|
// Detect wrap-around when dragging the widgets, where usual rotation delta is small,
|
||||||
|
// so a large jump in rotation (here, an arbitrary big value in the range of 0 to 2Pi)
|
||||||
|
// is considered a wrap-around for this purpose.
|
||||||
|
// NOTE: this is not a reliable way to detect angle wrap-around in general, and is only intended for
|
||||||
|
// the angle widgets!
|
||||||
|
if (deltaRotation > MathHelper.TwoPi * 0.8f)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public Vector2 GetDrawPos()
|
public Vector2 GetDrawPos()
|
||||||
{
|
{
|
||||||
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
|
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
|
||||||
@@ -764,7 +808,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (projectileID == 0) { return; }
|
if (projectileID == 0) { return; }
|
||||||
|
|
||||||
//ID ushort.MaxValue = launched without a projectile
|
//ID ushort.MaxValue = launched without a projectile
|
||||||
if (projectileID == ushort.MaxValue)
|
if (projectileID == LaunchWithoutProjectileId)
|
||||||
{
|
{
|
||||||
Launch(null, user);
|
Launch(null, user);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
string colorStr = (item.SpawnedInCurrentOutpost && !item.AllowStealing ? GUIStyle.Red : Color.White).ToStringHex();
|
string colorStr = (item.Illegitimate ? GUIStyle.Red : Color.White).ToStringHex();
|
||||||
|
|
||||||
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
|
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
|
||||||
if (item.GetComponent<Quality>() != null)
|
if (item.GetComponent<Quality>() != null)
|
||||||
@@ -478,10 +478,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
int row = (int)Math.Floor((double)i / slotsPerRow);
|
int row = (int)Math.Floor((double)i / slotsPerRow);
|
||||||
int slotsPerThisRow = Math.Min(slotsPerRow, capacity - row * slotsPerRow);
|
int slotsPerThisRow = Math.Min(slotsPerRow, capacity - row * slotsPerRow);
|
||||||
|
int slotNumberOnThisRow = i - row * slotsPerRow;
|
||||||
|
|
||||||
int rowWidth = (int)(rectSize.X * slotsPerThisRow + spacing.X * (slotsPerThisRow - 1));
|
int rowWidth = (int)(rectSize.X * slotsPerThisRow + spacing.X * (slotsPerThisRow - 1));
|
||||||
slotRect.X = (int)(center.X) - rowWidth / 2;
|
slotRect.X = (int)(center.X) - rowWidth / 2;
|
||||||
slotRect.X += (int)((rectSize.X + spacing.X) * (i % slotsPerThisRow));
|
slotRect.X += (int)((rectSize.X + spacing.X) * (slotNumberOnThisRow % slotsPerThisRow));
|
||||||
|
|
||||||
slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * row);
|
slotRect.Y = (int)(topLeft.Y + (rectSize.Y + spacing.Y) * row);
|
||||||
visualSlots[i] = new VisualSlot(slotRect);
|
visualSlots[i] = new VisualSlot(slotRect);
|
||||||
@@ -1185,6 +1186,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
DraggingItems.RemoveAll(it => !Character.Controlled.CanInteractWith(it));
|
DraggingItems.RemoveAll(it => !Character.Controlled.CanInteractWith(it));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DraggingItems.Any() && PlayerInput.PrimaryMouseButtonReleased())
|
if (DraggingItems.Any() && PlayerInput.PrimaryMouseButtonReleased())
|
||||||
{
|
{
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
@@ -1193,198 +1195,234 @@ namespace Barotrauma
|
|||||||
if (!DetermineMouseOnInventory(ignoreDraggedItem: true) &&
|
if (!DetermineMouseOnInventory(ignoreDraggedItem: true) &&
|
||||||
(CharacterHealth.OpenHealthWindow != null || mouseOnPortrait))
|
(CharacterHealth.OpenHealthWindow != null || mouseOnPortrait))
|
||||||
{
|
{
|
||||||
bool dropSuccessful = false;
|
if (TryPortraitAndHealthDrop(mouseOnPortrait))
|
||||||
foreach (Item item in DraggingItems)
|
|
||||||
{
|
{
|
||||||
var inventory = item.ParentInventory;
|
|
||||||
var indices = inventory?.FindIndices(item);
|
|
||||||
dropSuccessful |= (CharacterHealth.OpenHealthWindow ?? Character.Controlled.CharacterHealth).OnItemDropped(item, ignoreMousePos: mouseOnPortrait);
|
|
||||||
if (dropSuccessful)
|
|
||||||
{
|
|
||||||
if (indices != null && inventory.visualSlots != null)
|
|
||||||
{
|
|
||||||
foreach (int i in indices)
|
|
||||||
{
|
|
||||||
inventory.visualSlots[i]?.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (dropSuccessful)
|
|
||||||
{
|
|
||||||
DraggingItems.Clear();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedSlot == null)
|
if (selectedSlot == null)
|
||||||
{
|
{
|
||||||
if (DraggingItemToWorld &&
|
HandleOutsideInventoryDrop();
|
||||||
Character.Controlled.FocusedItem is { OwnInventory: { } inventory } item && item.GetComponent<ItemContainer>() is { } container &&
|
}
|
||||||
container.HasRequiredItems(Character.Controlled, addMessage: false) &&
|
else if (!DraggingItems.Any(it => selectedSlot.ParentInventory.slots[selectedSlot.SlotIndex].Contains(it)))
|
||||||
container.AllowDragAndDrop &&
|
{
|
||||||
inventory.CanBePut(DraggingItems.FirstOrDefault()))
|
HandleInventorySlotDrop();
|
||||||
|
}
|
||||||
|
|
||||||
|
DraggingItems.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedSlot != null && !CanSelectSlot(selectedSlot))
|
||||||
|
{
|
||||||
|
selectedSlot = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TryPortraitAndHealthDrop(bool mouseOnPortrait)
|
||||||
|
{
|
||||||
|
bool dropSuccessful = false;
|
||||||
|
foreach (Item item in DraggingItems)
|
||||||
|
{
|
||||||
|
var inventory = item.ParentInventory;
|
||||||
|
var indices = inventory?.FindIndices(item);
|
||||||
|
dropSuccessful |= (CharacterHealth.OpenHealthWindow ?? Character.Controlled.CharacterHealth).OnItemDropped(item, ignoreMousePos: mouseOnPortrait);
|
||||||
|
if (dropSuccessful)
|
||||||
{
|
{
|
||||||
bool anySuccess = false;
|
if (indices != null && inventory.visualSlots != null)
|
||||||
foreach (Item it in DraggingItems)
|
|
||||||
{
|
{
|
||||||
bool success = Character.Controlled.FocusedItem.OwnInventory.TryPutItem(it, Character.Controlled);
|
foreach (int i in indices)
|
||||||
if (!success) { break; }
|
|
||||||
anySuccess |= success;
|
|
||||||
}
|
|
||||||
if (anySuccess) { SoundPlayer.PlayUISound(GUISoundType.PickItem); }
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (Screen.Selected is SubEditorScreen)
|
|
||||||
{
|
|
||||||
if (DraggingItems.First()?.ParentInventory != null)
|
|
||||||
{
|
{
|
||||||
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(DraggingItems.First().ParentInventory, new List<Item>(DraggingItems), true));
|
inventory.visualSlots[i]?.ShowBorderHighlight(GUIStyle.Green, 0.1f, 0.4f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
SoundPlayer.PlayUISound(GUISoundType.DropItem);
|
}
|
||||||
bool removed = false;
|
}
|
||||||
if (Screen.Selected is SubEditorScreen editor)
|
if (dropSuccessful)
|
||||||
|
{
|
||||||
|
DraggingItems.Clear();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleOutsideInventoryDrop()
|
||||||
|
{
|
||||||
|
bool isTargetingValidContainer = Character.Controlled.FocusedItem is { OwnInventory: { } inventory } item &&
|
||||||
|
item.GetComponent<ItemContainer>() is { } container &&
|
||||||
|
container.HasRequiredItems(Character.Controlled, addMessage: false) &&
|
||||||
|
container.AllowDragAndDrop &&
|
||||||
|
inventory.CanBePut(DraggingItems.FirstOrDefault());
|
||||||
|
|
||||||
|
bool isTargetingValidCharacter = IsValidTargetForDragDropGive(Character.Controlled, Character.Controlled.FocusedCharacter);
|
||||||
|
|
||||||
|
if (DraggingItemToWorld && (isTargetingValidContainer || isTargetingValidCharacter))
|
||||||
|
{
|
||||||
|
bool anySuccess = false;
|
||||||
|
foreach (Item it in DraggingItems)
|
||||||
|
{
|
||||||
|
bool success = false;
|
||||||
|
if (isTargetingValidContainer)
|
||||||
{
|
{
|
||||||
if (editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition))
|
success = Character.Controlled.FocusedItem.OwnInventory.TryPutItem(it, Character.Controlled);
|
||||||
|
}
|
||||||
|
if (!success && isTargetingValidCharacter)
|
||||||
|
{
|
||||||
|
success = Character.Controlled.FocusedCharacter.Inventory.TryPutItem(it, Character.Controlled, CharacterInventory.AnySlot);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!success) { break; }
|
||||||
|
anySuccess = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anySuccess) { SoundPlayer.PlayUISound(GUISoundType.PickItem); }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (Screen.Selected is SubEditorScreen)
|
||||||
|
{
|
||||||
|
if (DraggingItems.First()?.ParentInventory != null)
|
||||||
|
{
|
||||||
|
SubEditorScreen.StoreCommand(new InventoryPlaceCommand(DraggingItems.First().ParentInventory, new List<Item>(DraggingItems), true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SoundPlayer.PlayUISound(GUISoundType.DropItem);
|
||||||
|
bool removed = false;
|
||||||
|
if (Screen.Selected is SubEditorScreen editor)
|
||||||
|
{
|
||||||
|
if (editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition))
|
||||||
|
{
|
||||||
|
DraggingItems.ForEachMod(it => it.Remove());
|
||||||
|
removed = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (editor.WiringMode)
|
||||||
{
|
{
|
||||||
DraggingItems.ForEachMod(it => it.Remove());
|
DraggingItems.ForEachMod(it => it.Remove());
|
||||||
removed = true;
|
removed = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (editor.WiringMode)
|
DraggingItems.ForEachMod(it => it.Drop(Character.Controlled));
|
||||||
{
|
|
||||||
DraggingItems.ForEachMod(it => it.Remove());
|
|
||||||
removed = true;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
DraggingItems.ForEachMod(it => it.Drop(Character.Controlled));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
DraggingItems.ForEachMod(it => it.Drop(Character.Controlled));
|
|
||||||
DraggingItems.First().CreateDroppedStack(DraggingItems, allowClientExecute: false);
|
|
||||||
}
|
|
||||||
SoundPlayer.PlayUISound(removed ? GUISoundType.PickItem : GUISoundType.DropItem);
|
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
else if (!DraggingItems.Any(it => selectedSlot.ParentInventory.slots[selectedSlot.SlotIndex].Contains(it)))
|
|
||||||
{
|
|
||||||
Inventory oldInventory = DraggingItems.First().ParentInventory;
|
|
||||||
Inventory selectedInventory = selectedSlot.ParentInventory;
|
|
||||||
int slotIndex = selectedSlot.SlotIndex;
|
|
||||||
int oldSlot = oldInventory == null ? 0 : Array.IndexOf(oldInventory.slots, DraggingItems);
|
|
||||||
|
|
||||||
//if attempting to drop into an invalid slot in the same inventory, try to move to the correct slot
|
|
||||||
if (selectedInventory.slots[slotIndex].Empty() &&
|
|
||||||
selectedInventory == Character.Controlled.Inventory &&
|
|
||||||
!DraggingItems.First().AllowedSlots.Any(a => a.HasFlag(Character.Controlled.Inventory.SlotTypes[slotIndex])) &&
|
|
||||||
DraggingItems.Any(it => selectedInventory.TryPutItem(it, Character.Controlled, it.AllowedSlots)))
|
|
||||||
{
|
{
|
||||||
if (selectedInventory.visualSlots != null)
|
DraggingItems.ForEachMod(it => it.Drop(Character.Controlled));
|
||||||
|
DraggingItems.First().CreateDroppedStack(DraggingItems, allowClientExecute: false);
|
||||||
|
}
|
||||||
|
SoundPlayer.PlayUISound(removed ? GUISoundType.PickItem : GUISoundType.DropItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleInventorySlotDrop()
|
||||||
|
{
|
||||||
|
Inventory oldInventory = DraggingItems.First().ParentInventory;
|
||||||
|
Inventory selectedInventory = selectedSlot.ParentInventory;
|
||||||
|
int slotIndex = selectedSlot.SlotIndex;
|
||||||
|
int oldSlot = oldInventory == null ? 0 : Array.IndexOf(oldInventory.slots, DraggingItems);
|
||||||
|
|
||||||
|
//if attempting to drop into an invalid slot in the same inventory, try to move to the correct slot
|
||||||
|
if (selectedInventory.slots[slotIndex].Empty() &&
|
||||||
|
selectedInventory == Character.Controlled.Inventory &&
|
||||||
|
!DraggingItems.First().AllowedSlots.Any(a => a.HasFlag(Character.Controlled.Inventory.SlotTypes[slotIndex])) &&
|
||||||
|
DraggingItems.Any(it => selectedInventory.TryPutItem(it, Character.Controlled, it.AllowedSlots)))
|
||||||
|
{
|
||||||
|
if (selectedInventory.visualSlots != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < selectedInventory.visualSlots.Length; i++)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < selectedInventory.visualSlots.Length; i++)
|
if (DraggingItems.Any(it => selectedInventory.slots[i].Contains(it)))
|
||||||
{
|
{
|
||||||
if (DraggingItems.Any(it => selectedInventory.slots[i].Contains(it)))
|
selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(Color.White, 0.1f, 0.4f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
|
||||||
|
}
|
||||||
|
SoundPlayer.PlayUISound(GUISoundType.PickItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
bool anySuccess = false;
|
||||||
|
//if we're dragging a stack of partial items or trying to drag to a stack of partial items
|
||||||
|
//(which should not normally exist, but can happen when e.g. fire damages a stack of items)
|
||||||
|
//don't allow combining because it leads to weird behavior (stack of items of mixed quality)
|
||||||
|
bool allowCombine = !(DraggingItems.Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1 ||
|
||||||
|
selectedInventory.GetItemsAt(slotIndex).Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1);
|
||||||
|
int itemCount = 0;
|
||||||
|
foreach (Item item in DraggingItems)
|
||||||
|
{
|
||||||
|
if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container &&
|
||||||
|
container.Inventory.CanBePut(item))
|
||||||
|
{
|
||||||
|
if (!container.AllowDragAndDrop || !container.AllowAccess)
|
||||||
|
{
|
||||||
|
allowCombine = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
anySuccess = true;
|
||||||
|
itemCount++;
|
||||||
|
}
|
||||||
|
if (!success || itemCount >= item.Prefab.GetMaxStackSize(selectedInventory))
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (anySuccess)
|
||||||
|
{
|
||||||
|
highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory == oldInventory || s.ParentInventory == selectedInventory);
|
||||||
|
if (SubEditorScreen.IsSubEditor())
|
||||||
|
{
|
||||||
|
foreach (Item draggingItem in DraggingItems)
|
||||||
|
{
|
||||||
|
if (selectedInventory.slots[slotIndex].Contains(draggingItem))
|
||||||
{
|
{
|
||||||
selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(Color.White, 0.1f, 0.4f);
|
SubEditorScreen.StoreCommand(new InventoryMoveCommand(oldInventory, selectedInventory, draggingItem, oldSlot, slotIndex));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
|
|
||||||
}
|
}
|
||||||
|
if (selectedInventory.visualSlots != null) { selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(Color.White, 0.1f, 0.4f); }
|
||||||
SoundPlayer.PlayUISound(GUISoundType.PickItem);
|
SoundPlayer.PlayUISound(GUISoundType.PickItem);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool anySuccess = false;
|
if (selectedInventory.visualSlots != null){ selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); }
|
||||||
bool allowCombine = true;
|
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
|
||||||
//if we're dragging a stack of partial items or trying to drag to a stack of partial items
|
|
||||||
//(which should not normally exist, but can happen when e.g. fire damages a stack of items)
|
|
||||||
//don't allow combining because it leads to weird behavior (stack of items of mixed quality)
|
|
||||||
if (DraggingItems.Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1 ||
|
|
||||||
selectedInventory.GetItemsAt(slotIndex).Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1)
|
|
||||||
{
|
|
||||||
allowCombine = false;
|
|
||||||
}
|
|
||||||
int itemCount = 0;
|
|
||||||
foreach (Item item in DraggingItems)
|
|
||||||
{
|
|
||||||
if (selectedInventory.GetItemAt(slotIndex)?.OwnInventory?.Container is { } container &&
|
|
||||||
container.Inventory.CanBePut(item))
|
|
||||||
{
|
|
||||||
if (!container.AllowDragAndDrop || !container.AllowAccess)
|
|
||||||
{
|
|
||||||
allowCombine = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
|
|
||||||
if (success)
|
|
||||||
{
|
|
||||||
anySuccess = true;
|
|
||||||
itemCount++;
|
|
||||||
}
|
|
||||||
if (!success || itemCount >= item.Prefab.GetMaxStackSize(selectedInventory))
|
|
||||||
{
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (anySuccess)
|
|
||||||
{
|
|
||||||
highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory == oldInventory || s.ParentInventory == selectedInventory);
|
|
||||||
if (SubEditorScreen.IsSubEditor())
|
|
||||||
{
|
|
||||||
foreach (Item draggingItem in DraggingItems)
|
|
||||||
{
|
|
||||||
if (selectedInventory.slots[slotIndex].Contains(draggingItem))
|
|
||||||
{
|
|
||||||
SubEditorScreen.StoreCommand(new InventoryMoveCommand(oldInventory, selectedInventory, draggingItem, oldSlot, slotIndex));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (selectedInventory.visualSlots != null) { selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(Color.White, 0.1f, 0.4f); }
|
|
||||||
SoundPlayer.PlayUISound(GUISoundType.PickItem);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (selectedInventory.visualSlots != null){ selectedInventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); }
|
|
||||||
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedInventory.HideTimer = 2.0f;
|
|
||||||
if (selectedSlot.ParentInventory?.Owner is Item parentItem && parentItem.ParentInventory != null)
|
|
||||||
{
|
|
||||||
for (int i = 0; i < parentItem.ParentInventory.capacity; i++)
|
|
||||||
{
|
|
||||||
if (parentItem.ParentInventory.HideSlot(i)) { continue; }
|
|
||||||
if (parentItem.ParentInventory.slots[i].FirstOrDefault() != parentItem) { continue; }
|
|
||||||
|
|
||||||
highlightedSubInventorySlots.Add(new SlotReference(
|
|
||||||
parentItem.ParentInventory, parentItem.ParentInventory.visualSlots[i],
|
|
||||||
i, false, selectedSlot.ParentInventory));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
DraggingItems.Clear();
|
|
||||||
DraggingSlot = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DraggingItems.Clear();
|
selectedInventory.HideTimer = 2.0f;
|
||||||
}
|
if (selectedSlot.ParentInventory?.Owner is Item parentItem && parentItem.ParentInventory != null)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < parentItem.ParentInventory.capacity; i++)
|
||||||
|
{
|
||||||
|
if (parentItem.ParentInventory.HideSlot(i)) { continue; }
|
||||||
|
if (parentItem.ParentInventory.slots[i].FirstOrDefault() != parentItem) { continue; }
|
||||||
|
|
||||||
if (selectedSlot != null && !CanSelectSlot(selectedSlot))
|
highlightedSubInventorySlots.Add(new SlotReference(
|
||||||
{
|
parentItem.ParentInventory, parentItem.ParentInventory.visualSlots[i],
|
||||||
selectedSlot = null;
|
i, false, selectedSlot.ParentInventory));
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DraggingItems.Clear();
|
||||||
|
DraggingSlot = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsValidTargetForDragDropGive(Character giver, Character receiver)
|
||||||
|
{
|
||||||
|
if (giver == null || receiver == null) { return false; }
|
||||||
|
if (receiver == giver) { return false; }
|
||||||
|
return receiver.IsInventoryAccessibleTo(giver, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool CanSelectSlot(SlotReference selectedSlot)
|
private static bool CanSelectSlot(SlotReference selectedSlot)
|
||||||
@@ -1504,6 +1542,31 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (DraggingItems.Any())
|
if (DraggingItems.Any())
|
||||||
|
{
|
||||||
|
DrawDragRelated();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedSlot != null && selectedSlot.Item != null)
|
||||||
|
{
|
||||||
|
Rectangle slotRect = selectedSlot.Slot.Rect;
|
||||||
|
slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint();
|
||||||
|
if (selectedSlot.TooltipNeedsRefresh())
|
||||||
|
{
|
||||||
|
selectedSlot.RefreshTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!slotIconTooltip.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
DrawToolTip(spriteBatch, slotIconTooltip, slotRect);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect);
|
||||||
|
}
|
||||||
|
slotIconTooltip = string.Empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DrawDragRelated()
|
||||||
{
|
{
|
||||||
if (DraggingSlot == null || (!DraggingSlot.MouseOn()))
|
if (DraggingSlot == null || (!DraggingSlot.MouseOn()))
|
||||||
{
|
{
|
||||||
@@ -1521,10 +1584,8 @@ namespace Barotrauma
|
|||||||
if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null)
|
if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null)
|
||||||
{
|
{
|
||||||
var shadowSprite = GUIStyle.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0];
|
var shadowSprite = GUIStyle.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0];
|
||||||
LocalizedString toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") :
|
|
||||||
Character.Controlled.FocusedItem != null ?
|
(LocalizedString toolTip, Color toolTipColor) = GetDragLabelTextAndColor(mouseOnHealthInterface);
|
||||||
TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, FormatCapitals.Yes) :
|
|
||||||
TextManager.Get(Screen.Selected is SubEditorScreen editor && editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition) ? "Delete" : "DropItem");
|
|
||||||
|
|
||||||
Vector2 nameSize = GUIStyle.Font.MeasureString(DraggingItems.First().Name);
|
Vector2 nameSize = GUIStyle.Font.MeasureString(DraggingItems.First().Name);
|
||||||
Vector2 toolTipSize = GUIStyle.SmallFont.MeasureString(toolTip);
|
Vector2 toolTipSize = GUIStyle.SmallFont.MeasureString(toolTip);
|
||||||
@@ -1544,7 +1605,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
GUI.DrawString(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), DraggingItems.First().Name, Color.White);
|
GUI.DrawString(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), DraggingItems.First().Name, Color.White);
|
||||||
GUI.DrawString(spriteBatch, textPos + new Vector2(toolTipSize.X * textOffset, 0), toolTip,
|
GUI.DrawString(spriteBatch, textPos + new Vector2(toolTipSize.X * textOffset, 0), toolTip,
|
||||||
color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? GUIStyle.Red : Color.LightGreen,
|
color: toolTipColor,
|
||||||
font: GUIStyle.SmallFont);
|
font: GUIStyle.SmallFont);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1587,24 +1648,31 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedSlot != null && selectedSlot.Item != null)
|
(LocalizedString, Color) GetDragLabelTextAndColor(bool mouseOnHealthInterface)
|
||||||
{
|
{
|
||||||
Rectangle slotRect = selectedSlot.Slot.Rect;
|
bool useDragDropGive = IsValidTargetForDragDropGive(Character.Controlled, Character.Controlled.FocusedCharacter);
|
||||||
slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint();
|
|
||||||
if (selectedSlot.TooltipNeedsRefresh())
|
Color toolTipColor = Color.LightGreen;
|
||||||
|
|
||||||
|
LocalizedString toolTip;
|
||||||
|
if (mouseOnHealthInterface)
|
||||||
{
|
{
|
||||||
selectedSlot.RefreshTooltip();
|
toolTip = TextManager.Get("QuickUseAction.UseTreatment");
|
||||||
}
|
}
|
||||||
|
else if (Character.Controlled.FocusedItem != null)
|
||||||
if (!slotIconTooltip.IsNullOrEmpty())
|
|
||||||
{
|
{
|
||||||
DrawToolTip(spriteBatch, slotIconTooltip, slotRect);
|
toolTip = TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, FormatCapitals.Yes);
|
||||||
|
}
|
||||||
|
else if (useDragDropGive)
|
||||||
|
{
|
||||||
|
toolTip = TextManager.GetWithVariable("GiveItemTo", "[character]", Character.Controlled.FocusedCharacter.Name, FormatCapitals.Yes);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect);
|
toolTipColor = GUIStyle.Red;
|
||||||
|
toolTip = TextManager.Get(Screen.Selected is SubEditorScreen editor && editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition) ? "Delete" : "DropItem");
|
||||||
}
|
}
|
||||||
slotIconTooltip = string.Empty;
|
return (toolTip, toolTipColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1801,7 +1869,7 @@ namespace Barotrauma
|
|||||||
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
|
DrawSideIcon(deconstructOrder.SymbolSprite, Direction.Right, TextManager.Get("tooltip.markedfordeconstruction"), GUIStyle.Red, out bool mouseOn);
|
||||||
if (mouseOn) { availableContextualOrder = (item, Tags.DontDeconstructThis); }
|
if (mouseOn) { availableContextualOrder = (item, Tags.DontDeconstructThis); }
|
||||||
}
|
}
|
||||||
else if (((item.SpawnedInCurrentOutpost && !item.AllowStealing) || (inventory != null && inventory.slots[slotIndex].Items.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
|
else if ((item.Illegitimate || (inventory != null && inventory.slots[slotIndex].Items.Any(it => it.Illegitimate))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
|
||||||
{
|
{
|
||||||
DrawSideIcon(CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand], Direction.Left, TextManager.Get("tooltip.stolenitem"), GUIStyle.Red, out _);
|
DrawSideIcon(CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand], Direction.Left, TextManager.Get("tooltip.stolenitem"), GUIStyle.Red, out _);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Color? overrideColor = null)
|
public void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Color? overrideColor = null)
|
||||||
{
|
{
|
||||||
if (!Visible || (!editing && HiddenInGame) || !SubEditorScreen.IsLayerVisible(this)) { return; }
|
if (!Visible || (!editing && IsHidden) || !SubEditorScreen.IsLayerVisible(this)) { return; }
|
||||||
|
|
||||||
if (editing)
|
if (editing)
|
||||||
{
|
{
|
||||||
@@ -424,7 +424,7 @@ namespace Barotrauma
|
|||||||
textureScale: Vector2.One * Scale,
|
textureScale: Vector2.One * Scale,
|
||||||
depth: d);
|
depth: d);
|
||||||
}
|
}
|
||||||
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, rotation: 0, depth);
|
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, rotation: 0, depth, overrideColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -445,7 +445,7 @@ namespace Barotrauma
|
|||||||
Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.002f);
|
Prefab.DamagedInfectedSprite?.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, Infector.HealthColor, Prefab.DamagedInfectedSprite.Origin, RotationRad, Scale, activeSprite.effects, depth - 0.002f);
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, -RotationRad, depth);
|
DrawDecorativeSprites(spriteBatch, DrawPosition, flippedX && Prefab.CanSpriteFlipX, flippedY && Prefab.CanSpriteFlipY, -RotationRad, depth, overrideColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (body.Enabled)
|
else if (body.Enabled)
|
||||||
@@ -456,30 +456,50 @@ namespace Barotrauma
|
|||||||
//don't draw the item on hands if it's also being worn
|
//don't draw the item on hands if it's also being worn
|
||||||
if (GetComponent<Wearable>() is { IsActive: true }) { return; }
|
if (GetComponent<Wearable>() is { IsActive: true }) { return; }
|
||||||
if (!back) { return; }
|
if (!back) { return; }
|
||||||
float depthStep = 0.000001f;
|
|
||||||
if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == this)
|
if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.RightHand) == this)
|
||||||
{
|
{
|
||||||
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightArm);
|
depth = GetHeldItemDepth(LimbType.RightHand, holdable, depth);
|
||||||
if (holdLimb?.ActiveSprite != null)
|
|
||||||
{
|
|
||||||
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() + depthStep * 2;
|
|
||||||
foreach (WearableSprite wearableSprite in holdLimb.WearingItems)
|
|
||||||
{
|
|
||||||
if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) { depth = Math.Max(wearableSprite.Sprite.Depth + depthStep, depth); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == this)
|
else if (holdable.Picker.Inventory?.GetItemInLimbSlot(InvSlotType.LeftHand) == this)
|
||||||
{
|
{
|
||||||
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftArm);
|
depth = GetHeldItemDepth(LimbType.LeftHand, holdable, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float GetHeldItemDepth(LimbType limb, Holdable holdable, float depth)
|
||||||
|
{
|
||||||
|
if (holdable?.Picker?.AnimController == null) { return depth; }
|
||||||
|
//offset used to make sure the item draws just slightly behind the right hand, or slightly in front of the left hand
|
||||||
|
float limbDepthOffset = 0.000001f;
|
||||||
|
float depthOffset = holdable.Picker.AnimController.GetDepthOffset();
|
||||||
|
//use the upper arm as a reference, to ensure the item gets drawn behind / in front of the whole arm (not just the forearm)
|
||||||
|
Limb holdLimb = holdable.Picker.AnimController.GetLimb(limb == LimbType.RightHand ? LimbType.RightArm : LimbType.LeftArm);
|
||||||
if (holdLimb?.ActiveSprite != null)
|
if (holdLimb?.ActiveSprite != null)
|
||||||
{
|
{
|
||||||
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() - depthStep * 2;
|
depth =
|
||||||
|
holdLimb.ActiveSprite.Depth
|
||||||
|
+ depthOffset
|
||||||
|
+ limbDepthOffset * 2 * (limb == LimbType.RightHand ? 1 : -1);
|
||||||
foreach (WearableSprite wearableSprite in holdLimb.WearingItems)
|
foreach (WearableSprite wearableSprite in holdLimb.WearingItems)
|
||||||
{
|
{
|
||||||
if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null) { depth = Math.Min(wearableSprite.Sprite.Depth - depthStep, depth); }
|
if (!wearableSprite.InheritLimbDepth && wearableSprite.Sprite != null)
|
||||||
|
{
|
||||||
|
depth =
|
||||||
|
limb == LimbType.RightHand ?
|
||||||
|
Math.Max(wearableSprite.Sprite.Depth + limbDepthOffset, depth) :
|
||||||
|
Math.Min(wearableSprite.Sprite.Depth - limbDepthOffset, depth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var head = holdable.Picker.AnimController.GetLimb(LimbType.Head);
|
||||||
|
if (head != null)
|
||||||
|
{
|
||||||
|
//ensure the holdable item is always drawn in front of the head no matter what the wearables or whatnot do with the sprite depths
|
||||||
|
depth =
|
||||||
|
limb == LimbType.RightHand ?
|
||||||
|
Math.Min(head.Sprite.Depth + depthOffset - limbDepthOffset, depth) :
|
||||||
|
Math.Max(head.Sprite.Depth + depthOffset + limbDepthOffset, depth);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return depth;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vector2 origin = GetSpriteOrigin(activeSprite);
|
Vector2 origin = GetSpriteOrigin(activeSprite);
|
||||||
@@ -489,7 +509,7 @@ namespace Barotrauma
|
|||||||
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
|
float d = Math.Min(depth + (fadeInBrokenSprite.Sprite.Depth - activeSprite.Depth - 0.000001f), 0.999f);
|
||||||
body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale);
|
body.Draw(spriteBatch, fadeInBrokenSprite.Sprite, color * fadeInBrokenSpriteAlpha, d, Scale);
|
||||||
}
|
}
|
||||||
DrawDecorativeSprites(spriteBatch, body.DrawPosition, flipX: body.Dir < 0, flipY: false, rotation: body.Rotation, depth: depth);
|
DrawDecorativeSprites(spriteBatch, body.DrawPosition, flipX: body.Dir < 0, flipY: false, rotation: body.Rotation, depth, overrideColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var upgrade in Upgrades)
|
foreach (var upgrade in Upgrades)
|
||||||
@@ -617,11 +637,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawDecorativeSprites(SpriteBatch spriteBatch, Vector2 drawPos, bool flipX, bool flipY, float rotation, float depth)
|
public void DrawDecorativeSprites(SpriteBatch spriteBatch, Vector2 drawPos, bool flipX, bool flipY, float rotation, float depth, Color? overrideColor = null)
|
||||||
{
|
{
|
||||||
foreach (var decorativeSprite in Prefab.DecorativeSprites)
|
foreach (var decorativeSprite in Prefab.DecorativeSprites)
|
||||||
{
|
{
|
||||||
Color decorativeSpriteColor = GetSpriteColor(decorativeSprite.Color).Multiply(GetSpriteColor(spriteColor));
|
Color decorativeSpriteColor = overrideColor ?? GetSpriteColor(decorativeSprite.Color).Multiply(GetSpriteColor(spriteColor));
|
||||||
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
|
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
|
||||||
|
|
||||||
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier,
|
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier,
|
||||||
|
|||||||
@@ -215,7 +215,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (Math.Sign(flowTargetHull.Rect.Y - rect.Y) != Math.Sign(lerpedFlowForce.Y)) { return; }
|
//do not emit particles unless water is flowing towards the target hull
|
||||||
|
//(using lerpedFlowForce smooths out "flickers" when the direction of flow is rapidly changing)
|
||||||
|
if (Math.Sign(flowTargetHull.WorldPosition.Y - WorldPosition.Y) != Math.Sign(lerpedFlowForce.Y)) { return; }
|
||||||
|
|
||||||
float particlesPerSec = Math.Max(open * rect.Width * particleAmountMultiplier, 10.0f);
|
float particlesPerSec = Math.Max(open * rect.Width * particleAmountMultiplier, 10.0f);
|
||||||
float emitInterval = 1.0f / particlesPerSec;
|
float emitInterval = 1.0f / particlesPerSec;
|
||||||
|
|||||||
@@ -210,7 +210,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
bool primaryMouseButtonHeld = PlayerInput.PrimaryMouseButtonHeld();
|
bool primaryMouseButtonHeld = PlayerInput.PrimaryMouseButtonHeld();
|
||||||
bool secondaryMouseButtonHeld = PlayerInput.SecondaryMouseButtonHeld();
|
bool secondaryMouseButtonHeld = PlayerInput.SecondaryMouseButtonHeld();
|
||||||
if (!primaryMouseButtonHeld && !secondaryMouseButtonHeld) { return; }
|
bool doubleClicked = PlayerInput.DoubleClicked();
|
||||||
|
bool secondaryDoubleClicked = PlayerInput.SecondaryDoubleClicked();
|
||||||
|
if (!primaryMouseButtonHeld && !secondaryMouseButtonHeld && !doubleClicked && !secondaryDoubleClicked) { return; }
|
||||||
|
|
||||||
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||||
Hull hull = FindHull(position);
|
Hull hull = FindHull(position);
|
||||||
@@ -218,29 +220,67 @@ namespace Barotrauma
|
|||||||
if (hull == null || hull.IdFreed) { return; }
|
if (hull == null || hull.IdFreed) { return; }
|
||||||
if (EditWater)
|
if (EditWater)
|
||||||
{
|
{
|
||||||
|
const float waterIncrement = 100000.0f;
|
||||||
if (primaryMouseButtonHeld)
|
if (primaryMouseButtonHeld)
|
||||||
{
|
{
|
||||||
ShowHulls = true;
|
SetWaterVolume(hull.WaterVolume + waterIncrement * deltaTime);
|
||||||
hull.WaterVolume += 100000.0f * deltaTime;
|
|
||||||
hull.networkUpdatePending = true;
|
|
||||||
hull.serverUpdateDelay = 0.5f;
|
|
||||||
}
|
}
|
||||||
else if (secondaryMouseButtonHeld)
|
else if (secondaryMouseButtonHeld)
|
||||||
{
|
{
|
||||||
hull.WaterVolume -= 100000.0f * deltaTime;
|
SetWaterVolume(hull.WaterVolume - waterIncrement * deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (doubleClicked)
|
||||||
|
{
|
||||||
|
SetWaterVolume(hull.Volume * MaxCompress);
|
||||||
|
}
|
||||||
|
else if (secondaryDoubleClicked)
|
||||||
|
{
|
||||||
|
SetWaterVolume(0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetWaterVolume(float newVolume)
|
||||||
|
{
|
||||||
|
ShowHulls = true;
|
||||||
|
hull.WaterVolume = newVolume;
|
||||||
hull.networkUpdatePending = true;
|
hull.networkUpdatePending = true;
|
||||||
hull.serverUpdateDelay = 0.5f;
|
hull.serverUpdateDelay = 0.5f;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
else if (EditFire)
|
else if (EditFire)
|
||||||
{
|
{
|
||||||
|
bool networkUpdate = false;
|
||||||
|
|
||||||
if (primaryMouseButtonHeld)
|
if (primaryMouseButtonHeld)
|
||||||
{
|
{
|
||||||
new FireSource(position, hull, isNetworkMessage: true);
|
new FireSource(position, hull, isNetworkMessage: true);
|
||||||
|
networkUpdate = true;
|
||||||
|
}
|
||||||
|
else if (secondaryMouseButtonHeld || secondaryDoubleClicked)
|
||||||
|
{
|
||||||
|
for (int index = hull.FireSources.Count - 1; index >= 0; index--)
|
||||||
|
{
|
||||||
|
var currentFireSource = hull.FireSources[index];
|
||||||
|
|
||||||
|
if (secondaryMouseButtonHeld)
|
||||||
|
{
|
||||||
|
const float extinguishAmount = 120f;
|
||||||
|
currentFireSource.Extinguish(deltaTime, extinguishAmount);
|
||||||
|
networkUpdate = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
currentFireSource.Remove();
|
||||||
|
networkUpdate = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (networkUpdate)
|
||||||
|
{
|
||||||
hull.networkUpdatePending = true;
|
hull.networkUpdatePending = true;
|
||||||
hull.serverUpdateDelay = 0.5f;
|
hull.serverUpdateDelay = 0.5f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -70,7 +70,7 @@ namespace Barotrauma.Lights
|
|||||||
|
|
||||||
public bool LightingEnabled = true;
|
public bool LightingEnabled = true;
|
||||||
|
|
||||||
public bool ObstructVision;
|
public float ObstructVisionAmount;
|
||||||
|
|
||||||
private readonly Texture2D visionCircle;
|
private readonly Texture2D visionCircle;
|
||||||
|
|
||||||
@@ -498,7 +498,7 @@ namespace Barotrauma.Lights
|
|||||||
{
|
{
|
||||||
foreach (MapEntity e in (Submarine.VisibleEntities ?? MapEntity.MapEntityList))
|
foreach (MapEntity e in (Submarine.VisibleEntities ?? MapEntity.MapEntityList))
|
||||||
{
|
{
|
||||||
if (e is Item item && !item.HiddenInGame && item.GetComponent<Wire>() is Wire wire)
|
if (e is Item item && !item.IsHidden && item.GetComponent<Wire>() is Wire wire)
|
||||||
{
|
{
|
||||||
wire.DebugDraw(spriteBatch, alpha: 0.4f);
|
wire.DebugDraw(spriteBatch, alpha: 0.4f);
|
||||||
}
|
}
|
||||||
@@ -664,7 +664,7 @@ namespace Barotrauma.Lights
|
|||||||
visibleHulls.Clear();
|
visibleHulls.Clear();
|
||||||
foreach (Hull hull in Hull.HullList)
|
foreach (Hull hull in Hull.HullList)
|
||||||
{
|
{
|
||||||
if (hull.HiddenInGame) { continue; }
|
if (hull.IsHidden) { continue; }
|
||||||
var drawRect =
|
var drawRect =
|
||||||
hull.Submarine == null ?
|
hull.Submarine == null ?
|
||||||
hull.Rect :
|
hull.Rect :
|
||||||
@@ -682,12 +682,12 @@ namespace Barotrauma.Lights
|
|||||||
|
|
||||||
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
|
public void UpdateObstructVision(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam, Vector2 lookAtPosition)
|
||||||
{
|
{
|
||||||
if ((!LosEnabled || LosMode == LosMode.None) && !ObstructVision) { return; }
|
if ((!LosEnabled || LosMode == LosMode.None) && ObstructVisionAmount <= 0.0f) { return; }
|
||||||
if (ViewTarget == null) return;
|
if (ViewTarget == null) return;
|
||||||
|
|
||||||
graphics.SetRenderTarget(LosTexture);
|
graphics.SetRenderTarget(LosTexture);
|
||||||
|
|
||||||
if (ObstructVision)
|
if (ObstructVisionAmount > 0.0f)
|
||||||
{
|
{
|
||||||
graphics.Clear(Color.Black);
|
graphics.Clear(Color.Black);
|
||||||
Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
|
Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
|
||||||
@@ -697,13 +697,14 @@ namespace Barotrauma.Lights
|
|||||||
|
|
||||||
//the visible area stretches to the maximum when the cursor is this far from the character
|
//the visible area stretches to the maximum when the cursor is this far from the character
|
||||||
const float MaxOffset = 256.0f;
|
const float MaxOffset = 256.0f;
|
||||||
const float MinHorizontalScale = 2.2f;
|
//the magic numbers here are just based on experimentation
|
||||||
const float MaxHorizontalScale = 2.8f;
|
float MinHorizontalScale = MathHelper.Lerp(3.5f, 1.5f, ObstructVisionAmount);
|
||||||
const float VerticalScale = 2.5f;
|
float MaxHorizontalScale = MinHorizontalScale * 1.25f;
|
||||||
|
float VerticalScale = MathHelper.Lerp(4.0f, 1.25f, ObstructVisionAmount);
|
||||||
|
|
||||||
//Starting point and scale-based modifier that moves the point of origin closer to the edge of the texture if the player moves their mouse further away, or vice versa.
|
//Starting point and scale-based modifier that moves the point of origin closer to the edge of the texture if the player moves their mouse further away, or vice versa.
|
||||||
float relativeOriginStartPosition = 0.22f; //Increasing this value moves the origin further behind the character
|
float relativeOriginStartPosition = 0.1f; //Increasing this value moves the origin further behind the character
|
||||||
float originStartPosition = visionCircle.Width * relativeOriginStartPosition;
|
float originStartPosition = visionCircle.Width * relativeOriginStartPosition * MinHorizontalScale;
|
||||||
float relativeOriginLookAtPosModifier = -0.055f; //Increase this value increases how much the vision changes by moving the mouse
|
float relativeOriginLookAtPosModifier = -0.055f; //Increase this value increases how much the vision changes by moving the mouse
|
||||||
float originLookAtPosModifier = visionCircle.Width * relativeOriginLookAtPosModifier;
|
float originLookAtPosModifier = visionCircle.Width * relativeOriginLookAtPosModifier;
|
||||||
|
|
||||||
|
|||||||
@@ -1115,13 +1115,23 @@ namespace Barotrauma
|
|||||||
|
|
||||||
float subCrushDepth = SubmarineInfo.GetSubCrushDepth(SubmarineSelection.CurrentOrPendingSubmarine(), ref pendingSubInfo);
|
float subCrushDepth = SubmarineInfo.GetSubCrushDepth(SubmarineSelection.CurrentOrPendingSubmarine(), ref pendingSubInfo);
|
||||||
string crushDepthWarningIconStyle = null;
|
string crushDepthWarningIconStyle = null;
|
||||||
if (connection.LevelData.InitialDepth * Physics.DisplayToRealWorldRatio > subCrushDepth)
|
|
||||||
|
var levelData = connection.LevelData;
|
||||||
|
float spawnDepth =
|
||||||
|
levelData.InitialDepth +
|
||||||
|
//base the warning on the start or end position of the level, whichever is deeper
|
||||||
|
levelData.Size.Y * Math.Max(levelData.GenerationParams.StartPosition.Y, levelData.GenerationParams.EndPosition.Y);
|
||||||
|
|
||||||
|
//"high warning" if the sub spawns at/below crush depth
|
||||||
|
if (spawnDepth * Physics.DisplayToRealWorldRatio > subCrushDepth)
|
||||||
{
|
{
|
||||||
iconCount++;
|
iconCount++;
|
||||||
crushDepthWarningIconStyle = "CrushDepthWarningHighIcon";
|
crushDepthWarningIconStyle = "CrushDepthWarningHighIcon";
|
||||||
tooltip = "crushdepthwarninghigh";
|
tooltip = "crushdepthwarninghigh";
|
||||||
}
|
}
|
||||||
else if ((connection.LevelData.InitialDepth + connection.LevelData.Size.Y) * Physics.DisplayToRealWorldRatio > subCrushDepth)
|
//"low warning" if the spawn position is less than the level's height away from crush depth
|
||||||
|
//(i.e. the crush depth is pretty close to the spawn pos, possibly inside the level or at least close enough that many parts of the abyss are unreachable)
|
||||||
|
else if ((spawnDepth + connection.LevelData.Size.Y) * Physics.DisplayToRealWorldRatio > subCrushDepth)
|
||||||
{
|
{
|
||||||
iconCount++;
|
iconCount++;
|
||||||
crushDepthWarningIconStyle = "CrushDepthWarningLowIcon";
|
crushDepthWarningIconStyle = "CrushDepthWarningLowIcon";
|
||||||
|
|||||||
@@ -382,7 +382,10 @@ namespace Barotrauma
|
|||||||
if (!HasBody && !ShowStructures) { return; }
|
if (!HasBody && !ShowStructures) { return; }
|
||||||
if (HasBody && !ShowWalls) { return; }
|
if (HasBody && !ShowWalls) { return; }
|
||||||
}
|
}
|
||||||
else if (HiddenInGame) { return; }
|
else if (IsHidden)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : IsHighlighted ? GUIStyle.Orange * Math.Max(spriteColor.A / (float) byte.MaxValue, 0.1f) : spriteColor;
|
Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : IsHighlighted ? GUIStyle.Orange * Math.Max(spriteColor.A / (float) byte.MaxValue, 0.1f) : spriteColor;
|
||||||
|
|
||||||
|
|||||||
@@ -20,13 +20,13 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool SelectableInEditor
|
public override bool SelectableInEditor
|
||||||
{
|
{
|
||||||
get { return !IsHidden(); }
|
get { return ShouldDrawIcon(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
|
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
|
||||||
{
|
{
|
||||||
if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; }
|
if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; }
|
||||||
if (IsHidden()) { return; }
|
if (!ShouldDrawIcon()) { return; }
|
||||||
|
|
||||||
Vector2 drawPos = Position;
|
Vector2 drawPos = Position;
|
||||||
if (Submarine != null) { drawPos += Submarine.DrawPosition; }
|
if (Submarine != null) { drawPos += Submarine.DrawPosition; }
|
||||||
@@ -59,8 +59,10 @@ namespace Barotrauma
|
|||||||
Color.White);
|
Color.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
Sprite sprite = iconSprites[SpawnType.ToString()];
|
|
||||||
Sprite sprite2 = null;
|
Sprite sprite2 = null;
|
||||||
|
//there are no sprites for all possible combinations of SpawnType flags, but in the vanilla game the only possible combination is
|
||||||
|
//SpawnType.Disabled + some other flag, in which case it's fine to just not show the icon.
|
||||||
|
iconSprites.TryGetValue(SpawnType.ToString(), out Sprite sprite);
|
||||||
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
|
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
|
||||||
{
|
{
|
||||||
sprite = iconSprites["Path"];
|
sprite = iconSprites["Path"];
|
||||||
@@ -87,9 +89,12 @@ namespace Barotrauma
|
|||||||
sprite = iconSprites["Ladder"];
|
sprite = iconSprites["Ladder"];
|
||||||
}
|
}
|
||||||
|
|
||||||
float spriteScale = iconSize / (float)sprite.SourceRect.Width;
|
if (sprite != null)
|
||||||
sprite.Draw(spriteBatch, drawPos, clr, origin: sprite.size / 2, scale: spriteScale, depth: 0.001f);
|
{
|
||||||
sprite2?.Draw(spriteBatch, drawPos + sprite.size * spriteScale * 0.5f, clr, origin: sprite2.size / 2, scale: spriteScale, depth: 0.001f);
|
float spriteScale = iconSize / (float)sprite.SourceRect.Width;
|
||||||
|
sprite.Draw(spriteBatch, drawPos, clr, origin: sprite.size / 2, scale: spriteScale, depth: 0.001f);
|
||||||
|
sprite2?.Draw(spriteBatch, drawPos + sprite.size * spriteScale * 0.5f, clr, origin: sprite2.size / 2, scale: spriteScale, depth: 0.001f);
|
||||||
|
}
|
||||||
|
|
||||||
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
|
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
|
||||||
{
|
{
|
||||||
@@ -160,22 +165,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool IsMouseOn(Vector2 position)
|
public override bool IsMouseOn(Vector2 position)
|
||||||
{
|
{
|
||||||
if (IsHidden()) { return false; }
|
if (!ShouldDrawIcon()) { return false; }
|
||||||
float dist = Vector2.DistanceSquared(position, WorldPosition);
|
float dist = Vector2.DistanceSquared(position, WorldPosition);
|
||||||
float radius = (SpawnType == SpawnType.Path ? WaypointSize : SpawnPointSize) * 0.6f;
|
float radius = (SpawnType == SpawnType.Path ? WaypointSize : SpawnPointSize) * 0.6f;
|
||||||
return dist < radius * radius;
|
return dist < radius * radius;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsHidden()
|
private bool ShouldDrawIcon()
|
||||||
{
|
{
|
||||||
if (!SubEditorScreen.IsLayerVisible(this)) { return true; }
|
if (!SubEditorScreen.IsLayerVisible(this)) { return false; }
|
||||||
if (spawnType == SpawnType.Path)
|
if (spawnType == SpawnType.Path)
|
||||||
{
|
{
|
||||||
return (!GameMain.DebugDraw && !ShowWayPoints);
|
return GameMain.DebugDraw || ShowWayPoints;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return (!GameMain.DebugDraw && !ShowSpawnPoints);
|
return GameMain.DebugDraw || ShowSpawnPoints;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ namespace Barotrauma.Networking
|
|||||||
txt = msg.ReadString();
|
txt = msg.ReadString();
|
||||||
|
|
||||||
string senderName = msg.ReadString();
|
string senderName = msg.ReadString();
|
||||||
|
Entity sender = null;
|
||||||
Character senderCharacter = null;
|
Character senderCharacter = null;
|
||||||
Client senderClient = null;
|
Client senderClient = null;
|
||||||
bool hasSenderClient = msg.ReadBoolean();
|
bool hasSenderClient = msg.ReadBoolean();
|
||||||
@@ -40,13 +41,14 @@ namespace Barotrauma.Networking
|
|||||||
=> c.SessionOrAccountIdMatches(userId));
|
=> c.SessionOrAccountIdMatches(userId));
|
||||||
if (senderClient != null) { senderName = senderClient.Name; }
|
if (senderClient != null) { senderName = senderClient.Name; }
|
||||||
}
|
}
|
||||||
bool hasSenderCharacter = msg.ReadBoolean();
|
bool hasSender = msg.ReadBoolean();
|
||||||
if (hasSenderCharacter)
|
if (hasSender)
|
||||||
{
|
{
|
||||||
senderCharacter = Entity.FindEntityByID(msg.ReadUInt16()) as Character;
|
sender = Entity.FindEntityByID(msg.ReadUInt16());
|
||||||
if (senderCharacter != null)
|
senderCharacter = sender as Character;
|
||||||
|
if (sender is Character or Item)
|
||||||
{
|
{
|
||||||
senderName = senderCharacter.Name;
|
senderName = OrderChatMessage.NameFromEntityOrNull(sender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -180,7 +182,7 @@ namespace Barotrauma.Networking
|
|||||||
GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType);
|
GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, senderCharacter, changeType, textColor: textColor);
|
GameMain.Client.AddChatMessage(txt, type, senderName, senderClient, sender, changeType, textColor: textColor);
|
||||||
if (type == ChatMessageType.Radio && CanUseRadio(senderCharacter, out WifiComponent radio))
|
if (type == ChatMessageType.Radio && CanUseRadio(senderCharacter, out WifiComponent radio))
|
||||||
{
|
{
|
||||||
Signal s = new Signal(txt, sender: senderCharacter, source: radio.Item);
|
Signal s = new Signal(txt, sender: senderCharacter, source: radio.Item);
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public override bool IsClient => true;
|
public override bool IsClient => true;
|
||||||
public override bool IsServer => false;
|
public override bool IsServer => false;
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
public float DebugServerVoipAmplitude;
|
||||||
|
#endif
|
||||||
|
|
||||||
public override Voting Voting { get; }
|
public override Voting Voting { get; }
|
||||||
|
|
||||||
@@ -112,6 +116,8 @@ namespace Barotrauma.Networking
|
|||||||
//has the client been given a character to control this round
|
//has the client been given a character to control this round
|
||||||
public bool HasSpawned;
|
public bool HasSpawned;
|
||||||
|
|
||||||
|
public float EndRoundTimeRemaining { get; private set; }
|
||||||
|
|
||||||
public LocalizedString TraitorFirstObjective;
|
public LocalizedString TraitorFirstObjective;
|
||||||
public TraitorEventPrefab TraitorMission = null;
|
public TraitorEventPrefab TraitorMission = null;
|
||||||
|
|
||||||
@@ -198,20 +204,6 @@ namespace Barotrauma.Networking
|
|||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
|
|
||||||
cameraFollowsSub = new GUITickBox(new RectTransform(new Vector2(0.05f, 0.05f), inGameHUD.RectTransform, anchor: Anchor.TopCenter, pivot: Pivot.CenterLeft)
|
|
||||||
{
|
|
||||||
AbsoluteOffset = new Point(0, HUDLayoutSettings.ButtonAreaTop.Y + HUDLayoutSettings.ButtonAreaTop.Height / 2),
|
|
||||||
MaxSize = new Point(GUI.IntScale(25))
|
|
||||||
}, TextManager.Get("CamFollowSubmarine"))
|
|
||||||
{
|
|
||||||
Selected = Camera.FollowSub,
|
|
||||||
OnSelected = (tbox) =>
|
|
||||||
{
|
|
||||||
Camera.FollowSub = tbox.Selected;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
chatBox = new ChatBox(inGameHUD, isSinglePlayer: false);
|
chatBox = new ChatBox(inGameHUD, isSinglePlayer: false);
|
||||||
chatBox.OnEnterMessage += EnterChatMessage;
|
chatBox.OnEnterMessage += EnterChatMessage;
|
||||||
chatBox.InputBox.OnTextChanged += TypingChatMessage;
|
chatBox.InputBox.OnTextChanged += TypingChatMessage;
|
||||||
@@ -250,6 +242,19 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
ShowLogButton.TextBlock.AutoScaleHorizontal = true;
|
ShowLogButton.TextBlock.AutoScaleHorizontal = true;
|
||||||
|
|
||||||
|
cameraFollowsSub = new GUITickBox(new RectTransform(new Vector2(0.1f, 0.4f), buttonContainer.RectTransform)
|
||||||
|
{
|
||||||
|
MinSize = new Point(150, 0)
|
||||||
|
}, TextManager.Get("CamFollowSubmarine"))
|
||||||
|
{
|
||||||
|
Selected = Camera.FollowSub,
|
||||||
|
OnSelected = (tbox) =>
|
||||||
|
{
|
||||||
|
Camera.FollowSub = tbox.Selected;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
GameMain.DebugDraw = false;
|
GameMain.DebugDraw = false;
|
||||||
Hull.EditFire = false;
|
Hull.EditFire = false;
|
||||||
@@ -674,6 +679,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
VoipClient.Read(inc);
|
VoipClient.Read(inc);
|
||||||
break;
|
break;
|
||||||
|
#if DEBUG
|
||||||
|
case ServerPacketHeader.VOICE_AMPLITUDE_DEBUG:
|
||||||
|
GameMain.Client.DebugServerVoipAmplitude = inc.ReadRangedSingle(min: 0, max: 1, bitCount: 8);
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
case ServerPacketHeader.QUERY_STARTGAME:
|
case ServerPacketHeader.QUERY_STARTGAME:
|
||||||
DebugConsole.Log("Received QUERY_STARTGAME packet.");
|
DebugConsole.Log("Received QUERY_STARTGAME packet.");
|
||||||
string subName = inc.ReadString();
|
string subName = inc.ReadString();
|
||||||
@@ -1327,7 +1337,7 @@ namespace Barotrauma.Networking
|
|||||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||||
{
|
{
|
||||||
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
|
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
|
||||||
campaign.CampaignUI?.CrewManagement?.RefreshPermissions();
|
campaign.CampaignUI?.HRManagerUI?.RefreshUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1382,6 +1392,7 @@ namespace Barotrauma.Networking
|
|||||||
ServerSettings.AllowRewiring = inc.ReadBoolean();
|
ServerSettings.AllowRewiring = inc.ReadBoolean();
|
||||||
ServerSettings.AllowImmediateItemDelivery = inc.ReadBoolean();
|
ServerSettings.AllowImmediateItemDelivery = inc.ReadBoolean();
|
||||||
ServerSettings.AllowFriendlyFire = inc.ReadBoolean();
|
ServerSettings.AllowFriendlyFire = inc.ReadBoolean();
|
||||||
|
ServerSettings.AllowDragAndDropGive = inc.ReadBoolean();
|
||||||
ServerSettings.LockAllDefaultWires = inc.ReadBoolean();
|
ServerSettings.LockAllDefaultWires = inc.ReadBoolean();
|
||||||
ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
|
ServerSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
|
||||||
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
|
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
|
||||||
@@ -1541,7 +1552,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GameMain.GameSession.StartRound(levelData, mirrorLevel);
|
GameMain.GameSession.StartRound(levelData, mirrorLevel, startOutpost: campaign?.GetPredefinedStartOutpost());
|
||||||
}
|
}
|
||||||
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
|
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
|
||||||
}
|
}
|
||||||
@@ -1812,6 +1823,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
GameStarted = inc.ReadBoolean();
|
GameStarted = inc.ReadBoolean();
|
||||||
bool allowSpectating = inc.ReadBoolean();
|
bool allowSpectating = inc.ReadBoolean();
|
||||||
|
bool permadeathMode = inc.ReadBoolean();
|
||||||
|
bool ironmanMode = inc.ReadBoolean();
|
||||||
|
|
||||||
ReadPermissions(inc);
|
ReadPermissions(inc);
|
||||||
|
|
||||||
@@ -1819,8 +1832,17 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (Screen.Selected != GameMain.GameScreen)
|
if (Screen.Selected != GameMain.GameScreen)
|
||||||
{
|
{
|
||||||
new GUIMessageBox(TextManager.Get("PleaseWait"), TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled"));
|
LocalizedString message;
|
||||||
if (Screen.Selected is not ModDownloadScreen) { GameMain.NetLobbyScreen.Select(); }
|
if (permadeathMode)
|
||||||
|
{
|
||||||
|
message = TextManager.Get(ironmanMode ? "RoundRunningIronman" : "RoundRunningPermadeath");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
message = TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled");
|
||||||
|
}
|
||||||
|
new GUIMessageBox(TextManager.Get("PleaseWait"), message);
|
||||||
|
if (!(Screen.Selected is ModDownloadScreen)) { GameMain.NetLobbyScreen.Select(); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1935,7 +1957,7 @@ namespace Barotrauma.Networking
|
|||||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||||
{
|
{
|
||||||
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
|
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
|
||||||
campaign.CampaignUI?.CrewManagement?.RefreshPermissions();
|
campaign.CampaignUI?.HRManagerUI?.RefreshUI();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2103,6 +2125,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
|
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
|
||||||
|
|
||||||
|
EndRoundTimeRemaining = inc.ReadSingle();
|
||||||
|
|
||||||
SegmentTableReader<ServerNetSegment>.Read(inc,
|
SegmentTableReader<ServerNetSegment>.Read(inc,
|
||||||
segmentDataReader: (segment, inc) =>
|
segmentDataReader: (segment, inc) =>
|
||||||
{
|
{
|
||||||
@@ -2373,7 +2397,16 @@ namespace Barotrauma.Networking
|
|||||||
WaitForNextRoundRespawn = waitForNextRoundRespawn;
|
WaitForNextRoundRespawn = waitForNextRoundRespawn;
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ClientPacketHeader.READY_TO_SPAWN);
|
msg.WriteByte((byte)ClientPacketHeader.READY_TO_SPAWN);
|
||||||
msg.WriteBoolean((bool)waitForNextRoundRespawn);
|
msg.WriteBoolean(GameMain.NetLobbyScreen.Spectating);
|
||||||
|
msg.WriteBoolean(waitForNextRoundRespawn);
|
||||||
|
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SendTakeOverBotRequest(CharacterInfo bot)
|
||||||
|
{
|
||||||
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
|
msg.WriteByte((byte)ClientPacketHeader.TAKEOVERBOT);
|
||||||
|
msg.WriteUInt16(bot.ID);
|
||||||
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2704,16 +2737,19 @@ namespace Barotrauma.Networking
|
|||||||
public override void AddChatMessage(ChatMessage message)
|
public override void AddChatMessage(ChatMessage message)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(message.Text)) { return; }
|
if (string.IsNullOrEmpty(message.Text)) { return; }
|
||||||
if (message.Sender != null && !message.Sender.IsDead)
|
if (message.SenderCharacter is { IsDead: false } sender)
|
||||||
{
|
{
|
||||||
if (message.Text.IsNullOrEmpty())
|
if (message.Text.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
message.Sender.ShowTextlessSpeechBubble(2.0f, message.Color);
|
sender.ShowTextlessSpeechBubble(2.0f, message.Color);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
message.Sender.ShowSpeechBubble(message.Color, message.Text);
|
sender.ShowSpeechBubble(message.Color, message.Text);
|
||||||
|
if (!sender.IsBot)
|
||||||
|
{
|
||||||
|
sender.TextChatVolume = 1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
GameMain.NetLobbyScreen.NewChatMessage(message);
|
GameMain.NetLobbyScreen.NewChatMessage(message);
|
||||||
@@ -3197,19 +3233,19 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
LocalizedString respawnText = string.Empty;
|
LocalizedString respawnText = string.Empty;
|
||||||
Color textColor = Color.White;
|
Color textColor = Color.White;
|
||||||
bool canChooseRespawn =
|
bool hideRespawnButtons = false;
|
||||||
GameMain.GameSession.GameMode is CampaignMode &&
|
|
||||||
Character.Controlled == null &&
|
if (EndRoundTimeRemaining > 0)
|
||||||
Level.Loaded?.Type != LevelData.LevelType.Outpost &&
|
{
|
||||||
(characterInfo == null || HasSpawned);
|
respawnText = TextManager.GetWithVariable("endinground", "[time]", ToolBox.SecondsToReadableTime(EndRoundTimeRemaining))
|
||||||
|
.Fallback(ToolBox.SecondsToReadableTime(EndRoundTimeRemaining), useDefaultLanguageIfFound: false);
|
||||||
|
}
|
||||||
if (RespawnManager.CurrentState == RespawnManager.State.Waiting)
|
if (RespawnManager.CurrentState == RespawnManager.State.Waiting)
|
||||||
{
|
{
|
||||||
if (RespawnManager.RespawnCountdownStarted)
|
if (RespawnManager.RespawnCountdownStarted)
|
||||||
{
|
{
|
||||||
float timeLeft = (float)(RespawnManager.RespawnTime - DateTime.Now).TotalSeconds;
|
float timeLeft = (float)(RespawnManager.RespawnTime - DateTime.Now).TotalSeconds;
|
||||||
respawnText = TextManager.GetWithVariable(
|
respawnText = TextManager.GetWithVariable("RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
|
||||||
RespawnManager.UsingShuttle && !RespawnManager.ForceSpawnInMainSub ?
|
|
||||||
"RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
|
|
||||||
}
|
}
|
||||||
else if (RespawnManager.PendingRespawnCount > 0)
|
else if (RespawnManager.PendingRespawnCount > 0)
|
||||||
{
|
{
|
||||||
@@ -3232,12 +3268,12 @@ namespace Barotrauma.Networking
|
|||||||
//textScale = 1.0f + phase * 0.5f;
|
//textScale = 1.0f + phase * 0.5f;
|
||||||
textColor = Color.Lerp(GUIStyle.Red, Color.White, 1.0f - phase);
|
textColor = Color.Lerp(GUIStyle.Red, Color.White, 1.0f - phase);
|
||||||
}
|
}
|
||||||
canChooseRespawn = false;
|
hideRespawnButtons = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameMain.GameSession?.SetRespawnInfo(
|
GameMain.GameSession.SetRespawnInfo(
|
||||||
visible: !respawnText.IsNullOrEmpty() || canChooseRespawn, text: respawnText.Value, textColor: textColor,
|
text: respawnText.Value, textColor: textColor,
|
||||||
buttonsVisible: canChooseRespawn, waitForNextRoundRespawn: (WaitForNextRoundRespawn ?? true));
|
waitForNextRoundRespawn: (WaitForNextRoundRespawn ?? true), hideButtons: hideRespawnButtons);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!ShowNetStats) { return; }
|
if (!ShowNetStats) { return; }
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
@@ -23,6 +22,14 @@ namespace Barotrauma.Networking
|
|||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ShowDeathPromptIfNeeded(float delay = 1.0f)
|
||||||
|
{
|
||||||
|
if (UseDeathPrompt)
|
||||||
|
{
|
||||||
|
DeathPrompt.Create(delay);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
partial void UpdateTransportingProjSpecific(float deltaTime)
|
partial void UpdateTransportingProjSpecific(float deltaTime)
|
||||||
{
|
{
|
||||||
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
|
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
|
||||||
@@ -37,72 +44,10 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CoroutineHandle respawnPromptCoroutine;
|
|
||||||
|
|
||||||
public void ShowRespawnPromptIfNeeded(float delay = 5.0f)
|
|
||||||
{
|
|
||||||
if (!UseRespawnPrompt) { return; }
|
|
||||||
if (CoroutineManager.IsCoroutineRunning(respawnPromptCoroutine) || GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "respawnquestionprompt"))
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
respawnPromptCoroutine = CoroutineManager.Invoke(() =>
|
|
||||||
{
|
|
||||||
if (Character.Controlled != null || (GameMain.GameSession is not { IsRunning: true })) { return; }
|
|
||||||
|
|
||||||
LocalizedString text;
|
|
||||||
GUIMessageBox respawnPrompt;
|
|
||||||
if (SkillLossPercentageOnImmediateRespawn > 0)
|
|
||||||
{
|
|
||||||
// Respawn asap with extra skill loss?
|
|
||||||
text = TextManager.GetWithVariable("respawnquestionprompt", "[percentage]", ((int)Math.Round(SkillLossPercentageOnImmediateRespawn)).ToString());
|
|
||||||
respawnPrompt = new GUIMessageBox(
|
|
||||||
TextManager.Get("tutorial.tryagainheader"), text,
|
|
||||||
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
|
|
||||||
{
|
|
||||||
UserData = "respawnquestionprompt"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// Respawn asap?
|
|
||||||
text = TextManager.Get("respawnquestionpromptnoloss");
|
|
||||||
respawnPrompt = new GUIMessageBox(
|
|
||||||
TextManager.Get("tutorial.tryagainheader"), text,
|
|
||||||
new LocalizedString[] { TextManager.Get("respawnquestionpromptrespawnnoloss"), TextManager.Get("respawnquestionpromptwait") })
|
|
||||||
{
|
|
||||||
UserData = "respawnquestionprompt"
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if (SkillLossPercentageOnDeath > 0)
|
|
||||||
{
|
|
||||||
// You have died... etc added BEFORE the above text
|
|
||||||
text =
|
|
||||||
TextManager.GetWithVariable("respawnskillpenalty", "[percentage]", ((int)SkillLossPercentageOnDeath).ToString()) +
|
|
||||||
"\n\n" + text;
|
|
||||||
};
|
|
||||||
|
|
||||||
respawnPrompt.Buttons[0].OnClicked += (btn, userdata) =>
|
|
||||||
{
|
|
||||||
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false);
|
|
||||||
respawnPrompt.Close();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
respawnPrompt.Buttons[1].OnClicked += (btn, userdata) =>
|
|
||||||
{
|
|
||||||
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: true);
|
|
||||||
respawnPrompt.Close();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}, delay: delay);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||||
{
|
{
|
||||||
bool respawnPromptPending = false;
|
bool respawnPromptPending = false;
|
||||||
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
|
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
|
||||||
ForceSpawnInMainSub = false;
|
|
||||||
switch (newState)
|
switch (newState)
|
||||||
{
|
{
|
||||||
case State.Transporting:
|
case State.Transporting:
|
||||||
@@ -122,7 +67,6 @@ namespace Barotrauma.Networking
|
|||||||
RequiredRespawnCount = msg.ReadUInt16();
|
RequiredRespawnCount = msg.ReadUInt16();
|
||||||
respawnPromptPending = msg.ReadBoolean();
|
respawnPromptPending = msg.ReadBoolean();
|
||||||
RespawnCountdownStarted = msg.ReadBoolean();
|
RespawnCountdownStarted = msg.ReadBoolean();
|
||||||
ForceSpawnInMainSub = msg.ReadBoolean();
|
|
||||||
ResetShuttle();
|
ResetShuttle();
|
||||||
float newRespawnTime = msg.ReadSingle();
|
float newRespawnTime = msg.ReadSingle();
|
||||||
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
|
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
|
||||||
@@ -136,7 +80,7 @@ namespace Barotrauma.Networking
|
|||||||
if (respawnPromptPending)
|
if (respawnPromptPending)
|
||||||
{
|
{
|
||||||
GameMain.Client.HasSpawned = true;
|
GameMain.Client.HasSpawned = true;
|
||||||
ShowRespawnPromptIfNeeded(delay: 1.0f);
|
DeathPrompt.Create(delay: 1.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.ReadPadBits();
|
msg.ReadPadBits();
|
||||||
|
|||||||
@@ -468,11 +468,28 @@ namespace Barotrauma.Networking
|
|||||||
};
|
};
|
||||||
AssignGUIComponent(nameof(SaveServerLogs), saveLogsBox);
|
AssignGUIComponent(nameof(SaveServerLogs), saveLogsBox);
|
||||||
|
|
||||||
|
LocalizedString newCampaignDefaultSalaryLabel = TextManager.Get("ServerSettingsNewCampaignDefaultSalary");
|
||||||
|
NetLobbyScreen.CreateLabeledSlider(listBox.Content, headerTag: "ServerSettingsNewCampaignDefaultSalary", valueLabelTag: "ServerSettingsKickVotesRequired", tooltipTag: "ServerSettingsNewCampaignDefaultSalaryToolTip",
|
||||||
|
out var defaultSalarySlider, out var defaultSalarySliderLabel);
|
||||||
|
defaultSalarySlider.Range = new Vector2(0, 100);
|
||||||
|
defaultSalarySlider.StepValue = 1;
|
||||||
|
defaultSalarySlider.OnMoved = (scrollBar, _) =>
|
||||||
|
{
|
||||||
|
if (scrollBar.UserData is not GUITextBlock text) { return false; }
|
||||||
|
text.Text = TextManager.AddPunctuation(
|
||||||
|
':',
|
||||||
|
newCampaignDefaultSalaryLabel,
|
||||||
|
TextManager.GetWithVariable("percentageformat", "[value]", ((int)Math.Round(scrollBar.BarScrollValue, digits: 0)).ToString()));
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
AssignGUIComponent(nameof(NewCampaignDefaultSalary), defaultSalarySlider);
|
||||||
|
defaultSalarySlider.OnMoved(defaultSalarySlider, defaultSalarySlider.BarScroll);
|
||||||
|
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
// game settings
|
// game settings
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
|
|
||||||
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform), isHorizontal: true)
|
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), listBox.Content.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomLeft)
|
||||||
{
|
{
|
||||||
Stretch = true,
|
Stretch = true,
|
||||||
RelativeSpacing = 0.05f
|
RelativeSpacing = 0.05f
|
||||||
@@ -683,7 +700,7 @@ namespace Barotrauma.Networking
|
|||||||
// antigriefing
|
// antigriefing
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
|
|
||||||
var tickBoxContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.25f), listBox.Content.RectTransform))
|
var tickBoxContainer = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.268f), listBox.Content.RectTransform))
|
||||||
{
|
{
|
||||||
AutoHideScrollBar = true,
|
AutoHideScrollBar = true,
|
||||||
UseGridLayout = true
|
UseGridLayout = true
|
||||||
@@ -693,6 +710,10 @@ namespace Barotrauma.Networking
|
|||||||
var allowFriendlyFire = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
|
var allowFriendlyFire = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
|
||||||
TextManager.Get("ServerSettingsAllowFriendlyFire"));
|
TextManager.Get("ServerSettingsAllowFriendlyFire"));
|
||||||
AssignGUIComponent(nameof(AllowFriendlyFire), allowFriendlyFire);
|
AssignGUIComponent(nameof(AllowFriendlyFire), allowFriendlyFire);
|
||||||
|
|
||||||
|
var allowDragAndDropGive = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
|
||||||
|
TextManager.Get("ServerSettingsAllowDragAndDropGive"));
|
||||||
|
AssignGUIComponent(nameof(AllowDragAndDropGive), allowDragAndDropGive);
|
||||||
|
|
||||||
var killableNPCs = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
|
var killableNPCs = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
|
||||||
TextManager.Get("ServerSettingsKillableNPCs"));
|
TextManager.Get("ServerSettingsKillableNPCs"));
|
||||||
|
|||||||
@@ -1,19 +1,10 @@
|
|||||||
using Concentus.Enums;
|
using Concentus.Enums;
|
||||||
using Concentus.Structs;
|
using Concentus.Structs;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
static partial class VoipConfig
|
static partial class VoipConfig
|
||||||
{
|
{
|
||||||
public const int FREQUENCY = 48000; //48Khz
|
|
||||||
public const int BITRATE = 16000; //16Kbps
|
|
||||||
public const int BUFFER_SIZE = (8 * MAX_COMPRESSED_SIZE * FREQUENCY) / BITRATE; //20ms window
|
|
||||||
|
|
||||||
public static OpusEncoder CreateEncoder()
|
public static OpusEncoder CreateEncoder()
|
||||||
{
|
{
|
||||||
var encoder = new OpusEncoder(FREQUENCY, 1, OpusApplication.OPUS_APPLICATION_VOIP);
|
var encoder = new OpusEncoder(FREQUENCY, 1, OpusApplication.OPUS_APPLICATION_VOIP);
|
||||||
@@ -22,10 +13,5 @@ namespace Barotrauma.Networking
|
|||||||
encoder.SignalType = OpusSignal.OPUS_SIGNAL_VOICE;
|
encoder.SignalType = OpusSignal.OPUS_SIGNAL_VOICE;
|
||||||
return encoder;
|
return encoder;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static OpusDecoder CreateDecoder()
|
|
||||||
{
|
|
||||||
return new OpusDecoder(FREQUENCY, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -228,8 +228,11 @@ namespace Barotrauma
|
|||||||
static MouseState latestMouseState; //the absolute latest state, do NOT use for player interaction
|
static MouseState latestMouseState; //the absolute latest state, do NOT use for player interaction
|
||||||
static KeyboardState keyboardState, oldKeyboardState;
|
static KeyboardState keyboardState, oldKeyboardState;
|
||||||
|
|
||||||
static double timeSinceClick;
|
static double timeSincePrimaryClick;
|
||||||
static Point lastClickPosition;
|
static Point lastPrimaryClickPosition;
|
||||||
|
|
||||||
|
static double timeSinceSecondaryClick;
|
||||||
|
static Point lastSecondaryClickPosition;
|
||||||
|
|
||||||
const float DoubleClickDelay = 0.4f;
|
const float DoubleClickDelay = 0.4f;
|
||||||
public static float MaxDoubleClickDistance
|
public static float MaxDoubleClickDistance
|
||||||
@@ -237,7 +240,8 @@ namespace Barotrauma
|
|||||||
get { return Math.Max(15.0f * Math.Max(GameMain.GraphicsHeight / 1920.0f, GameMain.GraphicsHeight / 1080.0f), 10.0f); }
|
get { return Math.Max(15.0f * Math.Max(GameMain.GraphicsHeight / 1920.0f, GameMain.GraphicsHeight / 1080.0f), 10.0f); }
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool doubleClicked;
|
static bool primaryDoubleClicked;
|
||||||
|
static bool secondaryDoubleClicked;
|
||||||
|
|
||||||
static bool allowInput;
|
static bool allowInput;
|
||||||
static bool wasWindowActive;
|
static bool wasWindowActive;
|
||||||
@@ -406,7 +410,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static bool DoubleClicked()
|
public static bool DoubleClicked()
|
||||||
{
|
{
|
||||||
return AllowInput && doubleClicked;
|
return AllowInput && primaryDoubleClicked;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool SecondaryDoubleClicked()
|
||||||
|
{
|
||||||
|
return AllowInput && secondaryDoubleClicked;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool KeyHit(InputType inputType)
|
public static bool KeyHit(InputType inputType)
|
||||||
@@ -466,7 +475,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void Update(double deltaTime)
|
public static void Update(double deltaTime)
|
||||||
{
|
{
|
||||||
timeSinceClick += deltaTime;
|
timeSincePrimaryClick += deltaTime;
|
||||||
|
timeSinceSecondaryClick += deltaTime;
|
||||||
|
|
||||||
if (!GameMain.WindowActive)
|
if (!GameMain.WindowActive)
|
||||||
{
|
{
|
||||||
@@ -495,11 +505,33 @@ namespace Barotrauma
|
|||||||
MouseSpeedPerSecond = MouseSpeed / (float)deltaTime;
|
MouseSpeedPerSecond = MouseSpeed / (float)deltaTime;
|
||||||
|
|
||||||
// Split into two to not accept drag & drop releasing as part of a double-click
|
// Split into two to not accept drag & drop releasing as part of a double-click
|
||||||
doubleClicked = false;
|
primaryDoubleClicked = false;
|
||||||
if (PrimaryMouseButtonClicked())
|
if (PrimaryMouseButtonClicked())
|
||||||
{
|
{
|
||||||
float dist = (mouseState.Position - lastClickPosition).ToVector2().Length();
|
primaryDoubleClicked = UpdateDoubleClicking(ref lastPrimaryClickPosition, ref timeSincePrimaryClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PrimaryMouseButtonDown())
|
||||||
|
{
|
||||||
|
lastPrimaryClickPosition = mouseState.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
secondaryDoubleClicked = false;
|
||||||
|
if (SecondaryMouseButtonClicked())
|
||||||
|
{
|
||||||
|
secondaryDoubleClicked = UpdateDoubleClicking(ref lastSecondaryClickPosition, ref timeSinceSecondaryClick);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SecondaryMouseButtonDown())
|
||||||
|
{
|
||||||
|
lastSecondaryClickPosition = mouseState.Position;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UpdateDoubleClicking(ref Point lastClickPosition, ref double timeSinceClick)
|
||||||
|
{
|
||||||
|
bool doubleClicked = false;
|
||||||
|
float dist = (mouseState.Position - lastClickPosition).ToVector2().Length();
|
||||||
|
|
||||||
if (timeSinceClick < DoubleClickDelay && dist < MaxDoubleClickDistance)
|
if (timeSinceClick < DoubleClickDelay && dist < MaxDoubleClickDistance)
|
||||||
{
|
{
|
||||||
doubleClicked = true;
|
doubleClicked = true;
|
||||||
@@ -513,11 +545,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
timeSinceClick = 0.0;
|
timeSinceClick = 0.0;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return doubleClicked;
|
||||||
if (PrimaryMouseButtonDown())
|
|
||||||
{
|
|
||||||
lastClickPosition = mouseState.Position;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public CampaignMode Campaign { get; }
|
public CampaignMode Campaign { get; }
|
||||||
|
|
||||||
public CrewManagement CrewManagement { get; set; }
|
public HRManagerUI HRManagerUI { get; set; }
|
||||||
|
|
||||||
public Store Store { get; private set; }
|
public Store Store { get; private set; }
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
var crewTab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
|
var crewTab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
|
||||||
tabs[(int)CampaignMode.InteractionType.Crew] = crewTab;
|
tabs[(int)CampaignMode.InteractionType.Crew] = crewTab;
|
||||||
CrewManagement = new CrewManagement(this, crewTab);
|
HRManagerUI = new HRManagerUI(this, crewTab);
|
||||||
|
|
||||||
// store tab -------------------------------------------------------------------------
|
// store tab -------------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ namespace Barotrauma
|
|||||||
submarineSelection?.Update();
|
submarineSelection?.Update();
|
||||||
break;
|
break;
|
||||||
case CampaignMode.InteractionType.Crew:
|
case CampaignMode.InteractionType.Crew:
|
||||||
CrewManagement?.Update();
|
HRManagerUI?.Update();
|
||||||
break;
|
break;
|
||||||
case CampaignMode.InteractionType.Store:
|
case CampaignMode.InteractionType.Store:
|
||||||
Store?.Update(deltaTime);
|
Store?.Update(deltaTime);
|
||||||
@@ -598,8 +598,8 @@ namespace Barotrauma
|
|||||||
Store.SelectStore(npc);
|
Store.SelectStore(npc);
|
||||||
break;
|
break;
|
||||||
case CampaignMode.InteractionType.Crew:
|
case CampaignMode.InteractionType.Crew:
|
||||||
CrewManagement.UpdateCrew();
|
HRManagerUI.UpdateCrew();
|
||||||
CrewManagement.UpdateHireables();
|
HRManagerUI.UpdateHireables();
|
||||||
break;
|
break;
|
||||||
case CampaignMode.InteractionType.PurchaseSub:
|
case CampaignMode.InteractionType.PurchaseSub:
|
||||||
submarineSelection ??= new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform);
|
submarineSelection ??= new SubmarineSelection(false, () => Campaign.ShowCampaignUI = false, tabs[(int)CampaignMode.InteractionType.PurchaseSub].RectTransform);
|
||||||
|
|||||||
@@ -781,7 +781,7 @@ namespace Barotrauma.CharacterEditor
|
|||||||
// Lightmaps
|
// Lightmaps
|
||||||
if (GameMain.LightManager.LightingEnabled && Character.Controlled != null)
|
if (GameMain.LightManager.LightingEnabled && Character.Controlled != null)
|
||||||
{
|
{
|
||||||
GameMain.LightManager.ObstructVision = Character.Controlled.ObstructVision;
|
GameMain.LightManager.ObstructVisionAmount = Character.Controlled.ObstructVisionAmount;
|
||||||
GameMain.LightManager.RenderLightMap(graphics, spriteBatch, cam);
|
GameMain.LightManager.RenderLightMap(graphics, spriteBatch, cam);
|
||||||
GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled.CursorWorldPosition);
|
GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled.CursorWorldPosition);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.Lights;
|
using Barotrauma.Lights;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
@@ -177,10 +177,15 @@ namespace Barotrauma
|
|||||||
Stopwatch sw = new Stopwatch();
|
Stopwatch sw = new Stopwatch();
|
||||||
sw.Start();
|
sw.Start();
|
||||||
|
|
||||||
GameMain.LightManager.ObstructVision =
|
if (Character.Controlled != null &&
|
||||||
Character.Controlled != null &&
|
(Character.Controlled.ViewTarget == Character.Controlled || Character.Controlled.ViewTarget == null))
|
||||||
Character.Controlled.ObstructVision &&
|
{
|
||||||
(Character.Controlled.ViewTarget == Character.Controlled || Character.Controlled.ViewTarget == null);
|
GameMain.LightManager.ObstructVisionAmount = Character.Controlled.ObstructVisionAmount;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameMain.LightManager.ObstructVisionAmount = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled?.CursorWorldPosition ?? Vector2.Zero);
|
GameMain.LightManager.UpdateObstructVision(graphics, spriteBatch, cam, Character.Controlled?.CursorWorldPosition ?? Vector2.Zero);
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace Barotrauma
|
|||||||
currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams);
|
currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams);
|
||||||
editorContainer.ClearChildren();
|
editorContainer.ClearChildren();
|
||||||
SortLevelObjectsList(currentLevelData);
|
SortLevelObjectsList(currentLevelData);
|
||||||
new SerializableEntityEditor(editorContainer.Content.RectTransform, selectedParams, false, true, elementHeight: 20);
|
new SerializableEntityEditor(editorContainer.Content.RectTransform, selectedParams, inGame: false, showName: true, elementHeight: 20, titleFont: GUIStyle.LargeFont);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -996,7 +996,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (Item item in Item.ItemList)
|
foreach (Item item in Item.ItemList)
|
||||||
{
|
{
|
||||||
if (item == null || item.HiddenInGame) { continue; }
|
if (item == null || item.IsHidden) { continue; }
|
||||||
foreach (var light in item.GetComponents<Items.Components.LightComponent>())
|
foreach (var light in item.GetComponents<Items.Components.LightComponent>())
|
||||||
{
|
{
|
||||||
light.Update((float)deltaTime, Cam);
|
light.Update((float)deltaTime, Cam);
|
||||||
|
|||||||
@@ -63,6 +63,9 @@ namespace Barotrauma
|
|||||||
private GUITickBox spectateBox;
|
private GUITickBox spectateBox;
|
||||||
public bool Spectating => spectateBox is { Selected: true, Visible: true };
|
public bool Spectating => spectateBox is { Selected: true, Visible: true };
|
||||||
|
|
||||||
|
public bool PermadeathMode => GameMain.Client?.ServerSettings?.RespawnMode == RespawnMode.Permadeath;
|
||||||
|
public bool PermanentlyDead => campaignCharacterInfo?.PermanentlyDead ?? false;
|
||||||
|
|
||||||
private GUILayoutGroup playerInfoContent;
|
private GUILayoutGroup playerInfoContent;
|
||||||
private GUIComponent changesPendingText;
|
private GUIComponent changesPendingText;
|
||||||
private bool createPendingChangesText = true;
|
private bool createPendingChangesText = true;
|
||||||
@@ -87,7 +90,14 @@ namespace Barotrauma
|
|||||||
private GUIFrame characterInfoFrame;
|
private GUIFrame characterInfoFrame;
|
||||||
private GUIFrame appearanceFrame;
|
private GUIFrame appearanceFrame;
|
||||||
|
|
||||||
private readonly List<GUIComponent> respawnSettingsElements = new List<GUIComponent>();
|
private GUISelectionCarousel<RespawnMode> respawnModeSelection;
|
||||||
|
private GUITextBlock respawnModeLabel;
|
||||||
|
private GUIComponent respawnIntervalElement;
|
||||||
|
|
||||||
|
private readonly List<GUIComponent> midRoundRespawnSettings = new List<GUIComponent>();
|
||||||
|
private readonly List<GUIComponent> permadeathEnabledRespawnSettings = new List<GUIComponent>();
|
||||||
|
private readonly List<GUIComponent> permadeathDisabledRespawnSettings = new List<GUIComponent>();
|
||||||
|
private readonly List<GUIComponent> ironmanDisabledRespawnSettings = new List<GUIComponent>();
|
||||||
private readonly List<GUIComponent> campaignDisabledElements = new List<GUIComponent>();
|
private readonly List<GUIComponent> campaignDisabledElements = new List<GUIComponent>();
|
||||||
|
|
||||||
public CharacterInfo.AppearanceCustomizationMenu CharacterAppearanceCustomizationMenu { get; set; }
|
public CharacterInfo.AppearanceCustomizationMenu CharacterAppearanceCustomizationMenu { get; set; }
|
||||||
@@ -191,7 +201,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public bool UsingShuttle
|
public bool UsingShuttle
|
||||||
{
|
{
|
||||||
get { return shuttleTickBox.Selected; }
|
get { return shuttleTickBox.Selected && !PermadeathMode; }
|
||||||
set { shuttleTickBox.Selected = value; }
|
set { shuttleTickBox.Selected = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -955,19 +965,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
// ------------------------------------------------------------------
|
// ------------------------------------------------------------------
|
||||||
|
|
||||||
var respawnBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), settingsContent.RectTransform) { AbsoluteOffset = new Point((int)respawnSettingsHeader.Padding.X, 0) },
|
var respawnModeHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), settingsContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
|
||||||
TextManager.Get("ServerSettingsAllowRespawning"))
|
respawnModeLabel = new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.0f), respawnModeHolder.RectTransform), TextManager.Get("RespawnMode"), wrap: true);
|
||||||
|
respawnModeSelection = new GUISelectionCarousel<RespawnMode>(new RectTransform(new Vector2(0.6f, 1.0f), respawnModeHolder.RectTransform));
|
||||||
|
foreach (var respawnMode in Enum.GetValues(typeof(RespawnMode)).Cast<RespawnMode>())
|
||||||
{
|
{
|
||||||
ToolTip = TextManager.Get("RespawnExplanation"),
|
respawnModeSelection.AddElement(respawnMode, TextManager.Get($"respawnmode.{respawnMode}"), TextManager.Get($"respawnmode.{respawnMode}.tooltip"));
|
||||||
OnSelected = (tickbox) =>
|
}
|
||||||
{
|
|
||||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
respawnModeSelection.ElementSelectionCondition += (value) => value != RespawnMode.Permadeath || SelectedMode == GameModePreset.MultiPlayerCampaign;
|
||||||
RefreshEnabledElements();
|
respawnModeSelection.OnValueChanged += (_) => GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
return true;
|
AssignComponentToServerSetting(respawnModeSelection, nameof(ServerSettings.RespawnMode));
|
||||||
}
|
|
||||||
};
|
|
||||||
AssignComponentToServerSetting(respawnBox, nameof(ServerSettings.AllowRespawn));
|
|
||||||
clientDisabledElements.Add(respawnBox);
|
|
||||||
|
|
||||||
GUILayoutGroup shuttleHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), settingsContent.RectTransform), isHorizontal: true)
|
GUILayoutGroup shuttleHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), settingsContent.RectTransform), isHorizontal: true)
|
||||||
{
|
{
|
||||||
@@ -977,7 +985,7 @@ namespace Barotrauma
|
|||||||
shuttleTickBox = new GUITickBox(new RectTransform(Vector2.One, shuttleHolder.RectTransform), TextManager.Get("RespawnShuttle"))
|
shuttleTickBox = new GUITickBox(new RectTransform(Vector2.One, shuttleHolder.RectTransform), TextManager.Get("RespawnShuttle"))
|
||||||
{
|
{
|
||||||
ToolTip = TextManager.Get("RespawnShuttleExplanation"),
|
ToolTip = TextManager.Get("RespawnShuttleExplanation"),
|
||||||
Selected = true,
|
Selected = !PermadeathMode,
|
||||||
OnSelected = (GUITickBox box) =>
|
OnSelected = (GUITickBox box) =>
|
||||||
{
|
{
|
||||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
@@ -985,7 +993,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
AssignComponentToServerSetting(shuttleTickBox, nameof(ServerSettings.UseRespawnShuttle));
|
AssignComponentToServerSetting(shuttleTickBox, nameof(ServerSettings.UseRespawnShuttle));
|
||||||
respawnSettingsElements.Add(shuttleTickBox);
|
midRoundRespawnSettings.Add(shuttleTickBox);
|
||||||
|
|
||||||
shuttleTickBox.TextBlock.RectTransform.SizeChanged += () =>
|
shuttleTickBox.TextBlock.RectTransform.SizeChanged += () =>
|
||||||
{
|
{
|
||||||
@@ -1008,9 +1016,9 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
ShuttleList.ListBox.RectTransform.MinSize = new Point(250, 0);
|
ShuttleList.ListBox.RectTransform.MinSize = new Point(250, 0);
|
||||||
shuttleHolder.RectTransform.MinSize = new Point(0, ShuttleList.RectTransform.Children.Max(c => c.MinSize.Y));
|
shuttleHolder.RectTransform.MinSize = new Point(0, ShuttleList.RectTransform.Children.Max(c => c.MinSize.Y));
|
||||||
respawnSettingsElements.Add(ShuttleList);
|
midRoundRespawnSettings.Add(ShuttleList);
|
||||||
|
|
||||||
var respawnIntervalElement = CreateLabeledSlider(settingsContent, "ServerSettingsRespawnInterval", "", "", out var respawnIntervalSlider, out var respawnIntervalSliderLabel,
|
respawnIntervalElement = CreateLabeledSlider(settingsContent, "ServerSettingsRespawnInterval", "", "", out var respawnIntervalSlider, out var respawnIntervalSliderLabel,
|
||||||
range: new Vector2(10.0f, 600.0f));
|
range: new Vector2(10.0f, 600.0f));
|
||||||
LocalizedString intervalLabel = respawnIntervalSliderLabel.Text;
|
LocalizedString intervalLabel = respawnIntervalSliderLabel.Text;
|
||||||
respawnIntervalSlider.StepValue = 10.0f;
|
respawnIntervalSlider.StepValue = 10.0f;
|
||||||
@@ -1026,7 +1034,6 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
respawnIntervalSlider.OnMoved(respawnIntervalSlider, respawnIntervalSlider.BarScroll);
|
respawnIntervalSlider.OnMoved(respawnIntervalSlider, respawnIntervalSlider.BarScroll);
|
||||||
respawnSettingsElements.AddRange(respawnIntervalElement.GetAllChildren());
|
|
||||||
AssignComponentToServerSetting(respawnIntervalSlider, nameof(ServerSettings.RespawnInterval));
|
AssignComponentToServerSetting(respawnIntervalSlider, nameof(ServerSettings.RespawnInterval));
|
||||||
|
|
||||||
var minRespawnElement = CreateLabeledSlider(settingsContent, "ServerSettingsMinRespawn", "", "ServerSettingsMinRespawnToolTip", out var minRespawnSlider, out var minRespawnSliderLabel,
|
var minRespawnElement = CreateLabeledSlider(settingsContent, "ServerSettingsMinRespawn", "", "ServerSettingsMinRespawnToolTip", out var minRespawnSlider, out var minRespawnSliderLabel,
|
||||||
@@ -1043,7 +1050,7 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
minRespawnSlider.OnMoved(minRespawnSlider, minRespawnSlider.BarScroll);
|
minRespawnSlider.OnMoved(minRespawnSlider, minRespawnSlider.BarScroll);
|
||||||
respawnSettingsElements.AddRange(minRespawnElement.GetAllChildren());
|
midRoundRespawnSettings.AddRange(minRespawnElement.GetAllChildren());
|
||||||
AssignComponentToServerSetting(minRespawnSlider, nameof(ServerSettings.MinRespawnRatio));
|
AssignComponentToServerSetting(minRespawnSlider, nameof(ServerSettings.MinRespawnRatio));
|
||||||
|
|
||||||
var respawnDurationElement = CreateLabeledSlider(settingsContent, "ServerSettingsRespawnDuration", "", "ServerSettingsRespawnDurationTooltip", out var respawnDurationSlider, out var respawnDurationSliderLabel,
|
var respawnDurationElement = CreateLabeledSlider(settingsContent, "ServerSettingsRespawnDuration", "", "ServerSettingsRespawnDurationTooltip", out var respawnDurationSlider, out var respawnDurationSliderLabel,
|
||||||
@@ -1068,7 +1075,7 @@ namespace Barotrauma
|
|||||||
return value <= 0.0f ? 1.0f : (value - scrollBar.Range.X) / (scrollBar.Range.Y - scrollBar.Range.X);
|
return value <= 0.0f ? 1.0f : (value - scrollBar.Range.X) / (scrollBar.Range.Y - scrollBar.Range.X);
|
||||||
};
|
};
|
||||||
respawnDurationSlider.OnMoved(respawnDurationSlider, respawnDurationSlider.BarScroll);
|
respawnDurationSlider.OnMoved(respawnDurationSlider, respawnDurationSlider.BarScroll);
|
||||||
respawnSettingsElements.AddRange(respawnDurationElement.GetAllChildren());
|
midRoundRespawnSettings.AddRange(respawnDurationElement.GetAllChildren());
|
||||||
AssignComponentToServerSetting(respawnDurationSlider, nameof(ServerSettings.MaxTransportTime));
|
AssignComponentToServerSetting(respawnDurationSlider, nameof(ServerSettings.MaxTransportTime));
|
||||||
|
|
||||||
var skillLossElement = CreateLabeledSlider(settingsContent, "ServerSettingsSkillLossPercentageOnDeath", "", "ServerSettingsSkillLossPercentageOnDeathToolTip",
|
var skillLossElement = CreateLabeledSlider(settingsContent, "ServerSettingsSkillLossPercentageOnDeath", "", "ServerSettingsSkillLossPercentageOnDeathToolTip",
|
||||||
@@ -1085,7 +1092,8 @@ namespace Barotrauma
|
|||||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
respawnSettingsElements.AddRange(skillLossElement.GetAllChildren());
|
permadeathDisabledRespawnSettings.AddRange(skillLossElement.GetAllChildren());
|
||||||
|
clientDisabledElements.AddRange(skillLossElement.GetAllChildren());
|
||||||
AssignComponentToServerSetting(skillLossSlider, nameof(ServerSettings.SkillLossPercentageOnDeath));
|
AssignComponentToServerSetting(skillLossSlider, nameof(ServerSettings.SkillLossPercentageOnDeath));
|
||||||
skillLossSlider.OnMoved(skillLossSlider, skillLossSlider.BarScroll);
|
skillLossSlider.OnMoved(skillLossSlider, skillLossSlider.BarScroll);
|
||||||
|
|
||||||
@@ -1103,11 +1111,71 @@ namespace Barotrauma
|
|||||||
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
respawnSettingsElements.AddRange(skillLossImmediateRespawnElement.GetAllChildren());
|
midRoundRespawnSettings.AddRange(skillLossImmediateRespawnElement.GetAllChildren());
|
||||||
|
permadeathDisabledRespawnSettings.AddRange(skillLossImmediateRespawnElement.GetAllChildren());
|
||||||
AssignComponentToServerSetting(skillLossImmediateRespawnSlider, nameof(ServerSettings.SkillLossPercentageOnImmediateRespawn));
|
AssignComponentToServerSetting(skillLossImmediateRespawnSlider, nameof(ServerSettings.SkillLossPercentageOnImmediateRespawn));
|
||||||
skillLossImmediateRespawnSlider.OnMoved(skillLossImmediateRespawnSlider, skillLossImmediateRespawnSlider.BarScroll);
|
skillLossImmediateRespawnSlider.OnMoved(skillLossImmediateRespawnSlider, skillLossImmediateRespawnSlider.BarScroll);
|
||||||
|
|
||||||
foreach (var respawnElement in respawnSettingsElements)
|
var newCharacterCostSliderElement = CreateLabeledSlider(settingsContent,
|
||||||
|
"ServerSettings.ReplaceCostPercentage", "", "ServerSettings.ReplaceCostPercentage.tooltip",
|
||||||
|
out var newCharacterCostSlider, out var newCharacterCostSliderLabel,
|
||||||
|
range: new Vector2(0, 200), step: 10f);
|
||||||
|
newCharacterCostSlider.StepValue = 10f;
|
||||||
|
newCharacterCostSlider.OnMoved = (GUIScrollBar scrollBar, float _) =>
|
||||||
|
{
|
||||||
|
GUITextBlock textBlock = scrollBar.UserData as GUITextBlock;
|
||||||
|
int currentMultiplier = (int)Math.Round(scrollBar.BarScrollValue);
|
||||||
|
if (currentMultiplier < 1)
|
||||||
|
{
|
||||||
|
textBlock.Text = TextManager.Get("ServerSettings.ReplaceCostPercentage.Free");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
textBlock.Text = TextManager.GetWithVariable("percentageformat", "[value]", currentMultiplier.ToString());
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
newCharacterCostSlider.OnReleased = (GUIScrollBar scrollBar, float barScroll) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
clientDisabledElements.AddRange(newCharacterCostSliderElement.GetAllChildren());
|
||||||
|
permadeathEnabledRespawnSettings.AddRange(newCharacterCostSliderElement.GetAllChildren());
|
||||||
|
ironmanDisabledRespawnSettings.AddRange(newCharacterCostSliderElement.GetAllChildren());
|
||||||
|
AssignComponentToServerSetting(newCharacterCostSlider, nameof(ServerSettings.ReplaceCostPercentage));
|
||||||
|
newCharacterCostSlider.OnMoved(newCharacterCostSlider, newCharacterCostSlider.BarScroll); // initialize
|
||||||
|
|
||||||
|
var allowBotTakeoverTickbox = new GUITickBox(new RectTransform(Vector2.One, settingsContent.RectTransform), TextManager.Get("AllowBotTakeover"))
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("AllowBotTakeover.Tooltip"),
|
||||||
|
Selected = GameMain.Client != null && GameMain.Client.ServerSettings.AllowBotTakeoverOnPermadeath,
|
||||||
|
OnSelected = (GUITickBox box) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AssignComponentToServerSetting(allowBotTakeoverTickbox, nameof(ServerSettings.AllowBotTakeoverOnPermadeath));
|
||||||
|
permadeathEnabledRespawnSettings.Add(allowBotTakeoverTickbox);
|
||||||
|
ironmanDisabledRespawnSettings.Add(allowBotTakeoverTickbox);
|
||||||
|
clientDisabledElements.Add(allowBotTakeoverTickbox);
|
||||||
|
|
||||||
|
var ironmanTickbox = new GUITickBox(new RectTransform(Vector2.One, settingsContent.RectTransform), TextManager.Get("IronmanMode").ToUpper())
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("IronmanMode.Tooltip"),
|
||||||
|
Selected = GameMain.Client != null && GameMain.Client.ServerSettings.IronmanMode,
|
||||||
|
OnSelected = (GUITickBox box) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
AssignComponentToServerSetting(ironmanTickbox, nameof(ServerSettings.IronmanMode));
|
||||||
|
permadeathEnabledRespawnSettings.Add(ironmanTickbox);
|
||||||
|
clientDisabledElements.Add(ironmanTickbox);
|
||||||
|
|
||||||
|
foreach (var respawnElement in midRoundRespawnSettings)
|
||||||
{
|
{
|
||||||
if (!clientDisabledElements.Contains(respawnElement))
|
if (!clientDisabledElements.Contains(respawnElement))
|
||||||
{
|
{
|
||||||
@@ -1650,19 +1718,31 @@ namespace Barotrauma
|
|||||||
bool campaignStarted = CampaignFrame.Visible;
|
bool campaignStarted = CampaignFrame.Visible;
|
||||||
bool gameStarted = client != null && client.GameStarted;
|
bool gameStarted = client != null && client.GameStarted;
|
||||||
|
|
||||||
//disable elements the client doesn't have access to
|
// First, enable or disable elements based on client permissions
|
||||||
foreach (var element in clientDisabledElements)
|
foreach (var element in clientDisabledElements)
|
||||||
{
|
{
|
||||||
element.Enabled = manageSettings;
|
element.Enabled = manageSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Then disable elements depending on other conditions
|
||||||
traitorElements.ForEach(e => e.Enabled &= settings.TraitorProbability > 0);
|
traitorElements.ForEach(e => e.Enabled &= settings.TraitorProbability > 0);
|
||||||
SetTraitorDangerIndicators(settings.TraitorDangerLevel);
|
SetTraitorDangerIndicators(settings.TraitorDangerLevel);
|
||||||
respawnSettingsElements.ForEach(e => e.Enabled &= settings.AllowRespawn);
|
respawnModeSelection.Enabled = respawnModeLabel.Enabled = manageSettings && !gameStarted;
|
||||||
|
midRoundRespawnSettings.ForEach(e => e.Enabled &= settings.RespawnMode == RespawnMode.MidRound);
|
||||||
|
permadeathDisabledRespawnSettings.ForEach(e => e.Enabled &= settings.RespawnMode != RespawnMode.Permadeath);
|
||||||
|
permadeathEnabledRespawnSettings.ForEach(e => e.Enabled &= settings.RespawnMode == RespawnMode.Permadeath && !gameStarted);
|
||||||
|
ironmanDisabledRespawnSettings.ForEach(e => e.Enabled &= !settings.IronmanMode);
|
||||||
|
|
||||||
|
// The respawn interval is used even if the shuttle is not
|
||||||
|
respawnIntervalElement.GetAllChildren().ForEach(e => e.Enabled = settings.RespawnMode != RespawnMode.BetweenRounds && manageSettings);
|
||||||
|
|
||||||
//go through the individual elements that are only enabled in a specific context
|
//go through the individual elements that are only enabled in a specific context
|
||||||
|
shuttleTickBox.Enabled &= !gameStarted;
|
||||||
if (ShuttleList != null)
|
if (ShuttleList != null)
|
||||||
{
|
{
|
||||||
ShuttleList.Enabled = ShuttleList.ButtonEnabled = HasPermission(ClientPermissions.SelectSub) && !gameStarted && settings.AllowRespawn;
|
// Shuttle list depends on shuttle tickbox
|
||||||
|
ShuttleList.Enabled &= shuttleTickBox.Enabled && HasPermission(ClientPermissions.SelectSub);
|
||||||
|
ShuttleList.ButtonEnabled = ShuttleList.Enabled;
|
||||||
}
|
}
|
||||||
if (SubList != null)
|
if (SubList != null)
|
||||||
{
|
{
|
||||||
@@ -1672,7 +1752,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
ModeList.Enabled = !gameStarted && (settings.AllowModeVoting || HasPermission(ClientPermissions.SelectMode));
|
ModeList.Enabled = !gameStarted && (settings.AllowModeVoting || HasPermission(ClientPermissions.SelectMode));
|
||||||
}
|
}
|
||||||
shuttleTickBox.Enabled &= !gameStarted;
|
|
||||||
|
|
||||||
RefreshStartButtonVisibility();
|
RefreshStartButtonVisibility();
|
||||||
|
|
||||||
@@ -1750,6 +1829,10 @@ namespace Barotrauma
|
|||||||
private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent, bool createPendingText = true)
|
private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent, bool createPendingText = true)
|
||||||
{
|
{
|
||||||
if (GameMain.Client == null) { return; }
|
if (GameMain.Client == null) { return; }
|
||||||
|
|
||||||
|
// When permanently dead and still characterless, spectating is the only option
|
||||||
|
spectateBox.Enabled = !PermanentlyDead;
|
||||||
|
|
||||||
createPendingChangesText = createPendingText;
|
createPendingChangesText = createPendingText;
|
||||||
if (characterInfo == null || CampaignCharacterDiscarded)
|
if (characterInfo == null || CampaignCharacterDiscarded)
|
||||||
{
|
{
|
||||||
@@ -1780,41 +1863,58 @@ namespace Barotrauma
|
|||||||
MaxTextLength = Client.MaxNameLength,
|
MaxTextLength = Client.MaxNameLength,
|
||||||
OverflowClip = true
|
OverflowClip = true
|
||||||
};
|
};
|
||||||
|
|
||||||
CharacterNameBox.OnEnterPressed += (tb, text) => { CharacterNameBox.Deselect(); return true; };
|
if (!allowEditing ||
|
||||||
CharacterNameBox.OnDeselected += (tb, key) =>
|
(PermanentlyDead && !characterInfo.RenamingEnabled))
|
||||||
{
|
{
|
||||||
if (GameMain.Client == null) { return; }
|
CharacterNameBox.Readonly = true;
|
||||||
string newName = Client.SanitizeName(tb.Text);
|
CharacterNameBox.Enabled = false;
|
||||||
if (newName == GameMain.Client.Name) return;
|
}
|
||||||
if (string.IsNullOrWhiteSpace(newName))
|
else
|
||||||
|
{
|
||||||
|
CharacterNameBox.OnEnterPressed += (tb, text) =>
|
||||||
{
|
{
|
||||||
tb.Text = GameMain.Client.Name;
|
CharacterNameBox.Deselect();
|
||||||
}
|
return true;
|
||||||
else
|
};
|
||||||
|
CharacterNameBox.OnDeselected += (tb, key) =>
|
||||||
{
|
{
|
||||||
if (isGameRunning)
|
if (GameMain.Client == null)
|
||||||
{
|
{
|
||||||
GameMain.Client.PendingName = tb.Text;
|
return;
|
||||||
TabMenu.PendingChanges = true;
|
}
|
||||||
if (createPendingText)
|
|
||||||
{
|
string newName = Client.SanitizeName(tb.Text);
|
||||||
CreateChangesPendingText();
|
if (newName == GameMain.Client.Name) { return; }
|
||||||
}
|
if (string.IsNullOrWhiteSpace(newName))
|
||||||
|
{
|
||||||
|
tb.Text = GameMain.Client.Name;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReadyToStartBox.Selected = false;
|
if (isGameRunning)
|
||||||
|
{
|
||||||
|
GameMain.Client.PendingName = tb.Text;
|
||||||
|
TabMenu.PendingChanges = true;
|
||||||
|
if (createPendingText)
|
||||||
|
{
|
||||||
|
CreateChangesPendingText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReadyToStartBox.Selected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMain.Client.SetName(tb.Text);
|
||||||
}
|
}
|
||||||
|
};
|
||||||
GameMain.Client.SetName(tb.Text);
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
//spacing
|
//spacing
|
||||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.006f), parent.RectTransform), style: null);
|
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.006f), parent.RectTransform), style: null);
|
||||||
|
|
||||||
if (allowEditing)
|
if (allowEditing && (!PermadeathMode || !isGameRunning))
|
||||||
{
|
{
|
||||||
GUILayoutGroup characterInfoTabs = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), parent.RectTransform), isHorizontal: true)
|
GUILayoutGroup characterInfoTabs = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), parent.RectTransform), isHorizontal: true)
|
||||||
{
|
{
|
||||||
@@ -1892,37 +1992,70 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.16f), parent.RectTransform, Anchor.TopCenter));
|
characterInfo.CreateIcon(new RectTransform(new Vector2(1.0f, 0.16f), parent.RectTransform, Anchor.TopCenter));
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont, wrap: true)
|
if (PermanentlyDead)
|
||||||
{
|
{
|
||||||
HoverColor = Color.Transparent,
|
new GUITextBlock(
|
||||||
SelectedColor = Color.Transparent
|
new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform),
|
||||||
};
|
TextManager.Get("deceased"),
|
||||||
|
textAlignment: Alignment.Center, font: GUIStyle.LargeFont);
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform), TextManager.Get("Skills"), font: GUIStyle.SubHeadingFont);
|
|
||||||
foreach (Skill skill in characterInfo.Job.GetSkills())
|
if (GameMain.Client?.ServerSettings is { IronmanMode: true })
|
||||||
|
{
|
||||||
|
new GUITextBlock(
|
||||||
|
new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform),
|
||||||
|
TextManager.Get("lobby.ironmaninfo"),
|
||||||
|
textAlignment: Alignment.Center, wrap: true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
new GUITextBlock(
|
||||||
|
new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform),
|
||||||
|
TextManager.Get("lobby.permadeathinfo"),
|
||||||
|
textAlignment: Alignment.Center, wrap: true);
|
||||||
|
new GUITextBlock(
|
||||||
|
new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform),
|
||||||
|
TextManager.Get("lobby.permadeathoptionsexplanation"),
|
||||||
|
textAlignment: Alignment.Center, wrap: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
{
|
{
|
||||||
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform), characterInfo.Job.Name, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont, wrap: true)
|
||||||
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform),
|
{
|
||||||
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), ((int)skill.Level).ToString()),
|
HoverColor = Color.Transparent,
|
||||||
textColor,
|
SelectedColor = Color.Transparent
|
||||||
font: GUIStyle.SmallFont);
|
};
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform), TextManager.Get("Skills"), font: GUIStyle.SubHeadingFont);
|
||||||
|
foreach (Skill skill in characterInfo.Job.GetSkills())
|
||||||
|
{
|
||||||
|
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
||||||
|
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform),
|
||||||
|
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), ((int)skill.Level).ToString()),
|
||||||
|
textColor,
|
||||||
|
font: GUIStyle.SmallFont);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Spacing
|
// Spacing
|
||||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), parent.RectTransform), style: null);
|
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.15f), parent.RectTransform), style: null);
|
||||||
|
|
||||||
new GUIButton(new RectTransform(new Vector2(0.8f, 0.1f), parent.RectTransform, Anchor.BottomCenter), TextManager.Get("CreateNew"))
|
if (GameMain.Client?.ServerSettings?.RespawnMode != RespawnMode.Permadeath)
|
||||||
{
|
{
|
||||||
IgnoreLayoutGroups = true,
|
// Button to create new character
|
||||||
OnClicked = (btn, userdata) =>
|
new GUIButton(new RectTransform(new Vector2(0.8f, 0.1f), parent.RectTransform, Anchor.BottomCenter), TextManager.Get("CreateNew"))
|
||||||
{
|
{
|
||||||
TryDiscardCampaignCharacter(() =>
|
IgnoreLayoutGroups = true,
|
||||||
|
OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
UpdatePlayerFrame(null, true, parent);
|
TryDiscardCampaignCharacter(() =>
|
||||||
});
|
{
|
||||||
return true;
|
UpdatePlayerFrame(null, true, parent);
|
||||||
}
|
});
|
||||||
};
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TeamPreferenceListBox = null;
|
TeamPreferenceListBox = null;
|
||||||
@@ -2095,14 +2228,20 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (GameMain.Client == null) { return; }
|
if (GameMain.Client == null) { return; }
|
||||||
spectateBox.Selected = spectate;
|
spectateBox.Selected = spectate;
|
||||||
|
|
||||||
if (spectate)
|
if (spectate)
|
||||||
{
|
{
|
||||||
playerInfoContent.ClearChildren();
|
|
||||||
|
|
||||||
GameMain.Client.CharacterInfo?.Remove();
|
GameMain.Client.CharacterInfo?.Remove();
|
||||||
GameMain.Client.CharacterInfo = null;
|
GameMain.Client.CharacterInfo = null;
|
||||||
GameMain.Client.Character?.Remove();
|
// TODO: The following lines are ancient, unexplained, and they cause a client spectating because of permadeath
|
||||||
GameMain.Client.Character = null;
|
// to get kicked from the server at round transition because the server expects to be in control of
|
||||||
|
// removing Characters and the client to still have one. Commenting these lines out for now, but
|
||||||
|
// if no side-effects occur, they can just be deleted.
|
||||||
|
//GameMain.Client.Character?.Remove();
|
||||||
|
//GameMain.Client.Character = null;
|
||||||
|
|
||||||
|
playerInfoContent.ClearChildren();
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(Vector2.One, playerInfoContent.RectTransform, Anchor.Center),
|
new GUITextBlock(new RectTransform(Vector2.One, playerInfoContent.RectTransform, Anchor.Center),
|
||||||
TextManager.Get("PlayingAsSpectator"),
|
TextManager.Get("PlayingAsSpectator"),
|
||||||
textAlignment: Alignment.Center);
|
textAlignment: Alignment.Center);
|
||||||
@@ -2118,6 +2257,10 @@ namespace Barotrauma
|
|||||||
// Server owner is allowed to spectate regardless of the server settings
|
// Server owner is allowed to spectate regardless of the server settings
|
||||||
if (GameMain.Client != null && GameMain.Client.IsServerOwner) { return; }
|
if (GameMain.Client != null && GameMain.Client.IsServerOwner) { return; }
|
||||||
|
|
||||||
|
// A client whose character has faced permadeath and hasn't chosen a new
|
||||||
|
// character yet has no choice but to spectate
|
||||||
|
if (campaignCharacterInfo != null && campaignCharacterInfo.PermanentlyDead) { return; }
|
||||||
|
|
||||||
// Show the player config menu if spectating is not allowed
|
// Show the player config menu if spectating is not allowed
|
||||||
if (spectateBox.Selected && !allowSpectating) { spectateBox.Selected = false; }
|
if (spectateBox.Selected && !allowSpectating) { spectateBox.Selected = false; }
|
||||||
|
|
||||||
@@ -3609,6 +3752,7 @@ namespace Barotrauma
|
|||||||
GameMain.GameSession = null;
|
GameMain.GameSession = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
respawnModeSelection.Refresh(); // not all respawn modes are compatible with all game modes
|
||||||
RefreshGameModeContent();
|
RefreshGameModeContent();
|
||||||
RefreshEnabledElements();
|
RefreshEnabledElements();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,38 +27,8 @@ namespace Barotrauma
|
|||||||
get => Submarine.MainSub;
|
get => Submarine.MainSub;
|
||||||
set => Submarine.MainSub = value;
|
set => Submarine.MainSub = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private enum LayerVisibility
|
|
||||||
{
|
|
||||||
Visible,
|
|
||||||
Invisible
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum LayerLinkage
|
private readonly record struct LayerData(bool IsVisible = true, bool IsGrouped = false);
|
||||||
{
|
|
||||||
Unlinked,
|
|
||||||
Linked
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly struct LayerData
|
|
||||||
{
|
|
||||||
public readonly LayerVisibility Visible;
|
|
||||||
public readonly LayerLinkage Linkage;
|
|
||||||
|
|
||||||
public static readonly LayerData Default = new LayerData(LayerVisibility.Visible, LayerLinkage.Unlinked);
|
|
||||||
|
|
||||||
public LayerData(LayerVisibility visible, LayerLinkage linkage)
|
|
||||||
{
|
|
||||||
Visible = visible;
|
|
||||||
Linkage = linkage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Deconstruct(out LayerVisibility isvisible, out LayerLinkage islinked)
|
|
||||||
{
|
|
||||||
isvisible = Visible;
|
|
||||||
islinked = Linkage;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Mode
|
public enum Mode
|
||||||
{
|
{
|
||||||
@@ -1105,11 +1075,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
GameSession gameSession = new GameSession(backedUpSubInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
|
GameSession gameSession = new GameSession(backedUpSubInfo, "", GameModePreset.TestMode, CampaignSettings.Empty, null);
|
||||||
gameSession.StartRound(null, false);
|
gameSession.StartRound(null, false);
|
||||||
(gameSession.GameMode as TestGameMode).OnRoundEnd = () =>
|
|
||||||
|
foreach ((string layerName, LayerData layerData) in Layers)
|
||||||
{
|
{
|
||||||
Submarine.Unload();
|
Identifier identifier = layerName.ToIdentifier();
|
||||||
GameMain.SubEditorScreen.Select();
|
bool enabled = layerData.IsVisible;
|
||||||
};
|
MainSub.SetLayerEnabled(identifier, enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (gameSession.GameMode is TestGameMode testGameMode)
|
||||||
|
{
|
||||||
|
testGameMode.OnRoundEnd = () =>
|
||||||
|
{
|
||||||
|
Submarine.Unload();
|
||||||
|
GameMain.SubEditorScreen.Select();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -1469,6 +1450,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var subInfo = new SubmarineInfo();
|
var subInfo = new SubmarineInfo();
|
||||||
MainSub = new Submarine(subInfo, showErrorMessages: false);
|
MainSub = new Submarine(subInfo, showErrorMessages: false);
|
||||||
|
ReconstructLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
MainSub.UpdateTransform(interpolate: false);
|
MainSub.UpdateTransform(interpolate: false);
|
||||||
@@ -1504,7 +1486,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImageManager.OnEditorSelected();
|
ImageManager.OnEditorSelected();
|
||||||
ReconstructLayers();
|
if (Layers.None())
|
||||||
|
{
|
||||||
|
ReconstructLayers();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnFileDropped(string filePath, string extension)
|
public override void OnFileDropped(string filePath, string extension)
|
||||||
@@ -1661,7 +1646,6 @@ namespace Barotrauma
|
|||||||
});
|
});
|
||||||
|
|
||||||
ClearFilter();
|
ClearFilter();
|
||||||
ClearLayers();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateDummyCharacter()
|
private void CreateDummyCharacter()
|
||||||
@@ -2165,32 +2149,32 @@ namespace Barotrauma
|
|||||||
if (Layers.Any())
|
if (Layers.Any())
|
||||||
{
|
{
|
||||||
var layerVisibilityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.01f), leftColumn.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
var layerVisibilityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.01f), leftColumn.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
|
var visibleLayers = Layers.Where(l => !MainSub.Info.LayersHiddenByDefault.Contains(l.Key.ToIdentifier()));
|
||||||
|
LocalizedString visibleLayersString = LocalizedString.Join(", ", visibleLayers.Select(l => TextManager.Capitalize(l.Key)) ?? ((LocalizedString)"None").ToEnumerable());
|
||||||
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layerVisibilityGroup.RectTransform), TextManager.Get("editor.layer.visiblebydefault"), textAlignment: Alignment.CenterLeft);
|
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layerVisibilityGroup.RectTransform), TextManager.Get("editor.layer.visiblebydefault"), textAlignment: Alignment.CenterLeft);
|
||||||
var layerVisibilityDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), layerVisibilityGroup.RectTransform),
|
var layerVisibilityDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1f), layerVisibilityGroup.RectTransform), text: visibleLayersString, selectMultiple: true);
|
||||||
text: LocalizedString.Join(", ", Layers.Where(l => !Submarine.MainSub?.Info?.LayersHiddenByDefault?.Contains(l.ToIdentifier()) ?? false).Select(lt => TextManager.Capitalize(lt.Key)) ?? ((LocalizedString)"None").ToEnumerable()), selectMultiple: true);
|
foreach (var layer in Layers)
|
||||||
foreach (string layerName in Layers.Keys)
|
|
||||||
{
|
{
|
||||||
|
string layerName = layer.Key;
|
||||||
layerVisibilityDropDown.AddItem(TextManager.Capitalize(layerName), layerName);
|
layerVisibilityDropDown.AddItem(TextManager.Capitalize(layerName), layerName);
|
||||||
if (MainSub?.Info == null) { continue; }
|
if (visibleLayers.Contains(layer))
|
||||||
if (!MainSub.Info.LayersHiddenByDefault.Contains(layerName.ToIdentifier()))
|
|
||||||
{
|
{
|
||||||
layerVisibilityDropDown.SelectItem(layerName);
|
layerVisibilityDropDown.SelectItem(layerName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
layerVisibilityDropDown.OnSelected += (_, __) =>
|
layerVisibilityDropDown.OnSelected += (button, obj) =>
|
||||||
{
|
{
|
||||||
if (MainSub.Info == null) { return false; }
|
string layerName = (string)obj;
|
||||||
MainSub.Info.LayersHiddenByDefault.Clear();
|
bool isVisible = layerVisibilityDropDown.SelectedDataMultiple.Contains(obj);
|
||||||
foreach (string layerName in Layers.Keys)
|
if (isVisible)
|
||||||
{
|
{
|
||||||
//selected as visible = not hidden
|
MainSub.Info.LayersHiddenByDefault.Remove(layerName.ToIdentifier());
|
||||||
if (layerVisibilityDropDown.SelectedDataMultiple.Any(o => o as string == layerName))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
MainSub.Info.LayersHiddenByDefault.Add(layerName.ToIdentifier());
|
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
MainSub.Info.LayersHiddenByDefault.Add(layerName.ToIdentifier());
|
||||||
|
}
|
||||||
|
UpdateLayerPanel();
|
||||||
layerVisibilityDropDown.Text = ToolBox.LimitString(layerVisibilityDropDown.Text.Value, layerVisibilityDropDown.Font, layerVisibilityDropDown.Rect.Width);
|
layerVisibilityDropDown.Text = ToolBox.LimitString(layerVisibilityDropDown.Text.Value, layerVisibilityDropDown.Font, layerVisibilityDropDown.Rect.Width);
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -2508,6 +2492,15 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get("allowdamageddevices"))
|
||||||
|
{
|
||||||
|
Selected = MainSub?.Info?.BeaconStationInfo?.AllowDamagedDevices ?? true,
|
||||||
|
OnSelected = (tb) =>
|
||||||
|
{
|
||||||
|
MainSub.Info.BeaconStationInfo.AllowDamagedDevices = tb.Selected;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get("allowdisconnectedwires"))
|
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), beaconSettingsContainer.RectTransform), TextManager.Get("allowdisconnectedwires"))
|
||||||
{
|
{
|
||||||
Selected = MainSub?.Info?.BeaconStationInfo?.AllowDisconnectedWires ?? true,
|
Selected = MainSub?.Info?.BeaconStationInfo?.AllowDisconnectedWires ?? true,
|
||||||
@@ -3932,15 +3925,14 @@ namespace Barotrauma
|
|||||||
MapEntity.HighlightedEntities.ToList() :
|
MapEntity.HighlightedEntities.ToList() :
|
||||||
new List<MapEntity>(MapEntity.SelectedList);
|
new List<MapEntity>(MapEntity.SelectedList);
|
||||||
|
|
||||||
Item target = null;
|
bool allowOpening = false;
|
||||||
|
var targetItem = (targets.Count == 1 ? targets.Single() : null) as Item;
|
||||||
var single = targets.Count == 1 ? targets.Single() : null;
|
// Do not offer the ability to open the inventory if the inventory should never be drawn
|
||||||
if (single is Item item && item.Components.Any(static ic => ic is not ConnectionPanel && ic is not Repairable && ic.GuiFrame != null))
|
allowOpening = targetItem is not null && targetItem.Components.Any(static ic =>
|
||||||
{
|
ic is not ConnectionPanel &&
|
||||||
// Do not offer the ability to open the inventory if the inventory should never be drawn
|
ic is not Repairable &&
|
||||||
var containers = item.GetComponents<ItemContainer>();
|
ic is not ItemContainer { DrawInventory: false } &&
|
||||||
if (containers.Any(static c => c.DrawInventory) || item.GetComponent<CircuitBox>() is not null) { target = item; }
|
ic.GuiFrame != null);
|
||||||
}
|
|
||||||
|
|
||||||
bool hasTargets = targets.Count > 0;
|
bool hasTargets = targets.Count > 0;
|
||||||
|
|
||||||
@@ -3984,7 +3976,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
||||||
List<ContextMenuOption> availableLayers = new List<ContextMenuOption>
|
List<ContextMenuOption> availableLayers = new List<ContextMenuOption>
|
||||||
{
|
{
|
||||||
new ContextMenuOption("editor.layer.nolayer", true, onSelected: () => { MoveToLayer(null, targets); })
|
new ContextMenuOption("editor.layer.nolayer", true, onSelected: () => { MoveToLayer(null, targets); })
|
||||||
@@ -3992,7 +3983,8 @@ namespace Barotrauma
|
|||||||
availableLayers.AddRange(Layers.Select(layer => new ContextMenuOption(layer.Key, true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
|
availableLayers.AddRange(Layers.Select(layer => new ContextMenuOption(layer.Key, true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
|
||||||
|
|
||||||
List<ContextMenuOption> availableLayerOptions = new List<ContextMenuOption>
|
List<ContextMenuOption> availableLayerOptions = new List<ContextMenuOption>
|
||||||
{ new ContextMenuOption("editor.layer.movetolayer", isEnabled: hasTargets, availableLayers.ToArray()),
|
{
|
||||||
|
new ContextMenuOption("editor.layer.movetolayer", isEnabled: hasTargets, availableLayers.ToArray()),
|
||||||
new ContextMenuOption("editor.layer.createlayer", isEnabled: hasTargets, onSelected: () => { CreateNewLayer(null, targets); }),
|
new ContextMenuOption("editor.layer.createlayer", isEnabled: hasTargets, onSelected: () => { CreateNewLayer(null, targets); }),
|
||||||
new ContextMenuOption("editor.layer.selectall", isEnabled: hasTargets, onSelected: () =>
|
new ContextMenuOption("editor.layer.selectall", isEnabled: hasTargets, onSelected: () =>
|
||||||
{
|
{
|
||||||
@@ -4006,7 +3998,7 @@ namespace Barotrauma
|
|||||||
availableLayerOptions.AddRange(Layers.Select(layer => new ContextMenuOption(layer.Key, true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
|
availableLayerOptions.AddRange(Layers.Select(layer => new ContextMenuOption(layer.Key, true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
|
||||||
|
|
||||||
GUIContextMenu.CreateContextMenu(
|
GUIContextMenu.CreateContextMenu(
|
||||||
new ContextMenuOption("label.openlabel", isEnabled: target != null, onSelected: () => OpenItem(target)),
|
new ContextMenuOption("label.openlabel", isEnabled: allowOpening, onSelected: () => OpenItem(targetItem)),
|
||||||
new ContextMenuOption("editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(targets)),
|
new ContextMenuOption("editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(targets)),
|
||||||
new ContextMenuOption("editor.copytoclipboard", isEnabled: hasTargets, onSelected: () => MapEntity.Copy(targets)),
|
new ContextMenuOption("editor.copytoclipboard", isEnabled: hasTargets, onSelected: () => MapEntity.Copy(targets)),
|
||||||
new ContextMenuOption("editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))),
|
new ContextMenuOption("editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))),
|
||||||
@@ -4061,13 +4053,13 @@ namespace Barotrauma
|
|||||||
MoveToLayer(name, content);
|
MoveToLayer(name, content);
|
||||||
}
|
}
|
||||||
|
|
||||||
Layers.Add(name, LayerData.Default);
|
Layers.Add(name, new LayerData());
|
||||||
UpdateLayerPanel();
|
UpdateLayerPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RenameLayer(string original, string newName)
|
private void RenameLayer(string original, string newName)
|
||||||
{
|
{
|
||||||
Layers.Remove(original);
|
Layers.Remove(original, out LayerData originalData);
|
||||||
|
|
||||||
foreach (MapEntity entity in MapEntity.MapEntityList.Where(entity => entity.Layer == original))
|
foreach (MapEntity entity in MapEntity.MapEntityList.Where(entity => entity.Layer == original))
|
||||||
{
|
{
|
||||||
@@ -4076,7 +4068,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!string.IsNullOrWhiteSpace(newName))
|
if (!string.IsNullOrWhiteSpace(newName))
|
||||||
{
|
{
|
||||||
Layers.TryAdd(newName, LayerData.Default);
|
Layers.TryAdd(newName, originalData);
|
||||||
}
|
}
|
||||||
UpdateLayerPanel();
|
UpdateLayerPanel();
|
||||||
}
|
}
|
||||||
@@ -4088,7 +4080,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(entity.Layer))
|
if (!string.IsNullOrWhiteSpace(entity.Layer))
|
||||||
{
|
{
|
||||||
Layers.TryAdd(entity.Layer, LayerData.Default);
|
Layers.TryAdd(entity.Layer, new LayerData(!entity.IsLayerHidden));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateLayerPanel();
|
UpdateLayerPanel();
|
||||||
@@ -4099,6 +4091,18 @@ namespace Barotrauma
|
|||||||
Layers.Clear();
|
Layers.Clear();
|
||||||
UpdateLayerPanel();
|
UpdateLayerPanel();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void SetLayerVisibility(string layerName, bool isVisible)
|
||||||
|
{
|
||||||
|
if (Layers.Remove(layerName, out LayerData layerData))
|
||||||
|
{
|
||||||
|
Layers.Add(layerName, layerData with { IsVisible = isVisible });
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Layers.Add(layerName, new LayerData(isVisible));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void PasteAssembly(string text = null, Vector2? pos = null)
|
private void PasteAssembly(string text = null, Vector2? pos = null)
|
||||||
{
|
{
|
||||||
@@ -4492,39 +4496,39 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Tries to open an item container in the submarine editor using the dummy character
|
/// Tries to open an item in the submarine editor using the dummy character
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="itemContainer">The item we want to open</param>
|
/// <param name="item">The item we want to open</param>
|
||||||
private void OpenItem(Item itemContainer)
|
private void OpenItem(Item item)
|
||||||
{
|
{
|
||||||
if (dummyCharacter == null || itemContainer == null) { return; }
|
if (dummyCharacter == null || item == null) { return; }
|
||||||
|
|
||||||
if ((itemContainer.GetComponent<Holdable>() is { Attached: false } || itemContainer.GetComponent<Wearable>() != null) && itemContainer.GetComponent<ItemContainer>() != null)
|
if ((item.GetComponent<Holdable>() is { Attached: false } || item.GetComponent<Wearable>() != null) && item.GetComponent<ItemContainer>() != null)
|
||||||
{
|
{
|
||||||
// We teleport our dummy character to the item so it appears as the entity stays still when in reality the dummy is holding it
|
// We teleport our dummy character to the item so it appears as the entity stays still when in reality the dummy is holding it
|
||||||
oldItemPosition = itemContainer.SimPosition;
|
oldItemPosition = item.SimPosition;
|
||||||
TeleportDummyCharacter(oldItemPosition);
|
TeleportDummyCharacter(oldItemPosition);
|
||||||
|
|
||||||
// Override this so we can be sure the container opens
|
// Override this so we can be sure the container opens
|
||||||
var container = itemContainer.GetComponent<ItemContainer>();
|
var container = item.GetComponent<ItemContainer>();
|
||||||
if (container != null) { container.KeepOpenWhenEquipped = true; }
|
if (container != null) { container.KeepOpenWhenEquipped = true; }
|
||||||
|
|
||||||
// We accept any slots except "Any" since that would take priority
|
// We accept any slots except "Any" since that would take priority
|
||||||
List<InvSlotType> allowedSlots = new List<InvSlotType>();
|
List<InvSlotType> allowedSlots = new List<InvSlotType>();
|
||||||
itemContainer.AllowedSlots.ForEach(type =>
|
item.AllowedSlots.ForEach(type =>
|
||||||
{
|
{
|
||||||
if (type != InvSlotType.Any) { allowedSlots.Add(type); }
|
if (type != InvSlotType.Any) { allowedSlots.Add(type); }
|
||||||
});
|
});
|
||||||
|
|
||||||
// Try to place the item in the dummy character's inventory
|
// Try to place the item in the dummy character's inventory
|
||||||
bool success = dummyCharacter.Inventory.TryPutItem(itemContainer, dummyCharacter, allowedSlots);
|
bool success = dummyCharacter.Inventory.TryPutItem(item, dummyCharacter, allowedSlots);
|
||||||
if (success) { OpenedItem = itemContainer; }
|
if (success) { OpenedItem = item; }
|
||||||
else { return; }
|
else { return; }
|
||||||
}
|
}
|
||||||
MapEntity.SelectedList.Clear();
|
MapEntity.SelectedList.Clear();
|
||||||
MapEntity.FilteredSelectedList.Clear();
|
MapEntity.FilteredSelectedList.Clear();
|
||||||
MapEntity.SelectEntity(itemContainer);
|
MapEntity.SelectEntity(item);
|
||||||
dummyCharacter.SelectedItem = itemContainer;
|
dummyCharacter.SelectedItem = item;
|
||||||
FilterEntities(entityFilterBox.Text);
|
FilterEntities(entityFilterBox.Text);
|
||||||
MapEntity.StopSelection();
|
MapEntity.StopSelection();
|
||||||
}
|
}
|
||||||
@@ -5176,7 +5180,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
new GUIButton(new RectTransform(new Vector2(0.6f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes };
|
new GUIButton(new RectTransform(new Vector2(0.6f, 1f), buttonHeaders.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale") { ForceUpperCase = ForceUpperCase.Yes };
|
||||||
|
|
||||||
foreach (var (layer, (visibility, linkage)) in Layers)
|
foreach ((string layer, (bool isVisible, bool isGrouped)) in Layers)
|
||||||
{
|
{
|
||||||
GUIFrame parent = new GUIFrame(new RectTransform(new Vector2(1f, 0.1f), layerList.Content.RectTransform), style: "ListBoxElement")
|
GUIFrame parent = new GUIFrame(new RectTransform(new Vector2(1f, 0.1f), layerList.Content.RectTransform), style: "ListBoxElement")
|
||||||
{
|
{
|
||||||
@@ -5188,7 +5192,7 @@ namespace Barotrauma
|
|||||||
GUILayoutGroup layerVisibilityLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1f), layerGroup.RectTransform), childAnchor: Anchor.Center);
|
GUILayoutGroup layerVisibilityLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.25f, 1f), layerGroup.RectTransform), childAnchor: Anchor.Center);
|
||||||
GUITickBox layerVisibleButton = new GUITickBox(new RectTransform(Vector2.One, layerVisibilityLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
|
GUITickBox layerVisibleButton = new GUITickBox(new RectTransform(Vector2.One, layerVisibilityLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
|
||||||
{
|
{
|
||||||
Selected = visibility == LayerVisibility.Visible,
|
Selected = isVisible,
|
||||||
OnSelected = box =>
|
OnSelected = box =>
|
||||||
{
|
{
|
||||||
if (!Layers.TryGetValue(layer, out LayerData data))
|
if (!Layers.TryGetValue(layer, out LayerData data))
|
||||||
@@ -5196,12 +5200,15 @@ namespace Barotrauma
|
|||||||
UpdateLayerPanel();
|
UpdateLayerPanel();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
//hiding a layer automatically deselects it (can't edit a hidden layer)
|
|
||||||
if (!box.Selected && layerList.SelectedData as string == layer)
|
if (!box.Selected && layerList.SelectedData as string == layer)
|
||||||
{
|
{
|
||||||
layerList.Deselect();
|
//hiding a layer automatically deselects it (can't edit a hidden layer)
|
||||||
|
if (!box.Selected)
|
||||||
|
{
|
||||||
|
layerList.Deselect();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Layers[layer] = new LayerData(box.Selected ? LayerVisibility.Visible : LayerVisibility.Invisible, data.Linkage);
|
Layers[layer] = data with { IsVisible = box.Selected };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -5209,7 +5216,7 @@ namespace Barotrauma
|
|||||||
GUILayoutGroup layerChainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 1f), layerGroup.RectTransform), childAnchor: Anchor.Center);
|
GUILayoutGroup layerChainLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.15f, 1f), layerGroup.RectTransform), childAnchor: Anchor.Center);
|
||||||
GUITickBox layerChainButton = new GUITickBox(new RectTransform(Vector2.One, layerChainLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
|
GUITickBox layerChainButton = new GUITickBox(new RectTransform(Vector2.One, layerChainLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
|
||||||
{
|
{
|
||||||
Selected = linkage == LayerLinkage.Linked,
|
Selected = isGrouped,
|
||||||
OnSelected = box =>
|
OnSelected = box =>
|
||||||
{
|
{
|
||||||
if (!Layers.TryGetValue(layer, out LayerData data))
|
if (!Layers.TryGetValue(layer, out LayerData data))
|
||||||
@@ -5218,7 +5225,7 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Layers[layer] = new LayerData(data.Visible, box.Selected ? LayerLinkage.Linked : LayerLinkage.Unlinked);
|
Layers[layer] = data with { IsGrouped = box.Selected };
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -5249,7 +5256,6 @@ namespace Barotrauma
|
|||||||
btn.ToolTip = originalBtnText;
|
btn.ToolTip = originalBtnText;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateUndoHistoryPanel()
|
public void UpdateUndoHistoryPanel()
|
||||||
@@ -6303,11 +6309,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!Layers.TryGetValue(entity.Layer, out LayerData data))
|
if (!Layers.TryGetValue(entity.Layer, out LayerData data))
|
||||||
{
|
{
|
||||||
Layers.TryAdd(entity.Layer, LayerData.Default);
|
Layers.TryAdd(entity.Layer, new LayerData(!entity.IsLayerHidden));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Visible == LayerVisibility.Visible;
|
return data.IsVisible;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsLayerLinked(MapEntity entity)
|
public static bool IsLayerLinked(MapEntity entity)
|
||||||
@@ -6316,11 +6322,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!Layers.TryGetValue(entity.Layer, out LayerData data))
|
if (!Layers.TryGetValue(entity.Layer, out LayerData data))
|
||||||
{
|
{
|
||||||
Layers.TryAdd(entity.Layer, LayerData.Default);
|
Layers.TryAdd(entity.Layer, new LayerData(!entity.IsLayerHidden));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Linkage == LayerLinkage.Linked;
|
return data.IsGrouped;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ImmutableHashSet<MapEntity> GetEntitiesInSameLayer(MapEntity entity)
|
public static ImmutableHashSet<MapEntity> GetEntitiesInSameLayer(MapEntity entity)
|
||||||
|
|||||||
@@ -326,7 +326,7 @@ namespace Barotrauma
|
|||||||
public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, IEnumerable<SerializableProperty> properties, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null)
|
public SerializableEntityEditor(RectTransform parent, ISerializableEntity entity, IEnumerable<SerializableProperty> properties, bool showName, string style = "", int elementHeight = 24, GUIFont titleFont = null)
|
||||||
: base(style, new RectTransform(Vector2.One, parent))
|
: base(style, new RectTransform(Vector2.One, parent))
|
||||||
{
|
{
|
||||||
this.elementHeight = (int)(elementHeight * GUI.Scale);
|
elementHeight = (int)(elementHeight * GUI.Scale);
|
||||||
var tickBoxStyle = GUIStyle.GetComponentStyle("GUITickBox");
|
var tickBoxStyle = GUIStyle.GetComponentStyle("GUITickBox");
|
||||||
var textBoxStyle = GUIStyle.GetComponentStyle("GUITextBox");
|
var textBoxStyle = GUIStyle.GetComponentStyle("GUITextBox");
|
||||||
var numberInputStyle = GUIStyle.GetComponentStyle("GUINumberInput");
|
var numberInputStyle = GUIStyle.GetComponentStyle("GUINumberInput");
|
||||||
@@ -343,7 +343,50 @@ namespace Barotrauma
|
|||||||
Color = Color.Black
|
Color = Color.Black
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
properties.ForEach(ep => CreateNewField(ep, entity));
|
|
||||||
|
List<Header> headers = new List<Header>()
|
||||||
|
{
|
||||||
|
//"no header" comes first = properties under no header are listed first
|
||||||
|
null
|
||||||
|
};
|
||||||
|
//check which header each property is under
|
||||||
|
Dictionary<SerializableProperty, Header> propertyHeaders = new Dictionary<SerializableProperty, Header>();
|
||||||
|
Header prevHeader = null;
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
var header = property.GetAttribute<Header>();
|
||||||
|
if (header != null)
|
||||||
|
{
|
||||||
|
prevHeader = header;
|
||||||
|
//Attribute.Equals is based on the equality of the fields,
|
||||||
|
//so in practice we treat identical headers split into different files/classes as the same header
|
||||||
|
if (!headers.Contains(header))
|
||||||
|
{
|
||||||
|
//collect headers into a list in the order they're encountered in
|
||||||
|
//(to keep them in the same order as they're defined in the code, as the dictionary is not in any particular order)
|
||||||
|
headers.Add(header);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
propertyHeaders[property] = prevHeader;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevHeader = null;
|
||||||
|
foreach (Header header in headers)
|
||||||
|
{
|
||||||
|
//go through all the properties that belong under this header
|
||||||
|
foreach (var property in properties)
|
||||||
|
{
|
||||||
|
if (!Equals(propertyHeaders[property], header)) { continue; }
|
||||||
|
//don't create a header if the previous header has the same text as this one (= if we already created this header before)
|
||||||
|
if (header != null && !Equals(header, prevHeader))
|
||||||
|
{
|
||||||
|
new GUITextBlock(new RectTransform(new Point(Rect.Width, Math.Max(elementHeight, 26)), layoutGroup.RectTransform, isFixedSize: true),
|
||||||
|
header.Text, textColor: GUIStyle.TextColorBright, font: GUIStyle.SubHeadingFont);
|
||||||
|
prevHeader = header;
|
||||||
|
}
|
||||||
|
CreateNewField(property, entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//scale the size of this component and the layout group to fit the children
|
//scale the size of this component and the layout group to fit the children
|
||||||
Recalculate();
|
Recalculate();
|
||||||
|
|||||||
@@ -137,22 +137,10 @@ namespace Barotrauma.Sounds
|
|||||||
{
|
{
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
outBuffer[i] = FloatToShort(inBuffer[i]);
|
outBuffer[i] = ToolBox.FloatToShortAudioSample(inBuffer[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static protected short FloatToShort(float fVal)
|
|
||||||
{
|
|
||||||
int temp = (int)(32767 * fVal);
|
|
||||||
if (temp > short.MaxValue) temp = short.MaxValue;
|
|
||||||
else if (temp < short.MinValue) temp = short.MinValue;
|
|
||||||
return (short)temp;
|
|
||||||
}
|
|
||||||
static protected float ShortToFloat(short shortVal)
|
|
||||||
{
|
|
||||||
return shortVal / 32767f;
|
|
||||||
}
|
|
||||||
|
|
||||||
public abstract int FillStreamBuffer(int samplePos, short[] buffer);
|
public abstract int FillStreamBuffer(int samplePos, short[] buffer);
|
||||||
|
|
||||||
public abstract float GetAmplitudeAtPlaybackPos(int playbackPos);
|
public abstract float GetAmplitudeAtPlaybackPos(int playbackPos);
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ namespace Barotrauma.Sounds
|
|||||||
float finalGain = gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume * client.VoiceVolume;
|
float finalGain = gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume * client.VoiceVolume;
|
||||||
for (int i = 0; i < readSamples; i++)
|
for (int i = 0; i < readSamples; i++)
|
||||||
{
|
{
|
||||||
float fVal = ShortToFloat(buffer[i]);
|
float fVal = ToolBox.ShortAudioSampleToFloat(buffer[i]);
|
||||||
|
|
||||||
if (finalGain > 1.0f) //TODO: take distance into account?
|
if (finalGain > 1.0f) //TODO: take distance into account?
|
||||||
{
|
{
|
||||||
@@ -128,7 +128,7 @@ namespace Barotrauma.Sounds
|
|||||||
fVal = Math.Clamp(filter.Process(fVal) * PostRadioFilterBoost, -1f, 1f);
|
fVal = Math.Clamp(filter.Process(fVal) * PostRadioFilterBoost, -1f, 1f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
buffer[i] = FloatToShort(fVal);
|
buffer[i] = ToolBox.FloatToShortAudioSample(fVal);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -99,10 +99,10 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
currentLobby?.SetData("EosEndpoint", puids[0].Value);
|
currentLobby?.SetData("EosEndpoint", puids[0].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
DebugConsole.Log("Lobby updated!");
|
DebugConsole.Log("Lobby updated!");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void SetServerListInfo(Identifier key, object value)
|
private static void SetServerListInfo(Identifier key, object value)
|
||||||
{
|
{
|
||||||
switch (value)
|
switch (value)
|
||||||
@@ -115,7 +115,7 @@ namespace Barotrauma.Steam
|
|||||||
.JoinEscaped(','));
|
.JoinEscaped(','));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentLobby?.SetData(key.Value.ToLowerInvariant(), value.ToString());
|
currentLobby?.SetData(key.Value.ToLowerInvariant(), value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.4.6.0</Version>
|
<Version>1.5.9.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.4.6.0</Version>
|
<Version>1.5.9.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
|
|||||||
@@ -6,8 +6,8 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.4.6.0</Version>
|
<Version>1.5.9.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.4.6.0</Version>
|
<Version>1.5.9.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
@@ -64,12 +64,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(Configuration)'!='Debug'">
|
<ItemGroup Condition="'$(Configuration)'!='Debug'">
|
||||||
|
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.4.6.0</Version>
|
<Version>1.5.9.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
@@ -70,12 +70,14 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(Configuration)'!='Debug'">
|
<ItemGroup Condition="'$(Configuration)'!='Debug'">
|
||||||
|
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Lidgren.Network\Lidgren.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Posix.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -26,6 +27,27 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GameMain.Server is { ServerSettings.RespawnMode: RespawnMode.Permadeath } &&
|
||||||
|
GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign &&
|
||||||
|
causeOfDeath != CauseOfDeathType.Disconnected)
|
||||||
|
{
|
||||||
|
Client ownerClient = GameMain.Server.ConnectedClients.FirstOrDefault(c => c.Character == this);
|
||||||
|
if (ownerClient != null)
|
||||||
|
{
|
||||||
|
ownerClient.SpectateOnly = true;
|
||||||
|
CharacterCampaignData matchingData = mpCampaign.GetClientCharacterData(ownerClient);
|
||||||
|
if (matchingData != null)
|
||||||
|
{
|
||||||
|
matchingData.ApplyPermadeath();
|
||||||
|
|
||||||
|
if (GameMain.Server is { ServerSettings.IronmanMode: true })
|
||||||
|
{
|
||||||
|
mpCampaign.SaveSingleCharacter(matchingData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter))
|
if (HasAbilityFlag(AbilityFlags.RetainExperienceForNewCharacter))
|
||||||
{
|
{
|
||||||
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
var ownerClient = GameMain.Server.ConnectedClients.Find(c => c.Character == this);
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ namespace Barotrauma
|
|||||||
msg.WriteUInt16(ID);
|
msg.WriteUInt16(ID);
|
||||||
msg.WriteString(Name);
|
msg.WriteString(Name);
|
||||||
msg.WriteString(OriginalName);
|
msg.WriteString(OriginalName);
|
||||||
|
msg.WriteBoolean(RenamingEnabled);
|
||||||
msg.WriteByte((byte)Head.Preset.TagSet.Count);
|
msg.WriteByte((byte)Head.Preset.TagSet.Count);
|
||||||
foreach (Identifier tag in Head.Preset.TagSet)
|
foreach (Identifier tag in Head.Preset.TagSet)
|
||||||
{
|
{
|
||||||
@@ -96,6 +97,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
msg.WriteInt32(ExperiencePoints);
|
msg.WriteInt32(ExperiencePoints);
|
||||||
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
|
msg.WriteRangedInteger(AdditionalTalentPoints, 0, MaxAdditionalTalentPoints);
|
||||||
|
msg.WriteBoolean(PermanentlyDead);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,6 +459,7 @@ namespace Barotrauma
|
|||||||
Client owner = controlEventData.Owner;
|
Client owner = controlEventData.Owner;
|
||||||
msg.WriteBoolean(owner == c && owner.Character == this);
|
msg.WriteBoolean(owner == c && owner.Character == this);
|
||||||
msg.WriteByte(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0);
|
msg.WriteByte(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.SessionId : (byte)0);
|
||||||
|
msg.WriteBoolean(info is { RenamingEnabled: true });
|
||||||
break;
|
break;
|
||||||
case CharacterStatusEventData statusEventData:
|
case CharacterStatusEventData statusEventData:
|
||||||
WriteStatus(msg, statusEventData.ForceAfflictionData);
|
WriteStatus(msg, statusEventData.ForceAfflictionData);
|
||||||
|
|||||||
@@ -963,7 +963,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
client.Muted = true;
|
client.Muted = true;
|
||||||
GameMain.Server.SendDirectChatMessage(TextManager.Get("MutedByServer").Value, client, ChatMessageType.MessageBox);
|
GameMain.Server.SendDirectChatMessage(TextManager.Get("MutedByServer").Value, client, ChatMessageType.MessageBox);
|
||||||
},
|
},
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
if (GameMain.Server == null) return null;
|
if (GameMain.Server == null) return null;
|
||||||
@@ -1599,6 +1599,19 @@ namespace Barotrauma
|
|||||||
NewMessage("Disabled RequireClientNameMatch");
|
NewMessage("Disabled RequireClientNameMatch");
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
AssignOnExecute("debugvoip", _ =>
|
||||||
|
{
|
||||||
|
VoipServerDecoder.DebugVoip = !VoipServerDecoder.DebugVoip;
|
||||||
|
NewMessage("Debugging voice chat is now " + (VoipServerDecoder.DebugVoip ? "enabled" : "disabled"), Color.White);
|
||||||
|
});
|
||||||
|
|
||||||
|
AssignOnClientRequestExecute("debugvoip", (client, _, _) =>
|
||||||
|
{
|
||||||
|
VoipServerDecoder.DebugVoip = !VoipServerDecoder.DebugVoip;
|
||||||
|
NewMessage("Debugging voice chat is now " + (VoipServerDecoder.DebugVoip ? "enabled" : "disabled") + " by " + client.Name, Color.White);
|
||||||
|
GameMain.Server.SendConsoleMessage("Debugging voice chat is now " + (VoipServerDecoder.DebugVoip ? "enabled" : "disabled"), client);
|
||||||
|
});
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
AssignOnClientRequestExecute(
|
AssignOnClientRequestExecute(
|
||||||
@@ -1761,11 +1774,7 @@ namespace Barotrauma
|
|||||||
"teleportcharacter|teleport",
|
"teleportcharacter|teleport",
|
||||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||||
{
|
{
|
||||||
Character tpCharacter = (args.Length == 0) ? client.Character : FindMatchingCharacter(args, false);
|
TeleportCharacter(cursorWorldPos, client.Character, args);
|
||||||
if (tpCharacter != null)
|
|
||||||
{
|
|
||||||
tpCharacter.TeleportTo(cursorWorldPos);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1922,6 +1931,17 @@ namespace Barotrauma
|
|||||||
foreach (Client c in GameMain.Server.ConnectedClients)
|
foreach (Client c in GameMain.Server.ConnectedClients)
|
||||||
{
|
{
|
||||||
if (c.Character != revivedCharacter) { continue; }
|
if (c.Character != revivedCharacter) { continue; }
|
||||||
|
|
||||||
|
// If killed in ironman mode, the character has been wiped from the save mid-round, so its
|
||||||
|
// original data needs to be restored to the save file (without making a backup of the dead character)
|
||||||
|
if (GameMain.Server.ServerSettings.IronmanMode && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
|
||||||
|
{
|
||||||
|
if (mpCampaign.RestoreSingleCharacterFromBackup(c) is CharacterCampaignData characterToRestore)
|
||||||
|
{
|
||||||
|
characterToRestore.CharacterInfo.PermanentlyDead = false;
|
||||||
|
mpCampaign.SaveSingleCharacter(characterToRestore, skipBackup: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//clients stop controlling the character when it dies, force control back
|
//clients stop controlling the character when it dies, force control back
|
||||||
GameMain.Server.SetClientCharacter(c, revivedCharacter);
|
GameMain.Server.SetClientCharacter(c, revivedCharacter);
|
||||||
@@ -2545,6 +2565,91 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
commands.Add(new Command("setsalary", "setsalary [0-100] [character/default]: Sets the salary of a certain character or the default salary to a percentage.", (string[] args) =>
|
||||||
|
{
|
||||||
|
if (args.Length < 2)
|
||||||
|
{
|
||||||
|
NewMessage($"Missing arguments. Expected at least 2 but got {args.Length} (amount, character)", Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign mpCampaign)
|
||||||
|
{
|
||||||
|
NewMessage("No campaign active.", Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(args[0], out int amount))
|
||||||
|
{
|
||||||
|
NewMessage($"{args[0]} is not a valid amount.", Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args[1].Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mpCampaign.Bank.SetRewardDistribution(amount);
|
||||||
|
NewMessage($"Set the default salary to {amount}%", Color.White);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Character character = FindMatchingCharacter(args.Skip(1).ToArray());
|
||||||
|
if (character is null)
|
||||||
|
{
|
||||||
|
NewMessage($"Character not found \"{args[1]}\".", Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
character.Wallet.SetRewardDistribution(amount);
|
||||||
|
NewMessage($"Set {character.Name}'s salary to {amount}%", Color.White);
|
||||||
|
}));
|
||||||
|
|
||||||
|
AssignOnClientRequestExecute(
|
||||||
|
"setsalary",
|
||||||
|
(senderClient, cursorWorldPos, args) =>
|
||||||
|
{
|
||||||
|
if (args.Length < 2)
|
||||||
|
{
|
||||||
|
GameMain.Server.SendConsoleMessage($"Missing arguments. Expected at least 2 but got {args.Length} (amount, character)", senderClient, Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!CampaignMode.AllowedToManageWallets(senderClient))
|
||||||
|
{
|
||||||
|
GameMain.Server.SendConsoleMessage("You are not allowed to manage wallets.", senderClient, Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameMain.GameSession?.Campaign is not MultiPlayerCampaign mpCampaign)
|
||||||
|
{
|
||||||
|
GameMain.Server.SendConsoleMessage("No campaign active.", senderClient, Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!int.TryParse(args[0], out int amount))
|
||||||
|
{
|
||||||
|
GameMain.Server.SendConsoleMessage($"{args[0]} is not a valid amount.", senderClient, Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args[1].Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
mpCampaign.Bank.SetRewardDistribution(amount);
|
||||||
|
GameMain.Server.SendConsoleMessage($"Set the default salary to {amount}%", senderClient);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Character character = FindMatchingCharacter(args.Skip(1).ToArray());
|
||||||
|
if (character is null)
|
||||||
|
{
|
||||||
|
GameMain.Server.SendConsoleMessage($"Character not found \"{args[1]}\".", senderClient, Color.Red);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
character.Wallet.SetRewardDistribution(amount);
|
||||||
|
GameMain.Server.SendConsoleMessage($"Set {character.Name}'s salary to {amount}%.", senderClient);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
commands.Add(new Command("readycheck", "Commence a ready check.", (string[] args) =>
|
commands.Add(new Command("readycheck", "Commence a ready check.", (string[] args) =>
|
||||||
{
|
{
|
||||||
if (Screen.Selected == GameMain.GameScreen && GameMain.NetworkMember != null)
|
if (Screen.Selected == GameMain.GameScreen && GameMain.NetworkMember != null)
|
||||||
@@ -2607,7 +2712,7 @@ namespace Barotrauma
|
|||||||
}));
|
}));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ServerRead(IReadMessage inc, Client sender)
|
public static void ServerRead(IReadMessage inc, Client sender)
|
||||||
{
|
{
|
||||||
string consoleCommand = inc.ReadString();
|
string consoleCommand = inc.ReadString();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Barotrauma.Networking;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class AlienRuinMission : Mission
|
partial class EliminateTargetsMission : Mission
|
||||||
{
|
{
|
||||||
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
public override void ServerWriteInitial(IWriteMessage msg, Client c)
|
||||||
{
|
{
|
||||||
@@ -123,6 +123,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public bool IsDuplicate(CharacterCampaignData other)
|
public bool IsDuplicate(CharacterCampaignData other)
|
||||||
{
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (RequireClientNameMatch)
|
||||||
|
{
|
||||||
|
return AccountId == other.AccountId && other.ClientAddress == ClientAddress && Name == other.Name;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
return AccountId == other.AccountId && other.ClientAddress == ClientAddress;
|
return AccountId == other.AccountId && other.ClientAddress == ClientAddress;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,6 +139,13 @@ namespace Barotrauma
|
|||||||
WalletData = null;
|
WalletData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ApplyPermadeath()
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
CharacterInfo.PermanentlyDead = true;
|
||||||
|
DebugConsole.NewMessage($"Permadeath applied on {Name}'s CharacterCampaignData.CharacterInfo.");
|
||||||
|
}
|
||||||
|
|
||||||
public void SpawnInventoryItems(Character character, Inventory inventory)
|
public void SpawnInventoryItems(Character character, Inventory inventory)
|
||||||
{
|
{
|
||||||
if (character == null)
|
if (character == null)
|
||||||
@@ -158,7 +171,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void ApplyWalletData(Character character)
|
public void ApplyWalletData(Character character)
|
||||||
{
|
{
|
||||||
character.Wallet = new Wallet(Option<Character>.Some(character), WalletData);
|
character.Wallet = new Wallet(Option.Some(character), WalletData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public XElement Save()
|
public XElement Save()
|
||||||
@@ -167,7 +180,6 @@ namespace Barotrauma
|
|||||||
new XAttribute("name", Name),
|
new XAttribute("name", Name),
|
||||||
new XAttribute("address", ClientAddress),
|
new XAttribute("address", ClientAddress),
|
||||||
new XAttribute("accountid", AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : ""));
|
new XAttribute("accountid", AccountId.TryUnwrap(out var accountId) ? accountId.StringRepresentation : ""));
|
||||||
|
|
||||||
CharacterInfo?.Save(element);
|
CharacterInfo?.Save(element);
|
||||||
if (itemData != null) { element.Add(itemData); }
|
if (itemData != null) { element.Add(itemData); }
|
||||||
if (healthData != null) { element.Add(healthData); }
|
if (healthData != null) { element.Add(healthData); }
|
||||||
|
|||||||
@@ -16,6 +16,12 @@ namespace Barotrauma
|
|||||||
private readonly HashSet<NetWalletTransaction> transactions = new HashSet<NetWalletTransaction>();
|
private readonly HashSet<NetWalletTransaction> transactions = new HashSet<NetWalletTransaction>();
|
||||||
private const float clientCheckInterval = 10;
|
private const float clientCheckInterval = 10;
|
||||||
private float clientCheckTimer = clientCheckInterval;
|
private float clientCheckTimer = clientCheckInterval;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Temporary backup storage for characters that have been overwritten by SaveSingleCharacter, this will be gone
|
||||||
|
/// once the round ends or the server closes. Currently needed to enable the console command "revive" in ironman mode.
|
||||||
|
/// </summary>
|
||||||
|
public List<CharacterCampaignData> replacedCharacterDataBackup = new List<CharacterCampaignData>();
|
||||||
|
|
||||||
public override Wallet GetWallet(Client client = null)
|
public override Wallet GetWallet(Client client = null)
|
||||||
{
|
{
|
||||||
@@ -295,6 +301,12 @@ namespace Barotrauma
|
|||||||
MoveDiscardedCharacterBalancesToBank();
|
MoveDiscardedCharacterBalancesToBank();
|
||||||
|
|
||||||
characterData.ForEach(cd => cd.HasSpawned = false);
|
characterData.ForEach(cd => cd.HasSpawned = false);
|
||||||
|
foreach (var cd in characterData)
|
||||||
|
{
|
||||||
|
//remove from crewmanager - we don't need to save the data there if it's been saved as CharacterCampaignData
|
||||||
|
//(e.g. if a client has taken over a bot, we need to do this to prevent it being saved twice)
|
||||||
|
CrewManager.RemoveCharacterInfo(cd.CharacterInfo);
|
||||||
|
}
|
||||||
|
|
||||||
SavePets();
|
SavePets();
|
||||||
|
|
||||||
@@ -380,6 +392,9 @@ namespace Barotrauma
|
|||||||
GameMain.GameSession.EventManager.RegisterEventHistory();
|
GameMain.GameSession.EventManager.RegisterEventHistory();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//store the currently active missions at this point so we can communicate their states to clients, they're cleared in EndRound
|
||||||
|
List<Mission> missions = GameMain.GameSession.Missions.ToList();
|
||||||
|
|
||||||
GameMain.GameSession.EndRound("", transitionType);
|
GameMain.GameSession.EndRound("", transitionType);
|
||||||
|
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
@@ -407,7 +422,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
|
|
||||||
GameMain.Server.EndGame(transitionType, wasSaved: true);
|
GameMain.Server.EndGame(transitionType, wasSaved: true, missions);
|
||||||
|
|
||||||
ForceMapUI = false;
|
ForceMapUI = false;
|
||||||
|
|
||||||
@@ -513,6 +528,9 @@ namespace Barotrauma
|
|||||||
Map?.Radiation?.UpdateRadiation(deltaTime);
|
Map?.Radiation?.UpdateRadiation(deltaTime);
|
||||||
|
|
||||||
base.Update(deltaTime);
|
base.Update(deltaTime);
|
||||||
|
|
||||||
|
MedicalClinic?.Update(deltaTime);
|
||||||
|
|
||||||
if (Level.Loaded != null)
|
if (Level.Loaded != null)
|
||||||
{
|
{
|
||||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
||||||
@@ -1165,51 +1183,73 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!AllowedToManageWallets(sender)) { return; }
|
if (!AllowedToManageWallets(sender)) { return; }
|
||||||
|
|
||||||
Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == update.Target);
|
if (update.Target.TryUnwrap(out ushort id))
|
||||||
targetCharacter?.Wallet.SetRewardDistribution(update.NewRewardDistribution);
|
{
|
||||||
GameServer.Log($"{sender.Name} changed the salary of {targetCharacter?.Name ?? "the bank"} to {update.NewRewardDistribution}%.", ServerLog.MessageType.Money);
|
Character targetCharacter = Character.CharacterList.FirstOrDefault(c => c.ID == id);
|
||||||
|
targetCharacter?.Wallet.SetRewardDistribution(update.NewRewardDistribution);
|
||||||
|
GameServer.Log($"{sender.Name} changed the salary of {targetCharacter?.Name} to {update.NewRewardDistribution}%.", ServerLog.MessageType.Money);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bank.SetRewardDistribution(update.NewRewardDistribution);
|
||||||
|
GameServer.Log($"{sender.Name} changed the default salary to {update.NewRewardDistribution}%.", ServerLog.MessageType.Money);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ResetSalaries(Client sender)
|
||||||
|
{
|
||||||
|
if (!AllowedToManageWallets(sender)) { return; }
|
||||||
|
|
||||||
|
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Player))
|
||||||
|
{
|
||||||
|
character.Wallet.SetRewardDistribution(Bank.RewardDistribution);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ServerReadCrew(IReadMessage msg, Client sender)
|
public void ServerReadCrew(IReadMessage msg, Client sender)
|
||||||
{
|
{
|
||||||
int[] pendingHires = null;
|
UInt16[] pendingHires = null;
|
||||||
|
|
||||||
bool updatePending = msg.ReadBoolean();
|
bool updatePending = msg.ReadBoolean();
|
||||||
if (updatePending)
|
if (updatePending)
|
||||||
{
|
{
|
||||||
ushort pendingHireLength = msg.ReadUInt16();
|
ushort pendingHireLength = msg.ReadUInt16();
|
||||||
pendingHires = new int[pendingHireLength];
|
pendingHires = new UInt16[pendingHireLength];
|
||||||
for (int i = 0; i < pendingHireLength; i++)
|
for (int i = 0; i < pendingHireLength; i++)
|
||||||
{
|
{
|
||||||
pendingHires[i] = msg.ReadInt32();
|
pendingHires[i] = msg.ReadUInt16();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool validateHires = msg.ReadBoolean();
|
bool validateHires = msg.ReadBoolean();
|
||||||
|
|
||||||
bool renameCharacter = msg.ReadBoolean();
|
bool renameCharacter = msg.ReadBoolean();
|
||||||
int renamedIdentifier = -1;
|
UInt16 renamedIdentifier = 0;
|
||||||
string newName = null;
|
string newName = null;
|
||||||
bool existingCrewMember = false;
|
bool existingCrewMember = false;
|
||||||
if (renameCharacter)
|
if (renameCharacter)
|
||||||
{
|
{
|
||||||
renamedIdentifier = msg.ReadInt32();
|
renamedIdentifier = msg.ReadUInt16();
|
||||||
newName = msg.ReadString();
|
newName = msg.ReadString();
|
||||||
existingCrewMember = msg.ReadBoolean();
|
existingCrewMember = msg.ReadBoolean();
|
||||||
|
if (!GameMain.Server.IsNameValid(sender, newName))
|
||||||
|
{
|
||||||
|
renameCharacter = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool fireCharacter = msg.ReadBoolean();
|
bool fireCharacter = msg.ReadBoolean();
|
||||||
int firedIdentifier = -1;
|
int firedIdentifier = -1;
|
||||||
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
|
if (fireCharacter) { firedIdentifier = msg.ReadUInt16(); }
|
||||||
|
|
||||||
Location location = map?.CurrentLocation;
|
Location location = map?.CurrentLocation;
|
||||||
CharacterInfo firedCharacter = null;
|
CharacterInfo firedCharacter = null;
|
||||||
|
(ushort id, string newName) appliedRename = (Entity.NullEntityID, string.Empty);
|
||||||
|
|
||||||
if (location != null && AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
|
if (location != null)
|
||||||
{
|
{
|
||||||
if (fireCharacter)
|
if (fireCharacter && AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
|
||||||
{
|
{
|
||||||
firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.GetIdentifier() == firedIdentifier);
|
firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == firedIdentifier);
|
||||||
if (firedCharacter != null && (firedCharacter.Character?.IsBot ?? true))
|
if (firedCharacter != null && (firedCharacter.Character?.IsBot ?? true))
|
||||||
{
|
{
|
||||||
CrewManager.FireCharacter(firedCharacter);
|
CrewManager.FireCharacter(firedCharacter);
|
||||||
@@ -1223,29 +1263,45 @@ namespace Barotrauma
|
|||||||
if (renameCharacter)
|
if (renameCharacter)
|
||||||
{
|
{
|
||||||
CharacterInfo characterInfo = null;
|
CharacterInfo characterInfo = null;
|
||||||
if (existingCrewMember && CrewManager != null)
|
if (AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
|
||||||
{
|
{
|
||||||
characterInfo = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
|
if (existingCrewMember && CrewManager != null)
|
||||||
|
{
|
||||||
|
characterInfo = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == renamedIdentifier);
|
||||||
|
}
|
||||||
|
else if (!existingCrewMember && location.HireManager != null)
|
||||||
|
{
|
||||||
|
characterInfo = location.HireManager.AvailableCharacters.FirstOrDefault(info => info.ID == renamedIdentifier);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if(!existingCrewMember && location.HireManager != null)
|
if (characterInfo == null && renamedIdentifier == sender.CharacterInfo?.ID)
|
||||||
{
|
{
|
||||||
characterInfo = location.HireManager.AvailableCharacters.FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == renamedIdentifier);
|
characterInfo = sender.CharacterInfo;
|
||||||
}
|
}
|
||||||
|
if (characterInfo != null &&
|
||||||
if (characterInfo != null && (characterInfo.Character?.IsBot ?? true))
|
(characterInfo.Character == null || characterInfo.Character is { IsBot: true } || (characterInfo.RenamingEnabled && characterInfo == sender.CharacterInfo)))
|
||||||
{
|
{
|
||||||
|
GameServer.Log($"{sender.Name} renamed the character \"{characterInfo.Name}\" as \"{newName}\".", ServerLog.MessageType.ServerMessage);
|
||||||
if (existingCrewMember)
|
if (existingCrewMember)
|
||||||
{
|
{
|
||||||
CrewManager.RenameCharacter(characterInfo, newName);
|
CrewManager.RenameCharacter(characterInfo, newName);
|
||||||
|
if (characterInfo == sender.CharacterInfo)
|
||||||
|
{
|
||||||
|
//renaming is only allowed once
|
||||||
|
characterInfo.RenamingEnabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
location.HireManager.RenameCharacter(characterInfo, newName);
|
location.HireManager.RenameCharacter(characterInfo, newName);
|
||||||
}
|
}
|
||||||
|
appliedRename = (characterInfo.ID, newName);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Tried to rename an invalid character ({renamedIdentifier})");
|
string errorMsg = $"Tried to rename an invalid character ({renamedIdentifier}, {characterInfo?.Name ?? "null"})";
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
GameMain.Server?.SendConsoleMessage(errorMsg, sender, Color.Red);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1262,9 +1318,9 @@ namespace Barotrauma
|
|||||||
if (updatePending)
|
if (updatePending)
|
||||||
{
|
{
|
||||||
List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>();
|
List<CharacterInfo> pendingHireInfos = new List<CharacterInfo>();
|
||||||
foreach (int identifier in pendingHires)
|
foreach (UInt16 identifier in pendingHires)
|
||||||
{
|
{
|
||||||
CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.GetIdentifierUsingOriginalName() == identifier);
|
CharacterInfo match = location.GetHireableCharacters().FirstOrDefault(info => info.ID == identifier);
|
||||||
if (match == null)
|
if (match == null)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Tried to add a character that doesn't exist ({identifier}) to pending hires");
|
DebugConsole.ThrowError($"Tried to add a character that doesn't exist ({identifier}) to pending hires");
|
||||||
@@ -1293,7 +1349,7 @@ namespace Barotrauma
|
|||||||
// bounce back
|
// bounce back
|
||||||
if (renameCharacter && existingCrewMember)
|
if (renameCharacter && existingCrewMember)
|
||||||
{
|
{
|
||||||
SendCrewState((renamedIdentifier, newName), firedCharacter);
|
SendCrewState(appliedRename, firedCharacter);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -1311,7 +1367,7 @@ namespace Barotrauma
|
|||||||
/// the client and the server when there's only one person on the server but when a second person joins both of
|
/// the client and the server when there's only one person on the server but when a second person joins both of
|
||||||
/// their available hires are different from the server.
|
/// their available hires are different from the server.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
public void SendCrewState((int id, string newName) renamedCrewMember = default, CharacterInfo firedCharacter = null)
|
public void SendCrewState((ushort id, string newName) renamedCrewMember = default, CharacterInfo firedCharacter = null, bool createNotification = true)
|
||||||
{
|
{
|
||||||
List<CharacterInfo> availableHires = new List<CharacterInfo>();
|
List<CharacterInfo> availableHires = new List<CharacterInfo>();
|
||||||
List<CharacterInfo> pendingHires = new List<CharacterInfo>();
|
List<CharacterInfo> pendingHires = new List<CharacterInfo>();
|
||||||
@@ -1327,6 +1383,8 @@ namespace Barotrauma
|
|||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ServerPacketHeader.CREW);
|
msg.WriteByte((byte)ServerPacketHeader.CREW);
|
||||||
|
|
||||||
|
msg.WriteBoolean(createNotification);
|
||||||
|
|
||||||
msg.WriteUInt16((ushort)availableHires.Count);
|
msg.WriteUInt16((ushort)availableHires.Count);
|
||||||
foreach (CharacterInfo hire in availableHires)
|
foreach (CharacterInfo hire in availableHires)
|
||||||
{
|
{
|
||||||
@@ -1337,7 +1395,7 @@ namespace Barotrauma
|
|||||||
msg.WriteUInt16((ushort)pendingHires.Count);
|
msg.WriteUInt16((ushort)pendingHires.Count);
|
||||||
foreach (CharacterInfo pendingHire in pendingHires)
|
foreach (CharacterInfo pendingHire in pendingHires)
|
||||||
{
|
{
|
||||||
msg.WriteInt32(pendingHire.GetIdentifierUsingOriginalName());
|
msg.WriteUInt16(pendingHire.ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
var hiredCharacters = CrewManager.GetCharacterInfos().Where(ci => ci.IsNewHire);
|
var hiredCharacters = CrewManager.GetCharacterInfos().Where(ci => ci.IsNewHire);
|
||||||
@@ -1348,16 +1406,16 @@ namespace Barotrauma
|
|||||||
msg.WriteInt32(info.Salary);
|
msg.WriteInt32(info.Salary);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool validRenaming = renamedCrewMember.id > -1 && !string.IsNullOrEmpty(renamedCrewMember.newName);
|
bool validRenaming = renamedCrewMember.id > 0 && !string.IsNullOrEmpty(renamedCrewMember.newName);
|
||||||
msg.WriteBoolean(validRenaming);
|
msg.WriteBoolean(validRenaming);
|
||||||
if (validRenaming)
|
if (validRenaming)
|
||||||
{
|
{
|
||||||
msg.WriteInt32(renamedCrewMember.id);
|
msg.WriteUInt16(renamedCrewMember.id);
|
||||||
msg.WriteString(renamedCrewMember.newName);
|
msg.WriteString(renamedCrewMember.newName);
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.WriteBoolean(firedCharacter != null);
|
msg.WriteBoolean(firedCharacter != null);
|
||||||
if (firedCharacter != null) { msg.WriteInt32(firedCharacter.GetIdentifier()); }
|
if (firedCharacter != null) { msg.WriteUInt16(firedCharacter.ID); }
|
||||||
|
|
||||||
GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
GameMain.Server.ServerPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
@@ -1369,6 +1427,8 @@ namespace Barotrauma
|
|||||||
//(can happen e.g. if someone starts a vote to buy something and then disconnects)
|
//(can happen e.g. if someone starts a vote to buy something and then disconnects)
|
||||||
if (client != null && !GameMain.Server.ConnectedClients.Contains(client)) { return false; }
|
if (client != null && !GameMain.Server.ConnectedClients.Contains(client)) { return false; }
|
||||||
|
|
||||||
|
if (price == 0) { return true; }
|
||||||
|
|
||||||
Wallet wallet = GetWallet(client);
|
Wallet wallet = GetWallet(client);
|
||||||
if (!AllowedToManageWallets(client))
|
if (!AllowedToManageWallets(client))
|
||||||
{
|
{
|
||||||
@@ -1475,5 +1535,57 @@ namespace Barotrauma
|
|||||||
lastSaveID++;
|
lastSaveID++;
|
||||||
DebugConsole.Log("Campaign saved, save ID " + lastSaveID);
|
DebugConsole.Log("Campaign saved, save ID " + lastSaveID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load the current character save file and add/replace a single character's data with a new version immediately.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="newData">New character to insert. If it matches one existing in the save, that will get replaced.</param>
|
||||||
|
/// <param name="skipBackup">By default, replaced characters will be temporarily backed up, but that might be unwanted
|
||||||
|
/// eg. when using this method to save a character itself restored from the backup.</param>
|
||||||
|
public void SaveSingleCharacter(CharacterCampaignData newData, bool skipBackup = false)
|
||||||
|
{
|
||||||
|
string characterDataPath = GetCharacterDataSavePath();
|
||||||
|
if (!File.Exists(characterDataPath))
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Failed to load the character data for the campaign. Could not find the file \"{characterDataPath}\".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var loadedCharacterData = XMLExtensions.TryLoadXml(characterDataPath);
|
||||||
|
if (loadedCharacterData?.Root == null) { return; }
|
||||||
|
var oldData = loadedCharacterData.Root.Elements()
|
||||||
|
.FirstOrDefault(subElement => new CharacterCampaignData(subElement).IsDuplicate(newData));
|
||||||
|
|
||||||
|
if (oldData != null)
|
||||||
|
{
|
||||||
|
if (!skipBackup)
|
||||||
|
{
|
||||||
|
replacedCharacterDataBackup.Add(new CharacterCampaignData(oldData));
|
||||||
|
}
|
||||||
|
oldData.Remove();
|
||||||
|
}
|
||||||
|
loadedCharacterData.Root.Add(newData.Save());
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadedCharacterData.SaveSafe(characterDataPath);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("Saving multiplayer campaign characters to \"" + characterDataPath + "\" failed!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public CharacterCampaignData RestoreSingleCharacterFromBackup(Client client)
|
||||||
|
{
|
||||||
|
if (replacedCharacterDataBackup.Find(cd => cd.MatchesClient(client)) is CharacterCampaignData characterToRestore)
|
||||||
|
{
|
||||||
|
replacedCharacterDataBackup.Remove(characterToRestore);
|
||||||
|
return characterToRestore;
|
||||||
|
}
|
||||||
|
|
||||||
|
return default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -22,6 +22,36 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly List<AfflictionSubscriber> afflictionSubscribers = new();
|
private readonly List<AfflictionSubscriber> afflictionSubscribers = new();
|
||||||
|
|
||||||
|
public void Update(float deltaTime)
|
||||||
|
{
|
||||||
|
processAfflictionChangesTimer -= deltaTime;
|
||||||
|
if (processAfflictionChangesTimer <= 0.0f)
|
||||||
|
{
|
||||||
|
foreach (var character in charactersWithAfflictionChanges)
|
||||||
|
{
|
||||||
|
ImmutableArray<NetAffliction> afflictions = GetAllAfflictions(character.CharacterHealth);
|
||||||
|
foreach (AfflictionSubscriber sub in afflictionSubscribers.ToList())
|
||||||
|
{
|
||||||
|
if (sub.Expiry < DateTimeOffset.Now)
|
||||||
|
{
|
||||||
|
afflictionSubscribers.Remove(sub);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sub.Target == character.Info)
|
||||||
|
{
|
||||||
|
ServerSend(new NetCrewMember(character.Info, afflictions),
|
||||||
|
header: NetworkHeader.AFFLICTION_UPDATE,
|
||||||
|
deliveryMethod: DeliveryMethod.Unreliable,
|
||||||
|
targetClient: sub.Subscriber);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
charactersWithAfflictionChanges.Clear();
|
||||||
|
processAfflictionChangesTimer = ProcessAfflictionChangesInterval;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void ServerRead(IReadMessage inc, Client sender)
|
public void ServerRead(IReadMessage inc, Client sender)
|
||||||
{
|
{
|
||||||
NetworkHeader header = (NetworkHeader)inc.ReadByte();
|
NetworkHeader header = (NetworkHeader)inc.ReadByte();
|
||||||
@@ -141,7 +171,7 @@ namespace Barotrauma
|
|||||||
if (foundInfo is { Character.CharacterHealth: { } health })
|
if (foundInfo is { Character.CharacterHealth: { } health })
|
||||||
{
|
{
|
||||||
pendingAfflictions = GetAllAfflictions(health);
|
pendingAfflictions = GetAllAfflictions(health);
|
||||||
infoId = foundInfo.GetIdentifierUsingOriginalName();
|
infoId = foundInfo.ID;
|
||||||
}
|
}
|
||||||
|
|
||||||
INetSerializableStruct writeCrewMember = new NetCrewMember
|
INetSerializableStruct writeCrewMember = new NetCrewMember
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ namespace Barotrauma.Items.Components
|
|||||||
private bool needsServerInitialization;
|
private bool needsServerInitialization;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When in multiplayer and the circuit box is loaded from the players inventory,
|
/// When in multiplayer and the circuit box are loaded from the player inventory,
|
||||||
/// We only load the components from XML on server side since only the server has access to CharacterCampaignData
|
/// We only load the components from XML on the server side
|
||||||
/// and then send a network event syncing the loaded properties. But circuit box properties are too complex to
|
/// since only the server has access to CharacterCampaignData
|
||||||
/// sync using the existing syncing logic so we instead send the state using <see cref="CircuitBoxInitializeStateFromServerEvent"/>.
|
/// and then send a network event syncing the loaded properties.
|
||||||
|
/// But circuit box properties are too complex to
|
||||||
|
/// sync using the existing syncing logic,
|
||||||
|
/// so we instead send the state using <see cref="CircuitBoxInitializeStateFromServerEvent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MarkServerRequiredInitialization()
|
public void MarkServerRequiredInitialization()
|
||||||
=> needsServerInitialization = true;
|
=> needsServerInitialization = true;
|
||||||
@@ -280,6 +283,15 @@ namespace Barotrauma.Items.Components
|
|||||||
CreateServerEvent(data with { Size = Vector2.Max(data.Size, CircuitBoxLabelNode.MinSize) });
|
CreateServerEvent(data with { Size = Vector2.Max(data.Size, CircuitBoxLabelNode.MinSize) });
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case CircuitBoxOpcode.RenameConnections:
|
||||||
|
{
|
||||||
|
var data = INetSerializableStruct.Read<CircuitBoxRenameConnectionLabelsEvent>(msg);
|
||||||
|
if (!CanAccessAndUnlocked(c)) { break; }
|
||||||
|
|
||||||
|
RenameConnectionLabelsInternal(data.Type, data.Override.ToDictionary());
|
||||||
|
CreateServerEvent(data);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
|
throw new ArgumentOutOfRangeException(nameof(header), header, "This opcode cannot be handled using entity events");
|
||||||
}
|
}
|
||||||
@@ -327,6 +339,7 @@ namespace Barotrauma.Items.Components
|
|||||||
Components: Components.Select(EventFromComponent).ToImmutableArray(),
|
Components: Components.Select(EventFromComponent).ToImmutableArray(),
|
||||||
Wires: Wires.Select(EventFromWire).ToImmutableArray(),
|
Wires: Wires.Select(EventFromWire).ToImmutableArray(),
|
||||||
Labels: Labels.Select(EventFromLabel).ToImmutableArray(),
|
Labels: Labels.Select(EventFromLabel).ToImmutableArray(),
|
||||||
|
LabelOverrides: InputOutputNodes.Select(EventFromLabelOverride).ToImmutableArray(),
|
||||||
InputPos: inputPos,
|
InputPos: inputPos,
|
||||||
OutputPos: outputPos);
|
OutputPos: outputPos);
|
||||||
|
|
||||||
@@ -347,6 +360,9 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
static CircuitBoxServerAddLabelEvent EventFromLabel(CircuitBoxLabelNode label)
|
static CircuitBoxServerAddLabelEvent EventFromLabel(CircuitBoxLabelNode label)
|
||||||
=> new(label.ID, label.Position, label.Size, label.Color, label.HeaderText, label.BodyText);
|
=> new(label.ID, label.Position, label.Size, label.Color, label.HeaderText, label.BodyText);
|
||||||
|
|
||||||
|
static CircuitBoxRenameConnectionLabelsEvent EventFromLabelOverride(CircuitBoxInputOutputNode node)
|
||||||
|
=> new(node.NodeType, node.ConnectionLabelOverrides.ToNetDictionary());
|
||||||
}
|
}
|
||||||
|
|
||||||
// we don't care about updating the view on server
|
// we don't care about updating the view on server
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ namespace Barotrauma.Items.Components
|
|||||||
//existing wire not in the list of new wires -> disconnect it
|
//existing wire not in the list of new wires -> disconnect it
|
||||||
if (!wires[i].Contains(existingWire))
|
if (!wires[i].Contains(existingWire))
|
||||||
{
|
{
|
||||||
if (existingWire.Locked)
|
if (existingWire.Locked || existingWire.Item.IsLayerHidden)
|
||||||
{
|
{
|
||||||
//this should not be possible unless the client is running a modified version of the game
|
//this should not be possible unless the client is running a modified version of the game
|
||||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " attempted to disconnect a locked wire from " +
|
GameServer.Log(GameServer.CharacterLogName(c.Character) + " attempted to disconnect a locked wire from " +
|
||||||
@@ -166,18 +166,6 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Wire disconnectedWire in DisconnectedWires.ToList())
|
|
||||||
{
|
|
||||||
if (disconnectedWire.Connections[0] == null &&
|
|
||||||
disconnectedWire.Connections[1] == null &&
|
|
||||||
!clientSideDisconnectedWires.Contains(disconnectedWire) &&
|
|
||||||
disconnectedWire.Item.ParentInventory == null)
|
|
||||||
{
|
|
||||||
disconnectedWire.Item.Drop(c.Character);
|
|
||||||
GameServer.Log(GameServer.CharacterLogName(c.Character) + " dropped " + disconnectedWire.Name, ServerLog.MessageType.Inventory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//go through new wires
|
//go through new wires
|
||||||
for (int i = 0; i < Connections.Count; i++)
|
for (int i = 0; i < Connections.Count; i++)
|
||||||
{
|
{
|
||||||
@@ -205,6 +193,18 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (Wire disconnectedWire in DisconnectedWires.ToList())
|
||||||
|
{
|
||||||
|
if (disconnectedWire.Connections[0] == null &&
|
||||||
|
disconnectedWire.Connections[1] == null &&
|
||||||
|
!clientSideDisconnectedWires.Contains(disconnectedWire) &&
|
||||||
|
disconnectedWire.Item.ParentInventory == null)
|
||||||
|
{
|
||||||
|
disconnectedWire.Item.Drop(c.Character);
|
||||||
|
GameServer.Log(GameServer.CharacterLogName(c.Character) + " dropped " + disconnectedWire.Name, ServerLog.MessageType.Inventory);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||||
|
|||||||
@@ -11,153 +11,42 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
private readonly Dictionary<Client, List<ushort>[]> receivedItemIds = new Dictionary<Client, List<ushort>[]>();
|
private readonly Dictionary<Client, List<ushort>[]> receivedItemIds = new Dictionary<Client, List<ushort>[]>();
|
||||||
|
|
||||||
public void ServerEventRead(IReadMessage msg, Client c)
|
public void ServerEventRead(IReadMessage msg, Client sender)
|
||||||
{
|
{
|
||||||
if (!receivedItemIds.TryGetValue(c, out List<ushort>[] receivedItemIdsFromClient))
|
// if the dictionary doesn't contain the client entry, create a new one
|
||||||
|
if (!receivedItemIds.TryGetValue(sender, out List<ushort>[] receivedItemIdsFromClient))
|
||||||
{
|
{
|
||||||
receivedItemIdsFromClient = new List<ushort>[capacity];
|
receivedItemIdsFromClient = new List<ushort>[capacity];
|
||||||
receivedItemIds.Add(c, receivedItemIdsFromClient);
|
receivedItemIds.Add(sender, receivedItemIdsFromClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Read some item ids from the message. readyToApply waits for all the data from possible multiple messages.
|
||||||
SharedRead(msg, receivedItemIdsFromClient, out bool readyToApply);
|
SharedRead(msg, receivedItemIdsFromClient, out bool readyToApply);
|
||||||
if (!readyToApply) { return; }
|
if (!readyToApply) { return; }
|
||||||
|
|
||||||
if (c == null || c.Character == null) { return; }
|
if (sender == null || sender.Character == null) { return; }
|
||||||
|
|
||||||
bool accessible = c.Character.CanAccessInventory(this);
|
if (!IsInventoryAccessible())
|
||||||
if (this is CharacterInventory characterInventory && accessible)
|
|
||||||
{
|
{
|
||||||
if (Owner == null || Owner is not Character ownerCharacter)
|
CreateCorrectiveNetworkEvent();
|
||||||
{
|
|
||||||
accessible = false;
|
|
||||||
}
|
|
||||||
else if (!characterInventory.AccessibleWhenAlive && !ownerCharacter.IsDead && !characterInventory.AccessibleByOwner)
|
|
||||||
{
|
|
||||||
accessible = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!accessible)
|
|
||||||
{
|
|
||||||
//create a network event to correct the client's inventory state
|
|
||||||
//otherwise they may have an item in their inventory they shouldn't have been able to pick up,
|
|
||||||
//and receiving an event for that inventory later will cause the item to be dropped
|
|
||||||
CreateNetworkEvent();
|
|
||||||
for (int i = 0; i < capacity; i++)
|
|
||||||
{
|
|
||||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
|
||||||
{
|
|
||||||
if (Entity.FindEntityByID(id) is not Item item) { continue; }
|
|
||||||
item.PositionUpdateInterval = 0.0f;
|
|
||||||
if (item.ParentInventory != null && item.ParentInventory != this)
|
|
||||||
{
|
|
||||||
item.ParentInventory.CreateNetworkEvent();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//we need to check which of the items the client can access at this point, before we start shuffling things around
|
|
||||||
//otherwise if you're e.g. holding an item to access a cabinet, and picking up an item from the cabinet unequips the item you're holding,
|
|
||||||
//you would fail to pick up the item because it gets unequipped before checking whether you can access the cabinet.
|
|
||||||
Dictionary<Item, bool> canAccessItem = new Dictionary<Item, bool>();
|
|
||||||
for (int i = 0; i < capacity; i++)
|
|
||||||
{
|
|
||||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
|
||||||
{
|
|
||||||
if (Entity.FindEntityByID(id) is not Item item) { continue; }
|
|
||||||
canAccessItem[item] = item.CanClientAccess(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Item> prevItems = new List<Item>(AllItems.Distinct());
|
List<Item> prevItems = new List<Item>(AllItems.Distinct());
|
||||||
List<Inventory> prevItemInventories = new List<Inventory>() { this };
|
List<Inventory> prevItemInventories = new List<Inventory>() { this };
|
||||||
for (int i = 0; i < capacity; i++)
|
|
||||||
{
|
|
||||||
foreach (Item item in slots[i].Items.ToList())
|
|
||||||
{
|
|
||||||
if (!receivedItemIdsFromClient[i].Contains(item.ID) && item.IsInteractable(c.Character))
|
|
||||||
{
|
|
||||||
Item droppedItem = item;
|
|
||||||
Entity prevOwner = Owner;
|
|
||||||
Inventory previousInventory = droppedItem.ParentInventory;
|
|
||||||
droppedItem.Drop(null);
|
|
||||||
droppedItem.PreviousParentInventory = previousInventory;
|
|
||||||
|
|
||||||
var previousCharacterInventory = prevOwner switch
|
//we need to check which of the items the client (sender) can access at this point, before we start shuffling things around
|
||||||
{
|
//otherwise if you're e.g. holding an item to access a cabinet, and picking up an item from the cabinet unequips the item you're holding,
|
||||||
Item itemInventory => itemInventory.FindParentInventory(inventory => inventory is CharacterInventory) as CharacterInventory,
|
//you would fail to pick up the item because it gets unequipped before checking whether you can access the cabinet.
|
||||||
Character character => character.Inventory,
|
var itemAccessibility = GetItemAccessibility();
|
||||||
_ => null
|
|
||||||
};
|
HandleRemovedItems();
|
||||||
|
|
||||||
if (previousCharacterInventory != null && previousCharacterInventory != c.Character?.Inventory)
|
HandleAddedItems();
|
||||||
{
|
|
||||||
GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(previousCharacterInventory, c, droppedItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (droppedItem.body != null && prevOwner != null)
|
|
||||||
{
|
|
||||||
droppedItem.body.SetTransform(prevOwner.SimPosition, 0.0f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
EnsureItemsInBothHands(sender.Character);
|
||||||
{
|
|
||||||
Item newItem = id == 0 ? null : Entity.FindEntityByID(id) as Item;
|
|
||||||
prevItemInventories.Add(newItem?.ParentInventory);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < capacity; i++)
|
receivedItemIds.Remove(sender);
|
||||||
{
|
|
||||||
foreach (ushort id in receivedItemIdsFromClient[i])
|
|
||||||
{
|
|
||||||
if (Entity.FindEntityByID(id) is not Item item || slots[i].Contains(item)) { continue; }
|
|
||||||
|
|
||||||
if (item.GetComponent<Pickable>() is not Pickable pickable ||
|
|
||||||
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(c.Character))
|
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"Client {c.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
|
|
||||||
item.Prefab.ContentPackage);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GameMain.Server != null)
|
|
||||||
{
|
|
||||||
var holdable = item.GetComponent<Holdable>();
|
|
||||||
if (holdable != null && !holdable.CanBeDeattached()) { continue; }
|
|
||||||
|
|
||||||
if (!prevItems.Contains(item) && !canAccessItem[item] &&
|
|
||||||
(c.Character == null || item.PreviousParentInventory == null || !c.Character.CanAccessInventory(item.PreviousParentInventory)))
|
|
||||||
{
|
|
||||||
#if DEBUG || UNSTABLE
|
|
||||||
DebugConsole.NewMessage($"Client {c.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
|
||||||
#endif
|
|
||||||
if (item.body != null && !c.PendingPositionUpdates.Contains(item))
|
|
||||||
{
|
|
||||||
c.PendingPositionUpdates.Enqueue(item);
|
|
||||||
}
|
|
||||||
item.PositionUpdateInterval = 0.0f;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
TryPutItem(item, i, true, true, c.Character, false);
|
|
||||||
for (int j = 0; j < capacity; j++)
|
|
||||||
{
|
|
||||||
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
|
||||||
{
|
|
||||||
slots[j].RemoveItem(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
EnsureItemsInBothHands(c.Character);
|
|
||||||
|
|
||||||
receivedItemIds.Remove(c);
|
|
||||||
|
|
||||||
CreateNetworkEvent();
|
CreateNetworkEvent();
|
||||||
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
||||||
@@ -165,43 +54,211 @@ namespace Barotrauma
|
|||||||
if (prevInventory != this) { prevInventory?.CreateNetworkEvent(); }
|
if (prevInventory != this) { prevInventory?.CreateNetworkEvent(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Item item in AllItems.DistinctBy(it => it.Prefab))
|
ServerLogAddedItems();
|
||||||
|
|
||||||
|
ServerLogRemovedItems();
|
||||||
|
|
||||||
|
#region local functions
|
||||||
|
bool IsInventoryAccessible() => sender.Character.CanAccessInventory(this, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited);
|
||||||
|
|
||||||
|
void CreateCorrectiveNetworkEvent()
|
||||||
{
|
{
|
||||||
if (item == null) { continue; }
|
// create a network event to correct the client's inventory state.
|
||||||
if (!prevItems.Contains(item))
|
// Otherwise they may have an item in their inventory they shouldn't have been able to pick up,
|
||||||
|
// and receiving an event for that inventory later will cause the item to be dropped
|
||||||
|
CreateNetworkEvent();
|
||||||
|
for (int i = 0; i < capacity; i++)
|
||||||
{
|
{
|
||||||
int amount = AllItems.Count(it => it.Prefab == item.Prefab && !prevItems.Contains(it));
|
foreach (ushort itemId in receivedItemIdsFromClient[i])
|
||||||
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
|
|
||||||
if (Owner == c.Character)
|
|
||||||
{
|
{
|
||||||
HumanAIController.ItemTaken(item, c.Character);
|
if (Entity.FindEntityByID(itemId) is not Item item) { continue; }
|
||||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} picked up {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
item.PositionUpdateInterval = 0.0f;
|
||||||
|
if (item.ParentInventory != null && item.ParentInventory != this)
|
||||||
|
{
|
||||||
|
item.ParentInventory.CreateNetworkEvent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dictionary<Item, bool> GetItemAccessibility()
|
||||||
|
{
|
||||||
|
Dictionary<Item, bool> itemAccessibility = new Dictionary<Item, bool>();
|
||||||
|
|
||||||
|
for (int i = 0; i < capacity; i++)
|
||||||
|
{
|
||||||
|
// for every item that the new inventory state contains
|
||||||
|
foreach (ushort itemId in receivedItemIdsFromClient[i])
|
||||||
|
{
|
||||||
|
// if there is no such item, skip
|
||||||
|
if (Entity.FindEntityByID(itemId) is not Item item) { continue; }
|
||||||
|
// add entry: can the sender access the item?
|
||||||
|
itemAccessibility[item] = item.CanClientAccess(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we now have accessibility for every item in the new inventory state
|
||||||
|
// but not for the items that were in the inventory before and perhaps dropped, so let's add those as well
|
||||||
|
foreach (var item in prevItems)
|
||||||
|
{
|
||||||
|
if (!itemAccessibility.ContainsKey(item))
|
||||||
|
{
|
||||||
|
itemAccessibility[item] = item.CanClientAccess(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemAccessibility;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleRemovedItems()
|
||||||
|
{
|
||||||
|
for (int slotIndex = 0; slotIndex < capacity; slotIndex++)
|
||||||
|
{
|
||||||
|
foreach (Item item in slots[slotIndex].Items.ToList())
|
||||||
|
{
|
||||||
|
bool shouldBeRemoved = !receivedItemIdsFromClient[slotIndex].Contains(item.ID) &&
|
||||||
|
item.IsInteractable(sender.Character); // item is interactable to sender: not hidden and player team
|
||||||
|
if (shouldBeRemoved)
|
||||||
|
{
|
||||||
|
bool itemAccessDenied = prevItems.Contains(item) && // if the item was in the inventory before
|
||||||
|
!itemAccessibility[item] && // and the sender is not allowed to access it
|
||||||
|
(item.PreviousParentInventory == null || // and either the item has no previous inventory
|
||||||
|
!sender.Character.CanAccessInventory(item.PreviousParentInventory)); // or the sender can't access the previous inventory
|
||||||
|
|
||||||
|
if (itemAccessDenied)
|
||||||
|
{
|
||||||
|
#if DEBUG || UNSTABLE
|
||||||
|
DebugConsole.NewMessage($"Client {sender.Name} failed to drop item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||||
|
#endif
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item droppedItem = item;
|
||||||
|
Entity prevOwner = Owner;
|
||||||
|
Inventory previousInventory = droppedItem.ParentInventory;
|
||||||
|
droppedItem.Drop(null);
|
||||||
|
droppedItem.PreviousParentInventory = previousInventory;
|
||||||
|
|
||||||
|
var previousCharacterInventory = prevOwner switch
|
||||||
|
{
|
||||||
|
Item itemInventory => itemInventory.FindParentInventory(inventory => inventory is CharacterInventory) as CharacterInventory,
|
||||||
|
Character character => character.Inventory,
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
|
||||||
|
if (previousCharacterInventory != null && previousCharacterInventory != sender.Character?.Inventory)
|
||||||
|
{
|
||||||
|
GameMain.Server?.KarmaManager.OnItemTakenFromPlayer(previousCharacterInventory, sender, droppedItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (droppedItem.body != null && prevOwner != null)
|
||||||
|
{
|
||||||
|
droppedItem.body.SetTransform(prevOwner.SimPosition, 0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (ushort id in receivedItemIdsFromClient[slotIndex])
|
||||||
|
{
|
||||||
|
Item newItem = id == 0 ? null : Entity.FindEntityByID(id) as Item;
|
||||||
|
prevItemInventories.Add(newItem?.ParentInventory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleAddedItems()
|
||||||
|
{
|
||||||
|
for (int slotIndex = 0; slotIndex < capacity; slotIndex++)
|
||||||
|
{
|
||||||
|
foreach (ushort id in receivedItemIdsFromClient[slotIndex])
|
||||||
|
{
|
||||||
|
if (Entity.FindEntityByID(id) is not Item item || slots[slotIndex].Contains(item)) { continue; }
|
||||||
|
|
||||||
|
if (item.GetComponent<Pickable>() is not Pickable pickable ||
|
||||||
|
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(sender.Character))
|
||||||
|
{
|
||||||
|
DebugConsole.AddWarning($"Client {sender.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
|
||||||
|
item.Prefab.ContentPackage);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (GameMain.Server != null)
|
||||||
|
{
|
||||||
|
var holdable = item.GetComponent<Holdable>();
|
||||||
|
if (holdable != null && !holdable.CanBeDeattached()) { continue; }
|
||||||
|
|
||||||
|
bool itemAccessDenied = !prevItems.Contains(item) && !itemAccessibility[item] &&
|
||||||
|
(sender.Character == null || item.PreviousParentInventory == null || !sender.Character.CanAccessInventory(item.PreviousParentInventory));
|
||||||
|
|
||||||
|
if (itemAccessDenied)
|
||||||
|
{
|
||||||
|
#if DEBUG || UNSTABLE
|
||||||
|
DebugConsole.NewMessage($"Client {sender.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||||
|
#endif
|
||||||
|
if (item.body != null && !sender.PendingPositionUpdates.Contains(item))
|
||||||
|
{
|
||||||
|
sender.PendingPositionUpdates.Enqueue(item);
|
||||||
|
}
|
||||||
|
item.PositionUpdateInterval = 0.0f;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TryPutItem(item, slotIndex, true, true, sender.Character, false);
|
||||||
|
for (int j = 0; j < capacity; j++)
|
||||||
|
{
|
||||||
|
if (slots[j].Contains(item) && !receivedItemIdsFromClient[j].Contains(item.ID))
|
||||||
|
{
|
||||||
|
slots[j].RemoveItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerLogAddedItems()
|
||||||
|
{
|
||||||
|
foreach (Item item in AllItems.DistinctBy(it => it.Prefab))
|
||||||
|
{
|
||||||
|
if (item == null) { continue; }
|
||||||
|
if (!prevItems.Contains(item))
|
||||||
|
{
|
||||||
|
int amount = AllItems.Count(it => it.Prefab == item.Prefab && !prevItems.Contains(it));
|
||||||
|
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
|
||||||
|
if (Owner == sender.Character)
|
||||||
|
{
|
||||||
|
HumanAIController.ItemTaken(item, sender.Character);
|
||||||
|
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} picked up {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} placed {amountText}{item.Name} in the inventory of {Owner}", ServerLog.MessageType.Inventory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerLogRemovedItems()
|
||||||
|
{
|
||||||
|
var droppedItems = prevItems.Where(it => it != null && !AllItems.Contains(it));
|
||||||
|
foreach (Item item in droppedItems.DistinctBy(it => it.Prefab))
|
||||||
|
{
|
||||||
|
var matchingItems = prevItems.Where(it => it.Prefab == item.Prefab && !AllItems.Contains(it));
|
||||||
|
int amount = matchingItems.Count();
|
||||||
|
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
|
||||||
|
if (Owner == sender.Character)
|
||||||
|
{
|
||||||
|
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} dropped {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} placed {amountText}{item.Name} in {Owner}", ServerLog.MessageType.Inventory);
|
GameServer.Log($"{GameServer.CharacterLogName(sender.Character)} removed {amountText}{item.Name} from the inventory of {Owner}", ServerLog.MessageType.Inventory);
|
||||||
}
|
}
|
||||||
|
item.CreateDroppedStack(matchingItems, allowClientExecute: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endregion
|
||||||
var droppedItems = prevItems.Where(it => it != null && !AllItems.Contains(it));
|
|
||||||
foreach (Item item in droppedItems.DistinctBy(it => it.Prefab))
|
|
||||||
{
|
|
||||||
var matchingItems = prevItems.Where(it => it.Prefab == item.Prefab && !AllItems.Contains(it));
|
|
||||||
int amount = matchingItems.Count();
|
|
||||||
string amountText = amount > 1 ? $"x{amount} " : string.Empty;
|
|
||||||
if (Owner == c.Character)
|
|
||||||
{
|
|
||||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} dropped {amountText}{item.Name}", ServerLog.MessageType.Inventory);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} removed {amountText}{item.Name} from {Owner}", ServerLog.MessageType.Inventory);
|
|
||||||
}
|
|
||||||
item.CreateDroppedStack(matchingItems, allowClientExecute: true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void EnsureItemsInBothHands(Character character)
|
private void EnsureItemsInBothHands(Character character)
|
||||||
{
|
{
|
||||||
if (this is not CharacterInventory charInv) { return; }
|
if (this is not CharacterInventory charInv) { return; }
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ namespace Barotrauma.Networking
|
|||||||
if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) { return; }
|
if (c.Character == null || c.Character.SpeechImpediment >= 100.0f || c.Character.IsDead) { return; }
|
||||||
if (orderMsg.Order.IsReport)
|
if (orderMsg.Order.IsReport)
|
||||||
{
|
{
|
||||||
HumanAIController.ReportProblem(orderMsg.Sender, orderMsg.Order);
|
HumanAIController.ReportProblem(orderMsg.Sender as Character, orderMsg.Order);
|
||||||
}
|
}
|
||||||
if (order != null)
|
if (order != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -8,6 +9,8 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
public bool VoiceEnabled = true;
|
public bool VoiceEnabled = true;
|
||||||
|
|
||||||
|
public VoipServerDecoder VoipServerDecoder;
|
||||||
|
|
||||||
public UInt16 LastRecvClientListUpdate
|
public UInt16 LastRecvClientListUpdate
|
||||||
= NetIdUtils.GetIdOlderThan(GameMain.Server.LastClientListUpdateID);
|
= NetIdUtils.GetIdOlderThan(GameMain.Server.LastClientListUpdateID);
|
||||||
|
|
||||||
@@ -15,7 +18,7 @@ namespace Barotrauma.Networking
|
|||||||
= NetIdUtils.GetIdOlderThan(GameMain.Server.ServerSettings.LastUpdateIdForFlag[ServerSettings.NetFlags.Properties]);
|
= NetIdUtils.GetIdOlderThan(GameMain.Server.ServerSettings.LastUpdateIdForFlag[ServerSettings.NetFlags.Properties]);
|
||||||
public UInt16 LastRecvServerSettingsUpdate
|
public UInt16 LastRecvServerSettingsUpdate
|
||||||
= NetIdUtils.GetIdOlderThan(GameMain.Server.ServerSettings.LastUpdateIdForFlag[ServerSettings.NetFlags.Properties]);
|
= NetIdUtils.GetIdOlderThan(GameMain.Server.ServerSettings.LastUpdateIdForFlag[ServerSettings.NetFlags.Properties]);
|
||||||
|
|
||||||
public UInt16 LastRecvLobbyUpdate
|
public UInt16 LastRecvLobbyUpdate
|
||||||
= NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID);
|
= NetIdUtils.GetIdOlderThan(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
|
|
||||||
@@ -129,6 +132,7 @@ namespace Barotrauma.Networking
|
|||||||
JobPreferences = new List<JobVariant>();
|
JobPreferences = new List<JobVariant>();
|
||||||
|
|
||||||
VoipQueue = new VoipQueue(SessionId, true, true);
|
VoipQueue = new VoipQueue(SessionId, true, true);
|
||||||
|
VoipServerDecoder = new VoipServerDecoder(VoipQueue, this);
|
||||||
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
|
GameMain.Server.VoipServer.RegisterQueue(VoipQueue);
|
||||||
|
|
||||||
//initialize to infinity, gets set to a proper value when initializing midround syncing
|
//initialize to infinity, gets set to a proper value when initializing midround syncing
|
||||||
@@ -277,5 +281,56 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
return Permissions.HasFlag(permission);
|
return Permissions.HasFlag(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryTakeOverBot(Character botCharacter)
|
||||||
|
{
|
||||||
|
if (GameMain.Server == null)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"TryTakeOverBot: Client {Name} requested to take over a bot but GameMain.Server is null!");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (GameMain.NetworkMember is not { ServerSettings.RespawnMode: RespawnMode.Permadeath })
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Client {Name} requested to take over a bot but Permadeath is not enabled!");
|
||||||
|
GameMain.Server.SendConsoleMessage($"Permadeath mode is not enabled, cannot take over a bot.", this, Color.Red);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (CharacterInfo == null)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Permadeath: Client {Name} requested to take over a bot, but they don't seem to have a character at all yet.");
|
||||||
|
GameMain.Server.SendConsoleMessage($"Permadeath: Taking over a bot requires having a character that died first.", this, Color.Red);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (CharacterInfo is not { PermanentlyDead: true })
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Permadeath: Client {Name} requested to take over a bot, but their character has not been permanently killed.");
|
||||||
|
GameMain.Server.SendConsoleMessage($"Permadeath: Could not take over the bot, previous character not permanently killed.", this, Color.Red);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!botCharacter.IsBot)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Permadeath: {Name} requested to take over a bot character, but the target character is not a bot!");
|
||||||
|
GameMain.Server.SendConsoleMessage($"Permadeath: Could not take over the target character because it is not a bot.", this, Color.Red);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (botCharacter.Info != null)
|
||||||
|
{
|
||||||
|
botCharacter.Info.RenamingEnabled = true; // Grant one opportunity to rename a taken over bot
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now that the old permanently killed character will be replaced, we can fully discard it
|
||||||
|
var mpCampaign = GameMain.GameSession?.Campaign as MultiPlayerCampaign;
|
||||||
|
mpCampaign?.DiscardClientCharacterData(this);
|
||||||
|
GameMain.Server.SetClientCharacter(this, botCharacter);
|
||||||
|
if (mpCampaign?.SetClientCharacterData(this) is CharacterCampaignData characterData)
|
||||||
|
{
|
||||||
|
//the bot has spawned, but the new CharacterCampaignData technically hasn't, because we just created it
|
||||||
|
characterData.HasSpawned = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
SpectateOnly = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,10 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private bool wasReadyToStartAutomatically;
|
private bool wasReadyToStartAutomatically;
|
||||||
private bool autoRestartTimerRunning;
|
private bool autoRestartTimerRunning;
|
||||||
private float endRoundTimer;
|
public float EndRoundTimer { get; private set; }
|
||||||
|
public float EndRoundDelay { get; private set; }
|
||||||
|
|
||||||
|
public float EndRoundTimeRemaining => EndRoundTimer > 0 ? EndRoundDelay - EndRoundTimer : 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chat messages that get sent to the owner of the server when the owner is determined
|
/// Chat messages that get sent to the owner of the server when the owner is determined
|
||||||
@@ -354,6 +357,10 @@ namespace Barotrauma.Networking
|
|||||||
if (ServerSettings.VoiceChatEnabled)
|
if (ServerSettings.VoiceChatEnabled)
|
||||||
{
|
{
|
||||||
VoipServer.SendToClients(connectedClients);
|
VoipServer.SendToClients(connectedClients);
|
||||||
|
foreach (var c in connectedClients)
|
||||||
|
{
|
||||||
|
c.VoipServerDecoder.DebugUpdate(deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameStarted)
|
if (GameStarted)
|
||||||
@@ -361,6 +368,7 @@ namespace Barotrauma.Networking
|
|||||||
RespawnManager?.Update(deltaTime);
|
RespawnManager?.Update(deltaTime);
|
||||||
|
|
||||||
entityEventManager.Update(connectedClients);
|
entityEventManager.Update(connectedClients);
|
||||||
|
bool permadeathMode = ServerSettings.RespawnMode == RespawnMode.Permadeath;
|
||||||
|
|
||||||
//go through the characters backwards to give rejoining clients control of the latest created character
|
//go through the characters backwards to give rejoining clients control of the latest created character
|
||||||
for (int i = Character.CharacterList.Count - 1; i >= 0; i--)
|
for (int i = Character.CharacterList.Count - 1; i >= 0; i--)
|
||||||
@@ -371,13 +379,15 @@ namespace Barotrauma.Networking
|
|||||||
Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && character.IsClientOwner(c));
|
Client owner = connectedClients.Find(c => (c.Character == null || c.Character == character) && character.IsClientOwner(c));
|
||||||
bool canOwnerTakeControl =
|
bool canOwnerTakeControl =
|
||||||
owner != null && owner.InGame && !owner.NeedsMidRoundSync &&
|
owner != null && owner.InGame && !owner.NeedsMidRoundSync &&
|
||||||
(!ServerSettings.AllowSpectating || !owner.SpectateOnly);
|
(!ServerSettings.AllowSpectating || !owner.SpectateOnly ||
|
||||||
|
(permadeathMode && (!character.IsDead || character.CauseOfDeath?.Type == CauseOfDeathType.Disconnected)));
|
||||||
if (!character.IsDead)
|
if (!character.IsDead)
|
||||||
{
|
{
|
||||||
character.KillDisconnectedTimer += deltaTime;
|
character.KillDisconnectedTimer += deltaTime;
|
||||||
character.SetStun(1.0f);
|
character.SetStun(1.0f);
|
||||||
|
|
||||||
if ((OwnerConnection == null || owner?.Connection != OwnerConnection) && character.KillDisconnectedTimer > ServerSettings.KillDisconnectedTime)
|
if ((OwnerConnection == null || owner?.Connection != OwnerConnection) &&
|
||||||
|
character.KillDisconnectedTimer > (permadeathMode ? ServerSettings.DespawnDisconnectedPermadeathTime : ServerSettings.KillDisconnectedTime))
|
||||||
{
|
{
|
||||||
character.Kill(CauseOfDeathType.Disconnected, null);
|
character.Kill(CauseOfDeathType.Disconnected, null);
|
||||||
continue;
|
continue;
|
||||||
@@ -402,8 +412,10 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
Voting.Update(deltaTime);
|
Voting.Update(deltaTime);
|
||||||
|
|
||||||
bool isCrewDead =
|
bool isCrewDown =
|
||||||
connectedClients.All(c => !c.UsingFreeCam && (c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated));
|
connectedClients.All(c => !c.UsingFreeCam && (c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated));
|
||||||
|
bool isSomeoneIncapacitatedNotDead =
|
||||||
|
connectedClients.Any(c => !c.UsingFreeCam && c.Character is { IsDead: false, IsIncapacitated: true });
|
||||||
|
|
||||||
bool subAtLevelEnd = false;
|
bool subAtLevelEnd = false;
|
||||||
if (Submarine.MainSub != null && GameMain.GameSession.GameMode is not PvPMode)
|
if (Submarine.MainSub != null && GameMain.GameSession.GameMode is not PvPMode)
|
||||||
@@ -432,45 +444,58 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float endRoundDelay = 1.0f;
|
EndRoundDelay = 1.0f;
|
||||||
if (ServerSettings.AutoRestart && isCrewDead)
|
if (permadeathMode && isCrewDown)
|
||||||
{
|
{
|
||||||
endRoundDelay = 5.0f;
|
if (EndRoundTimer <= 0.0f)
|
||||||
endRoundTimer += deltaTime;
|
{
|
||||||
|
CreateEntityEvent(RespawnManager);
|
||||||
|
}
|
||||||
|
EndRoundDelay = 120.0f;
|
||||||
|
EndRoundTimer += deltaTime;
|
||||||
|
}
|
||||||
|
else if (ServerSettings.AutoRestart && isCrewDown)
|
||||||
|
{
|
||||||
|
EndRoundDelay = isSomeoneIncapacitatedNotDead ? 120.0f : 5.0f;
|
||||||
|
EndRoundTimer += deltaTime;
|
||||||
}
|
}
|
||||||
else if (subAtLevelEnd && GameMain.GameSession?.GameMode is not CampaignMode)
|
else if (subAtLevelEnd && GameMain.GameSession?.GameMode is not CampaignMode)
|
||||||
{
|
{
|
||||||
endRoundDelay = 5.0f;
|
EndRoundDelay = 5.0f;
|
||||||
endRoundTimer += deltaTime;
|
EndRoundTimer += deltaTime;
|
||||||
}
|
}
|
||||||
else if (isCrewDead && (RespawnManager == null || !RespawnManager.CanRespawnAgain))
|
else if (isCrewDown && (RespawnManager == null || !RespawnManager.CanRespawnAgain))
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
if (endRoundTimer <= 0.0f)
|
if (EndRoundTimer <= 0.0f)
|
||||||
{
|
{
|
||||||
SendChatMessage(TextManager.GetWithVariable("CrewDeadNoRespawns", "[time]", "60").Value, ChatMessageType.Server);
|
SendChatMessage(TextManager.GetWithVariable("CrewDeadNoRespawns", "[time]", "120").Value, ChatMessageType.Server);
|
||||||
}
|
}
|
||||||
endRoundDelay = 60.0f;
|
EndRoundDelay = 120.0f;
|
||||||
endRoundTimer += deltaTime;
|
EndRoundTimer += deltaTime;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else if (isCrewDead && (GameMain.GameSession?.GameMode is CampaignMode))
|
else if (isCrewDown && (GameMain.GameSession?.GameMode is CampaignMode))
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
endRoundDelay = 2.0f;
|
EndRoundDelay = isSomeoneIncapacitatedNotDead ? 120.0f : 2.0f;
|
||||||
endRoundTimer += deltaTime;
|
EndRoundTimer += deltaTime;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
endRoundTimer = 0.0f;
|
EndRoundTimer = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endRoundTimer >= endRoundDelay)
|
if (EndRoundTimer >= EndRoundDelay)
|
||||||
{
|
{
|
||||||
if (ServerSettings.AutoRestart && isCrewDead)
|
if (permadeathMode && isCrewDown)
|
||||||
{
|
{
|
||||||
Log("Ending round (entire crew dead)", ServerLog.MessageType.ServerMessage);
|
Log("Ending round (entire crew dead or down and did not acquire new characters in time)", ServerLog.MessageType.ServerMessage);
|
||||||
|
}
|
||||||
|
else if (ServerSettings.AutoRestart && isCrewDown)
|
||||||
|
{
|
||||||
|
Log("Ending round (entire crew down)", ServerLog.MessageType.ServerMessage);
|
||||||
}
|
}
|
||||||
else if (subAtLevelEnd)
|
else if (subAtLevelEnd)
|
||||||
{
|
{
|
||||||
@@ -478,11 +503,11 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else if (RespawnManager == null)
|
else if (RespawnManager == null)
|
||||||
{
|
{
|
||||||
Log("Ending round (no living players left and respawning is not enabled during this round)", ServerLog.MessageType.ServerMessage);
|
Log("Ending round (no players left standing and respawning is not enabled during this round)", ServerLog.MessageType.ServerMessage);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Log("Ending round (no living players left)", ServerLog.MessageType.ServerMessage);
|
Log("Ending round (no players left standing)", ServerLog.MessageType.ServerMessage);
|
||||||
}
|
}
|
||||||
EndGame(wasSaved: false);
|
EndGame(wasSaved: false);
|
||||||
return;
|
return;
|
||||||
@@ -824,7 +849,7 @@ namespace Barotrauma.Networking
|
|||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
connectedClient.VoipQueue.Read(inc);
|
VoipServer.Read(inc, connectedClient);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ClientPacketHeader.SERVER_SETTINGS:
|
case ClientPacketHeader.SERVER_SETTINGS:
|
||||||
@@ -842,6 +867,9 @@ namespace Barotrauma.Networking
|
|||||||
case ClientPacketHeader.REWARD_DISTRIBUTION:
|
case ClientPacketHeader.REWARD_DISTRIBUTION:
|
||||||
ReadRewardDistributionMessage(inc, connectedClient);
|
ReadRewardDistributionMessage(inc, connectedClient);
|
||||||
break;
|
break;
|
||||||
|
case ClientPacketHeader.RESET_REWARD_DISTRIBUTION:
|
||||||
|
ResetRewardDistribution(connectedClient);
|
||||||
|
break;
|
||||||
case ClientPacketHeader.MEDICAL:
|
case ClientPacketHeader.MEDICAL:
|
||||||
ReadMedicalMessage(inc, connectedClient);
|
ReadMedicalMessage(inc, connectedClient);
|
||||||
break;
|
break;
|
||||||
@@ -854,6 +882,9 @@ namespace Barotrauma.Networking
|
|||||||
case ClientPacketHeader.READY_TO_SPAWN:
|
case ClientPacketHeader.READY_TO_SPAWN:
|
||||||
ReadReadyToSpawnMessage(inc, connectedClient);
|
ReadReadyToSpawnMessage(inc, connectedClient);
|
||||||
break;
|
break;
|
||||||
|
case ClientPacketHeader.TAKEOVERBOT:
|
||||||
|
ReadTakeOverBotMessage(inc, connectedClient);
|
||||||
|
break;
|
||||||
case ClientPacketHeader.FILE_REQUEST:
|
case ClientPacketHeader.FILE_REQUEST:
|
||||||
if (ServerSettings.AllowFileTransfers)
|
if (ServerSettings.AllowFileTransfers)
|
||||||
{
|
{
|
||||||
@@ -1307,6 +1338,14 @@ namespace Barotrauma.Networking
|
|||||||
mpCampaign.ServerReadRewardDistribution(inc, sender);
|
mpCampaign.ServerReadRewardDistribution(inc, sender);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ResetRewardDistribution(Client client)
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
|
||||||
|
{
|
||||||
|
mpCampaign.ResetSalaries(client);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ReadMedicalMessage(IReadMessage inc, Client sender)
|
private void ReadMedicalMessage(IReadMessage inc, Client sender)
|
||||||
{
|
{
|
||||||
@@ -1342,6 +1381,91 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ReadTakeOverBotMessage(IReadMessage inc, Client sender)
|
||||||
|
{
|
||||||
|
UInt16 botId = inc.ReadUInt16();
|
||||||
|
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign) { return; }
|
||||||
|
|
||||||
|
if (ServerSettings.IronmanMode)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Client {sender.Name} has requested to take over a bot in Ironman mode!");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (campaign.CurrentLocation.GetHireableCharacters().FirstOrDefault(c => c.ID == botId) is CharacterInfo hireableCharacter)
|
||||||
|
{
|
||||||
|
if (ServerSettings.ReplaceCostPercentage <= 0 ||
|
||||||
|
CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageMoney) ||
|
||||||
|
CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageHires))
|
||||||
|
{
|
||||||
|
if (campaign.TryHireCharacter(campaign.CurrentLocation, hireableCharacter, takeMoney: true, sender, buyingNewCharacter: true))
|
||||||
|
{
|
||||||
|
campaign.CurrentLocation.RemoveHireableCharacter(hireableCharacter);
|
||||||
|
SpawnAndTakeOverBot(campaign, hireableCharacter, sender);
|
||||||
|
campaign.SendCrewState(createNotification: false);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendConsoleMessage($"Could not hire the bot {hireableCharacter.Name}.", sender, Color.Red);
|
||||||
|
DebugConsole.ThrowError($"Client {sender.Name} failed to hire the bot {hireableCharacter.Name}.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendConsoleMessage($"Could not hire the bot {hireableCharacter.Name}. No permission to manage money or hires.", sender, Color.Red);
|
||||||
|
DebugConsole.ThrowError($"Client {sender.Name} failed to hire the bot {hireableCharacter.Name}. No permission to manage money or hires.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
CharacterInfo botInfo = GameMain.GameSession.CrewManager?.GetCharacterInfos()?.FirstOrDefault(i => i.ID == botId);
|
||||||
|
|
||||||
|
if (botInfo is { IsNewHire: true, Character: null })
|
||||||
|
{
|
||||||
|
SpawnAndTakeOverBot(campaign, botInfo, sender);
|
||||||
|
}
|
||||||
|
else if (botInfo?.Character == null || !botInfo.Character.IsBot)
|
||||||
|
{
|
||||||
|
SendConsoleMessage($"Could not find a bot with the id {botId}.", sender, Color.Red);
|
||||||
|
DebugConsole.ThrowError($"Client {sender.Name} failed to take over a bot (Could not find a bot with the id {botId}).");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
else if (ServerSettings.AllowBotTakeoverOnPermadeath)
|
||||||
|
{
|
||||||
|
sender.TryTakeOverBot(botInfo.Character);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendConsoleMessage($"Failed to take over a bot (taking control of bots is disallowed).", sender, Color.Red);
|
||||||
|
DebugConsole.ThrowError($"Client {sender.Name} failed to take over a bot (taking control of bots is disallowed).");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void SpawnAndTakeOverBot(CampaignMode campaign, CharacterInfo botInfo, Client client)
|
||||||
|
{
|
||||||
|
var mainSubSpawnpoint = WayPoint.SelectCrewSpawnPoints(botInfo.ToEnumerable().ToList(), Submarine.MainSub).FirstOrDefault();
|
||||||
|
var spawnWaypoint = campaign.CrewManager.GetOutpostSpawnpoints()?.FirstOrDefault() ?? mainSubSpawnpoint;
|
||||||
|
if (spawnWaypoint == null)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("SpawnAndTakeOverBot: Unable to find any spawn waypoints inside the sub");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Entity.Spawner.AddCharacterToSpawnQueue(botInfo.SpeciesName, spawnWaypoint.WorldPosition, botInfo, onSpawn: newCharacter =>
|
||||||
|
{
|
||||||
|
if (newCharacter == null)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("SpawnAndTakeOverBot: newCharacter is null somehow");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// No longer show the hired character in the HR list of current hires
|
||||||
|
campaign.CrewManager.RemoveCharacterInfo(botInfo);
|
||||||
|
newCharacter.TeamID = CharacterTeamType.Team1;
|
||||||
|
campaign.CrewManager.InitializeCharacter(newCharacter, mainSubSpawnpoint, spawnWaypoint);
|
||||||
|
client.TryTakeOverBot(newCharacter);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void ClientReadServerCommand(IReadMessage inc)
|
private void ClientReadServerCommand(IReadMessage inc)
|
||||||
{
|
{
|
||||||
Client sender = ConnectedClients.Find(x => x.Connection == inc.Sender);
|
Client sender = ConnectedClients.Find(x => x.Connection == inc.Sender);
|
||||||
@@ -1450,9 +1574,8 @@ namespace Barotrauma.Networking
|
|||||||
if (mpCampaign != null && Level.IsLoadedFriendlyOutpost && save)
|
if (mpCampaign != null && Level.IsLoadedFriendlyOutpost && save)
|
||||||
{
|
{
|
||||||
mpCampaign.SavePlayers();
|
mpCampaign.SavePlayers();
|
||||||
|
mpCampaign.HandleSaveAndQuit();
|
||||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||||
mpCampaign.UpdateStoreStock();
|
|
||||||
GameMain.GameSession?.EventManager?.RegisterEventHistory(registerFinishedOnly: true);
|
|
||||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1686,6 +1809,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
outmsg.WriteBoolean(GameStarted);
|
outmsg.WriteBoolean(GameStarted);
|
||||||
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
|
outmsg.WriteBoolean(ServerSettings.AllowSpectating);
|
||||||
|
outmsg.WriteBoolean(ServerSettings.RespawnMode == RespawnMode.Permadeath);
|
||||||
|
outmsg.WriteBoolean(ServerSettings.IronmanMode);
|
||||||
|
|
||||||
c.WritePermissions(outmsg);
|
c.WritePermissions(outmsg);
|
||||||
}
|
}
|
||||||
@@ -1776,6 +1901,7 @@ namespace Barotrauma.Networking
|
|||||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||||
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
||||||
outmsg.WriteSingle((float)NetTime.Now);
|
outmsg.WriteSingle((float)NetTime.Now);
|
||||||
|
outmsg.WriteSingle(EndRoundTimeRemaining);
|
||||||
|
|
||||||
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
||||||
{
|
{
|
||||||
@@ -1858,7 +1984,8 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
outmsg = new WriteOnlyMessage();
|
outmsg = new WriteOnlyMessage();
|
||||||
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
outmsg.WriteByte((byte)ServerPacketHeader.UPDATE_INGAME);
|
||||||
outmsg.WriteSingle((float)Lidgren.Network.NetTime.Now);
|
outmsg.WriteSingle((float)NetTime.Now);
|
||||||
|
outmsg.WriteSingle(EndRoundTimeRemaining);
|
||||||
|
|
||||||
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
using (var segmentTable = SegmentTableWriter<ServerNetSegment>.StartWriting(outmsg))
|
||||||
{
|
{
|
||||||
@@ -2291,7 +2418,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
SendStartMessage(roundStartSeed, campaign.NextLevel.Seed, GameMain.GameSession, connectedClients, includesFinalize: false);
|
SendStartMessage(roundStartSeed, campaign.NextLevel.Seed, GameMain.GameSession, connectedClients, includesFinalize: false);
|
||||||
GameMain.GameSession.StartRound(campaign.NextLevel, mirrorLevel: campaign.MirrorLevel);
|
GameMain.GameSession.StartRound(campaign.NextLevel, startOutpost: campaign.GetPredefinedStartOutpost(), mirrorLevel: campaign.MirrorLevel);
|
||||||
SubmarineSwitchLoad = false;
|
SubmarineSwitchLoad = false;
|
||||||
campaign.AssignClientCharacterInfos(connectedClients);
|
campaign.AssignClientCharacterInfos(connectedClients);
|
||||||
Log("Game mode: " + selectedMode.Name.Value, ServerLog.MessageType.ServerMessage);
|
Log("Game mode: " + selectedMode.Name.Value, ServerLog.MessageType.ServerMessage);
|
||||||
@@ -2320,17 +2447,18 @@ namespace Barotrauma.Networking
|
|||||||
yield return CoroutineStatus.Failure;
|
yield return CoroutineStatus.Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool missionAllowRespawn = !(GameMain.GameSession.GameMode is MissionMode missionMode) || !missionMode.Missions.Any(m => !m.AllowRespawn);
|
bool missionAllowRespawn = GameMain.GameSession.GameMode is not MissionMode missionMode || !missionMode.Missions.Any(m => !m.AllowRespawn);
|
||||||
bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost;
|
bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost;
|
||||||
|
|
||||||
if (ServerSettings.AllowRespawn && missionAllowRespawn)
|
if (ServerSettings.RespawnMode != RespawnMode.BetweenRounds && missionAllowRespawn)
|
||||||
{
|
{
|
||||||
RespawnManager = new RespawnManager(this, ServerSettings.UseRespawnShuttle && !isOutpost ? selectedShuttle : null);
|
RespawnManager = new RespawnManager(this, ServerSettings.UseRespawnShuttle && !isOutpost ? selectedShuttle : null);
|
||||||
}
|
}
|
||||||
if (campaign != null)
|
if (campaign != null)
|
||||||
{
|
{
|
||||||
campaign.CargoManager.CreatePurchasedItems();
|
campaign.CargoManager.CreatePurchasedItems();
|
||||||
campaign.SendCrewState();
|
//midround-joining clients need to be informed of pending/new hires at outposts
|
||||||
|
if (isOutpost) { campaign.SendCrewState(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
Level.Loaded?.SpawnNPCs();
|
Level.Loaded?.SpawnNPCs();
|
||||||
@@ -2371,6 +2499,8 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
//always allow the server owner to spectate even if it's disallowed in server settings
|
//always allow the server owner to spectate even if it's disallowed in server settings
|
||||||
teamClients.RemoveAll(c => c.Connection == OwnerConnection && c.SpectateOnly);
|
teamClients.RemoveAll(c => c.Connection == OwnerConnection && c.SpectateOnly);
|
||||||
|
// Clients with last character permanently dead spectate regardless of server settings
|
||||||
|
teamClients.RemoveAll(c => c.CharacterInfo != null && c.CharacterInfo.PermanentlyDead);
|
||||||
|
|
||||||
//if (!teamClients.Any() && n > 0) { continue; }
|
//if (!teamClients.Any() && n > 0) { continue; }
|
||||||
|
|
||||||
@@ -2439,6 +2569,7 @@ namespace Barotrauma.Networking
|
|||||||
wp.Submarine == Level.Loaded.StartOutpost &&
|
wp.Submarine == Level.Loaded.StartOutpost &&
|
||||||
wp.CurrentHull?.OutpostModuleTags != null &&
|
wp.CurrentHull?.OutpostModuleTags != null &&
|
||||||
wp.CurrentHull.OutpostModuleTags.Contains("airlock".ToIdentifier()));
|
wp.CurrentHull.OutpostModuleTags.Contains("airlock".ToIdentifier()));
|
||||||
|
|
||||||
while (spawnWaypoints.Count > characterInfos.Count)
|
while (spawnWaypoints.Count > characterInfos.Count)
|
||||||
{
|
{
|
||||||
spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count));
|
spawnWaypoints.RemoveAt(Rand.Int(spawnWaypoints.Count));
|
||||||
@@ -2486,13 +2617,17 @@ namespace Barotrauma.Networking
|
|||||||
characterData.ApplyWalletData(spawnedCharacter);
|
characterData.ApplyWalletData(spawnedCharacter);
|
||||||
spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]);
|
spawnedCharacter.GiveIdCardTags(mainSubWaypoints[i]);
|
||||||
spawnedCharacter.LoadTalents();
|
spawnedCharacter.LoadTalents();
|
||||||
|
|
||||||
characterData.HasSpawned = true;
|
characterData.HasSpawned = true;
|
||||||
}
|
}
|
||||||
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && spawnedCharacter.Info != null)
|
if (GameMain.GameSession?.GameMode is MultiPlayerCampaign mpCampaign && spawnedCharacter.Info != null)
|
||||||
{
|
{
|
||||||
spawnedCharacter.Info.SetExperience(Math.Max(spawnedCharacter.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(teamClients[i])));
|
spawnedCharacter.Info.SetExperience(Math.Max(spawnedCharacter.Info.ExperiencePoints, mpCampaign.GetSavedExperiencePoints(teamClients[i])));
|
||||||
mpCampaign.ClearSavedExperiencePoints(teamClients[i]);
|
mpCampaign.ClearSavedExperiencePoints(teamClients[i]);
|
||||||
|
|
||||||
|
if (spawnedCharacter.Info.LastRewardDistribution.TryUnwrap(out int salary))
|
||||||
|
{
|
||||||
|
spawnedCharacter.Wallet.SetRewardDistribution(salary);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
spawnedCharacter.SetOwnerClient(teamClients[i]);
|
spawnedCharacter.SetOwnerClient(teamClients[i]);
|
||||||
@@ -2584,11 +2719,12 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteInt32(seed);
|
msg.WriteInt32(seed);
|
||||||
msg.WriteIdentifier(gameSession.GameMode.Preset.Identifier);
|
msg.WriteIdentifier(gameSession.GameMode.Preset.Identifier);
|
||||||
bool missionAllowRespawn = GameMain.GameSession.GameMode is not MissionMode missionMode || !missionMode.Missions.Any(m => !m.AllowRespawn);
|
bool missionAllowRespawn = GameMain.GameSession.GameMode is not MissionMode missionMode || !missionMode.Missions.Any(m => !m.AllowRespawn);
|
||||||
msg.WriteBoolean(ServerSettings.AllowRespawn && missionAllowRespawn);
|
msg.WriteBoolean(ServerSettings.RespawnMode != RespawnMode.BetweenRounds && missionAllowRespawn);
|
||||||
msg.WriteBoolean(ServerSettings.AllowDisguises);
|
msg.WriteBoolean(ServerSettings.AllowDisguises);
|
||||||
msg.WriteBoolean(ServerSettings.AllowRewiring);
|
msg.WriteBoolean(ServerSettings.AllowRewiring);
|
||||||
msg.WriteBoolean(ServerSettings.AllowImmediateItemDelivery);
|
msg.WriteBoolean(ServerSettings.AllowImmediateItemDelivery);
|
||||||
msg.WriteBoolean(ServerSettings.AllowFriendlyFire);
|
msg.WriteBoolean(ServerSettings.AllowFriendlyFire);
|
||||||
|
msg.WriteBoolean(ServerSettings.AllowDragAndDropGive);
|
||||||
msg.WriteBoolean(ServerSettings.LockAllDefaultWires);
|
msg.WriteBoolean(ServerSettings.LockAllDefaultWires);
|
||||||
msg.WriteBoolean(ServerSettings.AllowLinkingWifiToChat);
|
msg.WriteBoolean(ServerSettings.AllowLinkingWifiToChat);
|
||||||
msg.WriteInt32(ServerSettings.MaximumMoneyTransferRequest);
|
msg.WriteInt32(ServerSettings.MaximumMoneyTransferRequest);
|
||||||
@@ -2696,7 +2832,7 @@ namespace Barotrauma.Networking
|
|||||||
GameMain.GameSession.CrewManager?.ServerWriteActiveOrders(msg);
|
GameMain.GameSession.CrewManager?.ServerWriteActiveOrders(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EndGame(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None, bool wasSaved = false)
|
public void EndGame(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None, bool wasSaved = false, IEnumerable<Mission> missions = null)
|
||||||
{
|
{
|
||||||
if (GameStarted)
|
if (GameStarted)
|
||||||
{
|
{
|
||||||
@@ -2712,14 +2848,14 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
string endMessage = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded");
|
string endMessage = TextManager.FormatServerMessage("RoundSummaryRoundHasEnded");
|
||||||
List<Mission> missions = GameMain.GameSession.Missions.ToList();
|
missions ??= GameMain.GameSession.Missions.ToList();
|
||||||
if (GameMain.GameSession is { IsRunning: true })
|
if (GameMain.GameSession is { IsRunning: true })
|
||||||
{
|
{
|
||||||
GameMain.GameSession.EndRound(endMessage);
|
GameMain.GameSession.EndRound(endMessage);
|
||||||
}
|
}
|
||||||
TraitorManager.TraitorResults? traitorResults = traitorManager?.GetEndResults() ?? null;
|
TraitorManager.TraitorResults? traitorResults = traitorManager?.GetEndResults() ?? null;
|
||||||
|
|
||||||
endRoundTimer = 0.0f;
|
EndRoundTimer = 0.0f;
|
||||||
|
|
||||||
if (ServerSettings.AutoRestart)
|
if (ServerSettings.AutoRestart)
|
||||||
{
|
{
|
||||||
@@ -2755,7 +2891,7 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteByte((byte)transitionType);
|
msg.WriteByte((byte)transitionType);
|
||||||
msg.WriteBoolean(wasSaved);
|
msg.WriteBoolean(wasSaved);
|
||||||
msg.WriteString(endMessage);
|
msg.WriteString(endMessage);
|
||||||
msg.WriteByte((byte)missions.Count);
|
msg.WriteByte((byte)missions.Count());
|
||||||
foreach (Mission mission in missions)
|
foreach (Mission mission in missions)
|
||||||
{
|
{
|
||||||
msg.WriteBoolean(mission.Completed);
|
msg.WriteBoolean(mission.Completed);
|
||||||
@@ -2799,6 +2935,12 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
logMsg = message.TextWithSender;
|
logMsg = message.TextWithSender;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (message.Sender is Character sender)
|
||||||
|
{
|
||||||
|
sender.TextChatVolume = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
Log(logMsg, ServerLog.MessageType.Chat);
|
Log(logMsg, ServerLog.MessageType.Chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2863,7 +3005,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsNameValid(Client c, string newName)
|
public bool IsNameValid(Client c, string newName)
|
||||||
{
|
{
|
||||||
newName = Client.SanitizeName(newName);
|
newName = Client.SanitizeName(newName);
|
||||||
|
|
||||||
@@ -2887,13 +3029,20 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Client nameTaken = ConnectedClients.Find(c2 => c != c2 && Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
|
Client nameTakenByClient = ConnectedClients.Find(c2 => c != c2 && Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
|
||||||
if (nameTaken != null)
|
if (nameTakenByClient != null)
|
||||||
{
|
{
|
||||||
SendDirectChatMessage($"ServerMessage.NameChangeFailedClientTooSimilar~[newname]={newName}~[takenname]={nameTaken.Name}", c, ChatMessageType.ServerMessageBox);
|
SendDirectChatMessage($"ServerMessage.NameChangeFailedClientTooSimilar~[newname]={newName}~[takenname]={nameTakenByClient.Name}", c, ChatMessageType.ServerMessageBox);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Character nameTakenByCharacter =
|
||||||
|
GameSession.GetSessionCrewCharacters(CharacterType.Both).FirstOrDefault(c2 => c2 != c.Character && Homoglyphs.Compare(c2.Name.ToLower(), newName.ToLower()));
|
||||||
|
if (nameTakenByCharacter != null)
|
||||||
|
{
|
||||||
|
SendDirectChatMessage($"ServerMessage.NameChangeFailedClientTooSimilar~[newname]={newName}~[takenname]={nameTakenByCharacter.Name}", c, ChatMessageType.ServerMessageBox);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3318,24 +3467,24 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public void SendOrderChatMessage(OrderChatMessage message)
|
public void SendOrderChatMessage(OrderChatMessage message)
|
||||||
{
|
{
|
||||||
if (message.Sender == null || message.Sender.SpeechImpediment >= 100.0f) { return; }
|
if (message.SenderCharacter == null || message.SenderCharacter.SpeechImpediment >= 100.0f) { return; }
|
||||||
//check which clients can receive the message and apply distance effects
|
//check which clients can receive the message and apply distance effects
|
||||||
foreach (Client client in ConnectedClients)
|
foreach (Client client in ConnectedClients)
|
||||||
{
|
{
|
||||||
if (message.Sender != null && client.Character != null && !client.Character.IsDead)
|
if (message.SenderCharacter != null && client.Character != null && !client.Character.IsDead)
|
||||||
{
|
{
|
||||||
//too far to hear the msg -> don't send
|
//too far to hear the msg -> don't send
|
||||||
if (!client.Character.CanHearCharacter(message.Sender)) { continue; }
|
if (!client.Character.CanHearCharacter(message.SenderCharacter)) { continue; }
|
||||||
}
|
}
|
||||||
SendDirectChatMessage(new OrderChatMessage(message.Order, message.Text, message.TargetCharacter, message.Sender, isNewOrder: message.IsNewOrder), client);
|
SendDirectChatMessage(new OrderChatMessage(message.Order, message.Text, message.TargetCharacter, message.Sender, isNewOrder: message.IsNewOrder), client);
|
||||||
}
|
}
|
||||||
if (!string.IsNullOrWhiteSpace(message.Text))
|
if (!string.IsNullOrWhiteSpace(message.Text))
|
||||||
{
|
{
|
||||||
AddChatMessage(new OrderChatMessage(message.Order, message.Text, message.TargetCharacter, message.Sender, isNewOrder: message.IsNewOrder));
|
AddChatMessage(new OrderChatMessage(message.Order, message.Text, message.TargetCharacter, message.Sender, isNewOrder: message.IsNewOrder));
|
||||||
if (ChatMessage.CanUseRadio(message.Sender, out var senderRadio))
|
if (ChatMessage.CanUseRadio(message.SenderCharacter, out var senderRadio))
|
||||||
{
|
{
|
||||||
//send to chat-linked wifi components
|
//send to chat-linked wifi components
|
||||||
Signal s = new Signal(message.Text, sender: message.Sender, source: senderRadio.Item);
|
Signal s = new Signal(message.Text, sender: message.SenderCharacter, source: senderRadio.Item);
|
||||||
senderRadio.TransmitSignal(s, sentFromChat: true);
|
senderRadio.TransmitSignal(s, sentFromChat: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3636,6 +3785,7 @@ namespace Barotrauma.Networking
|
|||||||
newCharacter.SetOwnerClient(client);
|
newCharacter.SetOwnerClient(client);
|
||||||
newCharacter.Enabled = true;
|
newCharacter.Enabled = true;
|
||||||
client.Character = newCharacter;
|
client.Character = newCharacter;
|
||||||
|
client.CharacterInfo = newCharacter.Info;
|
||||||
CreateEntityEvent(newCharacter, new Character.ControlEventData(client));
|
CreateEntityEvent(newCharacter, new Character.ControlEventData(client));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3679,6 +3829,14 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If a CharacterInfo for this Client already exists on the server, make sure it is used, and prevent the Client from replacing it
|
||||||
|
var existingCampaignData = (GameMain.GameSession?.Campaign as MultiPlayerCampaign)?.GetClientCharacterData(sender);
|
||||||
|
if (existingCampaignData != null)
|
||||||
|
{
|
||||||
|
sender.CharacterInfo = existingCampaignData.CharacterInfo;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, newName);
|
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, newName);
|
||||||
|
|
||||||
sender.CharacterInfo.RecreateHead(
|
sender.CharacterInfo.RecreateHead(
|
||||||
@@ -3841,7 +3999,7 @@ namespace Barotrauma.Networking
|
|||||||
foreach (Client c in unassigned)
|
foreach (Client c in unassigned)
|
||||||
{
|
{
|
||||||
//find all jobs that are still available
|
//find all jobs that are still available
|
||||||
var remainingJobs = jobList.FindAll(jp => assignedClientCount[jp] < jp.MaxNumber && c.Karma >= jp.MinKarma);
|
var remainingJobs = jobList.FindAll(jp => !jp.HiddenJob && assignedClientCount[jp] < jp.MaxNumber && c.Karma >= jp.MinKarma);
|
||||||
|
|
||||||
//all jobs taken, give a random job
|
//all jobs taken, give a random job
|
||||||
if (remainingJobs.Count == 0)
|
if (remainingJobs.Count == 0)
|
||||||
@@ -3884,9 +4042,15 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public void AssignBotJobs(List<CharacterInfo> bots, CharacterTeamType teamID)
|
public void AssignBotJobs(List<CharacterInfo> bots, CharacterTeamType teamID)
|
||||||
{
|
{
|
||||||
|
//shuffle first so the parts where we go through the prefabs
|
||||||
|
//and find ones there's too few of don't always pick the same job
|
||||||
|
List<JobPrefab> shuffledPrefabs = JobPrefab.Prefabs.Where(static jp => !jp.HiddenJob).ToList();
|
||||||
|
shuffledPrefabs.Shuffle();
|
||||||
|
|
||||||
Dictionary<JobPrefab, int> assignedPlayerCount = new Dictionary<JobPrefab, int>();
|
Dictionary<JobPrefab, int> assignedPlayerCount = new Dictionary<JobPrefab, int>();
|
||||||
foreach (JobPrefab jp in JobPrefab.Prefabs)
|
foreach (JobPrefab jp in shuffledPrefabs)
|
||||||
{
|
{
|
||||||
|
if (jp.HiddenJob) { continue; }
|
||||||
assignedPlayerCount.Add(jp, 0);
|
assignedPlayerCount.Add(jp, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3905,53 +4069,55 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<CharacterInfo> unassignedBots = new List<CharacterInfo>(bots);
|
List<CharacterInfo> unassignedBots = new List<CharacterInfo>(bots);
|
||||||
|
while (unassignedBots.Count > 0)
|
||||||
List<WayPoint> spawnPoints = WayPoint.WayPointList.FindAll(wp =>
|
|
||||||
wp.SpawnType == SpawnType.Human &&
|
|
||||||
wp.Submarine != null && wp.Submarine.TeamID == teamID)
|
|
||||||
.OrderBy(sp => Rand.Int(int.MaxValue))
|
|
||||||
.OrderBy(sp => sp.AssignedJob == null ? 0 : 1)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
bool canAssign = false;
|
|
||||||
do
|
|
||||||
{
|
{
|
||||||
canAssign = false;
|
//if there's any jobs left that must be included in the crew, assign those
|
||||||
foreach (WayPoint spawnPoint in spawnPoints)
|
var jobsBelowMinNumber = shuffledPrefabs.Where(jp => assignedPlayerCount[jp] < jp.MinNumber);
|
||||||
|
if (jobsBelowMinNumber.Any())
|
||||||
{
|
{
|
||||||
if (unassignedBots.Count == 0) { break; }
|
AssignJob(unassignedBots[0], jobsBelowMinNumber.GetRandomUnsynced());
|
||||||
|
|
||||||
JobPrefab jobPrefab = spawnPoint.AssignedJob ?? JobPrefab.Prefabs.GetRandomUnsynced();
|
|
||||||
if (assignedPlayerCount[jobPrefab] >= jobPrefab.MaxNumber) { continue; }
|
|
||||||
|
|
||||||
var variant = Rand.Range(0, jobPrefab.Variants, Rand.RandSync.ServerAndClient);
|
|
||||||
unassignedBots[0].Job = new Job(jobPrefab, Rand.RandSync.ServerAndClient, variant);
|
|
||||||
assignedPlayerCount[jobPrefab]++;
|
|
||||||
unassignedBots.Remove(unassignedBots[0]);
|
|
||||||
canAssign = true;
|
|
||||||
}
|
}
|
||||||
} while (unassignedBots.Count > 0 && canAssign);
|
else
|
||||||
|
{
|
||||||
|
//if there's any jobs left that are below the normal number of bots initially in the crew, assign those
|
||||||
|
var jobsBelowInitialCount = shuffledPrefabs.Where(jp => assignedPlayerCount[jp] < jp.InitialCount);
|
||||||
|
if (jobsBelowInitialCount.Any())
|
||||||
|
{
|
||||||
|
AssignJob(unassignedBots[0], jobsBelowInitialCount.GetRandomUnsynced());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
//no "must-have-jobs" left, break and start assigning randomly
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//find a suitable job for the rest of the bots
|
foreach (CharacterInfo c in unassignedBots.ToList())
|
||||||
foreach (CharacterInfo c in unassignedBots)
|
|
||||||
{
|
{
|
||||||
//find all jobs that are still available
|
//find all jobs that are still available
|
||||||
var remainingJobs = JobPrefab.Prefabs.Where(jp => assignedPlayerCount[jp] < jp.MaxNumber);
|
var remainingJobs = shuffledPrefabs.Where(jp => assignedPlayerCount[jp] < jp.MaxNumber);
|
||||||
//all jobs taken, give a random job
|
//all jobs taken, give a random job
|
||||||
if (remainingJobs.None())
|
if (remainingJobs.None())
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("Failed to assign a suitable job for bot \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
|
DebugConsole.ThrowError("Failed to assign a suitable job for bot \"" + c.Name + "\" (all jobs already have the maximum numbers of players). Assigning a random job...");
|
||||||
c.Job = Job.Random(Rand.RandSync.ServerAndClient);
|
AssignJob(c, shuffledPrefabs.GetRandomUnsynced());
|
||||||
assignedPlayerCount[c.Job.Prefab]++;
|
|
||||||
}
|
}
|
||||||
else //some jobs still left, choose one of them by random
|
else
|
||||||
{
|
{
|
||||||
var job = remainingJobs.GetRandomUnsynced();
|
//some jobs still left, choose one of them by random (preferring ones there's the least of in the crew)
|
||||||
var variant = Rand.Range(0, job.Variants);
|
var selectedJob = remainingJobs.GetRandomByWeight(jp => 1.0f / Math.Max(assignedPlayerCount[jp], 0.01f), Rand.RandSync.Unsynced);
|
||||||
c.Job = new Job(job, Rand.RandSync.Unsynced, variant);
|
AssignJob(c, selectedJob);
|
||||||
assignedPlayerCount[c.Job.Prefab]++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AssignJob(CharacterInfo bot, JobPrefab job)
|
||||||
|
{
|
||||||
|
int variant = Rand.Range(0, job.Variants);
|
||||||
|
bot.Job = new Job(job, Rand.RandSync.Unsynced, variant);
|
||||||
|
assignedPlayerCount[bot.Job.Prefab]++;
|
||||||
|
unassignedBots.Remove(bot);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Client FindClientWithJobPreference(List<Client> clients, JobPrefab job, bool forceAssign = false)
|
private Client FindClientWithJobPreference(List<Client> clients, JobPrefab job, bool forceAssign = false)
|
||||||
|
|||||||
@@ -458,15 +458,35 @@ namespace Barotrauma.Networking
|
|||||||
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
RemovePendingClient(pendingClient, PeerDisconnectPacket.WithReason(DisconnectReason.AuthenticationFailed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authenticators is null &&
|
||||||
|
GameMain.Server.ServerSettings.RequireAuthentication)
|
||||||
|
{
|
||||||
|
DebugConsole.NewMessage(
|
||||||
|
"The server is configured to require authentication from clients, but there are no authenticators available. " +
|
||||||
|
$"If you're for example trying to host a server in a local network without being connected to Steam or Epic Online Services, please set {nameof(GameMain.Server.ServerSettings.RequireAuthentication)} to false in the server settings.",
|
||||||
|
Microsoft.Xna.Framework.Color.Yellow);
|
||||||
|
}
|
||||||
|
|
||||||
if (authenticators is null
|
if (authenticators is null
|
||||||
|| !packet.AuthTicket.TryUnwrap(out var authTicket)
|
|| !packet.AuthTicket.TryUnwrap(out var authTicket)
|
||||||
|| !authenticators.TryGetValue(authTicket.Kind, out var authenticator))
|
|| !authenticators.TryGetValue(authTicket.Kind, out var authenticator))
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
DebugConsole.NewMessage($"Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
|
DebugConsole.NewMessage("Debug server accepts unauthenticated connections", Microsoft.Xna.Framework.Color.Yellow);
|
||||||
acceptClient(new AccountInfo(packet.AccountId));
|
acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
|
||||||
#else
|
#else
|
||||||
rejectClient();
|
if (GameMain.Server.ServerSettings.RequireAuthentication)
|
||||||
|
{
|
||||||
|
DebugConsole.NewMessage(
|
||||||
|
"A client attempted to join without an authentication ticket, but the server is configured to require authentication. " +
|
||||||
|
$"If you're for example trying to host a server in a local network without being connected to Steam or Epic Online Services, please set {nameof(GameMain.Server.ServerSettings.RequireAuthentication)} to false in the server settings.",
|
||||||
|
Microsoft.Xna.Framework.Color.Yellow);
|
||||||
|
rejectClient();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
acceptClient(new AccountInfo(new UnauthenticatedAccountId(packet.Name)));
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,8 @@ namespace Barotrauma.Networking
|
|||||||
private int pendingRespawnCount, requiredRespawnCount;
|
private int pendingRespawnCount, requiredRespawnCount;
|
||||||
private int prevPendingRespawnCount, prevRequiredRespawnCount;
|
private int prevPendingRespawnCount, prevRequiredRespawnCount;
|
||||||
|
|
||||||
|
public bool IsShuttleInsideLevel => RespawnShuttle != null && RespawnShuttle.WorldPosition.Y < Level.Loaded.Size.Y;
|
||||||
|
|
||||||
private IEnumerable<Client> GetClientsToRespawn()
|
private IEnumerable<Client> GetClientsToRespawn()
|
||||||
{
|
{
|
||||||
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
|
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
|
||||||
@@ -24,18 +26,27 @@ namespace Barotrauma.Networking
|
|||||||
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { continue; }
|
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { continue; }
|
||||||
if (c.Character != null && !c.Character.IsDead) { continue; }
|
if (c.Character != null && !c.Character.IsDead) { continue; }
|
||||||
|
|
||||||
//don't allow respawn if the client already has a character (they'll regain control once they're in sync)
|
|
||||||
var matchingData = campaign?.GetClientCharacterData(c);
|
var matchingData = campaign?.GetClientCharacterData(c);
|
||||||
|
|
||||||
|
//don't allow respawn if the client already has a character (they'll regain control once they're in sync)
|
||||||
if (matchingData != null && matchingData.HasSpawned &&
|
if (matchingData != null && matchingData.HasSpawned &&
|
||||||
Character.CharacterList.Any(c => c.Info == matchingData.CharacterInfo && !c.IsDead))
|
Character.CharacterList.Any(c => c.Info == matchingData.CharacterInfo && !c.IsDead))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (UseRespawnPrompt)
|
// Respawning might also be needed in permadeath mode for disconnected characters, but never for permanently dead ones
|
||||||
|
if (GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath } &&
|
||||||
|
(matchingData?.CharacterInfo is { PermanentlyDead: true } || c.Character is { IsDead: true }))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (campaign != null)
|
||||||
{
|
{
|
||||||
if (matchingData != null && matchingData.HasSpawned)
|
if (matchingData != null && matchingData.HasSpawned)
|
||||||
{
|
{
|
||||||
|
//in the campaign mode, wait for the client to choose whether they want to spawn
|
||||||
if (!c.WaitForNextRoundRespawn.HasValue || c.WaitForNextRoundRespawn.Value) { continue; }
|
if (!c.WaitForNextRoundRespawn.HasValue || c.WaitForNextRoundRespawn.Value) { continue; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -44,9 +55,9 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsRespawnPromptPendingForClient(Client c)
|
private static bool IsRespawnDecisionPendingForClient(Client c)
|
||||||
{
|
{
|
||||||
if (!UseRespawnPrompt || !(GameMain.GameSession.GameMode is MultiPlayerCampaign campaign)) { return false; }
|
if (Level.Loaded == null || GameMain.GameSession.GameMode is not MultiPlayerCampaign campaign) { return false; }
|
||||||
|
|
||||||
if (!c.InGame) { return false; }
|
if (!c.InGame) { return false; }
|
||||||
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { return false; }
|
if (c.SpectateOnly && (GameMain.Server.ServerSettings.AllowSpectating || GameMain.Server.OwnerConnection == c.Connection)) { return false; }
|
||||||
@@ -55,7 +66,9 @@ namespace Barotrauma.Networking
|
|||||||
var matchingData = campaign.GetClientCharacterData(c);
|
var matchingData = campaign.GetClientCharacterData(c);
|
||||||
if (matchingData != null && matchingData.HasSpawned)
|
if (matchingData != null && matchingData.HasSpawned)
|
||||||
{
|
{
|
||||||
if (Character.CharacterList.Any(c => c.Info == matchingData.CharacterInfo && !c.IsDead))
|
if (Character.CharacterList.Any(c =>
|
||||||
|
c.Info == matchingData.CharacterInfo &&
|
||||||
|
(!c.IsDead || c.CauseOfDeath is { Type: CauseOfDeathType.Disconnected })))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -180,22 +193,27 @@ namespace Barotrauma.Networking
|
|||||||
shuttleSteering.TargetVelocity = Vector2.Zero;
|
shuttleSteering.TargetVelocity = Vector2.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.Spawning);
|
|
||||||
|
|
||||||
Vector2 spawnPos = FindSpawnPos();
|
Vector2 spawnPos = FindSpawnPos();
|
||||||
RespawnCharacters(spawnPos);
|
RespawnCharacters(spawnPos, out bool anyCharacterSpawnedInShuttle);
|
||||||
|
if (anyCharacterSpawnedInShuttle)
|
||||||
CoroutineManager.StopCoroutines("forcepos");
|
|
||||||
if (spawnPos.Y > Level.Loaded.Size.Y)
|
|
||||||
{
|
{
|
||||||
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
GameServer.Log("Dispatching the respawn shuttle.", ServerLog.MessageType.Spawning);
|
||||||
|
CoroutineManager.StopCoroutines("forcepos");
|
||||||
|
if (spawnPos.Y > Level.Loaded.Size.Y)
|
||||||
|
{
|
||||||
|
CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
RespawnShuttle.SetPosition(spawnPos);
|
||||||
|
RespawnShuttle.Velocity = Vector2.Zero;
|
||||||
|
RespawnShuttle.NeutralizeBallast();
|
||||||
|
RespawnShuttle.EnableMaintainPosition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
RespawnShuttle.SetPosition(spawnPos);
|
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
||||||
RespawnShuttle.Velocity = Vector2.Zero;
|
|
||||||
RespawnShuttle.NeutralizeBallast();
|
|
||||||
RespawnShuttle.EnableMaintainPosition();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -204,7 +222,7 @@ namespace Barotrauma.Networking
|
|||||||
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
GameServer.Log("Respawning everyone in main sub.", ServerLog.MessageType.Spawning);
|
||||||
GameMain.Server.CreateEntityEvent(this);
|
GameMain.Server.CreateEntityEvent(this);
|
||||||
|
|
||||||
RespawnCharacters(null);
|
RespawnCharacters(shuttlePos: null, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,7 +262,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RespawnShuttle.WorldPosition.Y > Level.Loaded.Size.Y || DateTime.Now > despawnTime)
|
if (!IsShuttleInsideLevel || DateTime.Now > despawnTime)
|
||||||
{
|
{
|
||||||
CoroutineManager.StopCoroutines("forcepos");
|
CoroutineManager.StopCoroutines("forcepos");
|
||||||
|
|
||||||
@@ -289,7 +307,10 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (DateTime.Now > ReturnTime)
|
if (DateTime.Now > ReturnTime)
|
||||||
{
|
{
|
||||||
GameServer.Log("The respawn shuttle is leaving.", ServerLog.MessageType.ServerMessage);
|
if (IsShuttleInsideLevel)
|
||||||
|
{
|
||||||
|
GameServer.Log("The respawn shuttle is leaving.", ServerLog.MessageType.ServerMessage);
|
||||||
|
}
|
||||||
CurrentState = State.Returning;
|
CurrentState = State.Returning;
|
||||||
|
|
||||||
GameMain.Server.CreateEntityEvent(this);
|
GameMain.Server.CreateEntityEvent(this);
|
||||||
@@ -311,8 +332,8 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
return shuttleEmptyTimer > 1.0f;
|
return shuttleEmptyTimer > 1.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
partial void RespawnCharactersProjSpecific(Vector2? shuttlePos)
|
private void RespawnCharacters(Vector2? shuttlePos, out bool anyCharacterSpawnedInShuttle)
|
||||||
{
|
{
|
||||||
respawnedCharacters.Clear();
|
respawnedCharacters.Clear();
|
||||||
|
|
||||||
@@ -337,7 +358,7 @@ namespace Barotrauma.Networking
|
|||||||
//all characters are in Team 1 in game modes/missions with only one team.
|
//all characters are in Team 1 in game modes/missions with only one team.
|
||||||
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
|
//if at some point we add a game mode with multiple teams where respawning is possible, this needs to be reworked
|
||||||
c.TeamID = CharacterTeamType.Team1;
|
c.TeamID = CharacterTeamType.Team1;
|
||||||
if (c.CharacterInfo == null) { c.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, c.Name); }
|
c.CharacterInfo ??= new CharacterInfo(CharacterPrefab.HumanSpeciesName, c.Name);
|
||||||
}
|
}
|
||||||
List<CharacterInfo> characterInfos = clients.Select(c => c.CharacterInfo).ToList();
|
List<CharacterInfo> characterInfos = clients.Select(c => c.CharacterInfo).ToList();
|
||||||
|
|
||||||
@@ -378,6 +399,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
|
var cargoSp = WayPoint.WayPointList.Find(wp => wp.Submarine == respawnSub && wp.SpawnType == SpawnType.Cargo);
|
||||||
|
|
||||||
|
anyCharacterSpawnedInShuttle = false;
|
||||||
|
|
||||||
for (int i = 0; i < characterInfos.Count; i++)
|
for (int i = 0; i < characterInfos.Count; i++)
|
||||||
{
|
{
|
||||||
bool bot = i >= clients.Count;
|
bool bot = i >= clients.Count;
|
||||||
@@ -412,10 +435,19 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!forceSpawnInMainSub)
|
||||||
|
{
|
||||||
|
anyCharacterSpawnedInShuttle = true;
|
||||||
|
}
|
||||||
|
|
||||||
var character = Character.Create(characterInfos[i], (forceSpawnInMainSub ? mainSubSpawnPoints[i] : shuttleSpawnPoints[i]).WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot);
|
var character = Character.Create(characterInfos[i], (forceSpawnInMainSub ? mainSubSpawnPoints[i] : shuttleSpawnPoints[i]).WorldPosition, characterInfos[i].Name, isRemotePlayer: !bot, hasAi: bot);
|
||||||
characterCampaignData?.ApplyWalletData(character);
|
characterCampaignData?.ApplyWalletData(character);
|
||||||
character.TeamID = CharacterTeamType.Team1;
|
character.TeamID = CharacterTeamType.Team1;
|
||||||
character.LoadTalents();
|
character.LoadTalents();
|
||||||
|
if (characterInfos[i].LastRewardDistribution.TryUnwrap(out int salary))
|
||||||
|
{
|
||||||
|
character.Wallet.SetRewardDistribution(salary);
|
||||||
|
}
|
||||||
|
|
||||||
respawnedCharacters.Add(character);
|
respawnedCharacters.Add(character);
|
||||||
|
|
||||||
@@ -446,7 +478,7 @@ namespace Barotrauma.Networking
|
|||||||
$"Respawning {GameServer.ClientLogName(clients[i])} ({clients[i].Connection.Endpoint}) as {characterInfos[i].Job.Name}", ServerLog.MessageType.Spawning);
|
$"Respawning {GameServer.ClientLogName(clients[i])} ({clients[i].Connection.Endpoint}) as {characterInfos[i].Job.Name}", ServerLog.MessageType.Spawning);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (RespawnShuttle != null)
|
if (RespawnShuttle != null && anyCharacterSpawnedInShuttle)
|
||||||
{
|
{
|
||||||
List<Item> newRespawnItems = new List<Item>();
|
List<Item> newRespawnItems = new List<Item>();
|
||||||
Vector2 pos = cargoSp?.Position ?? character.Position;
|
Vector2 pos = cargoSp?.Position ?? character.Position;
|
||||||
@@ -567,9 +599,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
foreach (Skill skill in characterInfo.Job.GetSkills())
|
foreach (Skill skill in characterInfo.Job.GetSkills())
|
||||||
{
|
{
|
||||||
var skillPrefab = characterInfo.Job.Prefab.Skills.Find(s => skill.Identifier == s.Identifier);
|
skill.Level = GetReducedSkill(characterInfo, skill, skillLossPercentage);
|
||||||
if (skillPrefab == null || skill.Level < skillPrefab.LevelRange.End) { continue; }
|
|
||||||
skill.Level = MathHelper.Lerp(skill.Level, skillPrefab.LevelRange.End, skillLossPercentage / 100.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -585,14 +615,10 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteSingle((float)(ReturnTime - DateTime.Now).TotalSeconds);
|
msg.WriteSingle((float)(ReturnTime - DateTime.Now).TotalSeconds);
|
||||||
break;
|
break;
|
||||||
case State.Waiting:
|
case State.Waiting:
|
||||||
MultiPlayerCampaign campaign = GameMain.GameSession.GameMode as MultiPlayerCampaign;
|
|
||||||
var matchingData = campaign?.GetClientCharacterData(c);
|
|
||||||
bool forceSpawnInMainSub = matchingData != null && !matchingData.HasSpawned;
|
|
||||||
msg.WriteUInt16((ushort)pendingRespawnCount);
|
msg.WriteUInt16((ushort)pendingRespawnCount);
|
||||||
msg.WriteUInt16((ushort)requiredRespawnCount);
|
msg.WriteUInt16((ushort)requiredRespawnCount);
|
||||||
msg.WriteBoolean(IsRespawnPromptPendingForClient(c));
|
msg.WriteBoolean(IsRespawnDecisionPendingForClient(c));
|
||||||
msg.WriteBoolean(RespawnCountdownStarted);
|
msg.WriteBoolean(RespawnCountdownStarted);
|
||||||
msg.WriteBoolean(forceSpawnInMainSub);
|
|
||||||
msg.WriteSingle((float)(RespawnTime - DateTime.Now).TotalSeconds);
|
msg.WriteSingle((float)(RespawnTime - DateTime.Now).TotalSeconds);
|
||||||
break;
|
break;
|
||||||
case State.Returning:
|
case State.Returning:
|
||||||
|
|||||||
@@ -123,5 +123,21 @@ namespace Barotrauma.Networking
|
|||||||
return garbleAmount < 1.0f;
|
return garbleAmount < 1.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void Read(IReadMessage inc, Client connectedClient)
|
||||||
|
{
|
||||||
|
var queue = connectedClient.VoipQueue;
|
||||||
|
if (queue.Read(inc, discardData: false))
|
||||||
|
{
|
||||||
|
connectedClient.VoipServerDecoder.OnNewVoiceReceived();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
var msg = new WriteOnlyMessage().WithHeader(ServerPacketHeader.VOICE_AMPLITUDE_DEBUG);
|
||||||
|
msg.WriteRangedSingle(connectedClient.VoipServerDecoder.Amplitude, min: 0, max: 1, bitCount: 8);
|
||||||
|
|
||||||
|
GameMain.Server?.ServerPeer?.Send(msg, connectedClient.Connection, DeliveryMethod.Unreliable);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,230 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Barotrauma.IO;
|
||||||
|
using System.Text;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
using Concentus.Structs;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal sealed class VoipServerDecoder
|
||||||
|
{
|
||||||
|
private readonly OpusDecoder decoder;
|
||||||
|
private readonly VoipQueue queue;
|
||||||
|
private int lastRetrievedBufferID;
|
||||||
|
|
||||||
|
public float Amplitude { get; private set; }
|
||||||
|
|
||||||
|
private readonly Client ownerClient;
|
||||||
|
|
||||||
|
public VoipServerDecoder(VoipQueue q, Client owner)
|
||||||
|
{
|
||||||
|
ownerClient = owner;
|
||||||
|
decoder = VoipConfig.CreateDecoder();
|
||||||
|
queue = q;
|
||||||
|
lastRetrievedBufferID = q.LatestBufferID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool debugVoip;
|
||||||
|
/// <summary>
|
||||||
|
/// When set to true the server will write VOIP into an audio file for debugging purposes.
|
||||||
|
/// Useful if you're modifying this part of the code and want to be able to hear what the server "hears"
|
||||||
|
/// </summary>
|
||||||
|
public static bool DebugVoip
|
||||||
|
{
|
||||||
|
get => debugVoip;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
#if !DEBUG
|
||||||
|
debugVoip = false;
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("DebugVoip is only available in debug builds of the game");
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
|
||||||
|
debugVoip = value;
|
||||||
|
|
||||||
|
if (!value)
|
||||||
|
{
|
||||||
|
if (GameMain.Server is null) { return; }
|
||||||
|
foreach (var c in GameMain.Server.ConnectedClients)
|
||||||
|
{
|
||||||
|
c.VoipServerDecoder.ClearStoredDebugSamples();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly List<short[]> debugStoredSamples = new();
|
||||||
|
|
||||||
|
private float debugWriteTimerBacking;
|
||||||
|
private float DebugWriteTimer
|
||||||
|
{
|
||||||
|
get => debugWriteTimerBacking;
|
||||||
|
set => debugWriteTimerBacking = Math.Clamp(value, min: 0, max: DebugWriteTimeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool shouldWriteDebugFile;
|
||||||
|
private const float DebugWriteTimeout = 3f; // 3 seconds of no data before writing to file
|
||||||
|
|
||||||
|
public void OnNewVoiceReceived()
|
||||||
|
{
|
||||||
|
float amplitude = 0.0f;
|
||||||
|
for (int i = lastRetrievedBufferID + 1; i <= queue.LatestBufferID; i++)
|
||||||
|
{
|
||||||
|
queue.RetrieveBuffer(i, out int compressedSize, out byte[] compressedBuffer);
|
||||||
|
if (compressedSize <= 0) { continue; }
|
||||||
|
|
||||||
|
short[] buffer = new short[VoipConfig.BUFFER_SIZE];
|
||||||
|
decoder.Decode(compressedBuffer, 0, compressedSize, buffer, 0, VoipConfig.BUFFER_SIZE);
|
||||||
|
amplitude = Math.Max(amplitude, GetAmplitude(buffer));
|
||||||
|
lastRetrievedBufferID = i;
|
||||||
|
|
||||||
|
if (!DebugVoip) { continue; }
|
||||||
|
lock (debugStoredSamples) { debugStoredSamples.Add(buffer); }
|
||||||
|
}
|
||||||
|
|
||||||
|
Amplitude = amplitude;
|
||||||
|
|
||||||
|
if (DebugVoip)
|
||||||
|
{
|
||||||
|
DebugWriteTimer = DebugWriteTimeout;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DebugUpdate(float deltaTime)
|
||||||
|
{
|
||||||
|
if (!DebugVoip) { return; }
|
||||||
|
|
||||||
|
if (DebugWriteTimer > 0)
|
||||||
|
{
|
||||||
|
DebugWriteTimer -= deltaTime;
|
||||||
|
if (DebugWriteTimer <= 0)
|
||||||
|
{
|
||||||
|
shouldWriteDebugFile = true;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldWriteDebugFile) { return; }
|
||||||
|
|
||||||
|
lock (debugStoredSamples)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
WriteSamplesToWaveFile(debugStoredSamples,
|
||||||
|
filename: $"voip_{ownerClient.Name}_{DateTimeOffset.UtcNow.ToUnixTimeSeconds()}.wav",
|
||||||
|
sampleRate: VoipConfig.FREQUENCY,
|
||||||
|
channels: 1);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
debugStoredSamples.Clear();
|
||||||
|
shouldWriteDebugFile = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float GetAmplitude(short[] values)
|
||||||
|
{
|
||||||
|
float max = 0;
|
||||||
|
foreach (short v in values)
|
||||||
|
{
|
||||||
|
max = Math.Max(max, ToolBox.ShortAudioSampleToFloat(v));
|
||||||
|
}
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Writes the given audio samples to a wave file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="samples">The audio samples to write.</param>
|
||||||
|
/// <param name="filename">The name of the wave file to create.</param>
|
||||||
|
/// <param name="sampleRate">The sample rate of the audio.</param>
|
||||||
|
/// <param name="channels">The number of channels in the audio.</param>
|
||||||
|
private static void WriteSamplesToWaveFile(IReadOnlyList<short[]> samples, string filename, int sampleRate, short channels)
|
||||||
|
{
|
||||||
|
if (!samples.Any()) { return; }
|
||||||
|
|
||||||
|
var path = Path.Combine(Path.GetFullPath("AudioDebug"));
|
||||||
|
if (!Directory.Exists(path))
|
||||||
|
{
|
||||||
|
var dir = Directory.CreateDirectory(path);
|
||||||
|
if (dir is not { Exists: true }) { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
using var outFile = File.Create(Path.Combine(path, ToolBox.RemoveInvalidFileNameChars(filename)));
|
||||||
|
|
||||||
|
if (outFile is null)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("Failed to create audio debug file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// wave file format: https://docs.fileformat.com/audio/wav/
|
||||||
|
using var writer = new System.IO.BinaryWriter(outFile);
|
||||||
|
|
||||||
|
const short pcmFormat = 1; // PCM
|
||||||
|
const short bitsPerSample = 16; // 16 bits in a short
|
||||||
|
int byteRate = sampleRate * bitsPerSample * channels / 8;
|
||||||
|
short blockAlign = (short)(bitsPerSample * channels / 8);
|
||||||
|
|
||||||
|
// === FILE INFO === //
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("RIFF"));
|
||||||
|
long sizePos = outFile.Position;
|
||||||
|
writer.Write(0); // size of file, will be written later
|
||||||
|
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("WAVE"));
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("fmt ")); // trailing space is required, not a typo
|
||||||
|
|
||||||
|
writer.Write(16); // length of format header
|
||||||
|
|
||||||
|
// === AUDIO FORMAT === //
|
||||||
|
writer.Write(pcmFormat);
|
||||||
|
writer.Write(channels);
|
||||||
|
writer.Write(sampleRate);
|
||||||
|
writer.Write(byteRate);
|
||||||
|
writer.Write(blockAlign);
|
||||||
|
writer.Write(bitsPerSample);
|
||||||
|
|
||||||
|
// === SAMPLE DATA === //
|
||||||
|
writer.Write(Encoding.ASCII.GetBytes("data"));
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
long dataPos = outFile.Position;
|
||||||
|
writer.Write(0); // temporary data size
|
||||||
|
|
||||||
|
foreach (var sample in samples)
|
||||||
|
{
|
||||||
|
foreach (var s in sample)
|
||||||
|
{
|
||||||
|
writer.Write(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
writer.Flush();
|
||||||
|
|
||||||
|
// write the file size
|
||||||
|
writer.Seek((int)sizePos, System.IO.SeekOrigin.Begin);
|
||||||
|
writer.Write((int)(outFile.Length - 8)); // spec says to subtract 8 bytes from the file size
|
||||||
|
|
||||||
|
// write the data size
|
||||||
|
writer.Seek((int)dataPos, System.IO.SeekOrigin.Begin);
|
||||||
|
writer.Write((int)(outFile.Length - dataPos)); // size of the data only
|
||||||
|
|
||||||
|
writer.Flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearStoredDebugSamples()
|
||||||
|
{
|
||||||
|
lock (debugStoredSamples)
|
||||||
|
{
|
||||||
|
debugStoredSamples.Clear();
|
||||||
|
}
|
||||||
|
DebugWriteTimer = 0;
|
||||||
|
shouldWriteDebugFile = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -68,13 +68,13 @@ namespace Barotrauma.Steam
|
|||||||
foreach (var contentPackage in contentPackages)
|
foreach (var contentPackage in contentPackages)
|
||||||
{
|
{
|
||||||
Steamworks.SteamServer.SetKey(
|
Steamworks.SteamServer.SetKey(
|
||||||
$"contentpackage{index}",
|
$"contentpackage{index}",
|
||||||
new ServerListContentPackageInfo(contentPackage).ToString());
|
new ServerListContentPackageInfo(contentPackage).ToString());
|
||||||
index++;
|
index++;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Steamworks.SteamServer.SetKey(key.Value.ToLowerInvariant(), value.ToString());
|
Steamworks.SteamServer.SetKey(key.Value.ToLowerInvariant(), value.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.4.6.0</Version>
|
<Version>1.5.9.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
@@ -70,6 +70,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(Configuration)'!='Debug'">
|
<ItemGroup Condition="'$(Configuration)'!='Debug'">
|
||||||
|
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Release" />
|
||||||
@@ -77,6 +78,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
<ItemGroup Condition="'$(Configuration)'=='Debug'">
|
||||||
|
<ProjectReference Include="..\..\Libraries\Concentus\CSharp\Concentus\Concentus.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Facepunch.Steamworks\Facepunch.Steamworks.Win64.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Farseer Physics Engine 3.5\Farseer.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
<ProjectReference Include="..\..\Libraries\Hyper.ComponentModel\Hyper.ComponentModel.NetStandard.csproj" AdditionalProperties="Configuration=Debug" />
|
||||||
|
|||||||
@@ -33,6 +33,7 @@
|
|||||||
<Command name="togglekarmatestmode"/>
|
<Command name="togglekarmatestmode"/>
|
||||||
<Command name="respawnnow"/>
|
<Command name="respawnnow"/>
|
||||||
<Command name="traitorlist"/>
|
<Command name="traitorlist"/>
|
||||||
|
<Command name="setsalary"/>
|
||||||
</Preset>
|
</Preset>
|
||||||
|
|
||||||
<Preset
|
<Preset
|
||||||
@@ -94,5 +95,6 @@
|
|||||||
<Command name="togglecampaignteleport"/>
|
<Command name="togglecampaignteleport"/>
|
||||||
<Command name="respawnnow"/>
|
<Command name="respawnnow"/>
|
||||||
<Command name="traitorlist"/>
|
<Command name="traitorlist"/>
|
||||||
|
<Command name="setsalary"/>
|
||||||
</Preset>
|
</Preset>
|
||||||
</PermissionPresets>
|
</PermissionPresets>
|
||||||
@@ -184,6 +184,20 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is some condition met (e.g. entity null, indetectable, outside level) that prevents anyone from detecting the target?
|
||||||
|
/// </summary>
|
||||||
|
public bool ShouldBeIgnored()
|
||||||
|
{
|
||||||
|
if (InDetectable) { return true; }
|
||||||
|
if (Entity == null) { return true; }
|
||||||
|
if (Level.Loaded != null && WorldPosition.Y > Level.Loaded.Size.Y)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public AITarget(Entity e, XElement element) : this(e)
|
public AITarget(Entity e, XElement element) : this(e)
|
||||||
{
|
{
|
||||||
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
SightRange = element.GetAttributeFloat("sightrange", 0.0f);
|
||||||
|
|||||||
@@ -216,7 +216,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool IsAttackingOwner(Character other) =>
|
private bool IsAttackingOwner(Character other) =>
|
||||||
PetBehavior != null && PetBehavior.Owner != null &&
|
PetBehavior != null && PetBehavior.Owner != null &&
|
||||||
!other.IsUnconscious && !other.IsArrested &&
|
!other.IsUnconscious && !other.IsHandcuffed &&
|
||||||
other.AIController is HumanAIController humanAI &&
|
other.AIController is HumanAIController humanAI &&
|
||||||
humanAI.ObjectiveManager.CurrentObjective is AIObjectiveCombat combat &&
|
humanAI.ObjectiveManager.CurrentObjective is AIObjectiveCombat combat &&
|
||||||
combat.Enemy != null && combat.Enemy == PetBehavior.Owner;
|
combat.Enemy != null && combat.Enemy == PetBehavior.Owner;
|
||||||
@@ -2694,13 +2694,8 @@ namespace Barotrauma
|
|||||||
float maxModifier = 5;
|
float maxModifier = 5;
|
||||||
foreach (AITarget aiTarget in AITarget.List)
|
foreach (AITarget aiTarget in AITarget.List)
|
||||||
{
|
{
|
||||||
if (aiTarget.InDetectable) { continue; }
|
if (aiTarget.ShouldBeIgnored()) { continue; }
|
||||||
if (aiTarget.Entity == null) { continue; }
|
|
||||||
if (ignoredTargets.Contains(aiTarget)) { continue; }
|
if (ignoredTargets.Contains(aiTarget)) { continue; }
|
||||||
if (Level.Loaded != null && aiTarget.WorldPosition.Y > Level.Loaded.Size.Y)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (aiTarget.Type == AITarget.TargetType.HumanOnly) { continue; }
|
if (aiTarget.Type == AITarget.TargetType.HumanOnly) { continue; }
|
||||||
if (!TargetOutposts)
|
if (!TargetOutposts)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user