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
@@ -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)
@@ -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;
}
}
}
@@ -366,7 +366,7 @@ namespace Barotrauma
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType, npc);
CampaignUI.UpgradeStore?.RequestRefresh();
CampaignUI.UpgradeStore?.RequestRefresh(refreshUpgrades: true);
break;
}
@@ -395,13 +395,15 @@ namespace Barotrauma
protected void TryEndRoundWithFuelCheck(Action onConfirm, Action onReturnToMapScreen)
{
if (Submarine.MainSub == null) { return; }
Submarine.MainSub.CheckFuel();
bool lowFuel = Submarine.MainSub.Info.LowFuel;
if (PendingSubmarineSwitch != null)
{
lowFuel = TransferItemsOnSubSwitch ? (lowFuel && PendingSubmarineSwitch.LowFuel) : PendingSubmarineSwitch.LowFuel;
}
if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains("reactorfuel"))))
if (Level.IsLoadedFriendlyOutpost && lowFuel && CargoManager.PurchasedItems.None(i => i.Value.Any(pi => pi.ItemPrefab.Tags.Contains(Tags.ReactorFuel))))
{
var extraConfirmationBox =
new GUIMessageBox(TextManager.Get("lowfuelheader"),
@@ -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();
@@ -541,7 +542,7 @@ namespace Barotrauma
{
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
GameMain.GameSession = new GameSession(null, Option.None, CampaignDataPath.CreateRegular(savePath), GameModePreset.MultiPlayerCampaign, CampaignSettings.Empty, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID;
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
@@ -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)
@@ -1044,7 +1083,7 @@ namespace Barotrauma
return false;
}
public override void Save(XElement element)
public override void Save(XElement element, bool isSavingOnLoading)
{
//do nothing, the clients get the save files from the server
}
@@ -240,7 +240,7 @@ namespace Barotrauma
if (!savedOnStart)
{
GUI.SetSavingIndicatorState(true);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
SaveUtil.SaveGame(GameMain.GameSession.DataPath, isSavingOnLoading: true);
savedOnStart = true;
}
@@ -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;
@@ -448,7 +448,7 @@ namespace Barotrauma
if (success)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
SaveUtil.SaveGame(GameMain.GameSession.DataPath);
}
else
{
@@ -479,7 +479,7 @@ namespace Barotrauma
protected override void EndCampaignProjSpecific()
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
SaveUtil.SaveGame(GameMain.GameSession.DataPath);
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
GameMain.CampaignEndScreen.OnFinished = () =>
@@ -672,7 +672,7 @@ namespace Barotrauma
}
}
public override void Save(XElement element)
public override void Save(XElement element, bool isSavingOnLoading)
{
XElement modeElement = new XElement("SinglePlayerCampaign",
new XAttribute("purchasedlostshuttles", PurchasedLostShuttles),
@@ -7,7 +7,7 @@ using Barotrauma.Items.Components;
namespace Barotrauma
{
class TestGameMode : GameMode
partial class TestGameMode : GameMode
{
public Action OnRoundEnd;
@@ -22,18 +22,6 @@ namespace Barotrauma
private GUIButton createEventButton;
public TestGameMode(GameModePreset preset) : base(preset)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs.OrderBy(p => p.Identifier))
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
@@ -42,17 +30,24 @@ namespace Barotrauma
foreach (Submarine submarine in Submarine.Loaded)
{
submarine.NeutralizeBallast();
//normally the body would be made static during level generation,
//but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
if (submarine.Info.Type == SubmarineType.Outpost ||
submarine.Info.Type == SubmarineType.OutpostModule ||
submarine.Info.Type == SubmarineType.Wreck ||
submarine.Info.Type == SubmarineType.BeaconStation)
switch (submarine.Info.Type)
{
submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
case SubmarineType.Outpost:
case SubmarineType.OutpostModule:
case SubmarineType.Wreck:
case SubmarineType.BeaconStation:
//normally the body would be made static during level generation,
//but in the test mode we load the outpost/wreck/beacon as if it was a normal sub and need to do this manually
submarine.PhysicsBody.BodyType = FarseerPhysics.BodyType.Static;
if (submarine.Info.ShouldBeRuin)
{
submarine.Info.Type = SubmarineType.Ruin;
}
submarine.TeamID = submarine.Info.IsOutpost ? CharacterTeamType.FriendlyNPC : CharacterTeamType.None;
break;
}
}
if (SpawnOutpost)
{
GenerateOutpost(Submarine.MainSub);
@@ -86,7 +86,7 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
GameMain.GameSession = new GameSession(subInfo, Option.None, GameModePreset.Tutorial, missionPrefabs: null);
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams is not null)
@@ -138,7 +138,7 @@ namespace Barotrauma.Tutorials
character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
character.TeamID = CharacterTeamType.Team1;
Character.Controlled = character;
character.GiveJobItems(null);
character.GiveJobItems(isPvPMode: false, null);
var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
if (idCard == null)
@@ -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();
@@ -282,10 +286,10 @@ namespace Barotrauma
public void SetRespawnInfo(string text, Color textColor, bool waitForNextRoundRespawn, bool hideButtons = false)
{
if (topLeftButtonGroup == null) { return; }
bool permadeathMode = GameMain.NetworkMember?.ServerSettings is { RespawnMode: RespawnMode.Permadeath };
bool ironmanMode = GameMain.NetworkMember is { ServerSettings: { RespawnMode: RespawnMode.Permadeath, IronmanMode: true } };
bool ironmanMode = GameMain.NetworkMember?.ServerSettings is { IronmanModeActive: true };
bool hasRespawnOptions;
if (permadeathMode)
{
@@ -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)
{
@@ -205,7 +205,7 @@ namespace Barotrauma
if (Character.Controlled.SelectedItem.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag(Tags.Fuel)) > 1)
containedItems.Count(i => i.HasTag(Tags.ReactorFuel)) > 1)
{
if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; }
}
@@ -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)) },
@@ -0,0 +1,84 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
partial class PvPMode : MissionMode
{
private GUIComponent scoreContainer;
private readonly GUITextBlock[] scoreTexts = new GUITextBlock[2];
private readonly GUITextBlock[] scoreTextShadows = new GUITextBlock[2];
private readonly int[] prevScores = new int[2];
private void InitUI()
{
scoreContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), childAnchor: Anchor.TopRight)
{
CanBeFocused = false
};
for (int i = 0; i < 2; i++)
{
var frame = new GUIFrame(new RectTransform(new Point(scoreContainer.Rect.Width, GUI.IntScale(80)), scoreContainer.RectTransform), style: null)
{
CanBeFocused = false
};
new GUIImage(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight), style: i == 0 ? "CoalitionIcon" : "SeparatistIcon")
{
CanBeFocused = false
};
scoreTextShadows[i] = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(GUI.IntScale(38), GUI.IntScale(2)) },
string.Empty, textColor: GUIStyle.TextColorDark, textAlignment: Alignment.CenterRight, font: GUIStyle.SubHeadingFont)
{
CanBeFocused = false
};
scoreTexts[i] = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point(GUI.IntScale(40), 0) },
string.Empty, textAlignment: Alignment.CenterRight, font: GUIStyle.SubHeadingFont)
{
CanBeFocused = false
};
}
}
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
if (scoreContainer == null) { InitUI(); }
scoreContainer.Visible = false;
foreach (var mission in Missions)
{
if (mission is CombatMission combatMission && combatMission.HasWinScore)
{
for (int i = 0; i < 2; i++)
{
var scoreText = scoreTexts[i];
//one team very close to the win score, start flashing the score
if (combatMission.Scores[i] > combatMission.WinScore * 0.9f ||
combatMission.Scores[i] == combatMission.WinScore - 1)
{
if (scoreText.Parent.FlashTimer <= 0.0f)
{
scoreText.Parent.Flash(GUIStyle.Orange);
scoreText.Pulsate(Vector2.One, Vector2.One * 1.2f, scoreText.Parent.FlashTimer);
}
}
if (prevScores[i] != combatMission.Scores[i] || scoreText.Text.IsNullOrEmpty())
{
scoreText.Text = scoreTextShadows[i].Text = $"{combatMission.Scores[i]}/{combatMission.WinScore}";
scoreText.Parent.Flash(GUIStyle.Green);
scoreText.Parent.GetAnyChild<GUIImage>().Pulsate(Vector2.One, Vector2.One * 1.2f, scoreText.Parent.FlashTimer);
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
}
scoreText.Parent.RectTransform.NonScaledSize =
new Point(
(int)(scoreText.TextSize.X + scoreText.Padding.X + scoreText.Padding.X) + scoreText.Parent.GetChild<GUIImage>().Rect.Width + GUI.IntScale(10),
scoreText.Parent.Rect.Height);
scoreText.Parent.ForceLayoutRecalculation();
prevScores[i] = combatMission.Scores[i];
}
scoreContainer.Visible = true;
}
}
scoreContainer.AddToGUIUpdateList();
}
}
}
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
@@ -13,11 +14,13 @@ namespace Barotrauma
private float crewListAnimDelay = 0.25f;
private float missionIconAnimDelay;
private const float jobColumnWidthPercentage = 0.11f;
private const float characterColumnWidthPercentage = 0.44f;
private const float statusColumnWidthPercentage = 0.45f;
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;
private int jobColumnWidth, characterColumnWidth, statusColumnWidth, killColumnWidth, deathColumnWidth;
private readonly List<Mission> selectedMissions;
private readonly Location startLocation, endLocation;
@@ -109,6 +112,16 @@ namespace Barotrauma
CombatMission.GetTeamName(CharacterTeamType.Team2), textAlignment: Alignment.TopLeft, font: GUIStyle.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == CharacterTeamType.Team2), traitorResults);
if (CombatMission.Winner != CharacterTeamType.None)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewHeader.RectTransform),
TextManager.Get(CombatMission.Winner == CharacterTeamType.Team1 ? "pvpmode.victory" : "pvpmode.defeat"), textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont,
textColor: CombatMission.Winner == CharacterTeamType.Team1 ? GUIStyle.Green : GUIStyle.Red);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewHeader2.RectTransform),
TextManager.Get(CombatMission.Winner == CharacterTeamType.Team2 ? "pvpmode.victory" : "pvpmode.defeat"), textAlignment: Alignment.TopRight, font: GUIStyle.SubHeadingFont,
textColor: CombatMission.Winner == CharacterTeamType.Team2 ? GUIStyle.Green : GUIStyle.Red);
}
}
//header -------------------------------------------------------------------------------
@@ -238,11 +251,12 @@ namespace Barotrauma
textContent,
mission.Difficulty ?? 0,
mission.Prefab.Icon, mission.Prefab.IconColor,
mission.GetDifficultyToolTipText(),
out GUIImage missionIcon);
if (selectedMissions.Contains(mission))
{
UpdateMissionStateIcon(mission.Completed, missionIcon, animDelay);
UpdateMissionStateIcon(mission is CombatMission combatMission ? CombatMission.IsInWinningTeam(GameMain.Client?.Character) : mission.Completed, missionIcon, animDelay);
animDelay += 0.25f;
}
}
@@ -429,13 +443,14 @@ namespace Barotrauma
textContent,
difficultyIconCount: 0,
icon, GUIStyle.Red,
difficultyTooltipText: null,
out GUIImage missionIcon);
UpdateMissionStateIcon(traitorResults.VotedCorrectTraitor, missionIcon, iconAnimDelay);
return content;
}
public static GUIComponent CreateMissionEntry(GUIComponent parent, LocalizedString header, List<LocalizedString> textContent, int difficultyIconCount,
Sprite icon, Color iconColor, out GUIImage missionIcon)
Sprite icon, Color iconColor, RichString difficultyTooltipText, out GUIImage missionIcon)
{
int spacing = GUI.IntScale(5);
@@ -486,7 +501,8 @@ namespace Barotrauma
{
difficultyIndicatorGroup = new GUILayoutGroup(new RectTransform(new Point(missionTextGroup.Rect.Width, defaultLineHeight), parent: missionTextGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
AbsoluteSpacing = 1
AbsoluteSpacing = 1,
CanBeFocused = true
};
difficultyIndicatorGroup.RectTransform.MinSize = new Point(0, defaultLineHeight);
var difficultyColor = Mission.GetDifficultyColor(difficultyIconCount);
@@ -494,8 +510,8 @@ namespace Barotrauma
{
new GUIImage(new RectTransform(Vector2.One, difficultyIndicatorGroup.RectTransform, scaleBasis: ScaleBasis.Smallest), "DifficultyIndicator", scaleToFit: true)
{
CanBeFocused = false,
Color = difficultyColor
Color = difficultyColor,
ToolTip = difficultyTooltipText
};
}
}
@@ -567,7 +583,11 @@ namespace Barotrauma
LocalizedString locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.DisplayName : startLocation?.DisplayName;
string textTag;
if (gameOver)
if (gameMode is PvPMode)
{
textTag = "RoundSummaryRoundHasEnded";
}
else if (gameOver)
{
textTag = "RoundSummaryGameOver";
}
@@ -596,7 +616,14 @@ namespace Barotrauma
textTag = "RoundSummaryReturnToEmptyLocation";
break;
default:
textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
if (Submarine.MainSub == null)
{
textTag = "RoundSummaryRoundHasEnded";
}
else
{
textTag = Submarine.MainSub.AtEndExit ? "RoundSummaryProgress" : "RoundSummaryReturn";
}
break;
}
}
@@ -628,26 +655,34 @@ namespace Barotrauma
{
var headerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform, Anchor.TopCenter, minSize: new Point(0, (int)(30 * GUI.Scale))) { }, isHorizontal: true)
{
AbsoluteSpacing = 2
AbsoluteSpacing = 2,
Stretch = true
};
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
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");
float sizeMultiplier = 1.0f;
//sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
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;
}
else
{
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(StatusColumnWidthPercentage, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
statusColumnWidth = statusButton.Rect.Width;
}
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f);
statusButton.RectTransform.RelativeSize = new Vector2(statusColumnWidthPercentage * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = statusButton.TextBlock.Font = GUIStyle.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = statusButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = statusButton.ForceUpperCase = ForceUpperCase.Yes;
foreach (var btn in headerFrame.GetAllChildren<GUIButton>())
{
btn.TextBlock.Font = GUIStyle.HotkeyFont;
btn.ForceUpperCase = ForceUpperCase.Yes;
btn.CanBeFocused = false;
}
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
statusColumnWidth = statusButton.Rect.Width;
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
@@ -658,8 +693,28 @@ namespace Barotrauma
headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
killCounts.Clear();
if (GameMain.NetworkMember != null)
{
foreach (CharacterInfo characterInfo in characterInfos)
{
if (characterInfo == null) { continue; }
Character character = characterInfo.Character;
Client ownerClient = GameMain.NetworkMember.ConnectedClients.FirstOrDefault(c => c.Character == character);
int killCount = 0, deathCount = 0;
foreach (var mission in selectedMissions)
{
if (mission is not CombatMission combatMission) { continue; }
killCount += ownerClient == null ? combatMission.GetBotKillCount(characterInfo) : combatMission.GetClientKillCount(ownerClient);
deathCount += ownerClient == null ? combatMission.GetBotDeathCount(characterInfo) : combatMission.GetClientDeathCount(ownerClient);
}
killCounts[characterInfo] = killCount;
deathCounts[characterInfo] = deathCount;
}
}
float delay = crewListAnimDelay;
foreach (CharacterInfo characterInfo in characterInfos)
foreach (CharacterInfo characterInfo in characterInfos.OrderByDescending(ci => killCounts.GetValueOrDefault(ci)))
{
if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList, traitorResults, delay);
@@ -670,6 +725,10 @@ namespace Barotrauma
return crewList;
}
private readonly Dictionary<CharacterInfo, int> killCounts = new();
private readonly Dictionary<CharacterInfo, int> deathCounts = new();
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox, TraitorManager.TraitorResults? traitorResults, float animDelay)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement")
@@ -701,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;
@@ -741,8 +800,21 @@ namespace Barotrauma
}
}
GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(statusText.Value, GUIStyle.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
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);
}
else
{
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())