Unstable 1.8.4.0

This commit is contained in:
Markus Isberg
2025-03-12 12:56:27 +00:00
parent a4c3e868e4
commit a4a3427e4e
627 changed files with 29860 additions and 10018 deletions
@@ -28,7 +28,7 @@ namespace Barotrauma
public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame;
private GUIFrame crewArea;
private GUILayoutGroup crewArea;
private GUIListBox crewList;
private float crewListOpenState;
private bool _isCrewMenuOpen = true;
@@ -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;
@@ -93,12 +91,30 @@ namespace Barotrauma
#region Crew Area
crewArea = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), style: null, color: Color.Transparent)
crewArea = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), childAnchor: Anchor.TopCenter)
{
CanBeFocused = false
Stretch = true
};
crewArea.RectTransform.NonScaledSize = HUDLayoutSettings.CrewArea.Size;
for (int i = 0; i < 2; i++)
{
CharacterTeamType teamId = i == 0 ? CharacterTeamType.Team1 : CharacterTeamType.Team2;
var nameText = new GUITextBlock(new RectTransform(new Point(crewArea.Rect.Width - GUI.IntScale(10), GUI.IntScale(30)), crewArea.RectTransform), CombatMission.GetTeamName(teamId), textColor: CombatMission.GetTeamColor(teamId))
{
ForceUpperCase = ForceUpperCase.Yes,
TextGetter = () => CombatMission.GetTeamName(teamId),
Visible = false,
IgnoreLayoutGroups = true,
UserData = teamId
};
var teamIcon = new GUIImage(new RectTransform(Vector2.One, nameText.RectTransform, Anchor.CenterLeft, scaleBasis: ScaleBasis.BothHeight), style: i == 0 ? "CoalitionIcon" : "SeparatistIcon")
{
Color = nameText.TextColor
};
nameText.Padding = new Vector4(teamIcon.Rect.Width + nameText.Padding.X, nameText.Padding.Y, nameText.Padding.Z, nameText.Padding.W);
}
// AbsoluteOffset is set in UpdateProjectSpecific based on crewListOpenState
crewList = new GUIListBox(new RectTransform(Vector2.One, crewArea.RectTransform), style: null, isScrollBarOnDefaultSide: false)
{
@@ -177,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
@@ -290,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;
@@ -562,9 +592,19 @@ namespace Barotrauma
public bool CharacterClicked(GUIComponent component, object selection)
{
if (!AllowCharacterSwitch) { return false; }
if (!(selection is Character character) || character.IsDead || character.IsUnconscious) { return false; }
if (selection is not Character character || character.IsDead || character.IsUnconscious) { return false; }
if (!character.IsOnPlayerTeam) { return false; }
if (GameMain.IsMultiplayer)
{
if (Character.Controlled == null)
{
Camera cam = Screen.Selected.Cam;
cam.Position = character.DrawPosition;
}
return true;
}
SelectCharacter(character);
if (GUI.KeyboardDispatcher.Subscriber == crewList) { GUI.KeyboardDispatcher.Subscriber = null; }
return true;
@@ -631,10 +671,9 @@ namespace Barotrauma
{
if (crewList != this.crewList) { return; }
if (draggedElementData is not Character) { return; }
if (!IsSinglePlayer) { return; }
if (crewList.HasDraggedElementIndexChanged)
{
UpdateCrewListIndices();
if (IsSinglePlayer) { UpdateCrewListIndices(); }
}
else
{
@@ -645,10 +684,13 @@ namespace Barotrauma
private void ResetCrewListIndex(Character c)
{
if (c?.Info == null) { return; }
c.Info.CrewListIndex = -1;
UpdateCrewListIndices();
//default to the bottom of the list
c.Info.CrewListIndex = int.MaxValue;
}
/// <summary>
/// Refresh the <see cref="CharacterInfo.CrewListIndex"/> of the characters based on their order in the crew list
/// </summary>
private void UpdateCrewListIndices()
{
if (crewList == null) { return; }
@@ -661,20 +703,23 @@ namespace Barotrauma
}
}
/// <summary>
/// Order the crew list according to the characters' <see cref="CharacterInfo.CrewListIndex"/>
/// </summary>
private void SortCrewList()
{
if (crewList == null) { return; }
crewList.Content.RectTransform.SortChildren((x, y) =>
{
var infoX = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
var infoY = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
if (infoX.HasValue)
int? index1 = (x.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
int? index2 = (y.GUIComponent.UserData as Character)?.Info?.CrewListIndex;
if (index1.HasValue)
{
return infoY.HasValue ? infoX.Value.CompareTo(infoY.Value) : -1;
return index2.HasValue ? index1.Value.CompareTo(index2.Value) : -1;
}
else
{
return infoY.HasValue ? 1 : 0;
return index2.HasValue ? 1 : 0;
}
});
UpdateCrewListIndices();
@@ -1312,6 +1357,7 @@ namespace Barotrauma
{
if (ConversationAction.IsDialogOpen) { return; }
if (!AllowCharacterSwitch) { return; }
if (character == null || character.Removed) { return; }
//make the previously selected character wait in place for some time
//(so they don't immediately start idling and walking away from their station)
var aiController = Character.Controlled?.AIController;
@@ -1323,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)
@@ -1340,7 +1396,7 @@ namespace Barotrauma
if (index > lastIndex) { index = 0; }
if (index < 0) { index = lastIndex; }
if ((crewList.Content.GetChild(index)?.UserData as Character)?.IsOnPlayerTeam ?? false)
if (crewList.Content.GetChild(index)?.UserData is Character { IsOnPlayerTeam: true, Removed: false })
{
return index;
}
@@ -1635,6 +1691,18 @@ namespace Barotrauma
{
crewArea.Visible = characters.Count > 0 && CharacterHealth.OpenHealthWindow == null;
CharacterTeamType myTeam = Character.Controlled?.TeamID ?? GameMain.Client?.MyClient?.TeamID ?? CharacterTeamType.Team1;
if (GameMain.GameSession?.GameMode is PvPMode)
{
var team1Text = crewArea.GetChildByUserData(CharacterTeamType.Team1);
team1Text.Visible = myTeam == CharacterTeamType.Team1;
team1Text.IgnoreLayoutGroups = !team1Text.Visible;
var team2Text = crewArea.GetChildByUserData(CharacterTeamType.Team2);
team2Text.Visible = myTeam == CharacterTeamType.Team2;
team2Text.IgnoreLayoutGroups = !team2Text.Visible;
}
foreach (GUIComponent characterComponent in crewList.Content.Children)
{
if (characterComponent.UserData is Character character)
@@ -1645,7 +1713,7 @@ namespace Barotrauma
continue;
}
characterComponent.Visible = Character.Controlled == null || Character.Controlled.TeamID == character.TeamID;
characterComponent.Visible = myTeam == character.TeamID;
if (character.TeamID == CharacterTeamType.FriendlyNPC && Character.Controlled != null &&
(character.CurrentHull == Character.Controlled.CurrentHull || Vector2.DistanceSquared(Character.Controlled.WorldPosition, character.WorldPosition) < 500.0f * 500.0f))
{
@@ -1873,7 +1941,7 @@ namespace Barotrauma
{
get
{
if (GameMain.GameSession?.CrewManager == null)
if (GameMain.GameSession?.CrewManager == null || Screen.Selected is { IsEditor: true })
{
return false;
}
@@ -2010,7 +2078,7 @@ namespace Barotrauma
// Character context works differently to others as we still use the "basic" command interface,
// but the order will be automatically assigned to this character
isContextual = forceContextual;
if (entityContext is Character character)
if (entityContext is Character { Info: not null } character)
{
characterContext = character;
itemContext = null;
@@ -2098,7 +2166,7 @@ namespace Barotrauma
CreateNodeConnectors();
if (Character.Controlled != null)
{
Character.Controlled.dontFollowCursor = true;
Character.Controlled.FollowCursor = false;
}
HintManager.OnShowCommandInterface();
@@ -2242,7 +2310,7 @@ namespace Barotrauma
returnNodeHotkey = expandNodeHotkey = Keys.None;
if (Character.Controlled != null)
{
Character.Controlled.dontFollowCursor = false;
Character.Controlled.FollowCursor = true;
}
}
@@ -2511,7 +2579,7 @@ namespace Barotrauma
// --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) &&
subItems.Find(i => i.HasTag(Tags.NavTerminal) && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
nav.GetComponent<Steering>() is Steering { HasPower: true } steering)
{
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);
AddOrderNode(order);
@@ -2625,9 +2693,9 @@ namespace Barotrauma
bool IsNonDuplicateOrder(Order order) => IsNonDuplicateOrderPrefab(order.Prefab, order.Option);
bool IsNonDuplicateOrderPrefab(OrderPrefab orderPrefab, Identifier option = default)
{
return characterContext == null || (option.IsEmpty ?
return characterContext == null || (characterContext.CurrentOrders != null && (option.IsEmpty ?
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier) :
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi.Option == option));
characterContext.CurrentOrders.None(oi => oi?.Identifier == orderPrefab?.Identifier && oi?.Option == option)));
}
void AddOrderNodeWithIdentifier(string identifier)
{
@@ -3000,7 +3068,7 @@ namespace Barotrauma
{
optionElement.OnSecondaryClicked = (button, _) => CreateAssignmentNodes(button);
}
var colorMultiplier = characters.Any(c => c.CurrentOrders.Any(o => o != null &&
var colorMultiplier = characters.Any(c => c.CurrentOrders != null && c.CurrentOrders.Any(o => o != null &&
o.Identifier == userData.Order.Identifier &&
o.TargetEntity == userData.Order.TargetEntity)) ? 0.5f : 1f;
CreateNodeIcon(Vector2.One, optionElement.RectTransform, item.Prefab.MinimapIcon ?? order.SymbolSprite, order.Color * colorMultiplier, tooltip: item.Name);
@@ -3654,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)
@@ -3780,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;
}
}
}