v1.0.13.1 (first post-1.0 patch)
This commit is contained in:
@@ -10,7 +10,7 @@ namespace Barotrauma
|
|||||||
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
|
||||||
{
|
{
|
||||||
if (Character == Character.Controlled) { return; }
|
if (Character == Character.Controlled) { return; }
|
||||||
if (!debugai) { return; }
|
if (!DebugAI) { return; }
|
||||||
Vector2 pos = Character.WorldPosition;
|
Vector2 pos = Character.WorldPosition;
|
||||||
pos.Y = -pos.Y;
|
pos.Y = -pos.Y;
|
||||||
Vector2 textOffset = new Vector2(-40, -160);
|
Vector2 textOffset = new Vector2(-40, -160);
|
||||||
|
|||||||
@@ -19,6 +19,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private IEnumerable<CoroutineStatus> FadeOutColors(float time)
|
private IEnumerable<CoroutineStatus> FadeOutColors(float time)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
Dictionary<MapEntity, Color> originalColors = new Dictionary<MapEntity, Color>();
|
||||||
|
foreach (var item in thalamusItems)
|
||||||
|
{
|
||||||
|
originalColors.Add(item, item.SpriteColor);
|
||||||
|
}
|
||||||
|
foreach (var structure in thalamusStructures)
|
||||||
|
{
|
||||||
|
originalColors.Add(structure, structure.SpriteColor);
|
||||||
|
}
|
||||||
|
|
||||||
float timer = 0;
|
float timer = 0;
|
||||||
while (timer < time)
|
while (timer < time)
|
||||||
{
|
{
|
||||||
@@ -26,15 +37,16 @@ namespace Barotrauma
|
|||||||
float m = MathHelper.Lerp(1, Config.DeadEntityColorMultiplier, MathUtils.InverseLerp(0, time, timer));
|
float m = MathHelper.Lerp(1, Config.DeadEntityColorMultiplier, MathUtils.InverseLerp(0, time, timer));
|
||||||
foreach (var item in thalamusItems)
|
foreach (var item in thalamusItems)
|
||||||
{
|
{
|
||||||
|
if (item.Color.A == 0) { continue; }
|
||||||
if (item.Prefab.BrokenSprites.None())
|
if (item.Prefab.BrokenSprites.None())
|
||||||
{
|
{
|
||||||
Color c = item.Prefab.SpriteColor;
|
Color c = originalColors[item];
|
||||||
item.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
|
item.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
foreach (var structure in thalamusStructures)
|
foreach (var structure in thalamusStructures)
|
||||||
{
|
{
|
||||||
Color c = structure.Prefab.SpriteColor;
|
Color c = originalColors[structure];
|
||||||
structure.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
|
structure.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
|
||||||
}
|
}
|
||||||
yield return CoroutineStatus.Running;
|
yield return CoroutineStatus.Running;
|
||||||
|
|||||||
@@ -6,11 +6,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
partial class Attack
|
partial class Attack
|
||||||
{
|
{
|
||||||
[Serialize("StructureBlunt", IsPropertySaveable.Yes), Editable()]
|
[Serialize("StructureBlunt", IsPropertySaveable.Yes, description: "Name of the sound effect the attack makes when it hits a structure."), Editable()]
|
||||||
public string StructureSoundType { get; private set; }
|
public string StructureSoundType { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sound to play when the attack deals damage.
|
||||||
|
/// </summary>
|
||||||
private RoundSound sound;
|
private RoundSound sound;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Particle emitter to use when the attack deals damage.
|
||||||
|
/// </summary>
|
||||||
private ParticleEmitter particleEmitter;
|
private ParticleEmitter particleEmitter;
|
||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element)
|
partial void InitProjSpecific(ContentXElement element)
|
||||||
|
|||||||
@@ -582,7 +582,7 @@ namespace Barotrauma
|
|||||||
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
|
float closestItemDistance = Math.Max(aimAssistAmount, 2.0f);
|
||||||
foreach (MapEntity entity in entityList)
|
foreach (MapEntity entity in entityList)
|
||||||
{
|
{
|
||||||
if (!(entity is Item item))
|
if (entity is not Item item)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -153,6 +153,7 @@ namespace Barotrauma
|
|||||||
private static readonly List<BossProgressBar> bossProgressBars = new List<BossProgressBar>();
|
private static readonly List<BossProgressBar> bossProgressBars = new List<BossProgressBar>();
|
||||||
|
|
||||||
private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
|
private static readonly Dictionary<Identifier, LocalizedString> cachedHudTexts = new Dictionary<Identifier, LocalizedString>();
|
||||||
|
private static LanguageIdentifier cachedHudTextLanguage = LanguageIdentifier.None;
|
||||||
|
|
||||||
private static GUILayoutGroup bossHealthContainer;
|
private static GUILayoutGroup bossHealthContainer;
|
||||||
|
|
||||||
@@ -202,10 +203,15 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
|
public static LocalizedString GetCachedHudText(string textTag, InputType keyBind)
|
||||||
{
|
{
|
||||||
|
if (cachedHudTextLanguage != GameSettings.CurrentConfig.Language)
|
||||||
|
{
|
||||||
|
cachedHudTexts.Clear();
|
||||||
|
}
|
||||||
Identifier key = (textTag + keyBind).ToIdentifier();
|
Identifier key = (textTag + keyBind).ToIdentifier();
|
||||||
if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
|
if (cachedHudTexts.TryGetValue(key, out LocalizedString text)) { return text; }
|
||||||
text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
|
text = TextManager.GetWithVariable(textTag, "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(keyBind)).Value;
|
||||||
cachedHudTexts.Add(key, text);
|
cachedHudTexts.Add(key, text);
|
||||||
|
cachedHudTextLanguage = GameSettings.CurrentConfig.Language;
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -701,10 +701,11 @@ namespace Barotrauma
|
|||||||
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
|
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
|
||||||
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
|
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
|
||||||
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
|
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
|
||||||
|
|
||||||
float afflictionGrainStrength = affliction.GetScreenGrainStrength();
|
float afflictionGrainStrength = affliction.GetScreenGrainStrength();
|
||||||
if (afflictionGrainStrength > 0.0f)
|
if (afflictionGrainStrength > 0.0f)
|
||||||
{
|
{
|
||||||
grainStrength = Math.Max(grainStrength, affliction.GetScreenGrainStrength());
|
grainStrength = Math.Max(grainStrength, afflictionGrainStrength);
|
||||||
Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White;
|
Color afflictionGrainColor = affliction.GetActiveEffect()?.GrainColor ?? Color.White;
|
||||||
grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
|
grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
|
||||||
}
|
}
|
||||||
@@ -1020,12 +1021,8 @@ namespace Barotrauma
|
|||||||
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
|
||||||
{
|
{
|
||||||
var affliction = kvp.Key;
|
var affliction = kvp.Key;
|
||||||
if (affliction.Prefab.AfflictionOverlay != null)
|
affliction.Prefab.AfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
|
||||||
{
|
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
|
||||||
Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay;
|
|
||||||
ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
|
|
||||||
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
float damageOverlayAlpha = DamageOverlayTimer;
|
float damageOverlayAlpha = DamageOverlayTimer;
|
||||||
|
|||||||
+3
-1
@@ -125,9 +125,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static string IncrementModVersion(string modVersion)
|
public static string IncrementModVersion(string modVersion)
|
||||||
{
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(modVersion)) { return string.Empty; }
|
||||||
|
|
||||||
//look for an integer at the end of the string and increment it
|
//look for an integer at the end of the string and increment it
|
||||||
int startIndex = modVersion.Length - 1;
|
int startIndex = modVersion.Length - 1;
|
||||||
while (char.IsDigit(modVersion[startIndex])) { startIndex--; }
|
while (startIndex > 0 && char.IsDigit(modVersion[startIndex])) { startIndex--; }
|
||||||
startIndex++;
|
startIndex++;
|
||||||
|
|
||||||
if (startIndex >= modVersion.Length
|
if (startIndex >= modVersion.Length
|
||||||
|
|||||||
@@ -425,6 +425,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
CheatsEnabled = true;
|
CheatsEnabled = true;
|
||||||
SteamAchievementManager.CheatsEnabled = true;
|
SteamAchievementManager.CheatsEnabled = true;
|
||||||
|
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
|
||||||
|
{
|
||||||
|
campaign.CheatsEnabled = true;
|
||||||
|
}
|
||||||
NewMessage("Enabled cheat commands.", Color.Red);
|
NewMessage("Enabled cheat commands.", Color.Red);
|
||||||
#if USE_STEAM
|
#if USE_STEAM
|
||||||
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
|
NewMessage("Steam achievements have been disabled during this play session.", Color.Red);
|
||||||
@@ -632,15 +636,29 @@ namespace Barotrauma
|
|||||||
commands.Add(new Command("wikiimage_character", "Save an image of the currently controlled character with a transparent background.", (string[] args) =>
|
commands.Add(new Command("wikiimage_character", "Save an image of the currently controlled character with a transparent background.", (string[] args) =>
|
||||||
{
|
{
|
||||||
if (Character.Controlled == null) { return; }
|
if (Character.Controlled == null) { return; }
|
||||||
WikiImage.Create(Character.Controlled);
|
try
|
||||||
|
{
|
||||||
|
WikiImage.Create(Character.Controlled);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("The command 'wikiimage_character' failed.", e);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("wikiimage_sub", "Save an image of the main submarine with a transparent background.", (string[] args) =>
|
commands.Add(new Command("wikiimage_sub", "Save an image of the main submarine with a transparent background.", (string[] args) =>
|
||||||
{
|
{
|
||||||
if (Submarine.MainSub == null) { return; }
|
if (Submarine.MainSub == null) { return; }
|
||||||
MapEntity.SelectedList.Clear();
|
try
|
||||||
MapEntity.ClearHighlightedEntities();
|
{
|
||||||
WikiImage.Create(Submarine.MainSub);
|
MapEntity.SelectedList.Clear();
|
||||||
|
MapEntity.ClearHighlightedEntities();
|
||||||
|
WikiImage.Create(Submarine.MainSub);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError("The command 'wikiimage_sub' failed.", e);
|
||||||
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
AssignRelayToServer("kick", false);
|
AssignRelayToServer("kick", false);
|
||||||
@@ -1146,7 +1164,7 @@ namespace Barotrauma
|
|||||||
GameMain.LightManager.LosEnabled = true;
|
GameMain.LightManager.LosEnabled = true;
|
||||||
GameMain.LightManager.LosAlpha = 1f;
|
GameMain.LightManager.LosAlpha = 1f;
|
||||||
}
|
}
|
||||||
NewMessage("Dev mode " + (GameMain.DevMode ? "enabled" : "disabled"), Color.White);
|
NewMessage("Dev mode " + (GameMain.DevMode ? "enabled" : "disabled"), Color.Yellow);
|
||||||
});
|
});
|
||||||
AssignRelayToServer("devmode", false);
|
AssignRelayToServer("devmode", false);
|
||||||
|
|
||||||
@@ -1248,8 +1266,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
AssignOnExecute("debugai", (string[] args) =>
|
AssignOnExecute("debugai", (string[] args) =>
|
||||||
{
|
{
|
||||||
HumanAIController.debugai = !HumanAIController.debugai;
|
HumanAIController.DebugAI = !HumanAIController.DebugAI;
|
||||||
if (HumanAIController.debugai)
|
if (HumanAIController.DebugAI)
|
||||||
{
|
{
|
||||||
GameMain.DevMode = true;
|
GameMain.DevMode = true;
|
||||||
GameMain.DebugDraw = true;
|
GameMain.DebugDraw = true;
|
||||||
@@ -1264,7 +1282,7 @@ namespace Barotrauma
|
|||||||
GameMain.LightManager.LosEnabled = true;
|
GameMain.LightManager.LosEnabled = true;
|
||||||
GameMain.LightManager.LosAlpha = 1f;
|
GameMain.LightManager.LosAlpha = 1f;
|
||||||
}
|
}
|
||||||
NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.Yellow);
|
NewMessage(HumanAIController.DebugAI ? "AI debug info visible" : "AI debug info hidden", Color.Yellow);
|
||||||
});
|
});
|
||||||
AssignRelayToServer("debugai", false);
|
AssignRelayToServer("debugai", false);
|
||||||
|
|
||||||
@@ -2323,7 +2341,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (mapEntity is Item item)
|
if (mapEntity is Item item)
|
||||||
{
|
{
|
||||||
item.Rect = new Rectangle(item.Rect.X, item.Rect.Y,
|
item.Rect = item.DefaultRect = new Rectangle(item.Rect.X, item.Rect.Y,
|
||||||
(int)(item.Prefab.Sprite.size.X * item.Prefab.Scale),
|
(int)(item.Prefab.Sprite.size.X * item.Prefab.Scale),
|
||||||
(int)(item.Prefab.Sprite.size.Y * item.Prefab.Scale));
|
(int)(item.Prefab.Sprite.size.Y * item.Prefab.Scale));
|
||||||
}
|
}
|
||||||
@@ -2859,7 +2877,7 @@ namespace Barotrauma
|
|||||||
NewMessage("Valid ranks are:", Color.White);
|
NewMessage("Valid ranks are:", Color.White);
|
||||||
foreach (PermissionPreset permissionPreset in PermissionPreset.List)
|
foreach (PermissionPreset permissionPreset in PermissionPreset.List)
|
||||||
{
|
{
|
||||||
NewMessage(" - " + permissionPreset.Name, Color.White);
|
NewMessage(" - " + permissionPreset.DisplayName, Color.White);
|
||||||
}
|
}
|
||||||
ShowQuestionPrompt("Rank to grant to client " + args[0] + "?", (rank) =>
|
ShowQuestionPrompt("Rank to grant to client " + args[0] + "?", (rank) =>
|
||||||
{
|
{
|
||||||
@@ -3345,6 +3363,11 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
NewMessage("Level seed: " + Level.Loaded.Seed);
|
NewMessage("Level seed: " + Level.Loaded.Seed);
|
||||||
|
NewMessage("Level generation params: " + Level.Loaded.GenerationParams.Identifier);
|
||||||
|
NewMessage("Adjacent locations: " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()) + ", " + (Level.Loaded.StartLocation?.Type.Identifier ?? "none".ToIdentifier()));
|
||||||
|
NewMessage("Mirrored: " + Level.Loaded.Mirrored);
|
||||||
|
NewMessage("Level size: " + Level.Loaded.Size.X + "x" + Level.Loaded.Size.Y);
|
||||||
|
NewMessage("Minimum main path width: " + (Level.Loaded.LevelData?.MinMainPathWidth?.ToString() ?? "unknown"));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -517,6 +517,10 @@ namespace Barotrauma
|
|||||||
GlyphData gd = GetGlyphData(charIndex);
|
GlyphData gd = GetGlyphData(charIndex);
|
||||||
if (gd.TexIndex >= 0)
|
if (gd.TexIndex >= 0)
|
||||||
{
|
{
|
||||||
|
if (gd.TexIndex < 0 || gd.TexIndex >= textures.Count)
|
||||||
|
{
|
||||||
|
throw new ArgumentOutOfRangeException($"Error while rendering text. Texture index was out of range. Text: {text}, char: {charIndex} index: {gd.TexIndex}, texture count: {textures.Count}");
|
||||||
|
}
|
||||||
Texture2D tex = textures[gd.TexIndex];
|
Texture2D tex = textures[gd.TexIndex];
|
||||||
Vector2 drawOffset;
|
Vector2 drawOffset;
|
||||||
drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
|
drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void UpdatePending()
|
private void UpdatePending()
|
||||||
{
|
{
|
||||||
if (!(pendingHealList is { } healList)) { return; }
|
if (pendingHealList is not { } healList) { return; }
|
||||||
|
|
||||||
ImmutableArray<MedicalClinic.NetCrewMember> pendingList = medicalClinic.PendingHeals.ToImmutableArray();
|
ImmutableArray<MedicalClinic.NetCrewMember> pendingList = medicalClinic.PendingHeals.ToImmutableArray();
|
||||||
|
|
||||||
@@ -493,20 +493,26 @@ namespace Barotrauma
|
|||||||
|
|
||||||
GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
|
GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
|
||||||
{
|
{
|
||||||
OnClicked = (_, _) =>
|
OnClicked = (button, _) =>
|
||||||
{
|
{
|
||||||
|
if (isWaitingForServer) { return true; }
|
||||||
|
|
||||||
|
button.Enabled = false;
|
||||||
isWaitingForServer = true;
|
isWaitingForServer = true;
|
||||||
medicalClinic.TreatAllButtonAction(OnReceived);
|
|
||||||
|
bool wasSuccessful = medicalClinic.TreatAllButtonAction(_ => ReEnableButton());
|
||||||
|
if (!wasSuccessful) { ReEnableButton(); }
|
||||||
|
|
||||||
|
void ReEnableButton()
|
||||||
|
{
|
||||||
|
isWaitingForServer = false;
|
||||||
|
button.Enabled = true;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
|
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
|
||||||
|
|
||||||
void OnReceived(MedicalClinic.CallbackOnlyRequest obj)
|
|
||||||
{
|
|
||||||
isWaitingForServer = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
|
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
|
||||||
@@ -524,7 +530,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
new GUITextBlock(new RectTransform(Vector2.One, healthLayout.RectTransform), string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
|
new GUITextBlock(new RectTransform(Vector2.One, healthLayout.RectTransform), string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
|
||||||
{
|
{
|
||||||
TextGetter = () => TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)(info.Character?.HealthPercentage ?? 100f)}"),
|
TextGetter = () => TextManager.GetWithVariable("percentageformat", "[value]", $"{(int)MathF.Round(info.Character?.HealthPercentage ?? 100f)}"),
|
||||||
TextColor = GUIStyle.Green
|
TextColor = GUIStyle.Green
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -585,8 +591,10 @@ namespace Barotrauma
|
|||||||
OnClicked = (button, _) =>
|
OnClicked = (button, _) =>
|
||||||
{
|
{
|
||||||
button.Enabled = false;
|
button.Enabled = false;
|
||||||
medicalClinic.HealAllButtonAction(request =>
|
isWaitingForServer = true;
|
||||||
|
bool wasSuccessful = medicalClinic.HealAllButtonAction(request =>
|
||||||
{
|
{
|
||||||
|
isWaitingForServer = false;
|
||||||
switch (request.HealResult)
|
switch (request.HealResult)
|
||||||
{
|
{
|
||||||
case MedicalClinic.HealRequestResult.InsufficientFunds:
|
case MedicalClinic.HealRequestResult.InsufficientFunds:
|
||||||
@@ -600,6 +608,12 @@ namespace Barotrauma
|
|||||||
button.Enabled = true;
|
button.Enabled = true;
|
||||||
ClosePopup();
|
ClosePopup();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!wasSuccessful)
|
||||||
|
{
|
||||||
|
isWaitingForServer = false;
|
||||||
|
button.Enabled = true;
|
||||||
|
}
|
||||||
ClosePopup();
|
ClosePopup();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -610,11 +624,19 @@ namespace Barotrauma
|
|||||||
ClickSound = GUISoundType.Cart,
|
ClickSound = GUISoundType.Cart,
|
||||||
OnClicked = (button, _) =>
|
OnClicked = (button, _) =>
|
||||||
{
|
{
|
||||||
|
if (isWaitingForServer) { return true; }
|
||||||
|
|
||||||
button.Enabled = false;
|
button.Enabled = false;
|
||||||
medicalClinic.ClearAllButtonAction(_ =>
|
isWaitingForServer = true;
|
||||||
|
|
||||||
|
bool wasSuccessful = medicalClinic.ClearAllButtonAction(_ => ReEnableButton());
|
||||||
|
if (!wasSuccessful) { ReEnableButton(); }
|
||||||
|
|
||||||
|
void ReEnableButton()
|
||||||
{
|
{
|
||||||
|
isWaitingForServer = false;
|
||||||
button.Enabled = true;
|
button.Enabled = true;
|
||||||
});
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -701,10 +723,15 @@ namespace Barotrauma
|
|||||||
OnClicked = (button, _) =>
|
OnClicked = (button, _) =>
|
||||||
{
|
{
|
||||||
button.Enabled = false;
|
button.Enabled = false;
|
||||||
medicalClinic.RemovePendingButtonAction(crewMember, affliction, _ =>
|
bool wasSuccessful = medicalClinic.RemovePendingButtonAction(crewMember, affliction, _ =>
|
||||||
{
|
{
|
||||||
button.Enabled = true;
|
button.Enabled = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!wasSuccessful)
|
||||||
|
{
|
||||||
|
button.Enabled = true;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -792,7 +819,13 @@ namespace Barotrauma
|
|||||||
selectedCrewAfflictionList = popupAfflictionList;
|
selectedCrewAfflictionList = popupAfflictionList;
|
||||||
|
|
||||||
isWaitingForServer = true;
|
isWaitingForServer = true;
|
||||||
medicalClinic.RequestAfflictions(info, OnReceived);
|
bool wasSuccessful = medicalClinic.RequestAfflictions(info, OnReceived);
|
||||||
|
|
||||||
|
if (!wasSuccessful)
|
||||||
|
{
|
||||||
|
isWaitingForServer = false;
|
||||||
|
ClosePopup();
|
||||||
|
}
|
||||||
|
|
||||||
void OnReceived(MedicalClinic.AfflictionRequest request)
|
void OnReceived(MedicalClinic.AfflictionRequest request)
|
||||||
{
|
{
|
||||||
@@ -800,6 +833,16 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (request.Result != MedicalClinic.RequestResult.Success)
|
if (request.Result != MedicalClinic.RequestResult.Success)
|
||||||
{
|
{
|
||||||
|
switch (request.Result)
|
||||||
|
{
|
||||||
|
case MedicalClinic.RequestResult.CharacterInfoMissing:
|
||||||
|
DebugConsole.ThrowError($"Unable to select character \"{info.Character?.DisplayName}\" in medical clini because the character health was missing.");
|
||||||
|
break;
|
||||||
|
case MedicalClinic.RequestResult.CharacterNotFound:
|
||||||
|
DebugConsole.ThrowError($"Unable to select character \"{info.Character?.DisplayName} in medical clinic because the server was unable to find a character with ID {info.ID}.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
feedbackBlock.Text = GetErrorText(request.Result);
|
feedbackBlock.Text = GetErrorText(request.Result);
|
||||||
feedbackBlock.TextColor = GUIStyle.Red;
|
feedbackBlock.TextColor = GUIStyle.Red;
|
||||||
return;
|
return;
|
||||||
@@ -953,14 +996,20 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
|
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
|
||||||
|
|
||||||
ToggleElements(ElementState.Disabled, elementsToDisable);
|
ToggleElements(ElementState.Disabled, elementsToDisable);
|
||||||
medicalClinic.AddPendingButtonAction(existingMember, request =>
|
bool wasSuccessful = medicalClinic.AddPendingButtonAction(existingMember, request =>
|
||||||
{
|
{
|
||||||
if (request.Result == MedicalClinic.RequestResult.Timeout)
|
if (request.Result == MedicalClinic.RequestResult.Timeout)
|
||||||
{
|
{
|
||||||
ToggleElements(ElementState.Enabled, elementsToDisable);
|
ToggleElements(ElementState.Enabled, elementsToDisable);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (!wasSuccessful)
|
||||||
|
{
|
||||||
|
ToggleElements(ElementState.Enabled, elementsToDisable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString
|
#warning TODO: this doesn't seem like the right place for this, and it's not clear from the method signature how this differs from ToolBox.LimitString
|
||||||
@@ -1090,9 +1139,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return result switch
|
return result switch
|
||||||
{
|
{
|
||||||
MedicalClinic.RequestResult.Error => TextManager.Get("error"),
|
|
||||||
MedicalClinic.RequestResult.Timeout => TextManager.Get("medicalclinic.requesttimeout"),
|
MedicalClinic.RequestResult.Timeout => TextManager.Get("medicalclinic.requesttimeout"),
|
||||||
_ => "What the hell did you just do" // this should never happen
|
_ => TextManager.Get("error")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1072,18 +1072,19 @@ namespace Barotrauma
|
|||||||
if (save)
|
if (save)
|
||||||
{
|
{
|
||||||
GUI.SetSavingIndicatorState(true);
|
GUI.SetSavingIndicatorState(true);
|
||||||
|
|
||||||
if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
|
if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
|
||||||
{
|
{
|
||||||
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
|
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
|
||||||
}
|
}
|
||||||
|
if (GameSession.Campaign is CampaignMode campaign)
|
||||||
// Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called)
|
|
||||||
if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
|
|
||||||
{
|
{
|
||||||
spCampaign.UpdateStoreStock();
|
if (campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
|
||||||
|
{
|
||||||
|
spCampaign.UpdateStoreStock();
|
||||||
|
}
|
||||||
|
GameSession.EventManager?.RegisterEventHistory(registerFinishedOnly: true);
|
||||||
|
campaign.End();
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveUtil.SaveGame(GameSession.SavePath);
|
SaveUtil.SaveGame(GameSession.SavePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
|
if (Submarine.MainSub == null || Level.Loaded == null) { return; }
|
||||||
|
|
||||||
endRoundButton.Visible = false;
|
bool allowEndingRound = false;
|
||||||
|
endRoundButton.Color = endRoundButton.Style.Color;
|
||||||
|
endRoundButton.HoverColor = endRoundButton.Style.HoverColor;
|
||||||
|
RichString overrideEndRoundButtonToolTip = string.Empty;
|
||||||
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
|
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
|
||||||
LocalizedString buttonText = "";
|
LocalizedString buttonText = "";
|
||||||
switch (availableTransition)
|
switch (availableTransition)
|
||||||
@@ -159,12 +162,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
|
string textTag = availableTransition == TransitionType.ProgressToNextLocation ? "EnterLocation" : "EnterEmptyLocation";
|
||||||
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
|
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
|
||||||
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
|
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TransitionType.LeaveLocation:
|
case TransitionType.LeaveLocation:
|
||||||
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
||||||
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
|
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
|
||||||
break;
|
break;
|
||||||
case TransitionType.ReturnToPreviousLocation:
|
case TransitionType.ReturnToPreviousLocation:
|
||||||
case TransitionType.ReturnToPreviousEmptyLocation:
|
case TransitionType.ReturnToPreviousEmptyLocation:
|
||||||
@@ -172,32 +175,37 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
|
string textTag = availableTransition == TransitionType.ReturnToPreviousLocation ? "EnterLocation" : "EnterEmptyLocation";
|
||||||
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
buttonText = TextManager.GetWithVariable(textTag, "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
||||||
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
|
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case TransitionType.None:
|
case TransitionType.None:
|
||||||
default:
|
default:
|
||||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
|
bool inFriendlySub = Character.Controlled is { IsInFriendlySub: true };
|
||||||
!Level.Loaded.IsEndBiome &&
|
if (Level.Loaded.Type == LevelData.LevelType.Outpost && !Level.Loaded.IsEndBiome &&
|
||||||
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false)))
|
(inFriendlySub || (Character.Controlled?.CurrentHull?.OutpostModuleTags.Contains("airlock".ToIdentifier()) ?? false)))
|
||||||
{
|
{
|
||||||
|
if (Missions.Any(m => m is SalvageMission salvageMission && salvageMission.AnyTargetNeedsToBeRetrievedToSub))
|
||||||
|
{
|
||||||
|
overrideEndRoundButtonToolTip = TextManager.Get("SalvageTargetNotInSub");
|
||||||
|
endRoundButton.Color = GUIStyle.Red * 0.7f;
|
||||||
|
endRoundButton.HoverColor = GUIStyle.Red;
|
||||||
|
}
|
||||||
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
|
||||||
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
|
allowEndingRound = !ForceMapUI && !ShowCampaignUI;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
endRoundButton.Visible = false;
|
allowEndingRound = false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
|
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
|
||||||
{
|
{
|
||||||
endRoundButton.Visible = false;
|
allowEndingRound = false;
|
||||||
}
|
}
|
||||||
|
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = allowEndingRound; }
|
||||||
|
|
||||||
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = endRoundButton.Visible; }
|
endRoundButton.Visible = allowEndingRound && Character.Controlled is { IsIncapacitated: false };
|
||||||
|
|
||||||
if (endRoundButton.Visible)
|
if (endRoundButton.Visible)
|
||||||
{
|
{
|
||||||
if (!AllowedToManageCampaign(ClientPermissions.ManageMap))
|
if (!AllowedToManageCampaign(ClientPermissions.ManageMap))
|
||||||
@@ -215,7 +223,11 @@ namespace Barotrauma
|
|||||||
prevCampaignUIAutoOpenType = availableTransition;
|
prevCampaignUIAutoOpenType = availableTransition;
|
||||||
}
|
}
|
||||||
endRoundButton.Text = ToolBox.LimitString(buttonText.Value, endRoundButton.Font, endRoundButton.Rect.Width - 5);
|
endRoundButton.Text = ToolBox.LimitString(buttonText.Value, endRoundButton.Font, endRoundButton.Rect.Width - 5);
|
||||||
if (endRoundButton.Text != buttonText)
|
if (overrideEndRoundButtonToolTip != string.Empty)
|
||||||
|
{
|
||||||
|
endRoundButton.ToolTip = overrideEndRoundButtonToolTip;
|
||||||
|
}
|
||||||
|
else if (endRoundButton.Text != buttonText)
|
||||||
{
|
{
|
||||||
endRoundButton.ToolTip = buttonText;
|
endRoundButton.ToolTip = buttonText;
|
||||||
}
|
}
|
||||||
@@ -328,6 +340,11 @@ namespace Barotrauma
|
|||||||
CampaignUI.UpgradeStore?.RequestRefresh();
|
CampaignUI.UpgradeStore?.RequestRefresh();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (npc.AIController is HumanAIController humanAi && humanAi.IsInHostileFaction())
|
||||||
|
{
|
||||||
|
npc.Speak(TextManager.Get("dialoglowrepcampaigninteraction").Value, identifier: "dialoglowrepcampaigninteraction".ToIdentifier(), minDurationBetweenSimilar: 60.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddToGUIUpdateList()
|
public override void AddToGUIUpdateList()
|
||||||
|
|||||||
+10
-4
@@ -348,7 +348,7 @@ namespace Barotrauma
|
|||||||
//--------------------------------------
|
//--------------------------------------
|
||||||
|
|
||||||
//wait for the new level to be loaded
|
//wait for the new level to be loaded
|
||||||
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 60);
|
DateTime timeOut = DateTime.Now + GameClient.LevelTransitionTimeOut;
|
||||||
while (Level.Loaded == prevLevel || Level.Loaded == null)
|
while (Level.Loaded == prevLevel || Level.Loaded == null)
|
||||||
{
|
{
|
||||||
if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; }
|
if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; }
|
||||||
@@ -358,8 +358,12 @@ namespace Barotrauma
|
|||||||
endTransition.Stop();
|
endTransition.Stop();
|
||||||
overlayColor = Color.Transparent;
|
overlayColor = Color.Transparent;
|
||||||
|
|
||||||
if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); }
|
if (DateTime.Now > timeOut)
|
||||||
if (!(Screen.Selected is RoundSummaryScreen))
|
{
|
||||||
|
DebugConsole.ThrowError("Failed to start the round. Timed out while waiting for the level transition to finish.");
|
||||||
|
GameMain.NetLobbyScreen.Select();
|
||||||
|
}
|
||||||
|
if (Screen.Selected is not RoundSummaryScreen)
|
||||||
{
|
{
|
||||||
if (continueButton != null)
|
if (continueButton != null)
|
||||||
{
|
{
|
||||||
@@ -947,7 +951,9 @@ namespace Barotrauma
|
|||||||
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
|
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null)
|
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null &&
|
||||||
|
/*can't apply until we have the latest save file*/
|
||||||
|
!NetIdUtils.IdMoreRecent(pendingSaveID, LastSaveID))
|
||||||
{
|
{
|
||||||
CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
|
CampaignUI.CrewManagement.SetHireables(map.CurrentLocation, availableHires);
|
||||||
if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); }
|
if (hiredCharacters.Any()) { CampaignUI.CrewManagement.ValidateHires(hiredCharacters); }
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Undecided,
|
Undecided,
|
||||||
Success,
|
Success,
|
||||||
Error,
|
CharacterInfoMissing,
|
||||||
|
CharacterNotFound,
|
||||||
Timeout
|
Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -34,7 +35,9 @@ namespace Barotrauma
|
|||||||
private readonly List<RequestAction<CallbackOnlyRequest>> addRequests = new List<RequestAction<CallbackOnlyRequest>>();
|
private readonly List<RequestAction<CallbackOnlyRequest>> addRequests = new List<RequestAction<CallbackOnlyRequest>>();
|
||||||
private readonly List<RequestAction<CallbackOnlyRequest>> removeRequests = new List<RequestAction<CallbackOnlyRequest>>();
|
private readonly List<RequestAction<CallbackOnlyRequest>> removeRequests = new List<RequestAction<CallbackOnlyRequest>>();
|
||||||
|
|
||||||
public void RequestAfflictions(CharacterInfo info, Action<AfflictionRequest> onReceived)
|
private static readonly LeakyBucket requestBucket = new(RateLimitExpiry / (float)RateLimitMaxRequests, 10);
|
||||||
|
|
||||||
|
public bool RequestAfflictions(CharacterInfo info, Action<AfflictionRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
{
|
{
|
||||||
@@ -42,23 +45,26 @@ namespace Barotrauma
|
|||||||
if (Screen.Selected is TestScreen)
|
if (Screen.Selected is TestScreen)
|
||||||
{
|
{
|
||||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, TestAfflictions.ToImmutableArray()));
|
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, TestAfflictions.ToImmutableArray()));
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (info is not { Character.CharacterHealth: { } health })
|
if (info is not { Character.CharacterHealth: { } health })
|
||||||
{
|
{
|
||||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty));
|
onReceived.Invoke(new AfflictionRequest(RequestResult.CharacterInfoMissing, ImmutableArray<NetAffliction>.Empty));
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ImmutableArray<NetAffliction> pendingAfflictions = GetAllAfflictions(health).ToImmutableArray();
|
ImmutableArray<NetAffliction> pendingAfflictions = GetAllAfflictions(health);
|
||||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, pendingAfflictions));
|
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, pendingAfflictions));
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
afflictionRequests.Add(new RequestAction<AfflictionRequest>(onReceived, GetTimeout()));
|
return requestBucket.TryEnqueue(() =>
|
||||||
SendAfflictionRequest(info);
|
{
|
||||||
|
afflictionRequests.Add(new RequestAction<AfflictionRequest>(onReceived, GetTimeout()));
|
||||||
|
SendAfflictionRequest(info);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RequestLatestPending(Action<PendingRequest> onReceived)
|
public void RequestLatestPending(Action<PendingRequest> onReceived)
|
||||||
@@ -66,8 +72,11 @@ namespace Barotrauma
|
|||||||
// no need to worry about syncing when there's only one pair of eyes capable of looking at the UI
|
// no need to worry about syncing when there's only one pair of eyes capable of looking at the UI
|
||||||
if (GameMain.IsSingleplayer) { return; }
|
if (GameMain.IsSingleplayer) { return; }
|
||||||
|
|
||||||
pendingHealRequests.Add(new RequestAction<PendingRequest>(onReceived, GetTimeout()));
|
requestBucket.TryEnqueue(() =>
|
||||||
SendPendingRequest();
|
{
|
||||||
|
pendingHealRequests.Add(new RequestAction<PendingRequest>(onReceived, GetTimeout()));
|
||||||
|
SendPendingRequest();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Update(float deltaTime)
|
public void Update(float deltaTime)
|
||||||
@@ -79,6 +88,7 @@ namespace Barotrauma
|
|||||||
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
|
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||||
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
|
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||||
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
|
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||||
|
requestBucket.Update(deltaTime);
|
||||||
|
|
||||||
static void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
|
static void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
|
||||||
}
|
}
|
||||||
@@ -146,21 +156,25 @@ namespace Barotrauma
|
|||||||
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
|
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TreatAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
public bool TreatAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
{
|
{
|
||||||
AddEverythingToPending();
|
AddEverythingToPending();
|
||||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||||
OnUpdate?.Invoke();
|
OnUpdate?.Invoke();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
return requestBucket.TryEnqueue(() =>
|
||||||
ClientSend(null, NetworkHeader.ADD_EVERYTHING_TO_PENDING, DeliveryMethod.Reliable);
|
{
|
||||||
|
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||||
|
ClientSend(null, NetworkHeader.ADD_EVERYTHING_TO_PENDING, DeliveryMethod.Reliable);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void HealAllButtonAction(Action<HealRequest> onReceived)
|
|
||||||
|
public bool HealAllButtonAction(Action<HealRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
{
|
{
|
||||||
@@ -171,33 +185,39 @@ namespace Barotrauma
|
|||||||
OnUpdate?.Invoke();
|
OnUpdate?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (campaign?.CampaignUI?.MedicalClinic is { } ui)
|
if (campaign?.CampaignUI?.MedicalClinic is { } openedUi)
|
||||||
{
|
{
|
||||||
ui.ClosePopup();
|
openedUi.ClosePopup();
|
||||||
}
|
}
|
||||||
|
|
||||||
healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout()));
|
return requestBucket.TryEnqueue(() =>
|
||||||
ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable);
|
{
|
||||||
|
healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout()));
|
||||||
|
ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClearAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
public bool ClearAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
{
|
{
|
||||||
ClearPendingHeals();
|
ClearPendingHeals();
|
||||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||||
OnUpdate?.Invoke();
|
OnUpdate?.Invoke();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
clearAllRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
return requestBucket.TryEnqueue(() =>
|
||||||
ClientSend(null, NetworkHeader.CLEAR_PENDING, DeliveryMethod.Reliable);
|
{
|
||||||
|
clearAllRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||||
|
ClientSend(null, NetworkHeader.CLEAR_PENDING, DeliveryMethod.Reliable);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ClearRequstReceived()
|
private void ClearRequestReceived()
|
||||||
{
|
{
|
||||||
ClearPendingHeals();
|
ClearPendingHeals();
|
||||||
if (TryDequeue(clearAllRequests, out var callback))
|
if (TryDequeue(clearAllRequests, out var callback))
|
||||||
@@ -224,28 +244,31 @@ namespace Barotrauma
|
|||||||
OnUpdate?.Invoke();
|
OnUpdate?.Invoke();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddPendingButtonAction(NetCrewMember crewMember, Action<CallbackOnlyRequest> onReceived)
|
public bool AddPendingButtonAction(NetCrewMember crewMember, Action<CallbackOnlyRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
{
|
{
|
||||||
InsertPendingCrewMember(crewMember);
|
InsertPendingCrewMember(crewMember);
|
||||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||||
OnUpdate?.Invoke();
|
OnUpdate?.Invoke();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
return requestBucket.TryEnqueue(() =>
|
||||||
ClientSend(crewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable);
|
{
|
||||||
|
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||||
|
ClientSend(crewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RemovePendingButtonAction(NetCrewMember crewMember, NetAffliction affliction, Action<CallbackOnlyRequest> onReceived)
|
public bool RemovePendingButtonAction(NetCrewMember crewMember, NetAffliction affliction, Action<CallbackOnlyRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
{
|
{
|
||||||
RemovePendingAffliction(crewMember, affliction);
|
RemovePendingAffliction(crewMember, affliction);
|
||||||
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||||
OnUpdate?.Invoke();
|
OnUpdate?.Invoke();
|
||||||
return;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
INetSerializableStruct removedAffliction = new NetRemovedAffliction
|
INetSerializableStruct removedAffliction = new NetRemovedAffliction
|
||||||
@@ -254,11 +277,14 @@ namespace Barotrauma
|
|||||||
Affliction = affliction
|
Affliction = affliction
|
||||||
};
|
};
|
||||||
|
|
||||||
removeRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
return requestBucket.TryEnqueue(() =>
|
||||||
ClientSend(removedAffliction, NetworkHeader.REMOVE_PENDING, DeliveryMethod.Reliable);
|
{
|
||||||
|
removeRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||||
|
ClientSend(removedAffliction, NetworkHeader.REMOVE_PENDING, DeliveryMethod.Reliable);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void NewAdditonReceived(IReadMessage inc, MessageFlag flag)
|
private void NewAdditionReceived(IReadMessage inc, MessageFlag flag)
|
||||||
{
|
{
|
||||||
var crewMembers = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
|
var crewMembers = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
|
||||||
foreach (var crewMember in crewMembers)
|
foreach (var crewMember in crewMembers)
|
||||||
@@ -300,7 +326,7 @@ namespace Barotrauma
|
|||||||
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
||||||
if (TryDequeue(afflictionRequests, out var callback))
|
if (TryDequeue(afflictionRequests, out var callback))
|
||||||
{
|
{
|
||||||
RequestResult result = crewMember.CharacterInfoID is 0 ? RequestResult.Error : RequestResult.Success;
|
RequestResult result = crewMember.CharacterInfoID is 0 ? RequestResult.CharacterNotFound : RequestResult.Success;
|
||||||
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
|
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -336,7 +362,7 @@ namespace Barotrauma
|
|||||||
IWriteMessage msg = StartSending();
|
IWriteMessage msg = StartSending();
|
||||||
msg.WriteByte((byte)header);
|
msg.WriteByte((byte)header);
|
||||||
netStruct?.Write(msg);
|
netStruct?.Write(msg);
|
||||||
GameMain.Client.ClientPeer?.Send(msg, deliveryMethod);
|
GameMain.Client?.ClientPeer?.Send(msg, deliveryMethod);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientRead(IReadMessage inc)
|
public void ClientRead(IReadMessage inc)
|
||||||
@@ -356,7 +382,7 @@ namespace Barotrauma
|
|||||||
PendingRequestReceived(inc);
|
PendingRequestReceived(inc);
|
||||||
break;
|
break;
|
||||||
case NetworkHeader.ADD_PENDING:
|
case NetworkHeader.ADD_PENDING:
|
||||||
NewAdditonReceived(inc, flag);
|
NewAdditionReceived(inc, flag);
|
||||||
break;
|
break;
|
||||||
case NetworkHeader.REMOVE_PENDING:
|
case NetworkHeader.REMOVE_PENDING:
|
||||||
NewRemovalReceived(inc, flag);
|
NewRemovalReceived(inc, flag);
|
||||||
@@ -365,7 +391,7 @@ namespace Barotrauma
|
|||||||
HealRequestReceived(inc);
|
HealRequestReceived(inc);
|
||||||
break;
|
break;
|
||||||
case NetworkHeader.CLEAR_PENDING:
|
case NetworkHeader.CLEAR_PENDING:
|
||||||
ClearRequstReceived();
|
ClearRequestReceived();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void CreateMessageBox(string author)
|
private void CreateMessageBox(string author)
|
||||||
{
|
{
|
||||||
Vector2 relativeSize = new Vector2(0.3f * GUI.AspectRatioAdjustment, 0.15f);
|
Vector2 relativeSize = new Vector2(0.2f / GUI.AspectRatioAdjustment, 0.15f);
|
||||||
Point minSize = new Point(300, 200);
|
Point minSize = new Point(300, 200);
|
||||||
msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
|
msgBox = new GUIMessageBox(readyCheckHeader, readyCheckBody(author), new[] { yesButton, noButton }, relativeSize, minSize, type: GUIMessageBox.Type.Vote) { UserData = PromptData, Draggable = true };
|
||||||
|
|
||||||
|
|||||||
@@ -213,10 +213,11 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions.Where(m => m.Prefab.ShowInMenus));
|
List<Mission> missionsToDisplay = new List<Mission>(selectedMissions.Where(m => m.Prefab.ShowInMenus));
|
||||||
if (!selectedMissions.Any() && startLocation != null)
|
if (startLocation != null)
|
||||||
{
|
{
|
||||||
foreach (Mission mission in startLocation.SelectedMissions)
|
foreach (Mission mission in startLocation.SelectedMissions)
|
||||||
{
|
{
|
||||||
|
if (missionsToDisplay.Contains(mission)) { continue; }
|
||||||
if (!mission.Prefab.ShowInMenus) { continue; }
|
if (!mission.Prefab.ShowInMenus) { continue; }
|
||||||
if (mission.Locations[0] == mission.Locations[1] ||
|
if (mission.Locations[0] == mission.Locations[1] ||
|
||||||
mission.Locations.Contains(campaignMode?.Map.SelectedLocation))
|
mission.Locations.Contains(campaignMode?.Map.SelectedLocation))
|
||||||
@@ -316,7 +317,7 @@ namespace Barotrauma
|
|||||||
RichString reputationText = displayedMission.GetReputationRewardText();
|
RichString reputationText = displayedMission.GetReputationRewardText();
|
||||||
if (!reputationText.IsNullOrEmpty())
|
if (!reputationText.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform), reputationText, wrap: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalReward = displayedMission.GetFinalReward(Submarine.MainSub);
|
int totalReward = displayedMission.GetFinalReward(Submarine.MainSub);
|
||||||
|
|||||||
@@ -88,6 +88,11 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||||
{
|
{
|
||||||
|
UInt16 userID = msg.ReadUInt16();
|
||||||
|
if (userID != Entity.NullEntityID)
|
||||||
|
{
|
||||||
|
user = Entity.FindEntityByID(userID) as Character;
|
||||||
|
}
|
||||||
CurrPowerConsumption = powerConsumption;
|
CurrPowerConsumption = powerConsumption;
|
||||||
charging = true;
|
charging = true;
|
||||||
timer = Duration;
|
timer = Duration;
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ namespace Barotrauma.Items.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private float lightColorMultiplier;
|
private float lightColorMultiplier;
|
||||||
|
|
||||||
|
[Serialize(1.0f, IsPropertySaveable.Yes, description: "The scale of the light sprite.")]
|
||||||
|
public float LightSpriteScale { get; set; }
|
||||||
|
|
||||||
public Vector2 DrawSize
|
public Vector2 DrawSize
|
||||||
{
|
{
|
||||||
get { return new Vector2(Light.Range * 2, Light.Range * 2); }
|
get { return new Vector2(Light.Range * 2, Light.Range * 2); }
|
||||||
@@ -92,7 +95,13 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
color = new Color(lightColor, Light.OverrideLightSpriteAlpha.Value);
|
color = new Color(lightColor, Light.OverrideLightSpriteAlpha.Value);
|
||||||
}
|
}
|
||||||
Light.LightSprite.Draw(spriteBatch, new Vector2(drawPos.X, -drawPos.Y), color * lightBrightness, origin, -Light.Rotation, item.Scale, Light.LightSpriteEffect, itemDepth - 0.0001f);
|
Light.LightSprite.Draw(spriteBatch,
|
||||||
|
new Vector2(drawPos.X, -drawPos.Y),
|
||||||
|
color * lightBrightness,
|
||||||
|
origin,
|
||||||
|
-Light.Rotation,
|
||||||
|
item.Scale * LightSpriteScale,
|
||||||
|
Light.LightSpriteEffect, itemDepth - 0.0001f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (slotRect.Contains(PlayerInput.MousePosition))
|
if (slotRect.Contains(PlayerInput.MousePosition))
|
||||||
{
|
{
|
||||||
var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name);
|
var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name).Distinct();
|
||||||
LocalizedString toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients);
|
LocalizedString toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients);
|
||||||
if (suitableIngredients.Count() > 3) { toolTipText += "..."; }
|
if (suitableIngredients.Count() > 3) { toolTipText += "..."; }
|
||||||
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
|
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
|
||||||
@@ -550,6 +550,8 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
|
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toolTipText = $"‖color:{Color.White.ToStringHex()}‖{toolTipText}‖color:end‖";
|
||||||
if (!requiredItemPrefab.Description.IsNullOrEmpty())
|
if (!requiredItemPrefab.Description.IsNullOrEmpty())
|
||||||
{
|
{
|
||||||
toolTipText += '\n' + requiredItemPrefab.Description;
|
toolTipText += '\n' + requiredItemPrefab.Description;
|
||||||
@@ -594,7 +596,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (tooltip != null)
|
if (tooltip != null)
|
||||||
{
|
{
|
||||||
GUIComponent.DrawToolTip(spriteBatch, tooltip.Tooltip, tooltip.TargetElement);
|
GUIComponent.DrawToolTip(spriteBatch, RichString.Rich(tooltip.Tooltip), tooltip.TargetElement);
|
||||||
tooltip = null;
|
tooltip = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -403,7 +403,8 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private bool VisibleOnItemFinder(Item it)
|
private bool VisibleOnItemFinder(Item it)
|
||||||
{
|
{
|
||||||
if (!item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
|
if (it?.Submarine == null) { return false; }
|
||||||
|
if (item.Submarine == null || !item.Submarine.IsEntityFoundOnThisSub(it, includingConnectedSubs: true)) { return false; }
|
||||||
if (it.NonInteractable || it.HiddenInGame) { return false; }
|
if (it.NonInteractable || it.HiddenInGame) { return false; }
|
||||||
if (it.GetComponent<Pickable>() == null) { return false; }
|
if (it.GetComponent<Pickable>() == null) { return false; }
|
||||||
|
|
||||||
@@ -702,6 +703,12 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
|
private void DrawHUDFront(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||||
{
|
{
|
||||||
|
if (miniMapFrame == null)
|
||||||
|
{
|
||||||
|
//frame not created yet, could happen if the item hasn't been inside any sub this round?
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (Voltage < MinVoltage)
|
if (Voltage < MinVoltage)
|
||||||
{
|
{
|
||||||
Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
|
Vector2 textSize = GUIStyle.Font.MeasureString(noPowerTip);
|
||||||
@@ -1057,7 +1064,9 @@ namespace Barotrauma.Items.Components
|
|||||||
waterVolume += linkedHull.WaterVolume;
|
waterVolume += linkedHull.WaterVolume;
|
||||||
totalVolume += linkedHull.Volume;
|
totalVolume += linkedHull.Volume;
|
||||||
}
|
}
|
||||||
hullData.HullWaterAmount = MathHelper.Clamp((int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100);
|
hullData.HullWaterAmount =
|
||||||
|
waterVolume > 1.0f ?
|
||||||
|
MathHelper.Clamp((int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100) : 0.0f;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1311,7 +1311,6 @@ namespace Barotrauma.Items.Components
|
|||||||
float worldPingRadiusSqr = worldPingRadius * worldPingRadius;
|
float worldPingRadiusSqr = worldPingRadius * worldPingRadius;
|
||||||
|
|
||||||
disruptedDirections.Clear();
|
disruptedDirections.Clear();
|
||||||
if (Level.Loaded == null) { return; }
|
|
||||||
|
|
||||||
for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
|
for (var pingIndex = 0; pingIndex < activePingsCount; ++pingIndex)
|
||||||
{
|
{
|
||||||
@@ -1434,8 +1433,10 @@ namespace Barotrauma.Items.Components
|
|||||||
if (connectedSubs.Contains(submarine)) { continue; }
|
if (connectedSubs.Contains(submarine)) { continue; }
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle worldBorders = Submarine.MainSub.GetDockedBorders();
|
//display the actual walls if the ping source is inside the sub (but not inside a hull, that's handled above)
|
||||||
worldBorders.Location += Submarine.MainSub.WorldPosition.ToPoint();
|
//only relevant in the end levels or maybe custom subs with some kind of non-hulled parts
|
||||||
|
Rectangle worldBorders = submarine.GetDockedBorders();
|
||||||
|
worldBorders.Location += submarine.WorldPosition.ToPoint();
|
||||||
if (Submarine.RectContains(worldBorders, pingSource))
|
if (Submarine.RectContains(worldBorders, pingSource))
|
||||||
{
|
{
|
||||||
CreateBlipsForSubmarineWalls(submarine, pingSource, transducerPos, pingRadius, prevPingRadius, range, passive);
|
CreateBlipsForSubmarineWalls(submarine, pingSource, transducerPos, pingRadius, prevPingRadius, range, passive);
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ namespace Barotrauma.Items.Components
|
|||||||
User = Entity.FindEntityByID(userId) as Character;
|
User = Entity.FindEntityByID(userId) as Character;
|
||||||
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
Vector2 simPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
|
||||||
float rotation = msg.ReadSingle();
|
float rotation = msg.ReadSingle();
|
||||||
SpreadCounter = msg.ReadByte();
|
spreadIndex = msg.ReadByte();
|
||||||
if (User != null)
|
if (User != null)
|
||||||
{
|
{
|
||||||
Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false);
|
Shoot(User, simPosition, simPosition, rotation, ignoredBodies: User.AnimController.Limbs.Where(l => !l.IsSevered).Select(l => l.body.FarseerBody).ToList(), createNetworkEvent: false);
|
||||||
|
|||||||
+50
-37
@@ -12,6 +12,8 @@ namespace Barotrauma.Items.Components
|
|||||||
private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
|
private readonly List<GUIComponent> uiElements = new List<GUIComponent>();
|
||||||
private GUILayoutGroup uiElementContainer;
|
private GUILayoutGroup uiElementContainer;
|
||||||
|
|
||||||
|
private bool readingNetworkEvent;
|
||||||
|
|
||||||
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
|
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
|
||||||
|
|
||||||
public override bool RecreateGUIOnResolutionChange => true;
|
public override bool RecreateGUIOnResolutionChange => true;
|
||||||
@@ -100,7 +102,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
|
ValueChanged(ni.UserData as CustomInterfaceElement, ni.FloatValue);
|
||||||
}
|
}
|
||||||
else
|
else if (!readingNetworkEvent)
|
||||||
{
|
{
|
||||||
item.CreateClientEvent(this);
|
item.CreateClientEvent(this);
|
||||||
}
|
}
|
||||||
@@ -126,7 +128,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
|
ValueChanged(ni.UserData as CustomInterfaceElement, ni.IntValue);
|
||||||
}
|
}
|
||||||
else
|
else if (!readingNetworkEvent)
|
||||||
{
|
{
|
||||||
item.CreateClientEvent(this);
|
item.CreateClientEvent(this);
|
||||||
}
|
}
|
||||||
@@ -161,7 +163,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
|
TickBoxToggled(tBox.UserData as CustomInterfaceElement, tBox.Selected);
|
||||||
}
|
}
|
||||||
else
|
else if (!readingNetworkEvent)
|
||||||
{
|
{
|
||||||
item.CreateClientEvent(this);
|
item.CreateClientEvent(this);
|
||||||
}
|
}
|
||||||
@@ -181,12 +183,12 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
btn.OnClicked += (_, userdata) =>
|
btn.OnClicked += (_, userdata) =>
|
||||||
{
|
{
|
||||||
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;;
|
CustomInterfaceElement btnElement = userdata as CustomInterfaceElement;
|
||||||
if (GameMain.Client == null)
|
if (GameMain.Client == null)
|
||||||
{
|
{
|
||||||
ButtonClicked(btnElement);
|
ButtonClicked(btnElement);
|
||||||
}
|
}
|
||||||
else
|
else if (!readingNetworkEvent)
|
||||||
{
|
{
|
||||||
item.CreateClientEvent(this, new EventData(btnElement));
|
item.CreateClientEvent(this, new EventData(btnElement));
|
||||||
}
|
}
|
||||||
@@ -248,7 +250,7 @@ namespace Barotrauma.Items.Components
|
|||||||
int visibleElementCount = 0;
|
int visibleElementCount = 0;
|
||||||
foreach (var uiElement in uiElements)
|
foreach (var uiElement in uiElements)
|
||||||
{
|
{
|
||||||
if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; }
|
if (uiElement.UserData is not CustomInterfaceElement element) { continue; }
|
||||||
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
|
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || element.HasPropertyName || (element.Connection != null && element.Connection.Wires.Count > 0);
|
||||||
if (visible) { visibleElementCount++; }
|
if (visible) { visibleElementCount++; }
|
||||||
if (uiElement.Visible != visible)
|
if (uiElement.Visible != visible)
|
||||||
@@ -297,9 +299,10 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
LocalizedString CreateLabelText(int elementIndex)
|
LocalizedString CreateLabelText(int elementIndex)
|
||||||
{
|
{
|
||||||
return string.IsNullOrWhiteSpace(customInterfaceElementList[elementIndex].Label) ?
|
var label = customInterfaceElementList[elementIndex].Label;
|
||||||
|
return string.IsNullOrWhiteSpace(label) ?
|
||||||
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
|
TextManager.GetWithVariable("connection.signaloutx", "[num]", (elementIndex + 1).ToString()) :
|
||||||
customInterfaceElementList[elementIndex].Label;
|
TextManager.Get(label).Fallback(label);
|
||||||
}
|
}
|
||||||
|
|
||||||
uiElementContainer.Recalculate();
|
uiElementContainer.Recalculate();
|
||||||
@@ -334,7 +337,9 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (uiElements[i] is GUITextBox tb)
|
if (uiElements[i] is GUITextBox tb)
|
||||||
{
|
{
|
||||||
tb.Text = customInterfaceElementList[i].Signal;
|
tb.Text = Screen.Selected is { IsEditor: true } ?
|
||||||
|
customInterfaceElementList[i].Signal :
|
||||||
|
TextManager.Get(customInterfaceElementList[i].Signal).Value;
|
||||||
}
|
}
|
||||||
else if (uiElements[i] is GUINumberInput ni)
|
else if (uiElements[i] is GUINumberInput ni)
|
||||||
{
|
{
|
||||||
@@ -386,45 +391,53 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
readingNetworkEvent = true;
|
||||||
|
try
|
||||||
{
|
{
|
||||||
var element = customInterfaceElementList[i];
|
for (int i = 0; i < customInterfaceElementList.Count; i++)
|
||||||
if (element.HasPropertyName)
|
|
||||||
{
|
{
|
||||||
string newValue = msg.ReadString();
|
var element = customInterfaceElementList[i];
|
||||||
if (!element.IsNumberInput)
|
if (element.HasPropertyName)
|
||||||
{
|
{
|
||||||
TextChanged(element, newValue);
|
string newValue = msg.ReadString();
|
||||||
|
if (!element.IsNumberInput)
|
||||||
|
{
|
||||||
|
TextChanged(element, newValue);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (element.NumberType)
|
||||||
|
{
|
||||||
|
case NumberType.Int when int.TryParse(newValue, out int value):
|
||||||
|
ValueChanged(element, value);
|
||||||
|
break;
|
||||||
|
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
|
||||||
|
ValueChanged(element, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
switch (element.NumberType)
|
bool elementState = msg.ReadBoolean();
|
||||||
|
if (element.ContinuousSignal)
|
||||||
{
|
{
|
||||||
case NumberType.Int when int.TryParse(newValue, out int value):
|
((GUITickBox)uiElements[i]).Selected = elementState;
|
||||||
ValueChanged(element, value);
|
TickBoxToggled(element, elementState);
|
||||||
break;
|
}
|
||||||
case NumberType.Float when TryParseFloatInvariantCulture(newValue, out float value):
|
else if (elementState)
|
||||||
ValueChanged(element, value);
|
{
|
||||||
break;
|
ButtonClicked(element);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
bool elementState = msg.ReadBoolean();
|
|
||||||
if (element.ContinuousSignal)
|
|
||||||
{
|
|
||||||
((GUITickBox)uiElements[i]).Selected = elementState;
|
|
||||||
TickBoxToggled(element, elementState);
|
|
||||||
}
|
|
||||||
else if (elementState)
|
|
||||||
{
|
|
||||||
ButtonClicked(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateSignalsProjSpecific();
|
UpdateSignalsProjSpecific();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
readingNetworkEvent = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ namespace Barotrauma.Items.Components
|
|||||||
Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
|
Sprite pingCircle = GUIStyle.UIThermalGlow.Value.Sprite;
|
||||||
foreach (Limb limb in c.AnimController.Limbs)
|
foreach (Limb limb in c.AnimController.Limbs)
|
||||||
{
|
{
|
||||||
if (limb.Mass < 1.0f) { continue; }
|
if (limb.Mass < 0.5f && limb != c.AnimController.MainLimb) { continue; }
|
||||||
float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
|
float noise1 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.02f);
|
||||||
float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
|
float noise2 = PerlinNoise.GetPerlin((thermalEffectState + limb.Params.ID + c.ID) * 0.01f, (thermalEffectState + limb.Params.ID + c.ID) * 0.008f);
|
||||||
Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
|
Vector2 spriteScale = ConvertUnits.ToDisplayUnits(limb.body.GetSize()) / pingCircle.size * (noise1 * 0.5f + 2f);
|
||||||
|
|||||||
@@ -1398,7 +1398,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Failed to read component state - " + components[componentIndex].GetType() + " is not IServerSerializable.");
|
throw new Exception($"Failed to read component state - {components[componentIndex].GetType()} in item \"{Prefab.Identifier}\" is not IServerSerializable.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1411,13 +1411,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
throw new Exception("Failed to read inventory state - " + components[containerIndex].GetType() + " is not an ItemContainer.");
|
throw new Exception($"Failed to read inventory state - {components[containerIndex].GetType()} in item \"{Prefab.Identifier}\" is not an ItemContainer.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EventType.Status:
|
case EventType.Status:
|
||||||
|
bool loadingRound = msg.ReadBoolean();
|
||||||
float newCondition = msg.ReadSingle();
|
float newCondition = msg.ReadSingle();
|
||||||
SetCondition(newCondition, isNetworkEvent: true);
|
SetCondition(newCondition, isNetworkEvent: true, executeEffects: !loadingRound);
|
||||||
break;
|
break;
|
||||||
case EventType.AssignCampaignInteraction:
|
case EventType.AssignCampaignInteraction:
|
||||||
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
|
CampaignInteractionType = (CampaignMode.InteractionType)msg.ReadByte();
|
||||||
@@ -1459,9 +1460,9 @@ namespace Barotrauma
|
|||||||
byte length = msg.ReadByte();
|
byte length = msg.ReadByte();
|
||||||
for (int i = 0; i < length; i++)
|
for (int i = 0; i < length; i++)
|
||||||
{
|
{
|
||||||
var statIdentifier = INetSerializableStruct.Read<ItemStatManager.TalentStatIdentifier>(msg);
|
var statIdentifier = INetSerializableStruct.Read<TalentStatIdentifier>(msg);
|
||||||
var statValue = msg.ReadSingle();
|
var statValue = msg.ReadSingle();
|
||||||
StatManager.ApplyStat(statIdentifier, statValue);
|
StatManager.ApplyStatDirect(statIdentifier, statValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case EventType.Upgrade:
|
case EventType.Upgrade:
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -320,5 +321,45 @@ namespace Barotrauma
|
|||||||
return IsHorizontal ? rect.Height : rect.Width;
|
return IsHorizontal ? rect.Height : rect.Width;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void UpdateEditing(Camera cam, float deltaTime)
|
||||||
|
{
|
||||||
|
if (editingHUD == null || editingHUD.UserData != this)
|
||||||
|
{
|
||||||
|
editingHUD = CreateEditingHUD();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private GUIComponent CreateEditingHUD(bool inGame = false)
|
||||||
|
{
|
||||||
|
|
||||||
|
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.15f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) })
|
||||||
|
{
|
||||||
|
UserData = this
|
||||||
|
};
|
||||||
|
|
||||||
|
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), editingHUD.RectTransform, Anchor.Center))
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
AbsoluteSpacing = (int)(GUI.Scale * 5)
|
||||||
|
};
|
||||||
|
|
||||||
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("entityname.gap"), font: GUIStyle.LargeFont);
|
||||||
|
var hiddenInGameTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform), TextManager.Get("sp.hiddeningame.name"))
|
||||||
|
{
|
||||||
|
Selected = HiddenInGame
|
||||||
|
};
|
||||||
|
hiddenInGameTickBox.OnSelected += (GUITickBox tickbox) =>
|
||||||
|
{
|
||||||
|
HiddenInGame = tickbox.Selected;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
editingHUD.RectTransform.Resize(new Point(
|
||||||
|
editingHUD.Rect.Width,
|
||||||
|
(int)(paddedFrame.Children.Sum(c => c.Rect.Height + paddedFrame.AbsoluteSpacing) / paddedFrame.RectTransform.RelativeSize.Y * 1.25f)));
|
||||||
|
|
||||||
|
PositionEditingHUD();
|
||||||
|
|
||||||
|
return editingHUD;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -240,7 +240,8 @@ namespace Barotrauma.Lights
|
|||||||
range *
|
range *
|
||||||
((Character.Controlled?.Submarine != null && light.ParentSub == Character.Controlled?.Submarine) ? 2.0f : 1.0f) *
|
((Character.Controlled?.Submarine != null && light.ParentSub == Character.Controlled?.Submarine) ? 2.0f : 1.0f) *
|
||||||
(light.CastShadows ? 10.0f : 1.0f) *
|
(light.CastShadows ? 10.0f : 1.0f) *
|
||||||
(light.LightSourceParams.OverrideLightSpriteAlpha ?? (light.Color.A / 255.0f));
|
(light.LightSourceParams.OverrideLightSpriteAlpha ?? (light.Color.A / 255.0f)) *
|
||||||
|
light.PriorityMultiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
//find the lights with an active light volume
|
//find the lights with an active light volume
|
||||||
|
|||||||
@@ -377,6 +377,8 @@ namespace Barotrauma.Lights
|
|||||||
|
|
||||||
public float Priority;
|
public float Priority;
|
||||||
|
|
||||||
|
public float PriorityMultiplier = 1.0f;
|
||||||
|
|
||||||
private Vector2 lightTextureTargetSize;
|
private Vector2 lightTextureTargetSize;
|
||||||
|
|
||||||
public Vector2 LightTextureTargetSize
|
public Vector2 LightTextureTargetSize
|
||||||
|
|||||||
@@ -537,7 +537,7 @@ namespace Barotrauma
|
|||||||
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
|
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
|
||||||
if (!invalidMessage && i < Sections.Length)
|
if (!invalidMessage && i < Sections.Length)
|
||||||
{
|
{
|
||||||
SetDamage(i, damage);
|
SetDamage(i, damage, isNetworkEvent: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ namespace Barotrauma.Networking
|
|||||||
float gain = 1.0f;
|
float gain = 1.0f;
|
||||||
float noiseGain = 0.0f;
|
float noiseGain = 0.0f;
|
||||||
Vector3? position = null;
|
Vector3? position = null;
|
||||||
if (character != null)
|
if (character != null && !character.IsDead)
|
||||||
{
|
{
|
||||||
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
|
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
|
||||||
@@ -16,6 +15,10 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
sealed class GameClient : NetworkMember
|
sealed class GameClient : NetworkMember
|
||||||
{
|
{
|
||||||
|
public static readonly TimeSpan CampaignSaveTransferTimeOut = new TimeSpan(0, 0, seconds: 100);
|
||||||
|
//this should be longer than CampaignSaveTransferTimeOut - we shouldn't give up starting the round if we're still waiting for the save file
|
||||||
|
public static readonly TimeSpan LevelTransitionTimeOut = new TimeSpan(0, 0, seconds: 150);
|
||||||
|
|
||||||
public override bool IsClient => true;
|
public override bool IsClient => true;
|
||||||
public override bool IsServer => false;
|
public override bool IsServer => false;
|
||||||
|
|
||||||
@@ -76,6 +79,8 @@ namespace Barotrauma.Networking
|
|||||||
Interrupted
|
Interrupted
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private UInt16? debugStartGameCampaignSaveID;
|
||||||
|
|
||||||
private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
|
private RoundInitStatus roundInitStatus = RoundInitStatus.NotStarted;
|
||||||
|
|
||||||
public bool RoundStarting => roundInitStatus == RoundInitStatus.Starting || roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize;
|
public bool RoundStarting => roundInitStatus == RoundInitStatus.Starting || roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize;
|
||||||
@@ -512,6 +517,7 @@ namespace Barotrauma.Networking
|
|||||||
DisplayInLoadingScreens = true
|
DisplayInLoadingScreens = true
|
||||||
};
|
};
|
||||||
Quit();
|
Quit();
|
||||||
|
GUI.DisableHUD = false;
|
||||||
GameMain.ServerListScreen.Select();
|
GameMain.ServerListScreen.Select();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -859,17 +865,24 @@ namespace Barotrauma.Networking
|
|||||||
ContentFile file = ContentPackageManager.EnabledPackages.All
|
ContentFile file = ContentPackageManager.EnabledPackages.All
|
||||||
.Select(p =>
|
.Select(p =>
|
||||||
p.Files.FirstOrDefault(f => f.Path == filePath))
|
p.Files.FirstOrDefault(f => f.Path == filePath))
|
||||||
.FirstOrDefault(f => !(f is null));
|
.FirstOrDefault(f => f is not null);
|
||||||
contentToPreload.AddIfNotNull(file);
|
contentToPreload.AddIfNotNull(file);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
string campaignErrorInfo = string.Empty;
|
||||||
|
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign campaign)
|
||||||
|
{
|
||||||
|
campaignErrorInfo = $" Round start save ID: {debugStartGameCampaignSaveID}, last save id: {campaign.LastSaveID}, pending save id: {campaign.PendingSaveID}.";
|
||||||
|
}
|
||||||
|
|
||||||
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
|
GameMain.GameSession.EventManager.PreloadContent(contentToPreload);
|
||||||
|
|
||||||
int subEqualityCheckValue = inc.ReadInt32();
|
int subEqualityCheckValue = inc.ReadInt32();
|
||||||
if (subEqualityCheckValue != (Submarine.MainSub?.Info?.EqualityCheckVal ?? 0))
|
if (subEqualityCheckValue != (Submarine.MainSub?.Info?.EqualityCheckVal ?? 0))
|
||||||
{
|
{
|
||||||
string errorMsg = "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server." +
|
string errorMsg =
|
||||||
" There may have been an error in receiving the up-to-date submarine file from the server.";
|
"Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server. " +
|
||||||
|
$"There may have been an error in receiving the up-to-date submarine file from the server. Round init status: {roundInitStatus}." + campaignErrorInfo;
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw new Exception(errorMsg);
|
throw new Exception(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -886,7 +899,7 @@ namespace Barotrauma.Networking
|
|||||||
$"Mission equality check failed. Mission count doesn't match the server. " +
|
$"Mission equality check failed. Mission count doesn't match the server. " +
|
||||||
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
|
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
|
||||||
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
|
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
|
||||||
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
|
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))}). Round init status: {roundInitStatus}." + campaignErrorInfo;
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw new Exception(errorMsg);
|
throw new Exception(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -899,7 +912,7 @@ namespace Barotrauma.Networking
|
|||||||
$"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " +
|
$"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " +
|
||||||
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
|
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
|
||||||
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
|
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
|
||||||
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
|
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))}). Round init status: {roundInitStatus}." + campaignErrorInfo;
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw new Exception(errorMsg);
|
throw new Exception(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -922,7 +935,7 @@ namespace Barotrauma.Networking
|
|||||||
", level value count: " + levelEqualityCheckValues.Count +
|
", level value count: " + levelEqualityCheckValues.Count +
|
||||||
", seed: " + Level.Loaded.Seed +
|
", seed: " + Level.Loaded.Seed +
|
||||||
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
|
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortRepresentation + ")" +
|
||||||
", mirrored: " + Level.Loaded.Mirrored + ").";
|
", mirrored: " + Level.Loaded.Mirrored + "). Round init status: " + roundInitStatus + "." + campaignErrorInfo;
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw new Exception(errorMsg);
|
throw new Exception(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -1322,6 +1335,8 @@ namespace Barotrauma.Networking
|
|||||||
eventErrorWritten = false;
|
eventErrorWritten = false;
|
||||||
GameMain.NetLobbyScreen.StopWaitingForStartRound();
|
GameMain.NetLobbyScreen.StopWaitingForStartRound();
|
||||||
|
|
||||||
|
debugStartGameCampaignSaveID = null;
|
||||||
|
|
||||||
while (CoroutineManager.IsCoroutineRunning("EndGame"))
|
while (CoroutineManager.IsCoroutineRunning("EndGame"))
|
||||||
{
|
{
|
||||||
EndCinematic?.Stop();
|
EndCinematic?.Stop();
|
||||||
@@ -1471,7 +1486,28 @@ namespace Barotrauma.Networking
|
|||||||
roundInitStatus = RoundInitStatus.Interrupted;
|
roundInitStatus = RoundInitStatus.Interrupted;
|
||||||
yield return CoroutineStatus.Failure;
|
yield return CoroutineStatus.Failure;
|
||||||
}
|
}
|
||||||
else if (campaign.Map == null)
|
|
||||||
|
if (NetIdUtils.IdMoreRecent(campaign.PendingSaveID, campaign.LastSaveID) ||
|
||||||
|
NetIdUtils.IdMoreRecent(campaignSaveID, campaign.PendingSaveID))
|
||||||
|
{
|
||||||
|
campaign.PendingSaveID = campaignSaveID;
|
||||||
|
DateTime saveFileTimeOut = DateTime.Now + CampaignSaveTransferTimeOut;
|
||||||
|
while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID))
|
||||||
|
{
|
||||||
|
if (DateTime.Now > saveFileTimeOut)
|
||||||
|
{
|
||||||
|
GameStarted = true;
|
||||||
|
new GUIMessageBox(TextManager.Get("error"), TextManager.Get("campaignsavetransfer.timeout"));
|
||||||
|
GameMain.NetLobbyScreen.Select();
|
||||||
|
roundInitStatus = RoundInitStatus.Interrupted;
|
||||||
|
//use success status, even though this is a failure (no need to show a console error because we show it in the message box)
|
||||||
|
yield return CoroutineStatus.Success;
|
||||||
|
}
|
||||||
|
yield return new WaitForSeconds(0.1f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (campaign.Map == null)
|
||||||
{
|
{
|
||||||
GameStarted = true;
|
GameStarted = true;
|
||||||
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
|
DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
|
||||||
@@ -1480,30 +1516,14 @@ namespace Barotrauma.Networking
|
|||||||
yield return CoroutineStatus.Failure;
|
yield return CoroutineStatus.Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.PendingSaveID))
|
|
||||||
{
|
|
||||||
campaign.PendingSaveID = campaignSaveID;
|
|
||||||
DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0,0,60);
|
|
||||||
while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID))
|
|
||||||
{
|
|
||||||
if (DateTime.Now > saveFileTimeOut)
|
|
||||||
{
|
|
||||||
GameStarted = true;
|
|
||||||
DebugConsole.ThrowError("Failed to start campaign round (timed out while waiting for the up-to-date save file).");
|
|
||||||
GameMain.NetLobbyScreen.Select();
|
|
||||||
roundInitStatus = RoundInitStatus.Interrupted;
|
|
||||||
yield return CoroutineStatus.Failure;
|
|
||||||
}
|
|
||||||
yield return new WaitForSeconds(0.1f);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
campaign.Map.SelectLocation(selectedLocationIndex);
|
campaign.Map.SelectLocation(selectedLocationIndex);
|
||||||
|
|
||||||
LevelData levelData = nextLocationIndex > -1 ?
|
LevelData levelData = nextLocationIndex > -1 ?
|
||||||
campaign.Map.Locations[nextLocationIndex].LevelData :
|
campaign.Map.Locations[nextLocationIndex].LevelData :
|
||||||
campaign.Map.Connections[nextConnectionIndex].LevelData;
|
campaign.Map.Connections[nextConnectionIndex].LevelData;
|
||||||
|
|
||||||
|
debugStartGameCampaignSaveID = campaign.LastSaveID;
|
||||||
|
|
||||||
if (roundSummary != null)
|
if (roundSummary != null)
|
||||||
{
|
{
|
||||||
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
|
loadTask = campaign.SelectSummaryScreen(roundSummary, levelData, mirrorLevel, null);
|
||||||
@@ -1697,7 +1717,7 @@ namespace Barotrauma.Networking
|
|||||||
yield return CoroutineStatus.Success;
|
yield return CoroutineStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (GameMain.GameSession != null) { GameMain.GameSession.EndRound(endMessage, traitorResults, transitionType); }
|
GameMain.GameSession?.EndRound(endMessage, traitorResults, transitionType);
|
||||||
|
|
||||||
ServerSettings.ServerDetailsChanged = true;
|
ServerSettings.ServerDetailsChanged = true;
|
||||||
|
|
||||||
@@ -2587,31 +2607,24 @@ namespace Barotrauma.Networking
|
|||||||
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
|
public void WriteCharacterInfo(IWriteMessage msg, string newName = null)
|
||||||
{
|
{
|
||||||
msg.WriteBoolean(characterInfo == null);
|
msg.WriteBoolean(characterInfo == null);
|
||||||
|
msg.WritePadBits();
|
||||||
if (characterInfo == null) { return; }
|
if (characterInfo == null) { return; }
|
||||||
|
|
||||||
msg.WriteString(newName ?? string.Empty);
|
var head = characterInfo.Head;
|
||||||
|
|
||||||
msg.WriteByte((byte)characterInfo.Head.Preset.TagSet.Count);
|
var netInfo = new NetCharacterInfo(
|
||||||
foreach (Identifier tag in characterInfo.Head.Preset.TagSet)
|
NewName: newName ?? string.Empty,
|
||||||
{
|
Tags: head.Preset.TagSet.ToImmutableArray(),
|
||||||
msg.WriteIdentifier(tag);
|
HairIndex: (byte)head.HairIndex,
|
||||||
}
|
BeardIndex: (byte)head.BeardIndex,
|
||||||
msg.WriteByte((byte)characterInfo.Head.HairIndex);
|
MoustacheIndex: (byte)head.MoustacheIndex,
|
||||||
msg.WriteByte((byte)characterInfo.Head.BeardIndex);
|
FaceAttachmentIndex: (byte)head.FaceAttachmentIndex,
|
||||||
msg.WriteByte((byte)characterInfo.Head.MoustacheIndex);
|
SkinColor: head.SkinColor,
|
||||||
msg.WriteByte((byte)characterInfo.Head.FaceAttachmentIndex);
|
HairColor: head.HairColor,
|
||||||
msg.WriteColorR8G8B8(characterInfo.Head.SkinColor);
|
FacialHairColor: head.FacialHairColor,
|
||||||
msg.WriteColorR8G8B8(characterInfo.Head.HairColor);
|
JobVariants: GameMain.NetLobbyScreen.JobPreferences.Select(NetJobVariant.FromJobVariant).ToImmutableArray());
|
||||||
msg.WriteColorR8G8B8(characterInfo.Head.FacialHairColor);
|
|
||||||
|
|
||||||
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
msg.WriteNetSerializableStruct(netInfo);
|
||||||
int count = Math.Min(jobPreferences.Count, 3);
|
|
||||||
msg.WriteByte((byte)count);
|
|
||||||
for (int i = 0; i < count; i++)
|
|
||||||
{
|
|
||||||
msg.WriteIdentifier(jobPreferences[i].Prefab.Identifier);
|
|
||||||
msg.WriteByte((byte)jobPreferences[i].Variant);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Vote(VoteType voteType, object data)
|
public void Vote(VoteType voteType, object data)
|
||||||
@@ -2864,12 +2877,14 @@ namespace Barotrauma.Networking
|
|||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool SpectateClicked(GUIButton button, object userData)
|
public bool SpectateClicked(GUIButton button, object _)
|
||||||
{
|
{
|
||||||
MultiPlayerCampaign campaign =
|
MultiPlayerCampaign campaign =
|
||||||
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
|
GameMain.NetLobbyScreen.SelectedMode == GameMain.GameSession?.GameMode.Preset ?
|
||||||
GameMain.GameSession?.GameMode as MultiPlayerCampaign : null;
|
GameMain.GameSession?.GameMode as MultiPlayerCampaign : null;
|
||||||
if (campaign != null && campaign.LastSaveID < campaign.PendingSaveID)
|
|
||||||
|
if (FileReceiver.ActiveTransfers.Any(t => t.FileType == FileTransferType.CampaignSave) ||
|
||||||
|
(campaign != null && NetIdUtils.IdMoreRecent(campaign.PendingSaveID, campaign.LastSaveID)))
|
||||||
{
|
{
|
||||||
new GUIMessageBox("", TextManager.Get("campaignfiletransferinprogress"));
|
new GUIMessageBox("", TextManager.Get("campaignfiletransferinprogress"));
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -92,10 +92,10 @@ namespace Barotrauma.Networking
|
|||||||
Name = GameMain.Client.Name,
|
Name = GameMain.Client.Name,
|
||||||
OwnerKey = ownerKey,
|
OwnerKey = ownerKey,
|
||||||
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
|
SteamId = SteamManager.GetSteamId().Select(id => (AccountId)id),
|
||||||
SteamAuthTicket = steamAuthTicket switch
|
SteamAuthTicket = steamAuthTicket?.Data switch
|
||||||
{
|
{
|
||||||
null => Option<byte[]>.None(),
|
null => Option<byte[]>.None(),
|
||||||
var ticket => Option<byte[]>.Some(ticket.Data)
|
var ticketData => Option<byte[]>.Some(ticketData)
|
||||||
},
|
},
|
||||||
GameVersion = GameMain.Version.ToString(),
|
GameVersion = GameMain.Version.ToString(),
|
||||||
Language = GameSettings.CurrentConfig.Language.Value
|
Language = GameSettings.CurrentConfig.Language.Value
|
||||||
|
|||||||
+18
-1
@@ -111,8 +111,25 @@ namespace Barotrauma.Networking
|
|||||||
? NetworkConnection.TimeoutThresholdInGame
|
? NetworkConnection.TimeoutThresholdInGame
|
||||||
: NetworkConnection.TimeoutThreshold;
|
: NetworkConnection.TimeoutThreshold;
|
||||||
|
|
||||||
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
try
|
||||||
|
{
|
||||||
|
IReadMessage inc = new ReadOnlyMessage(data, false, 0, dataLength, ServerConnection);
|
||||||
|
ProcessP2PData(inc);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
string errorMsg = $"Client failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
|
||||||
|
GameAnalyticsManager.AddErrorEventOnce($"SteamP2PClientPeer.OnP2PData:ClientReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
#if DEBUG
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
#else
|
||||||
|
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessP2PData(IReadMessage inc)
|
||||||
|
{
|
||||||
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
var (deliveryMethod, packetHeader, initialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||||
|
|
||||||
if (!packetHeader.IsServerMessage()) { return; }
|
if (!packetHeader.IsServerMessage()) { return; }
|
||||||
|
|||||||
+21
-4
@@ -141,15 +141,31 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if (remotePeer.DisconnectTime != null) { return; }
|
if (remotePeer.DisconnectTime != null) { return; }
|
||||||
|
|
||||||
var peerPacketHeaders = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
try
|
||||||
|
{
|
||||||
|
ProcessP2PData(steamId, remotePeer, inc);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
string errorMsg = $"Server failed to read an incoming P2P message. {{{e}}}\n{e.StackTrace.CleanupStackTrace()}";
|
||||||
|
GameAnalyticsManager.AddErrorEventOnce($"SteamP2POwnerPeer.OnP2PData:OwnerReadException{e.TargetSite}", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
#if DEBUG
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
#else
|
||||||
|
if (GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PacketHeader packetHeader = peerPacketHeaders.PacketHeader;
|
private void ProcessP2PData(ulong steamId, RemotePeer remotePeer, IReadMessage inc)
|
||||||
|
{
|
||||||
|
var (deliveryMethod, packetHeader, connectionInitialization) = INetSerializableStruct.Read<PeerPacketHeaders>(inc);
|
||||||
|
|
||||||
if (!remotePeer.Authenticated && !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
|
if (remotePeer is { Authenticated: false, Authenticating: false } && packetHeader.IsConnectionInitializationStep())
|
||||||
{
|
{
|
||||||
remotePeer.DisconnectTime = null;
|
remotePeer.DisconnectTime = null;
|
||||||
|
|
||||||
ConnectionInitialization initialization = peerPacketHeaders.Initialization ?? throw new Exception("Initialization step missing");
|
ConnectionInitialization initialization = connectionInitialization ?? throw new Exception("Initialization step missing");
|
||||||
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
|
if (initialization == ConnectionInitialization.SteamTicketAndVersion)
|
||||||
{
|
{
|
||||||
remotePeer.Authenticating = true;
|
remotePeer.Authenticating = true;
|
||||||
@@ -181,6 +197,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
ForwardToServerProcess(outMsg);
|
ForwardToServerProcess(outMsg);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float deltaTime)
|
public override void Update(float deltaTime)
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ namespace Barotrauma.Networking
|
|||||||
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
|
if (Enum.TryParse(valueGetter("playstyle"), out PlayStyle playStyle)) { PlayStyle = playStyle; }
|
||||||
Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None;
|
Language = valueGetter("language")?.ToLanguageIdentifier() ?? LanguageIdentifier.None;
|
||||||
|
|
||||||
ContentPackages = ExtractContentPackageInfo(valueGetter).ToImmutableArray();
|
ContentPackages = ExtractContentPackageInfo(ServerName, valueGetter).ToImmutableArray();
|
||||||
|
|
||||||
bool getBool(string key)
|
bool getBool(string key)
|
||||||
{
|
{
|
||||||
@@ -437,8 +437,34 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ContentPackageInfo[] ExtractContentPackageInfo(Func<string, string?> valueGetter)
|
private static ContentPackageInfo[] ExtractContentPackageInfo(string serverName, Func<string, string?> valueGetter)
|
||||||
{
|
{
|
||||||
|
//workaround to ServerRules queries truncating the values to 255 bytes
|
||||||
|
int individualPackageIndex = 0;
|
||||||
|
string? individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
|
||||||
|
if (!individualPackage.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
List<ContentPackageInfo> contentPackages = new List<ContentPackageInfo>();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
string[] splitPackageInfo = individualPackage.Split(',');
|
||||||
|
if (splitPackageInfo.Length != 3)
|
||||||
|
{
|
||||||
|
DebugConsole.Log(
|
||||||
|
$"Error in a server's content package list: malformed content package info ({individualPackage}).");
|
||||||
|
return Array.Empty<ContentPackageInfo>();
|
||||||
|
}
|
||||||
|
string name = splitPackageInfo[0];
|
||||||
|
string hash = splitPackageInfo[1];
|
||||||
|
ulong.TryParse(splitPackageInfo[2], out ulong id);
|
||||||
|
contentPackages.Add(new ContentPackageInfo(name, hash, Option<ContentPackageId>.Some(new SteamWorkshopId(id))));
|
||||||
|
|
||||||
|
individualPackageIndex++;
|
||||||
|
individualPackage = valueGetter($"contentpackage{individualPackageIndex}");
|
||||||
|
} while (!individualPackage.IsNullOrEmpty());
|
||||||
|
return contentPackages.ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
string? joinedNames = valueGetter("contentpackage");
|
string? joinedNames = valueGetter("contentpackage");
|
||||||
string? joinedHashes = valueGetter("contentpackagehash");
|
string? joinedHashes = valueGetter("contentpackagehash");
|
||||||
string? joinedWorkshopIds = valueGetter("contentpackageid");
|
string? joinedWorkshopIds = valueGetter("contentpackageid");
|
||||||
@@ -448,9 +474,11 @@ namespace Barotrauma.Networking
|
|||||||
#warning TODO: genericize
|
#warning TODO: genericize
|
||||||
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
|
ulong[] contentPackageIds = joinedWorkshopIds.IsNullOrEmpty() ? new ulong[1] : SteamManager.ParseWorkshopIds(joinedWorkshopIds).ToArray();
|
||||||
|
|
||||||
if (contentPackageNames.Length != contentPackageHashes.Length
|
if (contentPackageNames.Length != contentPackageHashes.Length || contentPackageHashes.Length != contentPackageIds.Length)
|
||||||
|| contentPackageHashes.Length != contentPackageIds.Length)
|
|
||||||
{
|
{
|
||||||
|
DebugConsole.Log(
|
||||||
|
$"The number of names, hashes and Workshop IDs on server \"{serverName}\"" +
|
||||||
|
$" doesn't match: {contentPackageNames.Length} names ({string.Join(", ", contentPackageNames)}), {contentPackageHashes.Length} hashes, {contentPackageIds.Length} ids)");
|
||||||
return Array.Empty<ContentPackageInfo>();
|
return Array.Empty<ContentPackageInfo>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -35,7 +35,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
|
private static Option<ServerInfo> InfoFromListEntry(Steamworks.Data.ServerInfo entry) =>
|
||||||
entry.Name.IsNullOrEmpty()
|
entry.Name.IsNullOrEmpty() || entry.Address is null
|
||||||
? Option<ServerInfo>.None()
|
? Option<ServerInfo>.None()
|
||||||
: Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
|
: Option<ServerInfo>.Some(new ServerInfo(new LidgrenEndpoint(entry.Address, entry.ConnectionPort))
|
||||||
{
|
{
|
||||||
|
|||||||
+2
-2
@@ -71,10 +71,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (var lobby in lobbies)
|
foreach (var lobby in lobbies)
|
||||||
{
|
{
|
||||||
string lobbyOwnerStr = lobby.GetData("lobbyowner");
|
string lobbyOwnerStr = lobby.GetData("lobbyowner") ?? "";
|
||||||
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
|
lobbyQuery = lobbyQuery.WithoutKeyValue("lobbyowner", lobbyOwnerStr);
|
||||||
|
|
||||||
string serverName = lobby.GetData("name");
|
string serverName = lobby.GetData("name") ?? "";
|
||||||
if (string.IsNullOrEmpty(serverName)) { continue; }
|
if (string.IsNullOrEmpty(serverName)) { continue; }
|
||||||
|
|
||||||
var ownerId = SteamId.Parse(lobbyOwnerStr);
|
var ownerId = SteamId.Parse(lobbyOwnerStr);
|
||||||
|
|||||||
@@ -9,6 +9,9 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
partial class ServerSettings : ISerializableEntity
|
partial class ServerSettings : ISerializableEntity
|
||||||
{
|
{
|
||||||
|
private static readonly LocalizedString packetAmountTooltip = TextManager.Get("ServerSettingsMaxPacketAmountTooltip");
|
||||||
|
private static readonly RichString packetAmountTooltipWarning = RichString.Rich($"{packetAmountTooltip}\n\n‖color:gui.red‖{TextManager.Get("PacketLimitWarning")}‖end‖");
|
||||||
|
|
||||||
partial class NetPropertyData
|
partial class NetPropertyData
|
||||||
{
|
{
|
||||||
public GUIComponent GUIComponent;
|
public GUIComponent GUIComponent;
|
||||||
@@ -28,7 +31,15 @@ namespace Barotrauma.Networking
|
|||||||
if (GUIComponent == null) return null;
|
if (GUIComponent == null) return null;
|
||||||
else if (GUIComponent is GUITickBox tickBox) return tickBox.Selected;
|
else if (GUIComponent is GUITickBox tickBox) return tickBox.Selected;
|
||||||
else if (GUIComponent is GUITextBox textBox) return textBox.Text;
|
else if (GUIComponent is GUITextBox textBox) return textBox.Text;
|
||||||
else if (GUIComponent is GUIScrollBar scrollBar) return scrollBar.BarScrollValue;
|
else if (GUIComponent is GUIScrollBar scrollBar)
|
||||||
|
{
|
||||||
|
if (property.PropertyType == typeof(int))
|
||||||
|
{
|
||||||
|
return (int)MathF.Floor(scrollBar.BarScrollValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
return scrollBar.BarScrollValue;
|
||||||
|
}
|
||||||
else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected;
|
else if (GUIComponent is GUIRadioButtonGroup radioButtonGroup) return radioButtonGroup.Selected;
|
||||||
else if (GUIComponent is GUIDropDown dropdown) return dropdown.SelectedData;
|
else if (GUIComponent is GUIDropDown dropdown) return dropdown.SelectedData;
|
||||||
else if (GUIComponent is GUINumberInput numInput)
|
else if (GUIComponent is GUINumberInput numInput)
|
||||||
@@ -44,9 +55,9 @@ namespace Barotrauma.Networking
|
|||||||
else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value;
|
else if (GUIComponent is GUITextBox textBox) textBox.Text = (string)value;
|
||||||
else if (GUIComponent is GUIScrollBar scrollBar)
|
else if (GUIComponent is GUIScrollBar scrollBar)
|
||||||
{
|
{
|
||||||
if (value.GetType() == typeof(int))
|
if (value is int i)
|
||||||
{
|
{
|
||||||
scrollBar.BarScrollValue = (int)value;
|
scrollBar.BarScrollValue = i;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -941,11 +952,58 @@ namespace Barotrauma.Networking
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
GUILayoutGroup karmaAndDosLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), antigriefingTab.RectTransform), isHorizontal: false);
|
||||||
|
GUILayoutGroup lowerLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), karmaAndDosLayout.RectTransform), isHorizontal: true);
|
||||||
|
GUILayoutGroup upperLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), karmaAndDosLayout.RectTransform), isHorizontal: true);
|
||||||
|
|
||||||
// karma --------------------------------------------------------------------------
|
// karma --------------------------------------------------------------------------
|
||||||
|
|
||||||
var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsUseKarma"));
|
var karmaBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1f), upperLayout.RectTransform), TextManager.Get("ServerSettingsUseKarma"));
|
||||||
GetPropertyData(nameof(KarmaEnabled)).AssignGUIComponent(karmaBox);
|
GetPropertyData(nameof(KarmaEnabled)).AssignGUIComponent(karmaBox);
|
||||||
|
|
||||||
|
var enableDosProtection = new GUITickBox(new RectTransform(new Vector2(0.5f, 1f), upperLayout.RectTransform), TextManager.Get("ServerSettingsEnableDoSProtection"))
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("ServerSettingsEnableDoSProtectionTooltip")
|
||||||
|
};
|
||||||
|
GetPropertyData(nameof(EnableDoSProtection)).AssignGUIComponent(enableDosProtection);
|
||||||
|
|
||||||
|
CreateLabeledSlider(lowerLayout, "ServerSettingsMaxPacketAmount", out GUIScrollBar maxPacketSlider, out GUITextBlock maxPacketSliderLabel);
|
||||||
|
LocalizedString maxPacketCountLabel = maxPacketSliderLabel.Text;
|
||||||
|
maxPacketSlider.Step = 0.001f;
|
||||||
|
maxPacketSlider.Range = new Vector2(PacketLimitMin, PacketLimitMax);
|
||||||
|
maxPacketSlider.ToolTip = packetAmountTooltip;
|
||||||
|
maxPacketSlider.OnMoved = (scrollBar, _) =>
|
||||||
|
{
|
||||||
|
GUITextBlock textBlock = (GUITextBlock)scrollBar.UserData;
|
||||||
|
int value = (int)MathF.Floor(scrollBar.BarScrollValue);
|
||||||
|
|
||||||
|
LocalizedString valueText = value > PacketLimitMin
|
||||||
|
? value.ToString()
|
||||||
|
: TextManager.Get("ServerSettingsNoLimit");
|
||||||
|
|
||||||
|
switch (value)
|
||||||
|
{
|
||||||
|
case <= PacketLimitMin:
|
||||||
|
textBlock.TextColor = GUIStyle.Green;
|
||||||
|
scrollBar.ToolTip = packetAmountTooltip;
|
||||||
|
break;
|
||||||
|
case < PacketLimitWarning:
|
||||||
|
textBlock.TextColor = GUIStyle.Red;
|
||||||
|
scrollBar.ToolTip = packetAmountTooltipWarning;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
textBlock.TextColor = GUIStyle.TextColorNormal;
|
||||||
|
scrollBar.ToolTip = packetAmountTooltip;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
textBlock.Text = $"{maxPacketCountLabel} {valueText}";
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
GetPropertyData(nameof(MaxPacketAmount)).AssignGUIComponent(maxPacketSlider);
|
||||||
|
maxPacketSlider.OnMoved(maxPacketSlider, maxPacketSlider.BarScroll);
|
||||||
|
|
||||||
karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform));
|
karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform));
|
||||||
foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys)
|
foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -276,6 +276,8 @@ namespace Barotrauma.Networking
|
|||||||
if (GameMain.Client?.Character != null)
|
if (GameMain.Client?.Character != null)
|
||||||
{
|
{
|
||||||
var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default;
|
var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||||
|
if (GameMain.Client.Character.IsDead) { messageType = ChatMessageType.Dead; }
|
||||||
|
|
||||||
GameMain.Client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
GameMain.Client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||||
}
|
}
|
||||||
//encode audio and enqueue it
|
//encode audio and enqueue it
|
||||||
|
|||||||
@@ -146,7 +146,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
|
if ((client.VoipSound.CurrentAmplitude * client.VoipSound.Gain * GameMain.SoundManager.GetCategoryGainMultiplier("voip")) > 0.1f) //TODO: might need to tweak
|
||||||
{
|
{
|
||||||
if (client.Character != null && !client.Character.Removed)
|
if (client.Character != null && !client.Character.Removed && !client.Character.IsDead)
|
||||||
{
|
{
|
||||||
Vector3 clientPos = new Vector3(client.Character.WorldPosition.X, client.Character.WorldPosition.Y, 0.0f);
|
Vector3 clientPos = new Vector3(client.Character.WorldPosition.X, client.Character.WorldPosition.Y, 0.0f);
|
||||||
Vector3 listenerPos = GameMain.SoundManager.ListenerPosition;
|
Vector3 listenerPos = GameMain.SoundManager.ListenerPosition;
|
||||||
|
|||||||
@@ -169,7 +169,10 @@ namespace Barotrauma
|
|||||||
sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language);
|
sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language);
|
||||||
if (ContentPackageManager.EnabledPackages.All != null)
|
if (ContentPackageManager.EnabledPackages.All != null)
|
||||||
{
|
{
|
||||||
sb.AppendLine("Selected content packages: " + (!ContentPackageManager.EnabledPackages.All.Any() ? "None" : string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => c.Name))));
|
sb.AppendLine("Selected content packages: " +
|
||||||
|
(!ContentPackageManager.EnabledPackages.All.Any() ?
|
||||||
|
"None" :
|
||||||
|
string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => $"{c.Name} ({c.Hash?.ShortRepresentation ?? "unknown"})"))));
|
||||||
}
|
}
|
||||||
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
|
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
|
||||||
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
|
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
|
||||||
|
|||||||
+4
-8
@@ -18,7 +18,7 @@ namespace Barotrauma
|
|||||||
public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; }
|
public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; }
|
||||||
|
|
||||||
private GUIButton nextButton;
|
private GUIButton nextButton;
|
||||||
private GUILayoutGroup characterInfoColumns;
|
private GUIListBox characterInfoColumns;
|
||||||
|
|
||||||
public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
|
public SinglePlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<CampaignMode.SaveInfo> saveFiles = null)
|
||||||
: base(newGameContainer, loadGameContainer)
|
: base(newGameContainer, loadGameContainer)
|
||||||
@@ -249,11 +249,7 @@ namespace Barotrauma
|
|||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.04f), secondPageLayout.RectTransform),
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.04f), secondPageLayout.RectTransform),
|
||||||
TextManager.Get("Crew"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopLeft);
|
TextManager.Get("Crew"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.TopLeft);
|
||||||
|
|
||||||
characterInfoColumns = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true)
|
characterInfoColumns = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.86f), secondPageLayout.RectTransform), isHorizontal: true);
|
||||||
{
|
|
||||||
Stretch = true,
|
|
||||||
RelativeSpacing = 0.01f
|
|
||||||
};
|
|
||||||
|
|
||||||
var secondPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
|
var secondPageButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f),
|
||||||
secondPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
|
secondPageLayout.RectTransform), childAnchor: Anchor.BottomLeft, isHorizontal: true)
|
||||||
@@ -306,8 +302,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
for (int i = 0; i < characterInfos.Count; i++)
|
for (int i = 0; i < characterInfos.Count; i++)
|
||||||
{
|
{
|
||||||
var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f / characterInfos.Count, 1.0f),
|
var subLayout = new GUILayoutGroup(new RectTransform(new Vector2(Math.Max(1.0f / characterInfos.Count, 0.33f), 1.0f),
|
||||||
characterInfoColumns.RectTransform));
|
characterInfoColumns.Content.RectTransform));
|
||||||
|
|
||||||
var (characterInfo, job) = characterInfos[i];
|
var (characterInfo, job) = characterInfos[i];
|
||||||
|
|
||||||
|
|||||||
@@ -202,18 +202,21 @@ namespace Barotrauma
|
|||||||
case CampaignMode.InteractionType.PurchaseSub:
|
case CampaignMode.InteractionType.PurchaseSub:
|
||||||
submarineSelection?.Update();
|
submarineSelection?.Update();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CampaignMode.InteractionType.Crew:
|
case CampaignMode.InteractionType.Crew:
|
||||||
CrewManagement?.Update();
|
CrewManagement?.Update();
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CampaignMode.InteractionType.Store:
|
case CampaignMode.InteractionType.Store:
|
||||||
Store?.Update(deltaTime);
|
Store?.Update(deltaTime);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case CampaignMode.InteractionType.MedicalClinic:
|
case CampaignMode.InteractionType.MedicalClinic:
|
||||||
MedicalClinic?.Update(deltaTime);
|
MedicalClinic?.Update(deltaTime);
|
||||||
break;
|
break;
|
||||||
|
case CampaignMode.InteractionType.Map:
|
||||||
|
if (StartButton != null)
|
||||||
|
{
|
||||||
|
StartButton.Enabled = CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageMap) && Character.Controlled is { IsIncapacitated: false };
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -568,7 +571,6 @@ namespace Barotrauma
|
|||||||
StartButton.Visible = false;
|
StartButton.Visible = false;
|
||||||
missionList.Enabled = false;
|
missionList.Enabled = false;
|
||||||
}
|
}
|
||||||
//locationInfoPanel?.UpdateAuto(1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SelectTab(CampaignMode.InteractionType tab, Character npc = null)
|
public void SelectTab(CampaignMode.InteractionType tab, Character npc = null)
|
||||||
|
|||||||
@@ -343,7 +343,7 @@ namespace Barotrauma
|
|||||||
editorContainer.ClearChildren();
|
editorContainer.ClearChildren();
|
||||||
paramsList.Content.ClearChildren();
|
paramsList.Content.ClearChildren();
|
||||||
|
|
||||||
foreach (LevelGenerationParams genParams in LevelGenerationParams.LevelParams)
|
foreach (LevelGenerationParams genParams in LevelGenerationParams.LevelParams.OrderBy(p => p.Name))
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paramsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), paramsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
||||||
genParams.Identifier.Value)
|
genParams.Identifier.Value)
|
||||||
@@ -359,7 +359,7 @@ namespace Barotrauma
|
|||||||
editorContainer.ClearChildren();
|
editorContainer.ClearChildren();
|
||||||
caveParamsList.Content.ClearChildren();
|
caveParamsList.Content.ClearChildren();
|
||||||
|
|
||||||
foreach (CaveGenerationParams genParams in CaveGenerationParams.CaveParams)
|
foreach (CaveGenerationParams genParams in CaveGenerationParams.CaveParams.OrderBy(p => p.Name))
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), caveParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), caveParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
||||||
genParams.Name)
|
genParams.Name)
|
||||||
@@ -375,7 +375,7 @@ namespace Barotrauma
|
|||||||
editorContainer.ClearChildren();
|
editorContainer.ClearChildren();
|
||||||
ruinParamsList.Content.ClearChildren();
|
ruinParamsList.Content.ClearChildren();
|
||||||
|
|
||||||
foreach (RuinGenerationParams genParams in RuinGenerationParams.RuinParams)
|
foreach (RuinGenerationParams genParams in RuinGenerationParams.RuinParams.OrderBy(p => p.Identifier))
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), ruinParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), ruinParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
||||||
genParams.Name)
|
genParams.Name)
|
||||||
@@ -391,7 +391,7 @@ namespace Barotrauma
|
|||||||
editorContainer.ClearChildren();
|
editorContainer.ClearChildren();
|
||||||
outpostParamsList.Content.ClearChildren();
|
outpostParamsList.Content.ClearChildren();
|
||||||
|
|
||||||
foreach (OutpostGenerationParams genParams in OutpostGenerationParams.OutpostParams)
|
foreach (OutpostGenerationParams genParams in OutpostGenerationParams.OutpostParams.OrderBy(p => p.Name))
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), outpostParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), outpostParamsList.Content.RectTransform) { MinSize = new Point(0, 20) },
|
||||||
genParams.Name)
|
genParams.Name)
|
||||||
|
|||||||
@@ -2244,9 +2244,9 @@ namespace Barotrauma
|
|||||||
List<ContextMenuOption> rankOptions = new List<ContextMenuOption>();
|
List<ContextMenuOption> rankOptions = new List<ContextMenuOption>();
|
||||||
foreach (PermissionPreset rank in PermissionPreset.List)
|
foreach (PermissionPreset rank in PermissionPreset.List)
|
||||||
{
|
{
|
||||||
rankOptions.Add(new ContextMenuOption(rank.Name, isEnabled: true, onSelected: () =>
|
rankOptions.Add(new ContextMenuOption(rank.DisplayName, isEnabled: true, onSelected: () =>
|
||||||
{
|
{
|
||||||
LocalizedString label = TextManager.GetWithVariables(rank.Permissions == ClientPermissions.None ? "clearrankprompt" : "giverankprompt", ("[user]", client.Name), ("[rank]", rank.Name));
|
LocalizedString label = TextManager.GetWithVariables(rank.Permissions == ClientPermissions.None ? "clearrankprompt" : "giverankprompt", ("[user]", client.Name), ("[rank]", rank.DisplayName));
|
||||||
GUIMessageBox msgBox = new GUIMessageBox(string.Empty, label, new[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
|
GUIMessageBox msgBox = new GUIMessageBox(string.Empty, label, new[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
|
||||||
|
|
||||||
msgBox.Buttons[0].OnClicked = delegate
|
msgBox.Buttons[0].OnClicked = delegate
|
||||||
@@ -2350,7 +2350,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
foreach (PermissionPreset permissionPreset in PermissionPreset.List)
|
foreach (PermissionPreset permissionPreset in PermissionPreset.List)
|
||||||
{
|
{
|
||||||
rankDropDown.AddItem(permissionPreset.Name, permissionPreset, permissionPreset.Description);
|
rankDropDown.AddItem(permissionPreset.DisplayName, permissionPreset, permissionPreset.Description);
|
||||||
}
|
}
|
||||||
rankDropDown.AddItem(TextManager.Get("CustomRank"), null);
|
rankDropDown.AddItem(TextManager.Get("CustomRank"), null);
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.IO;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
|
using Barotrauma.Steam;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
using Microsoft.Xna.Framework.Input;
|
|
||||||
using Barotrauma.IO;
|
|
||||||
using Barotrauma.Steam;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -3546,10 +3546,45 @@ namespace Barotrauma
|
|||||||
TextManager.Get("LoadingVanillaSubmarineHeader"),
|
TextManager.Get("LoadingVanillaSubmarineHeader"),
|
||||||
TextManager.Get("LoadingVanillaSubmarineDesc"));
|
TextManager.Get("LoadingVanillaSubmarineDesc"));
|
||||||
|
|
||||||
public void LoadSub(SubmarineInfo info)
|
public void LoadSub(SubmarineInfo info, bool checkIdConflicts = true)
|
||||||
{
|
{
|
||||||
Submarine.Unload();
|
Submarine.Unload();
|
||||||
Submarine selectedSub = null;
|
Submarine selectedSub = null;
|
||||||
|
|
||||||
|
if (checkIdConflicts)
|
||||||
|
{
|
||||||
|
Dictionary<int, Identifier> entities = new Dictionary<int, Identifier>();
|
||||||
|
foreach (var subElement in info.SubmarineElement.Elements())
|
||||||
|
{
|
||||||
|
int id = subElement.GetAttributeInt("ID", -1);
|
||||||
|
Identifier identifier = subElement.GetAttributeIdentifier("identifier", string.Empty);
|
||||||
|
if (entities.TryGetValue(id, out Identifier duplicateEntity))
|
||||||
|
{
|
||||||
|
var errorMsg = new GUIMessageBox(
|
||||||
|
TextManager.Get("error"),
|
||||||
|
TextManager.GetWithVariables("subeditor.duplicateiderror",
|
||||||
|
("[entity1]", $"{duplicateEntity} ({id})"),
|
||||||
|
("[entity2]", $"{identifier} ({id})")),
|
||||||
|
new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
|
||||||
|
errorMsg.Buttons[0].OnClicked = (bnt, userdata) =>
|
||||||
|
{
|
||||||
|
subElement.Remove();
|
||||||
|
LoadSub(info, checkIdConflicts: false);
|
||||||
|
errorMsg.Close();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
errorMsg.Buttons[1].OnClicked = (bnt, userdata) =>
|
||||||
|
{
|
||||||
|
LoadSub(info, checkIdConflicts: false);
|
||||||
|
errorMsg.Close();
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
entities.Add(id, identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
selectedSub = new Submarine(info);
|
selectedSub = new Submarine(info);
|
||||||
@@ -5263,7 +5298,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayerInput.KeyHit(Keys.E) && mode == Mode.Default)
|
if (PlayerInput.KeyHit(InputType.Use) && mode == Mode.Default)
|
||||||
{
|
{
|
||||||
if (dummyCharacter != null)
|
if (dummyCharacter != null)
|
||||||
{
|
{
|
||||||
@@ -5354,6 +5389,16 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
var selectables = MapEntity.mapEntityList.Where(entity => entity.SelectableInEditor).ToList();
|
var selectables = MapEntity.mapEntityList.Where(entity => entity.SelectableInEditor).ToList();
|
||||||
|
foreach (var item in Item.ItemList)
|
||||||
|
{
|
||||||
|
//attached wires are not normally selectable (by clicking),
|
||||||
|
//but let's select them manually when selecting all
|
||||||
|
var wire = item.GetComponent<Wire>();
|
||||||
|
if (wire != null && wire.Connections.None(c => c == null) && !selectables.Contains(item))
|
||||||
|
{
|
||||||
|
selectables.Add(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
lock (selectables)
|
lock (selectables)
|
||||||
{
|
{
|
||||||
selectables.ForEach(MapEntity.AddSelection);
|
selectables.ForEach(MapEntity.AddSelection);
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ namespace Barotrauma.Steam
|
|||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
var itemTitle = new GUITextBlock(new RectTransform(Vector2.One, itemFrame.RectTransform),
|
var itemTitle = new GUITextBlock(new RectTransform(Vector2.One, itemFrame.RectTransform),
|
||||||
text: item.Title);
|
text: item.Title ?? "");
|
||||||
var itemDownloadProgress
|
var itemDownloadProgress
|
||||||
= new GUIProgressBar(new RectTransform((0.5f, 0.75f),
|
= new GUIProgressBar(new RectTransform((0.5f, 0.75f),
|
||||||
itemFrame.RectTransform, Anchor.CenterRight), 0.0f)
|
itemFrame.RectTransform, Anchor.CenterRight), 0.0f)
|
||||||
|
|||||||
@@ -105,7 +105,8 @@ namespace Barotrauma.Steam
|
|||||||
|
|
||||||
currentLobby?.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
|
currentLobby?.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
|
||||||
currentLobby?.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
|
currentLobby?.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
|
||||||
currentLobby?.SetData("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.UgcId)));
|
currentLobby?.SetData("contentpackageid", string.Join(",", contentPackages.Select(cp
|
||||||
|
=> cp.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : "")));
|
||||||
currentLobby?.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString());
|
currentLobby?.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString());
|
||||||
currentLobby?.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString());
|
currentLobby?.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString());
|
||||||
currentLobby?.SetData("voicechatenabled", serverSettings.VoiceChatEnabled.ToString());
|
currentLobby?.SetData("voicechatenabled", serverSettings.VoiceChatEnabled.ToString());
|
||||||
|
|||||||
@@ -16,6 +16,9 @@ namespace Barotrauma.Steam
|
|||||||
private static readonly List<Identifier> initializationErrors = new List<Identifier>();
|
private static readonly List<Identifier> initializationErrors = new List<Identifier>();
|
||||||
public static IReadOnlyList<Identifier> InitializationErrors => initializationErrors;
|
public static IReadOnlyList<Identifier> InitializationErrors => initializationErrors;
|
||||||
|
|
||||||
|
private static bool IsInitializedProjectSpecific
|
||||||
|
=> Steamworks.SteamClient.IsValid && Steamworks.SteamClient.IsLoggedOn;
|
||||||
|
|
||||||
private static void InitializeProjectSpecific()
|
private static void InitializeProjectSpecific()
|
||||||
{
|
{
|
||||||
if (IsInitialized) { return; }
|
if (IsInitialized) { return; }
|
||||||
@@ -23,7 +26,6 @@ namespace Barotrauma.Steam
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Steamworks.SteamClient.Init(AppID, false);
|
Steamworks.SteamClient.Init(AppID, false);
|
||||||
IsInitialized = Steamworks.SteamClient.IsLoggedOn && Steamworks.SteamClient.IsValid;
|
|
||||||
|
|
||||||
if (IsInitialized)
|
if (IsInitialized)
|
||||||
{
|
{
|
||||||
@@ -43,13 +45,11 @@ namespace Barotrauma.Steam
|
|||||||
}
|
}
|
||||||
catch (DllNotFoundException)
|
catch (DllNotFoundException)
|
||||||
{
|
{
|
||||||
IsInitialized = false;
|
|
||||||
initializationErrors.Add("SteamDllNotFound".ToIdentifier());
|
initializationErrors.Add("SteamDllNotFound".ToIdentifier());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("SteamManager initialization threw an exception", e);
|
DebugConsole.ThrowError("SteamManager initialization threw an exception", e);
|
||||||
IsInitialized = false;
|
|
||||||
initializationErrors.Add("SteamClientInitFailed".ToIdentifier());
|
initializationErrors.Add("SteamClientInitFailed".ToIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -111,7 +111,7 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
|
|
||||||
string thumbnailUrl = item.PreviewImageUrl;
|
string? thumbnailUrl = item.PreviewImageUrl;
|
||||||
if (thumbnailUrl.IsNullOrWhiteSpace()) { return null; }
|
if (thumbnailUrl.IsNullOrWhiteSpace()) { return null; }
|
||||||
var client = new RestClient(thumbnailUrl);
|
var client = new RestClient(thumbnailUrl);
|
||||||
var request = new RestRequest(".", Method.GET);
|
var request = new RestRequest(".", Method.GET);
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ namespace Barotrauma.Steam
|
|||||||
private string ExtractTitle(ItemOrPackage itemOrPackage)
|
private string ExtractTitle(ItemOrPackage itemOrPackage)
|
||||||
=> itemOrPackage.TryGet(out ContentPackage package)
|
=> itemOrPackage.TryGet(out ContentPackage package)
|
||||||
? package.Name
|
? package.Name
|
||||||
: ((Steamworks.Ugc.Item)itemOrPackage).Title;
|
: (((Steamworks.Ugc.Item)itemOrPackage).Title ?? "");
|
||||||
|
|
||||||
private void CreateWorkshopItemDetailContainer(
|
private void CreateWorkshopItemDetailContainer(
|
||||||
GUIFrame parent,
|
GUIFrame parent,
|
||||||
@@ -340,6 +340,8 @@ namespace Barotrauma.Steam
|
|||||||
|
|
||||||
subscribeButton.OnClicked = (button, o) =>
|
subscribeButton.OnClicked = (button, o) =>
|
||||||
{
|
{
|
||||||
|
if (!SteamManager.IsInitialized) { return false; }
|
||||||
|
|
||||||
if (!workshopItem.IsSubscribed)
|
if (!workshopItem.IsSubscribed)
|
||||||
{
|
{
|
||||||
workshopItem.Subscribe();
|
workshopItem.Subscribe();
|
||||||
@@ -360,6 +362,8 @@ namespace Barotrauma.Steam
|
|||||||
new RectTransform(Vector2.Zero, subscribeButton.RectTransform),
|
new RectTransform(Vector2.Zero, subscribeButton.RectTransform),
|
||||||
onUpdate: (deltaTime, component) =>
|
onUpdate: (deltaTime, component) =>
|
||||||
{
|
{
|
||||||
|
if (!SteamManager.IsInitialized) { return; }
|
||||||
|
|
||||||
if (subscribeButtonSprite.Style is { Identifier: { } styleId })
|
if (subscribeButtonSprite.Style is { Identifier: { } styleId })
|
||||||
{
|
{
|
||||||
if (workshopItem.IsSubscribed && styleId != minusButton)
|
if (workshopItem.IsSubscribed && styleId != minusButton)
|
||||||
@@ -380,6 +384,8 @@ namespace Barotrauma.Steam
|
|||||||
new RectTransform((1.22f, 1.22f), subscribeButtonSprite.RectTransform, Anchor.Center),
|
new RectTransform((1.22f, 1.22f), subscribeButtonSprite.RectTransform, Anchor.Center),
|
||||||
onDraw: (spriteBatch, component) =>
|
onDraw: (spriteBatch, component) =>
|
||||||
{
|
{
|
||||||
|
if (!SteamManager.IsInitialized) { return; }
|
||||||
|
|
||||||
bool visible = workshopItem.IsSubscribed
|
bool visible = workshopItem.IsSubscribed
|
||||||
&& (workshopItem.IsDownloading
|
&& (workshopItem.IsDownloading
|
||||||
|| workshopItem.IsDownloadPending
|
|| workshopItem.IsDownloadPending
|
||||||
@@ -407,6 +413,8 @@ namespace Barotrauma.Steam
|
|||||||
},
|
},
|
||||||
onUpdate: (deltaTime, component) =>
|
onUpdate: (deltaTime, component) =>
|
||||||
{
|
{
|
||||||
|
if (!SteamManager.IsInitialized) { return; }
|
||||||
|
|
||||||
displayedDownloadAmount = Math.Min(
|
displayedDownloadAmount = Math.Min(
|
||||||
workshopItem.DownloadAmount,
|
workshopItem.DownloadAmount,
|
||||||
MathHelper.Lerp(displayedDownloadAmount, workshopItem.DownloadAmount, 0.05f));
|
MathHelper.Lerp(displayedDownloadAmount, workshopItem.DownloadAmount, 0.05f));
|
||||||
@@ -450,7 +458,7 @@ namespace Barotrauma.Steam
|
|||||||
|
|
||||||
var title = new GUITextBlock(
|
var title = new GUITextBlock(
|
||||||
new RectTransform(Vector2.One, itemLayout.RectTransform),
|
new RectTransform(Vector2.One, itemLayout.RectTransform),
|
||||||
workshopItem.Title, font: GUIStyle.Font)
|
workshopItem.Title ?? "", font: GUIStyle.Font)
|
||||||
{
|
{
|
||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
@@ -570,7 +578,7 @@ namespace Barotrauma.Steam
|
|||||||
var titleAndAuthorLayout = new GUILayoutGroup(new RectTransform(Vector2.One, headerLayout.RectTransform));
|
var titleAndAuthorLayout = new GUILayoutGroup(new RectTransform(Vector2.One, headerLayout.RectTransform));
|
||||||
|
|
||||||
var selectedTitle =
|
var selectedTitle =
|
||||||
new GUITextBlock(new RectTransform((1.0f, 0.5f), titleAndAuthorLayout.RectTransform), workshopItem.Title,
|
new GUITextBlock(new RectTransform((1.0f, 0.5f), titleAndAuthorLayout.RectTransform), workshopItem.Title ?? "",
|
||||||
font: GUIStyle.LargeFont);
|
font: GUIStyle.LargeFont);
|
||||||
|
|
||||||
var author = workshopItem.Owner;
|
var author = workshopItem.Owner;
|
||||||
@@ -682,9 +690,9 @@ namespace Barotrauma.Steam
|
|||||||
|
|
||||||
TaskPool.Add($"Request username for {author.Id}", author.RequestInfoAsync(), (t) =>
|
TaskPool.Add($"Request username for {author.Id}", author.RequestInfoAsync(), (t) =>
|
||||||
{
|
{
|
||||||
authorButton.Text = author.Name;
|
authorButton.Text = author.Name ?? "";
|
||||||
authorButton.RectTransform.NonScaledSize =
|
authorButton.RectTransform.NonScaledSize =
|
||||||
((int)(authorButton.Font.MeasureString(author.Name).X + authorPadding.X + authorPadding.Z),
|
((int)(authorButton.Font.MeasureString(author.Name ?? "").X + authorPadding.X + authorPadding.Z),
|
||||||
authorButton.RectTransform.NonScaledSize.Y);
|
authorButton.RectTransform.NonScaledSize.Y);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -769,7 +777,7 @@ namespace Barotrauma.Steam
|
|||||||
|
|
||||||
var tagsLabel = new GUITextBlock(new RectTransform((1.0f, 0.12f), statsVertical0.RectTransform),
|
var tagsLabel = new GUITextBlock(new RectTransform((1.0f, 0.12f), statsVertical0.RectTransform),
|
||||||
TextManager.Get("WorkshopItemTags"), font: GUIStyle.SubHeadingFont);
|
TextManager.Get("WorkshopItemTags"), font: GUIStyle.SubHeadingFont);
|
||||||
CreateTagsList(workshopItem.Tags.ToIdentifiers(), new RectTransform((0.97f, 0.3f), statsVertical0.RectTransform), canBeFocused: false);
|
CreateTagsList((workshopItem.Tags ?? Array.Empty<string>()).ToIdentifiers(), new RectTransform((0.97f, 0.3f), statsVertical0.RectTransform), canBeFocused: false);
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
var descriptionListBox = new GUIListBox(new RectTransform((1.0f, 0.38f), verticalLayout.RectTransform));
|
var descriptionListBox = new GUIListBox(new RectTransform((1.0f, 0.38f), verticalLayout.RectTransform));
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal class LeakyBucket
|
||||||
|
{
|
||||||
|
private readonly Queue<Action> queue;
|
||||||
|
private readonly int capacity;
|
||||||
|
private readonly float cooldownInSeconds;
|
||||||
|
private float timer;
|
||||||
|
|
||||||
|
public LeakyBucket(float cooldownInSeconds, int capacity)
|
||||||
|
{
|
||||||
|
this.cooldownInSeconds = cooldownInSeconds;
|
||||||
|
this.capacity = capacity;
|
||||||
|
queue = new Queue<Action>(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float deltaTime)
|
||||||
|
{
|
||||||
|
if (timer > 0f)
|
||||||
|
{
|
||||||
|
timer -= deltaTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queue.Count is 0) { return; }
|
||||||
|
|
||||||
|
TryDequeue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void TryDequeue()
|
||||||
|
{
|
||||||
|
timer = cooldownInSeconds;
|
||||||
|
if (queue.TryDequeue(out var action))
|
||||||
|
{
|
||||||
|
action.Invoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool TryEnqueue(Action item)
|
||||||
|
{
|
||||||
|
if (queue.Count >= capacity) { return false; }
|
||||||
|
queue.Enqueue(item);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -142,11 +142,17 @@ namespace Barotrauma
|
|||||||
GameMain.Instance.GraphicsDevice.SetRenderTarget(rt);
|
GameMain.Instance.GraphicsDevice.SetRenderTarget(rt);
|
||||||
GameMain.Instance.GraphicsDevice.Clear(Color.Transparent);
|
GameMain.Instance.GraphicsDevice.Clear(Color.Transparent);
|
||||||
|
|
||||||
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, null, null, null, transform);
|
DrawBatch(() => Submarine.DrawBack(spriteBatch, true, e => e is Structure s && (e.SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite != null)));
|
||||||
Submarine.Draw(spriteBatch);
|
DrawBatch(() => Submarine.DrawBack(spriteBatch, true, e => (e is not Structure || e.SpriteDepth < 0.9f)));
|
||||||
Submarine.DrawFront(spriteBatch);
|
DrawBatch(() => Submarine.DrawDamageable(spriteBatch, null, editing: true));
|
||||||
Submarine.DrawDamageable(spriteBatch, null);
|
DrawBatch(() => Submarine.DrawFront(spriteBatch, editing: true));
|
||||||
spriteBatch.End();
|
|
||||||
|
void DrawBatch(Action drawAction)
|
||||||
|
{
|
||||||
|
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, null, null, null, transform);
|
||||||
|
drawAction.Invoke();
|
||||||
|
spriteBatch.End();
|
||||||
|
}
|
||||||
|
|
||||||
GameMain.Instance.GraphicsDevice.SetRenderTarget(null);
|
GameMain.Instance.GraphicsDevice.SetRenderTarget(null);
|
||||||
GameMain.Instance.GraphicsDevice.Viewport = prevViewport;
|
GameMain.Instance.GraphicsDevice.Viewport = prevViewport;
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.0.9.0</Version>
|
<Version>1.0.13.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
<Configurations>Debug;Release;Unstable</Configurations>
|
<Configurations>Debug;Release;Unstable</Configurations>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.0.9.0</Version>
|
<Version>1.0.13.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
<Configurations>Debug;Release;Unstable</Configurations>
|
<Configurations>Debug;Release;Unstable</Configurations>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma</Product>
|
<Product>Barotrauma</Product>
|
||||||
<Version>1.0.9.0</Version>
|
<Version>1.0.13.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>Barotrauma</AssemblyName>
|
<AssemblyName>Barotrauma</AssemblyName>
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
<Configurations>Debug;Release;Unstable</Configurations>
|
<Configurations>Debug;Release;Unstable</Configurations>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<ApplicationManifest>app.manifest</ApplicationManifest>
|
<ApplicationManifest>app.manifest</ApplicationManifest>
|
||||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.0.9.0</Version>
|
<Version>1.0.13.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
<Configurations>Debug;Release;Unstable</Configurations>
|
<Configurations>Debug;Release;Unstable</Configurations>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -6,14 +6,14 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.0.9.0</Version>
|
<Version>1.0.13.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
<Configurations>Debug;Release;Unstable</Configurations>
|
<Configurations>Debug;Release;Unstable</Configurations>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -13,6 +13,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
static partial class DebugConsole
|
static partial class DebugConsole
|
||||||
{
|
{
|
||||||
|
private static readonly RateLimiter rateLimiter = new(
|
||||||
|
maxRequests: 50,
|
||||||
|
expiryInSeconds: 5,
|
||||||
|
punishmentRules: new[]
|
||||||
|
{
|
||||||
|
(RateLimitAction.OnLimitReached, RateLimitPunishment.Announce),
|
||||||
|
(RateLimitAction.OnLimitDoubled, RateLimitPunishment.Kick)
|
||||||
|
});
|
||||||
|
|
||||||
public partial class Command
|
public partial class Command
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -608,12 +617,12 @@ namespace Barotrauma
|
|||||||
NewMessage("Valid ranks are:", Color.White);
|
NewMessage("Valid ranks are:", Color.White);
|
||||||
foreach (PermissionPreset permissionPreset in PermissionPreset.List)
|
foreach (PermissionPreset permissionPreset in PermissionPreset.List)
|
||||||
{
|
{
|
||||||
NewMessage(" - " + permissionPreset.Name, Color.White);
|
NewMessage(" - " + permissionPreset.DisplayName, Color.White);
|
||||||
}
|
}
|
||||||
|
|
||||||
ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) =>
|
ShowQuestionPrompt("Rank to grant to \"" + client.Name + "\"?", (rank) =>
|
||||||
{
|
{
|
||||||
PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.Equals(rank, StringComparison.OrdinalIgnoreCase));
|
PermissionPreset preset = PermissionPreset.List.Find(p => p.DisplayName.Equals(rank, StringComparison.OrdinalIgnoreCase));
|
||||||
if (preset == null)
|
if (preset == null)
|
||||||
{
|
{
|
||||||
ThrowError("Rank \"" + rank + "\" not found.");
|
ThrowError("Rank \"" + rank + "\" not found.");
|
||||||
@@ -622,7 +631,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
client.SetPermissions(preset.Permissions, preset.PermittedCommands);
|
client.SetPermissions(preset.Permissions, preset.PermittedCommands);
|
||||||
GameMain.Server.UpdateClientPermissions(client);
|
GameMain.Server.UpdateClientPermissions(client);
|
||||||
NewMessage("Assigned the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White);
|
NewMessage("Assigned the rank \"" + preset.DisplayName + "\" to " + client.Name + ".", Color.White);
|
||||||
}, args, 1);
|
}, args, 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1992,6 +2001,7 @@ namespace Barotrauma
|
|||||||
"freecam",
|
"freecam",
|
||||||
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
(Client client, Vector2 cursorWorldPos, string[] args) =>
|
||||||
{
|
{
|
||||||
|
client.UsingFreeCam = true;
|
||||||
GameMain.Server.SetClientCharacter(client, null);
|
GameMain.Server.SetClientCharacter(client, null);
|
||||||
client.SpectateOnly = true;
|
client.SpectateOnly = true;
|
||||||
}
|
}
|
||||||
@@ -2105,7 +2115,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
string rank = string.Join("", args.Skip(1));
|
string rank = string.Join("", args.Skip(1));
|
||||||
PermissionPreset preset = PermissionPreset.List.Find(p => p.Name.Equals(rank, StringComparison.OrdinalIgnoreCase));
|
PermissionPreset preset = PermissionPreset.List.Find(p => p.DisplayName.Equals(rank, StringComparison.OrdinalIgnoreCase));
|
||||||
if (preset == null)
|
if (preset == null)
|
||||||
{
|
{
|
||||||
GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient, Color.Red);
|
GameMain.Server.SendConsoleMessage("Rank \"" + rank + "\" not found.", senderClient, Color.Red);
|
||||||
@@ -2114,8 +2124,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
client.SetPermissions(preset.Permissions, preset.PermittedCommands);
|
client.SetPermissions(preset.Permissions, preset.PermittedCommands);
|
||||||
GameMain.Server.UpdateClientPermissions(client);
|
GameMain.Server.UpdateClientPermissions(client);
|
||||||
GameMain.Server.SendConsoleMessage($"Assigned the rank \"{preset.Name}\" to {client.Name}.", senderClient);
|
GameMain.Server.SendConsoleMessage($"Assigned the rank \"{preset.DisplayName}\" to {client.Name}.", senderClient);
|
||||||
NewMessage(senderClient.Name + " granted the rank \"" + preset.Name + "\" to " + client.Name + ".", Color.White);
|
NewMessage(senderClient.Name + " granted the rank \"" + preset.DisplayName + "\" to " + client.Name + ".", Color.White);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -2509,26 +2519,37 @@ namespace Barotrauma
|
|||||||
foreach (Item item in Item.ItemList)
|
foreach (Item item in Item.ItemList)
|
||||||
{
|
{
|
||||||
item.TryCreateServerEventSpam();
|
item.TryCreateServerEventSpam();
|
||||||
item.CreateStatusEvent();
|
item.CreateStatusEvent(loadingRound: false);
|
||||||
}
|
}
|
||||||
foreach (Structure wall in Structure.WallList)
|
foreach (Structure wall in Structure.WallList)
|
||||||
{
|
{
|
||||||
GameMain.Server.CreateEntityEvent(wall);
|
GameMain.Server.CreateEntityEvent(wall);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
commands.Add(new Command("stallfiletransfers", "stallfiletransfers [seconds]: A debug command that stalls each file transfer packet by the specified duration.", (string[] args) =>
|
commands.Add(new Command("stallfiletransfers", "stallfiletransfers [seconds]: A debug command that makes all file transfers take at least the specified duration.", (string[] args) =>
|
||||||
{
|
{
|
||||||
float seconds = 0.0f;
|
float seconds = 0.0f;
|
||||||
if (args.Length > 0)
|
if (args.Length > 0)
|
||||||
{
|
{
|
||||||
float.TryParse(args[0], out seconds);
|
float.TryParse(args[0], out seconds);
|
||||||
}
|
}
|
||||||
GameMain.Server.FileSender.StallPacketsTime = seconds;
|
GameMain.Server.FileSender.ForceMinimumFileTransferDuration = seconds;
|
||||||
NewMessage("Set file transfer stall time to " + seconds);
|
NewMessage("Set file transfer stall time to " + seconds);
|
||||||
}));
|
}));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ServerRead(IReadMessage inc, Client sender)
|
||||||
|
{
|
||||||
|
string consoleCommand = inc.ReadString();
|
||||||
|
float cursorX = inc.ReadSingle();
|
||||||
|
float cursorY = inc.ReadSingle();
|
||||||
|
|
||||||
|
if (rateLimiter.IsLimitReached(sender)) { return; }
|
||||||
|
|
||||||
|
ExecuteClientCommand(sender, new Vector2(cursorX, cursorY), consoleCommand);
|
||||||
|
}
|
||||||
|
|
||||||
public static void ExecuteClientCommand(Client client, Vector2 cursorWorldPos, string command)
|
public static void ExecuteClientCommand(Client client, Vector2 cursorWorldPos, string command)
|
||||||
{
|
{
|
||||||
if (GameMain.Server == null) return;
|
if (GameMain.Server == null) return;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
@@ -11,25 +12,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
internal partial class MedicalClinic
|
internal partial class MedicalClinic
|
||||||
{
|
{
|
||||||
private enum RateLimitResult
|
// allow 20 requests per 5 seconds, announce to chat if the limit is reached
|
||||||
{
|
private readonly RateLimiter rateLimiter = new(
|
||||||
OK,
|
maxRequests: RateLimitMaxRequests,
|
||||||
LimitReached
|
expiryInSeconds: RateLimitExpiry,
|
||||||
}
|
punishmentRules: (RateLimitAction.OnLimitReached, RateLimitPunishment.Announce));
|
||||||
|
|
||||||
private struct RateLimitInfo
|
|
||||||
{
|
|
||||||
public int Requests;
|
|
||||||
public const int MaxRequests = 10;
|
|
||||||
public DateTimeOffset Expiry;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly record struct AfflictionSubscriber(Client Subscriber, CharacterInfo Target, DateTimeOffset Expiry);
|
private readonly record struct AfflictionSubscriber(Client Subscriber, CharacterInfo Target, DateTimeOffset Expiry);
|
||||||
|
|
||||||
private readonly List<AfflictionSubscriber> afflictionSubscribers = new();
|
private readonly List<AfflictionSubscriber> afflictionSubscribers = new();
|
||||||
|
|
||||||
private readonly Dictionary<Client, RateLimitInfo> rateLimits = new();
|
|
||||||
|
|
||||||
public void ServerRead(IReadMessage inc, Client sender)
|
public void ServerRead(IReadMessage inc, Client sender)
|
||||||
{
|
{
|
||||||
NetworkHeader header = (NetworkHeader)inc.ReadByte();
|
NetworkHeader header = (NetworkHeader)inc.ReadByte();
|
||||||
@@ -65,7 +57,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void ProcessNewAddition(IReadMessage inc, Client client)
|
private void ProcessNewAddition(IReadMessage inc, Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
|
|
||||||
NetCrewMember newCrewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
NetCrewMember newCrewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
||||||
InsertPendingCrewMember(newCrewMember);
|
InsertPendingCrewMember(newCrewMember);
|
||||||
@@ -74,7 +66,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void ProcessAddEverything(Client client)
|
private void ProcessAddEverything(Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
AddEverythingToPending();
|
AddEverythingToPending();
|
||||||
ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
|
ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
|
||||||
}
|
}
|
||||||
@@ -92,7 +84,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void ProcessNewRemoval(IReadMessage inc, Client client)
|
private void ProcessNewRemoval(IReadMessage inc, Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
|
|
||||||
NetRemovedAffliction removed = INetSerializableStruct.Read<NetRemovedAffliction>(inc);
|
NetRemovedAffliction removed = INetSerializableStruct.Read<NetRemovedAffliction>(inc);
|
||||||
RemovePendingAffliction(removed.CrewMember, removed.Affliction);
|
RemovePendingAffliction(removed.CrewMember, removed.Affliction);
|
||||||
@@ -101,14 +93,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void ProcessRequestedPending(Client client)
|
private void ProcessRequestedPending(Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
|
|
||||||
ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
|
ServerSend(PendingHeals.ToNetCollection(), NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ProcessHealing(Client client)
|
private void ProcessHealing(Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
|
|
||||||
HealRequestResult result = HealAllPending(client: client);
|
HealRequestResult result = HealAllPending(client: client);
|
||||||
ServerSend(new NetHealRequest { Result = result }, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable, reponseClient: client);
|
ServerSend(new NetHealRequest { Result = result }, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable, reponseClient: client);
|
||||||
@@ -116,7 +108,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void ProcessClearing(Client client)
|
private void ProcessClearing(Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
|
|
||||||
if (!PendingHeals.Any()) { return; }
|
if (!PendingHeals.Any()) { return; }
|
||||||
|
|
||||||
@@ -126,7 +118,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void ProcessRequestedAfflictions(IReadMessage inc, Client client)
|
private void ProcessRequestedAfflictions(IReadMessage inc, Client client)
|
||||||
{
|
{
|
||||||
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
|
if (rateLimiter.IsLimitReached(client)) { return; }
|
||||||
|
|
||||||
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
||||||
|
|
||||||
@@ -135,6 +127,17 @@ namespace Barotrauma
|
|||||||
ImmutableArray<NetAffliction> pendingAfflictions = ImmutableArray<NetAffliction>.Empty;
|
ImmutableArray<NetAffliction> pendingAfflictions = ImmutableArray<NetAffliction>.Empty;
|
||||||
int infoId = 0;
|
int infoId = 0;
|
||||||
|
|
||||||
|
if (foundInfo is null)
|
||||||
|
{
|
||||||
|
StringBuilder sb = new();
|
||||||
|
foreach (CharacterInfo character in GetCrewCharacters())
|
||||||
|
{
|
||||||
|
sb.AppendLine($" - {character.DisplayName} ({character.ID})");
|
||||||
|
}
|
||||||
|
|
||||||
|
DebugConsole.ThrowError($"Could not find the requested crew member with ID {crewMember.CharacterInfoID}.\n{sb}");
|
||||||
|
}
|
||||||
|
|
||||||
if (foundInfo is { Character.CharacterHealth: { } health })
|
if (foundInfo is { Character.CharacterHealth: { } health })
|
||||||
{
|
{
|
||||||
pendingAfflictions = GetAllAfflictions(health);
|
pendingAfflictions = GetAllAfflictions(health);
|
||||||
@@ -158,32 +161,6 @@ namespace Barotrauma
|
|||||||
ServerSend(writeCrewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable, client);
|
ServerSend(writeCrewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable, client);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RateLimitResult CheckRateLimit(Client client)
|
|
||||||
{
|
|
||||||
if (rateLimits.TryGetValue(client, out RateLimitInfo rateLimitInfo))
|
|
||||||
{
|
|
||||||
if (rateLimitInfo.Expiry < DateTimeOffset.Now)
|
|
||||||
{
|
|
||||||
rateLimitInfo.Expiry = DateTimeOffset.Now.AddSeconds(5);
|
|
||||||
rateLimitInfo.Requests = 1;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (rateLimitInfo.Requests > RateLimitInfo.MaxRequests) { return RateLimitResult.LimitReached; }
|
|
||||||
|
|
||||||
rateLimitInfo.Requests++;
|
|
||||||
}
|
|
||||||
|
|
||||||
rateLimits[client] = rateLimitInfo;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
rateLimits.Add(client, new RateLimitInfo { Requests = 1, Expiry = DateTimeOffset.Now.AddSeconds(5) });
|
|
||||||
}
|
|
||||||
|
|
||||||
return RateLimitResult.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private IWriteMessage StartSending()
|
private IWriteMessage StartSending()
|
||||||
{
|
{
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Barotrauma.Networking;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -135,6 +136,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnsureItemsInBothHands(c.Character);
|
||||||
|
|
||||||
CreateNetworkEvent();
|
CreateNetworkEvent();
|
||||||
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
foreach (Inventory prevInventory in prevItemInventories.Distinct())
|
||||||
{
|
{
|
||||||
@@ -174,6 +177,33 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void EnsureItemsInBothHands(Character character)
|
||||||
|
{
|
||||||
|
if (this is not CharacterInventory charInv) { return; }
|
||||||
|
|
||||||
|
int leftHandSlot = charInv.FindLimbSlot(InvSlotType.LeftHand),
|
||||||
|
rightHandSlot = charInv.FindLimbSlot(InvSlotType.RightHand);
|
||||||
|
|
||||||
|
if (IsSlotIndexOutOfBound(leftHandSlot) || IsSlotIndexOutOfBound(rightHandSlot)) { return; }
|
||||||
|
|
||||||
|
TryPutInOppositeHandSlot(rightHandSlot, leftHandSlot);
|
||||||
|
TryPutInOppositeHandSlot(leftHandSlot, rightHandSlot);
|
||||||
|
|
||||||
|
void TryPutInOppositeHandSlot(int originalSlot, int otherHandSlot)
|
||||||
|
{
|
||||||
|
const InvSlotType bothHandSlot = InvSlotType.LeftHand | InvSlotType.RightHand;
|
||||||
|
|
||||||
|
foreach (Item it in slots[originalSlot].Items)
|
||||||
|
{
|
||||||
|
if (it.AllowedSlots.None(static s => s.HasFlag(bothHandSlot)) || slots[otherHandSlot].Contains(it)) { continue; }
|
||||||
|
|
||||||
|
TryPutItem(it, otherHandSlot, true, true, character, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsSlotIndexOutOfBound(int index) => index < 0 || index >= slots.Length;
|
||||||
|
}
|
||||||
|
|
||||||
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
public void ServerEventWrite(IWriteMessage msg, Client c, NetEntityEvent.IData extraData = null)
|
||||||
{
|
{
|
||||||
SharedWrite(msg, extraData);
|
SharedWrite(msg, extraData);
|
||||||
|
|||||||
@@ -65,7 +65,8 @@ namespace Barotrauma
|
|||||||
msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
|
msg.WriteUInt16(GameMain.Server.EntityEventManager.Events.Last()?.ID ?? (ushort)0);
|
||||||
itemContainer.Inventory.ServerEventWrite(msg, c);
|
itemContainer.Inventory.ServerEventWrite(msg, c);
|
||||||
break;
|
break;
|
||||||
case ItemStatusEventData _:
|
case ItemStatusEventData statusEvent:
|
||||||
|
msg.WriteBoolean(statusEvent.LoadingRound);
|
||||||
msg.WriteSingle(condition);
|
msg.WriteSingle(condition);
|
||||||
break;
|
break;
|
||||||
case AssignCampaignInteractionEventData _:
|
case AssignCampaignInteractionEventData _:
|
||||||
|
|||||||
@@ -102,9 +102,9 @@ namespace Barotrauma.Networking
|
|||||||
similarity *= 0.25f;
|
similarity *= 0.25f;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isOwner = GameMain.Server.OwnerConnection != null && c.Connection == GameMain.Server.OwnerConnection;
|
bool isSpamExempt = RateLimiter.IsExempt(c);
|
||||||
|
|
||||||
if (similarity + c.ChatSpamSpeed > 5.0f && !isOwner)
|
if (similarity + c.ChatSpamSpeed > 5.0f && !isSpamExempt)
|
||||||
{
|
{
|
||||||
GameMain.Server.KarmaManager.OnSpamFilterTriggered(c);
|
GameMain.Server.KarmaManager.OnSpamFilterTriggered(c);
|
||||||
|
|
||||||
@@ -125,7 +125,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
c.ChatSpamSpeed += similarity + 0.5f;
|
c.ChatSpamSpeed += similarity + 0.5f;
|
||||||
|
|
||||||
if (c.ChatSpamTimer > 0.0f && !isOwner)
|
if (c.ChatSpamTimer > 0.0f && !isSpamExempt)
|
||||||
{
|
{
|
||||||
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
|
ChatMessage denyMsg = Create("", TextManager.Get("SpamFilterBlocked").Value, ChatMessageType.Server, null);
|
||||||
c.ChatSpamTimer = 10.0f;
|
c.ChatSpamTimer = 10.0f;
|
||||||
|
|||||||
@@ -109,8 +109,6 @@ namespace Barotrauma.Networking
|
|||||||
const int MaxTransferCount = 16;
|
const int MaxTransferCount = 16;
|
||||||
const int MaxTransferCountPerRecipient = 5;
|
const int MaxTransferCountPerRecipient = 5;
|
||||||
|
|
||||||
public static TimeSpan MaxTransferDuration = new TimeSpan(0, 2, 0);
|
|
||||||
|
|
||||||
public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
|
public delegate void FileTransferDelegate(FileTransferOut fileStreamReceiver);
|
||||||
public FileTransferDelegate OnStarted;
|
public FileTransferDelegate OnStarted;
|
||||||
public FileTransferDelegate OnEnded;
|
public FileTransferDelegate OnEnded;
|
||||||
@@ -121,8 +119,9 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private readonly ServerPeer peer;
|
private readonly ServerPeer peer;
|
||||||
|
|
||||||
|
public static DateTime StartTime;
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public float StallPacketsTime { get; set; }
|
public float ForceMinimumFileTransferDuration { get; set; }
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public IReadOnlyList<FileTransferOut> ActiveTransfers => activeTransfers;
|
public IReadOnlyList<FileTransferOut> ActiveTransfers => activeTransfers;
|
||||||
@@ -172,6 +171,8 @@ namespace Barotrauma.Networking
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
StartTime = DateTime.Now;
|
||||||
|
|
||||||
OnStarted(transfer);
|
OnStarted(transfer);
|
||||||
GameMain.Server.LastClientListUpdateID++;
|
GameMain.Server.LastClientListUpdateID++;
|
||||||
|
|
||||||
@@ -259,7 +260,18 @@ namespace Barotrauma.Networking
|
|||||||
for (int i = 0; i < Math.Floor(transfer.PacketsPerUpdate); i++)
|
for (int i = 0; i < Math.Floor(transfer.PacketsPerUpdate); i++)
|
||||||
{
|
{
|
||||||
long remaining = transfer.Data.Length - transfer.SentOffset;
|
long remaining = transfer.Data.Length - transfer.SentOffset;
|
||||||
int sendByteCount = (remaining > chunkLen ? chunkLen : (int)remaining);
|
#if DEBUG
|
||||||
|
bool stalling = false;
|
||||||
|
float elapsedTime = (float)(DateTime.Now - StartTime).TotalSeconds;
|
||||||
|
if (elapsedTime < ForceMinimumFileTransferDuration)
|
||||||
|
{
|
||||||
|
int remainingChunks = (int)Math.Max(remaining / chunkLen, 1);
|
||||||
|
transfer.WaitTimer =
|
||||||
|
Math.Max(transfer.WaitTimer, (ForceMinimumFileTransferDuration - elapsedTime) / remainingChunks);
|
||||||
|
if (remainingChunks <= 1) { break; }
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
int sendByteCount = remaining > chunkLen ? chunkLen : (int)remaining;
|
||||||
|
|
||||||
message = new WriteOnlyMessage();
|
message = new WriteOnlyMessage();
|
||||||
message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
|
message.WriteByte((byte)ServerPacketHeader.FILE_TRANSFER);
|
||||||
@@ -293,11 +305,10 @@ namespace Barotrauma.Networking
|
|||||||
//this gets reset when packet loss or disorder sets in
|
//this gets reset when packet loss or disorder sets in
|
||||||
transfer.PacketsPerUpdate = Math.Min(FileTransferOut.MaxPacketsPerUpdate,
|
transfer.PacketsPerUpdate = Math.Min(FileTransferOut.MaxPacketsPerUpdate,
|
||||||
transfer.PacketsPerUpdate + 0.05f);
|
transfer.PacketsPerUpdate + 0.05f);
|
||||||
}
|
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
transfer.WaitTimer = Math.Max(transfer.WaitTimer, StallPacketsTime);
|
if (stalling) { break; }
|
||||||
#endif
|
#endif
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
@@ -330,7 +341,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
byte transferId = inc.ReadByte();
|
byte transferId = inc.ReadByte();
|
||||||
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
|
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId);
|
||||||
if (matchingTransfer != null) CancelTransfer(matchingTransfer);
|
if (matchingTransfer != null) { CancelTransfer(matchingTransfer); }
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (messageType == FileTransferMessageType.Data)
|
else if (messageType == FileTransferMessageType.Data)
|
||||||
@@ -359,6 +370,7 @@ namespace Barotrauma.Networking
|
|||||||
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
|
if (matchingTransfer.KnownReceivedOffset >= matchingTransfer.Data.Length)
|
||||||
{
|
{
|
||||||
matchingTransfer.Status = FileTransferStatus.Finished;
|
matchingTransfer.Status = FileTransferStatus.Finished;
|
||||||
|
DebugConsole.Log($"Finished sending file \"{matchingTransfer.FilePath}\" to \"{client.Name}\". Took {DateTime.Now - StartTime}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var defaultPerms = PermissionPreset.List.Find(p => p.Name == "None");
|
var defaultPerms = PermissionPreset.List.Find(p => p.Identifier == "None");
|
||||||
if (defaultPerms != null)
|
if (defaultPerms != null)
|
||||||
{
|
{
|
||||||
newClient.SetPermissions(defaultPerms.Permissions, defaultPerms.PermittedCommands);
|
newClient.SetPermissions(defaultPerms.Permissions, defaultPerms.PermittedCommands);
|
||||||
@@ -332,9 +332,8 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public void Update(float deltaTime)
|
public void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
#if CLIENT
|
dosProtection.Update(deltaTime);
|
||||||
if (ShowNetStats) { netStats.Update(deltaTime); }
|
|
||||||
#endif
|
|
||||||
if (!started) { return; }
|
if (!started) { return; }
|
||||||
|
|
||||||
if (ChildServerRelay.HasShutDown)
|
if (ChildServerRelay.HasShutDown)
|
||||||
@@ -387,10 +386,10 @@ namespace Barotrauma.Networking
|
|||||||
Voting.Update(deltaTime);
|
Voting.Update(deltaTime);
|
||||||
|
|
||||||
bool isCrewDead =
|
bool isCrewDead =
|
||||||
connectedClients.All(c => c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated);
|
connectedClients.All(c => !c.UsingFreeCam && (c.Character == null || c.Character.IsDead || c.Character.IsIncapacitated));
|
||||||
|
|
||||||
bool subAtLevelEnd = false;
|
bool subAtLevelEnd = false;
|
||||||
if (Submarine.MainSub != null && !(GameMain.GameSession.GameMode is PvPMode))
|
if (Submarine.MainSub != null && GameMain.GameSession.GameMode is not PvPMode)
|
||||||
{
|
{
|
||||||
if (Level.Loaded?.EndOutpost != null)
|
if (Level.Loaded?.EndOutpost != null)
|
||||||
{
|
{
|
||||||
@@ -692,10 +691,14 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly DoSProtection dosProtection = new();
|
||||||
|
|
||||||
private void ReadDataMessage(NetworkConnection sender, IReadMessage inc)
|
private void ReadDataMessage(NetworkConnection sender, IReadMessage inc)
|
||||||
{
|
{
|
||||||
var connectedClient = connectedClients.Find(c => c.Connection == sender);
|
var connectedClient = connectedClients.Find(c => c.Connection == sender);
|
||||||
|
|
||||||
|
using var _ = dosProtection.Start(connectedClient);
|
||||||
|
|
||||||
ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte();
|
ClientPacketHeader header = (ClientPacketHeader)inc.ReadByte();
|
||||||
switch (header)
|
switch (header)
|
||||||
{
|
{
|
||||||
@@ -772,9 +775,12 @@ namespace Barotrauma.Networking
|
|||||||
string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName);
|
string localSavePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveName);
|
||||||
if (CampaignMode.AllowedToManageCampaign(connectedClient, ClientPermissions.ManageRound))
|
if (CampaignMode.AllowedToManageCampaign(connectedClient, ClientPermissions.ManageRound))
|
||||||
{
|
{
|
||||||
ServerSettings.CampaignSettings = settings;
|
using (dosProtection.Pause(connectedClient))
|
||||||
ServerSettings.SaveSettings();
|
{
|
||||||
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
|
ServerSettings.CampaignSettings = settings;
|
||||||
|
ServerSettings.SaveSettings();
|
||||||
|
MultiPlayerCampaign.StartNewCampaign(localSavePath, matchingSub.FilePath, seed, settings);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -784,11 +790,14 @@ namespace Barotrauma.Networking
|
|||||||
if (GameStarted)
|
if (GameStarted)
|
||||||
{
|
{
|
||||||
SendDirectChatMessage(TextManager.Get("CampaignStartFailedRoundRunning").Value, connectedClient, ChatMessageType.MessageBox);
|
SendDirectChatMessage(TextManager.Get("CampaignStartFailedRoundRunning").Value, connectedClient, ChatMessageType.MessageBox);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
if (CampaignMode.AllowedToManageCampaign(connectedClient, ClientPermissions.ManageRound))
|
if (CampaignMode.AllowedToManageCampaign(connectedClient, ClientPermissions.ManageRound))
|
||||||
{
|
{
|
||||||
MultiPlayerCampaign.LoadCampaign(saveName);
|
using (dosProtection.Pause(connectedClient))
|
||||||
|
{
|
||||||
|
MultiPlayerCampaign.LoadCampaign(saveName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -1085,7 +1094,7 @@ namespace Barotrauma.Networking
|
|||||||
ChatMessage.ServerRead(inc, c);
|
ChatMessage.ServerRead(inc, c);
|
||||||
break;
|
break;
|
||||||
case ClientNetSegment.Vote:
|
case ClientNetSegment.Vote:
|
||||||
Voting.ServerRead(inc, c);
|
Voting.ServerRead(inc, c, dosProtection);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
return SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
|
return SegmentTableReader<ClientNetSegment>.BreakSegmentReading.Yes;
|
||||||
@@ -1246,7 +1255,7 @@ namespace Barotrauma.Networking
|
|||||||
entityEventManager.Read(inc, c);
|
entityEventManager.Read(inc, c);
|
||||||
break;
|
break;
|
||||||
case ClientNetSegment.Vote:
|
case ClientNetSegment.Vote:
|
||||||
Voting.ServerRead(inc, c);
|
Voting.ServerRead(inc, c, dosProtection);
|
||||||
break;
|
break;
|
||||||
case ClientNetSegment.SpectatingPos:
|
case ClientNetSegment.SpectatingPos:
|
||||||
c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
|
c.SpectatePos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
|
||||||
@@ -1406,19 +1415,23 @@ namespace Barotrauma.Networking
|
|||||||
bool quitCampaign = inc.ReadBoolean();
|
bool quitCampaign = inc.ReadBoolean();
|
||||||
if (GameStarted)
|
if (GameStarted)
|
||||||
{
|
{
|
||||||
Log($"Client \"{ClientLogName(sender)}\" ended the round.", ServerLog.MessageType.ServerMessage);
|
using (dosProtection.Pause(sender))
|
||||||
if (mpCampaign != null && Level.IsLoadedFriendlyOutpost && save)
|
|
||||||
{
|
{
|
||||||
mpCampaign.SavePlayers();
|
Log($"Client \"{ClientLogName(sender)}\" ended the round.", ServerLog.MessageType.ServerMessage);
|
||||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
if (mpCampaign != null && Level.IsLoadedFriendlyOutpost && save)
|
||||||
mpCampaign.UpdateStoreStock();
|
{
|
||||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
mpCampaign.SavePlayers();
|
||||||
|
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||||
|
mpCampaign.UpdateStoreStock();
|
||||||
|
GameMain.GameSession?.EventManager?.RegisterEventHistory(registerFinishedOnly: true);
|
||||||
|
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
save = false;
|
||||||
|
}
|
||||||
|
EndGame(wasSaved: save);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
save = false;
|
|
||||||
}
|
|
||||||
EndGame(wasSaved: save);
|
|
||||||
}
|
}
|
||||||
else if (mpCampaign != null)
|
else if (mpCampaign != null)
|
||||||
{
|
{
|
||||||
@@ -1442,45 +1455,54 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else if (CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageMap))
|
else if (CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageMap))
|
||||||
{
|
{
|
||||||
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath);
|
using (dosProtection.Pause(sender))
|
||||||
|
{
|
||||||
|
MultiPlayerCampaign.LoadCampaign(GameMain.GameSession.SavePath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (!GameStarted && !initiatedStartGame)
|
else if (!GameStarted && !initiatedStartGame)
|
||||||
{
|
{
|
||||||
Log("Client \"" + ClientLogName(sender) + "\" started the round.", ServerLog.MessageType.ServerMessage);
|
using (dosProtection.Pause(sender))
|
||||||
TryStartGame();
|
{
|
||||||
|
Log("Client \"" + ClientLogName(sender) + "\" started the round.", ServerLog.MessageType.ServerMessage);
|
||||||
|
TryStartGame();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (mpCampaign != null && (CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageMap)))
|
else if (mpCampaign != null && (CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageCampaign) || CampaignMode.AllowedToManageCampaign(sender, ClientPermissions.ManageMap)))
|
||||||
{
|
{
|
||||||
var availableTransition = mpCampaign.GetAvailableTransition(out _, out _);
|
using (dosProtection.Pause(sender))
|
||||||
//don't force location if we've teleported
|
|
||||||
bool forceLocation = !mpCampaign.Map.AllowDebugTeleport || mpCampaign.Map.CurrentLocation == Level.Loaded.StartLocation;
|
|
||||||
switch (availableTransition)
|
|
||||||
{
|
{
|
||||||
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
|
var availableTransition = mpCampaign.GetAvailableTransition(out _, out _);
|
||||||
if (forceLocation)
|
//don't force location if we've teleported
|
||||||
{
|
bool forceLocation = !mpCampaign.Map.AllowDebugTeleport || mpCampaign.Map.CurrentLocation == Level.Loaded.StartLocation;
|
||||||
mpCampaign.Map.SelectLocation(
|
switch (availableTransition)
|
||||||
mpCampaign.Map.CurrentLocation.Connections.Find(c => c.LevelData == Level.Loaded?.LevelData).OtherLocation(mpCampaign.Map.CurrentLocation));
|
{
|
||||||
}
|
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
|
||||||
mpCampaign.LoadNewLevel();
|
if (forceLocation)
|
||||||
break;
|
{
|
||||||
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
|
mpCampaign.Map.SelectLocation(
|
||||||
if (forceLocation)
|
mpCampaign.Map.CurrentLocation.Connections.Find(c => c.LevelData == Level.Loaded?.LevelData).OtherLocation(mpCampaign.Map.CurrentLocation));
|
||||||
{
|
}
|
||||||
mpCampaign.Map.SetLocation(mpCampaign.Map.Locations.IndexOf(Level.Loaded.EndLocation));
|
mpCampaign.LoadNewLevel();
|
||||||
}
|
break;
|
||||||
mpCampaign.LoadNewLevel();
|
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
|
||||||
break;
|
if (forceLocation)
|
||||||
case CampaignMode.TransitionType.None:
|
{
|
||||||
#if DEBUG || UNSTABLE
|
mpCampaign.Map.SetLocation(mpCampaign.Map.Locations.IndexOf(Level.Loaded.EndLocation));
|
||||||
DebugConsole.ThrowError($"Client \"{sender.Name}\" attempted to trigger a level transition. No transitions available.");
|
}
|
||||||
#endif
|
mpCampaign.LoadNewLevel();
|
||||||
return;
|
break;
|
||||||
default:
|
case CampaignMode.TransitionType.None:
|
||||||
Log("Client \"" + ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
|
#if DEBUG || UNSTABLE
|
||||||
mpCampaign.LoadNewLevel();
|
DebugConsole.ThrowError($"Client \"{sender.Name}\" attempted to trigger a level transition. No transitions available.");
|
||||||
break;
|
#endif
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
Log("Client \"" + ClientLogName(sender) + "\" ended the round.", ServerLog.MessageType.ServerMessage);
|
||||||
|
mpCampaign.LoadNewLevel();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1520,11 +1542,7 @@ namespace Barotrauma.Networking
|
|||||||
mpCampaign?.ServerRead(inc, sender);
|
mpCampaign?.ServerRead(inc, sender);
|
||||||
break;
|
break;
|
||||||
case ClientPermissions.ConsoleCommands:
|
case ClientPermissions.ConsoleCommands:
|
||||||
{
|
DebugConsole.ServerRead(inc, sender);
|
||||||
string consoleCommand = inc.ReadString();
|
|
||||||
Vector2 clientCursorPos = new Vector2(inc.ReadSingle(), inc.ReadSingle());
|
|
||||||
DebugConsole.ExecuteClientCommand(sender, clientCursorPos, consoleCommand);
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case ClientPermissions.ManagePermissions:
|
case ClientPermissions.ManagePermissions:
|
||||||
byte targetClientID = inc.ReadByte();
|
byte targetClientID = inc.ReadByte();
|
||||||
@@ -2582,16 +2600,20 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (!CampaignMode.AllowedToManageCampaign(client, ClientPermissions.ManageRound)) { return false; }
|
if (!CampaignMode.AllowedToManageCampaign(client, ClientPermissions.ManageRound)) { return false; }
|
||||||
|
|
||||||
const int MaxSaves = 255;
|
using (dosProtection.Pause(client))
|
||||||
var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false);
|
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
|
||||||
msg.WriteByte((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
|
|
||||||
msg.WriteByte((byte)Math.Min(saveInfos.Count, MaxSaves));
|
|
||||||
for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++)
|
|
||||||
{
|
{
|
||||||
msg.WriteNetSerializableStruct(saveInfos[i]);
|
const int MaxSaves = 255;
|
||||||
|
var saveInfos = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Multiplayer, includeInCompatible: false);
|
||||||
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
|
msg.WriteByte((byte)ServerPacketHeader.CAMPAIGN_SETUP_INFO);
|
||||||
|
msg.WriteByte((byte)Math.Min(saveInfos.Count, MaxSaves));
|
||||||
|
for (int i = 0; i < saveInfos.Count && i < MaxSaves; i++)
|
||||||
|
{
|
||||||
|
msg.WriteNetSerializableStruct(saveInfos[i]);
|
||||||
|
}
|
||||||
|
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3588,15 +3610,28 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly RateLimiter charInfoRateLimiter = new(
|
||||||
|
maxRequests: 5,
|
||||||
|
expiryInSeconds: 10,
|
||||||
|
punishmentRules: new[]
|
||||||
|
{
|
||||||
|
(RateLimitAction.OnLimitReached, RateLimitPunishment.Announce),
|
||||||
|
(RateLimitAction.OnLimitDoubled, RateLimitPunishment.Kick)
|
||||||
|
});
|
||||||
|
|
||||||
private void UpdateCharacterInfo(IReadMessage message, Client sender)
|
private void UpdateCharacterInfo(IReadMessage message, Client sender)
|
||||||
{
|
{
|
||||||
sender.SpectateOnly = message.ReadBoolean() && (ServerSettings.AllowSpectating || sender.Connection == OwnerConnection);
|
bool spectateOnly = message.ReadBoolean();
|
||||||
if (sender.SpectateOnly)
|
message.ReadPadBits();
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
string newName = message.ReadString();
|
sender.SpectateOnly = spectateOnly && (ServerSettings.AllowSpectating || sender.Connection == OwnerConnection);
|
||||||
|
if (sender.SpectateOnly) { return; }
|
||||||
|
|
||||||
|
var netInfo = INetSerializableStruct.Read<NetCharacterInfo>(message);
|
||||||
|
|
||||||
|
if (charInfoRateLimiter.IsLimitReached(sender)) { return; }
|
||||||
|
|
||||||
|
string newName = netInfo.NewName;
|
||||||
if (string.IsNullOrEmpty(newName))
|
if (string.IsNullOrEmpty(newName))
|
||||||
{
|
{
|
||||||
newName = sender.Name;
|
newName = sender.Name;
|
||||||
@@ -3614,42 +3649,31 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int tagCount = message.ReadByte();
|
|
||||||
HashSet<Identifier> tagSet = new HashSet<Identifier>();
|
|
||||||
for (int i = 0; i < tagCount; i++)
|
|
||||||
{
|
|
||||||
tagSet.Add(message.ReadIdentifier());
|
|
||||||
}
|
|
||||||
int hairIndex = message.ReadByte();
|
|
||||||
int beardIndex = message.ReadByte();
|
|
||||||
int moustacheIndex = message.ReadByte();
|
|
||||||
int faceAttachmentIndex = message.ReadByte();
|
|
||||||
Color skinColor = message.ReadColorR8G8B8();
|
|
||||||
Color hairColor = message.ReadColorR8G8B8();
|
|
||||||
Color facialHairColor = message.ReadColorR8G8B8();
|
|
||||||
|
|
||||||
List<JobVariant> jobPreferences = new List<JobVariant>();
|
|
||||||
int count = message.ReadByte();
|
|
||||||
for (int i = 0; i < Math.Min(count, 3); i++)
|
|
||||||
{
|
|
||||||
string jobIdentifier = message.ReadString();
|
|
||||||
int variant = message.ReadByte();
|
|
||||||
if (JobPrefab.Prefabs.TryGet(jobIdentifier, out JobPrefab jobPrefab))
|
|
||||||
{
|
|
||||||
if (jobPrefab.HiddenJob) { continue; }
|
|
||||||
jobPreferences.Add(new JobVariant(jobPrefab, variant));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, newName);
|
sender.CharacterInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, newName);
|
||||||
sender.CharacterInfo.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
|
|
||||||
sender.CharacterInfo.Head.SkinColor = skinColor;
|
|
||||||
sender.CharacterInfo.Head.HairColor = hairColor;
|
|
||||||
sender.CharacterInfo.Head.FacialHairColor = facialHairColor;
|
|
||||||
|
|
||||||
if (jobPreferences.Count > 0)
|
sender.CharacterInfo.RecreateHead(
|
||||||
|
tags: netInfo.Tags.ToImmutableHashSet(),
|
||||||
|
hairIndex: netInfo.HairIndex,
|
||||||
|
beardIndex: netInfo.BeardIndex,
|
||||||
|
moustacheIndex: netInfo.MoustacheIndex,
|
||||||
|
faceAttachmentIndex: netInfo.FaceAttachmentIndex);
|
||||||
|
|
||||||
|
sender.CharacterInfo.Head.SkinColor = netInfo.SkinColor;
|
||||||
|
sender.CharacterInfo.Head.HairColor = netInfo.HairColor;
|
||||||
|
sender.CharacterInfo.Head.FacialHairColor = netInfo.FacialHairColor;
|
||||||
|
|
||||||
|
if (netInfo.JobVariants.Length > 0)
|
||||||
{
|
{
|
||||||
sender.JobPreferences = jobPreferences;
|
List<JobVariant> variants = new List<JobVariant>();
|
||||||
|
foreach (NetJobVariant jv in netInfo.JobVariants)
|
||||||
|
{
|
||||||
|
if (jv.ToJobVariant() is { } variant)
|
||||||
|
{
|
||||||
|
variants.Add(variant);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.JobPreferences = variants;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+19
-4
@@ -24,11 +24,28 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected readonly Callbacks callbacks;
|
protected readonly Callbacks callbacks;
|
||||||
|
private readonly ImmutableArray<ContentPackage> contentPackages;
|
||||||
|
|
||||||
protected ServerPeer(Callbacks callbacks)
|
protected ServerPeer(Callbacks callbacks)
|
||||||
{
|
{
|
||||||
this.callbacks = callbacks;
|
this.callbacks = callbacks;
|
||||||
}
|
|
||||||
|
List<ContentPackage> contentPackageList = new List<ContentPackage>();
|
||||||
|
foreach (var cp in ContentPackageManager.EnabledPackages.All)
|
||||||
|
{
|
||||||
|
if (!cp.Files.Any()) { continue; }
|
||||||
|
if (!cp.HasMultiplayerSyncedContent && !cp.Files.All(f => f is SubmarineFile)) { continue; }
|
||||||
|
if (cp.UgcId.TryUnwrap(out var id1) &&
|
||||||
|
contentPackageList.FirstOrDefault(cp => cp.UgcId.TryUnwrap(out var id2) && id1.Equals(id2)) is ContentPackage existingPackage)
|
||||||
|
{
|
||||||
|
//there can be multiple enabled mods with the same UgcId if the player has e.g. created a local copy of a workshop mod
|
||||||
|
DebugConsole.AddWarning($"The content package \"{existingPackage.Name}\" ({existingPackage.Path}) has the same id as \"{cp.Name}\" ({cp.Path}). Ignoring the latter package.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
contentPackageList.Add(cp);
|
||||||
|
}
|
||||||
|
contentPackages = contentPackageList.ToImmutableArray();
|
||||||
|
}
|
||||||
|
|
||||||
public abstract void InitializeSteamServerCallbacks();
|
public abstract void InitializeSteamServerCallbacks();
|
||||||
|
|
||||||
@@ -250,9 +267,7 @@ namespace Barotrauma.Networking
|
|||||||
structToSend = new ServerPeerContentPackageOrderPacket
|
structToSend = new ServerPeerContentPackageOrderPacket
|
||||||
{
|
{
|
||||||
ServerName = GameMain.Server.ServerName,
|
ServerName = GameMain.Server.ServerName,
|
||||||
ContentPackages = ContentPackageManager.EnabledPackages.All
|
ContentPackages = contentPackages
|
||||||
.Where(cp => cp.Files.Any())
|
|
||||||
.Where(cp => cp.HasMultiplayerSyncedContent || cp.Files.All(f => f is SubmarineFile))
|
|
||||||
.Select(contentPackage => new ServerContentPackage(contentPackage, timeNow))
|
.Select(contentPackage => new ServerContentPackage(contentPackage, timeNow))
|
||||||
.ToImmutableArray()
|
.ToImmutableArray()
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -520,7 +520,7 @@ namespace Barotrauma.Networking
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
string presetName = clientElement.GetAttributeString("preset", "");
|
string presetName = clientElement.GetAttributeString("preset", "");
|
||||||
PermissionPreset preset = PermissionPreset.List.Find(p => p.Name == presetName);
|
PermissionPreset preset = PermissionPreset.List.Find(p => p.DisplayName == presetName);
|
||||||
if (preset == null)
|
if (preset == null)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError("Failed to restore saved permissions to the client \"" + clientName + "\". Permission preset \"" + presetName + "\" not found.");
|
DebugConsole.ThrowError("Failed to restore saved permissions to the client \"" + clientName + "\". Permission preset \"" + presetName + "\" not found.");
|
||||||
@@ -585,8 +585,7 @@ namespace Barotrauma.Networking
|
|||||||
foreach (SavedClientPermission clientPermission in ClientPermissions)
|
foreach (SavedClientPermission clientPermission in ClientPermissions)
|
||||||
{
|
{
|
||||||
var matchingPreset = PermissionPreset.List.Find(p => p.MatchesPermissions(clientPermission.Permissions, clientPermission.PermittedCommands));
|
var matchingPreset = PermissionPreset.List.Find(p => p.MatchesPermissions(clientPermission.Permissions, clientPermission.PermittedCommands));
|
||||||
#warning TODO: this is broken because of localization
|
if (matchingPreset != null && matchingPreset.Identifier == "None")
|
||||||
if (matchingPreset != null && matchingPreset.Name == "None")
|
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -600,7 +599,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
clientElement.Add(matchingPreset == null
|
clientElement.Add(matchingPreset == null
|
||||||
? new XAttribute("permissions", clientPermission.Permissions.ToString())
|
? new XAttribute("permissions", clientPermission.Permissions.ToString())
|
||||||
: new XAttribute("preset", matchingPreset.Name));
|
: new XAttribute("preset", matchingPreset.DisplayName));
|
||||||
|
|
||||||
if (clientPermission.Permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands))
|
if (clientPermission.Permissions.HasFlag(Networking.ClientPermissions.ConsoleCommands))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -225,7 +225,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ServerRead(IReadMessage inc, Client sender)
|
public void ServerRead(IReadMessage inc, Client sender, DoSProtection dosProtection)
|
||||||
{
|
{
|
||||||
if (GameMain.Server == null || sender == null) { return; }
|
if (GameMain.Server == null || sender == null) { return; }
|
||||||
|
|
||||||
@@ -337,7 +337,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
inc.ReadPadBits();
|
inc.ReadPadBits();
|
||||||
|
|
||||||
GameMain.Server.UpdateVoteStatus();
|
using (dosProtection.Pause(sender))
|
||||||
|
{
|
||||||
|
GameMain.Server.UpdateVoteStatus();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ServerWrite(IWriteMessage msg)
|
public void ServerWrite(IWriteMessage msg)
|
||||||
|
|||||||
@@ -158,7 +158,10 @@ namespace Barotrauma
|
|||||||
sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language);
|
sb.AppendLine("Language: " + GameSettings.CurrentConfig.Language);
|
||||||
if (ContentPackageManager.EnabledPackages.All != null)
|
if (ContentPackageManager.EnabledPackages.All != null)
|
||||||
{
|
{
|
||||||
sb.AppendLine("Selected content packages: " + (!ContentPackageManager.EnabledPackages.All.Any() ? "None" : string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => c.Name))));
|
sb.AppendLine("Selected content packages: " +
|
||||||
|
(!ContentPackageManager.EnabledPackages.All.Any() ?
|
||||||
|
"None" :
|
||||||
|
string.Join(", ", ContentPackageManager.EnabledPackages.All.Select(c => $"{c.Name} ({c.Hash?.ShortRepresentation ?? "unknown"})"))));
|
||||||
}
|
}
|
||||||
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
|
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
|
||||||
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
|
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
|
||||||
|
|||||||
@@ -5,12 +5,13 @@ namespace Barotrauma.Steam
|
|||||||
{
|
{
|
||||||
partial class SteamManager
|
partial class SteamManager
|
||||||
{
|
{
|
||||||
private static void InitializeProjectSpecific() { IsInitialized = true; }
|
private static void InitializeProjectSpecific() { }
|
||||||
|
|
||||||
|
private static bool IsInitializedProjectSpecific
|
||||||
|
=> Steamworks.SteamServer.IsValid;
|
||||||
|
|
||||||
public static bool CreateServer(Networking.GameServer server, bool isPublic)
|
public static bool CreateServer(Networking.GameServer server, bool isPublic)
|
||||||
{
|
{
|
||||||
IsInitialized = true;
|
|
||||||
|
|
||||||
Steamworks.SteamServerInit options = new Steamworks.SteamServerInit("Barotrauma", "Barotrauma")
|
Steamworks.SteamServerInit options = new Steamworks.SteamServerInit("Barotrauma", "Barotrauma")
|
||||||
{
|
{
|
||||||
GamePort = (ushort)server.Port,
|
GamePort = (ushort)server.Port,
|
||||||
@@ -56,10 +57,15 @@ namespace Barotrauma.Steam
|
|||||||
Steamworks.SteamServer.SetKey("message", server.ServerSettings.ServerMessageText);
|
Steamworks.SteamServer.SetKey("message", server.ServerSettings.ServerMessageText);
|
||||||
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
|
Steamworks.SteamServer.SetKey("version", GameMain.Version.ToString());
|
||||||
Steamworks.SteamServer.SetKey("playercount", server.ConnectedClients.Count.ToString());
|
Steamworks.SteamServer.SetKey("playercount", server.ConnectedClients.Count.ToString());
|
||||||
Steamworks.SteamServer.SetKey("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
|
int index = 0;
|
||||||
Steamworks.SteamServer.SetKey("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.Hash.StringRepresentation)));
|
foreach (var contentPackage in contentPackages)
|
||||||
Steamworks.SteamServer.SetKey("contentpackageid", string.Join(",", contentPackages.Select(cp
|
{
|
||||||
=> cp.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : "")));
|
string ugcIdStr = contentPackage.UgcId.TryUnwrap(out var ugcId) ? ugcId.StringRepresentation : string.Empty;
|
||||||
|
Steamworks.SteamServer.SetKey(
|
||||||
|
$"contentpackage{index}",
|
||||||
|
contentPackage.Name+","+ contentPackage.Hash.StringRepresentation + "," + ugcIdStr);
|
||||||
|
index++;
|
||||||
|
}
|
||||||
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
|
Steamworks.SteamServer.SetKey("modeselectionmode", server.ServerSettings.ModeSelectionMode.ToString());
|
||||||
Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
|
Steamworks.SteamServer.SetKey("subselectionmode", server.ServerSettings.SubSelectionMode.ToString());
|
||||||
Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
|
Steamworks.SteamServer.SetKey("voicechatenabled", server.ServerSettings.VoiceChatEnabled.ToString());
|
||||||
|
|||||||
@@ -20,19 +20,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
public delegate void MessageSender(string message);
|
public delegate void MessageSender(string message);
|
||||||
public void Greet(GameServer server, string codeWords, string codeResponse, MessageSender messageSender)
|
|
||||||
{
|
|
||||||
string greetingMessage = TextManager.FormatServerMessage(Mission.StartText,
|
|
||||||
("[codewords]", codeWords),
|
|
||||||
("[coderesponse]", codeResponse));
|
|
||||||
messageSender(greetingMessage);
|
|
||||||
Client traitorClient = server.ConnectedClients.Find(c => c.Character == Character);
|
|
||||||
Client ownerClient = server.ConnectedClients.Find(c => c.Connection == server.OwnerConnection);
|
|
||||||
if (traitorClient != ownerClient && ownerClient != null && ownerClient.Character == null)
|
|
||||||
{
|
|
||||||
GameMain.Server.SendTraitorMessage(ownerClient, CurrentObjective.StartMessageServerText.Value, Mission.Identifier, TraitorMessageType.ServerMessageBox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SendChatMessage(string serverText, Identifier iconIdentifier)
|
public void SendChatMessage(string serverText, Identifier iconIdentifier)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -224,10 +224,6 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
pendingMessages.Add(traitor, new List<string>());
|
pendingMessages.Add(traitor, new List<string>());
|
||||||
}
|
}
|
||||||
foreach (var traitor in Traitors.Values)
|
|
||||||
{
|
|
||||||
traitor.Greet(server, CodeWords, CodeResponse, message => pendingMessages[traitor].Add(message));
|
|
||||||
}
|
|
||||||
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message, Identifier)));
|
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessage(message, Identifier)));
|
||||||
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message, Identifier)));
|
pendingMessages.ForEach(traitor => traitor.Value.ForEach(message => traitor.Key.SendChatMessageBox(message, Identifier)));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,232 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal sealed class DoSProtection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A struct that executes an action when it's created and another one when it's disposed.
|
||||||
|
/// </summary>
|
||||||
|
public readonly ref struct DoSAction
|
||||||
|
{
|
||||||
|
private readonly Client sender;
|
||||||
|
private readonly Action<Client> end;
|
||||||
|
|
||||||
|
public DoSAction(Client sender, Action<Client> start, Action<Client> end)
|
||||||
|
{
|
||||||
|
this.sender = sender;
|
||||||
|
this.end = end;
|
||||||
|
start(sender);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
end(sender);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sealed class OffenseData
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Timer that keeps track of how long it takes to process a packet.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Stopwatch Stopwatch = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Amount of strikes the client has received for causing the server to slow down.
|
||||||
|
/// </summary>
|
||||||
|
public int Strikes;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// How many packets have been sent in the last minute.
|
||||||
|
/// </summary>
|
||||||
|
public int PacketCount;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the strikes and packet count.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetStrikes()
|
||||||
|
{
|
||||||
|
Strikes = 0;
|
||||||
|
PacketCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resets the timer.
|
||||||
|
/// </summary>
|
||||||
|
public void ResetTimer() => Stopwatch.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<Client, OffenseData> clients = new();
|
||||||
|
|
||||||
|
private float stopwatchResetTimer,
|
||||||
|
strikesResetTimer;
|
||||||
|
|
||||||
|
private const int StopwatchResetInterval = 1,
|
||||||
|
StrikesResetInterval = 60,
|
||||||
|
StrikeThreshold = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called when the server receives a packet to start logging how much time it takes to process.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The client to start a timer for.</param>
|
||||||
|
/// <returns>Nothing useful. Required for the "using" keyword.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Calling stop is not required, the timer will be stopped automatically when the function it was started in returns.
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// public void ServerRead(IReadMessage msg, Client c)
|
||||||
|
/// {
|
||||||
|
/// // start the timer
|
||||||
|
/// using var _ = dosProtection.Start(connectedClient);
|
||||||
|
///
|
||||||
|
/// if (condition)
|
||||||
|
/// {
|
||||||
|
/// // the timer will be stopped here.
|
||||||
|
/// return;
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// ProcessMessage(msg);
|
||||||
|
/// // the timer will be stopped here.
|
||||||
|
/// }
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public DoSAction Start(Client client) => new DoSAction(client, StartFor, EndFor);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Temporary pauses the timer for the client.
|
||||||
|
/// Used when we know a packet is going to slow down the server but we don't want to count it as a strike.
|
||||||
|
/// For example when a client is starting a round.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="client">The client to pause the timer for.</param>
|
||||||
|
/// <returns>Nothing useful. Required for the "using" keyword.</returns>
|
||||||
|
/// <remarks>
|
||||||
|
/// Calling resume is not required, the timer will be resumed automatically when the using block ends.
|
||||||
|
/// </remarks>
|
||||||
|
/// <example>
|
||||||
|
/// <code>
|
||||||
|
/// using (dos.Pause(client))
|
||||||
|
/// {
|
||||||
|
/// // do something that will slow down the server
|
||||||
|
/// }
|
||||||
|
/// // the timer will be resumed here
|
||||||
|
/// </code>
|
||||||
|
/// </example>
|
||||||
|
public DoSAction Pause(Client client) => new DoSAction(client, PauseFor, ResumeFor);
|
||||||
|
|
||||||
|
private void StartFor(Client client)
|
||||||
|
{
|
||||||
|
if (!clients.ContainsKey(client))
|
||||||
|
{
|
||||||
|
clients.Add(client, new OffenseData());
|
||||||
|
}
|
||||||
|
|
||||||
|
clients[client].Stopwatch.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void EndFor(Client client)
|
||||||
|
{
|
||||||
|
if (GetData(client) is not { } data) { return; }
|
||||||
|
|
||||||
|
data.PacketCount++;
|
||||||
|
data.Stopwatch.Stop();
|
||||||
|
UpdateOffense(client, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
// stops the clock but doesn't update offenses
|
||||||
|
private void PauseFor(Client client) => GetData(client)?.Stopwatch.Stop();
|
||||||
|
|
||||||
|
private void ResumeFor(Client client) => GetData(client)?.Stopwatch.Start();
|
||||||
|
|
||||||
|
private void UpdateOffense(Client client, OffenseData data)
|
||||||
|
{
|
||||||
|
if (GameMain.Server?.ServerSettings is not { } settings) { return; }
|
||||||
|
|
||||||
|
// client is sending too many packets, kick them
|
||||||
|
if (data.PacketCount > settings.MaxPacketAmount && settings.MaxPacketAmount > ServerSettings.PacketLimitMin)
|
||||||
|
{
|
||||||
|
AttemptKickClient(client, TextManager.Get("PacketLimitKicked"));
|
||||||
|
clients.Remove(client);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the stopwatch has been running for an entire second without the Update() method resetting it (which it does every second) then something is wrong
|
||||||
|
if (data.Stopwatch.ElapsedMilliseconds < 100) { return; }
|
||||||
|
|
||||||
|
data.Strikes++;
|
||||||
|
data.ResetTimer();
|
||||||
|
|
||||||
|
GameServer.Log($"{NetworkMember.ClientLogName(client)} is causing the server to slow down.", ServerLog.MessageType.DoSProtection);
|
||||||
|
|
||||||
|
// too many strikes, get them out of here
|
||||||
|
if (data.Strikes < StrikeThreshold) { return; }
|
||||||
|
|
||||||
|
if (settings.EnableDoSProtection)
|
||||||
|
{
|
||||||
|
AttemptKickClient(client, TextManager.Get("DoSProtectionKicked"));
|
||||||
|
}
|
||||||
|
|
||||||
|
clients.Remove(client);
|
||||||
|
|
||||||
|
static void AttemptKickClient(Client client, LocalizedString reason)
|
||||||
|
{
|
||||||
|
// ReSharper disable once ConvertToConstant.Local
|
||||||
|
bool doesRateLimitAffectClient =
|
||||||
|
#if DEBUG
|
||||||
|
true; // for testing
|
||||||
|
#else
|
||||||
|
!RateLimiter.IsExempt(client);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!doesRateLimitAffectClient)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GameMain.Server?.KickClient(client, reason.Value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(float deltaTime)
|
||||||
|
{
|
||||||
|
stopwatchResetTimer += deltaTime;
|
||||||
|
strikesResetTimer += deltaTime;
|
||||||
|
|
||||||
|
// reset the stopwatch every second
|
||||||
|
if (stopwatchResetTimer > StopwatchResetInterval)
|
||||||
|
{
|
||||||
|
stopwatchResetTimer = 0;
|
||||||
|
foreach (OffenseData data in clients.Values)
|
||||||
|
{
|
||||||
|
data.ResetTimer();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the strikes every minute
|
||||||
|
if (strikesResetTimer > StrikesResetInterval)
|
||||||
|
{
|
||||||
|
strikesResetTimer = 0;
|
||||||
|
foreach (var (client, data) in clients)
|
||||||
|
{
|
||||||
|
if (GameMain.Server?.ServerSettings is { MaxPacketAmount: > ServerSettings.PacketLimitMin } settings)
|
||||||
|
{
|
||||||
|
if (data.PacketCount > settings.MaxPacketAmount * 0.9f)
|
||||||
|
{
|
||||||
|
GameServer.Log($"{NetworkMember.ClientLogName(client)} is sending a lot of packets and almost got kicked! ({data.PacketCount}).", ServerLog.MessageType.DoSProtection);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data.ResetStrikes();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private OffenseData? GetData(Client client) => clients.TryGetValue(client, out OffenseData? data) ? data : null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,135 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
public enum RateLimitAction
|
||||||
|
{
|
||||||
|
Invalid,
|
||||||
|
OnLimitReached,
|
||||||
|
OnLimitDoubled,
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum RateLimitPunishment
|
||||||
|
{
|
||||||
|
None, // just ignore
|
||||||
|
Announce, // announce to the server
|
||||||
|
Kick, // kick the player
|
||||||
|
Ban // ban the player
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class RateLimiter
|
||||||
|
{
|
||||||
|
private sealed record RateLimit(DateTimeOffset Expiry)
|
||||||
|
{
|
||||||
|
public int RequestAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly Dictionary<Client, RateLimit> rateLimits = new();
|
||||||
|
private readonly HashSet<Client> expiredRateLimits = new();
|
||||||
|
private readonly Dictionary<Client, DateTimeOffset> recentlyAnnouncedOffenders = new();
|
||||||
|
|
||||||
|
private readonly int maxRequests, expiryInSeconds;
|
||||||
|
|
||||||
|
private readonly ImmutableDictionary<RateLimitAction, RateLimitPunishment> punishments;
|
||||||
|
|
||||||
|
public RateLimiter(int maxRequests, int expiryInSeconds, params (RateLimitAction Action, RateLimitPunishment Punishment)[] punishmentRules)
|
||||||
|
{
|
||||||
|
this.maxRequests = maxRequests;
|
||||||
|
this.expiryInSeconds = expiryInSeconds;
|
||||||
|
|
||||||
|
punishments = punishmentRules.ToImmutableDictionary(
|
||||||
|
static pair => pair.Action,
|
||||||
|
static pair => pair.Punishment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsLimitReached(Client client)
|
||||||
|
{
|
||||||
|
#if !DEBUG
|
||||||
|
if (IsExempt(client)) { return false; }
|
||||||
|
#endif
|
||||||
|
expiredRateLimits.Clear();
|
||||||
|
|
||||||
|
foreach (var (c, limit) in rateLimits)
|
||||||
|
{
|
||||||
|
if (limit.Expiry < DateTimeOffset.Now)
|
||||||
|
{
|
||||||
|
expiredRateLimits.Add(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Client c in expiredRateLimits)
|
||||||
|
{
|
||||||
|
rateLimits.Remove(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rateLimits.TryGetValue(client, out RateLimit? rateLimit))
|
||||||
|
{
|
||||||
|
rateLimit = new RateLimit(DateTimeOffset.Now.AddSeconds(expiryInSeconds));
|
||||||
|
rateLimits.Add(client, rateLimit);
|
||||||
|
}
|
||||||
|
|
||||||
|
rateLimit.RequestAmount++;
|
||||||
|
|
||||||
|
if (rateLimit.RequestAmount > maxRequests)
|
||||||
|
{
|
||||||
|
ProcessPunishment(client, rateLimit.RequestAmount);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ProcessPunishment(Client client, int requests)
|
||||||
|
{
|
||||||
|
bool isDosProtectionEnabled = GameMain.Server is { ServerSettings.EnableDoSProtection: true };
|
||||||
|
|
||||||
|
foreach (var (action, punishment) in punishments)
|
||||||
|
{
|
||||||
|
switch (action)
|
||||||
|
{
|
||||||
|
case RateLimitAction.Invalid:
|
||||||
|
continue;
|
||||||
|
case RateLimitAction.OnLimitReached when requests >= maxRequests:
|
||||||
|
case RateLimitAction.OnLimitDoubled when requests >= maxRequests * 2:
|
||||||
|
switch (punishment)
|
||||||
|
{
|
||||||
|
case RateLimitPunishment.None:
|
||||||
|
continue;
|
||||||
|
case RateLimitPunishment.Announce:
|
||||||
|
AnnounceOffender(client);
|
||||||
|
break;
|
||||||
|
case RateLimitPunishment.Ban when isDosProtectionEnabled:
|
||||||
|
GameMain.Server?.BanClient(client, TextManager.Get("SpamFilterKicked").Value);
|
||||||
|
break;
|
||||||
|
case RateLimitPunishment.Kick when isDosProtectionEnabled:
|
||||||
|
GameMain.Server?.KickClient(client, TextManager.Get("SpamFilterKicked").Value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AnnounceOffender(Client client)
|
||||||
|
{
|
||||||
|
if (recentlyAnnouncedOffenders.TryGetValue(client, out DateTimeOffset expiry))
|
||||||
|
{
|
||||||
|
if (expiry > DateTimeOffset.Now) { return; }
|
||||||
|
|
||||||
|
recentlyAnnouncedOffenders.Remove(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameServer.Log($"{NetworkMember.ClientLogName(client)} is sending too many packets!", ServerLog.MessageType.DoSProtection);
|
||||||
|
recentlyAnnouncedOffenders.Add(client, DateTimeOffset.Now.AddSeconds(expiryInSeconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsExempt(Client client) =>
|
||||||
|
(GameMain.Server.OwnerConnection != null && client.Connection == GameMain.Server.OwnerConnection)
|
||||||
|
|| client.HasPermission(ClientPermissions.SpamImmunity);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,14 @@
|
|||||||
<RootNamespace>Barotrauma</RootNamespace>
|
<RootNamespace>Barotrauma</RootNamespace>
|
||||||
<Authors>FakeFish, Undertow Games</Authors>
|
<Authors>FakeFish, Undertow Games</Authors>
|
||||||
<Product>Barotrauma Dedicated Server</Product>
|
<Product>Barotrauma Dedicated Server</Product>
|
||||||
<Version>1.0.9.0</Version>
|
<Version>1.0.13.1</Version>
|
||||||
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
<Copyright>Copyright © FakeFish 2018-2023</Copyright>
|
||||||
<Platforms>AnyCPU;x64</Platforms>
|
<Platforms>AnyCPU;x64</Platforms>
|
||||||
<AssemblyName>DedicatedServer</AssemblyName>
|
<AssemblyName>DedicatedServer</AssemblyName>
|
||||||
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
<ApplicationIcon>..\BarotraumaShared\Icon.ico</ApplicationIcon>
|
||||||
<Configurations>Debug;Release;Unstable</Configurations>
|
<Configurations>Debug;Release;Unstable</Configurations>
|
||||||
<InvariantGlobalization>true</InvariantGlobalization>
|
<InvariantGlobalization>true</InvariantGlobalization>
|
||||||
<WarningsAsErrors>;NU1605;CS0114;CS0108CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
<WarningsAsErrors>;NU1605;CS0114;CS0108;CS8597;CS8600;CS8601;CS8602;CS8603;CS8604;CS8605;CS8606;CS8607;CS8608;CS8609;CS8610;CS8611;CS8612;CS8613;CS8614;CS8615;CS8616;CS8617;CS8618;CS8619;CS8620;CS8621;CS8622;CS8624;CS8625;CS8626;CS8629;CS8631;CS8632;CS8633;CS8634;CS8638;CS8643;CS8644;CS8645;CS8653;CS8654;CS8655;CS8667;CS8669;CS8670;CS8714;CS8717;CS8765</WarningsAsErrors>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
<Preset
|
<Preset
|
||||||
name="Moderator"
|
name="Moderator"
|
||||||
description="Allowed to manage round settings, kick players and access server logs."
|
description="Allowed to manage round settings, kick players and access server logs."
|
||||||
permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney,ManageBotTalents">
|
permissions="ManageRound,Kick,SelectSub,SelectMode,ManageCampaign,ConsoleCommands,ServerLog,ManageSettings,ManageMoney,ManageBotTalents,SpamImmunity">
|
||||||
<Command name="clientlist"/>
|
<Command name="clientlist"/>
|
||||||
<Command name="readycheck"/>
|
<Command name="readycheck"/>
|
||||||
<Command name="autorestart"/>
|
<Command name="autorestart"/>
|
||||||
|
|||||||
@@ -107,12 +107,26 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool HasValidPath(bool requireNonDirty = false, bool requireUnfinished = true) =>
|
/// <summary>
|
||||||
steeringManager is IndoorsSteeringManager pathSteering &&
|
/// Is the current path valid, using the provided parameters.
|
||||||
pathSteering.CurrentPath != null &&
|
/// </summary>
|
||||||
(!requireUnfinished || !pathSteering.CurrentPath.Finished) &&
|
/// <param name="requireNonDirty"></param>
|
||||||
!pathSteering.CurrentPath.Unreachable &&
|
/// <param name="requireUnfinished"></param>
|
||||||
(!requireNonDirty || !pathSteering.IsPathDirty);
|
/// <param name="nodePredicate"></param>
|
||||||
|
/// <returns>When <paramref name="nodePredicate"/> is defined, returns false if any of the nodes fails to match the predicate.</returns>
|
||||||
|
public bool HasValidPath(bool requireNonDirty = true, bool requireUnfinished = true, Func<WayPoint, bool> nodePredicate = null)
|
||||||
|
{
|
||||||
|
if (SteeringManager is not IndoorsSteeringManager pathSteering) { return false; }
|
||||||
|
if (pathSteering.CurrentPath == null) { return false; }
|
||||||
|
if (pathSteering.CurrentPath.Unreachable) { return false; }
|
||||||
|
if (requireUnfinished && pathSteering.CurrentPath.Finished) { return false; }
|
||||||
|
if (requireNonDirty && pathSteering.IsPathDirty) { return false; }
|
||||||
|
if (nodePredicate != null)
|
||||||
|
{
|
||||||
|
return pathSteering.CurrentPath.Nodes.All(n => nodePredicate(n));
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool IsCurrentPathNullOrUnreachable => IsCurrentPathUnreachable || steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath == null;
|
public bool IsCurrentPathNullOrUnreachable => IsCurrentPathUnreachable || steeringManager is IndoorsSteeringManager pathSteering && pathSteering.CurrentPath == null;
|
||||||
public bool IsCurrentPathUnreachable => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable;
|
public bool IsCurrentPathUnreachable => steeringManager is IndoorsSteeringManager pathSteering && !pathSteering.IsPathDirty && pathSteering.CurrentPath != null && pathSteering.CurrentPath.Unreachable;
|
||||||
@@ -251,7 +265,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
private readonly HashSet<Item> unequippedItems = new HashSet<Item>();
|
private readonly HashSet<Item> unequippedItems = new HashSet<Item>();
|
||||||
public bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear = false, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false)
|
public bool TakeItem(Item item, CharacterInventory targetInventory, bool equip, bool wear = false, bool dropOtherIfCannotMove = true, bool allowSwapping = false, bool storeUnequipped = false, IEnumerable<Identifier> targetTags = null)
|
||||||
{
|
{
|
||||||
var pickable = item.GetComponent<Pickable>();
|
var pickable = item.GetComponent<Pickable>();
|
||||||
if (pickable == null) { return false; }
|
if (pickable == null) { return false; }
|
||||||
@@ -265,23 +279,28 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var holdable = item.GetComponent<Holdable>();
|
// Not allowed to wear -> don't use the Wearable component even when it's found.
|
||||||
if (holdable != null)
|
pickable = item.GetComponent<Holdable>();
|
||||||
{
|
|
||||||
pickable = holdable;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (item.ParentInventory is ItemInventory itemInventory)
|
if (item.ParentInventory is ItemInventory itemInventory)
|
||||||
{
|
{
|
||||||
if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; }
|
if (!itemInventory.Container.HasRequiredItems(Character, addMessage: false)) { return false; }
|
||||||
}
|
}
|
||||||
if (equip)
|
if (equip && pickable != null)
|
||||||
{
|
{
|
||||||
int targetSlot = -1;
|
int targetSlot = -1;
|
||||||
//check if all the slots required by the item are free
|
//check if all the slots required by the item are free
|
||||||
foreach (InvSlotType slots in pickable.AllowedSlots)
|
foreach (InvSlotType slots in pickable.AllowedSlots)
|
||||||
{
|
{
|
||||||
if (slots.HasFlag(InvSlotType.Any)) { continue; }
|
if (slots.HasFlag(InvSlotType.Any)) { continue; }
|
||||||
|
if (!wear)
|
||||||
|
{
|
||||||
|
if (slots != InvSlotType.RightHand && slots != InvSlotType.LeftHand && slots != (InvSlotType.RightHand | InvSlotType.LeftHand))
|
||||||
|
{
|
||||||
|
// Don't allow other than hand slots if not allowed to wear.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (int i = 0; i < targetInventory.Capacity; i++)
|
for (int i = 0; i < targetInventory.Capacity; i++)
|
||||||
{
|
{
|
||||||
if (targetInventory is CharacterInventory characterInventory)
|
if (targetInventory is CharacterInventory characterInventory)
|
||||||
@@ -294,7 +313,7 @@ namespace Barotrauma
|
|||||||
var otherItem = targetInventory.GetItemAt(i);
|
var otherItem = targetInventory.GetItemAt(i);
|
||||||
if (otherItem == null) { continue; }
|
if (otherItem == null) { continue; }
|
||||||
//try to move the existing item to LimbSlot.Any and continue if successful
|
//try to move the existing item to LimbSlot.Any and continue if successful
|
||||||
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.anySlot))
|
if (otherItem.AllowedSlots.Contains(InvSlotType.Any) && targetInventory.TryPutItem(otherItem, Character, CharacterInventory.AnySlot))
|
||||||
{
|
{
|
||||||
if (storeUnequipped && targetInventory.Owner == Character)
|
if (storeUnequipped && targetInventory.Owner == Character)
|
||||||
{
|
{
|
||||||
@@ -304,6 +323,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (dropOtherIfCannotMove)
|
if (dropOtherIfCannotMove)
|
||||||
{
|
{
|
||||||
|
if (otherItem.Prefab.Identifier == item.Prefab.Identifier || otherItem.HasIdentifierOrTags(targetTags))
|
||||||
|
{
|
||||||
|
// Shouldn't try dropping identical items, because that causes infinite looping when trying to get multiple items of the same type and if can't fit them all in the inventory.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
//if everything else fails, simply drop the existing item
|
//if everything else fails, simply drop the existing item
|
||||||
otherItem.Drop(Character);
|
otherItem.Drop(Character);
|
||||||
}
|
}
|
||||||
@@ -314,7 +338,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return targetInventory.TryPutItem(item, Character, CharacterInventory.anySlot);
|
return targetInventory.TryPutItem(item, Character, CharacterInventory.AnySlot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -339,7 +363,7 @@ namespace Barotrauma
|
|||||||
if (avoidDroppingInSea && !character.IsInFriendlySub)
|
if (avoidDroppingInSea && !character.IsInFriendlySub)
|
||||||
{
|
{
|
||||||
// If we are not inside a friendly sub (= same team), try to put the item in the inventory instead dropping it.
|
// If we are not inside a friendly sub (= same team), try to put the item in the inventory instead dropping it.
|
||||||
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.anySlot))
|
if (character.Inventory.TryPutItem(containedItem, character, CharacterInventory.AnySlot))
|
||||||
{
|
{
|
||||||
if (unequipMax.HasValue && ++removed >= unequipMax) { return; }
|
if (unequipMax.HasValue && ++removed >= unequipMax) { return; }
|
||||||
continue;
|
continue;
|
||||||
@@ -449,9 +473,10 @@ namespace Barotrauma
|
|||||||
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
|
Vector2 diff = EscapeTarget.WorldPosition - Character.WorldPosition;
|
||||||
float sqrDist = diff.LengthSquared();
|
float sqrDist = diff.LengthSquared();
|
||||||
bool isClose = sqrDist < MathUtils.Pow2(100);
|
bool isClose = sqrDist < MathUtils.Pow2(100);
|
||||||
if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathNullOrUnreachable || IsCurrentPathFinished)
|
if (Character.CurrentHull == null || isClose && !isClosedDoor || pathSteering == null || IsCurrentPathUnreachable || IsCurrentPathFinished)
|
||||||
{
|
{
|
||||||
// Very close to the target, outside, or at the end of the path -> try to steer through the gap
|
// Very close to the target, outside, or at the end of the path -> try to steer through the gap
|
||||||
|
Character.ReleaseSecondaryItem();
|
||||||
SteeringManager.Reset();
|
SteeringManager.Reset();
|
||||||
pathSteering?.ResetPath();
|
pathSteering?.ResetPath();
|
||||||
Vector2 dir = Vector2.Normalize(diff);
|
Vector2 dir = Vector2.Normalize(diff);
|
||||||
|
|||||||
@@ -480,7 +480,7 @@ namespace Barotrauma
|
|||||||
if (SelectedAiTarget?.Entity != null || EscapeTarget != null)
|
if (SelectedAiTarget?.Entity != null || EscapeTarget != null)
|
||||||
{
|
{
|
||||||
Entity t = SelectedAiTarget?.Entity ?? EscapeTarget;
|
Entity t = SelectedAiTarget?.Entity ?? EscapeTarget;
|
||||||
float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath(requireNonDirty: true) ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X;
|
float referencePos = Vector2.DistanceSquared(Character.WorldPosition, t.WorldPosition) > 100 * 100 && HasValidPath() ? PathSteering.CurrentPath.CurrentNode.WorldPosition.X : t.WorldPosition.X;
|
||||||
Character.AnimController.TargetDir = Character.WorldPosition.X < referencePos ? Direction.Right : Direction.Left;
|
Character.AnimController.TargetDir = Character.WorldPosition.X < referencePos ? Direction.Right : Direction.Left;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -3934,7 +3934,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
SteerAwayFromTheEnemy();
|
SteerAwayFromTheEnemy();
|
||||||
}
|
}
|
||||||
else if (canAttackDoors && HasValidPath(requireNonDirty: true, requireUnfinished: true))
|
else if (canAttackDoors && HasValidPath())
|
||||||
{
|
{
|
||||||
var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
var door = PathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? PathSteering.CurrentPath.NextNode?.ConnectedDoor;
|
||||||
if (door != null && !door.CanBeTraversed && !door.HasAccess(Character))
|
if (door != null && !door.CanBeTraversed && !door.HasAccess(Character))
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.Items.Components;
|
||||||
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Extensions;
|
|
||||||
using Barotrauma.Items.Components;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
partial class HumanAIController : AIController
|
partial class HumanAIController : AIController
|
||||||
{
|
{
|
||||||
public static bool debugai;
|
public static bool DebugAI;
|
||||||
public static bool DisableCrewAI;
|
public static bool DisableCrewAI;
|
||||||
|
|
||||||
private readonly AIObjectiveManager objectiveManager;
|
private readonly AIObjectiveManager objectiveManager;
|
||||||
@@ -55,6 +55,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly float obstacleRaycastIntervalShort = 1, obstacleRaycastIntervalLong = 5;
|
private readonly float obstacleRaycastIntervalShort = 1, obstacleRaycastIntervalLong = 5;
|
||||||
private float obstacleRaycastTimer;
|
private float obstacleRaycastTimer;
|
||||||
|
private bool isBlocked;
|
||||||
|
|
||||||
private readonly float enemyCheckInterval = 0.2f;
|
private readonly float enemyCheckInterval = 0.2f;
|
||||||
private readonly float enemySpotDistanceOutside = 800;
|
private readonly float enemySpotDistanceOutside = 800;
|
||||||
@@ -92,7 +93,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private readonly SteeringManager outsideSteering, insideSteering;
|
private readonly SteeringManager outsideSteering, insideSteering;
|
||||||
|
|
||||||
public bool UseIndoorSteeringOutside { get; set; } = false;
|
/// <summary>
|
||||||
|
/// Waypoints that are not linked to a sub (e.g. main path).
|
||||||
|
/// </summary>
|
||||||
|
public bool UseOutsideWaypoints { get; private set; }
|
||||||
|
|
||||||
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
public IndoorsSteeringManager PathSteering => insideSteering as IndoorsSteeringManager;
|
||||||
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
public HumanoidAnimController AnimController => Character.AnimController as HumanoidAnimController;
|
||||||
@@ -225,14 +229,15 @@ namespace Barotrauma
|
|||||||
IgnoredItems.Clear();
|
IgnoredItems.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsCloseEnoughToTarget(float threshold, bool useTargetSub = true)
|
// Note: returns false when useTargetSub is 'true' and the target is outside (targetSub is 'null')
|
||||||
|
bool IsCloseEnoughToTarget(float threshold, bool targetSub = true)
|
||||||
{
|
{
|
||||||
Entity target = SelectedAiTarget?.Entity;
|
Entity target = SelectedAiTarget?.Entity;
|
||||||
if (target == null)
|
if (target == null)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (useTargetSub)
|
if (targetSub)
|
||||||
{
|
{
|
||||||
if (target.Submarine is Submarine sub)
|
if (target.Submarine is Submarine sub)
|
||||||
{
|
{
|
||||||
@@ -244,62 +249,71 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow(threshold, 2);
|
return Vector2.DistanceSquared(Character.WorldPosition, target.WorldPosition) < MathUtils.Pow2(threshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool hasValidPath = HasValidPath();
|
bool isOutside = Character.Submarine == null;
|
||||||
|
if (isOutside)
|
||||||
if (Character.Submarine == null)
|
|
||||||
{
|
{
|
||||||
obstacleRaycastTimer -= deltaTime;
|
obstacleRaycastTimer -= deltaTime;
|
||||||
if (obstacleRaycastTimer <= 0)
|
if (obstacleRaycastTimer <= 0)
|
||||||
{
|
{
|
||||||
|
bool hasValidPath = HasValidPath();
|
||||||
|
isBlocked = false;
|
||||||
|
UseOutsideWaypoints = false;
|
||||||
obstacleRaycastTimer = obstacleRaycastIntervalLong;
|
obstacleRaycastTimer = obstacleRaycastIntervalLong;
|
||||||
if (SelectedAiTarget?.Entity == null || SelectedAiTarget.Entity is ISpatialEntity target && target.Submarine == null || !IsCloseEnoughToTarget(2000, useTargetSub: false))
|
ISpatialEntity spatialTarget = SelectedAiTarget?.Entity ?? ObjectiveManager.GetLastActiveObjective<AIObjectiveGoTo>()?.Target;
|
||||||
|
if (spatialTarget != null && (spatialTarget.Submarine == null || !IsCloseEnoughToTarget(2000, targetSub: false)))
|
||||||
{
|
{
|
||||||
// If the target is behind a level wall, switch to the pathing to get around the obstacles.
|
// If the target is behind a level wall, switch to the pathing to get around the obstacles.
|
||||||
ISpatialEntity spatialTarget = SelectedAiTarget?.Entity;
|
IEnumerable<FarseerPhysics.Dynamics.Body> ignoredBodies = null;
|
||||||
if (spatialTarget == null)
|
Vector2 rayEnd = spatialTarget.SimPosition;
|
||||||
|
Submarine targetSub = spatialTarget.Submarine;
|
||||||
|
if (targetSub != null)
|
||||||
{
|
{
|
||||||
var gotoObjective = ObjectiveManager.GetActiveObjective<AIObjectiveGoTo>();
|
rayEnd += targetSub.SimPosition;
|
||||||
spatialTarget = gotoObjective?.Target;
|
ignoredBodies = targetSub.PhysicsBody.FarseerBody.ToEnumerable();
|
||||||
}
|
}
|
||||||
if (spatialTarget == null)
|
var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
||||||
|
isBlocked = obstacle != null;
|
||||||
|
// Don't use outside waypoints when blocked by a sub, because we should use the waypoints linked to the sub instead.
|
||||||
|
UseOutsideWaypoints = isBlocked && (obstacle.UserData is not Submarine sub || sub.Info.IsRuin);
|
||||||
|
bool resetPath = false;
|
||||||
|
if (UseOutsideWaypoints)
|
||||||
{
|
{
|
||||||
UseIndoorSteeringOutside = false;
|
bool isUsingInsideWaypoints = hasValidPath && HasValidPath(nodePredicate: n => n.Submarine != null || n.Ruin != null);
|
||||||
|
if (isUsingInsideWaypoints)
|
||||||
|
{
|
||||||
|
resetPath = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
IEnumerable<FarseerPhysics.Dynamics.Body> ignoredBodies = null;
|
bool isUsingOutsideWaypoints = hasValidPath && HasValidPath(nodePredicate: n => n.Submarine == null && n.Ruin == null);
|
||||||
Vector2 rayEnd = spatialTarget.SimPosition;
|
if (isUsingOutsideWaypoints)
|
||||||
Submarine targetSub = spatialTarget.Submarine;
|
|
||||||
if (targetSub != null)
|
|
||||||
{
|
{
|
||||||
rayEnd += targetSub.SimPosition;
|
resetPath = true;
|
||||||
ignoredBodies = targetSub.PhysicsBody.FarseerBody.ToEnumerable();
|
|
||||||
}
|
}
|
||||||
var obstacle = Submarine.PickBody(SimPosition, rayEnd, ignoredBodies, collisionCategory: Physics.CollisionLevel | Physics.CollisionWall);
|
}
|
||||||
UseIndoorSteeringOutside = obstacle != null;
|
if (resetPath)
|
||||||
|
{
|
||||||
|
PathSteering.ResetPath();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else if (hasValidPath)
|
||||||
{
|
{
|
||||||
UseIndoorSteeringOutside = false;
|
obstacleRaycastTimer = obstacleRaycastIntervalShort;
|
||||||
if (hasValidPath)
|
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
||||||
|
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
||||||
{
|
{
|
||||||
obstacleRaycastTimer = obstacleRaycastIntervalShort;
|
if (connectedSub == Submarine.MainSub) { continue; }
|
||||||
// Swimming outside and using the path finder -> check that the path is not blocked with anything (the path finder doesn't know about other subs).
|
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
||||||
foreach (var connectedSub in Submarine.MainSub.GetConnectedSubs())
|
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
||||||
|
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
||||||
|
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
||||||
{
|
{
|
||||||
if (connectedSub == Submarine.MainSub) { continue; }
|
PathSteering.CurrentPath.Unreachable = true;
|
||||||
Vector2 rayStart = SimPosition - connectedSub.SimPosition;
|
break;
|
||||||
Vector2 dir = PathSteering.CurrentPath.CurrentNode.WorldPosition - WorldPosition;
|
|
||||||
Vector2 rayEnd = rayStart + dir.ClampLength(Character.AnimController.Collider.GetLocalFront().Length() * 5);
|
|
||||||
if (Submarine.CheckVisibility(rayStart, rayEnd, ignoreSubs: true) != null)
|
|
||||||
{
|
|
||||||
PathSteering.CurrentPath.Unreachable = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,10 +321,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
UseIndoorSteeringOutside = false;
|
UseOutsideWaypoints = false;
|
||||||
|
isBlocked = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Character.Submarine == null || Character.IsOnPlayerTeam && !Character.IsEscorted && !Character.IsOnFriendlyTeam(Character.Submarine.TeamID))
|
if (isOutside || Character.IsOnPlayerTeam && !Character.IsEscorted && !Character.IsOnFriendlyTeam(Character.Submarine.TeamID))
|
||||||
{
|
{
|
||||||
// Spot enemies while staying outside or inside an enemy ship.
|
// Spot enemies while staying outside or inside an enemy ship.
|
||||||
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
|
// does not apply for escorted characters, such as prisoners or terrorists who have their own behavior
|
||||||
@@ -352,12 +367,13 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bool useInsideSteering = !isOutside || isBlocked || HasValidPath() || IsCloseEnoughToTarget(steeringBuffer);
|
||||||
if (UseIndoorSteeringOutside || Character.CurrentHull?.Submarine != null || hasValidPath || IsCloseEnoughToTarget(steeringBuffer))
|
if (useInsideSteering)
|
||||||
{
|
{
|
||||||
if (steeringManager != insideSteering)
|
if (steeringManager != insideSteering)
|
||||||
{
|
{
|
||||||
insideSteering.Reset();
|
insideSteering.Reset();
|
||||||
|
PathSteering.ResetPath();
|
||||||
steeringManager = insideSteering;
|
steeringManager = insideSteering;
|
||||||
}
|
}
|
||||||
if (IsCloseEnoughToTarget(maxSteeringBuffer))
|
if (IsCloseEnoughToTarget(maxSteeringBuffer))
|
||||||
@@ -395,6 +411,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
objectiveManager.UpdateObjectives(deltaTime);
|
objectiveManager.UpdateObjectives(deltaTime);
|
||||||
|
|
||||||
|
UpdateDragged(deltaTime);
|
||||||
|
|
||||||
if (reportProblemsTimer > 0)
|
if (reportProblemsTimer > 0)
|
||||||
{
|
{
|
||||||
reportProblemsTimer -= deltaTime;
|
reportProblemsTimer -= deltaTime;
|
||||||
@@ -433,10 +451,19 @@ namespace Barotrauma
|
|||||||
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.Submarine.TeamID == Character.OriginalTeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
if (Character.Submarine != null && (Character.Submarine.TeamID == Character.TeamID || Character.Submarine.TeamID == Character.OriginalTeamID || Character.IsEscorted) && !Character.Submarine.Info.IsWreck)
|
||||||
{
|
{
|
||||||
ReportProblems();
|
ReportProblems();
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Allows bots to heal targets autonomously while swimming outside of the sub.
|
||||||
|
if (AIObjectiveRescueAll.IsValidTarget(Character, Character))
|
||||||
|
{
|
||||||
|
AddTargets<AIObjectiveRescueAll, Character>(Character, Character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
reportProblemsTimer = reportProblemsInterval;
|
reportProblemsTimer = reportProblemsInterval;
|
||||||
}
|
}
|
||||||
UpdateSpeaking();
|
SpeakAboutIssues();
|
||||||
UnequipUnnecessaryItems();
|
UnequipUnnecessaryItems();
|
||||||
reactTimer = GetReactionTime();
|
reactTimer = GetReactionTime();
|
||||||
}
|
}
|
||||||
@@ -904,17 +931,67 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
{
|
{
|
||||||
suitableContainer = targetContainer;
|
if (targetContainer != null &&
|
||||||
return true;
|
character.AIController is HumanAIController humanAI &&
|
||||||
|
humanAI.PathSteering.PathFinder.FindPath(character.SimPosition, targetContainer.SimPosition, character.Submarine, errorMsgStr: $"FindSuitableContainer ({character.DisplayName})", nodeFilter: node => node.Waypoint.CurrentHull != null).Unreachable)
|
||||||
|
{
|
||||||
|
ignoredItems.Add(targetContainer);
|
||||||
|
itemIndex = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
suitableContainer = targetContainer;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private float draggedTimer;
|
||||||
|
private float refuseDraggingTimer;
|
||||||
|
/// <summary>
|
||||||
|
/// The bot breaks free if being dragged by a human player from another team for longer than this
|
||||||
|
/// </summary>
|
||||||
|
private const float RefuseDraggingThresholdHigh = 10.0f;
|
||||||
|
/// <summary>
|
||||||
|
/// If the RefuseDraggingDuration is active (the bot recently broke free of being dragged), the bot breaks free much faster
|
||||||
|
/// </summary>
|
||||||
|
private const float RefuseDraggingThresholdLow = 0.5f;
|
||||||
|
private const float RefuseDraggingDuration = 30.0f;
|
||||||
|
|
||||||
|
private void UpdateDragged(float deltaTime)
|
||||||
|
{
|
||||||
|
if (Character.HumanPrefab is { AllowDraggingIndefinitely: true }) { return; }
|
||||||
|
if (Character.IsEscorted) { return; }
|
||||||
|
if (Character.LockHands) { return; }
|
||||||
|
|
||||||
|
//don't allow player characters who aren't in the same team to drag us for more than x seconds
|
||||||
|
if (Character.SelectedBy == null ||
|
||||||
|
!Character.SelectedBy.IsPlayer ||
|
||||||
|
Character.SelectedBy.TeamID == Character.TeamID)
|
||||||
|
{
|
||||||
|
refuseDraggingTimer -= deltaTime;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
draggedTimer += deltaTime;
|
||||||
|
if (draggedTimer > RefuseDraggingThresholdHigh ||
|
||||||
|
(refuseDraggingTimer > 0.0f && draggedTimer > RefuseDraggingThresholdLow))
|
||||||
|
{
|
||||||
|
draggedTimer = 0.0f;
|
||||||
|
refuseDraggingTimer = RefuseDraggingDuration;
|
||||||
|
Character.SelectedBy.DeselectCharacter();
|
||||||
|
Character.Speak(TextManager.Get("dialogrefusedragging").Value, delay: 0.5f, identifier: "refusedragging".ToIdentifier(), minDurationBetweenSimilar: 5.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void ReportProblems()
|
protected void ReportProblems()
|
||||||
{
|
{
|
||||||
Order newOrder = null;
|
Order newOrder = null;
|
||||||
Hull targetHull = null;
|
Hull targetHull = null;
|
||||||
bool speak = Character.SpeechImpediment < 100;
|
// for now, escorted characters use the report system to get targets but do not speak. escort-character specific dialogue could be implemented
|
||||||
|
bool speak = Character.SpeechImpediment < 100 && !Character.IsEscorted;
|
||||||
if (Character.CurrentHull != null)
|
if (Character.CurrentHull != null)
|
||||||
{
|
{
|
||||||
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
|
bool isFighting = ObjectiveManager.HasActiveObjective<AIObjectiveCombat>();
|
||||||
@@ -1013,25 +1090,21 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (newOrder != null && speak)
|
if (newOrder != null && speak)
|
||||||
{
|
{
|
||||||
// for now, escorted characters use the report system to get targets but do not speak. escort-character specific dialogue could be implemented
|
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
||||||
if (!Character.IsEscorted)
|
|
||||||
{
|
{
|
||||||
if (Character.TeamID == CharacterTeamType.FriendlyNPC)
|
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Default,
|
||||||
{
|
identifier: $"{newOrder.Prefab.Identifier}{targetHull?.RoomName ?? "null"}".ToIdentifier(),
|
||||||
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Default,
|
minDurationBetweenSimilar: 60.0f);
|
||||||
identifier: $"{newOrder.Prefab.Identifier}{targetHull?.RoomName ?? "null"}".ToIdentifier(),
|
}
|
||||||
minDurationBetweenSimilar: 60.0f);
|
else if (Character.IsOnPlayerTeam && GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
||||||
}
|
{
|
||||||
else if (Character.IsOnPlayerTeam && GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
|
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Order);
|
||||||
{
|
|
||||||
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName?.Value ?? "", givingOrderToSelf: false), ChatMessageType.Order);
|
|
||||||
#if SERVER
|
#if SERVER
|
||||||
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder
|
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder
|
||||||
.WithManualPriority(CharacterInfo.HighestManualOrderPriority)
|
.WithManualPriority(CharacterInfo.HighestManualOrderPriority)
|
||||||
.WithTargetEntity(targetHull)
|
.WithTargetEntity(targetHull)
|
||||||
.WithOrderGiver(Character), "", null, Character));
|
.WithOrderGiver(Character), "", null, Character));
|
||||||
#endif
|
#endif
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1062,21 +1135,33 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateSpeaking()
|
private void SpeakAboutIssues()
|
||||||
{
|
{
|
||||||
if (!Character.IsOnPlayerTeam) { return; }
|
if (!Character.IsOnPlayerTeam) { return; }
|
||||||
if (Character.SpeechImpediment >= 100) { return; }
|
if (Character.SpeechImpediment >= 100) { return; }
|
||||||
if (Character.Oxygen < 20.0f)
|
float minDelay = 0.5f, maxDelay = 2f;
|
||||||
|
if (Character.Oxygen < CharacterHealth.InsufficientOxygenThreshold)
|
||||||
{
|
{
|
||||||
Character.Speak(TextManager.Get("DialogLowOxygen").Value, null, Rand.Range(0.5f, 5.0f), "lowoxygen".ToIdentifier(), 30.0f);
|
string msgId = "DialogLowOxygen";
|
||||||
|
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||||
}
|
}
|
||||||
if (Character.Bleeding > 2.0f)
|
if (Character.Bleeding > 2.0f && !Character.IsMedic)
|
||||||
{
|
{
|
||||||
Character.Speak(TextManager.Get("DialogBleeding").Value, null, Rand.Range(0.5f, 5.0f), "bleeding".ToIdentifier(), 30.0f);
|
string msgId = "DialogBleeding";
|
||||||
|
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||||
}
|
}
|
||||||
if (Character.PressureTimer > 50.0f && Character.CurrentHull?.DisplayName != null)
|
if ((Character.CurrentHull == null || Character.CurrentHull.LethalPressure > 0) && !Character.IsProtectedFromPressure)
|
||||||
{
|
{
|
||||||
Character.Speak(TextManager.GetWithVariable("DialogPressure", "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, null, Rand.Range(0.5f, 5.0f), "pressure".ToIdentifier(), 30.0f);
|
if (Character.PressureProtection > 0)
|
||||||
|
{
|
||||||
|
string msgId = "DialogInsufficientPressureProtection";
|
||||||
|
Character.Speak(TextManager.Get(msgId).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||||
|
}
|
||||||
|
else if (Character.CurrentHull?.DisplayName != null)
|
||||||
|
{
|
||||||
|
string msgId = "DialogPressure";
|
||||||
|
Character.Speak(TextManager.GetWithVariable(msgId, "[roomname]", Character.CurrentHull.DisplayName, FormatCapitals.Yes).Value, delay: Rand.Range(minDelay, maxDelay), identifier: msgId.ToIdentifier(), minDurationBetweenSimilar: 30.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1205,7 +1290,7 @@ namespace Barotrauma
|
|||||||
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && attacker.CombatAction == null;
|
bool isAccidental = attacker.IsBot && !IsMentallyUnstable && !attacker.AIController.IsMentallyUnstable && attacker.CombatAction == null;
|
||||||
if (isAccidental)
|
if (isAccidental)
|
||||||
{
|
{
|
||||||
if (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold)
|
if (attacker.TeamID != Character.TeamID || (!Character.IsSecurity && cumulativeDamage > minorDamageThreshold))
|
||||||
{
|
{
|
||||||
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
|
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
|
||||||
}
|
}
|
||||||
@@ -1371,7 +1456,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (humanAI.ObjectiveManager.GetActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
|
if (humanAI.ObjectiveManager.GetLastActiveObjective<AIObjectiveCombat>()?.Enemy == attacker)
|
||||||
{
|
{
|
||||||
// Already targeting the attacker -> treat as a more serious threat.
|
// Already targeting the attacker -> treat as a more serious threat.
|
||||||
cumulativeDamage *= 2;
|
cumulativeDamage *= 2;
|
||||||
@@ -1556,7 +1641,7 @@ namespace Barotrauma
|
|||||||
hull.LethalPressure > 0 ||
|
hull.LethalPressure > 0 ||
|
||||||
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
|
hull.ConnectedGaps.Any(gap => !gap.IsRoomToRoom && gap.Open > 0.9f))
|
||||||
{
|
{
|
||||||
needsSuit = !Character.IsProtectedFromPressure;
|
needsSuit = (hull == null || hull.LethalPressure > 0) && !Character.IsImmuneToPressure;
|
||||||
return needsAir || needsSuit;
|
return needsAir || needsSuit;
|
||||||
}
|
}
|
||||||
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
if (hull.WaterPercentage > 60 || hull.OxygenPercentage < HULL_LOW_OXYGEN_PERCENTAGE + 1)
|
||||||
@@ -1656,7 +1741,7 @@ namespace Barotrauma
|
|||||||
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation != null && character.IsPlayer)
|
if (GameMain.GameSession?.Campaign?.Map?.CurrentLocation?.Reputation != null && character.IsPlayer)
|
||||||
{
|
{
|
||||||
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
|
var reputationLoss = damageAmount * Reputation.ReputationLossPerWallDamage;
|
||||||
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss);
|
GameMain.GameSession.Campaign.Map.CurrentLocation.Reputation.AddReputation(-reputationLoss, Reputation.MaxReputationLossFromWallDamage);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (accumulatedDamage <= WarningThreshold) { return; }
|
if (accumulatedDamage <= WarningThreshold) { return; }
|
||||||
@@ -1812,7 +1897,7 @@ namespace Barotrauma
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static void PropagateHullSafety(Character character, Hull hull)
|
public static void PropagateHullSafety(Character character, Hull hull)
|
||||||
{
|
{
|
||||||
DoForEachCrewMember(character, (humanAi) => humanAi.RefreshHullSafety(hull));
|
DoForEachBot(character, (humanAi) => humanAi.RefreshHullSafety(hull));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RefreshHullSafety(Hull hull)
|
private void RefreshHullSafety(Hull hull)
|
||||||
@@ -1885,7 +1970,7 @@ namespace Barotrauma
|
|||||||
private static bool AddTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
private static bool AddTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
||||||
{
|
{
|
||||||
bool targetAdded = false;
|
bool targetAdded = false;
|
||||||
DoForEachCrewMember(caller, humanAI =>
|
DoForEachBot(caller, humanAI =>
|
||||||
{
|
{
|
||||||
if (caller != humanAI.Character && caller.SpeechImpediment >= 100) { return; }
|
if (caller != humanAI.Character && caller.SpeechImpediment >= 100) { return; }
|
||||||
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
|
var objective = humanAI.ObjectiveManager.GetObjective<T1>();
|
||||||
@@ -1902,7 +1987,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void RemoveTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
public static void RemoveTargets<T1, T2>(Character caller, T2 target) where T1 : AIObjectiveLoop<T2>
|
||||||
{
|
{
|
||||||
DoForEachCrewMember(caller, humanAI =>
|
DoForEachBot(caller, humanAI =>
|
||||||
humanAI.ObjectiveManager.GetObjective<T1>()?.ReportedTargets.Remove(target));
|
humanAI.ObjectiveManager.GetObjective<T1>()?.ReportedTargets.Remove(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2024,6 +2109,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public float GetHullSafety(Hull hull, Character character, IEnumerable<Hull> visibleHulls = null)
|
public float GetHullSafety(Hull hull, Character character, IEnumerable<Hull> visibleHulls = null)
|
||||||
{
|
{
|
||||||
|
if (hull == null)
|
||||||
|
{
|
||||||
|
return CalculateHullSafety(hull, character, visibleHulls);
|
||||||
|
}
|
||||||
if (!knownHulls.TryGetValue(hull, out HullSafety hullSafety))
|
if (!knownHulls.TryGetValue(hull, out HullSafety hullSafety))
|
||||||
{
|
{
|
||||||
hullSafety = new HullSafety(CalculateHullSafety(hull, character, visibleHulls));
|
hullSafety = new HullSafety(CalculateHullSafety(hull, character, visibleHulls));
|
||||||
@@ -2038,6 +2127,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static float GetHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
public static float GetHullSafety(Hull hull, IEnumerable<Hull> visibleHulls, Character character, bool ignoreWater = false, bool ignoreOxygen = false, bool ignoreFire = false, bool ignoreEnemies = false)
|
||||||
{
|
{
|
||||||
|
if (hull == null)
|
||||||
|
{
|
||||||
|
return CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
|
||||||
|
}
|
||||||
HullSafety hullSafety;
|
HullSafety hullSafety;
|
||||||
if (character.AIController is HumanAIController controller)
|
if (character.AIController is HumanAIController controller)
|
||||||
{
|
{
|
||||||
@@ -2069,103 +2162,137 @@ namespace Barotrauma
|
|||||||
if (other.IsPet)
|
if (other.IsPet)
|
||||||
{
|
{
|
||||||
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
// Hostile NPCs are hostile to all pets, unless they are in the same team.
|
||||||
if (!sameTeam && me.TeamID == CharacterTeamType.None) { return false; }
|
return sameTeam || me.TeamID != CharacterTeamType.None;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
if (!me.IsSameSpeciesOrGroup(other)) { return false; }
|
||||||
}
|
}
|
||||||
if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
if (GameMain.GameSession?.GameMode is CampaignMode)
|
||||||
{
|
{
|
||||||
if ((me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1) ||
|
if ((me.TeamID == CharacterTeamType.FriendlyNPC && other.TeamID == CharacterTeamType.Team1) ||
|
||||||
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
|
(me.TeamID == CharacterTeamType.Team1 && other.TeamID == CharacterTeamType.FriendlyNPC))
|
||||||
{
|
{
|
||||||
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
|
Character npc = me.TeamID == CharacterTeamType.FriendlyNPC ? me : other;
|
||||||
Identifier npcFaction = npc.Faction;
|
|
||||||
Identifier currentLocationFaction = campaign.Map?.CurrentLocation?.Faction?.Prefab.Identifier ?? Identifier.Empty;
|
//NPCs that allow some campaign interaction are not turned hostile by low reputation
|
||||||
if (npcFaction.IsEmpty)
|
if (npc.CampaignInteractionType != CampaignMode.InteractionType.None) { return true; }
|
||||||
|
|
||||||
|
if (npc.AIController is HumanAIController npcAI)
|
||||||
{
|
{
|
||||||
//if faction identifier is not specified, assume the NPC is a member of the faction that owns the outpost
|
return !npcAI.IsInHostileFaction();
|
||||||
npcFaction = currentLocationFaction;
|
|
||||||
}
|
|
||||||
if (!currentLocationFaction.IsEmpty && npcFaction == currentLocationFaction)
|
|
||||||
{
|
|
||||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
|
||||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool IsActive(Character other) => other != null && !other.Removed && !other.IsDead && !other.IsUnconscious;
|
public bool IsInHostileFaction()
|
||||||
|
|
||||||
public static bool IsTrueForAllCrewMembers(Character character, Func<HumanAIController, bool> predicate)
|
|
||||||
{
|
{
|
||||||
if (character == null) { return false; }
|
if (GameMain.GameSession?.GameMode is not CampaignMode campaign) { return false; }
|
||||||
foreach (var c in Character.CharacterList)
|
if (Character.IsEscorted) { return false; }
|
||||||
|
|
||||||
|
Identifier npcFaction = Character.Faction;
|
||||||
|
Identifier currentLocationFaction = campaign.Map?.CurrentLocation?.Faction?.Prefab.Identifier ?? Identifier.Empty;
|
||||||
|
|
||||||
|
if (npcFaction.IsEmpty)
|
||||||
{
|
{
|
||||||
if (FilterCrewMember(character, c))
|
//if faction identifier is not specified, assume the NPC is a member of the faction that owns the outpost
|
||||||
{
|
npcFaction = currentLocationFaction;
|
||||||
if (!predicate(c.AIController as HumanAIController))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return true;
|
if (!currentLocationFaction.IsEmpty && npcFaction == currentLocationFaction)
|
||||||
}
|
|
||||||
|
|
||||||
public static bool IsTrueForAnyCrewMember(Character character, Func<HumanAIController, bool> predicate)
|
|
||||||
{
|
|
||||||
if (character == null) { return false; }
|
|
||||||
foreach (var c in Character.CharacterList)
|
|
||||||
{
|
{
|
||||||
if (FilterCrewMember(character, c))
|
if (campaign.CurrentLocation is { IsFactionHostile: true })
|
||||||
{
|
{
|
||||||
if (predicate(c.AIController as HumanAIController))
|
return true;
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int CountCrew(Character character, Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false)
|
public static bool IsActive(Character c) => c != null && c.Enabled && !c.IsUnconscious;
|
||||||
|
|
||||||
|
public static bool IsTrueForAllBotsInTheCrew(Character character, Func<HumanAIController, bool> predicate)
|
||||||
|
{
|
||||||
|
if (character == null) { return false; }
|
||||||
|
foreach (var c in Character.CharacterList)
|
||||||
|
{
|
||||||
|
if (!IsBotInTheCrew(character, c)) { continue; }
|
||||||
|
if (!predicate(c.AIController as HumanAIController))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool IsTrueForAnyBotInTheCrew(Character character, Func<HumanAIController, bool> predicate)
|
||||||
|
{
|
||||||
|
if (character == null) { return false; }
|
||||||
|
foreach (var c in Character.CharacterList)
|
||||||
|
{
|
||||||
|
if (!IsBotInTheCrew(character, c)) { continue; }
|
||||||
|
if (predicate(c.AIController as HumanAIController))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int CountBotsInTheCrew(Character character, Func<HumanAIController, bool> predicate = null)
|
||||||
{
|
{
|
||||||
if (character == null) { return 0; }
|
if (character == null) { return 0; }
|
||||||
int count = 0;
|
int count = 0;
|
||||||
foreach (var other in Character.CharacterList)
|
foreach (var other in Character.CharacterList)
|
||||||
{
|
{
|
||||||
if (onlyActive && !IsActive(other))
|
if (!IsBotInTheCrew(character, other)) { continue; }
|
||||||
|
if (predicate == null || predicate(other.AIController as HumanAIController))
|
||||||
{
|
{
|
||||||
continue;
|
count++;
|
||||||
}
|
|
||||||
if (onlyBots && other.IsPlayer)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (FilterCrewMember(character, other))
|
|
||||||
{
|
|
||||||
if (predicate == null || predicate(other.AIController as HumanAIController))
|
|
||||||
{
|
|
||||||
count++;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return count;
|
return count;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DoForEachCrewMember(Character character, Action<HumanAIController> action, float range = float.PositiveInfinity)
|
/// <summary>
|
||||||
|
/// Including the player characters in the same team.
|
||||||
|
/// </summary>
|
||||||
|
public bool IsTrueForAnyCrewMember(Func<Character, bool> predicate, bool onlyActive = true, bool onlyConnectedSubs = false)
|
||||||
|
{
|
||||||
|
foreach (var c in Character.CharacterList)
|
||||||
|
{
|
||||||
|
if (!IsActive(c)) { continue; }
|
||||||
|
if (c.TeamID != Character.TeamID) { continue; }
|
||||||
|
if (onlyActive && c.IsIncapacitated) { continue; }
|
||||||
|
if (onlyConnectedSubs)
|
||||||
|
{
|
||||||
|
if (Character.Submarine == null)
|
||||||
|
{
|
||||||
|
if (c.Submarine != null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c.Submarine != Character.Submarine && !Character.Submarine.GetConnectedSubs().Contains(c.Submarine))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (predicate(c))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DoForEachBot(Character character, Action<HumanAIController> action, float range = float.PositiveInfinity)
|
||||||
{
|
{
|
||||||
if (character == null) { return; }
|
if (character == null) { return; }
|
||||||
foreach (var c in Character.CharacterList)
|
foreach (var c in Character.CharacterList)
|
||||||
{
|
{
|
||||||
if (FilterCrewMember(character, c) && CheckReportRange(character, c, range))
|
if (IsBotInTheCrew(character, c) && CheckReportRange(character, c, range))
|
||||||
{
|
{
|
||||||
action(c.AIController as HumanAIController);
|
action(c.AIController as HumanAIController);
|
||||||
}
|
}
|
||||||
@@ -2185,7 +2312,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool FilterCrewMember(Character self, Character other) => other != null && !other.IsDead && !other.Removed && other.AIController is HumanAIController humanAi && humanAi.IsFriendly(self);
|
private static bool IsBotInTheCrew(Character self, Character other) => IsActive(other) && other.TeamID == self.TeamID && !other.IsIncapacitated && other.IsBot && other.AIController is HumanAIController;
|
||||||
|
|
||||||
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
|
public static bool IsItemTargetedBySomeone(ItemComponent target, CharacterTeamType team, out Character operatingCharacter)
|
||||||
{
|
{
|
||||||
@@ -2230,10 +2357,9 @@ namespace Barotrauma
|
|||||||
bool isOrder = IsOrderedToOperateThis(Character.AIController);
|
bool isOrder = IsOrderedToOperateThis(Character.AIController);
|
||||||
foreach (Character c in Character.CharacterList)
|
foreach (Character c in Character.CharacterList)
|
||||||
{
|
{
|
||||||
|
if (!IsActive(c)) { continue; }
|
||||||
if (c == Character) { continue; }
|
if (c == Character) { continue; }
|
||||||
if (c.Removed) { continue; }
|
|
||||||
if (c.TeamID != Character.TeamID) { continue; }
|
if (c.TeamID != Character.TeamID) { continue; }
|
||||||
if (c.IsIncapacitated) { continue; }
|
|
||||||
if (c.IsPlayer)
|
if (c.IsPlayer)
|
||||||
{
|
{
|
||||||
if (c.SelectedItem == target.Item)
|
if (c.SelectedItem == target.Item)
|
||||||
@@ -2301,9 +2427,9 @@ namespace Barotrauma
|
|||||||
bool isOrder = IsOrderedToRepairThis(Character.AIController as HumanAIController);
|
bool isOrder = IsOrderedToRepairThis(Character.AIController as HumanAIController);
|
||||||
foreach (var c in Character.CharacterList)
|
foreach (var c in Character.CharacterList)
|
||||||
{
|
{
|
||||||
|
if (!IsActive(c)) { continue; }
|
||||||
if (c == Character) { continue; }
|
if (c == Character) { continue; }
|
||||||
if (c.TeamID != Character.TeamID) { continue; }
|
if (c.TeamID != Character.TeamID) { continue; }
|
||||||
if (c.IsIncapacitated) { continue; }
|
|
||||||
other = c;
|
other = c;
|
||||||
if (c.IsPlayer)
|
if (c.IsPlayer)
|
||||||
{
|
{
|
||||||
@@ -2317,7 +2443,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
|
var repairItemsObjective = operatingAI.ObjectiveManager.GetObjective<AIObjectiveRepairItems>();
|
||||||
if (repairItemsObjective == null) { continue; }
|
if (repairItemsObjective == null) { continue; }
|
||||||
if (!(repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is AIObjectiveRepairItem activeObjective) || activeObjective.Item != target)
|
if (repairItemsObjective.SubObjectives.FirstOrDefault(o => o is AIObjectiveRepairItem) is not AIObjectiveRepairItem activeObjective || activeObjective.Item != target)
|
||||||
{
|
{
|
||||||
// Not targeting the same item.
|
// Not targeting the same item.
|
||||||
continue;
|
continue;
|
||||||
@@ -2352,11 +2478,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
#region Wrappers
|
#region Wrappers
|
||||||
public bool IsFriendly(Character other) => IsFriendly(Character, other);
|
public bool IsFriendly(Character other, bool onlySameTeam = false) => IsFriendly(Character, other, onlySameTeam);
|
||||||
public void DoForEachCrewMember(Action<HumanAIController> action) => DoForEachCrewMember(Character, action);
|
public bool IsTrueForAnyBotInTheCrew(Func<HumanAIController, bool> predicate) => IsTrueForAnyBotInTheCrew(Character, predicate);
|
||||||
public bool IsTrueForAnyCrewMember(Func<HumanAIController, bool> predicate) => IsTrueForAnyCrewMember(Character, predicate);
|
public bool IsTrueForAllBotsInTheCrew(Func<HumanAIController, bool> predicate) => IsTrueForAllBotsInTheCrew(Character, predicate);
|
||||||
public bool IsTrueForAllCrewMembers(Func<HumanAIController, bool> predicate) => IsTrueForAllCrewMembers(Character, predicate);
|
public int CountBotsInTheCrew(Func<HumanAIController, bool> predicate = null) => CountBotsInTheCrew(Character, predicate);
|
||||||
public int CountCrew(Func<HumanAIController, bool> predicate = null, bool onlyActive = true, bool onlyBots = false) => CountCrew(Character, predicate, onlyActive, onlyBots);
|
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,7 +180,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
private Vector2 CalculateSteeringSeek(Vector2 target, float weight, float minGapSize = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisibility = true)
|
||||||
{
|
{
|
||||||
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished;
|
bool needsNewPath = currentPath == null || currentPath.Unreachable || currentPath.Finished || currentPath.CurrentNode == null;
|
||||||
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
|
if (!needsNewPath && character.Submarine != null && character.Params.PathFinderPriority > 0.5f)
|
||||||
{
|
{
|
||||||
Vector2 targetDiff = target - currentTarget;
|
Vector2 targetDiff = target - currentTarget;
|
||||||
@@ -205,6 +205,24 @@ namespace Barotrauma
|
|||||||
if (needsNewPath || findPathTimer < -1.0f)
|
if (needsNewPath || findPathTimer < -1.0f)
|
||||||
{
|
{
|
||||||
IsPathDirty = true;
|
IsPathDirty = true;
|
||||||
|
if (!needsNewPath && findPathTimer < -1)
|
||||||
|
{
|
||||||
|
if (character.Submarine != null && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0)
|
||||||
|
{
|
||||||
|
// Not moving -> need a new path.
|
||||||
|
needsNewPath = true;
|
||||||
|
}
|
||||||
|
if (character.Submarine == null && currentPath?.CurrentNode is WayPoint wp && wp.CurrentHull != null)
|
||||||
|
{
|
||||||
|
// Current node inside, while we are outside
|
||||||
|
// -> Check that the current node is not too far (can happen e.g. if someone controls the character in the meanwhile)
|
||||||
|
float maxDist = 200;
|
||||||
|
if (Vector2.DistanceSquared(character.WorldPosition, wp.WorldPosition) > maxDist * maxDist)
|
||||||
|
{
|
||||||
|
needsNewPath = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
if (findPathTimer < 0)
|
if (findPathTimer < 0)
|
||||||
{
|
{
|
||||||
SkipCurrentPathNodes();
|
SkipCurrentPathNodes();
|
||||||
@@ -213,7 +231,7 @@ namespace Barotrauma
|
|||||||
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
pathFinder.InsideSubmarine = character.Submarine != null && !character.Submarine.Info.IsRuin;
|
||||||
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
pathFinder.ApplyPenaltyToOutsideNodes = character.Submarine != null && !character.IsProtectedFromPressure;
|
||||||
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
var newPath = pathFinder.FindPath(currentPos, target, character.Submarine, "(Character: " + character.Name + ")", minGapSize, startNodeFilter, endNodeFilter, nodeFilter, checkVisibility: checkVisibility);
|
||||||
bool useNewPath = needsNewPath || currentPath == null || currentPath.CurrentNode == null || character.Submarine != null && findPathTimer < -1 && Math.Abs(character.AnimController.TargetMovement.Combine()) <= 0;
|
bool useNewPath = needsNewPath;
|
||||||
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
if (!useNewPath && currentPath?.CurrentNode != null && newPath.Nodes.Any() && !newPath.Unreachable)
|
||||||
{
|
{
|
||||||
// Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset).
|
// Check if the new path is the same as the old, in which case we just ignore it and continue using the old path (or the progress would reset).
|
||||||
@@ -387,49 +405,57 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (character.IsClimbing && useLadders)
|
if (character.IsClimbing && useLadders)
|
||||||
{
|
{
|
||||||
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
|
if (currentLadder == null && nextLadder != null)
|
||||||
if (nextLadderSameAsCurrent || currentLadder != null && nextLadder != null && Math.Abs(currentLadder.Item.Position.X - nextLadder.Item.Position.X) < 50)
|
|
||||||
{
|
{
|
||||||
//climbing ladders -> don't move horizontally
|
// Climbing a ladder but the path is still on the node next to the ladder -> Skip the node.
|
||||||
diff.X = 0.0f;
|
NextNode(!doorsChecked);
|
||||||
}
|
}
|
||||||
//at the same height as the waypoint
|
else
|
||||||
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
|
||||||
float colliderSize = (collider.Height / 2 + collider.Radius) * 1.25f;
|
|
||||||
if (heightDiff < colliderSize)
|
|
||||||
{
|
{
|
||||||
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
bool nextLadderSameAsCurrent = IsNextLadderSameAsCurrent;
|
||||||
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
if (nextLadderSameAsCurrent || currentLadder != null && nextLadder != null && Math.Abs(currentLadder.Item.Position.X - nextLadder.Item.Position.X) < 50)
|
||||||
bool isAboveFloor = heightFromFloor > -0.1f;
|
|
||||||
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
|
||||||
if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
|
||||||
{
|
{
|
||||||
character.StopClimbing();
|
//climbing ladders -> don't move horizontally
|
||||||
|
diff.X = 0.0f;
|
||||||
}
|
}
|
||||||
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
//at the same height as the waypoint
|
||||||
|
float heightDiff = Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y);
|
||||||
|
float colliderSize = (collider.Height / 2 + collider.Radius) * 1.25f;
|
||||||
|
if (heightDiff < colliderSize)
|
||||||
{
|
{
|
||||||
// Try to change the ladder (hatches between two submarines)
|
float heightFromFloor = character.AnimController.GetHeightFromFloor();
|
||||||
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
// We need some margin, because if a hatch has closed, it's possible that the height from floor is slightly negative.
|
||||||
|
bool isAboveFloor = heightFromFloor > -0.1f;
|
||||||
|
// If the next waypoint is horizontally far, we don't want to keep holding the ladders
|
||||||
|
if (isAboveFloor && !currentPath.IsAtEndNode && (nextLadder == null || Math.Abs(currentPath.CurrentNode.WorldPosition.X - currentPath.NextNode.WorldPosition.X) > 50))
|
||||||
{
|
{
|
||||||
if (nextLadder.Item.TryInteract(character, forceSelectKey: true))
|
character.StopClimbing();
|
||||||
|
}
|
||||||
|
else if (nextLadder != null && !nextLadderSameAsCurrent)
|
||||||
|
{
|
||||||
|
// Try to change the ladder (hatches between two submarines)
|
||||||
|
if (character.SelectedSecondaryItem != nextLadder.Item && character.CanInteractWith(nextLadder.Item))
|
||||||
{
|
{
|
||||||
NextNode(!doorsChecked);
|
if (nextLadder.Item.TryInteract(character, forceSelectKey: true))
|
||||||
|
{
|
||||||
|
NextNode(!doorsChecked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10)
|
||||||
|
{
|
||||||
|
NextNode(!doorsChecked);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!currentPath.IsAtEndNode && (isAboveFloor || nextLadderSameAsCurrent || nextLadder == null && Math.Abs(diff.Y) < 10))
|
else if (nextLadder != null)
|
||||||
{
|
{
|
||||||
NextNode(!doorsChecked);
|
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
|
||||||
}
|
{
|
||||||
}
|
//if the current node is below the character and the next one is above (or vice versa)
|
||||||
else if (nextLadder != null)
|
//and both are on ladders, we can skip directly to the next one
|
||||||
{
|
//e.g. no point in going down to reach the starting point of a path when we could go directly to the one above
|
||||||
//if the current node is below the character and the next one is above (or vice versa)
|
NextNode(!doorsChecked);
|
||||||
//and both are on ladders, we can skip directly to the next one
|
}
|
||||||
//e.g. no point in going down to reach the starting point of a path when we could go directly to the one above
|
|
||||||
if (Math.Sign(currentPath.CurrentNode.WorldPosition.Y - character.WorldPosition.Y) != Math.Sign(currentPath.NextNode.WorldPosition.Y - character.WorldPosition.Y))
|
|
||||||
{
|
|
||||||
NextNode(!doorsChecked);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ConvertUnits.ToSimUnits(diff);
|
return ConvertUnits.ToSimUnits(diff);
|
||||||
@@ -486,7 +512,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
|
float targetDistance = Math.Max(colliderSize.X / 2 * margin, minWidth / 2);
|
||||||
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow && (door == null || door.CanBeTraversed))
|
if (horizontalDistance < targetDistance && !isTargetTooHigh && !isTargetTooLow && currentLadder == null && (door == null || door.CanBeTraversed))
|
||||||
{
|
{
|
||||||
NextNode(!doorsChecked);
|
NextNode(!doorsChecked);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ namespace Barotrauma
|
|||||||
foreach (Affliction affliction in afflictions)
|
foreach (Affliction affliction in afflictions)
|
||||||
{
|
{
|
||||||
var currentEffect = affliction.GetActiveEffect();
|
var currentEffect = affliction.GetActiveEffect();
|
||||||
if (currentEffect != null && !string.IsNullOrEmpty(currentEffect.DialogFlag.Value) && !currentFlags.Contains(currentEffect.DialogFlag))
|
if (currentEffect is { DialogFlag.IsEmpty: false } && !currentFlags.Contains(currentEffect.DialogFlag))
|
||||||
{
|
{
|
||||||
currentFlags.Add(currentEffect.DialogFlag);
|
currentFlags.Add(currentEffect.DialogFlag);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -506,15 +506,29 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static bool CanEquip(Character character, Item item)
|
protected static bool CanEquip(Character character, Item item, bool allowWearing)
|
||||||
{
|
{
|
||||||
bool canEquip = item != null;
|
if (item == null) { return false; }
|
||||||
if (canEquip && !item.AllowedSlots.Contains(InvSlotType.Any))
|
bool canEquip = false;
|
||||||
|
if (item.AllowedSlots.Contains(InvSlotType.Any))
|
||||||
|
{
|
||||||
|
if (character.Inventory.IsAnySlotAvailable(item))
|
||||||
|
{
|
||||||
|
canEquip = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!canEquip)
|
||||||
{
|
{
|
||||||
canEquip = false;
|
|
||||||
var inv = character.Inventory;
|
var inv = character.Inventory;
|
||||||
foreach (var allowedSlot in item.AllowedSlots)
|
foreach (var allowedSlot in item.AllowedSlots)
|
||||||
{
|
{
|
||||||
|
if (!allowWearing)
|
||||||
|
{
|
||||||
|
if (!allowedSlot.HasFlag(InvSlotType.RightHand) && !allowedSlot.HasFlag(InvSlotType.LeftHand))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
foreach (var slotType in inv.SlotTypes)
|
foreach (var slotType in inv.SlotTypes)
|
||||||
{
|
{
|
||||||
if (!allowedSlot.HasFlag(slotType)) { continue; }
|
if (!allowedSlot.HasFlag(slotType)) { continue; }
|
||||||
@@ -530,18 +544,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return canEquip;
|
return canEquip && character.Inventory.CanBePut(item);
|
||||||
}
|
|
||||||
protected bool CheckItemIdentifiersOrTags(Item item, ImmutableHashSet<Identifier> identifiersOrTags)
|
|
||||||
{
|
|
||||||
if (identifiersOrTags.Contains(item.Prefab.Identifier)) { return true; }
|
|
||||||
foreach (var identifier in identifiersOrTags)
|
|
||||||
{
|
|
||||||
if (item.HasTag(identifier)) { return true; }
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected bool CanEquip(Item item) => CanEquip(character, item);
|
protected bool CanEquip(Item item, bool allowWearing) => CanEquip(character, item, allowWearing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-5
@@ -64,7 +64,6 @@ namespace Barotrauma
|
|||||||
if (subObjectives.Any()) { return; }
|
if (subObjectives.Any()) { return; }
|
||||||
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
|
if (HumanAIController.FindSuitableContainer(character, item, ignoredContainers, ref itemIndex, out Item suitableContainer))
|
||||||
{
|
{
|
||||||
itemIndex = 0;
|
|
||||||
if (suitableContainer != null)
|
if (suitableContainer != null)
|
||||||
{
|
{
|
||||||
bool equip = item.GetComponent<Holdable>() != null ||
|
bool equip = item.GetComponent<Holdable>() != null ||
|
||||||
@@ -112,10 +111,7 @@ namespace Barotrauma
|
|||||||
Abandon = true;
|
Abandon = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||||
{
|
|
||||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckObjectiveSpecific()
|
protected override bool CheckObjectiveSpecific()
|
||||||
|
|||||||
+1
-1
@@ -121,7 +121,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return CanEquip(character, item);
|
return CanEquip(character, item, allowWearing: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void OnDeselected()
|
public override void OnDeselected()
|
||||||
|
|||||||
+54
-40
@@ -170,13 +170,33 @@ namespace Barotrauma
|
|||||||
return Priority;
|
return Priority;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
float damageFactor = MathUtils.InverseLerp(0.0f, 5.0f, character.GetDamageDoneByAttacker(Enemy) / 100.0f);
|
if (TargetEliminated)
|
||||||
Priority = TargetEliminated ? 0 : Math.Min((95 + damageFactor) * PriorityModifier, 100);
|
|
||||||
if (Priority > 0)
|
|
||||||
{
|
{
|
||||||
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
Priority = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 91-100
|
||||||
|
float minPriority = AIObjectiveManager.EmergencyObjectivePriority + 1;
|
||||||
|
float maxPriority = AIObjectiveManager.MaxObjectivePriority;
|
||||||
|
float priorityScale = maxPriority - minPriority;
|
||||||
|
float xDist = Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X);
|
||||||
|
float yDist = Math.Abs(character.WorldPosition.Y - Enemy.WorldPosition.Y);
|
||||||
|
if (HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull))
|
||||||
{
|
{
|
||||||
Priority = 0;
|
xDist /= 2;
|
||||||
|
yDist /= 2;
|
||||||
|
}
|
||||||
|
float distanceFactor = MathUtils.InverseLerp(3000, 0, xDist + yDist * 5);
|
||||||
|
float devotion = CumulatedDevotion / 100;
|
||||||
|
float additionalPriority = MathHelper.Lerp(0, priorityScale, Math.Clamp(devotion + distanceFactor, 0, 1));
|
||||||
|
Priority = Math.Min((minPriority + additionalPriority) * PriorityModifier, maxPriority);
|
||||||
|
if (Priority > 0)
|
||||||
|
{
|
||||||
|
if (EnemyAIController.IsLatchedToSomeoneElse(Enemy, character))
|
||||||
|
{
|
||||||
|
Priority = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Priority;
|
return Priority;
|
||||||
@@ -312,12 +332,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
AskHelp();
|
|
||||||
Retreat(deltaTime);
|
Retreat(deltaTime);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case CombatMode.Retreat:
|
case CombatMode.Retreat:
|
||||||
AskHelp();
|
|
||||||
Retreat(deltaTime);
|
Retreat(deltaTime);
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@@ -352,7 +370,7 @@ namespace Barotrauma
|
|||||||
Weapon = null;
|
Weapon = null;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (WeaponComponent.IsNotEmpty(character))
|
if (!WeaponComponent.IsEmpty(character))
|
||||||
{
|
{
|
||||||
// All good, the weapon is loaded
|
// All good, the weapon is loaded
|
||||||
break;
|
break;
|
||||||
@@ -470,7 +488,7 @@ namespace Barotrauma
|
|||||||
// Not in the inventory anymore or cannot find the weapon component
|
// Not in the inventory anymore or cannot find the weapon component
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!WeaponComponent.IsNotEmpty(character))
|
if (WeaponComponent.IsEmpty(character))
|
||||||
{
|
{
|
||||||
// Try reloading (and seek ammo)
|
// Try reloading (and seek ammo)
|
||||||
if (!Reload(seekAmmo))
|
if (!Reload(seekAmmo))
|
||||||
@@ -541,7 +559,7 @@ namespace Barotrauma
|
|||||||
priority /= 2;
|
priority /= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!weapon.IsNotEmpty(character))
|
if (weapon.IsEmpty(character))
|
||||||
{
|
{
|
||||||
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
if (weapon is RangedWeapon && !isAllowedToSeekWeapons)
|
||||||
{
|
{
|
||||||
@@ -554,7 +572,6 @@ namespace Barotrauma
|
|||||||
priority /= 2;
|
priority /= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Enemy.Params.Health.StunImmunity)
|
if (Enemy.Params.Health.StunImmunity)
|
||||||
{
|
{
|
||||||
if (weapon.Item.HasTag("stunner"))
|
if (weapon.Item.HasTag("stunner"))
|
||||||
@@ -750,7 +767,7 @@ namespace Barotrauma
|
|||||||
private bool Equip()
|
private bool Equip()
|
||||||
{
|
{
|
||||||
if (character.LockHands) { return false; }
|
if (character.LockHands) { return false; }
|
||||||
if (!WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
|
if (WeaponComponent.IsEmpty(character))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -783,6 +800,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void Retreat(float deltaTime)
|
private void Retreat(float deltaTime)
|
||||||
{
|
{
|
||||||
|
if (!Enemy.IsHuman)
|
||||||
|
{
|
||||||
|
SpeakRetreating();
|
||||||
|
}
|
||||||
RemoveFollowTarget();
|
RemoveFollowTarget();
|
||||||
RemoveSubObjective(ref seekAmmunitionObjective);
|
RemoveSubObjective(ref seekAmmunitionObjective);
|
||||||
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
|
if (retreatObjective != null && retreatObjective.Target != retreatTarget)
|
||||||
@@ -793,6 +814,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
// Swim away
|
// Swim away
|
||||||
SteeringManager.Reset();
|
SteeringManager.Reset();
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition));
|
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(character.WorldPosition - Enemy.WorldPosition));
|
||||||
SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2);
|
SteeringManager.SteeringAvoid(deltaTime, 5, weight: 2);
|
||||||
return;
|
return;
|
||||||
@@ -819,7 +841,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager)
|
TryAddSubObjective(ref retreatObjective, () => new AIObjectiveGoTo(retreatTarget, character, objectiveManager)
|
||||||
{
|
{
|
||||||
UsePathingOutside = false
|
UsePathingOutside = false,
|
||||||
|
SpeakIfFails = false
|
||||||
},
|
},
|
||||||
onAbandon: () =>
|
onAbandon: () =>
|
||||||
{
|
{
|
||||||
@@ -861,6 +884,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range))
|
if (sqrDistance > MathUtils.Pow2(meleeWeapon.Range))
|
||||||
{
|
{
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
// Swim towards the target
|
// Swim towards the target
|
||||||
SteeringManager.Reset();
|
SteeringManager.Reset();
|
||||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10);
|
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Enemy), weight: 10);
|
||||||
@@ -882,7 +906,8 @@ namespace Barotrauma
|
|||||||
UsePathingOutside = false,
|
UsePathingOutside = false,
|
||||||
IgnoreIfTargetDead = true,
|
IgnoreIfTargetDead = true,
|
||||||
TargetName = Enemy.DisplayName,
|
TargetName = Enemy.DisplayName,
|
||||||
AlwaysUseEuclideanDistance = false
|
AlwaysUseEuclideanDistance = false,
|
||||||
|
SpeakIfFails = false
|
||||||
},
|
},
|
||||||
onAbandon: () =>
|
onAbandon: () =>
|
||||||
{
|
{
|
||||||
@@ -966,7 +991,7 @@ namespace Barotrauma
|
|||||||
item.GetComponent<RangedWeapon>() != null)
|
item.GetComponent<RangedWeapon>() != null)
|
||||||
{
|
{
|
||||||
item.Drop(character);
|
item.Drop(character);
|
||||||
character.Inventory.TryPutItem(item, character, CharacterInventory.anySlot);
|
character.Inventory.TryPutItem(item, character, CharacterInventory.AnySlot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1028,54 +1053,43 @@ namespace Barotrauma
|
|||||||
if (Weapon.OwnInventory == null) { return true; }
|
if (Weapon.OwnInventory == null) { return true; }
|
||||||
// Eject empty ammo
|
// Eject empty ammo
|
||||||
HumanAIController.UnequipEmptyItems(Weapon);
|
HumanAIController.UnequipEmptyItems(Weapon);
|
||||||
RelatedItem item = null;
|
|
||||||
Item ammunition = null;
|
|
||||||
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
ImmutableHashSet<Identifier> ammunitionIdentifiers = null;
|
||||||
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
if (WeaponComponent.requiredItems.ContainsKey(RelatedItem.RelationType.Contained))
|
||||||
{
|
{
|
||||||
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
foreach (RelatedItem requiredItem in WeaponComponent.requiredItems[RelatedItem.RelationType.Contained])
|
||||||
{
|
{
|
||||||
ammunition = Weapon.OwnInventory.AllItems.FirstOrDefault(it => it.Condition > 0 && requiredItem.MatchesItem(it));
|
if (Weapon.OwnInventory.AllItems.Any(it => it.Condition > 0 && requiredItem.MatchesItem(it))) { continue; }
|
||||||
if (ammunition != null)
|
|
||||||
{
|
|
||||||
// Ammunition still remaining
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
item = requiredItem;
|
|
||||||
ammunitionIdentifiers = requiredItem.Identifiers;
|
ammunitionIdentifiers = requiredItem.Identifiers;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (WeaponComponent is MeleeWeapon meleeWeapon)
|
else if (WeaponComponent is MeleeWeapon meleeWeapon)
|
||||||
{
|
{
|
||||||
ammunitionIdentifiers = meleeWeapon.PreferredContainedItems;
|
ammunitionIdentifiers = meleeWeapon.PreferredContainedItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
// No ammo
|
// No ammo
|
||||||
if (ammunition == null)
|
if (ammunitionIdentifiers != null)
|
||||||
{
|
{
|
||||||
if (ammunitionIdentifiers != null)
|
// Try reload ammunition from inventory
|
||||||
|
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
||||||
|
Item ammunition = character.Inventory.FindItem(i => i.HasIdentifierOrTags(ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
||||||
|
if (ammunition != null)
|
||||||
{
|
{
|
||||||
// Try reload ammunition from inventory
|
var container = Weapon.GetComponent<ItemContainer>();
|
||||||
static bool IsInsideHeadset(Item i) => i.ParentInventory?.Owner is Item ownerItem && ownerItem.HasTag("mobileradio");
|
if (!container.Inventory.TryPutItem(ammunition, user: character))
|
||||||
ammunition = character.Inventory.FindItem(i => CheckItemIdentifiersOrTags(i, ammunitionIdentifiers) && i.Condition > 0 && !IsInsideHeadset(i), recursive: true);
|
|
||||||
if (ammunition != null)
|
|
||||||
{
|
{
|
||||||
var container = Weapon.GetComponent<ItemContainer>();
|
if (ammunition.ParentInventory == character.Inventory)
|
||||||
if (!container.Inventory.TryPutItem(ammunition, null))
|
|
||||||
{
|
{
|
||||||
if (ammunition.ParentInventory == character.Inventory)
|
ammunition.Drop(character);
|
||||||
{
|
|
||||||
ammunition.Drop(character);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (WeaponComponent.HasRequiredContainedItems(character, addMessage: false))
|
if (!WeaponComponent.IsEmpty(character))
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (ammunition == null && !HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
|
else if (!HoldPosition && IsOffensiveOrArrest && seekAmmo && ammunitionIdentifiers != null)
|
||||||
{
|
{
|
||||||
SeekAmmunition(ammunitionIdentifiers);
|
SeekAmmunition(ammunitionIdentifiers);
|
||||||
}
|
}
|
||||||
@@ -1270,7 +1284,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDuration: 30);
|
private void SpeakNoWeapons() => Speak("dialogcombatnoweapons".ToIdentifier(), delay: 0, minDuration: 30);
|
||||||
private void AskHelp() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
|
private void SpeakRetreating() => Speak("dialogcombatretreating".ToIdentifier(), delay: Rand.Range(0f, 1f), minDuration: 20);
|
||||||
|
|
||||||
private void Speak(Identifier textIdentifier, float delay, float minDuration)
|
private void Speak(Identifier textIdentifier, float delay, float minDuration)
|
||||||
{
|
{
|
||||||
|
|||||||
+5
-5
@@ -109,7 +109,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private bool CheckItem(Item item)
|
private bool CheckItem(Item item)
|
||||||
{
|
{
|
||||||
return CheckItemIdentifiersOrTags(item, itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
return item.HasIdentifierOrTags(itemIdentifiers) && item.ConditionPercentage >= ConditionLevel && item.HasAccess(character);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
@@ -156,15 +156,15 @@ namespace Barotrauma
|
|||||||
Inventory originalInventory = ItemToContain.ParentInventory;
|
Inventory originalInventory = ItemToContain.ParentInventory;
|
||||||
var slots = originalInventory?.FindIndices(ItemToContain);
|
var slots = originalInventory?.FindIndices(ItemToContain);
|
||||||
|
|
||||||
static bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
|
bool TryPutItem(Inventory inventory, int? targetSlot, Item itemToContain)
|
||||||
{
|
{
|
||||||
if (targetSlot.HasValue)
|
if (targetSlot.HasValue)
|
||||||
{
|
{
|
||||||
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: null);
|
return inventory.TryPutItem(itemToContain, targetSlot.Value, allowSwapping: false, allowCombine: false, user: character);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return inventory.TryPutItem(itemToContain, user: null);
|
return inventory.TryPutItem(itemToContain, user: character);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ namespace Barotrauma
|
|||||||
ItemToContain == null || ItemToContain.Removed ||
|
ItemToContain == null || ItemToContain.Removed ||
|
||||||
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
!ItemToContain.IsOwnedBy(character) || container.Item.GetRootInventoryOwner() is Character c && c != character,
|
||||||
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
|
SpeakIfFails = !objectiveManager.IsCurrentOrder<AIObjectiveCleanupItems>(),
|
||||||
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.DefaultReach)
|
endNodeFilter = n => Vector2.DistanceSquared(n.Waypoint.WorldPosition, container.Item.WorldPosition) <= MathUtils.Pow2(AIObjectiveGetItem.MaxReach)
|
||||||
},
|
},
|
||||||
onAbandon: () => Abandon = true,
|
onAbandon: () => Abandon = true,
|
||||||
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
onCompleted: () => RemoveSubObjective(ref goToObjective));
|
||||||
|
|||||||
+1
-1
@@ -66,7 +66,7 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
float devotion = CumulatedDevotion / 100;
|
float devotion = CumulatedDevotion / 100;
|
||||||
Priority = MathHelper.Lerp(0, 100, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
Priority = MathHelper.Lerp(0, AIObjectiveManager.MaxObjectivePriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Priority;
|
return Priority;
|
||||||
|
|||||||
+9
-4
@@ -28,7 +28,7 @@ namespace Barotrauma
|
|||||||
if (character.IsSecurity) { return 100; }
|
if (character.IsSecurity) { return 100; }
|
||||||
if (objectiveManager.IsOrder(this)) { return 100; }
|
if (objectiveManager.IsOrder(this)) { return 100; }
|
||||||
// If there's any security officers onboard, leave fighting for them.
|
// If there's any security officers onboard, leave fighting for them.
|
||||||
return HumanAIController.IsTrueForAnyCrewMember(c => c.Character.IsSecurity && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine) ? 0 : 100;
|
return HumanAIController.IsTrueForAnyCrewMember(c => c.IsSecurity, onlyActive: true, onlyConnectedSubs: true) ? 0 : 100;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override AIObjective ObjectiveConstructor(Character target)
|
protected override AIObjective ObjectiveConstructor(Character target)
|
||||||
@@ -37,8 +37,7 @@ namespace Barotrauma
|
|||||||
var combatObjective = new AIObjectiveCombat(character, target, combatMode, objectiveManager, PriorityModifier);
|
var combatObjective = new AIObjectiveCombat(character, target, combatMode, objectiveManager, PriorityModifier);
|
||||||
if (character.TeamID == CharacterTeamType.FriendlyNPC && target.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
if (character.TeamID == CharacterTeamType.FriendlyNPC && target.TeamID == CharacterTeamType.Team1 && GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||||
{
|
{
|
||||||
var reputation = campaign.Map?.CurrentLocation?.Reputation;
|
if (campaign.CurrentLocation is { IsFactionHostile: true })
|
||||||
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
|
|
||||||
{
|
{
|
||||||
combatObjective.holdFireCondition = () =>
|
combatObjective.holdFireCondition = () =>
|
||||||
{
|
{
|
||||||
@@ -66,7 +65,13 @@ namespace Barotrauma
|
|||||||
if (target.CurrentHull == null) { return false; }
|
if (target.CurrentHull == null) { return false; }
|
||||||
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
if (HumanAIController.IsFriendly(character, target)) { return false; }
|
||||||
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
if (!character.Submarine.IsConnectedTo(target.Submarine)) { return false; }
|
||||||
if (!targetCharactersInOtherSubs && character.Submarine.TeamID != target.Submarine.TeamID && character.Submarine.TeamID != character.OriginalTeamID) { return false; }
|
if (!targetCharactersInOtherSubs)
|
||||||
|
{
|
||||||
|
if (character.Submarine.TeamID != target.Submarine.TeamID && character.OriginalTeamID != target.Submarine.TeamID)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
if (target.HasAbilityFlag(AbilityFlags.IgnoredByEnemyAI)) { return false; }
|
||||||
if (target.IsArrested) { return false; }
|
if (target.IsArrested) { return false; }
|
||||||
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
if (EnemyAIController.IsLatchedToSomeoneElse(target, character)) { return false; }
|
||||||
|
|||||||
+1
-1
@@ -79,7 +79,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (mask != targetItem)
|
if (mask != targetItem)
|
||||||
{
|
{
|
||||||
character.Inventory.TryPutItem(mask, character, CharacterInventory.anySlot);
|
character.Inventory.TryPutItem(mask, character, CharacterInventory.AnySlot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-19
@@ -52,17 +52,14 @@ namespace Barotrauma
|
|||||||
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
objectiveManager.HasOrder<AIObjectiveReturn>(o => o.Priority > 0) ||
|
||||||
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
objectiveManager.HasActiveObjective<AIObjectiveRescue>() ||
|
||||||
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
objectiveManager.Objectives.Any(o => o is AIObjectiveCombat && o.Priority > 0))
|
||||||
&& ((character.IsImmuneToPressure && !character.IsLowInOxygen)|| HumanAIController.HasDivingSuit(character)) ? 0 : 100;
|
&& ((!character.IsLowInOxygen && character.IsImmuneToPressure)|| HumanAIController.HasDivingSuit(character)) ? 0 : AIObjectiveManager.EmergencyObjectivePriority - 10;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
|
if ((character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false)) ||
|
||||||
(HumanAIController.NeedsDivingGear(character.CurrentHull, out bool needsSuit) &&
|
NeedMoreDivingGear(character.CurrentHull, AIObjectiveFindDivingGear.GetMinOxygen(character)))
|
||||||
(needsSuit ?
|
|
||||||
!HumanAIController.HasDivingSuit(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)) :
|
|
||||||
!HumanAIController.HasDivingGear(character, conditionPercentage: AIObjectiveFindDivingGear.GetMinOxygen(character)))))
|
|
||||||
{
|
{
|
||||||
Priority = 100;
|
Priority = AIObjectiveManager.MaxObjectivePriority;
|
||||||
}
|
}
|
||||||
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
|
else if ((objectiveManager.IsCurrentOrder<AIObjectiveGoTo>() || objectiveManager.IsCurrentOrder<AIObjectiveReturn>()) &&
|
||||||
character.Submarine != null && !character.IsOnFriendlyTeam(character.Submarine.TeamID))
|
character.Submarine != null && !character.IsOnFriendlyTeam(character.Submarine.TeamID))
|
||||||
@@ -75,11 +72,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Priority = 0;
|
Priority = 0;
|
||||||
}
|
}
|
||||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
|
||||||
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
if (divingGearObjective != null && !divingGearObjective.IsCompleted && divingGearObjective.CanBeCompleted)
|
||||||
{
|
{
|
||||||
// Boost the priority while seeking the diving gear
|
// Boost the priority while seeking the diving gear
|
||||||
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.HighestOrderPriority + 20, 100));
|
Priority = Math.Max(Priority, Math.Min(AIObjectiveManager.EmergencyObjectivePriority - 1, AIObjectiveManager.MaxObjectivePriority));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return Priority;
|
return Priority;
|
||||||
@@ -111,7 +108,7 @@ namespace Barotrauma
|
|||||||
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
if (currenthullSafety > HumanAIController.HULL_SAFETY_THRESHOLD)
|
||||||
{
|
{
|
||||||
Priority -= priorityDecrease * deltaTime;
|
Priority -= priorityDecrease * deltaTime;
|
||||||
if (currenthullSafety >= 100)
|
if (currenthullSafety >= 100 && !character.IsLowInOxygen)
|
||||||
{
|
{
|
||||||
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
|
// Reduce the priority to zero so that the bot can get switch to other objectives immediately, e.g. when entering the airlock.
|
||||||
Priority = 0;
|
Priority = 0;
|
||||||
@@ -122,7 +119,7 @@ namespace Barotrauma
|
|||||||
float dangerFactor = (100 - currenthullSafety) / 100;
|
float dangerFactor = (100 - currenthullSafety) / 100;
|
||||||
Priority += dangerFactor * priorityIncrease * deltaTime;
|
Priority += dangerFactor * priorityIncrease * deltaTime;
|
||||||
}
|
}
|
||||||
Priority = MathHelper.Clamp(Priority, 0, 100);
|
Priority = MathHelper.Clamp(Priority, 0, AIObjectiveManager.MaxObjectivePriority);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -138,7 +135,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (resetPriority) { return; }
|
if (resetPriority) { return; }
|
||||||
var currentHull = character.CurrentHull;
|
var currentHull = character.CurrentHull;
|
||||||
bool dangerousPressure = !character.IsProtectedFromPressure && (currentHull == null || currentHull.LethalPressure > 0);
|
bool dangerousPressure = (currentHull == null || currentHull.LethalPressure > 0) && !character.IsProtectedFromPressure;
|
||||||
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
bool shouldActOnSuffocation = character.IsLowInOxygen && !character.AnimController.HeadInWater && HumanAIController.HasDivingSuit(character, requireOxygenTank: false);
|
||||||
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
|
if (!character.LockHands && (!dangerousPressure || shouldActOnSuffocation || cannotFindSafeHull))
|
||||||
{
|
{
|
||||||
@@ -200,16 +197,11 @@ namespace Barotrauma
|
|||||||
UpdateSimpleEscape(deltaTime);
|
UpdateSimpleEscape(deltaTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
|
searchHullTimer = SearchHullInterval * Rand.Range(0.9f, 1.1f);
|
||||||
previousSafeHull = currentSafeHull;
|
previousSafeHull = currentSafeHull;
|
||||||
currentSafeHull = potentialSafeHull;
|
currentSafeHull = potentialSafeHull;
|
||||||
|
cannotFindSafeHull = currentSafeHull == null || NeedMoreDivingGear(currentSafeHull);
|
||||||
cannotFindSafeHull = currentSafeHull == null || HumanAIController.NeedsDivingGear(currentSafeHull, out _);
|
currentSafeHull ??= previousSafeHull;
|
||||||
if (currentSafeHull == null)
|
|
||||||
{
|
|
||||||
currentSafeHull = previousSafeHull;
|
|
||||||
}
|
|
||||||
if (currentSafeHull != null && currentSafeHull != currentHull)
|
if (currentSafeHull != null && currentSafeHull != currentHull)
|
||||||
{
|
{
|
||||||
if (goToObjective?.Target != currentSafeHull)
|
if (goToObjective?.Target != currentSafeHull)
|
||||||
@@ -219,6 +211,7 @@ namespace Barotrauma
|
|||||||
TryAddSubObjective(ref goToObjective,
|
TryAddSubObjective(ref goToObjective,
|
||||||
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
|
constructor: () => new AIObjectiveGoTo(currentSafeHull, character, objectiveManager, getDivingGearIfNeeded: true)
|
||||||
{
|
{
|
||||||
|
SpeakIfFails = false,
|
||||||
AllowGoingOutside =
|
AllowGoingOutside =
|
||||||
character.IsProtectedFromPressure ||
|
character.IsProtectedFromPressure ||
|
||||||
character.CurrentHull == null ||
|
character.CurrentHull == null ||
|
||||||
@@ -300,6 +293,7 @@ namespace Barotrauma
|
|||||||
//only move if we haven't reached the edge of the room
|
//only move if we haven't reached the edge of the room
|
||||||
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
|
if (escapeVel.X < 0 && character.Position.X > left || escapeVel.X > 0 && character.Position.X < right)
|
||||||
{
|
{
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
character.AIController.SteeringManager.SteeringManual(deltaTime, escapeVel);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -349,7 +343,6 @@ namespace Barotrauma
|
|||||||
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
|
if (ignoredHulls != null && ignoredHulls.Contains(hull)) { continue; }
|
||||||
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
|
if (HumanAIController.UnreachableHulls.Contains(hull)) { continue; }
|
||||||
if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; }
|
if (connectedSubs != null && !connectedSubs.Contains(hull.Submarine)) { continue; }
|
||||||
|
|
||||||
//sort the hulls based on distance and which sub they're in
|
//sort the hulls based on distance and which sub they're in
|
||||||
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
|
//tends to make the method much faster, because we find a potential hull earlier and can discard further-away hulls more easily
|
||||||
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
|
//(for instance, an NPC in an outpost might otherwise go through all the hulls in the main sub first and do tons of expensive
|
||||||
@@ -493,5 +486,18 @@ namespace Barotrauma
|
|||||||
cannotFindDivingGear = false;
|
cannotFindDivingGear = false;
|
||||||
cannotFindSafeHull = false;
|
cannotFindSafeHull = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private bool NeedMoreDivingGear(Hull targetHull, float minOxygen = 0)
|
||||||
|
{
|
||||||
|
if (!HumanAIController.NeedsDivingGear(targetHull, out bool needsSuit)) { return false; }
|
||||||
|
if (needsSuit)
|
||||||
|
{
|
||||||
|
return !HumanAIController.HasDivingSuit(character, minOxygen);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return !HumanAIController.HasDivingGear(character, minOxygen);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+43
-27
@@ -37,40 +37,56 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Priority = 0;
|
Priority = 0;
|
||||||
Abandon = true;
|
Abandon = true;
|
||||||
|
return Priority;
|
||||||
}
|
}
|
||||||
else if (HumanAIController.IsTrueForAnyCrewMember(
|
float coopMultiplier = 1;
|
||||||
other => other != HumanAIController &&
|
foreach (var c in Character.CharacterList)
|
||||||
other.Character.IsBot &&
|
|
||||||
other.ObjectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks &&
|
|
||||||
fixLeaks.SubObjectives.Any(so => so is AIObjectiveFixLeak fixObjective && fixObjective.Leak == Leak)))
|
|
||||||
{
|
{
|
||||||
Priority = 0;
|
if (!HumanAIController.IsActive(c)) { continue; }
|
||||||
|
if (c.TeamID != character.TeamID) { continue; }
|
||||||
|
if (c == character) { continue; }
|
||||||
|
if (c.IsPlayer) { continue; }
|
||||||
|
if (c.AIController is HumanAIController otherAI )
|
||||||
|
{
|
||||||
|
if (otherAI.ObjectiveManager.GetFirstActiveObjective<AIObjectiveFixLeak>() is AIObjectiveFixLeak fixLeak)
|
||||||
|
{
|
||||||
|
if (fixLeak.Leak == Leak)
|
||||||
|
{
|
||||||
|
// Ignore leaks that others are already targeting
|
||||||
|
Priority = 0;
|
||||||
|
return Priority;
|
||||||
|
}
|
||||||
|
if (fixLeak.Leak.FlowTargetHull == Leak.FlowTargetHull)
|
||||||
|
{
|
||||||
|
// Reduce the priority of leaks that others should be targeting
|
||||||
|
coopMultiplier = 0.1f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
float reduction = isPriority ? 1 : 2;
|
||||||
|
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
|
||||||
|
if (operateObjective != null && objectiveManager.GetFirstActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
|
||||||
|
{
|
||||||
|
// Prioritize leaks that we are already fixing
|
||||||
|
Priority = maxPriority;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float reduction = isPriority ? 1 : 2;
|
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
||||||
float maxPriority = AIObjectiveManager.LowestOrderPriority - reduction;
|
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
||||||
if (operateObjective != null && objectiveManager.GetActiveObjective<AIObjectiveFixLeaks>() is AIObjectiveFixLeaks fixLeaks && fixLeaks.CurrentSubObjective == this)
|
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
||||||
|
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
||||||
|
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
||||||
|
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
|
||||||
{
|
{
|
||||||
// Prioritize leaks that we are already fixing
|
// Double the distance when the leak can be accessed from the current hull.
|
||||||
Priority = maxPriority;
|
distanceFactor *= 2;
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
float xDist = Math.Abs(character.WorldPosition.X - Leak.WorldPosition.X);
|
|
||||||
float yDist = Math.Abs(character.WorldPosition.Y - Leak.WorldPosition.Y);
|
|
||||||
// Vertical distance matters more than horizontal (climbing up/down is harder than moving horizontally).
|
|
||||||
// If the target is close, ignore the distance factor alltogether so that we keep fixing the leaks that are nearby.
|
|
||||||
float distanceFactor = isPriority || xDist < 200 && yDist < 100 ? 1 : MathHelper.Lerp(1, 0.1f, MathUtils.InverseLerp(0, 3000, xDist + yDist * 3.0f));
|
|
||||||
if (Leak.linkedTo.Any(e => e is Hull h && h == character.CurrentHull))
|
|
||||||
{
|
|
||||||
// Double the distance when the leak can be accessed from the current hull.
|
|
||||||
distanceFactor *= 2;
|
|
||||||
}
|
|
||||||
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
|
||||||
float devotion = CumulatedDevotion / 100;
|
|
||||||
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier), 0, 1));
|
|
||||||
}
|
}
|
||||||
|
float severity = isPriority ? 1 : AIObjectiveFixLeaks.GetLeakSeverity(Leak) / 100;
|
||||||
|
float devotion = CumulatedDevotion / 100;
|
||||||
|
Priority = MathHelper.Lerp(0, maxPriority, MathHelper.Clamp(devotion + (severity * distanceFactor * PriorityModifier * coopMultiplier), 0, 1));
|
||||||
}
|
}
|
||||||
return Priority;
|
return Priority;
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -38,9 +38,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected override float TargetEvaluation()
|
protected override float TargetEvaluation()
|
||||||
{
|
{
|
||||||
int totalLeaks = Targets.Count();
|
int totalLeaks = Targets.Count;
|
||||||
if (totalLeaks == 0) { return 0; }
|
if (totalLeaks == 0) { return 0; }
|
||||||
int otherFixers = HumanAIController.CountCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine, onlyBots: true);
|
int otherFixers = HumanAIController.CountBotsInTheCrew(c => c != HumanAIController && c.ObjectiveManager.IsCurrentObjective<AIObjectiveFixLeaks>() && c.Character.Submarine == character.Submarine);
|
||||||
bool anyFixers = otherFixers > 0;
|
bool anyFixers = otherFixers > 0;
|
||||||
if (objectiveManager.IsOrder(this))
|
if (objectiveManager.IsOrder(this))
|
||||||
{
|
{
|
||||||
@@ -52,7 +52,7 @@ namespace Barotrauma
|
|||||||
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
|
int secondaryLeaks = Targets.Count(l => l.IsRoomToRoom);
|
||||||
int leaks = totalLeaks - secondaryLeaks;
|
int leaks = totalLeaks - secondaryLeaks;
|
||||||
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / (float)otherFixers : 1;
|
float ratio = leaks == 0 ? 1 : anyFixers ? leaks / (float)otherFixers : 1;
|
||||||
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountCrew(onlyBots: true) > 0.75f))
|
if (anyFixers && (ratio <= 1 || otherFixers > 5 || otherFixers / (float)HumanAIController.CountBotsInTheCrew() > 0.75f))
|
||||||
{
|
{
|
||||||
// Enough fixers
|
// Enough fixers
|
||||||
return 0;
|
return 0;
|
||||||
|
|||||||
+103
-32
@@ -1,9 +1,11 @@
|
|||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.Items.Components;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -31,14 +33,15 @@ namespace Barotrauma
|
|||||||
private ISpatialEntity moveToTarget;
|
private ISpatialEntity moveToTarget;
|
||||||
private bool isDoneSeeking;
|
private bool isDoneSeeking;
|
||||||
public Item TargetItem => targetItem;
|
public Item TargetItem => targetItem;
|
||||||
private int currSearchIndex;
|
private int currentSearchIndex;
|
||||||
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
|
public ImmutableHashSet<Identifier> ignoredContainerIdentifiers;
|
||||||
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
|
public ImmutableHashSet<Identifier> ignoredIdentifiersOrTags;
|
||||||
private AIObjectiveGoTo goToObjective;
|
private AIObjectiveGoTo goToObjective;
|
||||||
private float currItemPriority;
|
private float currItemPriority;
|
||||||
private readonly bool checkInventory;
|
private readonly bool checkInventory;
|
||||||
|
|
||||||
public static float DefaultReach = 100;
|
public const float DefaultReach = 100;
|
||||||
|
public const float MaxReach = 150;
|
||||||
|
|
||||||
public bool AllowToFindDivingGear { get; set; } = true;
|
public bool AllowToFindDivingGear { get; set; } = true;
|
||||||
public bool MustBeSpecificItem { get; set; }
|
public bool MustBeSpecificItem { get; set; }
|
||||||
@@ -76,7 +79,7 @@ namespace Barotrauma
|
|||||||
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
public AIObjectiveGetItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, bool equip = true, float priorityModifier = 1)
|
||||||
: base(character, objectiveManager, priorityModifier)
|
: base(character, objectiveManager, priorityModifier)
|
||||||
{
|
{
|
||||||
currSearchIndex = -1;
|
currentSearchIndex = 0;
|
||||||
Equip = equip;
|
Equip = equip;
|
||||||
originalTarget = targetItem;
|
originalTarget = targetItem;
|
||||||
this.targetItem = targetItem;
|
this.targetItem = targetItem;
|
||||||
@@ -89,7 +92,7 @@ namespace Barotrauma
|
|||||||
public AIObjectiveGetItem(Character character, IEnumerable<Identifier> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
public AIObjectiveGetItem(Character character, IEnumerable<Identifier> identifiersOrTags, AIObjectiveManager objectiveManager, bool equip = true, bool checkInventory = true, float priorityModifier = 1, bool spawnItemIfNotFound = false)
|
||||||
: base(character, objectiveManager, priorityModifier)
|
: base(character, objectiveManager, priorityModifier)
|
||||||
{
|
{
|
||||||
currSearchIndex = -1;
|
currentSearchIndex = 0;
|
||||||
Equip = equip;
|
Equip = equip;
|
||||||
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
this.spawnItemIfNotFound = spawnItemIfNotFound;
|
||||||
this.checkInventory = checkInventory;
|
this.checkInventory = checkInventory;
|
||||||
@@ -125,7 +128,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
|
public static Func<PathNode, bool> CreateEndNodeFilter(ISpatialEntity targetEntity)
|
||||||
{
|
{
|
||||||
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(DefaultReach);
|
return n => (n.Waypoint.Ladders == null || n.Waypoint.IsInWater) && Vector2.DistanceSquared(n.Waypoint.WorldPosition, targetEntity.WorldPosition) <= MathUtils.Pow2(MaxReach);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool CheckInventory()
|
private bool CheckInventory()
|
||||||
@@ -246,7 +249,7 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
character.SelectCharacter(c);
|
character.SelectCharacter(c);
|
||||||
canInteract = character.CanInteractWith(c, maxDist: DefaultReach);
|
canInteract = character.CanInteractWith(c);
|
||||||
character.DeselectCharacter();
|
character.DeselectCharacter();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -268,7 +271,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Inventory itemInventory = targetItem.ParentInventory;
|
Inventory itemInventory = targetItem.ParentInventory;
|
||||||
var slots = itemInventory?.FindIndices(targetItem);
|
var slots = itemInventory?.FindIndices(targetItem);
|
||||||
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true))
|
if (HumanAIController.TakeItem(targetItem, character.Inventory, Equip, Wear, storeUnequipped: true, targetTags: IdentifiersOrTags))
|
||||||
{
|
{
|
||||||
if (TakeWholeStack && slots != null)
|
if (TakeWholeStack && slots != null)
|
||||||
{
|
{
|
||||||
@@ -298,8 +301,11 @@ namespace Barotrauma
|
|||||||
if (!Equip)
|
if (!Equip)
|
||||||
{
|
{
|
||||||
// Try equipping and wearing the item
|
// Try equipping and wearing the item
|
||||||
Wear = true;
|
|
||||||
Equip = true;
|
Equip = true;
|
||||||
|
if (!objectiveManager.HasActiveObjective<AIObjectiveCleanupItem>() && !objectiveManager.HasActiveObjective<AIObjectiveLoadItem>())
|
||||||
|
{
|
||||||
|
Wear = true;
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
@@ -342,6 +348,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Stopwatch sw;
|
||||||
|
private Stopwatch StopWatch => sw ??= new Stopwatch();
|
||||||
|
private readonly List<(Item item, float priority)> itemCandidates = new List<(Item, float)>();
|
||||||
|
private List<Item> itemList;
|
||||||
private void FindTargetItem()
|
private void FindTargetItem()
|
||||||
{
|
{
|
||||||
if (IdentifiersOrTags == null)
|
if (IdentifiersOrTags == null)
|
||||||
@@ -349,13 +359,16 @@ namespace Barotrauma
|
|||||||
if (targetItem == null)
|
if (targetItem == null)
|
||||||
{
|
{
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
DebugConsole.NewMessage($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.", Color.Red);
|
DebugConsole.AddWarning($"{character.Name}: Cannot find an item, because neither identifiers nor item was defined.");
|
||||||
#endif
|
#endif
|
||||||
Abandon = true;
|
Abandon = true;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (HumanAIController.DebugAI)
|
||||||
|
{
|
||||||
|
StopWatch.Restart();
|
||||||
|
}
|
||||||
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
float priority = Math.Clamp(objectiveManager.GetCurrentPriority(), 10, 100);
|
||||||
if (!CheckPathForEachItem)
|
if (!CheckPathForEachItem)
|
||||||
{
|
{
|
||||||
@@ -366,12 +379,22 @@ namespace Barotrauma
|
|||||||
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
|
CheckPathForEachItem = priority >= AIObjectiveManager.LowestOrderPriority && (objectiveManager.IsCurrentOrder<AIObjectiveFixLeaks>() || objectiveManager.CurrentOrder is AIObjectiveGoTo gotoOrder && gotoOrder.IsFollowOrderObjective);
|
||||||
}
|
}
|
||||||
bool checkPath = CheckPathForEachItem;
|
bool checkPath = CheckPathForEachItem;
|
||||||
bool hasCalledPathFinder = false;
|
// Reset if the character has switched subs.
|
||||||
int itemsPerFrame = (int)priority;
|
if (itemList != null && !character.Submarine.IsEntityFoundOnThisSub(itemList.FirstOrDefault(), includingConnectedSubs: true))
|
||||||
for (int i = 0; i < itemsPerFrame && currSearchIndex < Item.ItemList.Count - 1; i++)
|
|
||||||
{
|
{
|
||||||
currSearchIndex++;
|
currentSearchIndex = 0;
|
||||||
var item = Item.ItemList[currSearchIndex];
|
}
|
||||||
|
if (currentSearchIndex == 0)
|
||||||
|
{
|
||||||
|
itemCandidates.Clear();
|
||||||
|
itemList = character.Submarine.GetItems(alsoFromConnectedSubs: true);
|
||||||
|
}
|
||||||
|
int itemsPerFrame = (int)MathHelper.Lerp(30, 300, MathUtils.InverseLerp(10, 100, priority));
|
||||||
|
int checkedItems = 0;
|
||||||
|
for (int i = 0; i < itemsPerFrame && currentSearchIndex < itemList.Count; i++, currentSearchIndex++)
|
||||||
|
{
|
||||||
|
checkedItems++;
|
||||||
|
var item = itemList[currentSearchIndex];
|
||||||
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
Submarine itemSub = item.Submarine ?? item.ParentInventory?.Owner?.Submarine;
|
||||||
if (itemSub == null) { continue; }
|
if (itemSub == null) { continue; }
|
||||||
Submarine mySub = character.Submarine;
|
Submarine mySub = character.Submarine;
|
||||||
@@ -395,8 +418,6 @@ namespace Barotrauma
|
|||||||
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
if (ignoredContainerIdentifiers.Contains(item.ContainerIdentifier)) { continue; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Don't allow going into another sub, unless it's connected and of the same team and type.
|
|
||||||
if (!character.Submarine.IsEntityFoundOnThisSub(item, includingConnectedSubs: true)) { continue; }
|
|
||||||
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
if (character.IsItemTakenBySomeoneElse(item)) { continue; }
|
||||||
if (item.ParentInventory is ItemInventory itemInventory)
|
if (item.ParentInventory is ItemInventory itemInventory)
|
||||||
{
|
{
|
||||||
@@ -411,11 +432,14 @@ namespace Barotrauma
|
|||||||
if (rootInventoryOwner is Item ownerItem)
|
if (rootInventoryOwner is Item ownerItem)
|
||||||
{
|
{
|
||||||
if (!ownerItem.IsInteractable(character)) { continue; }
|
if (!ownerItem.IsInteractable(character)) { continue; }
|
||||||
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
if (ownerItem != item)
|
||||||
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
|
||||||
if (ownerItem != item.Container)
|
|
||||||
{
|
{
|
||||||
itemPriority *= 0.1f;
|
if (!(ownerItem.GetComponent<ItemContainer>()?.HasRequiredItems(character, addMessage: false) ?? true)) { continue; }
|
||||||
|
//the item is inside an item inside an item (e.g. fuel tank in a welding tool in a cabinet -> reduce priority to prefer items that aren't inside a tool)
|
||||||
|
if (ownerItem != item.Container)
|
||||||
|
{
|
||||||
|
itemPriority *= 0.1f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
Vector2 itemPos = (rootInventoryOwner ?? item).WorldPosition;
|
||||||
@@ -463,22 +487,69 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
itemPriority *= item.Condition / item.MaxCondition;
|
itemPriority *= item.Condition / item.MaxCondition;
|
||||||
}
|
}
|
||||||
|
if (checkPath)
|
||||||
|
{
|
||||||
|
itemCandidates.Add((item, itemPriority));
|
||||||
|
}
|
||||||
// Ignore if the item has a lower priority than the currently selected one
|
// Ignore if the item has a lower priority than the currently selected one
|
||||||
if (itemPriority < currItemPriority) { continue; }
|
if (itemPriority < currItemPriority) { continue; }
|
||||||
if (!hasCalledPathFinder && PathSteering != null && checkPath)
|
if (EvaluateCombatPriority && itemPriority <= 0)
|
||||||
{
|
{
|
||||||
hasCalledPathFinder = true;
|
// Not good enough
|
||||||
var path = PathSteering.PathFinder.FindPath(character.SimPosition, item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
continue;
|
||||||
if (path.Unreachable) { continue; }
|
|
||||||
}
|
}
|
||||||
currItemPriority = itemPriority;
|
currItemPriority = itemPriority;
|
||||||
targetItem = item;
|
targetItem = item;
|
||||||
moveToTarget = rootInventoryOwner ?? item;
|
moveToTarget = rootInventoryOwner ?? item;
|
||||||
}
|
}
|
||||||
if (currSearchIndex >= Item.ItemList.Count - 1)
|
if (currentSearchIndex >= itemList.Count - 1)
|
||||||
{
|
{
|
||||||
isDoneSeeking = true;
|
isDoneSeeking = true;
|
||||||
if (targetItem == null)
|
}
|
||||||
|
if (checkedItems > 0)
|
||||||
|
{
|
||||||
|
if (isDoneSeeking && itemCandidates.Any())
|
||||||
|
{
|
||||||
|
itemCandidates.Sort((x, y) => y.priority.CompareTo(x.priority));
|
||||||
|
}
|
||||||
|
if (HumanAIController.DebugAI && targetItem != null && StopWatch.ElapsedMilliseconds > 2)
|
||||||
|
{
|
||||||
|
var msg = $"Went through {checkedItems} of total {itemList.Count} items. Found item {targetItem.Name} in {StopWatch.ElapsedMilliseconds} ms. Completed: {isDoneSeeking}";
|
||||||
|
if (StopWatch.ElapsedMilliseconds > 5)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError(msg);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// An occasional warning now and then can be ignored, but multiple at the same time might indicate a performance issue.
|
||||||
|
DebugConsole.AddWarning(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (isDoneSeeking)
|
||||||
|
{
|
||||||
|
if (PathSteering == null)
|
||||||
|
{
|
||||||
|
itemCandidates.Clear();
|
||||||
|
}
|
||||||
|
if (itemCandidates.Any())
|
||||||
|
{
|
||||||
|
if (itemCandidates.FirstOrDefault() is { } itemCandidate)
|
||||||
|
{
|
||||||
|
var path = PathSteering.PathFinder.FindPath(character.SimPosition, itemCandidate.item.SimPosition, character.Submarine, errorMsgStr: $"AIObjectiveGetItem {character.DisplayName}", nodeFilter: node => node.Waypoint.CurrentHull != null);
|
||||||
|
if (path.Unreachable)
|
||||||
|
{
|
||||||
|
// Remove the invalid candidates and continue on the next frame.
|
||||||
|
itemCandidates.Remove(itemCandidate);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// The path was valid -> we are done.
|
||||||
|
itemCandidates.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (targetItem == null && itemCandidates.None())
|
||||||
{
|
{
|
||||||
if (spawnItemIfNotFound)
|
if (spawnItemIfNotFound)
|
||||||
{
|
{
|
||||||
@@ -569,11 +640,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!item.HasAccess(character)) { return false; }
|
if (!item.HasAccess(character)) { return false; }
|
||||||
if (ignoredItems.Contains(item)) { return false; };
|
if (ignoredItems.Contains(item)) { return false; };
|
||||||
if (ignoredIdentifiersOrTags != null && CheckItemIdentifiersOrTags(item, ignoredIdentifiersOrTags)) { return false; }
|
if (ignoredIdentifiersOrTags != null && item.HasIdentifierOrTags(ignoredIdentifiersOrTags)) { return false; }
|
||||||
if (item.Condition < TargetCondition) { return false; }
|
if (item.Condition < TargetCondition) { return false; }
|
||||||
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
if (ItemFilter != null && !ItemFilter(item)) { return false; }
|
||||||
if (RequireNonEmpty && item.Components.Any(i => !i.IsNotEmpty(character))) { return false; }
|
if (RequireNonEmpty && item.Components.Any(i => i.IsEmpty(character))) { return false; }
|
||||||
return CheckItemIdentifiersOrTags(item, IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
return item.HasIdentifierOrTags(IdentifiersOrTags) || (AllowVariants && !item.Prefab.VariantOf.IsEmpty && IdentifiersOrTags.Contains(item.Prefab.VariantOf));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Reset()
|
public override void Reset()
|
||||||
@@ -591,7 +662,7 @@ namespace Barotrauma
|
|||||||
targetItem = originalTarget;
|
targetItem = originalTarget;
|
||||||
moveToTarget = targetItem?.GetRootInventoryOwner();
|
moveToTarget = targetItem?.GetRootInventoryOwner();
|
||||||
isDoneSeeking = false;
|
isDoneSeeking = false;
|
||||||
currSearchIndex = 0;
|
currentSearchIndex = 0;
|
||||||
currItemPriority = 0;
|
currItemPriority = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+37
-10
@@ -257,7 +257,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: false))
|
else if (HumanAIController.HasValidPath(requireUnfinished: false))
|
||||||
{
|
{
|
||||||
waitUntilPathUnreachable = pathWaitingTime;
|
waitUntilPathUnreachable = pathWaitingTime;
|
||||||
}
|
}
|
||||||
@@ -364,7 +364,8 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
|
bool isRuins = character.Submarine?.Info.IsRuin != null || Target.Submarine?.Info.IsRuin != null;
|
||||||
if (!isRuins || !HumanAIController.HasValidPath(requireNonDirty: true, requireUnfinished: true))
|
bool isEitherOneInside = isInside || Target.Submarine != null;
|
||||||
|
if (isEitherOneInside && (!isRuins || !HumanAIController.HasValidPath()))
|
||||||
{
|
{
|
||||||
SeekGaps(maxGapDistance);
|
SeekGaps(maxGapDistance);
|
||||||
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
seekGapsTimer = seekGapsInterval * Rand.Range(0.1f, 1.1f);
|
||||||
@@ -388,6 +389,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
TargetGap = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -437,8 +442,8 @@ namespace Barotrauma
|
|||||||
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
|
var leftHandItem = character.GetEquippedItem(slotType: InvSlotType.LeftHand);
|
||||||
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
|
var rightHandItem = character.GetEquippedItem(slotType: InvSlotType.RightHand);
|
||||||
bool handsFull =
|
bool handsFull =
|
||||||
(leftHandItem != null && character.Inventory.CheckIfAnySlotAvailable(leftHandItem, inWrongSlot: false) == -1) ||
|
(leftHandItem != null && !character.Inventory.IsAnySlotAvailable(leftHandItem)) ||
|
||||||
(rightHandItem != null && character.Inventory.CheckIfAnySlotAvailable(rightHandItem, inWrongSlot: false) == -1);
|
(rightHandItem != null && !character.Inventory.IsAnySlotAvailable(rightHandItem));
|
||||||
if (!handsFull)
|
if (!handsFull)
|
||||||
{
|
{
|
||||||
bool hasBattery = false;
|
bool hasBattery = false;
|
||||||
@@ -473,7 +478,7 @@ namespace Barotrauma
|
|||||||
// Try to switch batteries
|
// Try to switch batteries
|
||||||
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
|
if (HumanAIController.HasItem(character, batteryTag, out IEnumerable<Item> batteries, conditionPercentage: 1, recursive: false))
|
||||||
{
|
{
|
||||||
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.anySlot));
|
scooter.ContainedItems.ForEachMod(emptyBattery => character.Inventory.TryPutItem(emptyBattery, character, CharacterInventory.AnySlot));
|
||||||
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
|
if (!scooter.Combine(batteries.OrderByDescending(b => b.Condition).First(), character))
|
||||||
{
|
{
|
||||||
useScooter = false;
|
useScooter = false;
|
||||||
@@ -488,7 +493,7 @@ namespace Barotrauma
|
|||||||
if (!useScooter)
|
if (!useScooter)
|
||||||
{
|
{
|
||||||
// Unequip
|
// Unequip
|
||||||
character.Inventory.TryPutItem(scooter, character, CharacterInventory.anySlot);
|
character.Inventory.TryPutItem(scooter, character, CharacterInventory.AnySlot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -511,13 +516,20 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
nodeFilter = n => n.Waypoint.CurrentHull != null;
|
||||||
}
|
}
|
||||||
else if (!isInside && HumanAIController.UseIndoorSteeringOutside)
|
else if (!isInside)
|
||||||
{
|
{
|
||||||
nodeFilter = n => n.Waypoint.Submarine == null;
|
if (HumanAIController.UseOutsideWaypoints)
|
||||||
|
{
|
||||||
|
nodeFilter = n => n.Waypoint.Submarine == null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
nodeFilter = n => n.Waypoint.Submarine != null || n.Waypoint.Ruin != null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isInside && !UsePathingOutside)
|
if (!isInside && !UsePathingOutside)
|
||||||
{
|
{
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
|
PathSteering.SteeringSeekSimple(character.GetRelativeSimPosition(Target), 10);
|
||||||
if (character.AnimController.InWater)
|
if (character.AnimController.InWater)
|
||||||
{
|
{
|
||||||
@@ -540,6 +552,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(Target.WorldPosition - character.WorldPosition));
|
||||||
if (character.AnimController.InWater)
|
if (character.AnimController.InWater)
|
||||||
{
|
{
|
||||||
@@ -560,6 +573,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
|
||||||
if (character.AnimController.InWater)
|
if (character.AnimController.InWater)
|
||||||
{
|
{
|
||||||
@@ -573,6 +587,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!character.HasEquippedItem("scooter".ToIdentifier())) { return; }
|
if (!character.HasEquippedItem("scooter".ToIdentifier())) { return; }
|
||||||
SteeringManager.Reset();
|
SteeringManager.Reset();
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
character.CursorPosition = targetWorldPos;
|
character.CursorPosition = targetWorldPos;
|
||||||
if (character.Submarine != null)
|
if (character.Submarine != null)
|
||||||
{
|
{
|
||||||
@@ -624,7 +639,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (target is Character c)
|
else if (target is Character c)
|
||||||
{
|
{
|
||||||
return c.CurrentHull;
|
return c.CurrentHull ?? c.AnimController.CurrentHull;
|
||||||
}
|
}
|
||||||
else if (target is Structure structure)
|
else if (target is Structure structure)
|
||||||
{
|
{
|
||||||
@@ -772,6 +787,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
StopMovement();
|
StopMovement();
|
||||||
HumanAIController.FaceTarget(Target);
|
HumanAIController.FaceTarget(Target);
|
||||||
|
if (Target is WayPoint { Ladders: null })
|
||||||
|
{
|
||||||
|
// Release ladders when ordered to wait at a spawnpoint.
|
||||||
|
// This is a special case specifically meant for NPCs that spawn in outposts with a wait order.
|
||||||
|
// Otherwise they might keep holding to the ladders when the target is just next to it.
|
||||||
|
// Releasing too early should be handled inside the IsCloseEnough property.
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
|
}
|
||||||
base.OnCompleted();
|
base.OnCompleted();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -781,6 +804,10 @@ namespace Barotrauma
|
|||||||
findDivingGear = null;
|
findDivingGear = null;
|
||||||
seekGapsTimer = 0;
|
seekGapsTimer = 0;
|
||||||
TargetGap = null;
|
TargetGap = null;
|
||||||
|
if (SteeringManager is IndoorsSteeringManager pathSteering)
|
||||||
|
{
|
||||||
|
pathSteering.ResetPath();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-9
@@ -163,15 +163,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
character.SelectedItem = null;
|
character.SelectedItem = null;
|
||||||
|
|
||||||
CleanupItems(deltaTime);
|
if (!character.IsClimbing)
|
||||||
|
{
|
||||||
|
CleanupItems(deltaTime);
|
||||||
|
}
|
||||||
|
|
||||||
if (behavior == BehaviorType.StayInHull && TargetHull == null && character.CurrentHull != null)
|
if (behavior == BehaviorType.StayInHull && TargetHull == null && character.CurrentHull != null)
|
||||||
{
|
{
|
||||||
TargetHull = character.CurrentHull;
|
TargetHull = character.CurrentHull;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool currentTargetIsInvalid = currentTarget == null || IsForbidden(currentTarget) || (PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
bool currentTargetIsInvalid =
|
||||||
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid)
|
currentTarget == null ||
|
||||||
|
IsForbidden(currentTarget) ||
|
||||||
|
(PathSteering.CurrentPath != null && PathSteering.CurrentPath.Nodes.Any(n => HumanAIController.UnsafeHulls.Contains(n.CurrentHull)));
|
||||||
|
|
||||||
|
if (behavior == BehaviorType.StayInHull && !currentTargetIsInvalid && !HumanAIController.UnsafeHulls.Contains(TargetHull))
|
||||||
{
|
{
|
||||||
currentTarget = TargetHull;
|
currentTarget = TargetHull;
|
||||||
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
bool stayInHull = character.CurrentHull == currentTarget && IsSteeringFinished() && !character.IsClimbing;
|
||||||
@@ -305,12 +312,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (character.IsClimbing)
|
if (character.IsClimbing)
|
||||||
{
|
{
|
||||||
if (character.AnimController.GetHeightFromFloor() < 0.1f)
|
PathSteering.Reset();
|
||||||
{
|
character.StopClimbing();
|
||||||
character.AnimController.Anim = AnimController.Animation.None;
|
|
||||||
character.SelectedSecondaryItem = null;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
var currentHull = character.CurrentHull;
|
var currentHull = character.CurrentHull;
|
||||||
if (!character.AnimController.InWater && currentHull != null)
|
if (!character.AnimController.InWater && currentHull != null)
|
||||||
@@ -362,6 +365,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
character.ReleaseSecondaryItem();
|
||||||
PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff));
|
PathSteering.SteeringManual(deltaTime, Vector2.Normalize(diff));
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
|
|||||||
+5
-13
@@ -24,7 +24,7 @@ namespace Barotrauma
|
|||||||
private ImmutableHashSet<Identifier> ValidContainableItemIdentifiers { get; }
|
private ImmutableHashSet<Identifier> ValidContainableItemIdentifiers { get; }
|
||||||
private static Dictionary<ItemPrefab, ImmutableHashSet<Identifier>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<Identifier>>();
|
private static Dictionary<ItemPrefab, ImmutableHashSet<Identifier>> AllValidContainableItemIdentifiers { get; } = new Dictionary<ItemPrefab, ImmutableHashSet<Identifier>>();
|
||||||
|
|
||||||
private int itemIndex = 0;
|
private int itemIndex;
|
||||||
private AIObjectiveDecontainItem decontainObjective;
|
private AIObjectiveDecontainItem decontainObjective;
|
||||||
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
|
private readonly HashSet<Item> ignoredItems = new HashSet<Item>();
|
||||||
private Item targetItem;
|
private Item targetItem;
|
||||||
@@ -219,9 +219,8 @@ namespace Barotrauma
|
|||||||
return Priority;
|
return Priority;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Update(float deltaTime)
|
protected override void Act(float deltaTime)
|
||||||
{
|
{
|
||||||
base.Update(deltaTime);
|
|
||||||
if (targetItem == null)
|
if (targetItem == null)
|
||||||
{
|
{
|
||||||
if (character.FindItem(ref itemIndex, out Item item, identifiers: ValidContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetPriority))
|
if (character.FindItem(ref itemIndex, out Item item, identifiers: ValidContainableItemIdentifiers, ignoreBroken: false, customPredicate: IsValidContainable, customPriorityFunction: GetPriority))
|
||||||
@@ -233,6 +232,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
targetItem = item;
|
targetItem = item;
|
||||||
}
|
}
|
||||||
|
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
||||||
float GetPriority(Item item)
|
float GetPriority(Item item)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -256,11 +256,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
else
|
||||||
|
|
||||||
protected override void Act(float deltaTime)
|
|
||||||
{
|
|
||||||
if (targetItem != null)
|
|
||||||
{
|
{
|
||||||
if(decontainObjective == null && !IsValidContainable(targetItem))
|
if(decontainObjective == null && !IsValidContainable(targetItem))
|
||||||
{
|
{
|
||||||
@@ -290,10 +286,6 @@ namespace Barotrauma
|
|||||||
Reset();
|
Reset();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
objectiveManager.GetObjective<AIObjectiveIdle>().Wander(deltaTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsValidContainable(Item item)
|
private bool IsValidContainable(Item item)
|
||||||
@@ -310,7 +302,7 @@ namespace Barotrauma
|
|||||||
if (parentItem.HasTag("donttakeitems")) { return false; }
|
if (parentItem.HasTag("donttakeitems")) { return false; }
|
||||||
}
|
}
|
||||||
if (!item.HasAccess(character)) { return false; }
|
if (!item.HasAccess(character)) { return false; }
|
||||||
if (!character.HasItem(item) && !CanEquip(item)) { return false; }
|
if (!character.HasItem(item) && !CanEquip(item, allowWearing: false)) { return false; }
|
||||||
if (!ItemContainer.CanBeContained(item)) { return false; }
|
if (!ItemContainer.CanBeContained(item)) { return false; }
|
||||||
if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; }
|
if (AIObjectiveLoadItems.ItemMatchesTargetCondition(item, TargetItemCondition)) { return false; }
|
||||||
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full)
|
if (TargetItemCondition == AIObjectiveLoadItems.ItemCondition.Full)
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (item == null || item.Removed) { return false; }
|
if (item == null || item.Removed) { return false; }
|
||||||
if (targetContainerTags.HasValue && !OrderPrefab.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
|
if (targetContainerTags.HasValue && !OrderPrefab.TargetItemsMatchItem(targetContainerTags.Value, item)) { return false; }
|
||||||
if (!(item.GetComponent<ItemContainer>() is ItemContainer container)) { return false; }
|
if ((item.GetComponent<ItemContainer>() is not ItemContainer container)) { return false; }
|
||||||
if (container.Inventory == null) { return false; }
|
if (container.Inventory == null) { return false; }
|
||||||
if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; }
|
if (targetCondition.HasValue && container.Inventory.IsFull() && container.Inventory.AllItems.None(i => ItemMatchesTargetCondition(i, targetCondition.Value))) { return false; }
|
||||||
if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; }
|
if (!AIObjectiveCleanupItems.IsItemInsideValidSubmarine(item, character)) { return false; }
|
||||||
|
|||||||
+6
-6
@@ -89,12 +89,7 @@ namespace Barotrauma
|
|||||||
var target = objective.Key;
|
var target = objective.Key;
|
||||||
if (!Targets.Contains(target))
|
if (!Targets.Contains(target))
|
||||||
{
|
{
|
||||||
var subObjective = objective.Value;
|
subObjectives.Remove(objective.Value);
|
||||||
if (CurrentSubObjective == subObjective)
|
|
||||||
{
|
|
||||||
CurrentSubObjective.Abandon = !CurrentSubObjective.IsCompleted;
|
|
||||||
}
|
|
||||||
subObjectives.Remove(subObjective);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SyncRemovedObjectives(Objectives, GetList());
|
SyncRemovedObjectives(Objectives, GetList());
|
||||||
@@ -157,6 +152,11 @@ namespace Barotrauma
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
float max = AIObjectiveManager.LowestOrderPriority - 1;
|
||||||
|
if (this is AIObjectiveRescueAll rescueObjective && rescueObjective.Targets.Contains(character))
|
||||||
|
{
|
||||||
|
// Allow higher prio
|
||||||
|
max = AIObjectiveManager.EmergencyObjectivePriority;
|
||||||
|
}
|
||||||
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
float value = MathHelper.Clamp((CumulatedDevotion + (targetValue * PriorityModifier)) / 100, 0, 1);
|
||||||
Priority = MathHelper.Lerp(0, max, value);
|
Priority = MathHelper.Lerp(0, max, value);
|
||||||
}
|
}
|
||||||
|
|||||||
+19
-6
@@ -20,6 +20,8 @@ namespace Barotrauma
|
|||||||
MaxValue = 2
|
MaxValue = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public const float MaxObjectivePriority = 100;
|
||||||
|
public const float EmergencyObjectivePriority = 90;
|
||||||
public const float HighestOrderPriority = 70;
|
public const float HighestOrderPriority = 70;
|
||||||
public const float LowestOrderPriority = 60;
|
public const float LowestOrderPriority = 60;
|
||||||
public const float RunPriority = 50;
|
public const float RunPriority = 50;
|
||||||
@@ -125,11 +127,21 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
CoroutineManager.StopCoroutines(delayedObjective.Value);
|
CoroutineManager.StopCoroutines(delayedObjective.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var prevIdleObjective = GetObjective<AIObjectiveIdle>();
|
||||||
|
|
||||||
DelayedObjectives.Clear();
|
DelayedObjectives.Clear();
|
||||||
Objectives.Clear();
|
Objectives.Clear();
|
||||||
FailedAutonomousObjectives = false;
|
FailedAutonomousObjectives = false;
|
||||||
AddObjective(new AIObjectiveFindSafety(character, this));
|
AddObjective(new AIObjectiveFindSafety(character, this));
|
||||||
AddObjective(new AIObjectiveIdle(character, this));
|
var newIdleObjective = new AIObjectiveIdle(character, this);
|
||||||
|
if (prevIdleObjective != null)
|
||||||
|
{
|
||||||
|
newIdleObjective.TargetHull = prevIdleObjective.TargetHull;
|
||||||
|
newIdleObjective.Behavior = prevIdleObjective.Behavior;
|
||||||
|
prevIdleObjective.PreferredOutpostModuleTypes.ForEach(t => newIdleObjective.PreferredOutpostModuleTypes.Add(t));
|
||||||
|
}
|
||||||
|
AddObjective(newIdleObjective);
|
||||||
int objectiveCount = Objectives.Count;
|
int objectiveCount = Objectives.Count;
|
||||||
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
|
foreach (var autonomousObjective in character.Info.Job.Prefab.AutonomousObjectives)
|
||||||
{
|
{
|
||||||
@@ -410,7 +422,7 @@ namespace Barotrauma
|
|||||||
newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
newObjective = new AIObjectiveGoTo(order.OrderGiver, character, this, repeat: true, priorityModifier: priorityModifier)
|
||||||
{
|
{
|
||||||
CloseEnough = Rand.Range(80f, 100f),
|
CloseEnough = Rand.Range(80f, 100f),
|
||||||
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver), onlyBots: true) * Rand.Range(0.8f, 1f), 4),
|
CloseEnoughMultiplier = Math.Min(1 + HumanAIController.CountBotsInTheCrew(c => c.ObjectiveManager.HasOrder<AIObjectiveGoTo>(o => o.Target == order.OrderGiver)) * Rand.Range(0.8f, 1f), 4),
|
||||||
ExtraDistanceOutsideSub = 100,
|
ExtraDistanceOutsideSub = 100,
|
||||||
ExtraDistanceWhileSwimming = 100,
|
ExtraDistanceWhileSwimming = 100,
|
||||||
AllowGoingOutside = true,
|
AllowGoingOutside = true,
|
||||||
@@ -633,10 +645,11 @@ namespace Barotrauma
|
|||||||
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
public AIObjective GetActiveObjective() => CurrentObjective?.GetActiveObjective();
|
||||||
public T GetOrder<T>() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T)?.Objective as T;
|
public T GetOrder<T>() where T : AIObjective => CurrentOrders.FirstOrDefault(o => o.Objective is T)?.Objective as T;
|
||||||
|
|
||||||
/// <summary>
|
public T GetLastActiveObjective<T>() where T : AIObjective
|
||||||
/// Returns the last active objective of the specific type.
|
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
||||||
/// </summary>
|
|
||||||
public T GetActiveObjective<T>() where T : AIObjective => CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).LastOrDefault(so => so is T) as T;
|
public T GetFirstActiveObjective<T>() where T : AIObjective
|
||||||
|
=> CurrentObjective?.GetSubObjectivesRecursive(includingSelf: true).FirstOrDefault(so => so is T) as T;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
/// Returns all active objectives of the specific type. Creates a new collection -> don't use too frequently.
|
||||||
|
|||||||
+2
-2
@@ -122,7 +122,7 @@ namespace Barotrauma
|
|||||||
else if (!isOrder)
|
else if (!isOrder)
|
||||||
{
|
{
|
||||||
var steering = component?.Item.GetComponent<Steering>();
|
var steering = component?.Item.GetComponent<Steering>();
|
||||||
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != HumanAIController && c.Character.IsCaptain)))
|
if (steering != null && (steering.AutoPilot || HumanAIController.IsTrueForAnyCrewMember(c => c != character && c.IsCaptain, onlyActive: true, onlyConnectedSubs: true)))
|
||||||
{
|
{
|
||||||
// Ignore if already set to autopilot or if there's a captain onboard
|
// Ignore if already set to autopilot or if there's a captain onboard
|
||||||
Priority = 0;
|
Priority = 0;
|
||||||
@@ -204,7 +204,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
if (operateTarget != null)
|
if (operateTarget != null)
|
||||||
{
|
{
|
||||||
if (HumanAIController.IsTrueForAnyCrewMember(other => other != HumanAIController && other.Character.IsBot && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
if (HumanAIController.IsTrueForAnyBotInTheCrew(other => other != HumanAIController && other.ObjectiveManager.GetActiveObjective() is AIObjectiveOperateItem operateObjective && operateObjective.operateTarget == operateTarget))
|
||||||
{
|
{
|
||||||
// Another crew member is already targeting this entity (leak).
|
// Another crew member is already targeting this entity (leak).
|
||||||
Abandon = true;
|
Abandon = true;
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user