Build 0.20.7.0
This commit is contained in:
@@ -657,12 +657,9 @@ namespace Barotrauma
|
||||
{
|
||||
if (Controlled == null || Controlled == this || (Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
|
||||
{
|
||||
InvisibleTimer = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
InvisibleTimer -= deltaTime;
|
||||
InvisibleTimer = Math.Min(InvisibleTimer, 1.0f);
|
||||
}
|
||||
InvisibleTimer -= deltaTime;
|
||||
}
|
||||
|
||||
foreach (GUIMessage message in guiMessages)
|
||||
|
||||
@@ -394,6 +394,7 @@ namespace Barotrauma
|
||||
|
||||
showHiddenAfflictionsButton = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: "GUIButtonCircular")
|
||||
{
|
||||
Visible = false,
|
||||
CanBeFocused = false
|
||||
};
|
||||
|
||||
|
||||
@@ -2219,6 +2219,15 @@ namespace Barotrauma
|
||||
}
|
||||
}));
|
||||
|
||||
commands.Add(new Command("spawnallitems", "", (string[] args) =>
|
||||
{
|
||||
var cursorPos = Screen.Selected.Cam?.ScreenToWorld(PlayerInput.MousePosition) ?? Vector2.Zero;
|
||||
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, cursorPos);
|
||||
}
|
||||
}));
|
||||
|
||||
commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) =>
|
||||
{
|
||||
float defaultZoom = Screen.Selected.Cam.DefaultZoom;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma;
|
||||
@@ -10,37 +11,66 @@ partial class UIHighlightAction : EventAction
|
||||
partial void UpdateProjSpecific()
|
||||
{
|
||||
bool useCircularFlash = false;
|
||||
GUIComponent component = null;
|
||||
|
||||
if (Id != ElementId.None)
|
||||
{
|
||||
component = GUI.GetAdditions().FirstOrDefault(c => Equals(Id, c.UserData));
|
||||
FindAndFlashComponents(c => Equals(Id, c.UserData));
|
||||
}
|
||||
else if (!EntityIdentifier.IsEmpty)
|
||||
{
|
||||
component = GUI.GetAdditions().FirstOrDefault(c =>
|
||||
FindAndFlashComponents(c =>
|
||||
c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier);
|
||||
}
|
||||
else if (!OrderIdentifier.IsEmpty)
|
||||
{
|
||||
useCircularFlash = true;
|
||||
bool foundMinimapNode = false;
|
||||
if (!OrderTargetTag.IsEmpty)
|
||||
{
|
||||
component =
|
||||
GUI.GetAdditions().FirstOrDefault(c =>
|
||||
c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order &&
|
||||
order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag));
|
||||
foundMinimapNode = FindAndFlashComponents(c =>
|
||||
c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order &&
|
||||
order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag));
|
||||
}
|
||||
if (!foundMinimapNode)
|
||||
{
|
||||
FindAndFlashComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption,
|
||||
c => c.UserData is Order order && order.Identifier == OrderIdentifier,
|
||||
c => Equals(OrderCategory, c.UserData));
|
||||
}
|
||||
component ??=
|
||||
GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption) ??
|
||||
GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier) ??
|
||||
GUI.GetAdditions().FirstOrDefault(c => Equals(OrderCategory, c.UserData));
|
||||
}
|
||||
|
||||
if (component != null && component.FlashTimer <= 0.0f)
|
||||
bool FindAndFlashComponents(params Func<GUIComponent, bool>[] predicates)
|
||||
{
|
||||
component.Flash(highlightColor, useCircularFlash: useCircularFlash);
|
||||
component.Bounce |= Bounce;
|
||||
foreach (var predicate in predicates)
|
||||
{
|
||||
if (HighlightMultiple)
|
||||
{
|
||||
bool found = false;
|
||||
foreach (var component in GUI.GetAdditions())
|
||||
{
|
||||
if (predicate(component))
|
||||
{
|
||||
Flash(component);
|
||||
found = true;
|
||||
}
|
||||
};
|
||||
return found;
|
||||
}
|
||||
else if (GUI.GetAdditions().FirstOrDefault(predicate) is GUIComponent component)
|
||||
{
|
||||
Flash(component);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void Flash(GUIComponent component)
|
||||
{
|
||||
if (component.FlashTimer <= 0.0f)
|
||||
{
|
||||
component.Flash(highlightColor, useCircularFlash: useCircularFlash);
|
||||
component.Bounce |= Bounce;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Threading;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -37,7 +38,7 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool IsCJK
|
||||
public TextManager.SpeciallyHandledCharCategory SpeciallyHandledCharCategory
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -84,17 +85,35 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element)
|
||||
=> TextManager.SpeciallyHandledCharCategories
|
||||
.Where(category => element.GetAttributeBool($"is{category}", category switch {
|
||||
// CJK isn't supported by default
|
||||
TextManager.SpeciallyHandledCharCategory.CJK => false,
|
||||
|
||||
// For backwards compatibility, we assume that Cyrillic is supported by default
|
||||
TextManager.SpeciallyHandledCharCategory.Cyrillic => true,
|
||||
|
||||
_ => throw new Exception("unreachable")
|
||||
}))
|
||||
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
|
||||
|
||||
public ScalableFont(ContentXElement element, GraphicsDevice gd = null)
|
||||
: this(
|
||||
element.GetAttributeContentPath("file")?.Value,
|
||||
(uint)element.GetAttributeInt("size", 14),
|
||||
gd,
|
||||
element.GetAttributeBool("dynamicloading", false),
|
||||
element.GetAttributeBool("iscjk", false))
|
||||
ExtractShccFromXElement(element))
|
||||
{
|
||||
}
|
||||
|
||||
public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false, bool isCJK = false)
|
||||
public ScalableFont(
|
||||
string filename,
|
||||
uint size,
|
||||
GraphicsDevice gd = null,
|
||||
bool dynamicLoading = false,
|
||||
TextManager.SpeciallyHandledCharCategory speciallyHandledCharCategory = TextManager.SpeciallyHandledCharCategory.None)
|
||||
{
|
||||
lock (globalMutex)
|
||||
{
|
||||
@@ -120,7 +139,7 @@ namespace Barotrauma
|
||||
this.textures = new List<Texture2D>();
|
||||
this.texCoords = new Dictionary<uint, GlyphData>();
|
||||
this.DynamicLoading = dynamicLoading;
|
||||
this.IsCJK = isCJK;
|
||||
this.SpeciallyHandledCharCategory = speciallyHandledCharCategory;
|
||||
this.graphicsDevice = gd;
|
||||
|
||||
if (gd != null && !dynamicLoading)
|
||||
|
||||
@@ -150,7 +150,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: fix implicit hiding
|
||||
public override bool Selected
|
||||
{
|
||||
get { return isSelected; }
|
||||
|
||||
@@ -179,6 +179,11 @@ namespace Barotrauma
|
||||
private set;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If enabled, the value wraps around to Max when you go below Min, and vice versa
|
||||
/// </summary>
|
||||
public bool WrapAround;
|
||||
|
||||
public float valueStep;
|
||||
|
||||
private float pressedTimer;
|
||||
@@ -403,13 +408,19 @@ namespace Barotrauma
|
||||
{
|
||||
if (MinValueFloat != null)
|
||||
{
|
||||
floatValue = Math.Max(floatValue, MinValueFloat.Value);
|
||||
MinusButton.Enabled = floatValue > MinValueFloat;
|
||||
floatValue =
|
||||
WrapAround && MinValueFloat.HasValue && floatValue < MinValueFloat.Value ?
|
||||
MaxValueFloat.Value :
|
||||
Math.Max(floatValue, MinValueFloat.Value);
|
||||
MinusButton.Enabled = WrapAround || floatValue > MinValueFloat;
|
||||
}
|
||||
if (MaxValueFloat != null)
|
||||
{
|
||||
floatValue = Math.Min(floatValue, MaxValueFloat.Value);
|
||||
PlusButton.Enabled = floatValue < MaxValueFloat;
|
||||
floatValue =
|
||||
WrapAround && MaxValueFloat.HasValue && floatValue > MaxValueFloat.Value ?
|
||||
MinValueFloat.Value :
|
||||
Math.Min(floatValue, MaxValueFloat.Value);
|
||||
PlusButton.Enabled = WrapAround || floatValue < MaxValueFloat;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,16 +428,16 @@ namespace Barotrauma
|
||||
{
|
||||
if (MinValueInt != null && intValue < MinValueInt.Value)
|
||||
{
|
||||
intValue = Math.Max(intValue, MinValueInt.Value);
|
||||
intValue = WrapAround && MaxValueInt.HasValue ? MaxValueInt.Value : Math.Max(intValue, MinValueInt.Value);
|
||||
UpdateText();
|
||||
}
|
||||
if (MaxValueInt != null && intValue > MaxValueInt.Value)
|
||||
{
|
||||
intValue = Math.Min(intValue, MaxValueInt.Value);
|
||||
intValue = WrapAround && MinValueInt.HasValue ? MinValueInt.Value : Math.Min(intValue, MaxValueInt.Value);
|
||||
UpdateText();
|
||||
}
|
||||
PlusButton.Enabled = MaxValueInt == null || intValue < MaxValueInt;
|
||||
MinusButton.Enabled = MinValueInt == null || intValue > MinValueInt;
|
||||
PlusButton.Enabled = WrapAround || MaxValueInt == null || intValue < MaxValueInt;
|
||||
MinusButton.Enabled = WrapAround || MinValueInt == null || intValue > MinValueInt;
|
||||
}
|
||||
|
||||
private void UpdateText()
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -45,16 +46,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private ScalableFont cjkFont;
|
||||
private ImmutableDictionary<TextManager.SpeciallyHandledCharCategory, ScalableFont> specialHandlingFonts;
|
||||
|
||||
public ScalableFont CjkFont
|
||||
public ScalableFont GetFontForCategory(TextManager.SpeciallyHandledCharCategory category)
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); }
|
||||
if (font.IsCJK) { return font; }
|
||||
return cjkFont;
|
||||
}
|
||||
if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); }
|
||||
if (font.SpeciallyHandledCharCategory.HasFlag(category)) { return font; }
|
||||
return specialHandlingFonts.TryGetValue(category, out var resultFont)
|
||||
? resultFont
|
||||
: specialHandlingFonts.TryGetValue(TextManager.SpeciallyHandledCharCategory.CJK, out resultFont)
|
||||
? resultFont
|
||||
: font;
|
||||
}
|
||||
|
||||
public LanguageIdentifier Language { get; private set; }
|
||||
@@ -70,40 +72,68 @@ namespace Barotrauma
|
||||
string fontPath = GetFontFilePath(element);
|
||||
uint size = GetFontSize(element);
|
||||
bool dynamicLoading = GetFontDynamicLoading(element);
|
||||
bool isCJK = GetIsCJK(element);
|
||||
var shcc = GetShcc(element);
|
||||
font?.Dispose();
|
||||
cjkFont?.Dispose();
|
||||
font = new ScalableFont(fontPath, size, GameMain.Instance.GraphicsDevice, dynamicLoading, isCJK)
|
||||
specialHandlingFonts?.Values.ForEach(f => f.Dispose());
|
||||
font = new ScalableFont(
|
||||
fontPath,
|
||||
size,
|
||||
GameMain.Instance.GraphicsDevice,
|
||||
dynamicLoading,
|
||||
shcc)
|
||||
{
|
||||
ForceUpperCase = element.GetAttributeBool("forceuppercase", false)
|
||||
};
|
||||
if (!isCJK)
|
||||
|
||||
var fallbackFonts = new Dictionary<TextManager.SpeciallyHandledCharCategory, ScalableFont>();
|
||||
foreach (var flag in TextManager.SpeciallyHandledCharCategories)
|
||||
{
|
||||
cjkFont = ExtractCjkFont(element)
|
||||
?? new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
|
||||
font.Size, GameMain.Instance.GraphicsDevice, dynamicLoading: true, isCJK: true);
|
||||
cjkFont.ForceUpperCase = font.ForceUpperCase;
|
||||
if (shcc.HasFlag(flag)) { continue; }
|
||||
|
||||
var extractedFont = ExtractFont(flag, element);
|
||||
if (extractedFont is null) { continue; }
|
||||
fallbackFonts.Add(flag, extractedFont);
|
||||
}
|
||||
fallbackFonts.Values.ForEach(ff => ff.ForceUpperCase = font.ForceUpperCase);
|
||||
specialHandlingFonts = fallbackFonts.ToImmutableDictionary();
|
||||
Language = GameSettings.CurrentConfig.Language;
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
font?.Dispose(); font = null;
|
||||
cjkFont?.Dispose(); cjkFont = null;
|
||||
font?.Dispose();
|
||||
font = null;
|
||||
specialHandlingFonts?.Values.ForEach(f => f.Dispose());
|
||||
specialHandlingFonts = null;
|
||||
}
|
||||
|
||||
private ScalableFont ExtractCjkFont(ContentXElement element)
|
||||
private ScalableFont ExtractFont(TextManager.SpeciallyHandledCharCategory flag, ContentXElement element)
|
||||
{
|
||||
foreach (var subElement in element.Elements().Reverse())
|
||||
{
|
||||
if (subElement.NameAsIdentifier() != "override") { continue; }
|
||||
if (subElement.GetAttributeBool("iscjk", false))
|
||||
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag))
|
||||
{
|
||||
return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
|
||||
ScalableFont hardcodedFallback(string path)
|
||||
=> new ScalableFont(
|
||||
path,
|
||||
font.Size,
|
||||
GameMain.Instance.GraphicsDevice,
|
||||
dynamicLoading: true,
|
||||
speciallyHandledCharCategory: flag);
|
||||
|
||||
return flag switch
|
||||
{
|
||||
TextManager.SpeciallyHandledCharCategory.CJK
|
||||
=> hardcodedFallback("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf"),
|
||||
TextManager.SpeciallyHandledCharCategory.Cyrillic
|
||||
=> hardcodedFallback("Content/Fonts/Oswald-Bold.ttf"),
|
||||
_ => null
|
||||
};
|
||||
}
|
||||
|
||||
private string GetFontFilePath(ContentXElement element)
|
||||
@@ -154,21 +184,21 @@ namespace Barotrauma
|
||||
return element.GetAttributeBool("dynamicloading", false);
|
||||
}
|
||||
|
||||
private bool GetIsCJK(XElement element)
|
||||
private TextManager.SpeciallyHandledCharCategory GetShcc(XElement element)
|
||||
{
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (IsValidOverride(subElement))
|
||||
{
|
||||
return subElement.GetAttributeBool("iscjk", false);
|
||||
return ScalableFont.ExtractShccFromXElement(subElement);
|
||||
}
|
||||
}
|
||||
return element.GetAttributeBool("iscjk", false);
|
||||
return ScalableFont.ExtractShccFromXElement(element);
|
||||
}
|
||||
|
||||
private bool IsValidOverride(XElement element)
|
||||
{
|
||||
if (!element.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { return false; }
|
||||
if (!element.IsOverride()) { return false; }
|
||||
var languages = element.GetAttributeIdentifierArray("language", Array.Empty<Identifier>());
|
||||
return languages.Any(l => l.ToLanguageIdentifier() == GameSettings.CurrentConfig.Language);
|
||||
}
|
||||
@@ -191,7 +221,7 @@ namespace Barotrauma
|
||||
private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
|
||||
|
||||
public ScalableFont GetFontForStr(string str) =>
|
||||
TextManager.IsCJK(str) ? Prefabs.ActivePrefab.CjkFont : Prefabs.ActivePrefab.Font;
|
||||
Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str));
|
||||
|
||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
||||
{
|
||||
|
||||
@@ -617,9 +617,9 @@ namespace Barotrauma
|
||||
Stretch = true
|
||||
};
|
||||
var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style: null);
|
||||
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
|
||||
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
|
||||
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
|
||||
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true };
|
||||
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true };
|
||||
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true };
|
||||
|
||||
var relevantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
|
||||
{
|
||||
|
||||
@@ -325,7 +325,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
var specializationList = GetSpecializationList();
|
||||
GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height));
|
||||
GetSpecializationList().Content.RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height), resizeChildren: false);
|
||||
|
||||
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
|
||||
|
||||
@@ -439,7 +439,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
if (character is null) { return false; }
|
||||
|
||||
|
||||
Identifier talentIdentifier = (Identifier)userData;
|
||||
if (talentOption.MaxChosenTalents is 1)
|
||||
{
|
||||
// deselect other buttons in tier by removing their selected talents from pool
|
||||
@@ -454,9 +455,11 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
Identifier talentIdentifier = (Identifier)userData;
|
||||
|
||||
if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents))
|
||||
if (character.HasTalent(talentIdentifier))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
else if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents))
|
||||
{
|
||||
if (!selectedTalents.Contains(talentIdentifier))
|
||||
{
|
||||
@@ -467,7 +470,7 @@ namespace Barotrauma
|
||||
selectedTalents.Remove(talentIdentifier);
|
||||
}
|
||||
}
|
||||
else if (!character.HasTalent(talentIdentifier))
|
||||
else
|
||||
{
|
||||
selectedTalents.Remove(talentIdentifier);
|
||||
}
|
||||
|
||||
@@ -314,7 +314,7 @@ namespace Barotrauma
|
||||
GraphicsDeviceManager.SynchronizeWithVerticalRetrace = GameSettings.CurrentConfig.Graphics.VSync;
|
||||
SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode);
|
||||
|
||||
defaultViewport = GraphicsDevice.Viewport;
|
||||
defaultViewport = new Viewport(0, 0, GraphicsWidth, GraphicsHeight);
|
||||
|
||||
if (recalculateFontsAndStyles)
|
||||
{
|
||||
@@ -353,6 +353,7 @@ namespace Barotrauma
|
||||
public void ResetViewPort()
|
||||
{
|
||||
GraphicsDevice.Viewport = defaultViewport;
|
||||
GraphicsDevice.ScissorRectangle = defaultViewport.Bounds;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -28,18 +28,27 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
if (node.ParentIndex > -1)
|
||||
{
|
||||
Vector2 diff = nodes[node.ParentIndex].WorldPosition - node.WorldPosition;
|
||||
float dist = diff.Length();
|
||||
Vector2 normalizedDiff = diff / dist;
|
||||
for (float x = 0.0f; x < dist; x += 50.0f)
|
||||
{
|
||||
var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", node.WorldPosition + normalizedDiff * x, Vector2.Zero);
|
||||
if (spark != null)
|
||||
{
|
||||
spark.Size *= 0.3f;
|
||||
}
|
||||
}
|
||||
CreateParticlesBetween(nodes[node.ParentIndex].WorldPosition, node.WorldPosition);
|
||||
}
|
||||
}
|
||||
foreach (var character in charactersInRange)
|
||||
{
|
||||
CreateParticlesBetween(character.character.WorldPosition, character.node.WorldPosition);
|
||||
}
|
||||
|
||||
static void CreateParticlesBetween(Vector2 start, Vector2 end)
|
||||
{
|
||||
const float ParticleInterval = 50.0f;
|
||||
Vector2 diff = end - start;
|
||||
float dist = diff.Length();
|
||||
Vector2 normalizedDiff = MathUtils.NearlyEqual(dist, 0.0f) ? Vector2.Zero : diff / dist;
|
||||
for (float x = 0.0f; x < dist; x += ParticleInterval)
|
||||
{
|
||||
var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", start + normalizedDiff * x, Vector2.Zero);
|
||||
if (spark != null)
|
||||
{
|
||||
spark.Size *= 0.3f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,10 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
private GUIFrame selectedItemFrame;
|
||||
private GUIFrame selectedItemReqsFrame;
|
||||
|
||||
|
||||
private GUITextBlock amountTextMin, amountTextMax;
|
||||
private GUIScrollBar amountInput;
|
||||
|
||||
public GUIButton ActivateButton
|
||||
{
|
||||
get { return activateButton; }
|
||||
@@ -160,14 +163,46 @@ namespace Barotrauma.Items.Components
|
||||
new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false };
|
||||
|
||||
// === ACTIVATE BUTTON === //
|
||||
var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterRight);
|
||||
activateButton = new GUIButton(new RectTransform(new Vector2(1f, 0.6f), buttonFrame.RectTransform),
|
||||
TextManager.Get(CreateButtonText), style: "DeviceButtonFixedSize")
|
||||
var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.9f), inputArea.RectTransform))
|
||||
{
|
||||
Stretch = true,
|
||||
RelativeSpacing = 0.05f
|
||||
};
|
||||
|
||||
var amountInputHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), buttonFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||
{
|
||||
Stretch = true
|
||||
};
|
||||
|
||||
amountTextMin = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||
|
||||
amountInput = new GUIScrollBar(new RectTransform(new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style: "GUISlider")
|
||||
{
|
||||
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||
{
|
||||
scrollBar.Step = 1.0f / Math.Max(scrollBar.Range.Y - 1, 1);
|
||||
AmountToFabricate = (int)MathF.Round(scrollBar.BarScrollValue);
|
||||
RefreshActivateButtonText();
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
item.CreateClientEvent(this);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
amountTextMax = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||
|
||||
activateButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.6f), buttonFrame.RectTransform),
|
||||
TextManager.Get(CreateButtonText), style: "DeviceButton")
|
||||
{
|
||||
OnClicked = StartButtonClicked,
|
||||
UserData = selectedItem,
|
||||
Enabled = false
|
||||
};
|
||||
};
|
||||
|
||||
//spacing
|
||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), buttonFrame.RectTransform), style: null);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -192,6 +227,21 @@ namespace Barotrauma.Items.Components
|
||||
CreateRecipes();
|
||||
}
|
||||
|
||||
private void RefreshActivateButtonText()
|
||||
{
|
||||
if (amountInput == null)
|
||||
{
|
||||
activateButton.Text = TextManager.Get(IsActive ? "FabricatorCancel" : CreateButtonText);
|
||||
}
|
||||
else
|
||||
{
|
||||
activateButton.Text =
|
||||
IsActive ?
|
||||
$"{TextManager.Get("FabricatorCancel")} ({amountRemaining})" :
|
||||
$"{TextManager.Get(CreateButtonText)} ({AmountToFabricate})";
|
||||
}
|
||||
}
|
||||
|
||||
partial void CreateRecipes()
|
||||
{
|
||||
itemList.Content.RectTransform.ClearChildren();
|
||||
@@ -247,7 +297,7 @@ namespace Barotrauma.Items.Components
|
||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||
}
|
||||
|
||||
private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
|
||||
private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
|
||||
{
|
||||
if (fabricationRecipe == null) { return ""; }
|
||||
if (fabricationRecipe.Amount > 1)
|
||||
@@ -269,7 +319,7 @@ namespace Barotrauma.Items.Components
|
||||
|
||||
partial void SelectProjSpecific(Character character)
|
||||
{
|
||||
var nonItems = itemList.Content.Children.Where(c => !(c.UserData is FabricationRecipe)).ToList();
|
||||
var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
|
||||
nonItems.ForEach(i => itemList.Content.RemoveChild(i));
|
||||
|
||||
itemList.Content.RectTransform.SortChildren((c1, c2) =>
|
||||
@@ -567,7 +617,7 @@ namespace Barotrauma.Items.Components
|
||||
bool recipeVisible = false;
|
||||
foreach (GUIComponent child in itemList.Content.Children.Reverse())
|
||||
{
|
||||
if (!(child.UserData is FabricationRecipe recipe))
|
||||
if (child.UserData is not FabricationRecipe recipe)
|
||||
{
|
||||
if (child.Enabled)
|
||||
{
|
||||
@@ -598,9 +648,23 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
this.selectedItem = selectedItem;
|
||||
|
||||
int max = Math.Max(selectedItem.TargetItem.MaxStackSize / selectedItem.Amount, 1);
|
||||
|
||||
if (amountInput != null)
|
||||
{
|
||||
float prevBarScroll = amountInput.BarScroll;
|
||||
amountInput.Range = new Vector2(1, max);
|
||||
amountInput.BarScroll = prevBarScroll;
|
||||
|
||||
amountTextMax.Text = max.ToString();
|
||||
amountInput.Enabled = amountTextMax.Enabled = max > 1;
|
||||
AmountToFabricate = Math.Min((int)amountInput.BarScrollValue, max);
|
||||
}
|
||||
RefreshActivateButtonText();
|
||||
|
||||
selectedItemFrame.ClearChildren();
|
||||
selectedItemReqsFrame.ClearChildren();
|
||||
|
||||
|
||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
||||
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
||||
|
||||
@@ -734,7 +798,9 @@ namespace Barotrauma.Items.Components
|
||||
outputSlot.Flash(GUIStyle.Red);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
amountRemaining = AmountToFabricate;
|
||||
|
||||
if (GameMain.Client != null)
|
||||
{
|
||||
pendingFabricatedItem = fabricatedItem != null ? null : selectedItem;
|
||||
@@ -777,7 +843,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (GUIComponent child in itemList.Content.Children)
|
||||
{
|
||||
if (!(child.UserData is FabricationRecipe recipe)) { continue; }
|
||||
if (child.UserData is not FabricationRecipe recipe) { continue; }
|
||||
|
||||
if (recipe != selectedItem &&
|
||||
(child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y))
|
||||
@@ -811,11 +877,14 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
|
||||
msg.WriteUInt32(recipeHash);
|
||||
msg.WriteRangedInteger(AmountToFabricate, 1, MaxAmountToFabricate);
|
||||
}
|
||||
|
||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||
{
|
||||
FabricatorState newState = (FabricatorState)msg.ReadByte();
|
||||
int amountToFabricate = msg.ReadRangedInteger(0, MaxAmountToFabricate);
|
||||
int amountRemaining = msg.ReadRangedInteger(0, MaxAmountToFabricate);
|
||||
float newTimeUntilReady = msg.ReadSingle();
|
||||
uint recipeHash = msg.ReadUInt32();
|
||||
UInt16 userID = msg.ReadUInt16();
|
||||
@@ -828,6 +897,8 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
|
||||
State = newState;
|
||||
this.amountToFabricate = amountToFabricate;
|
||||
this.amountRemaining = amountRemaining;
|
||||
if (newState == FabricatorState.Stopped || recipeHash == 0)
|
||||
{
|
||||
CancelFabricating();
|
||||
|
||||
@@ -802,21 +802,34 @@ namespace Barotrauma.Items.Components
|
||||
if (passivePingRadius > 0.0f)
|
||||
{
|
||||
if (activePingsCount == 0) { disruptedDirections.Clear(); }
|
||||
//emit "pings" from nearby sound-emitting AITargets to reveal what's around them
|
||||
foreach (AITarget t in AITarget.List)
|
||||
{
|
||||
if (t.Entity is Character c && !c.IsUnconscious && c.Params.HideInSonar) { continue; }
|
||||
if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
|
||||
|
||||
|
||||
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
|
||||
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
|
||||
|
||||
float dist = (float)Math.Sqrt(distSqr);
|
||||
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500 && t.IsWithinSector(transducerCenter))
|
||||
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500)
|
||||
{
|
||||
int prevBlipCount = sonarBlips.Count;
|
||||
Ping(t.WorldPosition, transducerCenter,
|
||||
Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f),
|
||||
Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f),
|
||||
passive: true, pingStrength: 0.5f);
|
||||
sonarBlips.Add(new SonarBlip(t.WorldPosition, 1.0f, 1.0f));
|
||||
//remove blips that weren't in the AITarget's sector
|
||||
if (t.HasSector())
|
||||
{
|
||||
for (int i = sonarBlips.Count - 1; i >= prevBlipCount; i--)
|
||||
{
|
||||
if (!t.IsWithinSector(sonarBlips[i].Position))
|
||||
{
|
||||
sonarBlips.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -411,6 +411,14 @@ namespace Barotrauma.Items.Components
|
||||
SpriteEffects.None, newDepth);
|
||||
}
|
||||
|
||||
if (GameMain.DebugDraw)
|
||||
{
|
||||
Vector2 firingPos = GetRelativeFiringPosition();
|
||||
firingPos.Y = -firingPos.Y;
|
||||
GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitX * 5, firingPos + Vector2.UnitX * 5, Color.Red);
|
||||
GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitY * 5, firingPos + Vector2.UnitY * 5, Color.Red);
|
||||
}
|
||||
|
||||
if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
|
||||
|
||||
const float widgetRadius = 60.0f;
|
||||
|
||||
@@ -343,38 +343,31 @@ namespace Barotrauma.Lights
|
||||
{
|
||||
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
|
||||
if (Character.Controlled?.FocusedCharacter == character) { continue; }
|
||||
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
|
||||
Color.Black :
|
||||
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.DeformSprite != null) { continue; }
|
||||
limb.Draw(spriteBatch, cam, lightColor);
|
||||
}
|
||||
}
|
||||
DrawCharacters(spriteBatch, cam, drawDeformSprites: false);
|
||||
spriteBatch.End();
|
||||
|
||||
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"];
|
||||
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
|
||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
|
||||
DrawCharacters(spriteBatch, cam, drawDeformSprites: true);
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
static void DrawCharacters(SpriteBatch spriteBatch, Camera cam, bool drawDeformSprites)
|
||||
{
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
|
||||
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible || character.InvisibleTimer > 0.0f) { continue; }
|
||||
if (Character.Controlled?.FocusedCharacter == character) { continue; }
|
||||
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
|
||||
Color.Black :
|
||||
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
|
||||
foreach (Limb limb in character.AnimController.Limbs)
|
||||
{
|
||||
if (limb.DeformSprite == null) { continue; }
|
||||
if (drawDeformSprites == (limb.DeformSprite == null)) { continue; }
|
||||
limb.Draw(spriteBatch, cam, lightColor);
|
||||
}
|
||||
}
|
||||
spriteBatch.End();
|
||||
}
|
||||
|
||||
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
|
||||
|
||||
@@ -697,17 +697,22 @@ namespace Barotrauma
|
||||
Vector2 size = new Vector2(Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X)), nameSize.Y + typeSize.Y + descSize.Y);
|
||||
|
||||
int highestSubTier = HighlightedLocation.HighestSubmarineTierAvailable();
|
||||
var overrideTiers = new List<(SubmarineClass subClass, int tier)>();
|
||||
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
|
||||
List<(SubmarineClass subClass, int tier)> overrideTiers = null;
|
||||
if (HighlightedLocation.CanHaveSubsForSale())
|
||||
{
|
||||
if (subClass == SubmarineClass.Undefined) { continue; }
|
||||
int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass);
|
||||
if (highestClassTier > 0 && highestClassTier > highestSubTier)
|
||||
overrideTiers = new List<(SubmarineClass subClass, int tier)>();
|
||||
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
|
||||
{
|
||||
overrideTiers.Add((subClass, highestClassTier));
|
||||
if (subClass == SubmarineClass.Undefined) { continue; }
|
||||
int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass);
|
||||
if (highestClassTier > 0 && highestClassTier > highestSubTier)
|
||||
{
|
||||
overrideTiers.Add((subClass, highestClassTier));
|
||||
}
|
||||
}
|
||||
}
|
||||
size.Y += ((highestSubTier > 0 ? 1 : 0) + overrideTiers.Count) * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y;
|
||||
int subAvailabilityTextCount = (highestSubTier > 0 ? 1 : 0) + (overrideTiers?.Count ?? 0);
|
||||
size.Y += subAvailabilityTextCount * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y;
|
||||
|
||||
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
|
||||
LocalizedString repLabelText = null, repValueText = null;
|
||||
@@ -745,9 +750,12 @@ namespace Barotrauma
|
||||
{
|
||||
DrawSubAvailabilityText("advancedsub.all", highestSubTier);
|
||||
}
|
||||
foreach (var (subClass, tier) in overrideTiers)
|
||||
if (overrideTiers != null)
|
||||
{
|
||||
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
|
||||
foreach (var (subClass, tier) in overrideTiers)
|
||||
{
|
||||
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
|
||||
}
|
||||
}
|
||||
void DrawSubAvailabilityText(string tag, int tier)
|
||||
{
|
||||
|
||||
@@ -14,6 +14,16 @@ namespace Barotrauma.Networking
|
||||
set;
|
||||
}
|
||||
|
||||
private SoundChannel radioNoiseChannel;
|
||||
private float radioNoise;
|
||||
|
||||
public float RadioNoise
|
||||
{
|
||||
get { return radioNoise; }
|
||||
set { radioNoise = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||
}
|
||||
|
||||
|
||||
private bool mutedLocally;
|
||||
public bool MutedLocally
|
||||
{
|
||||
@@ -42,35 +52,64 @@ namespace Barotrauma.Networking
|
||||
!HasPermission(ClientPermissions.Kick) &&
|
||||
!HasPermission(ClientPermissions.Unban);
|
||||
|
||||
public void UpdateSoundPosition()
|
||||
public void UpdateVoipSound()
|
||||
{
|
||||
if (VoipSound == null) { return; }
|
||||
|
||||
if (!VoipSound.IsPlaying)
|
||||
if (VoipSound == null || !VoipSound.IsPlaying)
|
||||
{
|
||||
DebugConsole.Log("Destroying voipsound");
|
||||
VoipSound.Dispose();
|
||||
radioNoiseChannel?.Dispose();
|
||||
radioNoiseChannel = null;
|
||||
if (VoipSound != null)
|
||||
{
|
||||
DebugConsole.Log("Destroying voipsound");
|
||||
VoipSound.Dispose();
|
||||
}
|
||||
VoipSound = null;
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
if (Screen.Selected is ModDownloadScreen)
|
||||
{
|
||||
VoipSound.Gain = 0.0f;
|
||||
}
|
||||
|
||||
float gain = 1.0f;
|
||||
float noiseGain = 0.0f;
|
||||
Vector3? position = null;
|
||||
if (character != null)
|
||||
{
|
||||
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
|
||||
{
|
||||
VoipSound.SetPosition(new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f));
|
||||
position = new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
VoipSound.SetPosition(null);
|
||||
float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition);
|
||||
VoipSound.Gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
|
||||
gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
|
||||
}
|
||||
if (RadioNoise > 0.0f)
|
||||
{
|
||||
noiseGain = gain * RadioNoise;
|
||||
gain *= 1.0f - RadioNoise;
|
||||
}
|
||||
}
|
||||
else
|
||||
VoipSound.SetPosition(position);
|
||||
VoipSound.Gain = gain;
|
||||
if (noiseGain > 0.0f)
|
||||
{
|
||||
VoipSound.SetPosition(null);
|
||||
VoipSound.Gain = 1.0f;
|
||||
if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying)
|
||||
{
|
||||
radioNoiseChannel = SoundPlayer.PlaySound("radiostatic");
|
||||
radioNoiseChannel.Category = "voip";
|
||||
radioNoiseChannel.Looping = true;
|
||||
}
|
||||
radioNoiseChannel.Near = VoipSound.Near;
|
||||
radioNoiseChannel.Far = VoipSound.Far;
|
||||
radioNoiseChannel.Position = position;
|
||||
radioNoiseChannel.Gain = noiseGain;
|
||||
}
|
||||
else if (radioNoiseChannel != null)
|
||||
{
|
||||
radioNoiseChannel.Gain = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,6 +197,11 @@ namespace Barotrauma.Networking
|
||||
VoipSound.Dispose();
|
||||
VoipSound = null;
|
||||
}
|
||||
if (radioNoiseChannel != null)
|
||||
{
|
||||
radioNoiseChannel.Dispose();
|
||||
radioNoiseChannel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -449,7 +449,7 @@ namespace Barotrauma.Networking
|
||||
foreach (Client c in ConnectedClients)
|
||||
{
|
||||
if (c.Character != null && c.Character.Removed) { c.Character = null; }
|
||||
c.UpdateSoundPosition();
|
||||
c.UpdateVoipSound();
|
||||
}
|
||||
|
||||
if (VoipCapture.Instance != null)
|
||||
|
||||
@@ -111,7 +111,7 @@ namespace Barotrauma.Networking
|
||||
|
||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
|
||||
{
|
||||
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
|
||||
if (!inc.SenderConnection.RemoteEndPoint.EquivalentTo(lidgrenEndpoint.NetEndpoint))
|
||||
{
|
||||
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
||||
continue;
|
||||
|
||||
@@ -260,6 +260,13 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (Screen.Selected is ModDownloadScreen)
|
||||
{
|
||||
allowEnqueue = false;
|
||||
captureTimer = 0;
|
||||
}
|
||||
|
||||
if (allowEnqueue || captureTimer > 0)
|
||||
{
|
||||
LastEnqueueAudio = DateTime.Now;
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
using Barotrauma.Sounds;
|
||||
using Barotrauma.Items.Components;
|
||||
using Barotrauma.Sounds;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Items.Components;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class VoipClient : IDisposable
|
||||
{
|
||||
private GameClient gameClient;
|
||||
private ClientPeer netClient;
|
||||
/// <summary>
|
||||
/// The "near" range of the voice chat (a percentage of either SpeakRange or radio range), further than this the volume starts to diminish
|
||||
/// </summary>
|
||||
const float RangeNear = 0.4f;
|
||||
|
||||
private readonly GameClient gameClient;
|
||||
private readonly ClientPeer netClient;
|
||||
private DateTime lastSendTime;
|
||||
private List<VoipQueue> queues;
|
||||
private readonly List<VoipQueue> queues;
|
||||
|
||||
private UInt16 storedBufferID = 0;
|
||||
|
||||
@@ -32,13 +35,13 @@ namespace Barotrauma.Networking
|
||||
|
||||
public void RegisterQueue(VoipQueue queue)
|
||||
{
|
||||
if (queue == VoipCapture.Instance) return;
|
||||
if (!queues.Contains(queue)) queues.Add(queue);
|
||||
if (queue == VoipCapture.Instance) { return; }
|
||||
if (!queues.Contains(queue)) { queues.Add(queue); }
|
||||
}
|
||||
|
||||
public void UnregisterQueue(VoipQueue queue)
|
||||
{
|
||||
if (queues.Contains(queue)) queues.Remove(queue);
|
||||
if (queues.Contains(queue)) { queues.Remove(queue); }
|
||||
}
|
||||
|
||||
public void SendToServer()
|
||||
@@ -85,6 +88,7 @@ namespace Barotrauma.Networking
|
||||
public void Read(IReadMessage msg)
|
||||
{
|
||||
byte queueId = msg.ReadByte();
|
||||
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
|
||||
|
||||
if (queue == null)
|
||||
@@ -105,9 +109,12 @@ namespace Barotrauma.Networking
|
||||
client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue);
|
||||
}
|
||||
GameMain.SoundManager.ForceStreamUpdate();
|
||||
|
||||
client.RadioNoise = 0.0f;
|
||||
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
|
||||
{
|
||||
float speechImpedimentMultiplier = 1.0f - client.Character.SpeechImpediment / 100.0f;
|
||||
bool spectating = Character.Controlled == null;
|
||||
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
||||
WifiComponent radio = null;
|
||||
var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||
@@ -115,11 +122,17 @@ namespace Barotrauma.Networking
|
||||
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
||||
if (messageType == ChatMessageType.Radio)
|
||||
{
|
||||
client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range);
|
||||
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||
if (distanceFactor > RangeNear && !spectating)
|
||||
{
|
||||
//noise starts increasing exponentially after 40% range
|
||||
client.RadioNoise = MathF.Pow(MathUtils.InverseLerp(RangeNear, 1.0f, distanceFactor), 2);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange);
|
||||
|
||||
client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier);
|
||||
}
|
||||
client.VoipSound.UseMuffleFilter =
|
||||
messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters &&
|
||||
|
||||
@@ -35,14 +35,14 @@ namespace Barotrauma
|
||||
};
|
||||
|
||||
// New game
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
||||
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(24)) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
||||
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform), string.Empty)
|
||||
{
|
||||
textFilterFunction = ToolBox.RemoveInvalidFileNameChars
|
||||
};
|
||||
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
||||
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
|
||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(24)) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
||||
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform), ToolBox.RandomSeed(8));
|
||||
|
||||
nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y));
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ namespace Barotrauma.Sounds
|
||||
private VorbisReader reader;
|
||||
|
||||
//key = sample rate, value = filter
|
||||
private static Dictionary<int, BiQuad> muffleFilters = new Dictionary<int, BiQuad>();
|
||||
private static readonly Dictionary<int, BiQuad> muffleFilters = new Dictionary<int, BiQuad>();
|
||||
|
||||
private static List<float> playbackAmplitude;
|
||||
private const int AMPLITUDE_SAMPLE_COUNT = 4410; //100ms in a 44100hz file
|
||||
@@ -29,8 +29,8 @@ namespace Barotrauma.Sounds
|
||||
|
||||
public override int FillStreamBuffer(int samplePos, short[] buffer)
|
||||
{
|
||||
if (!Stream) throw new Exception("Called FillStreamBuffer on a non-streamed sound!");
|
||||
if (reader == null) throw new Exception("Called FillStreamBuffer when the reader is null!");
|
||||
if (!Stream) { throw new Exception("Called FillStreamBuffer on a non-streamed sound!"); }
|
||||
if (reader == null) { throw new Exception("Called FillStreamBuffer when the reader is null!"); }
|
||||
|
||||
if (samplePos >= reader.TotalSamples * reader.Channels * 2) return 0;
|
||||
|
||||
|
||||
@@ -24,11 +24,12 @@ namespace Barotrauma.Sounds
|
||||
|
||||
public readonly bool StreamsReliably;
|
||||
|
||||
private readonly SoundManager.SourcePoolIndex sourcePoolIndex = SoundManager.SourcePoolIndex.Default;
|
||||
public virtual SoundManager.SourcePoolIndex SourcePoolIndex
|
||||
{
|
||||
get
|
||||
{
|
||||
return SoundManager.SourcePoolIndex.Default;
|
||||
return sourcePoolIndex;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,13 +60,14 @@ namespace Barotrauma.Sounds
|
||||
public float BaseNear;
|
||||
public float BaseFar;
|
||||
|
||||
public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement=null, bool getFullPath=true)
|
||||
public Sound(SoundManager owner, string filename, bool stream, bool streamsReliably, XElement xElement = null, bool getFullPath = true)
|
||||
{
|
||||
Owner = owner;
|
||||
Filename = getFullPath ? Path.GetFullPath(filename.CleanUpPath()).CleanUpPath() : filename;
|
||||
Stream = stream;
|
||||
StreamsReliably = streamsReliably;
|
||||
XElement = xElement;
|
||||
sourcePoolIndex = XElement.GetAttributeEnum("sourcepool", SoundManager.SourcePoolIndex.Default);
|
||||
|
||||
BaseGain = 1.0f;
|
||||
BaseNear = 100.0f;
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
using System;
|
||||
using Microsoft.Xna.Framework;
|
||||
using OpenAL;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Barotrauma.Sounds
|
||||
{
|
||||
@@ -17,7 +15,7 @@ namespace Barotrauma.Sounds
|
||||
|
||||
public SoundSourcePool(int sourceCount = SoundManager.SOURCE_COUNT)
|
||||
{
|
||||
int alError = Al.NoError;
|
||||
int alError;
|
||||
|
||||
ALSources = new uint[sourceCount];
|
||||
for (int i = 0; i < sourceCount; i++)
|
||||
@@ -83,7 +81,7 @@ namespace Barotrauma.Sounds
|
||||
class SoundChannel : IDisposable
|
||||
{
|
||||
private const int STREAM_BUFFER_SIZE = 8820;
|
||||
private short[] streamShortBuffer;
|
||||
private readonly short[] streamShortBuffer;
|
||||
|
||||
private string debugName = "SoundChannel";
|
||||
|
||||
@@ -312,12 +310,12 @@ namespace Barotrauma.Sounds
|
||||
|
||||
if (ALSourceIndex < 0) { return; }
|
||||
|
||||
if (!IsPlaying) return;
|
||||
if (!IsPlaying) { return; }
|
||||
|
||||
if (!IsStream)
|
||||
{
|
||||
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
||||
int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos);
|
||||
Al.GetSourcei(alSource, Al.SampleOffset, out int playbackPos);
|
||||
int alError = Al.GetError();
|
||||
if (alError != Al.NoError)
|
||||
{
|
||||
@@ -379,7 +377,7 @@ namespace Barotrauma.Sounds
|
||||
|
||||
if (!IsStream)
|
||||
{
|
||||
int playbackPos; Al.GetSourcei(alSource, Al.SampleOffset, out playbackPos);
|
||||
Al.GetSourcei(alSource, Al.SampleOffset, out int playbackPos);
|
||||
int alError = Al.GetError();
|
||||
if (alError != Al.NoError)
|
||||
{
|
||||
@@ -390,7 +388,7 @@ namespace Barotrauma.Sounds
|
||||
}
|
||||
else
|
||||
{
|
||||
float retVal = -1.0f;
|
||||
float retVal;
|
||||
Monitor.Enter(mutex);
|
||||
retVal = streamAmplitude;
|
||||
Monitor.Exit(mutex);
|
||||
@@ -432,8 +430,8 @@ namespace Barotrauma.Sounds
|
||||
private bool reachedEndSample;
|
||||
private int queueStartIndex;
|
||||
private readonly uint[] streamBuffers;
|
||||
private uint[] unqueuedBuffers;
|
||||
private float[] streamBufferAmplitudes;
|
||||
private readonly uint[] unqueuedBuffers;
|
||||
private readonly float[] streamBufferAmplitudes;
|
||||
|
||||
public int StreamSeekPos
|
||||
{
|
||||
@@ -448,18 +446,17 @@ namespace Barotrauma.Sounds
|
||||
}
|
||||
}
|
||||
|
||||
private object mutex;
|
||||
private readonly object mutex;
|
||||
|
||||
public bool IsPlaying
|
||||
{
|
||||
get
|
||||
{
|
||||
if (ALSourceIndex < 0) return false;
|
||||
if (IsStream && !reachedEndSample) return true;
|
||||
int state;
|
||||
if (ALSourceIndex < 0) { return false; }
|
||||
if (IsStream && !reachedEndSample) { return true; }
|
||||
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
||||
if (!Al.IsSource(alSource)) return false;
|
||||
Al.GetSourcei(alSource, Al.SourceState, out state);
|
||||
if (!Al.IsSource(alSource)) { return false; }
|
||||
Al.GetSourcei(alSource, Al.SourceState, out int state);
|
||||
int alError = Al.GetError();
|
||||
if (alError != Al.NoError)
|
||||
{
|
||||
@@ -710,8 +707,7 @@ namespace Barotrauma.Sounds
|
||||
{
|
||||
uint alSource = Sound.Owner.GetSourceFromIndex(Sound.SourcePoolIndex, ALSourceIndex);
|
||||
|
||||
int state;
|
||||
Al.GetSourcei(alSource, Al.SourceState, out state);
|
||||
Al.GetSourcei(alSource, Al.SourceState, out int state);
|
||||
bool playing = state == Al.Playing;
|
||||
int alError = Al.GetError();
|
||||
if (alError != Al.NoError)
|
||||
@@ -719,8 +715,7 @@ namespace Barotrauma.Sounds
|
||||
throw new Exception("Failed to determine playing state from streamed source: " + debugName + ", " + Al.GetErrorString(alError));
|
||||
}
|
||||
|
||||
int unqueuedBufferCount;
|
||||
Al.GetSourcei(alSource, Al.BuffersProcessed, out unqueuedBufferCount);
|
||||
Al.GetSourcei(alSource, Al.BuffersProcessed, out int unqueuedBufferCount);
|
||||
alError = Al.GetError();
|
||||
if (alError != Al.NoError)
|
||||
{
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Networking;
|
||||
using Concentus.Structs;
|
||||
using Microsoft.Xna.Framework;
|
||||
using OpenAL;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Barotrauma.Sounds
|
||||
{
|
||||
@@ -26,12 +24,12 @@ namespace Barotrauma.Sounds
|
||||
}
|
||||
}
|
||||
|
||||
private VoipQueue queue;
|
||||
private readonly VoipQueue queue;
|
||||
private int bufferID = 0;
|
||||
|
||||
private SoundChannel soundChannel;
|
||||
|
||||
private OpusDecoder decoder;
|
||||
private readonly OpusDecoder decoder;
|
||||
|
||||
public bool UseRadioFilter;
|
||||
public bool UseMuffleFilter;
|
||||
@@ -39,11 +37,11 @@ namespace Barotrauma.Sounds
|
||||
public float Near { get; private set; }
|
||||
public float Far { get; private set; }
|
||||
|
||||
private BiQuad[] muffleFilters = new BiQuad[]
|
||||
private readonly BiQuad[] muffleFilters = new BiQuad[]
|
||||
{
|
||||
new LowpassFilter(VoipConfig.FREQUENCY, 800)
|
||||
};
|
||||
private BiQuad[] radioFilters = new BiQuad[]
|
||||
private readonly BiQuad[] radioFilters = new BiQuad[]
|
||||
{
|
||||
new BandpassFilter(VoipConfig.FREQUENCY, 2000)
|
||||
};
|
||||
@@ -101,13 +99,14 @@ namespace Barotrauma.Sounds
|
||||
|
||||
public void ApplyFilters(short[] buffer, int readSamples)
|
||||
{
|
||||
float finalGain = gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume;
|
||||
for (int i = 0; i < readSamples; i++)
|
||||
{
|
||||
float fVal = ShortToFloat(buffer[i]);
|
||||
|
||||
if (gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume > 1.0f) //TODO: take distance into account?
|
||||
if (finalGain > 1.0f) //TODO: take distance into account?
|
||||
{
|
||||
fVal = Math.Clamp(fVal * gain * GameSettings.CurrentConfig.Audio.VoiceChatVolume, -1f, 1f);
|
||||
fVal = Math.Clamp(fVal * finalGain, -1f, 1f);
|
||||
}
|
||||
|
||||
if (UseMuffleFilter)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.20.6.0</Version>
|
||||
<Version>0.20.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.20.6.0</Version>
|
||||
<Version>0.20.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma</Product>
|
||||
<Version>0.20.6.0</Version>
|
||||
<Version>0.20.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>Barotrauma</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.20.6.0</Version>
|
||||
<Version>0.20.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.20.6.0</Version>
|
||||
<Version>0.20.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -9,11 +9,12 @@ namespace Barotrauma.Items.Components
|
||||
public void ServerEventRead(IReadMessage msg, Client c)
|
||||
{
|
||||
uint recipeHash = msg.ReadUInt32();
|
||||
|
||||
int amountToFabricate = msg.ReadRangedInteger(1, MaxAmountToFabricate);
|
||||
item.CreateServerEvent(this);
|
||||
|
||||
if (!item.CanClientAccess(c)) { return; }
|
||||
|
||||
AmountToFabricate = amountToFabricate;
|
||||
if (recipeHash == 0)
|
||||
{
|
||||
CancelFabricating(c.Character);
|
||||
@@ -24,6 +25,8 @@ namespace Barotrauma.Items.Components
|
||||
if (fabricatedItem != null && fabricatedItem.RecipeHash == recipeHash) { return; }
|
||||
if (recipeHash == 0) { return; }
|
||||
|
||||
amountRemaining = AmountToFabricate;
|
||||
|
||||
StartFabricating(fabricationRecipes[recipeHash], c.Character);
|
||||
}
|
||||
}
|
||||
@@ -56,6 +59,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
var componentData = ExtractEventData<EventData>(extraData);
|
||||
msg.WriteByte((byte)componentData.State);
|
||||
msg.WriteRangedInteger(AmountToFabricate, 0, MaxAmountToFabricate);
|
||||
msg.WriteRangedInteger(amountRemaining, 0, MaxAmountToFabricate);
|
||||
msg.WriteSingle(timeUntilReady);
|
||||
uint recipeHash = fabricatedItem?.RecipeHash ?? 0;
|
||||
msg.WriteUInt32(recipeHash);
|
||||
|
||||
@@ -2,19 +2,18 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace Barotrauma.Networking
|
||||
{
|
||||
class VoipServer
|
||||
{
|
||||
private ServerPeer netServer;
|
||||
private List<VoipQueue> queues;
|
||||
private Dictionary<VoipQueue,DateTime> lastSendTime;
|
||||
private readonly ServerPeer netServer;
|
||||
private readonly List<VoipQueue> queues;
|
||||
private readonly Dictionary<VoipQueue,DateTime> lastSendTime;
|
||||
|
||||
public VoipServer(ServerPeer server)
|
||||
{
|
||||
this.netServer = server;
|
||||
netServer = server;
|
||||
queues = new List<VoipQueue>();
|
||||
lastSendTime = new Dictionary<VoipQueue, DateTime>();
|
||||
}
|
||||
@@ -46,17 +45,19 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
|
||||
Client sender = clients.Find(c => c.VoipQueue == queue);
|
||||
if (sender == null) { return; }
|
||||
|
||||
foreach (Client recipient in clients)
|
||||
{
|
||||
if (recipient == sender) { continue; }
|
||||
|
||||
if (!CanReceive(sender, recipient)) { continue; }
|
||||
if (!CanReceive(sender, recipient, out float distanceFactor)) { continue; }
|
||||
|
||||
IWriteMessage msg = new WriteOnlyMessage();
|
||||
|
||||
msg.WriteByte((byte)ServerPacketHeader.VOICE);
|
||||
msg.WriteByte((byte)queue.QueueID);
|
||||
msg.WriteRangedSingle(distanceFactor, 0.0f, 1.0f, 8);
|
||||
queue.Write(msg);
|
||||
|
||||
netServer.Send(msg, recipient.Connection, DeliveryMethod.Unreliable);
|
||||
@@ -64,9 +65,15 @@ namespace Barotrauma.Networking
|
||||
}
|
||||
}
|
||||
|
||||
private bool CanReceive(Client sender, Client recipient)
|
||||
private static bool CanReceive(Client sender, Client recipient, out float distanceFactor)
|
||||
{
|
||||
if (Screen.Selected != GameMain.GameScreen) { return true; }
|
||||
if (Screen.Selected != GameMain.GameScreen)
|
||||
{
|
||||
distanceFactor = 0.0f;
|
||||
return true;
|
||||
}
|
||||
|
||||
distanceFactor = 0.0f;
|
||||
|
||||
//no-one can hear muted players
|
||||
if (sender.Muted) { return false; }
|
||||
@@ -74,30 +81,47 @@ namespace Barotrauma.Networking
|
||||
bool recipientSpectating = recipient.Character == null || recipient.Character.IsDead;
|
||||
bool senderSpectating = sender.Character == null || sender.Character.IsDead;
|
||||
|
||||
//TODO: only allow spectators to hear the voice chat if close enough to the speaker?
|
||||
|
||||
//non-spectators cannot hear spectators
|
||||
if (senderSpectating && !recipientSpectating) { return false; }
|
||||
|
||||
//both spectating, no need to do radio/distance checks
|
||||
if (recipientSpectating && senderSpectating) { return true; }
|
||||
|
||||
//spectators can hear non-spectators
|
||||
if (!senderSpectating && recipientSpectating) { return true; }
|
||||
//non-spectators cannot hear spectators, and spectators can always hear spectators
|
||||
if (senderSpectating)
|
||||
{
|
||||
return recipientSpectating;
|
||||
}
|
||||
|
||||
//sender can't speak
|
||||
if (sender.Character != null && sender.Character.SpeechImpediment >= 100.0f) { return false; }
|
||||
|
||||
//check if the message can be sent via radio
|
||||
WifiComponent recipientRadio = null;
|
||||
if (!sender.VoipQueue.ForceLocal &&
|
||||
ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) &&
|
||||
ChatMessage.CanUseRadio(recipient.Character, out WifiComponent recipientRadio))
|
||||
ChatMessage.CanUseRadio(sender.Character, out WifiComponent senderRadio) &&
|
||||
(recipientSpectating || ChatMessage.CanUseRadio(recipient.Character, out recipientRadio)))
|
||||
{
|
||||
if (recipientRadio.CanReceive(senderRadio)) { return true; }
|
||||
if (recipientSpectating)
|
||||
{
|
||||
if (recipient.SpectatePos == null) { return true; }
|
||||
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / senderRadio.Range, 0.0f, 1.0f);
|
||||
return distanceFactor < 1.0f;
|
||||
}
|
||||
else if (recipientRadio != null && recipientRadio.CanReceive(senderRadio))
|
||||
{
|
||||
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.Character.WorldPosition) / senderRadio.Range, 0.0f, 1.0f);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
//otherwise do a distance check
|
||||
return ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange) < 1.0f;
|
||||
if (recipientSpectating)
|
||||
{
|
||||
if (recipient.SpectatePos == null) { return true; }
|
||||
distanceFactor = MathHelper.Clamp(Vector2.Distance(sender.Character.WorldPosition, recipient.SpectatePos.Value) / ChatMessage.SpeakRange, 0.0f, 1.0f);
|
||||
return distanceFactor < 1.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
//otherwise do a distance check
|
||||
float garbleAmount = ChatMessage.GetGarbleAmount(recipient.Character, sender.Character, ChatMessage.SpeakRange);
|
||||
distanceFactor = garbleAmount;
|
||||
return garbleAmount < 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
<RootNamespace>Barotrauma</RootNamespace>
|
||||
<Authors>FakeFish, Undertow Games</Authors>
|
||||
<Product>Barotrauma Dedicated Server</Product>
|
||||
<Version>0.20.6.0</Version>
|
||||
<Version>0.20.7.0</Version>
|
||||
<Copyright>Copyright © FakeFish 2018-2022</Copyright>
|
||||
<Platforms>AnyCPU;x64</Platforms>
|
||||
<AssemblyName>DedicatedServer</AssemblyName>
|
||||
|
||||
@@ -102,6 +102,8 @@ namespace Barotrauma
|
||||
|
||||
private bool inDetectable;
|
||||
|
||||
public double InDetectableSetTime;
|
||||
|
||||
/// <summary>
|
||||
/// Should be reset to false each frame and kept indetectable by e.g. a status effect.
|
||||
/// </summary>
|
||||
@@ -115,7 +117,8 @@ namespace Barotrauma
|
||||
{
|
||||
inDetectable = value;
|
||||
if (inDetectable)
|
||||
{
|
||||
{
|
||||
InDetectableSetTime = Timing.TotalTime;
|
||||
NeedsUpdate = true;
|
||||
}
|
||||
}
|
||||
@@ -257,9 +260,14 @@ namespace Barotrauma
|
||||
SightRange -= speed * deltaTime * (MaxSightRange / FadeOutTime);
|
||||
}
|
||||
|
||||
public bool HasSector()
|
||||
{
|
||||
return sectorRad < MathHelper.TwoPi;
|
||||
}
|
||||
|
||||
public bool IsWithinSector(Vector2 worldPosition)
|
||||
{
|
||||
if (sectorRad >= MathHelper.TwoPi) { return true; }
|
||||
if (!HasSector()) { return true; }
|
||||
Vector2 diff = worldPosition - WorldPosition;
|
||||
return Math.Abs(MathUtils.GetShortestAngle(MathUtils.VectorToAngle(diff), MathUtils.VectorToAngle(sectorDir))) <= sectorRad * 0.5f;
|
||||
}
|
||||
|
||||
@@ -1083,7 +1083,7 @@ namespace Barotrauma
|
||||
State = AIState.Idle;
|
||||
return;
|
||||
}
|
||||
else
|
||||
else if (!owner.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI))
|
||||
{
|
||||
SelectedAiTarget = owner.AiTarget;
|
||||
}
|
||||
@@ -2147,7 +2147,6 @@ namespace Barotrauma
|
||||
if (SelectedAiTarget?.Entity == null) { return false; }
|
||||
if (AttackLimb?.attack == null) { return false; }
|
||||
if (damageTarget == null) { return false; }
|
||||
ActiveAttack = AttackLimb.attack;
|
||||
if (wallTarget != null)
|
||||
{
|
||||
// If the selected target is not the wall target, make the wall target the selected target.
|
||||
@@ -2156,9 +2155,10 @@ namespace Barotrauma
|
||||
{
|
||||
SelectTarget(aiTarget, GetTargetMemory(SelectedAiTarget, addIfNotFound: true).Priority);
|
||||
State = AIState.Attack;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if (damageTarget == null) { return false; }
|
||||
ActiveAttack = AttackLimb.attack;
|
||||
if (ActiveAttack.Ranged && ActiveAttack.RequiredAngleToShoot > 0)
|
||||
{
|
||||
Limb referenceLimb = GetLimbToRotate(ActiveAttack);
|
||||
@@ -3810,6 +3810,7 @@ namespace Barotrauma
|
||||
{
|
||||
SelectTarget(door.Item.AiTarget, SelectedTargetMemory.Priority);
|
||||
State = AIState.Attack;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3881,9 +3882,8 @@ namespace Barotrauma
|
||||
return targetLimb;
|
||||
}
|
||||
|
||||
private Character GetOwner(Item item)
|
||||
private static Character GetOwner(Item item)
|
||||
{
|
||||
// If the item is held by a character, attack the character instead.
|
||||
var pickable = item.GetComponent<Pickable>();
|
||||
if (pickable != null)
|
||||
{
|
||||
|
||||
@@ -413,28 +413,10 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void ApplyTreatment(Affliction affliction, Item item)
|
||||
{
|
||||
var targetLimb = targetCharacter.CharacterHealth.GetAfflictionLimb(affliction);
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in item.Components)
|
||||
{
|
||||
if (!ic.HasRequiredContainedItems(user: character, addMessage: false)) { continue; }
|
||||
#if CLIENT
|
||||
ic.PlaySound(ActionType.OnUse, character);
|
||||
#endif
|
||||
ic.WasUsed = true;
|
||||
ic.ApplyStatusEffects(ActionType.OnUse, 1.0f, targetCharacter, targetLimb, user: character);
|
||||
if (ic.DeleteOnUse)
|
||||
{
|
||||
remove = true;
|
||||
}
|
||||
}
|
||||
if (remove)
|
||||
{
|
||||
Entity.Spawner?.AddItemToRemoveQueue(item);
|
||||
}
|
||||
item.ApplyTreatment(character, targetCharacter, targetCharacter.CharacterHealth.GetAfflictionLimb(affliction));
|
||||
}
|
||||
|
||||
protected override bool CheckObjectiveSpecific()
|
||||
|
||||
@@ -1371,7 +1371,7 @@ namespace Barotrauma
|
||||
|
||||
Limb head = GetLimb(LimbType.Head);
|
||||
Limb torso = GetLimb(LimbType.Torso);
|
||||
|
||||
|
||||
Vector2 headDiff = targetHead == null ? diff : targetHead.SimPosition - character.SimPosition;
|
||||
targetMovement = new Vector2(diff.X, 0.0f);
|
||||
TargetDir = headDiff.X > 0.0f ? Direction.Right : Direction.Left;
|
||||
@@ -1386,15 +1386,25 @@ namespace Barotrauma
|
||||
|
||||
float prevVitality = target.Vitality;
|
||||
bool wasCritical = prevVitality < 0.0f;
|
||||
|
||||
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient) //Serverside code
|
||||
{
|
||||
target.Oxygen += deltaTime * 0.5f; //Stabilize them
|
||||
}
|
||||
|
||||
float cprBoost = character.GetStatValue(StatTypes.CPRBoost);
|
||||
|
||||
|
||||
int skill = (int)character.GetSkillLevel("medical");
|
||||
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (cprBoost >= 1f)
|
||||
{
|
||||
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
|
||||
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
|
||||
}
|
||||
}
|
||||
|
||||
//pump for 15 seconds (cprAnimTimer 0-15), then do mouth-to-mouth for 2 seconds (cprAnimTimer 15-17)
|
||||
if (cprAnimTimer > 15.0f && targetHead != null && head != null)
|
||||
{
|
||||
@@ -1405,23 +1415,15 @@ namespace Barotrauma
|
||||
torso.PullJointEnabled = true;
|
||||
|
||||
//Serverside code
|
||||
if (GameMain.NetworkMember == null || !GameMain.NetworkMember.IsClient)
|
||||
if (GameMain.NetworkMember is not { IsClient: true })
|
||||
{
|
||||
if (target.Oxygen < -10.0f)
|
||||
{
|
||||
if (cprBoost >= 1f)
|
||||
{
|
||||
//prevent the patient from suffocating no matter how fast their oxygen level is dropping
|
||||
target.Oxygen = Math.Max(target.Oxygen, -10.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
|
||||
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
|
||||
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
|
||||
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
|
||||
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
|
||||
}
|
||||
//stabilize the oxygen level but don't allow it to go positive and revive the character yet
|
||||
float stabilizationAmount = skill * CPRSettings.Active.StabilizationPerSkill;
|
||||
stabilizationAmount = MathHelper.Clamp(stabilizationAmount, CPRSettings.Active.StabilizationMin, CPRSettings.Active.StabilizationMax);
|
||||
character.Oxygen -= 1.0f / stabilizationAmount * deltaTime; //Worse skill = more oxygen required
|
||||
if (character.Oxygen > 0.0f) { target.Oxygen += stabilizationAmount * deltaTime; } //we didn't suffocate yet did we
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ namespace Barotrauma
|
||||
FriendlyNPC = 3
|
||||
}
|
||||
|
||||
public readonly record struct TalentResistanceIdentifier(Identifier ResistanceIdentifier, Identifier TalentIdentifier);
|
||||
|
||||
partial class Character : Entity, IDamageable, ISerializableEntity, IClientSerializable, IServerPositionSync
|
||||
{
|
||||
public readonly static List<Character> CharacterList = new List<Character>();
|
||||
@@ -347,7 +349,7 @@ namespace Barotrauma
|
||||
private readonly Dictionary<ItemPrefab, double> itemSelectedDurations = new Dictionary<ItemPrefab, double>();
|
||||
private double itemSelectedTime;
|
||||
|
||||
public float InvisibleTimer;
|
||||
public float InvisibleTimer { get; set; }
|
||||
|
||||
public readonly CharacterPrefab Prefab;
|
||||
|
||||
@@ -1372,7 +1374,28 @@ namespace Barotrauma
|
||||
tags.RemoveWhere(t => t.StartsWith("variant"));
|
||||
tags.Add($"variant{headId.Value}".ToIdentifier());
|
||||
}
|
||||
var oldHeadInfo = Info.Head;
|
||||
Info.RecreateHead(tags.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
||||
if (hairIndex == -1)
|
||||
{
|
||||
Info.Head.HairIndex = oldHeadInfo.HairIndex;
|
||||
}
|
||||
if (beardIndex == -1)
|
||||
{
|
||||
Info.Head.BeardIndex = oldHeadInfo.BeardIndex;
|
||||
}
|
||||
if (moustacheIndex == -1)
|
||||
{
|
||||
Info.Head.MoustacheIndex = oldHeadInfo.MoustacheIndex;
|
||||
}
|
||||
if (faceAttachmentIndex == -1)
|
||||
{
|
||||
Info.Head.FaceAttachmentIndex = oldHeadInfo.FaceAttachmentIndex;
|
||||
}
|
||||
Info.Head.SkinColor = oldHeadInfo.SkinColor;
|
||||
Info.Head.HairColor = oldHeadInfo.HairColor;
|
||||
Info.Head.FacialHairColor = oldHeadInfo.FacialHairColor;
|
||||
Info.CheckColors();
|
||||
#if CLIENT
|
||||
head.RecreateSprites();
|
||||
#endif
|
||||
@@ -3050,7 +3073,8 @@ namespace Barotrauma
|
||||
ApplyStatusEffects(AnimController.InWater ? ActionType.InWater : ActionType.NotInWater, deltaTime);
|
||||
ApplyStatusEffects(ActionType.OnActive, deltaTime);
|
||||
|
||||
if (aiTarget != null)
|
||||
//wait 0.1 seconds so status effects that continuously set InDetectable to true can keep the character InDetectable
|
||||
if (aiTarget != null && Timing.TotalTime > aiTarget.InDetectableSetTime + 0.1f)
|
||||
{
|
||||
aiTarget.InDetectable = false;
|
||||
}
|
||||
@@ -5087,25 +5111,48 @@ namespace Barotrauma
|
||||
return abilityFlags.HasFlag(abilityFlag) || CharacterHealth.HasFlag(abilityFlag);
|
||||
}
|
||||
|
||||
private readonly Dictionary<Identifier, float> abilityResistances = new Dictionary<Identifier, float>();
|
||||
|
||||
private readonly Dictionary<TalentResistanceIdentifier, float> abilityResistances = new();
|
||||
|
||||
public float GetAbilityResistance(AfflictionPrefab affliction)
|
||||
{
|
||||
return abilityResistances.TryGetValue(affliction.Identifier, out float value) ? value : abilityResistances.TryGetValue(affliction.AfflictionType, out float typeValue) ? typeValue : 1f;
|
||||
float resistance = 0f;
|
||||
bool hadResistance = false;
|
||||
|
||||
foreach (var (key, value) in abilityResistances)
|
||||
{
|
||||
if (key.ResistanceIdentifier == affliction.AfflictionType ||
|
||||
key.ResistanceIdentifier == affliction.Identifier)
|
||||
{
|
||||
resistance += value;
|
||||
hadResistance = true;
|
||||
}
|
||||
}
|
||||
|
||||
return hadResistance ? resistance : 1f;
|
||||
}
|
||||
|
||||
public void ChangeAbilityResistance(Identifier resistanceId, float value)
|
||||
public void ChangeAbilityResistance(TalentResistanceIdentifier identifier, float value)
|
||||
{
|
||||
if (abilityResistances.ContainsKey(resistanceId))
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
abilityResistances[resistanceId] *= value;
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Attempted to set ability resistance to an invalid value ({value})\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (abilityResistances.ContainsKey(identifier))
|
||||
{
|
||||
abilityResistances[identifier] *= value;
|
||||
}
|
||||
else
|
||||
{
|
||||
abilityResistances.Add(resistanceId, value);
|
||||
abilityResistances.Add(identifier, value);
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveAbilityResistance(TalentResistanceIdentifier identifier) => abilityResistances.Remove(identifier);
|
||||
|
||||
/// <summary>
|
||||
/// Compares just the species name and the group, ignores teams. There's a more complex version found in HumanAIController.cs
|
||||
/// </summary>
|
||||
|
||||
@@ -63,27 +63,34 @@ namespace Barotrauma
|
||||
public readonly CharacterInfo CharacterInfo;
|
||||
public readonly HeadPreset Preset;
|
||||
|
||||
private int hairIndex;
|
||||
public int HairIndex { get; set; }
|
||||
|
||||
public int HairIndex
|
||||
private int? hairWithHatIndex;
|
||||
|
||||
public void SetHairWithHatIndex()
|
||||
{
|
||||
get => hairIndex;
|
||||
set
|
||||
if (CharacterInfo.Hairs is null)
|
||||
{
|
||||
hairIndex = value;
|
||||
if (CharacterInfo.Hairs is null)
|
||||
if (HairIndex == -1)
|
||||
{
|
||||
HairWithHatIndex = value;
|
||||
return;
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError("Setting \"hairWithHatIndex\" before \"Hairs\" are defined!");
|
||||
#else
|
||||
DebugConsole.AddWarning("Setting \"hairWithHatIndex\" before \"Hairs\" are defined!");
|
||||
#endif
|
||||
}
|
||||
HairWithHatIndex = HairElement?.GetAttributeInt("replacewhenwearinghat", hairIndex) ?? -1;
|
||||
if (HairWithHatIndex < 0 || HairWithHatIndex >= CharacterInfo.Hairs.Count)
|
||||
hairWithHatIndex = HairIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
hairWithHatIndex = HairElement?.GetAttributeInt("replacewhenwearinghat", HairIndex) ?? -1;
|
||||
if (hairWithHatIndex < 0 || hairWithHatIndex >= CharacterInfo.Hairs.Count)
|
||||
{
|
||||
HairWithHatIndex = hairIndex;
|
||||
hairWithHatIndex = HairIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
public int HairWithHatIndex { get; private set; }
|
||||
|
||||
public int BeardIndex;
|
||||
public int MoustacheIndex;
|
||||
public int FaceAttachmentIndex;
|
||||
@@ -99,26 +106,29 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.Hairs == null) { return null; }
|
||||
if (hairIndex >= CharacterInfo.Hairs.Count)
|
||||
if (HairIndex >= CharacterInfo.Hairs.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairIndex})");
|
||||
DebugConsole.AddWarning($"Hair index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairIndex})");
|
||||
}
|
||||
return CharacterInfo.Hairs.ElementAtOrDefault(hairIndex);
|
||||
return CharacterInfo.Hairs.ElementAtOrDefault(HairIndex);
|
||||
}
|
||||
}
|
||||
public ContentXElement HairWithHatElement
|
||||
{
|
||||
get
|
||||
{
|
||||
if (CharacterInfo.Hairs == null) { return null; }
|
||||
if (HairWithHatIndex >= CharacterInfo.Hairs.Count)
|
||||
if (hairWithHatIndex == null)
|
||||
{
|
||||
DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {HairWithHatIndex})");
|
||||
SetHairWithHatIndex();
|
||||
}
|
||||
return CharacterInfo.Hairs.ElementAtOrDefault(HairWithHatIndex);
|
||||
if (CharacterInfo.Hairs == null) { return null; }
|
||||
if (hairWithHatIndex >= CharacterInfo.Hairs.Count)
|
||||
{
|
||||
DebugConsole.AddWarning($"Hair with hat index out of range (character: {CharacterInfo?.Name ?? "null"}, index: {hairWithHatIndex})");
|
||||
}
|
||||
return CharacterInfo.Hairs.ElementAtOrDefault(hairWithHatIndex.Value);
|
||||
}
|
||||
}
|
||||
|
||||
public ContentXElement BeardElement
|
||||
{
|
||||
get
|
||||
@@ -711,7 +721,7 @@ namespace Barotrauma
|
||||
private bool IsColorValid(in Color clr)
|
||||
=> clr.R != 0 || clr.G != 0 || clr.B != 0;
|
||||
|
||||
private void CheckColors()
|
||||
public void CheckColors()
|
||||
{
|
||||
if (!IsColorValid(Head.HairColor))
|
||||
{
|
||||
|
||||
@@ -27,6 +27,14 @@ namespace Barotrauma
|
||||
get { return _strength; }
|
||||
set
|
||||
{
|
||||
if (!MathUtils.IsValid(value))
|
||||
{
|
||||
#if DEBUG
|
||||
DebugConsole.ThrowError($"Attempted to set an affliction to an invalid strength ({value})\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
#endif
|
||||
return;
|
||||
}
|
||||
|
||||
if (_nonClampedStrength < 0 && value > 0)
|
||||
{
|
||||
_nonClampedStrength = value;
|
||||
@@ -440,9 +448,9 @@ namespace Barotrauma
|
||||
{
|
||||
statusEffect.Apply(type, deltaTime, characterHealth.Character, targetLimb);
|
||||
}
|
||||
if (targetLimb != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
if (characterHealth?.Character?.AnimController?.Limbs != null && statusEffect.HasTargetType(StatusEffect.TargetType.AllLimbs))
|
||||
{
|
||||
statusEffect.Apply(type, deltaTime, targetLimb.character, targets: targetLimb.character.AnimController.Limbs);
|
||||
statusEffect.Apply(type, deltaTime, characterHealth.Character, targets: characterHealth.Character.AnimController.Limbs);
|
||||
}
|
||||
if (statusEffect.HasTargetType(StatusEffect.TargetType.NearbyItems) ||
|
||||
statusEffect.HasTargetType(StatusEffect.TargetType.NearbyCharacters))
|
||||
|
||||
@@ -405,45 +405,53 @@ namespace Barotrauma
|
||||
|
||||
var root = doc.Root.FromPackage(pathToAppendage.ContentPackage);
|
||||
var limbElements = root.GetChildElements("limb").ToDictionary(e => e.GetAttributeString("id", null), e => e);
|
||||
//the IDs may need to be offset if the character has other extra appendages (e.g. from gene splicing)
|
||||
//that take up the IDs of this appendage
|
||||
int idOffset = 0;
|
||||
foreach (var jointElement in root.GetChildElements("joint"))
|
||||
{
|
||||
if (limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement))
|
||||
if (!limbElements.TryGetValue(jointElement.GetAttributeString("limb2", null), out ContentXElement limbElement)) { continue; }
|
||||
|
||||
var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
|
||||
Limb attachLimb = null;
|
||||
if (matchingAffliction.AttachLimbId > -1)
|
||||
{
|
||||
var jointParams = new RagdollParams.JointParams(jointElement, ragdoll.RagdollParams);
|
||||
Limb attachLimb = null;
|
||||
if (matchingAffliction.AttachLimbId > -1)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbName != null)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbType != LimbType.None)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
|
||||
}
|
||||
if (attachLimb == null)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
|
||||
}
|
||||
if (attachLimb != null)
|
||||
{
|
||||
jointParams.Limb1 = attachLimb.Params.ID;
|
||||
var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams)
|
||||
{
|
||||
// Ensure that we have a valid id for the new limb
|
||||
ID = ragdoll.Limbs.Length
|
||||
};
|
||||
jointParams.Limb2 = appendageLimbParams.ID;
|
||||
Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
|
||||
huskAppendage.body.Submarine = character.Submarine;
|
||||
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
|
||||
ragdoll.AddLimb(huskAppendage);
|
||||
ragdoll.AddJoint(jointParams);
|
||||
appendage.Add(huskAppendage);
|
||||
}
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == matchingAffliction.AttachLimbId);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbName != null)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Name == matchingAffliction.AttachLimbName);
|
||||
}
|
||||
else if (matchingAffliction.AttachLimbType != LimbType.None)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.type == matchingAffliction.AttachLimbType);
|
||||
}
|
||||
if (attachLimb == null)
|
||||
{
|
||||
attachLimb = ragdoll.Limbs.FirstOrDefault(l => !l.IsSevered && l.Params.ID == jointParams.Limb1);
|
||||
}
|
||||
if (attachLimb != null)
|
||||
{
|
||||
jointParams.Limb1 = attachLimb.Params.ID;
|
||||
//the joint attaches to a limb outside the character's normal limb count = to another part of the appendage
|
||||
// -> if the appendage's IDs have been offset, we need to take that into account to attach to the correct limb
|
||||
if (jointParams.Limb1 >= ragdoll.RagdollParams.Limbs.Count)
|
||||
{
|
||||
jointParams.Limb1 += idOffset;
|
||||
}
|
||||
var appendageLimbParams = new RagdollParams.LimbParams(limbElement, ragdoll.RagdollParams);
|
||||
if (idOffset == 0)
|
||||
{
|
||||
idOffset = ragdoll.Limbs.Length - appendageLimbParams.ID;
|
||||
}
|
||||
jointParams.Limb2 = appendageLimbParams.ID = ragdoll.Limbs.Length;
|
||||
Limb huskAppendage = new Limb(ragdoll, character, appendageLimbParams);
|
||||
huskAppendage.body.Submarine = character.Submarine;
|
||||
huskAppendage.body.SetTransform(attachLimb.SimPosition, attachLimb.Rotation);
|
||||
ragdoll.AddLimb(huskAppendage);
|
||||
ragdoll.AddJoint(jointParams);
|
||||
appendage.Add(huskAppendage);
|
||||
}
|
||||
}
|
||||
return appendage;
|
||||
}
|
||||
|
||||
@@ -887,7 +887,7 @@ namespace Barotrauma
|
||||
//clamp above 0.1 (no amount of oxygen low resistance should keep the character alive indefinitely)
|
||||
float decreaseSpeed = Math.Max(0.1f, 1f - oxygenlowResistance);
|
||||
//the character dies of oxygen deprivation in 100 seconds after losing consciousness
|
||||
OxygenAmount = MathHelper.Clamp(OxygenAmount - decreaseSpeed * deltaTime, -100.0f, 100.0f);
|
||||
OxygenAmount = MathHelper.Clamp(OxygenAmount - decreaseSpeed * deltaTime, -100.0f, 100.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -895,15 +895,21 @@ namespace Barotrauma
|
||||
float increaseSpeed = 10.0f;
|
||||
decreaseSpeed *= (1f - oxygenlowResistance);
|
||||
increaseSpeed *= (1f + oxygenlowResistance);
|
||||
|
||||
float holdBreathMultiplier = 1f + GetStatValue(StatTypes.HoldBreathMultiplier);
|
||||
decreaseSpeed *= holdBreathMultiplier;
|
||||
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f);
|
||||
float holdBreathMultiplier = Character.GetStatValue(StatTypes.HoldBreathMultiplier);
|
||||
if (holdBreathMultiplier <= -1.0f)
|
||||
{
|
||||
OxygenAmount = -100.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
decreaseSpeed /= 1.0f + Character.GetStatValue(StatTypes.HoldBreathMultiplier);
|
||||
OxygenAmount = MathHelper.Clamp(OxygenAmount + deltaTime * (Character.OxygenAvailable < InsufficientOxygenThreshold ? decreaseSpeed : increaseSpeed), -100.0f, 100.0f);
|
||||
}
|
||||
}
|
||||
|
||||
UpdateOxygenProjSpecific(prevOxygen, deltaTime);
|
||||
}
|
||||
|
||||
|
||||
partial void UpdateOxygenProjSpecific(float prevOxygen, float deltaTime);
|
||||
|
||||
partial void UpdateBleedingProjSpecific(AfflictionBleeding affliction, Limb targetLimb, float deltaTime);
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
using System.Collections.Immutable;
|
||||
using Microsoft.Xna.Framework;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGainSimultaneousSkill : CharacterAbility
|
||||
{
|
||||
@@ -15,6 +12,10 @@ namespace Barotrauma.Abilities
|
||||
skillIdentifier = abilityElement.GetAttributeIdentifier("skillidentifier", "");
|
||||
ignoreAbilitySkillGain = abilityElement.GetAttributeBool("ignoreabilityskillgain", true);
|
||||
targetAllies = abilityElement.GetAttributeBool("targetallies", false);
|
||||
if (skillIdentifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}: skill identifier not defined.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
@@ -23,13 +24,12 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (ignoreAbilitySkillGain && abilitySkillGain.GainedFromAbility) { return; }
|
||||
Identifier identifier = skillIdentifier == "inherit" ? abilitySkillGain.SkillIdentifier : skillIdentifier;
|
||||
|
||||
if (targetAllies)
|
||||
{
|
||||
foreach (Character character in Character.GetFriendlyCrew(Character))
|
||||
foreach (Character otherCharacter in Character.GetFriendlyCrew(Character))
|
||||
{
|
||||
if (character == Character) { continue; }
|
||||
Character.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true);
|
||||
if (otherCharacter == Character) { continue; }
|
||||
otherCharacter.Info?.IncreaseSkillLevel(identifier, abilitySkillGain.Value, gainedFromAbility: true);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveAffliction : CharacterAbility
|
||||
{
|
||||
@@ -18,7 +16,7 @@ namespace Barotrauma.Abilities
|
||||
|
||||
if (afflictionId.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterAbilityGiveAffliction - affliction identifier not set.");
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveAffliction - affliction identifier not set.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveFlag : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveMoney : CharacterAbility
|
||||
{
|
||||
@@ -13,6 +11,11 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
scalingStatIdentifier = abilityElement.GetAttributeIdentifier("scalingstatidentifier", Identifier.Empty);
|
||||
|
||||
if (amount == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, CharacterAbilityGiveMoney - amount of money set to 0.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyEffectSpecific(Character targetCharacter)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
public enum PermanentStatPlaceholder
|
||||
{
|
||||
@@ -28,6 +26,10 @@ namespace Barotrauma.Abilities
|
||||
public CharacterAbilityGivePermanentStat(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
|
||||
if (statIdentifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent \"{CharacterTalent.DebugIdentifier}\" - stat identifier not defined.");
|
||||
}
|
||||
string statTypeName = abilityElement.GetAttributeString("stattype", string.Empty);
|
||||
statType = string.IsNullOrEmpty(statTypeName) ? StatTypes.None : CharacterAbilityGroup.ParseStatType(statTypeName, CharacterTalent.DebugIdentifier);
|
||||
value = abilityElement.GetAttributeFloat("value", 0f);
|
||||
|
||||
@@ -11,6 +11,14 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
factionIdentifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
amount = abilityElement.GetAttributeFloat("amount", 0f);
|
||||
if (factionIdentifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, faction identifier not defined.");
|
||||
}
|
||||
if (amount == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of reputation to give is 0.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect()
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveResistance : CharacterAbility
|
||||
{
|
||||
@@ -10,17 +8,23 @@ namespace Barotrauma.Abilities
|
||||
public CharacterAbilityGiveResistance(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", abilityElement.GetAttributeIdentifier("resistance", Identifier.Empty));
|
||||
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f); // rename this to resistance for consistency
|
||||
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
|
||||
|
||||
if (resistanceId.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterAbilityGiveResistance - resistance identifier not set.");
|
||||
}
|
||||
if (MathUtils.NearlyEqual(multiplier, 1))
|
||||
{
|
||||
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - multiplier set to 1, which will do nothing.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, multiplier);
|
||||
TalentResistanceIdentifier identifier = new(resistanceId, CharacterTalent.Prefab.Identifier);
|
||||
Character.ChangeAbilityResistance(identifier, multiplier);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveStat : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityGiveTalentPoints : CharacterAbility
|
||||
{
|
||||
@@ -9,7 +6,11 @@ namespace Barotrauma.Abilities
|
||||
|
||||
public CharacterAbilityGiveTalentPoints(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
if (amount == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
|
||||
@@ -9,6 +9,10 @@ namespace Barotrauma.Abilities
|
||||
public CharacterAbilityGiveTalentPointsToAllies(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
amount = abilityElement.GetAttributeInt("amount", 0);
|
||||
if (amount == 0)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, amount of talent points to give is 0.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -6,6 +6,10 @@ namespace Barotrauma.Abilities
|
||||
public CharacterAbilityMarkAsLooted(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, identifier is empty in {nameof(CharacterAbilityMarkAsLooted)}.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using System;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyFlag : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -1,23 +1,25 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyResistance : CharacterAbility
|
||||
{
|
||||
private readonly Identifier resistanceId;
|
||||
private readonly float resistance;
|
||||
private readonly float multiplier;
|
||||
bool lastState;
|
||||
public override bool AllowClientSimulation => true;
|
||||
|
||||
// should probably be split to different classes
|
||||
public CharacterAbilityModifyResistance(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", "");
|
||||
resistance = abilityElement.GetAttributeFloat("resistance", 1f);
|
||||
resistanceId = abilityElement.GetAttributeIdentifier("resistanceid", abilityElement.GetAttributeIdentifier("resistance", Identifier.Empty));
|
||||
multiplier = abilityElement.GetAttributeFloat("multiplier", 1f);
|
||||
|
||||
if (resistanceId.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError("Error in CharacterAbilityModifyResistance - resistance identifier not set.");
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier} - resistance identifier not set in {nameof(CharacterAbilityModifyResistance)}.");
|
||||
}
|
||||
if (MathUtils.NearlyEqual(multiplier, 1.0f))
|
||||
{
|
||||
DebugConsole.AddWarning($"Possible error in talent {CharacterTalent.DebugIdentifier} - resistance set to 1, which will do nothing.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +27,15 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
if (conditionsMatched != lastState)
|
||||
{
|
||||
Character.ChangeAbilityResistance(resistanceId, conditionsMatched ? resistance : 1 / resistance);
|
||||
TalentResistanceIdentifier identifier = new(resistanceId, CharacterTalent.Prefab.Identifier);
|
||||
if (conditionsMatched)
|
||||
{
|
||||
Character.ChangeAbilityResistance(identifier, multiplier);
|
||||
}
|
||||
else
|
||||
{
|
||||
Character.RemoveAbilityResistance(identifier);
|
||||
}
|
||||
lastState = conditionsMatched;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyStat : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityModifyValue : CharacterAbility
|
||||
{
|
||||
@@ -11,6 +9,10 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
|
||||
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
|
||||
if (MathUtils.NearlyEqual(addedValue, 0.0f) && MathUtils.NearlyEqual(multiplyValue, 1.0f))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityModifyValue)} - added value is 0 and multiplier is 1, the ability will do nothing.");
|
||||
}
|
||||
}
|
||||
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPutItem : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -9,7 +9,11 @@ namespace Barotrauma.Abilities
|
||||
|
||||
public CharacterAbilityResetPermanentStat(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
|
||||
statIdentifier = abilityElement.GetAttributeIdentifier("statidentifier", Identifier.Empty);
|
||||
if (statIdentifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilityResetPermanentStat)} - statIdentifier is empty.");
|
||||
}
|
||||
}
|
||||
protected override void ApplyEffect(AbilityObject abilityObject)
|
||||
{
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityRevive : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -11,6 +11,10 @@ namespace Barotrauma.Abilities
|
||||
{
|
||||
identifier = abilityElement.GetAttributeIdentifier("identifier", Identifier.Empty);
|
||||
value = abilityElement.GetAttributeInt("value", 0);
|
||||
if (identifier.IsEmpty)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent {CharacterTalent.DebugIdentifier}, {nameof(CharacterAbilitySetMetadataInt)} - identifier is empty.");
|
||||
}
|
||||
}
|
||||
|
||||
public override void InitializeAbility(bool addingFirstTime)
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityApprenticeship : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -1,20 +1,17 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityAtmosMachine : CharacterAbility
|
||||
{
|
||||
private readonly float addedValue;
|
||||
private readonly float multiplyValue;
|
||||
private readonly Identifier[] tags;
|
||||
private readonly int maxMultiplyCount;
|
||||
|
||||
public CharacterAbilityAtmosMachine(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
addedValue = abilityElement.GetAttributeFloat("addedvalue", 0f);
|
||||
multiplyValue = abilityElement.GetAttributeFloat("multiplyvalue", 1f);
|
||||
tags = abilityElement.GetAttributeIdentifierArray("tags", Array.Empty<Identifier>());
|
||||
maxMultiplyCount = abilityElement.GetAttributeInt("maxmultiplycount", int.MaxValue);
|
||||
}
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityBountyHunter : CharacterAbility
|
||||
{
|
||||
private float vitalityPercentage;
|
||||
private readonly float vitalityPercentage;
|
||||
|
||||
public CharacterAbilityBountyHunter(CharacterAbilityGroup characterAbilityGroup, ContentXElement abilityElement) : base(characterAbilityGroup, abilityElement)
|
||||
{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityMultitasker : CharacterAbility
|
||||
{
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
class CharacterAbilityPsychoClown : CharacterAbility
|
||||
{
|
||||
private StatTypes statType;
|
||||
private float maxValue;
|
||||
private string afflictionIdentifier;
|
||||
private readonly StatTypes statType;
|
||||
private readonly float maxValue;
|
||||
private readonly string afflictionIdentifier;
|
||||
private float lastValue = 0f;
|
||||
public override bool AllowClientSimulation => true;
|
||||
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using Barotrauma.Items.Components;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#nullable enable
|
||||
|
||||
using Barotrauma.Extensions;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma.Abilities
|
||||
{
|
||||
|
||||
@@ -66,8 +66,8 @@ namespace Barotrauma
|
||||
IEnumerable<TalentSubTree> blockingSubTrees = tree.TalentSubTrees.Where(tst => tst.BlockedTrees.Contains(targetTree.Identifier)),
|
||||
requiredSubTrees = tree.TalentSubTrees.Where(tst => targetTree.RequiredTrees.Contains(tst.Identifier));
|
||||
|
||||
return requiredSubTrees.All(tst => tst.IsCompleted(selectedTalents)) && // check if we meet requirements
|
||||
!blockingSubTrees.Any(tst => tst.HasAnyTalent(selectedTalents)); // check if any other talent trees are blocking this one
|
||||
return requiredSubTrees.All(tst => tst.HasEnoughTalents(selectedTalents)) && // check if we meet requirements
|
||||
!blockingSubTrees.Any(tst => tst.HasAnyTalent(selectedTalents) && !tst.HasMaxTalents(selectedTalents)); // check if any other talent trees are blocking this one
|
||||
}
|
||||
|
||||
// i hate this function - markus
|
||||
@@ -128,16 +128,26 @@ namespace Barotrauma
|
||||
public static bool IsViableTalentForCharacter(Character character, Identifier talentIdentifier, IReadOnlyCollection<Identifier> selectedTalents)
|
||||
{
|
||||
if (character?.Info?.Job.Prefab == null) { return false; }
|
||||
|
||||
if (character.Info.GetTotalTalentPoints() - selectedTalents.Count <= 0) { return false; }
|
||||
|
||||
if (!JobTalentTrees.TryGet(character.Info.Job.Prefab.Identifier, out TalentTree talentTree)) { return false; }
|
||||
|
||||
foreach (var subTree in talentTree!.TalentSubTrees)
|
||||
{
|
||||
if (subTree.AllTalentIdentifiers.Contains(talentIdentifier) && subTree.HasMaxTalents(selectedTalents)) { return false; }
|
||||
|
||||
foreach (var talentOptionStage in subTree.TalentOptionStages)
|
||||
{
|
||||
bool hasTalentInThisTier = talentOptionStage.HasEnoughTalents(selectedTalents);
|
||||
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
|
||||
{
|
||||
return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
|
||||
}
|
||||
bool optionStageCompleted = talentOptionStage.HasEnoughTalents(selectedTalents);
|
||||
if (!optionStageCompleted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
/*bool hasTalentInThisTier = talentOptionStage.HasMaxTalents(selectedTalents);
|
||||
if (!hasTalentInThisTier)
|
||||
{
|
||||
if (talentOptionStage.TalentIdentifiers.Contains(talentIdentifier))
|
||||
@@ -145,7 +155,7 @@ namespace Barotrauma
|
||||
return TalentTreeMeetsRequirements(talentTree, subTree, selectedTalents);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
@@ -196,7 +206,8 @@ namespace Barotrauma
|
||||
public readonly ImmutableHashSet<Identifier> RequiredTrees;
|
||||
public readonly ImmutableHashSet<Identifier> BlockedTrees;
|
||||
|
||||
public bool IsCompleted(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.All(option => option.HasEnoughTalents(talents));
|
||||
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.All(option => option.HasEnoughTalents(talents));
|
||||
public bool HasMaxTalents(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.All(option => option.HasMaxTalents(talents));
|
||||
public bool HasAnyTalent(IReadOnlyCollection<Identifier> talents) => TalentOptionStages.Any(option => option.HasSelectedTalent(talents));
|
||||
|
||||
public TalentSubTree(ContentXElement subTreeElement)
|
||||
@@ -228,6 +239,13 @@ namespace Barotrauma
|
||||
|
||||
public IEnumerable<Identifier> TalentIdentifiers => talentIdentifiers;
|
||||
|
||||
/// <summary>
|
||||
/// How many talents need to be unlocked to consider this tree completed
|
||||
/// </summary>
|
||||
public readonly int RequiredTalents;
|
||||
/// <summary>
|
||||
/// How many talents can be unlocked in total
|
||||
/// </summary>
|
||||
public readonly int MaxChosenTalents;
|
||||
|
||||
/// <summary>
|
||||
@@ -236,8 +254,9 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly Dictionary<Identifier, ImmutableHashSet<Identifier>> ShowCaseTalents = new Dictionary<Identifier, ImmutableHashSet<Identifier>>();
|
||||
|
||||
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= MaxChosenTalents;
|
||||
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
|
||||
public bool HasEnoughTalents(CharacterInfo character) => CountMatchingTalents(character.UnlockedTalents) >= RequiredTalents;
|
||||
public bool HasEnoughTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= RequiredTalents;
|
||||
public bool HasMaxTalents(IReadOnlyCollection<Identifier> selectedTalents) => CountMatchingTalents(selectedTalents) >= MaxChosenTalents;
|
||||
|
||||
// No LINQ
|
||||
public bool HasSelectedTalent(IReadOnlyCollection<Identifier> selectedTalents)
|
||||
@@ -267,10 +286,15 @@ namespace Barotrauma
|
||||
|
||||
public TalentOption(ContentXElement talentOptionsElement, Identifier debugIdentifier)
|
||||
{
|
||||
MaxChosenTalents = talentOptionsElement.GetAttributeInt("maxchosentalents", 1);
|
||||
MaxChosenTalents = talentOptionsElement.GetAttributeInt(nameof(MaxChosenTalents), 1);
|
||||
RequiredTalents = talentOptionsElement.GetAttributeInt(nameof(RequiredTalents), MaxChosenTalents);
|
||||
|
||||
if (RequiredTalents > MaxChosenTalents)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - MaxChosenTalents is larger than RequiredTalents.");
|
||||
}
|
||||
|
||||
HashSet<Identifier> identifiers = new HashSet<Identifier>();
|
||||
|
||||
foreach (ContentXElement talentOptionElement in talentOptionsElement.Elements())
|
||||
{
|
||||
Identifier elementName = talentOptionElement.Name.ToIdentifier();
|
||||
@@ -293,6 +317,15 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
talentIdentifiers = identifiers.ToImmutableHashSet();
|
||||
|
||||
if (RequiredTalents > talentIdentifiers.Count)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - completing a stage of the tree requires more talents than there are in the stage.");
|
||||
}
|
||||
if (MaxChosenTalents > talentIdentifiers.Count)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in talent tree {debugIdentifier} - maximum number of talents to choose is larger than the number of talents.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Barotrauma
|
||||
[Serialize("", IsPropertySaveable.Yes)]
|
||||
public Identifier OrderTargetTag { get; set; }
|
||||
|
||||
[Serialize(OrderPriority.Top, IsPropertySaveable.Yes)]
|
||||
[Serialize(OrderPriority.Any, IsPropertySaveable.Yes)]
|
||||
public OrderPriority Priority { get; set; }
|
||||
|
||||
public CheckOrderAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
@@ -43,6 +43,9 @@ partial class UIHighlightAction : EventAction
|
||||
[Serialize(true, IsPropertySaveable.Yes)]
|
||||
public bool Bounce { get; set; }
|
||||
|
||||
[Serialize(false, IsPropertySaveable.Yes)]
|
||||
public bool HighlightMultiple { get; set; }
|
||||
|
||||
private bool isFinished;
|
||||
|
||||
public UIHighlightAction(ScriptedEvent parentEvent, ContentXElement element) : base(parentEvent, element) { }
|
||||
|
||||
@@ -207,25 +207,23 @@ namespace Barotrauma
|
||||
level.StartLocation.Connections.ForEach(c => c.Locked = false);
|
||||
}
|
||||
}
|
||||
|
||||
AddChildEvents(initialEventSet);
|
||||
|
||||
void AddChildEvents(EventSet eventSet)
|
||||
}
|
||||
RegisterNonRepeatableChildEvents(initialEventSet);
|
||||
void RegisterNonRepeatableChildEvents(EventSet eventSet)
|
||||
{
|
||||
if (eventSet == null) { return; }
|
||||
if (eventSet.OncePerLevel)
|
||||
{
|
||||
if (eventSet == null) { return; }
|
||||
if (eventSet.OncePerOutpost)
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
||||
{
|
||||
foreach (EventPrefab ep in eventSet.EventPrefabs.SelectMany(e => e.EventPrefabs))
|
||||
{
|
||||
nonRepeatableEvents.Add(ep);
|
||||
}
|
||||
}
|
||||
foreach (EventSet childSet in eventSet.ChildSets)
|
||||
{
|
||||
AddChildEvents(childSet);
|
||||
nonRepeatableEvents.Add(ep);
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (EventSet childSet in eventSet.ChildSets)
|
||||
{
|
||||
RegisterNonRepeatableChildEvents(childSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PreloadContent(GetFilesToPreload());
|
||||
@@ -374,26 +372,18 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public void RegisterEventHistory()
|
||||
{
|
||||
if (level?.LevelData != null)
|
||||
if (level?.LevelData == null) { return; }
|
||||
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
level.LevelData.EventsExhausted = true;
|
||||
if (level.LevelData.Type == LevelData.LevelType.Outpost)
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
level.LevelData.EventHistory.AddRange(selectedEvents.Values.SelectMany(v => v).Select(e => e.Prefab).Where(e => !level.LevelData.EventHistory.Contains(e)));
|
||||
if (level.LevelData.EventHistory.Count > MaxEventHistory)
|
||||
{
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
foreach (var usedUniqueSet in usedUniqueSets)
|
||||
{
|
||||
if (!level.LevelData.UsedUniqueSets.Contains(usedUniqueSet.Identifier))
|
||||
{
|
||||
level.LevelData.UsedUniqueSets.Add(usedUniqueSet.Identifier);
|
||||
}
|
||||
level.LevelData.EventHistory.RemoveRange(0, level.LevelData.EventHistory.Count - MaxEventHistory);
|
||||
}
|
||||
}
|
||||
level.LevelData.NonRepeatableEvents.AddRange(nonRepeatableEvents.Where(e => !level.LevelData.NonRepeatableEvents.Contains(e)));
|
||||
}
|
||||
|
||||
public void SkipEventCooldown()
|
||||
@@ -418,16 +408,11 @@ namespace Barotrauma
|
||||
|
||||
DebugConsole.NewMessage($"Loading event set {eventSet.Identifier}", Color.LightBlue, debugOnly: true);
|
||||
|
||||
if (eventSet.Unique && !usedUniqueSets.Contains(eventSet))
|
||||
{
|
||||
usedUniqueSets.Add(eventSet);
|
||||
}
|
||||
|
||||
int applyCount = 1;
|
||||
List<Func<Level.InterestingPosition, bool>> spawnPosFilter = new List<Func<Level.InterestingPosition, bool>>();
|
||||
if (eventSet.PerRuin)
|
||||
{
|
||||
applyCount = level.Ruins.Count();
|
||||
applyCount = level.Ruins.Count;
|
||||
foreach (var ruin in level.Ruins)
|
||||
{
|
||||
spawnPosFilter.Add(pos => pos.Ruin == ruin);
|
||||
@@ -435,7 +420,7 @@ namespace Barotrauma
|
||||
}
|
||||
else if (eventSet.PerCave)
|
||||
{
|
||||
applyCount = level.Caves.Count();
|
||||
applyCount = level.Caves.Count;
|
||||
foreach (var cave in level.Caves)
|
||||
{
|
||||
spawnPosFilter.Add(pos => pos.Cave == cave);
|
||||
@@ -452,8 +437,8 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
bool isPrefabSuitable(EventPrefab e)
|
||||
=> e.BiomeIdentifier.IsEmpty ||
|
||||
e.BiomeIdentifier == level.LevelData?.Biome?.Identifier;
|
||||
=> (e.BiomeIdentifier.IsEmpty || e.BiomeIdentifier == level.LevelData?.Biome?.Identifier) &&
|
||||
!level.LevelData.NonRepeatableEvents.Contains(e);
|
||||
|
||||
foreach (var subEventPrefab in eventSet.EventPrefabs)
|
||||
{
|
||||
@@ -610,8 +595,7 @@ namespace Barotrauma
|
||||
return
|
||||
level.Difficulty >= eventSet.MinLevelDifficulty && level.Difficulty <= eventSet.MaxLevelDifficulty &&
|
||||
level.LevelData.Type == eventSet.LevelType &&
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier) &&
|
||||
(!eventSet.Unique || !level.LevelData.UsedUniqueSets.Contains(eventSet.Identifier));
|
||||
(eventSet.BiomeIdentifier.IsEmpty || eventSet.BiomeIdentifier == level.LevelData.Biome.Identifier);
|
||||
}
|
||||
|
||||
private bool IsValidForLocation(EventSet eventSet, Location location)
|
||||
|
||||
@@ -114,15 +114,9 @@ namespace Barotrauma
|
||||
public readonly bool DisableInHuntingGrounds;
|
||||
|
||||
/// <summary>
|
||||
/// If true, events from this set shouldn't be selected again as long as they remain in <see cref="LevelData.NonRepeatableEvents"/> which has a limited size.
|
||||
/// Use <see cref="Unique"/> to prevent selecting the whole set again altogether.
|
||||
/// If true, events from this set can only occur once in the level.
|
||||
/// </summary>
|
||||
public readonly bool OncePerOutpost;
|
||||
|
||||
/// <summary>
|
||||
/// If true, the whole set can only be selected once for a level.
|
||||
/// </summary>
|
||||
public readonly bool Unique;
|
||||
public readonly bool OncePerLevel;
|
||||
|
||||
public readonly bool DelayWhenCrewAway;
|
||||
|
||||
@@ -289,8 +283,7 @@ namespace Barotrauma
|
||||
DisableInHuntingGrounds = element.GetAttributeBool("disableinhuntinggrounds", false);
|
||||
IgnoreCoolDown = element.GetAttributeBool("ignorecooldown", parentSet?.IgnoreCoolDown ?? (PerRuin || PerCave || PerWreck));
|
||||
DelayWhenCrewAway = element.GetAttributeBool("delaywhencrewaway", !PerRuin && !PerCave && !PerWreck);
|
||||
OncePerOutpost = element.GetAttributeBool("onceperoutpost", false);
|
||||
Unique = element.GetAttributeBool("unique", false);
|
||||
OncePerLevel = element.GetAttributeBool("onceperlevel", element.GetAttributeBool("onceperoutpost", false));
|
||||
TriggerEventCooldown = element.GetAttributeBool("triggereventcooldown", true);
|
||||
IsCampaignSet = element.GetAttributeBool("campaign", LevelType == LevelData.LevelType.Outpost || (parentSet?.IsCampaignSet ?? false));
|
||||
ResetTime = element.GetAttributeFloat("resettime", 0);
|
||||
|
||||
@@ -386,23 +386,27 @@ namespace Barotrauma
|
||||
#if CLIENT
|
||||
foreach (Character character in crewCharacters)
|
||||
{
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
character.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
character.Info?.GiveExperience(experienceGain, isMissionExperience: true);
|
||||
GiveMissionExperience(character.Info);
|
||||
}
|
||||
#else
|
||||
foreach (Barotrauma.Networking.Client c in GameMain.Server.ConnectedClients)
|
||||
{
|
||||
//give the experience to the stored characterinfo if the client isn't currently controlling a character
|
||||
CharacterInfo info = c.Character?.Info ?? c.CharacterInfo;
|
||||
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
|
||||
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
|
||||
GiveMissionExperience(c.Character?.Info ?? c.CharacterInfo);
|
||||
}
|
||||
foreach (Character bot in GameSession.GetSessionCrewCharacters(CharacterType.Bot))
|
||||
{
|
||||
GiveMissionExperience(bot.Info);
|
||||
}
|
||||
#endif
|
||||
|
||||
void GiveMissionExperience(CharacterInfo info)
|
||||
{
|
||||
var experienceGainMultiplierIndividual = new AbilityMissionExperienceGainMultiplier(this, 1f);
|
||||
info?.Character?.CheckTalents(AbilityEffectType.OnGainMissionExperience, experienceGainMultiplierIndividual);
|
||||
info?.GiveExperience((int)(experienceGain * experienceGainMultiplier.Value), isMissionExperience: true);
|
||||
}
|
||||
|
||||
// apply money gains afterwards to prevent them from affecting XP gains
|
||||
var missionMoneyGainMultiplier = new AbilityMissionMoneyGainMultiplier(this, 1f);
|
||||
crewCharacters.ForEach(c => c.CheckTalents(AbilityEffectType.OnGainMissionMoney, missionMoneyGainMultiplier));
|
||||
|
||||
@@ -306,14 +306,6 @@ namespace Barotrauma
|
||||
return validContainers;
|
||||
}
|
||||
|
||||
private static readonly (int quality, float commonness)[] qualityCommonnesses = new (int quality, float commonness)[Quality.MaxQuality + 1]
|
||||
{
|
||||
(0, 1.0f),
|
||||
(1, 0.0f),
|
||||
(2, 0.0f),
|
||||
(3, 0.0f),
|
||||
};
|
||||
|
||||
private static List<Item> CreateItems(ItemPrefab itemPrefab, List<ItemContainer> containers, KeyValuePair<ItemContainer, PreferredContainer> validContainer)
|
||||
{
|
||||
List<Item> newItems = new List<Item>();
|
||||
@@ -336,11 +328,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
var existingItem = validContainer.Key.Inventory.AllItems.FirstOrDefault(it => it.Prefab == itemPrefab);
|
||||
int quality =
|
||||
existingItem?.Quality ??
|
||||
ToolBox.SelectWeightedRandom(
|
||||
qualityCommonnesses.Select(q => q.quality).ToList(),
|
||||
qualityCommonnesses.Select(q => q.commonness).ToList(), Rand.RandSync.ServerAndClient);
|
||||
int quality = existingItem?.Quality ?? Quality.GetSpawnedItemQuality(validContainer.Key.Item.Submarine, Level.Loaded, Rand.RandSync.ServerAndClient);
|
||||
if (!validContainer.Key.Inventory.CanBePut(itemPrefab, quality: quality)) { break; }
|
||||
var item = new Item(itemPrefab, validContainer.Key.Item.Position, validContainer.Key.Item.Submarine, callOnItemLoaded: false)
|
||||
{
|
||||
|
||||
@@ -276,7 +276,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
//get all walls within range
|
||||
//get all walls within range the arc could potentially hit
|
||||
List<Entity> entitiesInRange = new List<Entity>(100);
|
||||
foreach (Structure structure in Structure.WallList)
|
||||
{
|
||||
@@ -284,10 +284,10 @@ namespace Barotrauma.Items.Components
|
||||
if (structure.Submarine != null&& !submarinesInRange.Contains(structure.Submarine)) { continue; }
|
||||
|
||||
var structureWorldRect = structure.WorldRect;
|
||||
if (worldPosition.X < structureWorldRect.X - range) continue;
|
||||
if (worldPosition.X > structureWorldRect.Right + range) continue;
|
||||
if (worldPosition.Y > structureWorldRect.Y + range) continue;
|
||||
if (worldPosition.Y < structureWorldRect.Y -structureWorldRect.Height - range) continue;
|
||||
if (worldPosition.X < structureWorldRect.X - range) { continue; }
|
||||
if (worldPosition.X > structureWorldRect.Right + range) { continue; }
|
||||
if (worldPosition.Y > structureWorldRect.Y + range) { continue; }
|
||||
if (worldPosition.Y < structureWorldRect.Y - structureWorldRect.Height - range) { continue; }
|
||||
|
||||
if (structure.Submarine != null)
|
||||
{
|
||||
@@ -317,6 +317,7 @@ namespace Barotrauma.Items.Components
|
||||
nodes.Add(new Node(worldPosition, -1));
|
||||
}
|
||||
|
||||
//get all characters within range the arc could potentially hit
|
||||
float totalRange = RaycastRange + range;
|
||||
foreach (Character character in Character.CharacterList)
|
||||
{
|
||||
@@ -325,11 +326,20 @@ namespace Barotrauma.Items.Components
|
||||
if (OutdoorsOnly && character.Submarine != null) { continue; }
|
||||
if (character.Submarine != null && !submarinesInRange.Contains(character.Submarine)) { continue; }
|
||||
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls ||
|
||||
(RaycastRange > 0.0f && MathUtils.LineToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition) < range * range * RangeMultiplierInWalls))
|
||||
if (Vector2.DistanceSquared(character.WorldPosition, worldPosition) < totalRange * totalRange * RangeMultiplierInWalls)
|
||||
{
|
||||
entitiesInRange.Add(character);
|
||||
charactersInRange.Add((character, nodes[0]));
|
||||
}
|
||||
//if the weapon does a raycast, check distance to the ray too (not just the end of the ray)
|
||||
if (RaycastRange > 0)
|
||||
{
|
||||
float distSqr = MathUtils.LineSegmentToPointDistanceSquared(worldPosition, item.WorldPosition, character.WorldPosition);
|
||||
//if the distance from the initial raycast to the character is small (e.g. goes through the character), we know it must hit
|
||||
if (distSqr < range * range * RangeMultiplierInWalls)
|
||||
{
|
||||
if (!entitiesInRange.Contains(character)) { entitiesInRange.Add(character); }
|
||||
charactersInRange.Add((character, nodes.First()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -378,7 +388,7 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
else if (entitiesInRange[i] is Character character)
|
||||
{
|
||||
dist = Vector2.Distance(character.WorldPosition, currPos);
|
||||
dist = MathUtils.LineSegmentToPointDistanceSquared(currPos, nodes[parentNodeIndex].WorldPosition, character.WorldPosition);
|
||||
}
|
||||
|
||||
if (dist < closestDist)
|
||||
@@ -494,17 +504,31 @@ namespace Barotrauma.Items.Components
|
||||
if (IgnoreUser && character == user) { continue; }
|
||||
if (OutdoorsOnly && character.Submarine != null) { continue; }
|
||||
|
||||
Vector2 characterMin = new Vector2(character.AnimController.Limbs.Min(l => l.WorldPosition.X), character.AnimController.Limbs.Min(l => l.WorldPosition.Y));
|
||||
Vector2 characterMax = new Vector2(character.AnimController.Limbs.Max(l => l.WorldPosition.X), character.AnimController.Limbs.Max(l => l.WorldPosition.Y));
|
||||
if (targetStructure.IsHorizontal)
|
||||
{
|
||||
if (otherEntity.WorldPosition.X < targetStructure.WorldRect.X) { continue; }
|
||||
if (otherEntity.WorldPosition.X > targetStructure.WorldRect.Right) { continue; }
|
||||
if (Math.Abs(otherEntity.WorldPosition.Y - targetStructure.WorldPosition.Y) > currentRange) { continue; }
|
||||
if (characterMax.X < targetStructure.WorldRect.X) { continue; }
|
||||
if (characterMin.X > targetStructure.WorldRect.Right) { continue; }
|
||||
if (Math.Abs(characterMin.Y - targetStructure.WorldPosition.Y) > currentRange &&
|
||||
Math.Abs(characterMax.Y - targetStructure.WorldPosition.Y) > currentRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (otherEntity.WorldPosition.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; }
|
||||
if (otherEntity.WorldPosition.Y > targetStructure.WorldRect.Y) { continue; }
|
||||
if (Math.Abs(otherEntity.WorldPosition.X - targetStructure.WorldPosition.X) > currentRange) { continue; }
|
||||
if (characterMax.Y < targetStructure.WorldRect.Y - targetStructure.Rect.Height) { continue; }
|
||||
if (characterMin.Y > targetStructure.WorldRect.Y) { continue; }
|
||||
if (Math.Abs(characterMin.X - targetStructure.WorldPosition.X) > currentRange &&
|
||||
Math.Abs(characterMax.X - targetStructure.WorldPosition.X) > currentRange)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (!charactersInRange.Any(c => c.character == character))
|
||||
{
|
||||
charactersInRange.Add((character, nodes[parentNodeIndex]));
|
||||
}
|
||||
float closestNodeDistSqr = float.MaxValue;
|
||||
int closestNodeIndex = -1;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public enum UseEnvironment
|
||||
{
|
||||
Air, Water, Both
|
||||
Air, Water, Both, None
|
||||
};
|
||||
|
||||
private float useState;
|
||||
@@ -44,6 +44,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
if (character == null || character.Removed) { return false; }
|
||||
if (!character.IsKeyDown(InputType.Aim) || character.Stun > 0.0f) { return false; }
|
||||
if (UsableIn == UseEnvironment.None) { return false; }
|
||||
|
||||
IsActive = true;
|
||||
useState = 0.1f;
|
||||
|
||||
@@ -476,6 +476,7 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
foreach (Item item in Inventory.AllItemsMod)
|
||||
{
|
||||
item.ApplyStatusEffects(ActionType.OnSuccess, 1.0f, ownerCharacter);
|
||||
item.ApplyStatusEffects(ActionType.OnUse, 1.0f, ownerCharacter);
|
||||
item.GetComponent<GeneticMaterial>()?.Equip(ownerCharacter);
|
||||
autoInjectCooldown = AutoInjectInterval;
|
||||
|
||||
@@ -450,6 +450,7 @@ namespace Barotrauma.Items.Components
|
||||
public override bool Select(Character activator)
|
||||
{
|
||||
if (activator == null || activator.Removed) { return false; }
|
||||
if (Item.Condition <= 0.0f && !UpdateWhenInactive) { return false; }
|
||||
|
||||
if (UsableIn == UseEnvironment.Water && !activator.AnimController.InWater ||
|
||||
UsableIn == UseEnvironment.Air && activator.AnimController.InWater)
|
||||
|
||||
@@ -15,6 +15,8 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
private ImmutableDictionary<uint, FabricationRecipe> fabricationRecipes; //this is not readonly because tutorials fuck this up!!!!
|
||||
|
||||
private const int MaxAmountToFabricate = 99;
|
||||
|
||||
private FabricationRecipe fabricatedItem;
|
||||
private float timeUntilReady;
|
||||
private float requiredTime;
|
||||
@@ -39,6 +41,16 @@ namespace Barotrauma.Items.Components
|
||||
[Serialize(1.0f, IsPropertySaveable.Yes)]
|
||||
public float SkillRequirementMultiplier { get; set; }
|
||||
|
||||
private int amountToFabricate;
|
||||
[Serialize(1, IsPropertySaveable.Yes)]
|
||||
public int AmountToFabricate
|
||||
{
|
||||
get { return amountToFabricate; }
|
||||
set { amountToFabricate = MathHelper.Clamp(value, 1, MaxAmountToFabricate); }
|
||||
}
|
||||
|
||||
private int amountRemaining;
|
||||
|
||||
private const float TinkeringSpeedIncrease = 2.5f;
|
||||
|
||||
private enum FabricatorState
|
||||
@@ -183,16 +195,20 @@ namespace Barotrauma.Items.Components
|
||||
if (selectedItem == null) { return; }
|
||||
if (!outputContainer.Inventory.CanBePut(selectedItem.TargetItem, selectedItem.OutCondition * selectedItem.TargetItem.Health)) { return; }
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
activateButton.Text = TextManager.Get("FabricatorCancel");
|
||||
#endif
|
||||
|
||||
IsActive = true;
|
||||
this.user = user;
|
||||
fabricatedItem = selectedItem;
|
||||
RefreshAvailableIngredients();
|
||||
|
||||
#if CLIENT
|
||||
itemList.Enabled = false;
|
||||
if (amountInput != null)
|
||||
{
|
||||
amountInput.Enabled = false;
|
||||
}
|
||||
RefreshActivateButtonText();
|
||||
#endif
|
||||
|
||||
bool isClient = GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient;
|
||||
if (!isClient)
|
||||
{
|
||||
@@ -249,10 +265,11 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
#elif CLIENT
|
||||
itemList.Enabled = true;
|
||||
if (activateButton != null)
|
||||
if (amountInput != null)
|
||||
{
|
||||
activateButton.Text = TextManager.Get(CreateButtonText);
|
||||
amountInput.Enabled = true;
|
||||
}
|
||||
RefreshActivateButtonText();
|
||||
#endif
|
||||
fabricatedItem = null;
|
||||
}
|
||||
@@ -518,20 +535,16 @@ namespace Barotrauma.Items.Components
|
||||
}
|
||||
}
|
||||
|
||||
//disabled "continuous fabrication" for now
|
||||
//before we enable it, there should be some UI controls for fabricating a specific number of items
|
||||
|
||||
/*var prevFabricatedItem = fabricatedItem;
|
||||
var prevFabricatedItem = fabricatedItem;
|
||||
var prevUser = user;
|
||||
CancelFabricating();
|
||||
if (CanBeFabricated(prevFabricatedItem))
|
||||
|
||||
amountRemaining--;
|
||||
if (amountRemaining > 0 && CanBeFabricated(prevFabricatedItem, availableIngredients, prevUser))
|
||||
{
|
||||
//keep fabricating if we can fabricate more
|
||||
StartFabricating(prevFabricatedItem, prevUser, addToServerLog: false);
|
||||
}*/
|
||||
|
||||
|
||||
CancelFabricating();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -594,7 +607,15 @@ namespace Barotrauma.Items.Components
|
||||
private bool CanBeFabricated(FabricationRecipe fabricableItem, IReadOnlyDictionary<Identifier, List<Item>> availableIngredients, Character character)
|
||||
{
|
||||
if (fabricableItem == null) { return false; }
|
||||
if (fabricableItem.RequiresRecipe && (character == null || !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier))) { return false; }
|
||||
if (fabricableItem.RequiresRecipe)
|
||||
{
|
||||
if (character == null) { return false; }
|
||||
if (!character.HasRecipeForItem(fabricableItem.TargetItem.Identifier) &&
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Bot).None(c => c.HasRecipeForItem(fabricableItem.TargetItem.Identifier)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (fabricableItem.RequiredMoney > 0)
|
||||
{
|
||||
|
||||
@@ -151,7 +151,6 @@ namespace Barotrauma.Items.Components
|
||||
set
|
||||
{
|
||||
bool changed = currentMode != value;
|
||||
|
||||
currentMode = value;
|
||||
#if CLIENT
|
||||
if (changed) { prevPassivePingRadius = float.MaxValue; }
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma.Items.Components
|
||||
@@ -9,14 +10,6 @@ namespace Barotrauma.Items.Components
|
||||
{
|
||||
public const int MaxQuality = 3;
|
||||
|
||||
public static readonly float[] QualityCommonnesses = new float[]
|
||||
{
|
||||
0.8f,
|
||||
0.15f,
|
||||
0.045f,
|
||||
0.005f,
|
||||
};
|
||||
|
||||
public enum StatType
|
||||
{
|
||||
Condition,
|
||||
@@ -81,5 +74,29 @@ namespace Barotrauma.Items.Components
|
||||
if (!statValues.ContainsKey(statType)) { return 0.0f; }
|
||||
return statValues[statType] * qualityLevel;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get a random quality for an item spawning in some sub, taking into account the type of the submarine and the difficulty of the current level
|
||||
/// (high-quality items become more common as difficulty increases)
|
||||
/// </summary>
|
||||
public static int GetSpawnedItemQuality(Submarine submarine, Level level, Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
||||
{
|
||||
if (submarine?.Info == null || level == null || submarine.Info.Type == SubmarineType.Player) { return 0; }
|
||||
|
||||
float difficultyFactor = MathHelper.Clamp(level.Difficulty, 0.0f, 1.0f);
|
||||
return ToolBox.SelectWeightedRandom(Enumerable.Range(0, MaxQuality + 1), q => GetCommonness(q, difficultyFactor), randSync);
|
||||
|
||||
static float GetCommonness(int quality, float difficultyFactor)
|
||||
{
|
||||
return quality switch
|
||||
{
|
||||
0 => 1,
|
||||
1 => MathHelper.Lerp(0.0f, 1f, difficultyFactor),
|
||||
2 => MathHelper.Lerp(0.0f, 1f, Math.Max(difficultyFactor-0.15f, 0f)), //15 difficulty transition to next biome - unlock Excellent loot
|
||||
3 => MathHelper.Lerp(0.0f, 1f, Math.Max(difficultyFactor-0.35f, 0f)), //35 difficulty transition to next biome - unlock Masterwork loot
|
||||
_ => 0.0f,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1617,11 +1617,7 @@ namespace Barotrauma
|
||||
|
||||
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null)
|
||||
{
|
||||
if (effect.intervalTimer > 0.0f)
|
||||
{
|
||||
effect.intervalTimer -= deltaTime;
|
||||
return;
|
||||
}
|
||||
if (effect.ShouldWaitForInterval(this, deltaTime)) { return; }
|
||||
if (!isNetworkEvent && checkCondition)
|
||||
{
|
||||
if (condition == 0.0f && !effect.AllowWhenBroken && effect.type != ActionType.OnBroken) { return; }
|
||||
@@ -2758,7 +2754,6 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool remove = false;
|
||||
foreach (ItemComponent ic in components)
|
||||
{
|
||||
@@ -2788,7 +2783,6 @@ namespace Barotrauma
|
||||
{
|
||||
var abilityItem = new AbilityApplyTreatment(user, character, this);
|
||||
user.CheckTalents(AbilityEffectType.OnApplyTreatment, abilityItem);
|
||||
|
||||
}
|
||||
|
||||
if (remove) { Spawner?.AddItemToRemoveQueue(this); }
|
||||
|
||||
@@ -15,10 +15,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
[NetworkSerialize]
|
||||
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct
|
||||
{
|
||||
public override int GetHashCode() => HashCode.Combine(TalentIdentifier, CharacterID, Stat);
|
||||
}
|
||||
public readonly record struct TalentStatIdentifier(ItemTalentStats Stat, Identifier TalentIdentifier, UInt32 CharacterID) : INetSerializableStruct;
|
||||
|
||||
private readonly Dictionary<TalentStatIdentifier, float> talentStats = new();
|
||||
|
||||
|
||||
@@ -3943,31 +3943,11 @@ namespace Barotrauma
|
||||
Submarine outpost = null;
|
||||
if (i == 0 && preSelectedStartOutpost == null || i == 1 && preSelectedEndOutpost == null)
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.Any() || LevelData.ForceOutpostGenerationParams != null)
|
||||
if (LevelData.OutpostGenerationParamsExist)
|
||||
{
|
||||
Location location = i == 0 ? StartLocation : EndLocation;
|
||||
|
||||
OutpostGenerationParams outpostGenerationParams = null;
|
||||
if (LevelData.ForceOutpostGenerationParams != null)
|
||||
{
|
||||
outpostGenerationParams = LevelData.ForceOutpostGenerationParams;
|
||||
}
|
||||
else
|
||||
{
|
||||
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
|
||||
suitableParams = OutpostGenerationParams.OutpostParams;
|
||||
}
|
||||
}
|
||||
|
||||
outpostGenerationParams = suitableParams.GetRandom(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
|
||||
OutpostGenerationParams outpostGenerationParams = LevelData.ForceOutpostGenerationParams ??
|
||||
LevelData.GetSuitableOutpostGenerationParams(location).GetRandom(Rand.RandSync.ServerAndClient);
|
||||
LocationType locationType = location?.Type;
|
||||
if (locationType == null)
|
||||
{
|
||||
|
||||
@@ -57,10 +57,19 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int? MinMainPathWidth;
|
||||
|
||||
/// <summary>
|
||||
/// Events that have previously triggered in this level. Used for making events the player hasn't seen yet more likely to trigger when re-entering the level. Has a maximum size of <see cref="EventManager.MaxEventHistory"/>.
|
||||
/// </summary>
|
||||
public readonly List<EventPrefab> EventHistory = new List<EventPrefab>();
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
public readonly HashSet<Identifier> UsedUniqueSets = new HashSet<Identifier>();
|
||||
|
||||
/// <summary>
|
||||
/// Events that have already triggered in this level and can never trigger again. <see cref="EventSet.OncePerLevel"/>.
|
||||
/// </summary>
|
||||
public readonly List<EventPrefab> NonRepeatableEvents = new List<EventPrefab>();
|
||||
|
||||
/// <summary>
|
||||
/// 'Exhaustible' sets won't appear in the same level until after one world step (~10 min, see Map.ProgressWorld) has passed. <see cref="EventSet.Exhaustible"/>.
|
||||
/// </summary>
|
||||
public bool EventsExhausted { get; set; }
|
||||
|
||||
/// <summary>
|
||||
@@ -144,8 +153,6 @@ namespace Barotrauma
|
||||
string[] nonRepeatablePrefabNames = element.GetAttributeStringArray("nonrepeatableevents", new string[] { });
|
||||
NonRepeatableEvents.AddRange(EventPrefab.Prefabs.Where(p => nonRepeatablePrefabNames.Any(n => p.Identifier == n)));
|
||||
|
||||
UsedUniqueSets = element.GetAttributeIdentifierArray(nameof(UsedUniqueSets), Array.Empty<Identifier>()).ToHashSet();
|
||||
|
||||
EventsExhausted = element.GetAttributeBool(nameof(EventsExhausted).ToLower(), false);
|
||||
}
|
||||
|
||||
@@ -245,6 +252,23 @@ namespace Barotrauma
|
||||
return levelData;
|
||||
}
|
||||
|
||||
public bool OutpostGenerationParamsExist => ForceOutpostGenerationParams != null || OutpostGenerationParams.OutpostParams.Any();
|
||||
|
||||
public static IEnumerable<OutpostGenerationParams> GetSuitableOutpostGenerationParams(Location location)
|
||||
{
|
||||
var suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || p.AllowedLocationTypes.Contains(location.Type.Identifier));
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
suitableParams = OutpostGenerationParams.OutpostParams.Where(p => location == null || !p.AllowedLocationTypes.Any());
|
||||
if (!suitableParams.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"No suitable outpost generation parameters found for the location type \"{location.Type.Identifier}\". Selecting random parameters.");
|
||||
suitableParams = OutpostGenerationParams.OutpostParams;
|
||||
}
|
||||
}
|
||||
return suitableParams;
|
||||
}
|
||||
|
||||
public void Save(XElement parentElement)
|
||||
{
|
||||
var newElement = new XElement("Level",
|
||||
@@ -287,11 +311,6 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (UsedUniqueSets.Any())
|
||||
{
|
||||
newElement.Add(new XAttribute(nameof(UsedUniqueSets), string.Join(',', UsedUniqueSets)));
|
||||
}
|
||||
|
||||
parentElement.Add(newElement);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1291,19 +1291,32 @@ namespace Barotrauma
|
||||
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass)
|
||||
public bool CanHaveSubsForSale()
|
||||
{
|
||||
if (!HasOutpost()) { return 0; }
|
||||
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
|
||||
return HasOutpost() && CanHaveCampaignInteraction(CampaignMode.InteractionType.PurchaseSub);
|
||||
}
|
||||
|
||||
public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined);
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass = SubmarineClass.Undefined)
|
||||
{
|
||||
if (CanHaveSubsForSale())
|
||||
{
|
||||
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info)
|
||||
{
|
||||
return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
|
||||
}
|
||||
|
||||
private bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
|
||||
{
|
||||
return LevelData != null &&
|
||||
LevelData.OutpostGenerationParamsExist &&
|
||||
LevelData.GetSuitableOutpostGenerationParams(this).Any(p => p.CanHaveCampaignInteraction(interactionType));
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (Type != OriginalType)
|
||||
|
||||
@@ -85,9 +85,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
public float StoreMaxReputationModifier { get; } = 0.1f;
|
||||
public float StoreSellPriceModifier { get; } = 0.8f;
|
||||
public float StoreSellPriceModifier { get; } = 0.3f;
|
||||
public float DailySpecialPriceModifier { get; } = 0.5f;
|
||||
public float RequestGoodPriceModifier { get; } = 1.5f;
|
||||
public float RequestGoodPriceModifier { get; } = 2f;
|
||||
public int StoreInitialBalance { get; } = 5000;
|
||||
/// <summary>
|
||||
/// In percentages
|
||||
|
||||
@@ -260,6 +260,21 @@ namespace Barotrauma
|
||||
return humanPrefabCollections.GetRandom(randSync);
|
||||
}
|
||||
|
||||
public bool CanHaveCampaignInteraction(CampaignMode.InteractionType interactionType)
|
||||
{
|
||||
foreach (var collection in humanPrefabCollections)
|
||||
{
|
||||
foreach (var prefab in collection)
|
||||
{
|
||||
if (prefab.CampaignInteractionType == interactionType)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public ImmutableHashSet<Identifier> GetStoreIdentifiers()
|
||||
{
|
||||
if (StoreIdentifiers == null)
|
||||
|
||||
@@ -164,7 +164,11 @@ namespace Barotrauma.Networking
|
||||
return command;
|
||||
}
|
||||
|
||||
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionmult = 2.0f)
|
||||
/// <summary>
|
||||
/// How much messages sent by <paramref name="sender"/> should get garbled. Takes the distance between the entities and optionally the obstructions between them into account (see <paramref name="obstructionMultiplier"/>).
|
||||
/// </summary>
|
||||
/// <param name="obstructionMultiplier">Values greater than or equal to 1 cause the message to get garbled more heavily when there's some obstruction between the characters. Values smaller than 1 mean the garbling only depends on distance.</param>
|
||||
public static float GetGarbleAmount(Entity listener, Entity sender, float range, float obstructionMultiplier = 2.0f)
|
||||
{
|
||||
if (listener == null || sender == null)
|
||||
{
|
||||
@@ -177,12 +181,12 @@ namespace Barotrauma.Networking
|
||||
|
||||
Hull listenerHull = listener == null ? null : Hull.FindHull(listener.WorldPosition);
|
||||
Hull sourceHull = sender == null ? null : Hull.FindHull(sender.WorldPosition);
|
||||
if (sourceHull != listenerHull)
|
||||
if (sourceHull != listenerHull && obstructionMultiplier >= 1.0f)
|
||||
{
|
||||
if ((sourceHull == null || !sourceHull.GetConnectedHulls(includingThis: false, searchDepth: 2, ignoreClosedGaps: true).Contains(listenerHull)) &&
|
||||
Submarine.CheckVisibility(listener.SimPosition, sender.SimPosition) != null)
|
||||
{
|
||||
dist = (dist + 100f) * obstructionmult;
|
||||
dist = (dist + 100f) * obstructionMultiplier;
|
||||
}
|
||||
}
|
||||
if (dist > range) { return 1.0f; }
|
||||
@@ -197,9 +201,9 @@ namespace Barotrauma.Networking
|
||||
return ApplyDistanceEffect(listener, Sender, Text, SpeakRange);
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionmult = 2.0f)
|
||||
public static string ApplyDistanceEffect(Entity listener, Entity sender, string text, float range, float obstructionMultiplier = 2.0f)
|
||||
{
|
||||
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionmult));
|
||||
return ApplyDistanceEffect(text, GetGarbleAmount(listener, sender, range, obstructionMultiplier));
|
||||
}
|
||||
|
||||
public static string ApplyDistanceEffect(string text, float garbleAmount)
|
||||
@@ -252,7 +256,7 @@ namespace Barotrauma.Networking
|
||||
var senderRadio = senderItem.GetComponent<WifiComponent>();
|
||||
if (!receiverRadio.CanReceive(senderRadio)) { continue; }
|
||||
|
||||
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range);
|
||||
string msg = ApplyDistanceEffect(receiverItem, senderItem, message, senderRadio.Range, obstructionMultiplier: 0);
|
||||
if (sender.SpeechImpediment > 0.0f)
|
||||
{
|
||||
//speech impediment doesn't reduce the range when using a radio, but adds extra garbling
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user