Unstable 0.16.0.0

This commit is contained in:
Markus Isberg
2022-01-14 01:28:24 +09:00
parent d9baeaa2e1
commit 7d6421a548
237 changed files with 6430 additions and 2205 deletions
@@ -1156,9 +1156,9 @@ namespace Barotrauma
} }
} }
partial void OnTalentGiven(string talentIdentifier) partial void OnTalentGiven(TalentPrefab talentPrefab)
{ {
AddMessage(TextManager.Get("talentname." + talentIdentifier.ToString()), GUI.Style.Yellow, playSound: this == Controlled); AddMessage(TextManager.Get("talentname." + talentPrefab.Identifier), GUI.Style.Yellow, playSound: this == Controlled);
} }
} }
} }
@@ -521,6 +521,7 @@ namespace Barotrauma
ch.SkinColor = skinColor; ch.SkinColor = skinColor;
ch.HairColor = hairColor; ch.HairColor = hairColor;
ch.FacialHairColor = facialHairColor; ch.FacialHairColor = facialHairColor;
ch.SetPersonalityTrait();
if (ch.Job != null) if (ch.Job != null)
{ {
foreach (KeyValuePair<string, float> skill in skillLevels) foreach (KeyValuePair<string, float> skill in skillLevels)
@@ -2,12 +2,19 @@
{ {
partial class AfflictionHusk : Affliction partial class AfflictionHusk : Affliction
{ {
private InfectionState? prevDisplayedMessage;
partial void UpdateMessages() partial void UpdateMessages()
{ {
if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; } if (Prefab is AfflictionPrefabHusk { SendMessages: false }) { return; }
if (prevDisplayedMessage.HasValue && prevDisplayedMessage.Value == State) { return; }
switch (State) switch (State)
{ {
case InfectionState.Dormant: case InfectionState.Dormant:
if (Strength < DormantThreshold * 0.5f)
{
return;
}
GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red); GUI.AddMessage(TextManager.Get("HuskDormant"), GUI.Style.Red);
break; break;
case InfectionState.Transition: case InfectionState.Transition:
@@ -23,6 +30,7 @@
default: default:
break; break;
} }
prevDisplayedMessage = State;
} }
} }
} }
@@ -531,6 +531,8 @@ namespace Barotrauma
bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f); bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
if (bloodParticleTimer <= 0.0f) if (bloodParticleTimer <= 0.0f)
{ {
Limb limb = targetLimb ?? Character.AnimController.MainLimb;
bool inWater = Character.AnimController.InWater; bool inWater = Character.AnimController.InWater;
var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air; var drawTarget = inWater ? Particles.ParticlePrefab.DrawTargetType.Water : Particles.ParticlePrefab.DrawTargetType.Air;
var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab.DrawTarget == drawTarget || e.Prefab.ParticlePrefab.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both); var emitter = Character.BloodEmitters.FirstOrDefault(e => e.Prefab.ParticlePrefab.DrawTarget == drawTarget || e.Prefab.ParticlePrefab.DrawTarget == Particles.ParticlePrefab.DrawTargetType.Both);
@@ -543,13 +545,13 @@ namespace Barotrauma
if (!inWater) if (!inWater)
{ {
bloodParticleSize *= 2.0f; bloodParticleSize *= 2.0f;
velocity = targetLimb.LinearVelocity * 100.0f; velocity = limb.LinearVelocity * 100.0f;
} }
// TODO: use the blood emitter? // TODO: use the blood emitter?
var blood = GameMain.ParticleManager.CreateParticle( var blood = GameMain.ParticleManager.CreateParticle(
inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir, inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
targetLimb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull); limb.WorldPosition, velocity, 0.0f, Character.AnimController.CurrentHull);
if (blood != null && !inWater) if (blood != null && !inWater)
{ {
@@ -1122,23 +1124,24 @@ namespace Barotrauma
} }
public static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction) public static Color GetAfflictionIconColor(AfflictionPrefab prefab, Affliction affliction)
{
return GetAfflictionIconColor(prefab, affliction.Strength);
}
public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
{ {
// No specific colors, use generic // No specific colors, use generic
if (prefab.IconColors == null) if (prefab.IconColors == null)
{ {
if (prefab.IsBuff) if (prefab.IsBuff)
{ {
return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, GUI.Style.BuffColorLow, GUI.Style.BuffColorMedium, GUI.Style.BuffColorHigh); return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, GUI.Style.BuffColorLow, GUI.Style.BuffColorMedium, GUI.Style.BuffColorHigh);
}
else
{
return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, GUI.Style.DebuffColorLow, GUI.Style.DebuffColorMedium, GUI.Style.DebuffColorHigh);
} }
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, GUI.Style.DebuffColorLow, GUI.Style.DebuffColorMedium, GUI.Style.DebuffColorHigh);
} }
else
{ return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, prefab.IconColors);
return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, prefab.IconColors);
}
} }
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction); public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
@@ -1153,7 +1156,7 @@ namespace Barotrauma
return; return;
} }
if (afflictionsDirty()) if (afflictionsDirty() || selectedLimb != currentDisplayedLimb)
{ {
var currentAfflictions = afflictions.Where(a => ShouldDisplayAfflictionOnLimb(a, selectedLimb)).Select(a => a.Key); var currentAfflictions = afflictions.Where(a => ShouldDisplayAfflictionOnLimb(a, selectedLimb)).Select(a => a.Key);
CreateAfflictionInfos(currentAfflictions); CreateAfflictionInfos(currentAfflictions);
@@ -1120,7 +1120,7 @@ namespace Barotrauma
return; return;
} }
if (Submarine.MainSub.SaveAs(Barotrauma.IO.Path.Combine(SubmarineInfo.SavePath, fileName + ".sub"))) if (Submarine.MainSub.TrySaveAs(Barotrauma.IO.Path.Combine(SubmarineInfo.SavePath, fileName + ".sub")))
{ {
NewMessage("Sub saved", Color.Green); NewMessage("Sub saved", Color.Green);
} }
@@ -2392,8 +2392,9 @@ namespace Barotrauma
commands.Add(new Command("querylobbies", "Queries all SteamP2P lobbies", (args) => commands.Add(new Command("querylobbies", "Queries all SteamP2P lobbies", (args) =>
{ {
TaskPool.Add("DebugQueryLobbies", TaskPool.Add("DebugQueryLobbies",
SteamManager.LobbyQueryRequest(), (t) => { SteamManager.LobbyQueryRequest(), (t) =>
var lobbies = ((Task<List<Steamworks.Data.Lobby>>)t).Result; {
t.TryGetResult(out List<Steamworks.Data.Lobby> lobbies);
foreach (var lobby in lobbies) foreach (var lobby in lobbies)
{ {
NewMessage(lobby.GetData("name") + ", " + lobby.GetData("lobbyowner"), Color.Yellow); NewMessage(lobby.GetData("name") + ", " + lobby.GetData("lobbyowner"), Color.Yellow);
@@ -60,7 +60,16 @@ namespace Barotrauma
ushort id = msg.ReadUInt16(); ushort id = msg.ReadUInt16();
bool scanned = msg.ReadBoolean(); bool scanned = msg.ReadBoolean();
Entity entity = Entity.FindEntityByID(id); Entity entity = Entity.FindEntityByID(id);
scanTargets.Add(entity as WayPoint, scanned); if (!(entity is WayPoint wayPoint))
{
string errorMsg = $"Failed to find a waypoint in ScanMission.ClientReadScanTargetStatus. Entity {id} was {(entity?.ToString() ?? null)}";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ScanMission.ClientReadScanTargetStatus", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
else
{
scanTargets.Add(wayPoint, scanned);
}
} }
} }
} }
@@ -1,8 +1,10 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using SharpFont; using SharpFont;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
@@ -130,7 +132,7 @@ namespace Barotrauma
/// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param> /// <param name="charRanges">Character ranges between each even element with their corresponding odd element. Default is 0x20 to 0xFFFF.</param>
/// <param name="texDims">Texture dimensions. Default is 512x512.</param> /// <param name="texDims">Texture dimensions. Default is 512x512.</param>
/// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param> /// <param name="baseChar">Base character used to shift all other characters downwards when rendering. Defaults to T.</param>
public void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 1024, uint baseChar = 0x54) private void RenderAtlas(GraphicsDevice gd, uint[] charRanges = null, int texDims = 1024, uint baseChar = 0x54)
{ {
if (DynamicLoading) { return; } if (DynamicLoading) { return; }
@@ -253,13 +255,19 @@ namespace Barotrauma
} }
} }
public void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54) private void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54)
=> DynamicRenderAtlas(gd, character.ToEnumerable(), texDims, baseChar);
private void DynamicRenderAtlas(GraphicsDevice gd, string str, int texDims = 1024, uint baseChar = 0x54)
=> DynamicRenderAtlas(gd, str.Distinct().Select(c => (uint)c), texDims, baseChar);
private void DynamicRenderAtlas(GraphicsDevice gd, IEnumerable<uint> characters, int texDims = 1024, uint baseChar = 0x54)
{ {
if (System.Threading.Thread.CurrentThread != GameMain.MainThread) if (System.Threading.Thread.CurrentThread != GameMain.MainThread)
{ {
CrossThread.RequestExecutionOnMainThread(() => CrossThread.RequestExecutionOnMainThread(() =>
{ {
DynamicRenderAtlas(gd, character, texDims, baseChar); DynamicRenderAtlas(gd, characters, texDims, baseChar);
}); });
return; return;
} }
@@ -271,7 +279,6 @@ namespace Barotrauma
lock (mutex) lock (mutex)
{ {
if (texCoords.ContainsKey(character)) { return; }
if (textures.Count == 0) if (textures.Count == 0)
{ {
this.texDims = texDims; this.texDims = texDims;
@@ -282,79 +289,90 @@ namespace Barotrauma
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color)); textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
} }
uint glyphIndex = face.GetCharIndex(character); bool anyChanges = false;
if (glyphIndex == 0) { return; } bool firstChar = true;
foreach (var character in characters)
face.SetPixelSizes(0, size);
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
{ {
if (face.Glyph.Metrics.HorizontalAdvance > 0) if (texCoords.ContainsKey(character)) { continue; }
uint glyphIndex = face.GetCharIndex(character);
if (glyphIndex == 0) { continue; }
face.SetPixelSizes(0, size);
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
{ {
//glyph is empty, but char still applies advance if (face.Glyph.Metrics.HorizontalAdvance > 0)
GlyphData blankData = new GlyphData( {
advance: (float)face.Glyph.Metrics.HorizontalAdvance, //glyph is empty, but char still applies advance
texIndex: -1); //indicates no texture because the glyph is empty GlyphData blankData = new GlyphData(
texCoords.Add(character, blankData); advance: (float)face.Glyph.Metrics.HorizontalAdvance,
texIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(character, blankData);
}
continue;
} }
return;
}
//stacktrace doesn't really work that well when RenderGlyph throws an exception //stacktrace doesn't really work that well when RenderGlyph throws an exception
face.Glyph.RenderGlyph(RenderMode.Normal); face.Glyph.RenderGlyph(RenderMode.Normal);
bitmap = (byte[])face.Glyph.Bitmap.BufferData.Clone(); bitmap = (byte[])face.Glyph.Bitmap.BufferData.Clone();
glyphWidth = face.Glyph.Bitmap.Width; glyphWidth = face.Glyph.Bitmap.Width;
glyphHeight = bitmap.Length / glyphWidth; glyphHeight = bitmap.Length / glyphWidth;
horizontalAdvance = face.Glyph.Metrics.HorizontalAdvance; horizontalAdvance = face.Glyph.Metrics.HorizontalAdvance;
drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop); drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop);
if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
{
throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions");
}
currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2);
if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1)
{
currentDynamicAtlasCoords.X = 0;
currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY;
currentDynamicAtlasNextY = 0;
}
//no more room in current texture atlas, create a new one
if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1)
{
currentDynamicAtlasCoords.X = 0;
currentDynamicAtlasCoords.Y = 0;
currentDynamicAtlasNextY = 0;
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
currentDynamicPixelBuffer = null;
}
GlyphData newData = new GlyphData( if (glyphWidth > texDims - 1 || glyphHeight > texDims - 1)
advance: (float)horizontalAdvance,
texIndex: textures.Count - 1,
texCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
drawOffset: drawOffset
);
texCoords.Add(character, newData);
if (currentDynamicPixelBuffer == null)
{
currentDynamicPixelBuffer = new uint[texDims * texDims];
textures[newData.TexIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims);
}
for (int y = 0; y < glyphHeight; y++)
{
for (int x = 0; x < glyphWidth; x++)
{ {
byte byteColor = bitmap[x + y * glyphWidth]; throw new Exception(filename + ", " + size.ToString() + ", " + (char)character + "; Glyph dimensions exceed texture atlas dimensions");
currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
} }
}
textures[newData.TexIndex].SetData<uint>(currentDynamicPixelBuffer);
currentDynamicAtlasCoords.X += glyphWidth + 2; currentDynamicAtlasNextY = Math.Max(currentDynamicAtlasNextY, glyphHeight + 2);
if (currentDynamicAtlasCoords.X + glyphWidth + 2 > texDims - 1)
{
currentDynamicAtlasCoords.X = 0;
currentDynamicAtlasCoords.Y += currentDynamicAtlasNextY;
currentDynamicAtlasNextY = 0;
}
//no more room in current texture atlas, create a new one
if (currentDynamicAtlasCoords.Y + glyphHeight + 2 > texDims - 1)
{
if (!firstChar) { textures[^1].SetData<uint>(currentDynamicPixelBuffer); }
currentDynamicAtlasCoords.X = 0;
currentDynamicAtlasCoords.Y = 0;
currentDynamicAtlasNextY = 0;
textures.Add(new Texture2D(gd, texDims, texDims, false, SurfaceFormat.Color));
currentDynamicPixelBuffer = null;
}
GlyphData newData = new GlyphData(
advance: (float)horizontalAdvance,
texIndex: textures.Count - 1,
texCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
drawOffset: drawOffset
);
texCoords.Add(character, newData);
if (currentDynamicPixelBuffer == null)
{
currentDynamicPixelBuffer = new uint[texDims * texDims];
textures[newData.TexIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims);
}
for (int y = 0; y < glyphHeight; y++)
{
for (int x = 0; x < glyphWidth; x++)
{
byte byteColor = bitmap[x + y * glyphWidth];
currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
}
}
currentDynamicAtlasCoords.X += glyphWidth + 2;
firstChar = false;
anyChanges = true;
}
if (anyChanges) { textures[^1].SetData<uint>(currentDynamicPixelBuffer); }
} }
} }
@@ -374,6 +392,10 @@ namespace Barotrauma
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth) public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, text);
}
int lineNum = 0; int lineNum = 0;
Vector2 currentPos = position; Vector2 currentPos = position;
@@ -390,10 +412,6 @@ namespace Barotrauma
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
GlyphData gd = GetGlyphData(charIndex); GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0) if (gd.TexIndex >= 0)
@@ -417,6 +435,10 @@ namespace Barotrauma
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color) public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, text);
}
Vector2 currentPos = position; Vector2 currentPos = position;
for (int i = 0; i < text.Length; i++) for (int i = 0; i < text.Length; i++)
@@ -429,10 +451,6 @@ namespace Barotrauma
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
GlyphData gd = GetGlyphData(charIndex); GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0) if (gd.TexIndex >= 0)
@@ -452,6 +470,10 @@ namespace Barotrauma
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData, int rtdOffset = 0) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData, int rtdOffset = 0)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, text);
}
int lineNum = 0; int lineNum = 0;
Vector2 currentPos = position; Vector2 currentPos = position;
@@ -472,10 +494,6 @@ namespace Barotrauma
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
Color currentTextColor; Color currentTextColor;
@@ -626,6 +644,10 @@ namespace Barotrauma
{ {
retVal.Y = baseHeight; retVal.Y = baseHeight;
} }
if (DynamicLoading)
{
DynamicRenderAtlas(graphicsDevice, text);
}
for (int i = 0; i < text.Length; i++) for (int i = 0; i < text.Length; i++)
{ {
@@ -636,10 +658,6 @@ namespace Barotrauma
continue; continue;
} }
uint charIndex = text[i]; uint charIndex = text[i];
if (DynamicLoading && !texCoords.ContainsKey(charIndex))
{
DynamicRenderAtlas(graphicsDevice, charIndex);
}
GlyphData gd = GetGlyphData(charIndex); GlyphData gd = GetGlyphData(charIndex);
currentLineX += gd.Advance; currentLineX += gd.Advance;
@@ -44,12 +44,12 @@ namespace Barotrauma
Waiting, // Hourglass Waiting, // Hourglass
WaitingBackground // Cursor + Hourglass WaitingBackground // Cursor + Hourglass
} }
public static class GUI public static class GUI
{ {
public static GUICanvas Canvas => GUICanvas.Instance; public static GUICanvas Canvas => GUICanvas.Instance;
public static CursorState MouseCursor = CursorState.Default; public static CursorState MouseCursor = CursorState.Default;
public static readonly SamplerState SamplerState = new SamplerState() public static readonly SamplerState SamplerState = new SamplerState()
{ {
Filter = TextureFilter.Linear, Filter = TextureFilter.Linear,
@@ -116,14 +116,14 @@ namespace Barotrauma
public static float SlicedSpriteScale public static float SlicedSpriteScale
{ {
get get
{ {
if (Math.Abs(1.0f - Scale) < 0.1f) if (Math.Abs(1.0f - Scale) < 0.1f)
{ {
//don't scale if very close to the "reference resolution" //don't scale if very close to the "reference resolution"
return 1.0f; return 1.0f;
} }
return Scale; return Scale;
} }
} }
@@ -306,7 +306,7 @@ namespace Barotrauma
t = new Texture2D(GraphicsDevice, 1, 1); t = new Texture2D(GraphicsDevice, 1, 1);
t.SetData(new Color[] { Color.White });// fill the texture with white t.SetData(new Color[] { Color.White });// fill the texture with white
}); });
SubmarineIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(452, 385, 182, 81), new Vector2(0.5f, 0.5f)); SubmarineIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(452, 385, 182, 81), new Vector2(0.5f, 0.5f));
arrow = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(393, 393, 49, 45), new Vector2(0.5f, 0.5f)); arrow = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(393, 393, 49, 45), new Vector2(0.5f, 0.5f));
SpeechBubbleIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(385, 449, 66, 60), new Vector2(0.5f, 0.5f)); SpeechBubbleIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(385, 449, 66, 60), new Vector2(0.5f, 0.5f));
@@ -314,7 +314,7 @@ namespace Barotrauma
} }
/// <summary> /// <summary>
/// By default, all the gui elements are drawn automatically in the same order they appear on the update list. /// By default, all the gui elements are drawn automatically in the same order they appear on the update list.
/// </summary> /// </summary>
public static void Draw(Camera cam, SpriteBatch spriteBatch) public static void Draw(Camera cam, SpriteBatch spriteBatch)
{ {
@@ -706,11 +706,11 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Immediate, effect: GameMain.GameScreen.PostProcessEffect); spriteBatch.Begin(SpriteSortMode.Immediate, effect: GameMain.GameScreen.PostProcessEffect);
float scale = Math.Max( float scale = Math.Max(
(float)GameMain.GraphicsWidth / backgroundSprite.SourceRect.Width, (float)GameMain.GraphicsWidth / backgroundSprite.SourceRect.Width,
(float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f; (float)GameMain.GraphicsHeight / backgroundSprite.SourceRect.Height) * 1.1f;
float paddingX = backgroundSprite.SourceRect.Width * scale - GameMain.GraphicsWidth; float paddingX = backgroundSprite.SourceRect.Width * scale - GameMain.GraphicsWidth;
float paddingY = backgroundSprite.SourceRect.Height * scale - GameMain.GraphicsHeight; float paddingY = backgroundSprite.SourceRect.Height * scale - GameMain.GraphicsHeight;
double noiseT = (Timing.TotalTime * 0.02f); double noiseT = (Timing.TotalTime * 0.02f);
Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f); Vector2 pos = new Vector2((float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0) - 0.5f, (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0.5f) - 0.5f);
pos = new Vector2(pos.X * paddingX, pos.Y * paddingY); pos = new Vector2(pos.X * paddingX, pos.Y * paddingY);
@@ -719,7 +719,7 @@ namespace Barotrauma
new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2 + pos,
null, Color.White, 0.0f, backgroundSprite.size / 2, null, Color.White, 0.0f, backgroundSprite.size / 2,
scale, SpriteEffects.None, 0.0f); scale, SpriteEffects.None, 0.0f);
spriteBatch.End(); spriteBatch.End();
} }
@@ -759,8 +759,8 @@ namespace Barotrauma
else else
{ {
additions.Enqueue(component); additions.Enqueue(component);
} }
} }
} }
/// <summary> /// <summary>
@@ -786,7 +786,7 @@ namespace Barotrauma
component.Children.ForEach(c => RemoveFromUpdateList(c)); component.Children.ForEach(c => RemoveFromUpdateList(c));
} }
} }
} }
} }
public static void ClearUpdateList() public static void ClearUpdateList()
@@ -900,7 +900,7 @@ namespace Barotrauma
{ {
GUIMessageBox.VisibleBox.AddToGUIUpdateList(); GUIMessageBox.VisibleBox.AddToGUIUpdateList();
} }
} }
} }
#endregion #endregion
@@ -941,7 +941,7 @@ namespace Barotrauma
inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame);
} }
if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) ||
(prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld() && !Inventory.DraggingItems.Any())) (prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld() && !Inventory.DraggingItems.Any()))
{ {
for (var i = updateList.Count - 1; i > inventoryIndex; i--) for (var i = updateList.Count - 1; i > inventoryIndex; i--)
@@ -967,7 +967,7 @@ namespace Barotrauma
return MouseOn; return MouseOn;
} }
} }
private static CursorState UpdateMouseCursorState(GUIComponent c) private static CursorState UpdateMouseCursorState(GUIComponent c)
{ {
lock (mutex) lock (mutex)
@@ -994,7 +994,7 @@ namespace Barotrauma
} }
if (Wire.DraggingWire != null) { return CursorState.Dragging; } if (Wire.DraggingWire != null) { return CursorState.Dragging; }
} }
if (c == null || c is GUICustomComponent) if (c == null || c is GUICustomComponent)
{ {
switch (Screen.Selected) switch (Screen.Selected)
@@ -1027,7 +1027,7 @@ namespace Barotrauma
} }
} }
} }
if (c != null && c.Visible) if (c != null && c.Visible)
{ {
if (c.AlwaysOverrideCursor) { return c.HoverCursor; } if (c.AlwaysOverrideCursor) { return c.HoverCursor; }
@@ -1036,20 +1036,20 @@ namespace Barotrauma
// And this is of course picked up as clickable area. // And this is of course picked up as clickable area.
// There has to be a better way of checking this but for now this works. // There has to be a better way of checking this but for now this works.
var monitorRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight); var monitorRect = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
var parent = FindInteractParent(c); var parent = FindInteractParent(c);
if (c.Enabled) if (c.Enabled)
{ {
// Some parent elements take priority // Some parent elements take priority
// but not when the child is a GUIButton or GUITickBox // but not when the child is a GUIButton or GUITickBox
if (!(parent is GUIButton) && !(parent is GUIListBox) || if (!(parent is GUIButton) && !(parent is GUIListBox) ||
(c is GUIButton) || (c is GUITickBox)) (c is GUIButton) || (c is GUITickBox))
{ {
if (!c.Rect.Equals(monitorRect)) { return c.HoverCursor; } if (!c.Rect.Equals(monitorRect)) { return c.HoverCursor; }
} }
} }
// Children in list boxes can be interacted with despite not having // Children in list boxes can be interacted with despite not having
// a GUIButton inside of them so instead of hard coding we check if // a GUIButton inside of them so instead of hard coding we check if
// the children can be interacted with by checking their hover state // the children can be interacted with by checking their hover state
@@ -1084,7 +1084,7 @@ namespace Barotrauma
{ {
// Health menus // Health menus
if (character.CharacterHealth.MouseOnElement) { return CursorState.Hand; } if (character.CharacterHealth.MouseOnElement) { return CursorState.Hand; }
if (character.SelectedCharacter != null) if (character.SelectedCharacter != null)
{ {
if (character.SelectedCharacter.CharacterHealth.MouseOnElement) if (character.SelectedCharacter.CharacterHealth.MouseOnElement)
@@ -1096,7 +1096,7 @@ namespace Barotrauma
// Character is hovering over an item placed in the world // Character is hovering over an item placed in the world
if (character.FocusedItem != null) { return CursorState.Hand; } if (character.FocusedItem != null) { return CursorState.Hand; }
} }
return CursorState.Default; return CursorState.Default;
static GUIComponent FindInteractParent(GUIComponent component) static GUIComponent FindInteractParent(GUIComponent component)
@@ -1130,7 +1130,7 @@ namespace Barotrauma
} }
} }
} }
static bool ContainsMouse(GUIComponent component) static bool ContainsMouse(GUIComponent component)
{ {
// If component has a mouse rectangle then use that, if not use it's physical rect // If component has a mouse rectangle then use that, if not use it's physical rect
@@ -1138,7 +1138,7 @@ namespace Barotrauma
component.MouseRect.Contains(PlayerInput.MousePosition) : component.MouseRect.Contains(PlayerInput.MousePosition) :
component.Rect.Contains(PlayerInput.MousePosition); component.Rect.Contains(PlayerInput.MousePosition);
} }
} }
} }
/// <summary> /// <summary>
@@ -1153,8 +1153,8 @@ namespace Barotrauma
{ {
MouseCursor = CursorState.Waiting; MouseCursor = CursorState.Waiting;
var timeOut = DateTime.Now + new TimeSpan(0, 0, waitSeconds); var timeOut = DateTime.Now + new TimeSpan(0, 0, waitSeconds);
while (DateTime.Now < timeOut) while (DateTime.Now < timeOut)
{ {
if (endCondition != null) if (endCondition != null)
{ {
try try
@@ -1163,13 +1163,13 @@ namespace Barotrauma
} }
catch { break; } catch { break; }
} }
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
if (MouseCursor == CursorState.Waiting) { MouseCursor = CursorState.Default; } if (MouseCursor == CursorState.Waiting) { MouseCursor = CursorState.Default; }
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
} }
public static void ClearCursorWait() public static void ClearCursorWait()
{ {
lock (mutex) lock (mutex)
@@ -1208,7 +1208,7 @@ namespace Barotrauma
{ {
debugDrawMetadataOffset--; debugDrawMetadataOffset--;
} }
if (PlayerInput.KeyHit(Keys.Down)) if (PlayerInput.KeyHit(Keys.Down))
{ {
debugDrawMetadataOffset++; debugDrawMetadataOffset++;
@@ -1240,17 +1240,20 @@ namespace Barotrauma
debugDrawMetadataOffset = 0; debugDrawMetadataOffset = 0;
} }
} }
} }
HandlePersistingElements(deltaTime); HandlePersistingElements(deltaTime);
RefreshUpdateList(); RefreshUpdateList();
UpdateMouseOn(); UpdateMouseOn();
Debug.Assert(updateList.Count == updateListSet.Count); Debug.Assert(updateList.Count == updateListSet.Count);
updateList.ForEach(c => c.UpdateAuto(deltaTime)); foreach (var c in updateList)
{
c.UpdateAuto(deltaTime);
}
UpdateMessages(deltaTime); UpdateMessages(deltaTime);
UpdateSavingIndicator(deltaTime); UpdateSavingIndicator(deltaTime);
} }
} }
private static void UpdateMessages(float deltaTime) private static void UpdateMessages(float deltaTime)
@@ -1281,17 +1284,16 @@ namespace Barotrauma
//only the first message (the currently visible one) is updated at a time //only the first message (the currently visible one) is updated at a time
break; break;
} }
foreach (GUIMessage msg in messages) foreach (GUIMessage msg in messages)
{ {
if (!msg.WorldSpace) { continue; } if (!msg.WorldSpace) { continue; }
msg.Timer -= deltaTime; msg.Timer -= deltaTime;
msg.Pos += msg.Velocity * deltaTime; msg.Pos += msg.Velocity * deltaTime;
} }
messages.RemoveAll(m => m.Timer <= 0.0f); messages.RemoveAll(m => m.Timer <= 0.0f);
} }
} }
private static void UpdateSavingIndicator(float deltaTime) private static void UpdateSavingIndicator(float deltaTime)
@@ -1628,7 +1630,7 @@ namespace Barotrauma
private static void DrawMessages(SpriteBatch spriteBatch, Camera cam) private static void DrawMessages(SpriteBatch spriteBatch, Camera cam)
{ {
if (messages.Count == 0) return; if (messages.Count == 0) { return; }
bool useScissorRect = messages.Any(m => !m.WorldSpace); bool useScissorRect = messages.Any(m => !m.WorldSpace);
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
@@ -1647,7 +1649,7 @@ namespace Barotrauma
msg.Font.DrawString(spriteBatch, msg.Text, drawPos + msg.DrawPos + Vector2.One, Color.Black, 0, msg.Origin, 1.0f, SpriteEffects.None, 0); msg.Font.DrawString(spriteBatch, msg.Text, drawPos + msg.DrawPos + Vector2.One, Color.Black, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
msg.Font.DrawString(spriteBatch, msg.Text, drawPos + msg.DrawPos, msg.Color, 0, msg.Origin, 1.0f, SpriteEffects.None, 0); msg.Font.DrawString(spriteBatch, msg.Text, drawPos + msg.DrawPos, msg.Color, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
break; break;
} }
if (useScissorRect) if (useScissorRect)
@@ -1656,11 +1658,11 @@ namespace Barotrauma
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
spriteBatch.Begin(SpriteSortMode.Deferred); spriteBatch.Begin(SpriteSortMode.Deferred);
} }
foreach (GUIMessage msg in messages) foreach (GUIMessage msg in messages)
{ {
if (!msg.WorldSpace) { continue; } if (!msg.WorldSpace) { continue; }
if (cam != null) if (cam != null)
{ {
float alpha = 1.0f; float alpha = 1.0f;
@@ -1669,7 +1671,7 @@ namespace Barotrauma
Vector2 drawPos = cam.WorldToScreen(msg.DrawPos); Vector2 drawPos = cam.WorldToScreen(msg.DrawPos);
msg.Font.DrawString(spriteBatch, msg.Text, drawPos + Vector2.One, Color.Black * alpha, 0, msg.Origin, 1.0f, SpriteEffects.None, 0); msg.Font.DrawString(spriteBatch, msg.Text, drawPos + Vector2.One, Color.Black * alpha, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
msg.Font.DrawString(spriteBatch, msg.Text, drawPos, msg.Color * alpha, 0, msg.Origin, 1.0f, SpriteEffects.None, 0); msg.Font.DrawString(spriteBatch, msg.Text, drawPos, msg.Color * alpha, 0, msg.Origin, 1.0f, SpriteEffects.None, 0);
} }
} }
messages.RemoveAll(m => m.Timer <= 0.0f); messages.RemoveAll(m => m.Timer <= 0.0f);
@@ -1770,7 +1772,7 @@ namespace Barotrauma
{ {
int textureWidth = Math.Max(radius * 2, 1); int textureWidth = Math.Max(radius * 2, 1);
int textureHeight = Math.Max(height + radius * 2, 1); int textureHeight = Math.Max(height + radius * 2, 1);
Color[] data = new Color[textureWidth * textureHeight]; Color[] data = new Color[textureWidth * textureHeight];
// Colour the entire texture transparent first. // Colour the entire texture transparent first.
@@ -1880,9 +1882,9 @@ namespace Barotrauma
/// Creates multiple elements with relative size and positions them automatically. /// Creates multiple elements with relative size and positions them automatically.
/// </summary> /// </summary>
public static List<T> CreateElements<T>(int count, Vector2 relativeSize, RectTransform parent, Func<RectTransform, T> constructor, public static List<T> CreateElements<T>(int count, Vector2 relativeSize, RectTransform parent, Func<RectTransform, T> constructor,
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null,
int absoluteSpacing = 0, float relativeSpacing = 0, Func<int, int> extraSpacing = null, int absoluteSpacing = 0, float relativeSpacing = 0, Func<int, int> extraSpacing = null,
int startOffsetAbsolute = 0, float startOffsetRelative = 0, bool isHorizontal = false) int startOffsetAbsolute = 0, float startOffsetRelative = 0, bool isHorizontal = false)
where T : GUIComponent where T : GUIComponent
{ {
return CreateElements(count, parent, constructor, relativeSize, null, anchor, pivot, minSize, maxSize, absoluteSpacing, relativeSpacing, extraSpacing, startOffsetAbsolute, startOffsetRelative, isHorizontal); return CreateElements(count, parent, constructor, relativeSize, null, anchor, pivot, minSize, maxSize, absoluteSpacing, relativeSpacing, extraSpacing, startOffsetAbsolute, startOffsetRelative, isHorizontal);
@@ -1891,8 +1893,8 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Creates multiple elements with absolute size and positions them automatically. /// Creates multiple elements with absolute size and positions them automatically.
/// </summary> /// </summary>
public static List<T> CreateElements<T>(int count, Point absoluteSize, RectTransform parent, Func<RectTransform, T> constructor, public static List<T> CreateElements<T>(int count, Point absoluteSize, RectTransform parent, Func<RectTransform, T> constructor,
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Anchor anchor = Anchor.TopLeft, Pivot? pivot = null,
int absoluteSpacing = 0, float relativeSpacing = 0, Func<int, int> extraSpacing = null, int absoluteSpacing = 0, float relativeSpacing = 0, Func<int, int> extraSpacing = null,
int startOffsetAbsolute = 0, float startOffsetRelative = 0, bool isHorizontal = false) int startOffsetAbsolute = 0, float startOffsetRelative = 0, bool isHorizontal = false)
where T : GUIComponent where T : GUIComponent
@@ -1991,7 +1993,7 @@ namespace Barotrauma
if (i == 0) if (i == 0)
numberInput.IntValue = value.X; numberInput.IntValue = value.X;
else else
numberInput.IntValue = value.Y; numberInput.IntValue = value.Y;
} }
return frame; return frame;
} }
@@ -2028,6 +2030,16 @@ namespace Barotrauma
return frame; return frame;
} }
public static void NotifyPrompt(string header, string body)
{
GUIMessageBox msgBox = new GUIMessageBox(header, body, new[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
msgBox.Buttons[0].OnClicked = delegate
{
msgBox.Close();
return true;
};
}
public static GUIMessageBox AskForConfirmation(string header, string body, Action onConfirm, Action onDeny = null) public static GUIMessageBox AskForConfirmation(string header, string body, Action onConfirm, Action onDeny = null)
{ {
string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") }; string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
@@ -2051,6 +2063,32 @@ namespace Barotrauma
return msgBox; return msgBox;
} }
public static GUIMessageBox PromptTextInput(string header, string body, Action<string> onConfirm)
{
string[] buttons = { TextManager.Get("Ok"), TextManager.Get("Cancel") };
GUIMessageBox msgBox = new GUIMessageBox(header, string.Empty, buttons, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
GUITextBox textBox = new GUITextBox(new RectTransform(Vector2.One, msgBox.Content.RectTransform), text: body)
{
OverflowClip = true
};
// Cancel button
msgBox.Buttons[1].OnClicked = delegate
{
msgBox.Close();
return true;
};
// Ok button
msgBox.Buttons[0].OnClicked = delegate
{
onConfirm.Invoke(textBox.Text);
msgBox.Close();
return true;
};
return msgBox;
}
#endregion #endregion
#region Element positioning #region Element positioning
@@ -2210,7 +2248,7 @@ namespace Barotrauma
if (disallowedAreas == null) { continue; } if (disallowedAreas == null) { continue; }
foreach (Rectangle rect2 in disallowedAreas) foreach (Rectangle rect2 in disallowedAreas)
{ {
if (!rect1.Intersects(rect2)) { continue; } if (!rect1.Intersects(rect2)) { continue; }
intersections = true; intersections = true;
Point centerDiff = rect1.Center - rect2.Center; Point centerDiff = rect1.Center - rect2.Center;
@@ -2330,8 +2368,8 @@ namespace Barotrauma
}); });
} }
CreateButton(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby" : "EndRound", buttonContainer, CreateButton(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby" : "EndRound", buttonContainer,
verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd", verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
action: () => action: () =>
{ {
GameMain.Client?.RequestRoundEnd(save: false); GameMain.Client?.RequestRoundEnd(save: false);
@@ -2448,7 +2486,10 @@ namespace Barotrauma
public static void ClearMessages() public static void ClearMessages()
{ {
messages.Clear(); lock (mutex)
{
messages.Clear();
}
} }
public static bool IsFourByThree() public static bool IsFourByThree()
@@ -219,12 +219,6 @@ namespace Barotrauma
GUI.Style.ButtonPulse.Draw(spriteBatch, expandRect, ToolBox.GradientLerp(pulseExpand, Color.White, Color.White, Color.Transparent)); GUI.Style.ButtonPulse.Draw(spriteBatch, expandRect, ToolBox.GradientLerp(pulseExpand, Color.White, Color.White, Color.Transparent));
} }
if (UserData is string s && s == "ReadyCheckButton" && ReadyCheck.lastReadyCheck > DateTime.Now)
{
float progress = (ReadyCheck.lastReadyCheck - DateTime.Now).Seconds / 60.0f;
Frame.Color = ToolBox.GradientLerp(progress, Color.White, GUI.Style.Red);
}
} }
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
@@ -317,7 +317,10 @@ namespace Barotrauma
set set
{ {
selected = value; selected = value;
Children.ForEach(c => c.Selected = value); foreach (var child in Children)
{
child.Selected = value;
}
} }
} }
public virtual ComponentState State public virtual ComponentState State
@@ -537,7 +540,10 @@ namespace Barotrauma
//would be real nice to un-jank this some day //would be real nice to un-jank this some day
ForceUpdate(); ForceUpdate();
ForceUpdate(); ForceUpdate();
foreach (var child in Children) { child.ForceLayoutRecalculation(); } foreach (var child in Children)
{
child.ForceLayoutRecalculation();
}
} }
public void ForceUpdate() => Update((float)Timing.Step); public void ForceUpdate() => Update((float)Timing.Step);
@@ -547,7 +553,10 @@ namespace Barotrauma
/// </summary> /// </summary>
public void UpdateChildren(float deltaTime, bool recursive) public void UpdateChildren(float deltaTime, bool recursive)
{ {
RectTransform.Children.ForEach(c => c.GUIComponent.UpdateManually(deltaTime, recursive, recursive)); foreach (var child in RectTransform.Children)
{
child.GUIComponent.UpdateManually(deltaTime, recursive, recursive);
}
} }
#endregion #endregion
@@ -583,7 +592,10 @@ namespace Barotrauma
/// </summary> /// </summary>
public virtual void DrawChildren(SpriteBatch spriteBatch, bool recursive) public virtual void DrawChildren(SpriteBatch spriteBatch, bool recursive)
{ {
RectTransform.Children.ForEach(c => c.GUIComponent.DrawManually(spriteBatch, recursive, recursive)); foreach (RectTransform child in RectTransform.Children)
{
child.GUIComponent.DrawManually(spriteBatch, recursive, recursive);
}
} }
protected Color _currentColor; protected Color _currentColor;
@@ -764,8 +776,8 @@ namespace Barotrauma
{ {
toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), richTextData, toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip"); toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), richTextData, toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip");
toolTipBlock.RectTransform.NonScaledSize = new Point( toolTipBlock.RectTransform.NonScaledSize = new Point(
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z), (int)(toolTipBlock.Font.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z),
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W)); (int)(toolTipBlock.Font.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W));
toolTipBlock.userData = toolTip; toolTipBlock.userData = toolTip;
} }
@@ -827,6 +827,13 @@ namespace Barotrauma
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
foreach (GUIComponent child in Children)
{
if (child == ScrollBar || child == Content || child == ContentBackground) { continue; }
throw new InvalidOperationException($"Children were found in {nameof(GUIListBox)}, Add them to {nameof(GUIListBox)}.{nameof(Content)} instead.");
}
if (!Visible) { return; } if (!Visible) { return; }
UpdateChildrenRect(); UpdateChildrenRect();
@@ -837,7 +844,6 @@ namespace Barotrauma
UpdateScrollBarSize(); UpdateScrollBarSize();
} }
if (FadeElements) if (FadeElements)
{ {
foreach (var (component, _) in childVisible) foreach (var (component, _) in childVisible)
@@ -319,28 +319,29 @@ namespace Barotrauma
AbsoluteSpacing = absoluteSpacing.Y, AbsoluteSpacing = absoluteSpacing.Y,
}; };
var bottomContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.3f), verticalLayoutGroup.RectTransform), style: null); var bottomContainer = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.3f), verticalLayoutGroup.RectTransform), style: null)
var tickBoxLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.67f, 1.0f), bottomContainer.RectTransform, anchor: Anchor.CenterLeft),
isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
Stretch = true, CanBeFocused = true
RelativeSpacing = 0.02f
}; };
var dontShowAgainTickBox = new GUITickBox(new RectTransform(new Vector2(0.5f, 1.0f), tickBoxLayoutGroup.RectTransform), var tickBoxLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.67f, 1.0f), bottomContainer.RectTransform, anchor: Anchor.CenterLeft))
{
CanBeFocused = true,
Stretch = true
};
Vector2 tickBoxRelativeSize = new Vector2(1.0f, 0.5f);
var dontShowAgainTickBox = new GUITickBox(new RectTransform(tickBoxRelativeSize, tickBoxLayoutGroup.RectTransform),
TextManager.Get("hintmessagebox.dontshowagain")) TextManager.Get("hintmessagebox.dontshowagain"))
{ {
ToolTip = TextManager.Get("hintmessagebox.dontshowagaintooltip"), ToolTip = TextManager.Get("hintmessagebox.dontshowagaintooltip"),
UserData = "dontshowagain" UserData = "dontshowagain"
}; };
var disableHintsTickBox = new GUITickBox(new RectTransform(tickBoxRelativeSize, tickBoxLayoutGroup.RectTransform),
//var disableHintsTickBox = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), tickBoxLayoutGroup.RectTransform), TextManager.Get("hintmessagebox.disablehints"))
// TextManager.Get("hintmessagebox.disablehints")) {
//{ ToolTip = TextManager.Get("hintmessagebox.disablehintstooltip"),
// ToolTip = TextManager.Get("hintmessagebox.disablehintstooltip"), UserData = "disablehints"
// UserData = "disablehints" };
//};
Buttons = new List<GUIButton>(1) Buttons = new List<GUIButton>(1)
{ {
@@ -379,12 +380,16 @@ namespace Barotrauma
upperContainerHeight = Math.Max(upperContainerHeight, Icon.Rect.Height); upperContainerHeight = Math.Max(upperContainerHeight, Icon.Rect.Height);
height += upperContainerHeight; height += upperContainerHeight;
height += absoluteSpacing.Y; height += absoluteSpacing.Y;
height += (int)((bottomContainer.RectTransform.RelativeSize.Y / topHorizontalLayoutGroup.RectTransform.RelativeSize.Y) * upperContainerHeight); int bottomContainerHeight = dontShowAgainTickBox.Rect.Height + disableHintsTickBox.Rect.Height;
height += bottomContainerHeight;
height += absoluteSpacing.Y; height += absoluteSpacing.Y;
if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); } if (minSize.HasValue) { height = Math.Max(height, minSize.Value.Y); }
InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, height); InnerFrame.RectTransform.NonScaledSize = new Point(InnerFrame.Rect.Width, height);
verticalLayoutGroup.RectTransform.NonScaledSize = GetVerticalLayoutGroupSize(); verticalLayoutGroup.RectTransform.NonScaledSize = GetVerticalLayoutGroupSize();
float upperContainerRelativeHeight = (float)upperContainerHeight / (upperContainerHeight + bottomContainerHeight);
topHorizontalLayoutGroup.RectTransform.RelativeSize = new Vector2(topHorizontalLayoutGroup.RectTransform.RelativeSize.X, upperContainerRelativeHeight);
bottomContainer.RectTransform.RelativeSize = new Vector2(bottomContainer.RectTransform.RelativeSize.X, 1.0f - upperContainerRelativeHeight);
verticalLayoutGroup.Recalculate(); verticalLayoutGroup.Recalculate();
topHorizontalLayoutGroup.Recalculate(); topHorizontalLayoutGroup.Recalculate();
Content.Recalculate(); Content.Recalculate();
@@ -29,6 +29,12 @@ namespace Barotrauma
ClampChildMouseRects(Content); ClampChildMouseRects(Content);
} }
public override void DrawChildren(SpriteBatch spriteBatch, bool recursive)
{
//do nothing (the children have to be drawn in the Draw method after the ScissorRectangle has been set)
return;
}
protected override void Draw(SpriteBatch spriteBatch) protected override void Draw(SpriteBatch spriteBatch)
{ {
if (!Visible) { return; } if (!Visible) { return; }
@@ -204,6 +204,12 @@ namespace Barotrauma
set { textColor = value; } set { textColor = value; }
} }
public Color DisabledTextColor
{
get => disabledTextColor;
set => disabledTextColor = value;
}
private Color? hoverTextColor; private Color? hoverTextColor;
public Color HoverTextColor public Color HoverTextColor
{ {
@@ -303,6 +309,10 @@ namespace Barotrauma
if (parseRichText) if (parseRichText)
{ {
RichTextData = Barotrauma.RichTextData.GetRichTextData(text, out text); RichTextData = Barotrauma.RichTextData.GetRichTextData(text, out text);
if (RichTextData != null && RichTextData.Count == 0)
{
RichTextData = null;
}
} }
//if the text is in chinese/korean/japanese and we're not using a CJK-compatible font, //if the text is in chinese/korean/japanese and we're not using a CJK-compatible font,
@@ -457,7 +467,7 @@ namespace Barotrauma
while (size == Vector2.Zero) while (size == Vector2.Zero)
{ {
try { size = Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text); } try { size = Font.MeasureString(string.IsNullOrEmpty(text) ? " " : text); }
catch { text = text.Substring(0, text.Length - 1); } catch { text = text.Length > 0 ? text.Substring(0, text.Length - 1) : ""; }
} }
return size; return size;
@@ -70,6 +70,8 @@ namespace Barotrauma
private Vector2 selectionEndPos; private Vector2 selectionEndPos;
private Vector2 selectionRectSize; private Vector2 selectionRectSize;
private GUICustomComponent caretAndSelectionRenderer;
private bool mouseHeldInside; private bool mouseHeldInside;
private readonly Memento<string> memento = new Memento<string>(); private readonly Memento<string> memento = new Memento<string>();
@@ -178,8 +180,7 @@ namespace Barotrauma
} }
set set
{ {
base.ToolTip = value; base.ToolTip = textBlock.ToolTip = caretAndSelectionRenderer.ToolTip = value;
textBlock.ToolTip = value;
} }
} }
@@ -268,7 +269,7 @@ namespace Barotrauma
CaretEnabled = true; CaretEnabled = true;
caretPosDirty = true; caretPosDirty = true;
new GUICustomComponent(new RectTransform(Vector2.One, frame.RectTransform), onDraw: DrawCaretAndSelection); caretAndSelectionRenderer = new GUICustomComponent(new RectTransform(Vector2.One, frame.RectTransform), onDraw: DrawCaretAndSelection);
int clearButtonWidth = 0; int clearButtonWidth = 0;
if (createClearButton) if (createClearButton)
File diff suppressed because it is too large Load Diff
@@ -637,7 +637,15 @@ namespace Barotrauma
public bool IsParentOf(RectTransform rectT, bool recursive = true) public bool IsParentOf(RectTransform rectT, bool recursive = true)
{ {
return children.Contains(rectT) || (recursive && children.Any(c => c.IsParentOf(rectT))); if (children.Contains(rectT)) { return true; }
if (recursive)
{
foreach (var child in children)
{
if (child.IsParentOf(rectT)) { return true; }
}
}
return false;
} }
public bool IsChildOf(RectTransform rectT, bool recursive = true) public bool IsChildOf(RectTransform rectT, bool recursive = true)
@@ -414,15 +414,8 @@ namespace Barotrauma
} }
else else
{ {
if (GameMain.Client == null) subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs())
{ .Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
subsToShow.AddRange(SubmarineInfo.SavedSubmarines.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
}
else
{
subsToShow.AddRange(GameMain.NetLobbyScreen.CampaignSubmarines.Where(s => !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
}
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass)); subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
} }
@@ -446,20 +439,11 @@ namespace Barotrauma
if (preview == null) if (preview == null)
{ {
SubmarineInfo potentialMatch; SubmarineInfo potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal);
if (GameMain.Client == null)
{
potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal);
}
else
{
potentialMatch = GameMain.NetLobbyScreen.CampaignSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal);
}
preview = potentialMatch?.PreviewImage; preview = potentialMatch?.PreviewImage;
// Try from savedsubmarines with name comparison as a backup // Try name comparison as a backup
if (preview == null) if (preview == null)
{ {
potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == info.Name); potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == info.Name);
@@ -1543,30 +1543,12 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(skillNames); GUITextBlock.AutoScaleAndNormalize(skillNames);
} }
private bool HasUnlockedAllTalents(Character controlledCharacter)
{
if (TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree))
{
foreach (TalentSubTree talentSubTree in talentTree.TalentSubTrees)
{
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
{
if (talentOption.Talents.None(t => controlledCharacter.HasTalent(t.Identifier)))
{
return false;
}
}
}
}
return true;
}
private void UpdateTalentButtons() private void UpdateTalentButtons()
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return; } if (controlledCharacter?.Info == null) { return; }
bool unlockedAllTalents = HasUnlockedAllTalents(controlledCharacter); bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
if (unlockedAllTalents) if (unlockedAllTalents)
{ {
@@ -16,7 +16,6 @@ using Microsoft.Xna.Framework.Input;
namespace Barotrauma namespace Barotrauma
{ {
internal class UpgradeStore internal class UpgradeStore
{ {
public readonly struct CategoryData public readonly struct CategoryData
@@ -1688,7 +1687,7 @@ namespace Barotrauma
private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(); private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign();
private static string FormatCurrency(int money, bool format = true) public static string FormatCurrency(int money, bool format = true)
{ {
return TextManager.GetWithVariable("CurrencyFormat", "[credits]", format ? string.Format(CultureInfo.InvariantCulture, "{0:N0}", money) : money.ToString()); return TextManager.GetWithVariable("CurrencyFormat", "[credits]", format ? string.Format(CultureInfo.InvariantCulture, "{0:N0}", money) : money.ToString());
} }
@@ -437,8 +437,8 @@ namespace Barotrauma
TaskPool.Add("AutoUpdateWorkshopItemsAsync", TaskPool.Add("AutoUpdateWorkshopItemsAsync",
SteamManager.AutoUpdateWorkshopItemsAsync(), (task) => SteamManager.AutoUpdateWorkshopItemsAsync(), (task) =>
{ {
bool result = ((Task<bool>)task).Result; if (!task.TryGetResult(out bool result)) { return; }
Config.WaitingForAutoUpdate = false; Config.WaitingForAutoUpdate = false;
}); });
@@ -756,7 +756,10 @@ namespace Barotrauma
} }
#if DEBUG #if DEBUG
CancelQuickStart |= PlayerInput.KeyDown(Keys.LeftShift); if (PlayerInput.KeyHit(Keys.LeftShift))
{
CancelQuickStart = !CancelQuickStart;
}
if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !CancelQuickStart) if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled || Config.TestScreenEnabled) && FirstLoad && !CancelQuickStart)
{ {
@@ -718,7 +718,7 @@ namespace Barotrauma
/// Sets the character's current order (if it's close enough to receive messages from orderGiver) and /// Sets the character's current order (if it's close enough to receive messages from orderGiver) and
/// displays the order in the crew UI /// displays the order in the crew UI
/// </summary> /// </summary>
public void SetCharacterOrder(Character character, Order order, string option, int priority, Character orderGiver, Hull targetHull = null) public void SetCharacterOrder(Character character, Order order, string option, int priority, Character orderGiver, Hull targetHull = null, bool isNewOrder = true)
{ {
if (order != null && order.TargetAllCharacters) if (order != null && order.TargetAllCharacters)
{ {
@@ -768,7 +768,7 @@ namespace Barotrauma
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
orderGiver.Speak(order.GetChatMessage("", hull?.DisplayName, givingOrderToSelf: character == orderGiver), ChatMessageType.Order); orderGiver.Speak(order.GetChatMessage("", hull?.DisplayName, givingOrderToSelf: character == orderGiver, isNewOrder: isNewOrder), ChatMessageType.Order);
} }
else else
{ {
@@ -784,7 +784,7 @@ namespace Barotrauma
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
character.SetOrder(order, option, priority, orderGiver, speak: orderGiver != character); character.SetOrder(order, option, priority, orderGiver, speak: orderGiver != character);
string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, priority: priority); string message = order?.GetChatMessage(character.Name, orderGiver?.CurrentHull?.DisplayName, givingOrderToSelf: character == orderGiver, orderOption: option, isNewOrder: isNewOrder);
orderGiver?.Speak(message); orderGiver?.Speak(message);
} }
else if (orderGiver != null) else if (orderGiver != null)
@@ -1071,7 +1071,7 @@ namespace Barotrauma
var priority = Math.Max(CharacterInfo.HighestManualOrderPriority - orderList.Content.GetChildIndex(orderComponent), 1); var priority = Math.Max(CharacterInfo.HighestManualOrderPriority - orderList.Content.GetChildIndex(orderComponent), 1);
if (orderInfo.ManualPriority == priority) { return; } if (orderInfo.ManualPriority == priority) { return; }
var character = (Character)orderList.UserData; var character = (Character)orderList.UserData;
SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, priority, Character.Controlled); SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, priority, Character.Controlled, isNewOrder: false);
} }
private string CreateOrderTooltip(Order orderPrefab, string option, Entity targetEntity) private string CreateOrderTooltip(Order orderPrefab, string option, Entity targetEntity)
@@ -2582,15 +2582,12 @@ namespace Barotrauma
{ {
contextualOrders.Remove(pumpOrderInfo); contextualOrders.Remove(pumpOrderInfo);
} }
if (contextualOrders.None()) orderIdentifier = "cleanupitems";
if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)))
{ {
orderIdentifier = "cleanupitems"; if (AIObjectiveCleanupItems.IsValidTarget(itemContext, Character.Controlled, checkInventory: false) || AIObjectiveCleanupItems.IsValidContainer(itemContext, Character.Controlled))
if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)))
{ {
if (AIObjectiveCleanupItems.IsValidTarget(itemContext, Character.Controlled, checkInventory: false) || AIObjectiveCleanupItems.IsValidContainer(itemContext, Character.Controlled)) contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled), null));
{
contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled), null));
}
} }
} }
AddIgnoreOrder(itemContext); AddIgnoreOrder(itemContext);
@@ -3523,16 +3520,6 @@ namespace Barotrauma
InitRound(); InitRound();
} }
public void EndRound()
{
//remove characterinfos whose characters have been removed or killed
characterInfos.RemoveAll(c => c.Character == null || c.Character.Removed || c.CauseOfDeath != null);
characters.Clear();
crewList.ClearChildren();
GUIContextMenu.CurrentContextMenu = null;
}
public void Reset() public void Reset()
{ {
characters.Clear(); characters.Clear();
@@ -242,6 +242,11 @@ namespace Barotrauma
{ {
ReadyCheckButton.RectTransform.ScreenSpaceOffset = endRoundButton.RectTransform.ScreenSpaceOffset; ReadyCheckButton.RectTransform.ScreenSpaceOffset = endRoundButton.RectTransform.ScreenSpaceOffset;
ReadyCheckButton.DrawManually(spriteBatch); ReadyCheckButton.DrawManually(spriteBatch);
if (ReadyCheck.ReadyCheckCooldown > DateTime.Now)
{
float progress = (ReadyCheck.ReadyCheckCooldown - DateTime.Now).Seconds / 60.0f;
ReadyCheckButton.Color = ToolBox.GradientLerp(progress, Color.White, GUI.Style.Red);
}
} }
} }
@@ -290,6 +295,9 @@ namespace Barotrauma
case InteractionType.Crew when GameMain.NetworkMember != null: case InteractionType.Crew when GameMain.NetworkMember != null:
CampaignUI.CrewManagement.SendCrewState(false); CampaignUI.CrewManagement.SendCrewState(false);
goto default; goto default;
case InteractionType.MedicalClinic:
CampaignUI.MedicalClinic.RequestLatestPending();
goto default;
default: default:
ShowCampaignUI = true; ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType); CampaignUI.SelectTab(npc.CampaignInteractionType);
@@ -319,6 +327,8 @@ namespace Barotrauma
{ {
base.Update(deltaTime); base.Update(deltaTime);
MedicalClinic?.Update(deltaTime);
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary); GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
@@ -172,7 +172,7 @@ namespace Barotrauma
} }
else else
{ {
indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0; indicator.Visible = Character.Controlled.Info.GetAvailableTalentPoints() > 0 && !Character.Controlled.HasUnlockedAllTalents();
} }
} }
} }
@@ -0,0 +1,386 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma
{
internal partial class MedicalClinic
{
public enum RequestResult
{
Undecided,
Success,
Error,
Timeout
}
public readonly struct RequestAction<T>
{
public readonly Action<T> Callback;
public readonly DateTimeOffset Timeout;
public RequestAction(Action<T> callback, DateTimeOffset timeout)
{
Callback = callback;
Timeout = timeout;
}
}
public readonly struct AfflictionRequest
{
public readonly RequestResult Result;
public readonly ImmutableArray<NetAffliction> Afflictions;
public AfflictionRequest(RequestResult result, ImmutableArray<NetAffliction> afflictions)
{
Result = result;
Afflictions = afflictions;
}
}
public readonly struct PendingRequest
{
public readonly RequestResult Result;
public readonly ImmutableArray<NetCrewMember> CrewMembers;
public PendingRequest(RequestResult result, ImmutableArray<NetCrewMember> crewMembers)
{
Result = result;
CrewMembers = crewMembers;
}
}
public readonly struct CallbackOnlyRequest
{
public readonly RequestResult Result;
public CallbackOnlyRequest(RequestResult result)
{
Result = result;
}
}
public readonly struct HealRequest
{
public readonly RequestResult Result;
public readonly HealRequestResult HealResult;
public HealRequest(RequestResult result, HealRequestResult healResult)
{
Result = result;
HealResult = healResult;
}
}
private readonly List<RequestAction<AfflictionRequest>> afflictionRequests = new List<RequestAction<AfflictionRequest>>();
private readonly List<RequestAction<PendingRequest>> pendingHealRequests = new List<RequestAction<PendingRequest>>();
private readonly List<RequestAction<CallbackOnlyRequest>> clearAllRequests = new List<RequestAction<CallbackOnlyRequest>>();
private readonly List<RequestAction<HealRequest>> healAllRequests = new List<RequestAction<HealRequest>>();
private readonly List<RequestAction<CallbackOnlyRequest>> addRequests = new List<RequestAction<CallbackOnlyRequest>>();
private readonly List<RequestAction<CallbackOnlyRequest>> removeRequests = new List<RequestAction<CallbackOnlyRequest>>();
public void RequestAfflictions(CharacterInfo info, Action<AfflictionRequest> onReceived)
{
if (GameMain.IsSingleplayer)
{
#if DEBUG && LINUX
if (Screen.Selected is TestScreen)
{
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, TestAfflictions.ToImmutableArray()));
return;
}
#endif
if (!(info is { Character: { CharacterHealth: { } health } }))
{
onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty));
return;
}
ImmutableArray<NetAffliction> pendingAfflictions = GetAllAfflictions(health).ToImmutableArray();
onReceived.Invoke(new AfflictionRequest(RequestResult.Success, pendingAfflictions));
return;
}
afflictionRequests.Add(new RequestAction<AfflictionRequest>(onReceived, GetTimeout()));
SendAfflictionRequest(info);
}
public void RequestLatestPending(Action<PendingRequest> onReceived)
{
// no need to worry about syncing when there's only one pair of eyes capable of looking at the UI
if (GameMain.IsSingleplayer) { return; }
pendingHealRequests.Add(new RequestAction<PendingRequest>(onReceived, GetTimeout()));
SendPendingRequest();
}
public void Update(float deltaTime)
{
DateTimeOffset now = DateTimeOffset.Now;
UpdateQueue(afflictionRequests, now, onTimeout: callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); });
UpdateQueue(pendingHealRequests, now, onTimeout: callback => { callback(new PendingRequest(RequestResult.Timeout, ImmutableArray<NetCrewMember>.Empty)); });
UpdateQueue(healAllRequests, now, onTimeout: callback => { callback(new HealRequest(RequestResult.Timeout, HealRequestResult.Unknown)); });
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
}
public bool IsAfflictionPending(NetCrewMember character, NetAffliction affliction)
{
foreach (NetCrewMember crewMember in PendingHeals)
{
if (!crewMember.CharacterEquals(character)) { continue; }
return crewMember.Afflictions.Any(a => a.AfflictionEquals(affliction));
}
return false;
}
private static bool TryDequeue<T>(List<RequestAction<T>> requestQueue, out Action<T> result)
{
RequestAction<T>? first = requestQueue.FirstOrNull();
if (!(first is { } action))
{
result = _ => { };
return false;
}
requestQueue.Remove(action);
result = action.Callback;
return true;
}
private static void UpdateQueue<T>(List<RequestAction<T>> requestQueue, DateTimeOffset now, Action<Action<T>> onTimeout)
{
HashSet<RequestAction<T>>? removals = null;
foreach (RequestAction<T> action in requestQueue)
{
if (action.Timeout < now)
{
onTimeout.Invoke(action.Callback);
removals ??= new HashSet<RequestAction<T>>();
removals.Add(action);
}
}
if (removals is null) { return; }
foreach (RequestAction<T> action in removals)
{
requestQueue.Remove(action);
}
}
// if you have more than 5000 ping there are probably more important things to worry about but hey just in case
private static DateTimeOffset GetTimeout() => DateTimeOffset.Now.AddSeconds(5).AddMilliseconds(GetPing());
private static int GetPing()
{
if (GameMain.IsSingleplayer || !(GameMain.Client?.Name is { } ownName) || !(GameMain.NetworkMember?.ConnectedClients is { } clients)) { return 0; }
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
}
public void HealAllButtonAction(Action<HealRequest> onReceived)
{
if (GameMain.IsSingleplayer)
{
HealRequestResult result = HealAllPending();
onReceived(new HealRequest(RequestResult.Success, HealAllPending()));
if (result == HealRequestResult.Success)
{
OnUpdate?.Invoke();
}
return;
}
healAllRequests.Add(new RequestAction<HealRequest>(onReceived, GetTimeout()));
ClientSend(null, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable);
}
public void ClearAllButtonAction(Action<CallbackOnlyRequest> onReceived)
{
if (GameMain.IsSingleplayer)
{
ClearPendingHeals();
onReceived(new CallbackOnlyRequest(RequestResult.Success));
OnUpdate?.Invoke();
return;
}
clearAllRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
ClientSend(null, NetworkHeader.CLEAR_PENDING, DeliveryMethod.Reliable);
}
private void ClearRequstReceived()
{
ClearPendingHeals();
if (TryDequeue(clearAllRequests, out var callback))
{
callback(new CallbackOnlyRequest(RequestResult.Success));
}
OnUpdate?.Invoke();
}
private void HealRequestReceived(IReadMessage inc)
{
NetHealRequest request = INetSerializableStruct.Read<NetHealRequest>(inc);
if (request.Result == HealRequestResult.Success)
{
HealAllPending(force: true);
}
if (TryDequeue(healAllRequests, out var callback))
{
callback(new HealRequest(RequestResult.Success, request.Result));
}
OnUpdate?.Invoke();
}
public void AddPendingButtonAction(NetCrewMember crewMember, Action<CallbackOnlyRequest> onReceived)
{
if (GameMain.IsSingleplayer)
{
InsertPendingCrewMember(crewMember);
onReceived(new CallbackOnlyRequest(RequestResult.Success));
OnUpdate?.Invoke();
return;
}
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
ClientSend(crewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable);
}
public void RemovePendingButtonAction(NetCrewMember crewMember, NetAffliction affliction, Action<CallbackOnlyRequest> onReceived)
{
if (GameMain.IsSingleplayer)
{
RemovePendingAffliction(crewMember, affliction);
onReceived(new CallbackOnlyRequest(RequestResult.Success));
OnUpdate?.Invoke();
return;
}
INetSerializableStruct removedAffliction = new NetRemovedAffliction
{
CrewMember = crewMember,
Affliction = affliction
};
removeRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
ClientSend(removedAffliction, NetworkHeader.REMOVE_PENDING, DeliveryMethod.Reliable);
}
private void NewAdditonReceived(IReadMessage inc, MessageFlag flag)
{
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
InsertPendingCrewMember(crewMember);
if (flag == MessageFlag.Response && TryDequeue(addRequests, out var callback))
{
callback(new CallbackOnlyRequest(RequestResult.Success));
}
OnUpdate?.Invoke();
}
private void NewRemovalReceived(IReadMessage inc, MessageFlag flag)
{
NetRemovedAffliction removed = INetSerializableStruct.Read<NetRemovedAffliction>(inc);
RemovePendingAffliction(removed.CrewMember, removed.Affliction);
if (flag == MessageFlag.Response && TryDequeue(removeRequests, out var callback))
{
callback(new CallbackOnlyRequest(RequestResult.Success));
}
OnUpdate?.Invoke();
}
private static void SendAfflictionRequest(CharacterInfo info)
{
INetSerializableStruct crewMember = new NetCrewMember
{
CharacterInfo = info,
Afflictions = Array.Empty<NetAffliction>()
};
ClientSend(crewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable);
}
private static void SendPendingRequest()
{
ClientSend(null, NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable);
}
private void AfflictionRequestReceived(IReadMessage inc)
{
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
if (TryDequeue(afflictionRequests, out var callback))
{
RequestResult result = crewMember.CharacterInfoID == 0 ? RequestResult.Error : RequestResult.Success;
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
}
}
private void PendingRequestReceived(IReadMessage inc)
{
NetPendingCrew pendingCrew = INetSerializableStruct.Read<NetPendingCrew>(inc);
if (TryDequeue(pendingHealRequests, out var callback))
{
callback(new PendingRequest(RequestResult.Success, pendingCrew.CrewMembers.ToImmutableArray()));
}
}
private static IWriteMessage StartSending()
{
IWriteMessage writeMessage = new WriteOnlyMessage();
writeMessage.Write((byte)ClientPacketHeader.MEDICAL);
return writeMessage;
}
private static void ClientSend(INetSerializableStruct? netStruct, NetworkHeader header, DeliveryMethod deliveryMethod)
{
IWriteMessage msg = StartSending();
msg.Write((byte)header);
netStruct?.Write(msg);
GameMain.Client.ClientPeer?.Send(msg, deliveryMethod);
}
public void ClientRead(IReadMessage inc)
{
NetworkHeader header = (NetworkHeader)inc.ReadByte();
MessageFlag flag = (MessageFlag)inc.ReadByte();
switch (header)
{
case NetworkHeader.REQUEST_AFFLICTIONS:
AfflictionRequestReceived(inc);
break;
case NetworkHeader.REQUEST_PENDING:
PendingRequestReceived(inc);
break;
case NetworkHeader.ADD_PENDING:
NewAdditonReceived(inc, flag);
break;
case NetworkHeader.REMOVE_PENDING:
NewRemovalReceived(inc, flag);
break;
case NetworkHeader.HEAL_PENDING:
HealRequestReceived(inc);
break;
case NetworkHeader.CLEAR_PENDING:
ClearRequstReceived();
break;
}
}
}
}
@@ -31,7 +31,7 @@ namespace Barotrauma
private GUIMessageBox? msgBox; private GUIMessageBox? msgBox;
private GUIMessageBox? resultsBox; private GUIMessageBox? resultsBox;
public static DateTime lastReadyCheck = DateTime.MinValue; public static DateTime ReadyCheckCooldown = DateTime.MinValue;
public static bool IsReadyCheck(GUIComponent? msgBox) => msgBox?.UserData as string == PromptData || msgBox?.UserData as string == ResultData; public static bool IsReadyCheck(GUIComponent? msgBox) => msgBox?.UserData as string == PromptData || msgBox?.UserData as string == ResultData;
@@ -273,10 +273,10 @@ namespace Barotrauma
public static void CreateReadyCheck() public static void CreateReadyCheck()
{ {
if (lastReadyCheck < DateTime.Now) if (ReadyCheckCooldown < DateTime.Now)
{ {
#if !DEBUG #if !DEBUG
lastReadyCheck = DateTime.Now.AddMinutes(1); ReadyCheckCooldown = DateTime.Now.AddMinutes(1);
#endif #endif
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte) ClientPacketHeader.READY_CHECK); msg.Write((byte) ClientPacketHeader.READY_CHECK);
@@ -285,7 +285,7 @@ namespace Barotrauma
return; return;
} }
GUIMessageBox msgBox = new GUIMessageBox(readyCheckHeader, readyCheckPleaseWait((lastReadyCheck - DateTime.Now).Seconds), new[] { closeButton }); GUIMessageBox msgBox = new GUIMessageBox(readyCheckHeader, readyCheckPleaseWait((ReadyCheckCooldown - DateTime.Now).Seconds), new[] { closeButton });
msgBox.Buttons[0].OnClicked = delegate msgBox.Buttons[0].OnClicked = delegate
{ {
msgBox.Close(); msgBox.Close();
@@ -524,6 +524,7 @@ namespace Barotrauma
return true; return true;
}; };
#if !OSX
var statisticsTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.045f), leftPanel.RectTransform), TextManager.Get("statisticsconsenttickbox")) var statisticsTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.045f), leftPanel.RectTransform), TextManager.Get("statisticsconsenttickbox"))
{ {
OnSelected = (GUITickBox tickBox) => OnSelected = (GUITickBox tickBox) =>
@@ -562,6 +563,8 @@ namespace Barotrauma
statisticsTickBox.OnSelected = prevHandler; statisticsTickBox.OnSelected = prevHandler;
statisticsTickBox.Enabled = GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error; statisticsTickBox.Enabled = GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error;
}); });
#endif
// right panel -------------------------------------- // right panel --------------------------------------
@@ -504,6 +504,13 @@ namespace Barotrauma
{ {
HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale); HUDLayoutSettings.InventoryTopY = visualSlots[0].EquipButtonRect.Y - (int)(15 * GUI.Scale);
} }
else
{
for (int i = 0; i < capacity; i++)
{
visualSlots[i].DrawOffset = Vector2.Zero;
}
}
} }
protected override void ControlInput(Camera cam) protected override void ControlInput(Camera cam)
@@ -28,8 +28,8 @@ namespace Barotrauma.Items.Components
} }
case AreaShape.Circle: case AreaShape.Circle:
Vector2 center = item.WorldPosition; Vector2 center = item.WorldPosition;
center.Y = -center.Y;
center += SpawnAreaOffset; center += SpawnAreaOffset;
center.Y = -center.Y;
spriteBatch.DrawCircle(center, SpawnAreaRadius, 32, GUI.Style.Red, thickness: 4f); spriteBatch.DrawCircle(center, SpawnAreaRadius, 32, GUI.Style.Red, thickness: 4f);
if (MaximumAmountRangePadding > 0f) if (MaximumAmountRangePadding > 0f)
@@ -51,8 +51,8 @@ namespace Barotrauma.Items.Components
} }
case AreaShape.Circle: case AreaShape.Circle:
Vector2 center = item.WorldPosition; Vector2 center = item.WorldPosition;
center.Y = -center.Y;
center += CrewAreaOffset; center += CrewAreaOffset;
center.Y = -center.Y;
spriteBatch.DrawCircle(center, CrewAreaRadius, 32, GUI.Style.Green); spriteBatch.DrawCircle(center, CrewAreaRadius, 32, GUI.Style.Green);
break; break;
} }
@@ -390,10 +390,10 @@ namespace Barotrauma.Items.Components
if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property)) if (SerializableProperties.TryGetValue(sound.VolumeProperty, out SerializableProperty property))
{ {
float newVolume = 0.0f; float newVolume;
try try
{ {
newVolume = (float)property.GetValue(this); newVolume = property.GetFloatValue(this);
} }
catch catch
{ {
@@ -40,7 +40,7 @@ namespace Barotrauma.Items.Components
} }
} }
partial void SetLightSourceTransform() partial void SetLightSourceTransformProjSpecific()
{ {
if (ParentBody != null) if (ParentBody != null)
{ {
@@ -6,21 +6,23 @@ namespace Barotrauma.Items.Components
{ {
partial class Controller : ItemComponent partial class Controller : ItemComponent
{ {
private bool chatBoxOriginalState;
private bool isHUDsHidden;
public override void DrawHUD(SpriteBatch spriteBatch, Character character) public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{ {
if (focusTarget != null && character.ViewTarget == focusTarget) if (focusTarget != null && character.ViewTarget == focusTarget)
{ {
foreach (ItemComponent ic in focusTarget.Components) foreach (ItemComponent ic in focusTarget.Components)
{ {
ic.DrawHUD(spriteBatch, character); if (ic.ShouldDrawHUD(character))
{
ic.DrawHUD(spriteBatch, character);
}
} }
} }
} }
private bool crewAreaOriginalState;
private bool chatBoxOriginalState;
private bool isHUDsHidden;
partial void HideHUDs(bool value) partial void HideHUDs(bool value)
{ {
if (isHUDsHidden == value) { return; } if (isHUDsHidden == value) { return; }
@@ -302,6 +302,12 @@ namespace Barotrauma.Items.Components
} }
HideEmptyItemListCategories(); HideEmptyItemListCategories();
if (selectedItem != null)
{
//reselect to recreate the info based on the new user's skills
SelectItem(character, selectedItem);
}
} }
private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
@@ -343,13 +349,14 @@ namespace Barotrauma.Items.Components
foreach (Item it in availableItems) foreach (Item it in availableItems)
{ {
if (it.ParentInventory == inputContainer.Inventory) { continue; } if (it.ParentInventory == inputContainer.Inventory) { continue; }
var rootContainer = it.GetRootContainer(); var rootInventoryOwner = it.GetRootInventoryOwner();
if (rootContainer?.OwnInventory?.visualSlots == null) { continue; } Inventory rootInventory = (rootInventoryOwner as Item)?.OwnInventory as Inventory ?? (rootInventoryOwner as Character)?.Inventory;
int availableSlotIndex = rootContainer.OwnInventory.FindIndex(it.Container == rootContainer ? it : it.Container); if (rootInventory?.visualSlots == null) { continue; }
int availableSlotIndex = rootInventory.FindIndex((it.Container != rootInventoryOwner ? it.Container : it) ?? it);
if (availableSlotIndex < 0) { continue; } if (availableSlotIndex < 0) { continue; }
if (rootContainer.OwnInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f) if (rootInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f)
{ {
rootContainer.OwnInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); rootInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
if (slotIndex < inputContainer.Capacity) if (slotIndex < inputContainer.Capacity)
{ {
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
@@ -406,9 +413,16 @@ namespace Barotrauma.Items.Components
{ {
toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%"; toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%";
} }
else if(requiredItem.MaxCondition < 1.0f) else if (requiredItem.MaxCondition < 1.0f)
{ {
toolTipText += " 0-" + (int)Math.Round(requiredItem.MaxCondition * 100) + "%"; if (requiredItem.MaxCondition <= 0.0f)
{
toolTipText += " " + (int)Math.Round(requiredItem.MaxCondition * 100) + "%";
}
else
{
toolTipText += " 0-" + (int)Math.Round(requiredItem.MaxCondition * 100) + "%";
}
} }
else if (requiredItem.MaxCondition <= 0.0f) else if (requiredItem.MaxCondition <= 0.0f)
{ {
@@ -524,16 +538,6 @@ namespace Barotrauma.Items.Components
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f }; var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f }; var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
/*var itemIcon = selectedItem.TargetItem.InventoryIcon ?? selectedItem.TargetItem.sprite;
if (itemIcon != null)
{
GUIImage img = new GUIImage(new RectTransform(new Point(40, 40), paddedFrame.RectTransform),
itemIcon, scaleToFit: true)
{
Color = selectedItem.TargetItem.InventoryIconColor
};
}*/
string itemName = GetRecipeNameAndAmount(selectedItem); string itemName = GetRecipeNameAndAmount(selectedItem);
string name = itemName; string name = itemName;
@@ -732,8 +736,6 @@ namespace Barotrauma.Items.Components
Character user = Entity.FindEntityByID(userID) as Character; Character user = Entity.FindEntityByID(userID) as Character;
State = newState; State = newState;
timeUntilReady = newTimeUntilReady;
if (newState == FabricatorState.Stopped || itemIndex == -1) if (newState == FabricatorState.Stopped || itemIndex == -1)
{ {
CancelFabricating(); CancelFabricating();
@@ -747,6 +749,7 @@ namespace Barotrauma.Items.Components
SelectItem(user, fabricationRecipes[itemIndex]); SelectItem(user, fabricationRecipes[itemIndex]);
StartFabricating(fabricationRecipes[itemIndex], user); StartFabricating(fabricationRecipes[itemIndex], user);
} }
timeUntilReady = newTimeUntilReady;
} }
} }
} }
@@ -537,9 +537,9 @@ namespace Barotrauma.Items.Components
if (item.Submarine == null && displayedSubs.Count > 0 || // item not inside a sub anymore, but display is still showing subs if (item.Submarine == null && displayedSubs.Count > 0 || // item not inside a sub anymore, but display is still showing subs
item.Submarine is { } itemSub && item.Submarine is { } itemSub &&
( (
!displayedSubs.Contains(itemSub) || // current sub not displayed !displayedSubs.Contains(itemSub) || // current sub not displayed
itemSub.DockedTo.Any(s => !displayedSubs.Contains(s)) || // some of the docked subs not displayed itemSub.DockedTo.Any(s => !displayedSubs.Contains(s) && itemSub.ConnectedDockingPorts[s].IsLocked) || // some of the docked subs not displayed
displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s)) // displaying a sub that shouldn't be displayed displayedSubs.Any(s => s != itemSub && !itemSub.DockedTo.Contains(s)) // displaying a sub that shouldn't be displayed
) || ) ||
prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || // resolution changed prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight || // resolution changed
!submarineContainer.Children.Any()) // We lack a GUI !submarineContainer.Children.Any()) // We lack a GUI
@@ -1092,6 +1092,12 @@ namespace Barotrauma.Items.Components
if (!(entity is Item it)) { continue; } if (!(entity is Item it)) { continue; }
if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent component)) { continue; } if (!electricalChildren.TryGetValue(miniMapGuiComponent, out GUIComponent component)) { continue; }
if (entity.Removed)
{
component.Visible = false;
continue;
}
if (item.Submarine == null || !hasPower) if (item.Submarine == null || !hasPower)
{ {
component.Color = component.OutlineColor = NoPowerElectricalColor; component.Color = component.OutlineColor = NoPowerElectricalColor;
@@ -1117,7 +1123,7 @@ namespace Barotrauma.Items.Components
int current = (int)-powerTransfer.CurrPowerConsumption, load = (int)powerTransfer.PowerLoad; int current = (int)-powerTransfer.CurrPowerConsumption, load = (int)powerTransfer.PowerLoad;
line1 = TextManager.GetWithVariable("statusmonitor.junctionpower.tooltip", "[amount]", current.ToString(), fallBackTag: "statusmonitor.junctioncurrent.tooltip"); line1 = TextManager.GetWithVariable("statusmonitor.junctionpower.tooltip", "[amount]", current.ToString(), fallBackTag: "statusmonitor.junctioncurrent.tooltip");
line2 = TextManager.GetWithVariable("statusmonitor.junctionload.tooltip", "[amount]", load.ToString()); line2 = TextManager.GetWithVariables("statusmonitor.junctionload.tooltip", new string[] { "[amount]", "[load]" }, new string[] { load.ToString(), load.ToString() });
} }
string line3 = TextManager.GetWithVariable("statusmonitor.durability.tooltip", "[amount]", durability.ToString()); string line3 = TextManager.GetWithVariable("statusmonitor.durability.tooltip", "[amount]", durability.ToString());
@@ -19,15 +19,6 @@ namespace Barotrauma.Items.Components
private readonly List<(Vector2 position, ParticleEmitter emitter)> pumpOutEmitters = new List<(Vector2 position, ParticleEmitter emitter)>(); private readonly List<(Vector2 position, ParticleEmitter emitter)> pumpOutEmitters = new List<(Vector2 position, ParticleEmitter emitter)>();
private readonly List<(Vector2 position, ParticleEmitter emitter)> pumpInEmitters = new List<(Vector2 position, ParticleEmitter emitter)>(); private readonly List<(Vector2 position, ParticleEmitter emitter)> pumpInEmitters = new List<(Vector2 position, ParticleEmitter emitter)>();
public float CurrentBrokenVolume
{
get
{
if (item.ConditionPercentage > 10.0f || !IsActive) { return 0.0f; }
return (1.0f - item.ConditionPercentage / 10.0f) * 100.0f;
}
}
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
@@ -193,8 +184,6 @@ namespace Barotrauma.Items.Components
private readonly float flickerFrequency = 1; private readonly float flickerFrequency = 1;
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
pumpSpeedLockTimer -= deltaTime;
isActiveLockTimer -= deltaTime;
autoControlIndicator.Selected = IsAutoControlled; autoControlIndicator.Selected = IsAutoControlled;
PowerButton.Enabled = isActiveLockTimer <= 0.0f; PowerButton.Enabled = isActiveLockTimer <= 0.0f;
if (HasPower) if (HasPower)
@@ -83,9 +83,7 @@ namespace Barotrauma.Items.Components
private const float ConnectedSubUpdateInterval = 1.0f; private const float ConnectedSubUpdateInterval = 1.0f;
float connectedSubUpdateTimer; float connectedSubUpdateTimer;
//Vector2 = vector from the ping source to the position of the disruption private readonly List<(Vector2 pos, float strength)> disruptedDirections = new List<(Vector2 pos, float strength)>();
//float = strength of the disruption, between 0-1
private readonly List<Pair<Vector2, float>> disruptedDirections = new List<Pair<Vector2, float>>();
private readonly Dictionary<object, CachedDistance> markerDistances = new Dictionary<object, CachedDistance>(); private readonly Dictionary<object, CachedDistance> markerDistances = new Dictionary<object, CachedDistance>();
@@ -455,7 +453,20 @@ namespace Barotrauma.Items.Components
zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll); zoomSlider.OnMoved(zoomSlider, zoomSlider.BarScroll);
} }
} }
Vector2 transducerCenter = GetTransducerPos();
if (steering != null && steering.DockingModeEnabled && steering.ActiveDockingSource != null)
{
Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f;
DisplayOffset = Vector2.Lerp(DisplayOffset, worldFocusPos - transducerCenter, 0.1f);
}
else
{
DisplayOffset = Vector2.Lerp(DisplayOffset, Vector2.Zero, 0.1f);
}
transducerCenter += DisplayOffset;
float distort = MathHelper.Clamp(1.0f - item.Condition / item.MaxCondition, 0.0f, 1.0f); float distort = MathHelper.Clamp(1.0f - item.Condition / item.MaxCondition, 0.0f, 1.0f);
for (int i = sonarBlips.Count - 1; i >= 0; i--) for (int i = sonarBlips.Count - 1; i >= 0; i--)
{ {
@@ -502,8 +513,6 @@ namespace Barotrauma.Items.Components
return; return;
} }
Vector2 transducerCenter = GetTransducerPos() + DisplayOffset;
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
nearbyObjectUpdateTimer -= deltaTime; nearbyObjectUpdateTimer -= deltaTime;
@@ -829,8 +838,7 @@ namespace Barotrauma.Items.Components
} }
} }
Vector2 transducerCenter = GetTransducerPos(); Vector2 transducerCenter = GetTransducerPos();// + DisplayOffset;
if (sonarBlips.Count > 0) if (sonarBlips.Count > 0)
{ {
@@ -840,7 +848,7 @@ namespace Barotrauma.Items.Components
foreach (SonarBlip sonarBlip in sonarBlips) foreach (SonarBlip sonarBlip in sonarBlips)
{ {
DrawBlip(spriteBatch, sonarBlip, transducerCenter, center, sonarBlip.FadeTimer / 2.0f * signalStrength, blipScale); DrawBlip(spriteBatch, sonarBlip, transducerCenter + DisplayOffset, center, sonarBlip.FadeTimer / 2.0f * signalStrength, blipScale);
} }
spriteBatch.End(); spriteBatch.End();
@@ -849,8 +857,8 @@ namespace Barotrauma.Items.Components
if (item.Submarine != null && !DetectSubmarineWalls) if (item.Submarine != null && !DetectSubmarineWalls)
{ {
DrawDockingPorts(spriteBatch, transducerCenter, signalStrength);
transducerCenter += DisplayOffset; transducerCenter += DisplayOffset;
DrawDockingPorts(spriteBatch, transducerCenter, signalStrength);
DrawOwnSubmarineBorders(spriteBatch, transducerCenter, signalStrength); DrawOwnSubmarineBorders(spriteBatch, transducerCenter, signalStrength);
} }
else else
@@ -1083,10 +1091,6 @@ namespace Barotrauma.Items.Components
{ {
DrawDockingIndicator(spriteBatch, steering, ref transducerCenter); DrawDockingIndicator(spriteBatch, steering, ref transducerCenter);
} }
else
{
DisplayOffset = Vector2.Lerp(DisplayOffset, Vector2.Zero, 0.1f);
}
foreach (DockingPort dockingPort in DockingPort.List) foreach (DockingPort dockingPort in DockingPort.List)
{ {
@@ -1131,9 +1135,6 @@ namespace Barotrauma.Items.Components
Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f; Vector2 worldFocusPos = (steering.ActiveDockingSource.Item.WorldPosition + steering.DockingTarget.Item.WorldPosition) / 2.0f;
worldFocusPos.X = steering.DockingTarget.Item.WorldPosition.X; worldFocusPos.X = steering.DockingTarget.Item.WorldPosition.X;
DisplayOffset = Vector2.Lerp(DisplayOffset, worldFocusPos - transducerCenter, 0.1f);
transducerCenter += DisplayOffset;
Vector2 sourcePortDiff = (steering.ActiveDockingSource.Item.WorldPosition - transducerCenter) * scale; Vector2 sourcePortDiff = (steering.ActiveDockingSource.Item.WorldPosition - transducerCenter) * scale;
Vector2 sourcePortPos = new Vector2(sourcePortDiff.X, -sourcePortDiff.Y); Vector2 sourcePortPos = new Vector2(sourcePortDiff.X, -sourcePortDiff.Y);
Vector2 targetPortDiff = (steering.DockingTarget.Item.WorldPosition - transducerCenter) * scale; Vector2 targetPortDiff = (steering.DockingTarget.Item.WorldPosition - transducerCenter) * scale;
@@ -1234,7 +1235,7 @@ namespace Barotrauma.Items.Components
Vector2 disruptionPos = new Vector2(levelObject.Position.X, levelObject.Position.Y); Vector2 disruptionPos = new Vector2(levelObject.Position.X, levelObject.Position.Y);
float disruptionDist = Vector2.Distance(pingSource, disruptionPos); float disruptionDist = Vector2.Distance(pingSource, disruptionPos);
disruptedDirections.Add(new Pair<Vector2, float>((disruptionPos - pingSource) / disruptionDist, disruptionStrength)); disruptedDirections.Add(((disruptionPos - pingSource) / disruptionDist, disruptionStrength));
CreateBlipsForDisruption(disruptionPos, disruptionStrength); CreateBlipsForDisruption(disruptionPos, disruptionStrength);
@@ -1246,7 +1247,7 @@ namespace Barotrauma.Items.Components
float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource); float distSqr = Vector2.DistanceSquared(aiTarget.WorldPosition, pingSource);
if (distSqr > worldPingRadiusSqr) { continue; } if (distSqr > worldPingRadiusSqr) { continue; }
float disruptionDist = (float)Math.Sqrt(distSqr); float disruptionDist = (float)Math.Sqrt(distSqr);
disruptedDirections.Add(new Pair<Vector2, float>((aiTarget.WorldPosition - pingSource) / disruptionDist, aiTarget.SonarDisruption)); disruptedDirections.Add(((aiTarget.WorldPosition - pingSource) / disruptionDist, aiTarget.SonarDisruption));
CreateBlipsForDisruption(aiTarget.WorldPosition, disruption); CreateBlipsForDisruption(aiTarget.WorldPosition, disruption);
} }
} }
@@ -1461,10 +1462,10 @@ namespace Barotrauma.Items.Components
float transducerDist = transducerDiff.Length(); float transducerDist = transducerDiff.Length();
Vector2 pingDirection = transducerDiff / transducerDist; Vector2 pingDirection = transducerDiff / transducerDist;
bool disrupted = false; bool disrupted = false;
foreach (Pair<Vector2, float> disruptDir in disruptedDirections) foreach ((Vector2 disruptPos, float disruptStrength) in disruptedDirections)
{ {
float dot = Vector2.Dot(pingDirection, disruptDir.First); float dot = Vector2.Dot(pingDirection, disruptPos);
if (dot > 1.0f - disruptDir.Second) if (dot > 1.0f - disruptStrength)
{ {
disrupted = true; disrupted = true;
break; break;
@@ -403,7 +403,7 @@ namespace Barotrauma.Items.Components
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
item.SendSignal("1", "toggle_docking"); item.SendSignal(new Signal("1", sender: Character.Controlled), "toggle_docking");
} }
else else
{ {
@@ -434,6 +434,7 @@ namespace Barotrauma.Items.Components
DeteriorateAlways = msg.ReadBoolean(); DeteriorateAlways = msg.ReadBoolean();
tinkeringDuration = msg.ReadSingle(); tinkeringDuration = msg.ReadSingle();
tinkeringStrength = msg.ReadSingle(); tinkeringStrength = msg.ReadSingle();
tinkeringPowersDevices = msg.ReadBoolean();
ushort currentFixerID = msg.ReadUInt16(); ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
@@ -46,6 +46,7 @@ namespace Barotrauma.Items.Components
child.Enabled = buttonsEnabled; child.Enabled = buttonsEnabled;
child.Children.ForEach(c => c.Enabled = buttonsEnabled); child.Children.ForEach(c => c.Enabled = buttonsEnabled);
} }
if (Container == null) { return; }
bool itemsContained = Container.Inventory.AllItems.Any(); bool itemsContained = Container.Inventory.AllItems.Any();
if (itemsContained) if (itemsContained)
{ {
@@ -77,7 +78,7 @@ namespace Barotrauma.Items.Components
{ {
if (GameMain.IsSingleplayer) if (GameMain.IsSingleplayer)
{ {
SendSignal((int)userData); SendSignal((int)userData, Character.Controlled);
} }
else else
{ {
@@ -98,6 +99,7 @@ namespace Barotrauma.Items.Components
partial void OnItemLoadedProjSpecific() partial void OnItemLoadedProjSpecific()
{ {
if (Container == null) { return; }
Container.AllowUIOverlap = true; Container.AllowUIOverlap = true;
Container.Inventory.RectTransform = containerHolder.RectTransform; Container.Inventory.RectTransform = containerHolder.RectTransform;
} }
@@ -109,7 +111,7 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), isServerMessage: true); SendSignal(msg.ReadRangedInteger(0, Signals.Length - 1), sender: null, isServerMessage: true);
} }
} }
} }
@@ -197,7 +197,7 @@ namespace Barotrauma.Items.Components
} }
} }
partial void UpdateProjSpecific() public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
bool elementVisibilityChanged = false; bool elementVisibilityChanged = false;
int visibleElementCount = 0; int visibleElementCount = 0;
@@ -209,6 +209,7 @@ namespace Barotrauma.Items.Components
if (uiElement.Visible != visible) if (uiElement.Visible != visible)
{ {
uiElement.Visible = visible; uiElement.Visible = visible;
uiElement.IgnoreLayoutGroups = !uiElement.Visible;
elementVisibilityChanged = true; elementVisibilityChanged = true;
} }
} }
@@ -223,6 +224,7 @@ namespace Barotrauma.Items.Components
uiElement.RectTransform.RelativeSize = new Vector2(1.0f, elementSize); uiElement.RectTransform.RelativeSize = new Vector2(1.0f, elementSize);
} }
GuiFrame.Visible = visibleElementCount > 0; GuiFrame.Visible = visibleElementCount > 0;
uiElementContainer.Recalculate();
} }
} }
@@ -12,9 +12,9 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
if (!editing || !MapEntity.SelectedList.Contains(item)) return; if (!editing || !MapEntity.SelectedList.Contains(item)) { return; }
Vector2 pos = item.WorldPosition + detectOffset; Vector2 pos = item.WorldPosition + TransformedDetectOffset;
pos.Y = -pos.Y; pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, pos - new Vector2(rangeX, rangeY), new Vector2(rangeX, rangeY) * 2.0f, Color.Cyan * 0.5f, isFilled: false, thickness: 2); GUI.DrawRectangle(spriteBatch, pos - new Vector2(rangeX, rangeY), new Vector2(rangeX, rangeY) * 2.0f, Color.Cyan * 0.5f, isFilled: false, thickness: 2);
} }
@@ -286,7 +286,6 @@ namespace Barotrauma.Items.Components
item.Color, depth, 0.3f); item.Color, depth, 0.3f);
} }
public static void UpdateEditing(List<Wire> wires) public static void UpdateEditing(List<Wire> wires)
{ {
var doubleClicked = PlayerInput.DoubleClicked(); var doubleClicked = PlayerInput.DoubleClicked();
@@ -509,6 +508,31 @@ namespace Barotrauma.Items.Components
} }
} }
public override void Move(Vector2 amount)
{
//only used in the sub editor, hence only in the client project
if (!item.IsSelected) { return; }
Vector2 wireNodeOffset = item.Submarine == null ? Vector2.Zero : item.Submarine.HiddenSubPosition + amount;
for (int i = 0; i < nodes.Count; i++)
{
if (i == 0 || i == nodes.Count - 1)
{
if (connections[0]?.Item != null && !connections[0].Item.IsSelected &&
(Submarine.RectContains(connections[0].Item.Rect, nodes[i] + wireNodeOffset) || Submarine.RectContains(connections[0].Item.Rect, nodes[i] + wireNodeOffset - amount)))
{
continue;
}
else if (connections[1]?.Item != null && !connections[1].Item.IsSelected &&
(Submarine.RectContains(connections[1].Item.Rect, nodes[i] + wireNodeOffset) || Submarine.RectContains(connections[1].Item.Rect, nodes[i] + wireNodeOffset - amount)))
{
continue;
}
}
nodes[i] += amount;
}
UpdateSections();
}
public bool IsMouseOn() public bool IsMouseOn()
{ {
if (GUI.MouseOn == null) if (GUI.MouseOn == null)
@@ -301,7 +301,7 @@ namespace Barotrauma.Items.Components
Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>(); Dictionary<AfflictionPrefab, float> combinedAfflictionStrengths = new Dictionary<AfflictionPrefab, float>();
foreach (Affliction affliction in allAfflictions) foreach (Affliction affliction in allAfflictions)
{ {
if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) continue; if (affliction.Strength < affliction.Prefab.ShowInHealthScannerThreshold || affliction.Strength <= 0.0f) { continue; }
if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab)) if (combinedAfflictionStrengths.ContainsKey(affliction.Prefab))
{ {
combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength; combinedAfflictionStrengths[affliction.Prefab] += affliction.Strength;
@@ -314,7 +314,7 @@ namespace Barotrauma.Items.Components
foreach (AfflictionPrefab affliction in combinedAfflictionStrengths.Keys) foreach (AfflictionPrefab affliction in combinedAfflictionStrengths.Keys)
{ {
texts.Add(TextManager.AddPunctuation(':', affliction.Name, Math.Max(((int)combinedAfflictionStrengths[affliction]), 1).ToString() + " %")); texts.Add(TextManager.AddPunctuation(':', affliction.Name, Math.Max((int)combinedAfflictionStrengths[affliction], 1).ToString() + " %"));
textColors.Add(Color.Lerp(GUI.Style.Orange, GUI.Style.Red, combinedAfflictionStrengths[affliction] / affliction.MaxStrength)); textColors.Add(Color.Lerp(GUI.Style.Orange, GUI.Style.Red, combinedAfflictionStrengths[affliction] / affliction.MaxStrength));
} }
} }
@@ -17,7 +17,7 @@ namespace Barotrauma
partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable partial class Item : MapEntity, IDamageable, ISerializableEntity, IServerSerializable, IClientSerializable
{ {
public static bool ShowItems = true, ShowWires = true; public static bool ShowItems = true, ShowWires = true;
private readonly List<PosInfo> positionBuffer = new List<PosInfo>(); private readonly List<PosInfo> positionBuffer = new List<PosInfo>();
private readonly List<ItemComponent> activeHUDs = new List<ItemComponent>(); private readonly List<ItemComponent> activeHUDs = new List<ItemComponent>();
@@ -89,8 +89,8 @@ namespace Barotrauma
{ {
if (itemInUseWarning == null) if (itemInUseWarning == null)
{ {
itemInUseWarning = new GUITextBlock(new RectTransform(new Point(10), GUI.Canvas), "", itemInUseWarning = new GUITextBlock(new RectTransform(new Point(10), GUI.Canvas), "",
textColor: GUI.Style.Orange, color: Color.Black, textColor: GUI.Style.Orange, color: Color.Black,
textAlignment: Alignment.Center, style: "OuterGlow"); textAlignment: Alignment.Center, style: "OuterGlow");
} }
return itemInUseWarning; return itemInUseWarning;
@@ -105,6 +105,9 @@ namespace Barotrauma
{ {
return false; return false;
} }
if (!SubEditorScreen.IsLayerVisible(this)) { return false;}
return parentInventory == null && (body == null || body.Enabled) && ShowItems; return parentInventory == null && (body == null || body.Enabled) && ShowItems;
} }
} }
@@ -154,7 +157,7 @@ namespace Barotrauma
if (containedSprite.UseWhenAttached) if (containedSprite.UseWhenAttached)
{ {
activeContainedSprite = containedSprite; activeContainedSprite = containedSprite;
activeSprite = containedSprite.Sprite; activeSprite = containedSprite.Sprite;
UpdateSpriteStates(0.0f); UpdateSpriteStates(0.0f);
return; return;
} }
@@ -196,7 +199,7 @@ namespace Barotrauma
{ {
brokenSprite.Sprite.EnsureLazyLoaded(); brokenSprite.Sprite.EnsureLazyLoaded();
} }
foreach (var decorativeSprite in ((ItemPrefab)prefab).DecorativeSprites) foreach (var decorativeSprite in ((ItemPrefab)prefab).DecorativeSprites)
{ {
decorativeSprite.Sprite.EnsureLazyLoaded(); decorativeSprite.Sprite.EnsureLazyLoaded();
@@ -255,7 +258,7 @@ namespace Barotrauma
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{ {
if (!Visible || (!editing && HiddenInGame)) { return; } if (!Visible || (!editing && HiddenInGame) || !SubEditorScreen.IsLayerVisible(this)) { return; }
if (editing) if (editing)
{ {
@@ -265,7 +268,7 @@ namespace Barotrauma
} }
else if (!ShowItems) { return; } else if (!ShowItems) { return; }
} }
Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange * Math.Max(GetSpriteColor().A / (float) byte.MaxValue, 0.1f) : GetSpriteColor(); Color color = IsIncludedInSelection && editing ? GUI.Style.Blue : IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange * Math.Max(GetSpriteColor().A / (float) byte.MaxValue, 0.1f) : GetSpriteColor();
//if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f); //if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f);
@@ -273,7 +276,7 @@ namespace Barotrauma
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null; bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null;
bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null; bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null;
if (renderTransparent) { color *= 0.15f; } if (renderTransparent) { color *= 0.15f; }
BrokenItemSprite fadeInBrokenSprite = null; BrokenItemSprite fadeInBrokenSprite = null;
float fadeInBrokenSpriteAlpha = 0.0f; float fadeInBrokenSpriteAlpha = 0.0f;
float displayCondition = FakeBroken ? 0.0f : ConditionPercentage; float displayCondition = FakeBroken ? 0.0f : ConditionPercentage;
@@ -322,7 +325,7 @@ namespace Barotrauma
Vector2 size = new Vector2(rect.Width, rect.Height); Vector2 size = new Vector2(rect.Width, rect.Height);
if (color.A > 0) if (color.A > 0)
{ {
activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset, activeSprite.DrawTiled(spriteBatch, new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)) + drawOffset,
size, color: color, size, color: color,
textureScale: Vector2.One * Scale, textureScale: Vector2.One * Scale,
depth: depth); depth: depth);
@@ -336,11 +339,11 @@ namespace Barotrauma
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.DrawTiled(spriteBatch, decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)), new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
size, color: color, size, color: color,
textureScale: Vector2.One * Scale, textureScale: Vector2.One * Scale,
@@ -380,7 +383,7 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, spriteAnimState[decorativeSprite].RandomOffsetMultiplier, flippedX && Prefab.CanSpriteFlipX ? rotationRad : -rotationRad) * Scale;
if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; } if (flippedX && Prefab.CanSpriteFlipX) { offset.X = -offset.X; }
if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; } if (flippedY && Prefab.CanSpriteFlipY) { offset.Y = -offset.Y; }
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
rotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects, rotationRad + rot, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
} }
@@ -440,11 +443,11 @@ namespace Barotrauma
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
} }
} }
foreach (var upgrade in Upgrades) foreach (var upgrade in Upgrades)
{ {
var upgradeSprites = GetUpgradeSprites(upgrade); var upgradeSprites = GetUpgradeSprites(upgrade);
foreach (var decorativeSprite in upgradeSprites) foreach (var decorativeSprite in upgradeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
@@ -456,7 +459,7 @@ namespace Barotrauma
rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects, rotation, decorativeSprite.GetScale(spriteAnimState[decorativeSprite].RandomScaleFactor) * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
} }
} }
activeSprite.effects = oldEffects; activeSprite.effects = oldEffects;
@@ -466,7 +469,7 @@ namespace Barotrauma
} }
} }
//use a backwards for loop because the drawable components may disable drawing, //use a backwards for loop because the drawable components may disable drawing,
//causing them to be removed from the list //causing them to be removed from the list
for (int i = drawableComponents.Count - 1; i >= 0; i--) for (int i = drawableComponents.Count - 1; i >= 0; i--)
{ {
@@ -501,7 +504,7 @@ namespace Barotrauma
Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2)); Vector2 drawPos = new Vector2(DrawPosition.X - rect.Width / 2, -(DrawPosition.Y + rect.Height / 2));
Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y))); Vector2 drawSize = new Vector2(MathF.Ceiling(rect.Width + Math.Abs(drawPos.X - (int)drawPos.X)), MathF.Ceiling(rect.Height + Math.Abs(drawPos.Y - (int)drawPos.Y)));
drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y)); drawPos = new Vector2(MathF.Floor(drawPos.X), MathF.Floor(drawPos.Y));
GUI.DrawRectangle(spriteBatch, drawPos, drawSize, GUI.DrawRectangle(spriteBatch, drawPos, drawSize,
Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); Color.White, false, 0, thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
foreach (Rectangle t in Prefab.Triggers) foreach (Rectangle t in Prefab.Triggers)
@@ -582,19 +585,25 @@ namespace Barotrauma
} }
} }
DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches); if (Prefab.DecorativeSpriteGroups.Count > 0)
{
DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches);
}
foreach (var upgrade in Upgrades) foreach (var upgrade in Upgrades)
{ {
var upgradeSprites = GetUpgradeSprites(upgrade); var upgradeSprites = GetUpgradeSprites(upgrade);
foreach (var decorativeSprite in upgradeSprites) foreach (var decorativeSprite in upgradeSprites)
{ {
var spriteState = spriteAnimState[decorativeSprite]; var spriteState = spriteAnimState[decorativeSprite];
spriteState.IsActive = true; spriteState.IsActive = true;
foreach (var _ in decorativeSprite.IsActiveConditionals.Where(conditional => !ConditionalMatches(conditional))) foreach (var conditional in decorativeSprite.IsActiveConditionals)
{ {
spriteState.IsActive = false; if (!ConditionalMatches(conditional))
break; {
spriteState.IsActive = false;
break;
}
} }
} }
} }
@@ -696,8 +705,8 @@ namespace Barotrauma
foreach (string tag in ip.PreferredContainers.SelectMany(pc => pc.Primary)) { availableTags.Add(tag); } foreach (string tag in ip.PreferredContainers.SelectMany(pc => pc.Primary)) { availableTags.Add(tag); }
foreach (string tag in ip.PreferredContainers.SelectMany(pc => pc.Secondary)) { availableTags.Add(tag); } foreach (string tag in ip.PreferredContainers.SelectMany(pc => pc.Secondary)) { availableTags.Add(tag); }
} }
//remove identifiers from the available container tags //remove identifiers from the available container tags
//(otherwise the list will include many irrelevant options, //(otherwise the list will include many irrelevant options,
//e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc) //e.g. "weldingtool" because a welding fuel tank can be placed inside the container, etc)
availableTags.RemoveWhere(t => MapEntityPrefab.List.Any(me => me.Identifier == t)); availableTags.RemoveWhere(t => MapEntityPrefab.List.Any(me => me.Identifier == t));
new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...") new GUIButton(new RectTransform(new Vector2(0.1f, 1), tagsField.RectTransform, Anchor.TopRight), "...")
@@ -749,7 +758,7 @@ namespace Barotrauma
{ {
me.FlipY(relativeToSub: false); me.FlipY(relativeToSub: false);
} }
if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); } if (!SelectedList.Contains(this)) { FlipY(relativeToSub: false); }
return true; return true;
} }
}; };
@@ -805,9 +814,9 @@ namespace Barotrauma
{ {
if (!ic.AllowInGameEditing) { continue; } if (!ic.AllowInGameEditing) { continue; }
if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 && if (SerializableProperty.GetProperties<InGameEditable>(ic).Count == 0 &&
!SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable(ic))) !SerializableProperty.GetProperties<ConditionallyEditable>(ic).Any(p => p.GetAttribute<ConditionallyEditable>().IsEditable(ic)))
{ {
continue; continue;
} }
} }
else else
@@ -869,7 +878,7 @@ namespace Barotrauma
textBox.Text = relatedItem.JoinedIdentifiers; textBox.Text = relatedItem.JoinedIdentifiers;
return true; return true;
}; };
} }
ic.CreateEditingHUD(componentEditor); ic.CreateEditingHUD(componentEditor);
componentEditor.Recalculate(); componentEditor.Recalculate();
@@ -892,7 +901,7 @@ namespace Barotrauma
return upgradeSprites; return upgradeSprites;
} }
public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false) public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
{ {
if (upgrade.Prefab.IsWallUpgrade) { return false; } if (upgrade.Prefab.IsWallUpgrade) { return false; }
@@ -949,7 +958,7 @@ namespace Barotrauma
//reset positions first //reset positions first
List<GUIComponent> elementsToMove = new List<GUIComponent>(); List<GUIComponent> elementsToMove = new List<GUIComponent>();
if (editingHUD != null && editingHUD.UserData == this && if (editingHUD != null && editingHUD.UserData == this &&
((HasInGameEditableProperties && Character.Controlled?.SelectedConstruction == this) || Screen.Selected == GameMain.SubEditorScreen)) ((HasInGameEditableProperties && Character.Controlled?.SelectedConstruction == this) || Screen.Selected == GameMain.SubEditorScreen))
{ {
elementsToMove.Add(editingHUD); elementsToMove.Add(editingHUD);
@@ -972,8 +981,8 @@ namespace Barotrauma
int disallowedPadding = (int)(50 * GUI.Scale); int disallowedPadding = (int)(50 * GUI.Scale);
disallowedAreas.Add(GameMain.GameSession.CrewManager.GetActiveCrewArea()); disallowedAreas.Add(GameMain.GameSession.CrewManager.GetActiveCrewArea());
disallowedAreas.Add(new Rectangle( disallowedAreas.Add(new Rectangle(
HUDLayoutSettings.ChatBoxArea.X - disallowedPadding, HUDLayoutSettings.ChatBoxArea.Y, HUDLayoutSettings.ChatBoxArea.X - disallowedPadding, HUDLayoutSettings.ChatBoxArea.Y,
HUDLayoutSettings.ChatBoxArea.Width + disallowedPadding, HUDLayoutSettings.ChatBoxArea.Height)); HUDLayoutSettings.ChatBoxArea.Width + disallowedPadding, HUDLayoutSettings.ChatBoxArea.Height));
} }
if (Screen.Selected is SubEditorScreen editor) if (Screen.Selected is SubEditorScreen editor)
@@ -985,8 +994,8 @@ namespace Barotrauma
GUI.PreventElementOverlap(elementsToMove, disallowedAreas, GUI.PreventElementOverlap(elementsToMove, disallowedAreas,
new Rectangle( new Rectangle(
0, 20, 0, 20,
GameMain.GraphicsWidth, GameMain.GraphicsWidth,
HUDLayoutSettings.InventoryTopY > 0 ? HUDLayoutSettings.InventoryTopY - 40 : GameMain.GraphicsHeight - 80)); HUDLayoutSettings.InventoryTopY > 0 ? HUDLayoutSettings.InventoryTopY - 40 : GameMain.GraphicsHeight - 80));
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
@@ -995,7 +1004,7 @@ namespace Barotrauma
var linkUIToComponent = ic.GetLinkUIToComponent(); var linkUIToComponent = ic.GetLinkUIToComponent();
if (linkUIToComponent == null) { continue; } if (linkUIToComponent == null) { continue; }
ic.GuiFrame.RectTransform.ScreenSpaceOffset = linkUIToComponent.GuiFrame.RectTransform.ScreenSpaceOffset; ic.GuiFrame.RectTransform.ScreenSpaceOffset = linkUIToComponent.GuiFrame.RectTransform.ScreenSpaceOffset;
} }
@@ -1110,14 +1119,14 @@ namespace Barotrauma
} }
} }
} }
public void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character) public void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character)
{ {
if (HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) if (HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped))
{ {
DrawEditing(spriteBatch, cam); DrawEditing(spriteBatch, cam);
} }
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
{ {
if (ic.CanBeSelected) if (ic.CanBeSelected)
@@ -1138,9 +1147,9 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, debugInitialHudPositions[i], Color.Orange); GUI.DrawRectangle(spriteBatch, debugInitialHudPositions[i], Color.Orange);
GUI.DrawRectangle(spriteBatch, ic.GuiFrame.Rect, Color.LightGreen); GUI.DrawRectangle(spriteBatch, ic.GuiFrame.Rect, Color.LightGreen);
GUI.DrawLine(spriteBatch, debugInitialHudPositions[i].Location.ToVector2(), ic.GuiFrame.Rect.Location.ToVector2(), Color.Orange); GUI.DrawLine(spriteBatch, debugInitialHudPositions[i].Location.ToVector2(), ic.GuiFrame.Rect.Location.ToVector2(), Color.Orange);
i++; i++;
} }
} }
} }
@@ -1262,7 +1271,7 @@ namespace Barotrauma
NetEntityEvent.Type eventType = NetEntityEvent.Type eventType =
(NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1); (NetEntityEvent.Type)msg.ReadRangedInteger(0, Enum.GetValues(typeof(NetEntityEvent.Type)).Length - 1);
switch (eventType) switch (eventType)
{ {
case NetEntityEvent.Type.ComponentState: case NetEntityEvent.Type.ComponentState:
@@ -1323,7 +1332,7 @@ namespace Barotrauma
ItemComponent targetComponent = componentIndex < components.Count ? components[componentIndex] : null; ItemComponent targetComponent = componentIndex < components.Count ? components[componentIndex] : null;
Character targetCharacter = FindEntityByID(targetCharacterID) as Character; Character targetCharacter = FindEntityByID(targetCharacterID) as Character;
Limb targetLimb = targetCharacter != null && targetLimbID < targetCharacter.AnimController.Limbs.Length ? Limb targetLimb = targetCharacter != null && targetLimbID < targetCharacter.AnimController.Limbs.Length ?
targetCharacter.AnimController.Limbs[targetLimbID] : null; targetCharacter.AnimController.Limbs[targetLimbID] : null;
Entity useTarget = FindEntityByID(useTargetID); Entity useTarget = FindEntityByID(useTargetID);
@@ -1334,7 +1343,7 @@ namespace Barotrauma
else else
{ {
targetComponent.ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, worldPosition: worldPosition); targetComponent.ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, worldPosition: worldPosition);
} }
} }
break; break;
case NetEntityEvent.Type.ChangeProperty: case NetEntityEvent.Type.ChangeProperty:
@@ -1346,7 +1355,7 @@ namespace Barotrauma
if (UpgradePrefab.Find(identifier) is { } upgradePrefab) if (UpgradePrefab.Find(identifier) is { } upgradePrefab)
{ {
Upgrade upgrade = new Upgrade(this, upgradePrefab, level); Upgrade upgrade = new Upgrade(this, upgradePrefab, level);
byte targetCount = msg.ReadByte(); byte targetCount = msg.ReadByte();
for (int i = 0; i < targetCount; i++) for (int i = 0; i < targetCount; i++)
{ {
@@ -1360,7 +1369,7 @@ namespace Barotrauma
AddUpgrade(upgrade, false); AddUpgrade(upgrade, false);
} }
break; break;
case NetEntityEvent.Type.Invalid: case NetEntityEvent.Type.Invalid:
break; break;
} }
@@ -1394,7 +1403,7 @@ namespace Barotrauma
Character targetCharacter = FindEntityByID(characterID) as Character; Character targetCharacter = FindEntityByID(characterID) as Character;
msg.Write(characterID); msg.Write(characterID);
msg.Write(targetCharacter == null ? (byte)255 : (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb)); msg.Write(targetCharacter == null ? (byte)255 : (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb));
break; break;
case NetEntityEvent.Type.ChangeProperty: case NetEntityEvent.Type.ChangeProperty:
WritePropertyChange(msg, extraData, true); WritePropertyChange(msg, extraData, true);
@@ -14,7 +14,7 @@ namespace Barotrauma
{ {
get get
{ {
return ShowGaps; return ShowGaps && SubEditorScreen.IsLayerVisible(this);
} }
} }
@@ -42,7 +42,7 @@ namespace Barotrauma
} }
} }
if (!editing || !ShowGaps) { return; } if (!editing || !ShowGaps || !SubEditorScreen.IsLayerVisible(this)) { return; }
Color clr = (open == 0.0f) ? GUI.Style.Red : Color.Cyan; Color clr = (open == 0.0f) ? GUI.Style.Red : Color.Cyan;
if (IsHighlighted) clr = Color.Gold; if (IsHighlighted) clr = Color.Gold;
@@ -128,8 +128,14 @@ namespace Barotrauma
{ {
//no flow particles between linked hulls (= rooms consisting of multiple hulls) //no flow particles between linked hulls (= rooms consisting of multiple hulls)
if (hull1.linkedTo.Contains(hull2)) { return; } if (hull1.linkedTo.Contains(hull2)) { return; }
if (hull1.linkedTo.Any(h => h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2))) { return; } foreach (Hull h in hull1.linkedTo)
if (hull2.linkedTo.Any(h => h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2))) { return; } {
if (h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; }
}
foreach (Hull h in hull2.linkedTo)
{
if (h.linkedTo.Contains(hull1) && h.linkedTo.Contains(hull2)) { return; }
}
} }
Vector2 pos = Position; Vector2 pos = Position;
@@ -177,7 +183,7 @@ namespace Barotrauma
"bubbles", "bubbles",
(Submarine == null ? pos : pos + Submarine.Position), (Submarine == null ? pos : pos + Submarine.Position),
velocity, 0, flowTargetHull); velocity, 0, flowTargetHull);
particleTimer -= emitInterval; particleTimer -= emitInterval;
} }
} }
@@ -34,14 +34,14 @@ namespace Barotrauma
private readonly List<RemoteDecal> remoteDecals = new List<RemoteDecal>(); private readonly List<RemoteDecal> remoteDecals = new List<RemoteDecal>();
private readonly HashSet<Decal> pendingDecalUpdates = new HashSet<Decal>(); private readonly HashSet<Decal> pendingDecalUpdates = new HashSet<Decal>();
private double lastAmbientLightEditTime; private double lastAmbientLightEditTime;
public override bool SelectableInEditor public override bool SelectableInEditor
{ {
get get
{ {
return ShowHulls; return ShowHulls && SubEditorScreen.IsLayerVisible(this);
} }
} }
@@ -133,39 +133,41 @@ namespace Barotrauma
else else
{ {
if (!entity.linkedTo.Contains(this)) { entity.linkedTo.Add(this); } if (!entity.linkedTo.Contains(this)) { entity.linkedTo.Add(this); }
if (!linkedTo.Contains(this)) { linkedTo.Add(entity); } if (!linkedTo.Contains(this)) { linkedTo.Add(entity); }
} }
} }
} }
partial void UpdateProjSpecific(float deltaTime, Camera cam) partial void UpdateProjSpecific(float deltaTime, Camera cam)
{ {
serverUpdateDelay -= deltaTime; if (GameMain.Client != null)
if (serverUpdateDelay <= 0.0f)
{ {
ApplyRemoteState(); serverUpdateDelay -= deltaTime;
} if (serverUpdateDelay <= 0.0f)
if (networkUpdatePending)
{
networkUpdateTimer += deltaTime;
if (networkUpdateTimer > 0.2f)
{ {
if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any()) ApplyRemoteState();
}
if (networkUpdatePending)
{
networkUpdateTimer += deltaTime;
if (networkUpdateTimer > 0.2f)
{ {
GameMain.NetworkMember?.CreateEntityEvent(this); if (!pendingSectionUpdates.Any() && !pendingDecalUpdates.Any())
{
GameMain.NetworkMember?.CreateEntityEvent(this);
}
foreach (Decal decal in pendingDecalUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { decal });
}
foreach (int pendingSectionUpdate in pendingSectionUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate });
}
pendingSectionUpdates.Clear();
networkUpdatePending = false;
networkUpdateTimer = 0.0f;
} }
foreach (Decal decal in pendingDecalUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { decal });
}
foreach (int pendingSectionUpdate in pendingSectionUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate });
}
pendingSectionUpdates.Clear();
networkUpdatePending = false;
networkUpdateTimer = 0.0f;
} }
} }
@@ -243,7 +245,7 @@ namespace Barotrauma
return; return;
} }
if (!ShowHulls && !GameMain.DebugDraw) { return; } if ((!ShowHulls || !SubEditorScreen.IsLayerVisible(this)) && !GameMain.DebugDraw) { return; }
if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; } if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; }
@@ -385,45 +387,42 @@ namespace Barotrauma
} }
} }
private static readonly Vector3[] corners = new Vector3[6];
private static readonly Vector2[] uvCoords = new Vector2[4];
private static readonly Vector3[] prevCorners = new Vector3[2];
private static readonly Vector2[] prevUVs = new Vector2[2];
private void UpdateVertices(Camera cam, EntityGrid entityGrid, WaterRenderer renderer) private void UpdateVertices(Camera cam, EntityGrid entityGrid, WaterRenderer renderer)
{ {
Vector2 submarinePos = Submarine == null ? Vector2.Zero : Submarine.DrawPosition; Vector2 submarinePos = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
//if there's no more space in the buffer, don't render the water in the hull //if there's no more space in the buffer, don't render the water in the hull
//not an ideal solution, but this seems to only happen in cases where the missing //not an ideal solution, but this seems to only happen in cases where the missing
//water is not very noticeable (e.g. zoomed very far out so that multiple subs and ruins are visible) //water is not very noticeable (e.g. zoomed very far out so that multiple subs and ruins are visible)
if (renderer.PositionInBuffer > renderer.vertices.Length - 6) if (renderer.PositionInBuffer > renderer.vertices.Length - 6)
{ {
return; return;
} }
if (!renderer.IndoorsVertices.ContainsKey(entityGrid))
{
renderer.IndoorsVertices[entityGrid] = new VertexPositionColorTexture[WaterRenderer.DefaultIndoorsBufferSize];
renderer.PositionInIndoorsBuffer[entityGrid] = 0;
}
//calculate where the surface should be based on the water volume //calculate where the surface should be based on the water volume
float top = rect.Y + submarinePos.Y; float top = rect.Y + submarinePos.Y;
float bottom = top - rect.Height; float bottom = top - rect.Height;
float renderSurface = drawSurface + submarinePos.Y; float renderSurface = drawSurface + submarinePos.Y;
if (bottom > cam.WorldView.Y || top < cam.WorldView.Y - cam.WorldView.Height) return; if (bottom > cam.WorldView.Y || top < cam.WorldView.Y - cam.WorldView.Height) { return; }
if (rect.X + submarinePos.X > cam.WorldView.Right || rect.Right + submarinePos.X < cam.WorldView.X) { return; }
Matrix transform = cam.Transform * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f; Matrix transform = cam.Transform * Matrix.CreateOrthographic(GameMain.GraphicsWidth, GameMain.GraphicsHeight, -1, 1) * 0.5f;
if (!update) if (!update)
{ {
// create the four corners of our triangle. // create the four corners of our triangle.
Vector3[] corners = new Vector3[4];
corners[0] = new Vector3(rect.X, rect.Y, 0.0f); corners[0] = new Vector3(rect.X, rect.Y, 0.0f);
corners[1] = new Vector3(rect.X + rect.Width, rect.Y, 0.0f); corners[1] = new Vector3(rect.X + rect.Width, rect.Y, 0.0f);
corners[2] = new Vector3(corners[1].X, rect.Y - rect.Height, 0.0f); corners[2] = new Vector3(corners[1].X, rect.Y - rect.Height, 0.0f);
corners[3] = new Vector3(corners[0].X, corners[2].Y, 0.0f); corners[3] = new Vector3(corners[0].X, corners[2].Y, 0.0f);
Vector2[] uvCoords = new Vector2[4];
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
corners[i] += new Vector3(submarinePos, 0.0f); corners[i] += new Vector3(submarinePos, 0.0f);
@@ -443,6 +442,15 @@ namespace Barotrauma
return; return;
} }
if (!renderer.IndoorsVertices.ContainsKey(entityGrid))
{
renderer.IndoorsVertices[entityGrid] = new VertexPositionColorTexture[WaterRenderer.DefaultIndoorsBufferSize];
}
if (!renderer.PositionInIndoorsBuffer.ContainsKey(entityGrid))
{
renderer.PositionInIndoorsBuffer[entityGrid] = 0;
}
float x = rect.X; float x = rect.X;
if (Submarine != null) { x += Submarine.DrawPosition.X; } if (Submarine != null) { x += Submarine.DrawPosition.X; }
@@ -454,20 +462,15 @@ namespace Barotrauma
x += start * WaveWidth; x += start * WaveWidth;
Vector3[] prevCorners = new Vector3[2];
Vector2[] prevUVs = new Vector2[2];
int width = WaveWidth; int width = WaveWidth;
for (int i = start; i < end; i++) for (int i = start; i < end; i++)
{ {
Vector3[] corners = new Vector3[6];
//top left //top left
corners[0] = new Vector3(x, top, 0.0f); corners[0] = new Vector3(x, top, 0.0f);
//watersurface left //watersurface left
corners[3] = new Vector3(corners[0].X, renderSurface + waveY[i], 0.0f); corners[3] = new Vector3(corners[0].X, renderSurface + waveY[i], 0.0f);
//top right //top right
corners[1] = new Vector3(x + width, top, 0.0f); corners[1] = new Vector3(x + width, top, 0.0f);
//watersurface right //watersurface right
@@ -477,7 +480,7 @@ namespace Barotrauma
corners[4] = new Vector3(x, bottom, 0.0f); corners[4] = new Vector3(x, bottom, 0.0f);
//bottom right //bottom right
corners[5] = new Vector3(x + width, bottom, 0.0f); corners[5] = new Vector3(x + width, bottom, 0.0f);
Vector2[] uvCoords = new Vector2[4]; Vector2[] uvCoords = new Vector2[4];
for (int n = 0; n < 4; n++) for (int n = 0; n < 4; n++)
{ {
@@ -714,7 +717,7 @@ namespace Barotrauma
} }
remoteBackgroundSections.Clear(); remoteBackgroundSections.Clear();
if (remoteDecals.Any()) if (remoteDecals.Count > 0)
{ {
decals.Clear(); decals.Clear();
foreach (RemoteDecal remoteDecal in remoteDecals) foreach (RemoteDecal remoteDecal in remoteDecals)
@@ -51,7 +51,7 @@ namespace Barotrauma
public readonly WaterVertexData IndoorsSurfaceBottomColor = new WaterVertexData(0.2f, 0.1f, 0.9f, 1.0f); public readonly WaterVertexData IndoorsSurfaceBottomColor = new WaterVertexData(0.2f, 0.1f, 0.9f, 1.0f);
public VertexPositionTexture[] vertices = new VertexPositionTexture[DefaultBufferSize]; public VertexPositionTexture[] vertices = new VertexPositionTexture[DefaultBufferSize];
public Dictionary<EntityGrid, VertexPositionColorTexture[]> IndoorsVertices = new Dictionary<EntityGrid, VertexPositionColorTexture[]>();// VertexPositionColorTexture[DefaultBufferSize * 2]; public Dictionary<EntityGrid, VertexPositionColorTexture[]> IndoorsVertices = new Dictionary<EntityGrid, VertexPositionColorTexture[]>();
public Effect WaterEffect public Effect WaterEffect
{ {
@@ -81,13 +81,17 @@ namespace Barotrauma
if (basicEffect == null) if (basicEffect == null)
{ {
basicEffect = new BasicEffect(GameMain.Instance.GraphicsDevice); basicEffect = new BasicEffect(GameMain.Instance.GraphicsDevice)
basicEffect.VertexColorEnabled = false; {
VertexColorEnabled = false,
basicEffect.TextureEnabled = true; TextureEnabled = true
};
} }
} }
private readonly VertexPositionColorTexture[] tempVertices = new VertexPositionColorTexture[6];
private readonly Vector3[] tempCorners = new Vector3[4];
public void RenderWater(SpriteBatch spriteBatch, RenderTarget2D texture, Camera cam) public void RenderWater(SpriteBatch spriteBatch, RenderTarget2D texture, Camera cam)
{ {
spriteBatch.GraphicsDevice.BlendState = BlendState.NonPremultiplied; spriteBatch.GraphicsDevice.BlendState = BlendState.NonPremultiplied;
@@ -139,29 +143,26 @@ namespace Barotrauma
WaterEffect.CurrentTechnique.Passes[0].Apply(); WaterEffect.CurrentTechnique.Passes[0].Apply();
VertexPositionColorTexture[] verts = new VertexPositionColorTexture[6];
Rectangle view = cam != null ? cam.WorldView : spriteBatch.GraphicsDevice.Viewport.Bounds; Rectangle view = cam != null ? cam.WorldView : spriteBatch.GraphicsDevice.Viewport.Bounds;
var corners = new Vector3[4]; tempCorners[0] = new Vector3(view.X, view.Y, 0.1f);
corners[0] = new Vector3(view.X, view.Y, 0.1f); tempCorners[1] = new Vector3(view.Right, view.Y, 0.1f);
corners[1] = new Vector3(view.Right, view.Y, 0.1f); tempCorners[2] = new Vector3(view.Right, view.Y - view.Height, 0.1f);
corners[2] = new Vector3(view.Right, view.Y - view.Height, 0.1f); tempCorners[3] = new Vector3(view.X, view.Y - view.Height, 0.1f);
corners[3] = new Vector3(view.X, view.Y - view.Height, 0.1f);
WaterVertexData backGroundColor = new WaterVertexData(0.1f, 0.1f, 0.5f, 1.0f); WaterVertexData backGroundColor = new WaterVertexData(0.1f, 0.1f, 0.5f, 1.0f);
verts[0] = new VertexPositionColorTexture(corners[0], backGroundColor, Vector2.Zero); tempVertices[0] = new VertexPositionColorTexture(tempCorners[0], backGroundColor, Vector2.Zero);
verts[1] = new VertexPositionColorTexture(corners[1], backGroundColor, Vector2.Zero); tempVertices[1] = new VertexPositionColorTexture(tempCorners[1], backGroundColor, Vector2.Zero);
verts[2] = new VertexPositionColorTexture(corners[2], backGroundColor, Vector2.Zero); tempVertices[2] = new VertexPositionColorTexture(tempCorners[2], backGroundColor, Vector2.Zero);
verts[3] = new VertexPositionColorTexture(corners[0], backGroundColor, Vector2.Zero); tempVertices[3] = new VertexPositionColorTexture(tempCorners[0], backGroundColor, Vector2.Zero);
verts[4] = new VertexPositionColorTexture(corners[2], backGroundColor, Vector2.Zero); tempVertices[4] = new VertexPositionColorTexture(tempCorners[2], backGroundColor, Vector2.Zero);
verts[5] = new VertexPositionColorTexture(corners[3], backGroundColor, Vector2.Zero); tempVertices[5] = new VertexPositionColorTexture(tempCorners[3], backGroundColor, Vector2.Zero);
spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, verts, 0, 2); spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, tempVertices, 0, 2);
foreach (KeyValuePair<EntityGrid, VertexPositionColorTexture[]> subVerts in IndoorsVertices) foreach (KeyValuePair<EntityGrid, VertexPositionColorTexture[]> subVerts in IndoorsVertices)
{ {
if (!PositionInIndoorsBuffer.ContainsKey(subVerts.Key) || PositionInIndoorsBuffer[subVerts.Key] == 0) continue; if (!PositionInIndoorsBuffer.ContainsKey(subVerts.Key) || PositionInIndoorsBuffer[subVerts.Key] == 0) { continue; }
offset = WavePos; offset = WavePos;
if (subVerts.Key.Submarine != null) { offset -= subVerts.Key.Submarine.WorldPosition; } if (subVerts.Key.Submarine != null) { offset -= subVerts.Key.Submarine.WorldPosition; }
@@ -207,11 +208,23 @@ namespace Barotrauma
basicEffect.CurrentTechnique.Passes[0].Apply(); basicEffect.CurrentTechnique.Passes[0].Apply();
} }
private readonly List<EntityGrid> buffersToRemove = new List<EntityGrid>();
public void ResetBuffers() public void ResetBuffers()
{ {
PositionInBuffer = 0; PositionInBuffer = 0;
PositionInIndoorsBuffer.Clear(); PositionInIndoorsBuffer.Clear();
IndoorsVertices.Clear(); buffersToRemove.Clear();
foreach (var buffer in IndoorsVertices.Keys)
{
if (buffer.Submarine?.Removed ?? false)
{
buffersToRemove.Add(buffer);
}
}
foreach (var bufferToRemove in buffersToRemove)
{
IndoorsVertices.Remove(bufferToRemove);
}
} }
public void Dispose() public void Dispose()
@@ -517,7 +517,7 @@ namespace Barotrauma.Lights
private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub) private void RefreshConvexHullList(ConvexHullList chList, Vector2 lightPos, Submarine sub)
{ {
var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub);
if (fullChList == null) return; if (fullChList == null) { return; }
chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, ch.BoundingBox)); chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, ch.BoundingBox));
@@ -530,105 +530,121 @@ namespace Barotrauma.Lights
/// </summary> /// </summary>
private void CheckHullsInRange() private void CheckHullsInRange()
{ {
List<Submarine> subs = new List<Submarine>(Submarine.Loaded); foreach (Submarine sub in Submarine.Loaded)
subs.Add(null);
foreach (Submarine sub in subs)
{ {
//find the list of convexhulls that belong to the sub CheckHullsInRange(sub);
var chList = hullsInRange.Find(x => x.Submarine == sub); }
//check convex hulls that aren't in any sub
CheckHullsInRange(null);
}
//not found -> create one private void CheckHullsInRange(Submarine sub)
if (chList == null) {
//find the list of convexhulls that belong to the sub
ConvexHullList chList = null;
foreach (var ch in hullsInRange)
{
if (ch.Submarine == sub)
{ {
chList = new ConvexHullList(sub); chList = ch;
hullsInRange.Add(chList); break;
NeedsRecalculation = true;
}
if (chList.List.Any(ch => ch.LastVertexChangeTime > lastRecalculationTime && !chList.IsHidden.Contains(ch)))
{
NeedsRecalculation = true;
}
Vector2 lightPos = position;
if (ParentSub == null)
{
//light and the convexhulls are both outside
if (sub == null)
{
if (NeedsHullCheck)
{
RefreshConvexHullList(chList, lightPos, null);
}
}
//light is outside, convexhulls inside a sub
else
{
lightPos -= sub.Position;
Rectangle subBorders = sub.Borders;
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//only draw if the light overlaps with the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, subBorders))
{
if (chList.List.Count > 0) NeedsRecalculation = true;
chList.List.Clear();
continue;
}
RefreshConvexHullList(chList, lightPos, sub);
}
}
else
{
//light is inside, convexhull outside
if (sub == null) continue;
//light and convexhull are both inside the same sub
if (sub == ParentSub)
{
if (NeedsHullCheck)
{
RefreshConvexHullList(chList, lightPos, sub);
}
}
//light and convexhull are inside different subs
else
{
if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) continue;
lightPos -= (sub.Position - ParentSub.Position);
Rectangle subBorders = sub.Borders;
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//don't draw any shadows if the light doesn't overlap with the borders of the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, subBorders))
{
if (chList.List.Count > 0) NeedsRecalculation = true;
chList.List.Clear();
continue;
}
//recalculate vertices if the subs have moved > 5 px relative to each other
Vector2 diff = ParentSub.WorldPosition - sub.WorldPosition;
if (!diffToSub.TryGetValue(sub, out Vector2 prevDiff))
{
diffToSub.Add(sub, diff);
NeedsRecalculation = true;
}
else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f * 5.0f)
{
diffToSub[sub] = diff;
NeedsRecalculation = true;
}
RefreshConvexHullList(chList, lightPos, sub);
}
} }
} }
//not found -> create one
if (chList == null)
{
chList = new ConvexHullList(sub);
hullsInRange.Add(chList);
NeedsRecalculation = true;
}
foreach (var ch in chList.List)
{
if (ch.LastVertexChangeTime > lastRecalculationTime && !chList.IsHidden.Contains(ch))
{
NeedsRecalculation = true;
break;
}
}
Vector2 lightPos = position;
if (ParentSub == null)
{
//light and the convexhulls are both outside
if (sub == null)
{
if (NeedsHullCheck)
{
RefreshConvexHullList(chList, lightPos, null);
}
}
//light is outside, convexhulls inside a sub
else
{
lightPos -= sub.Position;
Rectangle subBorders = sub.Borders;
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//only draw if the light overlaps with the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, subBorders))
{
if (chList.List.Count > 0) { NeedsRecalculation = true; }
chList.List.Clear();
return;
}
RefreshConvexHullList(chList, lightPos, sub);
}
}
else
{
//light is inside, convexhull outside
if (sub == null) { return; }
//light and convexhull are both inside the same sub
if (sub == ParentSub)
{
if (NeedsHullCheck)
{
RefreshConvexHullList(chList, lightPos, sub);
}
}
//light and convexhull are inside different subs
else
{
if (sub.DockedTo.Contains(ParentSub) && !NeedsHullCheck) { return; }
lightPos -= (sub.Position - ParentSub.Position);
Rectangle subBorders = sub.Borders;
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//don't draw any shadows if the light doesn't overlap with the borders of the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, subBorders))
{
if (chList.List.Count > 0) { NeedsRecalculation = true; }
chList.List.Clear();
return;
}
//recalculate vertices if the subs have moved > 5 px relative to each other
Vector2 diff = ParentSub.WorldPosition - sub.WorldPosition;
if (!diffToSub.TryGetValue(sub, out Vector2 prevDiff))
{
diffToSub.Add(sub, diff);
NeedsRecalculation = true;
}
else if (Vector2.DistanceSquared(diff, prevDiff) > 5.0f * 5.0f)
{
diffToSub[sub] = diff;
NeedsRecalculation = true;
}
RefreshConvexHullList(chList, lightPos, sub);
}
}
} }
private List<Vector2> FindRaycastHits() private List<Vector2> FindRaycastHits()
@@ -75,10 +75,12 @@ namespace Barotrauma
if (linkedTo.Contains(entity)) if (linkedTo.Contains(entity))
{ {
linkedTo.Remove(entity); linkedTo.Remove(entity);
entity.linkedTo.Remove(this);
} }
else else
{ {
linkedTo.Add(entity); linkedTo.Add(entity);
if (!entity.linkedTo.Contains(this)) { entity.linkedTo.Add(this); }
} }
} }
} }
@@ -30,6 +30,9 @@ namespace Barotrauma
{ {
return false; return false;
} }
if (!SubEditorScreen.IsLayerVisible(this)) { return false; }
return HasBody ? ShowWalls : ShowStructures; return HasBody ? ShowWalls : ShowStructures;
} }
} }
@@ -244,8 +247,10 @@ namespace Barotrauma
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{ {
if (prefab.sprite == null) { return; } if (prefab.sprite == null) { return; }
if (editing) if (editing)
{ {
if (!SubEditorScreen.IsLayerVisible(this)) { return; }
if (!HasBody && !ShowStructures) { return; } if (!HasBody && !ShowStructures) { return; }
if (HasBody && !ShowWalls) { return; } if (HasBody && !ShowWalls) { return; }
} }
@@ -273,6 +278,7 @@ namespace Barotrauma
if (prefab.sprite == null) { return; } if (prefab.sprite == null) { return; }
if (editing) if (editing)
{ {
if (!SubEditorScreen.IsLayerVisible(this)) { return; }
if (!HasBody && !ShowStructures) { return; } if (!HasBody && !ShowStructures) { return; }
if (HasBody && !ShowWalls) { return; } if (HasBody && !ShowWalls) { return; }
} }
@@ -285,13 +291,11 @@ namespace Barotrauma
//color = Color.Lerp(color, Color.Gold, 0.5f); //color = Color.Lerp(color, Color.Gold, 0.5f);
color = spriteColor; color = spriteColor;
Vector2 rectSize = rect.Size.ToVector2(); Vector2 rectSize = rect.Size.ToVector2();
if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; } if (BodyWidth > 0.0f) { rectSize.X = BodyWidth; }
if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; } if (BodyHeight > 0.0f) { rectSize.Y = BodyHeight; }
Vector2 bodyPos = WorldPosition + BodyOffset; Vector2 bodyPos = WorldPosition + BodyOffset * Scale;
GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White, GUI.DrawRectangle(spriteBatch, new Vector2(bodyPos.X, -bodyPos.Y), rectSize.X, rectSize.Y, BodyRotation, Color.White,
thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom))); thickness: Math.Max(1, (int)(2 / Screen.Selected.Cam.Zoom)));
@@ -465,7 +469,8 @@ namespace Barotrauma
public void UpdateSpriteStates(float deltaTime) public void UpdateSpriteStates(float deltaTime)
{ {
DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches); if (Prefab.DecorativeSpriteGroups.Count == 0) { return; }
DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches);
foreach (int spriteGroup in Prefab.DecorativeSpriteGroups.Keys) foreach (int spriteGroup in Prefab.DecorativeSpriteGroups.Keys)
{ {
for (int i = 0; i < Prefab.DecorativeSpriteGroups[spriteGroup].Count; i++) for (int i = 0; i < Prefab.DecorativeSpriteGroups[spriteGroup].Count; i++)
@@ -25,14 +25,11 @@ namespace Barotrauma
public readonly bool Stream; public readonly bool Stream;
public readonly bool IgnoreMuffling; public readonly bool IgnoreMuffling;
public readonly string Filename;
public string Filename
{
get { return Sound?.Filename; }
}
public RoundSound(XElement element, Sound sound) public RoundSound(XElement element, Sound sound)
{ {
Filename = sound?.Filename;
Sound = sound; Sound = sound;
Stream = sound.Stream; Stream = sound.Stream;
Range = element.GetAttributeFloat("range", 1000.0f); Range = element.GetAttributeFloat("range", 1000.0f);
@@ -146,6 +146,7 @@ namespace Barotrauma
private bool IsHidden() private bool IsHidden()
{ {
if (!SubEditorScreen.IsLayerVisible(this)) { return false; }
if (spawnType == SpawnType.Path) if (spawnType == SpawnType.Path)
{ {
return (!GameMain.DebugDraw && !ShowWayPoints); return (!GameMain.DebugDraw && !ShowWayPoints);
@@ -294,7 +295,7 @@ namespace Barotrauma
else else
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("Spawnpoint"), font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), TextManager.Get("Spawnpoint"), font: GUI.LargeFont);
var spawnTypeContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true) var spawnTypeContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -318,7 +319,10 @@ namespace Barotrauma
}; };
var descText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), var descText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("IDCardDescription"), font: GUI.SmallFont); TextManager.Get("IDCardDescription"), font: GUI.SmallFont)
{
ToolTip = TextManager.Get("IDCardDescriptionTooltip")
};
GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), IdCardDesc) GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), IdCardDesc)
{ {
MaxTextLength = 150, MaxTextLength = 150,
@@ -342,7 +346,10 @@ namespace Barotrauma
}; };
var idCardTagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), var idCardTagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("IDCardTags"), font: GUI.SmallFont); TextManager.Get("IDCardTags"), font: GUI.SmallFont)
{
ToolTip = TextManager.Get("IDCardTagsTooltip")
};
propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), idCardTagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags)) propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), idCardTagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags))
{ {
MaxTextLength = 60, MaxTextLength = 60,
@@ -414,6 +421,6 @@ namespace Barotrauma
PositionEditingHUD(); PositionEditingHUD();
return editingHUD; return editingHUD;
} }
} }
} }
@@ -78,7 +78,7 @@ namespace Barotrauma.Networking
txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, targetRoom, txt = orderPrefab.GetChatMessage(orderMessageInfo.TargetCharacter?.Name, targetRoom,
givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter, givingOrderToSelf: orderMessageInfo.TargetCharacter == senderCharacter,
orderOption: orderOption, orderOption: orderOption,
priority: orderMessageInfo.Priority); isNewOrder: orderMessageInfo.IsNewOrder);
if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen) if (GameMain.Client.GameStarted && Screen.Selected == GameMain.GameScreen)
{ {
@@ -948,6 +948,9 @@ namespace Barotrauma.Networking
case ServerPacketHeader.CREW: case ServerPacketHeader.CREW:
campaign?.ClientReadCrew(inc); campaign?.ClientReadCrew(inc);
break; break;
case ServerPacketHeader.MEDICAL:
campaign?.MedicalClinic?.ClientRead(inc);
break;
case ServerPacketHeader.READY_CHECK: case ServerPacketHeader.READY_CHECK:
ReadyCheck.ClientRead(inc); ReadyCheck.ClientRead(inc);
break; break;
@@ -1116,9 +1119,9 @@ namespace Barotrauma.Networking
disconnectReason != DisconnectReason.InvalidVersion) disconnectReason != DisconnectReason.InvalidVersion)
{ {
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"GameClient.HandleDisconnectMessage", "GameClient.HandleDisconnectMessage",
GameAnalyticsManager.ErrorSeverity.Debug, GameAnalyticsManager.ErrorSeverity.Debug,
"Client received a disconnect message. Reason: " + disconnectReason.ToString()); "Client received a disconnect message. Reason: " + disconnectReason.ToString());
} }
if (disconnectReason == DisconnectReason.ServerFull) if (disconnectReason == DisconnectReason.ServerFull)
@@ -1271,7 +1274,15 @@ namespace Barotrauma.Networking
private void ReadAchievement(IReadMessage inc) private void ReadAchievement(IReadMessage inc)
{ {
string achievementIdentifier = inc.ReadString(); string achievementIdentifier = inc.ReadString();
SteamAchievementManager.UnlockAchievement(achievementIdentifier); int amount = inc.ReadInt32();
if (amount == 0)
{
SteamAchievementManager.UnlockAchievement(achievementIdentifier);
}
else
{
SteamAchievementManager.IncrementStat(achievementIdentifier, amount);
}
} }
private void ReadTraitorMessage(IReadMessage inc) private void ReadTraitorMessage(IReadMessage inc)
@@ -1471,7 +1482,7 @@ namespace Barotrauma.Networking
serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean();
serverSettings.AllowLinkingWifiToChat = inc.ReadBoolean(); serverSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte(); GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits(); bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
GameMain.LightManager.LightingEnabled = true; GameMain.LightManager.LightingEnabled = true;
@@ -1483,6 +1494,8 @@ namespace Barotrauma.Networking
Task loadTask = null; Task loadTask = null;
var roundSummary = (GUIMessageBox.MessageBoxes.Find(c => c?.UserData is RoundSummary)?.UserData) as RoundSummary; var roundSummary = (GUIMessageBox.MessageBoxes.Find(c => c?.UserData is RoundSummary)?.UserData) as RoundSummary;
bool isOutpost = false;
if (gameMode != GameModePreset.MultiPlayerCampaign) if (gameMode != GameModePreset.MultiPlayerCampaign)
{ {
string levelSeed = inc.ReadString(); string levelSeed = inc.ReadString();
@@ -1621,6 +1634,7 @@ namespace Barotrauma.Networking
{ {
GameMain.GameSession.StartRound(levelData, mirrorLevel); GameMain.GameSession.StartRound(levelData, mirrorLevel);
} }
isOutpost = levelData.Type == LevelData.LevelType.Outpost;
} }
if (GameMain.Client?.ServerSettings?.Voting != null) if (GameMain.Client?.ServerSettings?.Voting != null)
@@ -1740,8 +1754,7 @@ namespace Barotrauma.Networking
if (respawnAllowed) if (respawnAllowed)
{ {
bool isOutpost = GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign && Level.Loaded?.Type == LevelData.LevelType.Outpost; respawnManager = new RespawnManager(this, usingShuttle && !isOutpost ? GameMain.NetLobbyScreen.SelectedShuttle : null);
respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle && !isOutpost ? GameMain.NetLobbyScreen.SelectedShuttle : null);
} }
gameStarted = true; gameStarted = true;
@@ -1872,7 +1885,7 @@ namespace Barotrauma.Networking
if (int.TryParse(ownedIndexes[i], out int index)) if (int.TryParse(ownedIndexes[i], out int index))
{ {
SubmarineInfo sub = GameMain.Client.ServerSubmarines[index]; SubmarineInfo sub = GameMain.Client.ServerSubmarines[index];
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "owned")) if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Owned))
{ {
GameMain.GameSession.OwnedSubmarines.Add(sub); GameMain.GameSession.OwnedSubmarines.Add(sub);
} }
@@ -1888,7 +1901,7 @@ namespace Barotrauma.Networking
if (int.TryParse(ownedIndexes[i], out index)) if (int.TryParse(ownedIndexes[i], out index))
{ {
SubmarineInfo sub = GameMain.Client.ServerSubmarines[index]; SubmarineInfo sub = GameMain.Client.ServerSubmarines[index];
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "owned")) if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Owned))
{ {
GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(sub); GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(sub);
} }
@@ -2090,13 +2103,6 @@ namespace Barotrauma.Networking
string selectShuttleName = inc.ReadString(); string selectShuttleName = inc.ReadString();
string selectShuttleHash = inc.ReadString(); string selectShuttleHash = inc.ReadString();
UInt16 campaignSubmarineIndexCount = inc.ReadUInt16();
List<int> campaignSubIndices = new List<int>();
for (int i = 0; i< campaignSubmarineIndexCount; i++)
{
campaignSubIndices.Add(inc.ReadUInt16());
}
bool allowSubVoting = inc.ReadBoolean(); bool allowSubVoting = inc.ReadBoolean();
bool allowModeVoting = inc.ReadBoolean(); bool allowModeVoting = inc.ReadBoolean();
@@ -2157,16 +2163,11 @@ namespace Barotrauma.Networking
if (GameMain.Client.IsServerOwner) RequestSelectMode(modeIndex); if (GameMain.Client.IsServerOwner) RequestSelectMode(modeIndex);
} }
if (campaignSubIndices != null) if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
{ {
GameMain.NetLobbyScreen.CampaignSubmarines = new List<SubmarineInfo>(); foreach (SubmarineInfo sub in ServerSubmarines.Where(s => !ServerSettings.HiddenSubs.Contains(s.Name)))
foreach (UInt16 campaignSubIndex in campaignSubIndices)
{ {
SubmarineInfo sub = GameMain.Client.ServerSubmarines[campaignSubIndex]; GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Campaign);
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "campaign"))
{
GameMain.NetLobbyScreen.CampaignSubmarines.Add(sub);
}
} }
} }
@@ -2599,7 +2600,6 @@ namespace Barotrauma.Networking
NetLobbyScreen.FailedSubInfo failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.Hash); NetLobbyScreen.FailedSubInfo failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.Hash);
if (failedCampaignSub != default) if (failedCampaignSub != default)
{ {
GameMain.NetLobbyScreen.CampaignSubmarines.Add(newSub);
GameMain.NetLobbyScreen.FailedCampaignSubs.Remove(failedCampaignSub); GameMain.NetLobbyScreen.FailedCampaignSubs.Remove(failedCampaignSub);
} }
@@ -100,7 +100,7 @@ namespace Barotrauma.Networking
if (!isActive) { return; } if (!isActive) { return; }
if (steamId != hostSteamId) { return; } if (steamId != hostSteamId) { return; }
Close($"SteamP2P connection failed: {error}"); Close($"SteamP2P connection failed: {error}");
OnDisconnectMessageReceived?.Invoke($"SteamP2P connection failed: {error}"); OnDisconnectMessageReceived?.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P connection failed: {error}");
} }
private void OnP2PData(ulong steamId, byte[] data, int dataLength) private void OnP2PData(ulong steamId, byte[] data, int dataLength)
@@ -167,14 +167,14 @@ namespace Barotrauma.Networking
if (state == null) if (state == null)
{ {
Close("SteamP2P connection could not be established"); Close("SteamP2P connection could not be established");
OnDisconnectMessageReceived?.Invoke("SteamP2P connection could not be established"); OnDisconnectMessageReceived?.Invoke(DisconnectReason.SteamP2PError.ToString());
} }
else else
{ {
if (state?.P2PSessionError != Steamworks.P2PSessionError.None) if (state?.P2PSessionError != Steamworks.P2PSessionError.None)
{ {
Close($"SteamP2P error code: {state?.P2PSessionError}"); Close($"SteamP2P error code: {state?.P2PSessionError}");
OnDisconnectMessageReceived?.Invoke($"SteamP2P error code: {state?.P2PSessionError}"); OnDisconnectMessageReceived?.Invoke($"{DisconnectReason.SteamP2PError}/SteamP2P error code: {state?.P2PSessionError}");
} }
} }
connectionStatusTimer = 1.0f; connectionStatusTimer = 1.0f;
@@ -210,7 +210,7 @@ namespace Barotrauma.Networking
if (timeout < 0.0) if (timeout < 0.0)
{ {
Close("Timed out"); Close("Timed out");
OnDisconnectMessageReceived?.Invoke(""); OnDisconnectMessageReceived?.Invoke(DisconnectReason.SteamP2PTimeOut.ToString());
return; return;
} }
@@ -349,13 +349,19 @@ namespace Barotrauma.Networking
outMsg.Write((byte)PacketHeader.IsDisconnectMessage); outMsg.Write((byte)PacketHeader.IsDisconnectMessage);
outMsg.Write(msg ?? "Disconnected"); outMsg.Write(msg ?? "Disconnected");
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable); try
sentBytes += outMsg.LengthBytes; {
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to send a disconnect message to the server using SteamP2P.", e);
}
Thread.Sleep(100); Thread.Sleep(100);
Steamworks.SteamNetworking.ResetActions(); Steamworks.SteamNetworking.ResetActions();
Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId); Steamworks.SteamNetworking.CloseP2PSessionWithUser(hostSteamId);
steamAuthTicket?.Cancel(); steamAuthTicket = null; steamAuthTicket?.Cancel(); steamAuthTicket = null;
@@ -81,16 +81,15 @@ namespace Barotrauma.Networking
public bool ContentPackagesMatch() public bool ContentPackagesMatch()
{ {
var myContentPackages = ContentPackage.AllPackages;
//make sure we have all the packages the server requires //make sure we have all the packages the server requires
if (ContentPackageHashes.Count != ContentPackageWorkshopIds.Count) { return false; } if (ContentPackageHashes.Count != ContentPackageWorkshopIds.Count) { return false; }
for (int i = 0; i < ContentPackageWorkshopIds.Count; i++) for (int i = 0; i < ContentPackageWorkshopIds.Count; i++)
{ {
string hash = ContentPackageHashes[i]; string hash = ContentPackageHashes[i];
UInt64 id = ContentPackageWorkshopIds[i]; UInt64 id = ContentPackageWorkshopIds[i];
if (!myContentPackages.Any(myPackage => myPackage.MD5hash.Hash == hash)) if (!GameMain.ServerListScreen.ContentPackagesByHash.ContainsKey(hash))
{ {
if (myContentPackages.Any(p => p.SteamWorkshopId == id)) { return false; } if (GameMain.ServerListScreen.ContentPackagesByWorkshopId.ContainsKey(id)) { return false; }
if (id == 0) { return false; } if (id == 0) { return false; }
} }
} }
@@ -98,12 +97,6 @@ namespace Barotrauma.Networking
return true; return true;
} }
public bool ContentPackagesMatch(IEnumerable<string> myContentPackageHashes)
{
HashSet<string> contentPackageHashes = new HashSet<string>(ContentPackageHashes);
return contentPackageHashes.SetEquals(myContentPackageHashes);
}
public void CreatePreviewWindow(GUIFrame frame) public void CreatePreviewWindow(GUIFrame frame)
{ {
if (frame == null) { return; } if (frame == null) { return; }
@@ -428,7 +421,7 @@ namespace Barotrauma.Networking
return; return;
} }
var rules = ((Task<Dictionary<string, string>>)t).Result; t.TryGetResult(out Dictionary<string, string> rules);
SteamManager.AssignServerRulesToServerInfo(rules, this); SteamManager.AssignServerRulesToServerInfo(rules, this);
onServerRulesReceived(this); onServerRulesReceived(this);
@@ -94,10 +94,10 @@ namespace Barotrauma.Networking
public void ClientAdminRead(IReadMessage incMsg) public void ClientAdminRead(IReadMessage incMsg)
{ {
int count = incMsg.ReadUInt16(); while (true)
for (int i = 0; i < count; i++)
{ {
UInt32 key = incMsg.ReadUInt32(); UInt32 key = incMsg.ReadUInt32();
if (key == 0) { break; }
if (netProperties.ContainsKey(key)) if (netProperties.ContainsKey(key))
{ {
bool changedLocally = netProperties[key].ChangedLocally; bool changedLocally = netProperties[key].ChangedLocally;
@@ -153,8 +153,11 @@ namespace Barotrauma.Networking
{ {
ReadExtraCargo(incMsg); ReadExtraCargo(incMsg);
} }
ReadHiddenSubs(incMsg); if (requiredFlags.HasFlag(NetFlags.HiddenSubs))
{
ReadHiddenSubs(incMsg);
}
GameMain.NetLobbyScreen.UpdateSubVisibility(); GameMain.NetLobbyScreen.UpdateSubVisibility();
bool isAdmin = incMsg.ReadBoolean(); bool isAdmin = incMsg.ReadBoolean();
@@ -143,8 +143,7 @@ namespace Barotrauma.Steam
return; return;
} }
currentLobby = ((Task<Steamworks.Data.Lobby?>)lobby).Result; lobby.TryGetResult(out currentLobby);
if (currentLobby == null) if (currentLobby == null)
{ {
DebugConsole.ThrowError("Failed to create Steam lobby: returned lobby was null"); DebugConsole.ThrowError("Failed to create Steam lobby: returned lobby was null");
@@ -250,7 +249,7 @@ namespace Barotrauma.Steam
TaskPool.Add("JoinLobbyAsync", Steamworks.SteamMatchmaking.JoinLobbyAsync(lobbyID), TaskPool.Add("JoinLobbyAsync", Steamworks.SteamMatchmaking.JoinLobbyAsync(lobbyID),
(lobby) => (lobby) =>
{ {
currentLobby = ((Task<Steamworks.Data.Lobby?>)lobby).Result; lobby.TryGetResult(out currentLobby);
lobbyState = LobbyState.Joined; lobbyState = LobbyState.Joined;
lobbyID = (currentLobby?.Id).Value; lobbyID = (currentLobby?.Id).Value;
if (joinServer) if (joinServer)
@@ -293,10 +292,11 @@ namespace Barotrauma.Steam
taskDone(); taskDone();
return; return;
} }
var lobbies = ((Task<List<Steamworks.Data.Lobby>>)t).Result; t.TryGetResult(out List<Steamworks.Data.Lobby> lobbies);
if (lobbies != null) IEnumerable<CoroutineStatus> lobbyAddCoroutine()
{ {
foreach (var lobby in lobbies) int i = 0;
foreach (var lobby in lobbies ?? Enumerable.Empty<Steamworks.Data.Lobby>())
{ {
if (string.IsNullOrEmpty(lobby.GetData("name"))) { continue; } if (string.IsNullOrEmpty(lobby.GetData("name"))) { continue; }
@@ -312,9 +312,13 @@ namespace Barotrauma.Steam
AssignLobbyDataToServerInfo(lobby, serverInfo); AssignLobbyDataToServerInfo(lobby, serverInfo);
addToServerList(serverInfo); addToServerList(serverInfo);
i++;
if (i >= 16) { yield return CoroutineStatus.Running; i = 0; }
} }
taskDone();
yield return CoroutineStatus.Success;
} }
taskDone(); CoroutineManager.StartCoroutine(lobbyAddCoroutine());
}); });
Steamworks.ServerList.Internet serverQuery = new Steamworks.ServerList.Internet(); Steamworks.ServerList.Internet serverQuery = new Steamworks.ServerList.Internet();
@@ -344,13 +348,10 @@ namespace Barotrauma.Steam
return; return;
} }
var rules = ((Task<Dictionary<string, string>>)t).Result; t.TryGetResult(out Dictionary<string, string> rules);
AssignServerRulesToServerInfo(rules, serverInfo); AssignServerRulesToServerInfo(rules, serverInfo);
CrossThread.RequestExecutionOnMainThread(() => addToServerList(serverInfo);
{
addToServerList(serverInfo);
});
}); });
} }
else else
@@ -618,7 +619,10 @@ namespace Barotrauma.Steam
.WithLongDescription(); .WithLongDescription();
if (requireTags != null) { query = query.WithTags(requireTags); } if (requireTags != null) { query = query.WithTags(requireTags); }
TaskPool.Add("GetSubscribedWorkshopItems", GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(((Task<List<Steamworks.Ugc.Item>>)task).Result); }); TaskPool.Add("GetSubscribedWorkshopItems", GetWorkshopItemsAsync(query), (task) =>
{
task.TryGetResult(out List<Steamworks.Ugc.Item> result); onItemsFound?.Invoke(result);
});
} }
public static void GetPopularWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, int amount, List<string> requireTags = null) public static void GetPopularWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, int amount, List<string> requireTags = null)
@@ -632,7 +636,7 @@ namespace Barotrauma.Steam
TaskPool.Add("GetPopularWorkshopItems", GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) => TaskPool.Add("GetPopularWorkshopItems", GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) =>
{ {
var entries = ((Task<List<Steamworks.Ugc.Item>>)task).Result; task.TryGetResult(out List<Steamworks.Ugc.Item> entries);
//count the number of each unique tag //count the number of each unique tag
foreach (var item in entries) foreach (var item in entries)
@@ -677,7 +681,10 @@ namespace Barotrauma.Steam
.WithLongDescription(); .WithLongDescription();
if (requireTags != null) query.WithTags(requireTags); if (requireTags != null) query.WithTags(requireTags);
TaskPool.Add("GetPublishedWorkshopItems", GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(((Task<List<Steamworks.Ugc.Item>>)task).Result); }); TaskPool.Add("GetPublishedWorkshopItems", GetWorkshopItemsAsync(query), (task) =>
{
task.TryGetResult(out List<Steamworks.Ugc.Item> result); onItemsFound?.Invoke(result);
});
} }
private static readonly HashSet<ulong> pendingWorkshopSubscriptions = new HashSet<ulong>(); private static readonly HashSet<ulong> pendingWorkshopSubscriptions = new HashSet<ulong>();
@@ -724,7 +731,7 @@ namespace Barotrauma.Steam
} }
else else
{ {
var item = ((Task<Steamworks.Ugc.Item?>)t).Result; t.TryGetResult(out Steamworks.Ugc.Item? item);
if (item != null) if (item != null)
{ {
if (item?.IsInstalled ?? false) if (item?.IsInstalled ?? false)
@@ -1077,7 +1084,7 @@ namespace Barotrauma.Steam
GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red); GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red);
return; return;
} }
string errorMsg = ((Task<string>)task).Result; task.TryGetResult(out string errorMsg);
if (!string.IsNullOrWhiteSpace(errorMsg)) if (!string.IsNullOrWhiteSpace(errorMsg))
{ {
DebugConsole.ThrowError($"Failed to copy \"{item.Title}\": {errorMsg}"); DebugConsole.ThrowError($"Failed to copy \"{item.Title}\": {errorMsg}");
@@ -246,6 +246,8 @@ namespace Barotrauma
foreach (string saveFile in saveFiles) foreach (string saveFile in saveFiles)
{ {
if (string.IsNullOrEmpty(saveFile)) { continue; }
string fileName = saveFile; string fileName = saveFile;
string subName = ""; string subName = "";
string saveTime = ""; string saveTime = "";
@@ -45,6 +45,8 @@ namespace Barotrauma
public UpgradeStore UpgradeStore { get; set; } public UpgradeStore UpgradeStore { get; set; }
public MedicalClinicUI MedicalClinic { get; set; }
public CampaignUI(CampaignMode campaign, GUIComponent container) public CampaignUI(CampaignMode campaign, GUIComponent container)
{ {
Campaign = campaign; Campaign = campaign;
@@ -270,6 +272,9 @@ namespace Barotrauma
// Submarine buying tab // Submarine buying tab
tabs[(int)CampaignMode.InteractionType.PurchaseSub] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f); tabs[(int)CampaignMode.InteractionType.PurchaseSub] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform, Anchor.TopLeft), color: Color.Black * 0.9f);
tabs[(int)CampaignMode.InteractionType.MedicalClinic] = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
MedicalClinic = new MedicalClinicUI(Campaign.MedicalClinic, GetTabContainer(CampaignMode.InteractionType.MedicalClinic));
// mission info ------------------------------------------------------------------------- // mission info -------------------------------------------------------------------------
locationInfoPanel = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.75f), GetTabContainer(CampaignMode.InteractionType.Map).RectTransform, Anchor.CenterRight) locationInfoPanel = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.75f), GetTabContainer(CampaignMode.InteractionType.Map).RectTransform, Anchor.CenterRight)
@@ -355,6 +360,10 @@ namespace Barotrauma
case CampaignMode.InteractionType.Store: case CampaignMode.InteractionType.Store:
Store?.Update(deltaTime); Store?.Update(deltaTime);
break; break;
case CampaignMode.InteractionType.MedicalClinic:
MedicalClinic?.Update(deltaTime);
break;
} }
} }
@@ -247,16 +247,6 @@ namespace Barotrauma
return msgBox; return msgBox;
} }
private void NotifyPrompt(string header, string body)
{
GUIMessageBox msgBox = new GUIMessageBox(header, body, new[] { TextManager.Get("Ok") }, new Vector2(0.2f, 0.175f), minSize: new Point(300, 175));
msgBox.Buttons[0].OnClicked = delegate
{
msgBox.Close();
return true;
};
}
private bool SaveProjectToFile(GUIButton button, object o) private bool SaveProjectToFile(GUIButton button, object o)
{ {
string directory = Path.GetFullPath("EventProjects"); string directory = Path.GetFullPath("EventProjects");
@@ -315,7 +305,7 @@ namespace Barotrauma
CreateNodes(prefab.ConfigElement, ref hadNodes); CreateNodes(prefab.ConfigElement, ref hadNodes);
if (!hadNodes) if (!hadNodes)
{ {
NotifyPrompt(TextManager.Get("EventEditor.RandomGenerationHeader"), TextManager.Get("EventEditor.RandomGenerationBody")); GUI.NotifyPrompt(TextManager.Get("EventEditor.RandomGenerationHeader"), TextManager.Get("EventEditor.RandomGenerationBody"));
} }
return true; return true;
}); });
@@ -221,7 +221,6 @@ namespace Barotrauma
public SubmarineInfo SelectedShuttle => ShuttleList.SelectedData as SubmarineInfo; public SubmarineInfo SelectedShuttle => ShuttleList.SelectedData as SubmarineInfo;
public MultiPlayerCampaignSetupUI CampaignSetupUI; public MultiPlayerCampaignSetupUI CampaignSetupUI;
public List<SubmarineInfo> CampaignSubmarines = new List<SubmarineInfo>();
// Passed onto the gamesession when created // Passed onto the gamesession when created
public List<SubmarineInfo> ServerOwnedSubmarines = new List<SubmarineInfo>(); public List<SubmarineInfo> ServerOwnedSubmarines = new List<SubmarineInfo>();
@@ -611,6 +610,7 @@ namespace Barotrauma
{ {
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
if (GameMain.Client == null) { return true; }
GameMain.Client.RequestStartRound(); GameMain.Client.RequestStartRound();
CoroutineManager.StartCoroutine(WaitForStartRound(StartButton), "WaitForStartRound"); CoroutineManager.StartCoroutine(WaitForStartRound(StartButton), "WaitForStartRound");
return true; return true;
@@ -628,7 +628,7 @@ namespace Barotrauma
{ {
OnSelected = (tickBox) => OnSelected = (tickBox) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, autoRestart: tickBox.Selected); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, autoRestart: tickBox.Selected);
return true; return true;
} }
}; };
@@ -655,6 +655,7 @@ namespace Barotrauma
}; };
ServerName.OnDeselected += (textBox, key) => ServerName.OnDeselected += (textBox, key) =>
{ {
if (GameMain.Client == null) { return; }
if (!textBox.Readonly) if (!textBox.Readonly)
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Name); GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Name);
@@ -669,6 +670,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("addtofavorites"), ToolTip = TextManager.Get("addtofavorites"),
OnSelected = (tickbox) => OnSelected = (tickbox) =>
{ {
if (GameMain.Client == null) { return true; }
ServerInfo info = GameMain.Client.ServerSettings.GetServerListInfo(); ServerInfo info = GameMain.Client.ServerSettings.GetServerListInfo();
if (tickbox.Selected) if (tickbox.Selected)
{ {
@@ -766,6 +768,7 @@ namespace Barotrauma
}; };
ServerMessage.OnDeselected += (textBox, key) => ServerMessage.OnDeselected += (textBox, key) =>
{ {
if (GameMain.Client == null) { return; }
if (!textBox.Readonly) if (!textBox.Readonly)
{ {
GameMain.Client?.ServerSettings?.ClientAdminWrite(ServerSettings.NetFlags.Message); GameMain.Client?.ServerSettings?.ClientAdminWrite(ServerSettings.NetFlags.Message);
@@ -849,7 +852,7 @@ namespace Barotrauma
Selected = true, Selected = true,
OnSelected = (GUITickBox box) => OnSelected = (GUITickBox box) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, useRespawnShuttle: box.Selected); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, useRespawnShuttle: box.Selected);
return true; return true;
} }
}; };
@@ -868,7 +871,7 @@ namespace Barotrauma
{ {
OnSelected = (component, obj) => OnSelected = (component, obj) =>
{ {
GameMain.Client.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: true); GameMain.Client?.RequestSelectSub(component.Parent.GetChildIndex(component), isShuttle: true);
return true; return true;
} }
}; };
@@ -970,7 +973,7 @@ namespace Barotrauma
{ {
OnClicked = (_, __) => OnClicked = (_, __) =>
{ {
GameMain.Client.RequestSelectMode(ModeList.Content.GetChildIndex(ModeList.Content.GetChildByUserData(GameModePreset.Sandbox))); GameMain.Client?.RequestSelectMode(ModeList.Content.GetChildIndex(ModeList.Content.GetChildByUserData(GameModePreset.Sandbox)));
return true; return true;
} }
}; };
@@ -1026,7 +1029,7 @@ namespace Barotrauma
{ {
int missionTypeOr = tickbox.Selected ? (int)tickbox.UserData : (int)MissionType.None; int missionTypeOr = tickbox.Selected ? (int)tickbox.UserData : (int)MissionType.None;
int missionTypeAnd = (int)MissionType.All & (!tickbox.Selected ? (~(int)tickbox.UserData) : (int)MissionType.All); int missionTypeAnd = (int)MissionType.All & (!tickbox.Selected ? (~(int)tickbox.UserData) : (int)MissionType.All);
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, (int)missionTypeOr, (int)missionTypeAnd); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, (int)missionTypeOr, (int)missionTypeAnd);
return true; return true;
} }
}; };
@@ -1059,7 +1062,7 @@ namespace Barotrauma
SeedBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), seedLabel.RectTransform, Anchor.CenterRight)); SeedBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), seedLabel.RectTransform, Anchor.CenterRight));
SeedBox.OnDeselected += (textBox, key) => SeedBox.OnDeselected += (textBox, key) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.LevelSeed); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.LevelSeed);
}; };
clientDisabledElements.Add(SeedBox); clientDisabledElements.Add(SeedBox);
LevelSeed = ToolBox.RandomSeed(8); LevelSeed = ToolBox.RandomSeed(8);
@@ -1080,7 +1083,7 @@ namespace Barotrauma
ToolTip = TextManager.Get("leveldifficultyexplanation"), ToolTip = TextManager.Get("leveldifficultyexplanation"),
OnReleased = (scrollbar, value) => OnReleased = (scrollbar, value) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, levelDifficulty: scrollbar.BarScrollValue); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, levelDifficulty: scrollbar.BarScrollValue);
return true; return true;
} }
}; };
@@ -1112,8 +1115,7 @@ namespace Barotrauma
{ {
OnClicked = (button, obj) => OnClicked = (button, obj) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, traitorSetting: -1); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, traitorSetting: -1);
return true; return true;
} }
}; };
@@ -1124,8 +1126,7 @@ namespace Barotrauma
{ {
OnClicked = (button, obj) => OnClicked = (button, obj) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, traitorSetting: 1); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, traitorSetting: 1);
return true; return true;
} }
}; };
@@ -1143,7 +1144,7 @@ namespace Barotrauma
{ {
OnClicked = (button, obj) => OnClicked = (button, obj) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botCount: -1); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botCount: -1);
return true; return true;
} }
}; };
@@ -1153,7 +1154,7 @@ namespace Barotrauma
{ {
OnClicked = (button, obj) => OnClicked = (button, obj) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botCount: 1); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botCount: 1);
return true; return true;
} }
}; };
@@ -1169,7 +1170,7 @@ namespace Barotrauma
{ {
OnClicked = (button, obj) => OnClicked = (button, obj) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botSpawnMode: -1); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botSpawnMode: -1);
return true; return true;
} }
}; };
@@ -1179,7 +1180,7 @@ namespace Barotrauma
{ {
OnClicked = (button, obj) => OnClicked = (button, obj) =>
{ {
GameMain.Client.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botSpawnMode: 1); GameMain.Client?.ServerSettings.ClientAdminWrite(ServerSettings.NetFlags.Misc, botSpawnMode: 1);
return true; return true;
} }
}; };
@@ -3576,7 +3577,13 @@ namespace Barotrauma
return false; return false;
} }
public bool CheckIfCampaignSubMatches(SubmarineInfo serverSubmarine, string deliveryData) public enum SubmarineDeliveryData
{
Owned,
Campaign
}
public bool CheckIfCampaignSubMatches(SubmarineInfo serverSubmarine, SubmarineDeliveryData deliveryData)
{ {
if (GameMain.Client == null) return false; if (GameMain.Client == null) return false;
@@ -3630,11 +3637,11 @@ namespace Barotrauma
{ {
FailedSubInfo fileInfo = (FailedSubInfo)userdata; FailedSubInfo fileInfo = (FailedSubInfo)userdata;
if (deliveryData == "owned") //owned!!!! if (deliveryData == SubmarineDeliveryData.Owned)
{ {
FailedOwnedSubs.Add(fileInfo); FailedOwnedSubs.Add(fileInfo);
} }
else if (deliveryData == "campaign") else if (deliveryData == SubmarineDeliveryData.Campaign)
{ {
FailedCampaignSubs.Add(fileInfo); FailedCampaignSubs.Add(fileInfo);
} }
@@ -7,6 +7,7 @@ using Microsoft.Xna.Framework.Graphics;
using RestSharp; using RestSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
@@ -21,6 +22,11 @@ namespace Barotrauma
//how often the client is allowed to refresh servers //how often the client is allowed to refresh servers
private readonly TimeSpan AllowedRefreshInterval = new TimeSpan(0, 0, 3); private readonly TimeSpan AllowedRefreshInterval = new TimeSpan(0, 0, 3);
public ImmutableDictionary<UInt64, ContentPackage> ContentPackagesByWorkshopId { get; private set; }
= ImmutableDictionary<UInt64, ContentPackage>.Empty;
public ImmutableDictionary<string, ContentPackage> ContentPackagesByHash { get; private set; }
= ImmutableDictionary<string, ContentPackage>.Empty;
private GUIFrame menu; private GUIFrame menu;
private GUIListBox serverList; private GUIListBox serverList;
@@ -1011,6 +1017,17 @@ namespace Barotrauma
public override void Select() public override void Select()
{ {
base.Select(); base.Select();
ContentPackagesByWorkshopId = ContentPackage.AllPackages
.Select(p => new KeyValuePair<UInt64, ContentPackage>(p.SteamWorkshopId, p))
.Where(p => p.Key != 0)
.GroupBy(x => x.Key).Select(g => g.First())
.ToImmutableDictionary();
ContentPackagesByHash = ContentPackage.AllPackages
.Select(p => new KeyValuePair<string, ContentPackage>(p.MD5hash.Hash, p))
.GroupBy(x => x.Key).Select(g => g.First())
.ToImmutableDictionary();
SelectedTab = ServerListTab.All; SelectedTab = ServerListTab.All;
LoadServerFilters(GameMain.Config.ServerFilterElement); LoadServerFilters(GameMain.Config.ServerFilterElement);
if (GameSettings.ShowOffensiveServerPrompt) if (GameSettings.ShowOffensiveServerPrompt)
@@ -1039,6 +1056,8 @@ namespace Barotrauma
public override void Deselect() public override void Deselect()
{ {
ContentPackagesByWorkshopId = ImmutableDictionary<UInt64, ContentPackage>.Empty;
ContentPackagesByHash = ImmutableDictionary<string, ContentPackage>.Empty;
base.Deselect(); base.Deselect();
GameMain.Config.SaveNewPlayerConfig(); GameMain.Config.SaveNewPlayerConfig();
@@ -1491,7 +1510,7 @@ namespace Barotrauma
} }
TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(friend.Id), (task) => TaskPool.Add($"Get{avatarSize}AvatarAsync", avatarFunc(friend.Id), (task) =>
{ {
Steamworks.Data.Image? img = ((Task<Steamworks.Data.Image?>)task).Result; if (!task.TryGetResult(out Steamworks.Data.Image? img)) { return; }
if (!img.HasValue) { return; } if (!img.HasValue) { return; }
var avatarImage = img.Value; var avatarImage = img.Value;
@@ -2203,7 +2222,7 @@ namespace Barotrauma
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve Workshop item info (ID {entry.Id})"); TaskPool.PrintTaskExceptions(t, $"Failed to retrieve Workshop item info (ID {entry.Id})");
return; return;
} }
Steamworks.Ugc.Item? item = ((Task<Steamworks.Ugc.Item?>)t).Result; t.TryGetResult(out Steamworks.Ugc.Item? item);
if (!item.HasValue) if (!item.HasValue)
{ {
@@ -2313,7 +2332,7 @@ namespace Barotrauma
{ {
var info = obj.Item1; var info = obj.Item1;
var text = obj.Item2; var text = obj.Item2;
info.Ping = ((Task<int>)rtt).Result; info.PingChecked = true; rtt.TryGetResult(out info.Ping); info.PingChecked = true;
text.TextColor = GetPingTextColor(info.Ping); text.TextColor = GetPingTextColor(info.Ping);
text.Text = info.Ping > -1 ? info.Ping.ToString() : "?"; text.Text = info.Ping > -1 ? info.Ping.ToString() : "?";
lock (activePings) lock (activePings)
@@ -854,7 +854,7 @@ namespace Barotrauma
(var it, var lb) = tuple; (var it, var lb) = tuple;
if (lb.Content.FindChild(item)?.GetChildByUserData("previewimage") is GUIImage previewImage) if (lb.Content.FindChild(item)?.GetChildByUserData("previewimage") is GUIImage previewImage)
{ {
previewImage.Sprite = ((Task<Sprite>)task).Result; if (task.TryGetResult(out Sprite sprite)) { previewImage.Sprite = sprite; }
} }
else else
{ {
@@ -99,6 +99,10 @@ namespace Barotrauma
private GUIFrame previouslyUsedPanel; private GUIFrame previouslyUsedPanel;
private GUIListBox previouslyUsedList; private GUIListBox previouslyUsedList;
private GUIButton visibilityButton;
private GUIFrame layerPanel;
private GUIListBox layerList;
private GUIFrame undoBufferPanel; private GUIFrame undoBufferPanel;
private GUIFrame undoBufferDisclaimer; private GUIFrame undoBufferDisclaimer;
private GUIListBox undoBufferList; private GUIListBox undoBufferList;
@@ -234,6 +238,8 @@ namespace Barotrauma
public bool WiringMode => mode == Mode.Wiring; public bool WiringMode => mode == Mode.Wiring;
public static readonly Dictionary<string, bool> Layers = new Dictionary<string, bool>();
public SubEditorScreen() public SubEditorScreen()
{ {
cam = new Camera cam = new Camera
@@ -320,19 +326,34 @@ namespace Barotrauma
new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), paddedTopPanel.RectTransform), style: "VerticalLine"); new GUIFrame(new RectTransform(new Vector2(0.01f, 0.9f), paddedTopPanel.RectTransform), style: "VerticalLine");
var visibilityButton = new GUIButton(new RectTransform(new Vector2(0.9f, 0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "SetupVisibilityButton") visibilityButton = new GUIButton(new RectTransform(new Vector2(0.9f, 0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "SetupVisibilityButton")
{ {
ToolTip = TextManager.Get("SubEditorVisibilityButton") + '\n' + TextManager.Get("SubEditorVisibilityToolTip"), ToolTip = TextManager.Get("SubEditorVisibilityButton") + '\n' + TextManager.Get("SubEditorVisibilityToolTip"),
OnClicked = (btn, userData) => OnClicked = (btn, userData) =>
{ {
previouslyUsedPanel.Visible = false; previouslyUsedPanel.Visible = false;
undoBufferPanel.Visible = false; undoBufferPanel.Visible = false;
layerPanel.Visible = false;
showEntitiesPanel.Visible = !showEntitiesPanel.Visible; showEntitiesPanel.Visible = !showEntitiesPanel.Visible;
showEntitiesPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height); showEntitiesPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height);
return true; return true;
} }
}; };
new GUIButton(new RectTransform(new Vector2(0.9f, 0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "EditorLayerButton")
{
ToolTip = TextManager.Get("editor.layer.button") + '\n' + TextManager.Get("editor.layer.tooltip"),
OnClicked = (btn, userData) =>
{
previouslyUsedPanel.Visible = false;
showEntitiesPanel.Visible = false;
undoBufferPanel.Visible = false;
layerPanel.Visible = !layerPanel.Visible;
layerPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height);
return true;
}
};
var previouslyUsedButton = new GUIButton(new RectTransform(new Vector2(0.9f, 0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "RecentlyUsedButton") var previouslyUsedButton = new GUIButton(new RectTransform(new Vector2(0.9f, 0.9f), paddedTopPanel.RectTransform, scaleBasis: ScaleBasis.BothHeight), "", style: "RecentlyUsedButton")
{ {
ToolTip = TextManager.Get("PreviouslyUsedLabel"), ToolTip = TextManager.Get("PreviouslyUsedLabel"),
@@ -340,6 +361,7 @@ namespace Barotrauma
{ {
showEntitiesPanel.Visible = false; showEntitiesPanel.Visible = false;
undoBufferPanel.Visible = false; undoBufferPanel.Visible = false;
layerPanel.Visible = false;
previouslyUsedPanel.Visible = !previouslyUsedPanel.Visible; previouslyUsedPanel.Visible = !previouslyUsedPanel.Visible;
previouslyUsedPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height); previouslyUsedPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height);
return true; return true;
@@ -353,6 +375,7 @@ namespace Barotrauma
{ {
showEntitiesPanel.Visible = false; showEntitiesPanel.Visible = false;
previouslyUsedPanel.Visible = false; previouslyUsedPanel.Visible = false;
layerPanel.Visible = false;
undoBufferPanel.Visible = !undoBufferPanel.Visible; undoBufferPanel.Visible = !undoBufferPanel.Visible;
undoBufferPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height); undoBufferPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(btn.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height);
return true; return true;
@@ -484,14 +507,81 @@ namespace Barotrauma
//----------------------------------------------- //-----------------------------------------------
layerPanel = new GUIFrame(new RectTransform(new Vector2(0.175f, 0.4f), GUI.Canvas))
{
Visible = false
};
GUILayoutGroup layerGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f), layerPanel.RectTransform, anchor: Anchor.Center));
layerList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), layerGroup.RectTransform))
{
ScrollBarVisible = true,
AutoHideScrollBar = false,
OnSelected = (component, o) =>
{
if (!(o is string layer)) { return false; }
MapEntity.SelectedList.Clear();
foreach (MapEntity entity in MapEntity.mapEntityList.Where(me => !me.Removed && me.Layer == layer))
{
if (entity.IsSelected) { continue; }
MapEntity.SelectedList.Add(entity);
}
return true;
}
};
GUILayoutGroup layerButtonGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), layerGroup.RectTransform));
GUILayoutGroup layerButtonTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), layerButtonGroup.RectTransform), isHorizontal: true);
GUIButton layerAddButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), layerButtonTopGroup.RectTransform), text: TextManager.Get("editor.layer.newlayer"), style: "GUIButtonFreeScale")
{
OnClicked = (button, o) =>
{
CreateNewLayer(null, MapEntity.SelectedList.ToList());
return true;
}
};
GUIButton layerDeleteButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1f), layerButtonTopGroup.RectTransform), text: TextManager.Get("editor.layer.deletelayer"), style: "GUIButtonFreeScale")
{
OnClicked = (button, o) =>
{
if (layerList.SelectedData is string layer)
{
RenameLayer(layer, null);
}
return true;
}
};
GUIButton layerRenameButton = new GUIButton(new RectTransform(new Vector2(1f, 0.5f), layerButtonGroup.RectTransform), text: TextManager.Get("editor.layer.renamelayer"), style: "GUIButtonFreeScale")
{
OnClicked = (button, o) =>
{
if (layerList.SelectedData is string layer)
{
GUI.PromptTextInput(TextManager.Get("editor.layer.renamelayer"), layer, newName =>
{
RenameLayer(layer, newName);
});
}
return true;
}
};
Vector2 subPanelSize = new Vector2(0.925f, 0.9f);
undoBufferPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) }) undoBufferPanel = new GUIFrame(new RectTransform(new Vector2(0.15f, 0.2f), GUI.Canvas) { MinSize = new Point(200, 200) })
{ {
Visible = false Visible = false
}; };
Vector2 undoSize = new Vector2(0.925f, 0.9f); undoBufferList = new GUIListBox(new RectTransform(subPanelSize, undoBufferPanel.RectTransform, Anchor.Center))
undoBufferList = new GUIListBox(new RectTransform(undoSize, undoBufferPanel.RectTransform, Anchor.Center))
{ {
ScrollBarVisible = true, ScrollBarVisible = true,
OnSelected = (_, userData) => OnSelected = (_, userData) =>
@@ -522,7 +612,7 @@ namespace Barotrauma
} }
}; };
undoBufferDisclaimer = new GUIFrame(new RectTransform(undoSize, undoBufferPanel.RectTransform, Anchor.Center), style: null) undoBufferDisclaimer = new GUIFrame(new RectTransform(subPanelSize, undoBufferPanel.RectTransform, Anchor.Center), style: null)
{ {
Color = Color.Black, Color = Color.Black,
Visible = false Visible = false
@@ -1354,7 +1444,7 @@ namespace Barotrauma
TimeSpan timeInEditor = DateTime.Now - editorSelectedTime; TimeSpan timeInEditor = DateTime.Now - editorSelectedTime;
#if USE_STEAM #if USE_STEAM
Steam.SteamManager.IncrementStat("hoursineditor", (float)timeInEditor.TotalHours); SteamAchievementManager.IncrementStat("hoursineditor", (float)timeInEditor.TotalHours);
#endif #endif
GUI.ForceMouseOn(null); GUI.ForceMouseOn(null);
@@ -1400,6 +1490,7 @@ namespace Barotrauma
}); });
ClearFilter(); ClearFilter();
ClearLayers();
} }
private void CreateDummyCharacter() private void CreateDummyCharacter()
@@ -1752,11 +1843,11 @@ namespace Barotrauma
DebugConsole.ThrowError($"Saving the preview image of the submarine \"{Submarine.MainSub.Info.Name}\" failed.", e); DebugConsole.ThrowError($"Saving the preview image of the submarine \"{Submarine.MainSub.Info.Name}\" failed.", e);
savePreviewImage = false; savePreviewImage = false;
} }
Submarine.MainSub.SaveAs(savePath, savePreviewImage ? imgStream : null); Submarine.MainSub.TrySaveAs(savePath, savePreviewImage ? imgStream : null);
} }
else else
{ {
Submarine.MainSub.SaveAs(savePath); Submarine.MainSub.TrySaveAs(savePath);
} }
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
@@ -2890,6 +2981,8 @@ namespace Barotrauma
}; };
adjustLightsPrompt.Buttons[1].OnClicked += adjustLightsPrompt.Close; adjustLightsPrompt.Buttons[1].OnClicked += adjustLightsPrompt.Close;
} }
ReconstructLayers();
} }
private void TryDeleteSub(SubmarineInfo sub) private void TryDeleteSub(SubmarineInfo sub)
@@ -3075,6 +3168,8 @@ namespace Barotrauma
if (container == null || container.DrawInventory) { target = item; } if (container == null || container.DrawInventory) { target = item; }
} }
bool hasTargets = targets.Count > 0;
// Holding shift brings up special context menu options // Holding shift brings up special context menu options
if (PlayerInput.IsShiftDown()) if (PlayerInput.IsShiftDown())
{ {
@@ -3083,7 +3178,7 @@ namespace Barotrauma
new ContextMenuOption("SubEditor.ToggleTransparency", isEnabled: true, onSelected: () => TransparentWiringMode = !TransparentWiringMode), new ContextMenuOption("SubEditor.ToggleTransparency", isEnabled: true, onSelected: () => TransparentWiringMode = !TransparentWiringMode),
new ContextMenuOption("SubEditor.ToggleGrid", isEnabled: true, onSelected: () => ShouldDrawGrid = !ShouldDrawGrid), new ContextMenuOption("SubEditor.ToggleGrid", isEnabled: true, onSelected: () => ShouldDrawGrid = !ShouldDrawGrid),
new ContextMenuOption("SubEditor.PasteAssembly", isEnabled: true, () => PasteAssembly()), new ContextMenuOption("SubEditor.PasteAssembly", isEnabled: true, () => PasteAssembly()),
new ContextMenuOption("Editor.SelectSame", isEnabled: targets.Count > 0, onSelected: delegate new ContextMenuOption("Editor.SelectSame", isEnabled: hasTargets, onSelected: delegate
{ {
bool doorGapSelected = targets.Any(t => t is Gap gap && gap.ConnectedDoor != null); bool doorGapSelected = targets.Any(t => t is Gap gap && gap.ConnectedDoor != null);
foreach (MapEntity match in MapEntity.mapEntityList.Where(e => e.prefab != null && targets.Any(t => t.prefab?.Identifier == e.prefab.Identifier) && !MapEntity.SelectedList.Contains(e))) foreach (MapEntity match in MapEntity.mapEntityList.Where(e => e.prefab != null && targets.Any(t => t.prefab?.Identifier == e.prefab.Identifier) && !MapEntity.SelectedList.Contains(e)))
@@ -3115,12 +3210,44 @@ namespace Barotrauma
} }
else else
{ {
List<ContextMenuOption> availableLayerOptions = new List<ContextMenuOption>
{
new ContextMenuOption("editor.layer.nolayer", true, onSelected: () => { MoveToLayer(null, targets); })
};
availableLayerOptions.AddRange(Layers.Select(layer => new ContextMenuOption(layer.Key, true, onSelected: () => { MoveToLayer(layer.Key, targets); })));
ContextMenuOption[] layerOptions =
{
new ContextMenuOption("editor.layer.movetolayer", isEnabled: hasTargets, availableLayerOptions.ToArray()),
new ContextMenuOption("editor.layer.createlayer", isEnabled: hasTargets, onSelected: () => { CreateNewLayer(null, targets); }),
new ContextMenuOption("editor.layer.selectall", isEnabled: hasTargets, onSelected: () =>
{
foreach (MapEntity match in MapEntity.mapEntityList.Where(e => targets.Any(t => !string.IsNullOrWhiteSpace(t.Layer) && t.Layer == e.Layer && !MapEntity.SelectedList.Contains(e))))
{
if (MapEntity.SelectedList.Contains(match)) { continue; }
MapEntity.SelectedList.Add(match);
}
}),
new ContextMenuOption("editor.layer.openlayermenu", isEnabled: true, onSelected: () =>
{
if (visibilityButton is null) { return; }
previouslyUsedPanel.Visible = false;
undoBufferPanel.Visible = false;
showEntitiesPanel.Visible = false;
layerPanel.Visible = !layerPanel.Visible;
layerPanel.RectTransform.AbsoluteOffset = new Point(Math.Max(Math.Max(visibilityButton.Rect.X, entityCountPanel.Rect.Right), saveAssemblyFrame.Rect.Right), TopPanel.Rect.Height);
})
};
GUIContextMenu.CreateContextMenu( GUIContextMenu.CreateContextMenu(
new ContextMenuOption("label.openlabel", isEnabled: target != null, onSelected: () => OpenItem(target)), new ContextMenuOption("label.openlabel", isEnabled: target != null, onSelected: () => OpenItem(target)),
new ContextMenuOption("editor.cut", isEnabled: targets.Count > 0, onSelected: () => MapEntity.Cut(targets)), new ContextMenuOption("editor.layer", isEnabled: hasTargets, layerOptions),
new ContextMenuOption("editor.copytoclipboard", isEnabled: targets.Count > 0, onSelected: () => MapEntity.Copy(targets)), new ContextMenuOption("editor.cut", isEnabled: hasTargets, onSelected: () => MapEntity.Cut(targets)),
new ContextMenuOption("editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))), new ContextMenuOption("editor.copytoclipboard", isEnabled: hasTargets, onSelected: () => MapEntity.Copy(targets)),
new ContextMenuOption("delete", isEnabled: targets.Count > 0, onSelected: delegate new ContextMenuOption("editor.paste", isEnabled: MapEntity.CopiedList.Any(), onSelected: () => MapEntity.Paste(cam.ScreenToWorld(PlayerInput.MousePosition))),
new ContextMenuOption("delete", isEnabled: hasTargets, onSelected: delegate
{ {
StoreCommand(new AddOrDeleteCommand(targets, true)); StoreCommand(new AddOrDeleteCommand(targets, true));
foreach (var me in targets) foreach (var me in targets)
@@ -3131,6 +3258,76 @@ namespace Barotrauma
} }
} }
private void MoveToLayer(string layer, List<MapEntity> content)
{
layer ??= string.Empty;
foreach (MapEntity entity in content)
{
entity.Layer = layer;
}
}
private void CreateNewLayer(string name, List<MapEntity> content)
{
if (string.IsNullOrWhiteSpace(name))
{
name = TextManager.Get("editor.layer.newlayer");
}
string incrementedName = name;
for (int i = 1; Layers.ContainsKey(incrementedName); i++)
{
incrementedName = $"{name} ({i})";
}
name = incrementedName;
if (content != null)
{
MoveToLayer(name, content);
}
Layers.Add(name, true);
UpdateLayerPanel();
}
private void RenameLayer(string original, string newName)
{
Layers.Remove(original);
foreach (MapEntity entity in MapEntity.mapEntityList.Where(entity => entity.Layer == original))
{
entity.Layer = newName ?? string.Empty;
}
if (!string.IsNullOrWhiteSpace(newName))
{
Layers.TryAdd(newName, true);
}
UpdateLayerPanel();
}
private void ReconstructLayers()
{
ClearLayers();
foreach (MapEntity entity in MapEntity.mapEntityList)
{
if (!string.IsNullOrWhiteSpace(entity.Layer))
{
Layers.TryAdd(entity.Layer, true);
}
}
UpdateLayerPanel();
}
private void ClearLayers()
{
Layers.Clear();
UpdateLayerPanel();
}
private void PasteAssembly(string text = null, Vector2? pos = null) private void PasteAssembly(string text = null, Vector2? pos = null)
{ {
pos ??= cam.ScreenToWorld(PlayerInput.MousePosition); pos ??= cam.ScreenToWorld(PlayerInput.MousePosition);
@@ -4044,6 +4241,7 @@ namespace Barotrauma
previouslyUsedPanel.AddToGUIUpdateList(); previouslyUsedPanel.AddToGUIUpdateList();
undoBufferPanel.AddToGUIUpdateList(); undoBufferPanel.AddToGUIUpdateList();
entityCountPanel.AddToGUIUpdateList(); entityCountPanel.AddToGUIUpdateList();
layerPanel.AddToGUIUpdateList();
TopPanel.AddToGUIUpdateList(); TopPanel.AddToGUIUpdateList();
if (WiringMode) if (WiringMode)
@@ -4147,9 +4345,55 @@ namespace Barotrauma
GameMain.SubEditorScreen.UpdateUndoHistoryPanel(); GameMain.SubEditorScreen.UpdateUndoHistoryPanel();
} }
private void UpdateLayerPanel()
{
if (layerPanel is null || layerList is null) { return; }
layerList.Content.ClearChildren();
layerList.Deselect();
foreach (var (layer, isVisible) in Layers)
{
GUIFrame parent = new GUIFrame(new RectTransform(new Vector2(1f, 0.1f), layerList.Content.RectTransform), style: "ListBoxElement")
{
UserData = layer
};
GUILayoutGroup layerGroup = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
GUITickBox layerVisibleButton = new GUITickBox(new RectTransform(Vector2.One, layerGroup.RectTransform, scaleBasis: ScaleBasis.BothHeight), string.Empty)
{
Selected = isVisible,
OnSelected = box =>
{
if (!Layers.TryGetValue(layer, out bool _))
{
UpdateLayerPanel();
return false;
}
Layers[layer] = box.Selected;
return true;
}
};
layerGroup.Recalculate();
new GUITextBlock(new RectTransform(new Vector2(1.0f - layerVisibleButton.RectTransform.RelativeSize.X, 1f), layerGroup.RectTransform), layer, textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
layerGroup.Recalculate();
}
layerList.RecalculateChildren();
}
public void UpdateUndoHistoryPanel() public void UpdateUndoHistoryPanel()
{ {
if (undoBufferPanel == null) { return; } if (undoBufferPanel is null) { return; }
undoBufferDisclaimer.Visible = mode == Mode.Wiring; undoBufferDisclaimer.Visible = mode == Mode.Wiring;
@@ -4203,7 +4447,7 @@ namespace Barotrauma
public override void Update(double deltaTime) public override void Update(double deltaTime)
{ {
SkipInventorySlotUpdate = false; SkipInventorySlotUpdate = false;
ImageManager.Update((float) deltaTime); ImageManager.Update((float)deltaTime);
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y) if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y)
{ {
@@ -4720,6 +4964,11 @@ namespace Barotrauma
if (!saveAssemblyFrame.Rect.Contains(PlayerInput.MousePosition) && dummyCharacter?.SelectedConstruction == null && !WiringMode && GUI.MouseOn == null) if (!saveAssemblyFrame.Rect.Contains(PlayerInput.MousePosition) && dummyCharacter?.SelectedConstruction == null && !WiringMode && GUI.MouseOn == null)
{ {
if (layerList is { Visible: true } && GUI.KeyboardDispatcher.Subscriber == layerList)
{
GUI.KeyboardDispatcher.Subscriber = null;
}
MapEntity.UpdateSelecting(cam); MapEntity.UpdateSelecting(cam);
} }
@@ -4998,7 +5247,7 @@ namespace Barotrauma
var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle; var prevScissorRect = GameMain.Instance.GraphicsDevice.ScissorRectangle;
Rectangle subDimensions = Submarine.MainSub.CalculateDimensions(false); Rectangle subDimensions = Submarine.MainSub.CalculateDimensions(onlyHulls: false);
Vector2 viewPos = subDimensions.Center.ToVector2(); Vector2 viewPos = subDimensions.Center.ToVector2();
float scale = Math.Min(width / (float)subDimensions.Width, height / (float)subDimensions.Height); float scale = Math.Min(width / (float)subDimensions.Width, height / (float)subDimensions.Height);
@@ -5087,5 +5336,19 @@ namespace Barotrauma
public static bool IsSubEditor() => Screen.Selected is SubEditorScreen && !Submarine.Unloading; public static bool IsSubEditor() => Screen.Selected is SubEditorScreen && !Submarine.Unloading;
public static bool IsWiringMode() => Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode && !Submarine.Unloading; public static bool IsWiringMode() => Screen.Selected == GameMain.SubEditorScreen && GameMain.SubEditorScreen.WiringMode && !Submarine.Unloading;
public static bool IsLayerVisible(MapEntity entity)
{
if (!IsSubEditor()) { return true; }
if (string.IsNullOrWhiteSpace(entity.Layer)) { return true; }
if (!Layers.TryGetValue(entity.Layer, out bool isVisible))
{
Layers.TryAdd(entity.Layer, true);
return true;
}
return isVisible;
}
} }
} }
@@ -23,6 +23,7 @@ namespace Barotrauma
private Submarine? submarine; private Submarine? submarine;
private Character? dummyCharacter; private Character? dummyCharacter;
public static Effect BlueprintEffect; public static Effect BlueprintEffect;
private GUIFrame container;
private TabMenu tabMenu; private TabMenu tabMenu;
@@ -42,21 +43,25 @@ namespace Barotrauma
return true; return true;
} }
}; };
} }
public override void Select() public override void Select()
{ {
base.Select(); base.Select();
container = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
var tab = new GUIFrame(new RectTransform(Vector2.One, container.RectTransform), color: Color.Black * 0.9f);
MedicalClinicUI clinic = new MedicalClinicUI(new MedicalClinic(null!), tab);
clinic.RequestLatestPending();
if (dummyCharacter is { Removed: false }) if (dummyCharacter is { Removed: false })
{ {
dummyCharacter?.Remove(); dummyCharacter?.Remove();
} }
dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false); // dummyCharacter = Character.Create(CharacterPrefab.HumanSpeciesName, Vector2.Zero, "", id: Entity.DummyID, hasAi: false);
dummyCharacter.Info.Job = new Job(JobPrefab.Prefabs.Where(jp => TalentTree.JobTalentTrees.ContainsKey(jp.Identifier)).GetRandom()); // dummyCharacter.Info.Job = new Job(JobPrefab.Prefabs.Where(jp => TalentTree.JobTalentTrees.ContainsKey(jp.Identifier)).GetRandom());
dummyCharacter.Info.Name = "Galldren"; // dummyCharacter.Info.Name = "Galldren";
dummyCharacter.Inventory.CreateSlots(); // dummyCharacter.Inventory.CreateSlots();
Character.Controlled = dummyCharacter; Character.Controlled = dummyCharacter;
GameMain.World.ProcessChanges(); GameMain.World.ProcessChanges();
@@ -67,9 +72,9 @@ namespace Barotrauma
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
{ {
Frame.AddToGUIUpdateList(); Frame.AddToGUIUpdateList();
CharacterHUD.AddToGUIUpdateList(dummyCharacter); container.AddToGUIUpdateList();
dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList(); // CharacterHUD.AddToGUIUpdateList(dummyCharacter);
tabMenu.AddToGUIUpdateList(); // dummyCharacter?.SelectedConstruction?.AddToGUIUpdateList();
} }
public override void Update(double deltaTime) public override void Update(double deltaTime)
@@ -92,12 +97,12 @@ namespace Barotrauma
graphics.Clear(BackgroundColor); graphics.Clear(BackgroundColor);
spriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: Cam.Transform); spriteBatch.Begin(SpriteSortMode.BackToFront, transformMatrix: Cam.Transform);
miniMapItem?.Draw(spriteBatch, false); // miniMapItem?.Draw(spriteBatch, false);
if (dummyCharacter is { } dummy) // if (dummyCharacter is { } dummy)
{ // {
dummyCharacter.DrawFront(spriteBatch, Cam); // dummyCharacter.DrawFront(spriteBatch, Cam);
dummyCharacter.Draw(spriteBatch, Cam); // dummyCharacter.Draw(spriteBatch, Cam);
} // }
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState);
@@ -567,8 +567,6 @@ namespace Barotrauma
{ {
if (SetPropertyValue(property, entity, numInput.FloatValue)) if (SetPropertyValue(property, entity, numInput.FloatValue))
{ {
// This causes stack overflow. What's the purpose of it?
//numInput.FloatValue = (float)property.GetValue(entity);
TrySendNetworkUpdate(entity, property); TrySendNetworkUpdate(entity, property);
} }
}; };
@@ -674,7 +672,16 @@ namespace Barotrauma
Text = value, Text = value,
OverflowClip = true OverflowClip = true
}; };
HashSet<MapEntity> editedEntities = new HashSet<MapEntity>();
propertyBox.OnTextChanged += (textBox, text) =>
{
foreach (var entity in MapEntity.SelectedList)
{
editedEntities.Add(entity);
}
return true;
};
propertyBox.OnDeselected += (textBox, keys) => OnApply(textBox); propertyBox.OnDeselected += (textBox, keys) => OnApply(textBox);
propertyBox.OnEnterPressed += (box, text) => OnApply(box); propertyBox.OnEnterPressed += (box, text) => OnApply(box);
refresh += () => refresh += () =>
@@ -684,12 +691,25 @@ namespace Barotrauma
bool OnApply(GUITextBox textBox) bool OnApply(GUITextBox textBox)
{ {
List<MapEntity> prevSelected = MapEntity.SelectedList.ToList();
//reselect the entities that were selected during editing
//otherwise multi-editing won't work when we deselect the entities with unapplied changes in the textbox
foreach (var entity in editedEntities)
{
MapEntity.SelectedList.Add(entity);
}
if (SetPropertyValue(property, entity, textBox.Text)) if (SetPropertyValue(property, entity, textBox.Text))
{ {
TrySendNetworkUpdate(entity, property); TrySendNetworkUpdate(entity, property);
textBox.Text = (string) property.GetValue(entity); textBox.Text = (string) property.GetValue(entity);
textBox.Flash(GUI.Style.Green, flashDuration: 1f); textBox.Flash(GUI.Style.Green, flashDuration: 1f);
} }
//restore the entities that were selected before applying
MapEntity.SelectedList.Clear();
foreach (var entity in prevSelected)
{
MapEntity.SelectedList.Add(entity);
}
return true; return true;
} }
@@ -14,9 +14,10 @@ namespace Barotrauma
{ {
private List<ParticleEmitter> particleEmitters; private List<ParticleEmitter> particleEmitters;
private static HashSet<StatusEffect> ActiveLoopingSounds = new HashSet<StatusEffect>(); private readonly static HashSet<StatusEffect> ActiveLoopingSounds = new HashSet<StatusEffect>();
private static double LastMuffleCheckTime; private static double LastMuffleCheckTime;
private readonly List<RoundSound> sounds = new List<RoundSound>(); private readonly List<RoundSound> sounds = new List<RoundSound>();
public IEnumerable<RoundSound> Sounds { get { return sounds; } }
private SoundSelectionMode soundSelectionMode; private SoundSelectionMode soundSelectionMode;
private SoundChannel soundChannel; private SoundChannel soundChannel;
private Entity soundEmitter; private Entity soundEmitter;
@@ -53,7 +54,7 @@ namespace Barotrauma
} }
} }
partial void ApplyProjSpecific(float deltaTime, Entity entity, IEnumerable<ISerializableEntity> targets, Hull hull, Vector2 worldPosition, bool playSound) partial void ApplyProjSpecific(float deltaTime, Entity entity, IReadOnlyList<ISerializableEntity> targets, Hull hull, Vector2 worldPosition, bool playSound)
{ {
if (playSound) if (playSound)
{ {
@@ -84,7 +85,14 @@ namespace Barotrauma
} }
else else
{ {
targetLimb = targets.FirstOrDefault(t => t is Limb) as Limb; for (int i = 0; i < targets.Count; i++)
{
if (targets[i] is Limb limb)
{
targetLimb = limb;
break;
}
}
} }
if (targetLimb != null && !targetLimb.Removed) if (targetLimb != null && !targetLimb.Removed)
{ {
@@ -147,10 +155,6 @@ namespace Barotrauma
GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace.CleanupStackTrace(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return; return;
} }
if (selectedSound.Sound.Disposed)
{
Submarine.ReloadRoundSound(selectedSound);
}
soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, worldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: hull, ignoreMuffling: selectedSound.IgnoreMuffling); soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, worldPosition, selectedSound.Volume, selectedSound.Range, hullGuess: hull, ignoreMuffling: selectedSound.IgnoreMuffling);
ignoreMuffling = selectedSound.IgnoreMuffling; ignoreMuffling = selectedSound.IgnoreMuffling;
if (soundChannel != null) { soundChannel.Looping = loopSound; } if (soundChannel != null) { soundChannel.Looping = loopSound; }
@@ -122,7 +122,7 @@ namespace Barotrauma
{ {
int width = 4096; int height = 4096; int width = 4096; int height = 4096;
Rectangle subDimensions = sub.CalculateDimensions(false); Rectangle subDimensions = sub.Borders;
Vector2 viewPos = subDimensions.Center.ToVector2(); Vector2 viewPos = subDimensions.Center.ToVector2();
float scale = Math.Min(width / (float)subDimensions.Width, height / (float)subDimensions.Height); float scale = Math.Min(width / (float)subDimensions.Width, height / (float)subDimensions.Height);
@@ -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>0.15.23.0</Version> <Version>0.16.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
+1 -1
View File
@@ -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>0.15.23.0</Version> <Version>0.16.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma</Product> <Product>Barotrauma</Product>
<Version>0.15.23.0</Version> <Version>0.16.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>Barotrauma</AssemblyName> <AssemblyName>Barotrauma</AssemblyName>
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.15.23.0</Version> <Version>0.16.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
+1 -1
View File
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.15.23.0</Version> <Version>0.16.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -61,9 +61,9 @@ namespace Barotrauma
GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateMoney }); GameMain.NetworkMember.CreateEntityEvent(this, new object[] { NetEntityEvent.Type.UpdateMoney });
} }
partial void OnTalentGiven(string talentIdentifier) partial void OnTalentGiven(TalentPrefab talentPrefab)
{ {
GameServer.Log($"{GameServer.CharacterLogName(this)} has gained the talent '{talentIdentifier}'", ServerLog.MessageType.Talent); GameServer.Log($"{GameServer.CharacterLogName(this)} has gained the talent '{talentPrefab.DisplayName}'", ServerLog.MessageType.Talent);
} }
} }
} }
@@ -288,15 +288,18 @@ namespace Barotrauma
{ {
UInt32 talentIdentifier = msg.ReadUInt32(); UInt32 talentIdentifier = msg.ReadUInt32();
var prefab = TalentPrefab.TalentPrefabs.Find(p => p.UIntIdentifier == talentIdentifier); var prefab = TalentPrefab.TalentPrefabs.Find(p => p.UIntIdentifier == talentIdentifier);
if (prefab != null) { talentSelection.Add(prefab.Identifier); } if (prefab == null) { continue; }
if (TalentTree.IsViableTalentForCharacter(this, prefab.Identifier, talentSelection))
{
GiveTalent(prefab.Identifier);
talentSelection.Add(prefab.Identifier);
}
} }
talentSelection = TalentTree.CheckTalentSelection(this, talentSelection); if (talentSelection.Count != talentCount)
foreach (string talent in talentSelection)
{ {
GiveTalent(talent); DebugConsole.AddWarning($"Failed to unlock talents: the amount of unlocked talents doesn't match (client: {talentCount}, server: {talentSelection.Count})");
} }
break; break;
} }
break; break;
@@ -17,6 +17,9 @@ namespace Barotrauma
{ {
public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version; public static readonly Version Version = Assembly.GetEntryAssembly().GetName().Version;
public static bool IsSingleplayer => NetworkMember == null;
public static bool IsMultiplayer => NetworkMember != null;
private static World world; private static World world;
public static World World public static World World
@@ -53,7 +53,7 @@ namespace Barotrauma
foreach (var activeOrder in ActiveOrders) foreach (var activeOrder in ActiveOrders)
{ {
if (!(activeOrder?.First is Order order) || activeOrder.Second.HasValue) { continue; } if (!(activeOrder?.First is Order order) || activeOrder.Second.HasValue) { continue; }
OrderChatMessage.WriteOrder(msg, order, null, order.TargetSpatialEntity, null, 0, order.WallSectionIndex); OrderChatMessage.WriteOrder(msg, order, targetCharacter: null, order.TargetSpatialEntity, orderOption: null, orderPriority: 0, order.WallSectionIndex, isNewOrder: true);
bool hasOrderGiver = order.OrderGiver != null; bool hasOrderGiver = order.OrderGiver != null;
msg.Write(hasOrderGiver); msg.Write(hasOrderGiver);
if (hasOrderGiver) if (hasOrderGiver)
@@ -348,7 +348,6 @@ namespace Barotrauma
} }
} }
} }
UpdateCampaignSubs();
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
PendingSubmarineSwitch = null; PendingSubmarineSwitch = null;
@@ -399,44 +398,12 @@ namespace Barotrauma
Map.OnMissionsSelected += (loc, mission) => { LastUpdateID++; }; Map.OnMissionsSelected += (loc, mission) => { LastUpdateID++; };
Reputation.OnAnyReputationValueChanged += () => { LastUpdateID++; }; Reputation.OnAnyReputationValueChanged += () => { LastUpdateID++; };
UpdateCampaignSubs();
//increment save ID so clients know they're lacking the most up-to-date save file //increment save ID so clients know they're lacking the most up-to-date save file
LastSaveID++; LastSaveID++;
} }
public static void UpdateCampaignSubs()
{
bool isSubmarineVisible(SubmarineInfo s)
=> !GameMain.Server.ServerSettings.HiddenSubs.Any(h
=> s.Name.Equals(h, StringComparison.OrdinalIgnoreCase));
List<SubmarineInfo> availableSubs =
SubmarineInfo.SavedSubmarines
.Where(s =>
s.IsCampaignCompatible
&& isSubmarineVisible(s))
.ToList();
if (!availableSubs.Any())
{
//None of the available subs were marked as campaign-compatible, just include all visible subs
availableSubs.AddRange(
SubmarineInfo.SavedSubmarines
.Where(isSubmarineVisible));
}
if (!availableSubs.Any())
{
//No subs are visible at all! Just make the selected one available
availableSubs.Add(GameMain.NetLobbyScreen.SelectedSub);
}
GameMain.NetLobbyScreen.CampaignSubmarines = availableSubs;
}
public bool CanPurchaseSub(SubmarineInfo info) public bool CanPurchaseSub(SubmarineInfo info)
=> info.Price <= Money && GameMain.NetLobbyScreen.CampaignSubmarines.Contains(info); => info.Price <= Money && GetCampaignSubs().Contains(info);
public void DiscardClientCharacterData(Client client) public void DiscardClientCharacterData(Client client)
{ {
@@ -0,0 +1,191 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
internal partial class MedicalClinic
{
private enum RateLimitResult
{
OK,
LimitReached
}
private struct RateLimitInfo
{
public int Requests;
public const int MaxRequests = 5;
public DateTimeOffset Expiry;
}
private readonly Dictionary<Client, RateLimitInfo> rateLimits = new Dictionary<Client, RateLimitInfo>();
public void ServerRead(IReadMessage inc, Client sender)
{
NetworkHeader header = (NetworkHeader)inc.ReadByte();
switch (header)
{
case NetworkHeader.REQUEST_AFFLICTIONS:
ProcessRequestedAfflictions(inc, sender);
break;
case NetworkHeader.REQUEST_PENDING:
ProcessRequestedPending(sender);
break;
case NetworkHeader.ADD_PENDING:
ProcessNewAddition(inc, sender);
break;
case NetworkHeader.REMOVE_PENDING:
ProcessNewRemoval(inc, sender);
break;
case NetworkHeader.HEAL_PENDING:
ProcessHealing(sender);
break;
case NetworkHeader.CLEAR_PENDING:
ProcessClearing(sender);
break;
}
}
private void ProcessNewAddition(IReadMessage inc, Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
NetCrewMember newCrewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
InsertPendingCrewMember(newCrewMember);
ServerSend(newCrewMember, NetworkHeader.ADD_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
private void ProcessNewRemoval(IReadMessage inc, Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
NetRemovedAffliction removed = INetSerializableStruct.Read<NetRemovedAffliction>(inc);
RemovePendingAffliction(removed.CrewMember, removed.Affliction);
ServerSend(removed, NetworkHeader.REMOVE_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
private void ProcessRequestedPending(Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
INetSerializableStruct writeCrewMember = new NetPendingCrew
{
CrewMembers = PendingHeals.ToArray()
};
ServerSend(writeCrewMember, NetworkHeader.REQUEST_PENDING, DeliveryMethod.Reliable, targetClient: client);
}
private void ProcessHealing(Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
HealRequestResult result = HealAllPending();
ServerSend(new NetHealRequest { Result = result }, NetworkHeader.HEAL_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
private void ProcessClearing(Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
if (!PendingHeals.Any()) { return; }
ClearPendingHeals();
ServerSend(null, NetworkHeader.CLEAR_PENDING, DeliveryMethod.Reliable, reponseClient: client);
}
private void ProcessRequestedAfflictions(IReadMessage inc, Client client)
{
if (CheckRateLimit(client) == RateLimitResult.LimitReached) { return; }
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
CharacterInfo? foundInfo = crewMember.FindCharacterInfo(GetCrewCharacters());
NetAffliction[] pendingAfflictions = Array.Empty<NetAffliction>();
int infoId = 0;
if (foundInfo is { Character: { CharacterHealth: { } health } })
{
pendingAfflictions = GetAllAfflictions(health);
infoId = foundInfo.GetIdentifierUsingOriginalName();
}
INetSerializableStruct writeCrewMember = new NetCrewMember
{
CharacterInfoID = infoId,
Afflictions = pendingAfflictions
};
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()
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.MEDICAL);
return msg;
}
private void ServerSend(INetSerializableStruct? netStruct, NetworkHeader header, DeliveryMethod deliveryMethod, Client? targetClient = null, Client? reponseClient = null)
{
if (targetClient is null)
{
foreach (Client c in GameMain.Server.ConnectedClients)
{
SendToClient(c);
}
return;
}
SendToClient(targetClient);
void SendToClient(Client c)
{
MessageFlag flag = MessageFlag.Announce;
if (reponseClient != null && reponseClient == c)
{
flag = MessageFlag.Response;
}
IWriteMessage msg = StartSending();
msg.Write((byte)header);
msg.Write((byte)flag);
netStruct?.Write(msg);
GameMain.Server.ServerPeer.Send(msg, c.Connection, deliveryMethod);
}
}
}
}
@@ -50,14 +50,14 @@ namespace Barotrauma.Items.Components
newSteeringInput = new Vector2(msg.ReadSingle(), msg.ReadSingle()); newSteeringInput = new Vector2(msg.ReadSingle(), msg.ReadSingle());
} }
if (!item.CanClientAccess(c)) return; if (!item.CanClientAccess(c)) { return; }
user = c.Character; user = c.Character;
AutoPilot = autoPilot; AutoPilot = autoPilot;
if (dockingButtonClicked) if (dockingButtonClicked)
{ {
item.SendSignal("1", "toggle_docking"); item.SendSignal(new Signal("1", sender: c.Character), "toggle_docking");
GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), true }); GameMain.Server.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ComponentState, item.GetComponentIndex(this), true });
} }
@@ -46,6 +46,7 @@ namespace Barotrauma.Items.Components
msg.Write(DeteriorateAlways); msg.Write(DeteriorateAlways);
msg.Write(tinkeringDuration); msg.Write(tinkeringDuration);
msg.Write(tinkeringStrength); msg.Write(tinkeringStrength);
msg.Write(tinkeringPowersDevices);
msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID); msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID);
msg.WriteRangedInteger((int)currentFixerAction, 0, 2); msg.WriteRangedInteger((int)currentFixerAction, 0, 2);
} }
@@ -8,7 +8,7 @@ namespace Barotrauma.Items.Components
{ {
int signalIndex = msg.ReadRangedInteger(0, Signals.Length - 1); int signalIndex = msg.ReadRangedInteger(0, Signals.Length - 1);
if (!item.CanClientAccess(c)) { return; } if (!item.CanClientAccess(c)) { return; }
if (!SendSignal(signalIndex)) { return; } if (!SendSignal(signalIndex, c.Character)) { return; }
GameServer.Log($"{GameServer.CharacterLogName(c.Character)} sent a signal \"{Signals[signalIndex]}\" from {item.Name}", ServerLog.MessageType.ItemInteraction); GameServer.Log($"{GameServer.CharacterLogName(c.Character)} sent a signal \"{Signals[signalIndex]}\" from {item.Name}", ServerLog.MessageType.ItemInteraction);
item.CreateServerEvent(this, new object[] { signalIndex }); item.CreateServerEvent(this, new object[] { signalIndex });
} }
@@ -218,33 +218,6 @@ namespace Barotrauma.Networking
if (shuttle != null) { GameMain.NetLobbyScreen.SelectedShuttle = shuttle; } if (shuttle != null) { GameMain.NetLobbyScreen.SelectedShuttle = shuttle; }
} }
List<SubmarineInfo> campaignSubs = new List<SubmarineInfo>();
if (serverSettings.CampaignSubmarines != null && serverSettings.CampaignSubmarines.Length > 0)
{
string[] submarines = serverSettings.CampaignSubmarines.Split(ServerSettings.SubmarineSeparatorChar);
for (int i = 0; i < submarines.Length; i++)
{
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == submarines[i]);
if (subInfo != null && subInfo.IsCampaignCompatible)
{
campaignSubs.Add(subInfo);
}
}
}
else
{
// Add vanilla submarines by default
for (int i = 0; i < SubmarineInfo.SavedSubmarines.Count(); i++)
{
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.ElementAt(i);
if (subInfo.IsVanillaSubmarine() && subInfo.IsCampaignCompatible)
{
campaignSubs.Add(SubmarineInfo.SavedSubmarines.ElementAt(i));
}
}
}
GameMain.NetLobbyScreen.CampaignSubmarines = campaignSubs;
started = true; started = true;
GameAnalyticsManager.AddDesignEvent("GameServer:Start"); GameAnalyticsManager.AddDesignEvent("GameServer:Start");
@@ -835,6 +808,9 @@ namespace Barotrauma.Networking
case ClientPacketHeader.CREW: case ClientPacketHeader.CREW:
ReadCrewMessage(inc, connectedClient); ReadCrewMessage(inc, connectedClient);
break; break;
case ClientPacketHeader.MEDICAL:
ReadMedicalMessage(inc, connectedClient);
break;
case ClientPacketHeader.READY_CHECK: case ClientPacketHeader.READY_CHECK:
ReadyCheck.ServerRead(inc, connectedClient); ReadyCheck.ServerRead(inc, connectedClient);
break; break;
@@ -879,16 +855,16 @@ namespace Barotrauma.Networking
} }
else if (entity is Character character) else if (entity is Character character)
{ {
errorStr = "Missing character " + character.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; errorStr = $"Missing character {character.Name} (event id {eventID}, entity id {entityID}).";
errorStrNoName = "Missing character " + character.SpeciesName + "(event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; errorStrNoName = $"Missing character {character.SpeciesName} (event id {eventID}, entity id {entityID}).";
} }
else if (entity is Item item) else if (entity is Item item)
{ {
errorStr = errorStrNoName = "Missing item " + item.Name + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; errorStr = errorStrNoName = $"Missing item {item.Name}, sub: {item.Submarine?.Info?.Name ?? "none"} (event id {eventID}, entity id {entityID}).";
} }
else else
{ {
errorStr = errorStrNoName = "Missing entity " + entity.ToString() + " (event id " + eventID.ToString() + ", entity id " + entityID.ToString() + ")."; errorStr = errorStrNoName = $"Missing entity {entity}, sub: {entity.Submarine?.Info?.Name ?? "none"} (event id {eventID}, entity id {entityID}).";
} }
break; break;
} }
@@ -1234,6 +1210,14 @@ namespace Barotrauma.Networking
} }
} }
private void ReadMedicalMessage(IReadMessage inc, Client sender)
{
if (GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign)
{
mpCampaign.MedicalClinic.ServerRead(inc, sender);
}
}
private void ReadReadyToSpawnMessage(IReadMessage inc, Client sender) private void ReadReadyToSpawnMessage(IReadMessage inc, Client sender)
{ {
sender.SpectateOnly = inc.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection); sender.SpectateOnly = inc.ReadBoolean() && (serverSettings.AllowSpectating || sender.Connection == OwnerConnection);
@@ -1861,24 +1845,6 @@ namespace Barotrauma.Networking
outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name); outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.Name);
outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.ToString()); outmsg.Write(GameMain.NetLobbyScreen.SelectedShuttle.MD5Hash.ToString());
List<int> campaignSubIndices = new List<int>();
if (GameMain.NetLobbyScreen.SelectedMode == GameModePreset.MultiPlayerCampaign)
{
IReadOnlyList<SubmarineInfo> subList = GameMain.NetLobbyScreen.GetSubList();
for (int i = 0; i < subList.Count; i++)
{
if (GameMain.NetLobbyScreen.CampaignSubmarines.Contains(subList[i]))
{
campaignSubIndices.Add(i);
}
}
}
outmsg.Write((UInt16)campaignSubIndices.Count);
foreach (int campaignSubIndex in campaignSubIndices)
{
outmsg.Write((UInt16)campaignSubIndex);
}
outmsg.Write(serverSettings.Voting.AllowSubVoting); outmsg.Write(serverSettings.Voting.AllowSubVoting);
outmsg.Write(serverSettings.Voting.AllowModeVoting); outmsg.Write(serverSettings.Voting.AllowModeVoting);
@@ -2166,7 +2132,6 @@ namespace Barotrauma.Networking
else else
{ {
SendStartMessage(roundStartSeed, GameMain.NetLobbyScreen.LevelSeed, GameMain.GameSession, connectedClients, false); SendStartMessage(roundStartSeed, GameMain.NetLobbyScreen.LevelSeed, GameMain.GameSession, connectedClients, false);
GameMain.GameSession.StartRound(GameMain.NetLobbyScreen.LevelSeed, serverSettings.SelectedLevelDifficulty); GameMain.GameSession.StartRound(GameMain.NetLobbyScreen.LevelSeed, serverSettings.SelectedLevelDifficulty);
Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage); Log("Game mode: " + selectedMode.Name, ServerLog.MessageType.ServerMessage);
Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage); Log("Submarine: " + selectedSub.Name, ServerLog.MessageType.ServerMessage);
@@ -2186,8 +2151,7 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
MissionMode missionMode = GameMain.GameSession.GameMode as MissionMode; bool missionAllowRespawn = !(GameMain.GameSession.GameMode is MissionMode missionMode) || !missionMode.Missions.Any(m => !m.AllowRespawn);
bool missionAllowRespawn = missionMode == null || !missionMode.Missions.Any(m => !m.AllowRespawn);
bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost; bool isOutpost = campaign != null && campaign.NextLevel?.Type == LevelData.LevelType.Outpost;
if (serverSettings.AllowRespawn && missionAllowRespawn) if (serverSettings.AllowRespawn && missionAllowRespawn)
@@ -2263,7 +2227,7 @@ namespace Barotrauma.Networking
characterInfos.Add(client.CharacterInfo); characterInfos.Add(client.CharacterInfo);
if (client.CharacterInfo.Job == null || client.CharacterInfo.Job.Prefab != client.AssignedJob.First) if (client.CharacterInfo.Job == null || client.CharacterInfo.Job.Prefab != client.AssignedJob.First)
{ {
client.CharacterInfo.Job = new Job(client.AssignedJob.First, client.AssignedJob.Second); client.CharacterInfo.Job = new Job(client.AssignedJob.First, Rand.RandSync.Unsynced, client.AssignedJob.Second);
} }
} }
@@ -2475,7 +2439,7 @@ namespace Barotrauma.Networking
msg.Write(serverSettings.LockAllDefaultWires); msg.Write(serverSettings.LockAllDefaultWires);
msg.Write(serverSettings.AllowRagdollButton); msg.Write(serverSettings.AllowRagdollButton);
msg.Write(serverSettings.AllowLinkingWifiToChat); msg.Write(serverSettings.AllowLinkingWifiToChat);
msg.Write(serverSettings.UseRespawnShuttle); msg.Write(serverSettings.UseRespawnShuttle || (gameStarted && respawnManager.UsingShuttle));
msg.Write((byte)serverSettings.LosMode); msg.Write((byte)serverSettings.LosMode);
msg.Write(includesFinalize); msg.WritePadBits(); msg.Write(includesFinalize); msg.WritePadBits();
@@ -3372,14 +3336,40 @@ namespace Barotrauma.Networking
} }
} }
public void IncrementStat(Character character, string achievementIdentifier, int amount)
{
achievementIdentifier = achievementIdentifier.ToLowerInvariant();
foreach (Client client in connectedClients)
{
if (client.Character == character)
{
IncrementStat(client, achievementIdentifier, amount);
return;
}
}
}
public void GiveAchievement(Client client, string achievementIdentifier) public void GiveAchievement(Client client, string achievementIdentifier)
{ {
if (client.GivenAchievements.Contains(achievementIdentifier)) return; if (client.GivenAchievements.Contains(achievementIdentifier)) { return; }
client.GivenAchievements.Add(achievementIdentifier); client.GivenAchievements.Add(achievementIdentifier);
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.ACHIEVEMENT); msg.Write((byte)ServerPacketHeader.ACHIEVEMENT);
msg.Write(achievementIdentifier); msg.Write(achievementIdentifier);
msg.Write(0);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
}
public void IncrementStat(Client client, string achievementIdentifier, int amount)
{
if (client.GivenAchievements.Contains(achievementIdentifier)) { return; }
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ServerPacketHeader.ACHIEVEMENT);
msg.Write(achievementIdentifier);
msg.Write(amount);
serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable); serverPeer.Send(msg, client.Connection, DeliveryMethod.Reliable);
} }
@@ -3733,7 +3723,7 @@ namespace Barotrauma.Networking
if (assignedPlayerCount[jobPrefab] >= jobPrefab.MaxNumber) { continue; } if (assignedPlayerCount[jobPrefab] >= jobPrefab.MaxNumber) { continue; }
var variant = Rand.Range(0, jobPrefab.Variants, Rand.RandSync.Server); var variant = Rand.Range(0, jobPrefab.Variants, Rand.RandSync.Server);
unassignedBots[0].Job = new Job(jobPrefab, variant); unassignedBots[0].Job = new Job(jobPrefab, Rand.RandSync.Server, variant);
assignedPlayerCount[jobPrefab]++; assignedPlayerCount[jobPrefab]++;
unassignedBots.Remove(unassignedBots[0]); unassignedBots.Remove(unassignedBots[0]);
canAssign = true; canAssign = true;
@@ -3756,7 +3746,7 @@ namespace Barotrauma.Networking
{ {
var job = remainingJobs.GetRandom(); var job = remainingJobs.GetRandom();
var variant = Rand.Range(0, job.Variants); var variant = Rand.Range(0, job.Variants);
c.Job = new Job(job, variant); c.Job = new Job(job, Rand.RandSync.Unsynced, variant);
assignedPlayerCount[c.Job.Prefab]++; assignedPlayerCount[c.Job.Prefab]++;
} }
} }
@@ -3842,16 +3832,6 @@ namespace Barotrauma.Networking
if (GameMain.NetLobbyScreen.SelectedSub != null) { serverSettings.SelectedSubmarine = GameMain.NetLobbyScreen.SelectedSub.Name; } if (GameMain.NetLobbyScreen.SelectedSub != null) { serverSettings.SelectedSubmarine = GameMain.NetLobbyScreen.SelectedSub.Name; }
if (GameMain.NetLobbyScreen.SelectedShuttle != null) { serverSettings.SelectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle.Name; } if (GameMain.NetLobbyScreen.SelectedShuttle != null) { serverSettings.SelectedShuttle = GameMain.NetLobbyScreen.SelectedShuttle.Name; }
if (GameMain.NetLobbyScreen.CampaignSubmarines != null)
{
string submarinesString = string.Empty;
for (int i = 0; i < GameMain.NetLobbyScreen.CampaignSubmarines.Count; i++)
{
submarinesString += GameMain.NetLobbyScreen.CampaignSubmarines[i].Name + ServerSettings.SubmarineSeparatorChar;
}
submarinesString.Trim(ServerSettings.SubmarineSeparatorChar);
serverSettings.CampaignSubmarines = submarinesString;
}
serverSettings.SaveSettings(); serverSettings.SaveSettings();
@@ -351,7 +351,7 @@ namespace Barotrauma.Networking
{ {
if (campaign?.GetClientCharacterData(c) == null || c.CharacterInfo.Job == null) if (campaign?.GetClientCharacterData(c) == null || c.CharacterInfo.Job == null)
{ {
c.CharacterInfo.Job = new Job(c.AssignedJob.First, c.AssignedJob.Second); c.CharacterInfo.Job = new Job(c.AssignedJob.First, Rand.RandSync.Unsynced, c.AssignedJob.Second);
} }
} }
@@ -11,6 +11,21 @@ namespace Barotrauma.Networking
{ {
partial class ServerSettings partial class ServerSettings
{ {
partial class NetPropertyData
{
private object lastSyncedValue;
public UInt16 LastUpdateID { get; private set; }
public void SyncValue()
{
if (!PropEquals(lastSyncedValue, Value))
{
LastUpdateID = (UInt16)(GameMain.NetLobbyScreen.LastUpdateID);
lastSyncedValue = Value;
}
}
}
public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml"; public static readonly string ClientPermissionsFile = "Data" + Path.DirectorySeparatorChar + "clientpermissions.xml";
public static readonly char SubmarineSeparatorChar = '|'; public static readonly char SubmarineSeparatorChar = '|';
@@ -35,20 +50,25 @@ namespace Barotrauma.Networking
LoadClientPermissions(); LoadClientPermissions();
} }
private void WriteNetProperties(IWriteMessage outMsg) private void WriteNetProperties(IWriteMessage outMsg, Client c)
{ {
outMsg.Write((UInt16)netProperties.Keys.Count);
foreach (UInt32 key in netProperties.Keys) foreach (UInt32 key in netProperties.Keys)
{ {
outMsg.Write(key); var property = netProperties[key];
netProperties[key].Write(outMsg); property.SyncValue();
if (property.LastUpdateID > c.LastRecvLobbyUpdate)
{
outMsg.Write(key);
netProperties[key].Write(outMsg);
}
} }
outMsg.Write((UInt32)0);
} }
public void ServerAdminWrite(IWriteMessage outMsg, Client c) public void ServerAdminWrite(IWriteMessage outMsg, Client c)
{ {
c.LastSentServerSettingsUpdate = LastPropertyUpdateId; c.LastSentServerSettingsUpdate = LastPropertyUpdateId;
WriteNetProperties(outMsg); WriteNetProperties(outMsg, c);
WriteMonsterEnabled(outMsg); WriteMonsterEnabled(outMsg);
BanList.ServerAdminWrite(outMsg, c); BanList.ServerAdminWrite(outMsg, c);
Whitelist.ServerAdminWrite(outMsg, c); Whitelist.ServerAdminWrite(outMsg, c);
@@ -79,8 +99,11 @@ namespace Barotrauma.Networking
{ {
WriteExtraCargo(outMsg); WriteExtraCargo(outMsg);
} }
WriteHiddenSubs(outMsg); if (requiredFlags.HasFlag(NetFlags.HiddenSubs))
{
WriteHiddenSubs(outMsg);
}
if (c.HasPermission(Networking.ClientPermissions.ManageSettings) if (c.HasPermission(Networking.ClientPermissions.ManageSettings)
&& !NetIdUtils.IdMoreRecentOrMatches(c.LastRecvServerSettingsUpdate, LastPropertyUpdateId)) && !NetIdUtils.IdMoreRecentOrMatches(c.LastRecvServerSettingsUpdate, LastPropertyUpdateId))
@@ -164,6 +187,7 @@ namespace Barotrauma.Networking
{ {
ReadHiddenSubs(incMsg); ReadHiddenSubs(incMsg);
changed |= true; changed |= true;
UpdateFlag(NetFlags.HiddenSubs);
} }
if (flags.HasFlag(NetFlags.Misc)) if (flags.HasFlag(NetFlags.Misc))
@@ -32,26 +32,6 @@ namespace Barotrauma
set { selectedShuttle = value; lastUpdateID++; } set { selectedShuttle = value; lastUpdateID++; }
} }
[Obsolete("TODO: this list shouldn't exist, the client should just use the visible subs list instead")]
public List<SubmarineInfo> CampaignSubmarines
{
get
{
return campaignSubmarines;
}
set
{
campaignSubmarines = value;
lastUpdateID++;
if (GameMain.NetworkMember?.ServerSettings != null)
{
GameMain.NetworkMember.ServerSettings.ServerDetailsChanged = true;
}
}
}
private List<SubmarineInfo> campaignSubmarines;
public GameModePreset[] GameModes { get; } public GameModePreset[] GameModes { get; }
private int selectedModeIndex; private int selectedModeIndex;
@@ -6,7 +6,7 @@
<RootNamespace>Barotrauma</RootNamespace> <RootNamespace>Barotrauma</RootNamespace>
<Authors>FakeFish, Undertow Games</Authors> <Authors>FakeFish, Undertow Games</Authors>
<Product>Barotrauma Dedicated Server</Product> <Product>Barotrauma Dedicated Server</Product>
<Version>0.15.23.0</Version> <Version>0.16.0.0</Version>
<Copyright>Copyright © FakeFish 2018-2020</Copyright> <Copyright>Copyright © FakeFish 2018-2020</Copyright>
<Platforms>AnyCPU;x64</Platforms> <Platforms>AnyCPU;x64</Platforms>
<AssemblyName>DedicatedServer</AssemblyName> <AssemblyName>DedicatedServer</AssemblyName>
@@ -144,6 +144,7 @@
<Submarine file="Submarines/Venture.sub" /> <Submarine file="Submarines/Venture.sub" />
<Submarine file="Submarines/Azimuth.sub" /> <Submarine file="Submarines/Azimuth.sub" />
<Submarine file="Submarines/R-29.sub" /> <Submarine file="Submarines/R-29.sub" />
<Submarine file="Submarines/Barsuk.sub" />
<Text file="Content/Texts/English/EnglishVanilla.xml" /> <Text file="Content/Texts/English/EnglishVanilla.xml" />
<Text file="Content/Texts/German/GermanVanilla.xml" /> <Text file="Content/Texts/German/GermanVanilla.xml" />
<Text file="Content/Texts/French/FrenchVanilla.xml" /> <Text file="Content/Texts/French/FrenchVanilla.xml" />
+2 -2
View File
@@ -2,8 +2,8 @@
http://www.barotraumagame.com http://www.barotraumagame.com
© 2018-2020 FakeFish Ltd. All rights reserved. © 2018-2022 FakeFish Ltd. All rights reserved.
© 2019-2020 Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved. © 2019-2022 Daedalic Entertainment GmbH. The Daedalic logo is a trademark of Daedalic Entertainment GmbH, Germany. All rights reserved.
Privacy policy: http://privacypolicy.daedalic.com Privacy policy: http://privacypolicy.daedalic.com
See the wiki for more detailed info and instructions: See the wiki for more detailed info and instructions:
@@ -150,7 +150,7 @@ namespace Barotrauma
private CoroutineHandle disableTailCoroutine; private CoroutineHandle disableTailCoroutine;
private readonly IEnumerable<Body> myBodies; private readonly List<Body> myBodies;
public LatchOntoAI LatchOntoAI { get; private set; } public LatchOntoAI LatchOntoAI { get; private set; }
public SwarmBehavior SwarmBehavior { get; private set; } public SwarmBehavior SwarmBehavior { get; private set; }
@@ -306,7 +306,8 @@ namespace Barotrauma
requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize); requiredHoleCount = (int)Math.Ceiling(ConvertUnits.ToDisplayUnits(colliderWidth) / Structure.WallSectionSize);
myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody); myBodies = Character.AnimController.Limbs.Select(l => l.body.FarseerBody).ToList();
myBodies.Add(Character.AnimController.Collider.FarseerBody);
} }
private CharacterParams.AIParams _aiParams; private CharacterParams.AIParams _aiParams;
@@ -1837,7 +1838,7 @@ namespace Barotrauma
if (!attack.IsValidTarget(target)) { return false; } if (!attack.IsValidTarget(target)) { return false; }
if (target is ISerializableEntity se && target is Character) if (target is ISerializableEntity se && target is Character)
{ {
if (attack.Conditionals.Any(c => !c.Matches(se))) { return false; } if (attack.Conditionals.Any(c => !c.TargetSelf && !c.Matches(se))) { return false; }
} }
if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { return false; } if (attack.Conditionals.Any(c => c.TargetSelf && !c.Matches(Character))) { return false; }
if (attack.Ranged) if (attack.Ranged)
@@ -2182,10 +2183,22 @@ namespace Barotrauma
float margin = MathHelper.PiOver4 * distanceFactor; float margin = MathHelper.PiOver4 * distanceFactor;
if (angle < margin) if (angle < margin)
{ {
var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel; var collisionCategories = Physics.CollisionCharacter | Physics.CollisionWall | Physics.CollisionLevel;
var pickedBody = Submarine.PickBody(weapon.SimPosition, target.SimPosition, myBodies, collisionCategories, allowInsideFixture: true); var pickedBody = Submarine.PickBody(weapon.SimPosition, Character.GetRelativeSimPosition(target), myBodies, collisionCategories, allowInsideFixture: true);
if (pickedBody != null) if (pickedBody != null)
{ {
if (target is MapEntity)
{
if (pickedBody.UserData is Submarine sub && sub == target.Submarine)
{
return true;
}
else if (target == pickedBody.UserData)
{
return true;
}
}
Character t = null; Character t = null;
if (pickedBody.UserData is Character c) if (pickedBody.UserData is Character c)
{ {
@@ -832,6 +832,8 @@ namespace Barotrauma
if (container == null) { return 0; } if (container == null) { return 0; }
if (!container.HasAccess(character)) { return 0; } if (!container.HasAccess(character)) { return 0; }
if (!container.Inventory.CanBePut(containableItem)) { return 0; } if (!container.Inventory.CanBePut(containableItem)) { return 0; }
var rootContainer = container.Item.GetRootContainer();
if (rootContainer?.GetComponent<Fabricator>() != null || rootContainer?.GetComponent<Fabricator>() != null) { return 0; }
if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined)) if (container.ShouldBeContained(containableItem, out bool isRestrictionsDefined))
{ {
if (isRestrictionsDefined) if (isRestrictionsDefined)
@@ -1088,6 +1090,7 @@ namespace Barotrauma
private void RespondToAttack(Character attacker, AttackResult attackResult) private void RespondToAttack(Character attacker, AttackResult attackResult)
{ {
float minorDamageThreshold = 10;
float healAmount = 0.0f; float healAmount = 0.0f;
if (attacker != null) if (attacker != null)
{ {
@@ -1135,6 +1138,7 @@ namespace Barotrauma
// Don't react to attackers that are outside of the sub (e.g. AoE attacks) // Don't react to attackers that are outside of the sub (e.g. AoE attacks)
return; return;
} }
bool isAttackerInfected = false;
bool isAttackerFightingEnemy = false; bool isAttackerFightingEnemy = false;
if (IsFriendly(attacker)) if (IsFriendly(attacker))
{ {
@@ -1155,10 +1159,14 @@ namespace Barotrauma
} }
else else
{ {
isAttackerInfected = attacker.CharacterHealth.GetAfflictionStrength("alieninfection") > 0;
// Inform other NPCs // Inform other NPCs
if (cumulativeDamage > 1 || totalDamage >= 10) if (isAttackerInfected || cumulativeDamage > 1 || totalDamage >= minorDamageThreshold)
{ {
InformOtherNPCs(cumulativeDamage); if (GameMain.IsMultiplayer || !attacker.IsPlayer || Character.TeamID != attacker.TeamID)
{
InformOtherNPCs(cumulativeDamage);
}
} }
if (Character.IsBot) if (Character.IsBot)
{ {
@@ -1167,7 +1175,7 @@ namespace Barotrauma
{ {
if (Character.IsSecurity) if (Character.IsSecurity)
{ {
if (attacker.TeamID != Character.TeamID && cumulativeDamage > 1 || cumulativeDamage > 10) if (attacker.TeamID != Character.TeamID && cumulativeDamage > 1 || cumulativeDamage > minorDamageThreshold)
{ {
Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.50f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 30.0f); Character.Speak(TextManager.Get("dialogattackedbyfriendlysecurityarrest"), null, 0.50f, "attackedbyfriendlysecurityarrest", minDurationBetweenSimilar: 30.0f);
} }
@@ -1181,26 +1189,8 @@ namespace Barotrauma
Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f); Character.Speak(TextManager.Get("DialogAttackedByFriendly"), null, 0.50f, "attackedbyfriendly", minDurationBetweenSimilar: 30.0f);
} }
} }
if (cumulativeDamage > 1 && attacker.TeamID != Character.TeamID) // If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay.
{ AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage), attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
// If the attacker is using a low damage and high frequency weapon like a repair tool, we shouldn't use any delay.
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage), attacker, delay: realDamage > 1 ? GetReactionTime() : 0);
}
else
{
// Don't react to minor (accidental) dmg done by characters that are in the same team
if (cumulativeDamage < 10)
{
if (!Character.IsSecurity && cumulativeDamage > 1)
{
AddCombatObjective(AIObjectiveCombat.CombatMode.Retreat, attacker);
}
}
else
{
AddCombatObjective(DetermineCombatMode(Character, cumulativeDamage, dmgThreshold: 50), attacker, GetReactionTime() * 2);
}
}
} }
if (!isAttackerFightingEnemy) if (!isAttackerFightingEnemy)
{ {
@@ -1242,13 +1232,13 @@ namespace Barotrauma
continue; continue;
} }
} }
var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing, dmgThreshold: attacker.TeamID == Character.TeamID ? 50 : 10); var combatMode = DetermineCombatMode(otherCharacter, cumulativeDamage, isWitnessing);
float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced); float delay = isWitnessing ? GetReactionTime() : Rand.Range(2.0f, 5.0f, Rand.RandSync.Unsynced);
otherHumanAI.AddCombatObjective(combatMode, attacker, delay); otherHumanAI.AddCombatObjective(combatMode, attacker, delay);
} }
} }
AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage, bool isWitnessing = false, float dmgThreshold = 10, bool allowOffensive = true) AIObjectiveCombat.CombatMode DetermineCombatMode(Character c, float cumulativeDamage, bool isWitnessing = false)
{ {
if (!IsFriendly(attacker)) if (!IsFriendly(attacker))
{ {
@@ -1268,6 +1258,17 @@ namespace Barotrauma
} }
else else
{ {
float dmgThreshold = attacker.TeamID == Character.TeamID ? 50 : minorDamageThreshold;
if (isAttackerInfected)
{
cumulativeDamage = 100;
}
if (GameMain.IsSingleplayer && attacker.IsPlayer && Character.TeamID == attacker.TeamID)
{
// Bots in the player team never act aggressively in single player when attacked by the player
dmgThreshold = minorDamageThreshold;
return cumulativeDamage > dmgThreshold ? AIObjectiveCombat.CombatMode.Retreat : AIObjectiveCombat.CombatMode.None;
}
if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine)) if (Character.Submarine == null || !Character.Submarine.GetConnectedSubs().Contains(attacker.Submarine))
{ {
// Outside or attacked from an unconnected submarine -> don't react. // Outside or attacked from an unconnected submarine -> don't react.
@@ -1279,17 +1280,17 @@ namespace Barotrauma
isAttackerFightingEnemy = true; isAttackerFightingEnemy = true;
return AIObjectiveCombat.CombatMode.None; return AIObjectiveCombat.CombatMode.None;
} }
else if (isWitnessing && Character.CombatAction != null && !c.IsSecurity) if (isWitnessing && Character.CombatAction != null && !c.IsSecurity)
{ {
return Character.CombatAction.WitnessReaction; return Character.CombatAction.WitnessReaction;
} }
else if (attacker.IsPlayer && FindInstigator() is Character instigator) if (attacker.IsPlayer && FindInstigator() is Character instigator)
{ {
// The guards don't react when the player there's an instigator around // The guards don't react to player's aggressions when there's an instigator around
isAttackerFightingEnemy = true; isAttackerFightingEnemy = true;
return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (instigator.CombatAction != null ? instigator.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat); return c.IsSecurity ? AIObjectiveCombat.CombatMode.None : (instigator.CombatAction != null ? instigator.CombatAction.WitnessReaction : AIObjectiveCombat.CombatMode.Retreat);
} }
else if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable)) if (attacker.TeamID == CharacterTeamType.FriendlyNPC && !(attacker.AIController.IsMentallyUnstable || attacker.AIController.IsMentallyUnstable))
{ {
if (c.IsSecurity) if (c.IsSecurity)
{ {
@@ -1307,15 +1308,11 @@ namespace Barotrauma
// Already targeting the attacker -> treat as a more serious threat. // Already targeting the attacker -> treat as a more serious threat.
cumulativeDamage *= 2; cumulativeDamage *= 2;
} }
if (attackResult.Afflictions != null && attackResult.Afflictions.Any(a => a is AfflictionHusk))
{
cumulativeDamage = 100;
}
if (cumulativeDamage > dmgThreshold) if (cumulativeDamage > dmgThreshold)
{ {
if (c.IsSecurity) if (c.IsSecurity)
{ {
return c.IsSecurity && allowOffensive ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Arrest; return c.IsSecurity ? AIObjectiveCombat.CombatMode.Offensive : AIObjectiveCombat.CombatMode.Arrest;
} }
else else
{ {
@@ -1838,7 +1835,7 @@ namespace Barotrauma
bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>(); bool ignoreFire = objectiveManager.CurrentOrder is AIObjectiveExtinguishFires extinguishOrder && extinguishOrder.Priority > 0 || objectiveManager.HasActiveObjective<AIObjectiveExtinguishFire>();
bool ignoreWater = HasDivingSuit(character); bool ignoreWater = HasDivingSuit(character);
bool ignoreOxygen = ignoreWater || HasDivingMask(character); bool ignoreOxygen = ignoreWater || HasDivingMask(character);
bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.Objectives.Any(o => o is AIObjectiveFightIntruders); bool ignoreEnemies = ObjectiveManager.IsCurrentOrder<AIObjectiveFightIntruders>() || ObjectiveManager.GetActiveObjectives<AIObjectiveFightIntruders>().Any();
float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies); float safety = CalculateHullSafety(hull, visibleHulls, character, ignoreWater, ignoreOxygen, ignoreFire, ignoreEnemies);
if (isCurrentHull) if (isCurrentHull)
{ {
@@ -51,19 +51,13 @@ namespace Barotrauma
private set; private set;
} }
/// <summary>
/// Returns true if the current or the next node is in ladders.
/// </summary>
public bool InLadders =>
currentPath != null && currentPath.CurrentNode != null &&
(currentPath.CurrentNode.Ladders != null && currentPath.CurrentNode.Ladders.Item.IsInteractable(character) ||
(currentPath.NextNode != null && currentPath.NextNode.Ladders != null && currentPath.NextNode.Ladders.Item.IsInteractable(character)));
/// <summary> /// <summary>
/// Returns true if any node in the path is in stairs /// Returns true if any node in the path is in stairs
/// </summary> /// </summary>
public bool InStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null); public bool InStairs => currentPath != null && currentPath.Nodes.Any(n => n.Stairs != null);
public bool IsCurrentNodeLadder => currentPath?.CurrentNode?.Ladders != null && currentPath.CurrentNode.Ladders.Item.IsInteractable(character);
public bool IsNextNodeLadder => GetNextLadder() != null; public bool IsNextNodeLadder => GetNextLadder() != null;
public bool IsNextLadderSameAsCurrent public bool IsNextLadderSameAsCurrent
@@ -99,14 +93,24 @@ namespace Barotrauma
base.Update(speed); base.Update(speed);
float step = 1.0f / 60.0f; float step = 1.0f / 60.0f;
checkDoorsTimer -= step; checkDoorsTimer -= step;
buttonPressTimer -= step; if (lastDoor.door == null || !lastDoor.shouldBeOpen || lastDoor.door.IsOpen)
{
buttonPressTimer = 0;
}
else
{
buttonPressTimer -= step;
}
findPathTimer -= step; findPathTimer -= step;
} }
public void SetPath(SteeringPath path) public void SetPath(SteeringPath path)
{ {
currentPath = path; currentPath = path;
if (path.Nodes.Any()) currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition; if (path.Nodes.Any())
{
currentTarget = path.Nodes[path.Nodes.Count - 1].SimPosition;
}
findPathTimer = Math.Min(findPathTimer, 1.0f); findPathTimer = Math.Min(findPathTimer, 1.0f);
IsPathDirty = false; IsPathDirty = false;
} }
@@ -124,15 +128,9 @@ namespace Barotrauma
public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true) public void SteeringSeek(Vector2 target, float weight, float minGapWidth = 0, Func<PathNode, bool> startNodeFilter = null, Func<PathNode, bool> endNodeFilter = null, Func<PathNode, bool> nodeFilter = null, bool checkVisiblity = true)
{ {
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.state && !lastDoor.door.IsOpen) // Have to use a variable here or resetting doesn't work.
{ Vector2 addition = CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again. steering += addition;
Reset();
}
else
{
steering += CalculateSteeringSeek(target, weight, minGapWidth, startNodeFilter, endNodeFilter, nodeFilter, checkVisiblity);
}
} }
/// <summary> /// <summary>
@@ -328,7 +326,13 @@ namespace Barotrauma
{ {
CheckDoorsInPath(); CheckDoorsInPath();
doorsChecked = true; doorsChecked = true;
} }
if (buttonPressTimer > 0 && lastDoor.door != null && lastDoor.shouldBeOpen && !lastDoor.door.IsOpen)
{
// We have pressed the button and are waiting for the door to open -> Hold still until we can press the button again.
Reset();
return Vector2.Zero;
}
Vector2 pos = host.WorldPosition; Vector2 pos = host.WorldPosition;
bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater; bool isDiving = character.AnimController.InWater && character.AnimController.HeadInWater;
// Only humanoids can climb ladders // Only humanoids can climb ladders
@@ -378,7 +382,7 @@ namespace Barotrauma
//at the same height as the waypoint //at the same height as the waypoint
if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < (collider.height / 2 + collider.radius) * 1.25f) if (Math.Abs(collider.SimPosition.Y - currentPath.CurrentNode.SimPosition.Y) < (collider.height / 2 + collider.radius) * 1.25f)
{ {
float heightFromFloor = character.AnimController.GetColliderBottom().Y - character.AnimController.FloorY; float heightFromFloor = character.AnimController.GetHeightFromFloor();
if (heightFromFloor <= 0.0f) if (heightFromFloor <= 0.0f)
{ {
diff.Y = Math.Max(diff.Y, 100); diff.Y = Math.Max(diff.Y, 100);
@@ -516,7 +520,7 @@ namespace Barotrauma
return ConvertUnits.ToDisplayUnits(Math.Max(colliderSize.X, colliderSize.Y)); return ConvertUnits.ToDisplayUnits(Math.Max(colliderSize.X, colliderSize.Y));
} }
private (Door door, bool state) lastDoor; private (Door door, bool shouldBeOpen) lastDoor;
private float GetDoorCheckTime() private float GetDoorCheckTime()
{ {
if (steering.LengthSquared() > 0) if (steering.LengthSquared() > 0)
@@ -539,7 +543,6 @@ namespace Barotrauma
WayPoint nextWaypoint = null; WayPoint nextWaypoint = null;
Door door = null; Door door = null;
bool shouldBeOpen = false; bool shouldBeOpen = false;
if (currentPath.Nodes.Count == 1) if (currentPath.Nodes.Count == 1)
{ {
door = currentPath.Nodes.First().ConnectedDoor; door = currentPath.Nodes.First().ConnectedDoor;
@@ -645,7 +648,7 @@ namespace Barotrauma
}); });
if (canAccess) if (canAccess)
{ {
bool pressButton = buttonPressTimer <= 0 || lastDoor.door != door || lastDoor.state != shouldBeOpen; bool pressButton = buttonPressTimer <= 0 || lastDoor.door != door || lastDoor.shouldBeOpen != shouldBeOpen;
if (door.HasIntegratedButtons) if (door.HasIntegratedButtons)
{ {
if (pressButton && character.CanSeeTarget(door.Item)) if (pressButton && character.CanSeeTarget(door.Item))
@@ -653,7 +656,7 @@ namespace Barotrauma
if (door.Item.TryInteract(character, forceSelectKey: true)) if (door.Item.TryInteract(character, forceSelectKey: true))
{ {
lastDoor = (door, shouldBeOpen); lastDoor = (door, shouldBeOpen);
buttonPressTimer = buttonPressCooldown; buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0;
} }
else else
{ {
@@ -671,7 +674,7 @@ namespace Barotrauma
if (closestButton.Item.TryInteract(character, forceSelectKey: true)) if (closestButton.Item.TryInteract(character, forceSelectKey: true))
{ {
lastDoor = (door, shouldBeOpen); lastDoor = (door, shouldBeOpen);
buttonPressTimer = buttonPressCooldown; buttonPressTimer = shouldBeOpen ? buttonPressCooldown : 0;
} }
else else
{ {
@@ -697,7 +700,6 @@ namespace Barotrauma
// The button is on the wrong side of the door or a wall // The button is on the wrong side of the door or a wall
currentPath.Unreachable = true; currentPath.Unreachable = true;
} }
lastDoor = (null, false);
return; return;
} }
} }
@@ -16,7 +16,6 @@ namespace Barotrauma
public virtual bool IgnoreUnsafeHulls => false; public virtual bool IgnoreUnsafeHulls => false;
public virtual bool AbandonWhenCannotCompleteSubjectives => true; public virtual bool AbandonWhenCannotCompleteSubjectives => true;
public virtual bool AllowSubObjectiveSorting => false; public virtual bool AllowSubObjectiveSorting => false;
public virtual bool ForceOrderPriority => true;
public virtual bool PrioritizeIfSubObjectivesActive => false; public virtual bool PrioritizeIfSubObjectivesActive => false;
/// <summary> /// <summary>
@@ -85,11 +85,12 @@ namespace Barotrauma
bool equip = item.GetComponent<Holdable>() != null || bool equip = item.GetComponent<Holdable>() != null ||
item.AllowedSlots.Any(s => s != InvSlotType.Any) && item.AllowedSlots.Any(s => s != InvSlotType.Any) &&
item.AllowedSlots.None(s => item.AllowedSlots.None(s =>
s == InvSlotType.Card || s == InvSlotType.Card ||
s == InvSlotType.Head || s == InvSlotType.Head ||
s == InvSlotType.Headset || s == InvSlotType.Headset ||
s == InvSlotType.InnerClothes || s == InvSlotType.InnerClothes ||
s == InvSlotType.OuterClothes); s == InvSlotType.OuterClothes ||
s == InvSlotType.HealthInterface);
TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>()) TryAddSubObjective(ref decontainObjective, () => new AIObjectiveDecontainItem(character, item, objectiveManager, targetContainer: suitableContainer.GetComponent<ItemContainer>())
{ {
@@ -11,7 +11,7 @@ namespace Barotrauma
public override string Identifier { get; set; } = "cleanup items"; public override string Identifier { get; set; } = "cleanup items";
public override bool KeepDivingGearOn => true; public override bool KeepDivingGearOn => true;
public override bool AllowAutomaticItemUnequipping => false; public override bool AllowAutomaticItemUnequipping => false;
public override bool ForceOrderPriority => false; protected override bool ForceOrderPriority => false;
public readonly List<Item> prioritizedItems = new List<Item>(); public readonly List<Item> prioritizedItems = new List<Item>();
@@ -117,7 +117,7 @@ namespace Barotrauma
private float AimSpeed => HumanAIController.AimSpeed; private float AimSpeed => HumanAIController.AimSpeed;
private float AimAccuracy => HumanAIController.AimAccuracy; private float AimAccuracy => HumanAIController.AimAccuracy;
private bool EnemyIsClose() => Enemy != null && character.CurrentHull != null && character.CurrentHull == Enemy.CurrentHull || Vector2.DistanceSquared(character.Position, Enemy.Position) < 500; private bool EnemyIsClose() => Enemy != null && Enemy.CurrentHull != null && HumanAIController.VisibleHulls.Contains(Enemy.CurrentHull) && Math.Abs(character.WorldPosition.X - Enemy.WorldPosition.X) < 300;
public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f) public AIObjectiveCombat(Character character, Character enemy, CombatMode mode, AIObjectiveManager objectiveManager, float priorityModifier = 1, float coolDown = 10.0f)
: base(character, objectiveManager, priorityModifier) : base(character, objectiveManager, priorityModifier)
@@ -366,7 +366,7 @@ namespace Barotrauma
} }
} }
} }
bool isAllowedToSeekWeapons = !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest; bool isAllowedToSeekWeapons = character.CurrentHull != null && !EnemyIsClose() && character.TeamID != CharacterTeamType.FriendlyNPC && IsOffensiveOrArrest;
if (!isAllowedToSeekWeapons) if (!isAllowedToSeekWeapons)
{ {
if (WeaponComponent == null) if (WeaponComponent == null)
@@ -1190,19 +1190,5 @@ namespace Barotrauma
} }
} }
} }
//private float CalculateEnemyStrength()
//{
// float enemyStrength = 0;
// AttackContext currentContext = character.GetAttackContext();
// foreach (Limb limb in Enemy.AnimController.Limbs)
// {
// if (limb.attack == null) continue;
// if (!limb.attack.IsValidContext(currentContext)) { continue; }
// if (!limb.attack.IsValidTarget(AttackTarget.Character)) { continue; }
// enemyStrength += limb.attack.GetTotalDamage(false);
// }
// return enemyStrength;
//}
} }
} }
@@ -159,7 +159,8 @@ namespace Barotrauma
{ {
TargetName = container.Item.Name, TargetName = container.Item.Name,
AbortCondition = obj => AbortCondition = obj =>
container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) || container?.Item == null || container.Item.Removed || container.Item.IsThisOrAnyContainerIgnoredByAI(character) ||
(container.Item.GetRootContainer()?.OwnInventory?.Locked ?? false) ||
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>()
@@ -40,6 +40,7 @@ namespace Barotrauma
public Func<Item, bool> RemoveExistingPredicate { get; set; } public Func<Item, bool> RemoveExistingPredicate { get; set; }
public int? RemoveExistingMax { get; set; } public int? RemoveExistingMax { get; set; }
public string AbandonGetItemDialogueIdentifier { get; set; } public string AbandonGetItemDialogueIdentifier { get; set; }
public Func<bool> AbandonGetItemDialogueCondition { get; set; }
public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1) public AIObjectiveDecontainItem(Character character, Item targetItem, AIObjectiveManager objectiveManager, ItemContainer sourceContainer = null, ItemContainer targetContainer = null, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) : base(character, objectiveManager, priorityModifier)
@@ -106,6 +107,7 @@ namespace Barotrauma
TryAddSubObjective(ref getItemObjective, TryAddSubObjective(ref getItemObjective,
constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip) constructor: () => new AIObjectiveGetItem(character, targetItem, objectiveManager, Equip)
{ {
CannotFindDialogueCondition = AbandonGetItemDialogueCondition,
CannotFindDialogueIdentifierOverride = AbandonGetItemDialogueIdentifier, CannotFindDialogueIdentifierOverride = AbandonGetItemDialogueIdentifier,
SpeakIfFails = AbandonGetItemDialogueIdentifier != null, SpeakIfFails = AbandonGetItemDialogueIdentifier != null,
TakeWholeStack = this.TakeWholeStack TakeWholeStack = this.TakeWholeStack
@@ -26,6 +26,7 @@ namespace Barotrauma
if (totalEnemies == 0) { return 0; } if (totalEnemies == 0) { return 0; }
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.
return HumanAIController.IsTrueForAnyCrewMember(c => c.Character.IsSecurity && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine) ? 0 : 100; return HumanAIController.IsTrueForAnyCrewMember(c => c.Character.IsSecurity && !c.Character.IsIncapacitated && c.Character.Submarine == character.Submarine) ? 0 : 100;
} }
@@ -64,6 +65,7 @@ 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 (character.Submarine.TeamID != 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; }
return true; return true;

Some files were not shown because too many files have changed in this diff Show More