Merge branch 'master' of https://github.com/Regalis11/Barotrauma into develop

This commit is contained in:
EvilFactory
2025-04-10 10:37:09 -03:00
296 changed files with 8420 additions and 2945 deletions

View File

@@ -73,7 +73,7 @@ body:
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.
options:
- v1.7.7.0 (Winter Update)
- v1.8.6.2 (Calm Before the Storm update)
- Other
validations:
required: true

1
.gitignore vendored
View File

@@ -54,6 +54,7 @@ temp.txt
# Private assets
Barotrauma/BarotraumaShared/Content/*
Barotrauma/**/GameAnalyticsKeys.cs
Deploy/DeployAll/PrivateKey.*
.github/ISSUE_TEMPLATE/release-checklist.md
#Rider

View File

@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework;
using FarseerPhysics;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
@@ -63,6 +64,25 @@ namespace Barotrauma
stringDrawPos += new Vector2(0, 20);
GUI.DrawString(spriteBatch, stringDrawPos, $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
}
if (currentObjective is AIObjectiveCombat
{
Weapon: Item weapon,
BlockedPositions: List<Vector2> blockedPositions
})
{
Vector2 weaponPos = weapon.DrawPosition;
weaponPos.Y = -weaponPos.Y;
foreach (Vector2 blockedPosition in blockedPositions)
{
Vector2 blockedPos = blockedPosition;
if (Character.Submarine != null)
{
blockedPos += Character.Submarine.DrawPosition;
}
blockedPos.Y = -blockedPos.Y;
GUI.DrawLine(spriteBatch, weaponPos, blockedPos, Color.Red);
}
}
}
Vector2 objectiveStringDrawPos = stringDrawPos + new Vector2(120, 40);

View File

@@ -23,13 +23,13 @@ namespace Barotrauma
partial void UpdateNetPlayerPositionProjSpecific(float deltaTime, float lowestSubPos)
{
if (character != GameMain.Client.Character || !character.CanMove)
if (character != GameMain.Client.Character)
{
//remove states without a timestamp (there may still be ID-based states
//in the list when the controlled character switches to timestamp-based interpolation)
character.MemState.RemoveAll(m => m.Timestamp == 0.0f);
//use simple interpolation for other players' characters and characters that can't move
//use simple interpolation for other players' characters
if (character.MemState.Count > 0)
{
CharacterStateInfo serverPos = character.MemState.Last();
@@ -142,7 +142,8 @@ namespace Barotrauma
else
{
float mainLimbDistSqrd = Vector2.DistanceSquared(MainLimb.PullJointWorldAnchorA, newPosition);
float mainLimbErrorTolerance = 0.1f;
float mainLimbErrorTolerance = character == GameMain.Client.Character ? 0.25f : 0.1f;
MainLimb.body.LinearVelocity = newVelocity;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (mainLimbDistSqrd > mainLimbErrorTolerance)
@@ -151,12 +152,13 @@ namespace Barotrauma
MainLimb.PullJointEnabled = true;
if (!ColliderControlsMovement && newVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(newPosition); }
}
else
{
MainLimb.body.LinearVelocity = newVelocity;
}
}
}
else if (!ColliderControlsMovement)
{
//correct velocity regardless of the positional error
MainLimb.body.LinearVelocity = newVelocity;
}
}
character.MemLocalState.Clear();
}
@@ -192,6 +194,8 @@ namespace Barotrauma
CharacterStateInfo serverPos = character.MemState.Last();
Collider.LastServerState = serverPos;
if (!character.isSynced)
{
SetPosition(serverPos.Position, lerp: false);
@@ -289,15 +293,30 @@ namespace Barotrauma
}
else if (errorMagnitude > 0.01f)
{
Collider.TargetPosition = Collider.SimPosition + positionError;
Collider.TargetRotation = Collider.Rotation + rotationError;
Collider.MoveToTargetPosition(lerp: true);
if (ColliderControlsMovement)
{
Collider.TargetPosition = Collider.SimPosition + positionError;
Collider.TargetRotation = Collider.Rotation + rotationError;
Collider.MoveToTargetPosition(lerp: true);
}
else
{
float mainLimbErrorTolerance = character == GameMain.Client.Character ? 0.25f : 0.1f;
//if the main limb is roughly at the correct position and the collider isn't moving (much at least),
//don't attempt to correct the position.
if (errorMagnitude > mainLimbErrorTolerance)
{
MainLimb.PullJointWorldAnchorB = MainLimb.SimPosition + positionError;
MainLimb.PullJointEnabled = true;
if (serverPos.LinearVelocity.LengthSquared() < 0.01f) { TryPlatformCorrection(MainLimb.SimPosition + positionError); }
}
}
}
}
}
if (character.MemLocalState.Count > 120) character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120);
if (character.MemLocalState.Count > 120) { character.MemLocalState.RemoveRange(0, character.MemLocalState.Count - 120); }
character.MemState.Clear();
}
}
@@ -602,15 +621,19 @@ namespace Barotrauma
void AdjustDepthOffset(Item item)
{
if (item?.GetComponent<Controller>() is { ControlCharacterPose: true, UserInCorrectPosition: true } controller && controller.User == character)
if (item == null) { return; }
foreach (var controller in item.GetComponents<Controller>())
{
if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
if (controller is { ControlCharacterPose: true, UserInCorrectPosition: true } && controller.User == character)
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
}
else
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
}
else
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
}
}
}
}

View File

@@ -9,6 +9,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
@@ -251,6 +252,8 @@ namespace Barotrauma
public Vector2 DrawPosition;
public float MoveUpAmount;
public readonly RichString Text;
public ImmutableArray<RichTextData>? RichTextData { get; private set; }
public readonly Character Character;
public readonly Submarine Submarine;
public readonly Vector2 TextSize;
@@ -260,8 +263,10 @@ namespace Barotrauma
public SpeechBubble(Character character, float lifeTime, Color color, string text = "")
{
Text = RichString.Rich(ToolBox.WrapText(text, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text)));
var richStr = RichString.Rich(text);
Text = ToolBox.WrapText(richStr.SanitizedValue, GUI.IntScale(300), GUIStyle.SmallFont.GetFontForStr(text));
TextSize = GUIStyle.SmallFont.MeasureString(Text);
RichTextData = richStr.RichTextData;
Character = character;
Position = GetDesiredPosition();
@@ -329,7 +334,7 @@ namespace Barotrauma
if (key == null) { continue; }
key.Reset();
}
if (GUI.InputBlockingMenuOpen)
if (GUI.InputBlockingMenuOpen || ConversationAction.IsDialogOpen)
{
cursorPosition =
Position + PlayerInput.MouseSpeed.ClampLength(10.0f); //apply a little bit of movement to the cursor pos to prevent AFK kicking
@@ -1193,7 +1198,7 @@ namespace Barotrauma
Vector2 bubbleSize = bubble.TextSize + Vector2.One * GUI.IntScale(15);
speechBubbleIconSliced.Draw(spriteBatch, new RectangleF(iconPos - bubbleSize / 2, bubbleSize), bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha);
}
GUI.DrawStringWithColors(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text.SanitizedValue, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, bubble.Text.RichTextData, font: GUIStyle.SmallFont);
GUI.DrawStringWithColors(spriteBatch, iconPos - bubble.TextSize / 2, bubble.Text.SanitizedValue, bubble.Color * Math.Min(bubble.LifeTime, 1.0f) * alpha, bubble.RichTextData, font: GUIStyle.SmallFont);
}
spriteBatch.End();
}

View File

@@ -392,6 +392,7 @@ namespace Barotrauma
foreach (var target in mission.HudIconTargets)
{
if (target.Submarine != character.Submarine) { continue; }
if (target.Removed) { continue; }
float alpha = GetDistanceBasedIconAlpha(target, maxDistance: mission.Prefab.HudIconMaxDistance);
if (alpha <= 0.0f) { continue; }
GUI.DrawIndicator(spriteBatch, target.DrawPosition, cam, 100.0f, mission.Prefab.HudIcon, mission.Prefab.HudIconColor * alpha);
@@ -750,7 +751,8 @@ namespace Barotrauma
}
textPos.X += 10.0f * GUI.Scale;
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet && character.IsFriendly(character.FocusedCharacter))
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet &&
character.FocusedCharacter.AIController is EnemyAIController enemyAI && enemyAI.PetBehavior.CanPlayWith(character))
{
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
GUIStyle.Green, Color.Black, 2, GUIStyle.SmallFont);
@@ -811,7 +813,7 @@ namespace Barotrauma
private static void AddBossProgressBar(ProgressBar progressBar)
{
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
if (healthBarMode == EnemyHealthBarMode.HideAll)
if (healthBarMode == EnemyHealthBarMode.HideAll && progressBar is not MissionProgressBar)
{
return;
}
@@ -885,7 +887,7 @@ namespace Barotrauma
for (int i = bossProgressBars.Count - 1; i >= 0 ; i--)
{
var bossHealthBar = bossProgressBars[i];
if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
if (bossHealthBar.FadeTimer <= 0 || (healthBarMode == EnemyHealthBarMode.HideAll && bossHealthBar is not MissionProgressBar))
{
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);

View File

@@ -552,6 +552,8 @@ namespace Barotrauma
string newName = inc.ReadString();
string originalName = inc.ReadString();
bool renamingEnabled = inc.ReadBoolean();
BotStatus botStatus = (BotStatus)inc.ReadByte();
int salary = inc.ReadInt32();
int tagCount = inc.ReadByte();
HashSet<Identifier> tagSet = new HashSet<Identifier>();
for (int i = 0; i < tagCount; i++)
@@ -602,6 +604,8 @@ namespace Barotrauma
MinReputationToHire = (factionId, minReputationToHire),
RenamingEnabled = renamingEnabled
};
ch.BotStatus = botStatus;
ch.Salary = salary;
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.Head.SkinColor = skinColor;
ch.Head.HairColor = hairColor;

View File

@@ -274,9 +274,7 @@ namespace Barotrauma
if (!fixedRotation)
{
rotation = msg.ReadSingle();
float MaxAngularVel = NetConfig.MaxPhysicsBodyAngularVelocity;
angularVelocity = msg.ReadRangedSingle(-MaxAngularVel, MaxAngularVel, 8);
angularVelocity = NetConfig.Quantize(angularVelocity.Value, -MaxAngularVel, MaxAngularVel, 8);
angularVelocity = msg.ReadSingle();
}
bool ignorePlatforms = msg.ReadBoolean();
@@ -318,7 +316,7 @@ namespace Barotrauma
msg.ReadPadBits();
int index = 0;
if (GameMain.Client.Character == this && CanMove)
if (GameMain.Client.Character == this)
{
var posInfo = new CharacterStateInfo(
pos, rotation,
@@ -386,6 +384,9 @@ namespace Barotrauma
GameMain.Client.HasSpawned = true;
GameMain.Client.Character = this;
GameMain.LightManager.LosEnabled = true;
#if DEBUG
GameMain.LightManager.LosEnabled = !GameMain.DevMode;
#endif
GameMain.LightManager.LosAlpha = 1f;
GameMain.Client.WaitForNextRoundRespawn = null;
}
@@ -408,6 +409,7 @@ namespace Barotrauma
break;
case EventType.Status:
ReadStatus(msg);
GodMode = msg.ReadBoolean();
break;
case EventType.UpdateSkills:
Identifier skillIdentifier = msg.ReadIdentifier();
@@ -764,7 +766,7 @@ namespace Barotrauma
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(includeReserveBench: true).FirstOrDefault(c => c.ID == info.ID);
GameMain.GameSession.CrewManager.RemoveCharacterInfo(duplicateCharacterInfo);
if (character.isDead)
{
@@ -784,6 +786,9 @@ namespace Barotrauma
if (!character.IsDead) { Controlled = character; }
GameMain.LightManager.LosEnabled = true;
#if DEBUG
GameMain.LightManager.LosEnabled = !GameMain.DevMode;
#endif
GameMain.LightManager.LosAlpha = 1f;
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
@@ -851,6 +856,7 @@ namespace Barotrauma
if (IsDead) { Revive(); }
CharacterHealth.ClientRead(msg);
}
byte severedLimbCount = msg.ReadByte();
for (int i = 0; i < severedLimbCount; i++)
{

View File

@@ -1143,6 +1143,8 @@ namespace Barotrauma
if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
statusIconVisibleTime[afflictionPrefab] += deltaTime;
Color color = GetAfflictionIconColor(afflictionPrefab, affliction);
var matchingIcon =
afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
@@ -1151,9 +1153,13 @@ namespace Barotrauma
matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
{
UserData = afflictionPrefab,
ToolTip = affliction.Prefab.Name,
ToolTip = $"‖color:{color.ToStringHex()}‖{affliction.Prefab.Name}‖color:end‖",
CanBeSelected = false
};
if (affliction.Prefab.ShowDescriptionInTooltip)
{
matchingIcon.ToolTip = matchingIcon.ToolTip + "\n" + affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.Self);
}
if (affliction == pressureAffliction)
{
matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
@@ -1162,6 +1168,8 @@ namespace Barotrauma
{
matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
}
matchingIcon.ToolTip = RichString.Rich(matchingIcon.ToolTip);
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
{
CanBeFocused = false
@@ -1172,7 +1180,7 @@ namespace Barotrauma
matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
}
var image = matchingIcon.GetChild<GUIImage>();
image.Color = GetAfflictionIconColor(afflictionPrefab, affliction);
image.Color = color;
image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);
if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)

View File

@@ -17,13 +17,16 @@ public static class InteractionLabelManager
public RectangleF TextRect { get; set; }
public RichString Text;
public readonly Vector2 OriginalItemPosition;
public bool OverlapPreventionDone;
public LabelData(Item item, RectangleF textRect, Camera drawCamera)
public LabelData(Item item, RectangleF textRect, RichString text, Camera drawCamera)
{
Item = item;
Text = text;
TextRect = textRect;
OriginalItemPosition = item.Position;
this.drawCamera = drawCamera;
@@ -106,7 +109,7 @@ public static class InteractionLabelManager
if (labels.None(l => l.Item == interactableInRange))
{
var labelData = new LabelData(interactableInRange, textRect, cam);
var labelData = new LabelData(interactableInRange, textRect, RichString.Rich(interactableInRange.Prefab.Name), cam);
labels.Add(labelData);
}
}
@@ -124,7 +127,7 @@ public static class InteractionLabelManager
private static RectangleF GetLabelRect(Item item, Camera cam)
{
// create rectangle for overlap prevention
Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(item.Name) * LabelScale;
Vector2 itemTextSizeScreen = GUIStyle.SubHeadingFont.MeasureString(RichString.Rich(item.Prefab.Name).SanitizedValue) * LabelScale;
Vector2 interactablePosScreen = cam.WorldToScreen(item.Position);
RectangleF textRect = new RectangleF(interactablePosScreen.X, interactablePosScreen.Y, itemTextSizeScreen.X, itemTextSizeScreen.Y);
// center the rectangle on the item
@@ -320,9 +323,11 @@ public static class InteractionLabelManager
GUIStyle.InteractionLabelBackground.Draw(spriteBatch, backgroundRect, color * 0.7f);
GUIStyle.SubHeadingFont.DrawString(spriteBatch,
labelData.Item.Name,
textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None, layerDepth: 0.0f,
GUIStyle.SubHeadingFont.DrawStringWithColors(spriteBatch,
labelData.Text.SanitizedValue,
textDrawPosScreen, color, rotation: 0, origin: Vector2.Zero, scale, spriteEffects: SpriteEffects.None,
layerDepth: 0.0f,
richTextData: labelData.Text.RichTextData,
forceUpperCase: ForceUpperCase.No);
}
}

View File

@@ -8,23 +8,41 @@ namespace Barotrauma
{
public GUIButton CreateInfoFrame(bool isPvP, out GUIComponent buttonContainer)
{
int width = 500, height = 400;
int windowPixelWidth = 500, windowPixelHeight = 400;
Point absoluteWindowSize = new Point((int)(windowPixelWidth * GUI.xScale), (int)(windowPixelHeight * GUI.yScale));
GUIButton frameHolder = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, frameHolder.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
GUIFrame frame = new GUIFrame(new RectTransform(new Point(width, height), frameHolder.RectTransform, Anchor.Center));
GUIFrame frame = new GUIFrame(new RectTransform(absoluteWindowSize, frameHolder.RectTransform, Anchor.Center));
GUIFrame paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUIStyle.LargeFont)
{
CanBeFocused = false
};
var contentList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.75f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) })
{
ScrollBarVisible = true,
AutoHideScrollBar = true,
CurrentSelectMode = GUIListBox.SelectMode.None,
Padding = new Vector4(0, GUI.Scale * 10, 0, 0),
Spacing = (int)(GUI.Scale * 5)
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), Name, font: GUIStyle.LargeFont);
var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.15f) },
Description, font: GUIStyle.SmallFont, wrap: true);
var skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) });
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
TextManager.Get("Skills"), font: GUIStyle.LargeFont);
var descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
Description, font: GUIStyle.SmallFont, wrap: true, textAlignment: Alignment.TopLeft)
{
CanBeFocused = false,
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
TextManager.Get("Skills"), font: GUIStyle.LargeFont)
{
CanBeFocused = false
};
foreach (SkillPrefab skill in Skills)
{
var levelRange = skill.GetLevelRange(isPvP);
@@ -33,9 +51,12 @@ namespace Barotrauma
levelRange.End > levelRange.Start ?
(int)levelRange.Start + " - " + (int)levelRange.End :
((int)levelRange.Start).ToString();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), contentList.Content.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), levelStr),
font: GUIStyle.SmallFont);
font: GUIStyle.SmallFont, wrap: true)
{
CanBeFocused = false
};
}
buttonContainer = paddedFrame;

View File

@@ -511,7 +511,7 @@ namespace Barotrauma
else
{
//2. check if the character file defines the texture directly
texturePath = character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
texturePath = character.Params.VariantFile?.GetRootExcludingOverride()?.GetAttributeContentPath("texture", character.Prefab.ContentPackage);
}
//3. check if the base prefab defines the texture
@@ -824,11 +824,13 @@ namespace Barotrauma
{
if (ActiveDeformations.Any())
{
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size);
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, flippedHorizontally: IsFlipped, false);
deformSprite.Deform(deformation);
if (LightSource != null && LightSource.DeformableLightSprite != null)
{
deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, dir == Direction.Left);
//apparently inversing on the y-axis is only necessary for light sprites (see 345a65ca6)
//it's a mystery why this is the case, something to do with sprite flipping being handled differently in light rendering?
deformation = SpriteDeformation.GetDeformation(ActiveDeformations, deformSprite.Size, flippedHorizontally: IsFlipped, inverseY: dir == Direction.Left);
LightSource.DeformableLightSprite.Deform(deformation);
}
}
@@ -879,7 +881,7 @@ namespace Barotrauma
var defSprite = conditionalSprite.DeformableSprite;
if (ActiveDeformations.Any())
{
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size);
var deformation = SpriteDeformation.GetDeformation(ActiveDeformations, defSprite.Size, flippedHorizontally: IsFlipped);
defSprite.Deform(deformation);
}
else

View File

@@ -1006,17 +1006,11 @@ namespace Barotrauma
AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
{
Vector2 cursorWorldPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
TeleportCharacter(cursorWorldPos, Character.Controlled, args);
TeleportCharacter(cursorWorldPos, Character.Controlled, args);
});
AssignOnExecute("spawn|spawncharacter", (string[] args) =>
{
SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), out string errorMsg);
if (!string.IsNullOrWhiteSpace(errorMsg))
{
ThrowError(errorMsg);
}
});
AssignOnExecute("spawn|spawncharacter", args => SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition)));
AssignOnExecute("spawnnpc", args => SpawnCharacter(args, GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition), true));
AssignOnExecute("los", (string[] args) =>
{
@@ -2937,33 +2931,60 @@ namespace Barotrauma
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
}));
commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) =>
commands.Add(new Command("dumpeventtexts", "dumpeventtexts [sourcepath] [destinationpath]: gets the texts from event files and writes them into a file along with xml tags that can be used in translation files. If the filepath arguments are omitted, all event files are gone through and written to Content/Texts/EventTexts.txt", (string[] args) =>
{
string filePath = args.Length > 0 ? args[0] : "Content/Texts/EventTexts.txt";
string sourcePath = args.Length > 0 ? Path.GetFullPath(args[0]) : string.Empty;
string destinationPath = args.Length > 1 ? args[1] : "Content/Texts/EventTexts.txt";
List<string> lines = new List<string>();
HashSet<XDocument> docs = new HashSet<XDocument>();
HashSet<string> textIds = new HashSet<string>();
Dictionary<string, string> existingTexts = new Dictionary<string, string>();
Dictionary<string, string> existingTexts = new Dictionary<string, string>();
foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs())
{
if (eventPrefab.Identifier.IsEmpty)
{
continue;
string dir = Path.GetDirectoryName(eventPrefab.FilePath.FullPath);
if (!sourcePath.IsNullOrEmpty() &&
Path.GetFullPath(eventPrefab.FilePath.FullPath) != sourcePath &&
Path.GetDirectoryName(eventPrefab.FilePath.FullPath) != sourcePath)
{
continue;
}
if (eventPrefab.Identifier.IsEmpty) { continue; }
docs.Add(eventPrefab.ConfigElement.Document);
getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier.Value);
NewMessage($"Collecting event texts from event \"{eventPrefab.Identifier}\"...", Color.Cyan);
}
if (lines.None())
{
if (sourcePath.IsNullOrEmpty())
{
ThrowError("Could not find any event texts. Have all the texts already been moved from the event files to the text files?");
}
else
{
ThrowError($"Could not find any event texts from \"{sourcePath}\". Are you sure the path is to a valid event xml file or a directory that contains event xml files?");
}
return;
}
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, lines);
try
{
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
File.WriteAllLines(destinationPath, lines);
}
catch (Exception e)
{
ThrowError($"Failed to open the file \"{filePath}\".", e);
ThrowError($"Failed to write to the file \"{destinationPath}\".", e);
}
try
{
ToolBox.OpenFileWithShell(Path.GetFullPath(destinationPath));
NewMessage($"Wrote the event texts to a text file in \"{destinationPath}\".", Color.Cyan);
}
catch (Exception e)
{
ThrowError($"Failed to open the file \"{destinationPath}\".", e);
}
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
@@ -2973,10 +2994,12 @@ namespace Barotrauma
};
foreach (XDocument doc in docs)
{
using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings))
string filePath = new System.Uri(doc.BaseUri).LocalPath;
using (var writer = XmlWriter.Create(filePath, settings))
{
doc.WriteTo(writer);
writer.Flush();
NewMessage($"Updated the event file \"{filePath}\".", Color.Cyan);
}
}
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
@@ -2995,10 +3018,6 @@ namespace Barotrauma
text = subTextElement?.GetAttributeString(textAttribute, null);
textElement = subTextElement;
}
if (text == null)
{
AddWarning("Failed to find text from the element " + element.ToString());
}
}
string textId = $"EventText.{parentName}";
@@ -3760,6 +3779,29 @@ namespace Barotrauma
string fileName = args[1];
character.AnimController.TryLoadAnimation(animationType, Path.GetFileNameWithoutExtension(fileName), out _, throwErrors: true);
}, isCheat: true));
commands.Add(new Command("startlocalmptestsession", "startlocalmptestsession [(optional) number of clients, defaults to 2]: starts a new mp test session with multiple clients connected to local dedicated server", (string[] args) =>
{
// if we are not in main menu, exit out
if (Screen.Selected != GameMain.MainMenuScreen)
{
ThrowError("Must be in main menu to start.");
return;
}
// try to parse the number of clients
int numClients = 2;
if (args.Length > 0)
{
if (!int.TryParse(args[0], out numClients))
{
ThrowError("Failed to parse the number of clients.");
return;
}
}
StartLocalMPSession(numClients);
}));
commands.Add(new Command("reloadwearables", "Reloads the sprites of all limbs and wearable sprites (clothing) of the controlled character. Provide id or name if you want to target another character.", args =>
{
@@ -4253,5 +4295,44 @@ namespace Barotrauma
componentCost += itemPrefab.DefaultPrice.Price;
}
}
public static void StartLocalMPSession(int numClients = 2)
{
try
{
if (Process.GetProcessesByName("DedicatedServer").Length == 0)
{
#if WINDOWS
Process.Start("DedicatedServer.exe", arguments: "-multiclienttestmode");
#else
Process.Start("./DedicatedServer", arguments: "-multiclienttestmode");
#endif
System.Threading.Thread.Sleep(1000);
}
GameMain.Client = new GameClient("client1",
new LidgrenEndpoint(System.Net.IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());
numClients = MathHelper.Clamp(numClients, 1, 4);
if (numClients > 1)
{
for (int i = 2; i <= numClients; i++)
{
System.Threading.Thread.Sleep(1000);
#if WINDOWS
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i);
#else
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i);
#endif
}
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to start the local MP test session", e);
}
}
}
}

View File

@@ -1,4 +1,4 @@
using Barotrauma.Extensions;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
@@ -21,7 +21,12 @@ namespace Barotrauma
}
}
public override bool DisplayAsCompleted => State > 0 && requireRescue.None();
public override bool DisplayAsCompleted =>
!DisplayAsFailed &&
State > 0 &&
//don't display as completed mid-round if there's NPCs to rescue (mission isn't guaranteed to complete yet)
requireRescue.None();
public override bool DisplayAsFailed => State == HostagesKilledState;
public override void ClientReadInitial(IReadMessage msg)
@@ -47,7 +52,7 @@ namespace Barotrauma
#if CLIENT
if (allowOrderingRescuees)
{
GameMain.GameSession.CrewManager.AddCharacterToCrewList(character);
GameMain.GameSession.CrewManager?.AddCharacterToCrewList(character);
}
#endif
}

View File

@@ -4,7 +4,6 @@ using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using static Barotrauma.MissionPrefab;
namespace Barotrauma
{
@@ -127,6 +126,7 @@ namespace Barotrauma
return string.Empty;
}
}
partial void DistributeExperienceToCrew(IEnumerable<Character> crew, int experienceGain)
{
foreach (Character character in crew)

View File

@@ -1,4 +1,6 @@
using Barotrauma.Networking;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Networking;
using FarseerPhysics;
namespace Barotrauma
@@ -7,26 +9,51 @@ namespace Barotrauma
{
public override bool DisplayAsCompleted => false;
public override bool DisplayAsFailed => false;
private void TryShowPickedUpMessage() => HandleMessage(ref pickedUpMessage);
private void TryShowRetrievedMessage()
{
if (DetermineCompleted())
{
if (!allRetrievedMessage.IsNullOrEmpty()) { CreateMessageBox(string.Empty, allRetrievedMessage); }
//no need to show this again, clear it
allRetrievedMessage = string.Empty;
HandleMessage(ref allRetrievedMessage);
}
else
{
if (!partiallyRetrievedMessage.IsNullOrEmpty()) { CreateMessageBox(string.Empty, partiallyRetrievedMessage); }
//no need to show this again, clear it
partiallyRetrievedMessage = string.Empty;
HandleMessage(ref partiallyRetrievedMessage);
}
}
private void HandleMessage(ref LocalizedString message)
{
if (!message.IsNullOrEmpty()) { CreateMessageBox(string.Empty, message); }
//no need to show this again, clear it
message = string.Empty;
}
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
byte characterCount = msg.ReadByte();
for (int i = 0; i < characterCount; i++)
{
Character character = Character.ReadSpawnData(msg);
characters.Add(character);
ushort itemCount = msg.ReadUInt16();
for (int j = 0; j < itemCount; j++)
{
Item.ReadSpawnData(msg);
}
}
if (characters.Contains(null))
{
throw new System.Exception("Error in SalvageMission.ClientReadInitial: character list contains null (mission: " + Prefab.Identifier + ")");
}
if (characters.Count != characterCount)
{
throw new System.Exception("Error in SalvageMission.ClientReadInitial: character count does not match the server count (" + characters + " != " + characters.Count + "mission: " + Prefab.Identifier + ")");
}
foreach (var target in targets)
{
@@ -81,24 +108,37 @@ namespace Barotrauma
{
base.ClientRead(msg);
bool atLeastOneTargetWasRetrieved = false;
bool showPickedUpMsg = false;
int targetCount = msg.ReadByte();
for (int i = 0; i < targetCount; i++)
{
var state = (Target.RetrievalState)msg.ReadByte();
if (i < targets.Count)
{
bool wasRetrieved = targets[i].Retrieved;
Target target = targets[i];
bool wasRetrieved = target.Retrieved;
bool wasPickedUp = target.State == Target.RetrievalState.PickedUp;
targets[i].State = state;
if (!wasRetrieved && targets[i].Retrieved)
if (!wasRetrieved && target.Retrieved)
{
atLeastOneTargetWasRetrieved = true;
}
else if (!wasPickedUp && target.State == Target.RetrievalState.PickedUp)
{
showPickedUpMsg = true;
}
}
}
if (atLeastOneTargetWasRetrieved)
{
TryShowRetrievedMessage();
}
if (showPickedUpMsg)
{
TryShowPickedUpMessage();
}
}
public override IEnumerable<Entity> HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item);
}
}

View File

@@ -18,17 +18,40 @@ namespace Barotrauma
public readonly ChatManager ChatManager = new ChatManager();
public bool IsSinglePlayer { get; private set; }
private bool _toggleOpen = true;
public bool ToggleOpen
{
get { return _toggleOpen; }
set
{
_toggleOpen = PreferChatBoxOpen = value;
if (value) { hideableElements.Visible = true; }
}
get => _toggleOpen;
set => SetToggleOpenState(value, setPreference: true);
}
public static ChatBox GetChatBox()
{
if (GameMain.GameSession?.GameMode is not GameMode gameMode) { return null; }
return gameMode.IsSinglePlayer ? GameMain.GameSession.CrewManager?.ChatBox : GameMain.Client?.ChatBox;
}
public static void AutoHideChatBox() => SetChatBoxOpen(false);
private void SetToggleOpenState(bool value, bool setPreference = true)
{
_toggleOpen = value;
if (setPreference)
{
PreferChatBoxOpen = value;
}
if (value) { hideableElements.Visible = true; }
}
public static void ResetChatBoxOpenState() => GetChatBox()?.ResetOpenState();
public void ResetOpenState() => SetOpen(PreferChatBoxOpen);
private static void SetChatBoxOpen(bool isOpen) => GetChatBox()?.SetOpen(isOpen);
private void SetOpen(bool value) => SetToggleOpenState(value, setPreference: false);
private float openState;
public static bool PreferChatBoxOpen = true;
@@ -199,7 +222,7 @@ namespace Barotrauma
if (channelMemPending)
{
int.TryParse(channelText.Text, out int newChannel);
radio.SetChannelMemory(index, newChannel);
SetChannelMemory(index, newChannel);
btn.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
("[index]", index.ToString()),
("[channel]", radio.GetChannelMemory(index).ToString()));
@@ -330,7 +353,7 @@ namespace Barotrauma
};
showNewMessagesButton.Visible = false;
ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen;
SetToggleOpenState(GameSettings.CurrentConfig.ChatOpen, setPreference: true);
}
public void Toggle()
@@ -802,6 +825,15 @@ namespace Barotrauma
}
}
}
private void SetChannelMemory(int index, int channel)
{
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
radio.SetChannelMemory(index, channel);
radio.Item.CreateClientEvent(radio);
}
}
public void ApplySelectionInputs() => ApplySelectionInputs(InputBox, true, ChatKeyStates.GetChatKeyStates());

View File

@@ -11,13 +11,14 @@ internal class DeathPrompt
{
private static CoroutineHandle? createPromptCoroutine;
private GUIFrame? deathPromptFrame;
private GUIComponent? skillPanel;
private GUIComponent? newCharacterPanel;
private GUIComponent? takeOverBotPanel;
private GUIComponent? content;
public static GUIComponent? takeOverBotPanelFrame;
private static GUIComponent? takeOverBotPanelFrame;
/// <summary>
/// Private constructor, because these should only be created using the Show method
@@ -73,11 +74,11 @@ internal class DeathPrompt
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))
deathPromptFrame = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.3f), background.RectTransform, Anchor.Center))
{
UserData = this
};
frame.FadeIn(wait: 0, duration: FadeInDuration);
deathPromptFrame.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)
{
@@ -90,7 +91,7 @@ internal class DeathPrompt
}
};
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), frame.RectTransform, Anchor.Center))
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.8f), deathPromptFrame.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.05f
@@ -188,7 +189,7 @@ internal class DeathPrompt
{
if (takeOverBotPanel == null)
{
CreateTakeOverBotPanel(frame, this);
CreateTakeOverBotPanel(deathPromptFrame, this);
}
else
{
@@ -202,7 +203,7 @@ internal class DeathPrompt
}
else
{
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
var respawnNowButton = new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), buttonContainerRight.RectTransform), TextManager.Get("deathprompt.respawnnow"))
{
OnClicked = (btn, userdata) =>
{
@@ -211,7 +212,13 @@ internal class DeathPrompt
return true;
},
Enabled = GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.MidRound }
}.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
};
if (GameMain.NetworkMember is { ServerSettings.RespawnMode: RespawnMode.BetweenRounds })
{
respawnNowButton.ToolTip = TextManager.Get("respawnnotavailable.respawnmode.betweenrounds");
}
respawnNowButton.FadeIn(wait: FadeInInterval * 4, duration: FadeInDuration, alsoChildren: true);
}
//"info buttons" at the bottom
@@ -249,7 +256,7 @@ internal class DeathPrompt
{
if (skillPanel == null)
{
CreateSkillPanel(frame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
CreateSkillPanel(deathPromptFrame, GameMain.Client?.Character?.Info ?? GameMain.Client?.CharacterInfo);
}
else
{
@@ -266,7 +273,7 @@ internal class DeathPrompt
{
if (newCharacterPanel == null)
{
CreateNewCharacterPanel(frame);
CreateNewCharacterPanel(deathPromptFrame);
}
else
{
@@ -279,15 +286,6 @@ internal class DeathPrompt
}
}
//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;
}
@@ -382,8 +380,10 @@ internal class DeathPrompt
{
OnClicked = (btn, userdata) =>
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
{
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(onYes: () =>
{
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
GameMain.NetLobbyScreen.CampaignCharacterDiscarded = false;
frame.Parent?.RemoveChild(frame);
newCharacterPanel = null;
});
@@ -465,6 +465,11 @@ internal class DeathPrompt
{
if (botList.SelectedData is CharacterInfo selectedCharacter && GameMain.Client is GameClient client)
{
if (!GetAvailableBots().Contains(selectedCharacter)) // Someone may have taken over the bot while the list was open, etc
{
CreateTakeOverBotPanel(frame, deathPrompt); // Update
return true;
}
client.SendTakeOverBotRequest(selectedCharacter);
GUIMessageBox.MessageBoxes.Remove(frame.Parent);
deathPrompt?.Close();
@@ -484,15 +489,26 @@ internal class DeathPrompt
return frame;
}
public void UpdateBotList()
{
if (deathPromptFrame != null && takeOverBotPanelFrame != null)
{
CloseBotPanel();
CreateTakeOverBotPanel(deathPromptFrame, deathPrompt: this);
}
}
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));
return crewManager.GetCharacterInfos(includeReserveBench: true).Where(c =>
// a bot on reserve bench
c.IsOnReserveBench ||
// an alive bot
(c.Character != null && c.Character is { IsBot: true, IsDead: false }) ||
// a newly hired bot that hasn't spawned yet
(c.Character == null && c.IsNewHire));
}
else
{

View File

@@ -106,12 +106,35 @@ namespace Barotrauma
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
public static float RelativeVerticalAspectRatio => VerticalAspectRatio / (ReferenceResolution.Y / ReferenceResolution.X);
/// <summary>
/// Returns the difference of the current aspect ratio to the reference aspect ratio (16:9).
/// E.g. if the aspect ratio is 16:9, returns 0; if it's 4:3, returns 0.444; if the aspect ratio is 12:5, returns -0.623.
/// </summary>
public static float AspectRatioDifference
{
get
{
// ~ 1.777
float referenceAspectRatio = ReferenceResolution.X / ReferenceResolution.Y;
float aspectRatioDifference = referenceAspectRatio - HorizontalAspectRatio;
if (MathUtils.NearlyEqual(aspectRatioDifference, 0))
{
// Handle possible rounding errors, so that we can trust that this returns 0 when the aspect ratio matches the reference aspect ratio.
return 0;
}
return aspectRatioDifference;
}
}
/// <summary>
/// A horizontal scaling factor for low aspect ratios (small width relative to height)
/// </summary>
public static float AspectRatioAdjustment => HorizontalAspectRatio < 1.4f ? (1.0f - (1.4f - HorizontalAspectRatio)) : 1.0f;
public static bool IsUltrawide => HorizontalAspectRatio > 2.3f;
public static bool IsHUDScaled => GameSettings.CurrentConfig.Graphics.HUDScale > 1 || GameSettings.CurrentConfig.Graphics.InventoryScale > 1;
public static int UIWidth
{
@@ -625,9 +648,13 @@ namespace Barotrauma
DrawMessages(spriteBatch, cam);
if (MouseOn != null && !MouseOn.ToolTip.IsNullOrWhiteSpace())
{
MouseOn.DrawToolTip(spriteBatch);
if (MouseOn != null)
{
if (!MouseOn.ToolTip.IsNullOrWhiteSpace())
{
MouseOn.DrawToolTip(spriteBatch);
}
MouseOn.OnDrawToolTip?.Invoke(MouseOn);
}
if (SubEditorScreen.IsSubEditor())
@@ -884,6 +911,11 @@ namespace Barotrauma
PauseMenu.AddToGUIUpdateList();
}
foreach (var openAccordion in GUIComponent.OpenAccordionPopups)
{
openAccordion.AddToGUIUpdateList(order: 1);
}
SocialOverlay.Instance?.AddToGuiUpdateList();
GUIContextMenu.AddActiveToGUIUpdateList();
@@ -2495,7 +2527,12 @@ namespace Barotrauma
{
IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("bugreportbutton") + $" (v{GameMain.Version})",
OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
OnClicked = (btn, userdata) =>
{
if (PauseMenuOpen) { TogglePauseMenu(); }
GameMain.Instance.ShowBugReporter();
return true;
}
};
CreateButton("PauseMenuResume", buttonContainer, null);
@@ -2531,23 +2568,37 @@ namespace Barotrauma
GameMain.GameSession?.EndRound("");
});
}
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound))
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null)
{
bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
if (canSave)
//server owner (host) can't return to the lobby without ending the round for everyone
if (!GameMain.Client.IsServerOwner)
{
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
{
GameMain.Client?.RequestRoundEnd(save: true);
});
CreateButton("ReturnToServerlobby", buttonContainer,
verificationTextTag: "PauseMenuReturnToServerLobbyVerificationSelf",
action: () =>
{
GameMain.Client?.EndRoundForSelf();
});
}
CreateButton(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby" : "EndRound", buttonContainer,
verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
action: () =>
if (GameMain.Client.HasPermission(ClientPermissions.ManageRound))
{
bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsFriendlyOutpostLevel();
if (canSave)
{
GameMain.Client?.RequestRoundEnd(save: false);
});
CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
{
GameMain.Client?.RequestEndRound(save: true);
}, color: GUIStyle.Red);
}
CreateButton("EndRound", buttonContainer,
verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
action: () =>
{
GameMain.Client?.RequestEndRound(save: false);
}, color: GUIStyle.Red);
}
}
}
@@ -2575,9 +2626,9 @@ namespace Barotrauma
}
void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null)
void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null, Color? color = null)
{
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
var button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
{
OnClicked = (btn, userData) =>
{
@@ -2593,25 +2644,29 @@ namespace Barotrauma
return true;
}
};
if (color.HasValue)
{
button.Color = color.Value;
}
}
void CreateVerificationPrompt(string textTag, Action confirmAction)
}
public static void CreateVerificationPrompt(string textTag, Action confirmAction)
{
var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "verificationprompt",
DrawOnTop = true
};
msgBox.Buttons[0].OnClicked = (_, __) =>
{
PauseMenuOpen = false;
confirmAction?.Invoke();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
UserData = "verificationprompt",
DrawOnTop = true
};
msgBox.Buttons[0].OnClicked = (_, __) =>
{
PauseMenuOpen = false;
confirmAction?.Invoke();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
private static bool TogglePauseMenu(GUIButton button, object obj)
@@ -2697,12 +2752,6 @@ namespace Barotrauma
}
}
public static bool IsFourByThree()
{
float aspectRatio = HorizontalAspectRatio;
return aspectRatio > 1.3f && aspectRatio < 1.4f;
}
public static void SetSavingIndicatorState(bool enabled)
{
if (enabled)

View File

@@ -9,6 +9,7 @@ using Barotrauma.IO;
using RestSharp;
using System.Net;
using Barotrauma.Steam;
using Steamworks;
namespace Barotrauma
{
@@ -158,6 +159,12 @@ namespace Barotrauma
public Action<GUIComponent> OnAddedToGUIUpdateList;
/// <summary>
/// Triggers when a tooltip should be draw on the component.
/// Note that the callback triggers even if the item has no tooltip (which can be useful for e.g. only contructing the tooltip when needed).
/// </summary>
public Action<GUIComponent> OnDrawToolTip;
public enum ComponentState { None, Hover, Pressed, Selected, HoverSelected };
protected Alignment alignment;
@@ -1120,7 +1127,7 @@ namespace Barotrauma
component = LoadGUIImage(element, parent);
break;
case "accordion":
return LoadAccordion(element, parent);
return LoadAccordion(element, parent, openOnTop: element.GetAttributeBool("openontop", false));
case "gridtext":
LoadGridText(element, parent);
return null;
@@ -1138,6 +1145,21 @@ namespace Barotrauma
FromXML(subElement, component is GUIListBox listBox ? listBox.Content.RectTransform : component.RectTransform);
}
component.toolTip = element.GetAttributeString("tooltip", string.Empty);
GUITextBlock textBlock = component as GUITextBlock ?? (component as GUIButton)?.TextBlock;
if (textBlock != null)
{
if (element.GetAttributeBool("autoscalevertical", false))
{
textBlock.AutoScaleVertical = true;
}
if (element.GetAttributeBool("autoscalehorizontal", false))
{
textBlock.AutoScaleHorizontal = true;
}
}
if (element.GetAttributeBool("resizetofitchildren", false))
{
Vector2 relativeResizeScale = element.GetAttributeVector2("relativeresizescale", Vector2.One);
@@ -1180,7 +1202,8 @@ namespace Barotrauma
{
foreach (XAttribute attribute in element.Attributes())
{
switch (attribute.Name.ToString().ToLowerInvariant())
string conditionName = attribute.Name.ToString().ToLowerInvariant();
switch (conditionName)
{
case "language":
var languages = element.GetAttributeIdentifierArray(attribute.Name.ToString(), Array.Empty<Identifier>())
@@ -1199,6 +1222,10 @@ namespace Barotrauma
var maxVersion = new Version(attribute.Value);
if (GameMain.Version > maxVersion) { return false; }
break;
case "identifierdismissed":
Identifier identifier = element.GetAttributeIdentifier(attribute.Name.ToString(), Identifier.Empty);
if (MainMenuScreen.DismissedNotifications.Contains(identifier)) { return false; }
break;
case "buildconfiguration":
switch (attribute.Value.ToString().ToLowerInvariant())
{
@@ -1222,6 +1249,20 @@ namespace Barotrauma
#endif
}
return false;
case "mingamelaunches":
if (int.TryParse(attribute.Value, out int minLaunches))
{
return SteamManager.GetStatInt(AchievementStat.GameLaunchCount) > minLaunches;
}
return false;
case "appsubscribed":
case "appnotsubscribed":
if (SteamManager.IsInitialized &&
int.TryParse(attribute.Value, out int appId))
{
return SteamApps.IsSubscribedToApp(appId) == (conditionName == "appsubscribed");
}
return false;
}
}
@@ -1262,12 +1303,18 @@ namespace Barotrauma
private static GUIButton LoadLink(XElement element, RectTransform parent)
{
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
var button = LoadGUIButton(element, parent);
string url = element.GetAttributeString("url", "");
button.OnClicked = (btn, userdata) =>
{
try
{
if (!identifier.IsEmpty)
{
MainMenuScreen.AddDismissedNotification(identifier);
}
if (SteamManager.IsInitialized)
{
SteamManager.OverlayCustomUrl(url);
@@ -1324,6 +1371,8 @@ namespace Barotrauma
private static GUIButton LoadGUIButton(XElement element, RectTransform parent)
{
Identifier identifier = element.GetAttributeIdentifier("identifier", Identifier.Empty);
string style = element.GetAttributeString("style", "");
if (style == "null") { style = null; }
@@ -1335,10 +1384,19 @@ namespace Barotrauma
element.GetAttributeString("text", "");
text = text.Replace(@"\n", "\n");
return new GUIButton(RectTransform.Load(element, parent),
var button = new GUIButton(RectTransform.Load(element, parent),
text: text,
textAlignment: textAlignment,
style: style);
button.OnClicked = (btn, userdata) =>
{
if (!identifier.IsEmpty)
{
MainMenuScreen.AddDismissedNotification(identifier);
}
return true;
};
return button;
}
private static GUIListBox LoadGUIListBox(XElement element, RectTransform parent)
@@ -1391,11 +1449,14 @@ namespace Barotrauma
{
sprite = new Sprite(element);
}
return new GUIImage(RectTransform.Load(element, parent), sprite, scaleToFit: true);
var scaleToFit = element.GetAttributeEnum("scaletofit", GUIImage.ScalingMode.ScaleToFitSmallestExtent);
return new GUIImage(RectTransform.Load(element, parent), sprite, scaleToFit: scaleToFit);
}
private static GUIButton LoadAccordion(ContentXElement element, RectTransform parent)
public static readonly List<GUIComponent> OpenAccordionPopups = new List<GUIComponent>();
/// <param name="openOnTop">Should the contents of the accordion be forced to open on top of other UI elements?</param>
private static GUIButton LoadAccordion(ContentXElement element, RectTransform parent, bool openOnTop)
{
var button = LoadGUIButton(element, parent);
List<GUIComponent> content = new List<GUIComponent>();
@@ -1407,6 +1468,7 @@ namespace Barotrauma
contentElement.Visible = false;
contentElement.IgnoreLayoutGroups = true;
content.Add(contentElement);
contentElement.UserData = (contentElement.RectTransform.Anchor, contentElement.RectTransform.Pivot);
}
}
button.OnClicked = (btn, userdata) =>
@@ -1414,8 +1476,32 @@ namespace Barotrauma
bool visible = content.FirstOrDefault()?.Visible ?? true;
foreach (GUIComponent contentElement in content)
{
contentElement.Visible = !visible;
contentElement.IgnoreLayoutGroups = !contentElement.Visible;
if (openOnTop)
{
contentElement.rectTransform.Parent = null;
//the element is drawn in screen space over anything (no longer a child of the original parent),
//we need to calculate the screen space position manually
contentElement.rectTransform.SetPosition(Anchor.TopLeft);
(Anchor anchor, Pivot pivot) = ((Anchor anchor, Pivot pivot))contentElement.UserData;
contentElement.rectTransform.ScreenSpaceOffset =
RectTransform.CalculateAnchorPoint(anchor, button.Rect) +
RectTransform.CalculatePivotOffset(pivot, contentElement.Rect.Size);
contentElement.Visible = true;
if (OpenAccordionPopups.Contains(contentElement))
{
OpenAccordionPopups.Remove(contentElement);
}
else
{
OpenAccordionPopups.Clear();
OpenAccordionPopups.Add(contentElement);
}
}
else
{
contentElement.Visible = !visible;
contentElement.IgnoreLayoutGroups = !contentElement.Visible;
}
}
if (button.Parent is GUILayoutGroup layoutGroup)
{

View File

@@ -2,7 +2,6 @@
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma
@@ -14,6 +13,24 @@ namespace Barotrauma
private static bool loadingTextures;
public enum ScalingMode
{
/// <summary>
/// No automatic scaling, the image is drawn using <see cref="Scale"/>
/// </summary>
None,
/// <summary>
/// Automatically scales the image so it fits the smallest extent of the component
/// (leaving empty space around the image if its aspect ratio is different than that of the GUIImage)
/// </summary>
ScaleToFitSmallestExtent,
/// <summary>
/// Automatically scales the image so it fits the largest extent of the component,
/// cutting out the parts that go outside the bounds of the component.
/// </summary>
ScaleToFitLargestExtent,
}
public static bool LoadingTextures
{
get
@@ -30,7 +47,7 @@ namespace Barotrauma
private bool crop;
private readonly bool scaleToFit;
private readonly ScalingMode scaleToFit;
private bool lazyLoaded, loading;
@@ -89,7 +106,7 @@ namespace Barotrauma
sprite = value;
sourceRect = value == null ? Rectangle.Empty : value.SourceRect;
origin = value == null ? Vector2.Zero : value.size / 2;
if (scaleToFit) { RecalculateScale(); }
if (scaleToFit != ScalingMode.None) { RecalculateScale(); }
}
}
@@ -97,17 +114,27 @@ namespace Barotrauma
public ComponentState? OverrideState = null;
public GUIImage(RectTransform rectT, string style, bool scaleToFit = false)
public GUIImage(RectTransform rectT, string style, bool scaleToFit)
: this(rectT, null, null, scaleToFit ? ScalingMode.ScaleToFitSmallestExtent : ScalingMode.None, style)
{
}
public GUIImage(RectTransform rectT, string style, ScalingMode scaleToFit = ScalingMode.None)
: this(rectT, null, null, scaleToFit, style)
{
}
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, bool scaleToFit = false)
public GUIImage(RectTransform rectT, Sprite sprite, bool scaleToFit, Rectangle? sourceRect = null)
: this(rectT, sprite, sourceRect, scaleToFit ? ScalingMode.ScaleToFitSmallestExtent : ScalingMode.None, null)
{
}
public GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect = null, ScalingMode scaleToFit = ScalingMode.None)
: this(rectT, sprite, sourceRect, scaleToFit, null)
{
}
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, ScalingMode scaleToFit, string style) : base(style, rectT)
{
this.scaleToFit = scaleToFit;
Sprite = sprite;
@@ -123,7 +150,7 @@ namespace Barotrauma
{
color = hoverColor = selectedColor = pressedColor = disabledColor = Color.White;
}
if (!scaleToFit)
if (scaleToFit == ScalingMode.None)
{
Scale = 1.0f;
}
@@ -176,9 +203,11 @@ namespace Barotrauma
Color currentColor = GetColor(State);
if (BlendState != null)
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
if (BlendState != null || scaleToFit == ScalingMode.ScaleToFitLargestExtent)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = Rectangle.Intersect(prevScissorRect, Rect);
spriteBatch.Begin(blendState: BlendState, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
@@ -205,9 +234,10 @@ namespace Barotrauma
Scale, SpriteEffects, 0.0f);
}
if (BlendState != null)
if (BlendState != null || scaleToFit == ScalingMode.ScaleToFitLargestExtent)
{
spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
}
}
@@ -219,9 +249,18 @@ namespace Barotrauma
sourceRect = sprite.SourceRect;
}
Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ?
1.0f :
Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
if (sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0)
{
Scale = 1.0f;
}
else if (scaleToFit == ScalingMode.ScaleToFitLargestExtent)
{
Scale = Math.Max(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
}
else
{
Scale = Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
}
}
private async Task<bool> LoadTextureAsync()

View File

@@ -1030,7 +1030,7 @@ namespace Barotrauma
while (index < Content.CountChildren)
{
GUIComponent child = Content.GetChild(index);
if (child.Visible)
if (child.Visible && child.CanBeFocused)
{
Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll)
@@ -1049,7 +1049,7 @@ namespace Barotrauma
while (index >= 0)
{
GUIComponent child = Content.GetChild(index);
if (child.Visible)
if (child.Visible && child.CanBeFocused)
{
Select(index, force, GetAutoScroll(!SmoothScroll && autoScroll == AutoScroll.Enabled), takeKeyBoardFocus, playSelectSound);
if (SmoothScroll)

View File

@@ -289,7 +289,7 @@ namespace Barotrauma
GUIStyle.Apply(Text, "", this);
Content.Recalculate();
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height);
new Point(Text.Rect.Width, Math.Min(Text.Rect.Height, GameMain.GraphicsHeight));
Text.RectTransform.IsFixedSize = true;
if (headerText.IsNullOrWhiteSpace())
{

View File

@@ -38,6 +38,8 @@ namespace Barotrauma
private static bool ReplacingPermanentlyDeadCharacter =>
GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } &&
GameMain.Client?.CharacterInfo is { PermanentlyDead: true };
private static bool ReserveBenchEnabled => GameMain.GameSession?.Campaign is MultiPlayerCampaign;
private bool hadPermissionToHire;
private static bool HasPermissionToHire => ReplacingPermanentlyDeadCharacter ?
@@ -277,13 +279,42 @@ namespace Barotrauma
}
else
{
PendingHires?.ForEach(ci => AddPendingHire(ci));
PendingHires?.ForEach(ci => AddPendingHire(ci, createNetworkMessage: false));
}
SetTotalHireCost();
}
UpdateCrew();
}
/// <summary>
/// This will simply update each of the HR view lists (hireables, pending hires, and crew) from the most up to date information.
/// It is a sane version of UpdateLocationView that won't break things even if used outside of whatever arbitrary conditions that one was made for.
/// </summary>
public void RefreshHRView()
{
if (campaign?.CurrentLocation is not Location currentLocation)
{
return;
}
if (characterPreviewFrame != null)
{
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null;
}
UpdateHireables(currentLocation);
if (pendingList != null)
{
pendingList.Content.ClearChildren();
PendingHires?.ForEach(ci => AddPendingHire(ci, checkCrewSizeLimit: false, createNetworkMessage: false)); // don't check limits here, just display the data as it is
SetTotalHireCost();
}
UpdateCrew();
}
public void UpdateHireables()
{
UpdateHireables(campaign?.CurrentLocation);
@@ -329,10 +360,11 @@ namespace Barotrauma
public void UpdateCrew()
{
crewList.Content.Children.ToList().ForEach(c => crewList.Content.RemoveChild(c));
foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos())
foreach (CharacterInfo ci in GameMain.GameSession.CrewManager.GetCharacterInfos(includeReserveBench: true))
{
if (c == null || !((c.Character?.IsBot ?? true) || campaign is SinglePlayerCampaign)) { continue; }
CreateCharacterFrame(c, crewList);
// CrewManager is used to store info on all characters including players, but we only want bots in HR
if (ci.Character != null && (ci.Character.IsRemotePlayer || !ci.Character.IsBot)) { continue; }
CreateCharacterFrame(ci, crewList);
}
SortCharacters(crewList, SortingMethod.JobAsc);
crewList.UpdateScrollBarSize();
@@ -369,6 +401,10 @@ namespace Barotrauma
((InfoSkill)x.GUIComponent.UserData).SkillLevel.CompareTo(((InfoSkill)y.GUIComponent.UserData).SkillLevel));
if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); }
}
// Always apply this in the end to group by reserve bench status (does nothing if there are no reserve benched bots)
list.Content.RectTransform.SortChildren((x, y) =>
((InfoSkill)x.GUIComponent.UserData).CharacterInfo.BotStatus.CompareTo(((InfoSkill)y.GUIComponent.UserData).CharacterInfo.BotStatus));
int? CompareReputationRequirement(GUIComponent c1, GUIComponent c2)
{
@@ -401,6 +437,8 @@ namespace Barotrauma
public GUIComponent CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox, bool hideSalary = false)
{
string characterName = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
Skill skill = null;
Color? jobColor = null;
if (characterInfo.Job != null)
@@ -415,6 +453,7 @@ namespace Barotrauma
};
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1,
Stretch = true
};
@@ -428,13 +467,15 @@ namespace Barotrauma
GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform)) { CanBeFocused = false };
GUILayoutGroup nameGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { CanBeFocused = false };
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameGroup.RectTransform),
listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name,
characterName,
textColor: jobColor, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false
};
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
const float smallColumnWidth = 0.6f / 3;
const float skillColumnWidth = smallColumnWidth * 0.7f;
const float buttonWidth = 0.12f;
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Title ?? characterInfo.Job.Name, textColor: Color.White, font: GUIStyle.SmallFont, textAlignment: Alignment.TopLeft)
{
@@ -449,33 +490,28 @@ namespace Barotrauma
}
}
var fullJobText = jobBlock.Text;
jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
if (jobBlock.Text != fullJobText)
{
jobBlock.ToolTip = fullJobText;
jobBlock.CanBeFocused = true;
}
float width = 0.6f / 3;
if (characterInfo.Job != null && skill != null)
{
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true);
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(skillColumnWidth, 0.6f), mainGroup.RectTransform), isHorizontal: true);
float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width;
new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(),
textAlignment: Alignment.CenterRight)
{
Padding = Vector4.Zero,
CanBeFocused = false
};
GUIImage skillIcon = new GUIImage(new RectTransform(Vector2.One, skillGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), skill.Icon, scaleToFit: true)
{
CanBeFocused = false
};
if (jobColor.HasValue) { skillIcon.Color = jobColor.Value; }
new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
}
if (!hideSalary)
{
if (listBox != crewList)
{
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform),
new GUITextBlock(new RectTransform(new Vector2(smallColumnWidth, 1.0f), mainGroup.RectTransform),
TextManager.FormatCurrency(ReplacingPermanentlyDeadCharacter ? campaign.NewCharacterCost(characterInfo) : HireManager.GetSalaryFor(characterInfo)),
textAlignment: Alignment.Center)
{
@@ -485,19 +521,24 @@ namespace Barotrauma
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 };
new GUIFrame(new RectTransform(new Vector2(smallColumnWidth, 1.0f), mainGroup.RectTransform), style: null) { CanBeFocused = false };
}
}
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(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
{
ToolTip = TextManager.Get("hirebutton"),
ToolTip = TextManager.Get(ReserveBenchEnabled ? "hirebutton.crew" : "hirebutton"),
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter,
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
OnClicked = (b, o) =>
{
var currentCharacterInfo = (CharacterInfo)o;
currentCharacterInfo.BotStatus = BotStatus.PendingHireToActiveService;
return AddPendingHire(currentCharacterInfo);
}
};
hireButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
{
@@ -505,7 +546,7 @@ namespace Barotrauma
{
return;
}
if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
if (PendingHires.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
{
if (btn.Enabled)
{
@@ -523,7 +564,7 @@ namespace Barotrauma
if (ReplacingPermanentlyDeadCharacter)
{
bool canHire = CanHire(characterInfo) && campaign.CanAffordNewCharacter(characterInfo);
var takeoverButton = new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
var takeoverButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementTakeControlButton")
{
ToolTip = canHire ? TextManager.Get("hireandtakecontrol") : TextManager.Get("hireandtakecontroldisabled"),
ClickSound = GUISoundType.ConfirmTransaction,
@@ -554,25 +595,90 @@ namespace Barotrauma
btn.Enabled = canHireCurrently;
};
}
if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
{
var hireToReserveBenchButton = new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddAsReserveButton")
{
ToolTip = TextManager.Get("hirebutton.reservebench"),
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo),
OnClicked = (b, o) =>
{
var currentCharacterInfo = (CharacterInfo)o;
currentCharacterInfo.BotStatus = BotStatus.PendingHireToReserveBench;
return AddPendingHire(currentCharacterInfo, checkCrewSizeLimit: false);
}
};
hireToReserveBenchButton.OnAddedToGUIUpdateList += (GUIComponent btn) =>
{
btn.Visible = ReserveBenchEnabled;
btn.Enabled = CanHire(characterInfo) && !ReplacingPermanentlyDeadCharacter;
};
}
}
else if (listBox == pendingList)
{
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
{
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform),
style: characterInfo.BotStatus == BotStatus.PendingHireToActiveService ? "CrewManagementReserveBenchButtonActive" : "CrewManagementReserveBenchButtonReserve")
{
UserData = characterInfo,
ToolTip = TextManager.Get(characterInfo.BotStatus == BotStatus.PendingHireToActiveService ? "ReserveBenchTogglePendingHire.Active" : "ReserveBenchTogglePendingHire.Reserve"),
Enabled = CanHire(characterInfo) && (characterInfo.BotStatus == BotStatus.PendingHireToActiveService || !ActiveServiceFull()), // note that this is a toggle
OnClicked = (btn, obj) =>
{
SelectCharacter(null, null, null);
var currentCharacterInfo = (CharacterInfo)obj;
GameMain.Client?.ToggleReserveBench(currentCharacterInfo, pendingHire: true);
return true;
}
};
}
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
{
ClickSound = GUISoundType.Cart,
UserData = characterInfo,
Enabled = CanHire(characterInfo),
Enabled = CanHire(characterInfo), // =just check user's rights
OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo)
};
}
else if (listBox == crewList && campaign != null)
{
var currentCrew = GameMain.GameSession.CrewManager.GetCharacterInfos();
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
if (ReserveBenchEnabled && !ReplacingPermanentlyDeadCharacter)
{
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform),
style: characterInfo.BotStatus == BotStatus.ActiveService ? "CrewManagementReserveBenchButtonActive" : "CrewManagementReserveBenchButtonReserve")
{
UserData = characterInfo,
ToolTip = TextManager.Get(characterInfo.BotStatus == BotStatus.ActiveService ? "ReserveBenchToggle.Active" : "ReserveBenchToggle.Reserve"),
Enabled = CanHire(characterInfo) && (characterInfo.BotStatus == BotStatus.ActiveService || !ActiveServiceFull()), // note that this is a toggle
OnClicked = (btn, obj) =>
{
SelectCharacter(null, null, null);
var currentCharacterInfo = (CharacterInfo)obj;
if (currentCharacterInfo.BotStatus == BotStatus.ActiveService && // switching to reserve bench
characterInfo.Character != null) // may not have a Character to remove if not spawned this round
{
GameMain.GameSession.CrewManager.RemoveCharacter(characterInfo.Character, removeInfo: true, resetCrewListIndex: true);
}
GameMain.Client?.ToggleReserveBench(currentCharacterInfo); // update changes to server
return true;
}
};
}
var cm = GameMain.GameSession.CrewManager;
// Can't fire if there's only one character in active service
var fireButtonEnabled = HasPermissionToHire && (characterInfo.IsOnReserveBench ||
(cm.GetCharacterInfos().Contains(characterInfo) && cm.GetCharacterInfos().Count() > 1));
new GUIButton(new RectTransform(new Vector2(buttonWidth, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
{
UserData = characterInfo,
//can't fire if there's only one character in the crew
Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermissionToHire,
Enabled = fireButtonEnabled,
OnClicked = (btn, obj) =>
{
var confirmDialog = new GUIMessageBox(
@@ -587,11 +693,25 @@ namespace Barotrauma
}
};
}
else
{
if (ReserveBenchEnabled && characterInfo.IsOnReserveBench) // Applies to unspecified listings like the death prompt and the bot list after permadeath
{
new GUIImage(new RectTransform(new Vector2(smallColumnWidth / 2, 0.6f), mainGroup.RectTransform), style: "CrewManagementReserveBenchIconReserve")
{
ToolTip = TextManager.Get("ReserveBenchStatus.Reserve.WillSpawn")
};
}
else
{
new GUILayoutGroup(new RectTransform(new Vector2(smallColumnWidth / 2, 0.6f), mainGroup.RectTransform)) { CanBeFocused = false };
}
}
if (listBox == pendingList || listBox == crewList)
{
nameBlock.RectTransform.Resize(new Point(nameBlock.Rect.Width - nameBlock.Rect.Height, nameBlock.Rect.Height));
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.RectTransform.Resize(new Point((int)(nameBlock.Padding.X + nameBlock.TextSize.X + nameBlock.Padding.Z), nameBlock.Rect.Height));
Point size = new Point((int)(0.7f * nameBlock.Rect.Height));
new GUIImage(new RectTransform(size, nameGroup.RectTransform), "EditIcon") { CanBeFocused = false };
@@ -605,6 +725,16 @@ namespace Barotrauma
};
}
//recalculate everything and truncate texts if needed
mainGroup.Recalculate();
nameBlock.Text = ToolBox.LimitString(characterName, nameBlock.Font, nameBlock.Rect.Width);
jobBlock.Text = ToolBox.LimitString(fullJobText, jobBlock.Font, jobBlock.Rect.Width);
if (jobBlock.Text != fullJobText)
{
jobBlock.ToolTip = fullJobText;
jobBlock.CanBeFocused = true;
}
bool CanHire(CharacterInfo thisCharacterInfo)
{
if (!HasPermissionToHire) { return false; }
@@ -613,6 +743,15 @@ namespace Barotrauma
return frame;
}
/// <summary>
/// Is there (going to be) no space left in active service?
/// </summary>
private bool ActiveServiceFull()
{
int pendingHireCount = PendingHires?.Count(ci => ci.BotStatus == BotStatus.PendingHireToActiveService) ?? 0;
return pendingHireCount + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize;
}
private bool EnoughReputationToHire(CharacterInfo characterInfo)
{
@@ -656,7 +795,7 @@ namespace Barotrauma
GUILayoutGroup infoLabelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
float blockHeight = 1.0f / 4;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"), textColor: GUIStyle.TextColorBright);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), "");
string name = listBox == hireableList ? characterInfo.OriginalName : characterInfo.Name;
nameBlock.Text = ToolBox.LimitString(name, nameBlock.Font, nameBlock.Rect.Width);
@@ -664,17 +803,17 @@ namespace Barotrauma
if (characterInfo.HasSpecifierTags)
{
var menuCategoryVar = characterInfo.Prefab.MenuCategoryVar;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get(menuCategoryVar), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get(characterInfo.ReplaceVars($"[{menuCategoryVar}]")));
}
if (characterInfo.Job is Job job)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), job.Name);
}
if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"), textColor: GUIStyle.TextColorBright);
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), trait.DisplayName);
}
infoLabelGroup.Recalculate();
@@ -727,9 +866,9 @@ namespace Barotrauma
return true;
}
private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true)
private bool AddPendingHire(CharacterInfo characterInfo, bool checkCrewSizeLimit = true, bool createNetworkMessage = true)
{
if (PendingHires.Count + campaign.CrewManager.GetCharacterInfos().Count() >= CrewManager.MaxCrewSize)
if (checkCrewSizeLimit && characterInfo.BotStatus == BotStatus.PendingHireToActiveService && ActiveServiceFull())
{
return false;
}
@@ -792,7 +931,7 @@ namespace Barotrauma
List<CharacterInfo> nonDuplicateHires = new List<CharacterInfo>();
hires.ForEach(hireInfo =>
{
if (campaign.CrewManager.GetCharacterInfos().None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
if (campaign.CrewManager.GetCharacterInfos(includeReserveBench: true).None(crewInfo => crewInfo.IsNewHire && crewInfo.GetIdentifierUsingOriginalName() == hireInfo.GetIdentifierUsingOriginalName()))
{
nonDuplicateHires.Add(hireInfo);
}
@@ -806,12 +945,21 @@ namespace Barotrauma
if (!campaign.CanAfford(total)) { return false; }
}
bool atLeastOneHired = false;
bool atLeastOneHiredToActiveDuty = false;
bool atLeastOneHiredToReserveBench = false;
foreach (CharacterInfo ci in nonDuplicateHires)
{
bool toReserveBench = ci.BotStatus == BotStatus.PendingHireToReserveBench;
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci, takeMoney: takeMoney))
{
atLeastOneHired = true;
if (toReserveBench)
{
atLeastOneHiredToReserveBench = true;
}
else
{
atLeastOneHiredToActiveDuty = true;
}
}
else
{
@@ -819,15 +967,27 @@ namespace Barotrauma
}
}
if (atLeastOneHired)
if (atLeastOneHiredToActiveDuty || atLeastOneHiredToReserveBench)
{
UpdateLocationView(campaign.Map.CurrentLocation, true);
SelectCharacter(null, null, null);
if (createNotification)
{
LocalizedString msg = string.Empty;
if (atLeastOneHiredToActiveDuty)
{
msg += TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName);
}
if (atLeastOneHiredToReserveBench)
{
if (!msg.IsNullOrEmpty()) { msg += "\n\n"; }
msg += GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath, IronmanMode: false } ?
TextManager.Get("crewhiredmessage.reservebench.permadeath") :
TextManager.Get( "crewhiredmessage.reservebench");
}
var dialog = new GUIMessageBox(
TextManager.Get("newcrewmembers"),
TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.DisplayName),
TextManager.Get("newcrewmembers"), msg,
new LocalizedString[] { TextManager.Get("Ok") });
dialog.Buttons[0].OnClicked += dialog.Close;
}
@@ -1034,7 +1194,7 @@ namespace Barotrauma
}
}
public void SetPendingHires(List<UInt16> characterInfos, Location location)
public void SetPendingHires(List<UInt16> characterInfos, bool[] characterInfoReserveBenchStatuses, Location location, bool checkCrewSizeLimit)
{
List<CharacterInfo> oldHires = PendingHires.ToList();
foreach (CharacterInfo pendingHire in oldHires)
@@ -1042,18 +1202,25 @@ namespace Barotrauma
RemovePendingHire(pendingHire, createNetworkMessage: false);
}
PendingHires.Clear();
int i = 0;
foreach (UInt16 identifier in characterInfos)
{
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.ID == identifier);
if (match != null)
{
AddPendingHire(match, createNetworkMessage: false);
match.BotStatus = characterInfoReserveBenchStatuses[i] ? BotStatus.PendingHireToReserveBench : BotStatus.PendingHireToActiveService;
AddPendingHire(match, checkCrewSizeLimit: checkCrewSizeLimit, createNetworkMessage: false);
if (!PendingHires.Contains(match))
{
DebugConsole.ThrowError("Failed to add a pending hire");
}
System.Diagnostics.Debug.Assert(PendingHires.Contains(match));
}
else
{
DebugConsole.ThrowError("Received a hire that doesn't exist.");
}
i++;
}
}
@@ -1064,7 +1231,7 @@ namespace Barotrauma
/// <param name="renameCharacter">When not null tell the server to rename this character. Item1 is the character to rename, Item2 is the new name, Item3 indicates whether the renamed character is already a part of the crew.</param>
/// <param name="firedCharacter">When not null tell the server to fire this character</param>
/// <param name="validateHires">When set to true will tell the server to validate pending hires</param>
public void SendCrewState(bool updatePending, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
public void SendCrewState(bool updatePending = false, (CharacterInfo info, string newName) renameCharacter = default, CharacterInfo firedCharacter = null, bool validateHires = false)
{
if (campaign is MultiPlayerCampaign)
{
@@ -1078,6 +1245,7 @@ namespace Barotrauma
foreach (CharacterInfo pendingHire in PendingHires)
{
msg.WriteUInt16(pendingHire.ID);
msg.WriteBoolean(pendingHire.BotStatus == BotStatus.PendingHireToReserveBench);
}
}
@@ -1089,7 +1257,9 @@ namespace Barotrauma
{
msg.WriteUInt16(renameCharacter.info.ID);
msg.WriteString(renameCharacter.newName);
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.ID == renameCharacter.info.ID) ?? false;
bool existingCrewMember =
campaign.CrewManager is CrewManager crewManager &&
crewManager.GetCharacterInfos(includeReserveBench: true).Any(ci => ci.ID == renameCharacter.info.ID);
msg.WriteBoolean(existingCrewMember);
}

View File

@@ -25,8 +25,6 @@ namespace Barotrauma
private Video currSplashScreen;
private DateTime videoStartTime;
private bool mirrorBackground;
public struct PendingSplashScreen
{
public string Filename;
@@ -108,8 +106,7 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, samplerState: GUI.SamplerState);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea,
spriteEffects: mirrorBackground ? SpriteEffects.FlipHorizontally : SpriteEffects.None);
GUI.DrawBackgroundSprite(spriteBatch, currentBackgroundTexture, Color.White, drawArea);
overlay.Draw(spriteBatch, Vector2.Zero, scale: overlayScale);
double noiseT = Timing.TotalTime * 0.02f;
@@ -386,7 +383,6 @@ namespace Barotrauma
{
currentBackgroundTexture = missions.Where(m => m.Prefab.HasPortraits).First().Prefab.GetPortrait(Rand.Int(int.MaxValue));
}
mirrorBackground = Rand.Range(0.0f, 1.0f) < 0.5f;
while (!drawn)
{

View File

@@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using Microsoft.Xna.Framework.Input;
using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
namespace Barotrauma
@@ -1573,15 +1574,24 @@ namespace Barotrauma
if (locationHasDealOnItem)
{
var relativeWidth = (0.9f * nameAndQuantityFrame.Rect.Height) / nameAndQuantityFrame.Rect.Width;
Vector2 dealIconSize = new Vector2(relativeWidth, 0.9f) * 0.5f;
var dealIcon = new GUIImage(
new RectTransform(new Vector2(relativeWidth, 0.9f), nameAndQuantityFrame.RectTransform, anchor: Anchor.CenterLeft)
new RectTransform(dealIconSize, nameAndQuantityFrame.RectTransform, anchor: Anchor.CenterRight)
{
AbsoluteOffset = new Point((int)nameBlock.Padding.X, 0)
},
"StoreDealIcon", scaleToFit: true)
{
CanBeFocused = false
CanBeFocused = false,
UserData = "StoreDealIcon"
};
var dealIconColor = dealIcon.Color;
if (forceDisable)
{
dealIconColor.A = 0;
}
dealIcon.Color = dealIconColor;
dealIcon.SetAsFirstChild();
}
bool isParentOnLeftSideOfInterface = parentComponent == storeBuyList || parentComponent == storeDailySpecialsGroup ||
@@ -1713,7 +1723,7 @@ namespace Barotrauma
mainGroup.Recalculate();
mainGroup.RectTransform.RecalculateChildren(true, true);
amountInput?.LayoutGroup.Recalculate();
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
nameBlock.Text = ToolBox.LimitString(nameBlock.Text.SanitizedString, nameBlock.Font, nameBlock.Rect.Width);
mainGroup.RectTransform.Children.ForEach(c => c.IsFixedSize = true);
return frame;
@@ -1795,6 +1805,9 @@ namespace Barotrauma
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
{
float full = 1f;
float dim = 0.7f;
float alpha = (enabled ? full : dim);
if (itemFrame?.UserData is not PurchasedItem pi) { return; }
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
if (!refreshFrameStatus) { return; }
@@ -1802,14 +1815,14 @@ namespace Barotrauma
{
if (pi.ItemPrefab?.InventoryIcon != null)
{
icon.Color = pi.ItemPrefab.InventoryIconColor * (enabled ? 1.0f : 0.5f);
icon.Color = pi.ItemPrefab.InventoryIconColor * alpha;
}
else if (pi.ItemPrefab?.Sprite != null)
{
icon.Color = pi.ItemPrefab.SpriteColor * (enabled ? 1.0f : 0.5f);
icon.Color = pi.ItemPrefab.SpriteColor * alpha;
}
};
var color = Color.White * (enabled ? 1.0f : 0.5f);
var color = Color.White * alpha;
if (itemFrame.FindChild("name", recursive: true) is GUITextBlock name)
{
name.TextColor = color;
@@ -1835,7 +1848,7 @@ namespace Barotrauma
}
if (itemFrame.FindChild("price", recursive: true) is GUITextBlock priceBlock)
{
priceBlock.TextColor = isDiscounted ? storeSpecialColor * (enabled ? 1.0f : 0.5f) : color;
priceBlock.TextColor = isDiscounted ? storeSpecialColor * alpha : color;
}
if (itemFrame.FindChild("addbutton", recursive: true) is GUIButton addButton)
{
@@ -1845,6 +1858,10 @@ namespace Barotrauma
{
removeButton.Enabled = enabled;
}
if (itemFrame.FindChild("StoreDealIcon", recursive: true) is GUIImage dealIcon)
{
dealIcon.Color = dealIcon.Color * alpha;
}
pi.IsStoreComponentEnabled = enabled;
itemFrame.UserData = pi;
}
@@ -2271,6 +2288,15 @@ namespace Barotrauma
{
updateStopwatch.Restart();
if (GameMain.DevMode)
{
if (PlayerInput.KeyDown(Keys.D0))
{
CreateUI();
needsRefresh = true;
}
}
if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y)
{
CreateUI();

View File

@@ -255,7 +255,7 @@ namespace Barotrauma
pageIndicators = new GUIImage[pageCount];
for (int i = 0; i < pageCount; i++)
{
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, null, true);
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, scaleToFit: true);
xPos += indicatorSize.X + HUDLayoutSettings.Padding;
}

View File

@@ -120,9 +120,20 @@ namespace Barotrauma
{
if (Client == null) { return; }
if (currentPing == Client.Ping) { return; }
currentPing = Client.Ping;
textBlock.Text = currentPing.ToString();
textBlock.TextColor = GetPingColor();
if (GameMain.NetworkMember != null && GameMain.NetworkMember.ConnectedClients.Contains(Client))
{
currentPing = Client.Ping;
textBlock.Text = currentPing.ToString();
textBlock.TextColor = GetPingColor();
textBlock.ToolTip = string.Empty;
}
else
{
currentPing = 0;
textBlock.Text = "-";
textBlock.TextColor = GUIStyle.Red;
textBlock.ToolTip = TextManager.Get("causeofdeathdescription.disconnected");
}
}
public void TryPermissionIconRefresh(Sprite icon)
@@ -461,7 +472,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
case InfoFrameTab.Talents:
talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
talentMenu.CreateGUI(infoFrameHolder, Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo);
break;
}
}
@@ -709,38 +720,49 @@ namespace Barotrauma
var connectedClients = GameMain.Client.ConnectedClients;
for (int i = 0; i < teamIDs.Count; i++)
for (int teamID = 0; teamID < teamIDs.Count; teamID++)
{
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[teamID]))
{
if (character is not AICharacter && connectedClients.Any(c => c.Character == null && c.Name == character.Name)) { continue; }
CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), i);
CreateMultiPlayerCharacterElement(character, GameMain.Client.PreviouslyConnectedClients.FirstOrDefault(c => c.Character == character), teamID);
}
foreach (CharacterInfo characterInfo in GameMain.GameSession.CrewManager?.GetReserveBenchInfos() ?? Enumerable.Empty<CharacterInfo>())
{
CreateMultiPlayerCharacterElement(character: null, client: null, teamID, justCharacterInfo: characterInfo);
}
}
for (int j = 0; j < connectedClients.Count; j++)
{
Client client = connectedClients[j];
if (!client.InGame || client.Character == null || client.Character.IsDead)
if (client.Character == null || client.Character.IsDead)
{
CreateMultiPlayerClientElement(client);
}
}
}
private void CreateMultiPlayerCharacterElement(Character character, Client client, int i)
/// <param name="justCharacterInfo">The character element can be generated based on just a CharacterInfo, and Character and Client can be left null. Otherwise, those are required and the CharacterInfo of the Character is used.</param>
private void CreateMultiPlayerCharacterElement(Character character, Client client, int teamID, CharacterInfo justCharacterInfo = null)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
CharacterInfo characterInfo = justCharacterInfo ?? character.Info;
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[teamID].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[teamID].Content.RectTransform), style: "ListBoxElement")
{
UserData = character,
UserData = character != null ? character : characterInfo,
Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? OwnCharacterBGColor : Color.Transparent
};
frame.OnSecondaryClicked += (component, data) =>
if (client != null)
{
NetLobbyScreen.CreateModerationContextMenu(client);
return true;
};
frame.OnSecondaryClicked += (component, data) =>
{
NetLobbyScreen.CreateModerationContextMenu(client);
return true;
};
}
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
@@ -748,7 +770,18 @@ namespace Barotrauma
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
onDraw: (sb, component) =>
{
if (client == null)
{
characterInfo?.DrawJobIcon(sb, component.Rect);
}
else
{
DrawClientJobIcon(sb, component.Rect, client);
}
})
{
CanBeFocused = false,
HoverColor = Color.White,
@@ -778,39 +811,60 @@ namespace Barotrauma
else
{
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
ToolBox.LimitString(characterInfo.Name, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: characterInfo.Job.Prefab.UIColor);
if (GameMain.GameSession?.GameMode is PvPMode)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotKillCount(character.Info) ?? 0).ToString()
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotKillCount(characterInfo) ?? 0).ToString()
};
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), string.Empty, textAlignment: Alignment.Center)
{
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotDeathCount(character.Info) ?? 0).ToString()
TextGetter = () => GameMain.GameSession.Missions.Sum(m => (m as CombatMission)?.GetBotDeathCount(characterInfo) ?? 0).ToString()
};
}
if (character is AICharacter)
{
linkedGUIList.Add(new LinkedGUI(character, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
// "BOT" instead of ping (which isn't relevant for bots)
linkedGUIList.Add(new LinkedGUI(character, frame,
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = ForceUpperCase.Yes }));
}
else
else if (characterInfo.IsOnReserveBench)
{
linkedGUIList.Add(new LinkedGUI(client: null, frame, textBlock: null, permissionIcon: null));
new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect))
// Reserve bench icon
new GUIImage(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height - 4), paddedFrame.RectTransform), style: "CrewManagementReserveBenchIconReserve", scaleToFit: true)
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White
ToolTip = TextManager.Get("ReserveBenchStatus.Reserve")
};
}
if (characterInfo.IsOnReserveBench)
{
//black bar to dim out the elements (1px shorter and to the right so it won't dim the left border too)
new GUIFrame(
new RectTransform(new Point(paddedFrame.Rect.Width - 1, frame.Rect.Height), paddedFrame.RectTransform, Anchor.Center)
{
AbsoluteOffset = new Point(1, 0)
},
style: null, color: Color.Black * 0.7f)
{
IgnoreLayoutGroups = true,
CanBeFocused = false
};
}
}
CreateWalletCrewFrame(character, paddedFrame);
if (character != null)
{
CreateWalletCrewFrame(character, paddedFrame);
}
else if (characterInfo.IsOnReserveBench)
{
// Empty column for reserve benched bots
new GUILayoutGroup(new RectTransform(new Point(walletColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), childAnchor: Anchor.Center) { CanBeFocused = false };
}
paddedFrame.Recalculate();
}
@@ -839,7 +893,7 @@ namespace Barotrauma
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client))
onDraw: (sb, component) => DrawClientJobIcon(sb, component.Rect, client))
{
CanBeFocused = false,
HoverColor = Color.White,
@@ -864,10 +918,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center),
permissionIcon));
if (client.Character is { } character)
{
CreateWalletCrewFrame(character, paddedFrame);
}
CreateWalletCrewFrame(client.Character, paddedFrame);
paddedFrame.Recalculate();
}
@@ -925,7 +976,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("walletdescription")
};
if (character.IsBot) { return; }
if (character == null || character.IsBot) { return; }
Sprite walletSprite = GUIStyle.CrewWalletIconSmall.Value.Sprite;
@@ -1033,13 +1084,13 @@ namespace Barotrauma
}
}
private void DrawNotInGameIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
private void DrawClientJobIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
{
if (client.Spectating)
{
spectateIcon.Draw(spriteBatch, area, Color.White);
}
else if (client.Character != null && client.Character.IsDead)
else if (client.Character != null && client.InGame)
{
client.Character.Info?.DrawJobIcon(spriteBatch, area);
}
@@ -1065,7 +1116,13 @@ namespace Barotrauma
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) { infoFrameHolder.RemoveChild(existingPreview); }
if (userData is CharacterInfo { IsOnReserveBench: true })
{
return true;
}
// Modal info panel that pops up on the right
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.69f), infoFrameHolder.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) })
{
UserData = "SelectedCharacter"
@@ -1089,7 +1146,7 @@ namespace Barotrauma
{
talentButton.OnClicked = (button, o) =>
{
talentMenu.CreateGUI(infoFrameHolder, character);
talentMenu.CreateGUI(infoFrameHolder, character.Info);
return true;
};
}
@@ -1474,7 +1531,7 @@ namespace Barotrauma
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.322f), paddedFrame.RectTransform), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client));
onDraw: (sb, component) => DrawClientJobIcon(sb, component.Rect, client));
GUIFont font = paddedFrame.Rect.Width < 280 ? GUIStyle.SmallFont : GUIStyle.Font;
@@ -1564,6 +1621,11 @@ namespace Barotrauma
}
linkedGUIList.Clear();
foreach (GUIListBox crewList in crewListArray)
{
crewList.Content.ClearChildren();
}
}
private void AddLineToLog(string line, PlayerConnectionChangeType type)
@@ -1648,7 +1710,7 @@ namespace Barotrauma
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.DisplayName, font: GUIStyle.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), locationInfoContainer.RectTransform), location.GetLocationTypeToDisplay().Name, font: GUIStyle.SubHeadingFont);
if (location.Faction?.Prefab != null)
{

View File

@@ -88,17 +88,17 @@ namespace Barotrauma
private StartAnimation? startAnimation;
private GUIComponent? talentMainArea;
public void CreateGUI(GUIFrame parent, Character? targetCharacter)
public void CreateGUI(GUIFrame parent, CharacterInfo? characterInfo)
{
this.characterInfo = characterInfo;
character = characterInfo?.Character;
parent.ClearChildren();
talentButtons.Clear();
talentShowCaseButtons.Clear();
talentCornerIcons.Clear();
showCaseTalentFrames.Clear();
character = targetCharacter;
characterInfo = targetCharacter?.Info;
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15);
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null);
@@ -999,8 +999,8 @@ namespace Barotrauma
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentApplyButton.Enabled = talentCount > 0;
talentResetButton.Enabled = talentCount > 0 || characterInfo.TalentRefundPoints > 0;
talentApplyButton.Enabled = character != null && talentCount > 0;
talentResetButton.Enabled = character != null && (talentCount > 0 || characterInfo.TalentRefundPoints > 0);
if (talentCount == 0 && characterInfo.TalentRefundPoints > 0)
{

View File

@@ -127,9 +127,10 @@ namespace Barotrauma
Campaign.OnMoneyChanged.RegisterOverwriteExisting(eventId, _ => RequestRefresh());
}
public void RequestRefresh()
public void RequestRefresh(bool refreshUpgrades = false)
{
needsRefresh = true;
if (refreshUpgrades) { SelectTab(UpgradeTab.Upgrade); }
}
private void RefreshAll()
@@ -673,6 +674,10 @@ namespace Barotrauma
}
}
}
if (!upgrades.ContainsKey(category) && HasSwappableItems(category))
{
upgrades.Add(category, new List<UpgradePrefab>());
}
}
foreach (var (category, prefabs) in upgrades)
@@ -771,20 +776,28 @@ namespace Barotrauma
{
if (Submarine.MainSub == null) { return false; }
subItems ??= GetSubItems();
return subItems.Any(i =>
i.Prefab.SwappableItem != null &&
!i.IsHidden && i.AllowSwapping &&
(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)));
return subItems.Any(item => HasSwappableItems(category, item));
}
private static bool HasSwappableItems(UpgradeCategory category, Item item)
{
if (Submarine.MainSub == null) { return false; }
return
item.Prefab.SwappableItem != null &&
!item.IsHidden && item.AllowSwapping &&
(item.Prefab.SwappableItem.CanBeBought || ItemPrefab.Prefabs.Any(ip => ip.SwappableItem?.ReplacementOnUninstall == item.Prefab.Identifier)) &&
Submarine.MainSub.IsEntityFoundOnThisSub(item, true) && category.ItemTags.Any(t => item.HasTag(t));
}
private static List<Item> GetSubItems() => Submarine.MainSub?.GetItems(true) ?? new List<Item>();
private void SelectUpgradeCategory(List<UpgradePrefab> prefabs, UpgradeCategory category, Submarine submarine)
{
if (selectedUpgradeCategoryLayout == null) { return; }
customizeTabOpen = false;
bool hasSwappableItems = HasSwappableItems(category);
bool hasUpgradeModules = prefabs.Count > 0;
customizeTabOpen = !hasUpgradeModules && hasSwappableItems;
GUIComponent[] categoryFrames = GetFrames(category);
foreach (GUIComponent itemFrame in itemPreviews.Values)
@@ -799,9 +812,7 @@ namespace Barotrauma
GUIFrame frame = new GUIFrame(rectT(1.0f, 0.4f, selectedUpgradeCategoryLayout));
GUIFrame paddedFrame = new GUIFrame(rectT(0.93f, 0.9f, frame, Anchor.Center), style: null);
bool hasSwappableItems = HasSwappableItems(category);
float listHeight = hasSwappableItems ? 0.9f : 1.0f;
float listHeight = hasSwappableItems && hasUpgradeModules ? 0.9f : 1.0f;
GUIListBox prefabList = new GUIListBox(rectT(1.0f, listHeight, paddedFrame, Anchor.BottomLeft))
{
@@ -810,7 +821,8 @@ namespace Barotrauma
ScrollBarVisible = true
};
if (hasSwappableItems)
//both swappable items and upgrade modules -> create 2 tabs
if (hasSwappableItems && hasUpgradeModules)
{
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1.0f, 0.1f, paddedFrame, anchor: Anchor.TopLeft), isHorizontal: true);
@@ -852,8 +864,15 @@ namespace Barotrauma
return true;
};
}
CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
//only either upgrade modules or swappable items -> just create the list
else if (hasUpgradeModules)
{
CreateUpgradePrefabList(prefabList, category, prefabs, submarine);
}
else if (hasSwappableItems)
{
CreateSwappableItemList(prefabList, category, submarine);
}
}
private void CreateUpgradePrefabList(GUIListBox parent, UpgradeCategory category, List<UpgradePrefab> prefabs, Submarine submarine)
@@ -1370,9 +1389,10 @@ namespace Barotrauma
Item[] entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToArray();
foreach (UpgradeCategory category in UpgradeCategory.Categories)
{
//hide categories with no upgrades in them
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
if (entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
//hide categories with no upgrades or swappables in them
bool hasSwappableItems = HasSwappableItems(category);
if (!hasSwappableItems && UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
if (hasSwappableItems || entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
{
yield return category;
}
@@ -1534,7 +1554,7 @@ namespace Barotrauma
description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W);
List<Entity> pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs)
where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast<Entity>().Distinct().ToList();
where (category.CanBeApplied(item, null) || HasSwappableItems(category, item)) && item.IsPlayerTeamInteractable select item).Cast<Entity>().Distinct().ToList();
List<ushort> ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List<ushort>();
pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID)));
@@ -1799,7 +1819,7 @@ namespace Barotrauma
// Disables the parent and only re-enables if the submarine contains valid items
if (!category.IsWallUpgrade && drawnSubmarine?.Info != null)
{
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0))
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0) && !HasSwappableItems(category))
{
parent.ToolTip = TextManager.Get("upgradecategorynotapplicable");
parent.Enabled = false;

View File

@@ -121,6 +121,7 @@ namespace Barotrauma
private readonly GameTime fixedTime;
public Option<ConnectCommand> ConnectCommand = Option<ConnectCommand>.None();
private string clientName;
private static SpriteBatch spriteBatch;
@@ -255,6 +256,16 @@ namespace Barotrauma
try
{
ConnectCommand = Barotrauma.Networking.ConnectCommand.Parse(ConsoleArguments);
string clientNameFlagArg = args.FirstOrDefault(arg => arg.StartsWith("-username"));
if (clientNameFlagArg != null)
{
int nextIndex = args.IndexOf(clientNameFlagArg) + 1;
if (nextIndex < args.Length)
{
clientName = args[nextIndex];
}
}
}
catch (IndexOutOfRangeException e)
{
@@ -740,7 +751,7 @@ namespace Barotrauma
fixedTime.IsRunningSlowly = gameTime.IsRunningSlowly;
TimeSpan addTime = new TimeSpan(0, 0, 0, 0, 16);
fixedTime.ElapsedGameTime = addTime;
fixedTime.TotalGameTime.Add(addTime);
fixedTime.TotalGameTime = fixedTime.TotalGameTime.Add(addTime);
base.Update(fixedTime);
PlayerInput.Update(Timing.Step);
@@ -821,19 +832,28 @@ namespace Barotrauma
}
MainMenuScreen.Select();
string clientNameString = clientName ?? MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername());
if (connectCommand.SteamLobbyIdOption.TryUnwrap(out var lobbyId))
{
SteamManager.JoinLobby(lobbyId.Value, joinServer: true);
}
else if (connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint)
&& nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints })
else if ((connectCommand.NameAndP2PEndpointsOption.TryUnwrap(out var nameAndEndpoint) && nameAndEndpoint is { ServerName: var serverName, Endpoints: var endpoints }))
{
Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
Client = new GameClient(clientNameString,
endpoints.Cast<Endpoint>().ToImmutableArray(),
string.IsNullOrWhiteSpace(serverName) ? endpoints.First().StringRepresentation : serverName,
Option<int>.None());
}
else if ((connectCommand.NameAndLidgrenEndpointOption.TryUnwrap(out var nameAndLidgrenEndpoint) && nameAndLidgrenEndpoint is { ServerName: var lidgrenServerName, Endpoint: var endpoint }))
{
Client = new GameClient(
clientNameString,
endpoint,
string.IsNullOrWhiteSpace(lidgrenServerName) ? endpoint.StringRepresentation : lidgrenServerName,
Option<int>.None());
}
ConnectCommand = Option<ConnectCommand>.None();
}
@@ -1183,11 +1203,12 @@ namespace Barotrauma
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
GameSession.RoundDuration);
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
//disabled to reduce the amount of data we collect through GA
/*GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
{
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.Prefab.Identifier);
}
}*/
GameSession.LogEndRoundStats(eventId);
if (GameSession.GameMode is TutorialMode tutorialMode)
{

View File

@@ -166,7 +166,7 @@ namespace Barotrauma
// check if the store can afford the item
if (store.Balance < itemValue) { continue; }
// TODO: Write logic for prioritizing certain items over others (e.g. lone Battery Cell should be preferred over one inside a Stun Baton)
var matchingItems = sellableItems.Where(i => i.Prefab == item.ItemPrefab);
var matchingItems = sellableItems.Where(i => i.Prefab.Identifier == item.ItemPrefabIdentifier);
int count = Math.Min(item.Quantity, matchingItems.Count());
SoldItem.SellOrigin origin = sellingMode == Store.StoreTab.Sell ? SoldItem.SellOrigin.Character : SoldItem.SellOrigin.Submarine;
if (origin == SoldItem.SellOrigin.Character || GameMain.IsSingleplayer)

View File

@@ -47,7 +47,7 @@ namespace Barotrauma
/// <summary>
/// This property stores the preference in settings. Don't use for automatic logic.
/// Use AutoShowCrewList(), AutoHideCrewList(), and ResetCrewList().
/// Use AutoHideCrewList(), and ResetCrewList().
/// </summary>
public bool IsCrewMenuOpen
{
@@ -62,11 +62,9 @@ namespace Barotrauma
public static bool PreferCrewMenuOpen = true;
public bool AutoShowCrewList() => _isCrewMenuOpen = true;
public void AutoHideCrewList() => _isCrewMenuOpen = false;
public void ResetCrewList() => _isCrewMenuOpen = PreferCrewMenuOpen;
public void ResetCrewListOpenState() => _isCrewMenuOpen = PreferCrewMenuOpen;
const float CommandNodeAnimDuration = 0.2f;
@@ -195,6 +193,12 @@ namespace Barotrauma
ChatBox.InputBox.OnTextChanged += ChatBox.TypingChatMessage;
}
else if (GameMain.Client == null)
{
//this method would throw a non-descriptive nullref exception later when trying to access the chatbox
//if we'd try to continue from here, better to throw a more descriptive one at this point
throw new InvalidOperationException($"Attempted to initialize {nameof(CrewManager)} for multiplayer, but no multiplayer client is active. Are you trying to load a multiplayer save in singleplayer?");
}
#endregion
@@ -308,6 +312,14 @@ namespace Barotrauma
#region Character list management
/// <summary>
/// Note: this is only works client-side. TODO: make it work server-side too?
/// </summary>
public IEnumerable<Character> GetCharacters()
{
return characters;
}
public Rectangle GetActiveCrewArea()
{
return crewArea.Rect;
@@ -1357,6 +1369,16 @@ namespace Barotrauma
{
GameSession.TabMenuInstance.SelectInfoFrameTab(TabMenu.SelectedTab);
}
if (character.SelectedItem?.GetComponent<Controller>() == null && character.SelectedCharacter == null)
{
ResetCrewListOpenState();
ChatBox.ResetChatBoxOpenState();
}
else
{
AutoHideCrewList();
ChatBox.AutoHideChatBox();
}
}
private int TryAdjustIndex(int amount)
@@ -1919,7 +1941,7 @@ namespace Barotrauma
{
get
{
if (GameMain.GameSession?.CrewManager == null)
if (GameMain.GameSession?.CrewManager == null || Screen.Selected is { IsEditor: true })
{
return false;
}
@@ -3700,7 +3722,7 @@ namespace Barotrauma
bool hasLeaks = Character.Controlled.CurrentHull.ConnectedGaps.Any(g => !g.IsRoomToRoom && g.Open > 0.0f);
ToggleReportButton("reportbreach", hasLeaks);
bool hasIntruders = Character.CharacterList.Any(c => c.CurrentHull == Character.Controlled.CurrentHull && AIObjectiveFightIntruders.IsValidTarget(c, Character.Controlled, false));
bool hasIntruders = Character.CharacterList.Any(c => c.CurrentHull == Character.Controlled.CurrentHull && AIObjectiveFightIntruders.IsValidTarget(c, Character.Controlled, targetCharactersInOtherSubs: false));
ToggleReportButton("reportintruders", hasIntruders);
foreach (GUIComponent reportButton in ReportButtonFrame.Children)
@@ -3826,5 +3848,72 @@ namespace Barotrauma
GameMain.GameSession?.CrewManager?.AddOrder(order, fadeOutTime);
}
}
private class CharacterInfoComparer : IEqualityComparer<CharacterInfo>
{
public bool Equals(CharacterInfo x, CharacterInfo y)
{
if (ReferenceEquals(x, y))
{
return true;
}
if (x is null || y is null)
{
return false;
}
return x.ID == y.ID;
}
public int GetHashCode(CharacterInfo obj)
{
return obj.ID;
}
}
public bool UpdateReserveBenchIfNeeded(IEnumerable<CharacterInfo> updatedReserveBench)
{
var newBench = updatedReserveBench.ToHashSet(new CharacterInfoComparer());
var currentBench = reserveBench.ToHashSet(new CharacterInfoComparer());
bool updateNeeded = !newBench.SetEquals(currentBench);
if (updateNeeded)
{
reserveBench.Clear(); // since this is the reserve bench (characters not instantiated), there's no need to retain any references etc
reserveBench.AddRange(updatedReserveBench);
}
return updateNeeded;
}
/// <summary>
/// This will update which CharacterInfos should be in CrewManager and which shouldn't, excluding the reserve bench.
/// The CharacterInfos themselves aren't updated, they will only be either added, removed, or kept as-is.
/// </summary>
public bool UpdateCrewManagerIfNecessary(List<CharacterInfo> updatedCrewManager)
{
// CharacterInfos no longer in the server's CrewManager
var toRemove = characterInfos.Where(original => updatedCrewManager.None(updated => updated.ID == original.ID)).ToList();
// CharacterInfos that are in the server's CrewManager but not on the client yet
var toAdd = updatedCrewManager.Where(updated => characterInfos.None(original => original.ID == updated.ID)).ToList();
foreach (CharacterInfo characterInfo in toRemove)
{
if (characterInfo.Character is Character existingCharacter)
{
if (!existingCharacter.IsBot) { continue; } // on client side players are also stored here, we should skip those in this case
RemoveCharacter(characterInfo.Character, removeInfo: true, resetCrewListIndex: true);
}
else
{
characterInfos.Remove(characterInfo);
}
}
characterInfos.AddRange(toAdd);
return toRemove.Count > 0 || toAdd.Count > 0;
}
}
}

View File

@@ -366,7 +366,7 @@ namespace Barotrauma
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, npc);
CampaignUI.UpgradeStore?.RequestRefresh();
CampaignUI.UpgradeStore?.RequestRefresh(refreshUpgrades: true);
break;
}

View File

@@ -532,6 +532,7 @@ namespace Barotrauma
bool isFirstRound = msg.ReadBoolean();
byte campaignID = msg.ReadByte();
byte roundId = msg.ReadByte();
UInt16 saveID = msg.ReadUInt16();
string mapSeed = msg.ReadString();
@@ -553,7 +554,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.Misc))
{
DebugConsole.Log("Received campaign update (Misc)");
DebugConsole.Log("Received campaign update (Misc), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
@@ -571,7 +572,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.MapAndMissions))
{
DebugConsole.Log("Received campaign update (MapAndMissions)");
DebugConsole.Log("Received campaign update (MapAndMissions), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
bool forceMapUI = msg.ReadBoolean();
bool allowDebugTeleport = msg.ReadBoolean();
@@ -634,7 +635,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.SubList))
{
DebugConsole.Log("Received campaign update (SubList)");
DebugConsole.Log("Received campaign update (SubList), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
ushort ownedSubCount = msg.ReadUInt16();
List<ushort> ownedSubIndices = new List<ushort>();
@@ -679,7 +680,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.UpgradeManager))
{
DebugConsole.Log("Received campaign update (UpgradeManager)");
DebugConsole.Log("Received campaign update (UpgradeManager), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
ushort pendingUpgradeCount = msg.ReadUInt16();
@@ -737,7 +738,7 @@ namespace Barotrauma
if (requiredFlags.HasFlag(NetFlags.ItemsInBuyCrate))
{
DebugConsole.Log("Received campaign update (ItemsInBuyCrate)");
DebugConsole.Log("Received campaign update (ItemsInBuyCrate), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var buyCrateItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.ItemsInBuyCrate, id, requireUpToDateSave: true))
@@ -753,7 +754,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.ItemsInSellFromSubCrate))
{
DebugConsole.Log("Received campaign update (ItemsInSellFromSubCrate)");
DebugConsole.Log("Received campaign update (ItemsInSellFromSubCrate), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var subSellCrateItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.ItemsInSellFromSubCrate, id, requireUpToDateSave: true))
@@ -769,7 +770,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.PurchasedItems))
{
DebugConsole.Log("Received campaign update (PuchasedItems)");
DebugConsole.Log("Received campaign update (PuchasedItems), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var purchasedItems = ReadPurchasedItems(msg, sender: null);
if (ShouldApply(NetFlags.PurchasedItems, id, requireUpToDateSave: true))
@@ -785,7 +786,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.SoldItems))
{
DebugConsole.Log("Received campaign update (SoldItems)");
DebugConsole.Log("Received campaign update (SoldItems), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
var soldItems = ReadSoldItems(msg);
if (ShouldApply(NetFlags.SoldItems, id, requireUpToDateSave: true))
@@ -801,7 +802,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.Reputation))
{
DebugConsole.Log("Received campaign update (Reputation)");
DebugConsole.Log("Received campaign update (Reputation), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
Dictionary<Identifier, float> factionReps = new Dictionary<Identifier, float>();
byte factionsCount = msg.ReadByte();
@@ -828,7 +829,7 @@ namespace Barotrauma
}
if (requiredFlags.HasFlag(NetFlags.CharacterInfo))
{
DebugConsole.Log("Received campaign update (CharacterInfo)");
DebugConsole.Log("Received campaign update (CharacterInfo), round id: " + roundId);
UInt16 id = msg.ReadUInt16();
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
@@ -837,16 +838,27 @@ namespace Barotrauma
{
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg, requireJobPrefabFound: !waitForModsDownloaded);
}
if (!waitForModsDownloaded && ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true))
//don't require the correct round ID for the character info if we're in the lobby
// = allow updating the character to the latest one in the lobby, even though we've not loaded to the same round as the server
if (!waitForModsDownloaded && ShouldApply(NetFlags.CharacterInfo, id, requireUpToDateSave: true, requireCorrectRoundId: Screen.Selected != GameMain.NetLobbyScreen))
{
if (myCharacterInfo != null)
{
GameMain.Client.CharacterInfo = myCharacterInfo;
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(myCharacterInfo);
GameMain.GameSession.RefreshAnyOpenPlayerInfo();
}
else
{
//don't reset the character info nor the open UI here here,
//the client needs it to be able to customize the character they want to next spawn as
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
//if we've already discarded our current character and the server is just "verifying" that,
//no need to refresh the UI (no changes, refreshing would just throw the client out of the character settings panel)
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
{
GameMain.GameSession.RefreshAnyOpenPlayerInfo();
}
}
}
}
@@ -863,8 +875,14 @@ namespace Barotrauma
}
campaign.SuppressStateSending = false;
bool ShouldApply(NetFlags flag, UInt16 id, bool requireUpToDateSave)
bool ShouldApply(NetFlags flag, UInt16 id, bool requireUpToDateSave, bool requireCorrectRoundId = true)
{
if (requireCorrectRoundId && roundId != campaign.RoundID)
{
DebugConsole.Log($"Received campaing update for a different round (client: {campaign.RoundID}, server: {roundId}), ignoring...");
return false;
}
if (NetIdUtils.IdMoreRecent(id, campaign.GetLastUpdateIdForFlag(flag)) &&
(!requireUpToDateSave || saveID == campaign.LastSaveID))
{
@@ -919,26 +937,42 @@ namespace Barotrauma
ushort pendingHireLength = msg.ReadUInt16();
List<UInt16> pendingHires = new List<UInt16>();
bool[] pendingHiresToReserveBench = new bool[pendingHireLength];
for (int i = 0; i < pendingHireLength; i++)
{
pendingHires.Add(msg.ReadUInt16());
pendingHiresToReserveBench[i] = msg.ReadBoolean();
}
ushort hiredLength = msg.ReadUInt16();
List<CharacterInfo> hiredCharacters = new List<CharacterInfo>();
for (int i = 0; i < hiredLength; i++)
List<CharacterInfo> updatedCrewManager = new List<CharacterInfo>();
ushort crewLength = msg.ReadUInt16();
for (int i = 0; i < crewLength; i++)
{
CharacterInfo hired = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
hired.Salary = msg.ReadInt32();
hiredCharacters.Add(hired);
CharacterInfo crewMember = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
if (crewMember.IsNewHire)
{
hiredCharacters.Add(crewMember);
}
updatedCrewManager.Add(crewMember);
}
bool crewManagerUpdated = GameMain.GameSession.CrewManager?.UpdateCrewManagerIfNecessary(updatedCrewManager) ?? false;
ushort reserveBenchLength = msg.ReadUInt16();
List<CharacterInfo> updatedReserveBench = new List<CharacterInfo>();
for (int i = 0; i < reserveBenchLength; i++)
{
CharacterInfo info = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
updatedReserveBench.Add(info);
}
bool reserveBenchUpdated = GameMain.GameSession.CrewManager?.UpdateReserveBenchIfNeeded(updatedReserveBench) ?? false;
bool renameCrewMember = msg.ReadBoolean();
if (renameCrewMember)
{
UInt16 renamedIdentifier = msg.ReadUInt16();
string newName = msg.ReadString();
CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == renamedIdentifier);
CharacterInfo renamedCharacter = CrewManager.GetCharacterInfos(includeReserveBench: true).FirstOrDefault(info => info.ID == renamedIdentifier);
if (renamedCharacter != null)
{
CrewManager.RenameCharacter(renamedCharacter, newName);
@@ -955,7 +989,7 @@ namespace Barotrauma
if (fireCharacter)
{
UInt16 firedIdentifier = msg.ReadUInt16();
CharacterInfo firedCharacter = CrewManager.GetCharacterInfos().FirstOrDefault(info => info.ID == firedIdentifier);
CharacterInfo firedCharacter = CrewManager.GetCharacterInfos(includeReserveBench: true).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
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
}
@@ -967,8 +1001,8 @@ namespace Barotrauma
{
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(); }
//don't check the crew size limit: if the server says someone's hired, then it's so
CampaignUI.HRManagerUI.SetPendingHires(pendingHires, pendingHiresToReserveBench, map.CurrentLocation, checkCrewSizeLimit: false);
}
}
else
@@ -979,6 +1013,11 @@ namespace Barotrauma
CurrentLocation?.ForceHireableCharacters(availableHires);
}
if (fireCharacter || renameCrewMember || crewManagerUpdated || reserveBenchUpdated)
{
CampaignUI?.HRManagerUI?.RefreshHRView();
GameMain.GameSession?.DeathPrompt?.UpdateBotList();
}
}
public void ClientReadMoney(IReadMessage inc)

View File

@@ -379,7 +379,7 @@ namespace Barotrauma
if (success)
{
// Event history must be registered before ending the round or it will be cleared
GameMain.GameSession.EventManager.RegisterEventHistory();
GameMain.GameSession.EventManager.StoreEventDataAtRoundEnd();
}
GameMain.GameSession.EndRound("", transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;

View File

@@ -110,9 +110,13 @@ namespace Barotrauma
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)
{
CanBeFocused = false,
Visible = false
};
respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform), "", wrap: true);
respawnInfoText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform), "", wrap: true)
{
CanBeFocused = false
};
deathChoiceButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), deathChoiceInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = HUDLayoutSettings.Padding,
@@ -189,7 +193,7 @@ namespace Barotrauma
if (GameMain.NetworkMember != null)
{
GameMain.NetLobbyScreen.CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList();
GameMain.NetLobbyScreen?.JobSelectionFrame?.AddToGUIUpdateList(order: 1);
}
DeathPrompt?.AddToGUIUpdateList();
@@ -335,6 +339,20 @@ namespace Barotrauma
}
}
}
/// <summary>
/// If there are any menu panels etc. open that contain information about the current player character, refresh it.
/// Useful when the player character changes, e.g. at permadeath, and subsequent taking over of a bot character.
/// </summary>
public void RefreshAnyOpenPlayerInfo()
{
DebugConsole.NewMessage($"Refreshing any open player info");
if (IsTabMenuOpen && TabMenu.SelectedTab == TabMenu.InfoFrameTab.Talents)
{
TabMenuInstance.SelectInfoFrameTab(TabMenu.InfoFrameTab.Talents);
}
// TODO: This can be expanded as need arises
}
public void Draw(SpriteBatch spriteBatch)
{

View File

@@ -316,7 +316,7 @@ namespace Barotrauma
if (affliction?.Prefab == null) { continue; }
if (affliction.Prefab.IsBuff) { continue; }
if (affliction.Prefab == AfflictionPrefab.OxygenLow) { continue; }
if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.IsEntityRadiated(character) ?? false)) { continue; }
if (affliction.Prefab == AfflictionPrefab.RadiationSickness && (GameMain.GameSession.Map?.Radiation?.DepthInRadiation(character) ?? 0) > 0) { continue; }
if (affliction.Strength < affliction.Prefab.ShowIconThreshold) { continue; }
DisplayHint("onafflictiondisplayed".ToIdentifier(),
variables: new[] { ("[key]".ToIdentifier(), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)) },

View File

@@ -14,11 +14,11 @@ namespace Barotrauma
private float crewListAnimDelay = 0.25f;
private float missionIconAnimDelay;
private const float JobColumnWidthPercentage = 0.1f;
private const float CharacterColumnWidthPercentage = 0.4f;
private const float StatusColumnWidthPercentage = 0.12f;
private const float KillColumnWidthPercentage = 0.1f;
private const float DeathColumnWidthPercentage = 0.1f;
private const float JobColumnWidthPercentage = 0.05f;
private const float CharacterColumnWidthPercentage = 0.35f;
private const float StatusColumnWidthPercentage = 0.25f;
private const float KillColumnWidthPercentage = 0.05f;
private const float DeathColumnWidthPercentage = 0.05f;
private int jobColumnWidth, characterColumnWidth, statusColumnWidth, killColumnWidth, deathColumnWidth;
@@ -467,7 +467,7 @@ namespace Barotrauma
};
if (icon != null)
{
missionIcon = new GUIImage(new RectTransform(new Point(iconSize), content.RectTransform), icon, null, true)
missionIcon = new GUIImage(new RectTransform(new Point(iconSize), content.RectTransform), icon, scaleToFit: true)
{
Color = iconColor,
HoverColor = iconColor,
@@ -661,15 +661,18 @@ namespace Barotrauma
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(JobColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(CharacterColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
if (gameMode is PvPMode)
if (gameMode is PvPMode && GameMain.NetworkMember?.RespawnManager != null)
{
var killButton = new GUIButton(new RectTransform(new Vector2(KillColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("killcount"), style: "GUIButtonSmallFreeScale");
killColumnWidth = killButton.Rect.Width;
var deathButton = new GUIButton(new RectTransform(new Vector2(DeathColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("deathcount"), style: "GUIButtonSmallFreeScale");
deathColumnWidth = deathButton.Rect.Width;
}
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(StatusColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
else
{
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(StatusColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
statusColumnWidth = statusButton.Rect.Width;
}
foreach (var btn in headerFrame.GetAllChildren<GUIButton>())
{
@@ -680,7 +683,6 @@ namespace Barotrauma
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
statusColumnWidth = statusButton.Rect.Width;
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
@@ -758,7 +760,7 @@ namespace Barotrauma
Character character = characterInfo.Character;
if (character == null || character.IsDead)
{
if (character == null && characterInfo.IsNewHire && characterInfo.CauseOfDeath == null)
if (character == null && (characterInfo.IsNewHire || characterInfo.BotStatus == BotStatus.ActiveService) && characterInfo.CauseOfDeath == null)
{
statusText = TextManager.Get("CampaignCrew.NewHire");
statusColor = GUIStyle.Blue;
@@ -798,19 +800,21 @@ namespace Barotrauma
}
}
if (gameMode is PvPMode pvpMode)
if (gameMode is PvPMode && GameMain.NetworkMember?.RespawnManager != null)
{
new GUITextBlock(new RectTransform(new Point(killColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
killCounts.GetValueOrDefault(characterInfo).ToString(), textAlignment: Alignment.Center);
new GUITextBlock(new RectTransform(new Point(deathColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
deathCounts.GetValueOrDefault(characterInfo).ToString(), textAlignment: Alignment.Center);
}
GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(statusText.Value, GUIStyle.Font, statusColumnWidth), textAlignment: Alignment.Center, textColor: statusColor, font: GUIStyle.SmallFont)
else
{
ToolTip = statusText.Value
};
GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(statusText.Value, GUIStyle.SmallFont, statusColumnWidth), textAlignment: Alignment.Center, textColor: statusColor, font: GUIStyle.SmallFont)
{
ToolTip = statusText.Value
};
}
frame.FadeIn(animDelay, 0.15f);
foreach (var child in frame.GetAllChildren())

View File

@@ -84,7 +84,7 @@ namespace Barotrauma
get { return layout; }
set
{
if (layout == value) return;
if (layout == value) { return; }
layout = value;
SetSlotPositions(layout);
}
@@ -259,8 +259,8 @@ namespace Barotrauma
int spacing = GUI.IntScale(5);
SlotSize = (SlotSpriteSmall.size * UIScale * GUI.AspectRatioAdjustment).ToPoint();
int bottomOffset = SlotSize.Y + spacing * 2 + ContainedIndicatorHeight;
int personalSlotY = GameMain.GraphicsHeight - bottomOffset * 2 - spacing * 2 - (int)(UnequippedIndicator.size.Y * UIScale);
int bottomOffset = GetBottomOffset(multiplier: 2);
int personalSlotY = GetVerticalOffsetFromBottom(multiplier: 2);
if (visualSlots == null) { CreateSlots(); }
if (visualSlots.None()) { return; }
@@ -353,7 +353,15 @@ namespace Barotrauma
case Layout.Left:
{
int x = HUDLayoutSettings.InventoryAreaLower.X;
if (!GUI.IsUltrawide && GUI.IsHUDScaled)
{
// On non-ultra-wide aspect ratios, the inventories can easily overlap with each other, if there's any scaling.
// So let's offset the other inventory to the left.
const float margin = 100;
x -= HUDLayoutSettings.ChatBoxArea.Width - (int)margin;
}
int personalSlotX = x;
float y = GameMain.GraphicsHeight - bottomOffset;
for (int i = 0; i < SlotPositions.Length; i++)
{
@@ -366,7 +374,7 @@ namespace Barotrauma
}
else
{
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + spacing;
}
}
@@ -380,7 +388,7 @@ namespace Barotrauma
continue;
}
if (!HideSlot(i) || SlotTypes[i] == InvSlotType.HealthInterface) { continue; }
SlotPositions[i] = new Vector2(x, GameMain.GraphicsHeight - bottomOffset);
SlotPositions[i] = new Vector2(x, y);
x += visualSlots[i].Rect.Width + spacing;
}
}
@@ -446,6 +454,9 @@ namespace Barotrauma
visualSlots[i].DrawOffset = Vector2.Zero;
}
}
int GetBottomOffset(int multiplier) => SlotSize.Y + spacing * multiplier + ContainedIndicatorHeight;
int GetVerticalOffsetFromBottom(int multiplier) => GameMain.GraphicsHeight - (GetBottomOffset(multiplier) + spacing) * multiplier - (int)(UnequippedIndicator.size.Y * UIScale);
}
protected override void ControlInput(Camera cam)

View File

@@ -34,6 +34,8 @@ namespace Barotrauma.Items.Components
}
private Vector2 _chargeSoundWindupPitchSlide;
public Vector2 BarrelScreenPos => Screen.Selected.Cam.WorldToScreen(item.DrawPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos));
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private readonly List<ParticleEmitter> particleEmitterCharges = new List<ParticleEmitter>();
@@ -86,28 +88,19 @@ namespace Barotrauma.Items.Components
currentCrossHairScale = currentCrossHairPointerScale = cam == null ? 1.0f : cam.Zoom;
if (crosshairSprite != null)
{
Vector2 aimRefWorldPos = character.AimRefPosition;
if (character.Submarine != null) { aimRefWorldPos += character.Submarine.Position; }
Vector2 itemPos = cam.WorldToScreen(aimRefWorldPos);
float rotation = (item.body.Dir == 1.0f) ? item.body.Rotation : item.body.Rotation - MathHelper.Pi;
Vector2 barrelDir = new Vector2((float)Math.Cos(rotation), -(float)Math.Sin(rotation));
Vector2 mouseDiff = itemPos - PlayerInput.MousePosition;
crosshairPos = new Vector2(
MathHelper.Clamp(itemPos.X + barrelDir.X * mouseDiff.Length(), 0, GameMain.GraphicsWidth),
MathHelper.Clamp(itemPos.Y + barrelDir.Y * mouseDiff.Length(), 0, GameMain.GraphicsHeight));
// Set position based on in-world aim
Vector2 barrelDir = (MathF.Cos(item.body.TransformedRotation), -MathF.Sin(item.body.TransformedRotation));
float mouseDist = Vector2.Distance(BarrelScreenPos, PlayerInput.MousePosition);
crosshairPos = Vector2.Clamp(BarrelScreenPos + barrelDir * mouseDist, Vector2.Zero, (GameMain.GraphicsWidth, GameMain.GraphicsHeight));
// Resize pointer based on current spread
float spread = GetSpread(character);
Projectile projectile = FindProjectile();
if (projectile != null)
{
spread += MathHelper.ToRadians(projectile.Spread);
if (FindProjectile() is Projectile projectile)
{
spread += MathHelper.ToRadians(projectile.Spread);
}
float crossHairDist = Vector2.Distance(item.WorldPosition, cam.ScreenToWorld(crosshairPos));
float spreadDist = (float)Math.Sin(spread) * crossHairDist;
currentCrossHairPointerScale = MathHelper.Clamp(spreadDist / Math.Min(crosshairSprite.size.X, crosshairSprite.size.Y), 0.1f, 10.0f);
float spreadAtRange = MathF.Sin(spread) * Vector2.Distance(BarrelScreenPos, crosshairPos);
currentCrossHairPointerScale = MathHelper.Clamp(spreadAtRange / Math.Min(crosshairSprite.size.X, crosshairSprite.size.Y), 0.1f, 10f);
}
currentCrossHairScale *= CrossHairScale;
crosshairPointerPos = PlayerInput.MousePosition;

View File

@@ -316,11 +316,13 @@ namespace Barotrauma.Items.Components
0.01f,
loopingSound.RoundSound.GetRandomFrequencyMultiplier(),
SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true;
item.CheckNeedsSoundUpdate(this);
//TODO: tweak
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range;
if (loopingSoundChannel != null)
{
loopingSoundChannel.Looping = true;
item.CheckNeedsSoundUpdate(this);
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range;
}
}
// Looping sound with manual selection mode should be changed if value of ManuallySelectedSound has changed
@@ -397,10 +399,12 @@ namespace Barotrauma.Items.Components
if (volume <= 0.0001f) { return; }
loopingSound = itemSound;
loopingSoundChannel = SoundPlayer.PlaySound(loopingSound.RoundSound, position, volume: 0.01f, hullGuess: item.CurrentHull);
loopingSoundChannel.Looping = true;
//TODO: tweak
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range;
if (loopingSoundChannel != null)
{
loopingSoundChannel.Looping = true;
loopingSoundChannel.Near = loopingSound.Range * 0.4f;
loopingSoundChannel.Far = loopingSound.Range;
}
}
}
else
@@ -740,6 +744,9 @@ namespace Barotrauma.Items.Components
}),
new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () =>
{
//ensure the offset is set to where the frame is now
//(it may have been repositioned by the overlap prevention logic, which doesn't set this offset)
GuiFrameOffset = GuiFrame.RectTransform.ScreenSpaceOffset;
LockGuiFramePosition = !LockGuiFramePosition;
guiFrameDragHandle.Enabled = !LockGuiFramePosition;
if (SerializableProperties.TryGetValue(nameof(LockGuiFramePosition).ToIdentifier(), out var property))

View File

@@ -474,11 +474,22 @@ namespace Barotrauma.Items.Components
int i = 0;
foreach (ContainedItem contained in containedItems)
{
Vector2 itemPos = currentItemPos;
if (contained.Item?.Sprite == null) { continue; }
if (contained.Hide) { continue; }
Vector2 itemPos = transformedItemPos;
int targetSlotIndex = ItemsUseInventoryPlacement ? Inventory.FindIndex(contained.Item) : i;
//interval set on both axes -> use a grid layout
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
{
itemPos += transformedItemIntervalHorizontal * (targetSlotIndex % ItemsPerRow);
itemPos += transformedItemIntervalVertical * (targetSlotIndex / ItemsPerRow);
}
else
{
itemPos += (transformedItemIntervalHorizontal + transformedItemIntervalVertical) * targetSlotIndex;
}
if (contained.ItemPos.HasValue)
{
Vector2 pos = contained.ItemPos.Value;
@@ -531,7 +542,7 @@ namespace Barotrauma.Items.Components
containedSpriteDepth = containedSpriteDepths[i];
}
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
SpriteEffects spriteEffects = SpriteEffects.None;
float spriteRotation = ItemRotation;
if (contained.Rotation != 0)
@@ -570,20 +581,6 @@ namespace Barotrauma.Items.Components
}
i++;
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
{
//interval set on both axes -> use a grid layout
currentItemPos += transformedItemIntervalHorizontal;
if (i % ItemsPerRow == 0)
{
currentItemPos = transformedItemPos;
currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow);
}
}
else
{
currentItemPos += transformedItemIntervalHorizontal + transformedItemIntervalVertical;
}
}
}

View File

@@ -35,6 +35,7 @@ namespace Barotrauma.Items.Components
{
Light.SpriteScale = Vector2.One * item.Scale;
Light.Position = ParentBody != null ? ParentBody.Position : item.Position;
SetLightSourceTransformProjSpecific();
}
partial void SetLightSourceState(bool enabled, float brightness)
@@ -51,27 +52,42 @@ namespace Barotrauma.Items.Components
partial void SetLightSourceTransformProjSpecific()
{
Vector2 offset = Vector2.Zero;
if (LightOffset != Vector2.Zero)
{
offset = Vector2.Transform(LightOffset, Matrix.CreateRotationZ(item.FlippedY ? -item.RotationRad - MathHelper.Pi : -item.RotationRad)) * item.Scale;
}
if (ParentBody != null)
{
Light.ParentBody = ParentBody;
Light.OffsetFromBody = offset;
}
else if (turret != null)
{
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y) + offset;
}
else if (item.body != null)
{
Light.ParentBody = item.body;
Light.OffsetFromBody = offset;
}
else
{
Light.Position = item.Position;
Light.Position = item.Position + offset;
}
PhysicsBody body = Light.ParentBody;
if (body != null && body.Enabled)
if (body != null)
{
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
if (body.Enabled)
{
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
}
else
{
Light.LightSpriteEffect = item.SpriteEffects;
}
}
else
{
@@ -85,11 +101,13 @@ namespace Barotrauma.Items.Components
if (Light?.LightSprite == null) { return; }
if ((item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
{
Vector2 offset = Vector2.Transform(LightOffset, Matrix.CreateRotationZ(item.FlippedY ? -item.RotationRad - MathHelper.Pi : -item.RotationRad)) * item.Scale;
Vector2 origin = Light.LightSprite.Origin;
if ((Light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = Light.LightSprite.SourceRect.Width - origin.X; }
if ((Light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = Light.LightSprite.SourceRect.Height - origin.Y; }
Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition;
Vector2 drawPos = item.body?.DrawPosition ?? item.DrawPosition + offset;
Color color = lightColor;
if (Light.OverrideLightSpriteAlpha.HasValue)

View File

@@ -6,7 +6,6 @@ namespace Barotrauma.Items.Components
{
partial class Controller : ItemComponent
{
private bool chatBoxOriginalState;
private bool isHUDsHidden;
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
@@ -36,45 +35,19 @@ namespace Barotrauma.Items.Components
partial void HideHUDs(bool value)
{
if (isHUDsHidden == value) { return; }
if (value == true)
if (value)
{
GameMain.GameSession?.CrewManager?.AutoHideCrewList();
ToggleChatBox(false, storeOriginalState: true);
ChatBox.AutoHideChatBox();
}
else
{
GameMain.GameSession?.CrewManager?.ResetCrewList();
ToggleChatBox(chatBoxOriginalState, storeOriginalState: false);
GameMain.GameSession?.CrewManager?.ResetCrewListOpenState();
ChatBox.ResetChatBoxOpenState();
}
isHUDsHidden = value;
}
private void ToggleChatBox(bool value, bool storeOriginalState)
{
var crewManager = GameMain.GameSession?.CrewManager;
if (crewManager == null) { return; }
if (crewManager.IsSinglePlayer)
{
if (crewManager.ChatBox != null)
{
if (storeOriginalState)
{
chatBoxOriginalState = crewManager.ChatBox.ToggleOpen;
}
crewManager.ChatBox.ToggleOpen = value;
}
}
else if (GameMain.Client != null)
{
if (storeOriginalState)
{
chatBoxOriginalState = GameMain.Client.ChatBox.ToggleOpen;
}
GameMain.Client.ChatBox.ToggleOpen = value;
}
}
#if DEBUG
public override void CreateEditingHUD(SerializableEntityEditor editor)
{

View File

@@ -11,11 +11,21 @@ namespace Barotrauma.Items.Components
{
partial class Fabricator : Powered, IServerSerializable, IClientSerializable
{
private enum SortBy
{
Category,
Alphabetical,
SkillRequirement,
Price
}
private GUIListBox itemList;
private GUIFrame selectedItemFrame;
private GUIFrame selectedItemReqsFrame;
private GUILayoutGroup outputTopArea, paddedOutputArea;
private GUITextBlock amountTextMax;
private GUIScrollBar amountInput;
@@ -26,6 +36,8 @@ namespace Barotrauma.Items.Components
private GUIButton activateButton;
private GUITextBox itemFilterBox;
private GUITickBox availableOnlyTickBox;
private GUIDropDown sortByDropdown;
private GUIComponent outputSlot;
private GUIComponent inputInventoryHolder, outputInventoryHolder;
@@ -33,6 +45,9 @@ namespace Barotrauma.Items.Components
private readonly List<GUIButton> itemCategoryButtons = new List<GUIButton>();
private MapEntityCategory? selectedItemCategory;
private GUITextBlock requiresRecipeText;
private GUITextBlock nothingToShowText;
public FabricationRecipe SelectedItem
{
get { return selectedItem; }
@@ -65,6 +80,15 @@ namespace Barotrauma.Items.Components
[Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)]
public string FabricationLimitReachedText { get; set; }
[Serialize(true, IsPropertySaveable.No)]
public bool ShowSortByDropdown { get; set; }
[Serialize(true, IsPropertySaveable.No)]
public bool ShowAvailableOnlyTickBox { get; set; }
[Serialize(true, IsPropertySaveable.No)]
public bool ShowCategoryButtons { get; set; }
public override bool RecreateGUIOnResolutionChange => true;
protected override void OnResolutionChanged()
@@ -99,7 +123,7 @@ namespace Barotrauma.Items.Components
itemCategoryButtons.Clear();
//only create category buttons if there's more than one category in addition to "All"
if (itemCategories.Count > 2)
if (ShowCategoryButtons && itemCategories.Count > 2)
{
// === Item category buttons ===
var categoryButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.05f, 1.0f), innerArea.RectTransform))
@@ -154,24 +178,24 @@ namespace Barotrauma.Items.Components
};
// === TOP AREA ===
var topFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.65f), mainFrame.RectTransform), style: "InnerFrameDark");
var topFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), mainFrame.RectTransform), style: "InnerFrameDark");
// === ITEM LIST ===
var itemListFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), topFrame.RectTransform), childAnchor: Anchor.Center);
var paddedItemFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), itemListFrame.RectTransform))
var paddedItemFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), itemListFrame.RectTransform), isHorizontal: false)
{
Stretch = true,
RelativeSpacing = 0.03f
Stretch = true
};
var filterArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.03f,
Stretch = true,
RelativeSpacing = 0.03f,
UserData = "filterarea"
};
new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), filterArea.RectTransform), TextManager.Get("serverlog.filter"), font: GUIStyle.SubHeadingFont)
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), filterArea.RectTransform), TextManager.Get("serverlog.filter"),
font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
Padding = Vector4.Zero,
Padding = Vector4.Zero,
AutoScaleVertical = true
};
itemFilterBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1.0f), filterArea.RectTransform), createClearButton: true)
@@ -183,29 +207,91 @@ namespace Barotrauma.Items.Components
FilterEntities(selectedItemCategory, text);
return true;
};
filterArea.RectTransform.MinSize = new Point(0, itemFilterBox.Rect.Height);
filterArea.RectTransform.MaxSize = new Point(int.MaxValue, itemFilterBox.Rect.Height);
itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), paddedItemFrame.RectTransform), style: null)
var sortByArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.03f,
Visible = ShowSortByDropdown,
IgnoreLayoutGroups = !ShowSortByDropdown
};
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), sortByArea.RectTransform), TextManager.Get("campaignstore.sortby"),
font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
Padding = Vector4.Zero,
AutoScaleVertical = true
};
sortByDropdown = new GUIDropDown(new RectTransform(new Vector2(0.8f, 1.0f), sortByArea.RectTransform));
foreach (SortBy sortBy in Enum.GetValues<SortBy>())
{
sortByDropdown.AddItem(TextManager.Get("fabricator.sortby." + sortBy), userData: sortBy);
}
sortByDropdown.Select(index: 0);
sortByDropdown.AfterSelected += (GUIComponent selected, object userdata) =>
{
FilterEntities(selectedItemCategory, itemFilterBox.Text);
SortItems(character: Character.Controlled);
return true;
};
sortByArea.RectTransform.MinSize = new Point(0, sortByDropdown.Rect.Height);
sortByArea.RectTransform.MaxSize = new Point(int.MaxValue, sortByDropdown.Rect.Height);
var availableOnlyTickBoxArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), paddedItemFrame.RectTransform), isHorizontal: true)
{
Stretch = true,
Visible = ShowAvailableOnlyTickBox,
IgnoreLayoutGroups = !ShowAvailableOnlyTickBox
};
new GUITextBlock(new RectTransform(new Vector2(0.4f, 1f), availableOnlyTickBoxArea.RectTransform), TextManager.Get("fabricator.onlyshowavailable"),
font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
{
Padding = Vector4.Zero,
AutoScaleVertical = true
};
availableOnlyTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f), availableOnlyTickBoxArea.RectTransform, scaleBasis: ScaleBasis.BothHeight), label: string.Empty)
{
ToolTip = TextManager.Get("fabricator.onlyshowavailable.tooltip")
};
availableOnlyTickBox.OnSelected += (tickbox) =>
{
FilterEntities(selectedItemCategory, itemFilterBox.Text);
return true;
};
availableOnlyTickBox.RectTransform.MinSize = new Point(availableOnlyTickBox.Rect.Height);
availableOnlyTickBox.RectTransform.IsFixedSize = true;
availableOnlyTickBoxArea.RectTransform.MinSize = new Point(0, availableOnlyTickBox.Rect.Height);
availableOnlyTickBoxArea.RectTransform.MaxSize = new Point(int.MaxValue, availableOnlyTickBox.Rect.Height);
itemList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), paddedItemFrame.RectTransform), style: null)
{
PlaySoundOnSelect = true,
OnSelected = (component, userdata) =>
{
selectedItem = userdata as FabricationRecipe;
if (selectedItem != null) { SelectItem(Character.Controlled, selectedItem); }
return true;
if (userdata is FabricationRecipe fabricationRecipe)
{
selectedItem = fabricationRecipe;
SelectItem(Character.Controlled, selectedItem);
return true;
}
else
{
return false;
}
}
};
// === SEPARATOR === //
new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), topFrame.RectTransform, Anchor.Center), style: "VerticalLine");
// === SEPARATOR === //
new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), topFrame.RectTransform, Anchor.Center), style: "VerticalLine");
// === OUTPUT AREA === //
var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1f), topFrame.RectTransform, Anchor.TopRight), childAnchor: Anchor.Center);
var paddedOutputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), outputArea.RectTransform));
var outputTopArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5F), paddedOutputArea.RectTransform, Anchor.Center), isHorizontal: true);
paddedOutputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), outputArea.RectTransform)) { Stretch = true };
outputTopArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), paddedOutputArea.RectTransform, Anchor.Center), isHorizontal: true);
// === OUTPUT SLOT === //
outputSlot = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), outputTopArea.RectTransform), style: null);
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.2f), outputSlot.RectTransform, Anchor.BottomCenter), style: null);
outputSlot = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.4f), outputTopArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: null);
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.0f), outputSlot.RectTransform, Anchor.BottomCenter), style: null);
new GUICustomComponent(new RectTransform(Vector2.One, outputInventoryHolder.RectTransform), DrawOutputOverLay) { CanBeFocused = false };
// === DESCRIPTION === //
selectedItemFrame = new GUIFrame(new RectTransform(new Vector2(0.6f, 1f), outputTopArea.RectTransform), style: null);
@@ -213,7 +299,7 @@ namespace Barotrauma.Items.Components
selectedItemReqsFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedOutputArea.RectTransform), style: null);
// === BOTTOM AREA === //
var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.3f), mainFrame.RectTransform), style: null);
var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.2f), mainFrame.RectTransform), style: null);
if (inputContainer.Capacity > 0)
{
@@ -298,6 +384,33 @@ namespace Barotrauma.Items.Components
CanBeFocused = false
};
CreateRecipes();
foreach (MapEntityCategory category in itemCategories)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("MapEntityCategory." + category), textColor: GUIStyle.TextColorBright)
{
CanBeFocused = false,
UserData = category,
Visible = false
};
}
requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUIStyle.SubHeadingFont)
{
AutoScaleHorizontal = true,
CanBeFocused = false
};
nothingToShowText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.8f), itemList.Content.RectTransform), TextManager.Get("noitemsheader"),
textAlignment: Alignment.Center, textColor: GUIStyle.TextColorDim)
{
CanBeFocused = false,
Visible = false
};
SortItems(character: Character.Controlled);
}
private void RefreshActivateButtonText()
@@ -343,7 +456,8 @@ namespace Barotrauma.Items.Components
};
}
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform), GetRecipeNameAndAmount(fi))
new GUITextBlock(new RectTransform(new Vector2(0.85f, 1f), container.RectTransform),
RichString.Rich(GetRecipeNameAndAmount(fi)), font: GUIStyle.SmallFont)
{
Padding = Vector4.Zero,
AutoScaleVertical = true,
@@ -397,73 +511,106 @@ namespace Barotrauma.Items.Components
if (character != Character.Controlled) { return; }
var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
nonItems.ForEach(i => itemList.Content.RemoveChild(i));
nonItems.ForEach(i => i.Visible = false);
SortItems(character: null);
FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
HideEmptyItemListCategories();
}
private void SortItems(Character character)
{
SortBy sortBy = (SortBy)sortByDropdown.SelectedData;
itemList.Content.RectTransform.SortChildren((c1, c2) =>
{
var item1 = c1.GUIComponent.UserData as FabricationRecipe;
var item2 = c2.GUIComponent.UserData as FabricationRecipe;
int itemPlacement1 = calculatePlacement(item1);
int itemPlacement2 = calculatePlacement(item2);
if (itemPlacement1 != itemPlacement2)
if (item1 == null && item2 == null)
{
return itemPlacement1 > itemPlacement2 ? -1 : 1;
return 0;
}
else if (item1 == null)
{
return -1;
}
else if (item2 == null)
{
return 1;
}
int calculatePlacement(FabricationRecipe recipe)
bool missingRecipe1 = MissingRequiredRecipe(item1, character);
bool missingRecipe2 = MissingRequiredRecipe(item2, character);
if (missingRecipe1 != missingRecipe2)
{
if (recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem))
{
return -2;
}
int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
return placement;
return missingRecipe1.CompareTo(missingRecipe2);
}
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
switch (sortBy)
{
case SortBy.Alphabetical:
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
case SortBy.Category:
var category1 = EnumExtensions.GetIndividualFlags(item1.TargetItem.Category).FirstOrDefault();
var category2 = EnumExtensions.GetIndividualFlags(item2.TargetItem.Category).FirstOrDefault();
if (category1 == category2)
{
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
}
return category1.CompareTo(category2);
case SortBy.SkillRequirement:
float skillRequirement1 = item1.RequiredSkills.Sum(skill => skill.Level);
float skillRequirement2 = item2.RequiredSkills.Sum(skill => skill.Level);
if (MathUtils.NearlyEqual(skillRequirement1, skillRequirement2))
{
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
}
return skillRequirement1.CompareTo(skillRequirement2);
case SortBy.Price:
float itemValue1 = item1.TargetItem.DefaultPrice?.Price ?? 0;
float itemValue2 = item2.TargetItem.DefaultPrice?.Price ?? 0;
if (MathUtils.NearlyEqual(itemValue1, itemValue2))
{
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
}
return itemValue2.CompareTo(itemValue1);
default:
throw new NotImplementedException($"Sorting by {sortBy} has not been implemented.");
}
});
var sufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorsufficientskills"), textColor: GUIStyle.Green, font: GUIStyle.SubHeadingFont)
if (sortBy == SortBy.Category)
{
AutoScaleHorizontal = true,
CanBeFocused = false
};
sufficientSkillsText.RectTransform.SetAsFirstChild();
foreach (var categoryText in itemList.Content.Children.Where(c => c.UserData?.GetType() == typeof(MapEntityCategory)).ToList())
{
categoryText.RectTransform.SetAsLastChild();
var category = (MapEntityCategory)categoryText.UserData;
var firstChildWithMatchingCategory = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe recipe && EnumExtensions.GetIndividualFlags(recipe.TargetItem.Category).FirstOrDefault() == category);
if (firstChildWithMatchingCategory != null)
{
categoryText.RectTransform.RepositionChildInHierarchy(itemList.Content.GetChildIndex(firstChildWithMatchingCategory));
categoryText.Visible = true;
}
else
{
categoryText.Visible = false;
}
}
}
var insufficientSkillsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorinsufficientskills"), textColor: Color.Orange, font: GUIStyle.SubHeadingFont)
requiresRecipeText.RectTransform.SetAsLastChild();
var firstMissingRecipe = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe recipe && MissingRequiredRecipe(recipe, character));
if (firstMissingRecipe != null)
{
AutoScaleHorizontal = true,
CanBeFocused = false
};
var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && FabricationDegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f);
if (firstinSufficient != null)
{
insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.GetChildIndex(firstMissingRecipe));
requiresRecipeText.Visible = true;
}
else
{
sufficientSkillsText.Visible = insufficientSkillsText.Visible = false;
sufficientSkillsText.Enabled = insufficientSkillsText.Enabled = false;
requiresRecipeText.Visible = false;
}
var requiresRecipeText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), itemList.Content.RectTransform),
TextManager.Get("fabricatorrequiresrecipe"), textColor: Color.Red, font: GUIStyle.SubHeadingFont)
{
AutoScaleHorizontal = true,
CanBeFocused = false
};
var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c =>
c.UserData is FabricationRecipe fabricableItem &&
fabricableItem.RequiresRecipe && !AnyOneHasRecipeForItem(character, fabricableItem.TargetItem));
if (firstRequiresRecipe != null)
{
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
}
FilterEntities(selectedItemCategory, itemFilterBox?.Text ?? string.Empty);
HideEmptyItemListCategories();
}
@@ -743,7 +890,7 @@ namespace Barotrauma.Items.Components
itemIcon.Draw(
spriteBatch,
slotRect.Center.ToVector2(),
color: targetItem.TargetItem.InventoryIconColor * 0.4f,
color: Color.Lerp(targetItem.TargetItem.InventoryIconColor, Color.TransparentBlack, 0.5f),
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y) * 0.9f);
}
}
@@ -757,6 +904,9 @@ namespace Barotrauma.Items.Components
private bool FilterEntities(MapEntityCategory? category, string filter)
{
bool onlyShowAvailable = availableOnlyTickBox is { Selected: true };
bool anyVisible = false;
foreach (GUIComponent child in itemList.Content.Children)
{
FabricationRecipe recipe = child.UserData as FabricationRecipe;
@@ -771,16 +921,35 @@ namespace Barotrauma.Items.Components
}
}
if (recipe.RequiresRecipe && recipe.HideIfNoRecipe)
{
if (Character.Controlled != null)
{
if (!AnyOneHasRecipeForItem(Character.Controlled, recipe.TargetItem))
{
child.Visible = false;
continue;
}
}
}
child.Visible =
(string.IsNullOrWhiteSpace(filter) || recipe.DisplayName.Contains(filter, StringComparison.OrdinalIgnoreCase)) &&
(!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value));
}
(!category.HasValue || recipe.TargetItem.Category.HasFlag(category.Value)) &&
(!onlyShowAvailable || CanBeFabricated(recipe, availableIngredients, Character.Controlled));
if (child.Visible)
{
anyVisible = true;
}
}
foreach (GUIButton btn in itemCategoryButtons)
{
btn.Selected = (MapEntityCategory?)btn.UserData == selectedItemCategory;
}
HideEmptyItemListCategories();
nothingToShowText.Visible = !anyVisible;
itemList.UserData = "itemlist";
return true;
}
@@ -788,7 +957,7 @@ namespace Barotrauma.Items.Components
private void HideEmptyItemListCategories()
{
bool visibleElementsChanged = false;
//go through the elements backwards, and disable the labels ("insufficient skills to fabricate", "recipe required...") if there's no items below them
//go through the elements backwards, and disable the labels if there's no items below them
bool recipeVisible = false;
foreach (GUIComponent child in itemList.Content.Children.Reverse())
{
@@ -810,6 +979,12 @@ namespace Barotrauma.Items.Components
}
}
SortBy sortBy = (SortBy)sortByDropdown.SelectedData;
if (sortBy != SortBy.Category)
{
itemList.Content.Children.Where(c => c.UserData?.GetType() == typeof(MapEntityCategory)).ForEach(c => c.Visible = false);
}
if (visibleElementsChanged)
{
itemList.UpdateScrollBarSize();
@@ -841,8 +1016,8 @@ namespace Barotrauma.Items.Components
private void CreateSelectedItemUI(SelectedRecipe recipe)
{
var (user, selectedItem, overrideRequiredTime) = recipe;
int max = Math.Max(selectedItem.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedItem.Amount, 1);
var (user, selectedRecipe, overrideRequiredTime) = recipe;
int max = Math.Max(selectedRecipe.TargetItem.GetMaxStackSize(outputContainer.Inventory) / selectedRecipe.Amount, 1);
if (amountInput != null)
{
@@ -859,18 +1034,59 @@ namespace Barotrauma.Items.Components
selectedItemFrame.ClearChildren();
selectedItemReqsFrame.ClearChildren();
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f, CanBeFocused = true };
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
LocalizedString itemName = GetRecipeNameAndAmount(selectedItem);
LocalizedString itemName = GetRecipeNameAndAmount(selectedRecipe);
LocalizedString name = itemName;
QualityResult result = GetFabricatedItemQuality(selectedItem, user);
QualityResult result = GetFabricatedItemQuality(selectedRecipe, user);
float quality = selectedItem.Quality ?? result.Quality;
if (quality > 0 || result.HasRandomQualityRollChance)
float minimumQuality = selectedRecipe.Quality ?? result.Quality;
LocalizedString qualityTooltip = string.Empty;
if (result.HasRandomQualityRollChance)
{
name = TextManager.GetWithVariable("itemname.quality" + (int)quality, "[itemname]", itemName + '\n')
float plusOnePercentage = result.TotalPlusOnePercentage;
float plusTwoPercentage = result.TotalPlusTwoPercentage;
string plusOnePercentageText = plusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
string plusTwoPercentageText = plusTwoPercentage.ToString("F1", CultureInfo.InvariantCulture);
int plusOneQuality = Math.Clamp(result.Quality + 1, min: 0, max: 3);
int plusTwoQuality = Math.Clamp(result.Quality + 2, min: 0, max: 3);
LocalizedString plusOneQualityText = TextManager.Get($"quality{plusOneQuality}");
LocalizedString plusTwoQualityText = TextManager.Get($"quality{plusTwoQuality}");
string localizationTag = plusTwoPercentage > 0f && plusOnePercentage > 0 && plusOneQuality != plusTwoQuality ? "meetsbonusrequirementtwice" : "meetsbonusrequirement";
var variables = new (string Key, LocalizedString Value)[]
{
("[chance]", plusOnePercentageText), ("[quality]", plusOneQualityText),
("[chance2]", plusTwoPercentageText), ("[quality2]", plusTwoQualityText)
};
if (MathUtils.NearlyEqual(plusOnePercentage, 0))
{
variables = new[] { ("[chance]", plusTwoPercentageText), ("[quality]", plusTwoQualityText) };
}
if (plusOneQuality == plusTwoQuality)
{
LocalizedString rawPercentage = result.PlusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
variables = new[] { ("[chance]", rawPercentage), ("[quality]", plusOneQualityText) };
}
if (plusOnePercentage >= 100.0f) { minimumQuality = plusOneQuality; }
if (plusTwoPercentage >= 100.0f) { minimumQuality = plusTwoQuality; }
qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
}
if (minimumQuality > 0 || result.HasRandomQualityRollChance)
{
name = TextManager.GetWithVariable("itemname.quality" + (int)minimumQuality, "[itemname]", itemName + '\n')
.Fallback(TextManager.GetWithVariable("itemname.quality3", "[itemname]", itemName + '\n'));
}
@@ -884,44 +1100,13 @@ namespace Barotrauma.Items.Components
{
var iconLayout = new GUIFrame(new RectTransform(new Vector2(0.4f, 1f), selectedItemFrame.RectTransform, anchor: Anchor.TopRight), style: null);
var icon = GameSession.CreateNotificationIcon(iconLayout, offset: true);
float percentage1 = result.TotalPlusOnePercentage;
float percentage2 = result.TotalPlusTwoPercentage;
string chance1text = percentage1.ToString("F1", CultureInfo.InvariantCulture);
string chance2text = percentage2.ToString("F1", CultureInfo.InvariantCulture);
int quality1 = Math.Clamp(result.Quality + 1, min: 0, max: 3);
int quality2 = Math.Clamp(result.Quality + 2, min: 0, max: 3);
LocalizedString quality1Text = TextManager.Get($"quality{quality1}");
LocalizedString quality2Text = TextManager.Get($"quality{quality2}");
string localizationTag = percentage2 > 0f && percentage1 > 0 && quality1 != quality2 ? "meetsbonusrequirementtwice" : "meetsbonusrequirement";
var variables = new (string Key, LocalizedString Value)[]
{
("[chance]", chance1text), ("[quality]", quality1Text),
("[chance2]", chance2text), ("[quality2]", quality2Text)
};
if (MathUtils.NearlyEqual(percentage1, 0))
{
variables = new[] { ("[chance]", chance2text), ("[quality]", quality2Text) };
}
if (quality1 == quality2)
{
LocalizedString rawPercentage = result.PlusOnePercentage.ToString("F1", CultureInfo.InvariantCulture);
variables = new[] { ("[chance]", rawPercentage), ("[quality]", quality1Text) };
}
LocalizedString qualityTooltip = TextManager.GetWithVariables(localizationTag, variables);
icon.ToolTip = RichString.Rich(qualityTooltip);
icon.Visible = icon.CanBeFocused = true;
}
outputTopArea.RectTransform.MaxSize = new Point(int.MaxValue, outputInventoryHolder.Rect.Height);
paddedOutputArea.Recalculate();
nameBlock.Padding = new Vector4(0, nameBlock.Padding.Y, GUI.IntScale(5), nameBlock.Padding.W);
if (nameBlock.TextScale < 0.7f)
{
@@ -932,31 +1117,41 @@ namespace Barotrauma.Items.Components
nameBlock.RectTransform.MinSize = new Point(0, (int)(nameBlock.TextSize.Y * nameBlock.TextScale));
}
if (!selectedItem.TargetItem.Description.IsNullOrEmpty())
bool largeUI = GuiFrame.Rect.Height > GUI.IntScale(500);
if (largeUI)
{
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
RichString.Rich(selectedItem.TargetItem.Description),
font: GUIStyle.SmallFont, wrap: true);
description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
paddedFrame.ChildAnchor = Anchor.CenterLeft;
}
while (description.Rect.Height + nameBlock.Rect.Height > paddedFrame.Rect.Height)
if (!selectedRecipe.TargetItem.Description.IsNullOrEmpty())
{
var descriptionParent = largeUI ? paddedReqFrame : paddedFrame;
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), descriptionParent.RectTransform),
RichString.Rich(selectedRecipe.TargetItem.Description),
font: GUIStyle.SmallFont, wrap: true);
if (!largeUI)
{
description.Padding = new Vector4(0, description.Padding.Y, description.Padding.Z, description.Padding.W);
}
while (description.Rect.Height + nameBlock.Rect.Height > descriptionParent.Rect.Height)
{
var lines = description.WrappedText.Split('\n');
if (lines.Count <= 1) { break; }
var newString = string.Join('\n', lines.Take(lines.Count - 1));
description.Text = newString.Substring(0, newString.Length - 4) + "...";
description.CalculateHeightFromText();
description.ToolTip = selectedItem.TargetItem.Description;
description.ToolTip = selectedRecipe.TargetItem.Description;
}
}
IEnumerable<Skill> inadequateSkills = Enumerable.Empty<Skill>();
if (user != null)
{
inadequateSkills = selectedItem.RequiredSkills.Where(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
inadequateSkills = selectedRecipe.RequiredSkills.Where(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
}
if (selectedItem.RequiredSkills.Any())
if (selectedRecipe.RequiredSkills.Any())
{
LocalizedString text = "";
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
@@ -965,20 +1160,20 @@ namespace Barotrauma.Items.Components
AutoScaleHorizontal = true,
ToolTip = TextManager.Get("fabricatorrequiredskills.tooltip")
};
foreach (Skill skill in selectedItem.RequiredSkills)
foreach (Skill skill in selectedRecipe.RequiredSkills)
{
text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + Math.Round(skill.Level * SkillRequirementMultiplier);
if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; }
if (skill != selectedRecipe.RequiredSkills.Last()) { text += "\n"; }
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUIStyle.SmallFont);
}
float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedRecipe.RequiredSkills);
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
float requiredTime = overrideRequiredTime.TryUnwrap(out var time)
? time
: (user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
: (user == null ? selectedRecipe.RequiredTime : GetRequiredTime(selectedRecipe, user));
if ((int)requiredTime > 0)
{
@@ -991,7 +1186,7 @@ namespace Barotrauma.Items.Components
font: GUIStyle.SmallFont);
}
if (SelectedItem.RequiredMoney > 0)
if (selectedRecipe.RequiredMoney > 0)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
TextManager.Get("subeditor.price"), textColor: ToolBox.GradientLerp(degreeOfSuccess, GUIStyle.Red, Color.Yellow, GUIStyle.Green), font: GUIStyle.SubHeadingFont)
@@ -1000,7 +1195,6 @@ namespace Barotrauma.Items.Components
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), TextManager.FormatCurrency(SelectedItem.RequiredMoney),
font: GUIStyle.SmallFont);
}
}
@@ -1068,6 +1262,15 @@ namespace Barotrauma.Items.Components
if (!IsActive)
{
if (outputContainer != null && outputContainer.Inventory.AllItems.Any())
{
if (outputContainer.Inventory.visualSlots is { } visualSlots && visualSlots.Any() &&
visualSlots[0].HighlightTimer <= 0.0f)
{
visualSlots[0].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f);
}
}
if (selectedItem != null && displayingForCharacter != character)
{
//reselect to recreate the info based on the new user's skills
@@ -1101,8 +1304,14 @@ namespace Barotrauma.Items.Components
activateButton.Enabled = canBeFabricated;
}
bool sufficientSkills = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f;
Color baseColor = MissingRequiredRecipe(recipe, character) ?
GUIStyle.Red :
(sufficientSkills ? GUIStyle.TextColorNormal : GUIStyle.Orange);
var childContainer = child.GetChild<GUILayoutGroup>();
childContainer.GetChild<GUITextBlock>().TextColor = Color.White * (canBeFabricated ? 1.0f : 0.5f);
childContainer.GetChild<GUITextBlock>().TextColor = baseColor * (canBeFabricated ? 1.0f : 0.5f);
childContainer.GetChild<GUIImage>().Color = recipe.TargetItem.InventoryIconColor * (canBeFabricated ? 1.0f : 0.5f);
var limitReachedText = child.FindChild(nameof(FabricationLimitReachedText));

View File

@@ -219,8 +219,9 @@ namespace Barotrauma.Items.Components
Vector2 size = isConnectedToSteering ? controlBoxSize : new Vector2(0.46f, 0.4f);
controlContainer = new GUIFrame(new RectTransform(size, GuiFrame.RectTransform, Anchor.BottomLeft), "ItemUI");
if (!isConnectedToSteering && !GUI.IsFourByThree())
if (!isConnectedToSteering && GUI.AspectRatioDifference <= 0)
{
// In wider than 4:3 aspect ratio, we'll limit the max size.
controlContainer.RectTransform.MaxSize = new Point((int)(380 * GUI.xScale), (int)(300 * GUI.yScale));
}
var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center)

View File

@@ -57,24 +57,42 @@ namespace Barotrauma.Items.Components
private GUIMessageBox enterOutpostPrompt, exitOutpostPrompt;
private bool levelStartSelected;
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
public bool LevelStartSelected
{
get { return levelStartTickBox.Selected; }
set { levelStartTickBox.Selected = value; }
get
{
return levelStartTickBox?.Selected ?? levelStartSelected;
}
set
{
TrySetTickBoxSelected(levelStartTickBox, ref levelStartSelected, value);
}
}
private bool levelEndSelected;
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
public bool LevelEndSelected
{
get { return levelEndTickBox.Selected; }
set { levelEndTickBox.Selected = value; }
get { return levelEndTickBox?.Selected ?? levelEndSelected; }
set
{
TrySetTickBoxSelected(levelEndTickBox, ref levelEndSelected, value);
}
}
private bool maintainPos;
[Serialize(defaultValue: false, isSaveable: IsPropertySaveable.Yes, AlwaysUseInstanceValues = true)]
public bool MaintainPos
{
get { return maintainPosTickBox.Selected; }
set { maintainPosTickBox.Selected = value; }
get
{
return maintainPosTickBox?.Selected ?? maintainPos;
}
set
{
TrySetTickBoxSelected(maintainPosTickBox, ref maintainPos, value);
}
}
private float steerRadius;
@@ -924,6 +942,21 @@ namespace Barotrauma.Items.Components
}
}
/// <summary>
/// Sets the value of the specified tickbox, or if it hasn't been instantiated (yet?), just the value of the backing field.
/// </summary>
private void TrySetTickBoxSelected(GUITickBox tickBox, ref bool backingValue, bool newValue)
{
if (tickBox == null)
{
backingValue = newValue;
}
else
{
tickBox.Selected = newValue;
}
}
private bool NudgeButtonClicked(GUIButton btn, object userdata)
{
if (!MaintainPos || !AutoPilot)

View File

@@ -180,7 +180,11 @@ namespace Barotrauma.Items.Components
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
var emitter = new ParticleEmitter(subElement);
//backwards compatibility: previously it was not possible to change if the particles use tracer points, they were always used on projectiles
//now emitters don't use them by default, except on projectiles
emitter.Prefab.Properties.UseTracerPoints = subElement.GetAttributeBool(nameof(emitter.Prefab.Properties.UseTracerPoints), true);
particleEmitters.Add(emitter);
break;
}
}

View File

@@ -58,7 +58,6 @@ namespace Barotrauma.Items.Components
}
}
partial void UseProjSpecific(float deltaTime, Vector2 raystart)
{
foreach (ParticleEmitter particleEmitter in particleEmitters)
@@ -88,25 +87,18 @@ namespace Barotrauma.Items.Components
MathUtils.InverseLerp(targetStructure.Prefab.MinHealth, targetStructure.Health, targetStructure.Health - targetStructure.SectionDamage(sectionIndex)),
GUIStyle.Red, GUIStyle.Green);
if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetStructure.Submarine != null) particlePos += targetStructure.Submarine.DrawPosition;
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
foreach (var emitter in particleEmitterHitStructure)
{
float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
EmitParticle(emitter, deltaTime, pickedPosition, targetStructure.Submarine);
}
}
partial void FixCharacterProjSpecific(Character user, float deltaTime, Character targetCharacter)
{
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetCharacter.Submarine != null) particlePos += targetCharacter.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitCharacter)
{
float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
EmitParticle(emitter, deltaTime, pickedPosition, targetCharacter.Submarine);
}
}
@@ -134,15 +126,22 @@ namespace Barotrauma.Items.Components
}
}
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach ((RelatedItem relatedItem, ParticleEmitter emitter) in particleEmitterHitItem)
{
if (!relatedItem.MatchesItem(targetItem)) { continue; }
float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
EmitParticle(emitter, deltaTime, pickedPosition, targetItem.Submarine);
}
}
private void EmitParticle(ParticleEmitter emitter, float deltaTime, Vector2 simPosition, Submarine targetSub)
{
Vector2 particlePos = ConvertUnits.ToDisplayUnits(simPosition);
if (targetSub != null) { particlePos += targetSub.DrawPosition; }
float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi,
tracerPoints: new Tuple<Vector2, Vector2>(item.WorldPosition + TransformedBarrelPos, particlePos));
}
#if DEBUG
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1, Color? overrideColor = null)
{

View File

@@ -21,9 +21,14 @@ namespace Barotrauma.Items.Components
ShapeExtensions.DrawCircle(spriteBatch, pos, range, 32, Color.Cyan * 0.5f, 3);
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{
SharedEventWrite(msg);
}
public void ClientEventRead(IReadMessage msg, float sendingTime)
{
Channel = msg.ReadRangedInteger(MinChannel, MaxChannel);
SharedEventRead(msg);
}
}
}

View File

@@ -106,13 +106,6 @@ namespace Barotrauma.Items.Components
private static int? selectedNodeIndex;
private static int? highlightedNodeIndex;
[Serialize(0.3f, IsPropertySaveable.No), Editable(MinValueFloat = 0.01f, MaxValueFloat = 10.0f, DecimalCount = 2)]
public float Width
{
get;
set;
}
public Vector2 DrawSize
{
get { return sectionExtents; }

View File

@@ -91,18 +91,19 @@ namespace Barotrauma.Items.Components
{
get
{
float size = Math.Max(transformedBarrelPos.X, transformedBarrelPos.Y);
if (barrelSprite != null)
float size = Math.Max(transformedBarrelPos.X, transformedBarrelPos.Y);
if (railSprite != null && barrelSprite != null)
{
if (railSprite != null)
{
size += Math.Max(Math.Max(barrelSprite.size.X, barrelSprite.size.Y), Math.Max(railSprite.size.X, railSprite.size.Y)) * item.Scale;
}
else
{
size += Math.Max(barrelSprite.size.X, barrelSprite.size.Y) * item.Scale;
}
size += Math.Max(Math.Max(barrelSprite.size.X, barrelSprite.size.Y), Math.Max(railSprite.size.X, railSprite.size.Y)) * item.Scale;
}
else if (railSprite != null)
{
size += Math.Max(railSprite.size.X, railSprite.size.Y) * item.Scale;
}
else if (barrelSprite != null)
{
size += Math.Max(barrelSprite.size.X, barrelSprite.size.Y) * item.Scale;
}
return Vector2.One * size * 2;
}
}

View File

@@ -1603,7 +1603,8 @@ namespace Barotrauma
shadowSprite.Draw(spriteBatch,
new Rectangle(itemPos.ToPoint() - new Point((iconSize / 2 - shadowPadding.X) * textDir - shadowSize.X * textOffset, iconSize / 2 + shadowPadding.Y), shadowSize), Color.Black * 0.8f);
GUI.DrawString(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), DraggingItems.First().Name, Color.White);
var richString = RichString.Rich(DraggingItems.First().Name);
GUI.DrawStringWithColors(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), richString.SanitizedValue, Color.White, richString.RichTextData);
GUI.DrawString(spriteBatch, textPos + new Vector2(toolTipSize.X * textOffset, 0), toolTip,
color: toolTipColor,
font: GUIStyle.SmallFont);

View File

@@ -676,12 +676,6 @@ namespace Barotrauma
origin.Y = -origin.Y + decorativeSprite.Sprite.size.Y;
spriteEffects |= SpriteEffects.FlipVertically;
}
if (body != null)
{
var ca = MathF.Cos(-body.DrawRotation);
var sa = MathF.Sin(-body.DrawRotation);
offset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
}
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(drawPos.X + offset.X, -(drawPos.Y + offset.Y)), decorativeSpriteColor, origin,
-rotation + spriteRotation, decorativeSprite.GetScale(ref spriteAnimState[decorativeSprite].ScaleState, spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, spriteEffects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
@@ -798,6 +792,11 @@ namespace Barotrauma
}
}
}
foreach (var containedItem in ContainedItems)
{
containedItem.UpdateSpriteStates(deltaTime);
}
}
public override void UpdateEditing(Camera cam, float deltaTime)
@@ -1762,7 +1761,7 @@ namespace Barotrauma
if (texts.Any() && !recreateHudTexts) { return texts; }
texts.Clear();
string nameText = Name;
string nameText = RichString.Rich(Prefab.Name).SanitizedValue;
if (Prefab.Tags.Contains("identitycard") || Tags.Contains("despawncontainer"))
{
string[] readTags = Tags.Split(',');
@@ -2502,18 +2501,24 @@ namespace Barotrauma
if (inventory != null)
{
if (inventorySlotIndex is >= 0 and < 255)
if (inventorySlotIndex is >= 0 and < 255 &&
!inventory.TryPutItem(item, inventorySlotIndex, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false, ignoreCondition: true) &&
inventory.IsSlotEmpty(inventorySlotIndex))
{
if (!inventory.TryPutItem(item, inventorySlotIndex, allowSwapping: false, allowCombine: false, user: null, createNetworkEvent: false, ignoreCondition: true) &&
inventory.IsSlotEmpty(inventorySlotIndex))
{
//If the item won't go nicely, force it to the slot. If the server says the item is in the slot, it should go in the slot.
//May happen e.g. when a character is configured to spawn with an item that won't normally go in its inventory slots.
inventory.ForceToSlot(item, index: inventorySlotIndex);
return item;
}
//If the item won't go nicely, force it to the slot. If the server says the item is in the slot, it should go in the slot.
//May happen e.g. when a character is configured to spawn with an item that won't normally go in its inventory slots.
inventory.ForceToSlot(item, index: inventorySlotIndex);
}
inventory.TryPutItem(item, user: null, allowedSlots: item.AllowedSlots, createNetworkEvent: false);
else
{
inventory.TryPutItem(item, user: null, allowedSlots: item.AllowedSlots, createNetworkEvent: false);
}
item.SetTransform(inventory.Owner.SimPosition, 0.0f);
item.Submarine = inventory.Owner.Submarine;
if (inventory.Owner is Character { Enabled: false } && item.body != null)
{
item.body.Enabled = false;
}
}
return item;

View File

@@ -26,7 +26,7 @@ namespace Barotrauma
private Vector3 velocity;
private float depth;
public float Depth { get; private set; }
private float alpha = 1.0f;
@@ -42,6 +42,8 @@ namespace Barotrauma
Vector2 drawPosition;
private bool flippedHorizontally;
public Vector2[,] CurrentSpriteDeformation
{
get;
@@ -88,6 +90,8 @@ namespace Barotrauma
Rand.Range(-prefab.Speed, prefab.Speed, Rand.RandSync.ClientOnly),
Rand.Range(0.0f, prefab.WanderZAmount, Rand.RandSync.ClientOnly));
Depth = Rand.Range(prefab.MinDepth, prefab.MaxDepth, Rand.RandSync.ClientOnly);
checkWallsTimer = Rand.Range(0.0f, CheckWallsInterval, Rand.RandSync.ClientOnly);
foreach (var subElement in prefab.Config.Elements())
@@ -104,6 +108,7 @@ namespace Barotrauma
default:
continue;
}
int j = 0;
foreach (XElement animationElement in subElement.Elements())
{
SpriteDeformation deformation = null;
@@ -118,7 +123,21 @@ namespace Barotrauma
deformation = SpriteDeformation.Load(animationElement, prefab.Name);
if (deformation != null)
{
deformation.Params = Prefab.SpriteDeformations[j].Params;
uniqueSpriteDeformations.Add(deformation);
if (prefab.DeformableSprite != null)
{
if (deformation.Resolution.X > prefab.DeformableSprite.Subdivisions.X ||
deformation.Resolution.Y > prefab.DeformableSprite.Subdivisions.Y)
{
DebugConsole.AddWarning(
$"Potential error in background creature {Prefab.Identifier}: deformation {deformation.GetType()} has a larger resolution ({deformation.Resolution})"+
$" than the amount of subdivisions on the deformable sprite ({prefab.DeformableSprite.Subdivisions}). Should the sprite be subdivided further to make full use of the deformation?",
contentPackage: Prefab.ContentPackage);
}
}
j++;
}
}
if (deformation != null)
@@ -127,12 +146,14 @@ namespace Barotrauma
}
}
}
flashTimer = Rand.Range(0.0f, prefab.FlashInterval, Rand.RandSync.Unsynced);
}
public void Update(float deltaTime)
{
position += new Vector2(velocity.X, velocity.Y) * deltaTime;
depth = MathHelper.Clamp(depth + velocity.Z * deltaTime, Prefab.MinDepth, Prefab.MaxDepth * 10);
Depth = MathHelper.Clamp(Depth + velocity.Z * deltaTime, Prefab.MinDepth, Prefab.MaxDepth);
if (Prefab.FlashInterval > 0.0f)
{
@@ -144,7 +165,7 @@ namespace Barotrauma
else
{
//value goes from 0 to 1 and back to 0 during the flash
alpha = (float)Math.Sin(-flashTimer / Prefab.FlashDuration * MathHelper.Pi) * PerlinNoise.GetPerlin((float)Timing.TotalTime * 0.1f, (float)Timing.TotalTime * 0.2f);
alpha = (float)Math.Sin(-flashTimer / Prefab.FlashDuration * MathHelper.Pi) * PerlinNoise.GetPerlin((float)Timing.TotalTime, (float)Timing.TotalTime * 0.5f);
if (flashTimer < -Prefab.FlashDuration)
{
flashTimer = Prefab.FlashInterval;
@@ -228,7 +249,13 @@ namespace Barotrauma
velocity = Vector3.Lerp(velocity, new Vector3(Steering.X, Steering.Y, velocity.Z), deltaTime);
UpdateDeformations(deltaTime);
//only flip if there's some horizontal movement speed (10% of the creature's speed)
//otherwise a creature swimming roughly up/down can flip around very frequently when the horizontal speed fluctuates around 0
if (Math.Abs(velocity.X) > Prefab.Speed * 0.1f)
{
flippedHorizontally = !Prefab.DisableFlipping && velocity.X < 0.0f;
}
UpdateDeformations(deltaTime, flippedHorizontally);
}
public void DrawLightSprite(SpriteBatch spriteBatch, Camera cam)
@@ -238,12 +265,17 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
Draw(spriteBatch,
cam,
Prefab.Sprite,
Prefab.DeformableSprite,
CurrentSpriteDeformation,
Color.Lerp(Color.White, Level.Loaded.BackgroundColor, depth / Math.Max(MaxDepth, Prefab.MaxDepth)) * alpha);
Color color =
Prefab.FadeOut ?
Color.Lerp(Color.White, Level.Loaded.BackgroundColor, Depth / Prefab.FadeOutDepth) * alpha :
Color.White * alpha;
Draw(spriteBatch,
cam,
Prefab.Sprite,
Prefab.DeformableSprite,
CurrentSpriteDeformation,
color);
}
private void Draw(SpriteBatch spriteBatch, Camera cam, Sprite sprite, DeformableSprite deformableSprite, Vector2[,] currentSpriteDeformation, Color color)
@@ -255,7 +287,7 @@ namespace Barotrauma
if (!Prefab.DisableRotation)
{
rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
if (velocity.X < 0.0f) { rotation -= MathHelper.Pi; }
if (flippedHorizontally) { rotation -= MathHelper.Pi; }
}
drawPosition = GetDrawPosition(cam);
@@ -266,8 +298,8 @@ namespace Barotrauma
color,
rotation,
scale,
Prefab.DisableFlipping || velocity.X > 0.0f ? SpriteEffects.None : SpriteEffects.FlipHorizontally,
Math.Min(depth / MaxDepth, 1.0f));
flippedHorizontally ? SpriteEffects.FlipHorizontally : SpriteEffects.None,
Math.Min(Depth / MaxDepth, 1.0f));
if (deformableSprite != null)
{
@@ -280,29 +312,29 @@ namespace Barotrauma
deformableSprite.Reset();
}
deformableSprite?.Draw(cam,
new Vector3(drawPosition.X, drawPosition.Y, Math.Min(depth / 10000.0f, 1.0f)),
new Vector3(drawPosition.X, drawPosition.Y, Math.Min(Depth / 10000.0f, 1.0f)),
deformableSprite.Origin,
rotation,
Vector2.One * scale,
color,
mirror: Prefab.DisableFlipping || velocity.X <= 0.0f);
mirror: flippedHorizontally);
}
}
public Vector2 GetDrawPosition(Camera cam)
{
Vector2 drawPosition = WorldPosition;
if (depth >= 0)
if (Depth >= 0)
{
Vector2 camOffset = drawPosition - cam.WorldViewCenter;
drawPosition -= camOffset * depth / MaxDepth;
drawPosition -= camOffset * Depth / MaxDepth;
}
return drawPosition;
}
public float GetScale()
{
return Math.Max(1.0f - depth / MaxDepth, 0.05f) * Prefab.Scale;
return Math.Max(1.0f - Depth / MaxDepth, 0.05f) * Prefab.Scale;
}
public Rectangle GetExtents(Camera cam)
@@ -328,7 +360,7 @@ namespace Barotrauma
}
}
private void UpdateDeformations(float deltaTime)
private void UpdateDeformations(float deltaTime, bool flippedHorizontally)
{
foreach (SpriteDeformation deformation in uniqueSpriteDeformations)
{
@@ -336,11 +368,13 @@ namespace Barotrauma
}
if (spriteDeformations.Count > 0)
{
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, Prefab.DeformableSprite.Size);
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, Prefab.DeformableSprite.Size,
flippedHorizontally: flippedHorizontally);
}
if (lightSpriteDeformations.Count > 0)
{
CurrentLightSpriteDeformation = SpriteDeformation.GetDeformation(lightSpriteDeformations, Prefab.DeformableLightSprite.Size);
CurrentLightSpriteDeformation = SpriteDeformation.GetDeformation(lightSpriteDeformations, Prefab.DeformableLightSprite.Size,
flippedHorizontally: flippedHorizontally);
}
}
}

View File

@@ -15,18 +15,19 @@ namespace Barotrauma
private float checkVisibleTimer;
private readonly List<BackgroundCreaturePrefab> prefabs = new List<BackgroundCreaturePrefab>();
private readonly List<BackgroundCreature> creatures = new List<BackgroundCreature>();
public BackgroundCreatureManager(IEnumerable<BackgroundCreaturePrefabsFile> files)
private readonly List<BackgroundCreature> visibleCreatures = new List<BackgroundCreature>();
public BackgroundCreatureManager()
{
foreach(var file in files)
/*foreach(var file in files)
{
LoadConfig(file.Path);
}
}*/
}
public BackgroundCreatureManager(string path)
/*public BackgroundCreatureManager(string path)
{
DebugConsole.AddWarning($"Couldn't find any BackgroundCreaturePrefabs files, falling back to {path}");
LoadConfig(ContentPath.FromRaw(null, path));
@@ -42,35 +43,34 @@ namespace Barotrauma
if (mainElement.IsOverride())
{
mainElement = mainElement.FirstElement();
prefabs.Clear();
Prefabs.Clear();
DebugConsole.NewMessage($"Overriding all background creatures with '{configPath}'", Color.MediumPurple);
}
else if (prefabs.Any())
else if (Prefabs.Any())
{
DebugConsole.NewMessage($"Loading additional background creatures from file '{configPath}'");
}
foreach (var element in mainElement.Elements())
{
prefabs.Add(new BackgroundCreaturePrefab(element));
Prefabs.Add(new BackgroundCreaturePrefab(element));
};
}
catch (Exception e)
{
DebugConsole.ThrowError(String.Format("Failed to load BackgroundCreatures from {0}", configPath), e);
}
}
}*/
public void SpawnCreatures(Level level, int count, Vector2? position = null)
{
creatures.Clear();
if (prefabs.Count == 0) { return; }
List<BackgroundCreaturePrefab> availablePrefabs = new List<BackgroundCreaturePrefab>(BackgroundCreaturePrefab.Prefabs.OrderBy(p => p.Identifier.Value));
if (availablePrefabs.Count == 0) { return; }
count = Math.Min(count, MaxCreatures);
List<BackgroundCreaturePrefab> availablePrefabs = new List<BackgroundCreaturePrefab>(prefabs);
for (int i = 0; i < count; i++)
{
Vector2 pos = Vector2.Zero;
@@ -93,7 +93,7 @@ namespace Barotrauma
pos = (Vector2)position;
}
var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level.GenerationParams)).ToList(), Rand.RandSync.ClientOnly);
var prefab = ToolBox.SelectWeightedRandom(availablePrefabs, availablePrefabs.Select(p => p.GetCommonness(level?.LevelData)).ToList(), Rand.RandSync.ClientOnly);
if (prefab == null) { break; }
int amount = Rand.Range(prefab.SwarmMin, prefab.SwarmMax + 1, Rand.RandSync.ClientOnly);
@@ -125,16 +125,27 @@ namespace Barotrauma
{
if (checkVisibleTimer < 0.0f)
{
visibleCreatures.Clear();
int margin = 500;
foreach (BackgroundCreature creature in creatures)
{
Rectangle extents = creature.GetExtents(cam);
bool wasVisible = creature.Visible;
creature.Visible =
extents.Right >= cam.WorldView.X - margin &&
extents.X <= cam.WorldView.Right + margin &&
extents.Bottom >= cam.WorldView.Y - cam.WorldView.Height - margin &&
extents.Y <= cam.WorldView.Y + margin;
if (creature.Visible)
{
//insertion sort according to depth
int i = 0;
while (i < visibleCreatures.Count)
{
if (visibleCreatures[i].Depth < creature.Depth) { break; }
i++;
}
visibleCreatures.Insert(i, creature);
}
}
checkVisibleTimer = VisibilityCheckInterval;
@@ -144,27 +155,24 @@ namespace Barotrauma
checkVisibleTimer -= deltaTime;
}
foreach (BackgroundCreature creature in creatures)
foreach (BackgroundCreature creature in visibleCreatures)
{
if (!creature.Visible) { continue; }
creature.Update(deltaTime);
}
}
public void Draw(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in creatures)
foreach (BackgroundCreature creature in visibleCreatures)
{
if (!creature.Visible) { continue; }
creature.Draw(spriteBatch, cam);
}
}
public void DrawLights(SpriteBatch spriteBatch, Camera cam)
{
foreach (BackgroundCreature creature in creatures)
foreach (BackgroundCreature creature in visibleCreatures)
{
if (!creature.Visible) { continue; }
creature.DrawLightSprite(spriteBatch, cam);
}
}

View File

@@ -1,65 +1,91 @@
using System.Collections.Generic;
using Barotrauma.SpriteDeformations;
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma
{
class BackgroundCreaturePrefab
class BackgroundCreaturePrefab : Prefab, ISerializableEntity
{
public readonly Sprite Sprite, LightSprite;
public readonly DeformableSprite DeformableSprite, DeformableLightSprite;
public readonly static PrefabCollection<BackgroundCreaturePrefab> Prefabs = new PrefabCollection<BackgroundCreaturePrefab>();
public readonly string Name;
public Sprite Sprite { get; private set; }
public Sprite LightSprite { get; private set; }
public DeformableSprite DeformableSprite { get; private set; }
public DeformableSprite DeformableLightSprite { get; private set; }
private readonly string name;
public readonly XElement Config;
[Serialize(1.0f, IsPropertySaveable.Yes)]
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float Speed { get; private set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f, DecimalCount = 3)]
public float WanderAmount { get; private set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f, DecimalCount = 3)]
public float WanderZAmount { get; private set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int SwarmMin { get; private set; }
[Serialize(1, IsPropertySaveable.Yes)]
[Serialize(1, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int SwarmMax { get; private set; }
[Serialize(200.0f, IsPropertySaveable.Yes)]
[Serialize(200.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float SwarmRadius { get; private set; }
[Serialize(0.2f, IsPropertySaveable.Yes)]
[Serialize(0.2f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10.0f)]
public float SwarmCohesion { get; private set; }
[Serialize(10.0f, IsPropertySaveable.Yes)]
[Serialize(10.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float MinDepth { get; private set; }
[Serialize(1000.0f, IsPropertySaveable.Yes)]
[Serialize(1000.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 10000.0f)]
public float MaxDepth { get; private set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(10000.0f, IsPropertySaveable.Yes, description: "Creatures fade out to the background color of the level the further they are from the camera. This value is the depth at which the object becomes \"maximally\" faded out."), Editable]
public float FadeOutDepth
{
get;
private set;
}
[Serialize(true, IsPropertySaveable.Yes), Editable]
public bool FadeOut { get; private set; }
[Serialize(false, IsPropertySaveable.Yes), Editable]
public bool DisableRotation { get; private set; }
[Serialize(false, IsPropertySaveable.Yes)]
[Serialize(false, IsPropertySaveable.Yes), Editable]
public bool DisableFlipping { get; private set; }
[Serialize(1.0f, IsPropertySaveable.Yes)]
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
public float Scale { get; private set; }
[Serialize(1.0f, IsPropertySaveable.Yes)]
[Serialize(1.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 100.0f)]
public float Commonness { get; private set; }
[Serialize(1000, IsPropertySaveable.Yes)]
[Serialize(1000, IsPropertySaveable.Yes), Editable(MinValueInt = 0, MaxValueInt = 1000)]
public int MaxCount { get; private set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float FlashInterval { get; private set; }
[Serialize(0.0f, IsPropertySaveable.Yes)]
[Serialize(0.0f, IsPropertySaveable.Yes), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float FlashDuration { get; private set; }
public string Name => name;
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
/// <summary>
/// Only used for editing sprite deformation parameters. The actual LevelObjects use separate SpriteDeformation instances.
/// </summary>
public List<SpriteDeformation> SpriteDeformations
{
get;
private set;
} = new List<SpriteDeformation>();
/// <summary>
/// Overrides the commonness of the object in a specific level type.
@@ -67,13 +93,13 @@ namespace Barotrauma
/// </summary>
public Dictionary<Identifier, float> OverrideCommonness = new Dictionary<Identifier, float>();
public BackgroundCreaturePrefab(ContentXElement element)
public BackgroundCreaturePrefab(ContentXElement element, BackgroundCreaturePrefabsFile file) : base(file, ParseIdentifier(element))
{
Name = element.Name.ToString();
name = element.Name.ToString();
Config = element;
SerializableProperty.DeserializeProperties(this, element);
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
foreach (var subElement in element.Elements())
{
@@ -84,6 +110,14 @@ namespace Barotrauma
break;
case "deformablesprite":
DeformableSprite = new DeformableSprite(subElement, lazyLoad: true);
foreach (XElement deformElement in subElement.Elements())
{
var deformation = SpriteDeformation.Load(deformElement, Name);
if (deformation != null)
{
SpriteDeformations.Add(deformation);
}
}
break;
case "lightsprite":
LightSprite = new Sprite(subElement, lazyLoad: true);
@@ -102,17 +136,42 @@ namespace Barotrauma
}
}
public float GetCommonness(LevelGenerationParams generationParams)
public static Identifier ParseIdentifier(XElement element)
{
if (generationParams != null &&
!generationParams.Identifier.IsEmpty &&
(OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) ||
(!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness))))
Identifier identifier = element.GetAttributeIdentifier("identifier", "");
if (identifier.IsEmpty)
{
identifier = element.NameAsIdentifier();
}
return identifier;
}
public float GetCommonness(LevelData levelData)
{
if (levelData?.GenerationParams is not { } generationParams || generationParams.Identifier.IsEmpty)
{
return Commonness;
}
if (OverrideCommonness.TryGetValue(generationParams.Identifier, out float commonness) || (!generationParams.OldIdentifier.IsEmpty && OverrideCommonness.TryGetValue(generationParams.OldIdentifier, out commonness)) ||
OverrideCommonness.TryGetValue(levelData.Biome.Identifier, out commonness))
{
return commonness;
}
return Commonness;
}
public override void Dispose()
{
Sprite?.Remove();
Sprite = null;
LightSprite?.Remove();
LightSprite = null;
DeformableLightSprite?.Remove();
DeformableLightSprite = null;
DeformableSprite?.Remove();
DeformableSprite = null;
}
}
}

View File

@@ -167,7 +167,7 @@ namespace Barotrauma
Prefab.OverrideProperties.Any(p => p != null && (p.Sprites.Any() || p.DeformableSprite != null));
}
public void Update(float deltaTime)
public void Update(float deltaTime, Camera cam)
{
CurrentRotation = Rotation;
if (ActivePrefab.SwingFrequency > 0.0f)
@@ -190,20 +190,24 @@ namespace Barotrauma
ScaleOscillateTimer += deltaTime * ActivePrefab.ScaleOscillationFrequency;
ScaleOscillateTimer = ScaleOscillateTimer % MathHelper.TwoPi;
CurrentScaleOscillation = Vector2.Lerp(CurrentScaleOscillation, ActivePrefab.ScaleOscillation, deltaTime * 10.0f);
float sin = (float)Math.Sin(ScaleOscillateTimer);
CurrentScale *= new Vector2(
1.0f + sin * CurrentScaleOscillation.X,
1.0f + sin * CurrentScaleOscillation.Y);
1.0f + sin * CurrentScaleOscillation.Y);
}
if (LightSources != null)
{
Vector2 position2D = new Vector2(Position.X, Position.Y);
Vector2 camDiff = position2D - cam.WorldViewCenter;
for (int i = 0; i < LightSources.Length; i++)
{
if (LightSourceTriggers[i] != null) LightSources[i].Enabled = LightSourceTriggers[i].IsTriggered;
if (LightSourceTriggers[i] != null) { LightSources[i].Enabled = LightSourceTriggers[i].IsTriggered; }
LightSources[i].Rotation = -CurrentRotation;
LightSources[i].SpriteScale = CurrentScale;
LightSources[i].Position =
position2D - camDiff * Position.Z * LevelObjectManager.ParallaxStrength;
}
}
@@ -237,7 +241,10 @@ namespace Barotrauma
{
SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, roundSound.GetRandomFrequencyMultiplier(), soundPos);
}
SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
if (SoundChannels[i] != null)
{
SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
}
}
}
else if (SoundChannels[i] != null && SoundChannels[i].IsPlaying)
@@ -259,7 +266,7 @@ namespace Barotrauma
}
deformation.Update(deltaTime);
}
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, ActivePrefab.DeformableSprite.Size);
CurrentSpriteDeformation = SpriteDeformation.GetDeformation(spriteDeformations, ActivePrefab.DeformableSprite.Size, flippedHorizontally: false);
if (LightSources != null)
{
foreach (LightSource lightSource in LightSources)

View File

@@ -33,19 +33,19 @@ namespace Barotrauma
visibleObjectsFront.Clear();
}
partial void UpdateProjSpecific(float deltaTime)
partial void UpdateProjSpecific(float deltaTime, Camera cam)
{
foreach (LevelObject obj in visibleObjectsBack)
{
obj.Update(deltaTime);
obj.Update(deltaTime, cam);
}
foreach (LevelObject obj in visibleObjectsMid)
{
obj.Update(deltaTime);
obj.Update(deltaTime, cam);
}
foreach (LevelObject obj in visibleObjectsFront)
{
obj.Update(deltaTime);
obj.Update(deltaTime, cam);
}
}
@@ -225,7 +225,7 @@ namespace Barotrauma
activeSprite?.Draw(
spriteBatch,
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z * ParallaxStrength,
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 3000.0f),
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / obj.Prefab.FadeOutDepth),
activeSprite.Origin,
obj.CurrentRotation,
obj.CurrentScale,

View File

@@ -196,10 +196,7 @@ namespace Barotrauma
if (flashCooldown <= 0.0f)
{
flashTimer = 1.0f;
if (level.GenerationParams.FlashSound != null)
{
level.GenerationParams.FlashSound.Play(1.0f, "default");
}
level.GenerationParams.FlashSound?.Play(1.0f, Sounds.SoundManager.SoundCategoryDefault);
flashCooldown = Rand.Range(level.GenerationParams.FlashInterval.X, level.GenerationParams.FlashInterval.Y, Rand.RandSync.Unsynced);
}
if (flashTimer > 0.0f)

View File

@@ -274,7 +274,7 @@ namespace Barotrauma.Lights
{
light.ParentBody.UpdateDrawPosition();
Vector2 pos = light.ParentBody.DrawPosition;
Vector2 pos = light.ParentBody.DrawPosition + light.OffsetFromBody;
if (light.ParentSub != null) { pos -= light.ParentSub.DrawPosition; }
light.Position = pos;
}

View File

@@ -450,6 +450,8 @@ namespace Barotrauma.Lights
set;
}
public Vector2 OffsetFromBody;
public DeformableSprite DeformableLightSprite
{
get;
@@ -561,9 +563,9 @@ namespace Barotrauma.Lights
// center is in the opposite direction from the ray (cheapest check first)
if (Vector2.Dot(ray, convexHull.BoundingBox.Center.ToVector2() - lightPos) <= 0 &&
/*ray doesn't hit the convex hull*/
!MathUtils.GetLineRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
!MathUtils.GetLineWorldRectangleIntersection(lightPos, lightPos + ray, bounds, out _) &&
/*normal vectors of the ray don't hit the convex hull */
!MathUtils.GetLineRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
!MathUtils.GetLineWorldRectangleIntersection(lightPos + normal, lightPos - normal, bounds, out _))
{
continue;
}

View File

@@ -378,17 +378,24 @@ namespace Barotrauma
bool showReputation = hudVisibility > 0.0f && location.Type.HasOutpost && location.Reputation != null;
LocationType locationTypeToDisplay = location.GetLocationTypeToDisplay(out Identifier overrideDescriptionIdentifier);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.DisplayName, font: GUIStyle.LargeFont) { Padding = Vector4.Zero };
if (!location.Type.Name.IsNullOrEmpty())
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), locationTypeToDisplay.Name, font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
}
CreateSpacing(10);
if (!location.Type.Description.IsNullOrEmpty())
var description = locationTypeToDisplay.Description;
if (!overrideDescriptionIdentifier.IsEmpty)
{
CreateTextWithIcon(location.Type.Description, location.Type.Sprite);
description = TextManager.Get(overrideDescriptionIdentifier);
}
if (!description.IsNullOrEmpty())
{
CreateTextWithIcon(description, locationTypeToDisplay.Sprite);
}
int highestSubTier = location.HighestSubmarineTierAvailable();
@@ -699,6 +706,7 @@ namespace Barotrauma
CurrentLocation.CreateStores();
ProgressWorld(campaign);
Radiation?.OnStep(1);
mapAnimQueue.Clear();
}
else
{
@@ -828,7 +836,7 @@ namespace Barotrauma
if (!rect.Intersects(drawRect)) { continue; }
Color color = location.Type.SpriteColor;
Color color = location.OverrideIconColor ?? location.Type.SpriteColor;
if (!location.Visited) { color = Color.White; }
if (location.Connections.Find(c => c.Locations.Contains(currentDisplayLocation)) == null)
{
@@ -850,6 +858,27 @@ namespace Barotrauma
iconScale *= notificationPulseAmount;
}
#if DEBUG
if (generationParams.ShowStoreInfo)
{
if (location.Stores == null || location.Stores.None())
{
color = Color.DarkBlue;
}
//stores created, but nothing in stock
else if (location.Stores.Values.None(s => s.Stock.Any()))
{
color = Color.Yellow;
}
else
{
color = Color.Green;
}
GUI.DrawString(spriteBatch, pos + Vector2.One * 20, "Time since visited: " +location.WorldStepsSinceVisited, Color.Yellow);
}
#endif
locationSprite.Draw(spriteBatch, pos, color,
scale: generationParams.LocationIconSize / locationSprite.size.X * iconScale * zoom);
@@ -948,8 +977,7 @@ namespace Barotrauma
DrawDecorativeHUD(spriteBatch, rect);
bool drawRadiationTooltip = true;
bool drawRadiationTooltip = HighlightedLocation == null;
if (tooltip != null)
{
GUIComponent.DrawToolTip(spriteBatch, tooltip.Value.tip, tooltip.Value.targetArea);
@@ -1058,7 +1086,7 @@ namespace Barotrauma
}
else
{
if (MathUtils.GetLineRectangleIntersection(start, end, new Rectangle(viewArea.X, viewArea.Y + viewArea.Height, viewArea.Width, viewArea.Height), out Vector2 intersection))
if (MathUtils.GetLineWorldRectangleIntersection(start, end, new Rectangle(viewArea.X, viewArea.Y + viewArea.Height, viewArea.Width, viewArea.Height), out Vector2 intersection))
{
if (!viewArea.Contains(start))
{

View File

@@ -7,18 +7,18 @@ namespace Barotrauma
{
internal partial class Radiation
{
private static readonly LocalizedString radiationTooltip = TextManager.Get("RadiationTooltip");
private int? radiationMultiplier;
private static float spriteIndex;
private readonly SpriteSheet? sheet = GUIStyle.RadiationAnimSpriteSheet;
private int maxFrames => (sheet?.FrameCount ?? 0) + 1;
private readonly SpriteSheet? radiationEdgeAnimSheet = GUIStyle.RadiationAnimSpriteSheet;
private int maxFrames => (radiationEdgeAnimSheet?.FrameCount ?? 0) + 1;
private bool isHovingOver;
private bool isHoveringOver;
public void Draw(SpriteBatch spriteBatch, Rectangle container, float zoom)
{
if (!Enabled) { return; }
UISprite? uiSprite = GUIStyle.Radiation;
UISprite? radiationMainSprite = GUIStyle.Radiation;
var (offsetX, offsetY) = Map.DrawOffset * zoom;
var (centerX, centerY) = container.Center.ToVector2();
var (halfSizeX, halfSizeY) = new Vector2(container.Width / 2f, container.Height / 2f) * zoom;
@@ -29,31 +29,41 @@ namespace Barotrauma
Vector2 spriteScale = new Vector2(zoom);
uiSprite?.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
radiationMainSprite?.Sprite.DrawTiled(spriteBatch, topLeft, size, color: Params.RadiationAreaColor, startOffset: Vector2.Zero, textureScale: spriteScale);
Vector2 topRight = topLeft + Vector2.UnitX * size.X;
int index = 0;
if (sheet != null)
if (radiationEdgeAnimSheet != null)
{
for (float i = 0; i <= size.Y; i += sheet.FrameSize.Y / 2f * zoom)
for (float i = 0; i <= size.Y; i += radiationEdgeAnimSheet.FrameSize.Y / 2f * zoom)
{
bool isEven = ++index % 2 == 0;
Vector2 origin = new Vector2(0.5f, 0) * sheet.FrameSize.X;
Vector2 origin = new Vector2(0.5f, 0) * radiationEdgeAnimSheet.FrameSize.X;
// every other sprite's animation is reversed to make it seem more chaotic
int sprite = (int) MathF.Floor(isEven ? spriteIndex : maxFrames - spriteIndex);
sheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
radiationEdgeAnimSheet.Draw(spriteBatch, sprite, topRight + new Vector2(0, i), Params.RadiationBorderTint, origin, 0f, spriteScale);
}
}
isHovingOver = container.Contains(PlayerInput.MousePosition) && PlayerInput.MousePosition.X < topLeft.X + size.X;
radiationMultiplier = null;
if (container.Contains(PlayerInput.MousePosition))
{
float rightEdge = topLeft.X + size.X;
float distanceFromRight = rightEdge - PlayerInput.MousePosition.X;
if (distanceFromRight >= 0)
{
radiationMultiplier = Math.Min(4, (int)(distanceFromRight / (Params.RadiationEffectMultipliedPerPixelDistance * zoom)) + 1);
}
}
}
public void DrawFront(SpriteBatch spriteBatch)
{
if (isHovingOver)
if (radiationMultiplier is int multiplier)
{
GUIComponent.DrawToolTip(spriteBatch, radiationTooltip, PlayerInput.MousePosition + new Vector2(18 * GUI.Scale));
var tooltip = TextManager.GetWithVariable("RadiationTooltip", "[jovianmultiplier]", multiplier.ToString());
GUIComponent.DrawToolTip(spriteBatch, tooltip, PlayerInput.MousePosition + new Vector2(18 * GUI.Scale));
}
}

View File

@@ -512,11 +512,8 @@ namespace Barotrauma
damageEffect.Parameters["aCutoff"].SetValue(newCutoff);
damageEffect.Parameters["cCutoff"].SetValue(newCutoff * 1.2f);
damageEffect.CurrentTechnique.Passes[0].Apply();
Submarine.DamageEffectCutoff = newCutoff;
Submarine.DamageEffectColor = color;
}
}
if (!HasDamage && i == 0)

View File

@@ -89,9 +89,13 @@ namespace Barotrauma
newRect = Submarine.AbsRect(placePosition, placeSize);
}
Sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), textureScale: TextureScale * Scale);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - GameMain.GraphicsWidth, -newRect.Y, newRect.Width + GameMain.GraphicsWidth * 2, newRect.Height), Color.White);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - GameMain.GraphicsHeight, newRect.Width, newRect.Height + GameMain.GraphicsHeight * 2), Color.White);
Sprite.DrawTiled(spriteBatch, new Vector2(newRect.X, -newRect.Y), new Vector2(newRect.Width, newRect.Height), textureScale: TextureScale * Scale, color: SpriteColor);
float thickness = Math.Max(1.0f / cam.Zoom, 1.0f);
int zoomInvariantWidth = (int)(GameMain.GraphicsWidth / cam.Zoom);
int zoomInvariantHeight = (int)(GameMain.GraphicsHeight / cam.Zoom);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X - zoomInvariantWidth, -newRect.Y, newRect.Width + zoomInvariantWidth * 2, newRect.Height), Color.White, thickness: thickness);
GUI.DrawRectangle(spriteBatch, new Rectangle(newRect.X, -newRect.Y - zoomInvariantHeight, newRect.Width, newRect.Height + zoomInvariantHeight * 2), Color.White, thickness: thickness);
}
public override void DrawPlacing(SpriteBatch spriteBatch, Rectangle placeRect, float scale = 1.0f, float rotation = 0.0f, SpriteEffects spriteEffects = SpriteEffects.None)
@@ -103,7 +107,7 @@ namespace Barotrauma
spriteBatch,
position,
placeRect.Size.ToVector2(),
color: Color.White * 0.8f,
color: SpriteColor * 0.8f,
origin: placeRect.Size.ToVector2() * 0.5f,
rotation: rotation,
textureScale: TextureScale * scale,

View File

@@ -159,7 +159,6 @@ namespace Barotrauma
}
public static float DamageEffectCutoff;
public static Color DamageEffectColor;
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
{
@@ -192,13 +191,6 @@ namespace Barotrauma
}
}
if (damageEffect != null)
{
damageEffect.Parameters["aCutoff"].SetValue(0.0f);
damageEffect.Parameters["cCutoff"].SetValue(0.0f);
DamageEffectCutoff = 0.0f;
}
}
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)

View File

@@ -120,7 +120,7 @@ namespace Barotrauma.Networking
if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying)
{
radioNoiseChannel = SoundPlayer.PlaySound("radiostatic");
radioNoiseChannel.Category = "voip";
radioNoiseChannel.Category = SoundManager.SoundCategoryVoip;
radioNoiseChannel.Looping = true;
}
radioNoiseChannel.Near = VoipSound.Near;

View File

@@ -50,25 +50,29 @@ readonly record struct ConnectCommand(
NameAndLidgrenEndpointOption: endpoint is LidgrenEndpoint lidgrenEndpoint
? Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, lidgrenEndpoint))
: Option.None,
SteamLobbyIdOption: Option.None) { }
SteamLobbyIdOption: Option.None)
{ }
public ConnectCommand(string serverName, ImmutableArray<P2PEndpoint> endpoints)
: this(
NameAndP2PEndpointsOption: Option.Some(new NameAndP2PEndpoints(ServerName: serverName, Endpoints: endpoints)),
NameAndLidgrenEndpointOption: Option.None,
SteamLobbyIdOption: Option.None) { }
SteamLobbyIdOption: Option.None)
{ }
public ConnectCommand(string serverName, LidgrenEndpoint endpoint)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.Some(new NameAndLidgrenEndpoint(ServerName: serverName, Endpoint: endpoint)),
SteamLobbyIdOption: Option.None) { }
SteamLobbyIdOption: Option.None)
{ }
public ConnectCommand(SteamLobbyId lobbyId)
: this(
NameAndP2PEndpointsOption: Option.None,
NameAndLidgrenEndpointOption: Option.None,
SteamLobbyIdOption: Option.Some(lobbyId)) { }
SteamLobbyIdOption: Option.Some(lobbyId))
{ }
public static Option<ConnectCommand> Parse(string str)
=> Parse(ToolBox.SplitCommand(str));

View File

@@ -40,12 +40,12 @@ namespace Barotrauma.Networking
{
if (string.IsNullOrEmpty(value)) { return; }
Name = value;
nameId++;
ForceNameJobTeamUpdate();
}
public void ForceNameJobTeamUpdate()
{
// Triggers SendLobbyUpdate() which causes the server to call GameServer.ClientReadLobby()
// Deviously triggers SendLobbyUpdate() which causes the server to call GameServer.ClientReadLobby()
nameId++;
}
@@ -584,6 +584,9 @@ namespace Barotrauma.Networking
private readonly List<IReadMessage> pendingIncomingMessages = new List<IReadMessage>();
private readonly List<IReadMessage> incomingMessagesToProcess = new List<IReadMessage>();
private CoroutineHandle startGameCoroutine;
private bool requestNewRoundStart;
private void ReadDataMessage(IReadMessage inc)
{
ServerPacketHeader header = (ServerPacketHeader)inc.ReadByte();
@@ -718,9 +721,6 @@ namespace Barotrauma.Networking
campaignUpdateIDs[flag] = inc.ReadUInt16();
}
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
if (campaign != null) { campaign.PendingSubmarineSwitch = null; }
GameMain.NetLobbyScreen.UsingShuttle = usingShuttle;
bool readyToStart;
@@ -742,13 +742,9 @@ namespace Barotrauma.Networking
campaign.LastSaveID == campaignSaveID &&
campaignUpdateIDs.All(kvp => campaign.GetLastUpdateIdForFlag(kvp.Key) == kvp.Value);
}
readyToStartMsg.WriteBoolean(readyToStart);
DebugConsole.Log(readyToStart ? "Ready to start." : "Not ready to start.");
WriteCharacterInfo(readyToStartMsg);
ClientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable);
SendStartGameResponse(readyToStart: readyToStart);
if (readyToStart && !CoroutineManager.IsCoroutineRunning("WaitForStartRound"))
{
@@ -775,15 +771,34 @@ namespace Barotrauma.Networking
break;
case ServerPacketHeader.STARTGAME:
DebugConsole.Log("Received STARTGAME packet.");
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
if (GameMain.NetLobbyScreen is not { AFKSelected: true } || !ServerSettings.AllowAFK)
{
//start without a loading screen if playing a campaign round
CoroutineManager.StartCoroutine(StartGame(inc));
if (startGameCoroutine != null && CoroutineManager.IsCoroutineRunning(startGameCoroutine))
{
DebugConsole.Log("New round started before the previous one had finished loading. Starting a new round once loading the round finishes...");
requestNewRoundStart = true;
}
else
{
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession?.GameMode is CampaignMode)
{
//start without a loading screen if playing a campaign round
DebugConsole.Log($"Starting {nameof(StartGame)} coroutine...");
startGameCoroutine = CoroutineManager.StartCoroutine(StartGame(inc));
}
else
{
GUIMessageBox.CloseAll();
DebugConsole.Log($"Starting {nameof(StartGame)} coroutine with a loading screen...");
startGameCoroutine = GameMain.Instance.ShowLoading(StartGame(inc), false);
}
}
}
else
{
GUIMessageBox.CloseAll();
GameMain.Instance.ShowLoading(StartGame(inc), false);
//reselect to refresh the state of the screen (to indicate the round is running)
GameStarted = true;
GameMain.NetLobbyScreen?.Select();
}
break;
case ServerPacketHeader.STARTGAMEFINALIZE:
@@ -930,10 +945,19 @@ namespace Barotrauma.Networking
contentToPreload.AddIfNotNull(file);
}
byte roundId = inc.ReadByte();
string campaignErrorInfo = string.Empty;
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
{
if (roundId != campaign.RoundID)
{
DebugConsole.AddWarning($"Received a StartGameFinalize message for an incorrect round (client: {campaign.RoundID}, server: {roundId}). The server might have started a new round before the client finished loading the previous one.");
requestNewRoundStart = true;
return;
}
campaignErrorInfo = $" Round start save ID: {debugStartGameCampaignSaveID}, last save id: {campaign.LastSaveID}, pending save id: {campaign.PendingSaveID}.";
}
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
@@ -1409,6 +1433,8 @@ namespace Barotrauma.Networking
private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc)
{
DebugConsole.Log($"Running {nameof(StartGame)} coroutine");
Character?.Remove();
Character = null;
HasSpawned = false;
@@ -1444,6 +1470,7 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!");
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1461,7 +1488,11 @@ namespace Barotrauma.Networking
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameMain.LightManager.LightingEnabled = true;
#if DEBUG
GameMain.LightManager.LightingEnabled = !GameMain.DevMode;
#endif
ServerSettings.ReadMonsterEnabled(inc);
@@ -1500,6 +1531,7 @@ namespace Barotrauma.Networking
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, SelectedSubType.Sub, GameMain.NetLobbyScreen.SubList))
{
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
@@ -1515,6 +1547,7 @@ namespace Barotrauma.Networking
if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, SelectedSubType.Shuttle, GameMain.NetLobbyScreen.ShuttleList.ListBox))
{
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
@@ -1544,6 +1577,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
if (GameMain.NetLobbyScreen.SelectedShuttle == null ||
@@ -1556,6 +1590,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1568,14 +1603,15 @@ namespace Barotrauma.Networking
}
else
{
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign))
if (GameMain.GameSession?.GameMode is not MultiPlayerCampaign campaign)
{
throw new InvalidOperationException("Attempted to start a campaign round when a campaign was not active.");
}
if (GameMain.GameSession?.CrewManager != null) { GameMain.GameSession.CrewManager.Reset(); }
GameMain.GameSession?.CrewManager?.Reset();
byte campaignID = inc.ReadByte();
byte roundID = inc.ReadByte();
UInt16 campaignSaveID = inc.ReadUInt16();
int nextLocationIndex = inc.ReadInt32();
int nextConnectionIndex = inc.ReadInt32();
@@ -1588,6 +1624,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to start campaign round (campaign ID does not match).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1604,6 +1641,7 @@ namespace Barotrauma.Networking
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("campaignsavetransfer.timeout"));
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
//use success status, even though this is a failure (no need to show a console error because we show it in the message box)
yield return CoroutineStatus.Success;
}
@@ -1617,6 +1655,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1630,12 +1669,18 @@ namespace Barotrauma.Networking
if (roundSummary != null)
{
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, () =>
{
DebugConsole.Log($"Set round ID from {campaign.RoundID} to {roundID}.");
campaign.RoundID = roundID;
});
roundSummary.ContinueButton.Visible = false;
}
else
{
GameMain.GameSession.StartRound(levelData, mirrorLevel, startOutpost: campaign?.GetPredefinedStartOutpost());
DebugConsole.Log($"Set round ID from {campaign.RoundID} to {roundID}.");
campaign.RoundID = roundID;
}
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
}
@@ -1654,9 +1699,16 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError("There was an error initializing the round (disconnected during the StartGame coroutine.)");
roundInitStatus = RoundInitStatus.Error;
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
if (requestNewRoundStart)
{
RequestNewRoundStart();
yield return CoroutineStatus.Success;
}
roundInitStatus = RoundInitStatus.WaitingForStartGameFinalize;
//wait for up to 30 seconds for the server to send the STARTGAMEFINALIZE message
@@ -1678,6 +1730,12 @@ namespace Barotrauma.Networking
{
while (true)
{
if (requestNewRoundStart)
{
RequestNewRoundStart();
yield return CoroutineStatus.Success;
}
try
{
if (DateTime.Now > requestFinalizeTime)
@@ -1742,10 +1800,12 @@ namespace Barotrauma.Networking
{
DebugConsole.ThrowError(roundInitStatus.ToString());
CoroutineManager.StartCoroutine(EndGame(""));
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
else
{
startGameCoroutine = null;
yield return CoroutineStatus.Success;
}
}
@@ -1754,6 +1814,7 @@ namespace Barotrauma.Networking
GameMain.GameSession.Submarine.Info.IsFileCorrupted)
{
DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Info.Name}\".");
startGameCoroutine = null;
yield return CoroutineStatus.Failure;
}
@@ -1801,6 +1862,17 @@ namespace Barotrauma.Networking
AddChatMessage(message, ChatMessageType.Server);
yield return CoroutineStatus.Success;
void RequestNewRoundStart()
{
GameMain.GameSession?.EndRound("");
GameMain.NetLobbyScreen.Select();
CoroutineManager.StopCoroutines("LevelTransition");
roundInitStatus = RoundInitStatus.Error;
startGameCoroutine = null;
SendJoinOngoingRequest(joinButton: null);
requestNewRoundStart = false;
}
}
public IEnumerable<CoroutineStatus> EndGame(string endMessage, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None, TraitorManager.TraitorResults? traitorResults = null)
@@ -1910,6 +1982,7 @@ namespace Barotrauma.Networking
GameStarted = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
bool allowAFK = inc.ReadBoolean();
bool permadeathMode = inc.ReadBoolean();
bool ironmanMode = inc.ReadBoolean();
@@ -1929,7 +2002,7 @@ namespace Barotrauma.Networking
message = TextManager.Get(allowSpectating ? "RoundRunningSpectateEnabled" : "RoundRunningSpectateDisabled");
}
new GUIMessageBox(TextManager.Get("PleaseWait"), message);
if (!(Screen.Selected is ModDownloadScreen)) { GameMain.NetLobbyScreen.Select(); }
if (Screen.Selected is not ModDownloadScreen) { GameMain.NetLobbyScreen.Select(); }
}
}
}
@@ -2002,8 +2075,7 @@ namespace Barotrauma.Networking
Name = tc.Name;
nameId = tc.NameId;
}
if (GameMain.NetLobbyScreen.CharacterNameBox != null &&
!GameMain.NetLobbyScreen.CharacterNameBox.Selected)
if (GameMain.NetLobbyScreen.CharacterNameBox is { Selected: false, Enabled: true })
{
GameMain.NetLobbyScreen.CharacterNameBox.Text = Name;
}
@@ -2117,6 +2189,7 @@ namespace Barotrauma.Networking
bool voiceChatEnabled = inc.ReadBoolean();
bool allowSpectating = inc.ReadBoolean();
bool allowAFK = inc.ReadBoolean();
float traitorProbability = inc.ReadSingle();
int traitorDangerLevel = inc.ReadRangedInteger(TraitorEventPrefab.MinDangerLevel, TraitorEventPrefab.MaxDangerLevel);
@@ -2194,6 +2267,7 @@ namespace Barotrauma.Networking
}
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
GameMain.NetLobbyScreen.SetAllowAFK(allowAFK);
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount);
@@ -2386,6 +2460,7 @@ namespace Barotrauma.Networking
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
outmsg.WriteUInt16(ChatMessage.LastID);
outmsg.WriteUInt16(LastClientListUpdateID);
outmsg.WriteBoolean(GameMain.NetLobbyScreen.AFKSelected);
outmsg.WriteUInt16(nameId);
outmsg.WriteString(Name);
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
@@ -2532,6 +2607,15 @@ namespace Barotrauma.Networking
msg.WriteUInt16(bot.ID);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void ToggleReserveBench(CharacterInfo bot, bool pendingHire = false)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.TOGGLE_RESERVE_BENCH);
msg.WriteUInt16(bot.ID);
msg.WriteBoolean(pendingHire);
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
public void RequestFile(FileTransferType fileType, string file, string fileHash)
{
@@ -2796,8 +2880,12 @@ namespace Barotrauma.Networking
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
{
msg.WriteBoolean(GameMain.NetLobbyScreen.Spectating);
msg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
bool writeInfo = characterInfo != null;
msg.WriteBoolean(writeInfo);
msg.WritePadBits();
if (characterInfo == null) { return; }
if (!writeInfo) { return; }
var head = characterInfo.Head;
@@ -3080,9 +3168,9 @@ namespace Barotrauma.Networking
}
/// <summary>
/// Tell the server to end the round (permission required)
/// Tell the server to end the round (permission required).
/// </summary>
public void RequestRoundEnd(bool save, bool quitCampaign = false)
public void RequestEndRound(bool save, bool quitCampaign = false)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
@@ -3094,7 +3182,31 @@ namespace Barotrauma.Networking
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
public bool JoinOnGoingClicked(GUIButton button, object _)
/// <summary>
/// End the round locally (just returning to the lobby without ending the round for everyone).
/// </summary>
public void EndRoundForSelf()
{
GameMain.GameSession?.EndRound(endMessage: string.Empty, createRoundSummary: false);
Submarine.Unload();
GameMain.NetLobbyScreen.Select();
Character.Controlled = null;
WaitForNextRoundRespawn = null;
RespawnManager = null;
EntityEventManager?.Clear();
LastSentEntityEventID = 0;
MyClient.CharacterID = Entity.NullEntityID;
roundInitStatus = RoundInitStatus.NotStarted;
IWriteMessage msg = new WriteOnlyMessage();
msg.WriteByte((byte)ClientPacketHeader.ENDROUND_SELF);
ClientPeer.Send(msg, DeliveryMethod.Reliable);
}
public bool SendJoinOngoingRequest(GUIButton joinButton)
{
MultiPlayerCampaign campaign =
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
@@ -3106,23 +3218,32 @@ namespace Barotrauma.Networking
new GUIMessageBox("", TextManager.Get("campaignfiletransferinprogress"));
return false;
}
if (button != null) { button.Enabled = false; }
if (joinButton != null) { joinButton.Enabled = false; }
if (campaign != null) { LateCampaignJoin = true; }
if (ClientPeer == null) { return false; }
//assume we have the required sub files to start the round
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
SendStartGameResponse(readyToStart: true);
return false;
}
private void SendStartGameResponse(bool readyToStart)
{
IWriteMessage readyToStartMsg = new WriteOnlyMessage();
readyToStartMsg.WriteByte((byte)ClientPacketHeader.RESPONSE_STARTGAME);
//assume we have the required sub files to start the round
//(if not, we'll find out when the server sends the STARTGAME message and can initiate a file transfer)
readyToStartMsg.WriteBoolean(true);
readyToStartMsg.WriteBoolean(readyToStart);
readyToStartMsg.WriteBoolean(GameMain.NetLobbyScreen.AFKSelected && ServerSettings.AllowAFK);
WriteCharacterInfo(readyToStartMsg);
ClientPeer.Send(readyToStartMsg, DeliveryMethod.Reliable);
return false;
}
public bool SetReadyToStart(GUITickBox tickBox)

View File

@@ -41,6 +41,9 @@ namespace Barotrauma.Networking
protected bool IsOwner => ownerKey.IsSome();
protected readonly Option<int> ownerKey;
/// <summary>
/// Has the ClientPeer been started? Set to true in <see cref="Start"/>, set to false when shutting the client down <see cref="Close(PeerDisconnectPacket)"/>.
/// </summary>
public bool IsActive => isActive;
protected bool isActive;
@@ -104,8 +107,11 @@ namespace Barotrauma.Networking
TaskPool.Add($"{GetType().Name}.{nameof(GetAccountId)}", GetAccountId(), t =>
{
// FIXME what to do with this?
//if (GameMain.Client?.ClientPeer is null) { return; }
if (!IsActive)
{
//client has become inactive (cancelled/disconnected while waiting for initialization)
return;
}
if (!t.TryGetResult(out Option<AccountId> accountId))
{

View File

@@ -63,6 +63,7 @@ namespace Barotrauma.Networking
var teamId = (CharacterTeamType)msg.ReadByte();
bool respawnPromptPending = false;
bool clientHasChosenNewBotViaShuttle = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
switch (newState)
{
@@ -70,18 +71,20 @@ namespace Barotrauma.Networking
teamSpecificState.ReturnCountdownStarted = msg.ReadBoolean();
maxTransportTime = msg.ReadSingle();
float transportTimeLeft = msg.ReadSingle();
teamSpecificState.ReturnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(transportTimeLeft * 1000.0f));
teamSpecificState.RespawnCountdownStarted = false;
SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Dynamic);
break;
case State.Waiting:
teamSpecificState.PendingRespawnCount = msg.ReadUInt16();
teamSpecificState.RequiredRespawnCount = msg.ReadUInt16();
respawnPromptPending = msg.ReadBoolean();
clientHasChosenNewBotViaShuttle = msg.ReadBoolean();
teamSpecificState.RespawnCountdownStarted = msg.ReadBoolean();
ResetShuttle(teamSpecificState);
float newRespawnTime = msg.ReadSingle();
teamSpecificState.RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
SetShuttleBodyType(teamSpecificState.TeamID, FarseerPhysics.BodyType.Static);
break;
case State.Returning:
teamSpecificState.RespawnCountdownStarted = false;
@@ -89,7 +92,7 @@ namespace Barotrauma.Networking
}
teamSpecificState.CurrentState = newState;
if (respawnPromptPending)
if (respawnPromptPending && !clientHasChosenNewBotViaShuttle)
{
GameMain.Client.HasSpawned = true;
DeathPrompt.Create(delay: 1.0f);

View File

@@ -180,7 +180,7 @@ namespace Barotrauma.Networking
{
float playStyleBannerAspectRatio = (float)playStyleBannerSprite.SourceRect.Width / (float)playStyleBannerSprite.SourceRect.Height;
playStyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 1.0f / playStyleBannerAspectRatio), frame.RectTransform, scaleBasis: ScaleBasis.BothWidth),
playStyleBannerSprite, null, true);
playStyleBannerSprite, scaleToFit: true);
playStyleBannerColor = playStyleBannerSprite.SourceElement.GetAttributeColor("bannercolor", Color.Black);
}
else
@@ -385,14 +385,24 @@ namespace Barotrauma.Networking
{ MinSize = new Point(0, 15) },
package.Name)
{
CanBeFocused = false
Enabled = false
};
packageText.Box.DisabledColor = packageText.Box.Color;
packageText.TextBlock.DisabledTextColor = packageText.TextBlock.TextColor;
if (!string.IsNullOrEmpty(package.Hash))
{
if (ContentPackageManager.AllPackages.Any(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash))
if (ContentPackageManager.AllPackages.FirstOrDefault(contentPackage => contentPackage.Hash.StringRepresentation == package.Hash) is { } matchingPackage)
{
packageText.TextColor = GUIStyle.Green;
packageText.Selected = true;
matchingPackage.TryFetchUgcDescription(onFinished: (string? description) =>
{
if (packageText.ToolTip.IsNullOrEmpty() &&
!string.IsNullOrEmpty(description))
{
packageText.ToolTip = description + "...";
}
});
}
//workshop download link found
else if (package.Id.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId)
@@ -437,7 +447,7 @@ namespace Barotrauma.Networking
public void UpdateInfo(Func<string, string?> valueGetter)
{
ServerMessage = valueGetter("message") ?? "";
ServerMessage = ExtractServerMessage(valueGetter);
if (Version.TryParse(valueGetter("version"), out var version))
{
GameVersion = version;
@@ -477,6 +487,22 @@ namespace Barotrauma.Networking
}
}
private static string ExtractServerMessage(Func<string, string?> valueGetter)
{
string msg = valueGetter("message") ?? string.Empty;
if (!msg.IsNullOrEmpty()) { return msg; }
int messageIndex = 0;
string splitMessage;
do
{
splitMessage = valueGetter($"message{messageIndex}") ?? string.Empty;
msg += splitMessage;
messageIndex++;
} while (!splitMessage.IsNullOrEmpty());
return msg;
}
private static ServerListContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
{
//workaround to ServerRules queries truncating the values to 255 bytes

View File

@@ -1,4 +1,4 @@
#nullable enable
#nullable enable
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -67,6 +67,7 @@ namespace Barotrauma
return null;
});
serverInfo.Checked = true;
serverInfo.HasPassword |= entry.Passworded;
onServerDataReceived(serverInfo, this);
});

View File

@@ -465,6 +465,12 @@ namespace Barotrauma.Networking
var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowSpectating"));
AssignGUIComponent(nameof(AllowSpectating), allowSpecBox);
var allowAfkBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform), TextManager.Get("ServerSettingsAllowAFK"))
{
ToolTip = TextManager.Get("ServerSettingsAllowAFK.tooltip")
};
AssignGUIComponent(nameof(AllowAFK), allowAfkBox);
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), listBox.Content.RectTransform),
TextManager.Get("LosEffect"));
var losModeSelection = new GUISelectionCarousel<LosMode>(new RectTransform(new Vector2(0.5f, 0.6f), losModeLabel.RectTransform, Anchor.CenterRight));

View File

@@ -116,12 +116,12 @@ namespace Barotrauma.Networking
bool spectating = Character.Controlled == null;
float rangeMultiplier = spectating ? 2.0f : 1.0f;
WifiComponent senderRadio = null;
var messageType =
!client.VoipQueue.ForceLocal &&
ChatMessage.CanUseRadio(client.Character, out senderRadio) &&
ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) &&
senderRadio.CanReceive(recipientRadio) ?
ChatMessageType.Radio : ChatMessageType.Default;
(spectating || (ChatMessage.CanUseRadio(Character.Controlled, out var recipientRadio) && senderRadio.CanReceive(recipientRadio)))
? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowTextlessSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
@@ -149,7 +149,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen?.SetPlayerSpeaking(client);
GameMain.GameSession?.CrewManager?.SetClientSpeaking(client);
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier(SoundManager.SoundCategoryVoip)) > 0.1f) //TODO: might need to tweak
{
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
{

View File

@@ -1,5 +1,6 @@
using Barotrauma.Extensions;
using FarseerPhysics;
using FarseerPhysics.Common;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -10,7 +11,7 @@ namespace Barotrauma.Particles
class Particle
{
private ParticlePrefab prefab;
private string debugName = "Particle (uninitialized)";
public delegate void OnChangeHullHandler(Vector2 position, Hull currentHull);
@@ -110,9 +111,12 @@ namespace Barotrauma.Particles
{
return debugName;
}
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, ParticleDrawOrder drawOrder = ParticleDrawOrder.Default, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
public void Init(ParticlePrefab prefab, Vector2 spawnPosition, Vector2 speed, float spawnRotation, Hull hullGuess = null, ParticleDrawOrder drawOrder = ParticleDrawOrder.Default, float collisionIgnoreTimer = 0f, float lifeTimeMultiplier = 1f, Tuple<Vector2, Vector2> tracerPoints = null)
{
this.prefab = prefab;
System.Diagnostics.Debug.Assert(position.IsValid(), "Attempted to spawn a particle at an invalid position.");
#if DEBUG
debugName = $"Particle ({prefab.Name})";
#else
@@ -124,14 +128,14 @@ namespace Barotrauma.Particles
animState = 0;
animFrame = 0;
currentHull = prefab.CanEnterSubs ? Hull.FindHull(position, hullGuess) : null;
currentHull = prefab.CanEnterSubs ? Hull.FindHull(spawnPosition, hullGuess) : null;
size = prefab.StartSizeMin + (prefab.StartSizeMax - prefab.StartSizeMin) * Rand.Range(0.0f, 1.0f);
if (tracerPoints != null)
{
size = new Vector2(Vector2.Distance(tracerPoints.Item1, tracerPoints.Item2), size.Y);
position = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
spawnPosition = (tracerPoints.Item1 + tracerPoints.Item2) / 2;
}
RefreshColliderSize();
@@ -139,23 +143,19 @@ namespace Barotrauma.Particles
sizeChange = prefab.SizeChangeMin + (prefab.SizeChangeMax - prefab.SizeChangeMin) * Rand.Range(0.0f, 1.0f);
changesSize = !sizeChange.NearlyEquals(Vector2.Zero);
this.position = position;
prevPosition = position;
drawPosition = position;
velocity = MathUtils.IsValid(speed) ? speed : Vector2.Zero;
if (currentHull?.Submarine != null)
{
velocity += ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
//convert to the sub's coordinate space
spawnPosition -= currentHull.Submarine.Position;
}
position = prevPosition = drawPosition = spawnPosition;
velocity = MathUtils.IsValid(speed) ? speed : Vector2.Zero;
this.rotation = rotation + Rand.Range(prefab.StartRotationMinRad, prefab.StartRotationMaxRad);
prevRotation = rotation;
rotation = spawnRotation + Rand.Range(prefab.StartRotationMinRad, prefab.StartRotationMaxRad);
prevRotation = spawnRotation;
angularVelocity = Rand.Range(prefab.AngularVelocityMinRad, prefab.AngularVelocityMaxRad);
if (prefab.LifeTimeMin <= 0.0f)
{
@@ -202,7 +202,7 @@ namespace Barotrauma.Particles
{
this.rotation = MathUtils.VectorToAngle(new Vector2(velocity.X, -velocity.Y));
prevRotation = rotation;
prevRotation = spawnRotation;
}
DrawOrder = drawOrder;
@@ -248,7 +248,7 @@ namespace Barotrauma.Particles
rotation += angularVelocity * deltaTime;
}
bool inWater = (currentHull == null || (currentHull.Submarine != null && position.Y - currentHull.Submarine.DrawPosition.Y < currentHull.Surface));
bool inWater = (currentHull == null || (currentHull.Submarine != null && position.Y < currentHull.Surface));
if (inWater)
{
velocity.X += velocityChangeWater.X * VelocityChangeMultiplier * deltaTime;
@@ -323,7 +323,7 @@ namespace Barotrauma.Particles
if (collisionIgnoreTimer > 0f)
{
collisionIgnoreTimer -= deltaTime;
if (collisionIgnoreTimer <= 0f) { currentHull ??= Hull.FindHull(position); }
if (collisionIgnoreTimer <= 0f) { currentHull ??= Hull.FindHull(position, guess: currentHull, useWorldCoordinates: false); }
return UpdateResult.Normal;
}
if (!prefab.UseCollision) { return UpdateResult.Normal; }
@@ -350,7 +350,7 @@ namespace Barotrauma.Particles
{
if (currentHull == null)
{
Hull collidedHull = Hull.FindHull(position);
Hull collidedHull = Hull.FindHull(position, useWorldCoordinates: true);
if (collidedHull != null)
{
if (prefab.DeleteOnCollision) { return UpdateResult.Delete; }
@@ -359,7 +359,7 @@ namespace Barotrauma.Particles
}
else
{
Rectangle hullRect = currentHull.WorldRect;
Rectangle hullRect = currentHull.Rect;
Vector2 collisionNormal = Vector2.Zero;
if (velocity.Y < 0.0f && position.Y - colliderRadius.Y < hullRect.Y - hullRect.Height)
{
@@ -377,9 +377,9 @@ namespace Barotrauma.Particles
{
if (gap.Open <= 0.9f || gap.IsHorizontal) { continue; }
if (gap.WorldRect.X > position.X || gap.WorldRect.Right < position.X) { continue; }
float hullCenterY = currentHull.WorldRect.Y - currentHull.WorldRect.Height / 2;
int gapDir = Math.Sign(gap.WorldRect.Y - hullCenterY);
if (gap.Rect.X > position.X || gap.Rect.Right < position.X) { continue; }
float hullCenterY = currentHull.Rect.Y - currentHull.Rect.Height / 2;
int gapDir = Math.Sign(gap.Rect.Y - hullCenterY);
if (Math.Sign(velocity.Y) != gapDir || Math.Sign(position.Y - hullCenterY) != gapDir) { continue; }
gapFound = true;
@@ -411,9 +411,9 @@ namespace Barotrauma.Particles
{
if (gap.Open <= 0.9f || !gap.IsHorizontal) { continue; }
if (gap.WorldRect.Y < position.Y || gap.WorldRect.Y - gap.WorldRect.Height > position.Y) { continue; }
int gapDir = Math.Sign(gap.WorldRect.Center.X - currentHull.WorldRect.Center.X);
if (Math.Sign(velocity.X) != gapDir || Math.Sign(position.X - currentHull.WorldRect.Center.X) != gapDir) { continue; }
if (gap.Rect.Y < position.Y || gap.WorldRect.Y - gap.Rect.Height > position.Y) { continue; }
int gapDir = Math.Sign(gap.Rect.Center.X - currentHull.Rect.Center.X);
if (Math.Sign(velocity.X) != gapDir || Math.Sign(position.X - currentHull.Rect.Center.X) != gapDir) { continue; }
gapFound = true;
break;
@@ -434,7 +434,7 @@ namespace Barotrauma.Particles
}
else
{
Hull newHull = Hull.FindHull(position, currentHull);
Hull newHull = Hull.FindHull(position, currentHull, useWorldCoordinates: false);
if (newHull != currentHull)
{
currentHull = newHull;
@@ -457,32 +457,25 @@ namespace Barotrauma.Particles
private void ApplyDrag(float dragCoefficient, float deltaTime)
{
Vector2 relativeVel = velocity;
if (currentHull?.Submarine != null)
{
relativeVel = velocity - ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
}
Vector2 newVel = velocity;
float speed = velocity.Length();
float speed = relativeVel.Length();
relativeVel /= speed;
if (speed < 0.01f) { return; }
newVel /= speed;
float drag = speed * speed * dragCoefficient * 0.01f * deltaTime;
if (drag > speed)
{
relativeVel = Vector2.Zero;
newVel = Vector2.Zero;
}
else
{
speed -= drag;
relativeVel *= speed;
newVel *= speed;
}
velocity = relativeVel;
if (currentHull?.Submarine != null)
{
velocity += ConvertUnits.ToDisplayUnits(currentHull.Submarine.Velocity);
}
velocity = newVel;
}
@@ -490,10 +483,7 @@ namespace Barotrauma.Particles
{
if (prevHull == null) { return; }
Rectangle prevHullRect = prevHull.WorldRect;
Vector2 subVel = prevHull?.Submarine != null ? ConvertUnits.ToDisplayUnits(prevHull.Submarine.Velocity) : Vector2.Zero;
velocity -= subVel;
Rectangle prevHullRect = prevHull.Rect;
if (Math.Abs(collisionNormal.X) > Math.Abs(collisionNormal.Y))
{
@@ -524,14 +514,12 @@ namespace Barotrauma.Particles
}
OnCollision?.Invoke(position, currentHull);
velocity += subVel;
}
private void OnWallCollisionOutside(Hull collisionHull)
{
Rectangle hullRect = collisionHull.WorldRect;
Rectangle hullRect = collisionHull.Rect;
Vector2 center = new Vector2(hullRect.X + hullRect.Width / 2, hullRect.Y - hullRect.Height / 2);
@@ -584,7 +572,13 @@ namespace Barotrauma.Particles
Color currColor = new Color(color.ToVector4() * ColorMultiplier);
Vector2 drawPos = new Vector2(drawPosition.X, -drawPosition.Y);
Vector2 drawPos = drawPosition;
if (currentHull?.Submarine is Submarine sub)
{
drawPos += sub.DrawPosition;
}
drawPos = new Vector2(drawPos.X, -drawPos.Y);
if (prefab.Sprites[spriteIndex] is SpriteSheet sheet)
{
sheet.Draw(

View File

@@ -99,6 +99,9 @@ namespace Barotrauma.Particles
[Editable, Serialize(1f, IsPropertySaveable.Yes)]
public float LifeTimeMultiplier { get; set; }
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Should the particle be drawn as a tracer (a line from a weapon to the point it hit)? Only supported on hitscan projectiles and repair tools. Defaults to true on hitscan projectiles.")]
public bool UseTracerPoints { get; set; }
[Editable, Serialize(ParticleDrawOrder.Default, IsPropertySaveable.Yes)]
public ParticleDrawOrder DrawOrder { get; set; }
@@ -224,7 +227,7 @@ namespace Barotrauma.Particles
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess,
particlePrefab.DrawOrder != ParticleDrawOrder.Default ? particlePrefab.DrawOrder : Prefab.DrawOrder,
lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: tracerPoints);
lifeTimeMultiplier: Prefab.Properties.LifeTimeMultiplier, tracerPoints: Prefab.Properties.UseTracerPoints ? tracerPoints : null);
if (particle != null)
{

View File

@@ -103,6 +103,9 @@ namespace Barotrauma.Particles
if (prefab == null || prefab.Sprites.Count == 0) { return null; }
if (particleCount >= MaxParticles)
{
//maximum number of particles reached, and this is not a high-prio particle or something that should always draw
// -> the particle won't be created, we can return early
if (particleCount >= MaxParticles && prefab.Priority == 0 && !prefab.DrawAlways) { return null; }
for (int i = 0; i < particleCount; i++)
{
if (particles[i].Prefab.Priority < prefab.Priority ||

View File

@@ -1,6 +1,5 @@
using Barotrauma.Networking;
using FarseerPhysics;
using Lidgren.Network;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
@@ -9,6 +8,18 @@ namespace Barotrauma
{
partial class PhysicsBody
{
/// <summary>
/// Last known state the server has told us about.
/// </summary>
public PosInfo LastServerState;
/// <summary>
/// An offset used to corrections to positional errors look smoother. When a large positional correction needs to be done in multiplayer,
/// the body is immediately moved to the correct position, but the draw position is interpolated to make the correction visually smoother.
/// This value means the offset from the "actual" corrected position of the body to the "fake", interpolated draw position.
/// </summary>
public Vector2 NetworkPositionErrorOffset => drawOffset;
public void Draw(DeformableSprite deformSprite, Camera cam, Vector2 scale, Color color, bool invert = false)
{
if (!Enabled) { return; }
@@ -69,9 +80,26 @@ namespace Barotrauma
GUI.DrawLine(spriteBatch,
new Vector2(pos.X, -pos.Y),
new Vector2(DrawPosition.X, -DrawPosition.Y),
Color.Purple * 0.75f, 0, 5);
Color.Purple * 0.5f, 0, 5);
}
if (IsValidShape(Radius, Height, Width))
{
DrawShape(drawPosition, DrawRotation, color);
}
if (LastServerState != null)
{
Vector2 drawPos = ConvertUnits.ToDisplayUnits(LastServerState.Position);
if (Submarine != null)
{
drawPos += Submarine.DrawPosition;
}
float rotation = LastServerState.Rotation ?? 0.0f;
DrawShape(drawPos, rotation, Color.Purple * 0.75f);
}
void DrawShape(Vector2 position, float rotation, Color color)
{
float radius = ConvertUnits.ToDisplayUnits(Radius);
float height = ConvertUnits.ToDisplayUnits(Height);
@@ -80,16 +108,16 @@ namespace Barotrauma
switch (BodyShape)
{
case Shape.Rectangle:
GUI.DrawRectangle(spriteBatch, DrawPosition.FlipY(), new Vector2(width, height), new Vector2(width, height) / 2, -DrawRotation, color);
GUI.DrawRectangle(spriteBatch, position.FlipY(), new Vector2(width, height), new Vector2(width, height) / 2, -rotation, color);
break;
case Shape.Capsule:
GUI.DrawCapsule(spriteBatch, DrawPosition.FlipY(), height, radius, -DrawRotation - MathHelper.PiOver2, color);
GUI.DrawCapsule(spriteBatch, position.FlipY(), height, radius, -rotation - MathHelper.PiOver2, color);
break;
case Shape.HorizontalCapsule:
GUI.DrawCapsule(spriteBatch, DrawPosition.FlipY(), width, radius, -DrawRotation, color);
GUI.DrawCapsule(spriteBatch, position.FlipY(), width, radius, -rotation, color);
break;
case Shape.Circle:
GUI.DrawDonutSection(spriteBatch, DrawPosition.FlipY(), new Range<float>(radius - 0.5f, radius + 0.5f), MathHelper.TwoPi, color, 0, -DrawRotation);
GUI.DrawDonutSection(spriteBatch, position.FlipY(), new Range<float>(radius - 0.5f, radius + 0.5f), MathHelper.TwoPi, color, 0, -rotation);
break;
default:
throw new NotImplementedException();
@@ -150,9 +178,10 @@ namespace Barotrauma
return null;
}
return lastProcessedNetworkState > sendingTime ?
null :
new PosInfo(newPosition, newRotation, newVelocity, newAngularVelocity, sendingTime);
if (lastProcessedNetworkState > sendingTime) { return null; }
LastServerState = new PosInfo(newPosition, newRotation, newVelocity, newAngularVelocity, sendingTime);
return LastServerState;
}
}
}

View File

@@ -632,7 +632,7 @@ namespace Barotrauma
saveFrame.GetChild<GUITextBlock>().TextColor = GUIStyle.Red;
continue;
}
if (docRoot.GetChildElement("multiplayercampaign") != null)
if (docRoot.GetAttributeBool("ismultiplayer", false))
{
//multiplayer campaign save in the wrong folder -> don't show the save
saveList.Content.RemoveChild(saveFrame);

View File

@@ -259,7 +259,7 @@ namespace Barotrauma
{
AutoScaleHorizontal = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.Type.Name, font: GUIStyle.SubHeadingFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), location.GetLocationTypeToDisplay().Name, font: GUIStyle.SubHeadingFont);
Sprite portrait = location.Type.GetPortrait(location.PortraitId);
portrait.EnsureLazyLoaded();

View File

@@ -1790,7 +1790,7 @@ namespace Barotrauma.CharacterEditor
XElement element = animation.MainElement;
if (element == null) { continue; }
element.SetAttributeValue("type", name);
string fullPath = AnimationParams.GetDefaultFile(name, animation.AnimationType);
string fullPath = AnimationParams.GetDefaultFilePath(name, animation.AnimationType);
element.Name = AnimationParams.GetDefaultFileName(name, animation.AnimationType);
#if DEBUG
element.Save(fullPath);
@@ -1818,7 +1818,7 @@ namespace Barotrauma.CharacterEditor
default: continue;
}
Type type = AnimationParams.GetParamTypeFromAnimType(animType, isHumanoid);
string fullPath = AnimationParams.GetDefaultFile(name, animType);
string fullPath = AnimationParams.GetDefaultFilePath(name, animType);
AnimationParams.Create(fullPath, name, animType, type);
}
}

View File

@@ -318,9 +318,15 @@ namespace Barotrauma
graphics.SetRenderTarget(renderTargetDamageable);
graphics.Clear(Color.Transparent);
DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"];
DamageEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.LinearWrap, effect: DamageEffect, transformMatrix: cam.Transform);
Submarine.DrawDamageable(spriteBatch, DamageEffect, false);
DamageEffect.Parameters["aCutoff"].SetValue(0.0f);
DamageEffect.Parameters["cCutoff"].SetValue(0.0f);
Submarine.DamageEffectCutoff = 0.0f;
DamageEffect.CurrentTechnique.Passes[0].Apply();
spriteBatch.End();
sw.Stop();

View File

@@ -22,13 +22,16 @@ namespace Barotrauma
public override Camera Cam { get; }
private GUIFrame leftPanel, rightPanel, bottomPanel, topPanel;
private Point prevResolution;
private LevelGenerationParams selectedParams;
private RuinGenerationParams selectedRuinGenerationParams;
private OutpostGenerationParams selectedOutpostGenerationParams;
private LevelObjectPrefab selectedLevelObject;
private BackgroundCreaturePrefab selectedBackgroundCreature;
private GUIListBox paramsList, ruinParamsList, caveParamsList, outpostParamsList, levelObjectList;
private GUIListBox paramsList, ruinParamsList, caveParamsList, outpostParamsList, levelObjectList, backgroundCreatureList;
private GUIListBox editorContainer;
private GUIButton spriteEditDoneButton;
@@ -69,10 +72,12 @@ namespace Barotrauma
UpdateCaveParamsList();
UpdateOutpostParamsList();
UpdateLevelObjectsList();
UpdateBackgroundCreatureList();
}
private void CreateUI()
{
Frame.ClearChildren();
leftPanel?.ClearChildren();
rightPanel?.ClearChildren();
leftPanel = new GUIFrame(new RectTransform(new Vector2(0.125f, 0.8f), Frame.RectTransform) { MinSize = new Point(150, 0) });
@@ -92,6 +97,7 @@ namespace Barotrauma
currentLevelData = LevelData.CreateRandom(seedBox.Text, generationParams: selectedParams);
editorContainer.ClearChildren();
SortLevelObjectsList(currentLevelData);
SortBackgroundCreaturesList(currentLevelData);
new SerializableEntityEditor(editorContainer.Content.RectTransform, selectedParams, inGame: false, showName: true, elementHeight: 20, titleFont: GUIStyle.LargeFont);
forceDifficultyInput.FloatValue = (selectedParams.MinLevelDifficulty + selectedParams.MaxLevelDifficulty) / 2f;
return true;
@@ -310,6 +316,7 @@ namespace Barotrauma
currentLevelData.AllowInvalidOutpost = allowInvalidOutpost.Selected;
var dummyLocations = GameSession.CreateDummyLocations(currentLevelData);
Level.Generate(currentLevelData, mirror: mirrorLevel.Selected, startLocation: dummyLocations[0], endLocation: dummyLocations[1]);
UpdateBackgroundCreatureList();
if (Submarine.MainSub != null)
{
@@ -385,9 +392,42 @@ namespace Barotrauma
};
bottomPanel = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.22f), Frame.RectTransform, Anchor.BottomLeft)
{ MaxSize = new Point(GameMain.GraphicsWidth - rightPanel.Rect.Width, 1000) }, style: "GUIFrameBottom");
{ MaxSize = new Point(GameMain.GraphicsWidth - rightPanel.Rect.Width, 1000) }, style: "GUIFrameBottom");
levelObjectList = new GUIListBox(new RectTransform(new Vector2(0.99f, 0.85f), bottomPanel.RectTransform, Anchor.Center))
var bottomPanelContents = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.9f), bottomPanel.RectTransform, Anchor.Center))
{
Stretch = true
};
var bottomPanelButtons = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), bottomPanelContents.RectTransform), isHorizontal: true);
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), bottomPanelButtons.RectTransform), TextManager.Get("leveleditor.levelobjects"), style: "GUITabButton")
{
Selected = true,
OnClicked = (btn, __) =>
{
bottomPanelButtons.Children.ForEach(c => c.Selected = c == btn);
levelObjectList.Visible = true;
levelObjectList.IgnoreLayoutGroups = false;
backgroundCreatureList.Visible = false;
backgroundCreatureList.IgnoreLayoutGroups = true;
return true;
}
};
new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), bottomPanelButtons.RectTransform), TextManager.Get("leveleditor.backgroundcreatures"), style: "GUITabButton")
{
OnClicked = (btn, __) =>
{
bottomPanelButtons.Children.ForEach(c => c.Selected = c == btn);
backgroundCreatureList.Visible = true;
backgroundCreatureList.IgnoreLayoutGroups = false;
levelObjectList.Visible = false;
levelObjectList.IgnoreLayoutGroups = true;
return true;
}
};
bottomPanelButtons.RectTransform.NonScaledSize = new Point(bottomPanelButtons.Rect.Width, bottomPanelButtons.Children.First().Rect.Height);
levelObjectList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), bottomPanelContents.RectTransform))
{
PlaySoundOnSelect = true,
UseGridLayout = true
@@ -399,6 +439,20 @@ namespace Barotrauma
return true;
};
backgroundCreatureList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.85f), bottomPanelContents.RectTransform))
{
PlaySoundOnSelect = true,
UseGridLayout = true,
Visible = false,
IgnoreLayoutGroups = true
};
backgroundCreatureList.OnSelected += (GUIComponent component, object obj) =>
{
selectedBackgroundCreature = obj as BackgroundCreaturePrefab;
CreateBackgroundCreatureEditor(selectedBackgroundCreature);
return true;
};
spriteEditDoneButton = new GUIButton(new RectTransform(new Point(200, 30), anchor: Anchor.BottomRight) { AbsoluteOffset = new Point(20, 20) },
TextManager.Get("leveleditor.spriteeditdone"))
{
@@ -411,6 +465,8 @@ namespace Barotrauma
topPanel = new GUIFrame(new RectTransform(new Point(400, 100), GUI.Canvas)
{ RelativeOffset = new Vector2(leftPanel.RectTransform.RelativeSize.X * 2, 0.0f) }, style: "GUIFrameTop");
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
}
public LevelEditorScreen()
@@ -585,6 +641,44 @@ namespace Barotrauma
}
}
private void UpdateBackgroundCreatureList()
{
editorContainer.ClearChildren();
backgroundCreatureList.Content.ClearChildren();
int objectsPerRow = (int)Math.Ceiling(backgroundCreatureList.Content.Rect.Width / Math.Max(100 * GUI.Scale, 100));
float relWidth = 1.0f / objectsPerRow;
foreach (BackgroundCreaturePrefab backgroundCreaturePrefab in BackgroundCreaturePrefab.Prefabs)
{
var frame = new GUIFrame(new RectTransform(
new Vector2(relWidth, relWidth * ((float)backgroundCreatureList.Content.Rect.Width / backgroundCreatureList.Content.Rect.Height)),
backgroundCreatureList.Content.RectTransform)
{ MinSize = new Point(0, 60) }, style: "ListBoxElementSquare")
{
UserData = backgroundCreaturePrefab
};
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.9f), frame.RectTransform, Anchor.Center), style: null);
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform, Anchor.BottomCenter),
text: ToolBox.LimitString(backgroundCreaturePrefab.Name, GUIStyle.SmallFont, paddedFrame.Rect.Width), textAlignment: Alignment.Center, font: GUIStyle.SmallFont)
{
CanBeFocused = false,
ToolTip = backgroundCreaturePrefab.Name
};
Sprite sprite = backgroundCreaturePrefab.Sprite ?? backgroundCreaturePrefab.DeformableSprite?.Sprite;
new GUIImage(new RectTransform(new Point(paddedFrame.Rect.Height, paddedFrame.Rect.Height - textBlock.Rect.Height),
paddedFrame.RectTransform, Anchor.TopCenter), sprite, scaleToFit: true)
{
LoadAsynchronously = true,
CanBeFocused = false
};
}
SortBackgroundCreaturesList(currentLevelData);
}
private void CreateCaveParamsEditor(CaveGenerationParams caveGenerationParams)
{
editorContainer.ClearChildren();
@@ -913,6 +1007,99 @@ namespace Barotrauma
});
}
private void SortBackgroundCreaturesList(LevelData levelData)
{
if (levelData == null) { return; }
//fade out levelobjects that don't spawn in this type of level
foreach (GUIComponent child in backgroundCreatureList.Content.Children)
{
if (child.UserData is not BackgroundCreaturePrefab creature) { continue; }
SetElementColorBasedOnCommonness(child, creature.GetCommonness(levelData));
}
//sort the levelobjects according to commonness in this level
backgroundCreatureList.Content.RectTransform.SortChildren((c1, c2) =>
{
var creature1 = c1.GUIComponent.UserData as BackgroundCreaturePrefab;
var creature2 = c2.GUIComponent.UserData as BackgroundCreaturePrefab;
return Math.Sign(creature2.GetCommonness(levelData) - creature1.GetCommonness(levelData));
});
}
private static void SetElementColorBasedOnCommonness(GUIComponent element, float commonness)
{
element.Color = commonness > 0.0f ? GUIStyle.Green * 0.4f : Color.Transparent;
element.SelectedColor = commonness > 0.0f ? GUIStyle.Green * 0.6f : Color.White * 0.5f;
element.HoverColor = commonness > 0.0f ? GUIStyle.Green * 0.7f : Color.White * 0.6f;
element.GetAnyChild<GUIImage>().Color = commonness > 0.0f ? Color.White : Color.DarkGray;
if (commonness <= 0.0f)
{
element.GetAnyChild<GUITextBlock>().TextColor = Color.DarkGray;
}
}
private void CreateBackgroundCreatureEditor(BackgroundCreaturePrefab backgroundCreaturePrefab)
{
editorContainer.ClearChildren();
var editor = new SerializableEntityEditor(editorContainer.Content.RectTransform, backgroundCreaturePrefab, false, true, elementHeight: 20, titleFont: GUIStyle.LargeFont);
if (selectedParams != null)
{
List<Identifier> availableIdentifiers = new List<Identifier>() { selectedParams.Identifier };
foreach (Identifier paramsId in availableIdentifiers)
{
var commonnessContainer = new GUILayoutGroup(new RectTransform(new Point(editor.Rect.Width, 70)) { IsFixedSize = true },
isHorizontal: false, childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = 5,
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), commonnessContainer.RectTransform),
TextManager.GetWithVariable("leveleditor.levelobjcommonness", "[leveltype]", paramsId.Value), textAlignment: Alignment.Center);
new GUINumberInput(new RectTransform(new Vector2(0.5f, 0.4f), commonnessContainer.RectTransform), NumberType.Float)
{
MinValueFloat = 0,
MaxValueFloat = 100,
FloatValue = backgroundCreaturePrefab.GetCommonness(currentLevelData),
OnValueChanged = (numberInput) =>
{
backgroundCreaturePrefab.OverrideCommonness[paramsId] = numberInput.FloatValue;
}
};
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), commonnessContainer.RectTransform), style: null);
editor.AddCustomContent(commonnessContainer, 1);
}
}
Sprite sprite = backgroundCreaturePrefab.Sprite ?? backgroundCreaturePrefab.DeformableSprite?.Sprite;
if (sprite != null)
{
editor.AddCustomContent(new GUIButton(new RectTransform(new Point(editor.Rect.Width / 2, (int)(25 * GUI.Scale))) { IsFixedSize = true },
TextManager.Get("leveleditor.editsprite"))
{
OnClicked = (btn, userdata) =>
{
editingSprite = sprite;
GameMain.SpriteEditorScreen.SelectSprite(editingSprite);
return true;
}
}, 1);
}
if (backgroundCreaturePrefab.DeformableSprite != null)
{
var deformEditor = backgroundCreaturePrefab.DeformableSprite.CreateEditor(editor, backgroundCreaturePrefab.SpriteDeformations, backgroundCreaturePrefab.Name);
deformEditor.GetChild<GUIDropDown>().OnSelected += (selected, userdata) =>
{
CreateBackgroundCreatureEditor(backgroundCreaturePrefab);
return true;
};
editor.AddCustomContent(deformEditor, editor.ContentCount);
}
}
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
@@ -1083,6 +1270,11 @@ namespace Barotrauma
public override void Update(double deltaTime)
{
if (GameMain.GraphicsWidth != prevResolution.X || GameMain.GraphicsHeight != prevResolution.Y)
{
RefreshUI(forceCreate: true);
}
if (lightingEnabled.Selected)
{
foreach (Item item in Item.ItemList)

View File

@@ -23,6 +23,8 @@ namespace Barotrauma
{
sealed class MainMenuScreen : Screen
{
public static HashSet<Identifier> DismissedNotifications = new HashSet<Identifier>();
private enum Tab
{
NewGame = 0,
@@ -512,6 +514,22 @@ namespace Barotrauma
return true;
}
};
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(40, 230) },
"Local MP Quickstart", style: "GUIButtonLarge", color: GUIStyle.Red)
{
IgnoreLayoutGroups = true,
UserData = Tab.Empty,
ToolTip = "Starts a server and another client and connects both to localhost, using names 'client1' and 'client2'.",
OnClicked = (tb, userdata) =>
{
SelectTab(tb, userdata);
DebugConsole.StartLocalMPSession(numClients: 2);
return true;
}
};
#endif
new GUIButton(new RectTransform(new Point(300, 30), Frame.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(40, 50) },
$"Open LuaCs Settings", style: "MainMenuGUIButton", color: GUIStyle.Red)
@@ -590,6 +608,12 @@ namespace Barotrauma
SelectTab(Tab.Empty);
}
public static void AddDismissedNotification(Identifier id)
{
DismissedNotifications.Add(id);
GameSettings.SaveCurrentConfig();
}
private void SetMenuTabPositioning()
{
foreach (GUIFrame menuTab in menuTabs.Values)
@@ -618,7 +642,7 @@ namespace Barotrauma
};
var tutorialPreview = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), tutorialContent.RectTransform)) { RelativeSpacing = 0.05f, Stretch = true };
var imageContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "InnerFrame");
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: true);
tutorialBanner = new GUIImage(new RectTransform(Vector2.One, imageContainer.RectTransform), style: null, scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent);
var infoContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), tutorialPreview.RectTransform), style: "GUIFrameListBox");
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), infoContainer.RectTransform, Anchor.Center), childAnchor: Anchor.TopLeft)
@@ -949,6 +973,7 @@ namespace Barotrauma
DebugConsole.ThrowError("Failed to find the job \"" + job + "\"!");
}
gamesession.CrewManager.AddCharacterInfo(characterInfo);
characterInfo.SetNameBasedOnJob();
}
gamesession.CrewManager.InitSinglePlayerRound();
}
@@ -1320,11 +1345,9 @@ namespace Barotrauma
catch (Exception e)
{
DebugConsole.ThrowError("Loading save \"" + path + "\" failed", e);
GameMain.GameSession = null;
return;
}
//TODO
//GameMain.LobbyScreen.Select();
}
#region UI Methods
@@ -1357,7 +1380,7 @@ namespace Barotrauma
var serverSettings = XMLExtensions.TryLoadXml(ServerSettings.SettingsFile, out _)?.Root ?? new XElement("serversettings");
var name = serverSettings.GetAttributeString("name", "");
var name = serverSettings.GetAttributeString(nameof(ServerSettings.ServerName), "");
var password = serverSettings.GetAttributeString("password", "");
var isPublic = serverSettings.GetAttributeBool("IsPublic", true);
var banAfterWrongPassword = serverSettings.GetAttributeBool("banafterwrongpassword", false);
@@ -1391,7 +1414,7 @@ namespace Barotrauma
var playstyleContainer = new GUIFrame(new RectTransform(new Vector2(1.35f, 0.1f), parent.RectTransform), style: null, color: Color.Black);
playstyleBanner = new GUIImage(new RectTransform(new Vector2(1.0f, 0.1f), playstyleContainer.RectTransform),
GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: true)
GUIStyle.GetComponentStyle($"PlayStyleBanner.{PlayStyle.Serious}").GetSprite(GUIComponent.ComponentState.None), scaleToFit: GUIImage.ScalingMode.ScaleToFitSmallestExtent)
{
UserData = PlayStyle.Serious
};

View File

@@ -67,8 +67,9 @@ namespace Barotrauma
public GUIButton ServerMessageButton { get; private set; }
public static GUIButton JobInfoFrame { get; set; }
private GUITickBox spectateBox;
private GUITickBox spectateBox, afkBox;
public bool Spectating => spectateBox is { Selected: true, Visible: true };
public bool AFKSelected => afkBox is { Selected: true, Visible: true };
public bool PermadeathMode => GameMain.Client?.ServerSettings?.RespawnMode == RespawnMode.Permadeath;
public bool PermanentlyDead => campaignCharacterInfo?.PermanentlyDead ?? false;
@@ -112,6 +113,8 @@ namespace Barotrauma
private readonly List<GUIComponent> respawnSettings = new();
public CharacterInfo.AppearanceCustomizationMenu CharacterAppearanceCustomizationMenu { get; set; }
private Point prevResolutionForJobSelectionFrame;
public GUIFrame JobSelectionFrame { get; private set; }
public GUIFrame JobPreferenceContainer { get; private set; }
@@ -209,6 +212,7 @@ namespace Barotrauma
private CharacterTeamType TeamPreference => SelectedMode == GameModePreset.PvP ? MultiplayerPreferences.Instance.TeamPreference : CharacterTeamType.Team1;
public GUIButton StartButton { get; private set; }
public GUIButton EndButton { get; private set; }
public GUITickBox ReadyToStartBox { get; private set; }
@@ -224,7 +228,7 @@ namespace Barotrauma
public bool UsingShuttle
{
get { return shuttleTickBox.Selected && !PermadeathMode; }
get { return shuttleTickBox.Selected; }
set { shuttleTickBox.Selected = value; }
}
@@ -473,10 +477,20 @@ namespace Barotrauma
AbsoluteSpacing = GUI.IntScale(5)
};
Favorite = new GUITickBox(new RectTransform(new Vector2(0.5f, 0.5f), serverInfoContent.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothHeight),
var topRightContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 0.5f), serverInfoContent.RectTransform, Anchor.TopRight),
isHorizontal: true, childAnchor: Anchor.TopRight)
{
AbsoluteSpacing = GUI.IntScale(5),
CanBeFocused = true
};
SettingsButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), topRightContainer.RectTransform, Anchor.TopRight),
TextManager.Get("ServerSettingsButton"), style: "GUIButtonFreeScale");
Favorite = new GUITickBox(new RectTransform(Vector2.One, topRightContainer.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothHeight),
"", null, "GUIServerListFavoriteTickBox")
{
IgnoreLayoutGroups = true,
Selected = false,
ToolTip = TextManager.Get("addtofavorites"),
OnSelected = (tickbox) =>
@@ -496,8 +510,6 @@ namespace Barotrauma
}
};
SettingsButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.4f), serverInfoContent.RectTransform, Anchor.TopRight),
TextManager.Get("ServerSettingsButton"), style: "GUIButtonFreeScale");
}
private void CreateServerMessagePopup(string serverName, string message)
@@ -698,11 +710,11 @@ namespace Barotrauma
if (GameMain.Client == null) { return false; }
if (GameMain.Client.GameStarted)
{
GameMain.Client.RequestRoundEnd(save: false);
GameMain.Client.RequestEndRound(save: false);
}
else
{
GameMain.Client.RequestRoundEnd(save: false, quitCampaign: true);
GameMain.Client.RequestEndRound(save: false, quitCampaign: true);
}
return true;
}
@@ -1216,7 +1228,7 @@ namespace Barotrauma
shuttleTickBox = new GUITickBox(new RectTransform(Vector2.One, shuttleHolder.RectTransform), TextManager.Get("RespawnShuttle"))
{
ToolTip = TextManager.Get("RespawnShuttleExplanation"),
Selected = !PermadeathMode,
Selected = true,
OnSelected = (GUITickBox box) =>
{
GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Properties);
@@ -1241,7 +1253,10 @@ namespace Barotrauma
{
OnSelected = (component, obj) =>
{
SelectShuttle((SubmarineInfo)obj);
SubmarineInfo subInfo = (SubmarineInfo)obj;
ShuttleList.Text = subInfo.DisplayName;
ShuttleList.ToolTip = subInfo.Description;
SelectShuttle(subInfo);
return true;
}
};
@@ -1539,8 +1554,8 @@ namespace Barotrauma
foreach (var disembarkPerkPrefab in DisembarkPerkPrefab.Prefabs
.OrderBy(static p => p.SortCategory)
.ThenBy(static p => p.Cost)
.ThenBy(static p => p.SortKey))
.ThenBy(static p => p.SortKey)
.ThenBy(static p => p.Cost))
{
if (disembarkPerkCategory != disembarkPerkPrefab.SortCategory)
{
@@ -1888,13 +1903,25 @@ namespace Barotrauma
Stretch = true
};
spectateBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 0.06f), myCharacterContent.RectTransform),
var checkBoxContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), myCharacterContent.RectTransform), isHorizontal: true)
{
Stretch = true
};
spectateBox = new GUITickBox(new RectTransform(new Vector2(0.6f, 1.0f), checkBoxContainer.RectTransform),
TextManager.Get("spectatebutton"))
{
Selected = false,
OnSelected = ToggleSpectate,
UserData = "spectate"
};
afkBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 1.0f), checkBoxContainer.RectTransform),
TextManager.Get("afkbutton"))
{
Selected = false,
ToolTip = TextManager.Get("afkbutton.tooltip")
};
checkBoxContainer.RectTransform.MinSize = new Point(0, spectateBox.RectTransform.MinSize.Y);
playerInfoContent = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), myCharacterContent.RectTransform))
{
@@ -2125,6 +2152,25 @@ namespace Barotrauma
joinOnGoingRoundButton = new GUIButton(new RectTransform(Vector2.One, roundControlsHolder.RectTransform),
TextManager.Get("ServerListJoin"));
EndButton = new GUIButton(new RectTransform(Vector2.One, roundControlsHolder.RectTransform),
TextManager.Get("endround"))
{
//spooky red color for a destructive action
Color = GUIStyle.Red,
OnClicked = (btn, obj) =>
{
if (GameMain.Client == null) { return true; }
GUI.CreateVerificationPrompt(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
() =>
{
GameMain.Client?.RequestEndRound(save: false);
});
return true;
},
Visible = false,
IgnoreLayoutGroups = true
};
// Start button
StartButton = new GUIButton(new RectTransform(Vector2.One, roundControlsHolder.RectTransform),
TextManager.Get("StartGameButton"))
@@ -2132,6 +2178,14 @@ namespace Barotrauma
OnClicked = (btn, obj) =>
{
if (GameMain.Client == null) { return true; }
//the player presumably no longer wants to be afk if they clicked the start button
if (afkBox.Selected)
{
afkBox.Flash(GUIStyle.Green);
}
afkBox.Selected = false;
if (CampaignSetupFrame.Visible && CampaignSetupUI != null)
{
CampaignSetupUI.StartGameClicked(btn, obj);
@@ -2146,6 +2200,7 @@ namespace Barotrauma
}
};
clientHiddenElements.Add(StartButton);
bottomBar.RectTransform.MinSize =
new Point(0, (int)Math.Max(ReadyToStartBox.RectTransform.MinSize.Y / 0.75f, StartButton.RectTransform.MinSize.Y));
@@ -2244,6 +2299,9 @@ namespace Barotrauma
RefreshEnabledElements();
createPendingChangesText = false;
TabMenu.PendingChanges = false;
if (GameMain.Client != null)
{
joinOnGoingRoundButton.Visible = GameMain.Client.GameStarted;
@@ -2258,8 +2316,18 @@ namespace Barotrauma
if (GameMain.Client != null)
{
afkBox.Visible = !GameMain.Client.IsServerOwner && GameMain.Client.ServerSettings.AllowAFK;
GameMain.Client.Voting.ResetVotes(GameMain.Client.ConnectedClients);
joinOnGoingRoundButton.OnClicked = GameMain.Client.JoinOnGoingClicked;
joinOnGoingRoundButton.OnClicked = (btn, userdata) =>
{
if (afkBox is { Selected: true })
{
afkBox.Selected = false;
afkBox.Flash(GUIStyle.Green);
}
GameMain.Client.SendJoinOngoingRequest(btn);
return true;
};
ReadyToStartBox.OnSelected = GameMain.Client.SetReadyToStart;
}
@@ -2293,7 +2361,7 @@ namespace Barotrauma
traitorElements.ForEach(e => e.Enabled &= settings.TraitorProbability > 0);
SetTraitorDangerIndicators(settings.TraitorDangerLevel);
respawnModeSelection.Enabled = respawnModeLabel.Enabled = manageSettings && !gameStarted;
midRoundRespawnSettings.ForEach(e => e.Enabled &= settings.RespawnMode == RespawnMode.MidRound);
midRoundRespawnSettings.ForEach(e => e.Enabled &= settings.RespawnMode != RespawnMode.BetweenRounds);
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);
@@ -2366,6 +2434,7 @@ namespace Barotrauma
if (campaignCharacterInfo != newCampaignCharacterInfo)
{
campaignCharacterInfo = newCampaignCharacterInfo;
SaveAppearance();
UpdatePlayerFrame(campaignCharacterInfo, false);
}
}
@@ -2391,7 +2460,7 @@ namespace Barotrauma
createPendingText: createPendingText);
}
private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent, bool createPendingText = true)
private void UpdatePlayerFrame(CharacterInfo characterInfo, bool allowEditing, GUIComponent parent, bool createPendingText = false)
{
if (GameMain.Client == null) { return; }
@@ -2402,7 +2471,7 @@ namespace Barotrauma
if (characterInfo == null || CampaignCharacterDiscarded)
{
characterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, GameMain.Client.Name, null);
characterInfo.RecreateHead(MultiplayerPreferences.Instance);
characterInfo.RecreateHead(MultiplayerPreferences.Instance); // not necessarily the head of the last character
GameMain.Client.CharacterInfo = characterInfo;
characterInfo.OmitJobInMenus = true;
}
@@ -2824,8 +2893,7 @@ namespace Barotrauma
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.GetWithVariable("startingequipmentname", "[number]", (variant + 1).ToString()), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center);
var itemIdentifiers = jobPrefab.JobItems[variant]
.Where(it => it.ShowPreview)
var itemIdentifiers = jobPrefab.GetJobItems(variant, it => it.ShowPreview)
.Select(it => it.GetItemIdentifier(team, isPvPMode))
.Distinct();
@@ -2958,6 +3026,16 @@ namespace Barotrauma
spectateBox.Visible = allowSpectating;
}
public void SetAllowAFK(bool allowAFK)
{
if (afkBox.Visible != allowAFK)
{
//reset selection when the AFK option becomes available or unavailable
afkBox.Selected = false;
afkBox.Visible = allowAFK;
}
}
public void SetAutoRestart(bool enabled, float timer = 0.0f)
{
autoRestartBox.Selected = enabled;
@@ -3059,20 +3137,26 @@ namespace Barotrauma
var subTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), frameLayout.RectTransform, Anchor.CenterLeft),
ToolBox.LimitString(sub.DisplayName.Value, GUIStyle.Font, subList.Rect.Width - 65), textAlignment: Alignment.CenterLeft)
{
ToolTip = sub.Description,
UserData = "nametext",
CanBeFocused = true
};
var pvpContainer = new GUIFrame(new RectTransform(new Vector2(0.3f, 1f), frameLayout.RectTransform, Anchor.CenterRight), style: null);
var pvpContainer = new GUIFrame(new RectTransform(new Vector2(0.3f, 1f), frameLayout.RectTransform, Anchor.CenterRight), style: null)
{
CanBeFocused = false
};
var coalitionIcon = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), pvpContainer.RectTransform, Anchor.CenterLeft), style: "CoalitionIcon")
{
Visible = false,
UserData = CoalitionIconUserData,
CanBeFocused = false
};
var separatistsIcon = new GUIFrame(new RectTransform(new Vector2(0.5f, 1f), pvpContainer.RectTransform, Anchor.CenterRight), style: "SeparatistIcon")
{
Visible = false,
UserData = SeparatistsIconUserData,
CanBeFocused = false
};
var matchingSub =
@@ -3973,7 +4057,7 @@ namespace Barotrauma
JobInfoFrame?.AddToGUIUpdateList();
CharacterAppearanceCustomizationMenu?.AddToGUIUpdateList();
JobSelectionFrame?.AddToGUIUpdateList();
JobSelectionFrame?.AddToGUIUpdateList(order: 1);
}
public override void Update(double deltaTime)
@@ -4119,10 +4203,10 @@ namespace Barotrauma
}
}
private static void DrawJobVariantItems(SpriteBatch spriteBatch, GUICustomComponent component, JobVariant jobPrefab, CharacterTeamType team, bool isPvPMode, int itemsPerRow)
private static void DrawJobVariantItems(SpriteBatch spriteBatch, GUICustomComponent component, JobVariant jobVariant, CharacterTeamType team, bool isPvPMode, int itemsPerRow)
{
var itemIdentifiers = jobPrefab.Prefab.JobItems[jobPrefab.Variant]
.Where(it => it.ShowPreview)
var allJobItems = jobVariant.Prefab.GetJobItems(jobVariant.Variant, it => it.ShowPreview);
var itemIdentifiers = allJobItems
.Select(it => it.GetItemIdentifier(team, isPvPMode))
.Distinct();
@@ -4162,7 +4246,7 @@ namespace Barotrauma
float iconScale = Math.Min(Math.Min(slotSize.X / icon.size.X, slotSize.Y / icon.size.Y), 2.0f) * 0.9f;
icon.Draw(spriteBatch, slotPos + slotSize.ToVector2() * 0.5f, scale: iconScale);
int count = jobPrefab.Prefab.JobItems[jobPrefab.Variant].Where(it => it.ShowPreview && it.ItemIdentifier == itemIdentifier).Sum(it => it.Amount);
int count = allJobItems.Where(it => it.GetItemIdentifier(team, isPvPMode) == itemIdentifier).Sum(it => it.Amount);
if (count > 1)
{
string itemCountText = "x" + count;
@@ -4352,57 +4436,103 @@ namespace Barotrauma
private bool OpenJobSelection(GUIComponent _, object __)
{
//recreate if resolution has changed
if (GameMain.GraphicsWidth != prevResolutionForJobSelectionFrame.X ||
GameMain.GraphicsHeight != prevResolutionForJobSelectionFrame.Y)
{
JobSelectionFrame = null;
}
prevResolutionForJobSelectionFrame = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
if (JobSelectionFrame != null)
{
JobSelectionFrame.Visible = true;
return true;
}
Point frameSize = new Point(characterInfoFrame.Rect.Width, (int)(characterInfoFrame.Rect.Height * 2 * 0.6f));
JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft)
{ AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - frameSize.X, characterInfoFrame.Rect.Bottom) }, style:"GUIFrameListBox");
var allJobs = JobPrefab.Prefabs.Where(jobPrefab => !jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0);
//find the jobs that aren't currently visible in the job list, create a preview of the first variant
var availableJobs =
allJobs.Where(jobPrefab => JobList.Content.Children.All(c => c.UserData is not JobVariant prefab || prefab.Prefab != jobPrefab))
.Select(j => new JobVariant(j, 0));
//find the jobs that are currently visible in the job list, create a preview of the variant chosen in the list
availableJobs = availableJobs.Concat(
allJobs.Where(jobPrefab => JobList.Content.Children.Any(c => (c.UserData is JobVariant prefab) && prefab.Prefab == jobPrefab))
.Select(j => (JobVariant)JobList.Content.FindChild(c => (c.UserData is JobVariant prefab) && prefab.Prefab == j).UserData));
availableJobs = availableJobs.ToList();
const int JobsPerRow = 3;
const int MaxRows = 4;
int rowCount = (int)Math.Ceiling(availableJobs.Count() / (float)JobsPerRow);
int jobButtonSize = GUI.IntScale(150);
const float listBoxRelativeSize = 0.95f;
Point frameSize = new Point(characterInfoFrame.Rect.Width, (int)(jobButtonSize * Math.Min(rowCount, MaxRows) / listBoxRelativeSize));
JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft), style: "GUIFrameListBox");
PositionJobSelectionFrame();
characterInfoFrame.RectTransform.SizeChanged += () =>
{
if (characterInfoFrame == null || JobSelectionFrame?.RectTransform == null) { return; }
Point size = new Point(characterInfoFrame.Rect.Width, (int)(characterInfoFrame.Rect.Height * 2 * 0.6f));
Point size = new Point(characterInfoFrame.Rect.Width, (int)(jobButtonSize * Math.Min(rowCount, MaxRows) / listBoxRelativeSize));
JobSelectionFrame.RectTransform.Resize(size);
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - size.X, characterInfoFrame.Rect.Bottom);
PositionJobSelectionFrame();
};
void PositionJobSelectionFrame()
{
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - JobSelectionFrame.Rect.Width, characterInfoFrame.Rect.Bottom);
if (characterInfoFrame.Rect.Bottom + JobSelectionFrame.Rect.Height > GameMain.GraphicsHeight)
{
//move to the left side of the info frame if the bottom goes below the screen
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.X - JobSelectionFrame.Rect.Width, characterInfoFrame.Rect.Bottom - JobSelectionFrame.Rect.Height / 2);
if (JobSelectionFrame.Rect.X < 0)
{
//scale if goes outside the screen horizontally
JobSelectionFrame.RectTransform.Resize(new Point(characterInfoFrame.Rect.X, JobSelectionFrame.Rect.Height));
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.X - JobSelectionFrame.Rect.Width, JobSelectionFrame.RectTransform.AbsoluteOffset.Y);
}
}
}
new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), JobSelectionFrame.RectTransform, anchor: Anchor.Center), style: "OuterGlow", color: Color.Black)
{
UserData = "outerglow",
CanBeFocused = false
};
var rows = new GUILayoutGroup(new RectTransform(Vector2.One, JobSelectionFrame.RectTransform)) { Stretch = true };
var row = new GUILayoutGroup(new RectTransform(Vector2.One, rows.RectTransform), true);
var jobSelectionList = new GUIListBox(new RectTransform(Vector2.One * listBoxRelativeSize, JobSelectionFrame.RectTransform, Anchor.Center), style: "GUIFrameListBox")
{
Padding = Vector4.One * GUI.IntScale(10)
};
var row = new GUILayoutGroup(new RectTransform(new Point(jobSelectionList.Content.Rect.Width, jobButtonSize), jobSelectionList.Content.RectTransform), isHorizontal: true)
{
Stretch = true
};
GUIButton jobButton = null;
var availableJobs = JobPrefab.Prefabs.Where(jobPrefab =>
!jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0 && JobList.Content.Children.All(c => c.UserData is not JobVariant prefab || prefab.Prefab != jobPrefab)
).Select(j => new JobVariant(j, 0));
availableJobs = availableJobs.Concat(
JobPrefab.Prefabs.Where(jobPrefab =>
!jobPrefab.HiddenJob && jobPrefab.MaxNumber > 0 && JobList.Content.Children.Any(c => (c.UserData is JobVariant prefab) && prefab.Prefab == jobPrefab)
).Select(j => (JobVariant)JobList.Content.FindChild(c => (c.UserData is JobVariant prefab) && prefab.Prefab == j).UserData));
availableJobs = availableJobs.ToList();
int itemsInRow = 0;
foreach (var jobPrefab in availableJobs)
{
if (itemsInRow >= 3)
if (itemsInRow >= JobsPerRow)
{
row = new GUILayoutGroup(new RectTransform(Vector2.One, rows.RectTransform), true);
row = new GUILayoutGroup(new RectTransform(new Point(jobSelectionList.Content.Rect.Width, jobButtonSize), jobSelectionList.Content.RectTransform), isHorizontal: true)
{
Stretch = true
};
itemsInRow = 0;
}
jobButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), row.RectTransform), style: "ListBoxElementSquare")
jobButton = new GUIButton(new RectTransform(new Point(jobButtonSize), row.RectTransform), style: "ListBoxElementSquare")
{
UserData = jobPrefab,
OnClicked = (btn, usdt) =>
@@ -4725,6 +4855,7 @@ namespace Barotrauma
public void RefreshStartButtonVisibility()
{
bool campaignActive = GameMain.GameSession?.GameMode is CampaignMode;
if (CampaignSetupUI != null && CampaignSetupFrame is { Visible: true })
{
//setting up a campaign -> start button only visible if we're in the "new game" tab (load game menu not visible)
@@ -4736,7 +4867,6 @@ namespace Barotrauma
else
{
//if a campaign is currently running, we must show the start button to allow continuing
bool campaignActive = GameMain.GameSession?.GameMode is CampaignMode;
StartButton.Visible =
(SelectedMode != GameModePreset.MultiPlayerCampaign || campaignActive) &&
!GameMain.Client.GameStarted && GameMain.Client.HasPermission(ClientPermissions.ManageRound);
@@ -4752,6 +4882,14 @@ namespace Barotrauma
? TextManager.Get("DisembarkPointsNotValid")
: string.Empty;
}
StartButton.IgnoreLayoutGroups = !StartButton.Visible;
//can end the round if round is running
EndButton.Visible =
!StartButton.Visible &&
GameMain.Client is { GameStarted: true } &&
(GameMain.Client.HasPermission(ClientPermissions.ManageRound) || (campaignActive && GameMain.Client.HasPermission(ClientPermissions.ManageCampaign)));
EndButton.IgnoreLayoutGroups = !EndButton.Visible;
}
public void RefreshChatrow()
@@ -4999,7 +5137,7 @@ namespace Barotrauma
private static GUIButton CreateJobVariantButton(JobVariant jobPrefab, int variantIndex, int variantCount, GUIComponent slot)
{
float relativeSize = 0.15f;
float relativeSize = 0.18f;
var btn = new GUIButton(new RectTransform(new Vector2(relativeSize), slot.RectTransform, Anchor.TopCenter, scaleBasis: ScaleBasis.BothHeight)
{ RelativeOffset = new Vector2(relativeSize * 1.3f * (variantIndex - (variantCount - 1) / 2.0f), 0.02f) },
@@ -5375,6 +5513,7 @@ namespace Barotrauma
text: sub.DisplayName)
{
UserData = "nametext",
ToolTip = sub.Description,
CanBeFocused = false
};

View File

@@ -2568,9 +2568,34 @@ namespace Barotrauma
{
outpostTagsBox.Text = MainSub.Info.OutpostTags.ConvertToString();
}
outpostTagsGroup.RectTransform.MaxSize = outpostTagsBox.RectTransform.MaxSize;
var triggerMissionTagsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), outpostSettingsContainer.RectTransform), isHorizontal: true)
{
Stretch = true
};
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), triggerMissionTagsGroup.RectTransform),
TextManager.Get("outpost.triggeroutpostmissionevents"), textAlignment: Alignment.CenterLeft, wrap: true);
var triggerMissionTagsBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), triggerMissionTagsGroup.RectTransform))
{
OnEnterPressed = (GUITextBox textBox, string text) =>
{
MainSub.Info.TriggerOutpostMissionEvents = text.ToIdentifiers().ToImmutableHashSet();
return true;
},
ToolTip = TextManager.Get("outpost.triggeroutpostmissionevents.tooltip"),
OverflowClip = true,
Text = "default"
};
triggerMissionTagsBox.OnDeselected += (textbox, _) =>
{
MainSub.Info.TriggerOutpostMissionEvents = triggerMissionTagsBox.Text.ToIdentifiers().ToImmutableHashSet();
};
if (MainSub.Info.TriggerOutpostMissionEvents != null)
{
triggerMissionTagsBox.Text = MainSub.Info.TriggerOutpostMissionEvents.ConvertToString();
}
triggerMissionTagsGroup.RectTransform.MaxSize = triggerMissionTagsBox.RectTransform.MaxSize;
//---------------------------------------
var enemySubmarineSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform))
@@ -5794,12 +5819,14 @@ namespace Barotrauma
{
foreach (LightComponent lightComponent in item.GetComponents<LightComponent>())
{
lightComponent.Light.Color =
(item.body == null || item.body.Enabled || item.ParentInventory is ItemInventory { Container.HideItems: false }) &&
bool visibleInContainer = item.FindParentInventory(static it => it is ItemInventory { Container.HideItems: true }) == null;
lightComponent.Light.Color =
((item.body == null || !item.body.Enabled) && !visibleInContainer) ||
/*the light is only visible when worn -> can't be visible in the editor*/
lightComponent.Parent is not Wearable ?
lightComponent.LightColor :
Color.Transparent;
lightComponent.Parent is Wearable ?
Color.Transparent :
lightComponent.LightColor;
lightComponent.Light.LightSpriteEffect = lightComponent.Item.SpriteEffects;
}
}

View File

@@ -811,9 +811,14 @@ namespace Barotrauma
DropdownEnum(leftColumn, v => TextManager.Get($"InteractionLabels.{v}"), null, unsavedConfig.InteractionLabelDisplayMode, v => unsavedConfig.InteractionLabelDisplayMode = v);
Label(rightColumn, TextManager.Get("HUDScale"), GUIStyle.SubHeadingFont);
Slider(rightColumn, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.HUDScale, v => unsavedConfig.Graphics.HUDScale = v);
// Restricts the max scale to 110% on 16:9, and to 100% on 4:3.
// Higher scales are allowed for wide aspect ratios, up to 125%.
//float scalar = MathUtils.InverseLerp(0f, 1.0f, 0.4f - GUI.AspectRatioDifference);
//float maxScale = MathHelper.Lerp(1.0f, 1.25f, scalar);
const float maxScale = 1.25f;
Slider(rightColumn, (0.75f, maxScale), 51, Percentage, unsavedConfig.Graphics.HUDScale, v => unsavedConfig.Graphics.HUDScale = v);
Label(rightColumn, TextManager.Get("InventoryScale"), GUIStyle.SubHeadingFont);
Slider(rightColumn, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Slider(rightColumn, (0.75f, maxScale), 51, Percentage, unsavedConfig.Graphics.InventoryScale, v => unsavedConfig.Graphics.InventoryScale = v);
Label(rightColumn, TextManager.Get("TextScale"), GUIStyle.SubHeadingFont);
Slider(rightColumn, (0.75f, 1.25f), 51, Percentage, unsavedConfig.Graphics.TextScale, v => unsavedConfig.Graphics.TextScale = v);
Spacer(rightColumn);

View File

@@ -104,19 +104,22 @@ namespace Barotrauma.Sounds
public virtual SoundChannel Play(float gain, float range, Vector2 position, bool muffle = false)
{
LogWarningIfStillLoading();
return new SoundChannel(this, gain, new Vector3(position.X, position.Y, 0.0f), 1.0f, range * 0.4f, range, "default", muffle);
if (Owner.CountPlayingInstances(this) >= MaxSimultaneousInstances) { return null; }
return new SoundChannel(this, gain, new Vector3(position.X, position.Y, 0.0f), 1.0f, range * 0.4f, range, SoundManager.SoundCategoryDefault, muffle);
}
public virtual SoundChannel Play(float gain, float range, float freqMult, Vector2 position, bool muffle = false)
{
LogWarningIfStillLoading();
return new SoundChannel(this, gain, new Vector3(position.X, position.Y, 0.0f), freqMult, range * 0.4f, range, "default", muffle);
if (Owner.CountPlayingInstances(this) >= MaxSimultaneousInstances) { return null; }
return new SoundChannel(this, gain, new Vector3(position.X, position.Y, 0.0f), freqMult, range * 0.4f, range, SoundManager.SoundCategoryDefault, muffle);
}
public virtual SoundChannel Play(Vector3? position, float gain, float freqMult = 1.0f, bool muffle = false)
{
LogWarningIfStillLoading();
return new SoundChannel(this, gain, position, freqMult, BaseNear, BaseFar, "default", muffle);
if (Owner.CountPlayingInstances(this) >= MaxSimultaneousInstances) { return null; }
return new SoundChannel(this, gain, position, freqMult, BaseNear, BaseFar, SoundManager.SoundCategoryDefault, muffle);
}
public virtual SoundChannel Play(float gain)
@@ -129,7 +132,7 @@ namespace Barotrauma.Sounds
return Play(BaseGain);
}
public virtual SoundChannel Play(float? gain, string category)
public virtual SoundChannel Play(float? gain, Identifier category)
{
if (Owner.CountPlayingInstances(this) >= MaxSimultaneousInstances) { return null; }
return new SoundChannel(this, gain ?? BaseGain, null, 1.0f, BaseNear, BaseFar, category);

View File

@@ -398,8 +398,8 @@ namespace Barotrauma.Sounds
}
}
private string category;
public string Category
private Identifier category;
public Identifier Category
{
get { return category; }
set
@@ -483,7 +483,7 @@ namespace Barotrauma.Sounds
}
}
public SoundChannel(Sound sound, float gain, Vector3? position, float freqMult, float near, float far, string category, bool muffle = false)
public SoundChannel(Sound sound, float gain, Vector3? position, float freqMult, float near, float far, Identifier category, bool muffle = false)
{
Sound = sound;

View File

@@ -12,11 +12,11 @@ namespace Barotrauma.Sounds
class SoundManager : IDisposable
{
public const int SourceCount = 32;
public const string SoundCategoryDefault = "default";
public const string SoundCategoryUi = "ui";
public const string SoundCategoryWaterAmbience = "waterambience";
public const string SoundCategoryMusic = "music";
public const string SoundCategoryVoip = "voip";
public static readonly Identifier SoundCategoryDefault = "default".ToIdentifier();
public static readonly Identifier SoundCategoryUi = "ui".ToIdentifier();
public static readonly Identifier SoundCategoryWaterAmbience = "waterambience".ToIdentifier();
public static readonly Identifier SoundCategoryMusic = "music".ToIdentifier();
public static readonly Identifier SoundCategoryVoip = "voip".ToIdentifier();
public bool Disabled
{
@@ -201,7 +201,7 @@ namespace Barotrauma.Sounds
}
}
private readonly Dictionary<string, CategoryModifier> categoryModifiers = new Dictionary<string, CategoryModifier>();
private readonly Dictionary<Identifier, CategoryModifier> categoryModifiers = new Dictionary<Identifier, CategoryModifier>();
public SoundManager()
{
@@ -548,10 +548,9 @@ namespace Barotrauma.Sounds
}
}
public void SetCategoryGainMultiplier(string category, float gain, int index=0)
public void SetCategoryGainMultiplier(Identifier category, float gain, int index=0)
{
if (Disabled) { return; }
category = category.ToLower();
lock (categoryModifiers)
{
if (!categoryModifiers.ContainsKey(category))
@@ -579,10 +578,9 @@ namespace Barotrauma.Sounds
}
}
public float GetCategoryGainMultiplier(string category, int index = -1)
public float GetCategoryGainMultiplier(Identifier category, int index = -1)
{
if (Disabled) { return 0.0f; }
category = category.ToLower();
lock (categoryModifiers)
{
if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return 1.0f; }
@@ -602,11 +600,10 @@ namespace Barotrauma.Sounds
}
}
public void SetCategoryMuffle(string category, bool muffle)
public void SetCategoryMuffle(Identifier category, bool muffle)
{
if (Disabled) { return; }
category = category.ToLower();
lock (categoryModifiers)
{
if (!categoryModifiers.ContainsKey(category))
@@ -627,18 +624,17 @@ namespace Barotrauma.Sounds
{
if (playingChannels[i][j] != null && playingChannels[i][j].IsPlaying)
{
if (playingChannels[i][j]?.Category.ToLower() == category) { playingChannels[i][j].Muffled = muffle; }
if (playingChannels[i][j]?.Category == category) { playingChannels[i][j].Muffled = muffle; }
}
}
}
}
}
public bool GetCategoryMuffle(string category)
public bool GetCategoryMuffle(Identifier category)
{
if (Disabled) { return false; }
category = category.ToLower();
lock (categoryModifiers)
{
if (categoryModifiers == null || !categoryModifiers.TryGetValue(category, out CategoryModifier categoryModifier)) { return false; }

View File

@@ -191,9 +191,12 @@ namespace Barotrauma
{
if (volume < 0.01f) { return; }
if (chn is not null) { waterAmbienceChannels.Remove(chn); }
chn = sound.Play(volume, "waterambience");
chn.Looping = true;
waterAmbienceChannels.Add(chn);
chn = sound.Play(volume, SoundManager.SoundCategoryWaterAmbience);
if (chn != null)
{
chn.Looping = true;
waterAmbienceChannels.Add(chn);
}
}
else
{
@@ -307,6 +310,7 @@ namespace Barotrauma
if (flowSoundChannels[i] == null || !flowSoundChannels[i].IsPlaying)
{
flowSoundChannels[i] = FlowSounds[i].Sound.Play(1.0f, FlowSoundRange, soundPos);
if (flowSoundChannels[i] == null) { continue; }
flowSoundChannels[i].Looping = true;
}
flowSoundChannels[i].Gain = Math.Max(flowVolumeRight[i], flowVolumeLeft[i]);
@@ -687,7 +691,22 @@ namespace Barotrauma
}
LogCurrentMusic();
updateMusicTimer = UpdateMusicInterval;
updateMusicTimer = UpdateMusicInterval;
if (mainTrack != null)
{
updateMusicTimer += mainTrack.MinimumPlayDuration;
}
}
bool muteBackgroundMusic = false;
for (int i = 0; i < SoundManager.SourceCount; i++)
{
SoundChannel playingSoundChannel = GameMain.SoundManager.GetSoundChannelFromIndex(SoundManager.SourcePoolIndex.Default, i);
if (playingSoundChannel is { MuteBackgroundMusic: true, IsPlaying: true })
{
muteBackgroundMusic = true;
break;
}
}
bool muteBackgroundMusic = false;
@@ -734,7 +753,7 @@ namespace Barotrauma
DisposeMusicChannel(i);
currentMusic[i] = targetMusic[i];
musicChannel[i] = currentMusic[i].Sound.Play(0.0f, i == noiseLoopIndex ? "default" : "music");
musicChannel[i] = currentMusic[i].Sound.Play(0.0f, i == noiseLoopIndex ? SoundManager.SoundCategoryDefault : SoundManager.SoundCategoryMusic);
if (targetMusic[i].ContinueFromPreviousTime)
{
musicChannel[i].StreamSeekPos = targetMusic[i].PreviousTime;
@@ -753,7 +772,7 @@ namespace Barotrauma
if (musicChannel[i] == null || !musicChannel[i].IsPlaying)
{
musicChannel[i]?.Dispose();
musicChannel[i] = currentMusic[i].Sound.Play(0.0f, i == noiseLoopIndex ? "default" : "music");
musicChannel[i] = currentMusic[i].Sound.Play(0.0f, i == noiseLoopIndex ? SoundManager.SoundCategoryDefault : SoundManager.SoundCategoryMusic);
musicChannel[i].Looping = true;
}
float targetGain = targetMusic[i].Volume;
@@ -874,6 +893,51 @@ namespace Barotrauma
}
Submarine targetSubmarine = Character.Controlled?.Submarine;
float intensity = (GameMain.GameSession?.EventManager?.MusicIntensity ?? 0) * 100.0f;
float enemyDistThreshold = 5000.0f;
if (targetSubmarine != null)
{
enemyDistThreshold = Math.Max(enemyDistThreshold, Math.Max(targetSubmarine.Borders.Width, targetSubmarine.Borders.Height) * 2.0f);
}
List<Character> monsterMusicCharacters = new List<Character>();
foreach (Character character in Character.CharacterList)
{
if (character.IsDead || !character.Enabled) { continue; }
if (character.AIController is not EnemyAIController { Enabled: true } enemyAI) { continue; }
if (!enemyAI.AttackHumans && !enemyAI.AttackRooms) { continue; }
bool specificMonsterMusicAvailable =
musicClips.Any(m => IsSuitableMusicClip(m, character.Params.MusicType, intensity));
if (specificMonsterMusicAvailable)
{
float maxDistSqr = MathF.Pow(enemyDistThreshold * character.Params.MusicRangeMultiplier, 2);
if (targetSubmarine != null)
{
if (Vector2.DistanceSquared(character.WorldPosition, targetSubmarine.WorldPosition) < maxDistSqr)
{
monsterMusicCharacters.Add(character);
}
}
else if (Character.Controlled != null)
{
if (Vector2.DistanceSquared(character.WorldPosition, Character.Controlled.WorldPosition) < maxDistSqr)
{
monsterMusicCharacters.Add(character);
}
}
}
}
if (monsterMusicCharacters.Any())
{
Character chosenCharacter = monsterMusicCharacters.GetRandomByWeight(c => c.Params.MusicCommonness, Rand.RandSync.Unsynced);
return chosenCharacter.Params.MusicType;
}
if (targetSubmarine != null && targetSubmarine.AtDamageDepth)
{
return "deep".ToIdentifier();
@@ -898,41 +962,6 @@ namespace Barotrauma
if (totalArea > 0.0f && floodedArea / totalArea > 0.25f) { return "flooded".ToIdentifier(); }
}
float intensity = (GameMain.GameSession?.EventManager?.MusicIntensity ?? 0) * 100.0f;
bool anyMonsterMusicAvailable =
musicClips.Any(m => IsSuitableMusicClip(m, "monster".ToIdentifier(), intensity) || IsSuitableMusicClip(m, "monsterambience".ToIdentifier(), intensity));
if (anyMonsterMusicAvailable)
{
float enemyDistThreshold = 5000.0f;
if (targetSubmarine != null)
{
enemyDistThreshold = Math.Max(enemyDistThreshold, Math.Max(targetSubmarine.Borders.Width, targetSubmarine.Borders.Height) * 2.0f);
}
foreach (Character character in Character.CharacterList)
{
if (character.IsDead || !character.Enabled) { continue; }
if (character.AIController is not EnemyAIController { Enabled: true } enemyAI) { continue; }
if (!enemyAI.AttackHumans && !enemyAI.AttackRooms) { continue; }
if (targetSubmarine != null)
{
if (Vector2.DistanceSquared(character.WorldPosition, targetSubmarine.WorldPosition) < enemyDistThreshold * enemyDistThreshold)
{
return "monster".ToIdentifier();
}
}
else if (Character.Controlled != null)
{
if (Vector2.DistanceSquared(character.WorldPosition, Character.Controlled.WorldPosition) < enemyDistThreshold * enemyDistThreshold)
{
return "monster".ToIdentifier();
}
}
}
}
if (GameMain.GameSession != null)
{
if (Submarine.Loaded != null && Level.Loaded != null && Submarine.MainSub != null && Submarine.MainSub.AtEndExit)
@@ -1012,7 +1041,7 @@ namespace Barotrauma
{
GUISound.GUISoundPrefabs
.Where(s => s.Type == soundType)
.GetRandomUnsynced()?.Sound?.Play(null, "ui");
.GetRandomUnsynced()?.Sound?.Play(null, SoundManager.SoundCategoryUi);
}
public static void PlayUISound(GUISoundType? soundType)

View File

@@ -248,6 +248,10 @@ namespace Barotrauma
public readonly bool StartFromRandomTime;
public readonly bool ContinueFromPreviousTime;
public int PreviousTime;
/// <summary>
/// The music is forced to play at least for this long when it triggers, even if the situation changes and makes the music no longer suitable.
/// </summary>
public readonly float MinimumPlayDuration;
public BackgroundMusic(ContentXElement element, SoundsFile file) : base(element, file, stream: true)
{
@@ -260,7 +264,8 @@ namespace Barotrauma
ForceIntensityTrack = element.GetAttributeFloat(nameof(ForceIntensityTrack), 0.0f);
}
StartFromRandomTime = element.GetAttributeBool(nameof(StartFromRandomTime), false);
ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false);
ContinueFromPreviousTime = element.GetAttributeBool(nameof(ContinueFromPreviousTime), false);
MinimumPlayDuration = element.GetAttributeFloat(nameof(MinimumPlayDuration), 0.0f);
}
}

View File

@@ -76,7 +76,7 @@ namespace Barotrauma.Sounds
soundChannel = null;
}
}
chn = new SoundChannel(this, gain, null, 1.0f, 1.0f, 3.0f, "video", false);
chn = new SoundChannel(this, gain, null, 1.0f, 1.0f, 3.0f, "video".ToIdentifier(), false);
lock (mutex)
{
soundChannel = chn;

View File

@@ -81,7 +81,7 @@ namespace Barotrauma.Sounds
soundChannel = null;
SoundChannel chn = new SoundChannel(this, 1.0f, null, 1.0f, 0.4f, 1.0f, "voip", false);
SoundChannel chn = new SoundChannel(this, 1.0f, null, 1.0f, 0.4f, 1.0f, SoundManager.SoundCategoryVoip, false);
soundChannel = chn;
Gain = 1.0f;
}

View File

@@ -23,7 +23,9 @@ namespace Barotrauma.SpriteDeformations
class CustomDeformation : SpriteDeformation
{
private List<Vector2[]> deformRows = new List<Vector2[]>();
private readonly List<Vector2[]> deformRows = new List<Vector2[]>();
private readonly Vector2[,] flippedDeformation;
private CustomDeformationParams CustomDeformationParams => Params as CustomDeformationParams;
@@ -81,40 +83,25 @@ namespace Barotrauma.SpriteDeformations
//construct an array for the desired resolution,
//interpolating values if the resolution configured in the xml is smaller
//deformation = new Vector2[Resolution.X, Resolution.Y];
float divX = 1.0f / Resolution.X, divY = 1.0f / Resolution.Y;
int newWidth = Resolution.X, newHeight = Resolution.Y;
Deformation = MathUtils.ResizeVector2Array(configDeformation, newWidth, newHeight);
flippedDeformation = new Vector2[Resolution.X, Resolution.Y];
for (int x = 0; x < Resolution.X; x++)
{
float normalizedX = x / (float)(Resolution.X - 1);
for (int y = 0; y < Resolution.Y; y++)
{
float normalizedY = y / (float)(Resolution.Y - 1);
Point indexTopLeft = new Point(
Math.Min((int)Math.Floor(normalizedX * (configDeformation.GetLength(0) - 1)), configDeformation.GetLength(0) - 1),
Math.Min((int)Math.Floor(normalizedY * (configDeformation.GetLength(1) - 1)), configDeformation.GetLength(1) - 1));
Point indexBottomRight = new Point(
Math.Min(indexTopLeft.X + 1, configDeformation.GetLength(0) - 1),
Math.Min(indexTopLeft.Y + 1, configDeformation.GetLength(1) - 1));
Vector2 deformTopLeft = configDeformation[indexTopLeft.X, indexTopLeft.Y];
Vector2 deformTopRight = configDeformation[indexBottomRight.X, indexTopLeft.Y];
Vector2 deformBottomLeft = configDeformation[indexTopLeft.X, indexBottomRight.Y];
Vector2 deformBottomRight = configDeformation[indexBottomRight.X, indexBottomRight.Y];
Deformation[x, y] = Vector2.Lerp(
Vector2.Lerp(deformTopLeft, deformTopRight, (normalizedX % divX) / divX),
Vector2.Lerp(deformBottomLeft, deformBottomRight, (normalizedX % divX) / divX),
(normalizedY % divY) / divY);
flippedDeformation[x, y] = Deformation[Resolution.X - x - 1, y]; // read the rows from right to left
}
}
}
protected override void GetDeformation(out Vector2[,] deformation, out float multiplier, bool inverse)
protected override void GetDeformation(out Vector2[,] deformation, out float multiplier, bool flippedHorizontally, bool inverseY)
{
deformation = Deformation;
deformation = flippedHorizontally ? flippedDeformation : Deformation;
multiplier = CustomDeformationParams.Frequency <= 0.0f ?
CustomDeformationParams.Amplitude :
(float)Math.Sin(inverse ? -phase : phase) * CustomDeformationParams.Amplitude;
(float)Math.Sin(inverseY ? -phase : phase) * CustomDeformationParams.Amplitude;
multiplier *= Params.Strength;
}

View File

@@ -54,7 +54,7 @@ namespace Barotrauma.SpriteDeformations
phase = Rand.Range(0.0f, MathHelper.TwoPi);
}
protected override void GetDeformation(out Vector2[,] deformation, out float multiplier, bool inverse)
protected override void GetDeformation(out Vector2[,] deformation, out float multiplier, bool flippedHorizontally, bool inverseY = false)
{
deformation = this.deformation;
multiplier = InflateParams.Frequency <= 0.0f ? InflateParams.Scale : (float)(Math.Sin(phase) + 1.0f) / 2.0f * InflateParams.Scale;

View File

@@ -56,7 +56,7 @@ namespace Barotrauma.SpriteDeformations
public JointBendDeformation(XElement element) : base(element, new JointBendDeformationParams(element)) { }
protected override void GetDeformation(out Vector2[,] deformation, out float multiplier, bool inverse)
protected override void GetDeformation(out Vector2[,] deformation, out float multiplier, bool flippedHorizontally, bool inverseY = false)
{
deformation = Deformation;
multiplier = 1.0f;// this.multiplier;

Some files were not shown because too many files have changed in this diff Show More