Added files that were missing from last commit

This commit is contained in:
Markus Isberg
2021-03-06 18:39:28 +02:00
parent cb969c959f
commit 0f6de8ada9
9 changed files with 1941 additions and 0 deletions
@@ -0,0 +1,18 @@
using Barotrauma.Networking;
namespace Barotrauma
{
partial class OutpostDestroyMission : AbandonedOutpostMission
{
public override void ClientReadInitial(IReadMessage msg)
{
base.ClientReadInitial(msg);
ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++)
{
var item = Item.ReadSpawnData(msg);
items.Add(item);
}
}
}
}
@@ -0,0 +1,653 @@
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
static class HintManager
{
private const string HintManagerFile = "hintmanager.xml";
private static HashSet<string> HintIdentifiers { get; set; }
private static Dictionary<string, HashSet<string>> HintTags { get; } = new Dictionary<string, HashSet<string>>();
/// <summary>
/// Hints that have already been shown this round and shouldn't be shown shown again until the next round
/// </summary>
private static HashSet<string> HintsIgnoredThisRound { get; } = new HashSet<string>();
private static GUIMessageBox ActiveHintMessageBox { get; set; }
private static Action OnUpdate { get; set; }
private static double TimeStoppedInteracting { get; set; }
/// <summary>
/// Seconds before any reminders can be shown
/// </summary>
private static int TimeBeforeReminders { get; set; }
/// <summary>
/// Seconds before another reminder can be shown
/// </summary>
private static int ReminderCooldown { get; set; }
private static double TimeReminderLastDisplayed { get; set; }
private static Queue<HintInfo> HintQueue { get; } = new Queue<HintInfo>();
public struct HintInfo
{
public string Identifier { get; }
public string Text { get; }
public Sprite Icon { get; }
public Color? IconColor { get; }
public Action OnUpdate { get; }
public HintInfo(string hintIdentifier, string text, Sprite icon, Color? iconColor, Action onUpdate)
{
Identifier = hintIdentifier;
Text = text;
Icon = icon;
IconColor = iconColor;
OnUpdate = onUpdate;
}
}
private static HashSet<Hull> BallastHulls { get; } = new HashSet<Hull>();
public static void Init()
{
if (File.Exists(HintManagerFile))
{
var doc = XMLExtensions.TryLoadXml(HintManagerFile);
if (doc?.Root != null)
{
HintIdentifiers = new HashSet<string>();
foreach (var element in doc.Root.Elements())
{
GetHintsRecursive(element, element.Name.ToString());
}
}
else
{
DebugConsole.ThrowError($"File \"{HintManagerFile}\" is empty - cannot initialize the HintManager!");
}
}
else
{
DebugConsole.ThrowError($"File \"{HintManagerFile}\" is missing - cannot initialize the HintManager!");
}
static void GetHintsRecursive(XElement element, string identifier)
{
if (!element.HasElements)
{
HintIdentifiers.Add(identifier);
if (element.GetAttributeStringArray("tags", null, convertToLowerInvariant: true) is string[] tags)
{
HintTags.TryAdd(identifier, tags.ToHashSet());
}
return;
}
else if (element.Name.ToString().Equals("reminder"))
{
TimeBeforeReminders = element.GetAttributeInt("timebeforereminders", TimeBeforeReminders);
ReminderCooldown = element.GetAttributeInt("remindercooldown", ReminderCooldown);
}
foreach (var childElement in element.Elements())
{
GetHintsRecursive(childElement, $"{identifier}.{childElement.Name}");
}
}
}
public static void Update()
{
if (HintIdentifiers == null || GameMain.Config.DisableInGameHints) { return; }
if (GameMain.GameSession == null || !GameMain.GameSession.IsRunning) { return; }
if (ActiveHintMessageBox != null)
{
if (ActiveHintMessageBox.Closed)
{
ActiveHintMessageBox = null;
OnUpdate = null;
}
else
{
OnUpdate?.Invoke();
return;
}
}
else if (HintQueue.TryDequeue(out var hint))
{
ActiveHintMessageBox = new GUIMessageBox(hint.Identifier, hint.Text, hint.Icon);
if (hint.IconColor.HasValue) { ActiveHintMessageBox.IconColor = hint.IconColor.Value; }
OnUpdate = hint.OnUpdate;
ActiveHintMessageBox.InnerFrame.Flash(color: hint.IconColor ?? Color.Orange, flashDuration: 0.75f);
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
}
CheckIsInteracting();
CheckIfDivingGearOutOfOxygen();
CheckAdjacentHulls();
CheckReminders();
}
public static void OnSetSelectedConstruction(Character character, Item oldConstruction, Item newConstruction)
{
if (oldConstruction == newConstruction) { return; }
if (Character.Controlled != null && Character.Controlled == character && oldConstruction != null && oldConstruction.GetComponent<Ladder>() == null)
{
TimeStoppedInteracting = Timing.TotalTime;
}
if (newConstruction != null && newConstruction.GetComponent<Ladder>() == null)
{
OnStartedInteracting(character, newConstruction);
}
}
private static void OnStartedInteracting(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled || item == null) { return; }
string hintIdentifierBase = "onstartedinteracting";
// onstartedinteracting.brokenitem
if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold))
{
if (EnqueueHint($"{hintIdentifierBase}.brokenitem")) { return; }
}
// onstartedinteracting.lootingisstealing
if (item.Submarine?.Info?.Type == SubmarineType.Outpost &&
item.ContainedItems.Any(i => i.SpawnedInOutpost))
{
if (EnqueueHint($"{hintIdentifierBase}.lootingisstealing")) { return; }
}
// onstartedinteracting.turretperiscope
if (item.HasTag("periscope") &&
item.GetConnectedComponents<Turret>().FirstOrDefault(t => t.Item.HasTag("turret")) is Turret)
{
if (EnqueueHint($"{hintIdentifierBase}.turretperiscope",
variableTags: new string[] { "[shootkey]", "[deselectkey]", },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect) }))
{ return; }
}
// onstartedinteracting.item...
hintIdentifierBase += ".item";
foreach (string hintIdentifier in HintIdentifiers)
{
if (!hintIdentifier.StartsWith(hintIdentifierBase)) { continue; }
if (!HintTags.TryGetValue(hintIdentifier, out var hintTags)) { continue; }
if (!item.HasTag(hintTags)) { continue; }
if (EnqueueHint(hintIdentifier)) { return; }
}
}
private static void CheckIsInteracting()
{
if (!CanDisplayHints()) { return; }
if (Character.Controlled?.SelectedConstruction == null) { return; }
if (Character.Controlled.SelectedConstruction.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedConstruction.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag("reactorfuel")) > 1)
{
if (EnqueueHint("onisinteracting.reactorwithextrarods")) { return; }
}
}
public static void OnRoundStarted()
{
// Make sure everything's been reset properly, OnRoundEnded() isn't always called when exiting a game
Reset();
var initRoundHandle = CoroutineManager.StartCoroutine(InitRound(), "HintManager.InitRound");
if (!CanDisplayHints(requireGameScreen: false)) { return; }
CoroutineManager.StartCoroutine(DisplayRoundStartedHints(initRoundHandle), "HintManager.DisplayRoundStartedHints");
static IEnumerable<object> InitRound()
{
while (Character.Controlled == null) { yield return CoroutineStatus.Running; }
// Get the ballast hulls on round start not to find them again and again later
BallastHulls.Clear();
var sub = Submarine.MainSubs.FirstOrDefault(s => s != null && s.TeamID == Character.Controlled.TeamID);
if (sub != null)
{
foreach (var item in sub.GetItems(true))
{
if (item.CurrentHull == null) { continue; }
if (item.GetComponent<Pump>() == null) { continue; }
if (!item.HasTag("ballast")) { continue; }
BallastHulls.Add(item.CurrentHull);
}
}
yield return CoroutineStatus.Success;
}
static IEnumerable<object> DisplayRoundStartedHints(CoroutineHandle initRoundHandle)
{
while (GameMain.Instance.LoadingScreenOpen || Screen.Selected != GameMain.GameScreen ||
CoroutineManager.IsCoroutineRunning(initRoundHandle) ||
CoroutineManager.IsCoroutineRunning("LevelTransition") ||
CoroutineManager.IsCoroutineRunning("SinglePlayerCampaign.DoInitialCameraTransition") ||
CoroutineManager.IsCoroutineRunning("MultiPlayerCampaign.DoInitialCameraTransition") ||
GUIMessageBox.VisibleBox != null || Character.Controlled == null)
{
yield return CoroutineStatus.Running;
}
OnStartedControlling();
if (!GameMain.GameSession.GameMode.IsSinglePlayer &&
GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled)
{
EnqueueHint("onroundstarted.voipdisabled", onUpdate: () =>
{
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Disabled) { return; }
ActiveHintMessageBox.Close();
});
}
yield return CoroutineStatus.Success;
}
}
public static void OnRoundEnded()
{
Reset();
}
private static void Reset()
{
CoroutineManager.StopCoroutines("HintManager.InitRound");
CoroutineManager.StopCoroutines("HintManager.DisplayRoundStartedHints");
if (ActiveHintMessageBox != null)
{
GUIMessageBox.MessageBoxes.Remove(ActiveHintMessageBox);
ActiveHintMessageBox = null;
}
OnUpdate = null;
HintQueue.Clear();
HintsIgnoredThisRound.Clear();
}
public static void OnSonarSpottedCharacter(Item sonar, Character spottedCharacter)
{
if (!CanDisplayHints()) { return; }
if (sonar == null || sonar.Removed) { return; }
if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; }
if (Character.Controlled == null || Character.Controlled.SelectedConstruction != sonar) { return; }
if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; }
EnqueueHint("onsonarspottedenemy");
}
public static void OnAfflictionDisplayed(Character character, Affliction affliction)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled || affliction?.Prefab == null) { return; }
EnqueueHint("onafflictiondisplayed",
variableTags: new string[1] { "[key]" },
variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Health) },
icon: affliction.Prefab.Icon,
iconColor: CharacterHealth.GetAfflictionIconColor(affliction),
onUpdate: () =>
{
if (CharacterHealth.OpenHealthWindow == null) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnShootWithoutAiming(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled) { return; }
if (character.SelectedConstruction != null || character.FocusedItem != null) { return; }
if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; }
if (GUI.MouseOn != null) { return; }
if (TimeStoppedInteracting + 1 > Timing.TotalTime) { return; }
string hintIdentifier = "onshootwithoutaiming";
if (!HintTags.TryGetValue(hintIdentifier, out var tags)) { return; }
if (!item.HasTag(tags)) { return; }
EnqueueHint(hintIdentifier,
variableTags: new string[1] { "[key]" },
variableValues: new string[1] { GameMain.Config.KeyBindText(InputType.Aim) },
onUpdate: () =>
{
if (character.SelectedConstruction == null && GUI.MouseOn == null && PlayerInput.KeyDown(InputType.Aim))
{
ActiveHintMessageBox.Close();
}
});
}
public static void OnWeldingDoor(Character character)
{
if(!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled) { return; }
EnqueueHint("onweldingdoor");
}
public static void OnTryOpenStuckDoor(Character character)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled) { return; }
EnqueueHint("ontryopenstuckdoor");
}
public static void OnShowCampaignInterface(CampaignMode.InteractionType interactionType)
{
if (!CanDisplayHints()) { return; }
if (interactionType == CampaignMode.InteractionType.None) { return; }
string hintIdentifier = $"onshowcampaigninterface.{interactionType.ToString().ToLowerInvariant()}";
EnqueueHint(hintIdentifier,
onUpdate: () =>
{
if (!(GameMain.GameSession?.Campaign is CampaignMode campaign) ||
(!campaign.ShowCampaignUI && !campaign.ForceMapUI) ||
campaign.CampaignUI?.SelectedTab != CampaignMode.InteractionType.Map)
{
ActiveHintMessageBox.Close();
}
});
}
public static void OnShowCommandInterface()
{
IgnoreReminder("commandinterface");
if (!CanDisplayHints()) { return; }
EnqueueHint("onshowcommandinterface",
onUpdate: () =>
{
if (CrewManager.IsCommandInterfaceOpen) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnShowHealthInterface()
{
if (!CanDisplayHints()) { return; }
if (CharacterHealth.OpenHealthWindow == null) { return; }
EnqueueHint("onshowhealthinterface", onUpdate: () =>
{
if (CharacterHealth.OpenHealthWindow != null) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnStoleItem(Character character, Item item)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled) { return; }
if (item == null || !item.SpawnedInOutpost || !item.StolenDuringRound) { return; }
EnqueueHint("onstoleitem", onUpdate: () =>
{
if (item == null || item.Removed || item.GetRootInventoryOwner() != character)
{
ActiveHintMessageBox.Close();
}
});
}
public static void OnHandcuffed(Character character)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled || !character.LockHands) { return; }
EnqueueHint("onhandcuffed", onUpdate: () =>
{
if (character != null && !character.Removed && character.LockHands) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnReactorOutOfFuel(Reactor reactor)
{
if (!CanDisplayHints()) { return; }
if (Character.Controlled == null || reactor == null) { return; }
if (reactor.Item.Submarine?.Info?.Type != SubmarineType.Player || reactor.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; }
if (!HasValidJob("engineer")) { return; }
EnqueueHint("onreactoroutoffuel", onUpdate: () =>
{
if (reactor?.Item != null && !reactor.Item.Removed && reactor.AvailableFuel < 1) { return; }
ActiveHintMessageBox.Close();
});
}
public static void OnAvailableTransition(CampaignMode.TransitionType transitionType)
{
if (!CanDisplayHints()) { return; }
if (transitionType == CampaignMode.TransitionType.None) { return; }
EnqueueHint($"onavailabletransition.{transitionType.ToString().ToLowerInvariant()}");
}
public static void OnShowSubInventory(Item item)
{
if (item?.Prefab == null) { return; }
if (item.Prefab.Identifier.Equals("toolbelt", StringComparison.OrdinalIgnoreCase))
{
IgnoreReminder("toolbelt");
}
}
public static void OnChangeCharacter()
{
IgnoreReminder("characterchange");
if (!CanDisplayHints()) { return; }
OnStartedControlling();
}
private static void OnStartedControlling()
{
if (Level.IsLoadedOutpost) { return; }
if (Character.Controlled?.Info?.Job?.Prefab == null) { return; }
EnqueueHint($"onstartedcontrolling.job.{Character.Controlled.Info.Job.Prefab.Identifier}",
icon: Character.Controlled.Info.Job.Prefab.Icon,
iconColor: Character.Controlled.Info.Job.Prefab.UIColor);
}
public static void OnAutoPilotPathUpdated(Steering steering)
{
if (!CanDisplayHints()) { return; }
if (Character.Controlled == null) { return; }
if (!HasValidJob("captain")) { return; }
if (steering?.Item?.Submarine?.Info == null) { return; }
if (steering.Item.Submarine.Info.Type != SubmarineType.Player) { return; }
if (steering.Item.Submarine.TeamID != Character.Controlled.TeamID) { return; }
if (!steering.AutoPilot || steering.MaintainPos) { return; }
if (steering.SteeringPath?.CurrentNode?.Tunnel?.Type != Level.TunnelType.MainPath) { return; }
if (!steering.SteeringPath.Finished && steering.SteeringPath.NextNode != null) { return; }
if (steering.LevelStartSelected && (Level.Loaded.StartOutpost == null || !steering.Item.Submarine.AtStartPosition)) { return; }
if (steering.LevelEndSelected && (Level.Loaded.EndOutpost == null || !steering.Item.Submarine.AtEndPosition)) { return; }
EnqueueHint("onautopilotreachedoutpost");
}
public static void OnStatusEffectApplied(ItemComponent component, ActionType actionType, Character character)
{
if (!CanDisplayHints()) { return; }
if (character == null || character != Character.Controlled) { return; }
// Could make this more generic if there will ever be any other status effect related hints
if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; }
EnqueueHint("onrepairfailed");
}
private static void CheckIfDivingGearOutOfOxygen()
{
if (!CanDisplayHints()) { return; }
var divingGear = Character.Controlled?.GetEquippedItem("diving");
if (divingGear?.OwnInventory == null) { return; }
if (divingGear.GetContainedItemConditionPercentage() > 0.05f) { return; }
EnqueueHint("ondivinggearoutofoxygen", onUpdate: () =>
{
if (divingGear == null || divingGear.Removed ||
Character.Controlled == null || !Character.Controlled.HasEquippedItem(divingGear) ||
divingGear.GetContainedItemConditionPercentage() > 0.05f)
{
ActiveHintMessageBox.Close();
}
});
}
private static void CheckAdjacentHulls()
{
if (!CanDisplayHints()) { return; }
if (Character.Controlled?.CurrentHull == null) { return; }
foreach (var gap in Character.Controlled.CurrentHull.ConnectedGaps)
{
if (!gap.IsRoomToRoom) { continue; }
if (gap.ConnectedDoor == null || gap.ConnectedDoor.Impassable) { continue; }
if (Vector2.DistanceSquared(Character.Controlled.WorldPosition, gap.ConnectedDoor.Item.WorldPosition) > 400 * 400) { continue; }
foreach (var me in gap.linkedTo)
{
if (me == Character.Controlled.CurrentHull) { continue; }
if (!(me is Hull adjacentHull)) { continue; }
if (adjacentHull.LethalPressure > 5.0f && EnqueueHint("onadjacenthull.highpressure")) { return; }
if (adjacentHull.WaterPercentage > 75 && !BallastHulls.Contains(adjacentHull) && EnqueueHint("onadjacenthull.highwaterpercentage")) { return; }
}
}
}
private static void CheckReminders()
{
if (!CanDisplayHints()) { return; }
if (Level.Loaded == null) { return; }
if (Timing.TotalTime < GameMain.GameSession.RoundStartTime + TimeBeforeReminders) { return; }
if (Timing.TotalTime < TimeReminderLastDisplayed + ReminderCooldown) { return; }
string hintIdentifierBase = "reminder";
if (GameMain.GameSession.GameMode.IsSinglePlayer)
{
if (EnqueueHint($"{hintIdentifierBase}.characterchange"))
{
TimeReminderLastDisplayed = Timing.TotalTime;
return;
}
}
if (Level.Loaded.Type != LevelData.LevelType.Outpost)
{
if (EnqueueHint($"{hintIdentifierBase}.commandinterface",
variableTags: new string[] { "[commandkey]" },
variableValues: new string[] { GameMain.Config.KeyBindText(InputType.Command) },
onUpdate: () =>
{
if (!CrewManager.IsCommandInterfaceOpen) { return; }
ActiveHintMessageBox.Close();
}))
{
TimeReminderLastDisplayed = Timing.TotalTime;
return;
}
}
if (Character.Controlled?.Inventory?.GetItemInLimbSlot(InvSlotType.Bag)?.Prefab?.Identifier == "toolbelt")
{
if (EnqueueHint($"{hintIdentifierBase}.toolbelt"))
{
TimeReminderLastDisplayed = Timing.TotalTime;
return;
}
}
}
private static bool EnqueueHint(string hintIdentifier, string[] variableTags = null, string[] variableValues = null, Sprite icon = null, Color? iconColor = null, Action onUpdate = null)
{
if (string.IsNullOrEmpty(hintIdentifier)) { return false; }
if (!HintIdentifiers.Contains(hintIdentifier)) { return false; }
if (GameMain.Config.IgnoredHints.Contains(hintIdentifier)) { return false; }
if (HintsIgnoredThisRound.Contains(hintIdentifier)) { return false; }
string text;
string textTag = $"hint.{hintIdentifier}";
if (variableTags != null && variableTags != null && variableTags.Length > 0 && variableTags.Length == variableValues.Length)
{
text = TextManager.GetWithVariables(textTag, variableTags, variableValues, returnNull: true);
}
else
{
text = TextManager.Get(textTag, returnNull: true);
}
if (string.IsNullOrEmpty(text))
{
#if DEBUG
DebugConsole.ThrowError($"No hint text found for text tag \"{textTag}\"");
#endif
return false;
}
var hint = new HintInfo(hintIdentifier, text, icon, iconColor, onUpdate);
HintQueue.Enqueue(hint);
HintsIgnoredThisRound.Add(hintIdentifier);
return true;
}
public static bool OnDontShowAgain(GUITickBox tickBox)
{
IgnoreHint((string)tickBox.UserData, ignore: tickBox.Selected);
return true;
}
private static void IgnoreHint(string hintIdentifier, bool ignore = true)
{
if (string.IsNullOrEmpty(hintIdentifier)) { return; }
if (!HintIdentifiers.Contains(hintIdentifier))
{
#if DEBUG
DebugConsole.ThrowError($"Tried to ignore a hint not defined in {HintManagerFile}: {hintIdentifier}");
#endif
return;
}
if (ignore)
{
GameMain.Config.IgnoredHints.Add(hintIdentifier);
}
else
{
GameMain.Config.IgnoredHints.Remove(hintIdentifier);
}
}
private static void IgnoreReminder(string reminderIdentifier)
{
HintsIgnoredThisRound.Add($"reminder.{reminderIdentifier}");
}
public static bool OnDisableHints(GUITickBox tickBox)
{
GameMain.Config.DisableInGameHints = tickBox.Selected;
return GameMain.Config.SaveNewPlayerConfig();
}
private static bool CanDisplayHints(bool requireGameScreen = true)
{
if (HintIdentifiers == null) { return false; }
if (GameMain.Config.DisableInGameHints) { return false; }
var gameMode = GameMain.GameSession?.GameMode;
if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; }
if (requireGameScreen && Screen.Selected != GameMain.GameScreen) { return false; }
return true;
}
private static bool HasValidJob(string jobIdentifier)
{
// In singleplayer, we can control all character so we don't care about job restrictions
if (GameMain.GameSession.GameMode.IsSinglePlayer) { return true; }
if (Character.Controlled.HasJob(jobIdentifier)) { return true; }
// In multiplayer, if there are players with the job, display the hint to all players
foreach (var c in GameMain.GameSession.CrewManager.GetCharacters())
{
if (c == null || !c.IsRemotePlayer) { continue; }
if (c.IsUnconscious || c.IsDead || c.Removed) { continue; }
if (!c.HasJob(jobIdentifier)) { continue; }
return false;
}
return true;
}
}
}
@@ -0,0 +1,556 @@
using Barotrauma.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
{
class SubmarinePreview : IDisposable
{
private SpriteRecorder spriteRecorder;
private SubmarineInfo submarineInfo;
private Camera camera;
private Task loadTask;
private volatile bool isDisposed;
private GUIFrame previewFrame;
private class HullCollection
{
public readonly List<Rectangle> Rects;
public readonly string Name;
public HullCollection(string identifier)
{
Rects = new List<Rectangle>();
Name = TextManager.Get(identifier, returnNull: true) ?? identifier;
}
public void AddRect(XElement element)
{
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
rect.Y = -rect.Y;
Rects.Add(rect);
}
}
private struct Door
{
public readonly Rectangle Rect;
public Door(Rectangle rect)
{
rect.Y = -rect.Y;
Rect = rect;
}
}
private Dictionary<string,HullCollection> hullCollections;
private List<Door> doors;
private static SubmarinePreview instance = null;
public static void Create(SubmarineInfo submarineInfo)
{
instance?.Dispose();
instance = new SubmarinePreview(submarineInfo);
}
private SubmarinePreview(SubmarineInfo subInfo)
{
camera = new Camera();
submarineInfo = subInfo;
spriteRecorder = new SpriteRecorder();
isDisposed = false;
loadTask = null;
hullCollections = new Dictionary<string, HullCollection>();
doors = new List<Door>();
previewFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: null);
new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, previewFrame.RectTransform, Anchor.Center), style: "GUIBackgroundBlocker");
new GUIButton(new RectTransform(Vector2.One, previewFrame.RectTransform), "", style: null)
{
OnClicked = (btn, obj) => { Dispose(); return false; }
};
var innerFrame = new GUIFrame(new RectTransform(Vector2.One * 0.9f, previewFrame.RectTransform, Anchor.Center));
var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One * 0.95f, innerFrame.RectTransform, Anchor.Center), isHorizontal: false);
var topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), verticalLayout.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.95f, 1f), topLayout.RectTransform), subInfo.DisplayName, font: GUI.LargeFont);
new GUIButton(new RectTransform(new Vector2(0.05f, 1f), topLayout.RectTransform), TextManager.Get("Close"))
{
OnClicked = (btn, obj) => { Dispose(); return false; }
};
new GUICustomComponent(new RectTransform(new Vector2(1f, 0.9f), verticalLayout.RectTransform, Anchor.Center),
(spriteBatch, component) => { camera.UpdateTransform(true); RenderSubmarine(spriteBatch, component.Rect); },
(deltaTime, component) => {
camera.MoveCamera(deltaTime, overrideMouseOn: component.Rect);
if (component.Rect.Contains(PlayerInput.MousePosition) &&
(PlayerInput.MidButtonHeld() || PlayerInput.LeftButtonHeld()))
{
Vector2 moveSpeed = PlayerInput.MouseSpeed * (float)deltaTime * 60.0f / camera.Zoom;
moveSpeed.X = -moveSpeed.X;
camera.Position += moveSpeed;
}
});
GeneratePreviewMeshes();
}
public static void AddToGUIUpdateList()
{
instance?.previewFrame?.AddToGUIUpdateList();
}
public Task GeneratePreviewMeshes()
{
if (loadTask != null) { throw new InvalidOperationException("Tried to start SubmarinePreview loadTask more than once!"); }
loadTask = Task.Run(GeneratePreviewMeshesInternal);
return loadTask;
}
private async Task GeneratePreviewMeshesInternal()
{
await Task.Yield();
spriteRecorder.Begin(SpriteSortMode.BackToFront);
HashSet<int> toIgnore = new HashSet<int>();
foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{
switch (subElement.Name.LocalName.ToLowerInvariant())
{
case "item":
foreach (var component in subElement.Elements())
{
switch (component.Name.LocalName.ToLowerInvariant())
{
case "itemcontainer":
ExtractItemContainerIds(component, toIgnore);
break;
case "connectionpanel":
ExtractConnectionPanelLinks(component, toIgnore);
break;
}
}
break;
}
if (isDisposed) { return; }
await Task.Yield();
}
foreach (var subElement in submarineInfo.SubmarineElement.Elements())
{
switch (subElement.Name.LocalName.ToLowerInvariant())
{
case "item":
if (!toIgnore.Contains(subElement.GetAttributeInt("ID", 0)))
{
BakeMapEntity(subElement);
}
break;
case "structure":
BakeMapEntity(subElement);
break;
case "hull":
string identifier = subElement.GetAttributeString("roomname", "").ToLowerInvariant();
if (!string.IsNullOrEmpty(identifier))
{
HullCollection hullCollection = null;
if (!hullCollections.TryGetValue(identifier, out hullCollection))
{
hullCollection = new HullCollection(identifier);
hullCollections.Add(identifier, hullCollection);
}
hullCollection.AddRect(subElement);
}
break;
}
if (isDisposed) { return; }
await Task.Yield();
}
spriteRecorder.End();
camera.Position = (spriteRecorder.Min + spriteRecorder.Max) * 0.5f;
float scaledSpan = (spriteRecorder.Max - spriteRecorder.Min).X / camera.Resolution.X;
camera.Zoom = 0.8f / scaledSpan;
camera.StopMovement();
}
private void ExtractItemContainerIds(XElement component, HashSet<int> ids)
{
string containedString = component.GetAttributeString("contained", "");
string[] itemIdStrings = containedString.Split(',');
for (int i = 0; i < itemIdStrings.Length; i++)
{
foreach (string idStr in itemIdStrings[i].Split(';'))
{
if (!int.TryParse(idStr, out int id)) { continue; }
if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
}
}
}
private void ExtractConnectionPanelLinks(XElement component, HashSet<int> ids)
{
var pins = component.Elements("input").Concat(component.Elements("output"));
foreach (var pin in pins)
{
var links = pin.Elements("link");
foreach (var link in links)
{
int id = link.GetAttributeInt("w", 0);
if (id != 0 && !ids.Contains(id)) { ids.Add(id); }
}
}
}
private void BakeMapEntity(XElement element)
{
string identifier = element.GetAttributeString("identifier", "");
if (string.IsNullOrEmpty(identifier)) { return; }
Rectangle rect = element.GetAttributeRect("rect", Rectangle.Empty);
if (rect.Equals(Rectangle.Empty)) { return; }
float depth = element.GetAttributeFloat("spritedepth", 1f);
bool flippedX = element.GetAttributeBool("flippedx", false);
bool flippedY = element.GetAttributeBool("flippedy", false);
float scale = element.GetAttributeFloat("scale", 1f);
Color color = element.GetAttributeColor("spritecolor", Color.White);
float rotation = element.GetAttributeFloat("rotation", 0f);
MapEntityPrefab prefab = MapEntityPrefab.List.First(p => p.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
var texture = prefab.sprite.Texture;
var srcRect = prefab.sprite.SourceRect;
SpriteEffects spriteEffects = SpriteEffects.None;
if (flippedX)
{
spriteEffects |= SpriteEffects.FlipHorizontally;
}
if (flippedY)
{
spriteEffects |= SpriteEffects.FlipVertically;
}
var prevEffects = prefab.sprite.effects;
prefab.sprite.effects ^= spriteEffects;
bool overrideSprite = false;
ItemPrefab itemPrefab = prefab as ItemPrefab;
StructurePrefab structurePrefab = prefab as StructurePrefab;
if (itemPrefab != null)
{
BakeItemComponents(itemPrefab, rect, color, scale, rotation, depth, out overrideSprite);
}
if (!overrideSprite)
{
if (structurePrefab != null)
{
ParseUpgrades(structurePrefab.ConfigElement, ref scale);
if (!prefab.ResizeVertical)
{
rect.Height = (int)(rect.Height * scale / prefab.Scale);
}
if (!prefab.ResizeHorizontal)
{
rect.Width = (int)(rect.Width * scale / prefab.Scale);
}
var textureScale = element.GetAttributeVector2("texturescale", Vector2.One);
Vector2 backGroundOffset = Vector2.Zero;
Vector2 textureOffset = element.GetAttributeVector2("textureoffset", Vector2.Zero);
if (flippedX) { textureOffset.X = -textureOffset.X; }
if (flippedY) { textureOffset.Y = -textureOffset.Y; }
backGroundOffset = new Vector2(
MathUtils.PositiveModulo((int)-textureOffset.X, prefab.sprite.SourceRect.Width),
MathUtils.PositiveModulo((int)-textureOffset.Y, prefab.sprite.SourceRect.Height));
prefab.sprite.DrawTiled(
spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f),
rect.Size.ToVector2(),
color: color,
startOffset: backGroundOffset,
textureScale: textureScale * scale,
depth: depth);
}
else if (itemPrefab != null)
{
ParseUpgrades(itemPrefab.ConfigElement, ref scale);
if (prefab.ResizeVertical || prefab.ResizeHorizontal)
{
if (!prefab.ResizeHorizontal)
{
rect.Width = (int)(prefab.sprite.size.X * scale);
}
if (!prefab.ResizeVertical)
{
rect.Height = (int)(prefab.sprite.size.Y * scale);
}
var spritePos = rect.Center.ToVector2();
//spritePos.Y = rect.Height - spritePos.Y;
prefab.sprite.DrawTiled(
spriteRecorder,
rect.Location.ToVector2() * new Vector2(1f, -1f),
rect.Size.ToVector2(),
color: color,
textureScale: Vector2.One * scale,
depth: depth);
foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
{
float offsetState = 0f;
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteRecorder,
new Vector2(spritePos.X + offset.X - rect.Width / 2, -(spritePos.Y + offset.Y + rect.Height / 2)),
rect.Size.ToVector2(), color: color,
textureScale: Vector2.One * scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.sprite.Depth), 0.999f));
}
}
else
{
rect.Width = (int)(rect.Width * scale / prefab.Scale);
rect.Height = (int)(rect.Height * scale / prefab.Scale);
var spritePos = rect.Center.ToVector2();
spritePos.Y -= rect.Height;
//spritePos.Y = rect.Height - spritePos.Y;
prefab.sprite.Draw(
spriteRecorder,
spritePos * new Vector2(1f, -1f),
color,
prefab.sprite.Origin,
rotation,
scale,
prefab.sprite.effects, depth);
foreach (var decorativeSprite in itemPrefab.DecorativeSprites)
{
float rotationState = 0f; float offsetState = 0f;
float rot = decorativeSprite.GetRotation(ref rotationState, 0f);
Vector2 offset = decorativeSprite.GetOffset(ref offsetState, Vector2.Zero) * scale;
if (flippedX && itemPrefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && itemPrefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteRecorder, new Vector2(spritePos.X + offset.X, -(spritePos.Y + offset.Y)), color,
MathHelper.ToRadians(rotation) + rot, decorativeSprite.GetScale(0f) * scale, prefab.sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.sprite.Depth), 0.999f));
}
}
}
}
prefab.sprite.effects = prevEffects;
}
private void BakeItemComponents(
ItemPrefab prefab,
Rectangle rect, Color color,
float scale, float rotation, float depth,
out bool overrideSprite)
{
overrideSprite = false;
foreach (var subElement in prefab.ConfigElement.Elements())
{
switch (subElement.Name.LocalName.ToLowerInvariant())
{
case "turret":
Sprite barrelSprite = null;
Sprite railSprite = null;
foreach (XElement turretSubElem in subElement.Elements())
{
switch (turretSubElem.Name.ToString().ToLowerInvariant())
{
case "barrelsprite":
barrelSprite = new Sprite(turretSubElem);
break;
case "railsprite":
railSprite = new Sprite(turretSubElem);
break;
}
}
var transformedBarrelPos = MathUtils.RotatePointAroundTarget(
subElement.GetAttributeVector2("barrelpos", Vector2.Zero) * scale,
new Vector2(rect.Width / 2, rect.Height / 2),
MathHelper.ToRadians(rotation));
Vector2 drawPos = new Vector2(rect.X + transformedBarrelPos.X, rect.Y - transformedBarrelPos.Y);
drawPos.Y = -drawPos.Y;
railSprite?.Draw(spriteRecorder,
drawPos,
color,
rotation + MathHelper.PiOver2, scale,
SpriteEffects.None, depth + (railSprite.Depth - prefab.sprite.Depth));
barrelSprite?.Draw(spriteRecorder,
drawPos - new Vector2((float)Math.Cos(MathHelper.ToRadians(rotation)), (float)Math.Sin(MathHelper.ToRadians(rotation))) * scale,
color,
rotation + MathHelper.PiOver2, scale,
SpriteEffects.None, depth + (barrelSprite.Depth - prefab.sprite.Depth));
break;
case "door":
doors.Add(new Door(rect));
var doorSpriteElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("sprite", StringComparison.OrdinalIgnoreCase));
if (doorSpriteElem != null)
{
string texturePath = subElement.GetAttributeString("texture", "");
Vector2 pos = rect.Location.ToVector2() * new Vector2(1f, -1f);
if (subElement.GetAttributeBool("horizontal", false))
{
pos.Y += (float)rect.Height * 0.5f;
}
else
{
pos.X += (float)rect.Width * 0.5f;
}
Sprite doorSprite = new Sprite(doorSpriteElem, texturePath.Contains("/") ? "" : Path.GetDirectoryName(prefab.FilePath));
spriteRecorder.Draw(doorSprite.Texture, pos,
new Rectangle((int)doorSprite.SourceRect.X,
(int)doorSprite.SourceRect.Y,
(int)doorSprite.size.X, (int)doorSprite.size.Y),
color, 0.0f, doorSprite.Origin, new Vector2(scale), SpriteEffects.None, doorSprite.Depth);
}
break;
case "ladder":
var backgroundSprElem = subElement.Elements().FirstOrDefault(e => e.Name.LocalName.Equals("backgroundsprite", StringComparison.OrdinalIgnoreCase));
if (backgroundSprElem != null)
{
Sprite backgroundSprite = new Sprite(backgroundSprElem);
backgroundSprite.DrawTiled(spriteRecorder,
new Vector2(rect.Left, -rect.Top) - backgroundSprite.Origin * scale,
new Vector2(backgroundSprite.size.X * scale, rect.Height), color: color,
textureScale: Vector2.One * scale,
depth: depth + 0.1f);
}
break;
}
}
}
public void ParseUpgrades(XElement prefabConfigElement, ref float scale)
{
foreach (var upgrade in prefabConfigElement.Elements("Upgrade"))
{
var upgradeVersion = new Version(upgrade.GetAttributeString("gameversion", "0.0.0.0"));
if (upgradeVersion >= submarineInfo.GameVersion)
{
string scaleModifier = upgrade.GetAttributeString("scale", "*1");
if (scaleModifier.StartsWith("*"))
{
scale *= float.Parse(scaleModifier.Substring(1));
}
else
{
scale = float.Parse(scaleModifier);
}
}
}
}
private void RenderSubmarine(SpriteBatch spriteBatch, Rectangle scissorRectangle)
{
if (spriteRecorder == null) { return; }
GUI.DrawRectangle(spriteBatch, scissorRectangle, new Color(0.051f, 0.149f, 0.271f, 1.0f), isFilled: true);
if (!spriteRecorder.ReadyToRender)
{
string waitText = "Generating preview...";
GUI.Font.DrawString(
spriteBatch,
waitText,
scissorRectangle.Center.ToVector2(),
Color.White,
0f,
GUI.Font.MeasureString(waitText) * 0.5f,
1f,
SpriteEffects.None,
0f);
return;
}
spriteBatch.End();
var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
spriteRecorder.Render(camera);
var mousePos = camera.ScreenToWorld(PlayerInput.MousePosition);
mousePos.Y = -mousePos.Y;
spriteBatch.Begin(SpriteSortMode.BackToFront, rasterizerState: GameMain.ScissorTestEnable, transformMatrix: camera.Transform);
GameMain.Instance.GraphicsDevice.ScissorRectangle = scissorRectangle;
foreach (var hullCollection in hullCollections.Values)
{
bool mouseOver = false;
foreach (var rect in hullCollection.Rects)
{
mouseOver = rect.Contains(mousePos);
if (mouseOver) { break; }
}
foreach (var rect in hullCollection.Rects)
{
GUI.DrawRectangle(spriteBatch, rect, mouseOver ? Color.Red : Color.Blue, depth: mouseOver ? 0.45f : 0.5f, thickness: (mouseOver ? 4f : 2f) / camera.Zoom);
}
if (mouseOver)
{
string str = hullCollection.Name;
Vector2 strSize = GUI.Font.MeasureString(str) / camera.Zoom;
Vector2 padding = new Vector2(30, 30) / camera.Zoom;
Vector2 shift = new Vector2(10, 0) / camera.Zoom;
GUI.DrawRectangle(spriteBatch, mousePos + shift, strSize + padding, Color.Black, isFilled: true, depth: 0.25f);
GUI.Font.DrawString(spriteBatch, str, mousePos + shift + (strSize + padding) * 0.5f, Color.White, 0f, strSize * camera.Zoom * 0.5f, 1f / camera.Zoom, SpriteEffects.None, 0f);
}
}
foreach (var door in doors)
{
GUI.DrawRectangle(spriteBatch, door.Rect, GUI.Style.Green * 0.5f, isFilled: true, depth: 0.4f);
}
spriteBatch.End();
GameMain.Instance.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred);
}
public void Dispose()
{
previewFrame = null;
spriteRecorder?.Dispose();
isDisposed = true;
}
}
}
@@ -0,0 +1,88 @@
using Barotrauma.Extensions;
using OpenAL;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma.Sounds
{
public class SoundBuffers : IDisposable
{
private static HashSet<uint> bufferPool = new HashSet<uint>();
#if OSX
public const int MaxBuffers = 400; //TODO: check that this value works for macOS
#else
public const int MaxBuffers = 32000;
#endif
public static int BuffersGenerated { get; private set; } = 0;
private Sound sound;
public uint AlBuffer { get; private set; } = 0;
public uint AlMuffledBuffer { get; private set; } = 0;
public SoundBuffers(Sound sound) { this.sound = sound; }
public void Dispose()
{
if (AlBuffer != 0) { bufferPool.Add(AlBuffer); }
if (AlMuffledBuffer != 0) { bufferPool.Add(AlMuffledBuffer); }
AlBuffer = 0;
AlMuffledBuffer = 0;
}
public bool RequestAlBuffers()
{
if (AlBuffer != 0) { return false; }
int alError = 0;
while (bufferPool.Count < 2 && BuffersGenerated < MaxBuffers)
{
Al.GenBuffer(out uint newBuffer);
alError = Al.GetError();
if (alError != Al.NoError)
{
DebugConsole.AddWarning($"Error when generating sound buffer: {Al.GetErrorString(alError)}. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated.");
BuffersGenerated = MaxBuffers;
}
else if (!Al.IsBuffer(newBuffer))
{
DebugConsole.AddWarning($"Error when generating sound buffer: result is not a valid buffer. {BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated.");
BuffersGenerated = MaxBuffers;
}
else
{
bufferPool.Add(newBuffer);
BuffersGenerated++;
if (BuffersGenerated >= MaxBuffers)
{
DebugConsole.AddWarning($"{BuffersGenerated} buffer(s) were generated. No more sound buffers will be generated.");
}
}
}
if (bufferPool.Count >= 2)
{
AlBuffer = bufferPool.First();
bufferPool.Remove(AlBuffer);
AlMuffledBuffer = bufferPool.First();
bufferPool.Remove(AlMuffledBuffer);
return true;
}
//can't generate any more OpenAL buffers! we'll have to steal a buffer from someone...
foreach (var otherSound in sound.Owner.LoadedSounds)
{
if (otherSound == sound) { continue; }
if (otherSound.IsPlaying()) { continue; }
if (otherSound.Buffers == null) { continue; }
if (otherSound.Buffers.AlBuffer == 0) { continue; }
AlBuffer = otherSound.Buffers.AlBuffer;
AlMuffledBuffer = otherSound.Buffers.AlMuffledBuffer;
otherSound.Buffers.AlBuffer = 0;
otherSound.Buffers.AlMuffledBuffer = 0;
return true;
}
return false;
}
}
}
@@ -0,0 +1,324 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace Barotrauma
{
public class SpriteRecorder : ISpriteBatch, IDisposable
{
private struct Command
{
public readonly Texture2D Texture;
public readonly VertexPositionColorTexture VertexBL;
public readonly VertexPositionColorTexture VertexBR;
public readonly VertexPositionColorTexture VertexTL;
public readonly VertexPositionColorTexture VertexTR;
public readonly float Depth;
public readonly Vector2 Min;
public readonly Vector2 Max;
public readonly int Index;
public bool Overlaps(Command other)
{
return
Min.X <= other.Max.X && Max.X >= other.Min.X &&
Min.Y <= other.Max.Y && Max.Y >= other.Min.Y;
}
public Command(
Texture2D texture,
Vector2 pos,
Rectangle srcRect,
Color color,
float rotation,
Vector2 origin,
Vector2 scale,
SpriteEffects effects,
float depth,
int index)
{
int srcRectLeft = srcRect.Left;
int srcRectRight = srcRect.Right;
int srcRectTop = srcRect.Top;
int srcRectBottom = srcRect.Bottom;
if (effects.HasFlag(SpriteEffects.FlipHorizontally))
{
var temp = srcRectRight;
srcRectRight = srcRectLeft;
srcRectLeft = temp;
}
if (effects.HasFlag(SpriteEffects.FlipVertically))
{
var temp = srcRectBottom;
srcRectBottom = srcRectTop;
srcRectTop = temp;
}
rotation = MathHelper.ToRadians(rotation);
float sin = (float)Math.Sin(rotation);
float cos = (float)Math.Cos(rotation);
var size = srcRect.Size.ToVector2() * scale;
Vector2 wAdd = new Vector2(size.X * cos, size.X * sin);
Vector2 hAdd = new Vector2(-size.Y * sin, size.Y * cos);
pos.X -= origin.X * scale.X * cos - origin.Y * scale.Y * sin;
pos.Y -= origin.Y * scale.Y * cos + origin.X * scale.X * sin;
Texture = texture;
Depth = depth;
VertexTL.Color = color;
VertexTR.Color = color;
VertexBL.Color = color;
VertexBR.Color = color;
VertexTL.Position = new Vector3(pos.X, pos.Y, 0f);
VertexTR.Position = new Vector3(pos.X + wAdd.X, pos.Y + wAdd.Y, 0f);
VertexBL.Position = new Vector3(pos.X + hAdd.X, pos.Y + hAdd.Y, 0f);
VertexBR.Position = new Vector3(pos.X + wAdd.X + hAdd.X, pos.Y + wAdd.Y + hAdd.Y, 0f);
Min = new Vector2(
MathUtils.Min
(
VertexTL.Position.X,
VertexTR.Position.X,
VertexBL.Position.X,
VertexBR.Position.X
),
MathUtils.Min
(
VertexTL.Position.Y,
VertexTR.Position.Y,
VertexBL.Position.Y,
VertexBR.Position.Y
));
Max = new Vector2(
MathUtils.Max
(
VertexTL.Position.X,
VertexTR.Position.X,
VertexBL.Position.X,
VertexBR.Position.X
),
MathUtils.Max
(
VertexTL.Position.Y,
VertexTR.Position.Y,
VertexBL.Position.Y,
VertexBR.Position.Y
));
VertexTL.TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectTop / (float)texture.Height);
VertexTR.TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectTop / (float)texture.Height);
VertexBL.TextureCoordinate = new Vector2((float)srcRectLeft / (float)texture.Width, (float)srcRectBottom / (float)texture.Height);
VertexBR.TextureCoordinate = new Vector2((float)srcRectRight / (float)texture.Width, (float)srcRectBottom / (float)texture.Height);
Index = index;
}
}
private struct RecordedBuffer
{
public readonly Texture2D Texture;
public readonly VertexBuffer VertexBuffer;
public readonly int PolyCount;
public RecordedBuffer(List<Command> commandList, int startIndex, int count)
{
Texture = commandList[startIndex].Texture;
VertexBuffer = new VertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, count * 4, BufferUsage.WriteOnly);
VertexPositionColorTexture[] vertices = new VertexPositionColorTexture[count * 4];
for (int i = 0; i < count; i++)
{
vertices[(i * 4) + 0] = commandList[startIndex + i].VertexBL;
vertices[(i * 4) + 1] = commandList[startIndex + i].VertexBR;
vertices[(i * 4) + 2] = commandList[startIndex + i].VertexTL;
vertices[(i * 4) + 3] = commandList[startIndex + i].VertexTR;
}
VertexBuffer.SetData(vertices);
PolyCount = count * 2;
}
}
public static BasicEffect BasicEffect = null;
private List<RecordedBuffer> recordedBuffers = new List<RecordedBuffer>();
private List<Command> commandList = new List<Command>();
private SpriteSortMode currentSortMode;
private IndexBuffer indexBuffer = null;
private int maxSpriteCount = 0;
public volatile bool ReadyToRender = false;
private volatile bool isDisposed = false;
public Vector2 Min { get; private set; }
public Vector2 Max { get; private set; }
public void Begin(SpriteSortMode sortMode)
{
ReadyToRender = false;
currentSortMode = sortMode;
}
public void Draw(Texture2D texture, Vector2 pos, Rectangle? srcRect, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects, float depth)
{
if (isDisposed) { return; }
Command command = new Command(texture, pos, srcRect ?? texture.Bounds, color, rotation, origin, scale, effects, depth, commandList?.Count ?? 0);
if (commandList.Count == 0) { Min = command.Min; Max = command.Max; }
Min = new Vector2(Math.Min(command.Min.X, Min.X), Math.Min(command.Min.Y, Min.Y));
Max = new Vector2(Math.Max(command.Max.X, Max.X), Math.Max(command.Max.Y, Max.Y));
commandList?.Add(command);
}
public void End()
{
if (isDisposed) { return; }
//sort commands according to the sorting
//mode given in the last Begin call
switch (currentSortMode)
{
case SpriteSortMode.FrontToBack:
commandList.Sort((c1, c2) =>
{
return c1.Depth < c2.Depth ? -1
: c1.Depth > c2.Depth ? 1
: c1.Index < c2.Index ? 1
: c1.Index > c2.Index ? -1
: 0;
});
break;
case SpriteSortMode.BackToFront:
commandList.Sort((c1, c2) =>
{
return c1.Depth < c2.Depth ? 1
: c1.Depth > c2.Depth ? -1
: c1.Index < c2.Index ? 1
: c1.Index > c2.Index ? -1
: 0;
});
break;
}
//try to place commands of the same texture
//contiguously for optimal buffer generation
//while maintaining the same visual result
for (int i=1;i<commandList.Count;i++)
{
if (commandList[i].Texture != commandList[i-1].Texture)
{
for (int j = i - 1; j >= 0; j--)
{
if (commandList[j].Texture == commandList[i].Texture)
{
//no commands between i and j overlap with
//i, therefore we can safely sift i down to
//make a contiguous block
commandList.SiftElement(i, j + 1);
break;
}
else if (commandList[j].Overlaps(commandList[i]))
{
//an overlapping command was found, therefore
//attempting to sift this one down would change
//the visual result
break;
}
}
}
}
if (isDisposed) { return; }
//each contiguous block of commands of the same texture
//requires a vertex buffer to be rendered
CrossThread.RequestExecutionOnMainThread(() =>
{
if (commandList.Count == 0) { return; }
int startIndex = 0;
for (int i = 1; i < commandList.Count; i++)
{
if (commandList[i].Texture != commandList[startIndex].Texture)
{
maxSpriteCount = Math.Max(maxSpriteCount, i - startIndex);
recordedBuffers.Add(new RecordedBuffer(commandList, startIndex, i - startIndex));
startIndex = i;
}
}
recordedBuffers.Add(new RecordedBuffer(commandList, startIndex, commandList.Count - startIndex));
maxSpriteCount = Math.Max(maxSpriteCount, commandList.Count - startIndex);
});
commandList.Clear();
ReadyToRender = true;
}
public void Render(Camera cam)
{
if (!ReadyToRender) { return; }
var gfxDevice = GameMain.Instance.GraphicsDevice;
BasicEffect ??= new BasicEffect(gfxDevice);
BasicEffect.Projection = Matrix.CreateOrthographicOffCenter(new Rectangle(0, 0, cam.Resolution.X, cam.Resolution.Y), -1f, 1f);
BasicEffect.View = cam.Transform;
BasicEffect.World = Matrix.Identity;
BasicEffect.TextureEnabled = true;
BasicEffect.VertexColorEnabled = true;
BasicEffect.Alpha = 1f;
int requiredIndexCount = maxSpriteCount * 6;
if (requiredIndexCount > 0 && (indexBuffer == null || indexBuffer.IndexCount < requiredIndexCount))
{
indexBuffer?.Dispose();
indexBuffer = new IndexBuffer(gfxDevice, IndexElementSize.SixteenBits, requiredIndexCount * 2, BufferUsage.WriteOnly);
ushort[] indices = new ushort[requiredIndexCount * 2];
for (int i=0;i<indices.Length;i+=6)
{
indices[i + 0] = (ushort)((i / 6) * 4 + 1);
indices[i + 1] = (ushort)((i / 6) * 4 + 0);
indices[i + 2] = (ushort)((i / 6) * 4 + 2);
indices[i + 3] = (ushort)((i / 6) * 4 + 1);
indices[i + 4] = (ushort)((i / 6) * 4 + 2);
indices[i + 5] = (ushort)((i / 6) * 4 + 3);
}
indexBuffer.SetData(indices);
}
gfxDevice.Indices = indexBuffer;
for (int i=0;i<recordedBuffers.Count;i++)
{
gfxDevice.SetVertexBuffer(recordedBuffers[i].VertexBuffer);
BasicEffect.Texture = recordedBuffers[i].Texture;
BasicEffect.CurrentTechnique.Passes[0].Apply();
gfxDevice.DrawIndexedPrimitives(PrimitiveType.TriangleList, 0, 0, recordedBuffers[i].PolyCount);
}
}
public void Dispose()
{
isDisposed = true;
if (recordedBuffers != null)
{
foreach (var buffer in recordedBuffers)
{
buffer.VertexBuffer.Dispose();
}
recordedBuffers.Clear(); recordedBuffers = null;
}
commandList?.Clear(); commandList = null;
indexBuffer?.Dispose(); indexBuffer = null;
ReadyToRender = false;
}
}
}
@@ -0,0 +1,19 @@
using Barotrauma.Networking;
using System.Collections.Generic;
namespace Barotrauma
{
partial class OutpostDestroyMission : AbandonedOutpostMission
{
private readonly List<Item> spawnedItems = new List<Item>();
public override void ServerWriteInitial(IWriteMessage msg, Client c)
{
base.ServerWriteInitial(msg, c);
msg.Write((ushort)spawnedItems.Count);
foreach (Item item in spawnedItems)
{
item.WriteSpawnData(msg, item.ID, Entity.NullEntityID, 0);
}
}
}
}
@@ -0,0 +1,67 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class UnlockPathAction : EventAction
{
public UnlockPathAction(ScriptedEvent parentEvent, XElement element) : base(parentEvent, element) { }
private bool isFinished = false;
public override bool IsFinished(ref string goTo)
{
return isFinished;
}
public override void Reset()
{
isFinished = false;
}
public override void Update(float deltaTime)
{
if (isFinished) { return; }
GameMain.GameSession?.Map?.CurrentLocation?.Connections.ForEach(c => c.Locked = false);
if (GameMain.GameSession?.Map?.CurrentLocation?.Connections != null)
{
foreach (LocationConnection connection in GameMain.GameSession?.Map?.CurrentLocation?.Connections)
{
if (!connection.Locked) { continue; }
#if SERVER
NotifyUnlock(connection);
#else
connection.Locked = false;
new GUIMessageBox(string.Empty, TextManager.Get("pathunlockedgeneric"),
new string[0], type: GUIMessageBox.Type.InGame, iconStyle: "UnlockPathIcon", relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128));
#endif
}
}
isFinished = true;
}
public override string ToDebugString()
{
return $"{ToolBox.GetDebugSymbol(isFinished)} {nameof(UnlockPathAction)}";
}
#if SERVER
private void NotifyUnlock(LocationConnection connection)
{
foreach (Client client in GameMain.Server.ConnectedClients)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ServerPacketHeader.EVENTACTION);
outmsg.Write((byte)EventManager.NetworkEventType.UNLOCKPATH);
outmsg.Write((UInt16)GameMain.GameSession.Map.Connections.IndexOf(connection));
GameMain.Server.ServerPeer.Send(outmsg, client.Connection, DeliveryMethod.Reliable);
}
}
#endif
}
}
@@ -0,0 +1,154 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
partial class OutpostDestroyMission : AbandonedOutpostMission
{
private readonly string itemTag;
private readonly XElement itemConfig;
private readonly List<Item> items = new List<Item>();
public override IEnumerable<Vector2> SonarPositions
{
get
{
if (State > 0)
{
return Enumerable.Empty<Vector2>();
}
else
{
return Targets.Select(t => t.WorldPosition);
}
}
}
private IEnumerable<Entity> Targets
{
get
{
if (State > 0)
{
return Enumerable.Empty<Entity>();
}
else
{
if (items.Any())
{
return items.Where(it => !it.Removed && it.Condition > 0.0f).Cast<Entity>().Concat(requireKill.Where(c => !c.Removed && !c.IsDead)).Concat(requireRescue);
}
else
{
return requireKill.Concat(requireRescue);
}
}
}
}
public OutpostDestroyMission(MissionPrefab prefab, Location[] locations) :
base(prefab, locations)
{
itemConfig = prefab.ConfigElement.Element("Items");
itemTag = prefab.ConfigElement.GetAttributeString("targetitem", "");
}
protected override void StartMissionSpecific(Level level)
{
items.Clear();
#if SERVER
spawnedItems.Clear();
#endif
if (!string.IsNullOrEmpty(itemTag))
{
var itemsToDestroy = Item.ItemList.FindAll(it => it.Submarine?.Info.Type != SubmarineType.Player && it.HasTag(itemTag));
if (!itemsToDestroy.Any())
{
DebugConsole.ThrowError($"Error in mission \"{Prefab.Identifier}\". Could not find an item with the tag \"{itemTag}\".");
}
else
{
items.AddRange(itemsToDestroy);
}
}
if (itemConfig != null && !IsClient)
{
foreach (XElement element in itemConfig.Elements())
{
string itemIdentifier = element.GetAttributeString("identifier", "");
if (!(MapEntityPrefab.Find(null, itemIdentifier) is ItemPrefab itemPrefab))
{
DebugConsole.ThrowError("Couldn't spawn item for outpost destroy mission: item prefab \"" + itemIdentifier + "\" not found");
continue;
}
string[] moduleFlags = element.GetAttributeStringArray("moduleflags", null);
string[] spawnPointTags = element.GetAttributeStringArray("spawnpointtags", null);
ISpatialEntity spawnPoint = SpawnAction.GetSpawnPos(
SpawnAction.SpawnLocationType.Outpost, SpawnType.Human | SpawnType.Enemy,
moduleFlags, spawnPointTags, element.GetAttributeBool("asfaraspossible", false));
if (spawnPoint == null)
{
var submarine = Submarine.Loaded.Find(s => s.Info.Type == SubmarineType.Outpost) ?? Submarine.MainSub;
spawnPoint = submarine.GetHulls(alsoFromConnectedSubs: false).GetRandom();
}
Vector2 spawnPos = spawnPoint.WorldPosition;
if (spawnPoint is WayPoint wp && wp.CurrentHull != null)
{
spawnPos = new Vector2(
MathHelper.Clamp(wp.WorldPosition.X + Rand.Range(-200, 200), wp.CurrentHull.WorldRect.X, wp.CurrentHull.WorldRect.Right),
wp.CurrentHull.WorldRect.Y - wp.CurrentHull.Rect.Height + 10.0f);
}
var item = new Item(itemPrefab, spawnPos, null);
items.Add(item);
#if SERVER
spawnedItems.Add(item);
#endif
}
}
base.StartMissionSpecific(level);
}
public override void Update(float deltaTime)
{
switch (state)
{
case 0:
if (items.Any())
{
if (items.All(it => it.Removed || it.Condition <= 0.0f) &&
requireKill.All(c => c.Removed || c.IsDead) &&
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
{
State = 1;
}
}
else
{
if (requireKill.All(c => c.Removed || c.IsDead) &&
requireRescue.All(c => c.Submarine?.Info.Type == SubmarineType.Player))
{
State = 1;
}
}
break;
#if SERVER
case 1:
if (!(GameMain.GameSession.GameMode is CampaignMode) && GameMain.Server != null)
{
if (!Submarine.MainSub.AtStartPosition || (wasDocked && !Submarine.MainSub.DockedTo.Contains(Level.Loaded.StartOutpost)))
{
GameMain.Server.EndGame();
State = 2;
}
}
break;
#endif
}
}
}
}
@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8" ?>
<hintmanager>
<onadjacenthull>
<highpressure/>
<highwaterpercentage/>
</onadjacenthull>
<onafflictiondisplayed/>
<onautopilotreachedoutpost/>
<onavailabletransition>
<progresstonextemptylocation/>
</onavailabletransition>
<ondivinggearoutofoxygen/>
<onhandcuffed/>
<onisinteracting>
<reactorwithextrarods/>
</onisinteracting>
<onreactoroutoffuel/>
<onrepairfailed/>
<onroundstarted>
<voipdisabled/>
</onroundstarted>
<onshootwithoutaiming tags="cuttingequipment,gun,weldingequipment"/>
<onshowcampaigninterface>
<map/>
</onshowcampaigninterface>
<onshowcommandinterface/>
<onshowhealthinterface/>
<onsonarspottedenemy/>
<onstartedcontrolling>
<job>
<assistant/>
<captain/>
<engineer/>
<mechanic/>
<medicaldoctor/>
<securityofficer/>
</job>
</onstartedcontrolling>
<onstartedinteracting>
<brokenitem/>
<lootingisstealing/>
<turretperiscope/>
<item>
<deconstructor tags="deconstructor"/>
<fabricator tags="fabricator,medicalfabricator"/>
<gunloader tags="coilgunammosource,railgunammosource"/>
<navterminal tags="navterminal"/>
<reactor tags="reactor"/>
</item>
</onstartedinteracting>
<onstoleitem/>
<ontryopenstuckdoor/>
<onweldingdoor/>
<reminder timebeforereminders="300" remindercooldown="120">
<characterchange/>
<commandinterface/>
<toolbelt/>
</reminder>
</hintmanager>