Added files that were missing from last commit
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user