(3a5d98b) v0.9.6.0

This commit is contained in:
Regalis
2019-12-17 14:38:24 +01:00
parent 5c95c53118
commit a3569b8bf0
95 changed files with 1579 additions and 728 deletions

View File

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.9.5.1")]
[assembly: AssemblyFileVersion("0.9.5.1")]
[assembly: AssemblyVersion("0.9.6.0")]
[assembly: AssemblyFileVersion("0.9.6.0")]

View File

@@ -85,8 +85,7 @@ namespace Barotrauma
{
if (character.Inventory != null)
{
if (!character.LockHands && character.Stun < 0.1f &&
(character.SelectedConstruction == null || character.SelectedConstruction?.GetComponent<Controller>()?.User != character))
if (!LockInventory(character))
{
character.Inventory.Update(deltaTime, cam);
}
@@ -325,7 +324,7 @@ namespace Barotrauma
}
if (character.Inventory != null && !character.LockHands)
{
character.Inventory.Locked = (character.SelectedConstruction?.GetComponent<Controller>()?.User == character);
character.Inventory.Locked = LockInventory(character);
character.Inventory.DrawOwn(spriteBatch);
character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
CharacterInventory.Layout.Default :
@@ -368,6 +367,17 @@ namespace Barotrauma
}
}
private static bool LockInventory(Character character)
{
if (character?.Inventory == null || !character.AllowInput || character.LockHands) { return true; }
//lock if using a controller, except if we're also using a connection panel in the same item
return
character.SelectedConstruction != null &&
character.SelectedConstruction?.GetComponent<Controller>()?.User == character &&
character.SelectedConstruction?.GetComponent<ConnectionPanel>()?.User != character;
}
private static void DrawOrderIndicator(SpriteBatch spriteBatch, Camera cam, Character character, Order order, float iconAlpha = 1.0f)
{
if (order.TargetAllCharacters)

View File

@@ -2041,6 +2041,16 @@ namespace Barotrauma
commands.Add(new Command("spawnsub", "spawnsub [subname]: Spawn a submarine at the position of the cursor", (string[] args) =>
{
if (GameMain.NetworkMember != null)
{
ThrowError("Cannot spawn additional submarines during a multiplayer session.");
return;
}
if (args.Length == 0)
{
ThrowError("Please enter the name of the submarine.");
return;
}
try
{
Submarine spawnedSub = Submarine.Load(args[0], false);

View File

@@ -220,6 +220,7 @@ namespace Barotrauma
msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false);
msgHolder.RectTransform.SizeChanged += Recalculate;
chatBox.RecalculateChildren();
chatBox.UpdateScrollBarSize();
}
CoroutineManager.StartCoroutine(UpdateMessageAnimation(msgHolder, 0.5f));

View File

@@ -435,12 +435,30 @@ namespace Barotrauma
public void SelectNext(bool force = false, bool autoScroll = true)
{
Select(Math.Min(Content.CountChildren - 1, SelectedIndex + 1), force, autoScroll);
int index = SelectedIndex + 1;
while (index < Content.CountChildren)
{
if (Content.GetChild(index).Visible)
{
Select(index, force, autoScroll);
break;
}
index++;
}
}
public void SelectPrevious(bool force = false, bool autoScroll = true)
{
Select(Math.Max(0, SelectedIndex - 1), force, autoScroll);
int index = SelectedIndex - 1;
while (index >= 0)
{
if (Content.GetChild(index).Visible)
{
Select(index, force, autoScroll);
break;
}
index--;
}
}
public void Select(int childIndex, bool force = false, bool autoScroll = true)

View File

@@ -233,7 +233,7 @@ namespace Barotrauma
switch (InputType)
{
case NumberType.Int:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c)).ToArray());
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsNumber(c) || c == '-').ToArray());
break;
case NumberType.Float:
TextBox.textFilterFunction = text => new string(text.Where(c => char.IsDigit(c) || c == '.' || c == '-').ToArray());

View File

@@ -126,6 +126,11 @@ namespace Barotrauma
set { text.Text = value; }
}
public Color? DefaultTextColor
{
get { return defaultTextColor; }
}
public GUITickBox(RectTransform rectT, string label, ScalableFont font = null, string style = "") : base(null, rectT)
{
CanBeFocused = true;

View File

@@ -318,6 +318,8 @@ namespace Barotrauma
private void InitUserStats()
{
return;
if (GameSettings.ShowUserStatisticsPrompt)
{
if (TextManager.ContainsTag("statisticspromptheader") && TextManager.ContainsTag("statisticsprompttext"))
@@ -404,14 +406,18 @@ namespace Barotrauma
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice);
DebugConsole.Init();
if (Config.AutoUpdateWorkshopItems)
CrossThread.RequestExecutionOnMainThread(() =>
{
if (SteamManager.AutoUpdateWorkshopItems())
if (Config.AutoUpdateWorkshopItems)
{
ContentPackage.LoadAll();
Config.ReloadContentPackages();
if (SteamManager.AutoUpdateWorkshopItems())
{
ContentPackage.LoadAll();
Config.ReloadContentPackages();
}
}
}
});
if (SelectedPackages.None())
{
@@ -844,6 +850,8 @@ namespace Barotrauma
SteamManager.Update((float)Timing.Step);
TaskPool.Update();
SoundManager?.Update();
Timing.Accumulator -= Timing.Step;

View File

@@ -431,7 +431,6 @@ namespace Barotrauma
}
else
{
characterArea.CanBeFocused = false;
characterArea.CanBeSelected = false;
}

View File

@@ -204,9 +204,14 @@ namespace Barotrauma
{
campaign.SuppressStateSending = true;
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex);
//we need to have the latest save file to display location/mission/store
if (campaign.LastSaveID == saveID)
{
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex);
campaign.CargoManager.SetPurchasedItems(purchasedItems);
}
campaign.startWatchmanID = startWatchmanID;
campaign.endWatchmanID = endWatchmanID;
@@ -215,7 +220,6 @@ namespace Barotrauma
campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles;
campaign.CargoManager.SetPurchasedItems(purchasedItems);
if (myCharacterInfo != null)
{

View File

@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
if (brokenSprite == null)
{
//broken doors turn black if no broken sprite has been configured
color = color * (item.Condition / item.Prefab.Health);
color *= (item.Condition / item.Prefab.Health);
color.A = 255;
}
@@ -184,7 +184,7 @@ namespace Barotrauma.Items.Components
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
{
if (isStuck ||
if ((IsStuck && !isNetworkMessage) ||
(PredictedState == null && isOpen == open) ||
(PredictedState != null && isOpen == PredictedState.Value && isOpen == open))
{
@@ -210,11 +210,7 @@ namespace Barotrauma.Items.Components
StopPicking(null);
PlaySound(forcedOpen ? ActionType.OnPicked : ActionType.OnUse, item.WorldPosition);
}
}
//opening a partially stuck door makes it less stuck
if (isOpen) stuck = MathHelper.Clamp(stuck - 30.0f, 0.0f, 100.0f);
}
}
public override void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
@@ -225,7 +221,7 @@ namespace Barotrauma.Items.Components
bool forcedOpen = msg.ReadBoolean();
SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen);
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
if (isStuck) { OpenState = 0.0f; }
PredictedState = null;
}
}

View File

@@ -17,7 +17,20 @@ namespace Barotrauma.Items.Components
{
get { return light; }
}
public override void OnScaleChanged()
{
light.SpriteScale = Vector2.One * item.Scale;
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
}
partial void SetLightSourceState(bool enabled, float brightness)
{
if (light == null) { return; }
light.Enabled = enabled;
light.Color = LightColor * brightness;
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{
if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f)
@@ -28,7 +41,7 @@ namespace Barotrauma.Items.Components
public override void FlipX(bool relativeToSub)
{
if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX)
if (light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null)
{
light.LightSpriteEffect = light.LightSpriteEffect == SpriteEffects.None ?
SpriteEffects.FlipHorizontally : SpriteEffects.None;
@@ -39,5 +52,11 @@ namespace Barotrauma.Items.Components
{
IsOn = msg.ReadBoolean();
}
protected override void RemoveComponentSpecific()
{
base.RemoveComponentSpecific();
light.Remove();
}
}
}

View File

@@ -77,6 +77,26 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
state = msg.ReadBoolean();
ushort userID = msg.ReadUInt16();
if (userID == 0)
{
if (user != null)
{
IsActive = false;
CancelUsing(user);
user = null;
}
}
else
{
Character newUser = Entity.FindEntityByID(userID) as Character;
if (newUser != user)
{
CancelUsing(user);
}
user = newUser;
IsActive = true;
}
}
}
}

View File

@@ -23,18 +23,23 @@ namespace Barotrauma.Items.Components
{
Enabled = false
};
powerIndicator.TextColor = powerIndicator.DefaultTextColor.Value;
highVoltageIndicator = new GUITickBox(new RectTransform(indicatorSize, paddedFrame.RectTransform) { AbsoluteOffset = new Point(0, (int)(40 * GUI.yScale)) },
TextManager.Get("PowerTransferHighVoltage"), style: "IndicatorLightRed")
{
ToolTip = TextManager.Get("PowerTransferTipOvervoltage"),
Enabled = false
};
highVoltageIndicator.TextColor = highVoltageIndicator.DefaultTextColor.Value;
lowVoltageIndicator = new GUITickBox(new RectTransform(indicatorSize, paddedFrame.RectTransform) { AbsoluteOffset = new Point(0, (int)(80 * GUI.yScale)) },
TextManager.Get("PowerTransferLowVoltage"), style: "IndicatorLightRed")
{
ToolTip = TextManager.Get("PowerTransferTipLowvoltage"),
Enabled = false
};
lowVoltageIndicator.TextColor = lowVoltageIndicator.DefaultTextColor.Value;
var textContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), paddedFrame.RectTransform, Anchor.TopRight));

View File

@@ -142,7 +142,7 @@ namespace Barotrauma.Items.Components
{
if (repairSoundChannel == null || !repairSoundChannel.IsPlaying)
{
repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull);
repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull);
}
}
else
@@ -221,8 +221,17 @@ namespace Barotrauma.Items.Components
deteriorationTimer = msg.ReadSingle();
deteriorateAlwaysResetTimer = msg.ReadSingle();
DeteriorateAlways = msg.ReadBoolean();
CurrentFixer = msg.ReadBoolean() ? Character.Controlled : null;
ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
if (currentFixerID == 0)
{
CurrentFixer = null;
}
else
{
CurrentFixer = Entity.FindEntityByID(currentFixerID) as Character;
}
}
public void ClientWrite(IWriteMessage msg, object[] extraData = null)

View File

@@ -119,6 +119,7 @@ namespace Barotrauma.Items.Components
{
DrawWire(spriteBatch, draggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, "");
}
panel.TriggerRewiringSound();
if (!PlayerInput.LeftButtonHeld())
{
@@ -129,7 +130,11 @@ namespace Barotrauma.Items.Components
panel.DisconnectedWires.Add(draggingConnected);
}
if (GameMain.Client != null) { panel.Item.CreateClientEvent(panel); }
if (GameMain.Client != null)
{
panel.Item.CreateClientEvent(panel);
}
draggingConnected = null;
}
}
@@ -205,7 +210,10 @@ namespace Barotrauma.Items.Components
SetWire(index, draggingConnected);
}
}
if (GameMain.Client != null) { panel.Item.CreateClientEvent(panel); }
if (GameMain.Client != null)
{
panel.Item.CreateClientEvent(panel);
}
draggingConnected = null;
}
}

View File

@@ -11,19 +11,28 @@ namespace Barotrauma.Items.Components
{
partial class ConnectionPanel : ItemComponent, IServerSerializable, IClientSerializable
{
//how long the rewiring sound plays after doing changes to the wiring
const float RewireSoundDuration = 5.0f;
public static Wire HighlightedWire;
private SoundChannel rewireSoundChannel;
private float rewireSoundTimer;
partial void InitProjSpecific(XElement element)
{
if (GuiFrame == null) return;
if (GuiFrame == null) { return; }
new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
{
UserData = this
};
}
public void TriggerRewiringSound()
{
rewireSoundTimer = RewireSoundDuration;
}
partial void UpdateProjSpecific(float deltaTime)
{
foreach (Wire wire in DisconnectedWires)
@@ -40,7 +49,9 @@ namespace Barotrauma.Items.Components
}
}
}
if (user != null && user.SelectedConstruction == item && HasRequiredItems(user, addMessage: false))
rewireSoundTimer -= deltaTime;
if (user != null && user.SelectedConstruction == item && rewireSoundTimer > 0.0f)
{
if (rewireSoundChannel == null || !rewireSoundChannel.IsPlaying)
{
@@ -51,12 +62,13 @@ namespace Barotrauma.Items.Components
{
rewireSoundChannel?.FadeOutAndDispose();
rewireSoundChannel = null;
rewireSoundTimer = 0.0f;
}
}
public override void Move(Vector2 amount)
{
if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) return;
if (item.Submarine == null || item.Submarine.Loading || Screen.Selected != GameMain.SubEditorScreen) { return; }
MoveConnectedWires(amount);
}
@@ -103,6 +115,7 @@ namespace Barotrauma.Items.Components
//delay reading the state until midround syncing is done
//because some of the wires connected to the panel may not exist yet
long msgStartPos = msg.BitPosition;
msg.ReadUInt16(); //user ID
foreach (Connection connection in Connections)
{
for (int i = 0; i < Connection.MaxLinked; i++)
@@ -121,6 +134,8 @@ namespace Barotrauma.Items.Components
}
else
{
//don't trigger rewiring sounds if the rewiring is being done by the local user (in that case we'll trigger it locally)
if (Character.Controlled == null || user != Character.Controlled) { TriggerRewiringSound(); }
ApplyRemoteState(msg);
}
}
@@ -130,6 +145,17 @@ namespace Barotrauma.Items.Components
List<Wire> prevWires = Connections.SelectMany(c => c.Wires.Where(w => w != null)).ToList();
List<Wire> newWires = new List<Wire>();
ushort userID = msg.ReadUInt16();
if (userID == 0)
{
user = null;
}
else
{
user = Entity.FindEntityByID(userID) as Character;
}
foreach (Connection connection in Connections)
{
connection.ClearConnections();

View File

@@ -145,6 +145,21 @@ namespace Barotrauma.Items.Components
}
Dock(DockingTarget);
if (joint == null)
{
string errorMsg = "Error while reading a docking port network event (Dock method did not create a joint between the ports)." +
" Submarine: " + (item.Submarine?.Name ?? "null") +
", target submarine: " + (DockingTarget.item.Submarine?.Name ?? "null");
if (item.Submarine?.DockedTo.Contains(DockingTarget.item.Submarine) ?? false)
{
errorMsg += "\nAlready docked.";
}
if (item.Submarine == DockingTarget.item.Submarine)
{
errorMsg += "\nTrying to dock the submarine to itself.";
}
GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
}
if (isLocked)
{

View File

@@ -916,21 +916,30 @@ namespace Barotrauma
case NetEntityEvent.Type.ApplyStatusEffect:
{
ActionType actionType = (ActionType)msg.ReadRangedInteger(0, Enum.GetValues(typeof(ActionType)).Length - 1);
byte componentIndex = msg.ReadByte();
ushort targetID = msg.ReadUInt16();
byte targetLimbID = msg.ReadByte();
byte componentIndex = msg.ReadByte();
ushort targetCharacterID = msg.ReadUInt16();
byte targetLimbID = msg.ReadByte();
ushort useTargetID = msg.ReadUInt16();
Vector2? worldPosition = null;
bool hasPosition = msg.ReadBoolean();
if (hasPosition)
{
worldPosition = new Vector2(msg.ReadSingle(), msg.ReadSingle());
}
ItemComponent targetComponent = componentIndex < components.Count ? components[componentIndex] : null;
Character target = FindEntityByID(targetID) as Character;
Limb targetLimb = target != null && targetLimbID < target.AnimController.Limbs.Length ? target.AnimController.Limbs[targetLimbID] : null;
Character targetCharacter = FindEntityByID(targetCharacterID) as Character;
Limb targetLimb = targetCharacter != null && targetLimbID < targetCharacter.AnimController.Limbs.Length ?
targetCharacter.AnimController.Limbs[targetLimbID] : null;
Entity useTarget = FindEntityByID(useTargetID);
if (targetComponent == null)
{
ApplyStatusEffects(actionType, 1.0f, target, targetLimb, true);
ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, true, worldPosition: worldPosition);
}
else
{
targetComponent.ApplyStatusEffects(actionType, 1.0f, target, targetLimb);
targetComponent.ApplyStatusEffects(actionType, 1.0f, targetCharacter, targetLimb, useTarget, worldPosition: worldPosition);
}
}
break;

View File

@@ -228,12 +228,12 @@ namespace Barotrauma.Lights
activeLights.Clear();
foreach (LightSource light in lights)
{
if (light.Color.A < 1 || light.Range < 1.0f || !light.Enabled) continue;
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.Range, viewRect)) continue;
if (!light.Enabled) { continue; }
if ((light.Color.A < 1 || light.Range < 1.0f) && !light.LightSourceParams.OverrideLightSpriteAlpha.HasValue) { continue; }
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.LightSourceParams.TextureRange, viewRect)) { continue; }
activeLights.Add(light);
}
//clear the lightmap
graphics.Clear(Color.Black);
graphics.BlendState = BlendState.Additive;
@@ -244,9 +244,9 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
foreach (LightSource light in activeLights)
{
if (!light.IsBackground) continue;
if (!light.IsBackground) { continue; }
light.DrawSprite(spriteBatch, cam);
light.DrawLightVolume(spriteBatch, lightEffect, transform);
if (light.Color.A > 0 && light.Range > 0.0f) { light.DrawLightVolume(spriteBatch, lightEffect, transform); }
backgroundSpritesDrawn = true;
}
GameMain.ParticleManager.Draw(spriteBatch, true, null, Particles.ParticleBlendState.Additive);
@@ -288,7 +288,7 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive, transformMatrix: spriteBatchTransform);
foreach (LightSource light in activeLights)
{
if (light.IsBackground) continue;
if (light.IsBackground) { continue; }
light.DrawSprite(spriteBatch, cam);
}
spriteBatch.End();
@@ -303,6 +303,8 @@ namespace Barotrauma.Lights
//draw characters to obstruct the highlighted items/characters and light sprites
//---------------------------------------------------------------------------------------------------
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidColor"];
SolidColorEffect.Parameters["color"].SetValue(Color.Black.ToVector4());
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList)
{
@@ -347,8 +349,8 @@ namespace Barotrauma.Lights
foreach (LightSource light in activeLights)
{
if (light.IsBackground) continue;
light.DrawLightVolume(spriteBatch, lightEffect, transform);
if (light.IsBackground) { continue; }
if (light.Color.A > 0 && light.Range > 0.0f) { light.DrawLightVolume(spriteBatch, lightEffect, transform); }
}
Vector3 offset = Vector3.Zero;// new Vector3(Submarine.MainSub.DrawPosition.X, Submarine.MainSub.DrawPosition.Y, 0.0f);
lightEffect.World = Matrix.CreateTranslation(Vector3.Zero) * transform;

View File

@@ -31,10 +31,22 @@ namespace Barotrauma.Lights
get { return range; }
set
{
range = MathHelper.Clamp(value, 0.0f, 2048.0f);
TextureRange = range;
if (OverrideLightTexture != null)
{
TextureRange += Math.Max(
Math.Abs(OverrideLightTexture.RelativeOrigin.X - 0.5f) * OverrideLightTexture.size.X,
Math.Abs(OverrideLightTexture.RelativeOrigin.Y - 0.5f) * OverrideLightTexture.size.Y);
}
}
}
public float TextureRange
{
get;
private set;
}
public Sprite OverrideLightTexture
{
@@ -89,6 +101,8 @@ namespace Barotrauma.Lights
break;
case "lighttexture":
OverrideLightTexture = new Sprite(subElement, preMultiplyAlpha: false);
//refresh TextureRange
Range = range;
break;
}
}
@@ -115,8 +129,16 @@ namespace Barotrauma.Lights
class LightSource
{
//how many pixels the position of the light needs to change for the light volume to be recalculated
const float MovementRecalculationThreshold = 10.0f;
//how many radians the light needs to rotate for the light volume to be recalculated
const float RotationRecalculationThreshold = 0.02f;
private static Texture2D lightTexture;
private VertexPositionColorTexture[] vertices;
private short[] indices;
private List<ConvexHullList> hullsInRange;
public Texture2D texture;
@@ -167,6 +189,9 @@ namespace Barotrauma.Lights
private int vertexCount;
private int indexCount;
private Vector2 translateVertices;
private float rotateVertices;
private readonly LightSourceParams lightSourceParams;
public LightSourceParams LightSourceParams => lightSourceParams;
@@ -177,26 +202,38 @@ namespace Barotrauma.Lights
get { return position; }
set
{
if (Math.Abs(position.X - value.X) < 0.1f && Math.Abs(position.Y - value.Y) < 0.1f) return;
Vector2 moveAmount = value - position;
if (Math.Abs(moveAmount.X) < 0.1f && Math.Abs(moveAmount.Y) < 0.1f) { return; }
position = value;
if (Vector2.DistanceSquared(prevCalculatedPosition, position) < 5.0f * 5.0f) return;
//translate light volume manually instead of doing a full recalculation when moving by a small amount
if (Vector2.DistanceSquared(prevCalculatedPosition, position) < MovementRecalculationThreshold * MovementRecalculationThreshold && vertices != null)
{
translateVertices = position - prevCalculatedPosition;
return;
}
NeedsHullCheck = true;
NeedsRecalculation = true;
prevCalculatedPosition = position;
}
}
private float prevCalculatedRotation;
private float rotation;
public float Rotation
{
get { return rotation; }
set
{
if (Math.Abs(rotation - value) < 0.01f) return;
if (Math.Abs(value - rotation) < 0.001f) { return; }
rotation = value;
if (Math.Abs(rotation - prevCalculatedRotation) < RotationRecalculationThreshold && vertices != null)
{
rotateVertices = rotation - prevCalculatedRotation;
return;
}
NeedsHullCheck = true;
NeedsRecalculation = true;
}
@@ -711,16 +748,25 @@ namespace Barotrauma.Lights
return retVal;
}
private void CalculateLightVertices(List<Vector2> rayCastHits)
{
List<VertexPositionColorTexture> vertices = new List<VertexPositionColorTexture>();
vertexCount = rayCastHits.Count * 2 + 1;
indexCount = (rayCastHits.Count) * 9;
//recreate arrays if they're too small or excessively large
if (vertices == null || vertices.Length < vertexCount || vertices.Length > vertexCount * 3)
{
vertices = new VertexPositionColorTexture[vertexCount];
indices = new short[indexCount];
}
Vector2 drawPos = position;
if (ParentSub != null) drawPos += ParentSub.DrawPosition;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
Vector2 uvOffset = Vector2.Zero;
Vector2 overrideTextureDims = Vector2.One;
if (OverrideLightTexture != null)
@@ -728,15 +774,15 @@ namespace Barotrauma.Lights
overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTexture.Origin;
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) origin.X = OverrideLightTexture.SourceRect.Width - origin.X;
if (LightSpriteEffect == SpriteEffects.FlipVertically) origin.Y = (OverrideLightTexture.SourceRect.Height - origin.Y);
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = OverrideLightTexture.SourceRect.Width - origin.X; }
if (LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = OverrideLightTexture.SourceRect.Height - origin.Y; }
uvOffset = (origin / overrideTextureDims) - new Vector2(0.5f, 0.5f);
}
// Add a vertex for the center of the mesh
vertices.Add(new VertexPositionColorTexture(new Vector3(position.X, position.Y, 0),
Color.White, new Vector2(0.5f, 0.5f) + uvOffset));
vertices[0] = new VertexPositionColorTexture(new Vector3(position.X, position.Y, 0),
Color.White, GetUV(new Vector2(0.5f, 0.5f) + uvOffset, LightSpriteEffect));
//hacky fix to exc excessively large light volumes (they used to be up to 4x the range of the light if there was nothing to block the rays).
//might want to tweak the raycast logic in a way that this isn't necessary
float boundRadius = Range * 1.1f / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y)));
@@ -800,68 +846,88 @@ namespace Barotrauma.Lights
//finally, create the vertices
VertexPositionColorTexture fullVert = new VertexPositionColorTexture(new Vector3(position.X + rawDiff.X, position.Y + rawDiff.Y, 0),
Color.White, new Vector2(0.5f, 0.5f) + diff);
Color.White, GetUV(new Vector2(0.5f, 0.5f) + diff, LightSpriteEffect));
VertexPositionColorTexture fadeVert = new VertexPositionColorTexture(new Vector3(position.X + rawDiff.X + nDiff.X, position.Y + rawDiff.Y + nDiff.Y, 0),
Color.White * 0.0f, new Vector2(0.5f, 0.5f) + diff);
Color.White * 0.0f, GetUV(new Vector2(0.5f, 0.5f) + diff, LightSpriteEffect));
vertices.Add(fullVert);
vertices.Add(fadeVert);
vertices[1 + i * 2] = fullVert;
vertices[1 + i * 2 + 1] = fadeVert;
}
// Compute the indices to form triangles
List<short> indices = new List<short>();
for (int i = 0; i < rayCastHits.Count-1; i++)
for (int i = 0; i < rayCastHits.Count - 1; i++)
{
//main light body
indices.Add(0);
indices.Add((short)((i*2 + 3) % vertices.Count));
indices.Add((short)((i*2 + 1) % vertices.Count));
indices[i * 9] = 0;
indices[i * 9 + 1] = (short)((i * 2 + 3) % vertexCount);
indices[i * 9 + 2] = (short)((i * 2 + 1) % vertexCount);
//faded light
indices.Add((short)((i*2 + 1) % vertices.Count));
indices.Add((short)((i*2 + 3) % vertices.Count));
indices.Add((short)((i*2 + 4) % vertices.Count));
indices[i * 9 + 3] = (short)((i * 2 + 1) % vertexCount);
indices[i * 9 + 4] = (short)((i * 2 + 3) % vertexCount);
indices[i * 9 + 5] = (short)((i * 2 + 4) % vertexCount);
indices.Add((short)((i*2 + 2) % vertices.Count));
indices.Add((short)((i*2 + 1) % vertices.Count));
indices.Add((short)((i*2 + 4) % vertices.Count));
indices[i * 9 + 6] = (short)((i * 2 + 2) % vertexCount);
indices[i * 9 + 7] = (short)((i * 2 + 1) % vertexCount);
indices[i * 9 + 8] = (short)((i * 2 + 4) % vertexCount);
}
//main light body
indices.Add(0);
indices.Add((short)(1));
indices.Add((short)(vertices.Count - 2));
indices[(rayCastHits.Count - 1) * 9] = 0;
indices[(rayCastHits.Count - 1) * 9 + 1] = (short)(1);
indices[(rayCastHits.Count - 1) * 9 + 2] = (short)(vertexCount - 2);
//faded light
indices.Add((short)(1));
indices.Add((short)(vertices.Count-1));
indices.Add((short)(vertices.Count-2));
indices[(rayCastHits.Count - 1) * 9 + 3] = (short)(1);
indices[(rayCastHits.Count - 1) * 9 + 4] = (short)(vertexCount - 1);
indices[(rayCastHits.Count - 1) * 9 + 5] = (short)(vertexCount - 2);
indices.Add((short)(1));
indices.Add((short)(2));
indices.Add((short)(vertices.Count-1));
vertexCount = vertices.Count;
indexCount = indices.Count;
indices[(rayCastHits.Count - 1) * 9 + 6] = (short)(1);
indices[(rayCastHits.Count - 1) * 9 + 7] = (short)(2);
indices[(rayCastHits.Count - 1) * 9 + 8] = (short)(vertexCount - 1);
//TODO: a better way to determine the size of the vertex buffer and handle changes in size?
//now we just create a buffer for 64 verts and make it larger if needed
if (lightVolumeBuffer == null)
{
lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, Math.Max(64, (int)(vertexCount*1.5)), BufferUsage.None);
lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), Math.Max(64*3, (int)(indexCount * 1.5)), BufferUsage.None);
lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, Math.Max(64, (int)(vertexCount * 1.5)), BufferUsage.None);
lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), Math.Max(64 * 3, (int)(indexCount * 1.5)), BufferUsage.None);
}
else if (vertexCount > lightVolumeBuffer.VertexCount || indexCount > lightVolumeIndexBuffer.IndexCount)
{
lightVolumeBuffer.Dispose();
lightVolumeIndexBuffer.Dispose();
lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, (int)(vertexCount*1.5), BufferUsage.None);
lightVolumeBuffer = new DynamicVertexBuffer(GameMain.Instance.GraphicsDevice, VertexPositionColorTexture.VertexDeclaration, (int)(vertexCount * 1.5), BufferUsage.None);
lightVolumeIndexBuffer = new DynamicIndexBuffer(GameMain.Instance.GraphicsDevice, typeof(short), (int)(indexCount * 1.5), BufferUsage.None);
}
lightVolumeBuffer.SetData<VertexPositionColorTexture>(vertices.ToArray());
lightVolumeIndexBuffer.SetData<short>(indices.ToArray());
lightVolumeBuffer.SetData<VertexPositionColorTexture>(vertices, 0, vertexCount);
lightVolumeIndexBuffer.SetData<short>(indices, 0, indexCount);
Vector2 GetUV(Vector2 vert, SpriteEffects effects)
{
if (effects == SpriteEffects.FlipHorizontally)
{
vert.X = 1.0f - vert.X;
}
else if (effects == SpriteEffects.FlipVertically)
{
vert.Y = 1.0f - vert.Y;
}
else if (effects == (SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically))
{
vert.X = 1.0f - vert.X;
vert.Y = 1.0f - vert.Y;
}
vert.Y = 1.0f - vert.Y;
return vert;
}
translateVertices = Vector2.Zero;
rotateVertices = 0.0f;
prevCalculatedPosition = position;
prevCalculatedRotation = rotation;
}
/// <summary>
@@ -944,15 +1010,12 @@ namespace Barotrauma.Lights
Vector2 drawPos = position;
if (ParentSub != null) drawPos += ParentSub.DrawPosition;
drawPos.Y = -drawPos.Y;
drawPos.Y = -drawPos.Y;
spriteBatch.Draw(currentTexture, drawPos, null, Color, -rotation, center, scale, SpriteEffects.None, 1);
return;
}
Vector3 offset = ParentSub == null ?
Vector3.Zero : new Vector3(ParentSub.DrawPosition.X, ParentSub.DrawPosition.Y, 0.0f);
lightEffect.World = Matrix.CreateTranslation(offset) * transform;
if (NeedsRecalculation)
{
@@ -962,8 +1025,16 @@ namespace Barotrauma.Lights
lastRecalculationTime = (float)Timing.TotalTime;
NeedsRecalculation = false;
}
if (vertexCount == 0) return;
Vector2 offset = ParentSub == null ? Vector2.Zero : ParentSub.DrawPosition;
lightEffect.World =
Matrix.CreateTranslation(-new Vector3(position, 0.0f)) *
Matrix.CreateRotationZ(rotateVertices) *
Matrix.CreateTranslation(new Vector3(position + offset + translateVertices, 0.0f)) *
transform;
if (vertexCount == 0) { return; }
lightEffect.DiffuseColor = (new Vector3(Color.R, Color.G, Color.B) * (Color.A / 255.0f)) / 255.0f;
if (OverrideLightTexture != null)

View File

@@ -483,6 +483,8 @@ namespace Barotrauma.Networking
if (DateTime.Now > timeOut)
{
clientPeer?.Close(Lidgren.Network.NetConnection.NoResponseMessage);
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionFailed"), TextManager.Get("CouldNotConnectToServer"));
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
reconnectBox?.Close(); reconnectBox = null;
break;
}
@@ -1232,7 +1234,9 @@ namespace Barotrauma.Networking
if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal)
{
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
string errorMsg = "Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" +
", mirrored: " + Level.Loaded.Mirrored + ").";
DebugConsole.ThrowError(errorMsg, createMessageBox: true);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + levelSeed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
CoroutineManager.StartCoroutine(EndGame(""));
@@ -1274,6 +1278,7 @@ namespace Barotrauma.Networking
gameStarted = false;
Character.Controlled = null;
SpawnAsTraitor = false;
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false;
respawnManager = null;
@@ -1432,6 +1437,8 @@ namespace Barotrauma.Networking
}
}
private bool initialUpdateReceived;
private void ReadLobbyUpdate(IReadMessage inc)
{
ServerNetObject objHeader;
@@ -1452,13 +1459,15 @@ namespace Barotrauma.Networking
UInt16 settingsLen = inc.ReadUInt16();
byte[] settingsData = inc.ReadBytes(settingsLen);
if (inc.ReadBoolean())
bool isInitialUpdate = inc.ReadBoolean();
if (isInitialUpdate)
{
if (GameSettings.VerboseLogging)
{
DebugConsole.NewMessage("Received initial lobby update, ID: " + updateID + ", last ID: " + GameMain.NetLobbyScreen.LastUpdateID, Color.Gray);
}
ReadInitialUpdate(inc);
initialUpdateReceived = true;
}
string selectSubName = inc.ReadString();
@@ -1489,7 +1498,9 @@ namespace Barotrauma.Networking
float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f;
//ignore the message if we already a more up-to-date one
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID))
//or if we're still waiting for the initial update
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) &&
(isInitialUpdate || initialUpdateReceived))
{
ReadWriteMessage settingsBuf = new ReadWriteMessage();
settingsBuf.Write(settingsData, 0, settingsLen); settingsBuf.BitPosition = 0;
@@ -2248,12 +2259,10 @@ namespace Barotrauma.Networking
{
if (gameStarted)
{
tickBox.Visible = false;
tickBox.Parent.Visible = false;
return false;
}
Vote(VoteType.StartRound, tickBox.Selected);
return true;
}

View File

@@ -200,6 +200,7 @@ namespace Barotrauma.Networking
}
msg.BitPosition += msgLength * 8;
msg.ReadPadBits();
}
else
{
@@ -213,6 +214,20 @@ namespace Barotrauma.Networking
try
{
ReadEvent(msg, entity, sendingTime);
msg.ReadPadBits();
if (msg.BitPosition != msgPosition + msgLength * 8)
{
string errorMsg = "Message byte position incorrect after reading an event for the entity \"" + entity.ToString()
+ "\". Read " + (msg.BitPosition - msgPosition) + " bits, expected message length was " + (msgLength * 8) + " bits.";
#if DEBUG
DebugConsole.ThrowError(errorMsg);
#endif
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
//TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick
//msg.BitPosition = (int)(msgPosition + msgLength * 8);
}
}
catch (Exception e)
@@ -231,9 +246,9 @@ namespace Barotrauma.Networking
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8);
msg.ReadPadBits();
}
}
msg.ReadPadBits();
}
return true;
}

View File

@@ -275,6 +275,7 @@ namespace Barotrauma.Networking
#if DEBUG
CoroutineManager.InvokeAfter(() =>
{
if (GameMain.Client == null) { return; }
if (Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedLoss && sendType != Facepunch.Steamworks.Networking.SendType.Reliable) { return; }
int count = Rand.Range(0.0f, 1.0f) < GameMain.Client.SimulatedDuplicatesChance ? 2 : 1;
for (int i = 0; i < count; i++)

View File

@@ -18,7 +18,17 @@ namespace Barotrauma.Networking
public UInt64 OwnerID;
public bool OwnerVerified;
public string ServerName;
private string serverName;
public string ServerName
{
get { return serverName; }
set
{
serverName = value;
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
}
}
public string ServerMessage;
public bool GameStarted;
public int PlayerCount;
@@ -455,38 +465,7 @@ namespace Barotrauma.Networking
if (SteamFriend.IsPlayingThisGame && SteamFriend.ServerLobbyId != 0)
{
LobbyID = SteamFriend.ServerLobbyId;
SteamManager.Instance.LobbyList.SetManualLobbyDataCallback(LobbyID, (lobby) =>
{
SteamManager.Instance.LobbyList.SetManualLobbyDataCallback(LobbyID, null);
if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { return; }
bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword);
int.TryParse(lobby.GetData("playercount"), out int currPlayers);
int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers);
//UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId);
string ip = lobby.GetData("hostipaddress");
UInt64 ownerId = SteamManager.SteamIDStringToUInt64(lobby.GetData("lobbyowner"));
if (OwnerID != ownerId) { return; }
if (string.IsNullOrWhiteSpace(ip)) { ip = ""; }
ServerName = lobby.Name;
Port = "";
QueryPort = "";
IP = ip;
PlayerCount = currPlayers;
MaxPlayers = maxPlayers;
HasPassword = hasPassword;
RespondedToSteamQuery = true;
LobbyID = lobby.LobbyID;
OwnerID = ownerId;
PingChecked = false;
OwnerVerified = true;
SteamManager.AssignLobbyDataToServerInfo(lobby, this);
onServerRulesReceived?.Invoke(this);
});
SteamManager.Instance.LobbyList.RequestLobbyData(LobbyID);
}
else

View File

@@ -326,8 +326,8 @@ namespace Barotrauma.Steam
localQuery.OnFinished = onFinished;
#endif
instance.client.LobbyList.OnLobbiesUpdated = () => { UpdateLobbyQuery(onServerFound, onServerRulesReceived, onFinished); };
instance.client.LobbyList.Refresh();
instance.client.LobbyList.Request();
return true;
}
@@ -382,42 +382,6 @@ namespace Barotrauma.Steam
return true;
}
private static void UpdateLobbyQuery(Action<Networking.ServerInfo> onServerFound, Action<Networking.ServerInfo> onServerRulesReceived, Action onFinished)
{
foreach (LobbyList.Lobby lobby in instance.client.LobbyList.Lobbies)
{
if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { continue; }
bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword);
int.TryParse(lobby.GetData("playercount"), out int currPlayers);
int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers);
UInt64 ownerId = SteamIDStringToUInt64(lobby.GetData("lobbyowner"));
//UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId);
string ip = lobby.GetData("hostipaddress");
if (string.IsNullOrWhiteSpace(ip)) { ip = ""; }
var serverInfo = new ServerInfo()
{
ServerName = lobby.Name,
Port = "",
QueryPort = "",
IP = ip,
PlayerCount = currPlayers,
MaxPlayers = maxPlayers,
HasPassword = hasPassword,
RespondedToSteamQuery = true,
LobbyID = lobby.LobbyID,
OwnerID = ownerId
};
serverInfo.PingChecked = false;
AssignLobbyDataToServerInfo(lobby, serverInfo);
onServerFound(serverInfo);
//onServerRulesReceived(serverInfo);
}
onFinished();
}
public static void AssignLobbyDataToServerInfo(LobbyList.Lobby lobby, ServerInfo serverInfo)
{
serverInfo.ServerMessage = lobby.GetData("message");
@@ -495,6 +459,7 @@ namespace Barotrauma.Steam
serverInfo.PingChecked = true;
serverInfo.Ping = s.Ping;
serverInfo.LobbyID = 0;
serverInfo.OwnerVerified = true;
if (responded)
{
s.FetchRules();

View File

@@ -248,8 +248,10 @@ namespace Barotrauma
MapEntityCategory newCategory = (MapEntityCategory)userdata;
if (newCategory != selectedItemCategory)
{
searchBox.Text = "";
searchBox.Text = "";
storeItemList.ScrollBar.BarScroll = 0f;
}
FilterStoreItems((MapEntityCategory)userdata, searchBox.Text);
return true;
}
@@ -947,7 +949,9 @@ namespace Barotrauma
var itemFrame = myItemList.Content.GetChildByUserData(pi);
if (itemFrame == null)
{
itemFrame = CreateItemFrame(pi, pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation), myItemList);
var priceInfo = pi.ItemPrefab.GetPrice(Campaign.Map.CurrentLocation);
if (priceInfo == null) { continue; }
itemFrame = CreateItemFrame(pi, priceInfo, myItemList);
}
itemFrame.GetChild(0).GetChild<GUINumberInput>().IntValue = pi.Quantity;
existingItemFrames.Add(itemFrame);

View File

@@ -99,6 +99,7 @@ namespace Barotrauma.CharacterEditor
private Rectangle spriteSheetRect;
private Rectangle CalculateSpritesheetRectangle() =>
Textures == null || Textures.None() ? Rectangle.Empty :
new Rectangle(
spriteSheetOffsetX,
spriteSheetOffsetY,
@@ -656,12 +657,6 @@ namespace Barotrauma.CharacterEditor
}
if (!isFrozen)
{
if (character.AnimController.Invalid)
{
Reset(new Character[] { character });
SpawnCharacter(currentCharacterConfig);
}
Submarine.MainSub.SetPrevTransform(Submarine.MainSub.Position);
Submarine.MainSub.Update((float)deltaTime);
@@ -722,6 +717,7 @@ namespace Barotrauma.CharacterEditor
foreach (Limb limb in character.AnimController.Limbs)
{
if (limb == null || limb.ActiveSprite == null) { continue; }
if (selectedJoints.Any(j => j.LimbA == limb || j.LimbB == limb)) { continue; }
// Select limbs on ragdoll
if (editLimbs && !spriteSheetRect.Contains(PlayerInput.MousePosition) && MathUtils.RectangleContainsPoint(GetLimbPhysicRect(limb), PlayerInput.MousePosition))
{
@@ -2727,11 +2723,19 @@ namespace Barotrauma.CharacterEditor
return false;
}
#endif
character.Params.Save();
GUI.AddMessage(GetCharacterEditorTranslation("CharacterSavedTo").Replace("[path]", CharacterParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5);
character.AnimController.SaveRagdoll();
GUI.AddMessage(GetCharacterEditorTranslation("RagdollSavedTo").Replace("[path]", RagdollParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5);
AnimParams.ForEach(p => p.Save());
if (!string.IsNullOrEmpty(RagdollParams.Texture) && !File.Exists(RagdollParams.Texture))
{
DebugConsole.ThrowError($"Invalid texture path: {RagdollParams.Texture}");
return false;
}
else
{
character.Params.Save();
GUI.AddMessage(GetCharacterEditorTranslation("CharacterSavedTo").Replace("[path]", CharacterParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5);
character.AnimController.SaveRagdoll();
GUI.AddMessage(GetCharacterEditorTranslation("RagdollSavedTo").Replace("[path]", RagdollParams.FullPath), Color.Green, font: GUI.Font, lifeTime: 5);
AnimParams.ForEach(p => p.Save());
}
return true;
};
// Spacing

View File

@@ -40,6 +40,10 @@ namespace Barotrauma.CharacterEditor
canEnterSubmarine = ragdoll.CanEnterSubmarine;
canWalk = ragdoll.CanWalk;
texturePath = ragdoll.Texture;
if (string.IsNullOrEmpty(texturePath) && !name.Equals(Character.HumanSpeciesName, StringComparison.OrdinalIgnoreCase))
{
texturePath = ragdoll.Limbs.FirstOrDefault()?.GetSprite().Texture;
}
}
public static Wizard instance;
@@ -265,8 +269,30 @@ namespace Barotrauma.CharacterEditor
};
if (ofd.ShowDialog() == DialogResult.OK)
{
string file = ofd.FileName;
string relativePath = UpdaterUtil.GetRelativePath(Path.GetFullPath(file), Environment.CurrentDirectory);
string destinationPath = relativePath;
//copy file to XML path if it's not located relative to the game's files
if (relativePath.StartsWith("..") ||
Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file))
{
destinationPath = Path.Combine(Path.GetDirectoryName(XMLPath), Path.GetFileName(file));
string destinationDir = Path.GetDirectoryName(destinationPath);
if (!Directory.Exists(destinationDir))
{
Directory.CreateDirectory(destinationDir);
}
if (!File.Exists(destinationPath))
{
File.Copy(file, Path.GetFullPath(destinationPath), overwrite: true);
}
}
isTextureSelected = true;
texturePathElement.Text = ToolBox.ConvertAbsoluteToRelativePath(ofd.FileName);
texturePathElement.Text = destinationPath;
}
return true;
}

View File

@@ -8,6 +8,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using RestSharp;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
@@ -84,7 +85,7 @@ namespace Barotrauma
}
}
#else
FetchRemoteContent(Frame.RectTransform);
FetchRemoteContent();
#endif
@@ -753,12 +754,20 @@ namespace Barotrauma
exeName = "DedicatedServer.exe";
}
string arguments = "-name \"" + name.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" +
string arguments = "-name \"" + ToolBox.EscapeCharacters(name) + "\"" +
" -public " + isPublicBox.Selected.ToString() +
" -playstyle " + ((PlayStyle)playstyleBanner.UserData).ToString() +
" -password \"" + passwordBox.Text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" +
" -maxplayers " + maxPlayersBox.Text;
if (!string.IsNullOrWhiteSpace(passwordBox.Text))
{
arguments += " -password \"" + ToolBox.EscapeCharacters(passwordBox.Text) + "\"";
}
else
{
arguments += " -nopassword";
}
int ownerKey = 0;
if (Steam.SteamManager.GetSteamID()!=0)
@@ -774,6 +783,7 @@ namespace Barotrauma
string filename = exeName;
#if LINUX || OSX
filename = "./" + Path.GetFileNameWithoutExtension(exeName);
arguments = ToolBox.EscapeCharacters(arguments);
#endif
var processInfo = new ProcessStartInfo
{
@@ -1149,34 +1159,15 @@ namespace Barotrauma
}
#endregion
private void FetchRemoteContent(RectTransform parent)
private void FetchRemoteContent()
{
if (string.IsNullOrEmpty(GameMain.Config.RemoteContentUrl)) { return; }
try
{
var client = new RestClient(GameMain.Config.RemoteContentUrl);
var request = new RestRequest("MenuContent.xml", Method.GET);
IRestResponse response = client.Execute(request);
if (response.ResponseStatus != ResponseStatus.Completed)
{
return;
}
if (response.StatusCode != HttpStatusCode.OK)
{
return;
}
string xml = response.Content;
int index = xml.IndexOf('<');
if (index > 0) { xml = xml.Substring(index, xml.Length - index); }
if (string.IsNullOrWhiteSpace(xml)) { return; }
XElement element = XDocument.Parse(xml)?.Root;
foreach (XElement subElement in element.Elements())
{
GUIComponent.FromXML(subElement, parent);
}
client.ExecuteAsync(request, RemoteContentReceived);
CoroutineManager.StartCoroutine(WairForRemoteContentReceived());
}
catch (Exception e)
@@ -1189,5 +1180,60 @@ namespace Barotrauma
return;
}
}
private IEnumerable<object> WairForRemoteContentReceived()
{
while (true)
{
lock (remoteContentLock)
{
if (remoteContentResponse != null) { break; }
}
yield return new WaitForSeconds(0.1f);
}
lock (remoteContentLock)
{
if (remoteContentResponse.ResponseStatus != ResponseStatus.Completed || remoteContentResponse.StatusCode != HttpStatusCode.OK)
{
yield return CoroutineStatus.Success;
}
try
{
string xml = remoteContentResponse.Content;
int index = xml.IndexOf('<');
if (index > 0) { xml = xml.Substring(index, xml.Length - index); }
if (!string.IsNullOrWhiteSpace(xml))
{
XElement element = XDocument.Parse(xml)?.Root;
foreach (XElement subElement in element.Elements())
{
GUIComponent.FromXML(subElement, Frame.RectTransform);
}
}
}
catch (Exception e)
{
#if DEBUG
DebugConsole.ThrowError("Reading received remote main menu content failed.", e);
#endif
GameAnalyticsManager.AddErrorEventOnce("MainMenuScreen.WairForRemoteContentReceived:Exception", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Reading received remote main menu content failed. " + e.Message);
}
}
yield return CoroutineStatus.Success;
}
private readonly object remoteContentLock = new object();
private IRestResponse remoteContentResponse;
private void RemoteContentReceived(IRestResponse response, RestRequestAsyncHandle handle)
{
lock (remoteContentLock)
{
remoteContentResponse = response;
}
}
}
}

View File

@@ -319,6 +319,14 @@ namespace Barotrauma
RelativeSpacing = panelSpacing
};
GameMain.Instance.OnResolutionChanged += () =>
{
if (innerFrame != null)
{
innerFrame.RectTransform.MaxSize = new Point(int.MaxValue, GameMain.GraphicsHeight - 50);
}
};
var panelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), innerFrame.RectTransform, Anchor.Center), isHorizontal: true)
{
Stretch = true,
@@ -440,6 +448,14 @@ namespace Barotrauma
Stretch = true
};
GameMain.Instance.OnResolutionChanged += () =>
{
if (panelContainer != null && sideBar != null)
{
sideBar.RectTransform.MaxSize = new Point(650, panelContainer.RectTransform.Rect.Height);
}
};
//player info panel ------------------------------------------------------------
myCharacterFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), sideBar.RectTransform));
@@ -1781,24 +1797,23 @@ namespace Barotrauma
RelativeSpacing = 0.03f
};
var headerContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true)
var headerContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedPlayerFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true
};
var nameText = new GUITextBlock(new RectTransform(new Vector2(0.75f, 1.0f), headerContainer.RectTransform),
var nameText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), headerContainer.RectTransform),
text: selectedClient.Name, font: GUI.LargeFont);
nameText.Text = ToolBox.LimitString(nameText.Text, nameText.Font, nameText.Rect.Width);
if (selectedClient.SteamID != 0 && Steam.SteamManager.IsInitialized)
{
var viewSteamProfileButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), headerContainer.RectTransform, Anchor.TopCenter),
var viewSteamProfileButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), headerContainer.RectTransform, Anchor.TopCenter) { MaxSize = new Point(int.MaxValue, (int)(40 * GUI.Scale)) },
TextManager.Get("ViewSteamProfile"))
{
UserData = selectedClient
};
GUITextBlock.AutoScaleAndNormalize(nameText, viewSteamProfileButton.TextBlock);
viewSteamProfileButton.TextBlock.AutoScale = true;
viewSteamProfileButton.OnClicked = (bt, userdata) =>
{
Steam.SteamManager.Instance.Overlay.OpenUrl("https://steamcommunity.com/profiles/" + selectedClient.SteamID.ToString());
@@ -2054,7 +2069,7 @@ namespace Barotrauma
}
var closeButton = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonAreaLower.RectTransform, Anchor.BottomRight),
TextManager.Get("Close"))
TextManager.Get("Close"), style: "GUIButtonLarge")
{
IgnoreLayoutGroups = true,
OnClicked = ClosePlayerFrame
@@ -2409,6 +2424,13 @@ namespace Barotrauma
AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom)
});
characterInfoFrame.RectTransform.SizeChanged += () =>
{
if (characterInfoFrame == null || HeadSelectionList?.RectTransform == null || button == null) { return; }
HeadSelectionList.RectTransform.Resize(new Point(characterInfoFrame.Rect.Width, (characterInfoFrame.Rect.Bottom - button.Rect.Bottom) + characterInfoFrame.Rect.Height * 2));
HeadSelectionList.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - characterInfoFrame.Rect.Width, button.Rect.Bottom);
};
new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), HeadSelectionList.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black)
{
UserData = "outerglow",
@@ -2446,7 +2468,7 @@ namespace Barotrauma
headSprite.SourceRect = new Rectangle(CharacterInfo.CalculateOffset(headSprite, head.Value.ToPoint()), headSprite.SourceRect.Size);
characterSprites.Add(headSprite);
if (row == null || itemsInRow >= 4)
if (itemsInRow >= 4 || row == null || gender != (Gender)row.UserData)
{
row = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.333f), HeadSelectionList.Content.RectTransform), true)
{
@@ -2536,6 +2558,14 @@ namespace Barotrauma
JobSelectionFrame = new GUIFrame(new RectTransform(frameSize, GUI.Canvas, Anchor.TopLeft)
{ AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - frameSize.X, characterInfoFrame.Rect.Bottom) }, "GUIFrameListBox");
characterInfoFrame.RectTransform.SizeChanged += () =>
{
if (characterInfoFrame == null || JobSelectionFrame?.RectTransform == null) { return; }
Point size = new Point(characterInfoFrame.Rect.Width, characterInfoFrame.Rect.Height * 2);
JobSelectionFrame.RectTransform.Resize(size);
JobSelectionFrame.RectTransform.AbsoluteOffset = new Point(characterInfoFrame.Rect.Right - size.X, characterInfoFrame.Rect.Bottom);
};
new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), JobSelectionFrame.RectTransform, Anchor.Center), style: "OuterGlow", color: Color.Black)
{
UserData = "outerglow",
@@ -2600,32 +2630,24 @@ namespace Barotrauma
image.Visible = currVisible == (variantIndex + 1);
}
var variantButton = new GUIButton(new RectTransform(new Vector2(0.15f), jobButton.RectTransform, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f, 0.05f + 0.2f * variantIndex) }, (variantIndex + 1).ToString(), style: null)
var variantButton = CreateJobVariantButton(jobPrefab, variantIndex, images.Length, jobButton);
variantButton.OnClicked = (btn, obj) =>
{
Color = new Color(50, 50, 50, 200),
HoverColor = Color.Gray * 0.75f,
PressedColor = Color.Black * 0.75f,
SelectedColor = new Color(45, 70, 100, 200),
UserData = new Pair<JobPrefab, int>(jobPrefab.First, variantIndex+1),
OnClicked = (btn, obj) =>
currSelected.Selected = false;
int k = ((Pair<JobPrefab, int>)obj).Second;
btn.Parent.UserData = obj;
for (int j = 0; j < images.Length; j++)
{
currSelected.Selected = false;
int k = ((Pair<JobPrefab, int>)obj).Second;
btn.Parent.UserData = obj;
for (int j = 0; j < images.Length; j++)
foreach (GUIImage image in images[j])
{
foreach (GUIImage image in images[j])
{
image.Visible = k == (j + 1);
}
image.Visible = k == (j + 1);
}
currSelected = btn;
currSelected.Selected = true;
return false;
}
};
currSelected = btn;
currSelected.Selected = true;
return false;
};
if (currVisible == (variantIndex + 1))
{
currSelected = variantButton;
@@ -2818,7 +2840,7 @@ namespace Barotrauma
subList.Enabled = !enabled && AllowSubSelection;
shuttleList.Enabled = !enabled && GameMain.Client.HasPermission(ClientPermissions.SelectSub);
StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && GameMain.Client.GameStarted && !enabled;
StartButton.Visible = GameMain.Client.HasPermission(ClientPermissions.ManageRound) && !GameMain.Client.GameStarted && !enabled;
if (campaignViewButton != null) { campaignViewButton.Visible = enabled; }
@@ -2943,34 +2965,28 @@ namespace Barotrauma
}
if (images.Length > 1)
{
var variantButton = new GUIButton(new RectTransform(new Vector2(0.15f), slot.RectTransform, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f, 0.25f + 0.2f * variantIndex) }, (variantIndex + 1).ToString(), style: null)
var variantButton = CreateJobVariantButton(jobPrefab, variantIndex, images.Length, slot);
variantButton.OnClicked = (btn, obj) =>
{
Color = new Color(50, 50, 50, 200),
HoverColor = Color.Gray * 0.75f,
PressedColor = Color.Black * 0.75f,
SelectedColor = new Color(45, 70, 100, 200),
Selected = jobPrefab.Second == (variantIndex + 1),
UserData = new Pair<JobPrefab, int>(jobPrefab.First, variantIndex + 1),
OnClicked = (btn, obj) =>
{
int k = ((Pair<JobPrefab, int>)obj).Second;
btn.Parent.UserData = obj;
UpdateJobPreferences(listBox);
return false;
}
int k = ((Pair<JobPrefab, int>)obj).Second;
btn.Parent.UserData = obj;
UpdateJobPreferences(listBox);
return false;
};
}
}
//info button
new GUIButton(new RectTransform(new Vector2(0.15f), slot.RectTransform, Anchor.TopLeft, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f) }, style: "GUIButtonInfo")
new GUIButton(new RectTransform(new Vector2(0.2f), slot.RectTransform, Anchor.TopLeft, scaleBasis: ScaleBasis.BothHeight)
{ RelativeOffset = new Vector2(0.05f) },
style: "GUIButtonInfo")
{
UserData = jobPrefab.First,
OnClicked = ViewJobInfo
};
//remove button
new GUIButton(new RectTransform(new Vector2(0.15f), slot.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothWidth) { RelativeOffset = new Vector2(0.05f) }, style: "GUICancelButton")
new GUIButton(new RectTransform(new Vector2(0.2f), slot.RectTransform, Anchor.TopRight, scaleBasis: ScaleBasis.BothHeight) { RelativeOffset = new Vector2(0.05f) }, style: "GUICancelButton")
{
UserData = i,
OnClicked = (btn, obj) =>
@@ -3013,6 +3029,24 @@ namespace Barotrauma
}
}
private GUIButton CreateJobVariantButton(Pair<JobPrefab, int> jobPrefab, int variantIndex, int variantCount, GUIComponent slot)
{
float relativeHeight = Math.Min(0.7f / variantCount, 0.2f);
var btn = new GUIButton(new RectTransform(new Vector2(relativeHeight), slot.RectTransform, scaleBasis: ScaleBasis.BothHeight)
{ RelativeOffset = new Vector2(0.05f, 0.25f + relativeHeight * 1.05f * variantIndex) },
(variantIndex + 1).ToString(), style: null)
{
Color = new Color(50, 50, 50, 200),
HoverColor = Color.Gray * 0.75f,
PressedColor = Color.Black * 0.75f,
SelectedColor = new Color(45, 70, 100, 200),
Selected = jobPrefab.Second == (variantIndex + 1),
UserData = new Pair<JobPrefab, int>(jobPrefab.First, variantIndex + 1),
};
return btn;
}
public Pair<string, string> FailedSelectedSub;
public Pair<string, string> FailedSelectedShuttle;

View File

@@ -13,6 +13,7 @@ using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml.Linq;
namespace Barotrauma
@@ -426,7 +427,7 @@ namespace Barotrauma
ToolTip = mode.Name,
Selected = true,
OnSelected = (tickBox) => { FilterServers(); return true; },
UserData = mode.Name
UserData = mode.Identifier
};
gameModeTickBoxes.Add(selectionTick);
filterTextList.Add(selectionTick.TextBlock);
@@ -736,6 +737,7 @@ namespace Barotrauma
info.PlayerCount = GameMain.Client.ConnectedClients.Count;
info.PingChecked = false;
info.HasPassword = serverSettings.HasPassword;
info.OwnerVerified = true;
if (isInfoNew)
{
@@ -747,6 +749,12 @@ namespace Barotrauma
public void AddToRecentServers(ServerInfo info)
{
if (!string.IsNullOrEmpty(info.IP))
{
//don't add localhost to recent servers
if (IPAddress.TryParse(info.IP, out IPAddress ip) && IPAddress.IsLoopback(ip)) { return; }
}
info.Recent = true;
ServerInfo existingInfo = recentServers.Find(serverInfo => info.OwnerID == serverInfo.OwnerID && (info.OwnerID != 0 ? true : (info.IP == serverInfo.IP && info.Port == serverInfo.Port)));
if (existingInfo == null)
@@ -898,6 +906,11 @@ namespace Barotrauma
base.Select();
SelectedTab = ServerListTab.All;
RefreshServers();
if (SteamManager.IsInitialized && SteamManager.Instance.LobbyList != null)
{
SteamManager.Instance.LobbyList.OnLobbyDataReceived = OnLobbyDataReceived;
}
}
public override void Deselect()
@@ -905,7 +918,7 @@ namespace Barotrauma
base.Deselect();
if (SteamManager.IsInitialized && SteamManager.Instance.LobbyList != null)
{
SteamManager.Instance.LobbyList.OnLobbiesUpdated = null;
SteamManager.Instance.LobbyList.OnLobbyDataReceived = null;
}
}
@@ -947,6 +960,7 @@ namespace Barotrauma
(remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion));
child.Visible =
serverInfo.OwnerVerified &&
serverInfo.ServerName.ToLowerInvariant().Contains(searchBox.Text.ToLowerInvariant()) &&
(!filterSameVersion.Selected || (remoteVersion != null && NetworkMember.IsCompatible(remoteVersion, GameMain.Version))) &&
(!filterPassword.Selected || !serverInfo.HasPassword) &&
@@ -1405,7 +1419,8 @@ namespace Barotrauma
{
yield return new WaitForSeconds((float)(refreshDisableTimer - DateTime.Now).TotalSeconds);
}
recentServers.Concat(favoriteServers).ForEach(si => si.OwnerVerified = false);
if (GameMain.Config.UseSteamMatchmaking)
{
serverList.ClearChildren();
@@ -1479,7 +1494,8 @@ namespace Barotrauma
PlayerCount = playerCount,
MaxPlayers = maxPlayers,
HasPassword = hasPassWord,
GameVersion = gameVersion
GameVersion = gameVersion,
OwnerVerified = true
};
foreach (string contentPackageName in contentPackageNames.Split(','))
{
@@ -1511,6 +1527,37 @@ namespace Barotrauma
}
}
private void OnLobbyDataReceived(Facepunch.Steamworks.LobbyList.Lobby lobby)
{
if (string.IsNullOrWhiteSpace(lobby.GetData("haspassword"))) { return; }
bool.TryParse(lobby.GetData("haspassword"), out bool hasPassword);
int.TryParse(lobby.GetData("playercount"), out int currPlayers);
int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers);
//UInt64.TryParse(lobby.GetData("connectsteamid"), out ulong connectSteamId);
string ip = lobby.GetData("hostipaddress");
UInt64 ownerId = SteamManager.SteamIDStringToUInt64(lobby.GetData("lobbyowner"));
ServerInfo newInfo = new ServerInfo();
if (string.IsNullOrWhiteSpace(ip)) { ip = ""; }
newInfo.ServerName = lobby.Name;
newInfo.Port = "";
newInfo.QueryPort = "";
newInfo.IP = ip;
newInfo.PlayerCount = currPlayers;
newInfo.MaxPlayers = maxPlayers;
newInfo.HasPassword = hasPassword;
newInfo.RespondedToSteamQuery = true;
newInfo.LobbyID = lobby.LobbyID;
newInfo.OwnerID = ownerId;
newInfo.PingChecked = false;
newInfo.OwnerVerified = true;
SteamManager.AssignLobbyDataToServerInfo(lobby, newInfo);
AddToServerList(newInfo);
}
private void AddToServerList(ServerInfo serverInfo)
{
var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) &&
@@ -1552,7 +1599,8 @@ namespace Barotrauma
if (serverInfo.OwnerVerified)
{
DebugConsole.NewMessage(serverInfo.OwnerID + " verified!");
var childrenToRemove = serverList.Content.FindChildren(c => (c.UserData is ServerInfo info) && info != serverInfo && info.OwnerID == serverInfo.OwnerID).ToList();
var childrenToRemove = serverList.Content.FindChildren(c => (c.UserData is ServerInfo info) && info != serverInfo &&
(serverInfo.OwnerID != 0 ? info.OwnerID == serverInfo.OwnerID : info.IP == serverInfo.IP)).ToList();
foreach (var child in childrenToRemove)
{
serverList.Content.RemoveChild(child);
@@ -1594,7 +1642,13 @@ namespace Barotrauma
UserData = "password"
};
var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[2] * 1.1f, 1.0f), serverContent.RectTransform), serverInfo.ServerName, style: "GUIServerListTextBox");
var serverName = new GUITextBlock(new RectTransform(new Vector2(columnRelativeWidth[2] * 1.1f, 1.0f), serverContent.RectTransform),
#if !DEBUG
serverInfo.ServerName,
#else
((serverInfo.OwnerID != 0 || serverInfo.LobbyID != 0) ? "[STEAMP2P] " : "[LIDGREN] ") + serverInfo.ServerName,
#endif
style: "GUIServerListTextBox");
new GUITickBox(new RectTransform(new Vector2(columnRelativeWidth[3], 0.9f), serverContent.RectTransform, Anchor.Center), label: "")
{
@@ -1793,7 +1847,7 @@ namespace Barotrauma
GameMain.Config.PlayerName = clientNameBox.Text;
GameMain.Config.SaveNewPlayerConfig();
CoroutineManager.StartCoroutine(ConnectToServer(endpoint, serverName));
CoroutineManager.StartCoroutine(ConnectToServer(endpoint, serverName), "ConnectToServer");
return true;
}
@@ -1822,40 +1876,31 @@ namespace Barotrauma
public void GetServerPing(ServerInfo serverInfo, GUITextBlock serverPingText)
{
if (activePings.Contains(serverInfo.IP)) { return; }
activePings.Add(serverInfo.IP);
if (CoroutineManager.IsCoroutineRunning("ConnectToServer")) { return; }
lock (activePings)
{
if (activePings.Contains(serverInfo.IP)) { return; }
activePings.Add(serverInfo.IP);
}
serverInfo.PingChecked = false;
serverInfo.Ping = -1;
var pingThread = new Thread(() => { PingServer(serverInfo, 1000); })
{
IsBackground = true
};
pingThread.Start();
CoroutineManager.StartCoroutine(UpdateServerPingText(serverInfo, serverPingText, 1000));
}
private IEnumerable<object> UpdateServerPingText(ServerInfo serverInfo, GUITextBlock serverPingText, int timeOut)
{
DateTime timeOutTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: timeOut);
while (DateTime.Now < timeOutTime)
{
if (serverInfo.PingChecked)
TaskPool.Add(PingServerAsync(serverInfo?.IP, 1000),
new Tuple<ServerInfo, GUITextBlock>(serverInfo, serverPingText),
(rtt, obj) =>
{
if (serverInfo.Ping != -1)
var info = obj.Item1;
var text = obj.Item2;
info.Ping = rtt.Result; info.PingChecked = true;
text.TextColor = GetPingTextColor(info.Ping);
text.Text = info.Ping > -1 ? info.Ping.ToString() : "?";
lock (activePings)
{
serverPingText.TextColor = GetPingTextColor(serverInfo.Ping);
}
serverPingText.Text = serverInfo.Ping > -1 ? serverInfo.Ping.ToString() : "?";
activePings.Remove(serverInfo.IP);
yield return CoroutineStatus.Success;
}
yield return CoroutineStatus.Running;
}
yield return CoroutineStatus.Success;
activePings.Remove(serverInfo.IP);
}
});
}
private Color GetPingTextColor(int ping)
@@ -1864,18 +1909,27 @@ namespace Barotrauma
return ToolBox.GradientLerp(ping / 200.0f, Color.LightGreen, Color.Yellow * 0.8f, Color.Red * 0.75f);
}
public void PingServer(ServerInfo serverInfo, int timeOut)
public async Task<int> PingServerAsync(string ip, int timeOut)
{
if (string.IsNullOrWhiteSpace(serverInfo?.IP))
await Task.Yield();
int activePingCount = 100;
while (activePingCount > 25)
{
serverInfo.PingChecked = true;
serverInfo.Ping = -1;
return;
lock (activePings)
{
activePingCount = activePings.Count;
}
await Task.Delay(25);
}
if (string.IsNullOrWhiteSpace(ip))
{
return -1;
}
long rtt = -1;
IPAddress address = null;
IPAddress.TryParse(serverInfo.IP, out address);
IPAddress.TryParse(ip, out address);
if (address != null)
{
//don't attempt to ping if the address is IPv6 and it's not supported
@@ -1902,8 +1956,8 @@ namespace Barotrauma
}
catch (PingException ex)
{
string errorMsg = "Failed to ping a server (" + serverInfo.ServerName + ", " + serverInfo.IP + ") - " + (ex?.InnerException?.Message ?? ex.Message);
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + serverInfo.IP, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg);
string errorMsg = "Failed to ping a server (" + ip + ") - " + (ex?.InnerException?.Message ?? ex.Message);
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ip, GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, errorMsg);
#if DEBUG
DebugConsole.NewMessage(errorMsg, Color.Red);
#endif
@@ -1911,10 +1965,9 @@ namespace Barotrauma
}
}
serverInfo.PingChecked = true;
serverInfo.Ping = (int)rtt;
return (int)rtt;
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{
graphics.Clear(Color.CornflowerBlue);

View File

@@ -30,7 +30,7 @@ namespace Barotrauma
private List<GUIButton> tabButtons = new List<GUIButton>();
private HashSet<string> pendingPreviewImageDownloads = new HashSet<string>();
private readonly HashSet<string> pendingPreviewImageDownloads = new HashSet<string>();
private Dictionary<string, Sprite> itemPreviewSprites = new Dictionary<string, Sprite>();
private enum Tab
@@ -379,10 +379,16 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(item.PreviewImageUrl))
{
string imagePreviewPath = Path.Combine(SteamManager.WorkshopItemPreviewImageFolder, item.Id + ".png");
if (!pendingPreviewImageDownloads.Contains(item.PreviewImageUrl))
{
pendingPreviewImageDownloads.Add(item.PreviewImageUrl);
bool isNewImage;
lock (pendingPreviewImageDownloads)
{
isNewImage = !pendingPreviewImageDownloads.Contains(item.PreviewImageUrl);
if (isNewImage) { pendingPreviewImageDownloads.Add(item.PreviewImageUrl); }
}
if (isNewImage)
{
if (File.Exists(imagePreviewPath))
{
File.Delete(imagePreviewPath);
@@ -397,10 +403,13 @@ namespace Barotrauma
var request = new RestRequest(fileName, Method.GET);
client.ExecuteAsync(request, response =>
{
pendingPreviewImageDownloads.Remove(item.PreviewImageUrl);
lock (pendingPreviewImageDownloads)
{
pendingPreviewImageDownloads.Remove(item.PreviewImageUrl);
}
OnPreviewImageDownloaded(response, imagePreviewPath);
CoroutineManager.StartCoroutine(WaitForItemPreviewDownloaded(item, listBox, imagePreviewPath));
});
});
}
else
{
@@ -410,7 +419,10 @@ namespace Barotrauma
}
catch (Exception e)
{
pendingPreviewImageDownloads.Remove(item.PreviewImageUrl);
lock (pendingPreviewImageDownloads)
{
pendingPreviewImageDownloads.Remove(item.PreviewImageUrl);
}
DebugConsole.ThrowError("Downloading the preview image of the Workshop item \"" + TextManager.EnsureUTF8(item.Title) + "\" failed.", e);
}
}
@@ -595,8 +607,13 @@ namespace Barotrauma
private IEnumerable<object> WaitForItemPreviewDownloaded(Facepunch.Steamworks.Workshop.Item item, GUIListBox listBox, string previewImagePath)
{
while (pendingPreviewImageDownloads.Contains(item.PreviewImageUrl))
while (true)
{
lock (pendingPreviewImageDownloads)
{
if (!pendingPreviewImageDownloads.Contains(item.PreviewImageUrl)) { break; }
}
yield return CoroutineStatus.Running;
}

View File

@@ -522,11 +522,6 @@ namespace Barotrauma
OnSelected = (GUITickBox obj) => { Gap.ShowGaps = obj.Selected; return true; },
};
tickBoxHolder.Children.ForEach(c =>
{
if (c is GUITickBox tb) { tb.RectTransform.MinSize = new Point(0, 32); }
});
GUITextBlock.AutoScaleAndNormalize(tickBoxHolder.Children.Where(c => c is GUITickBox).Select(c => ((GUITickBox)c).TextBlock));
//spacing

View File

@@ -14,7 +14,7 @@ namespace Barotrauma.SpriteDeformations
/// A positive value means that this deformation is or could be used for multiple sprites.
/// This behaviour is not automatic, and has to be implemented for any particular case separately (currently only used in Limbs).
/// </summary>
[Serialize(-1, true), Editable]
[Serialize(-1, true), Editable(minValue: -1, maxValue: 100)]
public int Sync
{
get;

View File

@@ -64,7 +64,7 @@ namespace Barotrauma
{
foreach (RoundSound sound in sounds)
{
if (sound.Sound == null)
if (sound?.Sound == null)
{
string errorMsg = $"Error in StatusEffect.ApplyProjSpecific1 (sound \"{sound.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull1" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
@@ -90,7 +90,7 @@ namespace Barotrauma
selectedSoundIndex = Rand.Int(sounds.Count);
}
var selectedSound = sounds[selectedSoundIndex];
if (selectedSound.Sound == null)
if (selectedSound?.Sound == null)
{
string errorMsg = $"Error in StatusEffect.ApplyProjSpecific2 (sound \"{selectedSound.Filename ?? "unknown"}\" was null)\n" + Environment.StackTrace;
GameAnalyticsManager.AddErrorEventOnce("StatusEffect.ApplyProjSpecific:SoundNull2" + Environment.StackTrace, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);

View File

@@ -31,5 +31,5 @@ using System.Runtime.InteropServices;
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("0.9.5.1")]
[assembly: AssemblyFileVersion("0.9.5.1")]
[assembly: AssemblyVersion("0.9.6.0")]
[assembly: AssemblyFileVersion("0.9.6.0")]

View File

@@ -268,8 +268,8 @@ namespace Barotrauma
break;
case NetEntityEvent.Type.Control:
msg.WriteRangedInteger(1, 0, 3);
Client owner = ((Client)extraData[1]);
msg.Write(owner == null ? (byte)0 : owner.ID);
Client owner = (Client)extraData[1];
msg.Write(owner != null && owner.Character == this && GameMain.Server.ConnectedClients.Contains(owner) ? owner.ID : (byte)0);
break;
case NetEntityEvent.Type.Status:
msg.WriteRangedInteger(2, 0, 3);
@@ -445,14 +445,14 @@ namespace Barotrauma
}
}
public void WriteSpawnData(IWriteMessage msg)
public void WriteSpawnData(IWriteMessage msg, UInt16 entityId)
{
if (GameMain.Server == null) return;
int msgLength = msg.LengthBytes;
msg.Write(Info == null);
msg.Write(ID);
msg.Write(entityId);
msg.Write(SpeciesName);
msg.Write(seed);

View File

@@ -73,7 +73,7 @@ namespace Barotrauma
{
Instance = this;
CommandLineArgs = args;
CommandLineArgs = ToolBox.MergeArguments(args);
World = new World(new Vector2(0, -9.82f));
FarseerPhysics.Settings.AllowSleep = true;
@@ -189,6 +189,13 @@ namespace Barotrauma
ownerKey = 0;
}
#if DEBUG
foreach (string s in CommandLineArgs)
{
Console.WriteLine(s);
}
#endif
for (int i = 0; i < CommandLineArgs.Length; i++)
{
switch (CommandLineArgs[i].Trim())
@@ -213,6 +220,9 @@ namespace Barotrauma
password = CommandLineArgs[i + 1];
i++;
break;
case "-nopassword":
password = "";
break;
case "-upnp":
case "-enableupnp":
bool.TryParse(CommandLineArgs[i + 1], out enableUpnp);
@@ -303,6 +313,7 @@ namespace Barotrauma
Server.Update((float)Timing.Step);
if (Server == null) { break; }
SteamManager.Update((float)Timing.Step);
TaskPool.Update();
CoroutineManager.Update((float)Timing.Step, (float)Timing.Step);
Timing.Accumulator -= Timing.Step;

View File

@@ -7,7 +7,7 @@ namespace Barotrauma.Items.Components
{
partial void SetState(bool open, bool isNetworkMessage, bool sendNetworkMessage, bool forcedOpen)
{
if (isStuck || isOpen == open)
if (IsStuck || isOpen == open)
{
return;
}

View File

@@ -7,6 +7,7 @@ namespace Barotrauma.Items.Components
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(state);
msg.Write(user == null ? (ushort)0 : user.ID);
}
}
}

View File

@@ -38,7 +38,7 @@ namespace Barotrauma.Items.Components
msg.Write(deteriorationTimer);
msg.Write(deteriorateAlwaysResetTimer);
msg.Write(DeteriorateAlways);
msg.Write(CurrentFixer == c.Character);
msg.Write(CurrentFixer == null ? (ushort)0 : CurrentFixer.ID);
msg.WriteRangedInteger((int)currentFixerAction, 0, 2);
}
}

View File

@@ -32,7 +32,6 @@ namespace Barotrauma.Items.Components
}
}
List<Wire> clientSideDisconnectedWires = new List<Wire>();
ushort disconnectedWireCount = msg.ReadUInt16();
for (int i = 0; i < disconnectedWireCount; i++)
@@ -179,6 +178,7 @@ namespace Barotrauma.Items.Components
public void ServerWrite(IWriteMessage msg, Client c, object[] extraData = null)
{
msg.Write(user == null ? (ushort)0 : user.ID);
ClientWrite(msg, extraData);
}
}

View File

@@ -100,16 +100,26 @@ namespace Barotrauma
{
ActionType actionType = (ActionType)extraData[1];
ItemComponent targetComponent = extraData.Length > 2 ? (ItemComponent)extraData[2] : null;
ushort targetID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0;
ushort characterID = extraData.Length > 3 ? (ushort)extraData[3] : (ushort)0;
Limb targetLimb = extraData.Length > 4 ? (Limb)extraData[4] : null;
ushort useTargetID = extraData.Length > 5 ? (ushort)extraData[5] : (ushort)0;
Vector2? worldPosition = null;
if (extraData.Length > 6) { worldPosition = (Vector2)extraData[6]; }
Character targetCharacter = FindEntityByID(targetID) as Character;
Character targetCharacter = FindEntityByID(characterID) as Character;
byte targetLimbIndex = targetLimb != null && targetCharacter != null ? (byte)Array.IndexOf(targetCharacter.AnimController.Limbs, targetLimb) : (byte)255;
msg.WriteRangedInteger((int)actionType, 0, Enum.GetValues(typeof(ActionType)).Length - 1);
msg.Write((byte)(targetComponent == null ? 255 : components.IndexOf(targetComponent)));
msg.Write(targetID);
msg.Write(characterID);
msg.Write(targetLimbIndex);
msg.Write(useTargetID);
msg.Write(worldPosition.HasValue);
if (worldPosition.HasValue)
{
msg.Write(worldPosition.Value.X);
msg.Write(worldPosition.Value.Y);
}
}
break;
case NetEntityEvent.Type.ChangeProperty:
@@ -197,7 +207,7 @@ namespace Barotrauma
}
}
public void WriteSpawnData(IWriteMessage msg)
public void WriteSpawnData(IWriteMessage msg, UInt16 entityID)
{
if (GameMain.Server == null) return;
@@ -209,7 +219,7 @@ namespace Barotrauma
msg.Write(Description);
}
msg.Write(ID);
msg.Write(entityID);
if (ParentInventory == null || ParentInventory.Owner == null)
{

View File

@@ -25,24 +25,23 @@ namespace Barotrauma
SpawnOrRemove entities = (SpawnOrRemove)extraData[0];
message.Write(entities.Remove);
if (entities.Remove)
{
message.Write(entities.Entity.ID);
message.Write(entities.OriginalID);
}
else
{
if (entities.Entity is Item)
{
message.Write((byte)SpawnableType.Item);
DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (ID: " + entities.Entity.ID + ")");
((Item)entities.Entity).WriteSpawnData(message);
DebugConsole.Log("Writing item spawn data " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
((Item)entities.Entity).WriteSpawnData(message, entities.OriginalID);
}
else if (entities.Entity is Character)
{
message.Write((byte)SpawnableType.Character);
DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (ID: " + entities.Entity.ID + ")");
((Character)entities.Entity).WriteSpawnData(message);
DebugConsole.Log("Writing character spawn data: " + entities.Entity.ToString() + " (original ID: " + entities.OriginalID + ", current ID: " + entities.Entity.ID + ")");
((Character)entities.Entity).WriteSpawnData(message, entities.OriginalID);
}
}
}

View File

@@ -134,10 +134,7 @@ namespace Barotrauma.Networking
serverSettings = new ServerSettings(this, name, port, queryPort, maxPlayers, isPublic, attemptUPnP);
KarmaManager.SelectPreset(serverSettings.KarmaPreset);
if (!string.IsNullOrEmpty(password))
{
serverSettings.SetPassword(password);
}
serverSettings.SetPassword(password);
ownerKey = ownKey;
@@ -874,7 +871,9 @@ namespace Barotrauma.Networking
if (Level.Loaded != null && levelEqualityCheckVal != Level.Loaded.EqualityCheckVal)
{
errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server (seed " + Level.Loaded.Seed + ").";
errorStr += " Level equality check failed. The level generated at your end doesn't match the level generated by the server(seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Name + " (" + Submarine.MainSub.MD5Hash.ShortHash + ")" +
", mirrored: " + Level.Loaded.Mirrored + ").";
}
Log(c.Name + " has reported an error: " + errorStr, ServerLog.MessageType.Error);

View File

@@ -306,6 +306,7 @@ namespace Barotrauma.Networking
}
ServerName = doc.Root.GetAttributeString("name", "");
if (ServerName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
ServerMessageText = doc.Root.GetAttributeString("ServerMessage", "");
GameMain.NetLobbyScreen.SelectedModeIdentifier = GameModeIdentifier;

View File

@@ -283,6 +283,7 @@
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\MTRandom.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\Rand.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\SaveUtil.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\TaskPool.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\ToolBox.cs" />
<Compile Include="$(MSBuildThisFileDirectory)Source\Utils\UpdaterUtil.cs" />
</ItemGroup>

View File

@@ -21,7 +21,7 @@ namespace Barotrauma
/// <summary>
/// How long does it take for the ai target to fade out if not kept alive.
/// </summary>
public float FadeOutTime { get; private set; }
public float FadeOutTime { get; private set; } = 1;
public bool Static { get; private set; }

View File

@@ -611,6 +611,7 @@ namespace Barotrauma
}
bool canAttack = true;
bool pursue = false;
if (IsCoolDownRunning)
{
switch (AttackingLimb.attack.AfterAttack)
@@ -623,6 +624,7 @@ namespace Barotrauma
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
{
canAttack = false;
pursue = true;
}
else
{
@@ -661,6 +663,7 @@ namespace Barotrauma
if (AttackingLimb.attack.AfterAttack == AIBehaviorAfterAttack.Pursue)
{
canAttack = false;
pursue = true;
}
else
{
@@ -853,32 +856,35 @@ namespace Barotrauma
if (pathSteering.CurrentPath != null)
{
// Attack doors
if (canAttackSub && pathSteering.CurrentPath.CurrentNode?.ConnectedDoor != null && SelectedAiTarget != pathSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget)
if (canAttackSub)
{
SelectTarget(pathSteering.CurrentPath.CurrentNode.ConnectedDoor.Item.AiTarget);
return;
// If the target is in the same hull, there shouldn't be any doors blocking the path
if (targetCharacter == null || targetCharacter.CurrentHull != Character.CurrentHull)
{
var door = pathSteering.CurrentPath.CurrentNode?.ConnectedDoor ?? pathSteering.CurrentPath.NextNode?.ConnectedDoor;
if (door != null && !door.IsOpen && door.Item.Condition > 0)
{
if (SelectedAiTarget != door.Item.AiTarget)
{
SelectTarget(door.Item.AiTarget, selectedTargetMemory.Priority);
return;
}
}
}
}
else if (canAttackSub && pathSteering.CurrentPath.NextNode?.ConnectedDoor != null && SelectedAiTarget != pathSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget)
// Steer towards the target if in the same room and swimming
if ((Character.AnimController.InWater || pursue) && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
{
SelectTarget(pathSteering.CurrentPath.NextNode.ConnectedDoor.Item.AiTarget);
return;
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
// Steer towards the target if in the same room and swimming
if (Character.AnimController.InWater && targetCharacter != null && VisibleHulls.Contains(targetCharacter.CurrentHull))
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
SteeringManager.SteeringManual(deltaTime, Vector2.Normalize(attackSimPos - steeringLimb.SimPosition));
}
else
{
SteeringManager.SteeringSeek(steerPos, 2);
// Switch to Idle when cannot reach the target and if cannot damage the walls
if ((!canAttackSub || wallTarget == null) && !pathSteering.IsPathDirty && pathSteering.CurrentPath.Unreachable)
{
State = AIState.Idle;
return;
}
State = AIState.Idle;
return;
}
}
}
@@ -1148,8 +1154,8 @@ namespace Barotrauma
{
selectedTargetMemory.Priority = 0;
}
return true;
}
return true;
}
return false;
}

View File

@@ -525,6 +525,7 @@ namespace Barotrauma
protected void ReportProblems()
{
Order newOrder = null;
Hull targetHull = null;
if (Character.CurrentHull != null)
{
foreach (var hull in VisibleHulls)
@@ -534,21 +535,21 @@ namespace Barotrauma
if (c.CurrentHull != hull || !c.Enabled) { continue; }
if (AIObjectiveFightIntruders.IsValidTarget(c, Character))
{
AddTargets<AIObjectiveFightIntruders, Character>(Character, c);
if (newOrder == null)
if (AddTargets<AIObjectiveFightIntruders, Character>(Character, c) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportintruders");
newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character);
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
if (AIObjectiveExtinguishFires.IsValidTarget(hull, Character))
{
AddTargets<AIObjectiveExtinguishFires, Hull>(Character, hull);
if (newOrder == null)
if (AddTargets<AIObjectiveExtinguishFires, Hull>(Character, hull) && newOrder == null)
{
var orderPrefab = Order.GetPrefab("reportfire");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
foreach (Character c in Character.CharacterList)
@@ -556,13 +557,11 @@ namespace Barotrauma
if (c.CurrentHull != hull) { continue; }
if (AIObjectiveRescueAll.IsValidTarget(c, Character))
{
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character))
if (AddTargets<AIObjectiveRescueAll, Character>(c, Character) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRescue>())
{
if (newOrder == null)
{
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, c.CurrentHull, null, orderGiver: Character);
}
var orderPrefab = Order.GetPrefab("requestfirstaid");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
@@ -570,11 +569,11 @@ namespace Barotrauma
{
if (AIObjectiveFixLeaks.IsValidTarget(gap, Character))
{
AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap);
if (newOrder == null && !gap.IsRoomToRoom)
if (AddTargets<AIObjectiveFixLeaks, Gap>(Character, gap) && newOrder == null && !gap.IsRoomToRoom)
{
var orderPrefab = Order.GetPrefab("reportbreach");
newOrder = new Order(orderPrefab, hull, null, orderGiver: Character);
targetHull = hull;
}
}
}
@@ -584,11 +583,11 @@ namespace Barotrauma
if (AIObjectiveRepairItems.IsValidTarget(item, Character))
{
if (item.Repairables.All(r => item.ConditionPercentage > r.ShowRepairUIThreshold)) { continue; }
AddTargets<AIObjectiveRepairItems, Item>(Character, item);
if (newOrder == null)
if (AddTargets<AIObjectiveRepairItems, Item>(Character, item) && newOrder == null && !ObjectiveManager.HasActiveObjective<AIObjectiveRepairItem>())
{
var orderPrefab = Order.GetPrefab("reportbrokendevices");
newOrder = new Order(orderPrefab, item.CurrentHull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
newOrder = new Order(orderPrefab, hull, item.Repairables?.FirstOrDefault(), orderGiver: Character);
targetHull = hull;
}
}
}
@@ -598,9 +597,9 @@ namespace Barotrauma
{
if (GameMain.GameSession?.CrewManager != null && GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime))
{
Character.Speak(newOrder.GetChatMessage("", Character.CurrentHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
Character.Speak(newOrder.GetChatMessage("", targetHull?.DisplayName, givingOrderToSelf: false), ChatMessageType.Order);
#if SERVER
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", Character.CurrentHull, null, Character));
GameMain.Server.SendOrderChatMessage(new OrderChatMessage(newOrder, "", targetHull, null, Character));
#endif
}
}

View File

@@ -191,7 +191,8 @@ namespace Barotrauma
{
diff.Y = 0.0f;
}
if (diff.LengthSquared() < 0.001f) { return -host.Steering; }
//if (diff.LengthSquared() < 0.001f) { return -host.Steering; }
if (diff == Vector2.Zero) { return Vector2.Zero; }
return Vector2.Normalize(diff) * weight;
}

View File

@@ -71,7 +71,7 @@ namespace Barotrauma
foreach (FireSource fs in targetHull.FireSources)
{
bool inRange = fs.IsInDamageRange(character, MathHelper.Clamp(fs.DamageRange * 1.5f, extinguisher.Range * 0.5f, extinguisher.Range));
bool move = !inRange;
bool move = !inRange || !HumanAIController.VisibleHulls.Contains(fs.Hull);
if (inRange || useExtinquisherTimer > 0.0f)
{
useExtinquisherTimer += deltaTime;
@@ -79,7 +79,6 @@ namespace Barotrauma
{
useExtinquisherTimer = 0.0f;
}
character.AIController.SteeringManager.Reset();
character.CursorPosition = fs.Position;
if (extinguisher.Item.RequireAimToUse)
{
@@ -106,20 +105,19 @@ namespace Barotrauma
{
sightLimb = character.AnimController.GetLimb(LimbType.LeftHand);
}
if (!character.CanSeeTarget(fs, sightLimb))
if (character.CanSeeTarget(fs, sightLimb))
{
move = true;
}
else
{
move = false;
character.SetInput(extinguisher.Item.IsShootable ? InputType.Shoot : InputType.Use, false, true);
extinguisher.Use(deltaTime, character);
if (!targetHull.FireSources.Contains(fs))
{
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.Name, true), null, 0, "putoutfire", 10.0f);
{
character.Speak(TextManager.GetWithVariable("DialogPutOutFire", "[roomname]", targetHull.RoomName, true), null, 0, "putoutfire", 10.0f);
}
}
else
{
move = true;
}
}
if (move)
{
@@ -128,6 +126,10 @@ namespace Barotrauma
onAbandon: () => Abandon = true,
onCompleted: () => RemoveSubObjective(ref gotoObjective));
}
else
{
character.AIController.SteeringManager.Reset();
}
break;
}
}

View File

@@ -11,7 +11,7 @@ namespace Barotrauma
{
public override string DebugTag => "fight intruders";
protected override float IgnoreListClearInterval => 30;
public virtual bool IgnoreUnsafeHulls => true;
public override bool IgnoreUnsafeHulls => true;
public AIObjectiveFightIntruders(Character character, AIObjectiveManager objectiveManager, float priorityModifier = 1)
: base(character, objectiveManager, priorityModifier) { }

View File

@@ -200,7 +200,7 @@ namespace Barotrauma
{
SteeringManager.SteeringSeek(character.GetRelativeSimPosition(Target), 10);
}
if (!insideSteering)
if (!insideSteering && character.CurrentHull == null)
{
SteeringManager.SteeringAvoid(deltaTime, lookAheadDistance: 5, weight: 1);
}

View File

@@ -293,7 +293,7 @@ namespace Barotrauma
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override auto pilot unless it's an order by a player
// Don't override unless it's an order by a player
Override = orderGiver == Character.Controlled || orderGiver.IsRemotePlayer
};
break;
@@ -302,7 +302,7 @@ namespace Barotrauma
newObjective = new AIObjectiveOperateItem(order.TargetItemComponent, character, this, option, requireEquip: false, useController: order.UseController, priorityModifier: priorityModifier)
{
IsLoop = true,
// Don't override auto control unless it's an order by a player
// Don't override unless it's an order by a player
Override = orderGiver == Character.Controlled || orderGiver.IsRemotePlayer
};
break;

View File

@@ -24,6 +24,8 @@ namespace Barotrauma
public Entity OperateTarget => operateTarget;
public ItemComponent Component => component;
public ItemComponent GetTarget() => useController ? controller : component;
public Func<bool> completionCondition;
public override float GetPriority()
@@ -35,6 +37,7 @@ namespace Barotrauma
}
if (component.Item.CurrentHull == null) { return 0; }
if (component.Item.CurrentHull.FireSources.Count > 0) { return 0; }
if (IsOperatedByAnother(GetTarget())) { return 0; }
if (Character.CharacterList.Any(c => c.CurrentHull == component.Item.CurrentHull && !HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c))) { return 0; }
float devotion = MathHelper.Min(10, Priority);
float value = devotion + AIObjectiveManager.OrderPriority * PriorityModifier;
@@ -58,6 +61,45 @@ namespace Barotrauma
}
}
private bool IsOperatedByAnother(ItemComponent target)
{
foreach (var c in Character.CharacterList)
{
if (c == character) { continue; }
if (!HumanAIController.IsFriendly(c)) { continue; }
if (c.SelectedConstruction != target.Item) { continue; }
// If the other character is player, don't try to operate
if (c.IsRemotePlayer || Character.Controlled == c) { return true; }
if (c.AIController is HumanAIController humanAi)
{
// If the other character is ordered to operate the item, let him do it
if (humanAi.ObjectiveManager.IsCurrentOrder<AIObjectiveOperateItem>())
{
return true;
}
else
{
if (target is Steering)
{
// Steering is hard-coded -> cannot use the required skills collection defined in the xml
return character.GetSkillLevel("helm") <= c.GetSkillLevel("helm");
}
else
{
return target.DegreeOfSuccess(character) <= target.DegreeOfSuccess(c);
}
}
}
else
{
// Shouldn't go here, unless we allow non-humans to operate items
return false;
}
}
return false;
}
protected override void Act(float deltaTime)
{
if (character.LockHands)
@@ -65,24 +107,24 @@ namespace Barotrauma
Abandon = true;
return;
}
ItemComponent target = useController ? controller : component;
ItemComponent target = GetTarget();
if (useController && controller == null)
{
character.Speak(TextManager.GetWithVariable("DialogCantFindController", "[item]", component.Item.Name, true), null, 2.0f, "cantfindcontroller", 30.0f);
Abandon = true;
return;
}
// Don't allow to operate an item that someone with a better skills already operates, unless this is an order
if (objectiveManager.CurrentOrder != this && IsOperatedByAnother(target))
{
// Don't abandon
return;
}
if (target.CanBeSelected)
{
if (character.CanInteractWith(target.Item, out _, checkLinked: false))
{
HumanAIController.FaceTarget(target.Item);
// Don't allow to operate an item that someone already operates, unless this objective is an order
if (objectiveManager.CurrentOrder != this && Character.CharacterList.Any(c => c.SelectedConstruction == target.Item && c != character && HumanAIController.IsFriendly(c) && HumanAIController.IsActive(c)))
{
// Don't abandon
return;
}
if (character.SelectedConstruction != target.Item)
{
target.Item.TryInteract(character, false, true);

View File

@@ -764,12 +764,17 @@ namespace Barotrauma
.Where(p => p.AfflictionType == "huskinfection")
.Select(p => p as AfflictionPrefabHusk)
.FirstOrDefault(p => p.TargetSpecies.Any(t => t.Equals(AfflictionHusk.GetNonHuskedSpeciesName(speciesName, p), StringComparison.InvariantCultureIgnoreCase)));
string nonHuskedSpeciesName = string.Empty;
if (matchingAffliction == null)
{
DebugConsole.ThrowError("Cannot find a husk infection that matches this species! Please add the speciesnames as 'targets' in the husk affliction prefab definition!");
return;
// Crashes if we fail to create a ragdoll -> Let's just use some ragdoll so that the user sees the error msg.
nonHuskedSpeciesName = IsHumanoid ? HumanSpeciesName : "crawler";
}
else
{
nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
}
string nonHuskedSpeciesName = AfflictionHusk.GetNonHuskedSpeciesName(speciesName, matchingAffliction);
ragdollParams = IsHumanoid ? RagdollParams.GetDefaultRagdollParams<HumanRagdollParams>(nonHuskedSpeciesName) : RagdollParams.GetDefaultRagdollParams<FishRagdollParams>(nonHuskedSpeciesName) as RagdollParams;
if (info == null)
{
@@ -1912,7 +1917,7 @@ namespace Barotrauma
{
if (findFocusedTimer <= 0.0f || Screen.Selected == GameMain.SubEditorScreen)
{
focusedCharacter = FindCharacterAtPosition(mouseSimPos);
focusedCharacter = CanInteract ? FindCharacterAtPosition(mouseSimPos) : null;
focusedItem = CanInteract ?
FindItemAtPosition(mouseSimPos, GameMain.Config.AimAssistAmount * (AnimController.InWater ? 1.5f : 1.0f)) : null;
findFocusedTimer = 0.05f;
@@ -1972,11 +1977,11 @@ namespace Barotrauma
{
DeselectCharacter();
}
else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged)
else if (focusedCharacter != null && IsKeyHit(InputType.Grab) && FocusedCharacter.CanBeDragged && CanInteract)
{
SelectCharacter(focusedCharacter);
}
else if (focusedCharacter != null && IsKeyHit(InputType.Health) && focusedCharacter.CharacterHealth.UseHealthWindow && CanInteractWith(focusedCharacter, 160f, false))
else if (focusedCharacter != null && IsKeyHit(InputType.Health) && focusedCharacter.CharacterHealth.UseHealthWindow && CanInteract && CanInteractWith(focusedCharacter, 160f, false))
{
if (focusedCharacter == SelectedCharacter)
{

View File

@@ -289,7 +289,7 @@ namespace Barotrauma
public static string GetNonHuskedSpeciesName(string huskedSpeciesName, AfflictionPrefabHusk prefab)
{
string nonTag = prefab.HuskedSpeciesName.Remove(AfflictionPrefabHusk.Tag);
return huskedSpeciesName.Remove(nonTag);
return huskedSpeciesName.ToLowerInvariant().Remove(nonTag);
}
}
}

View File

@@ -41,7 +41,7 @@ namespace Barotrauma
{
public AfflictionPrefabHusk(XElement element, Type type = null) : base(element, type)
{
HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null);
HuskedSpeciesName = element.GetAttributeString("huskedspeciesname", null).ToLowerInvariant();
if (HuskedSpeciesName == null)
{
DebugConsole.NewMessage($"No 'huskedspeciesname' defined for the husk affliction ({Identifier}) in {element.ToString()}", Color.Orange);

View File

@@ -85,7 +85,11 @@ namespace Barotrauma
public override void Update(float deltaTime)
{
if (IsClient) { return; }
if (IsClient)
{
if (item.ParentInventory != null) { item.body.FarseerBody.IsKinematic = false; }
return;
}
switch (State)
{
case 0:

View File

@@ -296,10 +296,11 @@ namespace Barotrauma
{
get
{
#if DEBUG
return false;
/*#if DEBUG
return false;
#endif
return sendUserStatistics;
return sendUserStatistics;*/
}
set
{

View File

@@ -25,7 +25,18 @@ namespace Barotrauma.Items.Components
private readonly bool autoOrientGap;
private bool isStuck;
public bool IsStuck => isStuck;
public bool IsStuck
{
get { return isStuck; }
private set
{
if (isStuck == value) { return; }
isStuck = value;
#if SERVER
item.CreateServerEvent(this);
#endif
}
}
private float resetPredictionTimer;
@@ -65,12 +76,12 @@ namespace Barotrauma.Items.Components
public float Stuck
{
get { return stuck; }
set
set
{
if (isOpen || isBroken || !CanBeWelded) return;
stuck = MathHelper.Clamp(value, 0.0f, 100.0f);
if (stuck <= 0.0f) isStuck = false;
if (stuck >= 100.0f) isStuck = true;
if (stuck <= 0.0f) { IsStuck = false; }
if (stuck >= 100.0f) { IsStuck = true; }
}
}
@@ -296,7 +307,7 @@ namespace Barotrauma.Items.Components
}
bool isClosing = false;
if (!isStuck)
if (!IsStuck)
{
if (PredictedState == null)
{
@@ -541,7 +552,7 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
if (isStuck) return;
if (IsStuck) return;
bool wasOpen = PredictedState == null ? isOpen : PredictedState.Value;

View File

@@ -9,7 +9,7 @@ namespace Barotrauma.Items.Components
{
partial class ElectricalDischarger : Powered
{
private static List<ElectricalDischarger> list = new List<ElectricalDischarger>();
private static readonly List<ElectricalDischarger> list = new List<ElectricalDischarger>();
public static IEnumerable<ElectricalDischarger> List
{
get { return list; }
@@ -48,14 +48,14 @@ namespace Barotrauma.Items.Components
}
}
[Serialize(100.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
[Serialize(500.0f, true, description: "How far the discharge can travel from the item."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 5000.0f)]
public float Range
{
get;
set;
}
[Serialize(10.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
[Serialize(25.0f, true, description: "How much further can the discharge be carried when moving across walls."), Editable(MinValueFloat = 0.0f, MaxValueFloat = 1000.0f)]
public float RangeMultiplierInWalls
{
get;
@@ -127,20 +127,42 @@ namespace Barotrauma.Items.Components
#if CLIENT
frameOffset = Rand.Int(electricitySprite.FrameCount);
#endif
if (timer > 0.0f)
{
if (charging)
{
if (Voltage > MinVoltage)
{
Discharge();
}
}
timer -= deltaTime;
}
else
if (timer <= 0.0f)
{
IsActive = false;
return;
}
timer -= deltaTime;
if (charging)
{
if (GetAvailableBatteryPower() >= powerConsumption)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float neededPower = powerConsumption;
while (neededPower > 0.0001f && batteries.Count > 0)
{
batteries.RemoveAll(b => b.Charge <= 0.0001f || b.MaxOutPut <= 0.0001f);
float takePower = neededPower / batteries.Count;
takePower = Math.Min(takePower, batteries.Min(b => Math.Min(b.Charge * 3600.0f, b.MaxOutPut)));
foreach (PowerContainer battery in batteries)
{
neededPower -= takePower;
battery.Charge -= takePower / 3600.0f;
#if SERVER
if (GameMain.Server != null)
{
battery.Item.CreateServerEvent(battery);
}
#endif
}
}
Discharge();
}
else if (Voltage > MinVoltage)
{
Discharge();
}
}
}

View File

@@ -141,7 +141,7 @@ namespace Barotrauma.Items.Components
//TODO: refactor the hitting logic (get rid of the magic numbers, make it possible to use different kinds of animations for different items)
if (!hitting)
{
bool aim = picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
bool aim = picker.AllowInput && picker.IsKeyDown(InputType.Aim) && reloadTimer <= 0 && (picker.SelectedConstruction == null || picker.SelectedConstruction.GetComponent<Ladder>() != null);
if (aim)
{
hitPos = MathUtils.WrapAnglePi(Math.Min(hitPos + deltaTime * 5f, MathHelper.PiOver4));

View File

@@ -96,11 +96,14 @@ namespace Barotrauma.Items.Components
{
useState -= deltaTime;
if (useState <= 0.0f) IsActive = false;
if (item.AiTarget != null)
if (useState <= 0.0f)
{
item.AiTarget.SoundRange = IsActive ? item.AiTarget.MaxSoundRange : item.AiTarget.MinSoundRange;
IsActive = false;
}
if (item.AiTarget != null && IsActive)
{
item.AiTarget.SoundRange = item.AiTarget.MaxSoundRange;
}
}

View File

@@ -399,7 +399,7 @@ namespace Barotrauma.Items.Components
case "activate":
case "use":
case "trigger_in":
item.Use(1.0f);
item.Use(1.0f, sender);
break;
case "toggle":
if (signal != "0")
@@ -669,7 +669,7 @@ namespace Barotrauma.Items.Components
}
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Character user = null)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb targetLimb = null, Entity useTarget = null, Character user = null, Vector2? worldPosition = null)
{
if (statusEffectLists == null) return;
@@ -680,20 +680,24 @@ namespace Barotrauma.Items.Components
{
if (broken && effect.type != ActionType.OnBroken) { continue; }
if (user != null) { effect.SetUser(user); }
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, false, false);
item.ApplyStatusEffect(effect, type, deltaTime, character, targetLimb, useTarget, false, false, worldPosition);
}
}
public virtual void Load(XElement componentElement, bool usePrefabValues)
{
if (componentElement == null || usePrefabValues) { return; }
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(this, attribute.Value);
if (componentElement != null && !usePrefabValues)
{
foreach (XAttribute attribute in componentElement.Attributes())
{
if (!SerializableProperties.TryGetValue(attribute.Name.ToString().ToLowerInvariant(), out SerializableProperty property)) continue;
property.TrySetValue(this, attribute.Value);
}
ParseMsg();
OverrideRequiredItems(componentElement);
}
ParseMsg();
OverrideRequiredItems(componentElement);
if (item.Submarine != null) { SerializableProperty.UpgradeGameVersion(this, originalElement, item.Submarine.GameVersion); }
}
/// <summary>

View File

@@ -123,9 +123,9 @@ namespace Barotrauma.Items.Components
if (user.AnimController.InWater)
{
if (diff.Length() > 30.0f)
if (diff.LengthSquared() > 30.0f * 30.0f)
{
user.AnimController.TargetMovement = Vector2.Clamp(diff*0.01f, -Vector2.One, Vector2.One);
user.AnimController.TargetMovement = Vector2.Clamp(diff * 0.01f, -Vector2.One, Vector2.One);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
}
else
@@ -136,11 +136,29 @@ namespace Barotrauma.Items.Components
else
{
diff.Y = 0.0f;
if (diff != Vector2.Zero && diff.LengthSquared() > 10.0f * 10.0f)
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient && user != Character.Controlled)
{
user.AnimController.TargetMovement = Vector2.Normalize(diff);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
if (Math.Abs(diff.X) > 20.0f)
{
//wait for the character to walk to the correct position
return;
}
else if (Math.Abs(diff.X) > 0.1f)
{
//aim to keep the collider at the correct position once close enough
user.AnimController.Collider.LinearVelocity = new Vector2(
diff.X * 0.1f,
user.AnimController.Collider.LinearVelocity.Y);
}
}
else
{
if (Math.Abs(diff.X) > 10.0f)
{
user.AnimController.TargetMovement = Vector2.Normalize(diff);
user.AnimController.TargetDir = diff.X > 0.0f ? Direction.Right : Direction.Left;
return;
}
}
user.AnimController.TargetMovement = Vector2.Zero;
}
@@ -293,7 +311,7 @@ namespace Barotrauma.Items.Components
private void CancelUsing(Character character)
{
if (character == null || character.Removed) return;
if (character == null || character.Removed) { return; }
foreach (LimbPos lb in limbPositions)
{
@@ -304,18 +322,21 @@ namespace Barotrauma.Items.Components
limb.PullJointEnabled = false;
}
if (character.SelectedConstruction == this.item) character.SelectedConstruction = null;
if (character.SelectedConstruction == this.item) { character.SelectedConstruction = null; }
character.AnimController.Anim = AnimController.Animation.None;
if (character == Character.Controlled)
{
HideHUDs(false);
}
#if SERVER
item.CreateServerEvent(this);
#endif
}
public override bool Select(Character activator)
{
if (activator == null || activator.Removed) return false;
if (activator == null || activator.Removed) { return false; }
//someone already using the item
if (user != null && !user.Removed)
@@ -330,10 +351,12 @@ namespace Barotrauma.Items.Components
}
else
{
user = activator;
user = activator;
IsActive = true;
}
#if SERVER
item.CreateServerEvent(this);
#endif
item.SendSignal(0, "1", "signal_out", user);
return true;
}

View File

@@ -86,7 +86,7 @@ namespace Barotrauma.Items.Components
if (item.CurrentHull == null) { return; }
float powerFactor = currPowerConsumption <= 0.0f ? 1.0f : Voltage;
float powerFactor = Math.Min(currPowerConsumption <= 0.0f ? 1.0f : Voltage, 1.0f);
currFlow = flowPercentage / 100.0f * maxFlow * powerFactor;
//less effective when in a bad condition

View File

@@ -640,9 +640,15 @@ namespace Barotrauma.Items.Components
}
}
if (lastUser != character && lastUser != null && lastUser.SelectedConstruction == item)
if (objective.Override)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
if (lastUser != null && lastUser != character && lastUser != lastAIUser)
{
if (lastUser.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogReactorTaken"), null, 0.0f, "reactortaken", 10.0f);
}
}
}
LastUser = lastAIUser = character;

View File

@@ -479,9 +479,12 @@ namespace Barotrauma.Items.Components
public override bool AIOperate(float deltaTime, Character character, AIObjectiveOperateItem objective)
{
if (user != character && user != null && user.SelectedConstruction == item)
if (objective.Override)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
if (user != character && user != null && user.SelectedConstruction == item)
{
character.Speak(TextManager.Get("DialogSteeringTaken"), null, 0.0f, "steeringtaken", 10.0f);
}
}
user = character;
if (!AutoPilot)

View File

@@ -175,9 +175,8 @@ namespace Barotrauma.Items.Components
else
{
currPowerConsumption = MathHelper.Lerp(currPowerConsumption, rechargeSpeed, 0.05f);
Charge += currPowerConsumption * Voltage / 3600.0f;
}
Charge += currPowerConsumption * Math.Min(Voltage, 1.0f) / 3600.0f;
}
if (charge <= 0.0f)
{

View File

@@ -280,6 +280,8 @@ namespace Barotrauma.Items.Components
{
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }
if (item.Condition <= 0.0f) { return; }
lastPowerProbeRecipients.Add(this);
if (power < 0.0f)
@@ -306,15 +308,15 @@ namespace Barotrauma.Items.Components
foreach (ItemComponent ic in recipient.Item.Components)
{
//powertransfer components don't need to receive the signal in the pass-through signal connections
//other junction boxes don't need to receive the signal in the pass-through signal connections
//because we relay it straight to the connected items without going through the whole chain of junction boxes
if (ic is PowerTransfer && connection.Name.Contains("signal")) { continue; }
if (ic is PowerTransfer && !(ic is RelayComponent) && connection.Name.Contains("signal")) { continue; }
ic.ReceiveSignal(stepsTaken, signal, recipient, source, sender, 0.0f, signalStrength);
}
foreach (StatusEffect effect in recipient.Effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f, null, null, false, false);
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, 1.0f);
}
}
}

View File

@@ -279,13 +279,30 @@ namespace Barotrauma.Items.Components
var pc = powerSource.Item.GetComponent<PowerContainer>();
if (pc != null)
{
float voltage = -pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
float voltage = pc.CurrPowerOutput / Math.Max(powered.CurrPowerConsumption, 1.0f);
powered.voltage += voltage;
}
}
}
}
/// <summary>
/// Returns the amount of power that can be supplied by batteries directly connected to the item
/// </summary>
protected float GetAvailableBatteryPower()
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
float batteryPower = Math.Min(battery.Charge * 3600.0f, battery.MaxOutPut);
availablePower += batteryPower;
}
return availablePower;
}
protected override void RemoveComponentSpecific()
{
poweredList.Remove(this);

View File

@@ -458,6 +458,11 @@ namespace Barotrauma.Items.Components
if (character != null) { character.LastDamageSource = item; }
#if CLIENT
PlaySound(ActionType.OnUse, item.WorldPosition, user: user);
PlaySound(ActionType.OnImpact, item.WorldPosition, user: user);
#endif
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{
if (target.Body.UserData is Limb targetLimb)
@@ -489,14 +494,26 @@ namespace Barotrauma.Items.Components
}
}
}
}
#if SERVER
if (GameMain.NetworkMember.IsServer)
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse });
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact });
}
if (GameMain.NetworkMember.IsServer)
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, targetLimb.character.ID, targetLimb, (ushort)0, item.WorldPosition });
}
#endif
}
else
{
ApplyStatusEffects(ActionType.OnUse, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
ApplyStatusEffects(ActionType.OnImpact, 1.0f, useTarget: target.Body.UserData as Entity, user: user);
#if SERVER
if (GameMain.NetworkMember.IsServer)
{
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnUse, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
GameMain.Server?.CreateEntityEvent(item, new object[] { NetEntityEvent.Type.ApplyStatusEffect, ActionType.OnImpact, this, (ushort)0, null, (target.Body.UserData as Entity)?.ID ?? 0, item.WorldPosition });
}
#endif
}
}
item.body.FarseerBody.OnCollision -= OnProjectileCollision;

View File

@@ -130,6 +130,12 @@ namespace Barotrauma.Items.Components
}
else
{
#if SERVER
if (CurrentFixer != character || currentFixerAction != action)
{
item.CreateServerEvent(this);
}
#endif
CurrentFixer = character;
CurrentFixerAction = action;
return true;
@@ -140,12 +146,15 @@ namespace Barotrauma.Items.Components
{
if (CurrentFixer == character)
{
#if SERVER
if (CurrentFixer != character || currentFixerAction != FixActions.None)
{
item.CreateServerEvent(this);
}
#endif
CurrentFixer.AnimController.Anim = AnimController.Animation.None;
CurrentFixer = null;
currentFixerAction = FixActions.None;
#if SERVER
item.CreateServerEvent(this);
#endif
#if CLIENT
repairSoundChannel?.FadeOutAndDispose();
repairSoundChannel = null;
@@ -214,16 +223,16 @@ namespace Barotrauma.Items.Components
return;
}
UpdateFixAnimation(CurrentFixer);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
if (CurrentFixer != null && (CurrentFixer.SelectedConstruction != item || !CurrentFixer.CanInteractWith(item) || CurrentFixer.IsDead))
{
StopRepairing(CurrentFixer);
return;
}
UpdateFixAnimation(CurrentFixer);
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
float successFactor = requiredSkills.Count == 0 ? 1.0f : DegreeOfSuccess(CurrentFixer, requiredSkills);
//item must have been below the repair threshold for the player to get an achievement or XP for repairing it

View File

@@ -258,7 +258,7 @@ namespace Barotrauma.Items.Components
foreach (StatusEffect effect in recipient.Effects)
{
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false);
recipient.Item.ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step);
}
}
}

View File

@@ -35,6 +35,11 @@ namespace Barotrauma.Items.Components
set { /*do nothing*/ }
}
public Character User
{
get { return user; }
}
public ConnectionPanel(Item item, XElement element)
: base(item, element)
{
@@ -126,6 +131,9 @@ namespace Barotrauma.Items.Components
if (user == null || user.SelectedConstruction != item)
{
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
#endif
user = null;
return;
}
@@ -135,6 +143,11 @@ namespace Barotrauma.Items.Components
user.AnimController.UpdateUseItem(true, item.WorldPosition + new Vector2(0.0f, 100.0f) * (((float)Timing.TotalTime / 10.0f) % 0.1f));
}
public override void UpdateBroken(float deltaTime, Camera cam)
{
Update(deltaTime, cam);
}
partial void UpdateProjSpecific(float deltaTime);
public override bool Select(Character picker)
@@ -147,13 +160,16 @@ namespace Barotrauma.Items.Components
}
user = picker;
#if SERVER
if (user != null) { item.CreateServerEvent(this); }
#endif
IsActive = true;
return true;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null || character != user) return false;
if (character == null || character != user) { return false; }
var powered = item.GetComponent<Powered>();
if (powered != null)
@@ -257,6 +273,10 @@ namespace Barotrauma.Items.Components
public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{
#if CLIENT
TriggerRewiringSound();
#endif
foreach (Connection connection in Connections)
{
foreach (Wire wire in connection.Wires)

View File

@@ -172,7 +172,7 @@ namespace Barotrauma.Items.Components
foreach (StatusEffect effect in ciElement.StatusEffects)
{
item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, true, false);
item.ApplyStatusEffect(effect, ciElement.State ? ActionType.OnUse : ActionType.OnSecondaryUse, 1.0f, null, null, null, true, false);
}
}
}

View File

@@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components
{
range = MathHelper.Clamp(value, 0.0f, 4096.0f);
#if CLIENT
if (light != null) light.Range = range;
if (light != null) { light.Range = range; }
#endif
}
}
@@ -75,7 +75,7 @@ namespace Barotrauma.Items.Components
get { return IsActive; }
set
{
if (IsActive == value) return;
if (IsActive == value) { return; }
IsActive = value;
#if SERVER
@@ -135,11 +135,8 @@ namespace Barotrauma.Items.Components
{
if (base.IsActive == value) { return; }
base.IsActive = value;
#if CLIENT
if (light == null) return;
light.Color = value ? lightColor : Color.Transparent;
if (!value) lightBrightness = 0.0f;
#endif
SetLightSourceState(value, value ? lightBrightness : 0.0f);
}
}
@@ -153,7 +150,8 @@ namespace Barotrauma.Items.Components
Position = item.Position,
CastShadows = castShadows,
IsBackground = drawBehindSubs,
SpriteScale = Vector2.One * item.Scale
SpriteScale = Vector2.One * item.Scale,
Range = range
};
#endif
@@ -161,40 +159,34 @@ namespace Barotrauma.Items.Components
item.AddTag("light");
}
#if CLIENT
public override void OnScaleChanged()
{
light.SpriteScale = Vector2.One * item.Scale;
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
}
#endif
public override void OnItemLoaded()
{
base.OnItemLoaded();
itemLoaded = true;
#if CLIENT
light.Color = IsActive ? lightColor : Color.Transparent;
if (!IsActive) lightBrightness = 0.0f;
#endif
SetLightSourceState(IsActive, lightBrightness);
}
public override void Update(float deltaTime, Camera cam)
{
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
}
UpdateOnActiveEffects(deltaTime);
#if CLIENT
light.ParentSub = item.Submarine;
#endif
if (item.Container != null)
{
light.Color = Color.Transparent;
SetLightSourceState(false, 0.0f);
return;
}
#if CLIENT
light.Position = ParentBody != null ? ParentBody.Position : item.Position;
#endif
PhysicsBody body = ParentBody ?? item.body;
if (body != null)
{
#if CLIENT
@@ -203,9 +195,7 @@ namespace Barotrauma.Items.Components
#endif
if (!body.Enabled)
{
#if CLIENT
light.Color = Color.Transparent;
#endif
SetLightSourceState(false, 0.0f);
return;
}
}
@@ -217,7 +207,6 @@ namespace Barotrauma.Items.Components
}
currPowerConsumption = powerConsumption;
if (Rand.Range(0.0f, 1.0f) < 0.05f && Voltage < Rand.Range(0.0f, MinVoltage))
{
#if CLIENT
@@ -240,36 +229,21 @@ namespace Barotrauma.Items.Components
if (blinkTimer > 0.5f)
{
#if CLIENT
light.Color = Color.Transparent;
#endif
SetLightSourceState(false, lightBrightness);
}
else
{
#if CLIENT
light.Color = lightColor * lightBrightness * (1.0f - Rand.Range(0.0f, Flicker));
light.Range = range;
#endif
SetLightSourceState(true, lightBrightness * (1.0f - Rand.Range(0.0f, flicker)));
}
if (item.AiTarget != null)
{
UpdateAITarget(item.AiTarget);
}
}
#if CLIENT
public override void UpdateBroken(float deltaTime, Camera cam)
{
light.Color = Color.Transparent;
lightBrightness = 0.0f;
if (powerIn == null && powerConsumption > 0.0f) { Voltage -= deltaTime; }
}
protected override void RemoveComponentSpecific()
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.RemoveComponentSpecific();
light.Remove();
SetLightSourceState(false, 0.0f);
}
#endif
public override bool Use(float deltaTime, Character character = null)
{
return true;
@@ -277,8 +251,6 @@ namespace Barotrauma.Items.Components
public override void ReceiveSignal(int stepsTaken, string signal, Connection connection, Item source, Character sender, float power = 0.0f, float signalStrength = 1.0f)
{
base.ReceiveSignal(stepsTaken, signal, connection, source, sender, power, signalStrength);
switch (connection.Name)
{
case "toggle":
@@ -300,13 +272,14 @@ namespace Barotrauma.Items.Components
private void UpdateAITarget(AITarget target)
{
//voltage > minVoltage || powerConsumption <= 0.0f; <- ?
target.Enabled = IsActive;
if (!IsActive) { return; }
if (target.MaxSightRange <= 0)
{
target.MaxSightRange = Range * 5;
}
target.SightRange = IsActive ? target.MaxSightRange * lightBrightness : 0;
target.SightRange = target.MaxSightRange * lightBrightness;
}
partial void SetLightSourceState(bool enabled, float brightness);
}
}

View File

@@ -102,7 +102,7 @@ namespace Barotrauma.Items.Components
public override void ReceivePowerProbeSignal(Connection connection, Item source, float power)
{
if (!IsOn) { return; }
if (!IsOn || item.Condition <= 0.0f) { return; }
//we've already received this signal
if (lastPowerProbeRecipients.Contains(this)) { return; }

View File

@@ -276,7 +276,7 @@ namespace Barotrauma.Items.Components
if (reload > 0.0f) return false;
if (GetAvailablePower() < powerConsumption)
if (GetAvailableBatteryPower() < powerConsumption)
{
#if CLIENT
if (!flashLowPower && character != null && character == Character.Controlled)
@@ -410,7 +410,7 @@ namespace Barotrauma.Items.Components
character.AIController.SelectTarget(null);
}
if (GetAvailablePower() < powerConsumption)
if (GetAvailableBatteryPower() < powerConsumption)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
@@ -540,21 +540,6 @@ namespace Barotrauma.Items.Components
return false;
}
private float GetAvailablePower()
{
var batteries = item.GetConnectedComponents<PowerContainer>();
float availablePower = 0.0f;
foreach (PowerContainer battery in batteries)
{
float batteryPower = Math.Min(battery.Charge*3600.0f, battery.MaxOutPut);
availablePower += batteryPower;
}
return availablePower;
}
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
{
var batteries = item.GetConnectedComponents<PowerContainer>();

View File

@@ -425,7 +425,7 @@ namespace Barotrauma
string[] splitTags = value.Split(',');
foreach (string tag in splitTags)
{
string[] splitTag = tag.Split(':');
string[] splitTag = tag.Trim().Split(':');
splitTag[0] = splitTag[0].ToLowerInvariant();
tags.Add(string.Join(":", splitTag));
}
@@ -1060,18 +1060,18 @@ namespace Barotrauma
return true;
}
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false)
public void ApplyStatusEffects(ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, Vector2? worldPosition = null)
{
if (!hasStatusEffectsOfType[(int)type]) { return; }
foreach (StatusEffect effect in statusEffectLists[type])
{
ApplyStatusEffect(effect, type, deltaTime, character, limb, isNetworkEvent, false);
ApplyStatusEffect(effect, type, deltaTime, character, limb, useTarget, isNetworkEvent, false, worldPosition);
}
}
readonly List<ISerializableEntity> targets = new List<ISerializableEntity>();
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, bool isNetworkEvent = false, bool checkCondition = true)
public void ApplyStatusEffect(StatusEffect effect, ActionType type, float deltaTime, Character character = null, Limb limb = null, Entity useTarget = null, bool isNetworkEvent = false, bool checkCondition = true, Vector2? worldPosition = null)
{
if (!isNetworkEvent && checkCondition)
{
@@ -1110,6 +1110,12 @@ namespace Barotrauma
if (targets.Count > 0) { hasTargets = true; }
}
if (effect.HasTargetType(StatusEffect.TargetType.UseTarget) && useTarget is ISerializableEntity serializableTarget)
{
hasTargets = true;
targets.Add(serializableTarget);
}
if (!hasTargets) { return; }
if (effect.HasTargetType(StatusEffect.TargetType.Hull) && CurrentHull != null)
@@ -1125,30 +1131,32 @@ namespace Barotrauma
}
}
if (effect.HasTargetType(StatusEffect.TargetType.Character))
if (character != null)
{
if (type == ActionType.OnContained && ParentInventory is CharacterInventory characterInventory)
if (effect.HasTargetType(StatusEffect.TargetType.Character))
{
targets.Add(characterInventory.Owner as ISerializableEntity);
if (type == ActionType.OnContained && ParentInventory is CharacterInventory characterInventory)
{
targets.Add(characterInventory.Owner as ISerializableEntity);
}
else
{
targets.Add(character);
}
}
else
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
targets.Add(character);
targets.AddRange(character.AnimController.Limbs.ToList());
}
}
if (effect.HasTargetType(StatusEffect.TargetType.Limb))
{
targets.Add(limb);
}
if (effect.HasTargetType(StatusEffect.TargetType.AllLimbs))
{
targets.AddRange(character.AnimController.Limbs.ToList());
}
if (Container != null && effect.HasTargetType(StatusEffect.TargetType.Parent)) targets.Add(Container);
effect.Apply(type, deltaTime, this, targets);
effect.Apply(type, deltaTime, this, targets, worldPosition);
}
@@ -1360,7 +1368,8 @@ namespace Barotrauma
{
if (transformDirty) { return false; }
Vector2 normal = contact.Manifold.LocalNormal;
contact.GetWorldManifold(out Vector2 normal, out _);
if (contact.FixtureA.Body == f1.Body) { normal = -normal; }
float impact = Vector2.Dot(f1.Body.LinearVelocity, -normal);
OnCollisionProjSpecific(f1, f2, contact, impact);
@@ -1568,7 +1577,7 @@ namespace Barotrauma
foreach (StatusEffect effect in connection.Effects)
{
if (condition <= 0.0f && effect.type != ActionType.OnBroken) { continue; }
if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step, null, null, false, false); }
if (signal != "0" && !string.IsNullOrEmpty(signal)) { ApplyStatusEffect(effect, ActionType.OnUse, (float)Timing.Step); }
}
connection.SendSignal(stepsTaken, signal, source ?? this, sender, power, signalStrength);
}

View File

@@ -575,6 +575,10 @@ namespace Barotrauma
public void ApplyFlowForces(float deltaTime, Item item)
{
if (item.body.Mass <= 0.0f)
{
return;
}
foreach (var gap in ConnectedGaps.Where(gap => gap.Open > 0))
{
var distance = MathHelper.Max(Vector2.DistanceSquared(item.Position, gap.Position) / 1000, 1f);

View File

@@ -180,14 +180,22 @@ namespace Barotrauma
}
linkedSub.filePath = element.GetAttributeString("filepath", "");
int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]);
int[] linkedToIds = element.GetAttributeIntArray("linkedto", new int[0]);
for (int i = 0; i < linkedToIds.Length; i++)
{
linkedSub.linkedToID.Add((ushort)linkedToIds[i]);
if (Screen.Selected == GameMain.SubEditorScreen)
{
if (FindEntityByID((ushort)linkedToIds[i]) is MapEntity linked)
{
linkedSub.linkedTo.Add(linked);
}
}
}
linkedSub.originalLinkedToID = (ushort)element.GetAttributeInt("originallinkedto", 0);
linkedSub.originalMyPortID = (ushort)element.GetAttributeInt("originalmyport", 0);
return linkedSub.loadSub ? linkedSub : null;
}

View File

@@ -107,11 +107,14 @@ namespace Barotrauma
{
public readonly Entity Entity;
public readonly UInt16 OriginalID;
public readonly bool Remove = false;
public SpawnOrRemove(Entity entity, bool remove)
{
Entity = entity;
OriginalID = entity.ID;
Remove = remove;
}
}

View File

@@ -328,7 +328,16 @@ namespace Barotrauma.Networking
}
}
public string ServerName;
private string serverName;
public string ServerName
{
get { return serverName; }
set
{
serverName = value;
if (serverName.Length > NetConfig.ServerNameMaxLength) { ServerName = ServerName.Substring(0, NetConfig.ServerNameMaxLength); }
}
}
private string serverMessageText;
public string ServerMessageText

View File

@@ -177,7 +177,7 @@ namespace Barotrauma
string[] readTags = valStr.Split(',');
int matches = 0;
foreach (string tag in readTags)
if (((Item)target).HasTag(tag)) matches++;
if (target is Item item && item.HasTag(tag)) matches++;
//If operator is == then it needs to match everything, otherwise if its != there must be zero matches.
return Operator == OperatorType.Equals ? matches >= readTags.Length : matches <= 0;
@@ -225,8 +225,7 @@ namespace Barotrauma
return success;
case ConditionType.SpeciesName:
if (target == null) { return Operator == OperatorType.NotEquals; }
Character targetCharacter = target as Character;
if (targetCharacter == null) { return false; }
if (!(target is Character targetCharacter)) { return false; }
return (Operator == OperatorType.Equals) == (targetCharacter.SpeciesName == valStr);
case ConditionType.EntityType:
switch (valStr)

View File

@@ -12,8 +12,10 @@ namespace Barotrauma
//only used if none of the selected content packages contain any text files
const string VanillaTextFilePath = "Content/Texts/EnglishVanilla.xml";
private static readonly object mutex = new object();
//key = language
private static Dictionary<string, List<TextPack>> textPacks = new Dictionary<string, List<TextPack>>();
private static Dictionary<string, List<TextPack>> textPacks;
private static readonly string[] serverMessageCharacters = new string[] { "~", "[", "]", "=" };
@@ -25,10 +27,16 @@ namespace Barotrauma
private set;
}
private static readonly HashSet<string> availableLanguages = new HashSet<string>();
private static HashSet<string> availableLanguages;
public static IEnumerable<string> AvailableLanguages
{
get { return availableLanguages; }
get
{
lock (mutex)
{
return new HashSet<string>(availableLanguages);
}
}
}
public static List<string> GetTextFiles()
@@ -55,25 +63,29 @@ namespace Barotrauma
/// </summary>
public static string GetTranslatedLanguageName(string language)
{
if (!textPacks.ContainsKey(language))
lock (mutex)
{
if (!textPacks.ContainsKey(language))
{
return language;
}
foreach (var textPack in textPacks[language])
{
if (textPack.Language == language)
{
return textPack.TranslatedName;
}
}
return language;
}
foreach (var textPack in textPacks[language])
{
if (textPack.Language == language)
{
return textPack.TranslatedName;
}
}
return language;
}
public static void LoadTextPacks(IEnumerable<ContentPackage> selectedContentPackages)
{
availableLanguages.Clear();
textPacks.Clear();
HashSet<string> newLanguages = new HashSet<string>();
Dictionary<string, List<TextPack>> newTextPacks = new Dictionary<string, List<TextPack>>();
var textFiles = ContentPackage.GetFilesOfType(selectedContentPackages, ContentType.Text);
foreach (string file in textFiles)
@@ -81,12 +93,12 @@ namespace Barotrauma
try
{
var textPack = new TextPack(file);
availableLanguages.Add(textPack.Language);
if (!textPacks.ContainsKey(textPack.Language))
newLanguages.Add(textPack.Language);
if (!newTextPacks.ContainsKey(textPack.Language))
{
textPacks.Add(textPack.Language, new List<TextPack>());
newTextPacks.Add(textPack.Language, new List<TextPack>());
}
textPacks[textPack.Language].Add(textPack);
newTextPacks[textPack.Language].Add(textPack);
}
catch (Exception e)
{
@@ -94,7 +106,7 @@ namespace Barotrauma
}
}
if (textPacks.Count == 0)
if (newTextPacks.Count == 0)
{
DebugConsole.ThrowError("No text files available in any of the selected content packages. Attempting to find a vanilla English text file...");
if (!File.Exists(VanillaTextFilePath))
@@ -102,9 +114,21 @@ namespace Barotrauma
throw new Exception("No text files found in any of the selected content packages or in the default text path!");
}
var textPack = new TextPack(VanillaTextFilePath);
availableLanguages.Add(textPack.Language);
textPacks.Add(textPack.Language, new List<TextPack>() { textPack });
newLanguages.Add(textPack.Language);
newTextPacks.Add(textPack.Language, new List<TextPack>() { textPack });
}
if (newTextPacks.Count == 0)
{
throw new Exception("Failed to load text packs!");
}
lock (mutex)
{
textPacks = newTextPacks;
availableLanguages = newLanguages;
}
Initialized = true;
}
@@ -112,74 +136,81 @@ namespace Barotrauma
{
if (string.IsNullOrEmpty(textTag)) { return false; }
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
foreach (TextPack textPack in textPacks[Language])
{
if (textPack.Get(textTag) != null) { return true; }
}
}
foreach (TextPack textPack in textPacks[Language])
{
if (textPack.Get(textTag) != null) { return true; }
}
return false;
}
public static string Get(string textTag, bool returnNull = false, string fallBackTag = null)
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(textTag);
if (text != null) { return text; }
}
if (!string.IsNullOrEmpty(fallBackTag))
{
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(fallBackTag);
if (text != null) { return text; }
}
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
string text = textPack.Get(textTag);
if (text != null)
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
#if DEBUG
DebugConsole.NewMessage("Text \"" + textTag + "\" not found for the language \"" + Language + "\". Using the English text \"" + text + "\" instead.");
#endif
return text;
throw new Exception("No text packs available in English!");
}
}
}
if (returnNull)
{
return null;
}
else
{
DebugConsole.ThrowError("Text \"" + textTag + "\" not found.");
return textTag;
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(textTag);
if (text != null) { return text; }
}
if (!string.IsNullOrEmpty(fallBackTag))
{
foreach (TextPack textPack in textPacks[Language])
{
string text = textPack.Get(fallBackTag);
if (text != null) { return text; }
}
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
string text = textPack.Get(textTag);
if (text != null)
{
#if DEBUG
DebugConsole.NewMessage("Text \"" + textTag + "\" not found for the language \"" + Language + "\". Using the English text \"" + text + "\" instead.");
#endif
return text;
}
}
}
if (returnNull)
{
return null;
}
else
{
DebugConsole.ThrowError("Text \"" + textTag + "\" not found.");
return textTag;
}
}
}
@@ -418,13 +449,16 @@ namespace Barotrauma
// And: replacement=formatter(value)
public static string GetServerMessage(string serverMessage)
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
}
@@ -588,58 +622,64 @@ namespace Barotrauma
public static List<string> GetAll(string textTag)
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
}
List<string> allText;
List<string> allText;
foreach (TextPack textPack in textPacks[Language])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
foreach (TextPack textPack in textPacks[Language])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
}
return null;
//if text was not found and we're using a language other than English, see if we can find an English version
//may happen, for example, if a user has selected another language and using mods that haven't been translated to that language
if (Language != "English" && textPacks.ContainsKey("English"))
{
foreach (TextPack textPack in textPacks["English"])
{
allText = textPack.GetAll(textTag);
if (allText != null) return allText;
}
}
return null;
}
}
public static List<KeyValuePair<string, string>> GetAllTagTextPairs()
{
if (!textPacks.ContainsKey(Language))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
DebugConsole.ThrowError("No text packs available for the selected language (" + Language + ")! Switching to English...");
Language = "English";
if (!textPacks.ContainsKey(Language))
{
throw new Exception("No text packs available in English!");
}
}
List<KeyValuePair<string, string>> allText = new List<KeyValuePair<string, string>>();
foreach (TextPack textPack in textPacks[Language])
{
allText.AddRange(textPack.GetAllTagTextPairs());
}
return allText;
}
List<KeyValuePair<string, string>> allText = new List<KeyValuePair<string, string>>();
foreach (TextPack textPack in textPacks[Language])
{
allText.AddRange(textPack.GetAllTagTextPairs());
}
return allText;
}
public static string ReplaceGenderPronouns(string text, Gender gender)
@@ -689,35 +729,41 @@ namespace Barotrauma
#if DEBUG
public static void CheckForDuplicates(string lang)
{
if (!textPacks.ContainsKey(lang))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
if (!textPacks.ContainsKey(lang))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.CheckForDuplicates(packIndex);
packIndex++;
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.CheckForDuplicates(packIndex);
packIndex++;
}
}
}
public static void WriteToCSV()
{
string lang = "English";
if (!textPacks.ContainsKey(lang))
lock (mutex)
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
string lang = "English";
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.WriteToCSV(packIndex);
packIndex++;
if (!textPacks.ContainsKey(lang))
{
DebugConsole.ThrowError("No text packs available for the selected language (" + lang + ")!");
return;
}
int packIndex = 0;
foreach (TextPack textPack in textPacks[lang])
{
textPack.WriteToCSV(packIndex);
packIndex++;
}
}
}
#endif

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace Barotrauma
{
public static class TaskPool
{
private struct TaskAction
{
public Task Task;
public Action<Task, object> OnCompletion;
public object UserData;
}
private static List<TaskAction> taskActions = new List<TaskAction>();
private static void AddInternal(Task task, Action<Task, object> onCompletion, object userdata)
{
lock (taskActions)
{
taskActions.Add(new TaskAction() { Task = task, OnCompletion = onCompletion, UserData = userdata });
}
}
public static void Add(Task task, Action<Task> onCompletion)
{
AddInternal(task, (Task t, object obj) => { onCompletion(t); }, null);
}
public static void Add<U>(Task task, U userdata, Action<Task, U> onCompletion) where U : class
{
AddInternal(task, (Task t, object obj) => { onCompletion(t, (U)obj); }, userdata);
}
public static void Add<T>(Task<T> task, Action<Task<T>> onCompletion)
{
AddInternal(task, (Task t, object obj) => { onCompletion((Task<T>)t); }, null);
}
public static void Add<T,U>(Task<T> task, U userdata, Action<Task<T>, U> onCompletion) where U : class
{
AddInternal(task, (Task t, object obj) => { onCompletion((Task<T>)t, (U)obj); }, userdata);
}
public static void Update()
{
lock (taskActions)
{
for (int i = 0; i < taskActions.Count; i++)
{
if (taskActions[i].Task.IsCompleted)
{
taskActions[i].OnCompletion?.Invoke(taskActions[i].Task, taskActions[i].UserData);
taskActions.RemoveAt(i);
i--;
}
}
}
}
public static void PrintTaskExceptions(Task task, string msg)
{
DebugConsole.ThrowError(msg);
foreach (Exception e in task.Exception.InnerExceptions)
{
DebugConsole.ThrowError(e.Message + "\n" + e.StackTrace);
}
}
}
}

View File

@@ -437,5 +437,90 @@ namespace Barotrauma
IEnumerable<string> filtered = splitted.SkipWhile(part => part != currentFolder).Skip(1);
return string.Join("/", filtered);
}
public static string EscapeCharacters(string str)
{
return str.Replace("\\", "\\\\").Replace("\"", "\\\"");
}
public static string UnescapeCharacters(string str)
{
string retVal = "";
for (int i=0;i<str.Length;i++)
{
if (str[i] != '\\')
{
retVal += str[i];
}
else if (i+1<str.Length)
{
if (str[i+1] == '\\')
{
retVal += "\\";
}
else if (str[i+1] == '\"')
{
retVal += "\"";
}
i++;
}
}
return retVal;
}
public static string ParseQuotedArgument(string[] arguments, int startIndex, out int endIndex)
{
#if WINDOWS
endIndex = startIndex + 1;
return arguments[startIndex];
#else
string retVal = "";
int currIndex = startIndex;
bool escaped = false;
if (arguments[startIndex][0] != '\"')
{
endIndex = startIndex+1;
return UnescapeCharacters(arguments[startIndex]);
}
while (currIndex < arguments.Length)
{
for (int i=currIndex == startIndex ? 1 : 0;i<arguments[currIndex].Length;i++)
{
if (!escaped)
{
if (arguments[currIndex][i] == '\\')
{
escaped = true;
}
else if (arguments[currIndex][i] == '\"')
{
endIndex = currIndex+1;
return UnescapeCharacters(retVal);
}
}
else
{
escaped = false;
}
retVal += arguments[currIndex][i];
}
retVal += " ";
currIndex++;
}
endIndex = arguments.Length;
return retVal;
#endif
}
public static string[] MergeArguments(string[] arguments)
{
List<string> mergedArgs = new List<string>();
for (int i=0;i<arguments.Length;)
{
mergedArgs.Add(ParseQuotedArgument(arguments, i, out i));
}
return mergedArgs.ToArray();
}
}
}

View File

@@ -1,3 +1,61 @@
---------------------------------------------------------------------------------------------------------
v0.9.6.0
---------------------------------------------------------------------------------------------------------
Networking/multiplayer fixes:
- Fixed the game occasionally freezing when joining a server.
- Fixed a bug that occasionally caused clients to disconnect with an "entity not found" error in the multiplayer. In technical terms, the issue occurred when the server spawned an entity with a given ID, sent a network event for that entity, and then spawned another entity that took up the ID of the previous entity. This could happen, for example, if in the multiplayer campaign a player happens to get an item with the same ID as an item in the inventory of another player who isn't currently present, and the other player joins the server. There are still most likely additional bugs that can cause similar error messages, so this fix will probably not get rid of the errors entirely.
- Fixed start button staying disabled in the server lobby when switching from the campaign mode to another game mode.
- Fixed clients occasionally getting disconnected due to the campaign store. Happened if a location had changed from one type to another (for example, natural formation to outpost), the players had bought something that wasn't originally available from the location and a new client joined.
- Fixed clients occasionally being prompted to download subs from the server even if they already have a matching sub.
- Fixed server having a password even if the password box is left empty in the in-game "host server" menu when hosting with the Linux version.
- Fixed server name being shortened to one word when hosting from the in-game "host server" menu with the Linux version.
- Fixed server name length not being restricted when set from serversettings.xml or using console commands.
- Fixed "ready to start" tickbox disappearing if a client joins while a round is running.
- Fixed traitors remaining as traitors client-side after restarting the round, allowing them to access the sabotage interface despite not having a traitor objective.
- Fixed characters occasionally not selecting/deselecting a periscope client-side, causing them to walk in place or not grab the periscope.
- Fixed male asian heads not appearing in the server lobby.
- Fixed campaign servers never being filtered out from the server browser when filtering based on game mode.
- Fixed clients not displaying the "could not connect" popup if the initial connection to a server times out.
- Fixed door weld state occasionally getting desynced, causing the door to be welded server-side but possible to open client-side.
Electricity fixes:
- Fixed junction boxes not passing signals to relays.
- Fixed broken junction boxes and relays carrying power.
- Fixed devices not receiving power if they're connected directly to batteries/supercapacitors with no relays or junction boxes in between.
- Fixed light components that receive power from somewhere else than a power input connection not going out when they lose power (e.g. lights on the diving suits).
- Fixed pumps and batteries operating/recharging faster when receiving overvoltage, with no upper limit, making it possible to for example operate pumps at 10x the speed by connecting them to a relay that's receiving 10x more power than is being drawn from it.
- Fixed LightComponents being toggled twice when they receive a signal to the toggle connection.
Misc fixes:
- Fixed "last used" listbox overlapping with the entity visibility tickboxes in the submarine editor on low resolutions.
- Fixed misaligned colliders on the "Shell A Cap 0 deg A/B" wall pieces.
- Fixed links disappearing between linked subs and docking ports when loading in the sub editor.
- Fixed loading freezing for up to 10 seconds if the game cannot fetch the remote content for the main menu (update notifications, changelogs, etc).
- Fixed lights on devices (junction boxes, nav terminals, reactor) being rendered on top of characters.
- Fixed lights lagging a little behind moving objects (e.g. diving suit light).
- Fixed characters being able to grab/heal others when handcuffed.
- Character editor: don't switch to the limb mode when clicking a limb in the joint edit mode. Fixes difficulties in using the joint angle widgets when the widgets are interloping limb source rects.
- Character editor: Fixed the texture path field not being copied if the source character has no texture path defined. In this case, we can use a texture path found in one of the limbs.
- Fixed bots incorrectly reporting problems in the room they are in instead of the room the issues is spotted at.
- Fixed bots reporting broken devices or asking medical attendance even if they are taking care of those things.
- Fixed bots saying "put out a fire in hull" instead of using the name of the room when they extinguish a fire.
- Fixed AITargets staing active even if the entity is not active (for example, the sonar could still be heard by monsters after it's turned off).
- The bots now only report about issues that have not been already been reported.
- Fixed monsters attacking doors that are open.
- Monsters keep pursuing the target if the behavior is "pursue" even when they cannot attack.
- Bots stop operating devices if a player starts operating them (unless they have been ordered to operate the device).
- Fixed bots playing the rewiring sound when repairing an item with a screwdriver.
- Fixed bots sometimes stopping behind a closed door when trying to extinguish a fire.
- Fixed chat messages occasionally extending below the lower bound of the chatbox.
- Fixed searchlight and turret lights disappearing before the light is fully off-screen.
- Waypoint fixes in Kastrull and the drone.
- Fixed Kastrull flooding when the drone is undocked.
- Fixed ballast pumps in Kastrull's drone being off by default.
- Fixed enormous in-game ballistic helmet sprite.
- Fire extinguishers can't be placed inside toolboxes anymore.
- Fixed lobby screen not scaling correctly after changing to a bigger resolution.
---------------------------------------------------------------------------------------------------------
v0.9.5.1
---------------------------------------------------------------------------------------------------------
@@ -8,8 +66,7 @@ v0.9.5.1
- Nerfed Hammerhead Spawns.
- Fixed occasional disconnections when the Hammerhead Matriarch releases her spawns.
- Fixed Hammerhead Matriarch exploding twice when it attacks.
- Fixed bots sometimes getting stuck in an objective loop, causing them to repeatedly drop and pick up
diving suits or spam doors.
- Fixed bots sometimes getting stuck in an objective loop, causing them to repeatedly drop and pick up diving suits or spam doors.
- Fixed bots being allowed to go outside without a diving suit.
- Fixed characters with a rectangular main collider being unable to use path finding.
- Fixed microphone volume scrollbar resetting to the maximum value when opening the settings menu.