(965c31410a) Unstable v0.10.4.0

This commit is contained in:
Juan Pablo Arce
2020-07-21 08:57:50 -03:00
parent 4f8bd39789
commit 33d3a41104
546 changed files with 45952 additions and 25762 deletions
@@ -0,0 +1,173 @@
using Barotrauma.Extensions;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class CargoManager
{
private class SoldEntity
{
public enum SellStatus
{
Confirmed,
Unconfirmed,
Local
}
public Item Item { get; }
public SellStatus Status { get; set; }
private SoldEntity(Item item, SellStatus status)
{
Item = item;
Status = status;
}
public static SoldEntity CreateInSinglePlayer(Item item) => new SoldEntity(item, SellStatus.Confirmed);
public static SoldEntity CreateInMultiPlayer(Item item) => new SoldEntity(item, SellStatus.Local);
}
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
public List<Item> GetSellableItems(Character character)
{
if (character == null) { return new List<Item>(); }
// Only consider items which have been:
// a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or
// b) sold locally in multiplier (SellStatus.Local), but the client has not received a campaing state update yet after selling them
var soldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
var sellables = Item.ItemList.FindAll(i => i?.Prefab != null && !i.Removed &&
i.GetRootInventoryOwner() == character &&
!i.SpawnedInOutpost &&
(i.ContainedItems == null || i.ContainedItems.None() || i.ContainedItems.All(ci => soldEntities.Any(se => se.Item == ci))) &&
i.IsFullCondition && soldEntities.None(se => se.Item == i));
// Prevent selling things like battery cells from headsets and oxygen tanks from diving masks
var slots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.OuterClothes, InvSlotType.Headset };
foreach (InvSlotType slot in slots)
{
var index = character.Inventory.FindLimbSlot(slot);
if (character.Inventory.Items[index] is Item item && item.ContainedItems != null)
{
foreach (Item containedItem in item.ContainedItems)
{
if (containedItem != null)
{
sellables.Remove(containedItem);
}
}
}
}
return sellables;
}
public void SetItemsInBuyCrate(List<PurchasedItem> items)
{
ItemsInBuyCrate.Clear();
ItemsInBuyCrate.AddRange(items);
OnItemsInBuyCrateChanged?.Invoke();
}
public void SetSoldItems(List<SoldItem> items)
{
SoldItems.Clear();
SoldItems.AddRange(items);
foreach (SoldEntity se in SoldEntities)
{
if (se.Status == SoldEntity.SellStatus.Confirmed) { continue; }
if (SoldItems.Any(si => si.ID == se.Item.ID && si.ItemPrefab == se.Item.Prefab && (GameMain.Client == null || GameMain.Client.ID == si.SellerID)))
{
se.Status = SoldEntity.SellStatus.Confirmed;
}
else
{
se.Status = SoldEntity.SellStatus.Unconfirmed;
}
}
OnSoldItemsChanged?.Invoke();
}
public void ModifyItemQuantityInSellCrate(ItemPrefab itemPrefab, int changeInQuantity)
{
PurchasedItem itemToSell = ItemsInSellCrate.Find(i => i.ItemPrefab == itemPrefab);
if (itemToSell != null)
{
itemToSell.Quantity += changeInQuantity;
if (itemToSell.Quantity < 1)
{
ItemsInSellCrate.Remove(itemToSell);
}
}
else if (changeInQuantity > 0)
{
itemToSell = new PurchasedItem(itemPrefab, changeInQuantity);
ItemsInSellCrate.Add(itemToSell);
}
OnItemsInSellCrateChanged?.Invoke();
}
public void SellItems(List<PurchasedItem> itemsToSell)
{
var itemsInInventory = GetSellableItems(Character.Controlled);
var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
var sellerId = GameMain.Client?.ID ?? 0;
foreach (PurchasedItem item in itemsToSell)
{
var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab, quantity: item.Quantity);
// check if the store can afford the item
if (location.StoreCurrentBalance < itemValue) { continue; }
var matchingItems = itemsInInventory.FindAll(i => i.Prefab == item.ItemPrefab);
if (matchingItems.Count <= item.Quantity)
{
foreach (Item i in matchingItems)
{
SoldItems.Add(new SoldItem(i.Prefab, i.ID, canAddToRemoveQueue, sellerId));
SoldEntities.Add(campaign.IsSinglePlayer ? SoldEntity.CreateInSinglePlayer(i) : SoldEntity.CreateInMultiPlayer(i));
if (canAddToRemoveQueue) { Entity.Spawner.AddToRemoveQueue(i); }
}
}
else
{
for (int i = 0; i < item.Quantity; i++)
{
var matchingItem = matchingItems[i];
SoldItems.Add(new SoldItem(matchingItem.Prefab, matchingItem.ID, canAddToRemoveQueue, sellerId));
SoldEntities.Add(campaign.IsSinglePlayer ? SoldEntity.CreateInSinglePlayer(matchingItem) : SoldEntity.CreateInMultiPlayer(matchingItem));
if (canAddToRemoveQueue) { Entity.Spawner.AddToRemoveQueue(matchingItem); }
}
}
// Exchange money
campaign.Map.CurrentLocation.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue;
// Remove from the sell crate
if (ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } itemToSell)
{
itemToSell.Quantity -= item.Quantity;
if (itemToSell.Quantity < 1)
{
ItemsInSellCrate.Remove(itemToSell);
}
}
}
OnSoldItemsChanged?.Invoke();
}
public void ClearSoldItemsProjSpecific()
{
SoldItems.Clear();
SoldEntities.Clear();
}
}
}
@@ -20,9 +20,6 @@ namespace Barotrauma
/// </summary>
const float CharacterWaitOnSwitch = 10.0f;
private readonly List<CharacterInfo> characterInfos = new List<CharacterInfo>();
private readonly List<Character> characters = new List<Character>();
private Point screenResolution;
#region UI
@@ -30,6 +27,7 @@ namespace Barotrauma
public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame;
private GUIComponent crewAreaWithButtons;
private GUIFrame crewArea;
private GUIListBox crewList;
private GUIButton commandButton, toggleCrewButton;
@@ -72,19 +70,7 @@ namespace Barotrauma
public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer)
{
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; }
var characterInfo = new CharacterInfo(subElement);
characterInfos.Add(characterInfo);
foreach (XElement invElement in subElement.Elements())
{
if (!invElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
characterInfo.InventoryData = invElement;
break;
}
}
AddCharacterElements(element);
}
partial void InitProjectSpecific()
@@ -96,7 +82,7 @@ namespace Barotrauma
#region Crew Area
var crewAreaWithButtons = new GUIFrame(
crewAreaWithButtons = new GUIFrame(
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform),
style: null,
color: Color.Transparent)
@@ -326,43 +312,6 @@ namespace Barotrauma
return characterInfos;
}
public void AddCharacter(Character character)
{
if (character.Removed)
{
DebugConsole.ThrowError("Tried to add a removed character to CrewManager!\n" + Environment.StackTrace);
return;
}
if (character.IsDead)
{
DebugConsole.ThrowError("Tried to add a dead character to CrewManager!\n" + Environment.StackTrace);
return;
}
if (!characters.Contains(character))
{
characters.Add(character);
}
if (!characterInfos.Contains(character.Info))
{
characterInfos.Add(character.Info);
}
AddCharacterToCrewList(character);
DisplayCharacterOrder(character, character.CurrentOrder, character.CurrentOrderOption);
}
public void AddCharacterInfo(CharacterInfo characterInfo)
{
if (characterInfos.Contains(characterInfo))
{
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
return;
}
characterInfos.Add(characterInfo);
}
/// <summary>
/// Remove the character from the crew (and crew menus).
/// </summary>
@@ -379,15 +328,6 @@ namespace Barotrauma
if (removeInfo) { characterInfos.Remove(character.Info); }
}
/// <summary>
/// Remove info of a selected character. The character will not be visible in any menus or the round summary.
/// </summary>
/// <param name="characterInfo"></param>
public void RemoveCharacterInfo(CharacterInfo characterInfo)
{
characterInfos.Remove(characterInfo);
}
private void AddCharacterToCrewList(Character character)
{
if (character == null) { return; }
@@ -516,7 +456,7 @@ namespace Barotrauma
};
new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform),
GUI.Style.GetComponentStyle("GUISoundIcon").Sprites[GUIComponent.ComponentState.None].FirstOrDefault().Sprite,
GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(),
scaleToFit: true)
{
CanBeFocused = false,
@@ -630,6 +570,22 @@ namespace Barotrauma
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
}
public void AddSinglePlayerChatMessage(ChatMessage message)
{
if (!IsSinglePlayer)
{
DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
return;
}
if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null)
{
GameMain.GameSession.CrewManager.SetCharacterSpeaking(message.Sender);
}
ChatBox.AddMessage(message);
}
private WifiComponent GetHeadset(Character character, bool requireEquipped)
{
if (character?.Inventory == null) return null;
@@ -861,27 +817,6 @@ namespace Barotrauma
return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
}
private struct OrderInfo
{
public string ComponentIdentifier { get; set; }
public Order Order { get; private set; }
public string OrderOption { get; private set; }
public OrderInfo(Order order, string orderOption)
{
ComponentIdentifier = "currentorder";
Order = order;
OrderOption = orderOption;
}
public OrderInfo(OrderInfo orderInfo)
{
ComponentIdentifier = "previousorder";
Order = orderInfo.Order;
OrderOption = orderInfo.OrderOption;
}
}
#endregion
#region Updating and drawing the UI
@@ -1123,6 +1058,7 @@ namespace Barotrauma
public void AddToGUIUpdateList()
{
if (GUI.DisableHUD) { return; }
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition")) { return; }
commandFrame?.AddToGUIUpdateList();
@@ -1145,6 +1081,8 @@ namespace Barotrauma
}
}
crewAreaWithButtons.Visible = !(GameMain.GameSession?.GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI);
guiFrame.AddToGUIUpdateList();
contextMenu?.AddToGUIUpdateList(false, 1);
subContextMenu?.AddToGUIUpdateList(false, 1);
@@ -1170,6 +1108,7 @@ namespace Barotrauma
private void SelectCharacter(Character character)
{
if (ConversationAction.IsDialogOpen) { return; }
if (!AllowCharacterSwitch) { 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)
@@ -1256,7 +1195,7 @@ namespace Barotrauma
WasCommandInterfaceDisabledThisUpdate = false;
if (PlayerInput.KeyDown(InputType.Command) && (GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber == crewList) &&
commandFrame == null && !clicklessSelectionActive && CanIssueOrders)
commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false))
{
if (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift))
{
@@ -1574,32 +1513,32 @@ namespace Barotrauma
get
{
#if DEBUG
return Character.Controlled == null || Character.Controlled.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
#else
return Character.Controlled?.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
if (Character.Controlled == null) { return true; }
#endif
return Character.Controlled?.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
}
}
private bool CanSomeoneHearCharacter()
{
#if DEBUG
return true;
#else
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
if (Character.Controlled == null) { return true; }
#endif
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
}
private Entity FindEntityContext()
{
if (Character.Controlled?.FocusedCharacter != null)
if (Character.Controlled?.FocusedCharacter is Character focusedCharacter && !focusedCharacter.IsDead &&
HumanAIController.IsFriendly(Character.Controlled, focusedCharacter) && Character.Controlled.TeamID == focusedCharacter.TeamID)
{
if (Character.Controlled?.FocusedItem != null)
{
Vector2 mousePos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
if (Vector2.Distance(mousePos, Character.Controlled.FocusedCharacter.WorldPosition) < Vector2.Distance(mousePos, Character.Controlled.FocusedItem.WorldPosition))
if (Vector2.Distance(mousePos, focusedCharacter.WorldPosition) < Vector2.Distance(mousePos, Character.Controlled.FocusedItem.WorldPosition))
{
return Character.Controlled.FocusedCharacter;
return focusedCharacter;
}
else
{
@@ -1608,7 +1547,7 @@ namespace Barotrauma
}
else
{
return Character.Controlled.FocusedCharacter;
return focusedCharacter;
}
}
@@ -1677,24 +1616,22 @@ namespace Barotrauma
"CommandNodeContainer",
scaleToFit: true)
{
Color = characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier,
HoverColor = characterContext.Info.Job.Prefab.UIColor,
Color = characterContext.Info?.Job?.Prefab != null ? characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier : Color.White,
HoverColor = characterContext.Info?.Job?.Prefab != null ? characterContext.Info.Job.Prefab.UIColor : Color.White,
UserData = "colorsource"
};
// Character icon
new GUICustomComponent(
var characterIcon = new GUICustomComponent(
new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center),
(spriteBatch, _) =>
{
if (!(entityContext is Character character)) { return; }
if (!(entityContext is Character character) || character?.Info == null) { return; }
var node = startNode;
character.Info.DrawJobIcon(spriteBatch,
new Rectangle((int)(node.Rect.X + node.Rect.Width * 0.5f), (int)(node.Rect.Y + node.Rect.Height * 0.1f), (int)(node.Rect.Width * 0.6f), (int)(node.Rect.Height * 0.8f)));
character.Info.DrawIcon(spriteBatch, new Vector2(node.Rect.X + node.Rect.Width * 0.35f, node.Center.Y), node.Rect.Size.ToVector2() * 0.7f);
})
{
ToolTip = characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")"
};
});
SetCharacterTooltip(characterIcon, entityContext as Character);
}
SetCenterNode(startNode);
@@ -1934,7 +1871,7 @@ namespace Barotrauma
c.HoverColor = c.Color;
c.PressedColor = c.Color;
c.SelectedColor = c.Color;
c.ToolTip = characterContext != null ? characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")" : null;
SetCharacterTooltip(c, characterContext);
}
node.OnClicked = null;
centerNode = node;
@@ -2040,7 +1977,7 @@ namespace Barotrauma
var reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
// ---> Create shortcut node for "Operate Reactor" order's "Power Up" option
if ((Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) &&
if ((Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item))
{
var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled);
@@ -2053,7 +1990,7 @@ namespace Barotrauma
// TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("captain")) &&
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) &&
sub.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{
@@ -2063,7 +2000,7 @@ namespace Barotrauma
// If player is not a security officer AND invaders are reported
// --> Create shorcut node for Fight Intruders order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("securityofficer")) &&
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) &&
(Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders)))
{
shortcutNodes.Add(
@@ -2072,7 +2009,7 @@ namespace Barotrauma
// If player is not a mechanic AND a breach has been reported
// --> Create shorcut node for Fix Leaks order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("mechanic")) &&
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) &&
(Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)))
{
shortcutNodes.Add(
@@ -2081,7 +2018,7 @@ namespace Barotrauma
// If player is not an engineer AND broken devices have been reported
// --> Create shortcut node for Repair Damaged Systems order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) &&
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
(Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)))
{
shortcutNodes.Add(
@@ -2741,11 +2678,11 @@ namespace Barotrauma
};
}
#if DEBUG
bool canHear = true;
#else
bool canHear = character.CanHearCharacter(Character.Controlled);
#if DEBUG
if (Character.Controlled == null) { canHear = true; }
#endif
if (!canHear)
{
node.CanBeFocused = orderIcon.CanBeFocused = false;
@@ -2904,18 +2841,29 @@ namespace Barotrauma
return sub;
}
private void SetCharacterTooltip(GUIComponent component, Character character)
{
if (component == null) { return; }
var tooltip = character?.Info != null ? characterContext.Info.DisplayName : null;
if (string.IsNullOrWhiteSpace(tooltip)) { component.ToolTip = tooltip; return; }
if (character.Info?.Job != null && !string.IsNullOrWhiteSpace(characterContext.Info.Job.Name)) { tooltip += " (" + characterContext.Info.Job.Name + ")"; }
component.ToolTip = tooltip;
}
#region Crew Member Assignment Logic
private Character GetCharacterForQuickAssignment(Order order)
{
var controllingCharacter = Character.Controlled != null;
#if !DEBUG
if (Character.Controlled == null) { return null; }
if (!controllingCharacter) { return null; }
#endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter))
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter) &&
(!controllingCharacter || operatingCharacter.CanHearCharacter(Character.Controlled)))
{
return operatingCharacter;
}
return GetCharactersSortedForOrder(order, false).FirstOrDefault() ?? Character.Controlled;
return GetCharactersSortedForOrder(order, false).FirstOrDefault(c => !controllingCharacter || c.CanHearCharacter(Character.Controlled)) ?? Character.Controlled;
}
private List<Character> GetCharactersForManualAssignment(Order order)
@@ -2934,11 +2882,17 @@ namespace Barotrauma
private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf)
{
return characters.FindAll(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID))
// 1. Prioritize those who are already ordered to operate the item target of the new 'operate' order
.OrderByDescending(c => c.CurrentOrder != null && order.Category == OrderCategory.Operate && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity)
// 2. Prioritize those who are currently dismissed
.ThenByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
// 3. Prioritize those who are not currently assigned with the same type of order (for example, when giving a 'Fix Leak' order, prioritize those who have a different order)
.ThenBy(c => c.CurrentOrder != null && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity)
// 4. Prioritize those with the appropriate job for the order
.ThenByDescending(c => order.HasAppropriateJob(c))
// 5. Prioritize those with the lowest "weight" of the current order
.ThenBy(c => c.CurrentOrder?.Weight)
// 6. Prioritize those with the best skill for the order
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
}
@@ -3013,40 +2967,7 @@ namespace Barotrauma
public void InitSinglePlayerRound()
{
crewList.ClearChildren();
characters.Clear();
WayPoint[] waypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
for (int i = 0; i < waypoints.Length; i++)
{
Character character;
character = Character.Create(characterInfos[i], waypoints[i].WorldPosition, characterInfos[i].Name);
if (character.Info != null)
{
if (!character.Info.StartItemsGiven && character.Info.InventoryData != null)
{
DebugConsole.ThrowError($"Error when initializing a single player round: character \"{character.Name}\" has not been given their initial items but has saved inventory data. Using the saved inventory data instead of giving the character new items.");
}
if (character.Info.InventoryData != null)
{
character.Info.SpawnInventoryItems(character.Inventory, character.Info.InventoryData);
}
else if (!character.Info.StartItemsGiven)
{
character.GiveJobItems(waypoints[i]);
}
character.Info.StartItemsGiven = true;
}
AddCharacter(character);
if (i == 0)
{
Character.Controlled = character;
}
}
conversationTimer = Rand.Range(5.0f, 10.0f);
InitRound();
}
public void EndRound()
@@ -3072,10 +2993,9 @@ namespace Barotrauma
foreach (CharacterInfo ci in characterInfos)
{
var infoElement = ci.Save(element);
if (ci.InventoryData != null)
{
infoElement.Add(ci.InventoryData);
}
if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); }
if (ci.HealthData != null) { infoElement.Add(ci.HealthData); }
if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
}
parentElement.Add(element);
}
@@ -1,10 +1,65 @@
using Microsoft.Xna.Framework;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma
{
abstract partial class CampaignMode : GameMode
{
protected bool crewDead;
protected Color overlayColor;
protected string overlayText, overlayTextBottom;
protected Color overlayTextColor;
protected Sprite overlaySprite;
protected GUIButton endRoundButton;
public GUIButton EndRoundButton => endRoundButton;
protected GUIFrame campaignUIContainer;
public CampaignUI CampaignUI;
public bool ForceMapUI
{
get;
protected set;
}
public override bool Paused
{
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
}
private bool showCampaignUI;
private bool wasChatBoxOpen;
public bool ShowCampaignUI
{
get { return showCampaignUI; }
set
{
if (value == showCampaignUI) { return; }
var chatBox = CrewManager?.ChatBox ?? GameMain.Client?.ChatBox;
if (value)
{
if (chatBox != null)
{
wasChatBoxOpen = chatBox.ToggleOpen;
chatBox.ToggleOpen = false;
}
}
else if (chatBox != null)
{
chatBox.ToggleOpen = wasChatBoxOpen;
}
showCampaignUI = value;
}
}
public override void ShowStartMessage()
{
if (Mission == null) return;
@@ -15,5 +70,216 @@ namespace Barotrauma
UserData = "missionstartmessage"
};
}
/// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary>
public bool AllowedToEndRound()
{
//allow ending the round if the client has permissions, is the owner, the only client in the server
//or if no-one has management permissions
if (GameMain.Client == null) { return true; }
return
GameMain.Client.HasPermission(ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
GameMain.Client.ConnectedClients.None(c =>
c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign)));
}
/// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary>
public bool AllowedToManageCampaign()
{
//allow ending the round if the client has permissions, is the owner, the only client in the server,
//or if no-one has management permissions
if (GameMain.Client == null) { return true; }
return
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
GameMain.Client.ConnectedClients.None(c =>
c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageCampaign)));
}
public override void Draw(SpriteBatch spriteBatch)
{
if (overlayColor.A > 0)
{
if (overlaySprite != null)
{
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.Black * (overlayColor.A / 255.0f), isFilled: true);
float scale = Math.Max(GameMain.GraphicsWidth / overlaySprite.size.X, GameMain.GraphicsHeight / overlaySprite.size.Y);
overlaySprite.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2, overlayColor, overlaySprite.size / 2, scale: scale);
}
else
{
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), overlayColor, isFilled: true);
}
if (!string.IsNullOrEmpty(overlayText) && overlayTextColor.A > 0)
{
var backgroundSprite = GUI.Style.GetComponentStyle("CommandBackground").GetDefaultSprite();
Vector2 centerPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2;
backgroundSprite.Draw(spriteBatch,
centerPos,
Color.White * (overlayTextColor.A / 255.0f),
origin: backgroundSprite.size / 2,
rotate: 0.0f,
scale: new Vector2(1.5f, 0.7f) * (GameMain.GraphicsWidth / 3 / backgroundSprite.size.X));
string wrappedText = ToolBox.WrapText(overlayText, GameMain.GraphicsWidth / 3, GUI.Font);
Vector2 textSize = GUI.Font.MeasureString(wrappedText);
Vector2 textPos = centerPos - textSize / 2;
GUI.DrawString(spriteBatch, textPos + Vector2.One, wrappedText, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, textPos, wrappedText, overlayTextColor);
if (!string.IsNullOrEmpty(overlayTextBottom))
{
Vector2 bottomTextPos = centerPos + new Vector2(0.0f, textSize.Y + 30 * GUI.Scale) - GUI.Font.MeasureString(overlayTextBottom) / 2;
GUI.DrawString(spriteBatch, bottomTextPos + Vector2.One, overlayTextBottom, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, bottomTextPos, overlayTextBottom, overlayTextColor);
}
}
}
if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"))
{
endRoundButton.Visible = false;
return;
}
if (Submarine.MainSub == null) { return; }
endRoundButton.Visible = false;
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
string buttonText = "";
switch (availableTransition)
{
case TransitionType.ProgressToNextLocation:
case TransitionType.ProgressToNextEmptyLocation:
if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
{
buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
break;
case TransitionType.LeaveLocation:
// not sure why this can happen at an outpost but it apparently can in multiplayer
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
break;
case TransitionType.ReturnToPreviousLocation:
case TransitionType.ReturnToPreviousEmptyLocation:
if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
{
buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
break;
case TransitionType.None:
default:
if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false)))
{
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
else
{
endRoundButton.Visible = false;
}
break;
}
if (endRoundButton.Visible)
{
endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5);
if (endRoundButton.Text != buttonText)
{
endRoundButton.ToolTip = buttonText;
}
endRoundButton.Enabled = AllowedToEndRound();
}
endRoundButton.DrawManually(spriteBatch);
}
public Task SelectSummaryScreen(RoundSummary roundSummary, LevelData newLevel, bool mirror, Action action)
{
var roundSummaryScreen = RoundSummaryScreen.Select(overlaySprite, roundSummary);
GUI.ClearCursorWait();
var loadTask = Task.Run(async () =>
{
await Task.Yield();
Rand.ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror);
Rand.ThreadId = 0;
});
TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
{
overlayColor = Color.Transparent;
action?.Invoke();
});
return loadTask;
}
partial void NPCInteractProjSpecific(Character npc, Character interactor)
{
if (npc == null || interactor == null) { return; }
switch (npc.CampaignInteractionType)
{
case InteractionType.None:
case InteractionType.Talk:
return;
case InteractionType.Upgrade when !UpgradeManager.CanUpgradeSub():
UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade"), IsSinglePlayer, npc);
return;
case InteractionType.Crew when GameMain.NetworkMember != null:
CampaignUI.CrewManagement.SendCrewState(false);
goto default;
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType);
CampaignUI.UpgradeStore?.RefreshAll();
break;
}
}
public override void AddToGUIUpdateList()
{
if (ShowCampaignUI || ForceMapUI)
{
campaignUIContainer?.AddToGUIUpdateList();
if (CampaignUI?.UpgradeStore?.HoveredItem != null)
{
if (CampaignUI.SelectedTab != InteractionType.Upgrade) { return; }
CampaignUI?.UpgradeStore?.ItemInfoFrame.AddToGUIUpdateList(order: 1);
}
}
base.AddToGUIUpdateList();
CrewManager.AddToGUIUpdateList();
endRoundButton.AddToGUIUpdateList();
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
}
if (ShowCampaignUI || ForceMapUI)
{
CampaignUI?.Update(deltaTime);
}
}
}
}
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal partial class CampaignMetadata
{
private const int MaxDrawnElements = 12;
public void DebugDraw(SpriteBatch spriteBatch, Vector2 pos, int debugDrawMetadataOffset, string[] ignoredMetadataInfo)
{
var campaignData = data;
foreach (string ignored in ignoredMetadataInfo)
{
if (!string.IsNullOrWhiteSpace(ignored))
{
campaignData = campaignData.Where(pair => !pair.Key.StartsWith(ignored)).ToDictionary(i => i.Key, i => i.Value);
}
}
int offset = 0;;
if (campaignData.Count > 0)
{
offset = debugDrawMetadataOffset % campaignData.Count;
if (offset < 0) { offset += campaignData.Count; }
}
var text = "Campaign metadata:\n";
int max = 0;
for (int i = offset; i < campaignData.Count + offset; i++)
{
int index = i;
if (index >= campaignData.Count) { index -= campaignData.Count; }
var (key, value) = campaignData.ElementAt(index);
if (max < MaxDrawnElements)
{
text += $"{key.ColorizeObject()}: {value.ColorizeObject()}\n";
max++;
}
else
{
text += "Use arrow keys to scroll";
break;
}
}
text = text.TrimEnd('\n');
List<RichTextData> richTextDatas = RichTextData.GetRichTextData(text, out text) ?? new List<RichTextData>();
Vector2 size = GUI.SmallFont.MeasureString(text);
Vector2 infoPos = new Vector2(GameMain.GraphicsWidth - size.X - 16, pos.Y + 8);
Rectangle infoRect = new Rectangle(infoPos.ToPoint(), size.ToPoint());
infoRect.Inflate(8, 8);
GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, infoRect, Color.White * 0.8f);
if (richTextDatas.Any())
{
GUI.DrawStringWithColors(spriteBatch, infoPos, text, Color.White, richTextDatas, font: GUI.SmallFont);
}
else
{
GUI.DrawString(spriteBatch, infoPos, text, Color.White, font: GUI.SmallFont);
}
float y = infoRect.Bottom + 16;
if (Campaign.Factions != null)
{
const string factionHeader = "Reputations";
Vector2 factionHeaderSize = GUI.SubHeadingFont.MeasureString(factionHeader);
Vector2 factionPos = new Vector2(GameMain.GraphicsWidth - (264 / 2) - factionHeaderSize.X / 2, y);
GUI.DrawString(spriteBatch, factionPos, factionHeader, Color.White, font: GUI.SubHeadingFont);
y += factionHeaderSize.Y + 8;
foreach (Faction faction in Campaign.Factions)
{
string name = faction.Prefab.Name;
Vector2 nameSize = GUI.SmallFont.MeasureString(name);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUI.SmallFont);
y += nameSize.Y + 5;
Color color = ToolBox.GradientLerp(faction.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(faction.Reputation.NormalizedValue * 255), 10), color, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White);
y += 15;
}
}
Location location = Campaign.Map?.CurrentLocation;
if (location?.Reputation != null)
{
string name = Campaign.Map?.CurrentLocation.Name;
Vector2 nameSize = GUI.SmallFont.MeasureString(name);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUI.SmallFont);
y += nameSize.Y + 5;
float normalizedReputation = MathUtils.InverseLerp(location.Reputation.MinReputation, location.Reputation.MaxReputation, location.Reputation.Value);
Color color = ToolBox.GradientLerp(normalizedReputation, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(normalizedReputation * 255), 10), color, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White);
}
richTextDatas.Clear();
}
}
}
@@ -1,7 +1,9 @@
using Barotrauma.Networking;
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Linq;
@@ -11,14 +13,30 @@ namespace Barotrauma
{
public bool SuppressStateSending = false;
private UInt16 startWatchmanID, endWatchmanID;
private UInt16 pendingSaveID = 1;
public UInt16 PendingSaveID
{
get
{
return pendingSaveID;
}
set
{
pendingSaveID = value;
//pending save ID 0 means "no save received yet"
//save IDs are always above 0, so we should never be waiting for 0
if (pendingSaveID == 0) { pendingSaveID++; }
}
}
public static void StartCampaignSetup(IEnumerable<string> saveFiles)
{
var parent = GameMain.NetLobbyScreen.CampaignSetupFrame;
parent.ClearChildren();
parent.Visible = true;
GameMain.NetLobbyScreen.HighlightMode(2);
GameMain.NetLobbyScreen.HighlightMode(
GameMain.NetLobbyScreen.ModeList.Content.GetChildIndex(GameMain.NetLobbyScreen.ModeList.Content.GetChildByUserData(GameModePreset.MultiPlayerCampaign)));
var layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center))
{
@@ -38,7 +56,7 @@ namespace Barotrauma
var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles);
GameMain.NetLobbyScreen.CampaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign"), style: "GUITabButton")
@@ -68,94 +86,488 @@ namespace Barotrauma
loadCampaignContainer.Visible = false;
GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock);
GameMain.NetLobbyScreen.CampaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign;
GameMain.NetLobbyScreen.CampaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign;
}
partial void InitProjSpecific()
{
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUICanvas.Instance),
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
CanBeFocused = false
};
campaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign;
campaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign;
int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(200);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
var availableTransition = GetAvailableTransition(out _, out _);
if (Character.Controlled != null &&
availableTransition == TransitionType.ReturnToPreviousLocation &&
Character.Controlled?.Submarine == Level.Loaded?.StartOutpost)
{
GameMain.Client.RequestStartRound();
}
else if (Character.Controlled != null &&
availableTransition == TransitionType.ProgressToNextLocation &&
Character.Controlled?.Submarine == Level.Loaded?.EndOutpost)
{
GameMain.Client.RequestStartRound();
}
else
{
ShowCampaignUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
}
return true;
}
};
buttonContainer.Recalculate();
}
private void InitCampaignUI()
{
campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
CampaignUI = new CampaignUI(this, campaignUIContainer)
{
StartRound = () =>
{
GameMain.Client.RequestStartRound();
}
};
}
public override void Start()
{
base.Start();
CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "MultiplayerCampaign.DoInitialCameraTransition");
}
protected override void LoadInitialLevel()
{
//clients should never call this
throw new InvalidOperationException("");
}
private IEnumerable<object> DoInitialCameraTransition()
{
while (GameMain.Instance.LoadingScreenOpen)
{
yield return CoroutineStatus.Running;
}
if (GameMain.Client.LateCampaignJoin)
{
GameMain.Client.LateCampaignJoin = false;
yield return CoroutineStatus.Success;
}
Character prevControlled = Character.Controlled;
if (prevControlled?.AIController != null)
{
prevControlled.AIController.Enabled = false;
}
GUI.DisableHUD = true;
if (IsFirstRound)
{
Character.Controlled = null;
if (prevControlled != null)
{
prevControlled.ClearInputs();
}
overlayColor = Color.LightGray;
overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId);
overlayTextColor = Color.Transparent;
overlayText = TextManager.GetWithVariables("campaignstart",
new string[] { "xxxx", "yyyy" },
new string[] { Map.CurrentLocation.Name, TextManager.Get("submarineclass." + Submarine.MainSub.Info.SubmarineClass) });
float fadeInDuration = 1.0f;
float textDuration = 10.0f;
float timer = 0.0f;
while (timer < textDuration)
{
// Try to grab the controlled here to prevent inputs, assigned late on multiplayer
if (Character.Controlled != null)
{
prevControlled = Character.Controlled;
Character.Controlled = null;
prevControlled.ClearInputs();
}
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running;
}
var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam,
null, null,
fadeOut: false,
duration: 5,
startZoom: 1.5f, endZoom: 1.0f)
{
AllowInterrupt = true,
RemoveControlFromCharacter = false
};
fadeInDuration = 1.0f;
timer = 0.0f;
overlayTextColor = Color.Transparent;
overlayText = "";
while (timer < fadeInDuration)
{
overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration);
timer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
overlayColor = Color.Transparent;
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
if (prevControlled != null)
{
Character.Controlled = prevControlled;
}
}
else
{
var transition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam,
null, null,
fadeOut: false,
duration: 5,
startZoom: 0.5f, endZoom: 1.0f)
{
AllowInterrupt = true,
RemoveControlFromCharacter = true
};
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
}
if (prevControlled != null)
{
prevControlled.SelectedConstruction = null;
if (prevControlled.AIController != null)
{
prevControlled.AIController.Enabled = true;
}
}
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null)
{
yield return CoroutineStatus.Success;
}
private IEnumerable<object> DoLevelTransition()
{
SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead";
SoundPlayer.OverrideMusicDuration = 18.0f;
Level prevLevel = Level.Loaded;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
crewDead = false;
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
if (continueButton != null)
{
continueButton.Visible = false;
}
Character.Controlled = null;
yield return new WaitForSeconds(0.1f);
GameMain.Client.EndCinematic?.Stop();
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
Alignment.Center,
fadeOut: false,
duration: EndTransitionDuration);
GameMain.Client.EndCinematic = endTransition;
Location portraitLocation = Map?.SelectedLocation ?? Map?.CurrentLocation ?? Level.Loaded?.StartLocation;
if (portraitLocation != null)
{
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
}
float fadeOutDuration = endTransition.Duration;
float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running)
{
t += CoroutineManager.UnscaledDeltaTime;
overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration);
yield return CoroutineStatus.Running;
}
overlayColor = Color.White;
yield return CoroutineStatus.Running;
//--------------------------------------
//wait for the new level to be loaded
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
while (Level.Loaded == prevLevel || Level.Loaded == null)
{
if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; }
yield return CoroutineStatus.Running;
}
endTransition.Stop();
overlayColor = Color.Transparent;
if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); }
if (!(Screen.Selected is RoundSummaryScreen))
{
if (continueButton != null)
{
continueButton.Visible = true;
}
}
yield return CoroutineStatus.Success;
}
public override void Update(float deltaTime)
{
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || Level.Loaded == null) { return; }
if (ShowCampaignUI || ForceMapUI)
{
if (CampaignUI == null) { InitCampaignUI(); }
Character.DisableControls = true;
}
base.Update(deltaTime);
if (startWatchmanID > 0 && startWatchman == null)
if (PlayerInput.RightButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{
startWatchman = Entity.FindEntityByID(startWatchmanID) as Character;
if (startWatchman != null) { InitializeWatchman(startWatchman); }
ShowCampaignUI = false;
if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary &&
roundSummary.ContinueButton != null &&
roundSummary.ContinueButton.Visible)
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
}
if (endWatchmanID > 0 && endWatchman == null)
if (!GUI.DisableHUD && !GUI.DisableUpperHUD)
{
endWatchman = Entity.FindEntityByID(endWatchmanID) as Character;
if (endWatchman != null) { InitializeWatchman(endWatchman); }
endRoundButton.UpdateManually(deltaTime);
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || ForceMapUI) { return; }
}
if (Level.Loaded.Type == LevelData.LevelType.Outpost)
{
if (wasDocked)
{
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
bool isDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
if (!isDocked)
{
//undocked from outpost, need to choose a destination
ForceMapUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
}
}
else
{
//wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost
if (!Submarine.MainSub.AtStartPosition)
{
ForceMapUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
}
}
if (CampaignUI == null) { InitCampaignUI(); }
}
}
protected override void WatchmanInteract(Character watchman, Character interactor)
public override void End(TransitionType transitionType = TransitionType.None)
{
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) ||
(watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition))
base.End(transitionType);
ForceMapUI = ShowCampaignUI = false;
UpgradeManager.CanUpgrade = true;
// remove all event dialogue boxes
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{
return;
if (mb is GUIMessageBox msgBox)
{
if (mb.UserData is Pair<string, ushort> pair && pair.First.Equals("conversationaction", StringComparison.OrdinalIgnoreCase))
{
msgBox.Close();
}
}
});
if (transitionType == TransitionType.End)
{
EndCampaign();
}
else
{
IsFirstRound = false;
CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition");
}
}
protected override void EndCampaignProjSpecific()
{
if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary)
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition");
GameMain.CampaignEndScreen.OnFinished = () =>
{
GameMain.NetLobbyScreen.Select();
if (GameMain.NetLobbyScreen.ContinueCampaignButton != null) { GameMain.NetLobbyScreen.ContinueCampaignButton.Enabled = false; }
if (GameMain.NetLobbyScreen.QuitCampaignButton != null) { GameMain.NetLobbyScreen.QuitCampaignButton.Enabled = false; }
};
}
private IEnumerable<object> DoEndCampaignCameraTransition()
{
Character controlled = Character.Controlled;
if (controlled != null)
{
controlled.AIController.Enabled = false;
}
if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt"))
{
return;
}
GUI.DisableHUD = true;
ISpatialEntity endObject = Level.Loaded.LevelObjectManager.GetAllObjects().FirstOrDefault(obj => obj.Prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.LevelEnd);
var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam,
null, Alignment.Center,
fadeOut: true,
duration: 10,
startZoom: null, endZoom: 0.2f);
if (GameMain.Client != null && interactor == Character.Controlled)
while (transition.Running)
{
var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]",
Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "watchmanprompt"
};
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
GameMain.Client.RequestRoundEnd();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
yield return CoroutineStatus.Running;
}
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
public void ClientWrite(IWriteMessage msg)
{
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles);
msg.Write((UInt16)CargoManager.ItemsInBuyCrate.Count);
foreach (PurchasedItem pi in CargoManager.ItemsInBuyCrate)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, 100);
}
msg.Write((UInt16)CargoManager.PurchasedItems.Count);
foreach (PurchasedItem pi in CargoManager.PurchasedItems)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, 100);
}
msg.Write((UInt16)CargoManager.SoldItems.Count);
foreach (SoldItem si in CargoManager.SoldItems)
{
msg.Write(si.ItemPrefab.Identifier);
msg.Write((UInt16)si.ID);
msg.Write(si.Removed);
msg.Write(si.SellerID);
}
msg.Write((ushort)UpgradeManager.PurchasedUpgrades.Count);
foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades)
{
msg.Write(prefab.Identifier);
msg.Write(category.Identifier);
msg.Write((byte)level);
}
}
//static because we may need to instantiate the campaign if it hasn't been done yet
public static void ClientRead(IReadMessage msg)
{
byte campaignID = msg.ReadByte();
UInt16 updateID = msg.ReadUInt16();
UInt16 saveID = msg.ReadUInt16();
string mapSeed = msg.ReadString();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte();
bool isFirstRound = msg.ReadBoolean();
byte campaignID = msg.ReadByte();
UInt16 updateID = msg.ReadUInt16();
UInt16 saveID = msg.ReadUInt16();
string mapSeed = msg.ReadString();
UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
Dictionary<string, float> factionReps = new Dictionary<string, float>();
byte factionsCount = msg.ReadByte();
for (int i = 0; i < factionsCount; i++)
{
factionReps.Add(msg.ReadString(), msg.ReadSingle());
}
UInt16 startWatchmanID = msg.ReadUInt16();
UInt16 endWatchmanID = msg.ReadUInt16();
bool forceMapUI = msg.ReadBoolean();
int money = msg.ReadInt32();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean();
byte missionCount = msg.ReadByte();
List<Pair<string, byte>> availableMissions = new List<Pair<string, byte>>();
for (int i = 0; i < missionCount; i++)
{
string missionIdentifier = msg.ReadString();
byte connectionIndex = msg.ReadByte();
availableMissions.Add(new Pair<string, byte>(missionIdentifier, connectionIndex));
}
UInt16? storeBalance = null;
if (msg.ReadBoolean())
{
storeBalance = msg.ReadUInt16();
}
UInt16 buyCrateItemCount = msg.ReadUInt16();
List<PurchasedItem> buyCrateItems = new List<PurchasedItem>();
for (int i = 0; i < buyCrateItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
buyCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
UInt16 purchasedItemCount = msg.ReadUInt16();
List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
@@ -166,65 +578,129 @@ namespace Barotrauma
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
UInt16 soldItemCount = msg.ReadUInt16();
List<SoldItem> soldItems = new List<SoldItem>();
for (int i = 0; i < soldItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
UInt16 id = msg.ReadUInt16();
bool removed = msg.ReadBoolean();
byte sellerId = msg.ReadByte();
soldItems.Add(new SoldItem(ItemPrefab.Prefabs[itemPrefabIdentifier], id, removed, sellerId));
}
ushort pendingUpgradeCount = msg.ReadUInt16();
List<PurchasedUpgrade> pendingUpgrades = new List<PurchasedUpgrade>();
for (int i = 0; i < pendingUpgradeCount; i++)
{
string upgradeIdentifier = msg.ReadString();
UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier);
string categoryIdentifier = msg.ReadString();
UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier);
int upgradeLevel = msg.ReadByte();
if (prefab == null || category == null) { continue; }
pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
}
bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null;
if (hasCharacterData)
{
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
}
MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign;
if (campaign == null || campaignID != campaign.CampaignID)
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID)
{
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath,
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign"));
campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, mapSeed);
campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign.CampaignID = campaignID;
campaign.GenerateMap(mapSeed);
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
//server has a newer save file
if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID))
{
/*//stop any active campaign save transfers, they're outdated now
List<FileReceiver.FileTransferIn> saveTransfers =
GameMain.Client.FileReceiver.ActiveTransfers.FindAll(t => t.FileType == FileTransferType.CampaignSave);
foreach (var transfer in saveTransfers)
{
GameMain.Client.FileReceiver.StopTransfer(transfer);
}
GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null);*/
campaign.PendingSaveID = saveID;
}
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID))
{
campaign.SuppressStateSending = true;
campaign.IsFirstRound = isFirstRound;
//we need to have the latest save file to display location/mission/store
if (campaign.LastSaveID == saveID)
{
campaign.ForceMapUI = forceMapUI;
UpgradeStore.WaitForServerUpdate = false;
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex);
campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems);
campaign.CargoManager.SetPurchasedItems(purchasedItems);
campaign.CargoManager.SetSoldItems(soldItems);
if (storeBalance.HasValue) { campaign.Map.CurrentLocation.StoreCurrentBalance = storeBalance.Value; }
campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades);
campaign.UpgradeManager.PurchasedUpgrades.Clear();
foreach (var (identifier, rep) in factionReps)
{
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (faction?.Reputation != null)
{
faction.Reputation.Value = rep;
}
else
{
DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\".");
}
}
if (reputation.HasValue)
{
campaign.Map.CurrentLocation.Reputation.Value = reputation.Value;
campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
}
foreach (var availableMission in availableMissions)
{
MissionPrefab missionPrefab = MissionPrefab.List.Find(mp => mp.Identifier == availableMission.First);
if (missionPrefab == null)
{
DebugConsole.ThrowError($"Error when receiving campaign data from the server: mission prefab \"{availableMission.First}\" not found.");
continue;
}
if (availableMission.Second < 0 || availableMission.Second >= campaign.Map.CurrentLocation.Connections.Count)
{
DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.First}\" out of range (index: {availableMission.Second}, current location: {campaign.Map.CurrentLocation.Name}, connections: {campaign.Map.CurrentLocation.Connections.Count}).");
continue;
}
LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.Second];
campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection);
}
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
campaign.startWatchmanID = startWatchmanID;
campaign.endWatchmanID = endWatchmanID;
bool shouldRefresh = campaign.Money != money ||
campaign.PurchasedHullRepairs != purchasedHullRepairs ||
campaign.PurchasedItemRepairs != purchasedItemRepairs ||
campaign.PurchasedLostShuttles != purchasedLostShuttles;
campaign.Money = money;
campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles;
if (shouldRefresh)
{
campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
}
if (myCharacterInfo != null)
{
GameMain.Client.CharacterInfo = myCharacterInfo;
@@ -240,9 +716,68 @@ namespace Barotrauma
}
}
public void ClientReadCrew(IReadMessage msg)
{
ushort availableHireLength = msg.ReadUInt16();
List<CharacterInfo> availableHires = new List<CharacterInfo>();
for (int i = 0; i < availableHireLength; i++)
{
CharacterInfo hire = CharacterInfo.ClientRead("human", msg);
hire.Salary = msg.ReadInt32();
availableHires.Add(hire);
}
ushort pendingHireLength = msg.ReadUInt16();
List<int> pendingHires = new List<int>();
for (int i = 0; i < pendingHireLength; i++)
{
pendingHires.Add(msg.ReadInt32());
}
bool validateHires = msg.ReadBoolean();
bool fireCharacter = msg.ReadBoolean();
int firedIdentifier = -1;
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
if (fireCharacter)
{
CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == 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); }
}
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null)
{
CampaignUI?.CrewManagement?.SetHireables(map.CurrentLocation, availableHires);
if (validateHires) { CampaignUI?.CrewManagement.ValidatePendingHires(); }
CampaignUI?.CrewManagement?.SetPendingHires(pendingHires, map?.CurrentLocation);
if (fireCharacter) { CampaignUI?.CrewManagement.UpdateCrew(); }
}
}
public override void Save(XElement element)
{
//do nothing, the clients get the save files from the server
}
public void LoadState(string filePath)
{
DebugConsole.Log($"Loading save file for an existing game session ({filePath})");
SaveUtil.DecompressToDirectory(filePath, SaveUtil.TempPath, null);
string gamesessionDocPath = Path.Combine(SaveUtil.TempPath, "gamesession.xml");
XDocument doc = XMLExtensions.TryLoadXml(gamesessionDocPath);
if (doc == null)
{
DebugConsole.ThrowError($"Failed to load the state of a multiplayer campaign. Could not open the file \"{gamesessionDocPath}\".");
return;
}
Load(doc.Root.Element("MultiPlayerCampaign"));
SubmarineInfo selectedSub;
GameMain.GameSession.OwnedSubmarines = SaveUtil.LoadOwnedSubmarines(doc, out selectedSub);
GameMain.GameSession.SubmarineInfo = selectedSub;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,80 +0,0 @@
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class SubTestMode : GameMode
{
public SubTestMode(GameModePreset preset, object param)
: base(preset, param)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
isRunning = true;
CrewManager.InitSinglePlayerRound();
Submarine.MainSub.SetPosition(Vector2.Zero);
}
public override void Draw(SpriteBatch spriteBatch)
{
if (!isRunning|| GUI.DisableHUD || GUI.DisableUpperHUD) return;
if (Submarine.MainSub == null) return;
}
public override void AddToGUIUpdateList()
{
if (!isRunning) return;
base.AddToGUIUpdateList();
CrewManager.AddToGUIUpdateList();
}
public override void Update(float deltaTime)
{
if (!isRunning) { return; }
base.Update(deltaTime);
}
public override void End(string endMessage = "")
{
isRunning = false;
GameMain.GameSession.EndRound("");
CrewManager.EndRound();
Submarine.Unload();
GameMain.SubEditorScreen.Select();
}
private bool EndRound(Submarine leavingSub)
{
isRunning = false;
End("");
return true;
}
}
}
@@ -0,0 +1,34 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
class TestGameMode : GameMode
{
public Action OnRoundEnd;
public TestGameMode(GameModePreset preset) : base(preset)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
CrewManager.InitSinglePlayerRound();
}
public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{
OnRoundEnd?.Invoke();
}
}
}
@@ -614,7 +614,7 @@ namespace Barotrauma.Tutorials
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false;
var cinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, 5.0f);
var cinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight, duration: 5.0f);
while (cinematic.Running)
{
@@ -101,6 +101,7 @@ namespace Barotrauma.Tutorials
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor"));
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
captain_medic.TeamID = Character.TeamType.Team1;
captain_medic.GiveJobItems(null);
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
@@ -123,14 +124,17 @@ namespace Barotrauma.Tutorials
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.TeamID = Character.TeamType.Team1;
captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.TeamID = Character.TeamType.Team1;
captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.TeamID = Character.TeamType.Team1;
captain_engineer.GiveJobItems();
captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false;
@@ -195,7 +199,7 @@ namespace Barotrauma.Tutorials
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
HighlightOrderOption("fireatwill");
}
while (!HasOrder(captain_security, "operateweapons", "fireatwill"));
while (!HasOrder(captain_security, "operateweapons"));
RemoveCompletedObjective(segments[2]);
yield return new WaitForSeconds(4f, false);
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command));
@@ -80,6 +80,7 @@ namespace Barotrauma.Tutorials
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
patient1.TeamID = Character.TeamType.Team1;
patient1.GiveJobItems(null);
patient1.CanSpeak = false;
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false);
@@ -87,22 +88,26 @@ namespace Barotrauma.Tutorials
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
patient2.TeamID = Character.TeamType.Team1;
patient2.GiveJobItems(null);
patient2.CanSpeak = false;
patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient1.TeamID = Character.TeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = Character.TeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient3.TeamID = Character.TeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3);
@@ -376,7 +376,7 @@ namespace Barotrauma.Tutorials
}
}
yield return null;
} while (!engineer_brokenJunctionBox.IsFullCondition); // Wait until repaired
} while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired
SetHighlight(engineer_brokenJunctionBox, false);
RemoveCompletedObjective(segments[2]);
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
@@ -408,15 +408,20 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4); // Repair junction box
while (ContentRunning) yield return null;
SetHighlight(engineer_submarineJunctionBox_1, true);
SetHighlight(engineer_submarineJunctionBox_2, true);
SetHighlight(engineer_submarineJunctionBox_3, true);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor);
SetHighlight(engineer_submarineJunctionBox_1, true);
SetHighlight(engineer_submarineJunctionBox_2, true);
SetHighlight(engineer_submarineJunctionBox_3, true);
Repairable repairableJunctionBoxComponent1 = engineer_submarineJunctionBox_1.GetComponent<Repairable>();
Repairable repairableJunctionBoxComponent2 = engineer_submarineJunctionBox_2.GetComponent<Repairable>();
Repairable repairableJunctionBoxComponent3 = engineer_submarineJunctionBox_3.GetComponent<Repairable>();
// Remove highlights when each individual machine is repaired
do { CheckJunctionBoxHighlights(); yield return null; } while (!engineer_submarineJunctionBox_1.IsFullCondition || !engineer_submarineJunctionBox_2.IsFullCondition || !engineer_submarineJunctionBox_3.IsFullCondition);
CheckJunctionBoxHighlights();
do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (engineer_submarineJunctionBox_1.Condition < repairableJunctionBoxComponent1.RepairThreshold || engineer_submarineJunctionBox_2.Condition < repairableJunctionBoxComponent2.RepairThreshold || engineer_submarineJunctionBox_3.Condition < repairableJunctionBoxComponent3.RepairThreshold);
CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3);
RemoveCompletedObjective(segments[4]);
yield return new WaitForSeconds(2f, false);
@@ -557,19 +562,19 @@ namespace Barotrauma.Tutorials
}
}
private void CheckJunctionBoxHighlights()
private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{
if (engineer_submarineJunctionBox_1.IsFullCondition && engineer_submarineJunctionBox_1.ExternalHighlight)
if (engineer_submarineJunctionBox_1.Condition > comp1.RepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight)
{
SetHighlight(engineer_submarineJunctionBox_1, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1);
}
if (engineer_submarineJunctionBox_2.IsFullCondition && engineer_submarineJunctionBox_2.ExternalHighlight)
if (engineer_submarineJunctionBox_2.Condition > comp2.RepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight)
{
SetHighlight(engineer_submarineJunctionBox_2, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2);
}
if (engineer_submarineJunctionBox_3.IsFullCondition && engineer_submarineJunctionBox_3.ExternalHighlight)
if (engineer_submarineJunctionBox_3.Condition > comp3.RepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight)
{
SetHighlight(engineer_submarineJunctionBox_3, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3);
@@ -385,7 +385,8 @@ namespace Barotrauma.Tutorials
}
}
if (!gotOxygenTank && mechanic.Inventory.FindItemByIdentifier("oxygentank") != null)
if (!gotOxygenTank && (mechanic.Inventory.FindItemByIdentifier("oxygentank") != null ||
mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") != null))
{
gotOxygenTank = true;
}
@@ -551,7 +552,7 @@ namespace Barotrauma.Tutorials
do
{
yield return null;
if (!mechanic_brokenPump.Item.IsFullCondition)
if (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold)
{
if (!mechanic.HasEquippedItem("wrench"))
{
@@ -575,7 +576,7 @@ namespace Barotrauma.Tutorials
}
}
}
} while (!mechanic_brokenPump.Item.IsFullCondition || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive);
} while (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive);
RemoveCompletedObjective(segments[9]);
SetHighlight(mechanic_brokenPump.Item, false);
do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening);
@@ -592,9 +593,14 @@ namespace Barotrauma.Tutorials
SetHighlight(mechanic_ballastPump_1.Item, true);
SetHighlight(mechanic_ballastPump_2.Item, true);
SetHighlight(mechanic_submarineEngine.Item, true);
Repairable repairablePumpComponent1 = mechanic_ballastPump_1.Item.GetComponent<Repairable>();
Repairable repairablePumpComponent2 = mechanic_ballastPump_2.Item.GetComponent<Repairable>();
Repairable repairableEngineComponent = mechanic_submarineEngine.Item.GetComponent<Repairable>();
// Remove highlights when each individual machine is repaired
do { CheckHighlights(); yield return null; } while (!mechanic_ballastPump_1.Item.IsFullCondition || !mechanic_ballastPump_2.Item.IsFullCondition || !mechanic_submarineEngine.Item.IsFullCondition);
CheckHighlights();
do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (mechanic_ballastPump_1.Item.Condition < repairablePumpComponent1.RepairThreshold || mechanic_ballastPump_2.Item.Condition < repairablePumpComponent2.RepairThreshold || mechanic_submarineEngine.Item.Condition < repairableEngineComponent.RepairThreshold);
CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent);
RemoveCompletedObjective(segments[10]);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null);
@@ -617,19 +623,19 @@ namespace Barotrauma.Tutorials
return false;
}
private void CheckHighlights()
private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{
if (mechanic_ballastPump_1.Item.IsFullCondition && mechanic_ballastPump_1.Item.ExternalHighlight)
if (mechanic_ballastPump_1.Item.Condition > comp1.RepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight)
{
SetHighlight(mechanic_ballastPump_1.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item);
}
if (mechanic_ballastPump_2.Item.IsFullCondition && mechanic_ballastPump_2.Item.ExternalHighlight)
if (mechanic_ballastPump_2.Item.Condition > comp2.RepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight)
{
SetHighlight(mechanic_ballastPump_2.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item);
}
if (mechanic_submarineEngine.Item.IsFullCondition && mechanic_submarineEngine.Item.ExternalHighlight)
if (mechanic_submarineEngine.Item.Condition > comp3.RepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight)
{
SetHighlight(mechanic_submarineEngine.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item);
@@ -315,13 +315,14 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
((EnemyAIController)officer_hammerhead.AIController).StayInsideLevel = false;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
SetHighlight(officer_coilgunPeriscope, true);
float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos);
do
{
float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition);
if (distance > originalDistance * 1.5f)
if (distance > originalDistance * 1.5f || officer_hammerhead.WorldPosition.Y > officer_coilgunPeriscope.WorldPosition.Y)
{
// Don't let the Hammerhead go too far.
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000));
@@ -329,7 +330,13 @@ namespace Barotrauma.Tutorials
if (distance > originalDistance)
{
// Ensure that the Hammerhead targets the player
officer.AiTarget.SoundRange = float.MaxValue;
officer.AiTarget.SightRange = float.MaxValue;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
if ((officer_hammerhead.AIController as EnemyAIController)?.SelectedTargetingParams != null)
{
((EnemyAIController)officer_hammerhead.AIController).SelectedTargetingParams.ReactDistance = 5000.0f;
}
/*var ai = officer_hammerhead.AIController as EnemyAIController;
ai.sight = 2.0f;*/
}
@@ -58,30 +58,31 @@ namespace Barotrauma.Tutorials
{
SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams);
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier.Equals(levelParams, StringComparison.OrdinalIgnoreCase));
yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(subInfo, "",
GameModePreset.List.Find(g => g.Identifier == "tutorial"));
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefab: null);
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams != null)
{
Biome biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b));
Biome biome =
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
LevelGenerationParams.GetBiomes().First();
if (startOutpostPath != string.Empty)
if (!string.IsNullOrEmpty(startOutpostPath))
{
startOutpost = new SubmarineInfo(startOutpostPath);
}
if (endOutpostPath != string.Empty)
if (!string.IsNullOrEmpty(endOutpostPath))
{
endOutpost = new SubmarineInfo(endOutpostPath);
}
Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost);
GameMain.GameSession.StartRound(tutorialLevel);
LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome);
GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost, endOutpost: endOutpost);
}
else
{
@@ -100,6 +101,13 @@ namespace Barotrauma.Tutorials
base.Start();
Submarine.MainSub.GodMode = true;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != null && wall.Submarine.Info.IsOutpost)
{
wall.Indestructible = true;
}
}
CharacterInfo charInfo = configElement.Element("Character") == null ?
new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) :
@@ -114,6 +122,7 @@ namespace Barotrauma.Tutorials
}
character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false);
character.TeamID = Character.TeamType.Team1;
Character.Controlled = character;
character.GiveJobItems(null);
@@ -126,19 +135,14 @@ namespace Barotrauma.Tutorials
idCard.AddTag("com");
idCard.AddTag("eng");
List<Entity> entities = Entity.GetEntityList();
for (int i = 0; i < entities.Count; i++)
foreach (Item item in Item.ItemList)
{
if (entities[i] is Item)
Door door = item.GetComponent<Door>();
if (door != null)
{
Door door = (entities[i] as Item).GetComponent<Door>();
if (door != null)
{
door.CanBeWelded = false;
}
door.CanBeWelded = false;
}
}
}
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
}
@@ -284,7 +288,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(waitBeforeFade);
var endCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, fadeOutTime);
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, duration: fadeOutTime);
currentTutorialCompleted = Completed = true;
while (endCinematic.Running) yield return null;
Stop();
@@ -26,6 +26,7 @@ namespace Barotrauma.Tutorials
protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 };
protected string playableContentPath;
protected Point screenResolution;
protected WindowMode windowMode;
protected float prevUIScale;
private GUIFrame holderFrame, objectiveFrame;
@@ -207,7 +208,7 @@ namespace Barotrauma.Tutorials
public virtual void AddToGUIUpdateList()
{
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale)
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameMain.Config.WindowMode != windowMode)
{
CreateObjectiveFrame();
}
@@ -340,6 +341,7 @@ namespace Barotrauma.Tutorials
}
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
windowMode = GameMain.Config.WindowMode;
prevUIScale = GUI.Scale;
}
@@ -11,8 +11,8 @@ namespace Barotrauma
tutorial.Initialize();
}
public TutorialMode(GameModePreset preset, object param)
: base(preset, param)
public TutorialMode(GameModePreset preset)
: base(preset)
{
}
@@ -21,6 +21,11 @@ namespace Barotrauma
base.Start();
GameMain.GameSession.CrewManager = new CrewManager(true);
Tutorial.Start();
foreach (Item item in Item.ItemList)
{
//don't consider the items to belong in the outpost to prevent the stealing icon from showing
item.SpawnedInOutpost = false;
}
}
public override void AddToGUIUpdateList()
@@ -1,14 +1,19 @@
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
partial class GameSession
{
public RoundSummary RoundSummary { get; private set; }
public RoundSummary RoundSummary
{
get;
private set;
}
public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null;
public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu;
private TabMenu tabMenu;
public bool ToggleTabMenu()
@@ -46,30 +51,20 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime)
{
if (GUI.DisableHUD) return;
if (GUI.DisableHUD) { return; }
if (GameMode.IsRunning)
if (tabMenu == null)
{
if (tabMenu == null)
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
{
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
{
ToggleTabMenu();
}
}
else
{
tabMenu.Update();
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
{
ToggleTabMenu();
}
ToggleTabMenu();
}
}
else
{
if (tabMenu != null)
tabMenu.Update();
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
{
ToggleTabMenu();
}
@@ -97,7 +92,6 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch)
{
if (GUI.DisableHUD) return;
GameMode?.Draw(spriteBatch);
}
}
@@ -1,185 +1,654 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
namespace Barotrauma
{
class RoundSummary
{
private Location startLocation, endLocation;
private const float jobColumnWidthPercentage = 0.11f;
private const float characterColumnWidthPercentage = 0.44f;
private const float statusColumnWidthPercentage = 0.45f;
private GameSession gameSession;
private int jobColumnWidth, characterColumnWidth, statusColumnWidth;
private Mission selectedMission;
public RoundSummary(GameSession gameSession)
private readonly SubmarineInfo sub;
private readonly Mission selectedMission;
private readonly Location startLocation, endLocation;
private readonly GameMode gameMode;
private readonly float initialLocationReputation;
private readonly Dictionary<Faction, float> initialFactionReputations = new Dictionary<Faction, float>();
public GUILayoutGroup ButtonArea { get; private set; }
public GUIButton ContinueButton { get; private set; }
public GUIComponent Frame { get; private set; }
public RoundSummary(SubmarineInfo sub, GameMode gameMode, Mission selectedMission, Location startLocation, Location endLocation)
{
this.gameSession = gameSession;
startLocation = gameSession.StartLocation;
endLocation = gameSession.EndLocation;
selectedMission = gameSession.Mission;
this.sub = sub;
this.gameMode = gameMode;
this.selectedMission = selectedMission;
this.startLocation = startLocation;
this.endLocation = endLocation;
initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f;
if (gameMode is CampaignMode campaignMode)
{
foreach (Faction faction in campaignMode.Factions)
{
initialFactionReputations.Add(faction, faction.Reputation.Value);
}
}
}
public GUIFrame CreateSummaryFrame(string endMessage)
public GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, List<TraitorMissionResult> traitorResults, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{
bool singleplayer = GameMain.NetworkMember == null;
bool gameOver = gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated);
bool progress = Submarine.MainSub.AtEndPosition;
bool gameOver =
gameSession.GameMode.IsSinglePlayer ?
gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated) :
gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated || c.IsBot);
if (!singleplayer)
{
SoundPlayer.OverrideMusicType = gameOver ? "crewdead" : "endround";
SoundPlayer.OverrideMusicDuration = 18.0f;
}
GUIFrame background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker");
GUIFrame frame = new GUIFrame(new RectTransform(Vector2.One, background.RectTransform, Anchor.Center), style: null)
GUIFrame background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker")
{
UserData = "roundsummary"
UserData = this
};
int width = 760, height = 500;
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.5f), frame.RectTransform, Anchor.Center, minSize: new Point(width, height)));
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center))
List<GUIComponent> rightPanels = new List<GUIComponent>();
int minWidth = 400, minHeight = 350;
int padding = GUI.IntScale(25.0f);
//crew panel -------------------------------------------------------------------------------
GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
GUIFrame crewFrameInner = new GUIFrame(new RectTransform(new Point(crewFrame.Rect.Width - padding * 2, crewFrame.Rect.Height - padding * 2), crewFrame.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner.RectTransform, Anchor.Center))
{
Stretch = true,
RelativeSpacing = 0.03f
Stretch = true
};
GUIListBox infoTextBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), paddedFrame.RectTransform))
var crewHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent.RectTransform),
TextManager.Get("crew"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
crewHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader.Rect.Height * 2.0f));
CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != Character.TeamType.Team2));
//another crew frame for the 2nd team in combat missions
if (gameSession.Mission is CombatMission)
{
Spacing = (int)(5 * GUI.Scale)
};
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), infoTextBox.Content.RectTransform), style: null);
string summaryText = TextManager.GetWithVariables(gameOver ? "RoundSummaryGameOver" :
(progress ? "RoundSummaryProgress" : "RoundSummaryReturn"), new string[2] { "[sub]", "[location]" },
new string[2] { Submarine.MainSub.Info.Name, progress ? GameMain.GameSession.EndLocation.Name : GameMain.GameSession.StartLocation.Name });
var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
summaryText, wrap: true);
GUIComponent endText = null;
if (!string.IsNullOrWhiteSpace(endMessage))
{
endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.GetServerMessage(endMessage), wrap: true);
crewHeader.Text = CombatMission.GetTeamName(Character.TeamType.Team1);
GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
rightPanels.Add(crewFrame2);
GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center))
{
Stretch = true
};
var crewHeader2 = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent2.RectTransform),
CombatMission.GetTeamName(Character.TeamType.Team2), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == Character.TeamType.Team2));
}
//don't show the mission info if the mission was not completed and there's no localized "mission failed" text available
if (GameMain.GameSession.Mission != null)
//header -------------------------------------------------------------------------------
string headerText = GetHeaderText(gameOver, transitionType);
GUITextBlock headerTextBlock = null;
if (!string.IsNullOrEmpty(headerText))
{
string message = GameMain.GameSession.Mission.Completed ? GameMain.GameSession.Mission.SuccessMessage : GameMain.GameSession.Mission.FailureMessage;
if (!string.IsNullOrEmpty(message))
{
//spacing
var spacingTransform = new RectTransform(new Vector2(1.0f, 0.1f), infoTextBox.Content.RectTransform);
new GUIFrame(spacingTransform, style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("Mission"), GameMain.GameSession.Mission.Name),
font: GUI.LargeFont);
var missionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
message, wrap: true);
if (GameMain.GameSession.Mission.Completed && singleplayer)
{
var missionReward = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.GetWithVariable("MissionReward", "[reward]", GameMain.GameSession.Mission.Reward.ToString()));
}
}
headerTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), crewFrame.RectTransform, Anchor.TopLeft, Pivot.BottomLeft),
headerText, textAlignment: Alignment.BottomLeft, font: GUI.LargeFont, wrap: true);
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("RoundSummaryCrewStatus"), font: GUI.LargeFont);
GUIListBox characterListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform, minSize: new Point(0, 75)), isHorizontal: true);
foreach (CharacterInfo characterInfo in gameSession.CrewManager.GetCharacterInfos())
//traitor panel -------------------------------------------------------------------------------
if (traitorResults != null && traitorResults.Any())
{
if (GameMain.GameSession.Mission is CombatMission &&
characterInfo.TeamID != GameMain.GameSession.WinningTeam)
{
continue;
}
GUIFrame traitorframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize));
rightPanels.Add(traitorframe);
GUIFrame traitorframeInner = new GUIFrame(new RectTransform(new Point(traitorframe.Rect.Width - padding * 2, traitorframe.Rect.Height - padding * 2), traitorframe.RectTransform, Anchor.Center), style: "InnerFrame");
var characterFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), characterListBox.Content.RectTransform, minSize: new Point(170, 0)))
var traitorContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), traitorframeInner.RectTransform, Anchor.Center))
{
CanBeFocused = false,
Stretch = true
};
characterInfo.CreateCharacterFrame(characterFrame,
characterInfo.Job != null ? (characterInfo.Name + '\n' + "(" + characterInfo.Job.Name + ")") : characterInfo.Name, null);
var traitorHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorContent.RectTransform),
TextManager.Get("traitors"), font: GUI.SubHeadingFont);
traitorHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(traitorHeader.Rect.Height * 2.0f));
string statusText = TextManager.Get("StatusOK");
Color statusColor = Color.DarkGreen;
GUIListBox listBox = CreateCrewList(traitorContent, traitorResults.SelectMany(tr => tr.Characters.Select(c => c.Info)));
Character character = characterInfo.Character;
if (character == null || character.IsDead)
foreach (var traitorResult in traitorResults)
{
if (characterInfo.CauseOfDeath == null)
var traitorMission = TraitorMissionPrefab.List.Find(t => t.Identifier == traitorResult.MissionIdentifier);
if (traitorMission == null) { continue; }
//spacing
new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(25)), listBox.Content.RectTransform), style: null);
var traitorResultHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), listBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
RelativeSpacing = 0.05f,
Stretch = true
};
new GUIImage(new RectTransform(new Point(traitorResultHorizontal.Rect.Height), traitorResultHorizontal.RectTransform), traitorMission.Icon, scaleToFit: true)
{
Color = traitorMission.IconColor
};
string traitorMessage = TextManager.GetServerMessage(traitorResult.EndMessage);
if (!string.IsNullOrEmpty(traitorMessage))
{
var textContent = new GUILayoutGroup(new RectTransform(Vector2.One, traitorResultHorizontal.RectTransform))
{
RelativeSpacing = 0.025f
};
var traitorStatusText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
TextManager.Get(traitorResult.Success ? "missioncompleted" : "missionfailed"),
textColor: traitorResult.Success ? GUI.Style.Green : GUI.Style.Red, font: GUI.SubHeadingFont);
var traitorMissionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
traitorMessage, font: GUI.SmallFont, wrap: true);
traitorResultHorizontal.Recalculate();
traitorStatusText.CalculateHeightFromText();
traitorMissionInfo.CalculateHeightFromText();
traitorStatusText.RectTransform.MinSize = new Point(0, traitorStatusText.Rect.Height);
traitorMissionInfo.RectTransform.MinSize = new Point(0, traitorMissionInfo.Rect.Height);
textContent.RectTransform.MaxSize = new Point(int.MaxValue, (int)((traitorStatusText.Rect.Height + traitorMissionInfo.Rect.Height) * 1.2f));
traitorResultHorizontal.RectTransform.MinSize = new Point(0, traitorStatusText.RectTransform.MinSize.Y + traitorMissionInfo.RectTransform.MinSize.Y);
}
else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null)
}
}
//reputation panel -------------------------------------------------------------------------------
if (gameMode is CampaignMode campaignMode)
{
GUIFrame reputationframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize));
rightPanels.Add(reputationframe);
GUIFrame reputationframeInner = new GUIFrame(new RectTransform(new Point(reputationframe.Rect.Width - padding * 2, reputationframe.Rect.Height - padding * 2), reputationframe.RectTransform, Anchor.Center), style: "InnerFrame");
var reputationContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), reputationframeInner.RectTransform, Anchor.Center))
{
Stretch = true
};
var reputationHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), reputationContent.RectTransform),
TextManager.Get("reputation"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
reputationHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(reputationHeader.Rect.Height * 2.0f));
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, reputationContent.RectTransform))
{
Padding = new Vector4(2, 5, 0, 0)
};
reputationList.ContentBackground.Color = Color.Transparent;
if (startLocation.Type.HasOutpost && startLocation.Reputation != null)
{
var iconStyle = GUI.Style.GetComponentStyle("LocationReputationIcon");
CreateReputationElement(
reputationList.Content,
startLocation.Name,
startLocation.Reputation.Value, startLocation.Reputation.NormalizedValue, initialLocationReputation,
startLocation.Type.Name, "",
iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White);
}
foreach (Faction faction in campaignMode.Factions)
{
float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction))
{
string errorMsg = "Character \"" + character.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified).";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
initialReputation = initialFactionReputations[faction];
}
else
{
statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ?
characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription :
TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString());
DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round).");
}
CreateReputationElement(
reputationList.Content,
faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
}
float otherElementHeight = 0.0f;
float maxDescriptionHeight = 0.0f;
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
maxDescriptionHeight = Math.Max(maxDescriptionHeight, descriptionElement.TextSize.Y * 1.1f);
otherElementHeight = Math.Max(otherElementHeight, descriptionElement.Parent.Rect.Height - descriptionElement.TextSize.Y);
}
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
descriptionElement.RectTransform.MaxSize = new Point(int.MaxValue, (int)(maxDescriptionHeight));
child.RectTransform.MaxSize = new Point(int.MaxValue, (int)((maxDescriptionHeight + otherElementHeight) * 1.2f));
(descriptionElement?.Parent as GUILayoutGroup).Recalculate();
}
}
//mission panel -------------------------------------------------------------------------------
GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.39f, 0.22f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4)));
GUIFrame missionframeInner = new GUIFrame(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center), style: "InnerFrame");
var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionframeInner.RectTransform, Anchor.Center))
{
RelativeSpacing = 0.05f,
Stretch = true
};
if (!string.IsNullOrWhiteSpace(endMessage))
{
var endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContent.RectTransform),
TextManager.GetServerMessage(endMessage), wrap: true);
endText.RectTransform.MinSize = new Point(0, endText.Rect.Height);
var line = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), missionContent.RectTransform), style: "HorizontalLine");
line.RectTransform.NonScaledSize = new Point(line.Rect.Width, GUI.IntScale(5.0f));
}
var missionContentHorizontal = new GUILayoutGroup(new RectTransform(Vector2.One, missionContent.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
{
RelativeSpacing = 0.025f,
Stretch = true
};
Mission displayedMission = selectedMission ?? startLocation.SelectedMission;
string missionMessage = "";
GUIImage missionIcon;
if (displayedMission != null)
{
missionMessage =
displayedMission == selectedMission ?
displayedMission.Completed ? displayedMission.SuccessMessage : displayedMission.FailureMessage :
displayedMission.Description;
missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), displayedMission.Prefab.Icon, scaleToFit: true)
{
Color = displayedMission.Prefab.IconColor
};
if (displayedMission == selectedMission)
{
new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true);
}
}
else
{
missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
}
var missionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, missionContentHorizontal.RectTransform))
{
RelativeSpacing = 0.05f
};
missionContentHorizontal.Recalculate();
missionContent.Recalculate();
missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height);
missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width);
GUITextBlock missionDescription = null;
if (displayedMission == null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.Get("nomission"), font: GUI.LargeFont);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("Mission"), displayedMission.Name), font: GUI.SubHeadingFont);
missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
missionMessage, wrap: true);
if (displayedMission == selectedMission && displayedMission.Completed)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.GetWithVariable("MissionReward", "[reward]", rewardText));
}
}
ButtonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), missionContent.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomRight)
{
IgnoreLayoutGroups = true,
RelativeSpacing = 0.025f
};
ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close"));
ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height);
ButtonArea.RectTransform.IsFixedSize = true;
missionContent.Recalculate();
//description overlapping with the buttons -> switch to small font
if (missionDescription != null && missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y)
{
missionDescription.Font = GUI.Style.SmallFont;
//still overlapping -> shorten the text
if (missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y && missionDescription.WrappedText.Contains('\n'))
{
missionDescription.ToolTip = missionDescription.Text;
missionDescription.Text = missionDescription.WrappedText.Split('\n').First() + "...";
}
}
// set layout -------------------------------------------------------------------
int panelSpacing = GUI.IntScale(20);
int totalHeight = crewFrame.Rect.Height + panelSpacing + missionframe.Rect.Height;
int totalWidth = crewFrame.Rect.Width;
crewFrame.RectTransform.AbsoluteOffset = new Point(0, (GameMain.GraphicsHeight - totalHeight) / 2);
missionframe.RectTransform.AbsoluteOffset = new Point(0, crewFrame.Rect.Bottom + panelSpacing);
if (rightPanels.Any())
{
totalWidth = crewFrame.Rect.Width * 2 + panelSpacing;
if (headerTextBlock != null)
{
headerTextBlock.RectTransform.MinSize = new Point(totalWidth, 0);
}
crewFrame.RectTransform.AbsoluteOffset = new Point(-(crewFrame.Rect.Width + panelSpacing) / 2, crewFrame.RectTransform.AbsoluteOffset.Y);
foreach (var rightPanel in rightPanels)
{
rightPanel.RectTransform.AbsoluteOffset = new Point((rightPanel.Rect.Width + panelSpacing) / 2, crewFrame.RectTransform.AbsoluteOffset.Y);
}
}
if (!(gameSession.GameMode is CampaignMode))
{
var shadow = new GUIFrame(new RectTransform(new Point((int)(totalWidth * 1.2f), GameMain.GraphicsHeight * 2), background.RectTransform, Anchor.Center), style: "OuterGlow")
{
Color = Color.Black
};
shadow.RectTransform.SetAsFirstChild();
}
Frame = background;
return background;
}
private string GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{
string locationName = Submarine.MainSub.AtEndPosition ? endLocation?.Name : startLocation?.Name;
string textTag;
if (gameOver)
{
textTag = "RoundSummaryGameOver";
}
else
{
switch (transitionType)
{
case CampaignMode.TransitionType.LeaveLocation:
locationName = startLocation?.Name;
textTag = "RoundSummaryLeaving";
break;
case CampaignMode.TransitionType.ProgressToNextLocation:
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
locationName = endLocation?.Name;
textTag = "RoundSummaryProgress";
break;
case CampaignMode.TransitionType.ReturnToPreviousLocation:
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
locationName = startLocation?.Name;
textTag = "RoundSummaryReturn";
break;
default:
textTag = Submarine.MainSub.AtEndPosition ? "RoundSummaryProgress" : "RoundSummaryReturn";
break;
}
}
if (textTag == null) { return ""; }
if (locationName == null)
{
DebugConsole.ThrowError($"Error while creating round summary: could not determine destination location. Start location: {startLocation?.Name ?? "null"}, end location: {endLocation?.Name ?? "null"}");
locationName = "[UNKNOWN]";
}
string subName = string.Empty;
SubmarineInfo currentOrPending = SubmarineSelection.CurrentOrPendingSubmarine();
if (currentOrPending != null)
{
subName = currentOrPending.DisplayName;
}
return TextManager.GetWithVariables(textTag, new string[2] { "[sub]", "[location]" }, new string[2] { subName, locationName });
}
private GUIListBox CreateCrewList(GUIComponent parent, IEnumerable<CharacterInfo> characterInfos)
{
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
};
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");
float sizeMultiplier = 1.0f;
//sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.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 = GUI.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = statusButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = statusButton.ForceUpperCase = true;
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
statusColumnWidth = statusButton.Rect.Width;
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
Padding = new Vector4(2, 5, 0, 0),
AutoHideScrollBar = false
};
crewList.ContentBackground.Color = Color.Transparent;
headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
foreach (CharacterInfo characterInfo in characterInfos)
{
if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList);
}
return crewList;
}
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement")
{
CanBeFocused = false,
UserData = characterInfo,
Color = (Character.Controlled?.Info == characterInfo) ? TabMenu.OwnCharacterBGColor : Color.Transparent
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => characterInfo.DrawJobIcon(sb, component.Rect))
{
ToolTip = characterInfo.Job.Name ?? "",
HoverColor = Color.White,
SelectedColor = Color.White
};
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(characterInfo.Name, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: characterInfo.Job.Prefab.UIColor);
string statusText = TextManager.Get("StatusOK");
Color statusColor = GUI.Style.Green;
Character character = characterInfo.Character;
if (character == null || character.IsDead)
{
if (characterInfo.IsNewHire)
{
statusText = TextManager.Get("CampaignCrew.NewHire");
statusColor = GUI.Style.Blue;
}
else if (characterInfo.CauseOfDeath == null)
{
statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
statusColor = Color.DarkRed;
}
else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null)
{
string errorMsg = "Character \"" + characterInfo.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified).";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
statusColor = GUI.Style.Red;
}
else
{
if (character.IsUnconscious)
{
statusText = TextManager.Get("Unconscious");
statusColor = Color.DarkOrange;
}
else if (character.Vitality / character.MaxVitality < 0.8f)
{
statusText = TextManager.Get("Injured");
statusColor = Color.DarkOrange;
}
statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ?
characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription :
TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString());
statusColor = Color.DarkRed;
}
}
else
{
if (character.IsUnconscious)
{
statusText = TextManager.Get("Unconscious");
statusColor = Color.DarkOrange;
}
else if (character.Vitality / character.MaxVitality < 0.8f)
{
statusText = TextManager.Get("Injured");
statusColor = Color.DarkOrange;
}
var textHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), characterFrame.RectTransform, Anchor.BottomCenter), style: "InnerGlow", color: statusColor);
new GUITextBlock(new RectTransform(Vector2.One, textHolder.RectTransform, Anchor.Center),
statusText, Color.White,
textAlignment: Alignment.Center,
wrap: true, font: GUI.SmallFont, style: null);
}
new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomRight)
GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(statusText, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
}
private void CreateReputationElement(GUIComponent parent,
string name, float reputation, float normalizedReputation, float initialReputation,
string shortDescription, string fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{
var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform), style: null);
if (backgroundPortrait != null)
{
RelativeSpacing = 0.05f,
UserData = "buttonarea"
new GUICustomComponent(new RectTransform(Vector2.One, factionFrame.RectTransform), onDraw: (sb, customComponent) =>
{
backgroundPortrait.Draw(sb, customComponent.Rect.Center.ToVector2(), customComponent.Color, backgroundPortrait.size / 2, scale: customComponent.Rect.Width / backgroundPortrait.size.X);
})
{
HideElementsOutsideFrame = true,
IgnoreLayoutGroups = true,
Color = iconColor * 0.2f
};
}
var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.02f,
Stretch = true
};
paddedFrame.Recalculate();
foreach (GUIComponent child in infoTextBox.Content.Children)
var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform))
{
child.CanBeFocused = false;
if (child is GUITextBlock textBlock)
{
textBlock.CalculateHeightFromText();
}
RelativeSpacing = 0.05f,
Stretch = true
};
var factionIcon = new GUIImage(new RectTransform(new Point((int)(factionInfoHorizontal.Rect.Height * 0.7f)), factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{
Color = iconColor
};
factionInfoHorizontal.Recalculate();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), factionTextContent.RectTransform),
name, font: GUI.SubHeadingFont)
{
Padding = Vector4.Zero
};
var factionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.6f), factionTextContent.RectTransform),
shortDescription, font: GUI.SmallFont, wrap: true)
{
UserData = "description",
Padding = Vector4.Zero
};
if (shortDescription != fullDescription && !string.IsNullOrEmpty(fullDescription))
{
factionDescription.ToolTip = fullDescription;
}
return frame;
var sliderHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), factionTextContent.RectTransform),
childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.05f,
Stretch = true
};
sliderHolder.RectTransform.MaxSize = new Point(int.MaxValue, GUI.IntScale(25.0f));
factionTextContent.Recalculate();
new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), onDraw: (sb, customComponent) =>
{
GUI.DrawRectangle(sb, customComponent.Rect, GUI.Style.ColorInventoryBackground, isFilled: true);
if (normalizedReputation < 0.5f)
{
int barWidth = (int)((0.5f - normalizedReputation) * customComponent.Rect.Width);
GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X - barWidth, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Red, isFilled: true);
}
else if (normalizedReputation > 0.5f)
{
int barWidth = (int)((normalizedReputation - 0.5f) * customComponent.Rect.Width);
GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Green, isFilled: true);
}
GUI.DrawLine(sb, new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Y - 2), new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Bottom + 2), factionDescription.TextColor, width: 1);
});
string reputationText = ((int)Math.Round(reputation)).ToString();
int reputationChange = (int)Math.Round( reputation - initialReputation);
if (Math.Abs(reputationChange) > 0)
{
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
string colorStr = XMLExtensions.ColorToString(reputationChange > 0 ? GUI.Style.Green : GUI.Style.Red);
var rtData = RichTextData.GetRichTextData($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)", out string sanitizedText);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
rtData, sanitizedText,
textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
reputationText,
textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont);
}
}
}
}
@@ -0,0 +1,81 @@
#nullable enable
using System;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class UpgradeManager
{
partial void UpgradeNPCSpeak(string text, bool isSinglePlayer, Character? character)
{
if (Level.Loaded?.StartOutpost?.Info?.OutpostNPCs == null) { return; }
if (character != null)
{
Speak(character);
return;
}
foreach (Character npc in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value))
{
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade)
{
Speak(npc);
break;
}
}
void Speak(Character npc)
{
ChatMessage message = ChatMessage.Create(npc.Name, text, ChatMessageType.Default, npc);
if (!isSinglePlayer)
{
GameMain.Client?.AddChatMessage(message);
}
else
{
GameMain.GameSession?.CrewManager?.AddSinglePlayerChatMessage(message);
}
}
}
/// <summary>
/// Server has notified us that upgrades were reset.
/// </summary>
/// <param name="inc"></param>
/// <see cref="UpgradeManager.SendUpgradeResetMessage"/>
public void ClientRead(IReadMessage inc)
{
bool shouldReset = inc.ReadBoolean();
int money = inc.ReadInt32();
// uint length = inc.ReadUInt32();
//
// for (int i = 0; i < length; i++)
// {
// string key = inc.ReadString();
// byte value = inc.ReadByte();
// Metadata.SetValue(key, value);
// }
Campaign.Money = money;
if (shouldReset)
{
ResetUpgrades();
}
// spentMoney is local, so this message box should only appear for those who have spent money on upgrades
if (spentMoney > 0)
{
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new [] { TextManager.Get("Ok") });
msgBox.Buttons[0].OnClicked += msgBox.Close;
}
spentMoney = 0;
PendingUpgrades.Clear();
PurchasedUpgrades.Clear();
CanUpgrade = false;
}
}
}