Release 1.10.5.0 - Autumn Update 2025
This commit is contained in:
@@ -419,7 +419,12 @@ namespace Barotrauma
|
|||||||
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
|
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
|
||||||
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.ToPoint()), headSprite.SourceRect.Size);
|
headSprite.SourceRect = new Rectangle(CalculateOffset(headSprite, Head.SheetIndex.ToPoint()), headSprite.SourceRect.Size);
|
||||||
SetHeadEffect(spriteBatch);
|
SetHeadEffect(spriteBatch);
|
||||||
headSprite.Draw(spriteBatch, screenPos, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects);
|
Vector2 origin = headSprite.Origin;
|
||||||
|
if (flip)
|
||||||
|
{
|
||||||
|
origin.X = headSprite.size.X - origin.X;
|
||||||
|
}
|
||||||
|
headSprite.Draw(spriteBatch, screenPos, origin: origin, scale: scale, color: Head.SkinColor, spriteEffect: spriteEffects);
|
||||||
if (AttachmentSprites != null)
|
if (AttachmentSprites != null)
|
||||||
{
|
{
|
||||||
float depthStep = 0.000001f;
|
float depthStep = 0.000001f;
|
||||||
@@ -467,6 +472,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
origin = head.Origin;
|
origin = head.Origin;
|
||||||
attachment.Sprite.Origin = origin;
|
attachment.Sprite.Origin = origin;
|
||||||
|
if (spriteEffects.HasFlag(SpriteEffects.FlipHorizontally))
|
||||||
|
{
|
||||||
|
origin.X = head.size.X - origin.X;
|
||||||
|
}
|
||||||
|
if (spriteEffects.HasFlag(SpriteEffects.FlipVertically))
|
||||||
|
{
|
||||||
|
origin.Y = head.size.Y - origin.Y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal static partial class DebugConsole
|
||||||
|
{
|
||||||
|
private static void InitShowSoldItems()
|
||||||
|
{
|
||||||
|
commands.Add(new Command("showsolditems",
|
||||||
|
"showsolditems [filter (no-defined/only-min/only-max/name:pattern)] [Include stores (true/false)] [Sold only (true/false)] [limit (number)] [Hide store overrides from output. (true/false)]: " +
|
||||||
|
"Lists items and their shop availability settings. Filter can be availability filter or name pattern (e.g. 'name:*rifle*'). Include stores controls whether to check store-specific overrides (default true). Sold only controls whether to show only sold items (default true). Limit parameter controls how many items to show (default 50). Hide store overrides from output, defaults to false.",
|
||||||
|
(string[] args) =>
|
||||||
|
{
|
||||||
|
string filter = args.Length > 0 ? args[0].ToLowerInvariant() : null;
|
||||||
|
bool includeStores = args.Length <= 1 || !args[1].Equals("false", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
bool soldOnly = args.Length <= 2 || !args[2].Equals("false", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
int limit = 50;
|
||||||
|
if (args.Length > 3 && int.TryParse(args[3], out int parsedLimit))
|
||||||
|
{
|
||||||
|
limit = Math.Max(1, parsedLimit);
|
||||||
|
}
|
||||||
|
bool hideStoreOverrides = args.Length > 4 && args[4].Equals("true", StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
|
||||||
|
var itemsWithPrice = ItemPrefab.Prefabs
|
||||||
|
.Where(item => item.ConfigElement.Element.Element("Price") != null);
|
||||||
|
|
||||||
|
// apply filtering
|
||||||
|
var matchingItems = itemsWithPrice
|
||||||
|
.OrderBy(i => i.Name.Value)
|
||||||
|
.Where(item => MatchesFilter(item, filter, includeStores, soldOnly))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// output results
|
||||||
|
NewMessage("=== Shop Item Availability ===", Color.Cyan);
|
||||||
|
NewMessage($"Filter: {filter ?? "all"}, IncludeStores: {includeStores}, SoldOnly: {soldOnly}, Limit: {limit}, HideStoreOverrides: {hideStoreOverrides}", Color.Yellow);
|
||||||
|
NewMessage($"Items: {matchingItems.Count} matching out of {itemsWithPrice.Count()} being sold (showing first {Math.Min(limit, matchingItems.Count)})", Color.LightGreen);
|
||||||
|
NewMessage("", Color.White);
|
||||||
|
|
||||||
|
foreach (var item in matchingItems.Take(limit))
|
||||||
|
{
|
||||||
|
PrintItemInfo(item, hideStoreOverrides);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getValidArgs: () =>
|
||||||
|
[
|
||||||
|
["all", "no-defined", "only-min", "only-max", "name:*"], // filter
|
||||||
|
["true", "false"], // includeStores
|
||||||
|
["true", "false"], // soldOnly
|
||||||
|
["10", "25", "50", "100"], // limit suggestions
|
||||||
|
["false", "true"] // hidestoreoverrides
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesFilter(ItemPrefab item, string filter, bool includeStores, bool soldOnly)
|
||||||
|
{
|
||||||
|
var priceElement = item.ConfigElement.Element.Element("Price");
|
||||||
|
if (priceElement == null) { return false; } // No price = not sold = don't include
|
||||||
|
if (!includeStores) { return MatchesPriceElement(priceElement, item, filter, soldOnly); }
|
||||||
|
|
||||||
|
// Check if Base element matches first...
|
||||||
|
if (MatchesPriceElement(priceElement, item, filter, soldOnly)) { return true; }
|
||||||
|
|
||||||
|
// ...then check store-specific price element matches
|
||||||
|
foreach (var storeElement in priceElement.Elements().Where(e => e.Name == "Price"))
|
||||||
|
{
|
||||||
|
if (MatchesPriceElement(storeElement, item, filter, soldOnly)) { return true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool MatchesPriceElement(XElement priceEl, ItemPrefab itemPrefab, string filter, bool soldOnly)
|
||||||
|
{
|
||||||
|
bool isSold = PriceInfo.GetSold(priceEl, true);
|
||||||
|
if (soldOnly && !isSold) { return false; }
|
||||||
|
if (filter == null) { return true; }
|
||||||
|
|
||||||
|
// Handle name pattern matching
|
||||||
|
if (filter.StartsWith("name:"))
|
||||||
|
{
|
||||||
|
string pattern = filter[5..];
|
||||||
|
string name = itemPrefab.Name.Value.ToLowerInvariant();
|
||||||
|
string identifier = itemPrefab.Identifier.Value.ToLowerInvariant();
|
||||||
|
|
||||||
|
// If pattern contains '*', treat as wildcard (convert to regex)
|
||||||
|
if (pattern.Contains('*'))
|
||||||
|
{
|
||||||
|
// Escape regex special chars except *
|
||||||
|
string regexPattern = System.Text.RegularExpressions.Regex.Escape(pattern).Replace("\\*", ".*");
|
||||||
|
return System.Text.RegularExpressions.Regex.IsMatch(name, $"^{regexPattern}$")
|
||||||
|
|| System.Text.RegularExpressions.Regex.IsMatch(identifier, $"^{regexPattern}$");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// No wildcards: match exactly
|
||||||
|
return name == pattern || identifier == pattern;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasMin = PriceInfo.HasMinAmountDefined(priceEl);
|
||||||
|
bool hasMax = PriceInfo.HasMaxAmountDefined(priceEl);
|
||||||
|
|
||||||
|
// Apply the filter logic
|
||||||
|
return filter switch
|
||||||
|
{
|
||||||
|
"no-defined" => !hasMin && !hasMax, // Neither min nor max defined
|
||||||
|
"only-min" => hasMin && !hasMax, // Only min defined
|
||||||
|
"only-max" => !hasMin && hasMax, // Only max defined
|
||||||
|
_ => true // No filter or show all
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void PrintItemInfo(ItemPrefab item, bool hideStoreOverrides = false)
|
||||||
|
{
|
||||||
|
var priceElement = item.ConfigElement.Element.Element("Price");
|
||||||
|
if (priceElement == null) { return; }
|
||||||
|
|
||||||
|
bool hasMinDefined = PriceInfo.HasMinAmountDefined(priceElement);
|
||||||
|
bool hasMaxDefined = PriceInfo.HasMaxAmountDefined(priceElement);
|
||||||
|
|
||||||
|
string minRaw = PriceInfo.GetMinAmountString(priceElement);
|
||||||
|
string maxRaw = PriceInfo.GetMaxAmountString(priceElement);
|
||||||
|
int minLevelDifficulty = PriceInfo.GetMinLevelDifficulty(priceElement, 0);
|
||||||
|
|
||||||
|
// Get the resolved values (what PriceInfo would actually use)
|
||||||
|
var priceInfo = new PriceInfo(priceElement);
|
||||||
|
int resolvedMin = priceInfo.MinAvailableAmount;
|
||||||
|
int resolvedMax = priceInfo.MaxAvailableAmount;
|
||||||
|
|
||||||
|
string minStatus = hasMinDefined ? $"XML:{minRaw}" : "DEFAULT:1";
|
||||||
|
string maxStatus = hasMaxDefined ? $"XML:{maxRaw}" : "DEFAULT:5";
|
||||||
|
|
||||||
|
string minLevelInfo = minLevelDifficulty > 0 ? $" | MinLvl: {minLevelDifficulty}" : "";
|
||||||
|
NewMessage($"{item.Name} ({item.Identifier}) | Min: {minStatus} → {resolvedMin} | Max: {maxStatus} → {resolvedMax}{minLevelInfo}", Color.White);
|
||||||
|
|
||||||
|
if (hideStoreOverrides) { return; }
|
||||||
|
|
||||||
|
var storeOverrides = priceElement.Elements().Where(e => e.Name == "Price")
|
||||||
|
.Select(p => {
|
||||||
|
string storeId = PriceInfo.GetStoreIdentifier(p, "unknown");
|
||||||
|
string storeMin = PriceInfo.GetMinAmountString(p);
|
||||||
|
string storeMax = PriceInfo.GetMaxAmountString(p);
|
||||||
|
bool? storeSold = PriceInfo.HasSoldDefined(p) ? PriceInfo.GetSold(p, true) : null;
|
||||||
|
|
||||||
|
// Check if this store overrides anything
|
||||||
|
if (storeMin != null || storeMax != null || storeSold != null)
|
||||||
|
{
|
||||||
|
var parts = new List<string>();
|
||||||
|
if (storeMin != null || storeMax != null)
|
||||||
|
{
|
||||||
|
parts.Add($"min:{storeMin ?? "base"}, max:{storeMax ?? "base"}");
|
||||||
|
}
|
||||||
|
if (storeSold != null)
|
||||||
|
{
|
||||||
|
parts.Add($"sold:{storeSold.Value.ToString().ToLowerInvariant()}");
|
||||||
|
}
|
||||||
|
return $"{storeId}({string.Join(", ", parts)})";
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})
|
||||||
|
.Where(s => s != null)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (storeOverrides.Count != 0)
|
||||||
|
{
|
||||||
|
NewMessage($" Store overrides: {string.Join(", ", storeOverrides)}", Color.Gray);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -397,6 +397,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private static void InitProjectSpecific()
|
private static void InitProjectSpecific()
|
||||||
{
|
{
|
||||||
|
InitShowSoldItems();
|
||||||
|
|
||||||
commands.Add(new Command("eosStat", "Query and display all logged in EOS users. Normally this is at most two users, but in a developer environment it could be more.", args =>
|
commands.Add(new Command("eosStat", "Query and display all logged in EOS users. Normally this is at most two users, but in a developer environment it could be more.", args =>
|
||||||
{
|
{
|
||||||
if (!EosInterface.Core.IsInitialized)
|
if (!EosInterface.Core.IsInitialized)
|
||||||
@@ -3466,6 +3468,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
commands.Add(new Command("multiclienttestmode", "Makes the client enable some special logic (such as using a client-specific folder for downloads) to prevent conflicts between multiple clients on the same machine. Useful for testing the campaign with multiple clients running locally.", (string[] args) =>
|
||||||
|
{
|
||||||
|
GameClient.MultiClientTestMode = !GameClient.MultiClientTestMode;
|
||||||
|
NewMessage($"{(GameClient.MultiClientTestMode ? "Enabled" : "Disabled")} MultiClientTestMode on the client.");
|
||||||
|
}));
|
||||||
|
AssignRelayToServer("multiclienttestmode", false);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
|
commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
|
||||||
@@ -4204,8 +4213,12 @@ namespace Barotrauma
|
|||||||
NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown"));
|
NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static void ReloadWearables(Character character, int variant = 0)
|
private static void ReloadWearables(Character character, int variant = 0)
|
||||||
{
|
{
|
||||||
foreach (var limb in character.AnimController.Limbs)
|
foreach (var limb in character.AnimController.Limbs)
|
||||||
@@ -4442,7 +4455,9 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
System.Threading.Thread.Sleep(1000);
|
System.Threading.Thread.Sleep(1000);
|
||||||
}
|
}
|
||||||
|
#if DEBUG
|
||||||
|
GameClient.MultiClientTestMode = true;
|
||||||
|
#endif
|
||||||
GameMain.Client = new GameClient("client1",
|
GameMain.Client = new GameClient("client1",
|
||||||
new LidgrenEndpoint(System.Net.IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());
|
new LidgrenEndpoint(System.Net.IPAddress.Loopback, NetConfig.DefaultPort), "localhost", Option<int>.None());
|
||||||
|
|
||||||
@@ -4454,9 +4469,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
System.Threading.Thread.Sleep(1000);
|
System.Threading.Thread.Sleep(1000);
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i);
|
Process.Start("Barotrauma.exe", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
|
||||||
#else
|
#else
|
||||||
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i);
|
Process.Start("./Barotrauma", arguments: "-connect server localhost -username client" + i + " -multiclienttestmode");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,10 @@
|
|||||||
{
|
{
|
||||||
partial class GoToMission : Mission
|
partial class GoToMission : Mission
|
||||||
{
|
{
|
||||||
public override bool DisplayAsCompleted => State >= Prefab.MaxProgressState;
|
public override bool DisplayAsCompleted =>
|
||||||
|
State >= Prefab.MaxProgressState &&
|
||||||
|
//if there's some additional check for completion, don't display as completed until we've checked it and set the mission as completed
|
||||||
|
(Completed || completeCheckDataAction == null);
|
||||||
public override bool DisplayAsFailed => false;
|
public override bool DisplayAsFailed => false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<Entity> HudIconTargets => targets.Where(static t => !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true }).Select(static t => t.Item);
|
public override IEnumerable<Entity> HudIconTargets => targets
|
||||||
|
.Where(static t => t.Item != null && !t.Retrieved && t.Item?.GetRootInventoryOwner() is not Character { IsLocalPlayer: true })
|
||||||
|
.Select(static t => t.Item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ using Microsoft.Xna.Framework.Input;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -50,9 +51,15 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
private RichString selectedTip;
|
private RichString selectedTip;
|
||||||
|
|
||||||
|
private string selectedTipString;
|
||||||
|
private ImmutableArray<RichTextData>? selectedTipRichTextData;
|
||||||
|
|
||||||
private void SetSelectedTip(LocalizedString tip)
|
private void SetSelectedTip(LocalizedString tip)
|
||||||
{
|
{
|
||||||
selectedTip = RichString.Rich(tip);
|
selectedTip = RichString.Rich(tip);
|
||||||
|
selectedTipString = string.Empty;
|
||||||
|
selectedTipRichTextData = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public float LoadState;
|
public float LoadState;
|
||||||
@@ -165,13 +172,20 @@ namespace Barotrauma
|
|||||||
textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
|
textPos.Y += GUIStyle.LargeFont.MeasureString(loadText.ToUpper()).Y * 1.2f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GUIStyle.Font.HasValue && selectedTip != null)
|
if (GUIStyle.Font.HasValue && selectedTip != null && !selectedTip.SanitizedValue.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
string wrappedTip = ToolBox.WrapText(selectedTip.SanitizedValue, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
|
//store the string value of the LocalizedString to prevent the text from changing if/when new text packs are loaded during the loading screen
|
||||||
string[] lines = wrappedTip.Split('\n');
|
if (selectedTipString.IsNullOrEmpty())
|
||||||
float lineHeight = GUIStyle.Font.MeasureString(selectedTip).Y;
|
{
|
||||||
|
selectedTipString = selectedTip.SanitizedValue;
|
||||||
|
selectedTipRichTextData = selectedTip.RichTextData;
|
||||||
|
}
|
||||||
|
|
||||||
if (selectedTip.RichTextData != null)
|
string wrappedTip = ToolBox.WrapText(selectedTipString, GameMain.GraphicsWidth * 0.3f, GUIStyle.Font.Value);
|
||||||
|
string[] lines = wrappedTip.Split('\n');
|
||||||
|
float lineHeight = GUIStyle.Font.MeasureString(selectedTipString).Y;
|
||||||
|
|
||||||
|
if (selectedTipRichTextData != null)
|
||||||
{
|
{
|
||||||
int rtdOffset = 0;
|
int rtdOffset = 0;
|
||||||
for (int i = 0; i < lines.Length; i++)
|
for (int i = 0; i < lines.Length; i++)
|
||||||
@@ -179,7 +193,7 @@ namespace Barotrauma
|
|||||||
GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
|
GUIStyle.Font.DrawStringWithColors(spriteBatch, lines[i],
|
||||||
new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
|
new Vector2(textPos.X, (int)(textPos.Y + i * lineHeight)),
|
||||||
Color.White,
|
Color.White,
|
||||||
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTip.RichTextData.Value, rtdOffset);
|
0f, Vector2.Zero, 1f, SpriteEffects.None, 0f, selectedTipRichTextData.Value, rtdOffset);
|
||||||
rtdOffset += lines[i].Length;
|
rtdOffset += lines[i].Length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -860,26 +860,22 @@ namespace Barotrauma
|
|||||||
FilterStoreItems(category, searchBox.Text);
|
FilterStoreItems(category, searchBox.Text);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyValuePair<Identifier, float>? GetReputationRequirement(PriceInfo priceInfo)
|
private static float GetReputationRequirement(PriceInfo priceInfo, Identifier faction)
|
||||||
{
|
{
|
||||||
return GameMain.GameSession?.Campaign is not null
|
return priceInfo.MinReputation.GetValueOrDefault(faction);
|
||||||
? priceInfo.MinReputation.FirstOrNull()
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static KeyValuePair<Identifier, float>? GetTooLowReputation(PriceInfo priceInfo)
|
private static bool ReputationRequirementsMet(PriceInfo priceInfo, Identifier faction)
|
||||||
{
|
{
|
||||||
|
if (priceInfo.MinReputation.None()) { return true; }
|
||||||
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
|
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
|
||||||
{
|
{
|
||||||
foreach (var minRep in priceInfo.MinReputation)
|
if (priceInfo.MinReputation.TryGetValue(faction, out float requirement))
|
||||||
{
|
{
|
||||||
if (MathF.Round(campaign.GetReputation(minRep.Key)) < minRep.Value)
|
return MathF.Round(campaign.GetReputation(faction)) >= requirement;
|
||||||
{
|
|
||||||
return minRep;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int prevDailySpecialCount, prevRequestedGoodsCount, prevSubRequestedGoodsCount;
|
int prevDailySpecialCount, prevRequestedGoodsCount, prevSubRequestedGoodsCount;
|
||||||
@@ -948,7 +944,7 @@ namespace Barotrauma
|
|||||||
SetPriceGetters(itemFrame, true);
|
SetPriceGetters(itemFrame, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && !GetTooLowReputation(priceInfo).HasValue);
|
SetItemFrameStatus(itemFrame, hasPermissions && quantity > 0 && ReputationRequirementsMet(priceInfo, ActiveStore.GetMerchantOrLocationFactionIdentifier()));
|
||||||
existingItemFrames.Add(itemFrame);
|
existingItemFrames.Add(itemFrame);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1464,8 +1460,9 @@ namespace Barotrauma
|
|||||||
PriceInfo priceInfo2 = item2.ItemPrefab.GetPriceInfo(ActiveStore);
|
PriceInfo priceInfo2 = item2.ItemPrefab.GetPriceInfo(ActiveStore);
|
||||||
if (priceInfo1 != null && priceInfo2 != null)
|
if (priceInfo1 != null && priceInfo2 != null)
|
||||||
{
|
{
|
||||||
var requiredReputation1 = GetTooLowReputation(priceInfo1)?.Value ?? 0.0f;
|
Identifier faction = ActiveStore.GetMerchantOrLocationFactionIdentifier();
|
||||||
var requiredReputation2 = GetTooLowReputation(priceInfo2)?.Value ?? 0.0f;
|
float requiredReputation1 = ReputationRequirementsMet(priceInfo1, faction) ? 0.0f : GetReputationRequirement(priceInfo1, faction);
|
||||||
|
float requiredReputation2 = ReputationRequirementsMet(priceInfo2, faction) ? 0.0f : GetReputationRequirement(priceInfo2, faction);
|
||||||
return requiredReputation1.CompareTo(requiredReputation2);
|
return requiredReputation1.CompareTo(requiredReputation2);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
@@ -1942,14 +1939,15 @@ namespace Barotrauma
|
|||||||
var campaign = GameMain.GameSession?.Campaign;
|
var campaign = GameMain.GameSession?.Campaign;
|
||||||
if (priceInfo != null && campaign != null)
|
if (priceInfo != null && campaign != null)
|
||||||
{
|
{
|
||||||
var requiredReputation = GetReputationRequirement(priceInfo);
|
Identifier faction = ActiveStore.GetMerchantOrLocationFactionIdentifier();
|
||||||
if (requiredReputation != null)
|
float requiredReputation = GetReputationRequirement(priceInfo, faction);
|
||||||
|
if (requiredReputation > 0)
|
||||||
{
|
{
|
||||||
var repStr = TextManager.GetWithVariables(
|
var repStr = TextManager.GetWithVariables(
|
||||||
"campaignstore.reputationrequired",
|
"campaignstore.reputationrequired",
|
||||||
("[amount]", ((int)requiredReputation.Value.Value).ToString()),
|
("[amount]", ((int)requiredReputation).ToString()),
|
||||||
("[faction]", TextManager.Get("faction." + requiredReputation.Value.Key).Value));
|
("[faction]", TextManager.Get("faction." + faction).Value));
|
||||||
Color color = MathF.Round(campaign.GetReputation(requiredReputation.Value.Key)) < requiredReputation.Value.Value ?
|
Color color = MathF.Round(campaign.GetReputation(faction)) < requiredReputation ?
|
||||||
GUIStyle.Orange : GUIStyle.Green;
|
GUIStyle.Orange : GUIStyle.Green;
|
||||||
toolTip += $"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖";
|
toolTip += $"\n‖color:{color.ToStringHex()}‖{repStr}‖color:end‖";
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1743,12 +1743,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void CreateMaterialCosts(GUIListBox list, UpgradePrefab prefab, int targetLevel)
|
static void CreateMaterialCosts(GUIListBox list, UpgradePrefab upgradePrefab, int targetLevel)
|
||||||
{
|
{
|
||||||
list.Content.ClearChildren();
|
list.Content.ClearChildren();
|
||||||
var allItems = CargoManager.FindAllItemsOnPlayerAndSub(Character.Controlled);
|
var allItems = CargoManager.FindAllItemsOnPlayerAndSub(Character.Controlled);
|
||||||
|
|
||||||
var resources = prefab.GetApplicableResources(targetLevel);
|
var resources = upgradePrefab.GetApplicableResources(targetLevel);
|
||||||
|
|
||||||
foreach (ApplicableResourceCollection collection in resources)
|
foreach (ApplicableResourceCollection collection in resources)
|
||||||
{
|
{
|
||||||
@@ -1769,7 +1769,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasItems = collection.Cost.Amount <= allItems.Count(collection.Cost.MatchesItem);
|
bool hasItems = collection.Cost.Amount <= allItems.Count(collection.Cost.MatchesItem);
|
||||||
|
|
||||||
Sprite icon = defaultItemPrefab.InventoryIcon ?? prefab.Sprite;
|
Sprite icon = defaultItemPrefab.InventoryIcon ?? defaultItemPrefab.Sprite;
|
||||||
Color iconColor = defaultItemPrefab.InventoryIcon is null ? defaultItemPrefab.SpriteColor : defaultItemPrefab.InventoryIconColor;
|
Color iconColor = defaultItemPrefab.InventoryIcon is null ? defaultItemPrefab.SpriteColor : defaultItemPrefab.InventoryIconColor;
|
||||||
|
|
||||||
GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true)
|
GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true)
|
||||||
@@ -1798,7 +1798,7 @@ namespace Barotrauma
|
|||||||
if (index > length) { index = 0; }
|
if (index > length) { index = 0; }
|
||||||
|
|
||||||
ItemPrefab currentPrefab = collection.MatchingItems[(int)MathF.Floor(index)];
|
ItemPrefab currentPrefab = collection.MatchingItems[(int)MathF.Floor(index)];
|
||||||
Sprite icon = currentPrefab.InventoryIcon ?? prefab.Sprite;
|
Sprite icon = currentPrefab.InventoryIcon ?? currentPrefab.Sprite;
|
||||||
Color iconColor = currentPrefab.InventoryIcon is null ? currentPrefab.SpriteColor : currentPrefab.InventoryIconColor;
|
Color iconColor = currentPrefab.InventoryIcon is null ? currentPrefab.SpriteColor : currentPrefab.InventoryIconColor;
|
||||||
itemIcon.Sprite = icon;
|
itemIcon.Sprite = icon;
|
||||||
itemIcon.Color = hasItems ? iconColor : iconColor * 0.9f;
|
itemIcon.Color = hasItems ? iconColor : iconColor * 0.9f;
|
||||||
|
|||||||
@@ -270,6 +270,13 @@ namespace Barotrauma
|
|||||||
ConnectCommand = Option<ConnectCommand>.None();
|
ConnectCommand = Option<ConnectCommand>.None();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if DEBUG
|
||||||
|
if (ConsoleArguments.Contains("-multiclienttestmode"))
|
||||||
|
{
|
||||||
|
DebugConsole.NewMessage("Enabled MultiClientTestMode on the client");
|
||||||
|
GameClient.MultiClientTestMode = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
|
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
|
||||||
|
|
||||||
PerformanceCounter = new PerformanceCounter();
|
PerformanceCounter = new PerformanceCounter();
|
||||||
|
|||||||
@@ -2866,10 +2866,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
contextualOrders.RemoveAll(o => !IsOrderAvailable(o));
|
contextualOrders.RemoveAll(o => !IsOrderAvailable(o));
|
||||||
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count));
|
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count));
|
||||||
bool disableNode = !CanCharacterBeHeard();
|
bool canCharacterBeHeard = !CanCharacterBeHeard();
|
||||||
for (int i = 0; i < contextualOrders.Count; i++)
|
for (int i = 0; i < contextualOrders.Count; i++)
|
||||||
{
|
{
|
||||||
var order = contextualOrders[i];
|
var order = contextualOrders[i];
|
||||||
|
bool disableNode = !canCharacterBeHeard && !order.TargetAllCharacters;
|
||||||
int hotkey = (i + 1) % 10;
|
int hotkey = (i + 1) % 10;
|
||||||
var component = order.Option.IsEmpty ?
|
var component = order.Option.IsEmpty ?
|
||||||
CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, hotkey, disableNode: disableNode, checkIfOrderCanBeHeard: false) :
|
CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, hotkey, disableNode: disableNode, checkIfOrderCanBeHeard: false) :
|
||||||
|
|||||||
@@ -258,9 +258,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
SlideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow);
|
SlideshowPlayer = new SlideshowPlayer(GUICanvas.Instance, slideshow);
|
||||||
}
|
}
|
||||||
var outpost = GameMain.GameSession.Level.StartOutpost;
|
var subToFocusTo = GameMain.GameSession.Level.StartOutpost ?? Submarine.MainSub;
|
||||||
var borders = outpost.GetDockedBorders();
|
var borders = subToFocusTo.GetDockedBorders();
|
||||||
borders.Location += outpost.WorldPosition.ToPoint();
|
borders.Location += subToFocusTo.WorldPosition.ToPoint();
|
||||||
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
|
GameMain.GameScreen.Cam.Position = new Vector2(borders.X + borders.Width / 2, borders.Y - borders.Height / 2);
|
||||||
float startZoom = 0.8f /
|
float startZoom = 0.8f /
|
||||||
((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X);
|
((float)Math.Max(borders.Width, borders.Height) / (float)GameMain.GameScreen.Cam.Resolution.X);
|
||||||
@@ -646,9 +646,12 @@ namespace Barotrauma
|
|||||||
modeElement.Add(GameMain.GameSession?.EventManager.Save());
|
modeElement.Add(GameMain.GameSession?.EventManager.Save());
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Identifier unlockedRecipe in GameMain.GameSession.UnlockedRecipes)
|
foreach ((CharacterTeamType team, Identifier unlockedRecipe) in GameMain.GameSession.UnlockedRecipes)
|
||||||
{
|
{
|
||||||
modeElement.Add(new XElement("unlockedrecipe", new XAttribute("identifier", unlockedRecipe)));
|
modeElement.Add(
|
||||||
|
new XElement("unlockedrecipe",
|
||||||
|
new XAttribute("identifier", unlockedRecipe),
|
||||||
|
new XAttribute("team", team)));
|
||||||
}
|
}
|
||||||
|
|
||||||
//save and remove all items that are in someone's inventory so they don't get included in the sub file as well
|
//save and remove all items that are in someone's inventory so they don't get included in the sub file as well
|
||||||
|
|||||||
@@ -197,8 +197,8 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
|
|
||||||
LocalizedString labelText = GetUILabel();
|
LocalizedString labelText = GetUILabel();
|
||||||
GUITextBlock label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopCenter),
|
GUITextBlock label = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform, Anchor.TopLeft),
|
||||||
labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft, wrap: true)
|
labelText, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopLeft, wrap: true)
|
||||||
{
|
{
|
||||||
IgnoreLayoutGroups = true
|
IgnoreLayoutGroups = true
|
||||||
};
|
};
|
||||||
@@ -206,6 +206,8 @@ namespace Barotrauma.Items.Components
|
|||||||
int buttonSize = GUIStyle.ItemFrameTopBarHeight;
|
int buttonSize = GUIStyle.ItemFrameTopBarHeight;
|
||||||
Point margin = new Point(buttonSize / 4, buttonSize / 6);
|
Point margin = new Point(buttonSize / 4, buttonSize / 6);
|
||||||
|
|
||||||
|
int buttonCount = 0;
|
||||||
|
|
||||||
GUILayoutGroup buttonArea = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, buttonSize - margin.Y * 2), content.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, margin.Y) },
|
GUILayoutGroup buttonArea = new GUILayoutGroup(new RectTransform(new Point(content.Rect.Width, buttonSize - margin.Y * 2), content.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, margin.Y) },
|
||||||
isHorizontal: true, childAnchor: Anchor.TopRight)
|
isHorizontal: true, childAnchor: Anchor.TopRight)
|
||||||
{
|
{
|
||||||
@@ -213,24 +215,37 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
if (Inventory.Capacity > 1)
|
if (Inventory.Capacity > 1)
|
||||||
{
|
{
|
||||||
new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "SortItemsButton")
|
if (ShowSortButton)
|
||||||
{
|
{
|
||||||
ToolTip = TextManager.Get("SortItemsAlphabetically"),
|
buttonCount++;
|
||||||
OnClicked = (btn, userdata) =>
|
new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "SortItemsButton")
|
||||||
{
|
{
|
||||||
SortItems();
|
ToolTip = TextManager.Get("SortItemsAlphabetically"),
|
||||||
return true;
|
OnClicked = (btn, userdata) =>
|
||||||
}
|
{
|
||||||
};
|
SortItems();
|
||||||
new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "MergeStacksButton")
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (ShowMergeButton)
|
||||||
{
|
{
|
||||||
ToolTip = TextManager.Get("MergeItemStacks"),
|
buttonCount++;
|
||||||
OnClicked = (btn, userdata) =>
|
new GUIButton(new RectTransform(Vector2.One, buttonArea.RectTransform, scaleBasis: ScaleBasis.Smallest), style: "MergeStacksButton")
|
||||||
{
|
{
|
||||||
MergeStacks();
|
ToolTip = TextManager.Get("MergeItemStacks"),
|
||||||
return true;
|
OnClicked = (btn, userdata) =>
|
||||||
}
|
{
|
||||||
};
|
MergeStacks();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (buttonCount > 0)
|
||||||
|
{
|
||||||
|
label.RectTransform.MaxSize = new Point(label.Parent.Rect.Width - buttonCount * buttonSize, int.MaxValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
float minInventoryAreaSize = 0.5f;
|
float minInventoryAreaSize = 0.5f;
|
||||||
|
|||||||
@@ -1078,7 +1078,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (hullData is null)
|
if (hullData is null)
|
||||||
{
|
{
|
||||||
hullData = new HullData();
|
hullData = new HullData();
|
||||||
GetLinkedHulls(hull, hullData.LinkedHulls);
|
hull.GetLinkedHulls(hullData.LinkedHulls);
|
||||||
hullDatas.Add(hull, hullData);
|
hullDatas.Add(hull, hullData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1586,19 +1586,6 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void GetLinkedHulls(Hull hull, List<Hull> linkedHulls)
|
|
||||||
{
|
|
||||||
foreach (var linkedEntity in hull.linkedTo)
|
|
||||||
{
|
|
||||||
if (linkedEntity is Hull linkedHull)
|
|
||||||
{
|
|
||||||
if (linkedHulls.Contains(linkedHull) || linkedHull.IsHidden) { continue; }
|
|
||||||
linkedHulls.Add(linkedHull);
|
|
||||||
GetLinkedHulls(linkedHull, linkedHulls);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings)
|
public static GUIFrame CreateMiniMap(Submarine sub, GUIComponent parent, MiniMapSettings settings)
|
||||||
{
|
{
|
||||||
return CreateMiniMap(sub, parent, settings, null, out _);
|
return CreateMiniMap(sub, parent, settings, null, out _);
|
||||||
@@ -1791,7 +1778,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; }
|
if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; }
|
||||||
|
|
||||||
List<Hull> linkedHulls = new List<Hull>();
|
List<Hull> linkedHulls = new List<Hull>();
|
||||||
GetLinkedHulls(hull, linkedHulls);
|
hull.GetLinkedHulls(linkedHulls);
|
||||||
|
|
||||||
linkedHulls.Remove(hull);
|
linkedHulls.Remove(hull);
|
||||||
|
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime)
|
partial void UpdateProjSpecific(float deltaTime)
|
||||||
{
|
{
|
||||||
if (FlowPercentage < 0.0f)
|
if (currFlow < 0f)
|
||||||
{
|
{
|
||||||
foreach (var (position, emitter) in pumpOutEmitters)
|
foreach (var (position, emitter) in pumpOutEmitters)
|
||||||
{
|
{
|
||||||
@@ -154,10 +154,10 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
||||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -FlowPercentage / 100.0f));
|
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, -currFlow / maxFlow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (FlowPercentage > 0.0f)
|
else if (currFlow > 0f)
|
||||||
{
|
{
|
||||||
foreach (var (position, emitter) in pumpInEmitters)
|
foreach (var (position, emitter) in pumpInEmitters)
|
||||||
{
|
{
|
||||||
@@ -174,7 +174,7 @@ namespace Barotrauma.Items.Components
|
|||||||
relativeParticlePos.Y = -relativeParticlePos.Y;
|
relativeParticlePos.Y = -relativeParticlePos.Y;
|
||||||
}
|
}
|
||||||
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
emitter.Emit(deltaTime, item.WorldPosition + relativeParticlePos, item.CurrentHull, angle,
|
||||||
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, FlowPercentage / 100.0f));
|
velocityMultiplier: MathHelper.Lerp(0.5f, 1.0f, currFlow / maxFlow));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -185,7 +185,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
autoControlIndicator.Selected = IsAutoControlled;
|
autoControlIndicator.Selected = IsAutoControlled;
|
||||||
PowerButton.Enabled = isActiveLockTimer <= 0.0f;
|
PowerButton.Enabled = isActiveLockTimer <= 0.0f;
|
||||||
if (HasPower)
|
if (HasPower && !Disabled)
|
||||||
{
|
{
|
||||||
flickerTimer = 0;
|
flickerTimer = 0;
|
||||||
powerLight.Selected = IsActive;
|
powerLight.Selected = IsActive;
|
||||||
@@ -229,6 +229,7 @@ namespace Barotrauma.Items.Components
|
|||||||
float flowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
float flowPercentage = msg.ReadRangedInteger(-10, 10) * 10.0f;
|
||||||
bool isActive = msg.ReadBoolean();
|
bool isActive = msg.ReadBoolean();
|
||||||
bool hijacked = msg.ReadBoolean();
|
bool hijacked = msg.ReadBoolean();
|
||||||
|
bool disabled = msg.ReadBoolean();
|
||||||
float? targetLevel;
|
float? targetLevel;
|
||||||
if (msg.ReadBoolean())
|
if (msg.ReadBoolean())
|
||||||
{
|
{
|
||||||
@@ -250,6 +251,7 @@ namespace Barotrauma.Items.Components
|
|||||||
FlowPercentage = flowPercentage;
|
FlowPercentage = flowPercentage;
|
||||||
IsActive = isActive;
|
IsActive = isActive;
|
||||||
Hijacked = hijacked;
|
Hijacked = hijacked;
|
||||||
|
Disabled = disabled;
|
||||||
TargetLevel = targetLevel;
|
TargetLevel = targetLevel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,8 +34,8 @@ namespace Barotrauma.Items.Components
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Serialize("0.5,0.5)", IsPropertySaveable.No)]
|
[Serialize("0.5,0.5", IsPropertySaveable.No)]
|
||||||
public Vector2 Origin { get; set; } = new Vector2(0.5f, 0.5f);
|
public Vector2 Origin { get; set; }
|
||||||
|
|
||||||
[Serialize(true, IsPropertySaveable.No, description: "")]
|
[Serialize(true, IsPropertySaveable.No, description: "")]
|
||||||
public bool BreakFromMiddle
|
public bool BreakFromMiddle
|
||||||
|
|||||||
@@ -314,9 +314,12 @@ namespace Barotrauma.Items.Components
|
|||||||
textColors.Add(GUIStyle.Orange);
|
textColors.Add(GUIStyle.Orange);
|
||||||
}
|
}
|
||||||
|
|
||||||
int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 100.0f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1);
|
if (target.NeedsOxygen)
|
||||||
texts.Add(OxygenTexts[oxygenTextIndex]);
|
{
|
||||||
textColors.Add(Color.Lerp(GUIStyle.Red, GUIStyle.Green, target.Oxygen / 100.0f));
|
int oxygenTextIndex = MathHelper.Clamp((int)Math.Floor((1.0f - (target.Oxygen / 100.0f)) * OxygenTexts.Length), 0, OxygenTexts.Length - 1);
|
||||||
|
texts.Add(OxygenTexts[oxygenTextIndex]);
|
||||||
|
textColors.Add(Color.Lerp(GUIStyle.Red, GUIStyle.Green, target.Oxygen / 100.0f));
|
||||||
|
}
|
||||||
|
|
||||||
if (target.Bleeding > 0.0f)
|
if (target.Bleeding > 0.0f)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -359,7 +359,7 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession)
|
if (!item.Prefab.UnlockedRecipeInToolTip.IsEmpty && GameMain.GameSession is { } GameSession)
|
||||||
{
|
{
|
||||||
if (GameSession.UnlockedRecipes.Contains(item.Prefab.UnlockedRecipeInToolTip))
|
if (GameSession.HasUnlockedRecipe(Character.Controlled, item.Prefab.UnlockedRecipeInToolTip))
|
||||||
{
|
{
|
||||||
toolTip += TextManager.Get("unlockedrecipe.true");
|
toolTip += TextManager.Get("unlockedrecipe.true");
|
||||||
}
|
}
|
||||||
@@ -1435,8 +1435,20 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (giver == null || receiver == null || draggedItems.None()) { return false; }
|
if (giver == null || receiver == null || draggedItems.None()) { return false; }
|
||||||
if (receiver == giver) { return false; }
|
if (receiver == giver) { return false; }
|
||||||
|
|
||||||
|
CharacterInventory.AccessLevel accessLevel;
|
||||||
|
if (draggedItems.Any(it => it.HasTag(Tags.HandLockerItem)))
|
||||||
|
{
|
||||||
|
//handcuffs can't be given to players by dragging and dropping (because it can allow handcuffing them)
|
||||||
|
accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
accessLevel = IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.AllowFriendly : CharacterInventory.AccessLevel.AllowBotsAndPets;
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
receiver.IsInventoryAccessibleTo(giver, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited) &&
|
receiver.IsInventoryAccessibleTo(giver, accessLevel) &&
|
||||||
receiver.Inventory.CanBePut(draggedItems.FirstOrDefault(), InvSlotType.Any);
|
receiver.Inventory.CanBePut(draggedItems.FirstOrDefault(), InvSlotType.Any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -501,10 +501,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
|
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
|
||||||
|
|
||||||
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.05f)
|
//change the parameters of the damage effect and start a new sprite batch if the damage is different by 5% or more
|
||||||
|
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f ||
|
||||||
|
//if we were previously rendering some small amount of damage but now 0 damage, make sure we update the parameters
|
||||||
|
//"no damage" vs "just a tiny fraction of damage" makes a difference, even though normally 5% differences in damage aren't noticeable
|
||||||
|
MathUtils.NearlyEqual(newCutoff, 0.0f) != MathUtils.NearlyEqual(Submarine.DamageEffectCutoff, 0.0f))
|
||||||
{
|
{
|
||||||
spriteBatch.End();
|
spriteBatch.End();
|
||||||
spriteBatch.Begin(SpriteSortMode.BackToFront,
|
spriteBatch.Begin(SpriteSortMode.Deferred,
|
||||||
BlendState.NonPremultiplied, SamplerState.LinearWrap,
|
BlendState.NonPremultiplied, SamplerState.LinearWrap,
|
||||||
null, null,
|
null, null,
|
||||||
damageEffect,
|
damageEffect,
|
||||||
|
|||||||
@@ -160,37 +160,39 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static float DamageEffectCutoff;
|
public static float DamageEffectCutoff;
|
||||||
|
|
||||||
|
private static readonly List<Structure> depthSortedDamageable = new List<Structure>();
|
||||||
|
|
||||||
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
|
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
|
||||||
{
|
{
|
||||||
if (!editing && visibleEntities != null)
|
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.MapEntityList;
|
||||||
|
|
||||||
|
depthSortedDamageable.Clear();
|
||||||
|
|
||||||
|
//insertion sort according to draw depth
|
||||||
|
foreach (MapEntity e in entitiesToRender)
|
||||||
{
|
{
|
||||||
foreach (MapEntity e in visibleEntities)
|
if (e is Structure structure && structure.DrawDamageEffect)
|
||||||
{
|
{
|
||||||
if (e is Structure structure && structure.DrawDamageEffect)
|
if (predicate != null)
|
||||||
{
|
{
|
||||||
if (predicate != null)
|
if (!predicate(e)) { continue; }
|
||||||
{
|
|
||||||
if (!predicate(structure)) { continue; }
|
|
||||||
}
|
|
||||||
structure.DrawDamage(spriteBatch, damageEffect, editing);
|
|
||||||
}
|
}
|
||||||
}
|
float drawDepth = structure.GetDrawDepth();
|
||||||
}
|
int i = 0;
|
||||||
else
|
while (i < depthSortedDamageable.Count)
|
||||||
{
|
|
||||||
foreach (Structure structure in Structure.WallList)
|
|
||||||
{
|
|
||||||
if (structure.DrawDamageEffect)
|
|
||||||
{
|
{
|
||||||
if (predicate != null)
|
float otherDrawDepth = depthSortedDamageable[i].GetDrawDepth();
|
||||||
{
|
if (otherDrawDepth < drawDepth) { break; }
|
||||||
if (!predicate(structure)) { continue; }
|
i++;
|
||||||
}
|
|
||||||
structure.DrawDamage(spriteBatch, damageEffect, editing);
|
|
||||||
}
|
}
|
||||||
|
depthSortedDamageable.Insert(i, structure);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
foreach (Structure s in depthSortedDamageable)
|
||||||
|
{
|
||||||
|
s.DrawDamage(spriteBatch, damageEffect, editing);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
|
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
|
||||||
@@ -287,7 +289,7 @@ namespace Barotrauma
|
|||||||
if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; }
|
if (combinedHulls.ContainsKey(hull) || combinedHulls.Values.Any(hh => hh.Contains(hull))) { continue; }
|
||||||
|
|
||||||
List<Hull> linkedHulls = new List<Hull>();
|
List<Hull> linkedHulls = new List<Hull>();
|
||||||
MiniMap.GetLinkedHulls(hull, linkedHulls);
|
hull.GetLinkedHulls(linkedHulls);
|
||||||
|
|
||||||
linkedHulls.Remove(hull);
|
linkedHulls.Remove(hull);
|
||||||
|
|
||||||
@@ -297,7 +299,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
combinedHulls.Add(hull, new HashSet<Hull>());
|
combinedHulls.Add(hull, new HashSet<Hull>());
|
||||||
}
|
}
|
||||||
|
|
||||||
combinedHulls[hull].Add(linkedHull);
|
combinedHulls[hull].Add(linkedHull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -567,6 +568,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (item.GetComponent<OxygenGenerator>() is not OxygenGenerator oxygenGenerator) { continue; }
|
if (item.GetComponent<OxygenGenerator>() is not OxygenGenerator oxygenGenerator) { continue; }
|
||||||
|
|
||||||
|
oxygenGenerator.GetVents();
|
||||||
|
|
||||||
Dictionary<Hull, float> hullOxygenFlow = new Dictionary<Hull, float>();
|
Dictionary<Hull, float> hullOxygenFlow = new Dictionary<Hull, float>();
|
||||||
|
|
||||||
foreach (var linkedTo in item.linkedTo)
|
foreach (var linkedTo in item.linkedTo)
|
||||||
|
|||||||
@@ -256,6 +256,15 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
string downloadFolder = downloadFolders[(FileTransferType)fileType];
|
string downloadFolder = downloadFolders[(FileTransferType)fileType];
|
||||||
|
#if CLIENT && DEBUG
|
||||||
|
if (GameClient.MultiClientTestMode)
|
||||||
|
{
|
||||||
|
//append the name of the client to the download folder to avoid multiple clients
|
||||||
|
//from trying to download a file into the same path at the same time
|
||||||
|
downloadFolder += "_" + GameMain.Client.Name;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
if (!Directory.Exists(downloadFolder))
|
if (!Directory.Exists(downloadFolder))
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ using System.Globalization;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Barotrauma.PerkBehaviors;
|
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
@@ -26,6 +25,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public float DebugServerVoipAmplitude;
|
public float DebugServerVoipAmplitude;
|
||||||
|
|
||||||
|
public static bool MultiClientTestMode;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public override Voting Voting { get; }
|
public override Voting Voting { get; }
|
||||||
@@ -873,8 +874,9 @@ namespace Barotrauma.Networking
|
|||||||
ReadAchievement(inc);
|
ReadAchievement(inc);
|
||||||
break;
|
break;
|
||||||
case ServerPacketHeader.UNLOCKRECIPE:
|
case ServerPacketHeader.UNLOCKRECIPE:
|
||||||
|
CharacterTeamType team = (CharacterTeamType)inc.ReadByte();
|
||||||
Identifier identifier = inc.ReadIdentifier();
|
Identifier identifier = inc.ReadIdentifier();
|
||||||
GameMain.GameSession.UnlockRecipe(identifier, showNotifications: true);
|
GameMain.GameSession?.UnlockRecipe(team, identifier, showNotifications: true);
|
||||||
break;
|
break;
|
||||||
case ServerPacketHeader.ACHIEVEMENT_STAT:
|
case ServerPacketHeader.ACHIEVEMENT_STAT:
|
||||||
ReadAchievementStat(inc);
|
ReadAchievementStat(inc);
|
||||||
|
|||||||
@@ -10,30 +10,31 @@ sealed class DualStackP2PSocket : P2PSocket
|
|||||||
private DualStackP2PSocket(
|
private DualStackP2PSocket(
|
||||||
Callbacks callbacks,
|
Callbacks callbacks,
|
||||||
Option<EosP2PSocket> eosSocket,
|
Option<EosP2PSocket> eosSocket,
|
||||||
Option<SteamListenSocket> steamSocket) :
|
Option<SteamListenSocket> steamSocket,
|
||||||
base(callbacks)
|
OwnerOrClient type) :
|
||||||
|
base(callbacks, type)
|
||||||
{
|
{
|
||||||
this.eosSocket = eosSocket;
|
this.eosSocket = eosSocket;
|
||||||
this.steamSocket = steamSocket;
|
this.steamSocket = steamSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
|
public static Result<P2PSocket, Error> Create(Callbacks callbacks, OwnerOrClient type)
|
||||||
{
|
{
|
||||||
var eosP2PSocketResult = EosP2PSocket.Create(callbacks);
|
var eosP2PSocketResult = EosP2PSocket.Create(callbacks, type);
|
||||||
var steamP2PSocketResult = SteamListenSocket.Create(callbacks);
|
var steamP2PSocketResult = SteamListenSocket.Create(callbacks, type);
|
||||||
if (eosP2PSocketResult.TryUnwrapFailure(out var eosError)
|
if (eosP2PSocketResult.TryUnwrapFailure(out var eosError)
|
||||||
&& steamP2PSocketResult.TryUnwrapFailure(out var steamError))
|
&& steamP2PSocketResult.TryUnwrapFailure(out var steamError))
|
||||||
{
|
{
|
||||||
return Result.Failure(new Error(eosError, steamError));
|
return Result.Failure(new Error(eosError, steamError));
|
||||||
}
|
}
|
||||||
return Result.Success((P2PSocket)new DualStackP2PSocket(
|
return Result.Success<P2PSocket>(new DualStackP2PSocket(
|
||||||
callbacks,
|
callbacks,
|
||||||
eosP2PSocketResult.TryUnwrapSuccess(out var eosP2PSocket)
|
eosP2PSocketResult.TryUnwrapSuccess(out var eosP2PSocket)
|
||||||
? Option.Some((EosP2PSocket)eosP2PSocket)
|
? Option.Some((EosP2PSocket)eosP2PSocket)
|
||||||
: Option.None,
|
: Option.None,
|
||||||
steamP2PSocketResult.TryUnwrapSuccess(out var steamP2PSocket)
|
steamP2PSocketResult.TryUnwrapSuccess(out var steamP2PSocket)
|
||||||
? Option.Some((SteamListenSocket)steamP2PSocket)
|
? Option.Some((SteamListenSocket)steamP2PSocket)
|
||||||
: Option.None));
|
: Option.None, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ProcessIncomingMessages()
|
public override void ProcessIncomingMessages()
|
||||||
|
|||||||
@@ -8,13 +8,14 @@ sealed class EosP2PSocket : P2PSocket
|
|||||||
|
|
||||||
private EosP2PSocket(
|
private EosP2PSocket(
|
||||||
Callbacks callbacks,
|
Callbacks callbacks,
|
||||||
EosInterface.P2PSocket eosSocket)
|
EosInterface.P2PSocket eosSocket,
|
||||||
: base(callbacks)
|
OwnerOrClient type)
|
||||||
|
: base(callbacks, type)
|
||||||
{
|
{
|
||||||
this.eosSocket = eosSocket;
|
this.eosSocket = eosSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
|
public static Result<P2PSocket, Error> Create(Callbacks callbacks, OwnerOrClient type)
|
||||||
{
|
{
|
||||||
if (!EosInterface.Core.IsInitialized) { return Result.Failure(new Error(ErrorCode.EosNotInitialized)); }
|
if (!EosInterface.Core.IsInitialized) { return Result.Failure(new Error(ErrorCode.EosNotInitialized)); }
|
||||||
|
|
||||||
@@ -26,19 +27,25 @@ sealed class EosP2PSocket : P2PSocket
|
|||||||
var socketCreateResult = EosInterface.P2PSocket.Create(puids[0], eosSocketId);
|
var socketCreateResult = EosInterface.P2PSocket.Create(puids[0], eosSocketId);
|
||||||
|
|
||||||
if (!socketCreateResult.TryUnwrapSuccess(out var eosSocket)) { return Result.Failure(new Error(ErrorCode.FailedToCreateEosP2PSocket, socketCreateResult.ToString())); }
|
if (!socketCreateResult.TryUnwrapSuccess(out var eosSocket)) { return Result.Failure(new Error(ErrorCode.FailedToCreateEosP2PSocket, socketCreateResult.ToString())); }
|
||||||
var retVal = new EosP2PSocket(callbacks, eosSocket);
|
var retVal = new EosP2PSocket(callbacks, eosSocket, type);
|
||||||
|
|
||||||
eosSocket.HandleIncomingConnection.Register("Event".ToIdentifier(), retVal.OnIncomingConnection);
|
eosSocket.HandleIncomingConnection.Register("Event".ToIdentifier(), retVal.OnIncomingConnection);
|
||||||
eosSocket.HandleClosedConnection.Register("Event".ToIdentifier(), retVal.OnConnectionClosed);
|
eosSocket.HandleClosedConnection.Register("Event".ToIdentifier(), retVal.OnConnectionClosed);
|
||||||
|
|
||||||
return Result.Success((P2PSocket)retVal);
|
return Result.Success<P2PSocket>(retVal);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ProcessIncomingMessages()
|
public override void ProcessIncomingMessages()
|
||||||
{
|
{
|
||||||
foreach (var msg in eosSocket.GetMessageBatch())
|
foreach (var msg in eosSocket.GetMessageBatch())
|
||||||
{
|
{
|
||||||
callbacks.OnData(new EosP2PEndpoint(msg.Sender), new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false));
|
EosP2PEndpoint endpoint = new EosP2PEndpoint(msg.Sender);
|
||||||
|
callbacks.OnData(endpoint, new ReadWriteMessage(msg.Buffer, 0, msg.ByteLength * 8, false));
|
||||||
|
|
||||||
|
if (Type is OwnerOrClient.Owner)
|
||||||
|
{
|
||||||
|
dosProtection.OnPacket(endpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ namespace Barotrauma.Networking;
|
|||||||
|
|
||||||
abstract class P2PSocket : IDisposable
|
abstract class P2PSocket : IDisposable
|
||||||
{
|
{
|
||||||
|
public readonly P2POwnerDoSProtection dosProtection;
|
||||||
|
public readonly OwnerOrClient Type;
|
||||||
|
|
||||||
|
public enum OwnerOrClient
|
||||||
|
{
|
||||||
|
Client,
|
||||||
|
Owner
|
||||||
|
}
|
||||||
|
|
||||||
public enum ErrorCode
|
public enum ErrorCode
|
||||||
{
|
{
|
||||||
EosNotInitialized,
|
EosNotInitialized,
|
||||||
@@ -38,12 +47,16 @@ abstract class P2PSocket : IDisposable
|
|||||||
public readonly record struct Callbacks(
|
public readonly record struct Callbacks(
|
||||||
Predicate<P2PEndpoint> OnIncomingConnection,
|
Predicate<P2PEndpoint> OnIncomingConnection,
|
||||||
Action<P2PEndpoint, PeerDisconnectPacket> OnConnectionClosed,
|
Action<P2PEndpoint, PeerDisconnectPacket> OnConnectionClosed,
|
||||||
|
P2POwnerDoSProtection.ExcessivePacketDelegate OnExcessivePackets,
|
||||||
Action<P2PEndpoint, IReadMessage> OnData);
|
Action<P2PEndpoint, IReadMessage> OnData);
|
||||||
protected readonly Callbacks callbacks;
|
protected readonly Callbacks callbacks;
|
||||||
|
|
||||||
protected P2PSocket(Callbacks callbacks)
|
protected P2PSocket(Callbacks callbacks, OwnerOrClient type)
|
||||||
{
|
{
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
|
Type = type;
|
||||||
|
|
||||||
|
dosProtection = new P2POwnerDoSProtection(callbacks.OnExcessivePackets);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void ProcessIncomingMessages();
|
public abstract void ProcessIncomingMessages();
|
||||||
|
|||||||
@@ -64,13 +64,13 @@ sealed class SteamConnectSocket : P2PSocket
|
|||||||
private readonly SteamP2PEndpoint expectedEndpoint;
|
private readonly SteamP2PEndpoint expectedEndpoint;
|
||||||
private readonly ConnectionManager connectionManager;
|
private readonly ConnectionManager connectionManager;
|
||||||
|
|
||||||
private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager) : base(callbacks)
|
private SteamConnectSocket(SteamP2PEndpoint expectedEndpoint, Callbacks callbacks, ConnectionManager connectionManager, OwnerOrClient type) : base(callbacks, type)
|
||||||
{
|
{
|
||||||
this.expectedEndpoint = expectedEndpoint;
|
this.expectedEndpoint = expectedEndpoint;
|
||||||
this.connectionManager = connectionManager;
|
this.connectionManager = connectionManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<P2PSocket, Error> Create(SteamP2PEndpoint endpoint, Callbacks callbacks)
|
public static Result<P2PSocket, Error> Create(SteamP2PEndpoint endpoint, Callbacks callbacks, OwnerOrClient type)
|
||||||
{
|
{
|
||||||
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
|
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
|
||||||
|
|
||||||
@@ -87,7 +87,7 @@ sealed class SteamConnectSocket : P2PSocket
|
|||||||
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
|
if (connectionManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
|
||||||
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
|
connectionManager.SetEndpointAndCallbacks(endpoint, callbacks);
|
||||||
|
|
||||||
return Result.Success((P2PSocket)new SteamConnectSocket(endpoint, callbacks, connectionManager));
|
return Result.Success<P2PSocket>(new SteamConnectSocket(endpoint, callbacks, connectionManager, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ProcessIncomingMessages()
|
public override void ProcessIncomingMessages()
|
||||||
|
|||||||
@@ -10,12 +10,14 @@ sealed class SteamListenSocket : P2PSocket
|
|||||||
private sealed class SocketManager : Steamworks.SocketManager, Steamworks.ISocketManager
|
private sealed class SocketManager : Steamworks.SocketManager, Steamworks.ISocketManager
|
||||||
{
|
{
|
||||||
private Callbacks callbacks;
|
private Callbacks callbacks;
|
||||||
|
private P2PSocket socket;
|
||||||
private readonly Dictionary<SteamP2PEndpoint, Steamworks.Data.Connection> endpointToConnection = new();
|
private readonly Dictionary<SteamP2PEndpoint, Steamworks.Data.Connection> endpointToConnection = new();
|
||||||
|
|
||||||
public void SetCallbacks(Callbacks callbacks)
|
public void SetCallbacks(Callbacks callbacks)
|
||||||
{
|
=> this.callbacks = callbacks;
|
||||||
this.callbacks = callbacks;
|
|
||||||
}
|
public void SetSocket(P2PSocket socket)
|
||||||
|
=> this.socket = socket;
|
||||||
|
|
||||||
public override void OnConnecting(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
|
public override void OnConnecting(Steamworks.Data.Connection connection, Steamworks.Data.ConnectionInfo info)
|
||||||
{
|
{
|
||||||
@@ -65,7 +67,7 @@ sealed class SteamListenSocket : P2PSocket
|
|||||||
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
|
callbacks.OnConnectionClosed(remoteEndpoint, peerDisconnectPacket);
|
||||||
base.OnDisconnected(connection, info);
|
base.OnDisconnected(connection, info);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnMessage(Steamworks.Data.Connection connection, Steamworks.Data.NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
|
public override void OnMessage(Steamworks.Data.Connection connection, Steamworks.Data.NetIdentity identity, IntPtr data, int size, long messageNum, long recvTime, int channel)
|
||||||
{
|
{
|
||||||
if (!identity.IsSteamId || data == IntPtr.Zero) { return; }
|
if (!identity.IsSteamId || data == IntPtr.Zero) { return; }
|
||||||
@@ -75,6 +77,11 @@ sealed class SteamListenSocket : P2PSocket
|
|||||||
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
|
Marshal.Copy(source: data, destination: dataArray, startIndex: 0, length: size);
|
||||||
|
|
||||||
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
|
callbacks.OnData(endpoint, new ReadWriteMessage(dataArray, bitPos: 0, lBits: size * 8, copyBuf: false));
|
||||||
|
|
||||||
|
if (socket?.Type is OwnerOrClient.Owner)
|
||||||
|
{
|
||||||
|
socket.dosProtection.OnPacket(endpoint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal bool SendMessage(SteamP2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
|
internal bool SendMessage(SteamP2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
|
||||||
@@ -107,26 +114,49 @@ sealed class SteamListenSocket : P2PSocket
|
|||||||
|
|
||||||
private SteamListenSocket(
|
private SteamListenSocket(
|
||||||
Callbacks callbacks,
|
Callbacks callbacks,
|
||||||
SocketManager socketManager)
|
SocketManager socketManager,
|
||||||
: base(callbacks)
|
OwnerOrClient type)
|
||||||
|
: base(callbacks, type)
|
||||||
{
|
{
|
||||||
this.socketManager = socketManager;
|
this.socketManager = socketManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Result<P2PSocket, Error> Create(Callbacks callbacks)
|
public static Result<P2PSocket, Error> Create(Callbacks callbacks, OwnerOrClient type)
|
||||||
{
|
{
|
||||||
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
|
if (!SteamManager.IsInitialized) { return Result.Failure(new Error(ErrorCode.SteamNotInitialized)); }
|
||||||
|
|
||||||
var socketManager = Steamworks.SteamNetworkingSockets.CreateRelaySocket<SocketManager>();
|
var socketManager = Steamworks.SteamNetworkingSockets.CreateRelaySocket<SocketManager>();
|
||||||
if (socketManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
|
if (socketManager is null) { return Result.Failure(new Error(ErrorCode.FailedToCreateSteamP2PSocket)); }
|
||||||
socketManager.SetCallbacks(callbacks);
|
|
||||||
|
|
||||||
return Result.Success((P2PSocket)new SteamListenSocket(callbacks, socketManager));
|
socketManager.SetCallbacks(callbacks);
|
||||||
|
P2PSocket socket = new SteamListenSocket(callbacks, socketManager, type);
|
||||||
|
socketManager.SetSocket(socket);
|
||||||
|
|
||||||
|
return Result.Success(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void ProcessIncomingMessages()
|
public override void ProcessIncomingMessages()
|
||||||
{
|
{
|
||||||
socketManager.Receive();
|
const int bufferSize = 32;
|
||||||
|
const int maxIterations = 100;
|
||||||
|
|
||||||
|
// could technically cause a stack overflow since the call is recursive,
|
||||||
|
// use a while loop instead
|
||||||
|
int iteration;
|
||||||
|
for (iteration = 0; iteration < maxIterations; iteration++)
|
||||||
|
{
|
||||||
|
int received = socketManager.Receive(bufferSize: bufferSize, receiveToEnd: false);
|
||||||
|
|
||||||
|
if (received < bufferSize)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (iteration >= maxIterations)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("Steam P2P socket received too many messages in a single frame.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
|
public override bool SendMessage(P2PEndpoint endpoint, IWriteMessage outMsg, DeliveryMethod deliveryMethod)
|
||||||
|
|||||||
@@ -59,11 +59,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint();
|
ServerConnection = ServerEndpoint.MakeConnectionFromEndpoint();
|
||||||
|
|
||||||
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
|
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnExcessivePackets, OnP2PData);
|
||||||
var socketCreateResult = ServerEndpoint switch
|
var socketCreateResult = ServerEndpoint switch
|
||||||
{
|
{
|
||||||
EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks),
|
EosP2PEndpoint => EosP2PSocket.Create(socketCallbacks, P2PSocket.OwnerOrClient.Client),
|
||||||
SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks),
|
SteamP2PEndpoint steamP2PEndpoint => SteamConnectSocket.Create(steamP2PEndpoint, socketCallbacks, P2PSocket.OwnerOrClient.Client),
|
||||||
_ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}")
|
_ => throw new Exception($"Invalid server endpoint: {ServerEndpoint.GetType()} {ServerEndpoint}")
|
||||||
};
|
};
|
||||||
socket = socketCreateResult.TryUnwrapSuccess(out var s)
|
socket = socketCreateResult.TryUnwrapSuccess(out var s)
|
||||||
@@ -97,6 +97,11 @@ namespace Barotrauma.Networking
|
|||||||
isActive = true;
|
isActive = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnExcessivePackets(P2PEndpoint endpoint, bool shouldBan)
|
||||||
|
{
|
||||||
|
// do nothing
|
||||||
|
}
|
||||||
|
|
||||||
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
|
private bool OnIncomingConnection(P2PEndpoint remoteEndpoint)
|
||||||
{
|
{
|
||||||
if (remoteEndpoint == ServerEndpoint)
|
if (remoteEndpoint == ServerEndpoint)
|
||||||
@@ -163,7 +168,7 @@ namespace Barotrauma.Networking
|
|||||||
int completeMessageLengthBits = completeMessage.Length * 8;
|
int completeMessageLengthBits = completeMessage.Length * 8;
|
||||||
incomingDataMessages.Add(new ReadWriteMessage(completeMessage.ToArray(), 0, completeMessageLengthBits, copyBuf: false));
|
incomingDataMessages.Add(new ReadWriteMessage(completeMessage.ToArray(), 0, completeMessageLengthBits, copyBuf: false));
|
||||||
}
|
}
|
||||||
else if (packetHeader.IsHeartbeatMessage())
|
else if (packetHeader.IsHeartbeatMessage() || packetHeader.IsDoSProtectionMessage())
|
||||||
{
|
{
|
||||||
return; //TODO: implement heartbeats
|
return; //TODO: implement heartbeats
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -88,8 +88,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
remotePeers.Clear();
|
remotePeers.Clear();
|
||||||
|
|
||||||
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnP2PData);
|
var socketCallbacks = new P2PSocket.Callbacks(OnIncomingConnection, OnConnectionClosed, OnExcessivePackets, OnP2PData);
|
||||||
var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks);
|
var socketCreateResult = DualStackP2PSocket.Create(socketCallbacks, type: P2PSocket.OwnerOrClient.Owner);
|
||||||
socket = socketCreateResult.TryUnwrapSuccess(out var s)
|
socket = socketCreateResult.TryUnwrapSuccess(out var s)
|
||||||
? s
|
? s
|
||||||
: throw new Exception($"Failed to create dual-stack socket: {socketCreateResult}");
|
: throw new Exception($"Failed to create dual-stack socket: {socketCreateResult}");
|
||||||
@@ -187,6 +187,29 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnExcessivePackets(P2PEndpoint endpoint, bool shouldBan)
|
||||||
|
{
|
||||||
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
|
msg.WriteNetSerializableStruct(new P2POwnerToServerHeader
|
||||||
|
{
|
||||||
|
EndpointStr = selfPrimaryEndpoint.StringRepresentation,
|
||||||
|
AccountInfo = selfAccountInfo
|
||||||
|
});
|
||||||
|
msg.WriteNetSerializableStruct(new PeerPacketHeaders
|
||||||
|
{
|
||||||
|
DeliveryMethod = DeliveryMethod.Reliable,
|
||||||
|
PacketHeader = PacketHeader.IsDoSProtectionMessage
|
||||||
|
});
|
||||||
|
msg.WriteNetSerializableStruct(new DoSProtectionPacket(endpoint.StringRepresentation, shouldBan));
|
||||||
|
string dcMsg = TextManager.Get(shouldBan ? "DoSProtectionBanned" : "DoSProtectionKicked")
|
||||||
|
.Fallback(TextManager.Get("DoSProtectionKicked")).Value;
|
||||||
|
|
||||||
|
msg.WriteNetSerializableStruct(shouldBan
|
||||||
|
? PeerDisconnectPacket.Banned(dcMsg)
|
||||||
|
: PeerDisconnectPacket.Kicked(dcMsg));
|
||||||
|
ForwardToServerProcess(msg);
|
||||||
|
}
|
||||||
|
|
||||||
private void StartAuthTask(IReadMessage inc, RemotePeer remotePeer)
|
private void StartAuthTask(IReadMessage inc, RemotePeer remotePeer)
|
||||||
{
|
{
|
||||||
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending;
|
remotePeer.AuthStatus = RemotePeer.AuthenticationStatus.AuthenticationPending;
|
||||||
|
|||||||
@@ -612,7 +612,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
public bool Equals(ServerInfo other)
|
public bool Equals(ServerInfo other)
|
||||||
=> other.Endpoints.Any(e => Endpoints.Contains(e));
|
=> other != null && other.Endpoints.Any(Endpoints.Contains);
|
||||||
|
|
||||||
public override int GetHashCode() => Endpoints.First().GetHashCode();
|
public override int GetHashCode() => Endpoints.First().GetHashCode();
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
public partial class ServerLog
|
public partial class ServerLog
|
||||||
{
|
{
|
||||||
|
const int MaxLines = 500;
|
||||||
|
|
||||||
public GUIButton LogFrame;
|
public GUIButton LogFrame;
|
||||||
private GUIListBox listBox;
|
private GUIListBox listBox;
|
||||||
private GUIButton reverseButton;
|
private GUIButton reverseButton;
|
||||||
@@ -17,6 +19,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private bool reverseOrder = false;
|
private bool reverseOrder = false;
|
||||||
|
|
||||||
|
private readonly bool[] msgTypeHidden = new bool[Enum.GetValues(typeof(MessageType)).Length];
|
||||||
|
|
||||||
private bool OnReverseClicked(GUIButton btn, object obj)
|
private bool OnReverseClicked(GUIButton btn, object obj)
|
||||||
{
|
{
|
||||||
SetMessageReversal(!reverseOrder);
|
SetMessageReversal(!reverseOrder);
|
||||||
@@ -105,7 +109,10 @@ namespace Barotrauma.Networking
|
|||||||
reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None);
|
reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None);
|
||||||
reverseButton.OnClicked = OnReverseClicked;
|
reverseButton.OnClicked = OnReverseClicked;
|
||||||
|
|
||||||
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform));
|
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform))
|
||||||
|
{
|
||||||
|
AutoHideScrollBar = false
|
||||||
|
};
|
||||||
|
|
||||||
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close"))
|
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close"))
|
||||||
{
|
{
|
||||||
@@ -127,7 +134,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
listBox.UpdateScrollBarSize();
|
listBox.UpdateScrollBarSize();
|
||||||
|
|
||||||
if (listBox.BarScroll == 0.0f || listBox.BarScroll == 1.0f) { listBox.BarScroll = 1.0f; }
|
//scrolled all the way down by default
|
||||||
|
listBox.BarScroll = 1.0f;
|
||||||
|
|
||||||
msgFilter = "";
|
msgFilter = "";
|
||||||
}
|
}
|
||||||
@@ -189,11 +197,19 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
float prevSize = listBox.BarSize;
|
float prevSize = listBox.BarSize;
|
||||||
|
|
||||||
|
GUIComponent firstVisibleLine = listBox.Content.Children.FirstOrDefault(c => c.Rect.Y > listBox.Content.Rect.Y);
|
||||||
|
int firstVisibileYPos = firstVisibleLine?.Rect.Y ?? 0;
|
||||||
|
|
||||||
|
while (listBox.Content.CountChildren > MaxLines)
|
||||||
|
{
|
||||||
|
listBox.Content.RemoveChild(reverseOrder ? listBox.Content.Children.Last() : listBox.Content.Children.First());
|
||||||
|
}
|
||||||
|
|
||||||
GUIFrame textContainer = null;
|
GUIFrame textContainer = null;
|
||||||
|
|
||||||
Anchor anchor = Anchor.TopLeft;
|
Anchor anchor = Anchor.TopLeft;
|
||||||
Pivot pivot = Pivot.TopLeft;
|
Pivot pivot = Pivot.TopLeft;
|
||||||
RichString richString = line.Text as RichString;
|
RichString richString = line.Text;
|
||||||
if (richString != null && richString.RichTextData.HasValue)
|
if (richString != null && richString.RichTextData.HasValue)
|
||||||
{
|
{
|
||||||
foreach (var data in richString.RichTextData.Value)
|
foreach (var data in richString.RichTextData.Value)
|
||||||
@@ -217,7 +233,7 @@ namespace Barotrauma.Networking
|
|||||||
line.Text, wrap: true, font: GUIStyle.SmallFont)
|
line.Text, wrap: true, font: GUIStyle.SmallFont)
|
||||||
{
|
{
|
||||||
TextColor = messageColor[line.Type],
|
TextColor = messageColor[line.Type],
|
||||||
Visible = !msgTypeHidden[(int)line.Type],
|
Visible = !ShouldFilterMessage(line),
|
||||||
CanBeFocused = false,
|
CanBeFocused = false,
|
||||||
UserData = line
|
UserData = line
|
||||||
};
|
};
|
||||||
@@ -247,31 +263,47 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f;
|
//if the list was scrolled to the bottom, or to the top while the list wasn't full yet,
|
||||||
|
//keep it scrolled to the bottom
|
||||||
|
if ((MathUtils.NearlyEqual(prevSize, 1.0f) && MathUtils.NearlyEqual(listBox.BarScroll, 0.0f)) ||
|
||||||
|
(prevSize < 1.0f && MathUtils.NearlyEqual(listBox.BarScroll, 1.0f)))
|
||||||
|
{
|
||||||
|
listBox.BarScroll = 1.0f;
|
||||||
|
}
|
||||||
|
//otherwise modify the scroll so the topmost element stays where it was (list doesn't jump as new lines are added when scrolled up)
|
||||||
|
else if (firstVisibleLine != null)
|
||||||
|
{
|
||||||
|
listBox.UpdateScrollBarSize();
|
||||||
|
listBox.RecalculateChildren();
|
||||||
|
int diff = firstVisibleLine.Rect.Y - firstVisibileYPos;
|
||||||
|
if (diff != 0)
|
||||||
|
{
|
||||||
|
listBox.BarScroll += diff / listBox.TotalSize * (prevSize / listBox.BarSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool FilterMessages()
|
private bool FilterMessages()
|
||||||
{
|
{
|
||||||
string filter = msgFilter == null ? "" : msgFilter.ToLower();
|
|
||||||
|
|
||||||
foreach (GUIComponent child in listBox.Content.Children)
|
foreach (GUIComponent child in listBox.Content.Children)
|
||||||
{
|
{
|
||||||
if (!(child is GUITextBlock textBlock)) { continue; }
|
if (child is not GUITextBlock) { continue; }
|
||||||
child.Visible = true;
|
child.Visible = true;
|
||||||
if (msgTypeHidden[(int)((LogMessage)child.UserData).Type])
|
child.Visible = !ShouldFilterMessage((LogMessage)child.UserData);
|
||||||
{
|
|
||||||
child.Visible = false;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
textBlock.Visible = string.IsNullOrEmpty(filter) || textBlock.Text.ToLower().Contains(filter);
|
|
||||||
}
|
}
|
||||||
listBox.UpdateScrollBarSize();
|
listBox.UpdateScrollBarSize();
|
||||||
listBox.BarScroll = 0.0f;
|
listBox.BarScroll = 1.0f;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool ShouldFilterMessage(LogMessage message)
|
||||||
|
{
|
||||||
|
if (msgTypeHidden[(int)message.Type]) { return true; }
|
||||||
|
string text = message.Text.SanitizedValue;
|
||||||
|
return !string.IsNullOrEmpty(msgFilter) && !text.Contains(msgFilter, StringComparison.InvariantCultureIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
private void SetMessageReversal(bool reverse)
|
private void SetMessageReversal(bool reverse)
|
||||||
{
|
{
|
||||||
if (reverseOrder == reverse) { return; }
|
if (reverseOrder == reverse) { return; }
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (IsValidShape(Radius, Height, Width))
|
if (IsValidShape(Radius, Height, Width))
|
||||||
{
|
{
|
||||||
DrawShape(drawPosition, DrawRotation, color);
|
DrawShape(DrawPosition, DrawRotation, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (LastServerState != null)
|
if (LastServerState != null)
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected virtual Color BackgroundColor => new Color(150, 150, 150);
|
protected virtual Color BackgroundColor => new Color(150, 150, 150);
|
||||||
|
|
||||||
private void DrawBack(SpriteBatch spriteBatch)
|
protected virtual void DrawBack(SpriteBatch spriteBatch)
|
||||||
{
|
{
|
||||||
Color outlineColor = Color.White * 0.8f;
|
Color outlineColor = Color.White * 0.8f;
|
||||||
Color fontColor = Color.White;
|
Color fontColor = Color.White;
|
||||||
@@ -253,9 +253,19 @@ namespace Barotrauma
|
|||||||
GUI.DrawRectangle(spriteBatch, HeaderRectangle, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
|
GUI.DrawRectangle(spriteBatch, HeaderRectangle, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
|
||||||
GUI.DrawRectangle(spriteBatch, bodyRect, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
|
GUI.DrawRectangle(spriteBatch, bodyRect, outlineColor, isFilled: false, depth: 1.0f, thickness: (int) Math.Max(1, 1.25f / camZoom));
|
||||||
|
|
||||||
|
DrawConnections(spriteBatch);
|
||||||
|
|
||||||
|
Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name);
|
||||||
|
GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DrawConnections(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
int x = 0, y = 0;
|
int x = 0, y = 0;
|
||||||
foreach (EventEditorNodeConnection connection in Connections)
|
foreach (EventEditorNodeConnection connection in Connections)
|
||||||
{
|
{
|
||||||
|
if (!ShouldDrawConnection(connection)) { continue; }
|
||||||
|
|
||||||
switch (connection.Type.NodeSide)
|
switch (connection.Type.NodeSide)
|
||||||
{
|
{
|
||||||
case NodeConnectionType.Side.Left:
|
case NodeConnectionType.Side.Left:
|
||||||
@@ -268,9 +278,11 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name);
|
protected virtual bool ShouldDrawConnection(EventEditorNodeConnection connection)
|
||||||
GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2), fontColor);
|
{
|
||||||
|
return true; // Base implementation draws all connections
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddConnection(NodeConnectionType connectionType)
|
public void AddConnection(NodeConnectionType connectionType)
|
||||||
@@ -337,6 +349,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
private readonly Type type;
|
private readonly Type type;
|
||||||
|
|
||||||
|
protected override Color BackgroundColor =>
|
||||||
|
EventEditorScreen.ConversationMode && !IsInstanceOf(type, typeof(ConversationAction))
|
||||||
|
? new Color(80, 80, 80) // Darker for non-conversation nodes in conversation mode
|
||||||
|
: new Color(150, 150, 150); // Normal color
|
||||||
|
|
||||||
public EventNode(Type type, string name) : base(name)
|
public EventNode(Type type, string name) : base(name)
|
||||||
{
|
{
|
||||||
this.type = type;
|
this.type = type;
|
||||||
@@ -387,8 +404,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Type? t = Type.GetType(element.GetAttributeString("type", string.Empty));
|
Type? t = Type.GetType(element.GetAttributeString("type", string.Empty));
|
||||||
if (t == null) { return null; }
|
if (t == null) { return null; }
|
||||||
|
|
||||||
|
string name = element.GetAttributeString("name", string.Empty);
|
||||||
|
int id = element.GetAttributeInt("i", -1);
|
||||||
|
|
||||||
EventNode newNode = new EventNode(t, element.GetAttributeString("name", string.Empty)) { ID = element.GetAttributeInt("i", -1) };
|
// Create the appropriate node type based on whether it's a conversation action
|
||||||
|
EditorNode newNode = IsInstanceOf(t, typeof(ConversationAction))
|
||||||
|
? new EventConversationNode(t, name) { ID = id }
|
||||||
|
: new EventNode(t, name) { ID = id };
|
||||||
|
|
||||||
float posX = element.GetAttributeFloat("xpos", 0f);
|
float posX = element.GetAttributeFloat("xpos", 0f);
|
||||||
float posY = element.GetAttributeFloat("ypos", 0f);
|
float posY = element.GetAttributeFloat("ypos", 0f);
|
||||||
newNode.Position = new Vector2(posX, posY);
|
newNode.Position = new Vector2(posX, posY);
|
||||||
|
|||||||
@@ -0,0 +1,398 @@
|
|||||||
|
#nullable enable
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Base class for event nodes that display text content and can have inner Text nodes
|
||||||
|
/// </summary>
|
||||||
|
internal abstract class EventTextDisplayNode(Type type, string name) : EventNode(type, name)
|
||||||
|
{
|
||||||
|
protected virtual bool ShowOptions => false;
|
||||||
|
|
||||||
|
private new Rectangle HeaderRectangle
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (!EventEditorScreen.ConversationMode) { return base.HeaderRectangle; }
|
||||||
|
|
||||||
|
Rectangle drawRect = GetDrawRectangle();
|
||||||
|
return new Rectangle(Position.ToPoint(), new Point(drawRect.Width, 32));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Rectangle GetDrawRectangle()
|
||||||
|
{
|
||||||
|
if (!EventEditorScreen.ConversationMode) { return base.GetDrawRectangle(); }
|
||||||
|
|
||||||
|
var textConnection = Connections.Find(c => string.Equals(c.Attribute, "Text", StringComparison.OrdinalIgnoreCase));
|
||||||
|
var optionConnections = ShowOptions ? Connections.Where(c => c.Type == NodeConnectionType.Option) : Enumerable.Empty<EventEditorNodeConnection>();
|
||||||
|
|
||||||
|
const int width = 300;
|
||||||
|
int height = 50;
|
||||||
|
|
||||||
|
// Calculate height for text section
|
||||||
|
if (textConnection != null)
|
||||||
|
{
|
||||||
|
string textContent = GetTextContent(textConnection);
|
||||||
|
if (!string.IsNullOrEmpty(textContent) && GUIStyle.Font.Value != null)
|
||||||
|
{
|
||||||
|
string wrappedText = ToolBox.WrapText(textContent, width - 16, GUIStyle.Font.Value);
|
||||||
|
Vector2 textSize = GUIStyle.Font.MeasureString(wrappedText);
|
||||||
|
height += (int)textSize.Y + 10;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
height += 25;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate height for each option (only for conversation nodes)
|
||||||
|
if (ShowOptions)
|
||||||
|
{
|
||||||
|
int optionIndex = 0;
|
||||||
|
foreach (var option in optionConnections)
|
||||||
|
{
|
||||||
|
string optionText = GetOptionText(option, optionIndex);
|
||||||
|
|
||||||
|
if (GUIStyle.Font.Value != null)
|
||||||
|
{
|
||||||
|
string wrappedOption = ToolBox.WrapText(optionText, width - 40, GUIStyle.Font.Value);
|
||||||
|
Vector2 optionSize = GUIStyle.Font.MeasureString(wrappedOption);
|
||||||
|
height += (int)optionSize.Y + 20;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
height += 40;
|
||||||
|
}
|
||||||
|
optionIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle rect = Rectangle;
|
||||||
|
return new Rectangle(rect.X, rect.Y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawBack(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (!EventEditorScreen.ConversationMode)
|
||||||
|
{
|
||||||
|
base.DrawBack(spriteBatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle bodyRect = GetDrawRectangle();
|
||||||
|
|
||||||
|
// Background colors
|
||||||
|
Color headerColor = IsSelected ? new Color(100, 150, 200) : new Color(120, 170, 220);
|
||||||
|
Color bodyColor = new Color(90, 120, 150);
|
||||||
|
Color borderColor = Color.LightBlue;
|
||||||
|
|
||||||
|
// Draw background
|
||||||
|
GUI.DrawRectangle(spriteBatch, HeaderRectangle, headerColor, isFilled: true, depth: 1.0f);
|
||||||
|
GUI.DrawRectangle(spriteBatch, bodyRect, bodyColor, isFilled: true, depth: 1.0f);
|
||||||
|
GUI.DrawRectangle(spriteBatch, HeaderRectangle, borderColor, isFilled: false, depth: 1.0f);
|
||||||
|
GUI.DrawRectangle(spriteBatch, bodyRect, borderColor, isFilled: false, depth: 1.0f);
|
||||||
|
|
||||||
|
// Draw header text
|
||||||
|
Vector2 headerSize = GUIStyle.SubHeadingFont.MeasureString(Name);
|
||||||
|
Vector2 headerPos = HeaderRectangle.Location.ToVector2() + (HeaderRectangle.Size.ToVector2() / 2) - (headerSize / 2);
|
||||||
|
GUIStyle.SubHeadingFont.DrawString(spriteBatch, Name, headerPos, Color.White);
|
||||||
|
|
||||||
|
// Draw text content
|
||||||
|
DrawTextContent(spriteBatch, bodyRect);
|
||||||
|
|
||||||
|
// Let base class handle standard connections (Activate, Next, etc.)
|
||||||
|
DrawConnections(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void DrawTextContent(SpriteBatch spriteBatch, Rectangle bodyRect)
|
||||||
|
{
|
||||||
|
var textConnection = Connections.Find(c => string.Equals(c.Attribute, "Text", StringComparison.OrdinalIgnoreCase));
|
||||||
|
var optionConnections = ShowOptions ? Connections.Where(c => c.Type == NodeConnectionType.Option) : Enumerable.Empty<EventEditorNodeConnection>();
|
||||||
|
|
||||||
|
Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||||
|
mousePos.Y = -mousePos.Y;
|
||||||
|
|
||||||
|
const int padding = 8;
|
||||||
|
int currentY = bodyRect.Y + padding + 30;
|
||||||
|
|
||||||
|
// Draw text section
|
||||||
|
if (textConnection != null)
|
||||||
|
{
|
||||||
|
string textContent = GetTextContent(textConnection);
|
||||||
|
|
||||||
|
// Wrap text and calculate height
|
||||||
|
string wrappedText = textContent;
|
||||||
|
int textHeight = 25;
|
||||||
|
if (GUIStyle.Font.Value != null)
|
||||||
|
{
|
||||||
|
wrappedText = ToolBox.WrapText(textContent, bodyRect.Width - 24, GUIStyle.Font.Value);
|
||||||
|
Vector2 textSize = GUIStyle.Font.MeasureString(wrappedText);
|
||||||
|
textHeight = (int)textSize.Y + 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle textRect = new Rectangle(bodyRect.X + padding, currentY, bodyRect.Width - padding * 2, textHeight);
|
||||||
|
|
||||||
|
// background
|
||||||
|
GUI.DrawRectangle(spriteBatch, textRect, new Color(70, 100, 130), isFilled: true);
|
||||||
|
GUI.DrawRectangle(spriteBatch, textRect, Color.CornflowerBlue, isFilled: false);
|
||||||
|
|
||||||
|
// wrapped text
|
||||||
|
Vector2 textPos = new Vector2(textRect.X + 4, textRect.Y + 4);
|
||||||
|
GUI.DrawString(spriteBatch, textPos, wrappedText, Color.Yellow, font: GUIStyle.Font);
|
||||||
|
|
||||||
|
// tooltip
|
||||||
|
if (textRect.Contains(mousePos))
|
||||||
|
{
|
||||||
|
string rawTextKey = GetRawTextKey(textConnection);
|
||||||
|
if (!string.IsNullOrEmpty(rawTextKey))
|
||||||
|
{
|
||||||
|
EventEditorScreen.DrawnTooltip = rawTextKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
currentY += textHeight + 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Draw options (only for conversation nodes)
|
||||||
|
if (ShowOptions)
|
||||||
|
{
|
||||||
|
DrawOptions(spriteBatch, bodyRect, optionConnections, currentY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void DrawOptions(SpriteBatch spriteBatch, Rectangle bodyRect, IEnumerable<EventEditorNodeConnection> optionConnections, int startY)
|
||||||
|
{
|
||||||
|
Vector2 mousePos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||||
|
mousePos.Y = -mousePos.Y;
|
||||||
|
const int padding = 8;
|
||||||
|
int currentY = startY;
|
||||||
|
|
||||||
|
int optionIndex = 0;
|
||||||
|
foreach (var option in optionConnections)
|
||||||
|
{
|
||||||
|
string optionText = GetOptionText(option, optionIndex);
|
||||||
|
|
||||||
|
// Wrap option text and calculate height
|
||||||
|
string wrappedOption = optionText;
|
||||||
|
int optionHeight = 30;
|
||||||
|
if (GUIStyle.Font.Value != null)
|
||||||
|
{
|
||||||
|
wrappedOption = ToolBox.WrapText(optionText, bodyRect.Width - 40, GUIStyle.Font.Value);
|
||||||
|
Vector2 optionSize = GUIStyle.Font.MeasureString(wrappedOption);
|
||||||
|
optionHeight = (int)optionSize.Y + 16;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle optionRect = new Rectangle(bodyRect.X + padding, currentY, bodyRect.Width - padding * 2, optionHeight);
|
||||||
|
|
||||||
|
// background - red for end conversation, blue for normal
|
||||||
|
Color optionBg = option.EndConversation ? new Color(120, 80, 80) : new Color(80, 80, 120);
|
||||||
|
GUI.DrawRectangle(spriteBatch, optionRect, optionBg, isFilled: true);
|
||||||
|
GUI.DrawRectangle(spriteBatch, optionRect, Color.White, isFilled: false);
|
||||||
|
|
||||||
|
Vector2 optionPos = new Vector2(optionRect.X + 4, optionRect.Y + 4);
|
||||||
|
GUI.DrawString(spriteBatch, optionPos, wrappedOption, Color.White, font: GUIStyle.Font);
|
||||||
|
|
||||||
|
// tooltip
|
||||||
|
if (optionRect.Contains(mousePos))
|
||||||
|
{
|
||||||
|
string rawOptionKey = option.OptionText ?? "";
|
||||||
|
if (!string.IsNullOrEmpty(rawOptionKey))
|
||||||
|
{
|
||||||
|
EventEditorScreen.DrawnTooltip = rawOptionKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// connection point
|
||||||
|
Rectangle connRect = new Rectangle(bodyRect.Right - 1, optionRect.Y + optionHeight / 2 - 8, 16, 16);
|
||||||
|
GUI.DrawRectangle(spriteBatch, connRect, Color.DarkGray, isFilled: true);
|
||||||
|
GUI.DrawRectangle(spriteBatch, connRect, Color.White, isFilled: false);
|
||||||
|
option.DrawRectangle = connRect;
|
||||||
|
|
||||||
|
// connection lines
|
||||||
|
foreach (var connected in option.ConnectedTo)
|
||||||
|
{
|
||||||
|
Vector2 start = new Vector2(connRect.Right, connRect.Center.Y);
|
||||||
|
Vector2 end = new Vector2(connected.DrawRectangle.Left, connected.DrawRectangle.Center.Y);
|
||||||
|
|
||||||
|
float knobLength = 24;
|
||||||
|
var (points, _) = ToolBox.GetSquareLineBetweenPoints(start, end, knobLength);
|
||||||
|
|
||||||
|
Color lineColor = GUIStyle.Red;
|
||||||
|
float lineWidth = Math.Max(2.0f, 2.0f / (Screen.Selected is EventEditorScreen eventEditor ? eventEditor.Cam.Zoom : 1.0f));
|
||||||
|
|
||||||
|
GUI.DrawLine(spriteBatch, points[0], points[1], lineColor, width: (int)lineWidth);
|
||||||
|
GUI.DrawLine(spriteBatch, points[1], points[2], lineColor, width: (int)lineWidth);
|
||||||
|
GUI.DrawLine(spriteBatch, points[2], points[3], lineColor, width: (int)lineWidth);
|
||||||
|
GUI.DrawLine(spriteBatch, points[3], points[4], lineColor, width: (int)lineWidth);
|
||||||
|
GUI.DrawLine(spriteBatch, points[4], points[5], lineColor, width: (int)lineWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
currentY += optionHeight + 5;
|
||||||
|
optionIndex++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetOptionText(EventEditorNodeConnection option, int optionIndex)
|
||||||
|
{
|
||||||
|
string optionTextKey = option.OptionText ?? $"Option {optionIndex + 1}";
|
||||||
|
var allVariants = TextManager.GetAll(optionTextKey);
|
||||||
|
|
||||||
|
int variantCount = allVariants.Count();
|
||||||
|
return variantCount switch
|
||||||
|
{
|
||||||
|
> 1 => $"[{variantCount} variants] {string.Join(" / ", allVariants)}",
|
||||||
|
1 => allVariants.First(),
|
||||||
|
_ => optionTextKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetTextContent(EventEditorNodeConnection textConnection)
|
||||||
|
{
|
||||||
|
string textContent = "";
|
||||||
|
|
||||||
|
// First check if there's a direct text attribute
|
||||||
|
if (textConnection.OverrideValue != null)
|
||||||
|
{
|
||||||
|
textContent = textConnection.OverrideValue.ToString() ?? "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
object? connectedValue = textConnection.GetValue();
|
||||||
|
if (connectedValue != null)
|
||||||
|
{
|
||||||
|
textContent = connectedValue.ToString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no direct text, check for inner Text nodes via Add connections
|
||||||
|
if (string.IsNullOrEmpty(textContent))
|
||||||
|
{
|
||||||
|
var addConnection = Connections.FirstOrDefault(c => c.Type == NodeConnectionType.Add);
|
||||||
|
if (addConnection != null && addConnection.ConnectedTo.Any())
|
||||||
|
{
|
||||||
|
var connectedNode = addConnection.ConnectedTo.First();
|
||||||
|
if (connectedNode.Parent?.Name == "Text")
|
||||||
|
{
|
||||||
|
// Get the text content from the connected Text node
|
||||||
|
var textNodeConnection = connectedNode.Parent.Connections.FirstOrDefault(c =>
|
||||||
|
string.Equals(c.Attribute, "tag", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (textNodeConnection?.OverrideValue != null)
|
||||||
|
{
|
||||||
|
textContent = textNodeConnection.OverrideValue.ToString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Translate the text if we found any
|
||||||
|
if (!string.IsNullOrEmpty(textContent))
|
||||||
|
{
|
||||||
|
var translated = TextManager.Get(textContent);
|
||||||
|
if (translated.Loaded)
|
||||||
|
{
|
||||||
|
textContent = translated.Value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetRawTextKey(EventEditorNodeConnection textConnection)
|
||||||
|
{
|
||||||
|
string textKey = "";
|
||||||
|
|
||||||
|
// First check if there's a direct text attribute
|
||||||
|
if (textConnection.OverrideValue != null)
|
||||||
|
{
|
||||||
|
textKey = textConnection.OverrideValue.ToString() ?? "";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var connectedValue = textConnection.GetValue();
|
||||||
|
if (connectedValue != null)
|
||||||
|
{
|
||||||
|
textKey = connectedValue.ToString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no direct text, check for inner Text nodes via Add connections
|
||||||
|
if (string.IsNullOrEmpty(textKey))
|
||||||
|
{
|
||||||
|
var addConnection = Connections.FirstOrDefault(c => c.Type == NodeConnectionType.Add);
|
||||||
|
if (addConnection != null && addConnection.ConnectedTo.Any())
|
||||||
|
{
|
||||||
|
var connectedNode = addConnection.ConnectedTo.First();
|
||||||
|
if (connectedNode.Parent?.Name == "Text")
|
||||||
|
{
|
||||||
|
// Get the text key from the connected Text node
|
||||||
|
var textNodeConnection = connectedNode.Parent.Connections.FirstOrDefault(c =>
|
||||||
|
string.Equals(c.Attribute, "tag", StringComparison.OrdinalIgnoreCase));
|
||||||
|
|
||||||
|
if (textNodeConnection?.OverrideValue != null)
|
||||||
|
{
|
||||||
|
textKey = textNodeConnection.OverrideValue.ToString() ?? "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return textKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool ShouldDrawConnection(EventEditorNodeConnection connection)
|
||||||
|
{
|
||||||
|
if (!EventEditorScreen.ConversationMode) { return base.ShouldDrawConnection(connection); }
|
||||||
|
|
||||||
|
// In conversation mode, exclude Options and Text since we draw them manually
|
||||||
|
// Also exclude Add connections since we hide the child Text nodes and display their content inline
|
||||||
|
return connection.Type == NodeConnectionType.Activate ||
|
||||||
|
connection.Type == NodeConnectionType.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DrawConnections(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
if (!EventEditorScreen.ConversationMode)
|
||||||
|
{
|
||||||
|
base.DrawConnections(spriteBatch);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In conversation mode, use the correct rectangle for connection positioning
|
||||||
|
Rectangle correctRect = GetDrawRectangle();
|
||||||
|
int x = 0, y = 0;
|
||||||
|
foreach (EventEditorNodeConnection connection in Connections)
|
||||||
|
{
|
||||||
|
if (!ShouldDrawConnection(connection)) { continue; }
|
||||||
|
|
||||||
|
switch (connection.Type.NodeSide)
|
||||||
|
{
|
||||||
|
case NodeConnectionType.Side.Left:
|
||||||
|
connection.Draw(spriteBatch, correctRect, y);
|
||||||
|
y++;
|
||||||
|
break;
|
||||||
|
case NodeConnectionType.Side.Right:
|
||||||
|
connection.Draw(spriteBatch, correctRect, x);
|
||||||
|
x++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EventConversationNode(Type type, string name) : EventTextDisplayNode(type, name)
|
||||||
|
{
|
||||||
|
protected override bool ShowOptions => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class EventLogNode(Type type, string name) : EventTextDisplayNode(type, name)
|
||||||
|
{
|
||||||
|
protected override bool ShowOptions => false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,6 +18,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override Camera Cam { get; }
|
public override Camera Cam { get; }
|
||||||
public static string? DrawnTooltip { get; set; }
|
public static string? DrawnTooltip { get; set; }
|
||||||
|
|
||||||
|
public static bool ConversationMode { get; set; }
|
||||||
|
|
||||||
public static readonly List<EditorNode> nodeList = new List<EditorNode>();
|
public static readonly List<EditorNode> nodeList = new List<EditorNode>();
|
||||||
|
|
||||||
@@ -37,6 +39,11 @@ namespace Barotrauma
|
|||||||
private LocationType? lastTestType;
|
private LocationType? lastTestType;
|
||||||
|
|
||||||
private GUITickBox? isTraitorEventBox;
|
private GUITickBox? isTraitorEventBox;
|
||||||
|
private GUITickBox? conversationModeBox;
|
||||||
|
|
||||||
|
private readonly LanguageIdentifier originalLanguage;
|
||||||
|
|
||||||
|
private GUIDropDown? languageDropdown;
|
||||||
|
|
||||||
private static int CreateID()
|
private static int CreateID()
|
||||||
{
|
{
|
||||||
@@ -50,25 +57,99 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Cam = new Camera();
|
Cam = new Camera();
|
||||||
nodeList.Clear();
|
nodeList.Clear();
|
||||||
|
|
||||||
|
originalLanguage = GameSettings.CurrentConfig.Language;
|
||||||
|
|
||||||
CreateGUI();
|
CreateGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Select()
|
||||||
|
{
|
||||||
|
GUI.PreventPauseMenuToggle = false;
|
||||||
|
projectName = TextManager.Get("EventEditor.Unnamed").Value;
|
||||||
|
|
||||||
|
UpdateLanguageDropdownSelection();
|
||||||
|
|
||||||
|
base.Select();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateLanguageDropdownSelection()
|
||||||
|
{
|
||||||
|
if (languageDropdown == null) { return; }
|
||||||
|
|
||||||
|
languageDropdown.SelectItem(GameSettings.CurrentConfig.Language);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void DeselectEditorSpecific()
|
||||||
|
{
|
||||||
|
// Restore the original language when leaving the editor
|
||||||
|
var config = GameSettings.CurrentConfig;
|
||||||
|
config.Language = originalLanguage;
|
||||||
|
GameSettings.SetCurrentConfig(config);
|
||||||
|
TextManager.LanguageChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly HashSet<EditorNode> hiddenNodesInConversationMode = new HashSet<EditorNode>();
|
||||||
|
|
||||||
|
private static bool ShouldHideNodeInConversationMode(EditorNode node)
|
||||||
|
{
|
||||||
|
return hiddenNodesInConversationMode.Contains(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void UpdateHiddenNodesInConversationMode()
|
||||||
|
{
|
||||||
|
hiddenNodesInConversationMode.Clear();
|
||||||
|
|
||||||
|
// Find all text display nodes (ConversationAction and EventLogAction) and mark their inner Text nodes (and descendants) as hidden
|
||||||
|
foreach (var textDisplayNode in nodeList.Where(IsEventTextDisplayNode))
|
||||||
|
{
|
||||||
|
var addConnection = textDisplayNode.Connections.FirstOrDefault(c => c.Type == NodeConnectionType.Add);
|
||||||
|
if (addConnection != null && addConnection.ConnectedTo.Any())
|
||||||
|
{
|
||||||
|
foreach (var connectedNode in addConnection.ConnectedTo)
|
||||||
|
{
|
||||||
|
if (connectedNode.Parent?.Name == "Text")
|
||||||
|
{
|
||||||
|
MarkNodeAndDescendantsAsHidden(connectedNode.Parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsEventTextDisplayNode(EditorNode node) => node is EventTextDisplayNode;
|
||||||
|
|
||||||
|
private static void MarkNodeAndDescendantsAsHidden(EditorNode node)
|
||||||
|
{
|
||||||
|
hiddenNodesInConversationMode.Add(node);
|
||||||
|
|
||||||
|
// Recursively mark all connected child nodes as hidden
|
||||||
|
foreach (var connection in node.Connections)
|
||||||
|
{
|
||||||
|
foreach (var connectedNode in connection.ConnectedTo)
|
||||||
|
{
|
||||||
|
if (connectedNode.Parent != null && !hiddenNodesInConversationMode.Contains(connectedNode.Parent))
|
||||||
|
{
|
||||||
|
MarkNodeAndDescendantsAsHidden(connectedNode.Parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateGUI()
|
private void CreateGUI()
|
||||||
{
|
{
|
||||||
GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.4f), GUI.Canvas) { MinSize = new Point(300, 420) });
|
GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.2f, 0.5f), GUI.Canvas) { MinSize = new Point(300, 520) });
|
||||||
GUILayoutGroup layoutGroup = new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame, Anchor.Center)) { Stretch = true, AbsoluteSpacing = GUI.IntScale(5) };
|
GUILayoutGroup layoutGroup = new GUILayoutGroup(RectTransform(0.9f, 0.9f, GuiFrame, Anchor.Center)) { Stretch = true, AbsoluteSpacing = GUI.IntScale(5) };
|
||||||
|
|
||||||
// === BUTTONS === //
|
// === BUTTONS === //
|
||||||
GUILayoutGroup buttonLayout = new GUILayoutGroup(RectTransform(1.0f, 0.50f, layoutGroup)) { RelativeSpacing = 0.04f };
|
GUILayoutGroup buttonLayout = new GUILayoutGroup(RectTransform(1.0f, 0.40f, layoutGroup)) { RelativeSpacing = 0.04f };
|
||||||
GUIButton newProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.NewProject"));
|
GUIButton newProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.NewProject"));
|
||||||
GUIButton saveProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.SaveProject"));
|
GUIButton saveProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.SaveProject"));
|
||||||
GUIButton loadProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.LoadProject"));
|
GUIButton loadProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.LoadProject"));
|
||||||
GUIButton exportProjectButton = new GUIButton(RectTransform(1.0f, 0.33f, buttonLayout), TextManager.Get("EventEditor.Export"));
|
GUIButton exportProjectButton = new GUIButton(RectTransform(1.0f, 0.25f, buttonLayout), TextManager.Get("EventEditor.Export"));
|
||||||
|
|
||||||
|
|
||||||
// === LOAD PREFAB === //
|
// === LOAD PREFAB === //
|
||||||
|
GUILayoutGroup loadEventLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup));
|
||||||
GUILayoutGroup loadEventLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
|
|
||||||
new GUITextBlock(RectTransform(1.0f, 0.5f, loadEventLayout), TextManager.Get("EventEditor.LoadEvent"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(RectTransform(1.0f, 0.5f, loadEventLayout), TextManager.Get("EventEditor.LoadEvent"), font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
GUILayoutGroup loadDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, loadEventLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
GUILayoutGroup loadDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, loadEventLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
@@ -76,8 +157,7 @@ namespace Barotrauma
|
|||||||
GUIButton loadButton = new GUIButton(RectTransform(0.2f, 1.0f, loadDropdownLayout), TextManager.Get("Load"));
|
GUIButton loadButton = new GUIButton(RectTransform(0.2f, 1.0f, loadDropdownLayout), TextManager.Get("Load"));
|
||||||
|
|
||||||
// === ADD ACTION === //
|
// === ADD ACTION === //
|
||||||
|
GUILayoutGroup addActionLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup));
|
||||||
GUILayoutGroup addActionLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
|
|
||||||
new GUITextBlock(RectTransform(1.0f, 0.5f, addActionLayout), TextManager.Get("EventEditor.AddAction"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(RectTransform(1.0f, 0.5f, addActionLayout), TextManager.Get("EventEditor.AddAction"), font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
GUILayoutGroup addActionDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addActionLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
GUILayoutGroup addActionDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addActionLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
@@ -85,7 +165,7 @@ namespace Barotrauma
|
|||||||
GUIButton addActionButton = new GUIButton(RectTransform(0.2f, 1.0f, addActionDropdownLayout), TextManager.Get("EventEditor.Add"));
|
GUIButton addActionButton = new GUIButton(RectTransform(0.2f, 1.0f, addActionDropdownLayout), TextManager.Get("EventEditor.Add"));
|
||||||
|
|
||||||
// === ADD VALUE === //
|
// === ADD VALUE === //
|
||||||
GUILayoutGroup addValueLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
|
GUILayoutGroup addValueLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup));
|
||||||
new GUITextBlock(RectTransform(1.0f, 0.5f, addValueLayout), TextManager.Get("EventEditor.AddValue"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(RectTransform(1.0f, 0.5f, addValueLayout), TextManager.Get("EventEditor.AddValue"), font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
GUILayoutGroup addValueDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addValueLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
GUILayoutGroup addValueDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addValueLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
@@ -93,7 +173,7 @@ namespace Barotrauma
|
|||||||
GUIButton addValueButton = new GUIButton(RectTransform(0.2f, 1.0f, addValueDropdownLayout), TextManager.Get("EventEditor.Add"));
|
GUIButton addValueButton = new GUIButton(RectTransform(0.2f, 1.0f, addValueDropdownLayout), TextManager.Get("EventEditor.Add"));
|
||||||
|
|
||||||
// === ADD SPECIAL === //
|
// === ADD SPECIAL === //
|
||||||
GUILayoutGroup addSpecialLayout = new GUILayoutGroup(RectTransform(1.0f, 0.125f, layoutGroup));
|
GUILayoutGroup addSpecialLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup));
|
||||||
new GUITextBlock(RectTransform(1.0f, 0.5f, addSpecialLayout), TextManager.Get("EventEditor.AddSpecial"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(RectTransform(1.0f, 0.5f, addSpecialLayout), TextManager.Get("EventEditor.AddSpecial"), font: GUIStyle.SubHeadingFont);
|
||||||
GUILayoutGroup addSpecialDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addSpecialLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
GUILayoutGroup addSpecialDropdownLayout = new GUILayoutGroup(RectTransform(1.0f, 0.5f, addSpecialLayout), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
GUIDropDown addSpecialDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, addSpecialDropdownLayout), elementCount: 1);
|
GUIDropDown addSpecialDropdown = new GUIDropDown(RectTransform(0.8f, 1.0f, addSpecialDropdownLayout), elementCount: 1);
|
||||||
@@ -156,7 +236,45 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
isTraitorEventBox = new GUITickBox(RectTransform(1.0f, 0.125f, layoutGroup), "Traitor event");
|
isTraitorEventBox = new GUITickBox(RectTransform(1.0f, 0.10f, layoutGroup), "Traitor event");
|
||||||
|
|
||||||
|
// === CONVERSATION MODE CHECKBOX === //
|
||||||
|
conversationModeBox = new GUITickBox(RectTransform(1.0f, 0.10f, layoutGroup), "Conversation Mode");
|
||||||
|
conversationModeBox.Selected = ConversationMode;
|
||||||
|
conversationModeBox.OnSelected = box =>
|
||||||
|
{
|
||||||
|
ConversationMode = !ConversationMode;
|
||||||
|
UpdateHiddenNodesInConversationMode();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
// === LANGUAGE SELECTION === //
|
||||||
|
GUILayoutGroup languageLayout = new GUILayoutGroup(RectTransform(1.0f, 0.10f, layoutGroup));
|
||||||
|
new GUITextBlock(RectTransform(1.0f, 0.5f, languageLayout), TextManager.Get("Language"), font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
|
var languages = TextManager.AvailableLanguages
|
||||||
|
.OrderBy(l => TextManager.GetTranslatedLanguageName(l).ToIdentifier());
|
||||||
|
|
||||||
|
languageDropdown = new GUIDropDown(RectTransform(1.0f, 0.5f, languageLayout), elementCount: 10);
|
||||||
|
foreach (var language in languages)
|
||||||
|
{
|
||||||
|
languageDropdown.AddItem(TextManager.GetTranslatedLanguageName(language), language);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Select current language
|
||||||
|
languageDropdown.SelectItem(GameSettings.CurrentConfig.Language);
|
||||||
|
|
||||||
|
languageDropdown.OnSelected = (component, userData) =>
|
||||||
|
{
|
||||||
|
if (userData is LanguageIdentifier selectedLanguage)
|
||||||
|
{
|
||||||
|
var config = GameSettings.CurrentConfig;
|
||||||
|
config.Language = selectedLanguage;
|
||||||
|
GameSettings.SetCurrentConfig(config);
|
||||||
|
TextManager.LanguageChanged();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
||||||
}
|
}
|
||||||
@@ -323,6 +441,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
GUI.NotifyPrompt(TextManager.Get("EventEditor.RandomGenerationHeader"), TextManager.Get("EventEditor.RandomGenerationBody"));
|
GUI.NotifyPrompt(TextManager.Get("EventEditor.RandomGenerationHeader"), TextManager.Get("EventEditor.RandomGenerationBody"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update hidden nodes after loading
|
||||||
|
UpdateHiddenNodesInConversationMode();
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
@@ -334,7 +455,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Vector2 spawnPos = Cam.WorldViewCenter;
|
Vector2 spawnPos = Cam.WorldViewCenter;
|
||||||
spawnPos.Y = -spawnPos.Y;
|
spawnPos.Y = -spawnPos.Y;
|
||||||
EventNode newNode = new EventNode(type, type.Name) { ID = CreateID() };
|
|
||||||
|
// Create the appropriate node type based on the action type
|
||||||
|
EditorNode newNode;
|
||||||
|
if (EditorNode.IsInstanceOf(type, typeof(ConversationAction)))
|
||||||
|
{
|
||||||
|
newNode = new EventConversationNode(type, type.Name) { ID = CreateID() };
|
||||||
|
}
|
||||||
|
else if (EditorNode.IsInstanceOf(type, typeof(EventLogAction)))
|
||||||
|
{
|
||||||
|
newNode = new EventLogNode(type, type.Name) { ID = CreateID() };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newNode = new EventNode(type, type.Name) { ID = CreateID() };
|
||||||
|
}
|
||||||
|
|
||||||
newNode.Position = spawnPos - newNode.Size / 2;
|
newNode.Position = spawnPos - newNode.Size / 2;
|
||||||
nodeList.Add(newNode);
|
nodeList.Add(newNode);
|
||||||
return true;
|
return true;
|
||||||
@@ -399,7 +535,19 @@ namespace Barotrauma
|
|||||||
Type? t = Type.GetType($"Barotrauma.{subElement.Name}");
|
Type? t = Type.GetType($"Barotrauma.{subElement.Name}");
|
||||||
if (t != null && EditorNode.IsInstanceOf(t, typeof(EventAction)))
|
if (t != null && EditorNode.IsInstanceOf(t, typeof(EventAction)))
|
||||||
{
|
{
|
||||||
newNode = new EventNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() };
|
// Create the appropriate node type based on the action type
|
||||||
|
if (EditorNode.IsInstanceOf(t, typeof(ConversationAction)))
|
||||||
|
{
|
||||||
|
newNode = new EventConversationNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() };
|
||||||
|
}
|
||||||
|
else if (EditorNode.IsInstanceOf(t, typeof(EventLogAction)))
|
||||||
|
{
|
||||||
|
newNode = new EventLogNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newNode = new EventNode(t, subElement.Name.ToString()) { Position = new Vector2(ident, 0), ID = CreateID() };
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -547,13 +695,6 @@ namespace Barotrauma
|
|||||||
return new RectTransform(new Vector2(x, y), parent.RectTransform, anchor);
|
return new RectTransform(new Vector2(x, y), parent.RectTransform, anchor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Select()
|
|
||||||
{
|
|
||||||
GUI.PreventPauseMenuToggle = false;
|
|
||||||
projectName = TextManager.Get("EventEditor.Unnamed").Value;
|
|
||||||
base.Select();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void AddToGUIUpdateList()
|
public override void AddToGUIUpdateList()
|
||||||
{
|
{
|
||||||
GuiFrame.AddToGUIUpdateList();
|
GuiFrame.AddToGUIUpdateList();
|
||||||
@@ -671,6 +812,7 @@ namespace Barotrauma
|
|||||||
private static void Load(XElement saveElement)
|
private static void Load(XElement saveElement)
|
||||||
{
|
{
|
||||||
nodeList.Clear();
|
nodeList.Clear();
|
||||||
|
hiddenNodesInConversationMode.Clear();
|
||||||
projectName = saveElement.GetAttributeString("name", TextManager.Get("EventEditor.Unnamed").Value);
|
projectName = saveElement.GetAttributeString("name", TextManager.Get("EventEditor.Unnamed").Value);
|
||||||
foreach (XElement element in saveElement.Elements())
|
foreach (XElement element in saveElement.Elements())
|
||||||
{
|
{
|
||||||
@@ -702,6 +844,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update hidden nodes after loading
|
||||||
|
UpdateHiddenNodesInConversationMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void CreateContextMenu(EditorNode node, EventEditorNodeConnection? connection = null)
|
private static void CreateContextMenu(EditorNode node, EventEditorNodeConnection? connection = null)
|
||||||
@@ -971,21 +1116,25 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (EditorNode node in nodeList.Where(node => node is SpecialNode))
|
foreach (EditorNode node in nodeList.Where(node => node is SpecialNode))
|
||||||
{
|
{
|
||||||
|
if (ConversationMode && ShouldHideNodeInConversationMode(node)) { continue; }
|
||||||
node.Draw(spriteBatch);
|
node.Draw(spriteBatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Render value nodes below event nodes
|
// Render value nodes below event nodes
|
||||||
foreach (EditorNode node in nodeList.Where(node => node is ValueNode))
|
foreach (EditorNode node in nodeList.Where(node => node is ValueNode))
|
||||||
{
|
{
|
||||||
|
if (ConversationMode && ShouldHideNodeInConversationMode(node)) { continue; }
|
||||||
node.Draw(spriteBatch);
|
node.Draw(spriteBatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (EditorNode node in nodeList.Where(node => node is EventNode))
|
foreach (EditorNode node in nodeList.Where(node => node is EventNode))
|
||||||
{
|
{
|
||||||
|
if (ConversationMode && ShouldHideNodeInConversationMode(node)) { continue; }
|
||||||
node.Draw(spriteBatch);
|
node.Draw(spriteBatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
draggedNode?.Draw(spriteBatch);
|
draggedNode?.Draw(spriteBatch);
|
||||||
|
|
||||||
foreach (var (node, _) in markedNodes)
|
foreach (var (node, _) in markedNodes)
|
||||||
{
|
{
|
||||||
node.Draw(spriteBatch);
|
node.Draw(spriteBatch);
|
||||||
@@ -1013,6 +1162,11 @@ namespace Barotrauma
|
|||||||
CreateGUI();
|
CreateGUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (PlayerInput.KeyHit(Keys.R) && PlayerInput.KeyDown(Keys.LeftShift))
|
||||||
|
{
|
||||||
|
CreateGUI();
|
||||||
|
}
|
||||||
|
|
||||||
Cam.MoveCamera((float) deltaTime, allowMove: true, allowZoom: GUI.MouseOn == null);
|
Cam.MoveCamera((float) deltaTime, allowMove: true, allowZoom: GUI.MouseOn == null);
|
||||||
Vector2 mousePos = Cam.ScreenToWorld(PlayerInput.MousePosition);
|
Vector2 mousePos = Cam.ScreenToWorld(PlayerInput.MousePosition);
|
||||||
mousePos.Y = -mousePos.Y;
|
mousePos.Y = -mousePos.Y;
|
||||||
|
|||||||
@@ -319,14 +319,21 @@ namespace Barotrauma
|
|||||||
graphics.Clear(Color.Transparent);
|
graphics.Clear(Color.Transparent);
|
||||||
|
|
||||||
DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"];
|
DamageEffect.CurrentTechnique = DamageEffect.Techniques["StencilShader"];
|
||||||
DamageEffect.CurrentTechnique.Passes[0].Apply();
|
//reset so any parameters left over from previous usages of the shader don't persist
|
||||||
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, SamplerState.LinearWrap, effect: DamageEffect, transformMatrix: cam.Transform);
|
ResetDamageEffect();
|
||||||
|
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, SamplerState.LinearWrap, effect: DamageEffect, transformMatrix: cam.Transform);
|
||||||
Submarine.DrawDamageable(spriteBatch, DamageEffect, false);
|
Submarine.DrawDamageable(spriteBatch, DamageEffect, false);
|
||||||
DamageEffect.Parameters["aCutoff"].SetValue(0.0f);
|
|
||||||
DamageEffect.Parameters["cCutoff"].SetValue(0.0f);
|
|
||||||
Submarine.DamageEffectCutoff = 0.0f;
|
|
||||||
DamageEffect.CurrentTechnique.Passes[0].Apply();
|
|
||||||
spriteBatch.End();
|
spriteBatch.End();
|
||||||
|
//reset so parameters set in DrawDamageable don't persist
|
||||||
|
ResetDamageEffect();
|
||||||
|
|
||||||
|
void ResetDamageEffect()
|
||||||
|
{
|
||||||
|
DamageEffect.Parameters["aCutoff"].SetValue(0.0f);
|
||||||
|
DamageEffect.Parameters["cCutoff"].SetValue(0.0f);
|
||||||
|
Submarine.DamageEffectCutoff = 0.0f;
|
||||||
|
DamageEffect.CurrentTechnique.Passes[0].Apply();
|
||||||
|
}
|
||||||
|
|
||||||
sw.Stop();
|
sw.Stop();
|
||||||
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontDamageable", sw.ElapsedTicks);
|
GameMain.PerformanceCounter.AddElapsedTicks("Draw:Map:FrontDamageable", sw.ElapsedTicks);
|
||||||
|
|||||||
@@ -2090,7 +2090,10 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
serverLogReverseButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), serverLogListboxLayout.RectTransform), style: "UIToggleButtonVertical");
|
serverLogReverseButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), serverLogListboxLayout.RectTransform), style: "UIToggleButtonVertical");
|
||||||
serverLogBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), serverLogListboxLayout.RectTransform));
|
serverLogBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), serverLogListboxLayout.RectTransform))
|
||||||
|
{
|
||||||
|
AutoHideScrollBar = false
|
||||||
|
};
|
||||||
|
|
||||||
//filter tickbox list ------------------------------------------------------------------
|
//filter tickbox list ------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -2198,7 +2201,7 @@ namespace Barotrauma
|
|||||||
OnClicked = (btn, obj) =>
|
OnClicked = (btn, obj) =>
|
||||||
{
|
{
|
||||||
if (GameMain.Client == null) { return true; }
|
if (GameMain.Client == null) { return true; }
|
||||||
GUI.CreateVerificationPrompt(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
|
GUI.CreateVerificationPrompt(GameMain.GameSession?.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
GameMain.Client?.RequestEndRound(save: false);
|
GameMain.Client?.RequestEndRound(save: false);
|
||||||
@@ -3304,11 +3307,17 @@ namespace Barotrauma
|
|||||||
VoteType voteType;
|
VoteType voteType;
|
||||||
if (component.Parent == GameMain.NetLobbyScreen.SubList.Content)
|
if (component.Parent == GameMain.NetLobbyScreen.SubList.Content)
|
||||||
{
|
{
|
||||||
if (SelectedMode == GameModePreset.PvP && MultiplayerPreferences.Instance.TeamPreference is not (CharacterTeamType.Team1 or CharacterTeamType.Team2))
|
if (SelectedMode == GameModePreset.PvP &&
|
||||||
|
MultiplayerPreferences.Instance.TeamPreference is not (CharacterTeamType.Team1 or CharacterTeamType.Team2))
|
||||||
{
|
{
|
||||||
|
if (TeamPreferenceListBox == null)
|
||||||
|
{
|
||||||
|
//refresh player frame to ensure we create the team preference list box
|
||||||
|
UpdatePlayerFrame(characterInfo: GameMain.Client?.CharacterInfo);
|
||||||
|
}
|
||||||
|
|
||||||
// we are in PvP but don't have a team selected, so we can't select a sub
|
// we are in PvP but don't have a team selected, so we can't select a sub
|
||||||
// and also highlight the team selection list
|
// and also highlight the team selection list
|
||||||
|
|
||||||
foreach (GUIComponent child in TeamPreferenceListBox.Content.Children)
|
foreach (GUIComponent child in TeamPreferenceListBox.Content.Children)
|
||||||
{
|
{
|
||||||
if (child.UserData is CharacterTeamType.None) { continue; }
|
if (child.UserData is CharacterTeamType.None) { continue; }
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Barotrauma.Extensions;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -42,6 +43,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override void Update(float deltaTime)
|
protected override void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
|
if (slideshowPrefab.Slides.IsEmpty) { return; }
|
||||||
|
|
||||||
var slide = slideshowPrefab.Slides[Math.Min(state, slideshowPrefab.Slides.Length - 1)];
|
var slide = slideshowPrefab.Slides[Math.Min(state, slideshowPrefab.Slides.Length - 1)];
|
||||||
if (!Visible || (Finished && timer > slide.FadeOutDuration)) { return; }
|
if (!Visible || (Finished && timer > slide.FadeOutDuration)) { return; }
|
||||||
|
|
||||||
@@ -104,6 +107,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void RefreshText()
|
private void RefreshText()
|
||||||
{
|
{
|
||||||
|
if (slideshowPrefab.Slides.IsEmpty) { return; }
|
||||||
var slide = slideshowPrefab.Slides[Math.Min(state, slideshowPrefab.Slides.Length - 1)];
|
var slide = slideshowPrefab.Slides[Math.Min(state, slideshowPrefab.Slides.Length - 1)];
|
||||||
currentText = slide.Text
|
currentText = slide.Text
|
||||||
.Replace("[submarine]", Submarine.MainSub?.Info.Name ?? GameMain.GameSession?.SubmarineInfo?.Name ?? "Unknown")
|
.Replace("[submarine]", Submarine.MainSub?.Info.Name ?? GameMain.GameSession?.SubmarineInfo?.Name ?? "Unknown")
|
||||||
|
|||||||
@@ -2088,7 +2088,7 @@ namespace Barotrauma
|
|||||||
if (packageToSaveTo != null)
|
if (packageToSaveTo != null)
|
||||||
{
|
{
|
||||||
var modProject = new ModProject(packageToSaveTo);
|
var modProject = new ModProject(packageToSaveTo);
|
||||||
var fileListPath = packageToSaveTo.Path;
|
string fileListPath = packageToSaveTo.Path;
|
||||||
if (packageToSaveTo == ContentPackageManager.VanillaCorePackage)
|
if (packageToSaveTo == ContentPackageManager.VanillaCorePackage)
|
||||||
{
|
{
|
||||||
#if !DEBUG
|
#if !DEBUG
|
||||||
@@ -2104,10 +2104,12 @@ namespace Barotrauma
|
|||||||
SubmarineType.Wreck => "Content/Map/Wrecks/{0}",
|
SubmarineType.Wreck => "Content/Map/Wrecks/{0}",
|
||||||
SubmarineType.BeaconStation => "Content/Map/BeaconStations/{0}",
|
SubmarineType.BeaconStation => "Content/Map/BeaconStations/{0}",
|
||||||
SubmarineType.EnemySubmarine => "Content/Map/EnemySubmarines/{0}",
|
SubmarineType.EnemySubmarine => "Content/Map/EnemySubmarines/{0}",
|
||||||
SubmarineType.OutpostModule => MainSub.Info.FilePath.Contains("RuinModules") ? "Content/Map/RuinModules/{0}" : "Content/Map/Outposts/{0}",
|
SubmarineType.OutpostModule => MainSub.Info.FilePath != null && MainSub.Info.FilePath.Contains("RuinModules") ? "Content/Map/RuinModules/{0}" : "Content/Map/Outposts/{0}",
|
||||||
_ => throw new InvalidOperationException()
|
_ => throw new InvalidOperationException()
|
||||||
}, savePath);
|
}, savePath);
|
||||||
modProject.ModVersion = "";
|
modProject.ModVersion = "";
|
||||||
|
addSubAndSave(modProject, savePath, fileListPath);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2116,27 +2118,41 @@ namespace Barotrauma
|
|||||||
if (existingFilePath != null)
|
if (existingFilePath != null)
|
||||||
{
|
{
|
||||||
savePath = existingFilePath;
|
savePath = existingFilePath;
|
||||||
|
addSubAndSave(modProject, savePath, fileListPath);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
//otherwise make sure we're not trying to overwrite another sub in the same package
|
//otherwise make sure we're not trying to overwrite another sub in the same package
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
savePath = Path.Combine(packageToSaveTo.Dir, savePath);
|
var existingSubInContentPackage =
|
||||||
if (File.Exists(savePath))
|
SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Type == MainSub?.Info?.Type && packageToSaveTo.GetFiles<BaseSubFile>().Any(f => f.Path == s.FilePath));
|
||||||
|
if (existingSubInContentPackage != null)
|
||||||
{
|
{
|
||||||
var verification = new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("subeditor.duplicatesubinpackage"),
|
string directoryName = Path.GetDirectoryName(existingSubInContentPackage.FilePath);
|
||||||
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
|
string directoryNameRelativeToPackage = Path.GetRelativePath(Path.GetDirectoryName(packageToSaveTo.Path), directoryName);
|
||||||
|
var verification = new GUIMessageBox(string.Empty, TextManager.GetWithVariable("subeditor.saveinexistingfolderprompt", "[folder]", directoryNameRelativeToPackage),
|
||||||
|
[TextManager.GetWithVariable("subeditor.saveinexistingfolderprompt.yes", "[folder]", directoryNameRelativeToPackage), TextManager.Get("subeditor.saveinexistingfolderprompt.no")]);
|
||||||
verification.Buttons[0].OnClicked = (_, _) =>
|
verification.Buttons[0].OnClicked = (_, _) =>
|
||||||
{
|
{
|
||||||
addSubAndSave(modProject, savePath, fileListPath);
|
savePath = Path.Combine(directoryNameRelativeToPackage, savePath);
|
||||||
|
trySaveWithDuplicateCheck(modProject, fileListPath);
|
||||||
|
verification.Close();
|
||||||
|
return true;
|
||||||
|
}; verification.Buttons[1].OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
trySaveWithDuplicateCheck(modProject, fileListPath);
|
||||||
verification.Close();
|
verification.Close();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
verification.Buttons[1].OnClicked = verification.Close;
|
return true;
|
||||||
return false;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
trySaveWithDuplicateCheck(modProject, fileListPath);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addSubAndSave(modProject, savePath, fileListPath);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -2150,9 +2166,28 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
ModProject modProject = new ModProject { Name = name };
|
ModProject modProject = new ModProject { Name = name };
|
||||||
addSubAndSave(modProject, savePath, Path.Combine(Path.GetDirectoryName(savePath), ContentPackage.FileListFileName));
|
addSubAndSave(modProject, savePath, Path.Combine(Path.GetDirectoryName(savePath), ContentPackage.FileListFileName));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void trySaveWithDuplicateCheck(ModProject modProject, string fileListPath)
|
||||||
|
{
|
||||||
|
savePath = Path.Combine(packageToSaveTo.Dir, savePath);
|
||||||
|
if (File.Exists(savePath))
|
||||||
|
{
|
||||||
|
var verification = new GUIMessageBox(TextManager.Get("warning"), TextManager.Get("subeditor.duplicatesubinpackage"),
|
||||||
|
[TextManager.Get("yes"), TextManager.Get("no")]);
|
||||||
|
verification.Buttons[0].OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
addSubAndSave(modProject, savePath, fileListPath);
|
||||||
|
verification.Close();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
verification.Buttons[1].OnClicked = verification.Close;
|
||||||
|
}
|
||||||
|
addSubAndSave(modProject, savePath, fileListPath);
|
||||||
|
}
|
||||||
|
|
||||||
void addSubAndSave(ModProject modProject, string filePath, string packagePath)
|
void addSubAndSave(ModProject modProject, string filePath, string packagePath)
|
||||||
{
|
{
|
||||||
filePath = filePath.CleanUpPath();
|
filePath = filePath.CleanUpPath();
|
||||||
@@ -2234,8 +2269,6 @@ namespace Barotrauma
|
|||||||
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
|
subNameLabel.Text = ToolBox.LimitString(MainSub.Info.Name, subNameLabel.Font, subNameLabel.Rect.Width);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateSaveScreen(bool quickSave = false)
|
private void CreateSaveScreen(bool quickSave = false)
|
||||||
@@ -2653,13 +2686,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
//---------------------------------------
|
//---------------------------------------
|
||||||
|
|
||||||
var extraSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.5f), subTypeDependentSettingFrame.RectTransform))
|
var extraSettingsContainer = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.75f), subTypeDependentSettingFrame.RectTransform))
|
||||||
{
|
{
|
||||||
CanBeFocused = true,
|
CanBeFocused = true,
|
||||||
Visible = false,
|
Visible = false,
|
||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
var extraSubInfo = GetExtraSubmarineInfo(MainSub?.Info);
|
||||||
|
|
||||||
var minDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true)
|
var minDifficultyGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), extraSettingsContainer.RectTransform), isHorizontal: true)
|
||||||
{
|
{
|
||||||
Stretch = true
|
Stretch = true
|
||||||
@@ -2668,12 +2703,12 @@ namespace Barotrauma
|
|||||||
TextManager.Get("minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true);
|
TextManager.Get("minleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true);
|
||||||
var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), minDifficultyGroup.RectTransform), NumberType.Int)
|
var numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), minDifficultyGroup.RectTransform), NumberType.Int)
|
||||||
{
|
{
|
||||||
IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MinLevelDifficulty ?? 0),
|
IntValue = (int)(extraSubInfo?.MinLevelDifficulty ?? 0),
|
||||||
MinValueInt = 0,
|
MinValueInt = 0,
|
||||||
MaxValueInt = 100,
|
MaxValueInt = 100,
|
||||||
OnValueChanged = (numberInput) =>
|
OnValueChanged = (numberInput) =>
|
||||||
{
|
{
|
||||||
MainSub.Info.GetExtraSubmarineInfo.MinLevelDifficulty = numberInput.IntValue;
|
extraSubInfo.MinLevelDifficulty = numberInput.IntValue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
minDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
|
minDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
|
||||||
@@ -2685,16 +2720,17 @@ namespace Barotrauma
|
|||||||
TextManager.Get("maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true);
|
TextManager.Get("maxleveldifficulty"), textAlignment: Alignment.CenterLeft, wrap: true);
|
||||||
numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), maxDifficultyGroup.RectTransform), NumberType.Int)
|
numInput = new GUINumberInput(new RectTransform(new Vector2(0.4f, 1.0f), maxDifficultyGroup.RectTransform), NumberType.Int)
|
||||||
{
|
{
|
||||||
IntValue = (int)(MainSub?.Info?.GetExtraSubmarineInfo?.MaxLevelDifficulty ?? 100),
|
IntValue = (int)(extraSubInfo?.MaxLevelDifficulty ?? 100),
|
||||||
MinValueInt = 0,
|
MinValueInt = 0,
|
||||||
MaxValueInt = 100,
|
MaxValueInt = 100,
|
||||||
OnValueChanged = (numberInput) =>
|
OnValueChanged = (numberInput) =>
|
||||||
{
|
{
|
||||||
MainSub.Info.GetExtraSubmarineInfo.MaxLevelDifficulty = numberInput.IntValue;
|
extraSubInfo.MaxLevelDifficulty = numberInput.IntValue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
maxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
|
maxDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
|
||||||
|
|
||||||
|
GUITextBox missionTagsBox = CreateMissionTagsUI(extraSettingsContainer, extraSubInfo?.MissionTags ?? Enumerable.Empty<Identifier>(), ChangeMissionTags);
|
||||||
|
|
||||||
//---------------------------------------
|
//---------------------------------------
|
||||||
|
|
||||||
@@ -2759,15 +2795,13 @@ namespace Barotrauma
|
|||||||
triggerMissionTagsGroup.RectTransform.MaxSize = triggerMissionTagsBox.RectTransform.MaxSize;
|
triggerMissionTagsGroup.RectTransform.MaxSize = triggerMissionTagsBox.RectTransform.MaxSize;
|
||||||
//---------------------------------------
|
//---------------------------------------
|
||||||
|
|
||||||
var enemySubmarineSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, subTypeDependentSettingFrame.RectTransform))
|
var enemySubmarineSettingsContainer = new GUILayoutGroup(new RectTransform(Vector2.One, extraSettingsContainer.RectTransform))
|
||||||
{
|
{
|
||||||
CanBeFocused = true,
|
CanBeFocused = true,
|
||||||
Visible = false,
|
Visible = false,
|
||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------
|
|
||||||
|
|
||||||
var enemySubmarineRewardGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), enemySubmarineSettingsContainer.RectTransform), isHorizontal: true)
|
var enemySubmarineRewardGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), enemySubmarineSettingsContainer.RectTransform), isHorizontal: true)
|
||||||
{
|
{
|
||||||
Stretch = true
|
Stretch = true
|
||||||
@@ -2802,26 +2836,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
enemySubmarineDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
|
enemySubmarineDifficultyGroup.RectTransform.MaxSize = numInput.TextBox.RectTransform.MaxSize;
|
||||||
var enemySubmarineTagsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), enemySubmarineSettingsContainer.RectTransform), isHorizontal: true)
|
|
||||||
{
|
|
||||||
Stretch = true
|
|
||||||
};
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), enemySubmarineTagsGroup.RectTransform),
|
|
||||||
TextManager.Get("sp.item.tags.name"), textAlignment: Alignment.CenterLeft, wrap: true);
|
|
||||||
var tagsBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), enemySubmarineTagsGroup.RectTransform))
|
|
||||||
{
|
|
||||||
OnEnterPressed = ChangeEnemySubTags,
|
|
||||||
OverflowClip = true,
|
|
||||||
Text = "default"
|
|
||||||
};
|
|
||||||
tagsBox.OnDeselected += (textbox, _) => ChangeEnemySubTags(textbox, textbox.Text);
|
|
||||||
if (MainSub?.Info?.EnemySubmarineInfo?.MissionTags != null)
|
|
||||||
{
|
|
||||||
tagsBox.Text = string.Join(',', MainSub.Info.EnemySubmarineInfo.MissionTags);
|
|
||||||
}
|
|
||||||
|
|
||||||
enemySubmarineTagsGroup.RectTransform.MaxSize = tagsBox.RectTransform.MaxSize;
|
|
||||||
enemySubmarineSettingsContainer.RectTransform.MinSize = new Point(0, enemySubmarineSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
|
|
||||||
|
|
||||||
//--------------------------------------------------------
|
//--------------------------------------------------------
|
||||||
|
|
||||||
@@ -2870,7 +2884,6 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
beaconSettingsContainer.RectTransform.MinSize = new Point(0, beaconSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
|
|
||||||
|
|
||||||
//------------------------------------------------------------------
|
//------------------------------------------------------------------
|
||||||
|
|
||||||
@@ -3110,13 +3123,26 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
MainSub.Info.EnemySubmarineInfo ??= new EnemySubmarineInfo(MainSub.Info);
|
MainSub.Info.EnemySubmarineInfo ??= new EnemySubmarineInfo(MainSub.Info);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update mission tags UI when submarine type changes
|
||||||
|
var newExtraSubInfo = GetExtraSubmarineInfo(MainSub.Info);
|
||||||
|
if (newExtraSubInfo != null)
|
||||||
|
{
|
||||||
|
missionTagsBox.Text = string.Join(',', newExtraSubInfo.MissionTags);
|
||||||
|
}
|
||||||
|
|
||||||
previewImageButtonHolder.Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage);
|
previewImageButtonHolder.Children.ForEach(c => c.Enabled = MainSub.Info.AllowPreviewImage);
|
||||||
outpostModuleSettingsContainer.Visible = type == SubmarineType.OutpostModule;
|
outpostModuleSettingsContainer.Visible = type == SubmarineType.OutpostModule;
|
||||||
extraSettingsContainer.Visible = type == SubmarineType.BeaconStation || type == SubmarineType.Wreck;
|
extraSettingsContainer.Visible = newExtraSubInfo != null;
|
||||||
beaconSettingsContainer.Visible = type == SubmarineType.BeaconStation;
|
beaconSettingsContainer.Visible = type == SubmarineType.BeaconStation;
|
||||||
|
beaconSettingsContainer.IgnoreLayoutGroups = !beaconSettingsContainer.Visible;
|
||||||
enemySubmarineSettingsContainer.Visible = type == SubmarineType.EnemySubmarine;
|
enemySubmarineSettingsContainer.Visible = type == SubmarineType.EnemySubmarine;
|
||||||
|
enemySubmarineSettingsContainer.IgnoreLayoutGroups = !enemySubmarineSettingsContainer.Visible;
|
||||||
subSettingsContainer.Visible = type == SubmarineType.Player;
|
subSettingsContainer.Visible = type == SubmarineType.Player;
|
||||||
outpostSettingsContainer.Visible = type == SubmarineType.Outpost;
|
outpostSettingsContainer.Visible = type == SubmarineType.Outpost;
|
||||||
|
|
||||||
|
extraSettingsContainer.Recalculate();
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
subSettingsContainer.RectTransform.MinSize = new Point(0, subSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
|
subSettingsContainer.RectTransform.MinSize = new Point(0, subSettingsContainer.RectTransform.Children.Sum(c => c.Children.Any() ? c.Children.Max(c2 => c2.MinSize.Y) : 0));
|
||||||
@@ -3412,6 +3438,18 @@ namespace Barotrauma
|
|||||||
if (quickSave) { SaveSub(packageToSaveInList.SelectedData as ContentPackage); }
|
if (quickSave) { SaveSub(packageToSaveInList.SelectedData as ContentPackage); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static ExtraSubmarineInfo GetExtraSubmarineInfo(SubmarineInfo subInfo)
|
||||||
|
{
|
||||||
|
if (subInfo == null) { return null; }
|
||||||
|
return subInfo.Type switch
|
||||||
|
{
|
||||||
|
SubmarineType.BeaconStation => subInfo.BeaconStationInfo,
|
||||||
|
SubmarineType.Wreck => subInfo.WreckInfo,
|
||||||
|
SubmarineType.EnemySubmarine => subInfo.EnemySubmarineInfo,
|
||||||
|
_ => null,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private void CreateSaveAssemblyScreen()
|
private void CreateSaveAssemblyScreen()
|
||||||
{
|
{
|
||||||
SetMode(Mode.Default);
|
SetMode(Mode.Default);
|
||||||
@@ -4948,29 +4986,46 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool ChangeEnemySubTags(GUITextBox textBox, string text)
|
private bool ChangeMissionTags(GUITextBox textBox, string text)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(text))
|
// Get the ExtraSubmarineInfo (all types inherit MissionTags from the parent class)
|
||||||
{
|
var extraSubInfo = GetExtraSubmarineInfo(MainSub?.Info);
|
||||||
textBox.Flash(GUIStyle.Red);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (MainSub.Info.EnemySubmarineInfo is { } enemySubInfo)
|
if (extraSubInfo?.MissionTags != null)
|
||||||
{
|
{
|
||||||
enemySubInfo.MissionTags.Clear();
|
extraSubInfo.MissionTags.Clear();
|
||||||
string[] tags = text.Split(',');
|
string[] tags = text.Split(',');
|
||||||
foreach (string tag in tags)
|
foreach (string tag in tags)
|
||||||
{
|
{
|
||||||
enemySubInfo.MissionTags.Add(tag.ToIdentifier());
|
extraSubInfo.MissionTags.Add(tag.ToIdentifier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
textBox.Text = text;
|
textBox.Text = text;
|
||||||
textBox.Flash(GUIStyle.Green);
|
textBox.Flash(GUIStyle.Green);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static GUITextBox CreateMissionTagsUI(GUIComponent parent, IEnumerable<Identifier> missionTags, GUITextBox.OnEnterHandler onEnterPressed)
|
||||||
|
{
|
||||||
|
var missionTagsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), parent.RectTransform), isHorizontal: true)
|
||||||
|
{
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), missionTagsGroup.RectTransform),
|
||||||
|
TextManager.Get("subeditor.missiontags"), textAlignment: Alignment.CenterLeft, wrap: true);
|
||||||
|
var tagsBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), missionTagsGroup.RectTransform))
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("subeditor.missiontags.tooltip"),
|
||||||
|
OnEnterPressed = onEnterPressed,
|
||||||
|
OverflowClip = true,
|
||||||
|
Text = missionTags != null ? string.Join(',', missionTags) : ""
|
||||||
|
};
|
||||||
|
tagsBox.OnDeselected += (textbox, _) => onEnterPressed(textbox, textbox.Text);
|
||||||
|
missionTagsGroup.RectTransform.MaxSize = tagsBox.RectTransform.MaxSize;
|
||||||
|
return tagsBox;
|
||||||
|
}
|
||||||
|
|
||||||
private void ChangeSubDescription(GUITextBox textBox, string text)
|
private void ChangeSubDescription(GUITextBox textBox, string text)
|
||||||
{
|
{
|
||||||
if (MainSub != null)
|
if (MainSub != null)
|
||||||
@@ -5851,7 +5906,8 @@ namespace Barotrauma
|
|||||||
if (entity is Item item && item.Components.Any(ic => ic is not ConnectionPanel && ic is not Repairable && ic.GuiFrame != null))
|
if (entity is Item item && item.Components.Any(ic => ic is not ConnectionPanel && ic is not Repairable && ic.GuiFrame != null))
|
||||||
{
|
{
|
||||||
var container = item.GetComponents<ItemContainer>().ToList();
|
var container = item.GetComponents<ItemContainer>().ToList();
|
||||||
if (!container.Any() || container.Any(ic => ic?.DrawInventory ?? false))
|
if (container.None() || container.Any(ic => ic?.DrawInventory ?? false) ||
|
||||||
|
item.GetComponent<CircuitBox>() != null)
|
||||||
{
|
{
|
||||||
OpenItem(item);
|
OpenItem(item);
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -36,11 +36,18 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public bool IsFiltered(ServerInfo info)
|
public bool IsFiltered(ServerInfo info)
|
||||||
{
|
{
|
||||||
if (!Filters.Any()) { return false; }
|
if (Filters.IsEmpty) { return false; }
|
||||||
|
|
||||||
foreach (var (type, value) in Filters)
|
foreach (var (type, value) in Filters)
|
||||||
{
|
{
|
||||||
if (!IsFiltered(info, type, value)) { return false; }
|
try
|
||||||
|
{
|
||||||
|
if (!IsFiltered(info, type, value)) { return false; }
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Failed to check filter type {type} on the server info {(info.ServerName ?? "null")}.", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -63,7 +70,9 @@ namespace Barotrauma
|
|||||||
SpamServerFilterType.MessageEquals => CompareEquals(desc, value),
|
SpamServerFilterType.MessageEquals => CompareEquals(desc, value),
|
||||||
SpamServerFilterType.MessageContains => CompareContains(desc, value),
|
SpamServerFilterType.MessageContains => CompareContains(desc, value),
|
||||||
|
|
||||||
SpamServerFilterType.Endpoint => info.Endpoints.First().StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase),
|
SpamServerFilterType.Endpoint =>
|
||||||
|
info.Endpoints != null &&
|
||||||
|
info.Endpoints.First().StringRepresentation.Equals(value, StringComparison.OrdinalIgnoreCase),
|
||||||
|
|
||||||
SpamServerFilterType.PlayerCountLarger => info.PlayerCount > parsedInt,
|
SpamServerFilterType.PlayerCountLarger => info.PlayerCount > parsedInt,
|
||||||
SpamServerFilterType.PlayerCountExact => info.PlayerCount == parsedInt,
|
SpamServerFilterType.PlayerCountExact => info.PlayerCount == parsedInt,
|
||||||
@@ -79,10 +88,23 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
static bool CompareEquals(string a, string b)
|
static bool CompareEquals(string a, string b)
|
||||||
=> a.Equals(b, StringComparison.OrdinalIgnoreCase) || Homoglyphs.Compare(a, b);
|
{
|
||||||
|
if (a == null || b == null)
|
||||||
|
{
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
return a.Equals(b, StringComparison.OrdinalIgnoreCase) || Homoglyphs.Compare(a, b);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
static bool CompareContains(string a, string b)
|
static bool CompareContains(string a, string b)
|
||||||
=> a.Contains(b, StringComparison.OrdinalIgnoreCase);
|
{
|
||||||
|
if (a == null || b == null)
|
||||||
|
{
|
||||||
|
return a == b;
|
||||||
|
}
|
||||||
|
return a.Contains(b, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public XElement Serialize()
|
public XElement Serialize()
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal sealed class P2POwnerDoSProtection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Delegate to be called when a client has sent too many packets in a short time.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="endpoint">The endpoint of the client.</param>
|
||||||
|
/// <param name="shouldBan">A suggestion to ban the client due to too many kicks.</param>
|
||||||
|
public delegate void ExcessivePacketDelegate(P2PEndpoint endpoint, bool shouldBan);
|
||||||
|
|
||||||
|
private readonly Dictionary<P2PEndpoint, int> packetCounts = new();
|
||||||
|
private readonly Dictionary<P2PEndpoint, int> kicksByEndpoint = new();
|
||||||
|
|
||||||
|
private readonly ExcessivePacketDelegate onExcessivePackets;
|
||||||
|
private double nextCheckTime;
|
||||||
|
|
||||||
|
// check every 10 seconds
|
||||||
|
private const int PacketCheckTimer = 10;
|
||||||
|
|
||||||
|
public P2POwnerDoSProtection(ExcessivePacketDelegate onExcessivePackets)
|
||||||
|
{
|
||||||
|
this.onExcessivePackets = onExcessivePackets;
|
||||||
|
nextCheckTime = Timing.TotalTime + PacketCheckTimer;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int MaxPacketCount
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
// Normally the packet limit is per second, but we want to check faster than that.
|
||||||
|
// multiply by 1.2 to allow for some leeway to allow the DoS protection deeper in the stack
|
||||||
|
// to handle this first.
|
||||||
|
const float limitMultiplier = (PacketCheckTimer / 60f) * 1.2f;
|
||||||
|
|
||||||
|
if (GameMain.Client?.ServerSettings is not { } serverSettings)
|
||||||
|
{
|
||||||
|
// Shouldn't happen, but just in case.
|
||||||
|
return (int)MathF.Ceiling(ServerSettings.PacketLimitDefault * limitMultiplier);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)MathF.Ceiling(serverSettings.MaxPacketAmount * MathF.Max(serverSettings.TickRate / (float)ServerSettings.DefaultTickRate, 1f) * limitMultiplier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool ShouldCheck()
|
||||||
|
{
|
||||||
|
if (GameMain.Client?.ServerSettings is { } serverSettings)
|
||||||
|
{
|
||||||
|
return serverSettings.EnableDoSProtection && serverSettings.MaxPacketAmount > ServerSettings.PacketLimitMin;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnPacket(P2PEndpoint endpoint)
|
||||||
|
{
|
||||||
|
if (!ShouldCheck()) { return; }
|
||||||
|
|
||||||
|
// count = default(int), if the endpoint is not in the dictionary
|
||||||
|
packetCounts.TryGetValue(endpoint, out int count);
|
||||||
|
packetCounts[endpoint] = ++count;
|
||||||
|
|
||||||
|
if (Timing.TotalTime > nextCheckTime)
|
||||||
|
{
|
||||||
|
foreach (P2PEndpoint e in packetCounts.Keys.ToArray())
|
||||||
|
{
|
||||||
|
CheckForExcessivePackets(e, count);
|
||||||
|
}
|
||||||
|
packetCounts.Clear();
|
||||||
|
nextCheckTime = Timing.TotalTime + PacketCheckTimer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckForExcessivePackets(P2PEndpoint endpoint, int count)
|
||||||
|
{
|
||||||
|
if (count > MaxPacketCount)
|
||||||
|
{
|
||||||
|
kicksByEndpoint.TryGetValue(endpoint, out int kickCount);
|
||||||
|
kicksByEndpoint[endpoint] = ++kickCount;
|
||||||
|
|
||||||
|
onExcessivePackets(endpoint, kickCount > 3);
|
||||||
|
|
||||||
|
packetCounts.Remove(endpoint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.9.8.0</Version>
|
<Version>1.10.5.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.9.8.0</Version>
|
<Version>1.10.5.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.9.8.0</Version>
|
<Version>1.10.5.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2024</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.9.8.0</Version>
|
<Version>1.10.5.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.9.8.0</Version>
|
<Version>1.10.5.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
@@ -1850,6 +1850,10 @@ namespace Barotrauma
|
|||||||
HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode, client);
|
HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode, client);
|
||||||
void ToggleGodMode(Character targetCharacter)
|
void ToggleGodMode(Character targetCharacter)
|
||||||
{
|
{
|
||||||
|
if (args.Length > 1 && bool.TryParse(args[1], out bool removeafflictions))
|
||||||
|
{
|
||||||
|
if (removeafflictions) { targetCharacter.CharacterHealth.RemoveAllAfflictions(); }
|
||||||
|
}
|
||||||
targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode;
|
targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode;
|
||||||
godmodeStateOnFirstCharacter = targetCharacter.GodMode;
|
godmodeStateOnFirstCharacter = targetCharacter.GodMode;
|
||||||
GameMain.NetworkMember.CreateEntityEvent(targetCharacter, new Character.CharacterStatusEventData());
|
GameMain.NetworkMember.CreateEntityEvent(targetCharacter, new Character.CharacterStatusEventData());
|
||||||
|
|||||||
@@ -1530,9 +1530,12 @@ namespace Barotrauma
|
|||||||
modeElement.Add(GameMain.GameSession?.EventManager.Save());
|
modeElement.Add(GameMain.GameSession?.EventManager.Save());
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Identifier unlockedRecipe in GameMain.GameSession.UnlockedRecipes)
|
foreach ((CharacterTeamType team, Identifier unlockedRecipe) in GameMain.GameSession.UnlockedRecipes)
|
||||||
{
|
{
|
||||||
modeElement.Add(new XElement("unlockedrecipe", new XAttribute("identifier", unlockedRecipe)));
|
modeElement.Add(
|
||||||
|
new XElement("unlockedrecipe",
|
||||||
|
new XAttribute("identifier", unlockedRecipe),
|
||||||
|
new XAttribute("team", team)));
|
||||||
}
|
}
|
||||||
|
|
||||||
CampaignMetadata?.Save(modeElement);
|
CampaignMetadata?.Save(modeElement);
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components
|
|||||||
const float NetworkUpdateInterval = 5.0f;
|
const float NetworkUpdateInterval = 5.0f;
|
||||||
private float networkUpdateTimer;
|
private float networkUpdateTimer;
|
||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime)
|
partial void UpdateNetworking(float deltaTime)
|
||||||
{
|
{
|
||||||
networkUpdateTimer -= deltaTime;
|
networkUpdateTimer -= deltaTime;
|
||||||
if (networkUpdateTimer <= 0.0f)
|
if (networkUpdateTimer <= 0.0f)
|
||||||
@@ -51,6 +51,7 @@ namespace Barotrauma.Items.Components
|
|||||||
msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10);
|
msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10);
|
||||||
msg.WriteBoolean(IsActive);
|
msg.WriteBoolean(IsActive);
|
||||||
msg.WriteBoolean(Hijacked);
|
msg.WriteBoolean(Hijacked);
|
||||||
|
msg.WriteBoolean(Disabled);
|
||||||
if (TargetLevel != null)
|
if (TargetLevel != null)
|
||||||
{
|
{
|
||||||
msg.WriteBoolean(true);
|
msg.WriteBoolean(true);
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class WifiComponent
|
partial class WifiComponent
|
||||||
{
|
{
|
||||||
|
private readonly int[] networkReceivedChannelMemory = new int[ChannelMemorySize];
|
||||||
|
|
||||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||||
{
|
{
|
||||||
SharedEventWrite(msg);
|
SharedEventWrite(msg);
|
||||||
@@ -11,8 +13,21 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public void ServerEventRead(IReadMessage msg, Client c)
|
public void ServerEventRead(IReadMessage msg, Client c)
|
||||||
{
|
{
|
||||||
SharedEventRead(msg);
|
int newChannel = msg.ReadRangedInteger(MinChannel, MaxChannel);
|
||||||
|
for (int i = 0; i < ChannelMemorySize; i++)
|
||||||
|
{
|
||||||
|
networkReceivedChannelMemory[i] = msg.ReadRangedInteger(MinChannel, MaxChannel);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item.CanClientAccess(c))
|
||||||
|
{
|
||||||
|
Channel = newChannel;
|
||||||
|
for (int i = 0; i < ChannelMemorySize; i++)
|
||||||
|
{
|
||||||
|
channelMemory[i] = networkReceivedChannelMemory[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create an event to notify other clients about the changes
|
// Create an event to notify other clients about the changes
|
||||||
item.CreateServerEvent(this);
|
item.CreateServerEvent(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ namespace Barotrauma
|
|||||||
ServerLogRemovedItems();
|
ServerLogRemovedItems();
|
||||||
|
|
||||||
#region local functions
|
#region local functions
|
||||||
bool IsInventoryAccessible() => sender.Character.CanAccessInventory(this, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.Allowed : CharacterInventory.AccessLevel.Limited);
|
bool IsInventoryAccessible() => sender.Character.CanAccessInventory(this, IsDragAndDropGiveAllowed ? CharacterInventory.AccessLevel.AllowFriendly : CharacterInventory.AccessLevel.AllowBotsAndPets);
|
||||||
|
|
||||||
void CreateCorrectiveNetworkEvent()
|
void CreateCorrectiveNetworkEvent()
|
||||||
{
|
{
|
||||||
@@ -177,7 +177,7 @@ namespace Barotrauma
|
|||||||
if (item.GetComponent<Pickable>() is not Pickable pickable ||
|
if (item.GetComponent<Pickable>() is not Pickable pickable ||
|
||||||
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(sender.Character))
|
(pickable.IsAttached && !pickable.PickingDone) || item.AllowedSlots.None() || !item.IsInteractable(sender.Character))
|
||||||
{
|
{
|
||||||
DebugConsole.AddWarning($"Client {sender.Name} tried to pick up a non-pickable item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
|
DebugConsole.AddWarning($"Client {sender.Name} failed to put \"{item}\" in the inventory of {Owner} (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"})",
|
||||||
item.Prefab.ContentPackage);
|
item.Prefab.ContentPackage);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -187,13 +187,20 @@ namespace Barotrauma
|
|||||||
var holdable = item.GetComponent<Holdable>();
|
var holdable = item.GetComponent<Holdable>();
|
||||||
if (holdable != null && !holdable.CanBeDeattached()) { continue; }
|
if (holdable != null && !holdable.CanBeDeattached()) { continue; }
|
||||||
|
|
||||||
|
|
||||||
bool itemAccessDenied = !prevItems.Contains(item) && !itemAccessibility[item] &&
|
bool itemAccessDenied = !prevItems.Contains(item) && !itemAccessibility[item] &&
|
||||||
(sender.Character == null || item.PreviousParentInventory == null || !sender.Character.CanAccessInventory(item.PreviousParentInventory));
|
(sender.Character == null || item.PreviousParentInventory == null || !sender.Character.CanAccessInventory(item.PreviousParentInventory));
|
||||||
|
|
||||||
|
//more restricted "adding" of handcuffs: we can't allow putting handcuffs on a player just because dragging and dropping is allowed
|
||||||
|
if (item.HasTag(Tags.HandLockerItem) && !itemAccessDenied)
|
||||||
|
{
|
||||||
|
itemAccessDenied =
|
||||||
|
!sender.Character.CanAccessInventory(this, CharacterInventory.AccessLevel.AllowBotsAndPets);
|
||||||
|
}
|
||||||
if (itemAccessDenied)
|
if (itemAccessDenied)
|
||||||
{
|
{
|
||||||
#if DEBUG || UNSTABLE
|
#if DEBUG || UNSTABLE
|
||||||
DebugConsole.NewMessage($"Client {sender.Name} failed to pick up item \"{item}\" (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
DebugConsole.NewMessage($"Client {sender.Name} failed to put \"{item}\" in the inventory of {Owner} (parent inventory: {item.ParentInventory?.Owner.ToString() ?? "null"}). No access.", Color.Yellow);
|
||||||
#endif
|
#endif
|
||||||
if (item.body != null && !sender.PendingPositionUpdates.Contains(item))
|
if (item.body != null && !sender.PendingPositionUpdates.Contains(item))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3716,7 +3716,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
UpdateVoteStatus();
|
UpdateVoteStatus();
|
||||||
|
|
||||||
SendChatMessage(peerDisconnectPacket.ChatMessage(client).Value, ChatMessageType.Server, changeType: peerDisconnectPacket.ConnectionChangeType);
|
SendChatMessage(peerDisconnectPacket.ChatMessage(client.Name).Value, ChatMessageType.Server, changeType: peerDisconnectPacket.ConnectionChangeType);
|
||||||
|
|
||||||
UpdateCrewFrame();
|
UpdateCrewFrame();
|
||||||
|
|
||||||
@@ -4234,13 +4234,14 @@ namespace Barotrauma.Networking
|
|||||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnlockRecipe(Identifier identifier)
|
public void UnlockRecipe(CharacterTeamType team, Identifier identifier)
|
||||||
{
|
{
|
||||||
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
|
msg.WriteByte((byte)ServerPacketHeader.UNLOCKRECIPE);
|
||||||
|
msg.WriteByte((byte)team);
|
||||||
|
msg.WriteIdentifier(identifier);
|
||||||
foreach (var client in connectedClients)
|
foreach (var client in connectedClients)
|
||||||
{
|
{
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
|
||||||
msg.WriteByte((byte)ServerPacketHeader.UNLOCKRECIPE);
|
|
||||||
msg.WriteIdentifier(identifier);
|
|
||||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,6 +137,10 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
Disconnect(connectedClient.Connection, PeerDisconnectPacket.Banned(banReason));
|
Disconnect(connectedClient.Connection, PeerDisconnectPacket.Banned(banReason));
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SendDisconnectMessage(senderEndpoint, PeerDisconnectPacket.Banned(banReason));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (packetHeader.IsDisconnectMessage())
|
else if (packetHeader.IsDisconnectMessage())
|
||||||
{
|
{
|
||||||
@@ -149,9 +153,10 @@ namespace Barotrauma.Networking
|
|||||||
Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
Disconnect(connectedClient.Connection, PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (packetHeader.IsHeartbeatMessage())
|
else if (packetHeader.IsHeartbeatMessage() || packetHeader.IsDoSProtectionMessage())
|
||||||
{
|
{
|
||||||
//message exists solely as a heartbeat, ignore its contents
|
// ignore these messages, heartbeat messages just need to be acknowledged,
|
||||||
|
// and only the owner should be sending DoS protection messages
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (packetHeader.IsConnectionInitializationStep())
|
else if (packetHeader.IsConnectionInitializationStep())
|
||||||
@@ -206,6 +211,49 @@ namespace Barotrauma.Networking
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (packetHeader.IsDoSProtectionMessage())
|
||||||
|
{
|
||||||
|
var packet = INetSerializableStruct.Read<DoSProtectionPacket>(inc);
|
||||||
|
var disconnectPacket = INetSerializableStruct.Read<PeerDisconnectPacket>(inc);
|
||||||
|
|
||||||
|
if (packet.Endpoint.TryUnwrap(out var endpoint))
|
||||||
|
{
|
||||||
|
PendingClient? pendingClient = pendingClients.Find(c => c.Connection.Endpoint == endpoint);
|
||||||
|
ClientConnectionData? connectedClientData = connectedClients.Find(c => c.Connection.Endpoint == endpoint);
|
||||||
|
string? clientName;
|
||||||
|
|
||||||
|
if (pendingClient != null)
|
||||||
|
{
|
||||||
|
clientName = pendingClient.Name;
|
||||||
|
if (packet.ShouldBan)
|
||||||
|
{
|
||||||
|
BanPendingClient(pendingClient, disconnectPacket.AdditionalInformation, duration: null);
|
||||||
|
}
|
||||||
|
RemovePendingClient(pendingClient, disconnectPacket);
|
||||||
|
}
|
||||||
|
else if (connectedClientData != null)
|
||||||
|
{
|
||||||
|
clientName = connectedClientData.TryGetClientName();
|
||||||
|
if (packet.ShouldBan)
|
||||||
|
{
|
||||||
|
connectedClientData.BanClient(serverSettings, disconnectPacket.AdditionalInformation, duration: null);
|
||||||
|
}
|
||||||
|
Disconnect(connectedClientData.Connection, disconnectPacket);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
string errorMsg = $"Unable to remove client {endpoint} for triggering DoS protection, client not found in pending or connected clients";
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
GameServer.Log(errorMsg, ServerLog.MessageType.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameServer.Log($"Client {clientName ?? endpoint.ToString()} {(packet.ShouldBan ? "banned" : "disconnected")} due to DoS protection (Sending too many packets).", ServerLog.MessageType.DoSProtection);
|
||||||
|
GameMain.Server?.SendChatMessage(disconnectPacket.ChatMessage(clientName).Value, ChatMessageType.Server, changeType: disconnectPacket.ConnectionChangeType);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (packetHeader.IsConnectionInitializationStep())
|
if (packetHeader.IsConnectionInitializationStep())
|
||||||
{
|
{
|
||||||
if (OwnerConnection is null)
|
if (OwnerConnection is null)
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace Barotrauma.Networking
|
|||||||
protected ServerPeer(Callbacks callbacks, ServerSettings serverSettings) : base(callbacks)
|
protected ServerPeer(Callbacks callbacks, ServerSettings serverSettings) : base(callbacks)
|
||||||
{
|
{
|
||||||
this.serverSettings = serverSettings;
|
this.serverSettings = serverSettings;
|
||||||
this.connectedClients = new List<ConnectedClient>();
|
this.connectedClients = new List<ClientConnectionData>();
|
||||||
this.pendingClients = new List<PendingClient>();
|
this.pendingClients = new List<PendingClient>();
|
||||||
|
|
||||||
List<ContentPackage> contentPackageList = new List<ContentPackage>();
|
List<ContentPackage> contentPackageList = new List<ContentPackage>();
|
||||||
@@ -65,21 +65,57 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected sealed class ConnectedClient
|
protected sealed class ClientConnectionData(TConnection connection)
|
||||||
{
|
{
|
||||||
public readonly TConnection Connection;
|
public readonly TConnection Connection = connection;
|
||||||
public readonly MessageFragmenter Fragmenter;
|
public readonly MessageFragmenter Fragmenter = new();
|
||||||
public readonly MessageDefragmenter Defragmenter;
|
public readonly MessageDefragmenter Defragmenter = new();
|
||||||
|
|
||||||
public ConnectedClient(TConnection connection)
|
/// <summary>
|
||||||
|
/// Attempts to retrieve the name of the client associated with this connection
|
||||||
|
/// from a higher layer.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>Name of the client if found, null otherwise.</returns>
|
||||||
|
public string? TryGetClientName()
|
||||||
{
|
{
|
||||||
Connection = connection;
|
if (GameMain.Server?.ConnectedClients is { } connClients)
|
||||||
Fragmenter = new();
|
{
|
||||||
Defragmenter = new();
|
foreach (Client? client in connClients)
|
||||||
|
{
|
||||||
|
if (client?.Connection is not { } clientConnection) { continue; }
|
||||||
|
|
||||||
|
if (clientConnection.EndpointMatches(Connection.Endpoint) )
|
||||||
|
{
|
||||||
|
return client.Name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void BanClient(ServerSettings settings, string banReason, TimeSpan? duration)
|
||||||
|
{
|
||||||
|
string clientName = TryGetClientName() ?? "Player";
|
||||||
|
|
||||||
|
Connection.AccountInfo.OtherMatchingIds.ForEach(BanAccountId);
|
||||||
|
|
||||||
|
if (Connection.AccountInfo.AccountId.TryUnwrap(out var accountId))
|
||||||
|
{
|
||||||
|
BanAccountId(accountId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
settings.BanList.BanPlayer(clientName, Connection.Endpoint, banReason, duration);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
|
||||||
|
void BanAccountId(AccountId id)
|
||||||
|
=> settings.BanList.BanPlayer(clientName, id, banReason, duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected readonly List<ConnectedClient> connectedClients;
|
protected readonly List<ClientConnectionData> connectedClients;
|
||||||
protected readonly List<PendingClient> pendingClients;
|
protected readonly List<PendingClient> pendingClients;
|
||||||
protected readonly ServerSettings serverSettings;
|
protected readonly ServerSettings serverSettings;
|
||||||
|
|
||||||
@@ -235,7 +271,7 @@ namespace Barotrauma.Networking
|
|||||||
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
|
if (pendingClient.InitializationStep == ConnectionInitialization.Success)
|
||||||
{
|
{
|
||||||
TConnection newConnection = pendingClient.Connection;
|
TConnection newConnection = pendingClient.Connection;
|
||||||
connectedClients.Add(new ConnectedClient(newConnection));
|
connectedClients.Add(new ClientConnectionData(newConnection));
|
||||||
pendingClients.Remove(pendingClient);
|
pendingClients.Remove(pendingClient);
|
||||||
|
|
||||||
callbacks.OnInitializationComplete.Invoke(newConnection, pendingClient.Name);
|
callbacks.OnInitializationComplete.Invoke(newConnection, pendingClient.Name);
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.9.8.0</Version>
|
<Version>1.10.5.0</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
|
|||||||
Binary file not shown.
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="[DebugOnlyTest]PipeTestSub" modversion="1.0.0" corepackage="False" gameversion="1.8.4.2">
|
||||||
|
<Submarine file="%ModDir%/Dugong_PipeTest.sub" />
|
||||||
|
</contentpackage>
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<contentpackage name="[DebugOnlyTest]testGapSub" modversion="1.0.3" corepackage="False" gameversion="1.9.5.0">
|
||||||
|
<Submarine file="%ModDir%/testGapSub.sub" />
|
||||||
|
</contentpackage>
|
||||||
Binary file not shown.
@@ -6,7 +6,6 @@
|
|||||||
<Workshop name="Meaningful Upgrades" id="2183524355" />
|
<Workshop name="Meaningful Upgrades" id="2183524355" />
|
||||||
<Workshop name="Community Conversation Pack" id="2435017882" />
|
<Workshop name="Community Conversation Pack" id="2435017882" />
|
||||||
<Workshop name="Stations from beyond" id="2585543390" />
|
<Workshop name="Stations from beyond" id="2585543390" />
|
||||||
<Workshop name="FrithsMissionTweak" id="2788861460" />
|
|
||||||
<Workshop name="New Wrecks For Barotrauma (With sellable wrecks)" id="2184257427" />
|
<Workshop name="New Wrecks For Barotrauma (With sellable wrecks)" id="2184257427" />
|
||||||
<Workshop name="DynamicEuropa" id="2532991202" />
|
<Workshop name="DynamicEuropa" id="2532991202" />
|
||||||
<Workshop name="32x Stack" id="2683570256" />
|
<Workshop name="32x Stack" id="2683570256" />
|
||||||
|
|||||||
@@ -312,9 +312,10 @@ namespace Barotrauma
|
|||||||
if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; }
|
if (!slots.HasFlag(characterInventory.SlotTypes[i])) { continue; }
|
||||||
}
|
}
|
||||||
targetSlot = i;
|
targetSlot = i;
|
||||||
//slot free, continue
|
|
||||||
var otherItem = targetInventory.GetItemAt(i);
|
var otherItem = targetInventory.GetItemAt(i);
|
||||||
|
//slot free, continue
|
||||||
if (otherItem == null) { continue; }
|
if (otherItem == null) { continue; }
|
||||||
|
if (!otherItem.IsInteractable(Character)) { return false; }
|
||||||
//try to move the existing item to LimbSlot.Any and continue if successful
|
//try to move the existing item to LimbSlot.Any and continue if successful
|
||||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.AnySlot))
|
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.AnySlot))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1092,6 +1092,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (!isFleeing)
|
if (!isFleeing)
|
||||||
{
|
{
|
||||||
|
CheckForDraggedCorpses();
|
||||||
|
|
||||||
foreach (Character target in Character.CharacterList)
|
foreach (Character target in Character.CharacterList)
|
||||||
{
|
{
|
||||||
if (target.CurrentHull != hull) { continue; }
|
if (target.CurrentHull != hull) { continue; }
|
||||||
@@ -1209,6 +1211,34 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void CheckForDraggedCorpses()
|
||||||
|
{
|
||||||
|
if (Character.IsOnPlayerTeam) { return; }
|
||||||
|
if (Character.Submarine is not { Info.IsOutpost: true }) { return; }
|
||||||
|
|
||||||
|
//find corpses in the same team
|
||||||
|
foreach (Character otherCharacter in Character.CharacterList)
|
||||||
|
{
|
||||||
|
if (otherCharacter.SelectedCharacter == null ||
|
||||||
|
!otherCharacter.SelectedCharacter.IsDead ||
|
||||||
|
otherCharacter.SelectedCharacter.TeamID != Character.TeamID ||
|
||||||
|
otherCharacter.IsInstigator)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Character.CanSeeTarget(otherCharacter)) { continue; }
|
||||||
|
|
||||||
|
// Player is dragging a corpse from our team
|
||||||
|
string dialogTag = Character.IsSecurity ? "dialogdraggingcorpsereactionsecurity" : "dialogdraggingcorpsereaction";
|
||||||
|
Character.Speak(TextManager.Get(dialogTag).Value, messageType: null,
|
||||||
|
delay: Rand.Range(0.5f, 1.0f), identifier: "dialogdraggingcorpsereaction".ToIdentifier(), minDurationBetweenSimilar: 10.0f);
|
||||||
|
|
||||||
|
AddCombatObjective(Character.IsSecurity ? AIObjectiveCombat.CombatMode.Arrest : AIObjectiveCombat.CombatMode.Retreat, otherCharacter);
|
||||||
|
break; // Only react to one at a time
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public override void OnHealed(Character healer, float healAmount)
|
public override void OnHealed(Character healer, float healAmount)
|
||||||
{
|
{
|
||||||
@@ -1660,6 +1690,10 @@ namespace Barotrauma
|
|||||||
public AIObjective SetForcedOrder(Order order)
|
public AIObjective SetForcedOrder(Order order)
|
||||||
{
|
{
|
||||||
var objective = ObjectiveManager.CreateObjective(order);
|
var objective = ObjectiveManager.CreateObjective(order);
|
||||||
|
if (order != null && !order.IsDismissal)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.Assert(objective != null);
|
||||||
|
}
|
||||||
ObjectiveManager.SetForcedOrder(objective);
|
ObjectiveManager.SetForcedOrder(objective);
|
||||||
return objective;
|
return objective;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ namespace Barotrauma
|
|||||||
private static List<Identifier> GetCurrentFlags(Character speaker)
|
private static List<Identifier> GetCurrentFlags(Character speaker)
|
||||||
{
|
{
|
||||||
var currentFlags = new List<Identifier>();
|
var currentFlags = new List<Identifier>();
|
||||||
if (Submarine.MainSub != null && Submarine.MainSub.AtDamageDepth) { currentFlags.Add("SubmarineDeep".ToIdentifier()); }
|
if (Submarine.MainSub != null && Submarine.MainSub.AtCosmeticDamageDepth) { currentFlags.Add("SubmarineDeep".ToIdentifier()); }
|
||||||
|
|
||||||
if (GameMain.GameSession != null && Level.Loaded != null)
|
if (GameMain.GameSession != null && Level.Loaded != null)
|
||||||
{
|
{
|
||||||
@@ -84,7 +84,6 @@ namespace Barotrauma
|
|||||||
if (GameMain.GameSession.RoundDuration < 120.0f &&
|
if (GameMain.GameSession.RoundDuration < 120.0f &&
|
||||||
speaker?.CurrentHull != null &&
|
speaker?.CurrentHull != null &&
|
||||||
GameMain.GameSession.Map?.CurrentLocation?.Reputation?.Value >= 0.0f &&
|
GameMain.GameSession.Map?.CurrentLocation?.Reputation?.Value >= 0.0f &&
|
||||||
(speaker.TeamID == CharacterTeamType.FriendlyNPC || speaker.TeamID == CharacterTeamType.None) &&
|
|
||||||
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
Character.CharacterList.Any(c => c.TeamID != speaker.TeamID && c.CurrentHull == speaker.CurrentHull))
|
||||||
{
|
{
|
||||||
currentFlags.Add("EnterOutpost".ToIdentifier());
|
currentFlags.Add("EnterOutpost".ToIdentifier());
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ namespace Barotrauma
|
|||||||
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true, bool requireValidContainer = true, bool ignoreItemsMarkedForDeconstruction = true)
|
public static bool IsValidTarget(Item item, Character character, bool checkInventory, bool allowUnloading = true, bool requireValidContainer = true, bool ignoreItemsMarkedForDeconstruction = true)
|
||||||
{
|
{
|
||||||
if (item == null) { return false; }
|
if (item == null) { return false; }
|
||||||
|
if (item.GetComponents<Pickable>().None(c => c is not Door && c.CanBePicked)) { return false; }
|
||||||
if (item.DontCleanUp) { return false; }
|
if (item.DontCleanUp) { return false; }
|
||||||
if (item.Illegitimate == character.IsOnPlayerTeam) { return false; }
|
if (item.Illegitimate == character.IsOnPlayerTeam) { return false; }
|
||||||
if (item.ParentInventory != null)
|
if (item.ParentInventory != null)
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly AIObjectiveFindSafety findSafety;
|
private readonly AIObjectiveFindSafety findSafety;
|
||||||
private readonly HashSet<ItemComponent> weapons = new HashSet<ItemComponent>();
|
private readonly HashSet<ItemComponent> weapons = new HashSet<ItemComponent>();
|
||||||
private readonly HashSet<Item> ignoredWeapons = new HashSet<Item>();
|
private readonly HashSet<ItemComponent> ignoredWeapons = new HashSet<ItemComponent>();
|
||||||
|
|
||||||
private AIObjectiveContainItem seekAmmunitionObjective;
|
private AIObjectiveContainItem seekAmmunitionObjective;
|
||||||
private AIObjectiveGoTo retreatObjective;
|
private AIObjectiveGoTo retreatObjective;
|
||||||
@@ -503,7 +503,8 @@ namespace Barotrauma
|
|||||||
HashSet<ItemComponent> allWeapons = FindWeaponsFromInventory();
|
HashSet<ItemComponent> allWeapons = FindWeaponsFromInventory();
|
||||||
while (allWeapons.Any())
|
while (allWeapons.Any())
|
||||||
{
|
{
|
||||||
Weapon = GetWeapon(allWeapons, out _weaponComponent);
|
Weapon = GetWeapon(allWeapons, out ItemComponent newWeaponComponent);
|
||||||
|
_weaponComponent = newWeaponComponent;
|
||||||
if (Weapon == null)
|
if (Weapon == null)
|
||||||
{
|
{
|
||||||
// No weapons
|
// No weapons
|
||||||
@@ -512,7 +513,7 @@ namespace Barotrauma
|
|||||||
if (!character.Inventory.Contains(Weapon) || WeaponComponent == null)
|
if (!character.Inventory.Contains(Weapon) || WeaponComponent == null)
|
||||||
{
|
{
|
||||||
// Not in the inventory anymore or cannot find the weapon component
|
// Not in the inventory anymore or cannot find the weapon component
|
||||||
allWeapons.Remove(WeaponComponent);
|
allWeapons.RemoveWhere(weaponComponent => weaponComponent.Item == Weapon);
|
||||||
Weapon = null;
|
Weapon = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -540,7 +541,7 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// No ammo and should not try to seek ammo.
|
// No ammo and should not try to seek ammo.
|
||||||
allWeapons.Remove(WeaponComponent);
|
allWeapons.RemoveWhere(weaponComponent => weaponComponent.Item == Weapon);
|
||||||
Weapon = null;
|
Weapon = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -980,7 +981,6 @@ namespace Barotrauma
|
|||||||
weapons.Clear();
|
weapons.Clear();
|
||||||
foreach (var item in character.Inventory.AllItems)
|
foreach (var item in character.Inventory.AllItems)
|
||||||
{
|
{
|
||||||
if (ignoredWeapons.Contains(item)) { continue; }
|
|
||||||
GetWeapons(item, weapons);
|
GetWeapons(item, weapons);
|
||||||
if (item.OwnInventory != null)
|
if (item.OwnInventory != null)
|
||||||
{
|
{
|
||||||
@@ -990,11 +990,12 @@ namespace Barotrauma
|
|||||||
return weapons;
|
return weapons;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetWeapons(Item item, ICollection<ItemComponent> weaponList)
|
private void GetWeapons(Item item, ICollection<ItemComponent> weaponList)
|
||||||
{
|
{
|
||||||
if (item == null) { return; }
|
if (item == null) { return; }
|
||||||
foreach (var component in item.Components)
|
foreach (var component in item.Components)
|
||||||
{
|
{
|
||||||
|
if (ignoredWeapons.Contains(component)) { continue; }
|
||||||
if (component.CombatPriority > 0)
|
if (component.CombatPriority > 0)
|
||||||
{
|
{
|
||||||
weaponList.Add(component);
|
weaponList.Add(component);
|
||||||
@@ -1332,19 +1333,21 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
SteeringManager.Reset();
|
SteeringManager.Reset();
|
||||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||||
ignoredWeapons.Add(Weapon);
|
ignoredWeapons.Add(WeaponComponent);
|
||||||
Weapon = null;
|
Weapon = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Reloads the ammunition found in the inventory.
|
/// Reloads the ammunition found in the inventory.
|
||||||
/// If seekAmmo is true, tries to get find the ammo elsewhere.
|
/// If seekAmmo is true, tries to get find the ammo elsewhere.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <returns>True if the weapon was reloaded successfully.</returns>
|
||||||
private bool Reload(bool seekAmmo)
|
private bool Reload(bool seekAmmo)
|
||||||
{
|
{
|
||||||
if (WeaponComponent == null) { return false; }
|
if (WeaponComponent == null) { return false; }
|
||||||
if (Weapon.OwnInventory == null) { return true; }
|
if (Weapon.OwnInventory == null) { return true; }
|
||||||
|
if (!Weapon.IsInteractable(character)) { return false; }
|
||||||
HumanAIController.UnequipEmptyItems(Weapon, allowDestroying: !character.IsOnPlayerTeam);
|
HumanAIController.UnequipEmptyItems(Weapon, allowDestroying: !character.IsOnPlayerTeam);
|
||||||
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
||||||
if (WeaponComponent.RequiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
if (WeaponComponent.RequiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||||
|
|||||||
@@ -443,6 +443,10 @@ namespace Barotrauma
|
|||||||
newCurrentObjective.Abandoned += () => DismissSelf(order);
|
newCurrentObjective.Abandoned += () => DismissSelf(order);
|
||||||
CurrentOrders.Add(order.WithObjective(newCurrentObjective));
|
CurrentOrders.Add(order.WithObjective(newCurrentObjective));
|
||||||
}
|
}
|
||||||
|
else if (!order.IsDismissal)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Failed to create an objective for the order: {order.Name}");
|
||||||
|
}
|
||||||
if (!HasOrders())
|
if (!HasOrders())
|
||||||
{
|
{
|
||||||
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
// Recreate objectives, because some of them may be removed, if impossible to complete (e.g. due to path finding)
|
||||||
@@ -458,6 +462,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates an AI objective based on the order. Note that the method can return null in the case of e.g. Dismissal orders or orders that erroneously target something non-interactable.
|
||||||
|
/// </summary>
|
||||||
public AIObjective CreateObjective(Order order, float priorityModifier = 1)
|
public AIObjective CreateObjective(Order order, float priorityModifier = 1)
|
||||||
{
|
{
|
||||||
if (order == null || order.IsDismissal) { return null; }
|
if (order == null || order.IsDismissal) { return null; }
|
||||||
|
|||||||
@@ -341,6 +341,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (component?.GetType() is Type componentType)
|
if (component?.GetType() is Type componentType)
|
||||||
{
|
{
|
||||||
|
// Items used via a controller (i.e. turrets) are not selectable but should still be valid targets.
|
||||||
|
if (!UseController && !component.CanBeSelected) { continue; }
|
||||||
if (componentType == ItemComponentType) { return component; }
|
if (componentType == ItemComponentType) { return component; }
|
||||||
if (CanTypeBeSubclass && componentType.IsSubclassOf(ItemComponentType)) { return component; }
|
if (CanTypeBeSubclass && componentType.IsSubclassOf(ItemComponentType)) { return component; }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using Microsoft.Xna.Framework;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using static Barotrauma.CharacterParams;
|
using static Barotrauma.CharacterParams;
|
||||||
|
|
||||||
@@ -434,6 +435,21 @@ namespace Barotrauma
|
|||||||
var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior;
|
var petBehavior = (c.AIController as EnemyAIController)?.PetBehavior;
|
||||||
if (petBehavior == null) { continue; }
|
if (petBehavior == null) { continue; }
|
||||||
|
|
||||||
|
//never save hostile pets or pets left outside
|
||||||
|
if (c.TeamID == CharacterTeamType.None ||
|
||||||
|
c.TeamID == CharacterTeamType.Team2 ||
|
||||||
|
c.Submarine == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
//pets must be in a player sub or owned by someone to be persistent
|
||||||
|
if (c.Submarine is not { Info.IsPlayer: true } &&
|
||||||
|
petBehavior.Owner is not { IsOnPlayerTeam: true })
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
XElement petElement = new XElement("pet",
|
XElement petElement = new XElement("pet",
|
||||||
new XAttribute("speciesname", c.SpeciesName),
|
new XAttribute("speciesname", c.SpeciesName),
|
||||||
new XAttribute("ownerhash", petBehavior.Owner?.Info?.GetIdentifier() ?? 0),
|
new XAttribute("ownerhash", petBehavior.Owner?.Info?.GetIdentifier() ?? 0),
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
enemyAi.PetBehavior?.Update(deltaTime);
|
enemyAi.PetBehavior?.Update(deltaTime);
|
||||||
}
|
}
|
||||||
if (IsDead || Vitality <= 0.0f || Stun > 0.0f || IsIncapacitated)
|
if (IsDead || IsUnconscious || Stun > 0.0f || IsIncapacitated)
|
||||||
{
|
{
|
||||||
//don't enable simple physics on dead/incapacitated characters
|
//don't enable simple physics on dead/incapacitated characters
|
||||||
//the ragdoll controls the movement of incapacitated characters instead of the collider,
|
//the ragdoll controls the movement of incapacitated characters instead of the collider,
|
||||||
|
|||||||
@@ -8,8 +8,15 @@ using Barotrauma.Extensions;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
abstract class AnimController : Ragdoll
|
abstract class AnimController : Ragdoll, ISerializableEntity
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Most of the properties in this class are read-only, but can be useful for conditionals
|
||||||
|
/// </summary>
|
||||||
|
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
|
||||||
|
|
||||||
|
public string Name => nameof(AnimController);
|
||||||
|
|
||||||
public Vector2 RightHandIKPos { get; protected set; }
|
public Vector2 RightHandIKPos { get; protected set; }
|
||||||
public Vector2 LeftHandIKPos { get; protected set; }
|
public Vector2 LeftHandIKPos { get; protected set; }
|
||||||
|
|
||||||
@@ -200,7 +207,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public float WalkPos { get; protected set; }
|
public float WalkPos { get; protected set; }
|
||||||
|
|
||||||
public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams) { }
|
public AnimController(Character character, string seed, RagdollParams ragdollParams = null) : base(character, seed, ragdollParams)
|
||||||
|
{
|
||||||
|
SerializableProperties = SerializableProperty.GetProperties(this);
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateAnimations(float deltaTime)
|
public void UpdateAnimations(float deltaTime)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2411,7 +2411,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (Inventory != null)
|
if (Inventory != null)
|
||||||
{
|
{
|
||||||
if (IsKeyHit(InputType.DropItem) && Screen.Selected is { IsEditor: false })
|
//this doesn't need to be run by the server, clients sync the contents of their inventory with the server instead of the inputs used to manipulate the inventory
|
||||||
|
#if CLIENT
|
||||||
|
if (IsKeyHit(InputType.DropItem) && Screen.Selected is { IsEditor: false } && CharacterHUD.ShouldDrawInventory(this))
|
||||||
{
|
{
|
||||||
foreach (Item item in HeldItems)
|
foreach (Item item in HeldItems)
|
||||||
{
|
{
|
||||||
@@ -2429,6 +2431,7 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
|
bool CanUseItemsWhenSelected(Item item) => item == null || !item.Prefab.DisableItemUsageWhenSelected;
|
||||||
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
|
if (CanUseItemsWhenSelected(SelectedItem) && CanUseItemsWhenSelected(SelectedSecondaryItem))
|
||||||
@@ -2632,6 +2635,7 @@ namespace Barotrauma
|
|||||||
public bool Unequip(Item item)
|
public bool Unequip(Item item)
|
||||||
{
|
{
|
||||||
if (!HasEquippedItem(item)) { return false; }
|
if (!HasEquippedItem(item)) { return false; }
|
||||||
|
if (!item.IsInteractable(this)) { return false; }
|
||||||
if (!TryPutItemInAnySlot(item))
|
if (!TryPutItemInAnySlot(item))
|
||||||
{
|
{
|
||||||
if (!TryPutItemInBag(item))
|
if (!TryPutItemInBag(item))
|
||||||
@@ -2642,7 +2646,7 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.Limited)
|
public bool CanAccessInventory(Inventory inventory, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets)
|
||||||
{
|
{
|
||||||
if (!CanInteract || inventory.Locked) { return false; }
|
if (!CanInteract || inventory.Locked) { return false; }
|
||||||
|
|
||||||
@@ -2686,7 +2690,7 @@ namespace Barotrauma
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Is the inventory accessible to the character? Doesn't check if the character can actually interact with it (distance checks etc).
|
/// Is the inventory accessible to the character? Doesn't check if the character can actually interact with it (distance checks etc).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.Limited)
|
public bool IsInventoryAccessibleTo(Character character, CharacterInventory.AccessLevel accessLevel = CharacterInventory.AccessLevel.AllowBotsAndPets)
|
||||||
{
|
{
|
||||||
if (Removed || Inventory == null) { return false; }
|
if (Removed || Inventory == null) { return false; }
|
||||||
if (!Inventory.AccessibleWhenAlive && !IsDead)
|
if (!Inventory.AccessibleWhenAlive && !IsDead)
|
||||||
@@ -2701,9 +2705,9 @@ namespace Barotrauma
|
|||||||
if (IsKnockedDownOrRagdolled || LockHands) { return true; }
|
if (IsKnockedDownOrRagdolled || LockHands) { return true; }
|
||||||
return accessLevel switch
|
return accessLevel switch
|
||||||
{
|
{
|
||||||
CharacterInventory.AccessLevel.Restricted => false,
|
CharacterInventory.AccessLevel.OnlyIfIncapacitated => false,
|
||||||
CharacterInventory.AccessLevel.Limited => (IsBot && IsOnSameTeam()) || IsFriendlyPet(),
|
CharacterInventory.AccessLevel.AllowBotsAndPets => (IsBot && IsOnSameTeam()) || IsFriendlyPet(),
|
||||||
CharacterInventory.AccessLevel.Allowed => IsOnSameTeam() || IsFriendlyPet(),
|
CharacterInventory.AccessLevel.AllowFriendly => IsOnSameTeam() || IsFriendlyPet(),
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -5714,7 +5718,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public bool HasRecipeForItem(Identifier recipeIdentifier)
|
public bool HasRecipeForItem(Identifier recipeIdentifier)
|
||||||
{
|
{
|
||||||
if (GameMain.GameSession != null && GameMain.GameSession.UnlockedRecipes.Contains(recipeIdentifier)) { return true; }
|
if (GameMain.GameSession != null && GameMain.GameSession.HasUnlockedRecipe(this, recipeIdentifier)) { return true; }
|
||||||
return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
|
return characterTalents.Any(t => t.UnlockedRecipes.Contains(recipeIdentifier));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -135,6 +135,9 @@ namespace Barotrauma
|
|||||||
private Affliction stunAffliction;
|
private Affliction stunAffliction;
|
||||||
public Affliction BloodlossAffliction { get => bloodlossAffliction; }
|
public Affliction BloodlossAffliction { get => bloodlossAffliction; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Is the character dead or below 0 vitality and not able to stay conscious?
|
||||||
|
/// </summary>
|
||||||
public bool IsUnconscious
|
public bool IsUnconscious
|
||||||
{
|
{
|
||||||
get { return Character.IsDead || (Vitality <= 0.0f && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious)); }
|
get { return Character.IsDead || (Vitality <= 0.0f && !Character.HasAbilityFlag(AbilityFlags.AlwaysStayConscious)); }
|
||||||
|
|||||||
@@ -106,6 +106,8 @@ namespace Barotrauma
|
|||||||
void AddTexturePath(string path)
|
void AddTexturePath(string path)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(path)) { return; }
|
if (string.IsNullOrEmpty(path)) { return; }
|
||||||
|
//if the path contains a gender variable, we can't load it yet because we don't know which gender we need
|
||||||
|
if (path.Contains("[GENDER]")) { return; }
|
||||||
texturePaths.Add(ContentPath.FromRaw(characterPrefab.ContentPackage, ragdollParams.Texture));
|
texturePaths.Add(ContentPath.FromRaw(characterPrefab.ContentPackage, ragdollParams.Texture));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -656,13 +656,17 @@ namespace Barotrauma
|
|||||||
NewMessage("***************", Color.Cyan);
|
NewMessage("***************", Color.Cyan);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("godmode", "godmode [character name]: Toggle character godmode. Makes the targeted character invulnerable to damage. If the name parameter is omitted, the controlled character will receive godmode.",
|
commands.Add(new Command("godmode", "godmode [character name] [remove afflictions (true/false)]: Toggle character godmode. Makes the targeted character invulnerable to damage. If the name parameter is omitted, the controlled character will receive godmode.",
|
||||||
(string[] args) =>
|
(string[] args) =>
|
||||||
{
|
{
|
||||||
bool? godmodeStateOnFirstCharacter = null;
|
bool? godmodeStateOnFirstCharacter = null;
|
||||||
HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode);
|
HandleCommandForCrewOrSingleCharacter(args, ToggleGodMode);
|
||||||
void ToggleGodMode(Character targetCharacter)
|
void ToggleGodMode(Character targetCharacter)
|
||||||
{
|
{
|
||||||
|
if (args.Length > 1 && bool.TryParse(args[1], out bool removeafflictions))
|
||||||
|
{
|
||||||
|
if (removeafflictions) { targetCharacter.CharacterHealth.RemoveAllAfflictions(); }
|
||||||
|
}
|
||||||
targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode;
|
targetCharacter.GodMode = godmodeStateOnFirstCharacter ?? !targetCharacter.GodMode;
|
||||||
godmodeStateOnFirstCharacter = targetCharacter.GodMode;
|
godmodeStateOnFirstCharacter = targetCharacter.GodMode;
|
||||||
NewMessage((targetCharacter.GodMode ? "Enabled godmode on " : "Disabled godmode on ") + targetCharacter.Name,
|
NewMessage((targetCharacter.GodMode ? "Enabled godmode on " : "Disabled godmode on ") + targetCharacter.Name,
|
||||||
|
|||||||
@@ -21,6 +21,9 @@
|
|||||||
[Serialize(0, IsPropertySaveable.Yes, description: "The state to set the mission to, or how much to add to the state of the mission.")]
|
[Serialize(0, IsPropertySaveable.Yes, description: "The state to set the mission to, or how much to add to the state of the mission.")]
|
||||||
public int State { get; set; }
|
public int State { get; set; }
|
||||||
|
|
||||||
|
[Serialize(false, IsPropertySaveable.Yes, description: "If set to true, the mission is forced to fail without a chance of retrying it.")]
|
||||||
|
public bool ForceFailure { get; set; }
|
||||||
|
|
||||||
private bool isFinished;
|
private bool isFinished;
|
||||||
|
|
||||||
public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
public MissionStateAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element)
|
||||||
@@ -31,7 +34,7 @@
|
|||||||
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.",
|
DebugConsole.ThrowError($"Error in event \"{parentEvent.Prefab.Identifier}\": MissionIdentifier has not been configured.",
|
||||||
contentPackage: element.ContentPackage);
|
contentPackage: element.ContentPackage);
|
||||||
}
|
}
|
||||||
if (Operation == OperationType.Add && State == 0)
|
if (Operation == OperationType.Add && State == 0 && !ForceFailure)
|
||||||
{
|
{
|
||||||
DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to add 0 to the mission state, which will do nothing.",
|
DebugConsole.AddWarning($"Potential error in event \"{parentEvent.Prefab.Identifier}\": {nameof(MissionStateAction)} is set to add 0 to the mission state, which will do nothing.",
|
||||||
contentPackage: element.ContentPackage);
|
contentPackage: element.ContentPackage);
|
||||||
@@ -54,6 +57,11 @@
|
|||||||
foreach (Mission mission in GameMain.GameSession.Missions)
|
foreach (Mission mission in GameMain.GameSession.Missions)
|
||||||
{
|
{
|
||||||
if (mission.Prefab.Identifier != MissionIdentifier) { continue; }
|
if (mission.Prefab.Identifier != MissionIdentifier) { continue; }
|
||||||
|
if (ForceFailure)
|
||||||
|
{
|
||||||
|
mission.ForceFailure = true;
|
||||||
|
}
|
||||||
|
|
||||||
switch (Operation)
|
switch (Operation)
|
||||||
{
|
{
|
||||||
case OperationType.Set:
|
case OperationType.Set:
|
||||||
|
|||||||
@@ -410,16 +410,39 @@ namespace Barotrauma
|
|||||||
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false, bool allowInPlayerView = true)
|
public static WayPoint GetSpawnPos(SpawnLocationType spawnLocation, SpawnType? spawnPointType, IEnumerable<Identifier> moduleFlags = null, IEnumerable<Identifier> spawnpointTags = null, bool asFarAsPossibleFromAirlock = false, bool requireTaggedSpawnPoint = false, bool allowInPlayerView = true)
|
||||||
{
|
{
|
||||||
bool requireHull = spawnLocation == SpawnLocationType.MainSub || spawnLocation == SpawnLocationType.Outpost;
|
bool requireHull = spawnLocation == SpawnLocationType.MainSub || spawnLocation == SpawnLocationType.Outpost;
|
||||||
List<WayPoint> potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull));
|
|
||||||
potentialSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.ConnectedDoor == null && wp.Ladders == null && wp.IsTraversable);
|
IEnumerable<WayPoint> potentialSpawnPoints = WayPoint.WayPointList.FindAll(wp => IsValidSubmarineType(spawnLocation, wp.Submarine) && (wp.CurrentHull != null || !requireHull));
|
||||||
|
potentialSpawnPoints = potentialSpawnPoints.Where(wp => wp.ConnectedDoor == null && wp.Ladders == null && wp.IsTraversable);
|
||||||
|
|
||||||
|
//find spawnpoints with the desired type, or any random spawnpoints if not specified
|
||||||
|
IEnumerable<WayPoint> spawnPointsWithCorrectType;
|
||||||
|
if (spawnPointType.HasValue)
|
||||||
|
{
|
||||||
|
spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp =>
|
||||||
|
spawnPointType.Value.HasFlag(wp.SpawnType) &&
|
||||||
|
//need to handle zero (SpawnType.Path) separately, because spawnPointType will always have the flag 0
|
||||||
|
(wp.SpawnType != 0 || spawnPointType.Value == 0));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
spawnPointsWithCorrectType = potentialSpawnPoints.Where(wp => wp.SpawnType != SpawnType.Path);
|
||||||
|
}
|
||||||
|
if (spawnPointsWithCorrectType.Any())
|
||||||
|
{
|
||||||
|
potentialSpawnPoints = spawnPointsWithCorrectType;
|
||||||
|
}
|
||||||
|
|
||||||
|
//with correct module flags, if there are any
|
||||||
if (moduleFlags != null && moduleFlags.Any())
|
if (moduleFlags != null && moduleFlags.Any())
|
||||||
{
|
{
|
||||||
var spawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains));
|
var spawnPointsWithCorrectFlags = potentialSpawnPoints.Where(wp => wp.CurrentHull is Hull h && h.OutpostModuleTags.Any(moduleFlags.Contains));
|
||||||
if (spawnPoints.Any())
|
if (spawnPointsWithCorrectFlags.Any())
|
||||||
{
|
{
|
||||||
potentialSpawnPoints = spawnPoints.ToList();
|
potentialSpawnPoints = spawnPointsWithCorrectFlags.ToList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//with correct spawn point tags, if there are any
|
||||||
if (spawnpointTags != null && spawnpointTags.Any())
|
if (spawnpointTags != null && spawnpointTags.Any())
|
||||||
{
|
{
|
||||||
var spawnPointsWithTag = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && wp.IsTraversable));
|
var spawnPointsWithTag = potentialSpawnPoints.Where(wp => spawnpointTags.Any(tag => wp.Tags.Contains(tag) && wp.ConnectedDoor == null && wp.IsTraversable));
|
||||||
@@ -461,16 +484,8 @@ namespace Barotrauma
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<WayPoint> validSpawnPoints;
|
//spawnpoints that match the desired criteria found, choose the best one next
|
||||||
if (spawnPointType.HasValue)
|
IEnumerable<WayPoint> validSpawnPoints = potentialSpawnPoints;
|
||||||
{
|
|
||||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => spawnPointType.Value.HasFlag(wp.SpawnType));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
validSpawnPoints = potentialSpawnPoints.FindAll(wp => wp.SpawnType != SpawnType.Path);
|
|
||||||
if (!validSpawnPoints.Any()) { validSpawnPoints = potentialSpawnPoints; }
|
|
||||||
}
|
|
||||||
|
|
||||||
//don't spawn in an airlock module if there are other options
|
//don't spawn in an airlock module if there are other options
|
||||||
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false);
|
var airlockSpawnPoints = potentialSpawnPoints.Where(wp => wp.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
@@ -23,10 +24,12 @@ namespace Barotrauma
|
|||||||
private bool swarmSpawned;
|
private bool swarmSpawned;
|
||||||
private readonly List<MonsterSet> monsterSets = new List<MonsterSet>();
|
private readonly List<MonsterSet> monsterSets = new List<MonsterSet>();
|
||||||
private readonly LocalizedString sonarLabel;
|
private readonly LocalizedString sonarLabel;
|
||||||
|
private readonly ImmutableArray<Identifier> beaconTags;
|
||||||
|
|
||||||
public BeaconMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
public BeaconMission(MissionPrefab prefab, Location[] locations, Submarine sub) : base(prefab, locations, sub)
|
||||||
{
|
{
|
||||||
swarmSpawned = false;
|
swarmSpawned = false;
|
||||||
|
beaconTags = prefab.ConfigElement.GetAttributeIdentifierArray("beacontags", []).ToImmutableArray();
|
||||||
|
|
||||||
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
foreach (var monsterElement in prefab.ConfigElement.GetChildElements("monster"))
|
||||||
{
|
{
|
||||||
@@ -185,6 +188,29 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
levelData.HasBeaconStation = true;
|
levelData.HasBeaconStation = true;
|
||||||
levelData.IsBeaconActive = false;
|
levelData.IsBeaconActive = false;
|
||||||
|
|
||||||
|
if (beaconTags.Length > 0)
|
||||||
|
{
|
||||||
|
var selectedBeacon = GetRandomBeaconByTags(beaconTags, levelData);
|
||||||
|
if (selectedBeacon != null)
|
||||||
|
{
|
||||||
|
levelData.ForceBeaconStation = selectedBeacon;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Beacon mission \"{Prefab.Identifier}\" could not find a suitable beacon station with beacontags \"{string.Join(", ", beaconTags)}\" for level difficulty {levelData.Difficulty:F1}.",
|
||||||
|
contentPackage: Prefab.ContentPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubmarineInfo GetRandomBeaconByTags(ImmutableArray<Identifier> tags, LevelData levelData)
|
||||||
|
{
|
||||||
|
return GetRandomSubmarineByTagsAndDifficulty(
|
||||||
|
tags,
|
||||||
|
levelData,
|
||||||
|
s => s.IsBeacon,
|
||||||
|
"beacon station");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -48,7 +48,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected static bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
protected static bool IsClient => GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||||
|
|
||||||
private readonly CheckDataAction completeCheckDataAction;
|
protected readonly CheckDataAction completeCheckDataAction;
|
||||||
|
|
||||||
public readonly ImmutableArray<LocalizedString> Headers;
|
public readonly ImmutableArray<LocalizedString> Headers;
|
||||||
public readonly ImmutableArray<LocalizedString> Messages;
|
public readonly ImmutableArray<LocalizedString> Messages;
|
||||||
@@ -110,9 +110,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public bool Failed
|
public bool Failed
|
||||||
{
|
{
|
||||||
get { return failed; }
|
get { return failed || ForceFailure; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool ForceFailure;
|
||||||
|
|
||||||
public virtual bool AllowRespawning
|
public virtual bool AllowRespawning
|
||||||
{
|
{
|
||||||
get { return true; }
|
get { return true; }
|
||||||
@@ -541,9 +543,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (GameMain.NetworkMember is not { IsClient: true })
|
if (GameMain.NetworkMember is not { IsClient: true })
|
||||||
{
|
{
|
||||||
completed =
|
completed =
|
||||||
|
!ForceFailure &&
|
||||||
DetermineCompleted() &&
|
DetermineCompleted() &&
|
||||||
(completeCheckDataAction == null ||completeCheckDataAction.GetSuccess());
|
(completeCheckDataAction == null || completeCheckDataAction.GetSuccess());
|
||||||
}
|
}
|
||||||
if (completed)
|
if (completed)
|
||||||
{
|
{
|
||||||
@@ -569,6 +572,10 @@ namespace Barotrauma
|
|||||||
TimesAttempted++;
|
TimesAttempted++;
|
||||||
|
|
||||||
EndMissionSpecific(completed);
|
EndMissionSpecific(completed);
|
||||||
|
if (ForceFailure)
|
||||||
|
{
|
||||||
|
failed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected abstract bool DetermineCompleted();
|
protected abstract bool DetermineCompleted();
|
||||||
@@ -829,6 +836,51 @@ namespace Barotrauma
|
|||||||
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.ServerAndClient),
|
cargoSpawnPos.Position.X + Rand.Range(-20.0f, 20.0f, Rand.RandSync.ServerAndClient),
|
||||||
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
cargoRoom.Rect.Y - cargoRoom.Rect.Height + itemPrefab.Size.Y / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a random submarine by tags, filtered by difficulty. Used by missions that force specific submarines (wrecks, beacons, etc.)
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="tags">Mission tags to match against</param>
|
||||||
|
/// <param name="seed">Random seed for selection</param>
|
||||||
|
/// <param name="submarineSelector">Function to filter submarines by type (e.g., s => s.IsWreck)</param>
|
||||||
|
/// <param name="submarineTypeName">Name of submarine type for error messages (e.g., "wreck", "beacon station")</param>
|
||||||
|
/// <returns>Selected submarine, or null if none found</returns>
|
||||||
|
protected static SubmarineInfo GetRandomSubmarineByTagsAndDifficulty(
|
||||||
|
IEnumerable<Identifier> tags,
|
||||||
|
LevelData levelData,
|
||||||
|
Func<SubmarineInfo, bool> submarineSelector,
|
||||||
|
string submarineTypeName)
|
||||||
|
{
|
||||||
|
var rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||||
|
float levelDifficulty = levelData.Difficulty;
|
||||||
|
|
||||||
|
var submarinesWithTags = SubmarineInfo.SavedSubmarines
|
||||||
|
.Where(submarineSelector)
|
||||||
|
.Where(s =>
|
||||||
|
{
|
||||||
|
return s.GetExtraSubmarineInfo is { } extraInfo && (tags.None() || tags.Any(t => extraInfo.MissionTags.Contains(t)));
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
var matchingSubmarines = submarinesWithTags
|
||||||
|
.Where(s =>
|
||||||
|
{
|
||||||
|
return s.GetExtraSubmarineInfo is { } extraInfo &&
|
||||||
|
levelDifficulty >= extraInfo.MinLevelDifficulty &&
|
||||||
|
levelDifficulty <= extraInfo.MaxLevelDifficulty;
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (matchingSubmarines.Count == 0)
|
||||||
|
{
|
||||||
|
if (submarinesWithTags.Count > 0)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Found {submarinesWithTags.Count} {submarineTypeName}(s) with matching tags \"{string.Join(", ", tags)}\", but none are suitable for level difficulty {levelDifficulty:F1}.");
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return matchingSubmarines[rand.Next(matchingSubmarines.Count)];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AbilityMissionMoneyGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
|
class AbilityMissionMoneyGainMultiplier : AbilityObject, IAbilityValue, IAbilityMission
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using FarseerPhysics;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -240,6 +241,8 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float requiredDeliveryAmount;
|
private readonly float requiredDeliveryAmount;
|
||||||
|
|
||||||
|
private readonly ImmutableArray<Identifier> wreckTags;
|
||||||
|
|
||||||
private LocalizedString pickedUpMessage;
|
private LocalizedString pickedUpMessage;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -311,12 +314,13 @@ namespace Barotrauma
|
|||||||
: base(prefab, locations, sub)
|
: base(prefab, locations, sub)
|
||||||
{
|
{
|
||||||
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeFloat(nameof(requiredDeliveryAmount), 0.98f);
|
requiredDeliveryAmount = prefab.ConfigElement.GetAttributeFloat(nameof(requiredDeliveryAmount), 0.98f);
|
||||||
|
|
||||||
//LevelData may not be instantiated at this point, in that case use the name identifier of the location
|
//LevelData may not be instantiated at this point, in that case use the name identifier of the location
|
||||||
rng = new MTRandom(ToolBox.StringToInt(
|
rng = new MTRandom(ToolBox.StringToInt(
|
||||||
locations[0].LevelData?.Seed ?? locations[0].NameIdentifier.Value +
|
locations[0].LevelData?.Seed ?? locations[0].NameIdentifier.Value +
|
||||||
locations[1].LevelData?.Seed ?? locations[1].NameIdentifier.Value));
|
locations[1].LevelData?.Seed ?? locations[1].NameIdentifier.Value));
|
||||||
|
|
||||||
|
wreckTags = prefab.ConfigElement.GetAttributeIdentifierArray("wrecktags", []).ToImmutableArray();
|
||||||
|
|
||||||
partiallyRetrievedMessage = GetMessage(nameof(partiallyRetrievedMessage));
|
partiallyRetrievedMessage = GetMessage(nameof(partiallyRetrievedMessage));
|
||||||
allRetrievedMessage = GetMessage(nameof(allRetrievedMessage));
|
allRetrievedMessage = GetMessage(nameof(allRetrievedMessage));
|
||||||
pickedUpMessage = GetMessage(nameof(pickedUpMessage));
|
pickedUpMessage = GetMessage(nameof(pickedUpMessage));
|
||||||
@@ -756,5 +760,31 @@ namespace Barotrauma
|
|||||||
target.Reset();
|
target.Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void AdjustLevelData(LevelData levelData)
|
||||||
|
{
|
||||||
|
if (wreckTags.Length > 0)
|
||||||
|
{
|
||||||
|
var selectedWreck = GetRandomWreckByTags(wreckTags, levelData);
|
||||||
|
if (selectedWreck != null)
|
||||||
|
{
|
||||||
|
levelData.ForceWreck = selectedWreck;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Salvage mission \"{Prefab.Identifier}\" could not find a suitable wreck with wrecktags \"{string.Join(", ", wreckTags)}\" for level difficulty {levelData.Difficulty:F1}.",
|
||||||
|
contentPackage: Prefab.ContentPackage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SubmarineInfo GetRandomWreckByTags(ImmutableArray<Identifier> tags, LevelData levelData)
|
||||||
|
{
|
||||||
|
return GetRandomSubmarineByTagsAndDifficulty(
|
||||||
|
tags,
|
||||||
|
levelData,
|
||||||
|
s => s.IsWreck,
|
||||||
|
"wreck");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -304,12 +304,17 @@ namespace Barotrauma
|
|||||||
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
|
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
|
||||||
var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToPurchase.Select(i => i.ItemPrefab));
|
var buyValues = GetBuyValuesAtCurrentLocation(storeIdentifier, itemsToPurchase.Select(i => i.ItemPrefab));
|
||||||
var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true);
|
var itemsInStoreCrate = GetBuyCrateItems(storeIdentifier, create: true);
|
||||||
foreach (PurchasedItem item in itemsToPurchase)
|
//handle checking which items can be purchased and deducting money first
|
||||||
|
foreach (PurchasedItem item in itemsToPurchase.ToList())
|
||||||
{
|
{
|
||||||
if (item.Quantity <= 0) { continue; }
|
if (item.Quantity <= 0) { continue; }
|
||||||
// Exchange money
|
// Exchange money
|
||||||
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
int itemValue = item.Quantity * buyValues[item.ItemPrefab];
|
||||||
if (!campaign.TryPurchase(client, itemValue)) { continue; }
|
if (!campaign.TryPurchase(client, itemValue))
|
||||||
|
{
|
||||||
|
itemsToPurchase.Remove(item);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
// Add to the purchased items
|
// Add to the purchased items
|
||||||
var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab && pi.DeliverImmediately == item.DeliverImmediately);
|
var purchasedItem = itemsPurchasedFromStore.Find(pi => pi.ItemPrefab == item.ItemPrefab && pi.DeliverImmediately == item.DeliverImmediately);
|
||||||
@@ -329,6 +334,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
store.Balance += itemValue;
|
store.Balance += itemValue;
|
||||||
}
|
}
|
||||||
|
//actually spawn the items at this point
|
||||||
if (GameMain.NetworkMember is not { IsClient: true })
|
if (GameMain.NetworkMember is not { IsClient: true })
|
||||||
{
|
{
|
||||||
Character targetCharacter;
|
Character targetCharacter;
|
||||||
|
|||||||
@@ -1724,7 +1724,10 @@ namespace Barotrauma
|
|||||||
GameMain.GameSession.EventManager.Load(subElement);
|
GameMain.GameSession.EventManager.Load(subElement);
|
||||||
break;
|
break;
|
||||||
case "unlockedrecipe":
|
case "unlockedrecipe":
|
||||||
GameMain.GameSession.UnlockRecipe(subElement.GetAttributeIdentifier("identifier", Identifier.Empty), showNotifications: false);
|
GameMain.GameSession.UnlockRecipe(
|
||||||
|
subElement.GetAttributeEnum("team", CharacterTeamType.Team1),
|
||||||
|
subElement.GetAttributeIdentifier("identifier", Identifier.Empty),
|
||||||
|
showNotifications: false);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -170,8 +170,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public Submarine? Submarine { get; set; }
|
public Submarine? Submarine { get; set; }
|
||||||
|
|
||||||
private readonly HashSet<Identifier> unlockedRecipes = new HashSet<Identifier>();
|
private readonly HashSet<(CharacterTeamType team, Identifier identifier)> unlockedRecipes = new HashSet<(CharacterTeamType, Identifier)>();
|
||||||
public IEnumerable<Identifier> UnlockedRecipes => unlockedRecipes;
|
public IEnumerable<(CharacterTeamType, Identifier)> UnlockedRecipes => unlockedRecipes;
|
||||||
|
|
||||||
public CampaignDataPath DataPath { get; set; }
|
public CampaignDataPath DataPath { get; set; }
|
||||||
|
|
||||||
@@ -1499,25 +1499,32 @@ namespace Barotrauma
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnlockRecipe(Identifier identifier, bool showNotifications)
|
public void UnlockRecipe(CharacterTeamType team, Identifier identifier, bool showNotifications)
|
||||||
{
|
{
|
||||||
if (unlockedRecipes.Add(identifier))
|
if (unlockedRecipes.Add((team, identifier)))
|
||||||
{
|
{
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
if (showNotifications)
|
if (showNotifications)
|
||||||
{
|
{
|
||||||
foreach (var character in GetSessionCrewCharacters(CharacterType.Both))
|
foreach (var character in GetSessionCrewCharacters(CharacterType.Both))
|
||||||
{
|
{
|
||||||
|
if (character.TeamID != team) { continue; }
|
||||||
LocalizedString recipeName = TextManager.Get($"entityname.{identifier}").Fallback(identifier.Value);
|
LocalizedString recipeName = TextManager.Get($"entityname.{identifier}").Fallback(identifier.Value);
|
||||||
character.AddMessage(TextManager.GetWithVariable("recipeunlockednotification", "[name]", recipeName).Value, GUIStyle.Yellow, playSound: true);
|
character.AddMessage(TextManager.GetWithVariable("recipeunlockednotification", "[name]", recipeName).Value, GUIStyle.Yellow, playSound: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
GameMain.Server.UnlockRecipe(identifier);
|
GameMain.Server.UnlockRecipe(team, identifier);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool HasUnlockedRecipe(Character character, Identifier itemIdentifier)
|
||||||
|
{
|
||||||
|
if (character == null) { return false; }
|
||||||
|
return unlockedRecipes.Contains((character.TeamID, itemIdentifier));
|
||||||
|
}
|
||||||
|
|
||||||
public static bool IsCompatibleWithEnabledContentPackages(IList<string> contentPackageNames, out LocalizedString errorMsg)
|
public static bool IsCompatibleWithEnabledContentPackages(IList<string> contentPackageNames, out LocalizedString errorMsg)
|
||||||
{
|
{
|
||||||
errorMsg = "";
|
errorMsg = "";
|
||||||
|
|||||||
@@ -17,15 +17,21 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// How much access other characters have to the inventory?
|
/// How much access other characters have to the inventory?
|
||||||
/// <see cref="Restricted"/> = Only accessible when character is knocked down or handcuffed.
|
|
||||||
/// <see cref="Limited"/> = Can also access inventories of bots on the same team and friendly pets.
|
|
||||||
/// <see cref="Allowed"/> = Can also access other players in the same team (used for drag and drop give).
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public enum AccessLevel
|
public enum AccessLevel
|
||||||
{
|
{
|
||||||
Restricted,
|
/// <summary>
|
||||||
Limited,
|
/// Only accessible when character is knocked down or handcuffed.
|
||||||
Allowed
|
/// </summary>
|
||||||
|
OnlyIfIncapacitated,
|
||||||
|
/// <summary>
|
||||||
|
/// Can also access inventories of bots on the same team and friendly pets.
|
||||||
|
/// </summary>
|
||||||
|
AllowBotsAndPets,
|
||||||
|
/// <summary>
|
||||||
|
/// Can also access other players in the same team (used for drag and drop give).
|
||||||
|
/// </summary>
|
||||||
|
AllowFriendly
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly Character character;
|
private readonly Character character;
|
||||||
@@ -342,8 +348,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (Item existingItem in slots[slot].Items.ToList())
|
foreach (Item existingItem in slots[slot].Items.ToList())
|
||||||
{
|
{
|
||||||
|
if (!existingItem.IsInteractable(character)) { continue; }
|
||||||
existingItem.Drop(user);
|
existingItem.Drop(user);
|
||||||
if (existingItem.ParentInventory != null) { existingItem.ParentInventory.RemoveItem(existingItem); }
|
existingItem.ParentInventory?.RemoveItem(existingItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma.Items.Components
|
namespace Barotrauma.Items.Components
|
||||||
@@ -197,7 +198,16 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
item.Drop(CurrentThrower, createNetworkEvent: GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer);
|
||||||
item.WaterDragCoefficient = WaterDragCoefficient;
|
item.WaterDragCoefficient = WaterDragCoefficient;
|
||||||
item.body.ApplyLinearImpulse(throwVector * ThrowForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
|
||||||
|
float throwForce = ThrowForce;
|
||||||
|
//Reduce force when aiming down
|
||||||
|
float downwardsDotProduct = Vector2.Dot(-Vector2.UnitY, throwVector); //1 when pointing directly down, 0 when sideways, -1 when up
|
||||||
|
if (downwardsDotProduct > 0)
|
||||||
|
{
|
||||||
|
throwForce *= (1.0f - downwardsDotProduct * 0.7f);
|
||||||
|
}
|
||||||
|
|
||||||
|
item.body.ApplyLinearImpulse(throwVector * throwForce * item.body.Mass * 3.0f, maxVelocity: NetConfig.MaxPhysicsBodyVelocity);
|
||||||
|
|
||||||
//disable platform collisions until the item comes back to rest again
|
//disable platform collisions until the item comes back to rest again
|
||||||
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
item.body.CollidesWith = Physics.CollisionWall | Physics.CollisionLevel;
|
||||||
|
|||||||
@@ -483,6 +483,9 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public virtual bool UpdateWhenInactive => false;
|
public virtual bool UpdateWhenInactive => false;
|
||||||
|
|
||||||
|
[Serialize(false, IsPropertySaveable.No, "If true, the component will retain its normal functionality when the item reaches 0 condition.")]
|
||||||
|
public bool UpdateWhenBroken { get; set; }
|
||||||
|
|
||||||
//called when isActive is true and condition > 0.0f
|
//called when isActive is true and condition > 0.0f
|
||||||
public virtual void Update(float deltaTime, Camera cam)
|
public virtual void Update(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -132,6 +132,12 @@ namespace Barotrauma.Items.Components
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serialize(true, IsPropertySaveable.No, description: "Should a button that allows sorting the items alphabetically be shown in the container's UI panel?")]
|
||||||
|
public bool ShowSortButton { get; set; }
|
||||||
|
|
||||||
|
[Serialize(true, IsPropertySaveable.No, description: "Should a button that merges items into stacks be shown in the container's UI panel?")]
|
||||||
|
public bool ShowMergeButton { get; set; }
|
||||||
|
|
||||||
[Serialize(true, IsPropertySaveable.Yes, description: "When this item is equipped, and you 'quick use' (double click / equip button) another equippable item, should the game attempt to move that item inside this one?")]
|
[Serialize(true, IsPropertySaveable.Yes, description: "When this item is equipped, and you 'quick use' (double click / equip button) another equippable item, should the game attempt to move that item inside this one?")]
|
||||||
public bool QuickUseMovesItemsInside { get; set; }
|
public bool QuickUseMovesItemsInside { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -406,6 +406,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (IsOutOfPower()) { return false; }
|
if (IsOutOfPower()) { return false; }
|
||||||
|
|
||||||
|
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
|
||||||
if (IsToggle && (activator == null || lastUsed < Timing.TotalTime - 0.1))
|
if (IsToggle && (activator == null || lastUsed < Timing.TotalTime - 0.1))
|
||||||
{
|
{
|
||||||
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
|
||||||
@@ -421,8 +422,7 @@ namespace Barotrauma.Items.Components
|
|||||||
item.SendSignal(new Signal(output, sender: user), "trigger_out");
|
item.SendSignal(new Signal(output, sender: user), "trigger_out");
|
||||||
}
|
}
|
||||||
|
|
||||||
lastUsed = Timing.TotalTime;
|
lastUsed = Timing.TotalTime;
|
||||||
ApplyStatusEffects(ActionType.OnUse, 1.0f, activator);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,6 +541,7 @@ namespace Barotrauma.Items.Components
|
|||||||
#if CLIENT
|
#if CLIENT
|
||||||
PlaySound(ActionType.OnUse, picker);
|
PlaySound(ActionType.OnUse, picker);
|
||||||
#endif
|
#endif
|
||||||
|
ApplyStatusEffects(ActionType.OnUse, 1f, picker);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,10 @@ namespace Barotrauma.Items.Components
|
|||||||
CurrFlow = 0.0f;
|
CurrFlow = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void GetVents()
|
/// <summary>
|
||||||
|
/// Finds all the linked vents and calculates how much oxygen should be distributed to each of them based on the hull volumes.
|
||||||
|
/// </summary>
|
||||||
|
public void GetVents()
|
||||||
{
|
{
|
||||||
totalHullVolume = 0.0f;
|
totalHullVolume = 0.0f;
|
||||||
ventList ??= new List<(Vent vent, float hullVolume)>();
|
ventList ??= new List<(Vent vent, float hullVolume)>();
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (item.ConditionPercentage > 10.0f || !IsActive) { return 0.0f; }
|
if (item.ConditionPercentage > 10.0f || !IsActive || Disabled) { return 0.0f; }
|
||||||
return (1.0f - item.ConditionPercentage / 10.0f) * 100.0f;
|
return (1.0f - item.ConditionPercentage / 10.0f) * 100.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,6 +62,23 @@ namespace Barotrauma.Items.Components
|
|||||||
set => maxFlow = value;
|
set => maxFlow = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool disabled;
|
||||||
|
[Serialize(false, IsPropertySaveable.Yes, description: "If true, the pump is unable to pump water.", alwaysUseInstanceValues: true)]
|
||||||
|
public bool Disabled
|
||||||
|
{
|
||||||
|
get => disabled;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (disabled == value) { return; }
|
||||||
|
disabled = value;
|
||||||
|
#if SERVER
|
||||||
|
//send a network update soon
|
||||||
|
//don't force to 0 though so this doesn't lead to spam if the property is toggled rapidly
|
||||||
|
networkUpdateTimer = Math.Min(networkUpdateTimer, 0.5f);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Editable, Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
[Editable, Serialize(true, IsPropertySaveable.Yes, alwaysUseInstanceValues: true)]
|
||||||
public bool IsOn
|
public bool IsOn
|
||||||
{
|
{
|
||||||
@@ -68,15 +86,13 @@ namespace Barotrauma.Items.Components
|
|||||||
set { IsActive = value; }
|
set { IsActive = value; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serialize(false, IsPropertySaveable.No)]
|
||||||
|
public bool CanCauseLethalPressure { get; set; }
|
||||||
|
|
||||||
private float currFlow;
|
private float currFlow;
|
||||||
public float CurrFlow
|
public float CurrFlow => IsActive ? Math.Abs(currFlow) : 0.0f;
|
||||||
{
|
|
||||||
get
|
public bool IsHullFull => item.CurrentHull != null && item.CurrentHull.WaterVolume >= item.CurrentHull.Volume * Hull.MaxCompress;
|
||||||
{
|
|
||||||
if (!IsActive) { return 0.0f; }
|
|
||||||
return Math.Abs(currFlow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool HasPower => IsActive && Voltage >= MinVoltage;
|
public override bool HasPower => IsActive && Voltage >= MinVoltage;
|
||||||
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
|
public bool IsAutoControlled => pumpSpeedLockTimer > 0.0f || isActiveLockTimer > 0.0f;
|
||||||
@@ -85,7 +101,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override bool UpdateWhenInactive => true;
|
public override bool UpdateWhenInactive => true;
|
||||||
|
|
||||||
public float CurrentStress => Math.Abs(flowPercentage / 100.0f);
|
public float CurrentStress => IsActive ? Math.Abs(flowPercentage / 100.0f) : 0.0f;
|
||||||
|
|
||||||
public Pump(Item item, ContentXElement element)
|
public Pump(Item item, ContentXElement element)
|
||||||
: base(item, element)
|
: base(item, element)
|
||||||
@@ -95,48 +111,42 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element);
|
partial void InitProjSpecific(ContentXElement element);
|
||||||
|
|
||||||
|
private readonly List<Hull> linkedHulls = [];
|
||||||
|
|
||||||
public override void Update(float deltaTime, Camera cam)
|
public override void Update(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
pumpSpeedLockTimer -= deltaTime;
|
pumpSpeedLockTimer -= deltaTime;
|
||||||
isActiveLockTimer -= deltaTime;
|
isActiveLockTimer -= deltaTime;
|
||||||
|
|
||||||
if (!IsActive)
|
currFlow = 0f;
|
||||||
|
|
||||||
|
if (item.CurrentHull == null)
|
||||||
{
|
{
|
||||||
|
if (TargetLevel != null) { FlowPercentage = 0f; }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currFlow = 0.0f;
|
|
||||||
|
|
||||||
if (TargetLevel != null)
|
if (TargetLevel != null)
|
||||||
{
|
{
|
||||||
float hullPercentage = 0.0f;
|
float hullWaterVolume = item.CurrentHull.WaterVolume;
|
||||||
if (item.CurrentHull != null)
|
float totalHullVolume = item.CurrentHull.Volume;
|
||||||
|
|
||||||
|
linkedHulls.Clear();
|
||||||
|
//hidden hulls still affect buoyancy, include them here
|
||||||
|
item.CurrentHull.GetLinkedHulls(linkedHulls, includeHiddenHulls: true);
|
||||||
|
foreach (var linkedHull in linkedHulls)
|
||||||
{
|
{
|
||||||
float hullWaterVolume = item.CurrentHull.WaterVolume;
|
hullWaterVolume += linkedHull.WaterVolume;
|
||||||
float totalHullVolume = item.CurrentHull.Volume;
|
totalHullVolume += linkedHull.Volume;
|
||||||
foreach (var linked in item.CurrentHull.linkedTo)
|
|
||||||
{
|
|
||||||
if ((linked is Hull linkedHull))
|
|
||||||
{
|
|
||||||
hullWaterVolume += linkedHull.WaterVolume;
|
|
||||||
totalHullVolume += linkedHull.Volume;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
hullPercentage = hullWaterVolume / totalHullVolume * 100.0f;
|
|
||||||
}
|
}
|
||||||
|
float hullPercentage = hullWaterVolume / totalHullVolume * 100.0f;
|
||||||
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
|
FlowPercentage = ((float)TargetLevel - hullPercentage) * 10.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasPower)
|
UpdateNetworking(deltaTime);
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateProjSpecific(deltaTime);
|
if (!IsActive || Disabled) { return; }
|
||||||
|
if (flowPercentage <= 0f && item.CurrentHull.WaterVolume <= 0f) { return; }
|
||||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
|
||||||
|
|
||||||
if (item.CurrentHull == null) { return; }
|
|
||||||
|
|
||||||
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
float powerFactor = Math.Min(currPowerConsumption <= 0.0f || MinVoltage <= 0.0f ? 1.0f : Voltage, MaxOverVoltageFactor);
|
||||||
|
|
||||||
@@ -150,8 +160,22 @@ namespace Barotrauma.Items.Components
|
|||||||
//less effective when in a bad condition
|
//less effective when in a bad condition
|
||||||
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
|
currFlow *= MathHelper.Lerp(0.5f, 1.0f, item.Condition / item.MaxCondition);
|
||||||
|
|
||||||
item.CurrentHull.WaterVolume += currFlow * deltaTime * Timing.FixedUpdateRate;
|
if (MathUtils.NearlyEqual(currFlow, 0f, epsilon: 0.01f))
|
||||||
if (item.CurrentHull.WaterVolume > item.CurrentHull.Volume) { item.CurrentHull.Pressure += 30.0f * deltaTime; }
|
{
|
||||||
|
currFlow = 0f; // Set to 0 for conditionals.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.CurrentHull.WaterVolume += currFlow * deltaTime * Timing.FixedUpdateRate;
|
||||||
|
|
||||||
|
if (flowPercentage > 0f && item.CurrentHull.WaterVolume > item.CurrentHull.Volume)
|
||||||
|
{
|
||||||
|
item.CurrentHull.Pressure += 30f * deltaTime;
|
||||||
|
if (CanCauseLethalPressure) { item.CurrentHull.LethalPressure += Hull.PressureBuildUpSpeed * deltaTime; }
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||||
|
UpdateProjSpecific(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void InfectBallast(Identifier identifier, bool allowMultiplePerShip = false)
|
public void InfectBallast(Identifier identifier, bool allowMultiplePerShip = false)
|
||||||
@@ -188,7 +212,7 @@ namespace Barotrauma.Items.Components
|
|||||||
public override float GetCurrentPowerConsumption(Connection connection = null)
|
public override float GetCurrentPowerConsumption(Connection connection = null)
|
||||||
{
|
{
|
||||||
//There shouldn't be other power connections to this
|
//There shouldn't be other power connections to this
|
||||||
if (connection != this.powerIn || !IsActive)
|
if (connection != this.powerIn || !IsActive || Disabled)
|
||||||
{
|
{
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -202,6 +226,8 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime);
|
partial void UpdateProjSpecific(float deltaTime);
|
||||||
|
|
||||||
|
partial void UpdateNetworking(float deltaTime);
|
||||||
|
|
||||||
public override void ReceiveSignal(Signal signal, Connection connection)
|
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||||
{
|
{
|
||||||
if (Hijacked) { return; }
|
if (Hijacked) { return; }
|
||||||
@@ -276,5 +302,11 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void RemoveComponentSpecific()
|
||||||
|
{
|
||||||
|
base.RemoveComponentSpecific();
|
||||||
|
linkedHulls.Clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,6 +82,11 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
#if CLIENT
|
#if CLIENT
|
||||||
CreateGUI();
|
CreateGUI();
|
||||||
|
if (Screen.Selected is not { IsEditor: true })
|
||||||
|
{
|
||||||
|
//set text via the property to refresh the UI
|
||||||
|
Name = name;
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,10 +13,24 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class TriggerComponent : ItemComponent
|
partial class TriggerComponent : ItemComponent
|
||||||
{
|
{
|
||||||
[Editable, Serialize(0.0f, IsPropertySaveable.Yes, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
|
[Editable, Serialize(0f, IsPropertySaveable.Yes, description: "The maximum amount of force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
|
||||||
public float Force { get; set; }
|
public float Force { get; set; }
|
||||||
|
|
||||||
|
[Editable, Serialize("0,0", IsPropertySaveable.Yes, description: "The maximum amount of directional force applied to the triggering entitites.", alwaysUseInstanceValues: true)]
|
||||||
|
public Vector2 DirectionalForce { get; set; }
|
||||||
|
|
||||||
|
[Editable, Serialize(false, IsPropertySaveable.Yes, $"If true, {nameof(DirectionalForce)} is relative to the angle between the target and the item, Similar to {nameof(Force)}.\nIf false, it always pushes in the same direction, with respect to the item's rotation.", alwaysUseInstanceValues: true)]
|
||||||
|
public bool RelativeDirectionalForce { get; set; }
|
||||||
|
|
||||||
|
[Editable, Serialize(true, IsPropertySaveable.Yes, "If false, no vertical force will be applied.", alwaysUseInstanceValues: true)]
|
||||||
|
public bool VerticalForce { get; set; }
|
||||||
|
|
||||||
|
[Editable, Serialize(true, IsPropertySaveable.Yes, "If false, no horizontal force will be applied.", alwaysUseInstanceValues: true)]
|
||||||
|
public bool HorizontalForce { get; set; }
|
||||||
|
|
||||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force gets higher the closer the triggerer is to the center of the trigger.", alwaysUseInstanceValues: true)]
|
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force gets higher the closer the triggerer is to the center of the trigger.", alwaysUseInstanceValues: true)]
|
||||||
public bool DistanceBasedForce { get; set; }
|
public bool DistanceBasedForce { get; set; }
|
||||||
|
|
||||||
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force fluctuates over time or if it stays constant.", alwaysUseInstanceValues: true)]
|
[Editable, Serialize(false, IsPropertySaveable.Yes, description: "Determines if the force fluctuates over time or if it stays constant.", alwaysUseInstanceValues: true)]
|
||||||
public bool ForceFluctuation { get; set; }
|
public bool ForceFluctuation { get; set; }
|
||||||
|
|
||||||
@@ -141,12 +155,29 @@ namespace Barotrauma.Items.Components
|
|||||||
get => base.IsActive;
|
get => base.IsActive;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
|
bool wasActive = base.IsActive;
|
||||||
|
|
||||||
base.IsActive = value;
|
base.IsActive = value;
|
||||||
if (!IsActive)
|
if (!IsActive)
|
||||||
{
|
{
|
||||||
TriggerActive = false;
|
TriggerActive = false;
|
||||||
triggerers.Clear();
|
triggerers.Clear();
|
||||||
}
|
}
|
||||||
|
else if (!wasActive && PhysicsBody?.FarseerBody != null)
|
||||||
|
{
|
||||||
|
//when the trigger becomes active, we need to check which entities are inside it
|
||||||
|
ContactEdge ce = PhysicsBody.FarseerBody.ContactList;
|
||||||
|
while (ce != null && ce.Contact != null)
|
||||||
|
{
|
||||||
|
if (ce.Contact.Enabled)
|
||||||
|
{
|
||||||
|
var thisFixture = ce.Contact.FixtureA.Body == PhysicsBody.FarseerBody ? ce.Contact.FixtureA : ce.Contact.FixtureB;
|
||||||
|
var otherFixture = ce.Contact.FixtureA.Body == PhysicsBody.FarseerBody ? ce.Contact.FixtureB : ce.Contact.FixtureA;
|
||||||
|
OnCollision(thisFixture, otherFixture, ce.Contact);
|
||||||
|
}
|
||||||
|
ce = ce.Next;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -374,7 +405,9 @@ namespace Barotrauma.Items.Components
|
|||||||
float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v);
|
float amount = MathUtils.InverseLerp(-1.0f, 1.0f, v);
|
||||||
CurrentForceFluctuation = MathHelper.Lerp(1.0f - ForceFluctuationStrength, 1.0f, amount);
|
CurrentForceFluctuation = MathHelper.Lerp(1.0f - ForceFluctuationStrength, 1.0f, amount);
|
||||||
ForceFluctuationTimer = 0.0f;
|
ForceFluctuationTimer = 0.0f;
|
||||||
GameMain.NetworkMember?.CreateEntityEvent(this);
|
#if SERVER
|
||||||
|
item.CreateServerEvent(this);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +431,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Math.Abs(Force) < 0.01f)
|
if (Force < 0.01f && DirectionalForce.LengthSquared() < 0.0001f)
|
||||||
{
|
{
|
||||||
// Just ignore very minimal forces
|
// Just ignore very minimal forces
|
||||||
continue;
|
continue;
|
||||||
@@ -436,7 +469,25 @@ namespace Barotrauma.Items.Components
|
|||||||
if (diff.LengthSquared() < 0.0001f) { return; }
|
if (diff.LengthSquared() < 0.0001f) { return; }
|
||||||
float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
float distanceFactor = DistanceBasedForce ? LevelTrigger.GetDistanceFactor(body, PhysicsBody, RadiusInDisplayUnits) : 1.0f;
|
||||||
if (distanceFactor <= 0.0f) { return; }
|
if (distanceFactor <= 0.0f) { return; }
|
||||||
Vector2 force = distanceFactor * (CurrentForceFluctuation * Force) * Vector2.Normalize(diff) * multiplier;
|
Vector2 radialForce = Force * Vector2.Normalize(diff);
|
||||||
|
Vector2 directionalForce;
|
||||||
|
if (RelativeDirectionalForce)
|
||||||
|
{
|
||||||
|
directionalForce = DirectionalForce * new Vector2(Math.Sign(diff.X), Math.Sign(diff.Y));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector2 flippedForce = DirectionalForce;
|
||||||
|
if (item.FlippedX) { flippedForce.X = -flippedForce.X; }
|
||||||
|
if (item.FlippedY) { flippedForce.Y = -flippedForce.Y; }
|
||||||
|
directionalForce = MathUtils.RotatePoint(flippedForce, -item.RotationRad);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vector2 force = (radialForce + directionalForce) * CurrentForceFluctuation * distanceFactor * multiplier;
|
||||||
|
|
||||||
|
if (!HorizontalForce) { force.Y = 0.0f; }
|
||||||
|
if (!VerticalForce) { force.Y = 0.0f; }
|
||||||
|
|
||||||
if (force.LengthSquared() < 0.01f) { return; }
|
if (force.LengthSquared() < 0.01f) { return; }
|
||||||
if (body.Mass < 1)
|
if (body.Mass < 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -461,20 +461,15 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public float ImpactTolerance
|
public float ImpactTolerance => Prefab.ImpactTolerance;
|
||||||
{
|
|
||||||
get { return Prefab.ImpactTolerance; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public float InteractDistance
|
|
||||||
{
|
|
||||||
get { return Prefab.InteractDistance; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public float InteractPriority
|
public float ImpactDamage => Prefab.ImpactDamage;
|
||||||
{
|
public float ImpactDamageProbability => Prefab.ImpactDamageProbability;
|
||||||
get { return Prefab.InteractPriority; }
|
|
||||||
}
|
public float InteractDistance => Prefab.InteractDistance;
|
||||||
|
|
||||||
|
public float InteractPriority => Prefab.InteractPriority;
|
||||||
|
|
||||||
|
|
||||||
public override Vector2 Position
|
public override Vector2 Position
|
||||||
{
|
{
|
||||||
@@ -1767,7 +1762,7 @@ namespace Barotrauma
|
|||||||
ic.Move(amount, ignoreContacts);
|
ic.Move(amount, ignoreContacts);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (body != null && (Submarine == null || !Submarine.Loading)) { FindHull(); }
|
if (body != null && (Submarine == null || !Submarine.Loading) || Screen.Selected is { IsEditor: true }) { FindHull(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public Rectangle TransformTrigger(Rectangle trigger, bool world = false)
|
public Rectangle TransformTrigger(Rectangle trigger, bool world = false)
|
||||||
@@ -2387,7 +2382,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
while (impactQueue.TryDequeue(out float impact))
|
while (impactQueue.TryDequeue(out float impact))
|
||||||
{
|
{
|
||||||
HandleCollision(impact);
|
ReceiveImpact(impact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (isDroppedStackOwner && body != null)
|
if (isDroppedStackOwner && body != null)
|
||||||
@@ -2461,7 +2456,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (ic.IsActive || ic.UpdateWhenInactive)
|
if (ic.IsActive || ic.UpdateWhenInactive)
|
||||||
{
|
{
|
||||||
if (condition <= 0.0f)
|
if (!ic.UpdateWhenBroken && condition <= 0.0f)
|
||||||
{
|
{
|
||||||
ic.UpdateBroken(deltaTime, cam);
|
ic.UpdateBroken(deltaTime, cam);
|
||||||
}
|
}
|
||||||
@@ -2713,25 +2708,35 @@ namespace Barotrauma
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleCollision(float impact)
|
public void ReceiveImpact(float impactStrength, bool recursive = true)
|
||||||
{
|
{
|
||||||
OnCollisionProjSpecific(impact);
|
OnCollisionProjSpecific(impactStrength);
|
||||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||||
|
|
||||||
if (ImpactTolerance > 0.0f && Math.Abs(impact) > ImpactTolerance && hasStatusEffectsOfType[(int)ActionType.OnImpact])
|
if (ImpactTolerance > 0.0f && Math.Abs(impactStrength) > ImpactTolerance && Rand.Range(0.0f, 1.0f) < ImpactDamageProbability)
|
||||||
{
|
{
|
||||||
foreach (StatusEffect effect in statusEffectLists[ActionType.OnImpact])
|
if (ImpactDamage != 0.0f)
|
||||||
{
|
{
|
||||||
ApplyStatusEffect(effect, ActionType.OnImpact, deltaTime: 1.0f);
|
Condition -= impactStrength * ImpactDamage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasStatusEffectsOfType[(int)ActionType.OnImpact])
|
||||||
|
{
|
||||||
|
foreach (StatusEffect effect in statusEffectLists[ActionType.OnImpact])
|
||||||
|
{
|
||||||
|
ApplyStatusEffect(effect, ActionType.OnImpact, deltaTime: 1.0f);
|
||||||
|
}
|
||||||
#if SERVER
|
#if SERVER
|
||||||
GameMain.Server?.CreateEntityEvent(this, new ApplyStatusEffectEventData(ActionType.OnImpact));
|
GameMain.Server?.CreateEntityEvent(this, new ApplyStatusEffectEventData(ActionType.OnImpact));
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!recursive) { return; }
|
||||||
|
|
||||||
foreach (Item contained in ContainedItems)
|
foreach (Item contained in ContainedItems)
|
||||||
{
|
{
|
||||||
if (contained.body != null) { contained.HandleCollision(impact); }
|
if (contained.body != null) { contained.ReceiveImpact(impactStrength, recursive: true); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3698,18 +3703,15 @@ namespace Barotrauma
|
|||||||
SerializableProperty property = extraData.SerializableProperty;
|
SerializableProperty property = extraData.SerializableProperty;
|
||||||
ISerializableEntity entity = extraData.Entity;
|
ISerializableEntity entity = extraData.Entity;
|
||||||
|
|
||||||
msg.WriteVariableUInt32((uint)allProperties.Count);
|
|
||||||
|
|
||||||
if (property != null)
|
if (property != null)
|
||||||
{
|
{
|
||||||
if (allProperties.Count > 1)
|
if (allProperties.Count > 1)
|
||||||
{
|
{
|
||||||
int propertyIndex = allProperties.FindIndex(p => p.property == property && p.obj == entity);
|
if (allProperties.None(p => p.property == property && p.obj == entity))
|
||||||
if (propertyIndex < 0)
|
|
||||||
{
|
{
|
||||||
throw new Exception($"Could not find the property \"{property.Name}\" in \"{entity.Name ?? "null"}\"");
|
throw new Exception($"Could not find the property \"{property.Name}\" in \"{entity.Name ?? "null"}\"");
|
||||||
}
|
}
|
||||||
msg.WriteVariableUInt32((uint)propertyIndex);
|
msg.WriteIdentifier(property.Name.ToIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
object value = property.GetValue(entity);
|
object value = property.GetValue(entity);
|
||||||
@@ -3814,21 +3816,11 @@ namespace Barotrauma
|
|||||||
var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties<Editable>();
|
var allProperties = inGameEditableOnly ? GetInGameEditableProperties(ignoreConditions: true) : GetProperties<Editable>();
|
||||||
if (allProperties.Count == 0) { return; }
|
if (allProperties.Count == 0) { return; }
|
||||||
|
|
||||||
int propertyCount = (int)msg.ReadVariableUInt32();
|
Identifier propertyIdentifier = msg.ReadIdentifier();
|
||||||
if (propertyCount != allProperties.Count)
|
int propertyIndex = allProperties.IndexOf(p => p.property.Name == propertyIdentifier);
|
||||||
|
if (propertyIndex < 0)
|
||||||
{
|
{
|
||||||
throw new Exception($"Error in {nameof(ReadPropertyChange)}. The number of properties on the item \"{Prefab.Identifier}\" does not match between the server and the client. Server: {propertyCount}, client: {allProperties.Count}.");
|
throw new Exception($"Error in {nameof(ReadPropertyChange)}. Could not find the property \"{propertyIdentifier}\" in item \"{Prefab.Identifier}\" (property count: {allProperties.Count}, in-game editable only: {inGameEditableOnly})");
|
||||||
}
|
|
||||||
|
|
||||||
int propertyIndex = 0;
|
|
||||||
if (allProperties.Count > 1)
|
|
||||||
{
|
|
||||||
propertyIndex = (int)msg.ReadVariableUInt32();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (propertyIndex >= allProperties.Count || propertyIndex < 0)
|
|
||||||
{
|
|
||||||
throw new Exception($"Error in {nameof(ReadPropertyChange)}. Property index out of bounds (item: {Prefab.Identifier}, index: {propertyIndex}, property count: {allProperties.Count}, in-game editable only: {inGameEditableOnly})");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool allowEditing = true;
|
bool allowEditing = true;
|
||||||
|
|||||||
@@ -821,6 +821,15 @@ namespace Barotrauma
|
|||||||
set { impactTolerance = Math.Max(value, 0.0f); }
|
set { impactTolerance = Math.Max(value, 0.0f); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Serialize(0.0f, IsPropertySaveable.No, description: "The amount of damage the item takes from impacts. Acts as a multiplier on the strength of the impact. Note that ImpactTolerance must be set for impacts to register.")]
|
||||||
|
public float ImpactDamage { get; set; }
|
||||||
|
|
||||||
|
[Serialize(1.0f, IsPropertySaveable.No, description: "Probability for impacts to register. Defaults to 1. Note that ImpactTolerance must also be set for impacts to register.")]
|
||||||
|
public float ImpactDamageProbability { get; set; }
|
||||||
|
|
||||||
|
[Serialize(false, IsPropertySaveable.No, "If true, submarine impacts will trigger OnImpact effects. Only applies to items with a null or non-dynamic physics body - items with dynamic bodies always react to impacts.")]
|
||||||
|
public bool ReceiveSubmarineImpacts { get; set; }
|
||||||
|
|
||||||
[Serialize(0.0f, IsPropertySaveable.No)]
|
[Serialize(0.0f, IsPropertySaveable.No)]
|
||||||
public float OnDamagedThreshold { get; set; }
|
public float OnDamagedThreshold { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -102,12 +102,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Index of the slot the target must be in when targeting a Contained item
|
/// Index of the slot the target must be in when targeting a Contained item or a character inventory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int TargetSlot = -1;
|
public int TargetSlot = -1;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The slot type the target must be in when targeting an item contained inside a character's inventory
|
/// The slot type the target must be in when targeting an item contained inside a character's inventory.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public InvSlotType CharacterInventorySlotType;
|
public InvSlotType CharacterInventorySlotType;
|
||||||
|
|
||||||
@@ -329,7 +329,6 @@ namespace Barotrauma
|
|||||||
IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
|
IgnoreInEditor = element.GetAttributeBool("ignoreineditor", false);
|
||||||
MatchOnEmpty = element.GetAttributeBool("matchonempty", false);
|
MatchOnEmpty = element.GetAttributeBool("matchonempty", false);
|
||||||
TargetSlot = element.GetAttributeInt("targetslot", -1);
|
TargetSlot = element.GetAttributeInt("targetslot", -1);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool CheckRequirements(Character character, Item parentItem)
|
public bool CheckRequirements(Character character, Item parentItem)
|
||||||
@@ -344,22 +343,21 @@ namespace Barotrauma
|
|||||||
return CheckItem(parentItem.Container, this);
|
return CheckItem(parentItem.Container, this);
|
||||||
case RelationType.Equipped:
|
case RelationType.Equipped:
|
||||||
if (character == null) { return false; }
|
if (character == null) { return false; }
|
||||||
var heldItems = character.HeldItems;
|
foreach (var item in character.Inventory.AllItemsMod)
|
||||||
if (RequireOrMatchOnEmpty && heldItems.None()) { return true; }
|
|
||||||
foreach (Item equippedItem in heldItems)
|
|
||||||
{
|
{
|
||||||
if (equippedItem == null) { continue; }
|
if (character.HasEquippedItem(item) && CheckItem(item, this))
|
||||||
if (CheckItem(equippedItem, this))
|
|
||||||
{
|
{
|
||||||
if (RequireEmpty && equippedItem.Condition > 0) { return false; }
|
if (RequireEmpty && item.Condition > 0) { return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
//got this far -> no matching item was equipped
|
||||||
|
//return true if we require or want to match "empty" (no matching item), otherwise false
|
||||||
|
return RequireOrMatchOnEmpty;
|
||||||
case RelationType.Picked:
|
case RelationType.Picked:
|
||||||
if (character == null) { return false; }
|
if (character == null) { return false; }
|
||||||
if (character.Inventory == null) { return MatchOnEmpty || RequireEmpty; }
|
if (character.Inventory == null) { return MatchOnEmpty || RequireEmpty; }
|
||||||
var allItems = character.Inventory.AllItems;
|
var allItems = TargetSlot == -1 ? character.Inventory.AllItems : character.Inventory.GetItemsAt(TargetSlot);
|
||||||
if (RequireOrMatchOnEmpty && allItems.None()) { return true; }
|
if (RequireOrMatchOnEmpty && allItems.None()) { return true; }
|
||||||
foreach (Item pickedItem in allItems)
|
foreach (Item pickedItem in allItems)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -396,7 +396,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) && Attack.Afflictions.None())
|
if (Attack.Afflictions.None() &&
|
||||||
|
MathUtils.NearlyEqual(force, 0.0f) && MathUtils.NearlyEqual(Attack.Stun, 0.0f) &&
|
||||||
|
MathUtils.NearlyEqual(Attack.ItemDamage, 0.0f) &&
|
||||||
|
MathUtils.NearlyEqual(Attack.StructureDamage, 0.0f))
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -370,18 +370,31 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override void Update(float deltaTime, Camera cam)
|
public override void Update(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
|
Hull hull1 = linkedTo.Count < 1 ? null : linkedTo[0] as Hull;
|
||||||
|
Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
|
||||||
|
|
||||||
int updateInterval = 4;
|
int updateInterval = 4;
|
||||||
float flowMagnitude = flowForce.LengthSquared();
|
//if one hull is at lethal pressure (connected to outside), and the other not yet,
|
||||||
if (flowMagnitude < 1.0f)
|
//we need frequent updates to quickly move water into the other hull
|
||||||
|
if (hull1 != null && hull2 != null &&
|
||||||
|
hull1.LethalPressure > 0.0f != hull2.LethalPressure > 0.0f)
|
||||||
{
|
{
|
||||||
//very sparse updates if there's practically no water moving
|
|
||||||
updateInterval = 8;
|
|
||||||
}
|
|
||||||
else if (linkedTo.Count == 2 && flowMagnitude > 10.0f)
|
|
||||||
{
|
|
||||||
//frequent updates if water is moving between hulls
|
|
||||||
updateInterval = 1;
|
updateInterval = 1;
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float flowMagnitude = flowForce.LengthSquared();
|
||||||
|
if (flowMagnitude < 1.0f)
|
||||||
|
{
|
||||||
|
//very sparse updates if there's practically no water moving
|
||||||
|
updateInterval = 8;
|
||||||
|
}
|
||||||
|
else if (linkedTo.Count == 2 && flowMagnitude > 10.0f)
|
||||||
|
{
|
||||||
|
//frequent updates if water is moving between hulls
|
||||||
|
updateInterval = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
updateCount++;
|
updateCount++;
|
||||||
if (updateCount < updateInterval) { return; }
|
if (updateCount < updateInterval) { return; }
|
||||||
@@ -409,8 +422,6 @@ namespace Barotrauma
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Hull hull1 = (Hull)linkedTo[0];
|
|
||||||
Hull hull2 = linkedTo.Count < 2 ? null : (Hull)linkedTo[1];
|
|
||||||
if (hull1 == hull2) { return; }
|
if (hull1 == hull2) { return; }
|
||||||
|
|
||||||
UpdateOxygen(hull1, hull2, deltaTime);
|
UpdateOxygen(hull1, hull2, deltaTime);
|
||||||
@@ -469,6 +480,8 @@ namespace Barotrauma
|
|||||||
higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y);
|
higherSurface = Math.Max(hull1.Surface, hull2.Surface + subOffset.Y);
|
||||||
float delta = 0.0f;
|
float delta = 0.0f;
|
||||||
|
|
||||||
|
Hull flowSourceHull = null;
|
||||||
|
|
||||||
//water level is above the lower boundary of the gap
|
//water level is above the lower boundary of the gap
|
||||||
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size)
|
if (Math.Max(hull1.Surface + hull1.WaveY[hull1.WaveY.Length - 1], hull2.Surface + subOffset.Y + hull2.WaveY[0]) > rect.Y - Size)
|
||||||
{
|
{
|
||||||
@@ -479,10 +492,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!(hull2.WaterVolume > 0.0f)) { return; }
|
if (!(hull2.WaterVolume > 0.0f)) { return; }
|
||||||
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
|
lowerSurface = hull1.Surface - hull1.WaveY[hull1.WaveY.Length - 1];
|
||||||
//delta = Math.Min((room2.water.pressure - room1.water.pressure) * sizeModifier, Math.Min(room2.water.Volume, room2.Volume));
|
|
||||||
//delta = Math.Min(delta, room1.Volume - room1.water.Volume + Water.MaxCompress);
|
|
||||||
|
|
||||||
flowTargetHull = hull1;
|
flowTargetHull = hull1;
|
||||||
|
flowSourceHull = hull2;
|
||||||
|
|
||||||
//make sure not to move more than what the room contains
|
//make sure not to move more than what the room contains
|
||||||
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 300.0f * sizeModifier * deltaTime, Math.Min(hull2.WaterVolume, hull2.Volume));
|
delta = Math.Min(((hull2.Pressure + subOffset.Y) - hull1.Pressure) * 300.0f * sizeModifier * deltaTime, Math.Min(hull2.WaterVolume, hull2.Volume));
|
||||||
@@ -504,6 +516,7 @@ namespace Barotrauma
|
|||||||
lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
|
lowerSurface = hull2.Surface - hull2.WaveY[hull2.WaveY.Length - 1];
|
||||||
|
|
||||||
flowTargetHull = hull2;
|
flowTargetHull = hull2;
|
||||||
|
flowSourceHull = hull1;
|
||||||
|
|
||||||
//make sure not to move more than what the room contains
|
//make sure not to move more than what the room contains
|
||||||
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 300.0f * sizeModifier * deltaTime, Math.Min(hull1.WaterVolume, hull1.Volume));
|
delta = Math.Min((hull1.Pressure - (hull2.Pressure + subOffset.Y)) * 300.0f * sizeModifier * deltaTime, Math.Min(hull1.WaterVolume, hull1.Volume));
|
||||||
@@ -547,7 +560,6 @@ namespace Barotrauma
|
|||||||
if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
|
if (hull2.Pressure + subOffset.Y > hull1.Pressure && hull2.WaterVolume > 0.0f)
|
||||||
{
|
{
|
||||||
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + (hull2.Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier);
|
float delta = Math.Min(hull2.WaterVolume - hull2.Volume + (hull2.Volume * Hull.MaxCompress), deltaTime * 8000.0f * sizeModifier);
|
||||||
|
|
||||||
//make sure not to place more water to the target room than it can hold
|
//make sure not to place more water to the target room than it can hold
|
||||||
if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress)
|
if (hull1.WaterVolume + delta > hull1.Volume * Hull.MaxCompress)
|
||||||
{
|
{
|
||||||
@@ -623,19 +635,30 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UpdateRoomToOut(float deltaTime, Hull hull1)
|
/// <summary>
|
||||||
|
/// How much water can flow through the gap to the hull if the gap is connected outside.
|
||||||
|
/// </summary>
|
||||||
|
private float GetWaterFlowFromOutside(Hull hull, float deltaTime, bool ignoreCurrentWater = false)
|
||||||
{
|
{
|
||||||
//a variable affecting the water flow through the gap
|
//a variable affecting the water flow through the gap
|
||||||
//the larger the gap is, the faster the water flows
|
//the larger the gap is, the faster the water flows
|
||||||
float sizeModifier = Size * open * open * (1.0f - overlappingGapFlowRateReduction);
|
float sizeModifier = Size * open * open * (1.0f - overlappingGapFlowRateReduction);
|
||||||
|
|
||||||
float delta = 500.0f * sizeModifier * deltaTime;
|
float delta = 500.0f * sizeModifier * deltaTime;
|
||||||
|
if (!ignoreCurrentWater)
|
||||||
|
{
|
||||||
|
delta = Math.Min(delta, hull.Volume * Hull.MaxCompress - hull.WaterVolume);
|
||||||
|
}
|
||||||
|
return delta;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UpdateRoomToOut(float deltaTime, Hull hull1)
|
||||||
|
{
|
||||||
|
float delta = GetWaterFlowFromOutside(hull1, deltaTime);
|
||||||
|
|
||||||
//make sure not to place more water to the target room than it can hold
|
//make sure not to place more water to the target room than it can hold
|
||||||
delta = Math.Min(delta, hull1.Volume * Hull.MaxCompress - hull1.WaterVolume);
|
|
||||||
hull1.WaterVolume += delta;
|
hull1.WaterVolume += delta;
|
||||||
|
|
||||||
if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 30.0f * deltaTime; }
|
if (hull1.WaterVolume > hull1.Volume) { hull1.Pressure += 100.0f * deltaTime; }
|
||||||
|
|
||||||
flowTargetHull = hull1;
|
flowTargetHull = hull1;
|
||||||
|
|
||||||
@@ -698,6 +721,65 @@ namespace Barotrauma
|
|||||||
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
|
hull1.LethalPressure += ((Submarine != null && Submarine.AtDamageDepth) ? 100.0f : Hull.PressureBuildUpSpeed) * PressureDistributionSpeed * deltaTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hull1.LethalPressure > 0)
|
||||||
|
{
|
||||||
|
SimulateWaterFlowFromOutsideToConnectedHulls(hull1, maxFlow: GetWaterFlowFromOutside(hull1, deltaTime, ignoreCurrentWater: true), deltaTime: deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Hull GetOtherLinkedHull(Hull hull1)
|
||||||
|
{
|
||||||
|
if (linkedTo.Count != 2 || hull1 == null) { return null; }
|
||||||
|
return (linkedTo[0] == hull1 ? linkedTo[1] : linkedTo[0]) as Hull;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static readonly HashSet<Hull> checkedHulls = new HashSet<Hull>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Simulates water flow from the source to all the hulls it's connected to across the sub, as if the water was coming directly from outside.
|
||||||
|
/// Used to prevent gaps from slowing down flooding when hulls are directly connected outside and highly pressurized.
|
||||||
|
/// </summary>
|
||||||
|
void SimulateWaterFlowFromOutsideToConnectedHulls(Hull hull, float maxFlow, float deltaTime)
|
||||||
|
{
|
||||||
|
checkedHulls.Clear();
|
||||||
|
checkedHulls.Add(hull);
|
||||||
|
foreach (var connectedGap in hull.ConnectedGaps)
|
||||||
|
{
|
||||||
|
if (connectedGap == this || !connectedGap.IsRoomToRoom || connectedGap.open <= 0.0f) { continue; }
|
||||||
|
var otherHull = connectedGap.GetOtherLinkedHull(hull);
|
||||||
|
if (otherHull == null) { continue; }
|
||||||
|
SimulateWaterFlowFromOutsideToConnectedHullsRecursive(otherHull, connectedGap, checkedHulls, hull, maxFlow, deltaTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SimulateWaterFlowFromOutsideToConnectedHullsRecursive(Hull targetHull, Gap gap, HashSet<Hull> checkedHulls, Hull originHull, float maxFlow, float deltaTime)
|
||||||
|
{
|
||||||
|
const float decay = 0.95f;
|
||||||
|
|
||||||
|
maxFlow = Math.Min(maxFlow, gap.GetWaterFlowFromOutside(targetHull, deltaTime, ignoreCurrentWater: true)) * decay;
|
||||||
|
if (maxFlow <= 0.001f) { return; }
|
||||||
|
|
||||||
|
checkedHulls.Add(targetHull);
|
||||||
|
|
||||||
|
//don't multiply by deltatime here, we already did that in GetWaterFlowFromOutside
|
||||||
|
targetHull.WaterVolume += maxFlow;
|
||||||
|
//lerp lethal pressure up very fast
|
||||||
|
if (targetHull.WaterVolume > targetHull.Volume)
|
||||||
|
{
|
||||||
|
targetHull.LethalPressure = Math.Max(targetHull.LethalPressure, MathHelper.Lerp(targetHull.LethalPressure, originHull.LethalPressure, 0.1f));
|
||||||
|
}
|
||||||
|
|
||||||
|
//stop pushing water to the following hulls once we get to a hull that's not at high pressure yet
|
||||||
|
if (targetHull.LethalPressure <= 0 || targetHull.WaterVolume < targetHull.Volume) { return; }
|
||||||
|
|
||||||
|
foreach (var connectedGap in targetHull.ConnectedGaps)
|
||||||
|
{
|
||||||
|
if (connectedGap == gap || !connectedGap.IsRoomToRoom || connectedGap.open <= 0.0f) { continue; }
|
||||||
|
var otherHull = connectedGap.GetOtherLinkedHull(targetHull);
|
||||||
|
if (otherHull == null || checkedHulls.Contains(otherHull)) { continue; }
|
||||||
|
SimulateWaterFlowFromOutsideToConnectedHullsRecursive(otherHull, connectedGap, checkedHulls, originHull, maxFlow, deltaTime);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool RefreshOutsideCollider()
|
public bool RefreshOutsideCollider()
|
||||||
@@ -884,6 +966,8 @@ namespace Barotrauma
|
|||||||
base.Remove();
|
base.Remove();
|
||||||
GapList.Remove(this);
|
GapList.Remove(this);
|
||||||
|
|
||||||
|
checkedHulls.Clear();
|
||||||
|
|
||||||
foreach (Hull hull in Hull.HullList)
|
foreach (Hull hull in Hull.HullList)
|
||||||
{
|
{
|
||||||
hull.ConnectedGaps.Remove(this);
|
hull.ConnectedGaps.Remove(this);
|
||||||
|
|||||||
@@ -785,7 +785,7 @@ namespace Barotrauma
|
|||||||
#region Shared network write
|
#region Shared network write
|
||||||
private void SharedStatusWrite(IWriteMessage msg)
|
private void SharedStatusWrite(IWriteMessage msg)
|
||||||
{
|
{
|
||||||
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
|
msg.WriteSingle(waterVolume);
|
||||||
|
|
||||||
System.Diagnostics.Debug.Assert(FireSources.Count <= MaxFireSources, $"Too many fire sources ({FireSources.Count}) in hull {ID} (max {MaxFireSources}).");
|
System.Diagnostics.Debug.Assert(FireSources.Count <= MaxFireSources, $"Too many fire sources ({FireSources.Count}) in hull {ID} (max {MaxFireSources}).");
|
||||||
msg.WriteRangedInteger(Math.Min(FireSources.Count, MaxFireSources), 0, MaxFireSources);
|
msg.WriteRangedInteger(Math.Min(FireSources.Count, MaxFireSources), 0, MaxFireSources);
|
||||||
@@ -833,7 +833,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SharedStatusRead(IReadMessage msg, out float newWaterVolume, out NetworkFireSource[] newFireSources)
|
private void SharedStatusRead(IReadMessage msg, out float newWaterVolume, out NetworkFireSource[] newFireSources)
|
||||||
{
|
{
|
||||||
newWaterVolume = msg.ReadRangedSingle(0.0f, 1.5f, 8) * Volume;
|
newWaterVolume = msg.ReadSingle();
|
||||||
|
|
||||||
int fireSourceCount = msg.ReadRangedInteger(0, MaxFireSources);
|
int fireSourceCount = msg.ReadRangedInteger(0, MaxFireSources);
|
||||||
newFireSources = new NetworkFireSource[fireSourceCount];
|
newFireSources = new NetworkFireSource[fireSourceCount];
|
||||||
@@ -1269,6 +1269,23 @@ namespace Barotrauma
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Recursively find all the hulls linked to the specified hull.
|
||||||
|
/// </summary>
|
||||||
|
public void GetLinkedHulls(List<Hull> linkedHulls, bool includeHiddenHulls = false)
|
||||||
|
{
|
||||||
|
foreach (var linkedEntity in linkedTo)
|
||||||
|
{
|
||||||
|
if (linkedEntity is Hull linkedHull)
|
||||||
|
{
|
||||||
|
if (linkedHulls.Contains(linkedHull)) { continue; }
|
||||||
|
if (!includeHiddenHulls && linkedHull.IsHidden) { continue; }
|
||||||
|
linkedHulls.Add(linkedHull);
|
||||||
|
linkedHull.GetLinkedHulls(linkedHulls, includeHiddenHulls);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void DetectItemVisibility(Character c=null)
|
public static void DetectItemVisibility(Character c=null)
|
||||||
{
|
{
|
||||||
if (c==null)
|
if (c==null)
|
||||||
|
|||||||
@@ -4291,6 +4291,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
placeableWrecks.RemoveAt(i);
|
placeableWrecks.RemoveAt(i);
|
||||||
}
|
}
|
||||||
|
// Exclude wrecks that have mission tags, those can't show up randomly
|
||||||
|
else if (wreckInfo.MissionTags.Count != 0)
|
||||||
|
{
|
||||||
|
placeableWrecks.RemoveAt(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (placeableWrecks.None())
|
if (placeableWrecks.None())
|
||||||
{
|
{
|
||||||
@@ -4742,6 +4747,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
beaconStationFiles.RemoveAt(i);
|
beaconStationFiles.RemoveAt(i);
|
||||||
}
|
}
|
||||||
|
// Exclude beacons that have mission tags, those can't show up randomly
|
||||||
|
else if (beaconInfo.MissionTags.Count != 0)
|
||||||
|
{
|
||||||
|
beaconStationFiles.RemoveAt(i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (beaconStationFiles.None())
|
if (beaconStationFiles.None())
|
||||||
@@ -4926,17 +4936,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1);
|
int corpseCount = Rand.Range(Loaded.GenerationParams.MinCorpseCount, Loaded.GenerationParams.MaxCorpseCount + 1);
|
||||||
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
|
var allSpawnPoints = WayPoint.WayPointList.FindAll(wp => wp.Submarine == wreck && wp.CurrentHull != null);
|
||||||
var pathPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Path);
|
var humanSpawnPoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Human);
|
||||||
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
|
var corpsePoints = allSpawnPoints.FindAll(wp => wp.SpawnType == SpawnType.Corpse);
|
||||||
if (!corpsePoints.Any() && !pathPoints.Any()) { continue; }
|
if (corpsePoints.None() && humanSpawnPoints.None()) { continue; }
|
||||||
pathPoints.Shuffle(Rand.RandSync.ServerAndClient);
|
humanSpawnPoints.Shuffle(Rand.RandSync.ServerAndClient);
|
||||||
// Sort by job so that we first spawn those with a predefined job (might have special id cards)
|
// Sort by job so that we first spawn those with a predefined job (might have special id cards)
|
||||||
corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList();
|
corpsePoints = corpsePoints.OrderBy(p => p.AssignedJob == null).ThenBy(p => Rand.Value()).ToList();
|
||||||
var usedJobs = new HashSet<JobPrefab>();
|
var usedJobs = new HashSet<JobPrefab>();
|
||||||
int spawnCounter = 0;
|
int spawnCounter = 0;
|
||||||
for (int j = 0; j < corpseCount; j++)
|
for (int j = 0; j < corpseCount; j++)
|
||||||
{
|
{
|
||||||
WayPoint sp = corpsePoints.FirstOrDefault() ?? pathPoints.FirstOrDefault();
|
WayPoint sp = corpsePoints.FirstOrDefault() ?? humanSpawnPoints.FirstOrDefault();
|
||||||
JobPrefab job = sp?.AssignedJob;
|
JobPrefab job = sp?.AssignedJob;
|
||||||
CorpsePrefab selectedPrefab;
|
CorpsePrefab selectedPrefab;
|
||||||
if (job == null)
|
if (job == null)
|
||||||
@@ -4949,8 +4959,8 @@ namespace Barotrauma
|
|||||||
if (selectedPrefab == null)
|
if (selectedPrefab == null)
|
||||||
{
|
{
|
||||||
corpsePoints.Remove(sp);
|
corpsePoints.Remove(sp);
|
||||||
pathPoints.Remove(sp);
|
humanSpawnPoints.Remove(sp);
|
||||||
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? pathPoints.FirstOrDefault(sp => sp.AssignedJob == null);
|
sp = corpsePoints.FirstOrDefault(sp => sp.AssignedJob == null) ?? humanSpawnPoints.FirstOrDefault(sp => sp.AssignedJob == null);
|
||||||
// Deduce the job from the selected prefab
|
// Deduce the job from the selected prefab
|
||||||
selectedPrefab = GetCorpsePrefab(usedJobs);
|
selectedPrefab = GetCorpsePrefab(usedJobs);
|
||||||
if (selectedPrefab != null)
|
if (selectedPrefab != null)
|
||||||
@@ -4972,7 +4982,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
worldPos = sp.WorldPosition;
|
worldPos = sp.WorldPosition;
|
||||||
corpsePoints.Remove(sp);
|
corpsePoints.Remove(sp);
|
||||||
pathPoints.Remove(sp);
|
humanSpawnPoints.Remove(sp);
|
||||||
}
|
}
|
||||||
|
|
||||||
job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p));
|
job ??= selectedPrefab.GetJobPrefab(predicate: p => !usedJobs.Contains(p));
|
||||||
|
|||||||
@@ -473,7 +473,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
|
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry) || m.ForceFailure);
|
||||||
return availableMissions;
|
return availableMissions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1091,6 +1091,12 @@ namespace Barotrauma
|
|||||||
mission.TimesAttempted = loadedMission.TimesAttempted;
|
mission.TimesAttempted = loadedMission.TimesAttempted;
|
||||||
availableMissions.Add(mission);
|
availableMissions.Add(mission);
|
||||||
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
|
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
|
||||||
|
|
||||||
|
var levelData = destination == this ? LevelData : Connections.FirstOrDefault(c => c.OtherLocation(this) == destination)?.LevelData;
|
||||||
|
if (levelData != null)
|
||||||
|
{
|
||||||
|
mission.AdjustLevelData(levelData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loadedMissions = null;
|
loadedMissions = null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry));
|
availableMissions.RemoveAll(m => m.Completed || (m.Failed && !m.Prefab.AllowRetry) || m.ForceFailure);
|
||||||
return availableMissions;
|
return availableMissions;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -235,15 +235,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
//backwards compatibility (or support for loading maps created with mods that modify the end biome setup):
|
//backwards compatibility (or support for loading maps created with mods that modify the end biome setup):
|
||||||
//if there's too few end locations, create more
|
//if there's too few end locations, create more
|
||||||
int missingOutpostCount = endLocations.First().Biome.EndBiomeLocationCount - endLocations.Count;
|
|
||||||
|
|
||||||
Location firstEndLocation = EndLocations[0];
|
Location firstEndLocation = EndLocations[0];
|
||||||
|
Biome endBiome = firstEndLocation.Biome;
|
||||||
|
int missingOutpostCount = endBiome.EndBiomeLocationCount - endLocations.Count;
|
||||||
|
|
||||||
for (int i = 0; i < missingOutpostCount; i++)
|
for (int i = 0; i < missingOutpostCount; i++)
|
||||||
{
|
{
|
||||||
Vector2 mapPos = new Vector2(
|
Vector2 mapPos = new Vector2(
|
||||||
MathHelper.Lerp(firstEndLocation.MapPosition.X, Width, MathHelper.Lerp(0.2f, 0.8f, i / (float)missingOutpostCount)),
|
MathHelper.Lerp(firstEndLocation.MapPosition.X, Width, MathHelper.Lerp(0.2f, 0.8f, i / (float)missingOutpostCount)),
|
||||||
Height * MathHelper.Lerp(0.2f, 1.0f, (float)rand.NextDouble()));
|
Height * MathHelper.Lerp(0.2f, 1.0f, (float)rand.NextDouble()));
|
||||||
var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, firstEndLocation.Biome.Identifier, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations);
|
var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, endBiome.Identifier, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations);
|
||||||
|
newEndLocation.Biome = endBiome;
|
||||||
newEndLocation.LevelData = new LevelData(newEndLocation, this, difficulty: 100.0f);
|
newEndLocation.LevelData = new LevelData(newEndLocation, this, difficulty: 100.0f);
|
||||||
Locations.Add(newEndLocation);
|
Locations.Add(newEndLocation);
|
||||||
endLocations.Add(newEndLocation);
|
endLocations.Add(newEndLocation);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user