(f0d812055) v0.9.9.0

This commit is contained in:
Joonas Rikkonen
2020-04-23 19:19:37 +03:00
parent b647059b93
commit ac37a3b0e4
391 changed files with 15054 additions and 5420 deletions
@@ -335,7 +335,8 @@ namespace Barotrauma
//an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution, //an ad-hoc way of allowing the players to have roughly the same maximum view distance regardless of the resolution,
//while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling //while still keeping the zoom around 1.0 when not looking further away (because otherwise we'd always be downsampling
//on lower resolutions, which doesn't look that good) //on lower resolutions, which doesn't look that good)
float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom, (float)Math.Sqrt(zoomOutAmount)); float newZoom = MathHelper.Lerp(unscaledZoom, scaledZoom,
(GameMain.Config == null || GameMain.Config.EnableMouseLook) ? (float)Math.Sqrt(zoomOutAmount) : 0.3f);
Zoom += (newZoom - zoom) / ZoomSmoothness; Zoom += (newZoom - zoom) / ZoomSmoothness;
@@ -44,7 +44,7 @@ namespace Barotrauma
else else
{ {
//color = Color.WhiteSmoke; //color = Color.WhiteSmoke;
// disable the indicators for structures, because they clutter the debug view // disable the indicators for structures and hulls, because they clutter the debug view
return; return;
} }
ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom); ShapeExtensions.DrawCircle(spriteBatch, pos, SightRange, 100, color, thickness: 1 / Screen.Selected.Cam.Zoom);
@@ -0,0 +1,50 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class WreckAI : IServerSerializable
{
private CoroutineHandle fadeOutRoutine;
partial void FadeOutColors()
{
if (fadeOutRoutine != null)
{
CoroutineManager.StopCoroutines(fadeOutRoutine);
}
fadeOutRoutine = CoroutineManager.StartCoroutine(FadeOutColors(Config.DeadEntityColorFadeOutTime));
}
private IEnumerable<object> FadeOutColors(float time)
{
float timer = 0;
while (timer < time)
{
timer += CoroutineManager.DeltaTime;
float m = MathHelper.Lerp(1, Config.DeadEntityColorMultiplier, MathUtils.InverseLerp(0, time, timer));
foreach (var item in thalamusItems)
{
if (item.Prefab.BrokenSprites.None())
{
Color c = item.prefab.SpriteColor;
item.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
}
}
foreach (var structure in thalamusStructures)
{
Color c = structure.prefab.SpriteColor;
structure.SpriteColor = new Color(c.R / 255f * m, c.G / 255f * m, c.B / 255f * m, c.A / 255f);
}
yield return CoroutineStatus.Running;
}
yield return CoroutineStatus.Success;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
IsAlive = msg.ReadBoolean();
}
}
}
@@ -286,7 +286,7 @@ namespace Barotrauma
{ {
bool inWater = limb.inWater; bool inWater = limb.inWater;
if (character.CurrentHull != null && if (character.CurrentHull != null &&
character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height && character.CurrentHull.Surface > character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height + 5.0f &&
limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent()) limb.SimPosition.Y < ConvertUnits.ToSimUnits(character.CurrentHull.Rect.Y - character.CurrentHull.Rect.Height) + limb.body.GetMaxExtent())
{ {
inWater = true; inWater = true;
@@ -370,6 +370,7 @@ namespace Barotrauma
foreach (var deformation in SpriteDeformations) foreach (var deformation in SpriteDeformations)
{ {
if (character.IsDead && deformation.Params.StopWhenHostIsDead) { continue; } if (character.IsDead && deformation.Params.StopWhenHostIsDead) { continue; }
if (!character.AnimController.InWater && deformation.Params.OnlyInWater) { continue; }
if (deformation.Params.UseMovementSine) if (deformation.Params.UseMovementSine)
{ {
if (this is AnimController animator) if (this is AnimController animator)
@@ -233,7 +233,7 @@ namespace Barotrauma
pressureParticleTimer += pressure * deltaTime; pressureParticleTimer += pressure * deltaTime;
if (pressureParticleTimer > 10.0f) if (pressureParticleTimer > 10.0f)
{ {
Particle p = GameMain.ParticleManager.CreateParticle("waterblood", WorldPosition + Rand.Vector(5.0f), Rand.Vector(10.0f)); GameMain.ParticleManager.CreateParticle(Params.BleedParticleWater, WorldPosition + Rand.Vector(5.0f), Rand.Vector(10.0f));
pressureParticleTimer = 0.0f; pressureParticleTimer = 0.0f;
} }
} }
@@ -355,7 +355,7 @@ namespace Barotrauma
} }
} }
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult) partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun)
{ {
if (attackResult.Damage <= 1.0f || IsDead) { return; } if (attackResult.Damage <= 1.0f || IsDead) { return; }
if (soundTimer < soundInterval * 0.5f) if (soundTimer < soundInterval * 0.5f)
@@ -365,7 +365,7 @@ namespace Barotrauma
} }
} }
partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction) partial void KillProjSpecific(CauseOfDeathType causeOfDeath, Affliction causeOfDeathAffliction, bool log)
{ {
if (GameMain.NetworkMember != null && controlled == this) if (GameMain.NetworkMember != null && controlled == this)
{ {
@@ -444,6 +444,7 @@ namespace Barotrauma
if (item.body != null && !item.body.Enabled) continue; if (item.body != null && !item.body.Enabled) continue;
if (item.ParentInventory != null) continue; if (item.ParentInventory != null) continue;
if (ignoredItems != null && ignoredItems.Contains(item)) continue; if (ignoredItems != null && ignoredItems.Contains(item)) continue;
if (Screen.Selected is SubEditorScreen editor && editor.WiringMode && item.GetComponent<ConnectionPanel>() == null) { continue; }
if (draggingItemToWorld) if (draggingItemToWorld)
{ {
@@ -466,7 +467,7 @@ namespace Barotrauma
//modify the distance based on the size of the trigger (preferring smaller items) //modify the distance based on the size of the trigger (preferring smaller items)
distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (transformedTrigger.Width + transformedTrigger.Height) / 250.0f); distanceToItem *= MathHelper.Lerp(0.05f, 2.0f, (transformedTrigger.Width + transformedTrigger.Height) / 250.0f);
} }
else else if (!item.Prefab.RequireCursorInsideTrigger)
{ {
Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height); Rectangle itemDisplayRect = new Rectangle(item.InteractionRect.X, item.InteractionRect.Y - item.InteractionRect.Height, item.InteractionRect.Width, item.InteractionRect.Height);
@@ -551,7 +552,7 @@ namespace Barotrauma
{ {
if (!enabled) { return; } if (!enabled) { return; }
if (!IsDead && !IsUnconscious) if (!IsDead && !IsIncapacitated)
{ {
if (soundTimer > 0) if (soundTimer > 0)
{ {
@@ -603,6 +604,11 @@ namespace Barotrauma
} }
} }
partial void SetOrderProjSpecific(Order order, string orderOption)
{
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption);
}
public static void AddAllToGUIUpdateList() public static void AddAllToGUIUpdateList()
{ {
for (int i = 0; i < CharacterList.Count; i++) for (int i = 0; i < CharacterList.Count; i++)
@@ -812,6 +818,7 @@ namespace Barotrauma
{ {
if (sounds == null || sounds.Count == 0) { return; } if (sounds == null || sounds.Count == 0) { return; }
if (soundChannel != null && soundChannel.IsPlaying) { return; } if (soundChannel != null && soundChannel.IsPlaying) { return; }
if (GameMain.SoundManager?.Disabled ?? true) { return; }
var matchingSounds = sounds.Where(s => var matchingSounds = sounds.Where(s =>
s.Type == soundType && s.Type == soundType &&
@@ -820,6 +827,7 @@ namespace Barotrauma
var matchingSoundsList = matchingSounds.ToList(); var matchingSoundsList = matchingSounds.ToList();
var selectedSound = matchingSoundsList[Rand.Int(matchingSoundsList.Count)]; var selectedSound = matchingSoundsList[Rand.Int(matchingSoundsList.Count)];
if (selectedSound?.Sound == null) { return; }
soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, CurrentHull); soundChannel = SoundPlayer.PlaySound(selectedSound.Sound, AnimController.WorldPosition, selectedSound.Volume, selectedSound.Range, CurrentHull);
soundTimer = soundInterval; soundTimer = soundInterval;
} }
@@ -2,6 +2,7 @@
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -37,6 +38,9 @@ namespace Barotrauma
} }
} }
private static bool shouldRecreateHudTexts = true;
private static bool heldDownShiftWhenGotHudTexts;
private static bool ShouldDrawInventory(Character character) private static bool ShouldDrawInventory(Character character)
{ {
return return
@@ -61,7 +65,7 @@ namespace Barotrauma
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
if (!character.IsUnconscious && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f)
{ {
if (character.Inventory != null) if (character.Inventory != null)
{ {
@@ -90,7 +94,7 @@ namespace Barotrauma
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
if (!character.IsUnconscious && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f)
{ {
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null) if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null)
{ {
@@ -140,7 +144,11 @@ namespace Barotrauma
else else
{ {
focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f); focusedItemOverlayTimer = Math.Max(focusedItemOverlayTimer - deltaTime, 0.0f);
if (focusedItemOverlayTimer <= 0.0f) focusedItem = null; if (focusedItemOverlayTimer <= 0.0f)
{
focusedItem = null;
shouldRecreateHudTexts = true;
}
} }
} }
@@ -154,6 +162,7 @@ namespace Barotrauma
brokenItemsCheckTimer = 1.0f; brokenItemsCheckTimer = 1.0f;
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; }
if (!item.Repairables.Any(r => item.ConditionPercentage <= r.AIRepairThreshold)) { continue; } if (!item.Repairables.Any(r => item.ConditionPercentage <= r.AIRepairThreshold)) { continue; }
if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; } if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
@@ -201,19 +210,19 @@ namespace Barotrauma
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
} }
if (!character.IsUnconscious && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f)
{ {
if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected) if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
{ {
DrawCharacterHoverTexts(spriteBatch, cam, character); DrawCharacterHoverTexts(spriteBatch, cam, character);
} }
float circleSize;
if (character.FocusedItem != null) if (character.FocusedItem != null)
{ {
if (focusedItem != character.FocusedItem) if (focusedItem != character.FocusedItem)
{ {
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer); focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
shouldRecreateHudTexts = true;
} }
focusedItem = character.FocusedItem; focusedItem = character.FocusedItem;
} }
@@ -221,7 +230,7 @@ namespace Barotrauma
if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay) if (focusedItem != null && focusedItemOverlayTimer > ItemOverlayDelay)
{ {
Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition); Vector2 circlePos = cam.WorldToScreen(focusedItem.DrawPosition);
circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f; float circleSize = Math.Max(focusedItem.Rect.Width, focusedItem.Rect.Height) * 1.5f;
circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f); circleSize = MathHelper.Clamp(circleSize, 45.0f, 100.0f) * Math.Min((focusedItemOverlayTimer - 1.0f) * 5.0f, 1.0f);
if (circleSize > 0.0f) if (circleSize > 0.0f)
{ {
@@ -237,7 +246,14 @@ namespace Barotrauma
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld) if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
{ {
var hudTexts = focusedItem.GetHUDTexts(character); bool shiftDown = PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift);
if(shouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown)
{
shouldRecreateHudTexts = true;
heldDownShiftWhenGotHudTexts = shiftDown;
}
var hudTexts = focusedItem.GetHUDTexts(character, shouldRecreateHudTexts);
shouldRecreateHudTexts = false;
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X); int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
@@ -309,7 +325,7 @@ namespace Barotrauma
(int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f), (int)(HUDLayoutSettings.BottomRightInfoArea.Y + HUDLayoutSettings.BottomRightInfoArea.Height * 0.1f),
(int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2), (int)(HUDLayoutSettings.BottomRightInfoArea.Width / 2),
(int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f))); (int)(HUDLayoutSettings.BottomRightInfoArea.Height * 0.7f)));
character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2((int)(-4 * GUI.Scale), (int)(2 * GUI.Scale)), targetWidth: HUDLayoutSettings.PortraitArea.Width, true); character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, 4 * GUI.Scale), targetWidth: HUDLayoutSettings.PortraitArea.Width, true);
} }
mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && !character.ShouldLockHud(); mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && !character.ShouldLockHud();
if (mouseOnPortrait) if (mouseOnPortrait)
@@ -327,7 +343,7 @@ namespace Barotrauma
} }
} }
if (!character.IsUnconscious && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f)
{ {
if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null) if (character.IsHumanoid && character.SelectedCharacter != null && character.SelectedCharacter.Inventory != null)
{ {
@@ -10,84 +10,116 @@ namespace Barotrauma
{ {
partial class CharacterInfo partial class CharacterInfo
{ {
public const float BgScale = 1.2f;
private static Sprite infoAreaPortraitBG; private static Sprite infoAreaPortraitBG;
public static void Init() public static void Init()
{ {
infoAreaPortraitBG = new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0); infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.Sprites[GUIComponent.ComponentState.None][0].Sprite;
new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
} }
public GUIFrame CreateInfoFrame(GUIFrame frame) public GUIComponent CreateInfoFrame(GUIFrame frame, bool returnParent, Sprite permissionIcon = null)
{ {
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), frame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.1f) }) var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.874f, 0.58f), frame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) })
{ {
Stretch = true, RelativeSpacing = 0.05f
RelativeSpacing = 0.03f //Stretch = true
}; };
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform), isHorizontal: true) var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.322f), paddedFrame.RectTransform), isHorizontal: true);
{
RelativeSpacing = 0.05f,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1.0f), headerArea.RectTransform), new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2())); onDraw: (sb, component) => DrawInfoFrameCharacterIcon(sb, component.Rect));
ScalableFont font = paddedFrame.Rect.Width < 280 ? GUI.SmallFont : GUI.Font; ScalableFont font = paddedFrame.Rect.Width < 280 ? GUI.SmallFont : GUI.Font;
var headerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 1.0f), headerArea.RectTransform)) var headerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(0.575f, 1.0f), headerArea.RectTransform))
{ {
RelativeSpacing = 0.05f, RelativeSpacing = 0.02f,
Stretch = true Stretch = true
}; };
Color? nameColor = null; Color? nameColor = null;
if (Job != null) { nameColor = Job.Prefab.UIColor; } if (Job != null) { nameColor = Job.Prefab.UIColor; }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
Name, textColor: nameColor, font: GUI.LargeFont) GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), ToolBox.LimitString(Name, GUI.Font, headerTextArea.Rect.Width), textColor: nameColor, font: GUI.Font)
{ {
Padding = Vector4.Zero, ForceUpperCase = true,
AutoScaleHorizontal = true Padding = Vector4.Zero
}; };
if (permissionIcon != null)
{
Point iconSize = permissionIcon.SourceRect.Size;
int iconWidth = (int)((float)characterNameBlock.Rect.Height / iconSize.Y * iconSize.X);
new GUIImage(new RectTransform(new Point(iconWidth, characterNameBlock.Rect.Height), characterNameBlock.RectTransform) { AbsoluteOffset = new Point(-iconWidth - 2, 0) }, permissionIcon) { IgnoreLayoutGroups = true };
}
if (Job != null) if (Job != null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), Job.Name, textColor: Job.Prefab.UIColor, font: font)
Job.Name, textColor: Job.Prefab.UIColor, font: font); {
Padding = Vector4.Zero
};
} }
if (personalityTrait != null) if (personalityTrait != null)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + personalityTrait.Name.Replace(" ", ""))), font: font)
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + personalityTrait.Name.Replace(" ", ""))), font: font); {
Padding = Vector4.Zero
};
} }
//spacing if (Job != null && (Character == null || !Character.IsDead))
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), paddedFrame.RectTransform), style: null);
if (Job != null)
{ {
var skillsArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
{
Stretch = true
};
var skills = Job.Skills; var skills = Job.Skills;
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level)); skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero };
TextManager.Get("Skills") + ":", font: font);
foreach (Skill skill in skills) foreach (Skill skill in skills)
{ {
Color textColor = Color.White * (0.5f + skill.Level / 200.0f); Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), var skillName = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font) { Padding = Vector4.Zero };
TextManager.Get("SkillName." + skill.Identifier), textColor: textColor, font: font); new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), ((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), skillName.RectTransform), }
((int)skill.Level).ToString(), textColor: textColor, font: font, textAlignment: Alignment.CenterRight); }
else if (Character != null && Character.IsDead)
{
var deadArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
{
Stretch = true
};
string deadDescription = TextManager.AddPunctuation(':', TextManager.Get("deceased") + "\n" + Character.CauseOfDeath.Affliction?.CauseOfDeathDescription ??
TextManager.AddPunctuation(':', TextManager.Get("CauseOfDeath"), TextManager.Get("CauseOfDeath." + Character.CauseOfDeath.Type.ToString())));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), deadArea.RectTransform), deadDescription, textColor: GUI.Style.Red, font: font, textAlignment: Alignment.TopLeft) { Padding = Vector4.Zero };
}
if (returnParent)
{
return frame;
}
else
{
return paddedFrame;
} }
} }
return frame; private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect)
{
Vector2 targetAreaSize = componentRect.Size.ToVector2();
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
DrawIcon(sb, componentRect.Location.ToVector2() + headSprite.size / 2 * scale, targetAreaSize);
} }
public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData) public GUIFrame CreateCharacterFrame(GUIComponent parent, string text, object userData)
@@ -171,6 +203,7 @@ namespace Barotrauma
public void DrawBackground(SpriteBatch spriteBatch) public void DrawBackground(SpriteBatch spriteBatch)
{ {
if (infoAreaPortraitBG == null) { return; }
infoAreaPortraitBG.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea.Location.ToVector2(), Color.White, Vector2.Zero, 0.0f, infoAreaPortraitBG.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea.Location.ToVector2(), Color.White, Vector2.Zero, 0.0f,
scale: new Vector2( scale: new Vector2(
HUDLayoutSettings.BottomRightInfoArea.Width / (float)infoAreaPortraitBG.SourceRect.Width, HUDLayoutSettings.BottomRightInfoArea.Width / (float)infoAreaPortraitBG.SourceRect.Width,
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Linq; using System.Linq;
@@ -277,7 +278,7 @@ namespace Barotrauma
break; break;
case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 3); int eventType = msg.ReadRangedInteger(0, 4);
switch (eventType) switch (eventType)
{ {
case 0: case 0:
@@ -337,6 +338,36 @@ namespace Barotrauma
info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f); info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
} }
break; break;
case 4:
int attackLimbIndex = msg.ReadByte();
UInt16 targetEntityID = msg.ReadUInt16();
int targetLimbIndex = msg.ReadByte();
if (attackLimbIndex >= AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds ({attackLimbIndex})");
break;
}
Limb attackLimb = AnimController.Limbs[attackLimbIndex];
IDamageable targetEntity = FindEntityByID(targetEntityID) as IDamageable;
Limb targetLimb = null;
if (targetEntity == null)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target entity not found (ID {targetEntityID})");
break;
}
if (targetEntity is Character targetCharacter)
{
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds ({targetLimbIndex})");
break;
}
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
}
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
break; break;
@@ -384,6 +415,36 @@ namespace Barotrauma
character = Create(speciesName, position, seed, info, GameMain.Client.ID != ownerId, hasAi); character = Create(speciesName, position, seed, info, GameMain.Client.ID != ownerId, hasAi);
character.ID = id; character.ID = id;
character.TeamID = (TeamType)teamID; character.TeamID = (TeamType)teamID;
// Check if the character has a current order
if (inc.ReadBoolean())
{
int orderPrefabIndex = inc.ReadByte();
Entity targetEntity = FindEntityByID(inc.ReadUInt16());
Character orderGiver = inc.ReadBoolean() ? FindEntityByID(inc.ReadUInt16()) as Character : null;
int orderOptionIndex = inc.ReadByte();
if (orderPrefabIndex >= 0 && orderPrefabIndex < Order.PrefabList.Count)
{
var orderPrefab = Order.PrefabList[orderPrefabIndex];
if (!orderPrefab.MustSetTarget || (targetEntity != null && (targetEntity as Item).Components.Any(c => c?.GetType() == orderPrefab.ItemComponentType)))
{
character.SetOrder(
new Order(orderPrefab, targetEntity, (targetEntity as Item)?.Components.FirstOrDefault(c => c?.GetType() == orderPrefab.ItemComponentType), orderGiver: orderGiver),
orderOptionIndex >= 0 && orderOptionIndex < orderPrefab.Options.Length ? orderPrefab.Options[orderOptionIndex] : null,
orderGiver, speak: false);
}
else
{
DebugConsole.ThrowError("Could not set order \"" + orderPrefab.Identifier + "\" for character \"" + character.Name + "\" because required target entity was not found.");
}
}
else
{
DebugConsole.ThrowError("Invalid order prefab index - index (" + orderPrefabIndex + ") out of bounds.");
}
}
bool containsStatusData = inc.ReadBoolean(); bool containsStatusData = inc.ReadBoolean();
if (containsStatusData) if (containsStatusData)
{ {
@@ -14,8 +14,8 @@ namespace Barotrauma
public SoundType Type => Params.State; public SoundType Type => Params.State;
public Gender Gender => Params.Gender; public Gender Gender => Params.Gender;
public float Volume => roundSound.Volume; public float Volume => roundSound == null ? 0.0f : roundSound.Volume;
public float Range => roundSound.Range; public float Range => roundSound == null ? 0.0f : roundSound.Range;
public Sound Sound => roundSound?.Sound; public Sound Sound => roundSound?.Sound;
public CharacterSound(CharacterParams.SoundParams soundParams) public CharacterSound(CharacterParams.SoundParams soundParams)
@@ -246,11 +246,13 @@ namespace Barotrauma
} }
private GUIFrame healthBarHolder; private GUIFrame healthBarHolder;
private Point healthBarOffset private Point healthBarOffset
{ {
get get
{ {
return new Point(5 - (int)Math.Ceiling(1 - 1 * GUI.Scale), (int)Math.Min(Math.Ceiling(17 * GUI.Scale), 20)); // 0.38775510204f = percentage of offset before reaching the healthbar portion of the graphic going from bottom upwards
return new Point(2, (int)(HUDLayoutSettings.HealthBarArea.Size.Y * 0.38775510204f));
} }
} }
@@ -258,7 +260,7 @@ namespace Barotrauma
{ {
get get
{ {
return new Point(healthBarHolder.Rect.Width - (int)Math.Ceiling(Math.Min(46 * GUI.Scale, 53)), (int)(healthBarHolder.Rect.Height - Math.Min(23 * GUI.Scale, 25)) / 2); return new Point((int)Math.Ceiling(HUDLayoutSettings.HealthBarArea.Size.X - 45 * GUI.Scale), (int)(healthBarHolder.Rect.Height - Math.Min(23 * GUI.Scale, 25)) / 2);
} }
} }
@@ -303,7 +305,7 @@ namespace Barotrauma
healthShadowSize = 1.0f; healthShadowSize = 1.0f;
healthBar = new GUIProgressBar(new RectTransform(healthBarSize, healthBarHolder.RectTransform, Anchor.BottomRight), healthBar = new GUIProgressBar(new RectTransform(healthBarSize, healthBarHolder.RectTransform, Anchor.BottomRight),
barSize: 1.0f, color: GUIColorSettings.HealthBarColorHigh, style: horizontal ? "CharacterHealthBarSlider" : "GUIProgressBarVertical", showFrame: false) barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: horizontal ? "CharacterHealthBarSlider" : "GUIProgressBarVertical", showFrame: false)
{ {
HoverCursor = CursorState.Hand, HoverCursor = CursorState.Hand,
Enabled = true, Enabled = true,
@@ -503,8 +505,6 @@ namespace Barotrauma
Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ? Character.Controlled.AnimController.Anim = (Character.Controlled.AnimController.Anim == AnimController.Animation.CPR) ?
AnimController.Animation.None : AnimController.Animation.CPR; AnimController.Animation.None : AnimController.Animation.CPR;
button.Selected = Character.Controlled.AnimController.Anim == AnimController.Animation.CPR;
selectedCharacter.AnimController.ResetPullJoints(); selectedCharacter.AnimController.ResetPullJoints();
if (GameMain.Client != null) if (GameMain.Client != null)
@@ -520,8 +520,10 @@ namespace Barotrauma
UpdateAlignment(); UpdateAlignment();
suicideButton = new GUIButton(new RectTransform(new Vector2(0.06f, 0.02f), GUI.Canvas, Anchor.TopCenter) suicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 0.02f), GUI.Canvas, Anchor.TopCenter)
{ MinSize = new Point(150, 20), RelativeOffset = new Vector2(0.0f, 0.01f) }, {
MinSize = new Point(150, 20), RelativeOffset = new Vector2(0.0f, 0.01f)
},
TextManager.Get("GiveInButton"), style: "GUIButtonLarge") TextManager.Get("GiveInButton"), style: "GUIButtonLarge")
{ {
ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"), ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"),
@@ -647,10 +649,14 @@ namespace Barotrauma
bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f); bloodParticleTimer -= deltaTime * (affliction.Strength / 10.0f);
if (bloodParticleTimer <= 0.0f) if (bloodParticleTimer <= 0.0f)
{ {
bool inWater = Character.AnimController.InWater;
float bloodParticleSize = MathHelper.Lerp(0.5f, 1.0f, affliction.Strength / 100.0f); float bloodParticleSize = MathHelper.Lerp(0.5f, 1.0f, affliction.Strength / 100.0f);
if (!Character.AnimController.InWater) bloodParticleSize *= 2.0f; if (!inWater)
{
bloodParticleSize *= 2.0f;
}
var blood = GameMain.ParticleManager.CreateParticle( var blood = GameMain.ParticleManager.CreateParticle(
Character.AnimController.InWater ? "waterblood" : "blooddrop", inWater ? Character.Params.BleedParticleWater : Character.Params.BleedParticleAir,
targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull); targetLimb.WorldPosition, Rand.Vector(affliction.Strength), 0.0f, Character.AnimController.CurrentHull);
if (blood != null) if (blood != null)
@@ -761,7 +767,8 @@ namespace Barotrauma
{ {
OpenHealthWindow = null; OpenHealthWindow = null;
} }
else if (Character.Controlled == Character && Character.Controlled.FocusedCharacter == null) else if (Character.Controlled == Character &&
(Character.Controlled.FocusedCharacter?.CharacterHealth == null || !Character.Controlled.FocusedCharacter.CharacterHealth.UseHealthWindow))
{ {
OpenHealthWindow = this; OpenHealthWindow = this;
forceAfflictionContainerUpdate = true; forceAfflictionContainerUpdate = true;
@@ -847,7 +854,7 @@ namespace Barotrauma
} }
else else
{ {
healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(DisplayedVitality / MaxVitality, GUIColorSettings.HealthBarColorLow, GUIColorSettings.HealthBarColorMedium, GUIColorSettings.HealthBarColorHigh); healthBar.Color = healthWindowHealthBar.Color = ToolBox.GradientLerp(DisplayedVitality / MaxVitality, GUI.Style.HealthBarColorLow, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorHigh);
healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f; healthBar.HoverColor = healthWindowHealthBar.HoverColor = healthBar.Color * 2.0f;
healthBar.BarSize = healthWindowHealthBar.BarSize = healthBar.BarSize = healthWindowHealthBar.BarSize =
(DisplayedVitality > 0.0f) ? (DisplayedVitality > 0.0f) ?
@@ -934,7 +941,7 @@ namespace Barotrauma
healthBar.State = GUIComponent.ComponentState.None; healthBar.State = GUIComponent.ComponentState.None;
} }
suicideButton.Visible = Character == Character.Controlled && Character.IsUnconscious && !Character.IsDead; suicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated;
cprButton.Visible = cprButton.Visible =
Character == Character.Controlled?.SelectedCharacter Character == Character.Controlled?.SelectedCharacter
@@ -942,6 +949,10 @@ namespace Barotrauma
&& !Character.IsDead && !Character.IsDead
&& openHealthWindow == this; && openHealthWindow == this;
cprButton.IgnoreLayoutGroups = !cprButton.Visible; cprButton.IgnoreLayoutGroups = !cprButton.Visible;
cprButton.Selected =
Character.Controlled != null &&
Character == Character.Controlled.SelectedCharacter &&
Character.Controlled.AnimController.Anim == AnimController.Animation.CPR;
cprFrame.RectTransform.Resize(new Vector2(0.7f, 1.0f)); cprFrame.RectTransform.Resize(new Vector2(0.7f, 1.0f));
cprButton.RectTransform.Resize(new Vector2(1.0f, 1.0f)); cprButton.RectTransform.Resize(new Vector2(1.0f, 1.0f));
@@ -1132,11 +1143,11 @@ namespace Barotrauma
{ {
if (prefab.IsBuff) if (prefab.IsBuff)
{ {
return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, GUIColorSettings.BuffColorLow, GUIColorSettings.BuffColorMedium, GUIColorSettings.BuffColorHigh); return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, GUI.Style.BuffColorLow, GUI.Style.BuffColorMedium, GUI.Style.BuffColorHigh);
} }
else else
{ {
return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, GUIColorSettings.DebuffColorLow, GUIColorSettings.DebuffColorMedium, GUIColorSettings.DebuffColorHigh); return ToolBox.GradientLerp(affliction.Strength / prefab.MaxStrength, GUI.Style.DebuffColorLow, GUI.Style.DebuffColorMedium, GUI.Style.DebuffColorHigh);
} }
} }
else else
@@ -1870,10 +1881,11 @@ namespace Barotrauma
healthBarHolder.Visible = value; healthBarHolder.Visible = value;
} }
private readonly List<Pair<AfflictionPrefab, float>> newAfflictions = new List<Pair<AfflictionPrefab, float>>();
private readonly List<Triplet<LimbHealth, AfflictionPrefab, float>> newLimbAfflictions = new List<Triplet<LimbHealth, AfflictionPrefab, float>>();
public void ClientRead(IReadMessage inc) public void ClientRead(IReadMessage inc)
{ {
List<Pair<AfflictionPrefab, float>> newAfflictions = new List<Pair<AfflictionPrefab, float>>(); newAfflictions.Clear();
byte afflictionCount = inc.ReadByte(); byte afflictionCount = inc.ReadByte();
for (int i = 0; i < afflictionCount; i++) for (int i = 0; i < afflictionCount; i++)
{ {
@@ -1913,7 +1925,7 @@ namespace Barotrauma
} }
} }
List<Triplet<LimbHealth, AfflictionPrefab, float>> newLimbAfflictions = new List<Triplet<LimbHealth, AfflictionPrefab, float>>(); newLimbAfflictions.Clear();
byte limbAfflictionCount = inc.ReadByte(); byte limbAfflictionCount = inc.ReadByte();
for (int i = 0; i < limbAfflictionCount; i++) for (int i = 0; i < limbAfflictionCount; i++)
{ {
@@ -83,22 +83,22 @@ namespace Barotrauma
// pos.Y = -pos.Y; // pos.Y = -pos.Y;
// ShapeExtensions.DrawPoint(spriteBatch, pos, GUI.Style.Red, size: 5); // ShapeExtensions.DrawPoint(spriteBatch, pos, GUI.Style.Red, size: 5);
//} //}
return;
// A debug visualisation on the bezier curve between limbs. // A debug visualisation on the bezier curve between limbs.
var start = LimbA.WorldPosition; /*var start = LimbA.WorldPosition;
var end = LimbB.WorldPosition; var end = LimbB.WorldPosition;
var jointAPos = ConvertUnits.ToDisplayUnits(LocalAnchorA); var jointAPos = ConvertUnits.ToDisplayUnits(LocalAnchorA);
var control = start + Vector2.Transform(jointAPos, Matrix.CreateRotationZ(LimbA.Rotation)); var control = start + Vector2.Transform(jointAPos, Matrix.CreateRotationZ(LimbA.Rotation));
start.Y = -start.Y; start.Y = -start.Y;
end.Y = -end.Y; end.Y = -end.Y;
control.Y = -control.Y; control.Y = -control.Y;
//GUI.DrawRectangle(spriteBatch, start, Vector2.One * 5, Color.White, true); GUI.DrawRectangle(spriteBatch, start, Vector2.One * 5, Color.White, true);
//GUI.DrawRectangle(spriteBatch, end, Vector2.One * 5, Color.Black, true); GUI.DrawRectangle(spriteBatch, end, Vector2.One * 5, Color.Black, true);
//GUI.DrawRectangle(spriteBatch, control, Vector2.One * 5, Color.Black, true); GUI.DrawRectangle(spriteBatch, control, Vector2.One * 5, Color.Black, true);
//GUI.DrawLine(spriteBatch, start, end, Color.White); GUI.DrawLine(spriteBatch, start, end, Color.White);
//GUI.DrawLine(spriteBatch, start, control, Color.Black); GUI.DrawLine(spriteBatch, start, control, Color.Black);
//GUI.DrawLine(spriteBatch, control, end, Color.Black); GUI.DrawLine(spriteBatch, control, end, Color.Black);
GUI.DrawBezierWithDots(spriteBatch, start, end, control, 1000, GUI.Style.Red); GUI.DrawBezierWithDots(spriteBatch, start, end, control, 1000, GUI.Style.Red);*/
} }
} }
@@ -110,6 +110,7 @@ namespace Barotrauma
private float wetTimer; private float wetTimer;
private float dripParticleTimer; private float dripParticleTimer;
private float deadTimer;
/// <summary> /// <summary>
/// Note that different limbs can share the same deformations. /// Note that different limbs can share the same deformations.
@@ -418,15 +419,24 @@ namespace Barotrauma
} }
} }
partial void AddDamageProjSpecific(Vector2 simPosition, List<Affliction> afflictions, bool playSound, List<DamageModifier> appliedDamageModifiers) partial void AddDamageProjSpecific(IEnumerable<Affliction> afflictions, bool playSound, IEnumerable<DamageModifier> appliedDamageModifiers)
{ {
float bleedingDamage = character.CharacterHealth.DoesBleed ? afflictions.FindAll(a => a is AfflictionBleeding).Sum(a => a.GetVitalityDecrease(character.CharacterHealth)) : 0; float bleedingDamage = character.CharacterHealth.DoesBleed ? afflictions.Where(a => a is AfflictionBleeding).Sum(a => a.GetVitalityDecrease(character.CharacterHealth)) : 0;
float damage = afflictions.FindAll(a => a.Prefab.AfflictionType == "damage").Sum(a => a.GetVitalityDecrease(character.CharacterHealth)); float damage = afflictions.Where(a => a.Prefab.AfflictionType == "damage").Sum(a => a.GetVitalityDecrease(character.CharacterHealth));
float damageMultiplier = 1; float damageMultiplier = 1;
foreach (DamageModifier damageModifier in appliedDamageModifiers) foreach (DamageModifier damageModifier in appliedDamageModifiers)
{
foreach (var afflictionPrefab in AfflictionPrefab.List)
{
if (damageModifier.MatchesAffliction(afflictionPrefab.Identifier, afflictionPrefab.AfflictionType))
{
if (afflictionPrefab.Effects.Any(e => e.MaxVitalityDecrease > 0))
{ {
damageMultiplier *= damageModifier.DamageMultiplier; damageMultiplier *= damageModifier.DamageMultiplier;
} }
}
}
}
if (playSound) if (playSound)
{ {
string damageSoundType = (bleedingDamage > damage) ? "LimbSlash" : "LimbBlunt"; string damageSoundType = (bleedingDamage > damage) ? "LimbSlash" : "LimbBlunt";
@@ -477,13 +487,21 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (!body.Enabled) return; if (!body.Enabled) { return; }
if (!character.IsDead) if (!character.IsDead)
{ {
DamageOverlayStrength -= deltaTime; DamageOverlayStrength -= deltaTime;
BurnOverlayStrength -= deltaTime; BurnOverlayStrength -= deltaTime;
} }
else
{
var spriteParams = Params.GetSprite();
if (spriteParams.DeadColorTime > 0 && deadTimer < spriteParams.DeadColorTime)
{
deadTimer += deltaTime;
}
}
if (inWater) if (inWater)
{ {
@@ -524,7 +542,12 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null) public void Draw(SpriteBatch spriteBatch, Camera cam, Color? overrideColor = null)
{ {
float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f; float brightness = 1.0f - (burnOverLayStrength / 100.0f) * 0.5f;
Color color = new Color(brightness, brightness, brightness); var spriteParams = Params.GetSprite();
Color color = new Color(spriteParams.Color.R / 255f * brightness, spriteParams.Color.G / 255f * brightness, spriteParams.Color.B / 255f * brightness, spriteParams.Color.A / 255f);
if (deadTimer > 0)
{
color = Color.Lerp(color, spriteParams.DeadColor, MathUtils.InverseLerp(0, spriteParams.DeadColorTime, deadTimer));
}
color = overrideColor ?? color; color = overrideColor ?? color;
@@ -594,13 +617,19 @@ namespace Barotrauma
foreach (var decorativeSprite in DecorativeSprites) foreach (var decorativeSprite in DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Color c = new Color(decorativeSprite.Color.R / 255f * brightness, decorativeSprite.Color.G / 255f * brightness, decorativeSprite.Color.B / 255f * brightness, decorativeSprite.Color.A / 255f);
if (deadTimer > 0)
{
c = Color.Lerp(c, spriteParams.DeadColor, MathUtils.InverseLerp(0, Params.GetSprite().DeadColorTime, deadTimer));
}
c = overrideColor ?? c;
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
var ca = (float)Math.Cos(-body.Rotation); var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation); var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(body.DrawPosition.X + transformedOffset.X, -(body.DrawPosition.Y + transformedOffset.Y)), c,
-body.Rotation + rotation, Scale, spriteEffect, -body.Rotation + rotation, decorativeSprite.Scale * Scale, spriteEffect,
depth: decorativeSprite.Sprite.Depth); depth: decorativeSprite.Sprite.Depth);
} }
float depthStep = 0.000001f; float depthStep = 0.000001f;
@@ -12,6 +12,7 @@ using System.Xml.Linq;
using System.Globalization; using System.Globalization;
using FarseerPhysics; using FarseerPhysics;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Steam;
namespace Barotrauma namespace Barotrauma
{ {
@@ -217,6 +218,8 @@ namespace Barotrauma
case "toggleupperhud": case "toggleupperhud":
case "togglecharacternames": case "togglecharacternames":
case "fpscounter": case "fpscounter":
case "dumptofile":
case "findentityids":
return true; return true;
default: default:
return client.HasConsoleCommandPermission(command); return client.HasConsoleCommandPermission(command);
@@ -423,7 +426,8 @@ namespace Barotrauma
{ {
if (args.Length > 0) if (args.Length > 0)
{ {
Submarine.Load(string.Join(" ", args), true); var subInfo = new SubmarineInfo(string.Join(" ", args));
Submarine.MainSub = Submarine.Load(subInfo, true);
} }
GameMain.SubEditorScreen.Select(); GameMain.SubEditorScreen.Select();
}, isCheat: true)); }, isCheat: true));
@@ -464,16 +468,23 @@ namespace Barotrauma
} }
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks debug logging.", (string[] args) =>
{
SteamManager.NetworkingDebugLog = !SteamManager.NetworkingDebugLog;
}));
AssignRelayToServer("kick", false); AssignRelayToServer("kick", false);
AssignRelayToServer("kickid", false); AssignRelayToServer("kickid", false);
AssignRelayToServer("ban", false); AssignRelayToServer("ban", false);
AssignRelayToServer("banid", false); AssignRelayToServer("banid", false);
AssignRelayToServer("dumpids", false); AssignRelayToServer("dumpids", false);
AssignRelayToServer("dumptofile", false);
AssignRelayToServer("findentityids", false); AssignRelayToServer("findentityids", false);
AssignRelayToServer("campaigninfo", false); AssignRelayToServer("campaigninfo", false);
AssignRelayToServer("help", false); AssignRelayToServer("help", false);
AssignRelayToServer("verboselogging", false); AssignRelayToServer("verboselogging", false);
AssignRelayToServer("freecam", false); AssignRelayToServer("freecam", false);
AssignRelayToServer("steamnetdebug", false);
#if DEBUG #if DEBUG
AssignRelayToServer("crash", false); AssignRelayToServer("crash", false);
AssignRelayToServer("simulatedlatency", false); AssignRelayToServer("simulatedlatency", false);
@@ -513,13 +524,14 @@ namespace Barotrauma
AssignOnExecute("explosion", (string[] args) => AssignOnExecute("explosion", (string[] args) =>
{ {
Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
float range = 500, force = 10, damage = 50, structureDamage = 10, empStrength = 0.0f; float range = 500, force = 10, damage = 50, structureDamage = 10, itemDamage = 100, empStrength = 0.0f;
if (args.Length > 0) float.TryParse(args[0], out range); if (args.Length > 0) float.TryParse(args[0], out range);
if (args.Length > 1) float.TryParse(args[1], out force); if (args.Length > 1) float.TryParse(args[1], out force);
if (args.Length > 2) float.TryParse(args[2], out damage); if (args.Length > 2) float.TryParse(args[2], out damage);
if (args.Length > 3) float.TryParse(args[3], out structureDamage); if (args.Length > 3) float.TryParse(args[3], out structureDamage);
if (args.Length > 4) float.TryParse(args[4], out empStrength); if (args.Length > 4) float.TryParse(args[4], out itemDamage);
new Explosion(range, force, damage, structureDamage, empStrength).Explode(explosionPos, null); if (args.Length > 5) float.TryParse(args[5], out empStrength);
new Explosion(range, force, damage, structureDamage, itemDamage, empStrength).Explode(explosionPos, null);
}); });
AssignOnExecute("teleportcharacter|teleport", (string[] args) => AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
@@ -834,7 +846,7 @@ namespace Barotrauma
return; return;
} }
if (Submarine.SaveCurrent(System.IO.Path.Combine(Submarine.SavePath, fileName + ".sub"))) if (Submarine.MainSub.SaveAs(System.IO.Path.Combine(SubmarineInfo.SavePath, fileName + ".sub")))
{ {
NewMessage("Sub saved", Color.Green); NewMessage("Sub saved", Color.Green);
} }
@@ -843,7 +855,8 @@ namespace Barotrauma
commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) => commands.Add(new Command("load|loadsub", "load [submarine name]: Load a submarine.", (string[] args) =>
{ {
if (args.Length == 0) return; if (args.Length == 0) return;
Submarine.Load(string.Join(" ", args), true); SubmarineInfo subInfo = new SubmarineInfo(string.Join(" ", args));
Submarine.Load(subInfo, true);
})); }));
commands.Add(new Command("cleansub", "", (string[] args) => commands.Add(new Command("cleansub", "", (string[] args) =>
@@ -1086,23 +1099,69 @@ namespace Barotrauma
if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) { return; } if (args.Length != 2 || Screen.Selected != GameMain.SubEditorScreen) { return; }
foreach (MapEntity me in MapEntity.SelectedList) foreach (MapEntity me in MapEntity.SelectedList)
{ {
if (me is ISerializableEntity serializableEntity) bool propertyFound = false;
if (!(me is ISerializableEntity serializableEntity)) { continue; }
if (serializableEntity.SerializableProperties == null) { continue; }
if (serializableEntity.SerializableProperties.TryGetValue(args[0].ToLowerInvariant(), out SerializableProperty property))
{ {
if (serializableEntity.SerializableProperties == null) propertyFound = true;
object prevValue = property.GetValue(me);
if (property.TrySetValue(me, args[1]))
{ {
continue; NewMessage($"Changed the value \"{args[0]}\" from {(prevValue?.ToString() ?? null)} to {args[1]} on entity \"{me.ToString()}\".", Color.LightGreen);
} }
if (!serializableEntity.SerializableProperties.TryGetValue(args[0].ToLowerInvariant(), out SerializableProperty property)) else
{ {
NewMessage("Property \"" + args[0] + "\" not found in the entity \"" + me.ToString() + "\".", Color.Orange); NewMessage($"Failed to set the value of \"{args[0]}\" to \"{args[1]}\" on the entity \"{me.ToString()}\".", Color.Orange);
continue;
} }
if (!property.TrySetValue(me, args[1])) }
if (me is Item item)
{ {
NewMessage("Failed to set the value of \"" + args[0] + "\" to \"" + args[1] + "\" on the entity \"" + me.ToString() + "\".", Color.Orange); foreach (ItemComponent ic in item.Components)
{
ic.SerializableProperties.TryGetValue(args[0].ToLowerInvariant(), out SerializableProperty componentProperty);
if (componentProperty == null) { continue; }
propertyFound = true;
object prevValue = componentProperty.GetValue(ic);
if (componentProperty.TrySetValue(ic, args[1]))
{
NewMessage($"Changed the value \"{args[0]}\" from {prevValue} to {args[1]} on item \"{me.ToString()}\", component \"{ic.GetType().Name}\".", Color.LightGreen);
}
else
{
NewMessage($"Failed to set the value of \"{args[0]}\" to \"{args[1]}\" on the item \"{me.ToString()}\", component \"{ic.GetType().Name}\".", Color.Orange);
} }
} }
} }
if (!propertyFound)
{
NewMessage($"Property \"{args[0]}\" not found in the entity \"{me.ToString()}\".", Color.Orange);
}
}
},
() =>
{
List<string> propertyList = new List<string>();
foreach (MapEntity me in MapEntity.SelectedList)
{
if (!(me is ISerializableEntity serializableEntity)) { continue; }
if (serializableEntity.SerializableProperties == null) { continue; }
propertyList.AddRange(serializableEntity.SerializableProperties.Select(p => p.Key));
if (me is Item item)
{
foreach (ItemComponent ic in item.Components)
{
propertyList.AddRange(ic.SerializableProperties.Select(p => p.Key));
}
}
}
return new string[][]
{
propertyList.Distinct().ToArray(),
new string[0]
};
})); }));
commands.Add(new Command("checkmissingloca", "", (string[] args) => commands.Add(new Command("checkmissingloca", "", (string[] args) =>
@@ -1135,7 +1194,7 @@ namespace Barotrauma
} }
} }
foreach (Submarine sub in Submarine.SavedSubmarines) foreach (SubmarineInfo sub in SubmarineInfo.SavedSubmarines)
{ {
string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant(); string nameIdentifier = "submarine.name." + sub.Name.ToLowerInvariant();
if (!tags[language].Contains(nameIdentifier)) if (!tags[language].Contains(nameIdentifier))
@@ -2269,7 +2328,8 @@ namespace Barotrauma
} }
try try
{ {
Submarine spawnedSub = Submarine.Load(args[0], false); SubmarineInfo subInfo = new SubmarineInfo(args[0]);
Submarine spawnedSub = Submarine.Load(subInfo, false);
spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition)); spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
} }
catch (Exception e) catch (Exception e)
@@ -2347,7 +2407,7 @@ namespace Barotrauma
switch (firstArg) switch (firstArg)
{ {
case "name": case "name":
var sprites = Sprite.LoadedSprites.Where(s => s.Name?.ToLowerInvariant() == secondArg.ToLowerInvariant()); var sprites = Sprite.LoadedSprites.Where(s => s.Name != null && s.Name.Equals(secondArg, StringComparison.OrdinalIgnoreCase));
if (sprites.Any()) if (sprites.Any())
{ {
foreach (var s in sprites) foreach (var s in sprites)
@@ -2363,7 +2423,7 @@ namespace Barotrauma
} }
case "identifier": case "identifier":
case "id": case "id":
sprites = Sprite.LoadedSprites.Where(s => s.EntityID?.ToLowerInvariant() == secondArg.ToLowerInvariant()); sprites = Sprite.LoadedSprites.Where(s => s.EntityID != null && s.EntityID.Equals(secondArg, StringComparison.OrdinalIgnoreCase));
if (sprites.Any()) if (sprites.Any())
{ {
foreach (var s in sprites) foreach (var s in sprites)
@@ -24,7 +24,7 @@ namespace Barotrauma
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "icon") { continue; } if (!subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase)) { continue; }
Icon = new Sprite(subElement); Icon = new Sprite(subElement);
IconColor = subElement.GetAttributeColor("color", Color.White); IconColor = subElement.GetAttributeColor("color", Color.White);
} }
@@ -6,14 +6,39 @@ namespace Barotrauma
partial class SalvageMission : Mission partial class SalvageMission : Mission
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{
bool usedExistingItem = msg.ReadBoolean();
if (usedExistingItem)
{
ushort id = msg.ReadUInt16();
item = Entity.FindEntityByID(id) as Item;
if (item == null)
{
throw new System.Exception("Error in SalvageMission.ClientReadInitial: failed to find item " + id + " (mission: " + Prefab.Identifier + ")");
}
}
else
{ {
item = Item.ReadSpawnData(msg); item = Item.ReadSpawnData(msg);
if (item == null) if (item == null)
{ {
throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")"); throw new System.Exception("Error in SalvageMission.ClientReadInitial: spawned item was null (mission: " + Prefab.Identifier + ")");
} }
}
int executedEffectCount = msg.ReadByte();
for (int i = 0; i < executedEffectCount; i++)
{
int index1 = msg.ReadByte();
int index2 = msg.ReadByte();
var selectedEffect = statusEffects[index1][index2];
item.ApplyStatusEffect(selectedEffect, selectedEffect.type, deltaTime: 1.0f, worldPosition: item.Position);
}
if (item.body != null)
{
item.body.FarseerBody.BodyType = BodyType.Kinematic; item.body.FarseerBody.BodyType = BodyType.Kinematic;
} }
} }
}
} }
@@ -7,76 +7,6 @@ using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
public class ColorData
{
public int StartIndex, EndIndex;
public Color Color;
private const char colorDefinitionIndicator = '‖';
private const char lineChangeIndicator = '\n';
private const string colorDefinitionStartString = "‖color:";
private const string coloringEndDefinition = "‖color:end‖";
public static List<ColorData> GetColorData(string text, out string sanitizedText)
{
List<ColorData> textColors = null;
if (text != null && text.IndexOf(colorDefinitionIndicator) != -1 && text.Contains(colorDefinitionStartString))
{
textColors = new List<ColorData>();
List<int> lineChangeIndexes = null;
int currentIndex = text.IndexOf(lineChangeIndicator);
if (currentIndex != -1)
{
lineChangeIndexes = new List<int>();
lineChangeIndexes.Add(currentIndex);
int startIndex = currentIndex + 1;
while (true)
{
if (startIndex >= text.Length) break;
currentIndex = text.IndexOf(lineChangeIndicator, startIndex);
if (currentIndex == -1) break;
lineChangeIndexes.Add(currentIndex);
startIndex = currentIndex + 1;
}
}
while (text.IndexOf(colorDefinitionStartString) != -1)
{
ColorData colorData = new ColorData();
int colorDefinitionStartIndex = text.IndexOf(colorDefinitionStartString);
int colorDefinitionEndIndex = text.IndexOf(colorDefinitionIndicator, colorDefinitionStartIndex + 1);
string[] colorDefinition = text.Substring(colorDefinitionStartIndex + colorDefinitionStartString.Length, colorDefinitionEndIndex - colorDefinitionStartIndex - colorDefinitionStartString.Length).Split(',');
colorData.StartIndex = colorDefinitionStartIndex;
colorData.Color = new Color(int.Parse(colorDefinition[0]), int.Parse(colorDefinition[1]), int.Parse(colorDefinition[2]));
text = text.Remove(colorDefinitionStartIndex, colorDefinitionEndIndex - colorDefinitionStartIndex + 1);
colorData.EndIndex = text.IndexOf(coloringEndDefinition);
text = text.Remove(colorData.EndIndex, coloringEndDefinition.Length);
if (lineChangeIndexes != null)
{
for (int i = 0; i < lineChangeIndexes.Count; i++)
{
if (colorData.StartIndex > lineChangeIndexes[i])
{
colorData.StartIndex--;
colorData.EndIndex--;
}
}
}
textColors.Add(colorData);
}
}
sanitizedText = text;
return textColors;
}
}
public class ScalableFont : IDisposable public class ScalableFont : IDisposable
{ {
private static List<ScalableFont> FontList = new List<ScalableFont>(); private static List<ScalableFont> FontList = new List<ScalableFont>();
@@ -492,12 +422,12 @@ namespace Barotrauma
} }
} }
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<ColorData> colorData) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData)
{ {
DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, colorData); DrawStringWithColors(sb, text, position, color, rotation, origin, new Vector2(scale), se, layerDepth, richTextData);
} }
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<ColorData> colorData) public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth, List<RichTextData> richTextData)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
@@ -505,8 +435,8 @@ namespace Barotrauma
Vector2 currentPos = position; Vector2 currentPos = position;
Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation)); Vector2 advanceUnit = rotation == 0.0f ? Vector2.UnitX : new Vector2((float)Math.Cos(rotation), (float)Math.Sin(rotation));
int colorDataIndex = 0; int richTextDataIndex = 0;
ColorData currentColorData = colorData[colorDataIndex]; RichTextData currentRichTextData = richTextData[richTextDataIndex];
for (int i = 0; i < text.Length; i++) for (int i = 0; i < text.Length; i++)
{ {
@@ -527,15 +457,19 @@ namespace Barotrauma
Color currentTextColor; Color currentTextColor;
if (currentColorData != null && i > currentColorData.EndIndex + lineNum) if (currentRichTextData != null && i > currentRichTextData.EndIndex + lineNum)
{ {
colorDataIndex++; richTextDataIndex++;
currentColorData = colorDataIndex < colorData.Count ? colorData[colorDataIndex] : null; currentRichTextData = richTextDataIndex < richTextData.Count ? richTextData[richTextDataIndex] : null;
} }
if (currentColorData != null && currentColorData.StartIndex + lineNum <= i && i <= currentColorData.EndIndex + lineNum) if (currentRichTextData != null && currentRichTextData.StartIndex + lineNum <= i && i <= currentRichTextData.EndIndex + lineNum)
{ {
currentTextColor = currentColorData.Color; currentTextColor = currentRichTextData.Color ?? color;
if (!string.IsNullOrEmpty(currentRichTextData.Metadata))
{
currentTextColor = Color.Lerp(currentTextColor, Color.White, 0.5f);
}
} }
else else
{ {
@@ -52,7 +52,20 @@ namespace Barotrauma
public GUITextBox InputBox { get; private set; } public GUITextBox InputBox { get; private set; }
public GUIButton ToggleButton; private GUIButton toggleButton;
public GUIButton ToggleButton
{
get => toggleButton;
set
{
if (toggleButton != null)
{
toggleButton.RectTransform.Parent = null;
}
toggleButton = value;
}
}
private GUIButton showNewMessagesButton; private GUIButton showNewMessagesButton;
@@ -190,22 +203,49 @@ namespace Barotrauma
var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null, var msgHolder = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.0f), chatBox.Content.RectTransform, Anchor.TopCenter), style: null,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f); color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f);
GUITextBlock senderNameBlock = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) }, GUITextBlock senderNameTimestamp = new GUITextBlock(new RectTransform(new Vector2(0.98f, 0.0f), msgHolder.RectTransform) { AbsoluteOffset = new Point((int)(5 * GUI.Scale), 0) },
ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null) ChatMessage.GetTimeStamp(), textColor: Color.LightGray, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null)
{ {
CanBeFocused = true CanBeFocused = true
}; };
if (!string.IsNullOrEmpty(senderName)) if (!string.IsNullOrEmpty(senderName))
{ {
new GUITextBlock(new RectTransform(new Vector2(0.8f, 1.0f), senderNameBlock.RectTransform) { AbsoluteOffset = new Point((int)(senderNameBlock.TextSize.X), 0) }, var senderNameBlock = new GUIButton(new RectTransform(new Vector2(0.8f, 1.0f), senderNameTimestamp.RectTransform) { AbsoluteOffset = new Point((int)(senderNameTimestamp.TextSize.X), 0) },
senderName, textColor: senderColor, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null) senderName, textAlignment: Alignment.TopLeft, style: null, color: Color.Transparent)
{ {
CanBeFocused = true TextBlock =
{
Padding = Vector4.Zero
},
Font = GUI.SmallFont,
CanBeFocused = true,
ForceUpperCase = false,
UserData = message.SenderClient,
OnClicked = (_, o) =>
{
if (!(o is Client client)) { return false; }
GameMain.NetLobbyScreen?.SelectPlayer(client);
return true;
},
OnSecondaryClicked = (_, o) =>
{
if (!(o is Client client)) { return false; }
GameMain.GameSession?.CrewManager?.CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client);
return true;
},
Text = senderName
}; };
senderNameBlock.RectTransform.NonScaledSize = senderNameBlock.TextBlock.TextSize.ToPoint();
senderNameBlock.TextBlock.OverrideTextColor(senderColor);
if (senderNameBlock.UserData != null)
{
senderNameBlock.TextBlock.HoverTextColor = Color.White;
}
} }
var msgText =new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform) var msgText =new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), msgHolder.RectTransform)
{ AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameBlock == null ? 0 : senderNameBlock.Rect.Height) }, { AbsoluteOffset = new Point((int)(10 * GUI.Scale), senderNameTimestamp == null ? 0 : senderNameTimestamp.Rect.Height) },
displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true, displayedText, textColor: message.Color, font: GUI.SmallFont, textAlignment: Alignment.TopLeft, style: null, wrap: true,
color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f) color: ((chatBox.Content.CountChildren % 2) == 0) ? Color.Transparent : Color.Black * 0.1f)
{ {
@@ -230,7 +270,7 @@ namespace Barotrauma
msgHolder.RectTransform.SizeChanged -= Recalculate; msgHolder.RectTransform.SizeChanged -= Recalculate;
//resize the holder to match the size of the message and add some spacing //resize the holder to match the size of the message and add some spacing
msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue); msgText.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - msgText.RectTransform.AbsoluteOffset.X, int.MaxValue);
senderNameBlock.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameBlock.RectTransform.AbsoluteOffset.X, int.MaxValue); senderNameTimestamp.RectTransform.MaxSize = new Point(msgHolder.Rect.Width - senderNameTimestamp.RectTransform.AbsoluteOffset.X, int.MaxValue);
msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText()); msgHolder.Children.ForEach(c => (c as GUITextBlock)?.CalculateHeightFromText());
msgHolder.RectTransform.Resize(new Point(msgHolder.Rect.Width, msgHolder.Children.Sum(c => c.Rect.Height) + (int)(10 * GUI.Scale)), resizeChildren: false); 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; msgHolder.RectTransform.SizeChanged += Recalculate;
@@ -247,6 +287,11 @@ namespace Barotrauma
showNewMessagesButton.Visible = true; showNewMessagesButton.Visible = true;
} }
if (message.Type == ChatMessageType.Server && message.ChangeType != PlayerConnectionChangeType.None)
{
TabMenu.StorePlayerConnectionChangeMessage(message);
}
if (!ToggleOpen) if (!ToggleOpen)
{ {
var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip") var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
@@ -149,7 +149,7 @@ namespace Barotrauma
Point size = new Point(0, 0); Point size = new Point(0, 0);
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "size") { continue; } if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue)); Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y) if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{ {
@@ -348,6 +348,12 @@ namespace Barotrauma
} }
IEnumerable<string> files = null; IEnumerable<string> files = null;
if (currentFileTypePattern == null)
{
files = Directory.GetFiles(currentDirectory);
}
else
{
foreach (string pattern in currentFileTypePattern.Split(',')) foreach (string pattern in currentFileTypePattern.Split(','))
{ {
string patternTrimmed = pattern.Trim(); string patternTrimmed = pattern.Trim();
@@ -361,6 +367,7 @@ namespace Barotrauma
files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed)); files = files.Concat(Directory.EnumerateFiles(currentDirectory, patternTrimmed));
} }
} }
}
foreach (var file in files) foreach (var file in files)
{ {
@@ -26,7 +26,8 @@ namespace Barotrauma
Click, Click,
PickItem, PickItem,
PickItemFail, PickItemFail,
DropItem DropItem,
PopupMenu
} }
public enum CursorState public enum CursorState
@@ -82,6 +83,9 @@ namespace Barotrauma
public static float Scale => (GameMain.GraphicsWidth / ReferenceResolution.X + GameMain.GraphicsHeight / ReferenceResolution.Y) / 2.0f * GameSettings.HUDScale; public static float Scale => (GameMain.GraphicsWidth / ReferenceResolution.X + GameMain.GraphicsHeight / ReferenceResolution.Y) / 2.0f * GameSettings.HUDScale;
public static float xScale => GameMain.GraphicsWidth / ReferenceResolution.X * GameSettings.HUDScale; public static float xScale => GameMain.GraphicsWidth / ReferenceResolution.X * GameSettings.HUDScale;
public static float yScale => GameMain.GraphicsHeight / ReferenceResolution.Y * GameSettings.HUDScale; public static float yScale => GameMain.GraphicsHeight / ReferenceResolution.Y * GameSettings.HUDScale;
public static int IntScale(float f) => (int)(f * Scale);
public static int IntScaleFloor(float f) => (int)Math.Floor(f * Scale);
public static int IntScaleCeiling(float f) => (int) Math.Ceiling(f * Scale);
public static float HorizontalAspectRatio => GameMain.GraphicsWidth / (float)GameMain.GraphicsHeight; public static float HorizontalAspectRatio => GameMain.GraphicsWidth / (float)GameMain.GraphicsHeight;
public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth; public static float VerticalAspectRatio => GameMain.GraphicsHeight / (float)GameMain.GraphicsWidth;
public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y); public static float RelativeHorizontalAspectRatio => HorizontalAspectRatio / (ReferenceResolution.X / ReferenceResolution.Y);
@@ -242,6 +246,7 @@ namespace Barotrauma
sounds[(int)GUISoundType.RadioMessage] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/RadioMsg.ogg", false); sounds[(int)GUISoundType.RadioMessage] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/RadioMsg.ogg", false);
sounds[(int)GUISoundType.DeadMessage] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/DeadMsg.ogg", false); sounds[(int)GUISoundType.DeadMessage] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/DeadMsg.ogg", false);
sounds[(int)GUISoundType.Click] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/Click.ogg", false); sounds[(int)GUISoundType.Click] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/Click.ogg", false);
sounds[(int)GUISoundType.PopupMenu] = GameMain.SoundManager.LoadSound("Content/Sounds/UI/PopupMenu.ogg", false);
sounds[(int)GUISoundType.PickItem] = GameMain.SoundManager.LoadSound("Content/Sounds/PickItem.ogg", false); sounds[(int)GUISoundType.PickItem] = GameMain.SoundManager.LoadSound("Content/Sounds/PickItem.ogg", false);
sounds[(int)GUISoundType.PickItemFail] = GameMain.SoundManager.LoadSound("Content/Sounds/PickItemFail.ogg", false); sounds[(int)GUISoundType.PickItemFail] = GameMain.SoundManager.LoadSound("Content/Sounds/PickItemFail.ogg", false);
@@ -494,9 +499,27 @@ namespace Barotrauma
if (MouseOn != null) if (MouseOn != null)
{ {
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 500, 20), RectTransform mouseOnRect = MouseOn.RectTransform;
$"Selected UI Element: {MouseOn.GetType().Name} ({ (MouseOn.Style?.Element.Name.LocalName ?? "no style") }, {MouseOn.Rect})", bool isAbsoluteOffsetInUse = mouseOnRect.AbsoluteOffset != Point.Zero || mouseOnRect.RelativeOffset == Vector2.Zero;
Color.LightGreen, Color.Black * 0.5f, 0, SmallFont);
string selectedString = $"Selected UI Element: {MouseOn.GetType().Name} ({ MouseOn.Style?.Element.Name.LocalName ?? "no style" }, {MouseOn.Rect}";
string offsetString = $"Relative Offset: {mouseOnRect.RelativeOffset} | Absolute Offset: {(isAbsoluteOffsetInUse ? mouseOnRect.AbsoluteOffset : mouseOnRect.ParentRect.MultiplySize(mouseOnRect.RelativeOffset))}{(isAbsoluteOffsetInUse ? "" : " (Calculated from RelativeOffset)")}";
string anchorPivotString = $"Anchor: {mouseOnRect.Anchor} | Pivot: {mouseOnRect.Pivot}";
Vector2 selectedStringSize = SmallFont.MeasureString(selectedString);
Vector2 offsetStringSize = SmallFont.MeasureString(offsetString);
Vector2 anchorPivotStringSize = SmallFont.MeasureString(anchorPivotString);
int padding = IntScale(10);
int yPos = padding;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)selectedStringSize.X - padding, yPos), selectedString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)selectedStringSize.Y + padding / 2;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)offsetStringSize.X - padding, yPos), offsetString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)offsetStringSize.Y + padding / 2;
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (int)anchorPivotStringSize.X - padding, yPos), anchorPivotString, Color.LightGreen, Color.Black, 0, SmallFont);
yPos += (int)anchorPivotStringSize.Y + padding / 2;
} }
} }
@@ -519,6 +542,34 @@ namespace Barotrauma
MouseOn.DrawToolTip(spriteBatch); MouseOn.DrawToolTip(spriteBatch);
} }
if (SubEditorScreen.IsSubEditor())
{
// Draw our "infinite stack" on the cursor
switch (SubEditorScreen.DraggedItemPrefab)
{
case ItemPrefab itemPrefab:
{
var sprite = itemPrefab.InventoryIcon ?? itemPrefab.sprite;
sprite?.Draw(spriteBatch, PlayerInput.MousePosition, scale: Math.Min(64 / sprite.size.X, 64 / sprite.size.Y) * Scale);
break;
}
case ItemAssemblyPrefab iPrefab:
{
var (x, y) = PlayerInput.MousePosition;
foreach (var pair in iPrefab.DisplayEntities)
{
Rectangle dRect = pair.Second;
dRect = new Rectangle(x: (int)(dRect.X * iPrefab.Scale + x),
y: (int)(dRect.Y * iPrefab.Scale - y),
width: (int)(dRect.Width * iPrefab.Scale),
height: (int)(dRect.Height * iPrefab.Scale));
pair.First.DrawPlacing(spriteBatch, dRect, pair.First.Scale * iPrefab.Scale);
}
break;
}
}
}
if (GameMain.WindowActive && !HideCursor) if (GameMain.WindowActive && !HideCursor)
{ {
spriteBatch.End(); spriteBatch.End();
@@ -539,6 +590,10 @@ namespace Barotrauma
GameMain.GameScreen.PostProcessEffect.Parameters["blurDistance"].SetValue(0.001f * aberrationStrength); GameMain.GameScreen.PostProcessEffect.Parameters["blurDistance"].SetValue(0.001f * aberrationStrength);
GameMain.GameScreen.PostProcessEffect.Parameters["chromaticAberrationStrength"].SetValue(new Vector3(-0.025f, -0.01f, -0.05f) * GameMain.GameScreen.PostProcessEffect.Parameters["chromaticAberrationStrength"].SetValue(new Vector3(-0.025f, -0.01f, -0.05f) *
(float)(PerlinNoise.CalculatePerlin(aberrationT, aberrationT, 0) + 0.5f) * aberrationStrength); (float)(PerlinNoise.CalculatePerlin(aberrationT, aberrationT, 0) + 0.5f) * aberrationStrength);
Matrix.CreateOrthographicOffCenter(0, GameMain.GraphicsWidth, GameMain.GraphicsHeight, 0, 0, -1, out Matrix projection);
GameMain.GameScreen.PostProcessEffect.Parameters["MatrixTransform"].SetValue(projection);
GameMain.GameScreen.PostProcessEffect.CurrentTechnique = GameMain.GameScreen.PostProcessEffect.Techniques["BlurChromaticAberration"]; GameMain.GameScreen.PostProcessEffect.CurrentTechnique = GameMain.GameScreen.PostProcessEffect.Techniques["BlurChromaticAberration"];
GameMain.GameScreen.PostProcessEffect.CurrentTechnique.Passes[0].Apply(); GameMain.GameScreen.PostProcessEffect.CurrentTechnique.Passes[0].Apply();
@@ -778,6 +833,8 @@ namespace Barotrauma
if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; } if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; }
if (GUIScrollBar.DraggingBar != null) { return GUIScrollBar.DraggingBar.Bar.HoverCursor; } if (GUIScrollBar.DraggingBar != null) { return GUIScrollBar.DraggingBar.Bar.HoverCursor; }
if (SubEditorScreen.IsSubEditor() && SubEditorScreen.DraggedItemPrefab != null) { return CursorState.Hand; }
// Wire cursors // Wire cursors
if (Character.Controlled != null) if (Character.Controlled != null)
{ {
@@ -814,8 +871,7 @@ namespace Barotrauma
case SubEditorScreen editor: case SubEditorScreen editor:
{ {
// Portrait area // Portrait area
if ((editor.CharacterMode || editor.WiringMode) && if (editor.WiringMode && HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition))
HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition))
{ {
return CursorState.Hand; return CursorState.Hand;
} }
@@ -1135,7 +1191,7 @@ namespace Barotrauma
font.DrawString(sb, text, pos, color); font.DrawString(sb, text, pos, color);
} }
public static void DrawStringWithColors(SpriteBatch sb, Vector2 pos, string text, Color color, List<ColorData> colorData, Color? backgroundColor = null, int backgroundPadding = 0, ScalableFont font = null, float depth = 0.0f) public static void DrawStringWithColors(SpriteBatch sb, Vector2 pos, string text, Color color, List<RichTextData> richTextData, Color? backgroundColor = null, int backgroundPadding = 0, ScalableFont font = null, float depth = 0.0f)
{ {
if (font == null) font = Font; if (font == null) font = Font;
if (backgroundColor != null) if (backgroundColor != null)
@@ -1144,7 +1200,7 @@ namespace Barotrauma
DrawRectangle(sb, pos - Vector2.One * backgroundPadding, textSize + Vector2.One * 2.0f * backgroundPadding, (Color)backgroundColor, true, depth, 5); DrawRectangle(sb, pos - Vector2.One * backgroundPadding, textSize + Vector2.One * 2.0f * backgroundPadding, (Color)backgroundColor, true, depth, 5);
} }
font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, colorData); font.DrawStringWithColors(sb, text, pos, color, 0.0f, Vector2.Zero, 1f, SpriteEffects.None, depth, richTextData);
} }
public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1) public static void DrawRectangle(SpriteBatch sb, Vector2 start, Vector2 size, Color clr, bool isFilled = false, float depth = 0.0f, int thickness = 1)
@@ -1922,6 +1978,19 @@ namespace Barotrauma
return true; return true;
}; };
} }
else if (GameMain.GameSession.GameMode is SubTestMode)
{
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor"))
{
OnClicked = (btn, userdata) =>
{
GameMain.GameSession.GameMode.End("");
return true;
}
};
button.OnClicked += TogglePauseMenu;
}
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound)) else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound))
{ {
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("EndRound")) new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("EndRound"))
@@ -1,24 +0,0 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public class GUIColorSettings
{
// Inventory
public static Color EquipmentSlotIconColor = new Color(99, 70, 64);
// Health HUD
public static Color BuffColorLow = Color.LightGreen;
public static Color BuffColorMedium = Color.Green;
public static Color BuffColorHigh = Color.DarkGreen;
public static Color DebuffColorLow = Color.DarkSalmon;
public static Color DebuffColorMedium = Color.Red;
public static Color DebuffColorHigh = Color.DarkRed;
public static Color HealthBarColorLow = Color.Red;
public static Color HealthBarColorMedium = Color.Orange;
public static Color HealthBarColorHigh = new Color(78, 114, 88);
}
}
@@ -18,6 +18,9 @@ namespace Barotrauma
public CursorState HoverCursor = CursorState.Default; public CursorState HoverCursor = CursorState.Default;
public delegate bool SecondaryButtonDownHandler(GUIComponent component, object userData);
public SecondaryButtonDownHandler OnSecondaryClicked;
public IEnumerable<GUIComponent> Children => RectTransform.Children.Select(c => c.GUIComponent); public IEnumerable<GUIComponent> Children => RectTransform.Children.Select(c => c.GUIComponent);
public T GetChild<T>() where T : GUIComponent public T GetChild<T>() where T : GUIComponent
@@ -202,12 +205,12 @@ namespace Barotrauma
set set
{ {
RawToolTip = value; RawToolTip = value;
TooltipColorData = ColorData.GetColorData(value, out value); TooltipRichTextData = RichTextData.GetRichTextData(value, out value);
toolTip = value; toolTip = value;
} }
} }
public List<ColorData> TooltipColorData = null; public List<RichTextData> TooltipRichTextData = null;
public GUIComponentStyle Style public GUIComponentStyle Style
{ {
@@ -451,6 +454,15 @@ namespace Barotrauma
protected virtual void Update(float deltaTime) protected virtual void Update(float deltaTime)
{ {
if (!Visible) return; if (!Visible) return;
if (CanBeFocused && OnSecondaryClicked != null)
{
if (GUI.IsMouseOn(this) && PlayerInput.SecondaryMouseButtonClicked())
{
OnSecondaryClicked?.Invoke(this, userData);
}
}
if (flashTimer > 0.0f) if (flashTimer > 0.0f)
{ {
flashTimer -= deltaTime; flashTimer -= deltaTime;
@@ -638,10 +650,10 @@ namespace Barotrauma
public void DrawToolTip(SpriteBatch spriteBatch) public void DrawToolTip(SpriteBatch spriteBatch)
{ {
if (!Visible) return; if (!Visible) return;
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipColorData); DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect, TooltipRichTextData);
} }
public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement, List<ColorData> colorData = null) public static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle targetElement, List<RichTextData> richTextData = null)
{ {
if (Tutorials.Tutorial.ContentRunning) { return; } if (Tutorials.Tutorial.ContentRunning) { return; }
@@ -651,7 +663,7 @@ namespace Barotrauma
if (toolTipBlock == null || (string)toolTipBlock.userData != toolTip) if (toolTipBlock == null || (string)toolTipBlock.userData != toolTip)
{ {
toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), colorData, toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip"); toolTipBlock = new GUITextBlock(new RectTransform(new Point(width, height), null), richTextData, toolTip, font: GUI.SmallFont, wrap: true, style: "GUIToolTip");
toolTipBlock.RectTransform.NonScaledSize = new Point( toolTipBlock.RectTransform.NonScaledSize = new Point(
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z), (int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).X + padding.X + toolTipBlock.Padding.X + toolTipBlock.Padding.Z),
(int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W)); (int)(GUI.SmallFont.MeasureString(toolTipBlock.WrappedText).Y + padding.Y + toolTipBlock.Padding.Y + toolTipBlock.Padding.W));
@@ -787,8 +799,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() == "conditional" && if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase) && !CheckConditional(subElement))
!CheckConditional(subElement))
{ {
return null; return null;
} }
@@ -837,7 +848,7 @@ namespace Barotrauma
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() == "conditional") { continue; } if (subElement.Name.ToString().Equals("conditional", StringComparison.OrdinalIgnoreCase)) { continue; }
FromXML(subElement, component is GUIListBox listBox ? listBox.Content.RectTransform : component.RectTransform); FromXML(subElement, component is GUIListBox listBox ? listBox.Content.RectTransform : component.RectTransform);
} }
@@ -1005,7 +1016,7 @@ namespace Barotrauma
private static GUIFrame LoadGUIFrame(XElement element, RectTransform parent) private static GUIFrame LoadGUIFrame(XElement element, RectTransform parent)
{ {
string style = element.GetAttributeString("style", element.Name.ToString().ToLowerInvariant() == "spacing" ? null : ""); string style = element.GetAttributeString("style", element.Name.ToString().Equals("spacing", StringComparison.OrdinalIgnoreCase) ? null : "");
if (style == "null") { style = null; } if (style == "null") { style = null; }
return new GUIFrame(RectTransform.Load(element, parent), style: style); return new GUIFrame(RectTransform.Load(element, parent), style: style);
} }
@@ -1,12 +1,17 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma namespace Barotrauma
{ {
public class GUIImage : GUIComponent public class GUIImage : GUIComponent
{ {
//paths of the textures that are being loaded asynchronously
private static readonly HashSet<string> activeTextureLoads = new HashSet<string>();
public float Rotation; public float Rotation;
private Sprite sprite; private Sprite sprite;
@@ -15,7 +20,11 @@ namespace Barotrauma
private bool crop; private bool crop;
private bool scaleToFit; private readonly bool scaleToFit;
private bool lazyLoaded, loading;
public bool LoadAsynchronously;
public bool Crop public bool Crop
{ {
@@ -75,7 +84,6 @@ namespace Barotrauma
private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT) private GUIImage(RectTransform rectT, Sprite sprite, Rectangle? sourceRect, bool scaleToFit, string style) : base(style, rectT)
{ {
this.scaleToFit = scaleToFit; this.scaleToFit = scaleToFit;
sprite?.EnsureLazyLoaded();
Sprite = sprite; Sprite = sprite;
if (sourceRect.HasValue) if (sourceRect.HasValue)
{ {
@@ -94,19 +102,45 @@ namespace Barotrauma
Scale = 1.0f; Scale = 1.0f;
} }
else else
{
if (Sprite != null && !Sprite.LazyLoad)
{ {
rectT.SizeChanged += RecalculateScale; rectT.SizeChanged += RecalculateScale;
} }
}
Enabled = true; Enabled = true;
} }
protected override void Draw(SpriteBatch spriteBatch) protected override void Draw(SpriteBatch spriteBatch)
{ {
if (!Visible) return; if (!Visible || loading) { return; }
if (Parent != null) { State = Parent.State; } if (Parent != null) { State = Parent.State; }
if (OverrideState != null) { State = OverrideState.Value; } if (OverrideState != null) { State = OverrideState.Value; }
if (Sprite != null && Sprite.LazyLoad && !lazyLoaded)
{
if (LoadAsynchronously)
{
loading = true;
TaskPool.Add(LoadTextureAsync(), (Task) =>
{
loading = false;
lazyLoaded = true;
RectTransform.SizeChanged += RecalculateScale;
RecalculateScale();
});
return;
}
else
{
Sprite.EnsureLazyLoaded();
RectTransform.SizeChanged += RecalculateScale;
RecalculateScale();
lazyLoaded = true;
}
}
Color currentColor = GetColor(State); Color currentColor = GetColor(State);
if (BlendState != null) if (BlendState != null)
@@ -146,9 +180,52 @@ namespace Barotrauma
private void RecalculateScale() private void RecalculateScale()
{ {
if (sourceRect == Rectangle.Empty && sprite != null)
{
sourceRect = sprite.SourceRect;
}
Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ? Scale = sprite == null || sprite.SourceRect.Width == 0 || sprite.SourceRect.Height == 0 ?
1.0f : 1.0f :
Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height); Math.Min(RectTransform.Rect.Width / (float)sprite.SourceRect.Width, RectTransform.Rect.Height / (float)sprite.SourceRect.Height);
} }
private async Task<bool> LoadTextureAsync()
{
await Task.Yield();
bool wait = true;
{
//if another GUIImage is already loading the same texture, wait for it to finish
while (wait)
{
await Task.Delay(5);
lock (activeTextureLoads)
{
wait = activeTextureLoads.Contains(Sprite.FullPath);
}
}
}
try
{
lock (activeTextureLoads)
{
activeTextureLoads.Add(Sprite.FullPath);
}
Sprite.EnsureLazyLoaded();
}
finally
{
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 10);
while (!Sprite.Loaded && DateTime.Now < timeOut)
{
await Task.Delay(5);
}
lock (activeTextureLoads)
{
activeTextureLoads.Remove(Sprite.FullPath);
}
}
return true;
}
} }
} }
@@ -76,7 +76,18 @@ namespace Barotrauma
public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT) public GUILayoutGroup(RectTransform rectT, bool isHorizontal = false, Anchor childAnchor = Anchor.TopLeft) : base(null, rectT)
{ {
#if DEBUG
if (GameMain.DebugDraw)
{
CanBeFocused = true;
}
else
{
#endif
CanBeFocused = false; CanBeFocused = false;
#if DEBUG
}
#endif
this.isHorizontal = isHorizontal; this.isHorizontal = isHorizontal;
this.childAnchor = childAnchor; this.childAnchor = childAnchor;
@@ -71,6 +71,11 @@ namespace Barotrauma
} }
} }
/// <summary>
/// true if mouse down should select elements instead of mouse up
/// </summary>
private bool useMouseDownToSelect = false;
private Vector4? overridePadding; private Vector4? overridePadding;
public Vector4 Padding public Vector4 Padding
{ {
@@ -80,7 +85,11 @@ namespace Barotrauma
if (Style == null) { return Vector4.Zero; } if (Style == null) { return Vector4.Zero; }
return Style.Padding; return Style.Padding;
} }
set { overridePadding = value; } set
{
dimensionsNeedsRecalculation = true;
overridePadding = value;
}
} }
public GUIComponent SelectedComponent public GUIComponent SelectedComponent
@@ -182,10 +191,11 @@ namespace Barotrauma
public GUIComponent DraggedElement => draggedElement; public GUIComponent DraggedElement => draggedElement;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param> /// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true) : base(style, rectT) public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT)
{ {
CanBeFocused = true; CanBeFocused = true;
selected = new List<GUIComponent>(); selected = new List<GUIComponent>();
this.useMouseDownToSelect = useMouseDownToSelect;
ContentBackground = new GUIFrame(new RectTransform(Vector2.One, rectT), style) ContentBackground = new GUIFrame(new RectTransform(Vector2.One, rectT), style)
{ {
CanBeFocused = false CanBeFocused = false
@@ -237,7 +247,7 @@ namespace Barotrauma
UpdateDimensions(); UpdateDimensions();
} }
private void UpdateDimensions() public void UpdateDimensions()
{ {
dimensionsNeedsRecalculation = false; dimensionsNeedsRecalculation = false;
ContentBackground.RectTransform.Resize(Rect.Size); ContentBackground.RectTransform.Resize(Rect.Size);
@@ -403,7 +413,10 @@ namespace Barotrauma
if (Enabled && CanBeFocused && child.CanBeFocused && (GUI.IsMouseOn(child)) && child.Rect.Contains(PlayerInput.MousePosition)) if (Enabled && CanBeFocused && child.CanBeFocused && (GUI.IsMouseOn(child)) && child.Rect.Contains(PlayerInput.MousePosition))
{ {
child.State = ComponentState.Hover; child.State = ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonClicked())
var mouseDown = useMouseDownToSelect ? PlayerInput.PrimaryMouseButtonDown() : PlayerInput.PrimaryMouseButtonClicked();
if (mouseDown)
{ {
Select(i, autoScroll: false); Select(i, autoScroll: false);
} }
@@ -426,7 +439,7 @@ namespace Barotrauma
} }
else else
{ {
child.State = ComponentState.None; child.State = !child.ExternalHighlight ? ComponentState.None : ComponentState.Hover;
} }
} }
} }
@@ -249,7 +249,10 @@ namespace Barotrauma
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint(); InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f); openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn))
{
inGameCloseTimer += deltaTime; inGameCloseTimer += deltaTime;
}
if (inGameCloseTimer >= inGameCloseTime) if (inGameCloseTimer >= inGameCloseTime)
{ {
@@ -59,11 +59,41 @@ namespace Barotrauma
/// </summary> /// </summary>
public Color Blue { get; private set; } = Color.Blue; public Color Blue { get; private set; } = Color.Blue;
/// <summary>
/// General yellow color used for elements whose colors are set from code
/// </summary>
public Color Yellow { get; private set; } = Color.Yellow;
public Color ColorInventoryEmpty { get; private set; } = Color.Red;
public Color ColorInventoryHalf { get; private set; } = Color.Orange;
public Color ColorInventoryFull { get; private set; } = Color.LightGreen;
public Color ColorInventoryBackground { get; private set; } = Color.Gray;
public Color TextColor { get; private set; } = Color.White * 0.8f; public Color TextColor { get; private set; } = Color.White * 0.8f;
public Color TextColorBright { get; private set; } = Color.White * 0.9f; public Color TextColorBright { get; private set; } = Color.White * 0.9f;
public Color TextColorDark { get; private set; } = Color.Black * 0.9f; public Color TextColorDark { get; private set; } = Color.Black * 0.9f;
public Color TextColorDim { get; private set; } = Color.White * 0.6f; public Color TextColorDim { get; private set; } = Color.White * 0.6f;
// Inventory
public Color EquipmentSlotIconColor { get; private set; } = new Color(99, 70, 64);
// Health HUD
public Color BuffColorLow { get; private set; } = Color.LightGreen;
public Color BuffColorMedium { get; private set; } = Color.Green;
public Color BuffColorHigh { get; private set; } = Color.DarkGreen;
public Color DebuffColorLow { get; private set; } = Color.DarkSalmon;
public Color DebuffColorMedium { get; private set; } = Color.Red;
public Color DebuffColorHigh { get; private set; } = Color.DarkRed;
public Color HealthBarColorLow { get; private set; } = Color.Red;
public Color HealthBarColorMedium { get; private set; } = Color.Orange;
public Color HealthBarColorHigh { get; private set; } = new Color(78, 114, 88);
public Color EquipmentIndicatorNotEquipped { get; private set; } = Color.Gray;
public Color EquipmentIndicatorEquipped { get; private set; } = new Color(105, 202, 125);
public Color EquipmentIndicatorRunningOut { get; private set; } = new Color(202, 105, 105);
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale); public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale); public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
@@ -103,10 +133,25 @@ namespace Barotrauma
case "blue": case "blue":
Blue = subElement.GetAttributeColor("color", Blue); Blue = subElement.GetAttributeColor("color", Blue);
break; break;
case "yellow":
Yellow = subElement.GetAttributeColor("color", Yellow);
break;
case "colorinventoryempty":
ColorInventoryEmpty = subElement.GetAttributeColor("color", ColorInventoryEmpty);
break;
case "colorinventoryhalf":
ColorInventoryHalf = subElement.GetAttributeColor("color", ColorInventoryHalf);
break;
case "colorinventoryfull":
ColorInventoryFull = subElement.GetAttributeColor("color", ColorInventoryFull);
break;
case "colorinventorybackground":
ColorInventoryBackground = subElement.GetAttributeColor("color", ColorInventoryBackground);
break;
case "textcolordark": case "textcolordark":
TextColorDark = subElement.GetAttributeColor("color", TextColorDark); TextColorDark = subElement.GetAttributeColor("color", TextColorDark);
break; break;
case "TextColorBright": case "textcolorbright":
TextColorBright = subElement.GetAttributeColor("color", TextColorBright); TextColorBright = subElement.GetAttributeColor("color", TextColorBright);
break; break;
case "textcolordim": case "textcolordim":
@@ -116,6 +161,45 @@ namespace Barotrauma
case "textcolor": case "textcolor":
TextColor = subElement.GetAttributeColor("color", TextColor); TextColor = subElement.GetAttributeColor("color", TextColor);
break; break;
case "equipmentsloticoncolor":
EquipmentSlotIconColor = subElement.GetAttributeColor("color", EquipmentSlotIconColor);
break;
case "buffcolorlow":
BuffColorLow = subElement.GetAttributeColor("color", BuffColorLow);
break;
case "buffcolormedium":
BuffColorMedium = subElement.GetAttributeColor("color", BuffColorMedium);
break;
case "buffcolorhigh":
BuffColorHigh = subElement.GetAttributeColor("color", BuffColorHigh);
break;
case "debuffcolorlow":
DebuffColorLow = subElement.GetAttributeColor("color", DebuffColorLow);
break;
case "debuffcolormedium":
DebuffColorMedium = subElement.GetAttributeColor("color", DebuffColorMedium);
break;
case "debuffcolorhigh":
DebuffColorHigh = subElement.GetAttributeColor("color", DebuffColorHigh);
break;
case "healthbarcolorlow":
HealthBarColorLow = subElement.GetAttributeColor("color", HealthBarColorLow);
break;
case "healthbarcolormedium":
HealthBarColorMedium = subElement.GetAttributeColor("color", HealthBarColorMedium);
break;
case "healthbarcolorhigh":
HealthBarColorHigh = subElement.GetAttributeColor("color", HealthBarColorHigh);
break;
case "equipmentindicatornotequipped":
EquipmentIndicatorNotEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorNotEquipped);
break;
case "equipmentindicatorequipped":
EquipmentIndicatorEquipped = subElement.GetAttributeColor("color", EquipmentIndicatorEquipped);
break;
case "equipmentindicatorrunningout":
EquipmentIndicatorRunningOut = subElement.GetAttributeColor("color", EquipmentIndicatorRunningOut);
break;
case "uiglow": case "uiglow":
UIGlow = new UISprite(subElement); UIGlow = new UISprite(subElement);
break; break;
@@ -250,9 +334,8 @@ namespace Barotrauma
//check if any of the language override fonts want to override the font size as well //check if any of the language override fonts want to override the font size as well
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
if (GameMain.Config.Language.ToLowerInvariant() == language)
{ {
uint overrideFontSize = GetFontSize(subElement, 0); uint overrideFontSize = GetFontSize(subElement, 0);
if (overrideFontSize > 0) { return overrideFontSize; } if (overrideFontSize > 0) { return overrideFontSize; }
@@ -261,7 +344,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "size") { continue; } if (!subElement.Name.ToString().Equals("size", StringComparison.OrdinalIgnoreCase)) { continue; }
Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue)); Point maxResolution = subElement.GetAttributePoint("maxresolution", new Point(int.MaxValue, int.MaxValue));
if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y) if (GameMain.GraphicsWidth <= maxResolution.X && GameMain.GraphicsHeight <= maxResolution.Y)
{ {
@@ -275,9 +358,8 @@ namespace Barotrauma
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
if (GameMain.Config.Language.ToLowerInvariant() == language)
{ {
return subElement.GetAttributeString("file", ""); return subElement.GetAttributeString("file", "");
} }
@@ -289,9 +371,8 @@ namespace Barotrauma
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
if (GameMain.Config.Language.ToLowerInvariant() == language)
{ {
return subElement.GetAttributeBool("dynamicloading", false); return subElement.GetAttributeBool("dynamicloading", false);
} }
@@ -303,9 +384,8 @@ namespace Barotrauma
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "override") { continue; } if (!subElement.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { continue; }
string language = subElement.GetAttributeString("language", "").ToLowerInvariant(); if (GameMain.Config.Language.Equals(subElement.GetAttributeString("language", ""), StringComparison.OrdinalIgnoreCase))
if (GameMain.Config.Language.ToLowerInvariant() == language)
{ {
return subElement.GetAttributeBool("iscjk", false); return subElement.GetAttributeBool("iscjk", false);
} }
@@ -238,8 +238,39 @@ namespace Barotrauma
get { return censoredText; } get { return censoredText; }
} }
private List<ColorData> colorData = null; public class StrikethroughSettings
private bool hasColorHighlight = false; {
private Color color = GUI.Style.Red;
private int thickness;
private int expand;
public StrikethroughSettings(Color? color = null, int thickness = 1, int expand = 0)
{
if (color != null) this.color = color.Value;
this.thickness = thickness;
this.expand = expand;
}
public void Draw(SpriteBatch spriteBatch, float textSizeHalf, float xPos, float yPos)
{
ShapeExtensions.DrawLine(spriteBatch, new Vector2(xPos - textSizeHalf - expand, yPos), new Vector2(xPos + textSizeHalf + expand, yPos), color, thickness);
}
}
public StrikethroughSettings Strikethrough = null;
private readonly List<RichTextData> richTextData = null;
private readonly bool hasColorHighlight = false;
public struct ClickableArea
{
public RichTextData Data;
public delegate void OnClickDelegate(GUITextBlock textBlock, ClickableArea area);
public OnClickDelegate OnClick;
}
public List<ClickableArea> ClickableAreas { get; private set; } = new List<ClickableArea>();
/// <summary> /// <summary>
/// This is the new constructor. /// This is the new constructor.
@@ -282,11 +313,11 @@ namespace Barotrauma
Enabled = true; Enabled = true;
Censor = false; Censor = false;
} }
public GUITextBlock(RectTransform rectT, List<ColorData> colorData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false) public GUITextBlock(RectTransform rectT, List<RichTextData> richTextData, string text, Color? textColor = null, ScalableFont font = null, Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool playerInput = false)
: this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput) : this(rectT, text, textColor, font, textAlignment, wrap, style, color, playerInput)
{ {
this.colorData = colorData; this.richTextData = richTextData;
hasColorHighlight = colorData != null; hasColorHighlight = richTextData != null;
} }
public void CalculateHeightFromText(int padding = 0) public void CalculateHeightFromText(int padding = 0)
@@ -427,6 +458,129 @@ namespace Barotrauma
disabledTextColor = color; disabledTextColor = color;
} }
protected List<Tuple<Vector2, int>> GetAllPositions()
{
float halfHeight = Font.MeasureString("T").Y * 0.5f;
string textDrawn = Censor ? CensoredText : WrappedText;
var positions = new List<Tuple<Vector2, int>>();
if (textDrawn.Contains("\n"))
{
string[] lines = textDrawn.Split('\n');
int index = 0;
int totalIndex = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
totalIndex += line.Length;
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y;
for (int j = 0; j <= line.Length; j++)
{
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j));
Vector2 indexPos = new Vector2(lineTextSize.X + Padding.X, totalTextHeight + Padding.Y - halfHeight);
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
}
index = totalIndex;
}
}
else
{
textDrawn = Censor ? CensoredText : Text;
for (int i = 0; i <= Text.Length; i++)
{
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i));
Vector2 indexPos = new Vector2(textSize.X + Padding.X, textSize.Y + Padding.Y - halfHeight) + TextPos - Origin;
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(indexPos, i));
}
}
return positions;
}
public int GetCaretIndexFromScreenPos(Vector2 pos)
{
return GetCaretIndexFromLocalPos(pos - Rect.Location.ToVector2());
}
public int GetCaretIndexFromLocalPos(Vector2 pos)
{
var positions = GetAllPositions();
if (positions.Count == 0) { return 0; }
float halfHeight = Font.MeasureString("T").Y * 0.5f;
var currPosition = positions[0];
for (int i = 1; i < positions.Count; i++)
{
var p1 = positions[i];
var p2 = currPosition;
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
if (diffY < -3.0f)
{
currPosition = p1; continue;
}
else if (diffY > 3.0f)
{
continue;
}
else
{
diffY = Math.Abs(p1.Item1.Y - pos.Y);
if (diffY < halfHeight)
{
//we are on this line, select the nearest character
float diffX = Math.Abs(p1.Item1.X - pos.X) - Math.Abs(p2.Item1.X - pos.X);
if (diffX < -1.0f)
{
currPosition = p1; continue;
}
else
{
continue;
}
}
else
{
//we are on a different line, preserve order
if (p1.Item2 < p2.Item2)
{
if (p1.Item1.Y > pos.Y) { currPosition = p1; }
}
else if (p1.Item2 > p2.Item2)
{
if (p1.Item1.Y < pos.Y) { currPosition = p1; }
}
continue;
}
}
}
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
return currPosition != null ? currPosition.Item2 : Text.Length;
}
protected override void Update(float deltaTime)
{
base.Update(deltaTime);
if (ClickableAreas.Any() && (GUI.MouseOn?.IsParentOf(this) ?? true) && Rect.Contains(PlayerInput.MousePosition))
{
int index = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
foreach (ClickableArea clickableArea in ClickableAreas)
{
if (clickableArea.Data.StartIndex <= index && index <= clickableArea.Data.EndIndex)
{
GUI.MouseCursor = CursorState.Hand;
if (PlayerInput.PrimaryMouseButtonClicked())
{
clickableArea.OnClick?.Invoke(this, clickableArea);
}
break;
}
}
}
}
protected override void Draw(SpriteBatch spriteBatch) protected override void Draw(SpriteBatch spriteBatch)
{ {
if (!Visible) { return; } if (!Visible) { return; }
@@ -480,7 +634,12 @@ namespace Barotrauma
else else
{ {
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos, Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text), pos,
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, colorData); currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, richTextData);
}
if (Strikethrough != null)
{
Strikethrough.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X, ForceUpperCase ? pos.Y : pos.Y + GUI.Scale * 2f);
} }
} }
@@ -69,6 +69,14 @@ namespace Barotrauma
private readonly Memento<string> memento = new Memento<string>(); private readonly Memento<string> memento = new Memento<string>();
// Skip one update cycle, fixes Enter key instantly deselecting the chatbox
private bool skipUpdate;
public GUIFrame Frame
{
get { return frame; }
}
public GUITextBlock.TextGetterHandler TextGetter public GUITextBlock.TextGetterHandler TextGetter
{ {
get { return textBlock.TextGetter; } get { return textBlock.TextGetter; }
@@ -357,114 +365,14 @@ namespace Barotrauma
caretPosDirty = false; caretPosDirty = false;
} }
protected List<Tuple<Vector2, int>> GetAllPositions()
{
float halfHeight = Font.MeasureString("T").Y * 0.5f;
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
var positions = new List<Tuple<Vector2, int>>();
if (textDrawn.Contains("\n"))
{
string[] lines = textDrawn.Split('\n');
int index = 0;
int totalIndex = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
totalIndex += line.Length;
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y;
for (int j = 0; j <= line.Length; j++)
{
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j));
Vector2 indexPos = new Vector2(lineTextSize.X + textBlock.Padding.X, totalTextHeight + textBlock.Padding.Y - halfHeight);
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
}
index = totalIndex;
}
}
else
{
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
for (int i = 0; i <= textBlock.Text.Length; i++)
{
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i));
Vector2 indexPos = new Vector2(textSize.X + textBlock.Padding.X, textSize.Y + textBlock.Padding.Y - halfHeight) + textBlock.TextPos - textBlock.Origin;
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(indexPos, i));
}
}
return positions;
}
public int GetCaretIndexFromScreenPos(Vector2 pos)
{
return GetCaretIndexFromLocalPos(pos - textBlock.Rect.Location.ToVector2());
}
public int GetCaretIndexFromLocalPos(Vector2 pos)
{
var positions = GetAllPositions();
if (positions.Count==0) { return 0; }
float halfHeight = Font.MeasureString("T").Y * 0.5f;
var currPosition = positions[0];
for (int i=1;i<positions.Count;i++)
{
var p1 = positions[i];
var p2 = currPosition;
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
if (diffY < -3.0f)
{
currPosition = p1; continue;
}
else if (diffY > 3.0f)
{
continue;
}
else
{
diffY = Math.Abs(p1.Item1.Y - pos.Y);
if (diffY < halfHeight)
{
//we are on this line, select the nearest character
float diffX = Math.Abs(p1.Item1.X - pos.X) - Math.Abs(p2.Item1.X - pos.X);
if (diffX < -1.0f)
{
currPosition = p1; continue;
}
else
{
continue;
}
}
else
{
//we are on a different line, preserve order
if (p1.Item2 < p2.Item2)
{
if (p1.Item1.Y > pos.Y) { currPosition = p1; }
}
else if (p1.Item2 > p2.Item2)
{
if (p1.Item1.Y < pos.Y) { currPosition = p1; }
}
continue;
}
}
}
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
return currPosition != null ? currPosition.Item2 : textBlock.Text.Length;
}
public void Select(int forcedCaretIndex = -1) public void Select(int forcedCaretIndex = -1)
{ {
skipUpdate = true;
if (memento.Current == null) if (memento.Current == null)
{ {
memento.Store(Text); memento.Store(Text);
} }
CaretIndex = forcedCaretIndex == - 1 ? GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex; CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
ClearSelection(); ClearSelection();
selected = true; selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
@@ -494,6 +402,13 @@ namespace Barotrauma
if (flashTimer > 0.0f) flashTimer -= deltaTime; if (flashTimer > 0.0f) flashTimer -= deltaTime;
if (!Enabled) { return; } if (!Enabled) { return; }
if (skipUpdate)
{
skipUpdate = false;
return;
}
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this)))) if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this))))
{ {
State = ComponentState.Hover; State = ComponentState.Hover;
@@ -513,7 +428,7 @@ namespace Barotrauma
{ {
if (!MathUtils.NearlyEqual(PlayerInput.MouseSpeed.X, 0)) if (!MathUtils.NearlyEqual(PlayerInput.MouseSpeed.X, 0))
{ {
CaretIndex = GetCaretIndexFromScreenPos(PlayerInput.MousePosition); CaretIndex = textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
CalculateCaretPos(); CalculateCaretPos();
CalculateSelection(); CalculateSelection();
} }
@@ -788,7 +703,7 @@ namespace Barotrauma
InitSelectionStart(); InitSelectionStart();
} }
float lineHeight = Font.MeasureString("T").Y; float lineHeight = Font.MeasureString("T").Y;
int newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y-lineHeight)); int newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y-lineHeight));
CaretIndex = newIndex; CaretIndex = newIndex;
caretTimer = 0; caretTimer = 0;
HandleSelection(); HandleSelection();
@@ -799,7 +714,7 @@ namespace Barotrauma
InitSelectionStart(); InitSelectionStart();
} }
lineHeight = Font.MeasureString("T").Y; lineHeight = Font.MeasureString("T").Y;
newIndex = GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y+lineHeight)); newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y+lineHeight));
CaretIndex = newIndex; CaretIndex = newIndex;
caretTimer = 0; caretTimer = 0;
HandleSelection(); HandleSelection();
@@ -112,16 +112,16 @@ namespace Barotrauma
//slice from the top of the screen for misc buttons (info, end round, server controls) //slice from the top of the screen for misc buttons (info, end round, server controls)
ButtonAreaTop = new Rectangle(Padding, Padding, GameMain.GraphicsWidth - Padding * 2, (int)(50 * GUI.Scale)); ButtonAreaTop = new Rectangle(Padding, Padding, GameMain.GraphicsWidth - Padding * 2, (int)(50 * GUI.Scale));
int infoAreaWidth = (int)(142 * GUI.Scale * CharacterInfo.BgScale); int infoAreaWidth = (int)(142 * GUI.Scale);
int infoAreaHeight = (int)(98 * GUI.Scale * CharacterInfo.BgScale); int infoAreaHeight = (int)(98 * GUI.Scale);
int portraitSize = (int)(125 * GUI.Scale); int portraitSize = (int)(infoAreaHeight * 0.95f);
BottomRightInfoArea = new Rectangle(GameMain.GraphicsWidth - Padding * 2 - infoAreaWidth, GameMain.GraphicsHeight - Padding * 2 - infoAreaHeight, infoAreaWidth, infoAreaHeight); BottomRightInfoArea = new Rectangle(GameMain.GraphicsWidth - Padding * 2 - infoAreaWidth, GameMain.GraphicsHeight - Padding * 2 - infoAreaHeight, infoAreaWidth, infoAreaHeight);
PortraitArea = new Rectangle(GameMain.GraphicsWidth - Padding - portraitSize, GameMain.GraphicsHeight - Padding - portraitSize, portraitSize, portraitSize); PortraitArea = new Rectangle(GameMain.GraphicsWidth - portraitSize, BottomRightInfoArea.Bottom - portraitSize + Padding / 2, portraitSize, portraitSize);
//horizontal slices at the corners of the screen for health bar and affliction icons //horizontal slices at the corners of the screen for health bar and affliction icons
int afflictionAreaHeight = (int)(50 * GUI.Scale); int afflictionAreaHeight = (int)(50 * GUI.Scale);
int healthBarWidth = (int)((BottomRightInfoArea.Width + CharacterInventory.SlotSize.X + CharacterInventory.Spacing) * 1.1f); int healthBarWidth = BottomRightInfoArea.Width + CharacterInventory.SlotSize.X + CharacterInventory.Spacing * 2 + CharacterInventory.HideButtonWidth;
int healthBarHeight = (int)Math.Max(50f * GUI.Scale, 25f); int healthBarHeight = (int)(50f * GUI.Scale);
HealthBarArea = new Rectangle(BottomRightInfoArea.X - (healthBarWidth - BottomRightInfoArea.Width) + (int)(2 * GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + (int)(10 * GUI.Scale), healthBarWidth, healthBarHeight); HealthBarArea = new Rectangle(BottomRightInfoArea.X - (healthBarWidth - BottomRightInfoArea.Width) + (int)(2 * GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + (int)(10 * GUI.Scale), healthBarWidth, healthBarHeight);
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight); AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
@@ -338,6 +338,8 @@ namespace Barotrauma
PendingSplashScreens.Clear(); PendingSplashScreens.Clear();
currSplashScreen = null; currSplashScreen = null;
} }
if (currSplashScreen == null) { return; }
} }
if (currSplashScreen.IsPlaying) if (currSplashScreen.IsPlaying)
@@ -651,6 +651,13 @@ namespace Barotrauma
Parent.ChildrenChanged?.Invoke(this); Parent.ChildrenChanged?.Invoke(this);
} }
public void ReverseChildren()
{
children.Reverse();
RecalculateAll(false, false, true);
Parent.ChildrenChanged?.Invoke(this);
}
public void SetAsLastChild() public void SetAsLastChild()
{ {
if (IsLastChild) { return; } if (IsLastChild) { return; }
@@ -0,0 +1,932 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
class TabMenu
{
public static bool PendingChanges = false;
private static bool initialized = false;
private static UISprite spectateIcon, deadIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon;
private enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor };
private static InfoFrameTab selectedTab;
private GUIFrame infoFrame, contentFrame;
private readonly List<GUIButton> tabButtons = new List<GUIButton>();
private GUIFrame infoFrameHolder;
private List<LinkedGUI> linkedGUIList;
private GUIListBox logList;
private GUIListBox[] crewListArray;
private float sizeMultiplier = 1f;
private IEnumerable<Character> crew;
private List<Character.TeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022";
private static Color ownCharacterBGColor = Color.Gold * 0.7f;
private class LinkedGUI
{
private const ushort lowPingThreshold = 100;
private const ushort mediumPingThreshold = 200;
private ushort currentPing;
private Client client;
private Character character;
private bool hasCharacter;
private GUITextBlock textBlock;
private GUIFrame frame;
public LinkedGUI(Client client, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock)
{
this.client = client;
this.textBlock = textBlock;
this.frame = frame;
this.hasCharacter = hasCharacter;
}
public LinkedGUI(Character character, GUIFrame frame, bool hasCharacter, GUITextBlock textBlock)
{
this.character = character;
this.textBlock = textBlock;
this.frame = frame;
this.hasCharacter = hasCharacter;
}
public bool HasMultiplayerCharacterChanged()
{
if (client == null) return false;
bool characterState = client.Character != null;
if (characterState && client.Character.IsDead) characterState = false;
return hasCharacter != characterState;
}
public bool HasMultiplayerCharacterDied()
{
if (client == null || !hasCharacter || client.Character == null) return false;
return client.Character.IsDead;
}
public bool HasAICharacterDied()
{
if (character == null) return false;
return character.IsDead;
}
public void TryPingRefresh()
{
if (client == null) return;
if (currentPing == client.Ping) return;
currentPing = client.Ping;
textBlock.Text = currentPing.ToString();
textBlock.TextColor = GetPingColor();
}
private Color GetPingColor()
{
if (currentPing < lowPingThreshold)
{
return GUI.Style.Green;
}
else if (currentPing < mediumPingThreshold)
{
return GUI.Style.Yellow;
}
else
{
return GUI.Style.Red;
}
}
public void Remove(GUIFrame parent)
{
parent.RemoveChild(frame);
}
}
public void Initialize()
{
spectateIcon = GUI.Style.GetComponentStyle("SpectateIcon").Sprites[GUIComponent.ComponentState.None][0];
deadIcon = GUI.Style.GetComponentStyle("DeadIcon").Sprites[GUIComponent.ComponentState.None][0];
disconnectedIcon = GUI.Style.GetComponentStyle("DisconnectedIcon").Sprites[GUIComponent.ComponentState.None][0];
ownerIcon = GUI.Style.GetComponentStyle("OwnerIcon").Sprites[GUIComponent.ComponentState.None][0].Sprite;
moderatorIcon = GUI.Style.GetComponentStyle("ModeratorIcon").Sprites[GUIComponent.ComponentState.None][0].Sprite;
initialized = true;
}
public TabMenu()
{
if (!initialized) Initialize();
CreateInfoFrame(selectedTab);
SelectInfoFrameTab(null, selectedTab);
}
public void Update()
{
if (selectedTab != InfoFrameTab.Crew) return;
if (linkedGUIList == null) return;
if (GameMain.IsMultiplayer)
{
for (int i = 0; i < linkedGUIList.Count; i++)
{
linkedGUIList[i].TryPingRefresh();
if (linkedGUIList[i].HasMultiplayerCharacterChanged() || linkedGUIList[i].HasMultiplayerCharacterDied() || linkedGUIList[i].HasAICharacterDied())
{
RemoveCurrentElements();
CreateMultiPlayerList(true);
return;
}
}
}
else
{
for (int i = 0; i < linkedGUIList.Count; i++)
{
if (linkedGUIList[i].HasAICharacterDied())
{
RemoveCurrentElements();
CreateSinglePlayerList(true);
}
}
}
}
public void AddToGUIUpdateList()
{
infoFrame?.AddToGUIUpdateList();
NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList();
}
public static void OnRoundEnded()
{
storedMessages.Clear();
PendingChanges = false;
}
private void CreateInfoFrame(InfoFrameTab selectedTab)
{
tabButtons.Clear();
infoFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
switch (selectedTab)
{
case InfoFrameTab.Crew:
case InfoFrameTab.Mission:
case InfoFrameTab.Traitor:
default:
contentFrame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.667f), infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { /*MinSize = new Point(width, height),*/ RelativeOffset = new Vector2(0.025f, 0.12f) });
break;
case InfoFrameTab.MyCharacter:
contentFrame = new GUIFrame(new RectTransform(new Vector2(0.33f, 0.5f), infoFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { /*MinSize = new Point(width, height),*/ RelativeOffset = new Vector2(0.025f, 0.12f) });
break;
}
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.958f, 0.943f), contentFrame.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, GUI.IntScale(17.5f)) }, style: null);
var buttonArea = new GUILayoutGroup(new RectTransform(new Point(innerFrame.Rect.Width, GUI.IntScale(25f)), innerFrame.RectTransform) { AbsoluteOffset = new Point(2, 0) }, isHorizontal: true)
{
RelativeSpacing = 0.01f
};
infoFrameHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.926f), innerFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter), style: null);
var crewButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("Crew"), style: "GUITabButton")
{
UserData = InfoFrameTab.Crew,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(crewButton);
var missionButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("Mission"), style: "GUITabButton")
{
UserData = InfoFrameTab.Mission,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(missionButton);
bool isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
if (isTraitor && GameMain.Client.TraitorMission != null)
{
var traitorButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("tabmenu.traitor"), style: "GUITabButton")
{
UserData = InfoFrameTab.Traitor,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(traitorButton);
}
if (GameMain.NetworkMember != null)
{
var myCharacterButton = new GUIButton(new RectTransform(new Vector2(0.245f, 1.0f), buttonArea.RectTransform), TextManager.Get("tabmenu.character"), style: "GUITabButton")
{
UserData = InfoFrameTab.MyCharacter,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(myCharacterButton);
}
}
private bool SelectInfoFrameTab(GUIButton button, object userData)
{
selectedTab = (InfoFrameTab)userData;
CreateInfoFrame(selectedTab);
tabButtons.ForEach(tb => tb.Selected = (InfoFrameTab)tb.UserData == selectedTab);
switch (selectedTab)
{
case InfoFrameTab.Crew:
CreateCrewListFrame(infoFrameHolder);
break;
case InfoFrameTab.Mission:
CreateMissionInfo(infoFrameHolder);
break;
case InfoFrameTab.Traitor:
TraitorMissionPrefab traitorMission = GameMain.Client.TraitorMission;
Character traitor = GameMain.Client.Character;
if (traitor == null || traitorMission == null) return false;
CreateTraitorInfo(infoFrameHolder, traitorMission, traitor);
break;
case InfoFrameTab.MyCharacter:
if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameHolder);
break;
}
return true;
}
private const float jobColumnWidthPercentage = 0.138f;
private const float characterColumnWidthPercentage = 0.656f;
private const float pingColumnWidthPercentage = 0.206f;
private int jobColumnWidth, characterColumnWidth, pingColumnWidth;
private void CreateCrewListFrame(GUIFrame crewFrame)
{
crew = GameMain.GameSession.CrewManager.GetCharacters();
teamIDs = crew.Select(c => c.TeamID).Distinct().ToList();
// Show own team first when there's more than one team
if (teamIDs.Count > 1 && GameMain.Client.Character != null)
{
Character.TeamType ownTeam = GameMain.Client.Character.TeamID;
teamIDs = teamIDs.OrderBy(i => i != ownTeam).ThenBy(i => i).ToList();
}
if (!teamIDs.Any()) teamIDs.Add(Character.TeamType.None);
var content = new GUILayoutGroup(new RectTransform(Vector2.One, crewFrame.RectTransform));
crewListArray = new GUIListBox[teamIDs.Count];
GUILayoutGroup[] headerFrames = new GUILayoutGroup[teamIDs.Count];
float nameHeight = 0.075f;
Vector2 crewListSize = new Vector2(1f, 1f / teamIDs.Count - (teamIDs.Count > 1 ? nameHeight * 1.1f : 0f));
for (int i = 0; i < teamIDs.Count; i++)
{
if (teamIDs.Count > 1)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, nameHeight), content.RectTransform), CombatMission.GetTeamName(teamIDs[i]), textColor: i == 0 ? GUI.Style.Green : GUI.Style.Orange) { ForceUpperCase = true };
}
headerFrames[i] = new GUILayoutGroup(new RectTransform(Vector2.Zero, content.RectTransform, Anchor.TopLeft, Pivot.BottomLeft) { AbsoluteOffset = new Point(2, -1) }, isHorizontal: true)
{
AbsoluteSpacing = 2,
UserData = i
};
GUIListBox crewList = new GUIListBox(new RectTransform(crewListSize, content.RectTransform))
{
Padding = new Vector4(2, 5, 0, 0),
AutoHideScrollBar = false
};
crewList.UpdateDimensions();
if (teamIDs.Count > 1)
{
crewList.OnSelected = (component, obj) =>
{
for (int i = 0; i < crewListArray.Length; i++)
{
if (crewListArray[i] == crewList) continue;
crewListArray[i].Deselect();
}
SelectElement(component.UserData, crewList);
return true;
};
}
else
{
crewList.OnSelected = (component, obj) =>
{
SelectElement(component.UserData, crewList);
return true;
};
}
crewListArray[i] = crewList;
}
for (int i = 0; i < teamIDs.Count; i++)
{
headerFrames[i].RectTransform.RelativeSize = new Vector2(1f - crewListArray[i].ScrollBar.Rect.Width / (float)crewListArray[i].Rect.Width, GUI.HotkeyFont.Size / (float)crewFrame.RectTransform.Rect.Height * 1.5f);
if (!GameMain.IsMultiplayer)
{
CreateSinglePlayerListContentHolder(headerFrames[i]);
}
else
{
CreateMultiPlayerListContentHolder(headerFrames[i]);
}
}
crewFrame.RectTransform.AbsoluteOffset = new Point(0, (int)(headerFrames[0].Rect.Height * headerFrames.Length) - (teamIDs.Count > 1 ? GUI.IntScale(10f) : 0));
if (GameMain.IsMultiplayer)
{
CreateMultiPlayerList(false);
CreateMultiPlayerLogContent(crewFrame);
}
else
{
CreateSinglePlayerList(false);
}
}
private void CreateSinglePlayerListContentHolder(GUILayoutGroup headerFrame)
{
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2((1f - jobColumnWidthPercentage * sizeMultiplier) * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = GUI.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = true;
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
}
private void CreateSinglePlayerList(bool refresh)
{
if (refresh)
{
crew = GameMain.GameSession.CrewManager.GetCharacters();
}
linkedGUIList = new List<LinkedGUI>();
for (int i = 0; i < teamIDs.Count; i++)
{
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
{
CreateSinglePlayerCharacterElement(character, i);
}
}
}
private void CreateSinglePlayerCharacterElement(Character character, int i)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
{
UserData = character,
Color = (Character.Controlled == character) ? ownCharacterBGColor : Color.Transparent
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White
};
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, null));
}
private void CreateMultiPlayerListContentHolder(GUILayoutGroup headerFrame)
{
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
GUIButton pingButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("serverlistping"), style: "GUIButtonSmallFreeScale");
sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f);
pingButton.RectTransform.RelativeSize = new Vector2(pingColumnWidthPercentage * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = pingButton.TextBlock.Font = GUI.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = pingButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = pingButton.ForceUpperCase = true;
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
pingColumnWidth = pingButton.Rect.Width;
}
private void CreateMultiPlayerList(bool refresh)
{
if (refresh)
{
crew = GameMain.GameSession.CrewManager.GetCharacters();
}
linkedGUIList = new List<LinkedGUI>();
List<Client> connectedClients = GameMain.Client.ConnectedClients;
for (int i = 0; i < teamIDs.Count; i++)
{
foreach (Character character in crew.Where(c => c.TeamID == teamIDs[i]))
{
if (!(character is AICharacter) && connectedClients.Find(c => c.Character == null && c.Name == character.Name) != null) continue;
CreateMultiPlayerCharacterElement(character, GameMain.Client.ConnectedClients.Find(c => c.Character == character), i);
}
}
for (int j = 0; j < connectedClients.Count; j++)
{
Client client = connectedClients[j];
if (!client.InGame || client.Character == null || client.Character.IsDead)
{
CreateMultiPlayerClientElement(client);
}
}
}
private void CreateMultiPlayerCharacterElement(Character character, Client client, int i)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
{
UserData = character,
Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? ownCharacterBGColor : Color.Transparent
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => character.Info.DrawJobIcon(sb, component.Rect))
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White
};
if (client != null)
{
CreateNameWithPermissionIcon(client, paddedFrame);
linkedGUIList.Add(new LinkedGUI(client, frame, true, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center)));
}
else
{
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(character.Info.Name, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: character.Info.Job.Prefab.UIColor);
if (character is AICharacter)
{
linkedGUIList.Add(new LinkedGUI(character, frame, !character.IsDead, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), TextManager.Get("tabmenu.bot"), textAlignment: Alignment.Center) { ForceUpperCase = true }));
}
else
{
linkedGUIList.Add(new LinkedGUI(client: null, frame, true, null));
new GUICustomComponent(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => DrawDisconnectedIcon(sb, component.Rect))
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White
};
}
}
}
private void CreateMultiPlayerClientElement(Client client)
{
int teamIndex = GetTeamIndex(client);
if (teamIndex == -1) teamIndex = 0;
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[teamIndex].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[teamIndex].Content.RectTransform), style: "ListBoxElement")
{
UserData = client,
Color = Color.Transparent
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center),
onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client))
{
CanBeFocused = false,
HoverColor = Color.White,
SelectedColor = Color.White
};
CreateNameWithPermissionIcon(client, paddedFrame);
linkedGUIList.Add(new LinkedGUI(client, frame, false, new GUITextBlock(new RectTransform(new Point(pingColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform), client.Ping.ToString(), textAlignment: Alignment.Center)));
}
private int GetTeamIndex(Client client)
{
if (teamIDs.Count <= 1) return 0;
if (client.Character != null)
{
return teamIDs.IndexOf(client.Character.TeamID);
}
if (client.CharacterID != 0)
{
foreach (Character c in crew)
{
if (client.CharacterID == c.ID)
{
return teamIDs.IndexOf(c.TeamID);
}
}
}
else
{
foreach (Character c in crew)
{
if (client.Name == c.Name)
{
return teamIDs.IndexOf(c.TeamID);
}
}
}
return 0;
}
private void CreateNameWithPermissionIcon(Client client, GUILayoutGroup paddedFrame)
{
GUITextBlock characterNameBlock;
Sprite permissionIcon = GetPermissionIcon(client);
if (permissionIcon != null)
{
Point iconSize = permissionIcon.SourceRect.Size;
float characterNameWidthAdjustment = (iconSize.X + paddedFrame.AbsoluteSpacing) / characterColumnWidth;
characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(client.Name, GUI.Font, (int)(characterColumnWidth - paddedFrame.Rect.Width * characterNameWidthAdjustment)), textAlignment: Alignment.Center, textColor: client.Character != null ? client.Character.Info.Job.Prefab.UIColor : Color.White);
float iconWidth = iconSize.X / (float)characterColumnWidth;
int xOffset = (int)(jobColumnWidth + characterNameBlock.TextPos.X - GUI.Font.MeasureString(characterNameBlock.Text).X / 2f - paddedFrame.AbsoluteSpacing - iconWidth * paddedFrame.Rect.Width);
new GUIImage(new RectTransform(new Vector2(iconWidth, 1f), paddedFrame.RectTransform) { AbsoluteOffset = new Point(xOffset + 2, 0) }, permissionIcon) { IgnoreLayoutGroups = true };
}
else
{
characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(client.Name, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: client.Character != null ? client.Character.Info.Job.Prefab.UIColor : Color.White);
}
if (client.Character != null && client.Character.IsDead)
{
characterNameBlock.Strikethrough = new GUITextBlock.StrikethroughSettings(null, GUI.IntScale(1f), GUI.IntScale(5f));
}
}
private Sprite GetPermissionIcon(Client client)
{
if (GameMain.NetworkMember == null || client == null || !client.HasPermissions) return null;
if (!client.AllowKicking) // Owner cannot be kicked
{
return ownerIcon;
}
else
{
return moderatorIcon;
}
}
private void DrawNotInGameIcon(SpriteBatch spriteBatch, Rectangle area, Client client)
{
if (client.Spectating)
{
spectateIcon.Draw(spriteBatch, area, Color.White);
}
else if (client.Character != null && client.Character.IsDead)
{
client.Character.Info.DrawJobIcon(spriteBatch, area);
}
else
{
Vector2 stringOffset = GUI.GlobalFont.MeasureString(inLobbyString) / 2f;
GUI.GlobalFont.DrawString(spriteBatch, inLobbyString, area.Center.ToVector2() - stringOffset, Color.White);
}
}
private void DrawDisconnectedIcon(SpriteBatch spriteBatch, Rectangle area)
{
disconnectedIcon.Draw(spriteBatch, area, GUI.Style.Red);
}
/// <summary>
/// Select an element from CrewListFrame
/// </summary>
private bool SelectElement(object userData, GUIComponent crewList)
{
Character character = userData as Character;
Client client = userData as Client;
GUIComponent existingPreview = infoFrameHolder.FindChild("SelectedCharacter");
if (existingPreview != null) infoFrameHolder.RemoveChild(existingPreview);
GUIFrame background = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), infoFrameHolder.RectTransform, Anchor.TopLeft, Pivot.TopRight) { RelativeOffset = new Vector2(-0.061f, 0) })
{
UserData = "SelectedCharacter"
};
if (character != null)
{
if (GameMain.NetworkMember == null)
{
GUIComponent preview = character.Info.CreateInfoFrame(background, false, null);
}
else
{
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
GameMain.Client.SelectCrewCharacter(character, preview);
}
}
else if (client != null)
{
GUIComponent preview = CreateClientInfoFrame(background, client, GetPermissionIcon(client));
if (GameMain.NetworkMember != null) GameMain.Client.SelectCrewClient(client, preview);
}
return true;
}
private GUIComponent CreateClientInfoFrame(GUIFrame frame, Client client, Sprite permissionIcon = null)
{
GUIComponent paddedFrame;
if (client.Character == null)
{
paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.874f, 0.58f), frame.RectTransform, Anchor.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.05f) })
{
RelativeSpacing = 0.05f
//Stretch = true
};
var headerArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.322f), paddedFrame.RectTransform), isHorizontal: true);
new GUICustomComponent(new RectTransform(new Vector2(0.425f, 1.0f), headerArea.RectTransform),
onDraw: (sb, component) => DrawNotInGameIcon(sb, component.Rect, client));
ScalableFont font = paddedFrame.Rect.Width < 280 ? GUI.SmallFont : GUI.Font;
var headerTextArea = new GUILayoutGroup(new RectTransform(new Vector2(0.575f, 1.0f), headerArea.RectTransform))
{
RelativeSpacing = 0.02f,
Stretch = true
};
GUITextBlock clientNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), ToolBox.LimitString(client.Name, GUI.Font, headerTextArea.Rect.Width), textColor: Color.White, font: GUI.Font)
{
ForceUpperCase = true,
Padding = Vector4.Zero
};
if (permissionIcon != null)
{
Point iconSize = permissionIcon.SourceRect.Size;
int iconWidth = (int)((float)clientNameBlock.Rect.Height / iconSize.Y * iconSize.X);
new GUIImage(new RectTransform(new Point(iconWidth, clientNameBlock.Rect.Height), clientNameBlock.RectTransform) { AbsoluteOffset = new Point(-iconWidth - 2, 0) }, permissionIcon) { IgnoreLayoutGroups = true };
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), client.Spectating ? TextManager.Get("playingasspectator") : TextManager.Get("tabmenu.inlobby"), textColor: Color.White, font: font, wrap: true)
{
Padding = Vector4.Zero
};
}
else
{
paddedFrame = client.Character.Info.CreateInfoFrame(frame, false, permissionIcon);
}
return paddedFrame;
}
private void CreateMultiPlayerLogContent(GUIFrame crewFrame)
{
var logContainer = new GUIFrame(new RectTransform(new Vector2(0.543f, 0.717f), crewFrame.RectTransform, Anchor.TopRight, Pivot.TopLeft) { RelativeOffset = new Vector2(-0.061f, 0) });
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.900f, 0.900f), logContainer.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0f, 0.0475f) }, style: null);
var content = new GUILayoutGroup(new RectTransform(Vector2.One, innerFrame.RectTransform))
{
Stretch = true
};
logList = new GUIListBox(new RectTransform(Vector2.One, content.RectTransform))
{
Padding = new Vector4(0, 10 * GUI.Scale, 0, 10 * GUI.Scale),
UserData = crewFrame,
AutoHideScrollBar = false,
Spacing = (int)(5 * GUI.Scale)
};
foreach (Pair<string, PlayerConnectionChangeType> pair in storedMessages)
{
AddLineToLog(pair.First, pair.Second);
}
logList.BarScroll = 1f;
}
private static readonly List<Pair<string, PlayerConnectionChangeType>> storedMessages = new List<Pair<string, PlayerConnectionChangeType>>();
public static void StorePlayerConnectionChangeMessage(ChatMessage message)
{
if (!GameMain.GameSession?.GameMode?.IsRunning ?? true) { return; }
string msg = ChatMessage.GetTimeStamp() + message.TextWithSender;
storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType));
if (GameSession.IsTabMenuOpen)
{
TabMenu instance = GameSession.TabMenuInstance;
instance.AddLineToLog(msg, message.ChangeType);
// Update crew
if (selectedTab == InfoFrameTab.Crew)
{
instance.RemoveCurrentElements();
instance.CreateMultiPlayerList(true);
}
}
}
private void RemoveCurrentElements()
{
for (int i = 0; i < crewListArray.Length; i++)
{
for (int j = 0; j < linkedGUIList.Count; j++)
{
linkedGUIList[j].Remove(crewListArray[i].Content);
}
}
linkedGUIList.Clear();
}
private void AddLineToLog(string line, PlayerConnectionChangeType type)
{
Color textColor = Color.White;
switch (type)
{
case PlayerConnectionChangeType.Joined:
textColor = GUI.Style.Green;
break;
case PlayerConnectionChangeType.Kicked:
textColor = GUI.Style.Orange;
break;
case PlayerConnectionChangeType.Disconnected:
textColor = GUI.Style.Yellow;
break;
case PlayerConnectionChangeType.Banned:
textColor = GUI.Style.Red;
break;
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), logList.Content.RectTransform), line, wrap: true, font: GUI.SmallFont)
{
TextColor = textColor,
CanBeFocused = false,
UserData = line
}.CalculateHeightFromText();
//if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f;
}
private void CreateMissionInfo(GUIFrame infoFrame)
{
infoFrame.ClearChildren();
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = (int)(0.0245f * missionFrame.Rect.Height);
Location endLocation = GameMain.GameSession.EndLocation;
Sprite portrait = endLocation.Type.GetPortrait(endLocation.PortraitId);
bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0;
int contentWidth = hasPortrait ? (int)(missionFrame.Rect.Width * 0.951f) : missionFrame.Rect.Width - padding * 2;
Vector2 locationNameSize = GUI.LargeFont.MeasureString(endLocation.Name);
Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(endLocation.Name);
GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, endLocation.Name, font: GUI.LargeFont);
GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, endLocation.Type.Name, font: GUI.SubHeadingFont);
int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2;
GUIFrame missionDescriptionHolder;
if (hasPortrait)
{
GUIFrame missionImageHolder = new GUIFrame(new RectTransform(new Point(contentWidth, (int)(missionFrame.Rect.Height * 0.588f)), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) });
float portraitAspectRatio = portrait.SourceRect.Width / portrait.SourceRect.Height;
GUIImage portraitImage = new GUIImage(new RectTransform(new Vector2(1.0f, 1f), missionImageHolder.RectTransform), portrait, scaleToFit: true);
missionImageHolder.RectTransform.NonScaledSize = new Point(portraitImage.Rect.Size.X, (int)(portraitImage.Rect.Size.X / portraitAspectRatio));
missionDescriptionHolder = new GUIFrame(new RectTransform(new Point(contentWidth, 0), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, missionImageHolder.RectTransform.AbsoluteOffset.Y + missionImageHolder.Rect.Height + padding) }, style: null);
}
else
{
missionDescriptionHolder = new GUIFrame(new RectTransform(new Point(contentWidth, 0), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationInfoYOffset) }, style: null);
}
Mission mission = GameMain.GameSession?.Mission;
if (mission != null)
{
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.744f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.225f, 0f) }, false, childAnchor: Anchor.TopLeft);
string missionNameString = ToolBox.WrapText(mission.Name, missionTextGroup.Rect.Width, GUI.LargeFont);
string missionDescriptionString = ToolBox.WrapText(mission.Description, missionTextGroup.Rect.Width, GUI.Font);
string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", mission.Reward.ToString()), missionTextGroup.Rect.Width, GUI.Font);
Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
Vector2 missionRewardSize = GUI.Font.MeasureString(missionRewardString);
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y));
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height;
int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
}
else
{
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft), false, childAnchor: Anchor.TopLeft);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), TextManager.Get("NoMission"), font: GUI.LargeFont);
}
}
private void CreateTraitorInfo(GUIFrame infoFrame, TraitorMissionPrefab traitorMission, Character traitor)
{
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = (int)(0.0245f * missionFrame.Rect.Height);
GUIFrame missionDescriptionHolder = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, 0), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, style: null);
GUILayoutGroup missionTextGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.65f, 0f), missionDescriptionHolder.RectTransform, Anchor.CenterLeft) { RelativeOffset = new Vector2(0.319f, 0f) }, false, childAnchor: Anchor.TopLeft);
string missionNameString = ToolBox.WrapText(TextManager.Get("tabmenu.traitor"), missionTextGroup.Rect.Width, GUI.LargeFont);
string missionDescriptionString = ToolBox.WrapText(traitor.TraitorCurrentObjective, missionTextGroup.Rect.Width, GUI.Font);
Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y));
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
float aspectRatio = traitorMission.Icon.SourceRect.Width / traitorMission.Icon.SourceRect.Height;
int iconWidth = (int)(0.319f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * aspectRatio));
Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), traitorMission.Icon, null, true) { Color = traitorMission.IconColor };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
}
}
}
@@ -66,7 +66,7 @@ namespace Barotrauma
if (vanillaContent == null) if (vanillaContent == null)
{ {
// TODO: Dynamic method for defining and finding the vanilla content package. // TODO: Dynamic method for defining and finding the vanilla content package.
vanillaContent = ContentPackage.List.SingleOrDefault(cp => Path.GetFileName(cp.Path).ToLowerInvariant() == "vanilla 0.9.xml"); vanillaContent = ContentPackage.List.SingleOrDefault(cp => Path.GetFileName(cp.Path).Equals("vanilla 0.9.xml", StringComparison.OrdinalIgnoreCase));
} }
return vanillaContent; return vanillaContent;
} }
@@ -112,6 +112,8 @@ namespace Barotrauma
public event Action OnResolutionChanged; public event Action OnResolutionChanged;
private bool exiting;
public static GameMain Instance public static GameMain Instance
{ {
get; get;
@@ -144,7 +146,17 @@ namespace Barotrauma
public static bool WindowActive public static bool WindowActive
{ {
get { return Instance == null || Instance.IsActive; } get
{
try
{
return Instance != null && !Instance.exiting && Instance.IsActive;
}
catch (NullReferenceException)
{
return false;
}
}
} }
public static GameClient Client; public static GameClient Client;
@@ -179,10 +191,14 @@ namespace Barotrauma
{ {
Content.RootDirectory = "Content"; Content.RootDirectory = "Content";
GraphicsDeviceManager = new GraphicsDeviceManager(this); #if DEBUG && WINDOWS
GraphicsAdapter.UseDebugLayers = true;
GraphicsDeviceManager.IsFullScreen = false; #endif
GraphicsDeviceManager.GraphicsProfile = GfxProfile; GraphicsDeviceManager = new GraphicsDeviceManager(this)
{
IsFullScreen = false,
GraphicsProfile = GfxProfile
};
GraphicsDeviceManager.ApplyChanges(); GraphicsDeviceManager.ApplyChanges();
Window.Title = "Barotrauma"; Window.Title = "Barotrauma";
@@ -329,6 +345,8 @@ namespace Barotrauma
//do this here because we need it for the loading screen //do this here because we need it for the loading screen
WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content); WaterRenderer.Instance = new WaterRenderer(base.GraphicsDevice, Content);
Quad.Init(GraphicsDevice);
loadingScreenOpen = true; loadingScreenOpen = true;
TitleScreen = new LoadingScreen(GraphicsDevice) TitleScreen = new LoadingScreen(GraphicsDevice)
{ {
@@ -385,6 +403,13 @@ namespace Barotrauma
} }
} }
public class LoadingException : Exception
{
public LoadingException(Exception e) : base("Loading was interrupted due to an error.", innerException: e)
{
}
}
private IEnumerable<object> Load(bool isSeparateThread) private IEnumerable<object> Load(bool isSeparateThread)
{ {
if (GameSettings.VerboseLogging) if (GameSettings.VerboseLogging)
@@ -402,7 +427,7 @@ namespace Barotrauma
SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume, 0); SoundManager.SetCategoryGainMultiplier("ui", Config.SoundVolume, 0);
SoundManager.SetCategoryGainMultiplier("waterambience", Config.SoundVolume, 0); SoundManager.SetCategoryGainMultiplier("waterambience", Config.SoundVolume, 0);
SoundManager.SetCategoryGainMultiplier("music", Config.MusicVolume, 0); SoundManager.SetCategoryGainMultiplier("music", Config.MusicVolume, 0);
SoundManager.SetCategoryGainMultiplier("voip", Config.VoiceChatVolume * 20.0f, 0); SoundManager.SetCategoryGainMultiplier("voip", Math.Min(Config.VoiceChatVolume, 1.0f), 0);
if (Config.EnableSplashScreen && !ConsoleArguments.Contains("-skipintro")) if (Config.EnableSplashScreen && !ConsoleArguments.Contains("-skipintro"))
{ {
@@ -430,7 +455,7 @@ namespace Barotrauma
{ {
bool waitingForWorkshopUpdates = true; bool waitingForWorkshopUpdates = true;
bool result = false; bool result = false;
TaskPool.Add(SteamManager.AutoUpdateWorkshopItems(), (task) => TaskPool.Add(SteamManager.AutoUpdateWorkshopItemsAsync(), (task) =>
{ {
result = task.Result; result = task.Result;
waitingForWorkshopUpdates = false; waitingForWorkshopUpdates = false;
@@ -503,6 +528,7 @@ namespace Barotrauma
Tutorials.Tutorial.Init(); Tutorials.Tutorial.Init();
MapGenerationParams.Init(); MapGenerationParams.Init();
LevelGenerationParams.LoadPresets(); LevelGenerationParams.LoadPresets();
WreckAIConfig.LoadAll();
ScriptedEventSet.LoadPrefabs(); ScriptedEventSet.LoadPrefabs();
AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions));
SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings));
@@ -518,6 +544,7 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs));
CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses));
NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations)); NPCConversation.LoadAll(GetFilesOfType(ContentType.NPCConversations));
@@ -527,7 +554,7 @@ namespace Barotrauma
GameModePreset.Init(); GameModePreset.Init();
Submarine.RefreshSavedSubs(); SubmarineInfo.RefreshSavedSubs();
TitleScreen.LoadState = 65.0f; TitleScreen.LoadState = 65.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -664,6 +691,8 @@ namespace Barotrauma
{ {
#if DEBUG #if DEBUG
DebugConsole.ThrowError($"Failed to parse a Steam friend's connect invitation command ({connectCommand})", e); DebugConsole.ThrowError($"Failed to parse a Steam friend's connect invitation command ({connectCommand})", e);
#else
DebugConsole.Log($"Failed to parse a Steam friend's connect invitation command ({connectCommand})\n" + e.StackTrace);
#endif #endif
ConnectName = null; ConnectName = null;
ConnectEndpoint = null; ConnectEndpoint = null;
@@ -756,12 +785,7 @@ namespace Barotrauma
if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine)) if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
{ {
string errMsg = "Loading was interrupted due to an error"; throw new LoadingException(loadingCoroutine.Exception);
if (loadingCoroutine.Exception != null)
{
errMsg += ": " + loadingCoroutine.Exception.Message + "\n" + loadingCoroutine.Exception.StackTrace;
}
throw new Exception(errMsg);
} }
} }
else if (hasLoaded) else if (hasLoaded)
@@ -823,6 +847,10 @@ namespace Barotrauma
{ {
(GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); (GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI();
} }
else if (GameSession.IsTabMenuOpen)
{
gameSession.ToggleTabMenu();
}
else if (GUI.PauseMenuOpen) else if (GUI.PauseMenuOpen)
{ {
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
@@ -831,7 +859,8 @@ namespace Barotrauma
else if ((Character.Controlled == null || !itemHudActive()) else if ((Character.Controlled == null || !itemHudActive())
//TODO: do we need to check Inventory.SelectedSlot? //TODO: do we need to check Inventory.SelectedSlot?
&& Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null && Inventory.SelectedSlot == null && CharacterHealth.OpenHealthWindow == null
&& !CrewManager.IsCommandInterfaceOpen) && !CrewManager.IsCommandInterfaceOpen
&& !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled.SelectedConstruction != null))
{ {
// Otherwise toggle pausing, unless another window/interface is open. // Otherwise toggle pausing, unless another window/interface is open.
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
@@ -922,7 +951,7 @@ namespace Barotrauma
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Update total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Update total", sw.ElapsedTicks);
PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks / (float)TimeSpan.TicksPerMillisecond); PerformanceCounter.UpdateTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
PerformanceCounter.UpdateIterationsGraph.Update(updateIterations); PerformanceCounter.UpdateIterationsGraph.Update(updateIterations);
} }
@@ -944,11 +973,14 @@ namespace Barotrauma
double deltaTime = gameTime.ElapsedGameTime.TotalSeconds; double deltaTime = gameTime.ElapsedGameTime.TotalSeconds;
if (Timing.FrameLimit > 0)
{
double step = 1.0 / Timing.FrameLimit; double step = 1.0 / Timing.FrameLimit;
while (!Config.VSyncEnabled && sw.Elapsed.TotalSeconds + deltaTime < step) while (!Config.VSyncEnabled && sw.Elapsed.TotalSeconds + deltaTime < step)
{ {
Thread.Sleep(1); Thread.Sleep(1);
} }
}
PerformanceCounter.Update(sw.Elapsed.TotalSeconds + deltaTime); PerformanceCounter.Update(sw.Elapsed.TotalSeconds + deltaTime);
@@ -972,7 +1004,7 @@ namespace Barotrauma
sw.Stop(); sw.Stop();
PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks); PerformanceCounter.AddElapsedTicks("Draw total", sw.ElapsedTicks);
PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks / (float)TimeSpan.TicksPerMillisecond); PerformanceCounter.DrawTimeGraph.Update(sw.ElapsedTicks * 1000.0f / (float)Stopwatch.Frequency);
} }
@@ -1101,7 +1133,10 @@ namespace Barotrauma
UserData = "https://steamcommunity.com/app/602960/discussions/1/", UserData = "https://steamcommunity.com/app/602960/discussions/1/",
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
SteamManager.OverlayCustomURL(userdata as string); if (!SteamManager.OverlayCustomURL(userdata as string))
{
ShowOpenUrlInWebBrowserPrompt(userdata as string);
}
msgBox.Close(); msgBox.Close();
return true; return true;
} }
@@ -1138,7 +1173,9 @@ namespace Barotrauma
protected override void OnExiting(object sender, EventArgs args) protected override void OnExiting(object sender, EventArgs args)
{ {
if (NetworkMember != null) NetworkMember.Disconnect(); exiting = true;
DebugConsole.NewMessage("Exiting...");
NetworkMember?.Disconnect();
SteamManager.ShutDown(); SteamManager.ShutDown();
try try
File diff suppressed because it is too large Load Diff
@@ -134,7 +134,7 @@ namespace Barotrauma
foreach (PurchasedItem pi in CargoManager.PurchasedItems) foreach (PurchasedItem pi in CargoManager.PurchasedItems)
{ {
msg.Write(pi.ItemPrefab.Identifier); msg.Write(pi.ItemPrefab.Identifier);
msg.Write((UInt16)pi.Quantity); msg.WriteRangedInteger(pi.Quantity, 0, 100);
} }
} }
@@ -162,7 +162,7 @@ namespace Barotrauma
for (int i = 0; i < purchasedItemCount; i++) for (int i = 0; i < purchasedItemCount; i++)
{ {
string itemPrefabIdentifier = msg.ReadString(); string itemPrefabIdentifier = msg.ReadString();
UInt16 itemQuantity = msg.ReadUInt16(); int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity)); purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
} }
@@ -276,7 +276,11 @@ namespace Barotrauma
c.SaveInventory(c.Inventory, inventoryElement); c.SaveInventory(c.Inventory, inventoryElement);
c.Info.InventoryData = inventoryElement; c.Info.InventoryData = inventoryElement;
c.Inventory?.DeleteAllItems(); c.Inventory?.DeleteAllItems();
c.ResetCurrentOrder();
} }
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
} }
@@ -0,0 +1,80 @@
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class SubTestMode : GameMode
{
public SubTestMode(GameModePreset preset, object param)
: base(preset, param)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
isRunning = true;
CrewManager.InitSinglePlayerRound();
Submarine.MainSub.SetPosition(Vector2.Zero);
}
public override void Draw(SpriteBatch spriteBatch)
{
if (!isRunning|| GUI.DisableHUD || GUI.DisableUpperHUD) return;
if (Submarine.MainSub == null) return;
}
public override void AddToGUIUpdateList()
{
if (!isRunning) return;
base.AddToGUIUpdateList();
CrewManager.AddToGUIUpdateList();
}
public override void Update(float deltaTime)
{
if (!isRunning) { return; }
base.Update(deltaTime);
}
public override void End(string endMessage = "")
{
isRunning = false;
GameMain.GameSession.EndRound("");
CrewManager.EndRound();
Submarine.Unload();
GameMain.SubEditorScreen.Select();
}
private bool EndRound(Submarine leavingSub)
{
isRunning = false;
End("");
return true;
}
}
}
@@ -246,7 +246,7 @@ namespace Barotrauma.Tutorials
{ {
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f); //captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false); yield return new WaitForSeconds(1.0f, false);
} while (!Submarine.MainSub.AtEndPosition || Submarine.MainSub.DockedTo.Any()); } while (!Submarine.MainSub.AtEndPosition || !Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(segments[6]); RemoveCompletedObjective(segments[6]);
yield return new WaitForSeconds(3f, false); yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
@@ -284,7 +284,9 @@ namespace Barotrauma.Tutorials
private bool IsSelectedItem(Item item) private bool IsSelectedItem(Item item)
{ {
return captain?.SelectedConstruction == item; return
captain?.SelectedConstruction == item ||
(captain?.SelectedConstruction?.linkedTo?.Contains(item) ?? false);
} }
} }
} }
@@ -156,7 +156,7 @@ namespace Barotrauma.Tutorials
//yield return new WaitForSeconds(2.5f); //yield return new WaitForSeconds(2.5f);
doctor.SetStun(1.5f); doctor.SetStun(1.5f);
var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0); var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0, itemDamage: 0);
explosion.DisableParticles(); explosion.DisableParticles();
GameMain.GameScreen.Cam.Shake = shakeAmount; GameMain.GameScreen.Cam.Shake = shakeAmount;
explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null); explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null);
@@ -21,8 +21,8 @@ namespace Barotrauma.Tutorials
private string levelSeed; private string levelSeed;
private string levelParams; private string levelParams;
private Submarine startOutpost = null; private SubmarineInfo startOutpost = null;
private Submarine endOutpost = null; private SubmarineInfo endOutpost = null;
private bool currentTutorialCompleted = false; private bool currentTutorialCompleted = false;
private float fadeOutTime = 3f; private float fadeOutTime = 3f;
protected float waitBeforeFade = 4f; protected float waitBeforeFade = 4f;
@@ -56,13 +56,13 @@ namespace Barotrauma.Tutorials
private IEnumerable<object> Loading() private IEnumerable<object> Loading()
{ {
Submarine.MainSub = Submarine.Load(submarinePath, "", true); SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams); LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(Submarine.MainSub, "", GameMain.GameSession = new GameSession(subInfo, "",
GameModePreset.List.Find(g => g.Identifier == "tutorial")); GameModePreset.List.Find(g => g.Identifier == "tutorial"));
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
@@ -72,12 +72,12 @@ namespace Barotrauma.Tutorials
if (startOutpostPath != string.Empty) if (startOutpostPath != string.Empty)
{ {
startOutpost = Submarine.Load(startOutpostPath, "", false); startOutpost = new SubmarineInfo(startOutpostPath);
} }
if (endOutpostPath != string.Empty) if (endOutpostPath != string.Empty)
{ {
endOutpost = Submarine.Load(endOutpostPath, "", false); endOutpost = new SubmarineInfo(endOutpostPath);
} }
Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost); Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost);
@@ -160,11 +160,11 @@ namespace Barotrauma.Tutorials
switch (this.spawnSub) switch (this.spawnSub)
{ {
case "startoutpost": case "startoutpost":
spawnSub = startOutpost; spawnSub = Level.Loaded.StartOutpost;
break; break;
case "endoutpost": case "endoutpost":
spawnSub = endOutpost; spawnSub = Level.Loaded.EndOutpost;
break; break;
default: default:
@@ -533,15 +533,15 @@ namespace Barotrauma.Tutorials
titleBlock.RectTransform.IsFixedSize = true; titleBlock.RectTransform.IsFixedSize = true;
} }
List<ColorData> colorData = ColorData.GetColorData(text, out text); List<RichTextData> richTextData = RichTextData.GetRichTextData(text, out text);
GUITextBlock textBlock; GUITextBlock textBlock;
if (colorData == null) if (richTextData == null)
{ {
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true); textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), " " + text, wrap: true);
} }
else else
{ {
textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), colorData, " " + text, wrap: true); textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), richTextData, " " + text, wrap: true);
} }
textBlock.RectTransform.IsFixedSize = true; textBlock.RectTransform.IsFixedSize = true;
@@ -1,159 +1,41 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
partial class GameSession partial class GameSession
{ {
private InfoFrameTab selectedTab;
private GUIFrame infoFrame;
private readonly List<GUIButton> tabButtons = new List<GUIButton>();
private GUIFrame infoFrameContent;
public RoundSummary RoundSummary { get; private set; } public RoundSummary RoundSummary { get; private set; }
public static bool IsInfoFrameOpen => GameMain.GameSession?.infoFrame != null; public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null;
public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu;
private bool ToggleInfoFrame() private TabMenu tabMenu;
public bool ToggleTabMenu()
{ {
if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null) if (GameMain.NetworkMember != null && GameMain.NetLobbyScreen != null)
{ {
if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; } if (GameMain.NetLobbyScreen.HeadSelectionList != null) { GameMain.NetLobbyScreen.HeadSelectionList.Visible = false; }
if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; } if (GameMain.NetLobbyScreen.JobSelectionFrame != null) { GameMain.NetLobbyScreen.JobSelectionFrame.Visible = false; }
} }
if (infoFrame == null) if (tabMenu == null && GameMode is TutorialMode == false)
{ {
CreateInfoFrame(); tabMenu = new TabMenu();
SelectInfoFrameTab(null, selectedTab);
} }
else else
{ {
infoFrame = null; tabMenu = null;
NetLobbyScreen.JobInfoFrame = null;
} }
return true; return true;
} }
public void CreateInfoFrame()
{
int width = 600, height = 400;
tabButtons.Clear();
infoFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.35f), infoFrame.RectTransform, Anchor.Center) { MinSize = new Point(width, height), RelativeOffset = new Vector2(0.0f, 0.033f) });
var paddedFrame = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center), style: null);
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.08f), paddedFrame.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.01f
};
infoFrameContent = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.85f), paddedFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.08f) }, style: "InnerFrame");
var crewButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("Crew"), style: "GUITabButton")
{
UserData = InfoFrameTab.Crew,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(crewButton);
var missionButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("Mission"), style: "GUITabButton")
{
UserData = InfoFrameTab.Mission,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(missionButton);
if (GameMain.NetworkMember != null)
{
var myCharacterButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("MyCharacter"), style: "GUITabButton")
{
UserData = InfoFrameTab.MyCharacter,
OnClicked = SelectInfoFrameTab
};
tabButtons.Add(myCharacterButton);
}
/*TODO: fix
if (GameMain.Server != null)
{
var manageButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1.0f), buttonArea.RectTransform), TextManager.Get("ManagePlayers"))
{
UserData = InfoFrameTab.ManagePlayers,
OnClicked = SelectInfoFrameTab
};
}*/
}
private bool SelectInfoFrameTab(GUIButton button, object userData)
{
selectedTab = (InfoFrameTab)userData;
CreateInfoFrame();
tabButtons.ForEach(tb => tb.Selected = (InfoFrameTab)tb.UserData == selectedTab);
switch (selectedTab)
{
case InfoFrameTab.Crew:
CrewManager.CreateCrewListFrame(CrewManager.GetCharacters(), infoFrameContent);
break;
case InfoFrameTab.Mission:
CreateMissionInfo(infoFrameContent);
break;
case InfoFrameTab.MyCharacter:
if (GameMain.NetworkMember == null) { return false; }
GameMain.NetLobbyScreen.CreatePlayerFrame(infoFrameContent);
break;
case InfoFrameTab.ManagePlayers:
//TODO: fix
//GameMain.Server.ManagePlayersFrame(infoFrameContent);
break;
}
return true;
}
private void CreateMissionInfo(GUIFrame infoFrame)
{
infoFrameContent.ClearChildren();
var isTraitor = GameMain.Client?.Character?.IsTraitor ?? false;
var missionFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, isTraitor ? 0.95f : 0.45f), infoFrameContent.RectTransform))
{
RelativeSpacing = 0.05f
};
if (Mission != null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Name, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), TextManager.GetWithVariable("MissionReward", "[reward]", Mission.Reward.ToString()));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform), Mission.Description, wrap: true);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionFrame.RectTransform, Anchor.TopCenter), TextManager.Get("NoMission"), font: GUI.LargeFont);
}
if (isTraitor)
{
var traitorFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.45f), infoFrameContent.RectTransform, Anchor.BottomLeft))
{
RelativeSpacing = 0.05f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), TextManager.Get("Traitors"), font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorFrame.RectTransform), GameMain.Client.Character.TraitorCurrentObjective, wrap: true);
}
}
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
GameMode?.AddToGUIUpdateList(); GameMode?.AddToGUIUpdateList();
infoFrame?.AddToGUIUpdateList(); tabMenu?.AddToGUIUpdateList();
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
@@ -166,17 +48,31 @@ namespace Barotrauma
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
if (PlayerInput.KeyDown(InputType.InfoTab) && if (GameMode.IsRunning)
(GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber is GUIListBox))
{ {
if (infoFrame == null) if (tabMenu == null)
{ {
ToggleInfoFrame(); if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
{
ToggleTabMenu();
} }
} }
else if (infoFrame != null) else
{ {
ToggleInfoFrame(); tabMenu.Update();
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
{
ToggleTabMenu();
}
}
}
else
{
if (tabMenu != null)
{
ToggleTabMenu();
}
} }
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
@@ -202,9 +98,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch) public void Draw(SpriteBatch spriteBatch)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
GameMode?.Draw(spriteBatch); GameMode?.Draw(spriteBatch);
//infoFrame?.DrawManually(spriteBatch);
} }
} }
} }
@@ -24,7 +24,7 @@ namespace Barotrauma
public GUIFrame CreateSummaryFrame(string endMessage) public GUIFrame CreateSummaryFrame(string endMessage)
{ {
bool singleplayer = GameMain.NetworkMember == null; bool singleplayer = GameMain.NetworkMember == null;
bool gameOver = gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsUnconscious); bool gameOver = gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated);
bool progress = Submarine.MainSub.AtEndPosition; bool progress = Submarine.MainSub.AtEndPosition;
if (!singleplayer) if (!singleplayer)
{ {
@@ -55,7 +55,7 @@ namespace Barotrauma
string summaryText = TextManager.GetWithVariables(gameOver ? "RoundSummaryGameOver" : string summaryText = TextManager.GetWithVariables(gameOver ? "RoundSummaryGameOver" :
(progress ? "RoundSummaryProgress" : "RoundSummaryReturn"), new string[2] { "[sub]", "[location]" }, (progress ? "RoundSummaryProgress" : "RoundSummaryReturn"), new string[2] { "[sub]", "[location]" },
new string[2] { Submarine.MainSub.Name, progress ? GameMain.GameSession.EndLocation.Name : GameMain.GameSession.StartLocation.Name }); new string[2] { Submarine.MainSub.Info.Name, progress ? GameMain.GameSession.EndLocation.Name : GameMain.GameSession.StartLocation.Name });
var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform), var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
summaryText, wrap: true); summaryText, wrap: true);
@@ -18,6 +18,7 @@ namespace Barotrauma
{ {
Graphics, Graphics,
Audio, Audio,
VoiceChat,
Controls, Controls,
#if DEBUG #if DEBUG
Debug Debug
@@ -60,6 +61,7 @@ namespace Barotrauma
keyMapping[(int)InputType.CrewOrders] = new KeyOrMouse(Keys.C); keyMapping[(int)InputType.CrewOrders] = new KeyOrMouse(Keys.C);
keyMapping[(int)InputType.Voice] = new KeyOrMouse(Keys.V); keyMapping[(int)InputType.Voice] = new KeyOrMouse(Keys.V);
keyMapping[(int)InputType.LocalVoice] = new KeyOrMouse(Keys.B);
keyMapping[(int)InputType.Command] = new KeyOrMouse(MouseButton.MiddleMouse); keyMapping[(int)InputType.Command] = new KeyOrMouse(MouseButton.MiddleMouse);
if (Language == "French") if (Language == "French")
@@ -443,11 +445,13 @@ namespace Barotrauma
UserData = tab UserData = tab
}; };
float tabWidth = 0.25f;
#if DEBUG #if DEBUG
tabWidth = 0.2f;
if (tab != Tab.Debug) if (tab != Tab.Debug)
{ {
#endif #endif
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), tabButtonHolder.RectTransform), tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform),
TextManager.Get("SettingsTab." + tab.ToString()), style: "GUITabButton") TextManager.Get("SettingsTab." + tab.ToString()), style: "GUITabButton")
{ {
UserData = tab, UserData = tab,
@@ -457,7 +461,7 @@ namespace Barotrauma
} }
else else
{ {
tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), tabButtonHolder.RectTransform), "Debug", style: "GUITabButton") tabButtons[(int)tab] = new GUIButton(new RectTransform(new Vector2(tabWidth, 1.0f), tabButtonHolder.RectTransform), "Debug", style: "GUITabButton")
{ {
UserData = tab, UserData = tab,
OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; } OnClicked = (bt, userdata) => { SelectTab((Tab)userdata); return true; }
@@ -677,7 +681,7 @@ namespace Barotrauma
var audioContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.Audio].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var audioContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.Audio].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
Stretch = true, Stretch = false,
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
@@ -718,15 +722,16 @@ namespace Barotrauma
style: "GUISlider", barSize: 0.05f) style: "GUISlider", barSize: 0.05f)
{ {
UserData = voiceChatVolumeText, UserData = voiceChatVolumeText,
BarScroll = VoiceChatVolume, Range = new Vector2(0.0f, 2.0f),
OnMoved = (scrollBar, scroll) =>
{
ChangeSliderText(scrollBar, scroll);
VoiceChatVolume = scroll;
return true;
},
Step = 0.05f Step = 0.05f
}; };
voiceChatScrollBar.BarScrollValue = VoiceChatVolume;
voiceChatScrollBar.OnMoved = (scrollBar, scroll) =>
{
ChangeSliderText(scrollBar, scrollBar.BarScrollValue);
VoiceChatVolume = scrollBar.BarScrollValue;
return true;
};
voiceChatScrollBar.OnMoved(voiceChatScrollBar, voiceChatScrollBar.BarScroll); voiceChatScrollBar.OnMoved(voiceChatScrollBar, voiceChatScrollBar.BarScroll);
GUITickBox muteOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, audioContent.RectTransform), TextManager.Get("MuteOnFocusLost")) GUITickBox muteOnFocusLostBox = new GUITickBox(new RectTransform(tickBoxScale, audioContent.RectTransform), TextManager.Get("MuteOnFocusLost"))
@@ -765,7 +770,15 @@ namespace Barotrauma
} }
}; };
new GUITextBlock(new RectTransform(textBlockScale, audioContent.RectTransform), TextManager.Get("VoiceChat"), font: GUI.SubHeadingFont); /// Voice chat tab ----------------------------------------------------------------
var voiceChatContent = new GUILayoutGroup(new RectTransform(new Vector2(0.97f, 0.97f), tabs[(int)Tab.VoiceChat].RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{
Stretch = false,
RelativeSpacing = 0.01f
};
//new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("VoiceChat"), font: GUI.SubHeadingFont);
CaptureDeviceNames = Alc.GetStringList((IntPtr)null, Alc.CaptureDeviceSpecifier); CaptureDeviceNames = Alc.GetStringList((IntPtr)null, Alc.CaptureDeviceSpecifier);
foreach (string name in CaptureDeviceNames) foreach (string name in CaptureDeviceNames)
@@ -773,7 +786,7 @@ namespace Barotrauma
DebugConsole.NewMessage(name + " " + name.Length.ToString(), Color.Lime); DebugConsole.NewMessage(name + " " + name.Length.ToString(), Color.Lime);
} }
GUITickBox directionalVoiceChat = new GUITickBox(new RectTransform(tickBoxScale, audioContent.RectTransform), TextManager.Get("DirectionalVoiceChat")) GUITickBox directionalVoiceChat = new GUITickBox(new RectTransform(tickBoxScale, voiceChatContent.RectTransform), TextManager.Get("DirectionalVoiceChat"))
{ {
Selected = UseDirectionalVoiceChat, Selected = UseDirectionalVoiceChat,
ToolTip = TextManager.Get("DirectionalVoiceChatToolTip"), ToolTip = TextManager.Get("DirectionalVoiceChatToolTip"),
@@ -794,7 +807,7 @@ namespace Barotrauma
VoiceSetting = VoiceMode.Disabled; VoiceSetting = VoiceMode.Disabled;
} }
#if (!OSX) #if (!OSX)
var deviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), audioContent.RectTransform), TrimAudioDeviceName(VoiceCaptureDevice), CaptureDeviceNames.Count); var deviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), voiceChatContent.RectTransform), TrimAudioDeviceName(VoiceCaptureDevice), CaptureDeviceNames.Count);
if (CaptureDeviceNames?.Count > 0) if (CaptureDeviceNames?.Count > 0)
{ {
foreach (string name in CaptureDeviceNames) foreach (string name in CaptureDeviceNames)
@@ -819,7 +832,7 @@ namespace Barotrauma
} }
#else #else
var defaultDeviceGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), audioContent.RectTransform), true, Anchor.CenterLeft); var defaultDeviceGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), voiceChatContent.RectTransform), true, Anchor.CenterLeft);
var currentDeviceTextBlock = new GUITextBlock(new RectTransform(new Vector2(.7f, 0.75f), null), var currentDeviceTextBlock = new GUITextBlock(new RectTransform(new Vector2(.7f, 0.75f), null),
TextManager.AddPunctuation(':', TextManager.Get("CurrentDevice"), TrimAudioDeviceName(VoiceCaptureDevice)), font: GUI.SubHeadingFont) TextManager.AddPunctuation(':', TextManager.Get("CurrentDevice"), TrimAudioDeviceName(VoiceCaptureDevice)), font: GUI.SubHeadingFont)
{ {
@@ -857,15 +870,15 @@ namespace Barotrauma
#endif #endif
var voiceModeCount = Enum.GetNames(typeof(VoiceMode)).Length; var voiceModeCount = Enum.GetNames(typeof(VoiceMode)).Length;
var voiceModeDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), audioContent.RectTransform), elementCount: voiceModeCount); var voiceModeDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), voiceChatContent.RectTransform), elementCount: voiceModeCount);
for (int i = 0; i < voiceModeCount; i++) for (int i = 0; i < voiceModeCount; i++)
{ {
var voiceMode = "VoiceMode." + ((VoiceMode)i).ToString(); var voiceMode = "VoiceMode." + ((VoiceMode)i).ToString();
voiceModeDropDown.AddItem(TextManager.Get(voiceMode), userData: i, toolTip: TextManager.Get(voiceMode + "ToolTip")); voiceModeDropDown.AddItem(TextManager.Get(voiceMode), userData: i, toolTip: TextManager.Get(voiceMode + "ToolTip"));
} }
var micVolumeText = new GUITextBlock(new RectTransform(textBlockScale, audioContent.RectTransform), TextManager.Get("MicrophoneVolume"), font: GUI.SubHeadingFont); var micVolumeText = new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("MicrophoneVolume"), font: GUI.SubHeadingFont);
var micVolumeSlider = new GUIScrollBar(new RectTransform(textBlockScale, audioContent.RectTransform), var micVolumeSlider = new GUIScrollBar(new RectTransform(textBlockScale, voiceChatContent.RectTransform),
style: "GUISlider", barSize: 0.05f) style: "GUISlider", barSize: 0.05f)
{ {
UserData = micVolumeText, UserData = micVolumeText,
@@ -882,7 +895,7 @@ namespace Barotrauma
}; };
micVolumeSlider.OnMoved(micVolumeSlider, micVolumeSlider.BarScroll); micVolumeSlider.OnMoved(micVolumeSlider, micVolumeSlider.BarScroll);
var extraVoiceSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), audioContent.RectTransform, Anchor.BottomCenter), style: null); var extraVoiceSettingsContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), voiceChatContent.RectTransform, Anchor.BottomCenter), style: null);
var voiceActivityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), extraVoiceSettingsContainer.RectTransform)) var voiceActivityGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), extraVoiceSettingsContainer.RectTransform))
{ {
@@ -928,8 +941,8 @@ namespace Barotrauma
return true; return true;
}; };
var voiceInputContainer = new GUILayoutGroup( var voiceInputContainerHorizontal = new GUILayoutGroup(
new RectTransform(new Vector2(1.0f, 0.25f), extraVoiceSettingsContainer.RectTransform) new RectTransform(new Vector2(1.0f, 0.5f), extraVoiceSettingsContainer.RectTransform)
{ {
RelativeOffset = new Vector2(0.0f, voiceActivityGroup.RectTransform.RelativeSize.Y + 0.1f) RelativeOffset = new Vector2(0.0f, voiceActivityGroup.RectTransform.RelativeSize.Y + 0.1f)
}, },
@@ -937,6 +950,11 @@ namespace Barotrauma
{ {
Visible = VoiceSetting == VoiceMode.PushToTalk Visible = VoiceSetting == VoiceMode.PushToTalk
}; };
var voiceInputContainer = new GUILayoutGroup(
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), voiceInputContainer.RectTransform), TextManager.Get("InputType.Voice"), font: GUI.SubHeadingFont);
var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice)) var voiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), voiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.Voice))
{ {
@@ -945,6 +963,39 @@ namespace Barotrauma
}; };
voiceKeyBox.OnSelected += KeyBoxSelected; voiceKeyBox.OnSelected += KeyBoxSelected;
var localVoiceInputContainer = new GUILayoutGroup(
new RectTransform(new Vector2(0.5f, 1.0f), voiceInputContainerHorizontal.RectTransform),
isHorizontal: true, childAnchor: Anchor.CenterLeft);
new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), localVoiceInputContainer.RectTransform), TextManager.Get("InputType.LocalVoice"), font: GUI.SubHeadingFont);
var localVoiceKeyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), localVoiceInputContainer.RectTransform, Anchor.TopRight), text: KeyBindText(InputType.LocalVoice))
{
SelectedColor = Color.Gold * 0.3f,
UserData = InputType.LocalVoice
};
localVoiceKeyBox.OnSelected += KeyBoxSelected;
var cutoffPreventionText = new GUITextBlock(new RectTransform(textBlockScale, voiceChatContent.RectTransform), TextManager.Get("CutoffPrevention"), font: GUI.SubHeadingFont)
{
ToolTip = TextManager.Get("CutoffPreventionTooltip")
};
var cutoffPreventionSlider = new GUIScrollBar(new RectTransform(textBlockScale, voiceChatContent.RectTransform),
style: "GUISlider", barSize: 0.05f)
{
UserData = micVolumeText,
Range = new Vector2(0,540),
Step = 1.0f / 9.0f
};
cutoffPreventionSlider.BarScrollValue = VoiceChatCutoffPrevention;
cutoffPreventionSlider.OnMoved = (scrollBar, scroll) =>
{
VoiceChatCutoffPrevention = (int)scrollBar.BarScrollValue;
cutoffPreventionText.Text = TextManager.Get("CutoffPrevention") +
" " + TextManager.GetWithVariable("timeformatmilliseconds", "[milliseconds]", VoiceChatCutoffPrevention.ToString());
return true;
};
cutoffPreventionSlider.OnMoved(cutoffPreventionSlider, cutoffPreventionSlider.BarScrollValue);
voiceModeDropDown.OnSelected = (GUIComponent selected, object userData) => voiceModeDropDown.OnSelected = (GUIComponent selected, object userData) =>
{ {
try try
@@ -961,7 +1012,7 @@ namespace Barotrauma
{ {
VoiceSetting = vMode = VoiceMode.Disabled; VoiceSetting = vMode = VoiceMode.Disabled;
voiceActivityGroup.Visible = false; voiceActivityGroup.Visible = false;
voiceInputContainer.Visible = false; voiceInputContainerHorizontal.Visible = false;
return true; return true;
} }
} }
@@ -977,7 +1028,7 @@ namespace Barotrauma
noiseGateText.Visible = (vMode == VoiceMode.Activity); noiseGateText.Visible = (vMode == VoiceMode.Activity);
noiseGateSlider.Visible = (vMode == VoiceMode.Activity); noiseGateSlider.Visible = (vMode == VoiceMode.Activity);
voiceActivityGroup.Visible = (vMode != VoiceMode.Disabled); voiceActivityGroup.Visible = (vMode != VoiceMode.Disabled);
voiceInputContainer.Visible = (vMode == VoiceMode.PushToTalk); voiceInputContainerHorizontal.Visible = (vMode == VoiceMode.PushToTalk);
UnsavedSettings = true; UnsavedSettings = true;
} }
catch (Exception e) catch (Exception e)
@@ -1182,7 +1233,7 @@ namespace Barotrauma
{ RelativeOffset = new Vector2(0.02f, 0.02f) }) { RelativeOffset = new Vector2(0.02f, 0.02f) })
{ RelativeSpacing = 0.01f }; { RelativeSpacing = 0.01f };
var automaticQuickStartTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Enable automatic quickstart", style: "GUITickBox"); var automaticQuickStartTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic quickstart enabled", style: "GUITickBox");
automaticQuickStartTickBox.Selected = AutomaticQuickStartEnabled; automaticQuickStartTickBox.Selected = AutomaticQuickStartEnabled;
automaticQuickStartTickBox.ToolTip = "Will the game automatically move on to Quickstart when the game is launched"; automaticQuickStartTickBox.ToolTip = "Will the game automatically move on to Quickstart when the game is launched";
automaticQuickStartTickBox.OnSelected = (tickBox) => automaticQuickStartTickBox.OnSelected = (tickBox) =>
@@ -1192,7 +1243,7 @@ namespace Barotrauma
return true; return true;
}; };
var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Show splash screen", style: "GUITickBox"); var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Splash screen enabled", style: "GUITickBox");
showSplashScreenTickBox.Selected = EnableSplashScreen; showSplashScreenTickBox.Selected = EnableSplashScreen;
showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched"; showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched";
showSplashScreenTickBox.OnSelected = (tickBox) => showSplashScreenTickBox.OnSelected = (tickBox) =>
@@ -1202,7 +1253,7 @@ namespace Barotrauma
return true; return true;
}; };
var verboseLoggingTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Enable verbose logging", style: "GUITickBox"); var verboseLoggingTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Verbose logging enabled", style: "GUITickBox");
verboseLoggingTickBox.Selected = VerboseLogging; verboseLoggingTickBox.Selected = VerboseLogging;
verboseLoggingTickBox.ToolTip = "Should verbose logging be used"; verboseLoggingTickBox.ToolTip = "Should verbose logging be used";
verboseLoggingTickBox.OnSelected = (tickBox) => verboseLoggingTickBox.OnSelected = (tickBox) =>
@@ -1211,6 +1262,16 @@ namespace Barotrauma
UnsavedSettings = true; UnsavedSettings = true;
return true; return true;
}; };
var textManagerDebugModeTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "TextManager debug mode enabled", style: "GUITickBox");
textManagerDebugModeTickBox.Selected = TextManagerDebugModeEnabled;
textManagerDebugModeTickBox.ToolTip = "Does the TextManager return the text tags for debug purposes?";
textManagerDebugModeTickBox.OnSelected = (tickBox) =>
{
TextManagerDebugModeEnabled = tickBox.Selected;
UnsavedSettings = true;
return true;
};
#endif #endif
UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created UnsavedSettings = false; // Reset unsaved settings to false once the UI has been created
@@ -1279,7 +1340,7 @@ namespace Barotrauma
string[] prefixes = { "OpenAL Soft on " }; string[] prefixes = { "OpenAL Soft on " };
foreach (string prefix in prefixes) foreach (string prefix in prefixes)
{ {
if (name.StartsWith(prefix, StringComparison.InvariantCulture)) if (name.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
{ {
return name.Remove(0, prefix.Length); return name.Remove(0, prefix.Length);
} }
@@ -1292,7 +1353,7 @@ namespace Barotrauma
{ {
switch (tab) switch (tab)
{ {
case Tab.Audio: case Tab.VoiceChat:
if (VoiceSetting != VoiceMode.Disabled) if (VoiceSetting != VoiceMode.Disabled)
{ {
if (GameMain.Client == null && VoipCapture.Instance == null) if (GameMain.Client == null && VoipCapture.Instance == null)
@@ -43,6 +43,7 @@ namespace Barotrauma
public Vector2[] SlotPositions; public Vector2[] SlotPositions;
public static Point SlotSize; public static Point SlotSize;
public static int Spacing; public static int Spacing;
public static int HideButtonWidth;
private Layout layout; private Layout layout;
public Layout CurrentLayout public Layout CurrentLayout
@@ -72,21 +73,43 @@ namespace Barotrauma
get { return personalSlotArea; } get { return personalSlotArea; }
} }
private GUIImage[] indicators = new GUIImage[5];
private int[] indicatorIndexes = new int[5];
private Vector2 indicatorSpriteSize;
private GUILayoutGroup indicatorGroup;
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
Hidden = true; Hidden = true;
hideButton = new GUIButton(new RectTransform(new Point((int)(30 * GUI.Scale), (int)(60 * GUI.Scale)), GUI.Canvas) hideButton = new GUIButton(new RectTransform(new Point((int)(31f * (HUDLayoutSettings.BottomRightInfoArea.Height / 100f)), HUDLayoutSettings.BottomRightInfoArea.Height), GUI.Canvas)
{ AbsoluteOffset = HUDLayoutSettings.CrewArea.Location }, { AbsoluteOffset = HUDLayoutSettings.CrewArea.Location },
"", style: "UIToggleButton"); "", style: "EquipmentToggleButton");
hideButton.Children.ForEach(c => c.SpriteEffects = SpriteEffects.FlipHorizontally);
indicatorGroup = new GUILayoutGroup(new RectTransform(Point.Zero, hideButton.RectTransform)) { IsHorizontal = false };
indicatorGroup.ChildAnchor = Anchor.TopCenter;
indicatorSpriteSize = GUI.Style.GetComponentStyle("EquipmentIndicatorDivingSuit").Sprites[GUIComponent.ComponentState.None][0].Sprite.size;
indicators[0] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorDivingSuit");
indicators[1] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorID");
indicators[2] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorOutfit");
indicators[3] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorHeadwear");
indicators[4] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorHeadphones");
indicatorIndexes[0] = FindLimbSlot(InvSlotType.OuterClothes);
indicatorIndexes[1] = FindLimbSlot(InvSlotType.Card);
indicatorIndexes[2] = FindLimbSlot(InvSlotType.InnerClothes);
indicatorIndexes[3] = FindLimbSlot(InvSlotType.Head);
indicatorIndexes[4] = FindLimbSlot(InvSlotType.Headset);
for (int i = 0; i < indicators.Length; i++)
{
indicators[i].CanBeFocused = false;
}
hideButton.OnClicked += (GUIButton btn, object userdata) => hideButton.OnClicked += (GUIButton btn, object userdata) =>
{ {
hidePersonalSlots = !hidePersonalSlots; hidePersonalSlots = !hidePersonalSlots;
foreach (GUIComponent child in btn.Children)
{
child.SpriteEffects = hidePersonalSlots ? SpriteEffects.None : SpriteEffects.FlipHorizontally;
}
return true; return true;
}; };
@@ -245,6 +268,26 @@ namespace Barotrauma
return false; return false;
} }
private void SetIndicatorSizes()
{
indicatorGroup.RectTransform.AbsoluteOffset = new Point((int)Math.Round(4 * GUI.Scale), (int)Math.Round(7 * GUI.Scale));
indicatorGroup.RectTransform.NonScaledSize = new Point(hideButton.Rect.Width - indicatorGroup.RectTransform.AbsoluteOffset.X * 2, hideButton.Rect.Height - indicatorGroup.RectTransform.AbsoluteOffset.Y * 2);
indicatorGroup.AbsoluteSpacing = (int)Math.Ceiling(2 * GUI.Scale);
int indicatorHeight = (indicatorGroup.RectTransform.NonScaledSize.Y - indicatorGroup.AbsoluteSpacing * (indicators.Length - 1)) / indicators.Length;
int indicatorWidth = (int)(indicatorSpriteSize.X / (indicatorSpriteSize.Y / indicatorHeight));
if (HideButtonWidth % 2 != indicatorWidth % 2) indicatorWidth++;
Point indicatorSize = new Point(indicatorWidth, indicatorHeight);
for (int i = 0; i < indicators.Length; i++)
{
indicators[i].RectTransform.NonScaledSize = indicatorSize;
}
}
private void SetSlotPositions(Layout layout) private void SetSlotPositions(Layout layout)
{ {
bool isFourByThree = GUI.IsFourByThree(); bool isFourByThree = GUI.IsFourByThree();
@@ -257,6 +300,8 @@ namespace Barotrauma
Spacing = (int)(8 * UIScale); Spacing = (int)(8 * UIScale);
} }
HideButtonWidth = (int)(31f * (HUDLayoutSettings.BottomRightInfoArea.Height / 100f));
SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint(); SlotSize = !isFourByThree ? (SlotSpriteSmall.size * UIScale).ToPoint() : (SlotSpriteSmall.size * UIScale * .925f).ToPoint();
int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight; int bottomOffset = SlotSize.Y + Spacing * 2 + ContainedIndicatorHeight;
@@ -272,8 +317,7 @@ namespace Barotrauma
int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s)); int normalSlotCount = SlotTypes.Count(s => !PersonalSlots.HasFlag(s));
int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2; int x = GameMain.GraphicsWidth / 2 - normalSlotCount * (SlotSize.X + Spacing) / 2;
int upperX = HUDLayoutSettings.BottomRightInfoArea.X - Spacing * 2 - SlotSize.X - SlotSize.X / 2; int upperX = HUDLayoutSettings.BottomRightInfoArea.X - SlotSize.X - Spacing * 4 - HideButtonWidth;
//int upperX = GameMain.GraphicsWidth - personalSlotCount * (slotSize.X + spacing) + (int)(11 * GUI.Scale) + spacing;
//make sure the rightmost normal slot doesn't overlap with the personal slots //make sure the rightmost normal slot doesn't overlap with the personal slots
x -= Math.Max((x + normalSlotCount * (SlotSize.X + Spacing)) - (upperX - personalSlotCount * (SlotSize.X + Spacing)), 0); x -= Math.Max((x + normalSlotCount * (SlotSize.X + Spacing)) - (upperX - personalSlotCount * (SlotSize.X + Spacing)), 0);
@@ -300,11 +344,11 @@ namespace Barotrauma
if (hideButtonSlotIndex > -1) if (hideButtonSlotIndex > -1)
{ {
hideButton.RectTransform.SetPosition(Anchor.TopLeft, Pivot.TopLeft); hideButton.RectTransform.SetPosition(Anchor.TopLeft, Pivot.TopLeft);
hideButton.RectTransform.NonScaledSize = new Point(SlotSize.X / 2, HUDLayoutSettings.BottomRightInfoArea.Height); hideButton.RectTransform.NonScaledSize = new Point(HideButtonWidth, HUDLayoutSettings.BottomRightInfoArea.Height);
hideButton.RectTransform.AbsoluteOffset = new Point( hideButton.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.BottomRightInfoArea.Left - HideButtonWidth + GUI.IntScaleCeiling(2f), HUDLayoutSettings.BottomRightInfoArea.Y + GUI.IntScaleCeiling(1f));
personalSlotArea.Right + Spacing,
HUDLayoutSettings.BottomRightInfoArea.Y);
hideButton.Visible = true; hideButton.Visible = true;
SetIndicatorSizes();
} }
} }
break; break;
@@ -447,7 +491,7 @@ namespace Barotrauma
((selectedSlot != null && selectedSlot.IsSubSlot) || (draggingItem != null && (draggingSlot == null || !draggingSlot.MouseOn()))); ((selectedSlot != null && selectedSlot.IsSubSlot) || (draggingItem != null && (draggingSlot == null || !draggingSlot.MouseOn())));
if (CharacterHealth.OpenHealthWindow != null) hoverOnInventory = true; if (CharacterHealth.OpenHealthWindow != null) hoverOnInventory = true;
if (layout == Layout.Default) if (layout == Layout.Default && (Screen.Selected != GameMain.SubEditorScreen || Screen.Selected is SubEditorScreen editor && editor.WiringMode))
{ {
if (hideButton.Visible) if (hideButton.Visible)
{ {
@@ -458,6 +502,7 @@ namespace Barotrauma
Math.Min(hidePersonalSlotsState + deltaTime * 5.0f, 1.0f) : Math.Min(hidePersonalSlotsState + deltaTime * 5.0f, 1.0f) :
Math.Max(hidePersonalSlotsState - deltaTime * 5.0f, 0.0f); Math.Max(hidePersonalSlotsState - deltaTime * 5.0f, 0.0f);
bool personalSlotsMoving = hidePersonalSlotsState > 0 && hidePersonalSlotsState < 1f;
for (int i = 0; i < slots.Length; i++) for (int i = 0; i < slots.Length; i++)
{ {
if (!PersonalSlots.HasFlag(SlotTypes[i])) { continue; } if (!PersonalSlots.HasFlag(SlotTypes[i])) { continue; }
@@ -466,6 +511,7 @@ namespace Barotrauma
if (selectedSlot?.Slot == slots[i]) { selectedSlot = null; } if (selectedSlot?.Slot == slots[i]) { selectedSlot = null; }
highlightedSubInventorySlots.RemoveWhere(s => s.Slot == slots[i]); highlightedSubInventorySlots.RemoveWhere(s => s.Slot == slots[i]);
} }
slots[i].IsMoving = personalSlotsMoving;
slots[i].DrawOffset = Vector2.Lerp(Vector2.Zero, new Vector2(personalSlotArea.Width, 0.0f), hidePersonalSlotsState); slots[i].DrawOffset = Vector2.Lerp(Vector2.Zero, new Vector2(personalSlotArea.Width, 0.0f), hidePersonalSlotsState);
} }
} }
@@ -539,6 +585,19 @@ namespace Barotrauma
} }
} }
// In sub editor we cannot hover over the slot because they are not rendered so we override it here
if (Screen.Selected is SubEditorScreen subEditor && !subEditor.WiringMode)
{
for (int i = 0; i < slots.Length; i++)
{
var subInventory = GetSubInventory(i);
if (subInventory != null)
{
ShowSubInventory(new SlotReference(this, slots[i], i, false, Items[i].GetComponent<ItemContainer>().Inventory), deltaTime, cam, hideSubInventories, true);
}
}
}
foreach (var subInventorySlot in hideSubInventories) foreach (var subInventorySlot in hideSubInventories)
{ {
if (subInventorySlot.Inventory == null) continue; if (subInventorySlot.Inventory == null) continue;
@@ -551,6 +610,8 @@ namespace Barotrauma
if (character == Character.Controlled && character.SelectedCharacter == null) // Permanently open subinventories only available when the default UI layout is in use -> not when grabbing characters if (character == Character.Controlled && character.SelectedCharacter == null) // Permanently open subinventories only available when the default UI layout is in use -> not when grabbing characters
{ {
UpdateEquipmentIndicators();
//remove the highlighted slots of other characters' inventories when not grabbing anyone //remove the highlighted slots of other characters' inventories when not grabbing anyone
highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory != this && s.ParentInventory?.Owner is Character); highlightedSubInventorySlots.RemoveWhere(s => s.ParentInventory != this && s.ParentInventory?.Owner is Character);
@@ -652,6 +713,39 @@ namespace Barotrauma
} }
} }
private void UpdateEquipmentIndicators()
{
for (int i = 0; i < indicators.Length; i++)
{
Item item = Items[indicatorIndexes[i]];
if (item != null)
{
Wearable wearable = item.GetComponent<Wearable>();
if (wearable != null && wearable.DisplayContainedStatus)
{
float conditionPercentage = item.GetContainedItemConditionPercentage();
if (conditionPercentage != -1)
{
indicators[i].Color = ToolBox.GradientLerp(conditionPercentage, GUI.Style.EquipmentIndicatorRunningOut, GUI.Style.EquipmentIndicatorEquipped);
}
else
{
indicators[i].Color = GUI.Style.EquipmentIndicatorRunningOut;
}
}
else
{
indicators[i].Color = GUI.Style.EquipmentIndicatorEquipped;
}
}
else
{
indicators[i].Color = GUI.Style.EquipmentIndicatorNotEquipped;
}
}
}
private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory) private void ShowSubInventory(SlotReference slotRef, float deltaTime, Camera cam, List<SlotReference> hideSubInventories, bool isEquippedSubInventory)
{ {
Rectangle hoverArea = GetSubInventoryHoverArea(slotRef); Rectangle hoverArea = GetSubInventoryHoverArea(slotRef);
@@ -771,7 +865,7 @@ namespace Barotrauma
{ {
return QuickUseAction.TakeFromCharacter; return QuickUseAction.TakeFromCharacter;
} }
else if (character.SelectedItems.Any(i => i?.OwnInventory != null && i.OwnInventory.CanBePut(item))) else if (character.SelectedItems.Any(i => i?.OwnInventory != null && i.OwnInventory.CanBePut(item)) && allowInventorySwap)
{ {
return QuickUseAction.PutToEquippedItem; return QuickUseAction.PutToEquippedItem;
} }
@@ -799,13 +893,40 @@ namespace Barotrauma
private void QuickUseItem(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment) private void QuickUseItem(Item item, bool allowEquip, bool allowInventorySwap, bool allowApplyTreatment)
{ {
if (Screen.Selected is SubEditorScreen editor && !editor.WiringMode && !Submarine.Unloading)
{
// Find the slot the item was contained in and flash it
if (item.ParentInventory?.slots != null)
{
var invSlots = item.ParentInventory.slots;
var invItems = item.ParentInventory.Items;
for (int i = 0; i < invSlots.Length; i++)
{
if (i < 0 || invSlots.Length <= i || i < 0 || invItems.Length <= i) { break; }
var slot = invSlots[i];
var slotItem = invItems[i];
if (slotItem == item)
{
slot.ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.4f);
GUI.PlayUISound(GUISoundType.PickItem);
break;
}
}
}
item.Remove();
return;
}
var quickUseAction = GetQuickUseAction(item, allowEquip, allowInventorySwap, allowApplyTreatment); var quickUseAction = GetQuickUseAction(item, allowEquip, allowInventorySwap, allowApplyTreatment);
bool success = false; bool success = false;
switch (quickUseAction) switch (quickUseAction)
{ {
case QuickUseAction.Equip: case QuickUseAction.Equip:
//attempt to put in a free slot first //attempt to put in a free slot first
for (int i = 0; i < capacity; i++) for (int i = capacity - 1; i >= 0; i--)
{ {
if (Items[i] != null) continue; if (Items[i] != null) continue;
if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) continue; if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) continue;
@@ -815,7 +936,7 @@ namespace Barotrauma
if (!success) if (!success)
{ {
for (int i = 0; i < capacity; i++) for (int i = capacity - 1; i >= 0; i--)
{ {
if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) continue; if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) continue;
//something else already equipped in the slot, attempt to unequip it //something else already equipped in the slot, attempt to unequip it
@@ -964,7 +1085,7 @@ namespace Barotrauma
if (limbSlotIcons.ContainsKey(SlotTypes[i])) if (limbSlotIcons.ContainsKey(SlotTypes[i]))
{ {
var icon = limbSlotIcons[SlotTypes[i]]; var icon = limbSlotIcons[SlotTypes[i]];
icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, GUIColorSettings.EquipmentSlotIconColor, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X); icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, GUI.Style.EquipmentSlotIconColor, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X);
} }
continue; continue;
} }
@@ -52,10 +52,14 @@ namespace Barotrauma.Items.Components
get { return sounds.Count > 0; } get { return sounds.Count > 0; }
} }
private bool[] hasSoundsOfType; private readonly bool[] hasSoundsOfType;
private Dictionary<ActionType, List<ItemSound>> sounds; private readonly Dictionary<ActionType, List<ItemSound>> sounds;
private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes; private Dictionary<ActionType, SoundSelectionMode> soundSelectionModes;
protected float correctionTimer;
public float IsActiveTimer;
public GUILayoutSettings DefaultLayout { get; protected set; } public GUILayoutSettings DefaultLayout { get; protected set; }
public GUILayoutSettings AlternativeLayout { get; protected set; } public GUILayoutSettings AlternativeLayout { get; protected set; }
@@ -230,20 +234,23 @@ namespace Barotrauma.Items.Components
if (loopingSound != null) if (loopingSound != null)
{ {
float targetGain = 0.0f;
if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range || if (Vector3.DistanceSquared(GameMain.SoundManager.ListenerPosition, new Vector3(item.WorldPosition, 0.0f)) > loopingSound.Range * loopingSound.Range ||
(targetGain = GetSoundVolume(loopingSound)) <= 0.0001f) (GetSoundVolume(loopingSound)) <= 0.0001f)
{ {
if (loopingSoundChannel != null) if (loopingSoundChannel != null)
{ {
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSoundChannel.FadeOutAndDispose();
loopingSoundChannel = null;
loopingSound = null;
} }
return; return;
} }
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound) if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
{ {
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSoundChannel.FadeOutAndDispose();
loopingSoundChannel = null;
loopingSound = null;
} }
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{ {
@@ -259,7 +266,6 @@ namespace Barotrauma.Items.Components
return; return;
} }
ItemSound itemSound = null;
var matchingSounds = sounds[type]; var matchingSounds = sounds[type];
if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying) if (loopingSoundChannel == null || !loopingSoundChannel.IsPlaying)
{ {
@@ -277,7 +283,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (ItemSound sound in matchingSounds) foreach (ItemSound sound in matchingSounds)
{ {
PlaySound(sound, item.WorldPosition, user); PlaySound(sound, item.WorldPosition);
} }
return; return;
} }
@@ -286,13 +292,12 @@ namespace Barotrauma.Items.Components
index = Rand.Int(matchingSounds.Count); index = Rand.Int(matchingSounds.Count);
} }
itemSound = matchingSounds[index]; PlaySound(matchingSounds[index], item.WorldPosition);
PlaySound(matchingSounds[index], item.WorldPosition, user);
} }
} }
private void PlaySound(ItemSound itemSound, Vector2 position, Character user = null) private void PlaySound(ItemSound itemSound, Vector2 position)
{ {
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range) if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range)
{ {
@@ -301,8 +306,7 @@ namespace Barotrauma.Items.Components
if (itemSound.Loop) if (itemSound.Loop)
{ {
loopingSound = itemSound; if (loopingSoundChannel != null && loopingSoundChannel.Sound != itemSound.RoundSound.Sound)
if (loopingSoundChannel != null && loopingSoundChannel.Sound != loopingSound.RoundSound.Sound)
{ {
loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null; loopingSoundChannel.FadeOutAndDispose(); loopingSoundChannel = null;
} }
@@ -310,6 +314,7 @@ namespace Barotrauma.Items.Components
{ {
float volume = GetSoundVolume(itemSound); float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; } if (volume <= 0.0001f) { return; }
loopingSound = itemSound;
loopingSoundChannel = loopingSound.RoundSound.Sound.Play( loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(position.X, position.Y, 0.0f), new Vector3(position.X, position.Y, 0.0f),
0.01f, 0.01f,
@@ -3,11 +3,17 @@ using Barotrauma.Lights;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Collections.Generic;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
partial class LightComponent : Powered, IServerSerializable, IDrawableComponent partial class LightComponent : Powered, IServerSerializable, IDrawableComponent
{ {
private bool? lastReceivedState;
private CoroutineHandle resetPredictionCoroutine;
private float resetPredictionTimer;
public Vector2 DrawSize public Vector2 DrawSize
{ {
get { return new Vector2(light.Range * 2, light.Range * 2); } get { return new Vector2(light.Range * 2, light.Range * 2); }
@@ -32,6 +38,12 @@ namespace Barotrauma.Items.Components
light.Color = LightColor.Multiply(brightness); light.Color = LightColor.Multiply(brightness);
} }
public override void OnItemLoaded()
{
base.OnItemLoaded();
SetLightSourceState(IsActive, lightBrightness);
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn) if (light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn)
@@ -49,9 +61,36 @@ namespace Barotrauma.Items.Components
} }
} }
partial void OnStateChanged()
{
if (GameMain.Client == null || !lastReceivedState.HasValue) { return; }
//reset to last known server state after the state hasn't changed in 1.0 seconds client-side
resetPredictionTimer = 1.0f;
if (resetPredictionCoroutine == null || !CoroutineManager.IsCoroutineRunning(resetPredictionCoroutine))
{
resetPredictionCoroutine = CoroutineManager.StartCoroutine(ResetPredictionAfterDelay());
}
}
/// <summary>
/// Reset client-side prediction of the light's state to the last known state sent by the server after resetPredictionTimer runs out
/// </summary>
private IEnumerable<object> ResetPredictionAfterDelay()
{
while (resetPredictionTimer > 0.0f)
{
resetPredictionTimer -= CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
if (lastReceivedState.HasValue) { IsActive = lastReceivedState.Value; }
resetPredictionCoroutine = null;
yield return CoroutineStatus.Success;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
IsOn = msg.ReadBoolean(); IsActive = msg.ReadBoolean();
lastReceivedState = IsActive;
} }
protected override void RemoveComponentSpecific() protected override void RemoveComponentSpecific()
@@ -140,11 +140,11 @@ namespace Barotrauma.Items.Components
var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1f), bottomFrame.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomLeft); var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 1f), bottomFrame.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomLeft);
// === INPUT SLOTS === // // === INPUT SLOTS === //
inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.8f, 1f), inputArea.RectTransform), style: null); inputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(0.7f, 1f), inputArea.RectTransform), style: null);
new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false }; new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false };
// === ACTIVATE BUTTON === // // === ACTIVATE BUTTON === //
var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterRight); var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterRight);
activateButton = new GUIButton(new RectTransform(new Vector2(1f, 0.6f), buttonFrame.RectTransform), activateButton = new GUIButton(new RectTransform(new Vector2(1f, 0.6f), buttonFrame.RectTransform),
TextManager.Get("FabricatorCreate"), style: "DeviceButton") TextManager.Get("FabricatorCreate"), style: "DeviceButton")
{ {
@@ -357,6 +357,8 @@ namespace Barotrauma.Items.Components
{ {
overlayComponent.RectTransform.SetAsLastChild(); overlayComponent.RectTransform.SetAsLastChild();
if (outputContainer.Inventory.Items.First() != null) { return; }
FabricationRecipe targetItem = fabricatedItem ?? selectedItem; FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
if (targetItem != null) if (targetItem != null)
{ {
@@ -91,6 +91,7 @@ namespace Barotrauma.Items.Components
if ((item.Submarine == null && displayedSubs.Count > 0) || //item not inside a sub anymore, but display is still showing subs if ((item.Submarine == null && displayedSubs.Count > 0) || //item not inside a sub anymore, but display is still showing subs
!displayedSubs.Contains(item.Submarine) || //current sub not displayer !displayedSubs.Contains(item.Submarine) || //current sub not displayer
item.Submarine.DockedTo.Any(s => !displayedSubs.Contains(s)) || //some of the docked subs not diplayed item.Submarine.DockedTo.Any(s => !displayedSubs.Contains(s)) || //some of the docked subs not diplayed
!submarineContainer.Children.Any() || // We lack a GUI
displayedSubs.Any(s => s != item.Submarine && !item.Submarine.DockedTo.Contains(s))) //displaying a sub that shouldn't be displayed displayedSubs.Any(s => s != item.Submarine && !item.Submarine.DockedTo.Contains(s))) //displaying a sub that shouldn't be displayed
{ {
CreateHUD(); CreateHUD();
@@ -125,12 +126,14 @@ namespace Barotrauma.Items.Components
return; return;
} }
if (!submarineContainer.Children.Any()) { return; }
foreach (GUIComponent child in submarineContainer.Children.First().Children) foreach (GUIComponent child in submarineContainer.Children.First().Children)
{ {
if (child.UserData is Hull hull) if (child.UserData is Hull hull)
{ {
if (hull.Submarine == null || !hull.Submarine.IsOutpost) { continue; } if (hull.Submarine == null || !hull.Submarine.Info.IsOutpost) { continue; }
string text = TextManager.GetWithVariable("MiniMapOutpostDockingInfo", "[outpost]", hull.Submarine.Name); string text = TextManager.GetWithVariable("MiniMapOutpostDockingInfo", "[outpost]", hull.Submarine.Info.Name);
Vector2 textSize = GUI.Font.MeasureString(text); Vector2 textSize = GUI.Font.MeasureString(text);
Vector2 textPos = child.Center; Vector2 textPos = child.Center;
if (textPos.X + textSize.X / 2 > submarineContainer.Rect.Right) if (textPos.X + textSize.X / 2 > submarineContainer.Rect.Right)
@@ -151,7 +154,7 @@ namespace Barotrauma.Items.Components
foreach (Hull hull in Hull.hullList) foreach (Hull hull in Hull.hullList)
{ {
var hullFrame = submarineContainer.Children.First().FindChild(hull); var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull);
if (hullFrame == null) { continue; } if (hullFrame == null) { continue; }
if (GUI.MouseOn == hullFrame || hullFrame.IsParentOf(GUI.MouseOn)) if (GUI.MouseOn == hullFrame || hullFrame.IsParentOf(GUI.MouseOn))
@@ -175,7 +178,7 @@ namespace Barotrauma.Items.Components
foreach (Hull hull in Hull.hullList) foreach (Hull hull in Hull.hullList)
{ {
if (hull.Submarine == null) continue; if (hull.Submarine == null) continue;
var hullFrame = submarineContainer.Children.First().FindChild(hull); var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull);
if (hullFrame == null) continue; if (hullFrame == null) continue;
hullDatas.TryGetValue(hull, out HullData hullData); hullDatas.TryGetValue(hull, out HullData hullData);
@@ -99,6 +99,10 @@ namespace Barotrauma.Items.Components
Step = 0.05f, Step = 0.05f,
OnMoved = (GUIScrollBar scrollBar, float barScroll) => OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{ {
if (pumpSpeedLockTimer <= 0.0f)
{
targetLevel = null;
}
float newValue = barScroll * 200.0f - 100.0f; float newValue = barScroll * 200.0f - 100.0f;
if (Math.Abs(newValue - FlowPercentage) < 0.1f) { return false; } if (Math.Abs(newValue - FlowPercentage) < 0.1f) { return false; }
@@ -192,9 +192,10 @@ namespace Barotrauma.Items.Components
{ {
RelativeOffset = new Vector2(0, fissionMeter.RectTransform.RelativeOffset.Y + meterSize.Y) RelativeOffset = new Vector2(0, fissionMeter.RectTransform.RelativeOffset.Y + meterSize.Y)
}, },
style: "DeviceSlider", barSize: 0.1f) style: "DeviceSlider", barSize: 0.15f)
{ {
Enabled = false, Enabled = false,
Step = 1.0f / 255,
OnMoved = (GUIScrollBar bar, float scrollAmount) => OnMoved = (GUIScrollBar bar, float scrollAmount) =>
{ {
LastUser = Character.Controlled; LastUser = Character.Controlled;
@@ -209,9 +210,10 @@ namespace Barotrauma.Items.Components
{ {
RelativeOffset = new Vector2(0, turbineMeter.RectTransform.RelativeOffset.Y + meterSize.Y) RelativeOffset = new Vector2(0, turbineMeter.RectTransform.RelativeOffset.Y + meterSize.Y)
}, },
style: "DeviceSlider", barSize: 0.1f, isHorizontal: true) style: "DeviceSlider", barSize: 0.15f, isHorizontal: true)
{ {
Enabled = false, Enabled = false,
Step = 1.0f / 255,
OnMoved = (GUIScrollBar bar, float scrollAmount) => OnMoved = (GUIScrollBar bar, float scrollAmount) =>
{ {
LastUser = Character.Controlled; LastUser = Character.Controlled;
@@ -715,8 +717,14 @@ namespace Barotrauma.Items.Components
targetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8); targetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8); degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8);
if (Math.Abs(FissionRateScrollBar.BarScroll - targetFissionRate / 100.0f) > 0.01f)
{
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f; FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f;
}
if (Math.Abs(TurbineOutputScrollBar.BarScroll - targetTurbineOutput / 100.0f) > 0.01f)
{
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f; TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f;
}
IsActive = true; IsActive = true;
} }
@@ -490,6 +490,7 @@ namespace Barotrauma.Items.Components
disruptedDirections.Clear(); disruptedDirections.Clear();
foreach (AITarget t in AITarget.List) foreach (AITarget t in AITarget.List)
{ {
if (t.Entity is Character c && c.Params.HideInSonar) { continue; }
if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; } if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter); float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
@@ -647,6 +648,8 @@ namespace Barotrauma.Items.Components
if (GameMain.GameSession == null) { return; } if (GameMain.GameSession == null) { return; }
if (Level.Loaded == null) { return; }
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
GameMain.GameSession.StartLocation.Name, GameMain.GameSession.StartLocation.Name,
"outpost", "outpost",
@@ -699,8 +702,8 @@ namespace Barotrauma.Items.Components
if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } if (sub.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
sub.Name, sub.Info.DisplayName,
sub.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine", sub.Info.HasTag(SubmarineTag.Shuttle) ? "shuttle" : "submarine",
sub.WorldPosition - transducerCenter, sub.WorldPosition - transducerCenter,
displayScale, center, DisplayRadius * 0.95f); displayScale, center, DisplayRadius * 0.95f);
} }
@@ -799,8 +802,11 @@ namespace Barotrauma.Items.Components
{ {
if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } if (Level.Loaded != null && dockingPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (dockingPort.Item.Submarine == null) { continue; }
if (dockingPort.Item.Submarine.Info.IsWreck) { continue; }
//don't show the docking ports of the opposing team on the sonar //don't show the docking ports of the opposing team on the sonar
if (item.Submarine != null && dockingPort.Item.Submarine != null) if (item.Submarine != null)
{ {
if ((dockingPort.Item.Submarine.TeamID == Character.TeamType.Team1 && item.Submarine.TeamID == Character.TeamType.Team2) || if ((dockingPort.Item.Submarine.TeamID == Character.TeamType.Team1 && item.Submarine.TeamID == Character.TeamType.Team2) ||
(dockingPort.Item.Submarine.TeamID == Character.TeamType.Team2 && item.Submarine.TeamID == Character.TeamType.Team1)) (dockingPort.Item.Submarine.TeamID == Character.TeamType.Team2 && item.Submarine.TeamID == Character.TeamType.Team1))
@@ -1125,6 +1131,7 @@ namespace Barotrauma.Items.Components
foreach (Character c in Character.CharacterList) foreach (Character c in Character.CharacterList)
{ {
if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; } if (c.AnimController.CurrentHull != null || !c.Enabled) { continue; }
if (c.Params.HideInSonar) { continue; }
if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; } if (DetectSubmarineWalls && c.AnimController.CurrentHull == null && item.CurrentHull != null) { continue; }
if (c.AnimController.SimplePhysicsEnabled) if (c.AnimController.SimplePhysicsEnabled)
@@ -46,6 +46,8 @@ namespace Barotrauma.Items.Components
private float checkConnectedPortsTimer; private float checkConnectedPortsTimer;
private const float CheckConnectedPortsInterval = 1.0f; private const float CheckConnectedPortsInterval = 1.0f;
public DockingPort ActiveDockingSource, DockingTarget;
private Vector2 keyboardInput = Vector2.Zero; private Vector2 keyboardInput = Vector2.Zero;
private float inputCumulation; private float inputCumulation;
@@ -665,7 +667,7 @@ namespace Barotrauma.Items.Components
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
{ {
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsInfoFrameOpen) if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen)
{ {
Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom; Vector2 displaySubPos = (-sonar.DisplayOffset * sonar.Zoom) / sonar.Range * sonar.DisplayRadius * sonar.Zoom;
displaySubPos.Y = -displaySubPos.Y; displaySubPos.Y = -displaySubPos.Y;
@@ -0,0 +1,76 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Projectile : ItemComponent
{
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
bool isStuck = msg.ReadBoolean();
if (isStuck)
{
ushort submarineID = msg.ReadUInt16();
ushort hullID = msg.ReadUInt16();
Vector2 simPosition = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
Vector2 axis = new Vector2(
msg.ReadSingle(),
msg.ReadSingle());
UInt16 entityID = msg.ReadUInt16();
Entity entity = Entity.FindEntityByID(entityID);
Submarine submarine = Entity.FindEntityByID(submarineID) as Submarine;
Hull hull = Entity.FindEntityByID(hullID) as Hull;
item.Submarine = submarine;
item.CurrentHull = hull;
item.body.SetTransform(simPosition, item.body.Rotation);
if (entity is Character character)
{
byte limbIndex = msg.ReadByte();
if (limbIndex >= character.AnimController.Limbs.Length)
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Limb index out of bounds ({limbIndex}, character: {character.ToString()})");
return;
}
if (character.Removed) { return; }
var limb = character.AnimController.Limbs[limbIndex];
StickToTarget(limb.body.FarseerBody, axis);
}
else if (entity is Structure structure)
{
byte bodyIndex = msg.ReadByte();
if (bodyIndex == 255) { bodyIndex = 0; }
if (bodyIndex >= structure.Bodies.Count)
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Structure body index out of bounds ({bodyIndex}, structure: {structure.ToString()})");
return;
}
var body = structure.Bodies[bodyIndex];
StickToTarget(body, axis);
}
else if (entity is Item item)
{
if (item.Removed) { return; }
StickToTarget(item.body.FarseerBody, axis);
}
else if (entity is Submarine sub)
{
StickToTarget(sub.PhysicsBody.FarseerBody, axis);
}
else
{
DebugConsole.ThrowError($"Failed to read a projectile update from the server. Invalid stick target ({entity?.ToString() ?? "null"}, {entityID})");
}
}
else
{
Unstick();
}
}
}
}
@@ -117,9 +117,18 @@ namespace Barotrauma.Items.Components
case "emitter": case "emitter":
case "particleemitter": case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement)); particleEmitters.Add(new ParticleEmitter(subElement));
particleEmitterConditionRanges.Add(new Vector2( float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f);
subElement.GetAttributeFloat("mincondition", 0.0f), float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f);
subElement.GetAttributeFloat("maxcondition", 100.0f)));
if (maxCondition < minCondition)
{
DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition.");
float temp = maxCondition;
maxCondition = minCondition;
minCondition = temp;
}
particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition));
break; break;
} }
} }
@@ -236,15 +245,7 @@ namespace Barotrauma.Items.Components
DeteriorateAlways = msg.ReadBoolean(); DeteriorateAlways = msg.ReadBoolean();
ushort currentFixerID = msg.ReadUInt16(); ushort currentFixerID = msg.ReadUInt16();
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
if (currentFixerID == 0)
{
CurrentFixer = null;
}
else
{
CurrentFixer = Entity.FindEntityByID(currentFixerID) as Character;
}
} }
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
@@ -0,0 +1,172 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Rope : ItemComponent, IDrawableComponent
{
private Sprite sprite, startSprite, endSprite;
[Serialize(5, false)]
public int SpriteWidth
{
get;
set;
}
[Serialize("255,255,255,255", false)]
public Color SpriteColor
{
get;
set;
}
[Serialize(false, false)]
public bool Tile
{
get;
set;
}
public Vector2 DrawSize
{
get
{
if (target == null || source == null) { return Vector2.Zero; }
return new Vector2(
Math.Abs(target.DrawPosition.X - source.DrawPosition.X),
Math.Abs(target.DrawPosition.Y - source.DrawPosition.Y)) * 1.5f;
}
}
partial void InitProjSpecific(XElement element)
{
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "sprite":
sprite = new Sprite(subElement);
break;
case "startsprite":
startSprite = new Sprite(subElement);
break;
case "endsprite":
endSprite = new Sprite(subElement);
break;
}
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
if (target == null) { return; }
Vector2 startPos = new Vector2(source.DrawPosition.X, -source.DrawPosition.Y);
var turret = source?.GetComponent<Turret>();
if (turret != null)
{
startPos = new Vector2(source.WorldRect.X + turret.TransformedBarrelPos.X, -(source.WorldRect.Y - turret.TransformedBarrelPos.Y));
if (turret.BarrelSprite != null)
{
startPos += new Vector2((float)Math.Cos(turret.Rotation), (float)Math.Sin(turret.Rotation)) * turret.BarrelSprite.size.Y * turret.BarrelSprite.RelativeOrigin.Y * item.Scale * 0.9f;
}
}
Vector2 endPos = new Vector2(target.DrawPosition.X, -target.DrawPosition.Y);
if (Snapped)
{
float snapState = 1.0f - snapTimer / SnapAnimDuration;
Vector2 diff = target.DrawPosition - source.DrawPosition;
diff.Y = -diff.Y;
int width = (int)(SpriteWidth * snapState);
if (width > 0.0f)
{
DrawRope(spriteBatch, endPos - diff * snapState * 0.5f, endPos, width);
DrawRope(spriteBatch, startPos, startPos + diff * snapState * 0.5f, width);
}
}
else
{
DrawRope(spriteBatch, startPos, endPos, SpriteWidth);
}
if (startSprite != null || endSprite != null)
{
Vector2 dir = endPos - startPos;
float angle = (float)Math.Atan2(dir.Y, dir.X);
if (startSprite != null)
{
float depth = Math.Min(item.GetDrawDepth() + (startSprite.Depth - item.Sprite.Depth), 0.999f);
startSprite?.Draw(spriteBatch, startPos, SpriteColor, angle, depth: depth);
}
if (endSprite != null)
{
float depth = Math.Min(item.GetDrawDepth() + (endSprite.Depth - item.Sprite.Depth), 0.999f);
endSprite?.Draw(spriteBatch, endPos, SpriteColor, angle, depth: depth);
}
}
}
private void DrawRope(SpriteBatch spriteBatch, Vector2 startPos, Vector2 endPos, int width)
{
float depth = sprite == null ?
item.Sprite.Depth + 0.001f :
Math.Min(item.GetDrawDepth() + (sprite.Depth - item.Sprite.Depth), 0.999f);
if (sprite?.Texture == null)
{
GUI.DrawLine(spriteBatch,
startPos,
endPos,
SpriteColor, depth: depth, width: width);
return;
}
if (Tile)
{
float length = Vector2.Distance(startPos, endPos);
Vector2 dir = (endPos - startPos) / length;
float x;
for (x = 0.0f; x <= length - sprite.size.X; x += sprite.size.X)
{
GUI.DrawLine(spriteBatch, sprite,
startPos + dir * (x - 5.0f),
startPos + dir * (x + sprite.size.X),
SpriteColor, depth: depth, width: width);
}
float leftOver = length - x;
if (leftOver > 0.0f)
{
GUI.DrawLine(spriteBatch, sprite,
startPos + dir * (x - 5.0f),
endPos,
SpriteColor, depth: depth, width: width);
}
}
else
{
GUI.DrawLine(spriteBatch, sprite,
startPos,
endPos,
SpriteColor, depth: depth, width: width);
}
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
snapped = msg.ReadBoolean();
}
protected override void RemoveComponentSpecific()
{
sprite?.Remove(); sprite = null;
startSprite?.Remove(); startSprite = null;
endSprite?.Remove(); endSprite = null;
}
}
}
@@ -45,13 +45,48 @@ namespace Barotrauma.Items.Components
float elementSize = Math.Min(1.0f / visibleElements.Count(), 1); float elementSize = Math.Min(1.0f / visibleElements.Count(), 1);
foreach (CustomInterfaceElement ciElement in visibleElements) foreach (CustomInterfaceElement ciElement in visibleElements)
{ {
if (ciElement.ContinuousSignal) if (!string.IsNullOrEmpty(ciElement.PropertyName))
{
var layoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.02f,
UserData = ciElement
};
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform),
TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label);
var textBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), layoutGroup.RectTransform), "", style: "GUITextBoxNoIcon")
{
OverflowClip = true,
UserData = ciElement
};
//reset size restrictions set by the Style to make sure the elements can fit the interface
textBox.RectTransform.MinSize = textBox.Frame.RectTransform.MinSize = new Point(0, 0);
textBox.RectTransform.MaxSize = textBox.Frame.RectTransform.MaxSize = new Point(int.MaxValue, int.MaxValue);
textBox.OnDeselected += (tb, key) =>
{
if (GameMain.Client == null)
{
TextChanged(tb.UserData as CustomInterfaceElement, textBox.Text);
}
else
{
item.CreateClientEvent(this);
}
};
textBox.OnEnterPressed += (tb, text) =>
{
tb.Deselect();
return true;
};
uiElements.Add(textBox);
}
else if (ciElement.ContinuousSignal)
{ {
var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform) var tickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, elementSize), uiElementContainer.RectTransform)
{ {
MaxSize = ElementMaxSize MaxSize = ElementMaxSize
}, }, TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label)
TextManager.Get(ciElement.Label, returnNull: true) ?? ciElement.Label)
{ {
UserData = ciElement UserData = ciElement
}; };
@@ -148,7 +183,7 @@ namespace Barotrauma.Items.Components
foreach (var uiElement in uiElements) foreach (var uiElement in uiElements)
{ {
if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; } if (!(uiElement.UserData is CustomInterfaceElement element)) { continue; }
bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || (element.Connection != null && element.Connection.Wires.Any(w => w != null)); bool visible = Screen.Selected == GameMain.SubEditorScreen || element.StatusEffects.Any() || !string.IsNullOrEmpty(element.PropertyName) || (element.Connection != null && element.Connection.Wires.Any(w => w != null));
if (visible) { visibleElementCount++; } if (visible) { visibleElementCount++; }
if (uiElement.Visible != visible) if (uiElement.Visible != visible)
{ {
@@ -188,6 +223,22 @@ namespace Barotrauma.Items.Components
customInterfaceElementList[i].Label; customInterfaceElementList[i].Label;
tickBox.TextBlock.Wrap = tickBox.Text.Contains(' '); tickBox.TextBlock.Wrap = tickBox.Text.Contains(' ');
} }
if (uiElements[i] is GUITextBox textBox)
{
var textBlock = textBox.Parent.GetChild<GUITextBlock>();
textBlock.Text = string.IsNullOrWhiteSpace(customInterfaceElementList[i].Label) ?
TextManager.GetWithVariable("connection.signaloutx", "[num]", (i + 1).ToString()) :
customInterfaceElementList[i].Label;
textBlock.Wrap = textBlock.Text.Contains(' ');
foreach (ISerializableEntity e in item.AllPropertyObjects)
{
if (e.SerializableProperties.ContainsKey(customInterfaceElementList[i].PropertyName))
{
textBox.Text = e.SerializableProperties[customInterfaceElementList[i].PropertyName].GetValue(e) as string;
}
}
}
} }
uiElementContainer.Recalculate(); uiElementContainer.Recalculate();
@@ -206,6 +257,10 @@ namespace Barotrauma.Items.Components
{ {
textBlocks.Add(tickBox.TextBlock); textBlocks.Add(tickBox.TextBlock);
} }
else if (element is GUILayoutGroup)
{
textBlocks.Add(element.GetChild<GUITextBlock>());
}
} }
uiElementContainer.Recalculate(); uiElementContainer.Recalculate();
GUITextBlock.AutoScaleAndNormalize(textBlocks); GUITextBlock.AutoScaleAndNormalize(textBlocks);
@@ -216,7 +271,11 @@ namespace Barotrauma.Items.Components
//extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything) //extradata contains an array of buttons clicked by the player (or nothing if the player didn't click anything)
for (int i = 0; i < customInterfaceElementList.Count; i++) for (int i = 0; i < customInterfaceElementList.Count; i++)
{ {
if (customInterfaceElementList[i].ContinuousSignal) if (!string.IsNullOrEmpty(customInterfaceElementList[i].PropertyName))
{
msg.Write(((GUITextBox)uiElements[i]).Text);
}
else if (customInterfaceElementList[i].ContinuousSignal)
{ {
msg.Write(((GUITickBox)uiElements[i]).Selected); msg.Write(((GUITickBox)uiElements[i]).Selected);
} }
@@ -230,6 +289,12 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
for (int i = 0; i < customInterfaceElementList.Count; i++) for (int i = 0; i < customInterfaceElementList.Count; i++)
{
if (!string.IsNullOrEmpty(customInterfaceElementList[i].PropertyName))
{
TextChanged(customInterfaceElementList[i], msg.ReadString());
}
else
{ {
bool elementState = msg.ReadBoolean(); bool elementState = msg.ReadBoolean();
if (customInterfaceElementList[i].ContinuousSignal) if (customInterfaceElementList[i].ContinuousSignal)
@@ -243,6 +308,7 @@ namespace Barotrauma.Items.Components
} }
} }
} }
}
protected override void RemoveComponentSpecific() protected override void RemoveComponentSpecific()
{ {
@@ -56,15 +56,6 @@ namespace Barotrauma.Items.Components
}; };
} }
public override void OnItemLoaded()
{
base.OnItemLoaded();
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
{
ShowOnDisplay(DisplayedWelcomeMessage);
}
}
private void SendOutput(string input) private void SendOutput(string input)
{ {
if (input.Length > MaxMessageLength) if (input.Length > MaxMessageLength)
@@ -123,6 +114,11 @@ namespace Barotrauma.Items.Components
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
{ {
base.AddToGUIUpdateList(); base.AddToGUIUpdateList();
if (!string.IsNullOrEmpty(DisplayedWelcomeMessage))
{
ShowOnDisplay(DisplayedWelcomeMessage);
DisplayedWelcomeMessage = "";
}
if (shouldSelectInputBox) if (shouldSelectInputBox)
{ {
inputBox.Select(); inputBox.Select();
@@ -17,14 +17,60 @@ namespace Barotrauma.Items.Components
partial class WireSection partial class WireSection
{ {
public VertexPositionColorTexture[] vertices;
public VertexPositionColorTexture[] shiftedVertices;
private float cachedWidth = 0f;
private void RecalculateVertices(Wire wire, float width)
{
if (MathUtils.NearlyEqual(cachedWidth, width)) { return; }
cachedWidth = width;
vertices = new VertexPositionColorTexture[4];
Vector2 expandDir = start-end;
expandDir.Normalize();
float temp = expandDir.X;
expandDir.X = -expandDir.Y;
expandDir.Y = -temp;
Rectangle srcRect = wire.wireSprite.SourceRect;
expandDir *= width * srcRect.Height * 0.5f;
Vector2 rectLocation = srcRect.Location.ToVector2();
Vector2 rectSize = srcRect.Size.ToVector2();
Vector2 textureSize = new Vector2(wire.wireSprite.Texture.Width, wire.wireSprite.Texture.Height);
Vector2 topLeftUv = rectLocation / textureSize;
Vector2 bottomRightUv = (rectLocation + rectSize) / textureSize;
Vector2 invStart = new Vector2(start.X, -start.Y);
Vector2 invEnd = new Vector2(end.X, -end.Y);
vertices[0] = new VertexPositionColorTexture(new Vector3(invStart + expandDir, 0f), Color.White, topLeftUv);
vertices[2] = new VertexPositionColorTexture(new Vector3(invEnd + expandDir, 0f), Color.White, new Vector2(bottomRightUv.X, topLeftUv.Y));
vertices[1] = new VertexPositionColorTexture(new Vector3(invStart - expandDir, 0f), Color.White, new Vector2(topLeftUv.X, bottomRightUv.Y));
vertices[3] = new VertexPositionColorTexture(new Vector3(invEnd - expandDir, 0f), Color.White, bottomRightUv);
shiftedVertices = (VertexPositionColorTexture[])vertices.Clone();
}
public void Draw(SpriteBatch spriteBatch, Wire wire, Color color, Vector2 offset, float depth, float width = 0.3f) public void Draw(SpriteBatch spriteBatch, Wire wire, Color color, Vector2 offset, float depth, float width = 0.3f)
{ {
if (width <= 0f) { return; }
RecalculateVertices(wire, width);
for (int i=0;i<vertices.Length;i++)
{
shiftedVertices[i].Color = color;
shiftedVertices[i].Position = vertices[i].Position;
shiftedVertices[i].Position.X += offset.X;
shiftedVertices[i].Position.Y -= offset.Y;
}
spriteBatch.Draw(wire.wireSprite.Texture, spriteBatch.Draw(wire.wireSprite.Texture,
new Vector2(start.X + offset.X, -(start.Y + offset.Y)), null, color, shiftedVertices,
-angle,
new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f),
new Vector2(length / wire.wireSprite.Texture.Width, width),
SpriteEffects.None,
depth); depth);
} }
@@ -34,10 +80,10 @@ namespace Barotrauma.Items.Components
end.Y = -end.Y; end.Y = -end.Y;
spriteBatch.Draw(wire.wireSprite.Texture, spriteBatch.Draw(wire.wireSprite.Texture,
start, null, color, start, wire.wireSprite.SourceRect, color,
MathUtils.VectorToAngle(end - start), MathUtils.VectorToAngle(end - start),
new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f), new Vector2(0.0f, wire.wireSprite.size.Y / 2.0f),
new Vector2((Vector2.Distance(start, end)) / wire.wireSprite.Texture.Width, width), new Vector2((Vector2.Distance(start, end)) / wire.wireSprite.size.X, width),
SpriteEffects.None, SpriteEffects.None,
depth); depth);
} }
@@ -50,6 +96,13 @@ namespace Barotrauma.Items.Components
private static int? selectedNodeIndex; private static int? selectedNodeIndex;
private static int? highlightedNodeIndex; private static int? highlightedNodeIndex;
[Serialize(0.3f, false)]
public float Width
{
get;
set;
}
public Vector2 DrawSize public Vector2 DrawSize
{ {
get { return sectionExtents; } get { return sectionExtents; }
@@ -72,7 +125,7 @@ namespace Barotrauma.Items.Components
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() == "wiresprite") if (subElement.Name.ToString().Equals("wiresprite", StringComparison.OrdinalIgnoreCase))
{ {
overrideSprite = new Sprite(subElement); overrideSprite = new Sprite(subElement);
break; break;
@@ -103,26 +156,26 @@ namespace Barotrauma.Items.Components
drawOffset = sub.DrawPosition + sub.HiddenSubPosition; drawOffset = sub.DrawPosition + sub.HiddenSubPosition;
} }
float depth = item.IsSelected ? 0.0f : wireSprite.Depth + ((item.ID % 100) * 0.00001f); float depth = item.IsSelected ? 0.0f : Screen.Selected is SubEditorScreen editor && editor.WiringMode ? 0.00002f : wireSprite.Depth + ((item.ID % 100) * 0.00001f);
if (item.IsHighlighted) if (item.IsHighlighted)
{ {
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, 0.7f); section.Draw(spriteBatch, this, Screen.Selected == GameMain.GameScreen ? higlightColor : editorHighlightColor, drawOffset, depth + 0.00001f, Width * 2.0f);
} }
} }
else if (item.IsSelected) else if (item.IsSelected)
{ {
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, editorSelectedColor, drawOffset, depth + 0.00001f, 0.7f); section.Draw(spriteBatch, this, editorSelectedColor, drawOffset, depth + 0.00001f, Width * 2.0f);
} }
} }
foreach (WireSection section in sections) foreach (WireSection section in sections)
{ {
section.Draw(spriteBatch, this, item.Color, drawOffset, depth, 0.3f); section.Draw(spriteBatch, this, item.Color, drawOffset, depth, Width);
} }
if (nodes.Count > 0) if (nodes.Count > 0)
@@ -167,13 +220,13 @@ namespace Barotrauma.Items.Components
spriteBatch, this, spriteBatch, this,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.Color, 0.0f, 0.3f); item.Color, 0.0f, Width);
WireSection.Draw( WireSection.Draw(
spriteBatch, this, spriteBatch, this,
new Vector2(newNodePos.X, newNodePos.Y) + drawOffset, new Vector2(newNodePos.X, newNodePos.Y) + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, itemDepth, 0.3f); item.Color, itemDepth, Width);
GUI.DrawRectangle(spriteBatch, new Vector2(newNodePos.X + drawOffset.X, -(newNodePos.Y + drawOffset.Y)) - Vector2.One * 3, Vector2.One * 6, item.Color); GUI.DrawRectangle(spriteBatch, new Vector2(newNodePos.X + drawOffset.X, -(newNodePos.Y + drawOffset.Y)) - Vector2.One * 3, Vector2.One * 6, item.Color);
} }
@@ -183,7 +236,7 @@ namespace Barotrauma.Items.Components
spriteBatch, this, spriteBatch, this,
new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset, new Vector2(nodes[nodes.Count - 1].X, nodes[nodes.Count - 1].Y) + drawOffset,
item.DrawPosition, item.DrawPosition,
item.Color, 0.0f, 0.3f); item.Color, 0.0f, Width);
} }
} }
} }
@@ -235,7 +288,7 @@ namespace Barotrauma.Items.Components
Wire equippedWire = Wire equippedWire =
Character.Controlled?.SelectedItems[0]?.GetComponent<Wire>() ?? Character.Controlled?.SelectedItems[0]?.GetComponent<Wire>() ??
Character.Controlled?.SelectedItems[1]?.GetComponent<Wire>(); Character.Controlled?.SelectedItems[1]?.GetComponent<Wire>();
if (equippedWire != null) if (equippedWire != null && GUI.MouseOn == null)
{ {
if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedConstruction == null) if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedConstruction == null)
{ {
@@ -404,6 +457,20 @@ namespace Barotrauma.Items.Components
} }
} }
public bool IsMouseOn()
{
if (GUI.MouseOn == null)
{
Vector2 mousePos = GameMain.SubEditorScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
if (item.Submarine != null) { mousePos -= (item.Submarine.Position + item.Submarine.HiddenSubPosition); }
if (GetClosestNodeIndex(mousePos, 10, out _) > -1) { return true; }
if (GetClosestSectionIndex(mousePos, 10, out _) > -1) { return true; }
}
return false;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent)); int eventIndex = msg.ReadRangedInteger(0, (int)Math.Ceiling(MaxNodeCount / (float)MaxNodesPerNetworkEvent));
@@ -19,6 +19,8 @@ namespace Barotrauma.Items.Components
private float recoilTimer; private float recoilTimer;
private float RetractionTime => Math.Max(Reload * RetractionDurationMultiplier, RecoilTime);
private RoundSound startMoveSound, endMoveSound, moveSound; private RoundSound startMoveSound, endMoveSound, moveSound;
private SoundChannel moveSoundChannel; private SoundChannel moveSoundChannel;
@@ -83,6 +85,11 @@ namespace Barotrauma.Items.Components
} }
} }
public Sprite BarrelSprite
{
get { return barrelSprite; }
}
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
@@ -126,7 +133,7 @@ namespace Barotrauma.Items.Components
partial void LaunchProjSpecific() partial void LaunchProjSpecific()
{ {
recoilTimer = Math.Max(Reload, 0.1f); recoilTimer = RetractionTime;
PlaySound(ActionType.OnUse); PlaySound(ActionType.OnUse);
Vector2 particlePos = new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y); Vector2 particlePos = new Vector2(item.WorldRect.X + transformedBarrelPos.X, item.WorldRect.Y - transformedBarrelPos.Y);
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
@@ -135,6 +142,12 @@ namespace Barotrauma.Items.Components
} }
} }
public override void UpdateBroken(float deltaTime, Camera cam)
{
base.UpdateBroken(deltaTime, cam);
recoilTimer -= deltaTime;
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
recoilTimer -= deltaTime; recoilTimer -= deltaTime;
@@ -223,21 +236,30 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y); Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
if (item.Submarine != null) drawPos += item.Submarine.DrawPosition; if (item.Submarine != null)
{
drawPos += item.Submarine.DrawPosition;
}
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
float recoilOffset = 0.0f; float recoilOffset = 0.0f;
if (RecoilDistance > 0.0f && recoilTimer > 0.0f) if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f)
{ {
//move the barrel backwards 0.1 seconds after launching float diff = RetractionTime - RecoilTime;
if (recoilTimer >= Math.Max(Reload, 0.1f) - 0.1f) if (recoilTimer >= diff)
{ {
recoilOffset = RecoilDistance * (1.0f - (recoilTimer - (Math.Max(Reload, 0.1f) - 0.1f)) / 0.1f); //move the barrel backwards 0.1 seconds (defined by RecoilTime) after launching
recoilOffset = RecoilDistance * (1.0f - (recoilTimer - diff) / RecoilTime);
} }
else if (recoilTimer <= diff - RetractionDelay)
{
//move back to normal position while reloading //move back to normal position while reloading
float t = diff - RetractionDelay;
recoilOffset = RecoilDistance * recoilTimer / t;
}
else else
{ {
recoilOffset = RecoilDistance * recoilTimer / (Math.Max(Reload, 0.1f) - 0.1f); recoilOffset = RecoilDistance;
} }
} }
@@ -376,6 +398,19 @@ namespace Barotrauma.Items.Components
return widget; return widget;
} }
private void GetAvailablePower(out float availableCharge, out float availableCapacity)
{
var batteries = item.GetConnectedComponents<PowerContainer>();
availableCharge = 0.0f;
availableCapacity = 0.0f;
foreach (PowerContainer battery in batteries)
{
availableCharge += battery.Charge;
availableCapacity += battery.Capacity;
}
}
/// <summary> /// <summary>
/// Returns correct angle between -2PI and +2PI /// Returns correct angle between -2PI and +2PI
/// </summary> /// </summary>
@@ -488,17 +523,31 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
UInt16 projectileID = msg.ReadUInt16(); UInt16 projectileID = msg.ReadUInt16();
//projectile removed, do nothing float newTargetRotation = msg.ReadRangedSingle(minRotation, maxRotation, 16);
if (projectileID == 0) return;
Item projectile = Entity.FindEntityByID(projectileID) as Item; if (Character.Controlled == null || user != Character.Controlled)
if (projectile == null) {
targetRotation = newTargetRotation;
}
//projectile removed, do nothing
if (projectileID == 0) { return; }
//ID ushort.MaxValue = launched without a projectile
if (projectileID == ushort.MaxValue)
{
Launch(null);
}
else
{
if (!(Entity.FindEntityByID(projectileID) is Item projectile))
{ {
DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found"); DebugConsole.ThrowError("Failed to launch a projectile - item with the ID \"" + projectileID + " not found");
return; return;
} }
Launch(projectile, launchRotation: newTargetRotation);
}
Launch(projectile);
} }
} }
} }
@@ -150,8 +150,8 @@ namespace Barotrauma.Items.Components
if (joint == null) if (joint == null)
{ {
string errorMsg = "Error while reading a docking port network event (Dock method did not create a joint between the ports)." + 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") + " Submarine: " + (item.Submarine?.Info.Name ?? "null") +
", target submarine: " + (DockingTarget.item.Submarine?.Name ?? "null"); ", target submarine: " + (DockingTarget.item.Submarine?.Info.Name ?? "null");
if (item.Submarine?.ConnectedDockingPorts.ContainsKey(DockingTarget.item.Submarine) ?? false) if (item.Submarine?.ConnectedDockingPorts.ContainsKey(DockingTarget.item.Submarine) ?? false)
{ {
errorMsg += "\nAlready docked."; errorMsg += "\nAlready docked.";
@@ -45,12 +45,17 @@ namespace Barotrauma
public float QuickUseTimer; public float QuickUseTimer;
public string QuickUseButtonToolTip; public string QuickUseButtonToolTip;
public bool IsMoving = false;
private static Rectangle offScreenRect = new Rectangle(new Point(-1000, 0), Point.Zero);
public GUIComponent.ComponentState EquipButtonState; public GUIComponent.ComponentState EquipButtonState;
public Rectangle EquipButtonRect public Rectangle EquipButtonRect
{ {
get get
{ {
// Returns a point off-screen, Rectangle.Empty places buttons in the top left of the screen
if (IsMoving) return offScreenRect;
int buttonDir = Math.Sign(SubInventoryDir); int buttonDir = Math.Sign(SubInventoryDir);
float sizeY = Inventory.UnequippedIndicator.size.Y * Inventory.UIScale * Inventory.IndicatorScaleAdjustment; float sizeY = Inventory.UnequippedIndicator.size.Y * Inventory.UIScale * Inventory.IndicatorScaleAdjustment;
@@ -186,7 +191,7 @@ namespace Barotrauma
public Item Item; public Item Item;
public bool IsSubSlot; public bool IsSubSlot;
public string Tooltip; public string Tooltip;
public List<ColorData> TooltipColorData; public List<RichTextData> TooltipRichTextData;
public SlotReference(Inventory parentInventory, InventorySlot slot, int slotIndex, bool isSubSlot, Inventory subInventory = null) public SlotReference(Inventory parentInventory, InventorySlot slot, int slotIndex, bool isSubSlot, Inventory subInventory = null)
{ {
@@ -196,7 +201,7 @@ namespace Barotrauma
Inventory = subInventory; Inventory = subInventory;
IsSubSlot = isSubSlot; IsSubSlot = isSubSlot;
Item = ParentInventory.Items[slotIndex]; Item = ParentInventory.Items[slotIndex];
TooltipColorData = ColorData.GetColorData(GetTooltip(Item), out Tooltip); TooltipRichTextData = RichTextData.GetRichTextData(GetTooltip(Item), out Tooltip);
} }
private string GetTooltip(Item item) private string GetTooltip(Item item)
@@ -445,7 +450,28 @@ namespace Barotrauma
} }
}*/ }*/
bool mouseOn = interactRect.Contains(PlayerInput.MousePosition) && !Locked && !mouseOnGUI; bool mouseOn = interactRect.Contains(PlayerInput.MousePosition) && !Locked && !mouseOnGUI && !slot.Disabled;
// Delete item from container in sub editor
if (SubEditorScreen.IsSubEditor() && PlayerInput.IsCtrlDown())
{
draggingItem = null;
var mouseDrag = SubEditorScreen.MouseDragStart != Vector2.Zero && Vector2.Distance(PlayerInput.MousePosition, SubEditorScreen.MouseDragStart) >= GUI.Scale * 20;
if (mouseOn && (PlayerInput.PrimaryMouseButtonClicked() || mouseDrag))
{
if (item != null)
{
if (mouseDrag) { item.OwnInventory?.DeleteAllItems(); }
slot.ShowBorderHighlight(GUI.Style.Red, 0.1f, 0.4f);
if (!mouseDrag)
{
GUI.PlayUISound(GUISoundType.PickItem);
}
item.Remove();
}
}
}
if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld()) if (PlayerInput.LeftButtonHeld() && PlayerInput.RightButtonHeld())
{ {
mouseOn = false; mouseOn = false;
@@ -471,11 +497,14 @@ namespace Barotrauma
// && // &&
//(highlightedSubInventories.Count == 0 || highlightedSubInventories.Contains(this) || highlightedSubInventorySlot?.Slot == slot || highlightedSubInventory.Owner == item)) //(highlightedSubInventories.Count == 0 || highlightedSubInventories.Contains(this) || highlightedSubInventorySlot?.Slot == slot || highlightedSubInventory.Owner == item))
{ {
slot.State = GUIComponent.ComponentState.Hover; slot.State = GUIComponent.ComponentState.Hover;
if (selectedSlot == null || (!selectedSlot.IsSubSlot && isSubSlot)) if (selectedSlot == null || (!selectedSlot.IsSubSlot && isSubSlot))
{ {
selectedSlot = new SlotReference(this, slot, slotIndex, isSubSlot, Items[slotIndex]?.GetComponent<ItemContainer>()?.Inventory); var slotRef = new SlotReference(this, slot, slotIndex, isSubSlot, Items[slotIndex]?.GetComponent<ItemContainer>()?.Inventory);
if (Screen.Selected is SubEditorScreen editor && !editor.WiringMode && slotRef.ParentInventory is CharacterInventory) { return; }
selectedSlot = slotRef;
} }
if (draggingItem == null) if (draggingItem == null)
@@ -664,17 +693,30 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Check if the mouse is hovering on top of the slot
/// </summary>
/// <param name="slot">The desired slot we want to check</param>
/// <returns>True if our mouse is hover on the slot, false otherwise</returns>
public static bool IsMouseOnSlot(InventorySlot slot)
{
var rect = new Rectangle(slot.InteractRect.X, slot.InteractRect.Y, slot.InteractRect.Width, slot.InteractRect.Height);
rect.Offset(slot.DrawOffset);
return rect.Contains(PlayerInput.MousePosition);
}
/// <summary> /// <summary>
/// Is the mouse on any inventory element (slot, equip button, subinventory...) /// Is the mouse on any inventory element (slot, equip button, subinventory...)
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public static bool IsMouseOnInventory() public static bool IsMouseOnInventory()
{ {
var isSubEditor = Screen.Selected is SubEditorScreen editor && !editor.WiringMode;
if (Character.Controlled == null) return false; if (Character.Controlled == null) return false;
if (draggingItem != null || DraggingInventory != null) return true; if (draggingItem != null || DraggingInventory != null) return true;
if (Character.Controlled.Inventory != null) if (Character.Controlled.Inventory != null && !isSubEditor)
{ {
var inv = Character.Controlled.Inventory; var inv = Character.Controlled.Inventory;
for (var i = 0; i < inv.slots.Length; i++) for (var i = 0; i < inv.slots.Length; i++)
@@ -694,7 +736,8 @@ namespace Barotrauma
} }
} }
} }
if (Character.Controlled.SelectedCharacter?.Inventory != null)
if (Character.Controlled.SelectedCharacter?.Inventory != null && !isSubEditor)
{ {
var inv = Character.Controlled.SelectedCharacter.Inventory; var inv = Character.Controlled.SelectedCharacter.Inventory;
for (var i = 0; i < inv.slots.Length; i++) for (var i = 0; i < inv.slots.Length; i++)
@@ -825,9 +868,9 @@ namespace Barotrauma
return CursorState.Default; return CursorState.Default;
} }
protected static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle highlightedSlot, List<ColorData> colorData = null) protected static void DrawToolTip(SpriteBatch spriteBatch, string toolTip, Rectangle highlightedSlot, List<RichTextData> richTextData = null)
{ {
GUIComponent.DrawToolTip(spriteBatch, toolTip, highlightedSlot, colorData); GUIComponent.DrawToolTip(spriteBatch, toolTip, highlightedSlot, richTextData);
} }
public void DrawSubInventory(SpriteBatch spriteBatch, int slotIndex) public void DrawSubInventory(SpriteBatch spriteBatch, int slotIndex)
@@ -941,10 +984,35 @@ namespace Barotrauma
} }
else else
{ {
GUI.PlayUISound(GUISoundType.DropItem); bool removed = false;
if (Screen.Selected is SubEditorScreen editor)
{
if (editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition))
{
draggingItem.Remove();
removed = true;
}
else
{
if (editor.WiringMode)
{
draggingItem.Remove();
removed = true;
}
else
{
draggingItem.Drop(Character.Controlled); draggingItem.Drop(Character.Controlled);
} }
} }
}
else
{
draggingItem.Drop(Character.Controlled);
}
GUI.PlayUISound(removed ? GUISoundType.PickItem : GUISoundType.DropItem);
}
}
else if (selectedSlot.ParentInventory.Items[selectedSlot.SlotIndex] != draggingItem) else if (selectedSlot.ParentInventory.Items[selectedSlot.SlotIndex] != draggingItem)
{ {
Inventory selectedInventory = selectedSlot.ParentInventory; Inventory selectedInventory = selectedSlot.ParentInventory;
@@ -1033,7 +1101,9 @@ namespace Barotrauma
} }
if (subSlot.Slot.SubInventoryDir < 0) if (subSlot.Slot.SubInventoryDir < 0)
{ {
hoverArea.Height -= hoverArea.Bottom - subSlot.Slot.Rect.Bottom; // 24/2/2020 - the below statement makes the sub inventory extend all the way to the bottom of the screen because of a double negative
// Not sure if it's intentional or not but it was causing hover issues and disabling it seems to have no detrimental effects.
// hoverArea.Height -= hoverArea.Bottom - subSlot.Slot.Rect.Bottom;
} }
else else
{ {
@@ -1083,7 +1153,7 @@ namespace Barotrauma
string toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") : string toolTip = mouseOnHealthInterface ? TextManager.Get("QuickUseAction.UseTreatment") :
Character.Controlled.FocusedItem != null ? Character.Controlled.FocusedItem != null ?
TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, true) : TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, true) :
TextManager.Get("DropItem"); TextManager.Get(Screen.Selected is SubEditorScreen editor && editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition) ? "Delete" : "DropItem");
int textWidth = (int)Math.Max(GUI.Font.MeasureString(draggingItem.Name).X, GUI.SmallFont.MeasureString(toolTip).X); int textWidth = (int)Math.Max(GUI.Font.MeasureString(draggingItem.Name).X, GUI.SmallFont.MeasureString(toolTip).X);
int textSpacing = (int)(15 * GUI.Scale); int textSpacing = (int)(15 * GUI.Scale);
Point shadowBorders = (new Point(40, 10)).Multiply(GUI.Scale); Point shadowBorders = (new Point(40, 10)).Multiply(GUI.Scale);
@@ -1106,7 +1176,7 @@ namespace Barotrauma
{ {
Rectangle slotRect = selectedSlot.Slot.Rect; Rectangle slotRect = selectedSlot.Slot.Rect;
slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint(); slotRect.Location += selectedSlot.Slot.DrawOffset.ToPoint();
DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect, selectedSlot.TooltipColorData); DrawToolTip(spriteBatch, selectedSlot.Tooltip, slotRect, selectedSlot.TooltipRichTextData);
} }
} }
@@ -1140,16 +1210,21 @@ namespace Barotrauma
/*if (inventory != null && (CharacterInventory.PersonalSlots.HasFlag(type) || (inventory.isSubInventory && (inventory.Owner as Item) != null /*if (inventory != null && (CharacterInventory.PersonalSlots.HasFlag(type) || (inventory.isSubInventory && (inventory.Owner as Item) != null
&& (inventory.Owner as Item).AllowedSlots.Any(a => CharacterInventory.PersonalSlots.HasFlag(a))))) && (inventory.Owner as Item).AllowedSlots.Any(a => CharacterInventory.PersonalSlots.HasFlag(a)))))
{ {
slotColor = slot.IsHighlighted ? GUIColorSettings.EquipmentSlotColor : GUIColorSettings.EquipmentSlotColor * 0.8f; slotColor = slot.IsHighlighted ? GUI.Style.EquipmentSlotColor : GUI.Style.EquipmentSlotColor * 0.8f;
} }
else else
{ {
slotColor = slot.IsHighlighted ? GUIColorSettings.InventorySlotColor : GUIColorSettings.InventorySlotColor * 0.8f; slotColor = slot.IsHighlighted ? GUI.Style.InventorySlotColor : GUI.Style.InventorySlotColor * 0.8f;
}*/ }*/
if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; } if (inventory != null && inventory.Locked) { slotColor = Color.Gray * 0.5f; }
spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor); spriteBatch.Draw(slotSprite.Texture, rect, slotSprite.SourceRect, slotColor);
if (SubEditorScreen.IsSubEditor() && PlayerInput.IsCtrlDown() && selectedSlot?.Slot == slot)
{
GUI.DrawRectangle(spriteBatch, rect, GUI.Style.Red * 0.3f, isFilled: true);
}
bool canBePut = false; bool canBePut = false;
if (draggingItem != null && inventory != null && slotIndex > -1 && slotIndex < inventory.slots.Length) if (draggingItem != null && inventory != null && slotIndex > -1 && slotIndex < inventory.slots.Length)
@@ -1203,13 +1278,15 @@ namespace Barotrauma
dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - ContainedIndicatorHeight, rect.Width, ContainedIndicatorHeight); dir < 0 ? rect.Bottom + HUDLayoutSettings.Padding / 2 : rect.Y - HUDLayoutSettings.Padding / 2 - ContainedIndicatorHeight, rect.Width, ContainedIndicatorHeight);
containedIndicatorArea.Inflate(-4, 0); containedIndicatorArea.Inflate(-4, 0);
Color backgroundColor = GUI.Style.ColorInventoryBackground;
if (itemContainer.ContainedStateIndicator?.Texture == null) if (itemContainer.ContainedStateIndicator?.Texture == null)
{ {
containedIndicatorArea.Inflate(0, -2); containedIndicatorArea.Inflate(0, -2);
GUI.DrawRectangle(spriteBatch, containedIndicatorArea, Color.Gray * 0.9f, true); GUI.DrawRectangle(spriteBatch, containedIndicatorArea, backgroundColor, true);
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Rectangle(containedIndicatorArea.X, containedIndicatorArea.Y, (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Height), new Rectangle(containedIndicatorArea.X, containedIndicatorArea.Y, (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Height),
ToolBox.GradientLerp(containedState, Color.Red, Color.Orange, Color.LightGreen) * 0.8f, true); ToolBox.GradientLerp(containedState, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull) * 0.8f, true);
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(containedIndicatorArea.X + (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Y), new Vector2(containedIndicatorArea.X + (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Y),
new Vector2(containedIndicatorArea.X + (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Bottom), new Vector2(containedIndicatorArea.X + (int)(containedIndicatorArea.Width * containedState), containedIndicatorArea.Bottom),
@@ -1228,12 +1305,12 @@ namespace Barotrauma
} }
indicatorSprite.Draw(spriteBatch, containedIndicatorArea.Center.ToVector2(), indicatorSprite.Draw(spriteBatch, containedIndicatorArea.Center.ToVector2(),
(inventory != null && inventory.Locked) ? Color.Gray * 0.5f : Color.Gray * 0.9f, (inventory != null && inventory.Locked) ? backgroundColor * 0.5f : backgroundColor,
origin: indicatorSprite.size / 2, origin: indicatorSprite.size / 2,
rotate: 0.0f, rotate: 0.0f,
scale: indicatorScale); scale: indicatorScale);
Color indicatorColor = ToolBox.GradientLerp(containedState, Color.Red, Color.Orange, Color.LightGreen); Color indicatorColor = ToolBox.GradientLerp(containedState, GUI.Style.ColorInventoryEmpty, GUI.Style.ColorInventoryHalf, GUI.Style.ColorInventoryFull);
if (inventory != null && inventory.Locked) { indicatorColor *= 0.5f; } if (inventory != null && inventory.Locked) { indicatorColor *= 0.5f; }
spriteBatch.Draw(indicatorSprite.Texture, containedIndicatorArea.Center.ToVector2(), spriteBatch.Draw(indicatorSprite.Texture, containedIndicatorArea.Center.ToVector2(),
@@ -29,14 +29,8 @@ namespace Barotrauma
private bool editingHUDRefreshPending; private bool editingHUDRefreshPending;
private float editingHUDRefreshTimer; private float editingHUDRefreshTimer;
class SpriteState
{
public float RotationState;
public float OffsetState;
public bool IsActive = true;
}
private Dictionary<DecorativeSprite, SpriteState> spriteAnimState = new Dictionary<DecorativeSprite, SpriteState>(); private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>();
private Sprite activeSprite; private Sprite activeSprite;
public override Sprite Sprite public override Sprite Sprite
@@ -44,10 +38,19 @@ namespace Barotrauma
get { return activeSprite; } get { return activeSprite; }
} }
public override bool DrawOverWater public override Rectangle Rect
{ {
get { return base.DrawOverWater || (GetComponent<Wire>() != null && IsSelected); } get { return base.Rect; }
set
{
cachedVisibleSize = null;
base.Rect = value;
} }
}
public override bool DrawBelowWater => (!(Screen.Selected is SubEditorScreen editor) || !editor.WiringMode || !isWire) && base.DrawBelowWater;
public override bool DrawOverWater => base.DrawOverWater || (IsSelected || Screen.Selected is SubEditorScreen editor && editor.WiringMode) && isWire;
private GUITextBlock itemInUseWarning; private GUITextBlock itemInUseWarning;
private GUITextBlock ItemInUseWarning private GUITextBlock ItemInUseWarning
@@ -160,8 +163,16 @@ namespace Barotrauma
foreach (var decorativeSprite in ((ItemPrefab)prefab).DecorativeSprites) foreach (var decorativeSprite in ((ItemPrefab)prefab).DecorativeSprites)
{ {
decorativeSprite.Sprite.EnsureLazyLoaded(); decorativeSprite.Sprite.EnsureLazyLoaded();
spriteAnimState.Add(decorativeSprite, new SpriteState()); spriteAnimState.Add(decorativeSprite, new DecorativeSprite.State());
} }
UpdateSpriteStates(0.0f);
}
private Vector2? cachedVisibleSize;
public void ResetCachedVisibleSize()
{
cachedVisibleSize = null;
} }
public override bool IsVisible(Rectangle worldView) public override bool IsVisible(Rectangle worldView)
@@ -173,19 +184,28 @@ namespace Barotrauma
} }
//no drawable components and the body has been disabled = nothing to draw //no drawable components and the body has been disabled = nothing to draw
if (drawableComponents.Count == 0 && body != null && !body.Enabled) if (!hasComponentsToDraw && body != null && !body.Enabled)
{ {
return false; return false;
} }
Vector2 size;
if (cachedVisibleSize.HasValue)
{
size = cachedVisibleSize.Value;
}
else
{
float padding = 100.0f; float padding = 100.0f;
Vector2 size = new Vector2(rect.Width + padding, rect.Height + padding); size = new Vector2(rect.Width + padding, rect.Height + padding);
foreach (IDrawableComponent drawable in drawableComponents) foreach (IDrawableComponent drawable in drawableComponents)
{ {
size.X = Math.Max(drawable.DrawSize.X, size.X); size.X = Math.Max(drawable.DrawSize.X, size.X);
size.Y = Math.Max(drawable.DrawSize.Y, size.Y); size.Y = Math.Max(drawable.DrawSize.Y, size.Y);
} }
size *= 0.5f; size *= 0.5f;
cachedVisibleSize = size;
}
//cache world position so we don't need to calculate it 4 times //cache world position so we don't need to calculate it 4 times
Vector2 worldPosition = WorldPosition; Vector2 worldPosition = WorldPosition;
@@ -197,8 +217,8 @@ namespace Barotrauma
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{ {
if (!Visible || (!editing && HiddenInGame)) return; if (!Visible || (!editing && HiddenInGame)) { return; }
if (editing && !ShowItems) return; if (editing && !ShowItems) { return; }
Color color = IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange : GetSpriteColor(); Color color = IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUI.Style.Orange : GetSpriteColor();
//if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f); //if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f);
@@ -268,7 +288,7 @@ namespace Barotrauma
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
SpriteRotation + rotation, Scale, activeSprite.effects, SpriteRotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
} }
} }
@@ -312,7 +332,7 @@ namespace Barotrauma
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y); Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color,
-body.Rotation + rotation, Scale, activeSprite.effects, -body.Rotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth)); depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
} }
} }
@@ -398,46 +418,7 @@ namespace Barotrauma
public void UpdateSpriteStates(float deltaTime) public void UpdateSpriteStates(float deltaTime)
{ {
foreach (int spriteGroup in Prefab.DecorativeSpriteGroups.Keys) DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches);
{
for (int i = 0; i < Prefab.DecorativeSpriteGroups[spriteGroup].Count; i++)
{
var decorativeSprite = Prefab.DecorativeSpriteGroups[spriteGroup][i];
if (decorativeSprite == null) { continue; }
if (spriteGroup > 0)
{
int activeSpriteIndex = ID % Prefab.DecorativeSpriteGroups[spriteGroup].Count;
if (i != activeSpriteIndex)
{
spriteAnimState[decorativeSprite].IsActive = false;
continue;
}
}
//check if the sprite is active (whether it should be drawn or not)
var spriteState = spriteAnimState[decorativeSprite];
spriteState.IsActive = true;
foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
{
if (!ConditionalMatches(conditional))
{
spriteState.IsActive = false;
break;
}
}
if (!spriteState.IsActive) { continue; }
//check if the sprite should be animated
bool animate = true;
foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
{
if (!ConditionalMatches(conditional)) { animate = false; break; }
}
if (!animate) { continue; }
spriteState.OffsetState += deltaTime;
spriteState.RotationState += deltaTime;
}
}
} }
public override void UpdateEditing(Camera cam) public override void UpdateEditing(Camera cam)
@@ -716,6 +697,13 @@ namespace Barotrauma
HUDLayoutSettings.ChatBoxArea.Width + disallowedPadding, HUDLayoutSettings.ChatBoxArea.Height)); HUDLayoutSettings.ChatBoxArea.Width + disallowedPadding, HUDLayoutSettings.ChatBoxArea.Height));
} }
if (Screen.Selected is SubEditorScreen editor)
{
disallowedAreas.Add(editor.EntityMenu.Rect);
disallowedAreas.Add(editor.TopPanel.Rect);
disallowedAreas.Add(editor.ToggleEntityMenuButton.Rect);
}
GUI.PreventElementOverlap(elementsToMove, disallowedAreas, GUI.PreventElementOverlap(elementsToMove, disallowedAreas,
new Rectangle( new Rectangle(
0, 20, 0, 20,
@@ -871,19 +859,21 @@ namespace Barotrauma
} }
readonly List<ColoredText> texts = new List<ColoredText>(); readonly List<ColoredText> texts = new List<ColoredText>();
public List<ColoredText> GetHUDTexts(Character character) public List<ColoredText> GetHUDTexts(Character character, bool recreateHudTexts = true)
{ {
// Always create the texts if they have not yet been created
if (texts.Any() && !recreateHudTexts) { return texts; }
texts.Clear(); texts.Clear();
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
{ {
if (string.IsNullOrEmpty(ic.DisplayMsg)) continue; if (string.IsNullOrEmpty(ic.DisplayMsg)) { continue; }
if (!ic.CanBePicked && !ic.CanBeSelected) continue; if (!ic.CanBePicked && !ic.CanBeSelected) { continue; }
if (ic is Holdable holdable && !holdable.CanBeDeattached()) continue; if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; }
Color color = Color.Gray; Color color = Color.Gray;
if (ic.HasRequiredItems(character, false)) if (ic.HasRequiredItems(character, false))
{ {
if (ic is Repairable repairable) if (ic is Repairable)
{ {
if (!IsFullCondition) { color = Color.Cyan; } if (!IsFullCondition) { color = Color.Cyan; }
} }
@@ -892,9 +882,12 @@ namespace Barotrauma
color = Color.Cyan; color = Color.Cyan;
} }
} }
texts.Add(new ColoredText(ic.DisplayMsg, color, false)); texts.Add(new ColoredText(ic.DisplayMsg, color, false));
} }
if ((PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) && CrewManager.DoesItemHaveContextualOrders(this))
{
texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false));
}
return texts; return texts;
} }
@@ -902,17 +895,17 @@ namespace Barotrauma
{ {
if (Screen.Selected is SubEditorScreen) if (Screen.Selected is SubEditorScreen)
{ {
if (editingHUD != null && editingHUD.UserData == this) editingHUD.AddToGUIUpdateList(); if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(); }
} }
else else
{ {
if (HasInGameEditableProperties) if (HasInGameEditableProperties)
{ {
if (editingHUD != null && editingHUD.UserData == this) editingHUD.AddToGUIUpdateList(); if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(); }
} }
} }
if (Character.Controlled != null && Character.Controlled?.SelectedConstruction != this) return; if (Character.Controlled != null && Character.Controlled?.SelectedConstruction != this) { return; }
bool needsLayoutUpdate = false; bool needsLayoutUpdate = false;
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
@@ -1218,6 +1211,8 @@ namespace Barotrauma
} }
} }
byte bodyType = msg.ReadByte();
byte teamID = msg.ReadByte(); byte teamID = msg.ReadByte();
bool tagsChanged = msg.ReadBoolean(); bool tagsChanged = msg.ReadBoolean();
string tags = ""; string tags = "";
@@ -1256,8 +1251,8 @@ namespace Barotrauma
{ {
if (itemContainerIndex < 0 || itemContainerIndex >= parentItem.components.Count) if (itemContainerIndex < 0 || itemContainerIndex >= parentItem.components.Count)
{ {
string errorMsg = "Failed to spawn item \"" + (itemIdentifier ?? "null") + string errorMsg =
"\" in the inventory of \"" + parentItem.prefab.Identifier + "\" (component index out of range). Index: " + itemContainerIndex + ", components: " + parentItem.components.Count + "."; $"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of \"{parentItem.prefab.Identifier} ({parentItem.ID})\" (component index out of range). Index: {itemContainerIndex}, components: {parentItem.components.Count}.";
GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ?? "null") + (itemIdentifier ?? "null"), GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ?? "null") + (itemIdentifier ?? "null"),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg); errorMsg);
@@ -1275,6 +1270,11 @@ namespace Barotrauma
ID = itemId ID = itemId
}; };
if (item.body != null)
{
item.body.BodyType = (BodyType)bodyType;
}
foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>()) foreach (WifiComponent wifiComponent in item.GetComponents<WifiComponent>())
{ {
wifiComponent.TeamID = (Character.TeamType)teamID; wifiComponent.TeamID = (Character.TeamType)teamID;
@@ -66,7 +66,6 @@ namespace Barotrauma
[Serialize("", false)] [Serialize("", false)]
public string ImpactSoundTag { get; private set; } public string ImpactSoundTag { get; private set; }
public override void UpdatePlacing(Camera cam) public override void UpdatePlacing(Camera cam)
{ {
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
@@ -77,6 +76,8 @@ namespace Barotrauma
return; return;
} }
var potentialContainer = MapEntity.GetPotentialContainer(position);
if (!ResizeHorizontal && !ResizeVertical) if (!ResizeHorizontal && !ResizeVertical)
{ {
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
@@ -88,6 +89,14 @@ namespace Barotrauma
item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f); item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
item.FindHull(); item.FindHull();
if (PlayerInput.IsShiftDown())
{
if (potentialContainer?.OwnInventory?.TryPutItem(item, Character.Controlled) ?? false)
{
GUI.PlayUISound(GUISoundType.PickItem);
}
}
placePosition = Vector2.Zero; placePosition = Vector2.Zero;
return; return;
} }
@@ -124,6 +133,12 @@ namespace Barotrauma
} }
} }
if (potentialContainer != null)
{
potentialContainer.IsHighlighted = true;
}
//if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null; //if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null;
} }
@@ -141,26 +156,10 @@ namespace Barotrauma
if (!ResizeHorizontal && !ResizeVertical) if (!ResizeHorizontal && !ResizeVertical)
{ {
sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y) + sprite.size / 2.0f * Scale, SpriteColor, scale: Scale); sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y) + sprite.size / 2.0f * Scale, SpriteColor, scale: Scale);
} }
else else
{ {
Vector2 placeSize = size; sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), size, color: SpriteColor);
if (placePosition == Vector2.Zero)
{
if (PlayerInput.PrimaryMouseButtonHeld()) placePosition = position;
}
else
{
if (ResizeHorizontal)
placeSize.X = Math.Max(position.X - placePosition.X, size.X);
if (ResizeVertical)
placeSize.Y = Math.Max(placePosition.Y - position.Y, size.Y);
position = placePosition;
}
if (sprite != null) sprite.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
} }
} }
@@ -24,7 +24,7 @@ namespace Barotrauma
public override void Draw(SpriteBatch sb, bool editing, bool back = true) public override void Draw(SpriteBatch sb, bool editing, bool back = true)
{ {
if (GameMain.DebugDraw) if (!GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
{ {
Vector2 center = new Vector2(WorldRect.X + rect.Width / 2.0f, -(WorldRect.Y - rect.Height / 2.0f)); Vector2 center = new Vector2(WorldRect.X + rect.Width / 2.0f, -(WorldRect.Y - rect.Height / 2.0f));
GUI.DrawLine(sb, center, center + new Vector2(flowForce.X, -flowForce.Y) / 10.0f, GUI.Style.Red); GUI.DrawLine(sb, center, center + new Vector2(flowForce.X, -flowForce.Y) / 10.0f, GUI.Style.Red);
@@ -41,7 +41,7 @@ namespace Barotrauma
} }
} }
if (!editing || !ShowGaps) return; if (!editing || !ShowGaps) { return; }
Color clr = (open == 0.0f) ? GUI.Style.Red : Color.Cyan; Color clr = (open == 0.0f) ? GUI.Style.Red : Color.Cyan;
if (IsHighlighted) clr = Color.Gold; if (IsHighlighted) clr = Color.Gold;
@@ -76,6 +76,8 @@ namespace Barotrauma
clr * 0.6f, width: lineWidth); clr * 0.6f, width: lineWidth);
} }
if (linkedTo.Count != 2 || linkedTo[0] != linkedTo[1])
{
for (int i = 0; i < linkedTo.Count; i++) for (int i = 0; i < linkedTo.Count; i++)
{ {
Vector2 dir = IsHorizontal ? Vector2 dir = IsHorizontal ?
@@ -103,6 +105,7 @@ namespace Barotrauma
new Vector2(Math.Min(rect.Width, arrowWidth) / GUI.Arrow.size.X, arrowSize / GUI.Arrow.size.Y), new Vector2(Math.Min(rect.Width, arrowWidth) / GUI.Arrow.size.X, arrowSize / GUI.Arrow.size.Y),
SpriteEffects.None, depth); SpriteEffects.None, depth);
} }
}
if (IsSelected) if (IsSelected)
{ {
@@ -246,7 +246,7 @@ namespace Barotrauma
if (!ShowHulls && !GameMain.DebugDraw) return; if (!ShowHulls && !GameMain.DebugDraw) return;
if (!editing && !GameMain.DebugDraw) return; if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) return;
Rectangle drawRect = Rectangle drawRect =
Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height); Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
@@ -269,7 +269,8 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * Math.Min(waterVolume / Volume, 1.0f))), Color.Cyan, true); GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * Math.Min(waterVolume / Volume, 1.0f))), Color.Cyan, true);
if (WaterVolume > Volume) if (WaterVolume > Volume)
{ {
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * (waterVolume - Volume) / MaxCompress)), GUI.Style.Red, true); float maxExcessWater = Volume * MaxCompress;
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, (int)(100 * (waterVolume - Volume) / maxExcessWater)), GUI.Style.Red, true);
} }
GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, 100), Color.Black); GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.Center.X, -drawRect.Y + drawRect.Height / 2, 10, 100), Color.Black);
@@ -450,7 +451,7 @@ namespace Barotrauma
} }
//we only create a new quad if this is the first or the last one, of if there's a wave large enough that we need more geometry //we only create a new quad if this is the first or the last one, of if there's a wave large enough that we need more geometry
if (i == end - 1 || i == start || Math.Abs(prevCorners[1].Y - corners[3].Y) > 1.0f) if (i == end - 1 || i == start || Math.Abs(prevCorners[1].Y - corners[2].Y) > 0.01f)
{ {
renderer.vertices[renderer.PositionInBuffer] = new VertexPositionTexture(prevCorners[0], prevUVs[0]); renderer.vertices[renderer.PositionInBuffer] = new VertexPositionTexture(prevCorners[0], prevUVs[0]);
renderer.vertices[renderer.PositionInBuffer + 1] = new VertexPositionTexture(corners[1], uvCoords[1]); renderer.vertices[renderer.PositionInBuffer + 1] = new VertexPositionTexture(corners[1], uvCoords[1]);
@@ -33,7 +33,9 @@ namespace Barotrauma
foreach (Pair<MapEntityPrefab, Rectangle> entity in DisplayEntities) foreach (Pair<MapEntityPrefab, Rectangle> entity in DisplayEntities)
{ {
Rectangle drawRect = entity.Second; Rectangle drawRect = entity.Second;
drawRect.Location += Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint();
drawRect.Location += placePosition != Vector2.Zero ? placePosition.ToPoint() : Submarine.MouseToWorldGrid(cam, Submarine.MainSub).ToPoint();
entity.First.DrawPlacing(spriteBatch, drawRect, entity.First.Scale); entity.First.DrawPlacing(spriteBatch, drawRect, entity.First.Scale);
} }
} }
@@ -39,7 +39,7 @@ namespace Barotrauma
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "sprite") continue; if (!subElement.Name.ToString().Equals("sprite", System.StringComparison.OrdinalIgnoreCase)) { continue; }
Sprite = new Sprite(subElement, lazyLoad: true); Sprite = new Sprite(subElement, lazyLoad: true);
break; break;
@@ -1,7 +1,7 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using FarseerPhysics.Dynamics; using System;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using FarseerPhysics; using FarseerPhysics;
@@ -54,7 +54,7 @@ namespace Barotrauma
if (renderer == null) return; if (renderer == null) return;
renderer.Draw(spriteBatch, cam); renderer.Draw(spriteBatch, cam);
if (GameMain.DebugDraw) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
{ {
foreach (InterestingPosition pos in positionsOfInterest) foreach (InterestingPosition pos in positionsOfInterest)
{ {
@@ -78,6 +78,35 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5); GUI.DrawRectangle(spriteBatch, ruinArea, Color.DarkSlateBlue, false, 0, 5);
} }
foreach (var positions in wreckPositions.Values)
{
for (int i = 0; i < positions.Count; i++)
{
float t = (i + 1) / (float)positions.Count;
float multiplier = MathHelper.Lerp(0, 1, t);
Color color = Color.Red * multiplier;
var pos = positions[i];
pos.Y = -pos.Y;
var size = new Vector2(100);
GUI.DrawRectangle(spriteBatch, pos - size / 2, size, color, thickness: 10);
if (i < positions.Count - 1)
{
var nextPos = positions[i + 1];
nextPos.Y = -nextPos.Y;
GUI.DrawLine(spriteBatch, pos, nextPos, color, width: 10);
}
}
}
foreach (var rects in blockedRects.Values)
{
foreach (var rect in rects)
{
Rectangle newRect = rect;
newRect.Y = -newRect.Y;
GUI.DrawRectangle(spriteBatch, newRect, Color.Red, thickness: 5);
}
}
} }
} }
@@ -126,7 +126,7 @@ namespace Barotrauma
int j = 0; int j = 0;
foreach (XElement subElement in Prefab.Config.Elements()) foreach (XElement subElement in Prefab.Config.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() != "deformablesprite") continue; if (!subElement.Name.ToString().Equals("deformablesprite", StringComparison.OrdinalIgnoreCase)) { continue; }
foreach (XElement animationElement in subElement.Elements()) foreach (XElement animationElement in subElement.Elements())
{ {
var newDeformation = SpriteDeformation.Load(animationElement, Prefab.Name); var newDeformation = SpriteDeformation.Load(animationElement, Prefab.Name);
@@ -161,7 +161,7 @@ namespace Barotrauma
bool elementFound = false; bool elementFound = false;
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() == "overridecommonness" if (subElement.Name.ToString().Equals("overridecommonness", System.StringComparison.OrdinalIgnoreCase)
&& subElement.GetAttributeString("leveltype", "") == overrideCommonness.Key) && subElement.GetAttributeString("leveltype", "") == overrideCommonness.Key)
{ {
subElement.Attribute("commonness").Value = overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture); subElement.Attribute("commonness").Value = overrideCommonness.Value.ToString("G", CultureInfo.InvariantCulture);
@@ -228,7 +228,7 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch, Camera cam) public void Draw(SpriteBatch spriteBatch, Camera cam)
{ {
if (GameMain.DebugDraw && cam.Zoom > 0.05f) if (GameMain.DebugDraw && cam.Zoom > 0.1f)
{ {
var cells = level.GetCells(cam.WorldViewCenter, 2); var cells = level.GetCells(cam.WorldViewCenter, 2);
foreach (VoronoiCell cell in cells) foreach (VoronoiCell cell in cells)
@@ -94,8 +94,8 @@ namespace Barotrauma
WaterEffect.Parameters["xTexture"].SetValue(texture); WaterEffect.Parameters["xTexture"].SetValue(texture);
Vector2 distortionStrength = cam == null ? DistortionStrength : DistortionStrength * cam.Zoom; Vector2 distortionStrength = cam == null ? DistortionStrength : DistortionStrength * cam.Zoom;
WaterEffect.Parameters["xWaveWidth"].SetValue(DistortionStrength.X); WaterEffect.Parameters["xWaveWidth"].SetValue(distortionStrength.X);
WaterEffect.Parameters["xWaveHeight"].SetValue(DistortionStrength.Y); WaterEffect.Parameters["xWaveHeight"].SetValue(distortionStrength.Y);
if (BlurAmount > 0.0f) if (BlurAmount > 0.0f)
{ {
WaterEffect.CurrentTechnique = WaterEffect.Techniques["WaterShaderBlurred"]; WaterEffect.CurrentTechnique = WaterEffect.Techniques["WaterShaderBlurred"];
@@ -111,6 +111,9 @@ namespace Barotrauma
offset += (cam.Position - new Vector2(cam.WorldView.Width / 2.0f, -cam.WorldView.Height / 2.0f)); offset += (cam.Position - new Vector2(cam.WorldView.Width / 2.0f, -cam.WorldView.Height / 2.0f));
offset.Y += cam.WorldView.Height; offset.Y += cam.WorldView.Height;
offset.X += cam.WorldView.Width; offset.X += cam.WorldView.Width;
#if LINUX || OSX
offset.X += cam.WorldView.Width;
#endif
offset *= DistortionScale; offset *= DistortionScale;
} }
offset.Y = -offset.Y; offset.Y = -offset.Y;
@@ -176,6 +179,9 @@ namespace Barotrauma
spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, subVerts.Value, 0, PositionInIndoorsBuffer[subVerts.Key] / 3); spriteBatch.GraphicsDevice.DrawUserPrimitives(PrimitiveType.TriangleList, subVerts.Value, 0, PositionInIndoorsBuffer[subVerts.Key] / 3);
} }
WaterEffect.Parameters["xTexture"].SetValue((Texture2D)null);
WaterEffect.CurrentTechnique.Passes[0].Apply();
} }
public void ScrollWater(Vector2 vel, float deltaTime) public void ScrollWater(Vector2 vel, float deltaTime)
@@ -196,6 +202,9 @@ namespace Barotrauma
graphicsDevice.SamplerStates[0] = SamplerState.PointWrap; graphicsDevice.SamplerStates[0] = SamplerState.PointWrap;
graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, PositionInBuffer / 3); graphicsDevice.DrawUserPrimitives<VertexPositionTexture>(PrimitiveType.TriangleList, vertices, 0, PositionInBuffer / 3);
basicEffect.Texture = null;
basicEffect.CurrentTechnique.Passes[0].Apply();
} }
public void ResetBuffers() public void ResetBuffers()
@@ -229,7 +229,16 @@ namespace Barotrauma.Lights
light.Position = light.ParentBody.DrawPosition; light.Position = light.ParentBody.DrawPosition;
if (light.ParentSub != null) { light.Position -= light.ParentSub.DrawPosition; } if (light.ParentSub != null) { light.Position -= light.ParentSub.DrawPosition; }
} }
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, light.LightSourceParams.TextureRange, viewRect)) { continue; }
float range = light.LightSourceParams.TextureRange;
if (light.LightSprite != null)
{
float spriteRange = Math.Max(
light.LightSprite.size.X * light.SpriteScale.X * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.X - 0.5f)),
light.LightSprite.size.Y * light.SpriteScale.Y * (0.5f + Math.Abs(light.LightSprite.RelativeOrigin.Y - 0.5f)));
range = Math.Max(spriteRange, range);
}
if (!MathUtils.CircleIntersectsRectangle(light.WorldPosition, range, viewRect)) { continue; }
activeLights.Add(light); activeLights.Add(light);
} }
@@ -977,7 +977,7 @@ namespace Barotrauma.Lights
origin, -Rotation, SpriteScale, LightSpriteEffect); origin, -Rotation, SpriteScale, LightSpriteEffect);
} }
if (GameMain.DebugDraw) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
{ {
Vector2 drawPos = position; Vector2 drawPos = position;
if (ParentSub != null) { drawPos += ParentSub.DrawPosition; } if (ParentSub != null) { drawPos += ParentSub.DrawPosition; }
@@ -14,28 +14,7 @@ namespace Barotrauma
{ {
if (!editing || wallVertices == null) { return; } if (!editing || wallVertices == null) { return; }
Color color = IsHighlighted ? GUI.Style.Orange : GUI.Style.Green; Draw(spriteBatch, Position);
if (IsSelected) { color = GUI.Style.Red; }
Vector2 pos = Position;
for (int i = 0; i < wallVertices.Count; i++)
{
Vector2 startPos = wallVertices[i] + pos;
startPos.Y = -startPos.Y;
Vector2 endPos = wallVertices[(i + 1) % wallVertices.Count] + pos;
endPos.Y = -endPos.Y;
GUI.DrawLine(spriteBatch,
startPos,
endPos,
color, 0.0f, 5);
}
pos.Y = -pos.Y;
GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color, 0.0f, 5);
GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color, 0.0f, 5);
if (!Item.ShowLinks) { return; } if (!Item.ShowLinks) { return; }
@@ -50,6 +29,32 @@ namespace Barotrauma
} }
} }
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos, float alpha = 1.0f)
{
Color color = (IsHighlighted) ? GUI.Style.Orange : GUI.Style.Green;
if (IsSelected) { color = GUI.Style.Red; }
Vector2 pos = drawPos;
for (int i = 0; i < wallVertices.Count; i++)
{
Vector2 startPos = wallVertices[i] + pos;
startPos.Y = -startPos.Y;
Vector2 endPos = wallVertices[(i + 1) % wallVertices.Count] + pos;
endPos.Y = -endPos.Y;
GUI.DrawLine(spriteBatch,
startPos,
endPos,
color * alpha, 0.0f, 5);
}
pos.Y = -pos.Y;
GUI.DrawLine(spriteBatch, pos + Vector2.UnitY * 50.0f, pos - Vector2.UnitY * 50.0f, color * alpha, 0.0f, 5);
GUI.DrawLine(spriteBatch, pos + Vector2.UnitX * 50.0f, pos - Vector2.UnitX * 50.0f, color * alpha, 0.0f, 5);
}
public override void UpdateEditing(Camera cam) public override void UpdateEditing(Camera cam)
{ {
if (editingHUD == null || editingHUD.UserData as LinkedSubmarine != this) if (editingHUD == null || editingHUD.UserData as LinkedSubmarine != this)
@@ -100,7 +105,6 @@ namespace Barotrauma
} }
var pathContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true); var pathContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), isHorizontal: true);
var pathBox = new GUITextBox(new RectTransform(new Vector2(0.75f, 1.0f), pathContainer.RectTransform), filePath, font: GUI.SmallFont); var pathBox = new GUITextBox(new RectTransform(new Vector2(0.75f, 1.0f), pathContainer.RectTransform), filePath, font: GUI.SmallFont);
var reloadButton = new GUIButton(new RectTransform(new Vector2(0.25f / pathBox.RectTransform.RelativeSize.X, 1.0f), pathBox.RectTransform, Anchor.CenterRight, Pivot.CenterLeft), var reloadButton = new GUIButton(new RectTransform(new Vector2(0.25f / pathBox.RectTransform.RelativeSize.X, 1.0f), pathBox.RectTransform, Anchor.CenterRight, Pivot.CenterLeft),
TextManager.Get("ReloadLinkedSub"), style: "GUIButtonSmall") TextManager.Get("ReloadLinkedSub"), style: "GUIButtonSmall")
@@ -131,8 +135,9 @@ namespace Barotrauma
return false; return false;
} }
XDocument doc = Submarine.OpenFile(pathBox.Text); XDocument doc = SubmarineInfo.OpenFile(pathBox.Text);
if (doc == null || doc.Root == null) return false; if (doc == null || doc.Root == null) return false;
doc.Root.SetAttributeValue("filepath", pathBox.Text);
pathBox.Flash(GUI.Style.Green); pathBox.Flash(GUI.Style.Green);
@@ -16,8 +16,15 @@ namespace Barotrauma
private static Vector2 startMovingPos = Vector2.Zero; private static Vector2 startMovingPos = Vector2.Zero;
private static float keyDelay;
public static Vector2 StartMovingPos => startMovingPos; public static Vector2 StartMovingPos => startMovingPos;
// Quick undo/redo for size and movement only. TODO: Remove if we do a more general implementation.
private Memento<Rectangle> rectMemento;
public event Action<Rectangle> Resized;
private static bool resizing; private static bool resizing;
private int resizeDirX, resizeDirY; private int resizeDirX, resizeDirY;
@@ -127,7 +134,7 @@ namespace Barotrauma
if (highlightedListBox == null || if (highlightedListBox == null ||
(GUI.MouseOn != highlightedListBox && !highlightedListBox.IsParentOf(GUI.MouseOn))) (GUI.MouseOn != highlightedListBox && !highlightedListBox.IsParentOf(GUI.MouseOn)))
{ {
UpdateHighlightedListBox(null); UpdateHighlightedListBox(null, false);
return; return;
} }
} }
@@ -142,11 +149,14 @@ namespace Barotrauma
{ {
if (PlayerInput.KeyDown(Keys.Delete)) if (PlayerInput.KeyDown(Keys.Delete))
{ {
selectedList.ForEach(e => e.Remove()); selectedList.ForEach(e =>
{
e.Remove();
});
selectedList.Clear(); selectedList.Clear();
} }
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl)) if (PlayerInput.IsCtrlDown())
{ {
if (PlayerInput.KeyHit(Keys.C)) if (PlayerInput.KeyHit(Keys.C))
{ {
@@ -239,32 +249,7 @@ namespace Barotrauma
} }
} }
if (PlayerInput.MouseSpeed.LengthSquared() > 10) UpdateHighlighting(highlightedEntities);
{
highlightTimer = 0.0f;
}
else
{
bool mouseNearHighlightBox = false;
if (highlightedListBox != null)
{
Rectangle expandedRect = highlightedListBox.Rect;
expandedRect.Inflate(20, 20);
mouseNearHighlightBox = expandedRect.Contains(PlayerInput.MousePosition);
if (!mouseNearHighlightBox) highlightedListBox = null;
}
highlightTimer += (float)Timing.Step;
if (highlightTimer > 1.0f)
{
if (!mouseNearHighlightBox)
{
UpdateHighlightedListBox(highlightedEntities);
highlightTimer = 0.0f;
}
}
}
} }
if (highLightedEntity != null) highLightedEntity.isHighlighted = true; if (highLightedEntity != null) highLightedEntity.isHighlighted = true;
@@ -272,35 +257,65 @@ namespace Barotrauma
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
int up = PlayerInput.KeyDown(Keys.Up) ? 1 : 0,
down = PlayerInput.KeyDown(Keys.Down) ? -1 : 0,
left = PlayerInput.KeyDown(Keys.Left) ? -1 : 0,
right = PlayerInput.KeyDown(Keys.Right) ? 1 : 0;
int xKeysDown = (left + right);
int yKeysDown = (up + down);
if (xKeysDown != 0 || yKeysDown != 0) { keyDelay += (float) Timing.Step; } else { keyDelay = 0; }
Vector2 nudgeAmount = Vector2.Zero; Vector2 nudgeAmount = Vector2.Zero;
if (keyDelay >= 0.5f)
{
nudgeAmount.Y = yKeysDown;
nudgeAmount.X = xKeysDown;
}
if (PlayerInput.KeyHit(Keys.Up)) nudgeAmount.Y = 1f; if (PlayerInput.KeyHit(Keys.Up)) nudgeAmount.Y = 1f;
if (PlayerInput.KeyHit(Keys.Down)) nudgeAmount.Y = -1f; if (PlayerInput.KeyHit(Keys.Down)) nudgeAmount.Y = -1f;
if (PlayerInput.KeyHit(Keys.Left)) nudgeAmount.X = -1f; if (PlayerInput.KeyHit(Keys.Left)) nudgeAmount.X = -1f;
if (PlayerInput.KeyHit(Keys.Right)) nudgeAmount.X = 1f; if (PlayerInput.KeyHit(Keys.Right)) nudgeAmount.X = 1f;
if (nudgeAmount != Vector2.Zero) if (nudgeAmount != Vector2.Zero)
{ {
foreach (MapEntity entityToNudge in selectedList) foreach (MapEntity entityToNudge in selectedList) { entityToNudge.Move(nudgeAmount); }
}
}
else
{ {
entityToNudge.Move(nudgeAmount); keyDelay = 0;
}
}
} }
bool isShiftDown = PlayerInput.IsShiftDown();
//started moving selected entities //started moving selected entities
if (startMovingPos != Vector2.Zero) if (startMovingPos != Vector2.Zero)
{ {
Item targetContainer = GetPotentialContainer(position, selectedList);
if (targetContainer != null) { targetContainer.IsHighlighted = true; }
if (PlayerInput.PrimaryMouseButtonReleased()) if (PlayerInput.PrimaryMouseButtonReleased())
{ {
//mouse released -> move the entities to the new position of the mouse //mouse released -> move the entities to the new position of the mouse
Vector2 moveAmount = position - startMovingPos; Vector2 moveAmount = position - startMovingPos;
if (!isShiftDown)
{
moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X; moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X;
moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y; moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y;
if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y) }
if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y || isShiftDown)
{ {
moveAmount = Submarine.VectorToWorldGrid(moveAmount); if (!isShiftDown) { moveAmount = Submarine.VectorToWorldGrid(moveAmount); }
//clone //clone
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl)) if (PlayerInput.IsCtrlDown())
{ {
var clones = Clone(selectedList); var clones = Clone(selectedList);
selectedList = clones; selectedList = clones;
@@ -308,6 +323,7 @@ namespace Barotrauma
} }
else // move else // move
{ {
List<MapEntity> deposited = new List<MapEntity>();
foreach (MapEntity e in selectedList) foreach (MapEntity e in selectedList)
{ {
if (e.rectMemento == null) if (e.rectMemento == null)
@@ -316,8 +332,23 @@ namespace Barotrauma
e.rectMemento.Store(e.Rect); e.rectMemento.Store(e.Rect);
} }
e.Move(moveAmount); e.Move(moveAmount);
if (isShiftDown && e is Item item && targetContainer != null)
{
if (targetContainer.OwnInventory.TryPutItem(item, Character.Controlled))
{
GUI.PlayUISound(GUISoundType.DropItem);
deposited.Add(item);
}
else
{
GUI.PlayUISound(GUISoundType.PickItemFail);
}
}
e.rectMemento.Store(e.Rect); e.rectMemento.Store(e.Rect);
} }
deposited.ForEach(entity => { selectedList.Remove(entity); });
} }
} }
startMovingPos = Vector2.Zero; startMovingPos = Vector2.Zero;
@@ -352,8 +383,7 @@ namespace Barotrauma
if (PlayerInput.PrimaryMouseButtonReleased()) if (PlayerInput.PrimaryMouseButtonReleased())
{ {
if (PlayerInput.KeyDown(Keys.LeftControl) || if (PlayerInput.IsCtrlDown())
PlayerInput.KeyDown(Keys.RightControl))
{ {
foreach (MapEntity e in newSelection) foreach (MapEntity e in newSelection)
{ {
@@ -436,7 +466,84 @@ namespace Barotrauma
} }
} }
private static void UpdateHighlightedListBox(List<MapEntity> highlightedEntities) public static Item GetPotentialContainer(Vector2 position, List<MapEntity> entities = null)
{
Item targetContainer = null;
bool isShiftDown = PlayerInput.IsShiftDown();
if (!isShiftDown) return null;
foreach (MapEntity e in mapEntityList)
{
if (!e.SelectableInEditor ||!(e is Item potentialContainer)) { continue; }
if (e.IsMouseOn(position))
{
if (entities == null)
{
if (potentialContainer.OwnInventory != null && potentialContainer.ParentInventory == null && !potentialContainer.OwnInventory.IsFull())
{
targetContainer = potentialContainer;
break;
}
}
else
{
foreach (MapEntity selectedEntity in entities)
{
if (!(selectedEntity is Item selectedItem)) { continue; }
if (potentialContainer.OwnInventory != null && potentialContainer.ParentInventory == null && potentialContainer != selectedItem &&
potentialContainer.OwnInventory.CanBePut(selectedItem))
{
targetContainer = potentialContainer;
break;
}
}
}
}
if (targetContainer != null) { break; }
}
return targetContainer;
}
/// <summary>
/// Updates the logic that runs the highlight box when the mouse is sitting still.
/// </summary>
/// <see cref="UpdateHighlightedListBox"/>
/// <param name="highlightedEntities"></param>
/// <param name="wiringMode">true to give items tooltip showing their connection</param>
public static void UpdateHighlighting(List<MapEntity> highlightedEntities, bool wiringMode = false)
{
if (PlayerInput.MouseSpeed.LengthSquared() > 10)
{
highlightTimer = 0.0f;
}
else
{
bool mouseNearHighlightBox = false;
if (highlightedListBox != null)
{
Rectangle expandedRect = highlightedListBox.Rect;
expandedRect.Inflate(20, 20);
mouseNearHighlightBox = expandedRect.Contains(PlayerInput.MousePosition);
if (!mouseNearHighlightBox) highlightedListBox = null;
}
highlightTimer += (float)Timing.Step;
if (highlightTimer > 1.0f)
{
if (!mouseNearHighlightBox)
{
UpdateHighlightedListBox(highlightedEntities, wiringMode);
highlightTimer = 0.0f;
}
}
}
}
private static void UpdateHighlightedListBox(List<MapEntity> highlightedEntities, bool wiringMode)
{ {
if (highlightedEntities == null || highlightedEntities.Count < 2) if (highlightedEntities == null || highlightedEntities.Count < 2)
{ {
@@ -453,14 +560,37 @@ namespace Barotrauma
highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas) highlightedListBox = new GUIListBox(new RectTransform(new Point(180, highlightedEntities.Count * 18 + 5), GUI.Canvas)
{ {
MaxSize = new Point(int.MaxValue, 256),
ScreenSpaceOffset = PlayerInput.MousePosition.ToPoint() + new Point(15) ScreenSpaceOffset = PlayerInput.MousePosition.ToPoint() + new Point(15)
}, style: "GUIToolTip"); }, style: "GUIToolTip");
foreach (MapEntity entity in highlightedEntities) foreach (MapEntity entity in highlightedEntities)
{ {
var tooltip = string.Empty;
if (wiringMode && entity is Item item)
{
var wire = item.GetComponent<Wire>();
if (wire?.Connections != null)
{
for (var i = 0; i < wire.Connections.Length; i++)
{
var conn = wire.Connections[i];
if (conn != null)
{
string[] tags = { "[item]", "[pin]" };
string[] values = { conn.Item?.Name, conn.Name };
tooltip += TextManager.GetWithVariables("wirelistformat",tags , values);
}
if (i != wire.Connections.Length - 1) { tooltip += '\n'; }
}
}
}
var textBlock = new GUITextBlock(new RectTransform(new Point(highlightedListBox.Content.Rect.Width, 15), highlightedListBox.Content.RectTransform), var textBlock = new GUITextBlock(new RectTransform(new Point(highlightedListBox.Content.Rect.Width, 15), highlightedListBox.Content.RectTransform),
ToolBox.LimitString(entity.Name, GUI.SmallFont, 140), font: GUI.SmallFont) ToolBox.LimitString(entity.Name, GUI.SmallFont, 140), font: GUI.SmallFont)
{ {
ToolTip = tooltip,
UserData = entity UserData = entity
}; };
} }
@@ -469,8 +599,7 @@ namespace Barotrauma
{ {
MapEntity entity = obj as MapEntity; MapEntity entity = obj as MapEntity;
if (PlayerInput.KeyDown(Keys.LeftControl) || if (PlayerInput.IsCtrlDown() && !wiringMode)
PlayerInput.KeyDown(Keys.RightControl))
{ {
if (selectedList.Contains(entity)) if (selectedList.Contains(entity))
{ {
@@ -480,11 +609,10 @@ namespace Barotrauma
{ {
AddSelection(entity); AddSelection(entity);
} }
return true;
} }
else
{
SelectEntity(entity); SelectEntity(entity);
}
return true; return true;
}; };
@@ -553,6 +681,10 @@ namespace Barotrauma
{ {
item.UpdateSpriteStates(deltaTime); item.UpdateSpriteStates(deltaTime);
} }
else if (me is Structure structure)
{
structure.UpdateSpriteStates(deltaTime);
}
} }
} }
@@ -570,24 +702,52 @@ namespace Barotrauma
{ {
Vector2 moveAmount = position - startMovingPos; Vector2 moveAmount = position - startMovingPos;
moveAmount.Y = -moveAmount.Y; moveAmount.Y = -moveAmount.Y;
bool isShiftDown = PlayerInput.IsShiftDown();
if (!isShiftDown)
{
moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X; moveAmount.X = (float)(moveAmount.X > 0.0f ? Math.Floor(moveAmount.X / Submarine.GridSize.X) : Math.Ceiling(moveAmount.X / Submarine.GridSize.X)) * Submarine.GridSize.X;
moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y; moveAmount.Y = (float)(moveAmount.Y > 0.0f ? Math.Floor(moveAmount.Y / Submarine.GridSize.Y) : Math.Ceiling(moveAmount.Y / Submarine.GridSize.Y)) * Submarine.GridSize.Y;
}
//started moving the selected entities //started moving the selected entities
if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y) if (Math.Abs(moveAmount.X) >= Submarine.GridSize.X || Math.Abs(moveAmount.Y) >= Submarine.GridSize.Y || isShiftDown)
{ {
foreach (MapEntity e in selectedList) foreach (MapEntity e in selectedList)
{ {
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
if (e is Item item) switch (e)
{
case Item item:
{ {
if (item.FlippedX && item.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally; if (item.FlippedX && item.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally;
if (item.flippedY && item.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically; if (item.flippedY && item.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically;
break;
} }
else if (e is Structure structure) case Structure structure:
{ {
if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally; if (structure.FlippedX && structure.Prefab.CanSpriteFlipX) spriteEffects ^= SpriteEffects.FlipHorizontally;
if (structure.flippedY && structure.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically; if (structure.flippedY && structure.Prefab.CanSpriteFlipY) spriteEffects ^= SpriteEffects.FlipVertically;
break;
}
case WayPoint wayPoint:
{
Vector2 drawPos = e.WorldPosition;
drawPos.Y = -drawPos.Y;
drawPos += moveAmount;
wayPoint.Draw(spriteBatch, drawPos);
continue;
}
case LinkedSubmarine linkedSub:
{
var ma = moveAmount;
ma.Y = -ma.Y;
Vector2 lPos = linkedSub.Position;
lPos += ma;
linkedSub.Draw(spriteBatch, lPos, alpha: 0.5f);
break;
}
} }
e.prefab?.DrawPlacing(spriteBatch, e.prefab?.DrawPlacing(spriteBatch,
new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteEffects); new Rectangle(e.WorldRect.Location + new Point((int)moveAmount.X, (int)-moveAmount.Y), e.WorldRect.Size), e.Scale, spriteEffects);
@@ -643,7 +803,7 @@ namespace Barotrauma
} }
} }
if ((PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))) if (PlayerInput.IsCtrlDown())
{ {
if (PlayerInput.KeyHit(Keys.N)) if (PlayerInput.KeyHit(Keys.N))
{ {
@@ -721,7 +881,10 @@ namespace Barotrauma
CopyEntities(entities); CopyEntities(entities);
entities.ForEach(e => e.Remove()); entities.ForEach(e =>
{
e.Remove();
});
entities.Clear(); entities.Clear();
} }
@@ -834,6 +997,7 @@ namespace Barotrauma
resizeDirX = x; resizeDirX = x;
resizeDirY = y; resizeDirY = y;
resizing = true; resizing = true;
startMovingPos = Vector2.Zero;
} }
} }
} }
@@ -851,6 +1015,11 @@ namespace Barotrauma
Vector2 mousePos = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 mousePos = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (PlayerInput.IsShiftDown())
{
mousePos = cam.ScreenToWorld(PlayerInput.MousePosition);
}
if (resizeDirX > 0) if (resizeDirX > 0)
{ {
mousePos.X = Math.Max(mousePos.X, rect.X + Submarine.GridSize.X); mousePos.X = Math.Max(mousePos.X, rect.X + Submarine.GridSize.X);
@@ -8,6 +8,12 @@ namespace Barotrauma
{ {
public virtual void UpdatePlacing(Camera cam) public virtual void UpdatePlacing(Camera cam)
{ {
if (PlayerInput.SecondaryMouseButtonClicked())
{
selected = null;
return;
}
Vector2 placeSize = Submarine.GridSize; Vector2 placeSize = Submarine.GridSize;
if (placePosition == Vector2.Zero) if (placePosition == Vector2.Zero)
@@ -41,12 +47,6 @@ namespace Barotrauma
newRect.Y = -newRect.Y; newRect.Y = -newRect.Y;
} }
if (PlayerInput.SecondaryMouseButtonHeld())
{
placePosition = Vector2.Zero;
selected = null;
}
} }
public virtual void DrawPlacing(SpriteBatch spriteBatch, Camera cam) public virtual void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
@@ -18,11 +18,13 @@ namespace Barotrauma
private List<ConvexHull> convexHulls; private List<ConvexHull> convexHulls;
private readonly Dictionary<DecorativeSprite, DecorativeSprite.State> spriteAnimState = new Dictionary<DecorativeSprite, DecorativeSprite.State>();
public override bool SelectableInEditor public override bool SelectableInEditor
{ {
get get
{ {
return HasBody ? ShowWalls : ShowStructures;; return HasBody ? ShowWalls : ShowStructures;
} }
} }
@@ -38,6 +40,14 @@ namespace Barotrauma
{ {
Prefab.sprite?.EnsureLazyLoaded(); Prefab.sprite?.EnsureLazyLoaded();
Prefab.BackgroundSprite?.EnsureLazyLoaded(); Prefab.BackgroundSprite?.EnsureLazyLoaded();
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
decorativeSprite.Sprite.EnsureLazyLoaded();
spriteAnimState.Add(decorativeSprite, new DecorativeSprite.State());
}
UpdateSpriteStates(0.0f);
} }
partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation) partial void CreateConvexHull(Vector2 position, Vector2 size, float rotation)
@@ -156,19 +166,19 @@ namespace Barotrauma
{ {
Rectangle worldRect = WorldRect; Rectangle worldRect = WorldRect;
if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) return false; if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; }
if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) return false; if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; }
return true; return true;
} }
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{ {
if (prefab.sprite == null) return; if (prefab.sprite == null) { return; }
if (editing) if (editing)
{ {
if (!HasBody && !ShowStructures) return; if (!HasBody && !ShowStructures) { return; }
if (HasBody && !ShowWalls) return; if (HasBody && !ShowWalls) { return; }
} }
Draw(spriteBatch, editing, back, null); Draw(spriteBatch, editing, back, null);
@@ -188,12 +198,13 @@ namespace Barotrauma
private void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Effect damageEffect = null) private void Draw(SpriteBatch spriteBatch, bool editing, bool back = true, Effect damageEffect = null)
{ {
if (prefab.sprite == null) return; if (prefab.sprite == null) { return; }
if (editing) if (editing)
{ {
if (!HasBody && !ShowStructures) return; if (!HasBody && !ShowStructures) { return; }
if (HasBody && !ShowWalls) return; if (HasBody && !ShowWalls) { return; }
} }
else if (HiddenInGame) { return; }
Color color = IsHighlighted ? GUI.Style.Orange : spriteColor; Color color = IsHighlighted ? GUI.Style.Orange : spriteColor;
if (IsSelected && editing) if (IsSelected && editing)
@@ -254,7 +265,7 @@ namespace Barotrauma
spriteBatch, spriteBatch,
new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)), new Vector2(rect.X + drawOffset.X, -(rect.Y + drawOffset.Y)),
new Vector2(rect.Width, rect.Height), new Vector2(rect.Width, rect.Height),
color: color, color: Prefab.BackgroundSpriteColor,
textureScale: TextureScale * Scale, textureScale: TextureScale * Scale,
startOffset: backGroundOffset, startOffset: backGroundOffset,
depth: Math.Max(Prefab.BackgroundSprite.Depth + (ID % 255) * 0.000001f, depth + 0.000001f)); depth: Math.Max(Prefab.BackgroundSprite.Depth + (ID % 255) * 0.000001f, depth + 0.000001f));
@@ -318,10 +329,20 @@ namespace Barotrauma
depth: depth, depth: depth,
textureScale: TextureScale * Scale); textureScale: TextureScale * Scale);
} }
foreach (var decorativeSprite in Prefab.DecorativeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
rotation, decorativeSprite.Scale * Scale, prefab.sprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - prefab.sprite.Depth), 0.999f));
}
prefab.sprite.effects = oldEffects; prefab.sprite.effects = oldEffects;
} }
if (GameMain.DebugDraw) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.5f)
{ {
if (Bodies != null) if (Bodies != null)
{ {
@@ -353,12 +374,70 @@ namespace Barotrauma
} }
} }
public void UpdateSpriteStates(float deltaTime)
{
DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches);
foreach (int spriteGroup in Prefab.DecorativeSpriteGroups.Keys)
{
for (int i = 0; i < Prefab.DecorativeSpriteGroups[spriteGroup].Count; i++)
{
var decorativeSprite = Prefab.DecorativeSpriteGroups[spriteGroup][i];
if (decorativeSprite == null) { continue; }
if (spriteGroup > 0)
{
int activeSpriteIndex = ID % Prefab.DecorativeSpriteGroups[spriteGroup].Count;
if (i != activeSpriteIndex)
{
spriteAnimState[decorativeSprite].IsActive = false;
continue;
}
}
//check if the sprite is active (whether it should be drawn or not)
var spriteState = spriteAnimState[decorativeSprite];
spriteState.IsActive = true;
foreach (PropertyConditional conditional in decorativeSprite.IsActiveConditionals)
{
if (!ConditionalMatches(conditional))
{
spriteState.IsActive = false;
break;
}
}
if (!spriteState.IsActive) { continue; }
//check if the sprite should be animated
bool animate = true;
foreach (PropertyConditional conditional in decorativeSprite.AnimationConditionals)
{
if (!ConditionalMatches(conditional)) { animate = false; break; }
}
if (!animate) { continue; }
spriteState.OffsetState += deltaTime;
spriteState.RotationState += deltaTime;
}
}
}
private bool ConditionalMatches(PropertyConditional conditional)
{
if (!string.IsNullOrEmpty(conditional.TargetItemComponentName))
{
return false;
}
else
{
if (!conditional.Matches(this)) { return false; }
}
return true;
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
byte sectionCount = msg.ReadByte(); byte sectionCount = msg.ReadByte();
if (sectionCount != Sections.Length) if (sectionCount != Sections.Length)
{ {
string errorMsg = $"Error while reading a network event for the structure \"{Name}\". Section count does not match (server: {sectionCount} client: {Sections.Length})"; string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". Section count does not match (server: {sectionCount} client: {Sections.Length})";
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
} }
@@ -1,13 +1,29 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
partial class StructurePrefab : MapEntityPrefab partial class StructurePrefab : MapEntityPrefab
{ {
public Color BackgroundSpriteColor
{
get;
private set;
}
public List<DecorativeSprite> DecorativeSprites = new List<DecorativeSprite>();
public Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
public override void UpdatePlacing(Camera cam) public override void UpdatePlacing(Camera cam)
{ {
if (PlayerInput.SecondaryMouseButtonClicked())
{
selected = null;
return;
}
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
Vector2 size = ScaledSize; Vector2 size = ScaledSize;
Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y); Rectangle newRect = new Rectangle((int)position.X, (int)position.Y, (int)size.X, (int)size.Y);
@@ -40,7 +56,7 @@ namespace Barotrauma
if (PlayerInput.PrimaryMouseButtonReleased()) if (PlayerInput.PrimaryMouseButtonReleased())
{ {
newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position); newRect.Location -= MathUtils.ToPoint(Submarine.MainSub.Position);
var structure = new Structure(newRect, this, Submarine.MainSub) new Structure(newRect, this, Submarine.MainSub)
{ {
Submarine = Submarine.MainSub Submarine = Submarine.MainSub
}; };
@@ -49,8 +65,6 @@ namespace Barotrauma
return; return;
} }
} }
if (PlayerInput.SecondaryMouseButtonHeld()) selected = null;
} }
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam) public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
@@ -60,9 +74,6 @@ namespace Barotrauma
if (placePosition == Vector2.Zero) if (placePosition == Vector2.Zero)
{ {
if (PlayerInput.PrimaryMouseButtonHeld())
placePosition = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
newRect.X = (int)position.X; newRect.X = (int)position.X;
newRect.Y = (int)position.Y; newRect.Y = (int)position.Y;
} }
@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
@@ -35,7 +36,6 @@ namespace Barotrauma
partial class Submarine : Entity, IServerSerializable partial class Submarine : Entity, IServerSerializable
{ {
public Sprite PreviewImage;
public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub) public static Vector2 MouseToWorldGrid(Camera cam, Submarine sub)
{ {
Vector2 position = PlayerInput.MousePosition; Vector2 position = PlayerInput.MousePosition;
@@ -56,6 +56,8 @@ namespace Barotrauma
private static List<RoundSound> roundSounds = null; private static List<RoundSound> roundSounds = null;
public static RoundSound LoadRoundSound(XElement element, bool stream = false) public static RoundSound LoadRoundSound(XElement element, bool stream = false)
{ {
if (GameMain.SoundManager?.Disabled ?? true) { return null; }
string filename = element.GetAttributeString("file", ""); string filename = element.GetAttributeString("file", "");
if (string.IsNullOrEmpty(filename)) filename = element.GetAttributeString("sound", ""); if (string.IsNullOrEmpty(filename)) filename = element.GetAttributeString("sound", "");
@@ -222,7 +224,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5); GUI.DrawRectangle(spriteBatch, worldBorders, Color.White, false, 0, 5);
if (sub.subBody == null || sub.subBody.PositionBuffer.Count < 2) continue; if (sub.SubBody == null || sub.subBody.PositionBuffer.Count < 2) continue;
Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position); Vector2 prevPos = ConvertUnits.ToDisplayUnits(sub.subBody.PositionBuffer[0].Position);
prevPos.Y = -prevPos.Y; prevPos.Y = -prevPos.Y;
@@ -245,7 +247,7 @@ namespace Barotrauma
public static Color DamageEffectColor; public static Color DamageEffectColor;
private static readonly List<Structure> depthSortedDamageable = new List<Structure>(); private static readonly List<Structure> depthSortedDamageable = new List<Structure>();
public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false) public static void DrawDamageable(SpriteBatch spriteBatch, Effect damageEffect, bool editing = false, Predicate<MapEntity> predicate = null)
{ {
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList; var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
@@ -256,6 +258,10 @@ namespace Barotrauma
{ {
if (e is Structure structure && structure.DrawDamageEffect) if (e is Structure structure && structure.DrawDamageEffect)
{ {
if (predicate != null)
{
if (!predicate(e)) continue;
}
float drawDepth = structure.GetDrawDepth(); float drawDepth = structure.GetDrawDepth();
int i = 0; int i = 0;
while (i < depthSortedDamageable.Count) while (i < depthSortedDamageable.Count)
@@ -329,125 +335,6 @@ namespace Barotrauma
} }
} }
public static bool SaveCurrent(string filePath, MemoryStream previewImage = null)
{
if (MainSub == null)
{
MainSub = new Submarine(filePath);
}
MainSub.filePath = filePath;
return MainSub.SaveAs(filePath, previewImage);
}
public void CreatePreviewWindow(GUIComponent parent)
{
var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null);
if (PreviewImage == null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded"));
}
else
{
var submarinePreviewBackground = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), style: null) { Color = Color.Black };
new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), PreviewImage, scaleToFit: true);
new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black);
}
var descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter))
{
UserData = "descriptionbox",
ScrollBarVisible = true,
Spacing = 5
};
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), descriptionBox.Content.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("submarine.name." + Name, true) ?? Name, font: GUI.LargeFont, wrap: true) { ForceUpperCase = true, CanBeFocused = false };
float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth;
ScalableFont font = descriptionBox.Rect.Width < 350 ? GUI.SmallFont : GUI.Font;
Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio;
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
}
if (!string.IsNullOrEmpty(RecommendedCrewExperience))
{
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
}
if (RequiredContentPackages.Any())
{
var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height);
}
// show what game version the submarine was created on
if (!IsVanillaSubmarine() && GameVersion != null)
{
var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height);
}
GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>());
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null);
if (!string.IsNullOrEmpty(Description))
{
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true) { CanBeFocused = false, ForceUpperCase = true };
}
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), Description, font: font, wrap: true)
{
CanBeFocused = false
};
}
public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null) public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null)
{ {
Rectangle worldBorders = GetDockedBorders(); Rectangle worldBorders = GetDockedBorders();
@@ -496,21 +383,6 @@ namespace Barotrauma
} }
} }
public bool IsVanillaSubmarine()
{
var vanilla = GameMain.VanillaContent;
if (vanilla != null)
{
var vanillaSubs = vanilla.GetFilesOfType(ContentType.Submarine);
string pathToCompare = filePath.Replace(@"\", @"/").ToLowerInvariant();
if (vanillaSubs.Any(sub => sub.Replace(@"\", @"/").ToLowerInvariant() == pathToCompare))
{
return true;
}
}
return false;
}
public void CheckForErrors() public void CheckForErrors()
{ {
List<string> errorMsgs = new List<string>(); List<string> errorMsgs = new List<string>();
@@ -531,6 +403,11 @@ namespace Barotrauma
} }
} }
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Human))
{
errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning"));
}
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path)) if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path))
{ {
errorMsgs.Add(TextManager.Get("NoWaypointsWarning")); errorMsgs.Add(TextManager.Get("NoWaypointsWarning"));
@@ -605,7 +482,7 @@ namespace Barotrauma
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Name); var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Info.Name);
msg.ReadPadBits(); msg.ReadPadBits();
if (posInfo != null) if (posInfo != null)
@@ -0,0 +1,149 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
partial class SubmarineInfo : IDisposable
{
public Sprite PreviewImage;
partial void InitProjectSpecific()
{
string previewImageData = SubmarineElement.GetAttributeString("previewimage", "");
if (!string.IsNullOrEmpty(previewImageData))
{
try
{
using (MemoryStream mem = new MemoryStream(Convert.FromBase64String(previewImageData)))
{
var texture = TextureLoader.FromStream(mem, path: FilePath);
if (texture == null) { throw new Exception("PreviewImage texture returned null"); }
PreviewImage = new Sprite(texture, null, null);
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e);
GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.");
PreviewImage = null;
}
}
}
public void CreatePreviewWindow(GUIComponent parent)
{
var content = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null);
if (PreviewImage == null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), TextManager.Get(SavedSubmarines.Contains(this) ? "SubPreviewImageNotFound" : "SubNotDownloaded"));
}
else
{
var submarinePreviewBackground = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform), style: null) { Color = Color.Black };
new GUIImage(new RectTransform(new Vector2(0.98f), submarinePreviewBackground.RectTransform, Anchor.Center), PreviewImage, scaleToFit: true);
new GUIFrame(new RectTransform(Vector2.One, submarinePreviewBackground.RectTransform), "InnerGlow", color: Color.Black);
}
var descriptionBox = new GUIListBox(new RectTransform(new Vector2(1, 0.5f), content.RectTransform, Anchor.BottomCenter))
{
UserData = "descriptionbox",
ScrollBarVisible = true,
Spacing = 5
};
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), descriptionBox.Content.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("submarine.name." + Name, true) ?? Name, font: GUI.LargeFont, wrap: true) { ForceUpperCase = true, CanBeFocused = false };
float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth;
ScalableFont font = descriptionBox.Rect.Width < 350 ? GUI.SmallFont : GUI.Font;
Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio;
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
}
if (!string.IsNullOrEmpty(RecommendedCrewExperience))
{
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
}
if (RequiredContentPackages.Any())
{
var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height);
}
// show what game version the submarine was created on
if (!IsVanillaSubmarine() && GameVersion != null)
{
var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height);
}
GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>());
//space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null);
if (!string.IsNullOrEmpty(Description))
{
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform),
TextManager.Get("SaveSubDialogDescription", fallBackTag: "WorkshopItemDescription"), font: GUI.Font, wrap: true)
{ CanBeFocused = false, ForceUpperCase = true };
}
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), Description, font: font, wrap: true)
{
CanBeFocused = false
};
}
}
}
@@ -1,17 +1,16 @@
using Microsoft.Xna.Framework; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.Items.Components;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
partial class WayPoint : MapEntity partial class WayPoint : MapEntity
{ {
private static Texture2D iconTexture; private static Dictionary<SpawnType, Sprite> iconSprites;
private const int IconSize = 32; private const int WaypointSize = 12, SpawnPointSize = 32;
private static int[] iconIndices = { 3, 0, 1, 2 };
public override bool IsVisible(Rectangle worldView) public override bool IsVisible(Rectangle worldView)
{ {
@@ -23,58 +22,58 @@ namespace Barotrauma
get { return !IsHidden(); } get { return !IsHidden(); }
} }
public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true) public override void Draw(SpriteBatch spriteBatch, bool editing, bool back = true)
{ {
if (!editing && !GameMain.DebugDraw) { return; } if (!editing && (!GameMain.DebugDraw || Screen.Selected.Cam.Zoom < 0.1f)) { return; }
if (IsHidden()) { return; } if (IsHidden()) { return; }
//Rectangle drawRect =
// Submarine == null ? rect : new Rectangle((int)(Submarine.DrawPosition.X + rect.X), (int)(Submarine.DrawPosition.Y + rect.Y), rect.Width, rect.Height);
Vector2 drawPos = Position; Vector2 drawPos = Position;
if (Submarine != null) drawPos += Submarine.DrawPosition; if (Submarine != null) { drawPos += Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
Color clr = currentHull == null ? Color.Blue : Color.White; Draw(spriteBatch, drawPos);
}
public void Draw(SpriteBatch spriteBatch, Vector2 drawPos)
{
Color clr = currentHull == null ? Color.CadetBlue : GUI.Style.Green;
if (spawnType != SpawnType.Path) { clr = Color.Gray; }
if (isObstructed) if (isObstructed)
{ {
clr = Color.Black; clr = Color.Black;
} }
if (IsSelected) clr = GUI.Style.Red; if (IsHighlighted || IsHighlighted) { clr = Color.Lerp(clr, Color.White, 0.8f); }
if (IsHighlighted) clr = Color.DarkRed;
int iconX = iconIndices[(int)spawnType] * IconSize % iconTexture.Width; int iconSize = spawnType == SpawnType.Path ? WaypointSize : SpawnPointSize;
int iconY = (int)(Math.Floor(iconIndices[(int)spawnType] * IconSize / (float)iconTexture.Width)) * IconSize; if (ConnectedGap != null || Ladders != null || Stairs != null || SpawnType != SpawnType.Path) { iconSize = (int)(iconSize * 1.5f); }
int iconSize = IconSize; if (IsSelected || IsHighlighted)
if (ConnectedGap != null)
{ {
iconSize = (int)(iconSize * 1.5f); int glowSize = (int)(iconSize * 1.5f);
} GUI.Style.UIGlowCircular.Draw(spriteBatch,
if (Ladders != null) new Rectangle((int)(drawPos.X - glowSize / 2), (int)(drawPos.Y - glowSize / 2), glowSize, glowSize),
{ Color.White);
iconSize = (int)(iconSize * 1.5f);
}
if (Stairs != null)
{
iconSize = (int)(iconSize * 1.5f);
} }
spriteBatch.Draw(iconTexture, Sprite sprite = iconSprites[SpawnType];
new Rectangle((int)(drawPos.X - iconSize / 2), (int)(drawPos.Y - iconSize / 2), iconSize, iconSize), if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
new Rectangle(iconX, iconY, IconSize, IconSize), clr); {
sprite = iconSprites[SpawnType.Path];
//GUI.DrawRectangle(spriteBatch, new Rectangle(drawRect.X, -drawRect.Y, rect.Width, rect.Height), clr, true); }
sprite.Draw(spriteBatch, drawPos, clr, scale: iconSize / (float)sprite.SourceRect.Width, depth: 0.001f);
//GUI.SmallFont.DrawString(spriteBatch, Position.ToString(), new Vector2(Position.X, -Position.Y), Color.White); sprite.RelativeOrigin = Vector2.One * 0.5f;
if (spawnType == SpawnType.Human && AssignedJob?.Icon != null)
{
AssignedJob.Icon.Draw(spriteBatch, drawPos, AssignedJob.UIColor, scale: iconSize / (float)AssignedJob.Icon.SourceRect.Width * 0.8f, depth: 0.0f);
}
foreach (MapEntity e in linkedTo) foreach (MapEntity e in linkedTo)
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
drawPos, drawPos,
new Vector2(e.DrawPosition.X, -e.DrawPosition.Y), new Vector2(e.DrawPosition.X, -e.DrawPosition.Y),
isObstructed ? Color.Gray : GUI.Style.Green, width: 5); (isObstructed ? Color.Gray : GUI.Style.Green) * 0.7f, width: 5, depth: 0.002f);
} }
GUI.SmallFont.DrawString(spriteBatch, GUI.SmallFont.DrawString(spriteBatch,
@@ -83,6 +82,14 @@ namespace Barotrauma
Color.WhiteSmoke); Color.WhiteSmoke);
} }
public override bool IsMouseOn(Vector2 position)
{
if (IsHidden()) { return false; }
float dist = Vector2.DistanceSquared(position, WorldPosition);
float radius = (SpawnType == SpawnType.Path ? WaypointSize : SpawnPointSize) * 0.6f;
return dist < radius * radius;
}
private bool IsHidden() private bool IsHidden()
{ {
if (spawnType == SpawnType.Path) if (spawnType == SpawnType.Path)
@@ -106,6 +113,29 @@ namespace Barotrauma
{ {
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (PlayerInput.KeyDown(Keys.Space))
{
foreach (MapEntity e in mapEntityList)
{
if (e.GetType() != typeof(WayPoint)) continue;
if (e == this) continue;
if (!Submarine.RectContains(e.Rect, position)) continue;
if (linkedTo.Contains(e))
{
linkedTo.Remove(e);
e.linkedTo.Remove(this);
}
else
{
linkedTo.Add(e);
e.linkedTo.Add(this);
}
}
}
else
{
// Update gaps, ladders, and stairs // Update gaps, ladders, and stairs
UpdateLinkedEntity(position, Gap.GapList, gap => ConnectedGap = gap, gap => UpdateLinkedEntity(position, Gap.GapList, gap => ConnectedGap = gap, gap =>
{ {
@@ -144,16 +174,6 @@ namespace Barotrauma
// Stairs = null; // Stairs = null;
// } // }
//}); //});
foreach (MapEntity e in mapEntityList)
{
if (e.GetType() != typeof(WayPoint)) continue;
if (e == this) continue;
if (!Submarine.RectContains(e.Rect, position)) continue;
linkedTo.Add(e);
e.linkedTo.Add(this);
} }
} }
} }
@@ -178,14 +198,19 @@ namespace Barotrauma
private bool ChangeSpawnType(GUIButton button, object obj) private bool ChangeSpawnType(GUIButton button, object obj)
{ {
GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock; GUITextBlock spawnTypeText = button.Parent.GetChildByUserData("spawntypetext") as GUITextBlock;
spawnType += (int)button.UserData; spawnType += (int)button.UserData;
var values = Enum.GetValues(typeof(SpawnType));
if (spawnType > SpawnType.Cargo) spawnType = SpawnType.Human; int firstIndex = 1;
if (spawnType < SpawnType.Human) spawnType = SpawnType.Cargo; int lastIndex = values.Length - 1;
if ((int)spawnType > lastIndex)
{
spawnType = (SpawnType)firstIndex;
}
if ((int)spawnType < firstIndex)
{
spawnType = (SpawnType)values.GetValue(lastIndex);
}
spawnTypeText.Text = spawnType.ToString(); spawnTypeText.Text = spawnType.ToString();
return true; return true;
} }
@@ -17,10 +17,12 @@ namespace Barotrauma.Networking
{ {
UInt16 ID = msg.ReadUInt16(); UInt16 ID = msg.ReadUInt16();
ChatMessageType type = (ChatMessageType)msg.ReadByte(); ChatMessageType type = (ChatMessageType)msg.ReadByte();
PlayerConnectionChangeType changeType = PlayerConnectionChangeType.None;
string txt = ""; string txt = "";
if (type != ChatMessageType.Order) if (type != ChatMessageType.Order)
{ {
changeType = (PlayerConnectionChangeType)msg.ReadByte();
txt = msg.ReadString(); txt = msg.ReadString();
} }
@@ -114,7 +116,7 @@ namespace Barotrauma.Networking
GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType); GameMain.Client.ServerSettings.ServerLog?.WriteLine(txt, messageType);
break; break;
default: default:
GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter); GameMain.Client.AddChatMessage(txt, type, senderName, senderCharacter, changeType);
break; break;
} }
LastID = ID; LastID = ID;
@@ -14,8 +14,10 @@ namespace Barotrauma.Networking
public UInt64 SteamID; public UInt64 SteamID;
public byte ID; public byte ID;
public UInt16 CharacterID; public UInt16 CharacterID;
public float Karma;
public bool Muted; public bool Muted;
public bool InGame; public bool InGame;
public bool HasPermissions;
public bool AllowKicking; public bool AllowKicking;
} }
@@ -44,6 +46,8 @@ namespace Barotrauma.Networking
public bool AllowKicking; public bool AllowKicking;
public float Karma;
public void UpdateSoundPosition() public void UpdateSoundPosition()
{ {
if (VoipSound == null) { return; } if (VoipSound == null) { return; }
@@ -72,6 +76,7 @@ namespace Barotrauma.Networking
else else
{ {
VoipSound.SetPosition(null); VoipSound.SetPosition(null);
VoipSound.Gain = 1.0f;
} }
} }
@@ -28,6 +28,8 @@ namespace Barotrauma.Networking
get { return name; } get { return name; }
} }
public string PendingName = string.Empty;
public void SetName(string value) public void SetName(string value)
{ {
value = value.Replace(":", "").Replace(";", ""); value = value.Replace(":", "").Replace(";", "");
@@ -78,7 +80,7 @@ namespace Barotrauma.Networking
private List<Client> otherClients; private List<Client> otherClients;
private readonly List<Submarine> serverSubmarines = new List<Submarine>(); private readonly List<SubmarineInfo> serverSubmarines = new List<SubmarineInfo>();
private string serverIP, serverName; private string serverIP, serverName;
@@ -112,6 +114,7 @@ namespace Barotrauma.Networking
public bool SpawnAsTraitor; public bool SpawnAsTraitor;
public string TraitorFirstObjective; public string TraitorFirstObjective;
public TraitorMissionPrefab TraitorMission = null;
public byte ID public byte ID
{ {
@@ -482,19 +485,24 @@ namespace Barotrauma.Networking
if (requiresPw && !canStart && !connectCancelled) if (requiresPw && !canStart && !connectCancelled)
{ {
GUI.ClearCursorWait();
reconnectBox?.Close(); reconnectBox = null; reconnectBox?.Close(); reconnectBox = null;
string pwMsg = TextManager.Get("PasswordRequired"); string pwMsg = TextManager.Get("PasswordRequired");
var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, var msgBox = new GUIMessageBox(pwMsg, "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") },
relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, 170)); relativeSize: new Vector2(0.25f, 0.1f), minSize: new Point(400, (int)(170 * Math.Max(1.0f, GUI.Scale))));
var passwordHolder = new GUILayoutGroup(new RectTransform(Vector2.One, msgBox.Content.RectTransform), childAnchor: Anchor.TopCenter); var passwordHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), msgBox.Content.RectTransform), childAnchor: Anchor.TopCenter);
var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f) , passwordHolder.RectTransform) { MinSize = new Point(0, 20) }) var passwordBox = new GUITextBox(new RectTransform(new Vector2(0.8f, 1f), passwordHolder.RectTransform) { MinSize = new Point(0, 20) })
{ {
UserData = "password", UserData = "password",
Censor = true Censor = true
}; };
msgBox.Content.Recalculate();
msgBox.Content.RectTransform.MinSize = new Point(0, msgBox.Content.RectTransform.Children.Sum(c => c.Rect.Height));
msgBox.Content.Parent.RectTransform.MinSize = new Point(0, (int)(msgBox.Content.RectTransform.MinSize.Y / msgBox.Content.RectTransform.RelativeSize.Y));
var okButton = msgBox.Buttons[0]; var okButton = msgBox.Buttons[0];
var cancelButton = msgBox.Buttons[1]; var cancelButton = msgBox.Buttons[1];
@@ -509,6 +517,7 @@ namespace Barotrauma.Networking
{ {
requiresPw = false; requiresPw = false;
connectCancelled = true; connectCancelled = true;
GameMain.ServerListScreen.Select();
return true; return true;
}; };
@@ -540,6 +549,7 @@ namespace Barotrauma.Networking
foreach (Client c in ConnectedClients) foreach (Client c in ConnectedClients)
{ {
if (c.Character != null && c.Character.Removed) { c.Character = null; }
c.UpdateSoundPosition(); c.UpdateSoundPosition();
} }
@@ -664,6 +674,7 @@ namespace Barotrauma.Networking
if (header != ServerPacketHeader.STARTGAMEFINALIZE && if (header != ServerPacketHeader.STARTGAMEFINALIZE &&
header != ServerPacketHeader.ENDGAME && header != ServerPacketHeader.ENDGAME &&
header != ServerPacketHeader.PING_REQUEST &&
roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize) roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
{ {
//rewind the header byte we just read //rewind the header byte we just read
@@ -674,6 +685,31 @@ namespace Barotrauma.Networking
switch (header) switch (header)
{ {
case ServerPacketHeader.PING_REQUEST:
IWriteMessage response = new WriteOnlyMessage();
response.Write((byte)ClientPacketHeader.PING_RESPONSE);
byte requestLen = inc.ReadByte();
response.Write(requestLen);
for (int i=0;i<requestLen;i++)
{
byte b = inc.ReadByte();
response.Write(b);
}
clientPeer.Send(response, DeliveryMethod.Unreliable);
break;
case ServerPacketHeader.CLIENT_PINGS:
byte clientCount = inc.ReadByte();
for (int i=0;i<clientCount;i++)
{
byte clientId = inc.ReadByte();
UInt16 clientPing = inc.ReadUInt16();
Client client = ConnectedClients.Find(c => c.ID == clientId);
if (client != null)
{
client.Ping = clientPing;
}
}
break;
case ServerPacketHeader.UPDATE_LOBBY: case ServerPacketHeader.UPDATE_LOBBY:
ReadLobbyUpdate(inc); ReadLobbyUpdate(inc);
break; break;
@@ -837,7 +873,7 @@ namespace Barotrauma.Networking
if (Level.Loaded.EqualityCheckVal != levelEqualityCheckVal) 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 + ")" + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" +
", mirrored: " + Level.Loaded.Mirrored + ")."; ", mirrored: " + Level.Loaded.Mirrored + ").";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
@@ -1051,11 +1087,13 @@ namespace Barotrauma.Networking
var missionPrefab = TraitorMissionPrefab.List.Find(t => t.Identifier == missionIdentifier); var missionPrefab = TraitorMissionPrefab.List.Find(t => t.Identifier == missionIdentifier);
Sprite icon = missionPrefab?.Icon; Sprite icon = missionPrefab?.Icon;
switch(messageType) { switch(messageType)
{
case TraitorMessageType.Objective: case TraitorMessageType.Objective:
var isTraitor = !string.IsNullOrEmpty(message); var isTraitor = !string.IsNullOrEmpty(message);
SpawnAsTraitor = isTraitor; SpawnAsTraitor = isTraitor;
TraitorFirstObjective = message; TraitorFirstObjective = message;
TraitorMission = missionPrefab;
if (Character != null) if (Character != null)
{ {
Character.IsTraitor = isTraitor; Character.IsTraitor = isTraitor;
@@ -1326,7 +1364,6 @@ namespace Barotrauma.Networking
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
});*/ });*/
GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level, GameMain.GameSession.StartRound(campaign.Map.SelectedConnection.Level,
reloadSub: true,
mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]); mirrorLevel: campaign.Map.CurrentLocation != campaign.Map.SelectedConnection.Locations[0]);
} }
@@ -1406,9 +1443,9 @@ namespace Barotrauma.Networking
} }
} }
if (GameMain.GameSession.Submarine.IsFileCorrupted) if (GameMain.GameSession.Submarine.Info.IsFileCorrupted)
{ {
DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Name}\"."); DebugConsole.ThrowError($"Failed to start a round. Could not load the submarine \"{GameMain.GameSession.Submarine.Info.Name}\".");
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -1498,8 +1535,8 @@ namespace Barotrauma.Networking
bool requiredContentPackagesInstalled = inc.ReadBoolean(); bool requiredContentPackagesInstalled = inc.ReadBoolean();
var matchingSub = var matchingSub =
Submarine.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash) ?? SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == subName && s.MD5Hash.Hash == subHash) ??
new Submarine(Path.Combine(Submarine.SavePath, subName) + ".sub", subHash, false); new SubmarineInfo(Path.Combine(SubmarineInfo.SavePath, subName) + ".sub", subHash, tryLoad: false);
matchingSub.RequiredContentPackagesInstalled = requiredContentPackagesInstalled; matchingSub.RequiredContentPackagesInstalled = requiredContentPackagesInstalled;
serverSubmarines.Add(matchingSub); serverSubmarines.Add(matchingSub);
@@ -1533,9 +1570,11 @@ namespace Barotrauma.Networking
string name = inc.ReadString(); string name = inc.ReadString();
string preferredJob = inc.ReadString(); string preferredJob = inc.ReadString();
UInt16 characterID = inc.ReadUInt16(); UInt16 characterID = inc.ReadUInt16();
float karma = inc.ReadSingle();
bool muted = inc.ReadBoolean(); bool muted = inc.ReadBoolean();
bool inGame = inc.ReadBoolean(); bool inGame = inc.ReadBoolean();
bool allowKicking = inc.ReadBoolean(); bool hasPermissions = inc.ReadBoolean();
bool allowKicking = inc.ReadBoolean() || IsServerOwner;
inc.ReadPadBits(); inc.ReadPadBits();
tempClients.Add(new TempClient tempClients.Add(new TempClient
@@ -1546,8 +1585,10 @@ namespace Barotrauma.Networking
Name = name, Name = name,
PreferredJob = preferredJob, PreferredJob = preferredJob,
CharacterID = characterID, CharacterID = characterID,
Karma = karma,
Muted = muted, Muted = muted,
InGame = inGame, InGame = inGame,
HasPermissions = hasPermissions,
AllowKicking = allowKicking AllowKicking = allowKicking
}); });
} }
@@ -1575,7 +1616,9 @@ namespace Barotrauma.Networking
existingClient.NameID = tc.NameID; existingClient.NameID = tc.NameID;
existingClient.PreferredJob = tc.PreferredJob; existingClient.PreferredJob = tc.PreferredJob;
existingClient.Character = null; existingClient.Character = null;
existingClient.Karma = tc.Karma;
existingClient.Muted = tc.Muted; existingClient.Muted = tc.Muted;
existingClient.HasPermissions = tc.HasPermissions;
existingClient.InGame = tc.InGame; existingClient.InGame = tc.InGame;
existingClient.AllowKicking = tc.AllowKicking; existingClient.AllowKicking = tc.AllowKicking;
GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient); GameMain.NetLobbyScreen.SetPlayerNameAndJobPreference(existingClient);
@@ -2028,15 +2071,15 @@ namespace Barotrauma.Networking
{ {
case FileTransferType.Submarine: case FileTransferType.Submarine:
new GUIMessageBox(TextManager.Get("ServerDownloadFinished"), TextManager.GetWithVariable("FileDownloadedNotification", "[filename]", transfer.FileName)); new GUIMessageBox(TextManager.Get("ServerDownloadFinished"), TextManager.GetWithVariable("FileDownloadedNotification", "[filename]", transfer.FileName));
var newSub = new Submarine(transfer.FilePath); var newSub = new SubmarineInfo(transfer.FilePath);
if (newSub.IsFileCorrupted) { return; } if (newSub.IsFileCorrupted) { return; }
var existingSubs = Submarine.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.Hash == newSub.MD5Hash.Hash).ToList(); var existingSubs = SubmarineInfo.SavedSubmarines.Where(s => s.Name == newSub.Name && s.MD5Hash.Hash == newSub.MD5Hash.Hash).ToList();
foreach (Submarine existingSub in existingSubs) foreach (SubmarineInfo existingSub in existingSubs)
{ {
existingSub.Dispose(); existingSub.Dispose();
} }
Submarine.AddToSavedSubs(newSub); SubmarineInfo.AddToSavedSubs(newSub);
for (int i = 0; i < 2; i++) for (int i = 0; i < 2; i++)
{ {
@@ -2045,8 +2088,8 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.SubList.Content.Children; GameMain.NetLobbyScreen.SubList.Content.Children;
var subElement = subListChildren.FirstOrDefault(c => var subElement = subListChildren.FirstOrDefault(c =>
((Submarine)c.UserData).Name == newSub.Name && ((SubmarineInfo)c.UserData).Name == newSub.Name &&
((Submarine)c.UserData).MD5Hash.Hash == newSub.MD5Hash.Hash); ((SubmarineInfo)c.UserData).MD5Hash.Hash == newSub.MD5Hash.Hash);
if (subElement == null) continue; if (subElement == null) continue;
subElement.GetChild<GUITextBlock>().TextColor = new Color(subElement.GetChild<GUITextBlock>().TextColor, 1.0f); subElement.GetChild<GUITextBlock>().TextColor = new Color(subElement.GetChild<GUITextBlock>().TextColor, 1.0f);
@@ -2074,17 +2117,17 @@ namespace Barotrauma.Networking
if (campaign == null) { return; } if (campaign == null) { return; }
GameMain.GameSession.SavePath = transfer.FilePath; GameMain.GameSession.SavePath = transfer.FilePath;
if (GameMain.GameSession.Submarine == null) if (GameMain.GameSession.SubmarineInfo == null)
{ {
var gameSessionDoc = SaveUtil.LoadGameSessionDoc(GameMain.GameSession.SavePath); var gameSessionDoc = SaveUtil.LoadGameSessionDoc(GameMain.GameSession.SavePath);
string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub"; string subPath = Path.Combine(SaveUtil.TempPath, gameSessionDoc.Root.GetAttributeString("submarine", "")) + ".sub";
GameMain.GameSession.Submarine = new Submarine(subPath, ""); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(subPath, "");
} }
SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession); SaveUtil.LoadGame(GameMain.GameSession.SavePath, GameMain.GameSession);
GameMain.GameSession?.Submarine?.CheckSubsLeftBehind(); GameMain.GameSession?.SubmarineInfo?.CheckSubsLeftBehind();
if (GameMain.GameSession?.Submarine?.Name != null) if (GameMain.GameSession?.SubmarineInfo?.Name != null)
{ {
GameMain.NetLobbyScreen.TryDisplayCampaignSubmarine(GameMain.GameSession.Submarine); GameMain.NetLobbyScreen.TryDisplayCampaignSubmarine(GameMain.GameSession.SubmarineInfo);
} }
campaign.LastSaveID = campaign.PendingSaveID; campaign.LastSaveID = campaign.PendingSaveID;
@@ -2119,8 +2162,7 @@ namespace Barotrauma.Networking
{ {
if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; } if (!permissions.HasFlag(ClientPermissions.ConsoleCommands)) { return false; }
commandName = commandName.ToLowerInvariant(); if (permittedConsoleCommands.Any(c => c.Equals(commandName, StringComparison.OrdinalIgnoreCase))) { return true; }
if (permittedConsoleCommands.Any(c => c.ToLowerInvariant() == commandName)) { return true; }
//check aliases //check aliases
foreach (DebugConsole.Command command in DebugConsole.Commands) foreach (DebugConsole.Command command in DebugConsole.Commands)
@@ -2371,7 +2413,7 @@ namespace Barotrauma.Networking
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
public void SetupNewCampaign(Submarine sub, string saveName, string mapSeed) public void SetupNewCampaign(SubmarineInfo sub, string saveName, string mapSeed)
{ {
GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false; GameMain.NetLobbyScreen.CampaignSetupFrame.Visible = false;
@@ -2537,6 +2579,11 @@ namespace Barotrauma.Networking
inGameHUD.AddToGUIUpdateList(); inGameHUD.AddToGUIUpdateList();
GameMain.NetLobbyScreen.FileTransferFrame?.AddToGUIUpdateList(); GameMain.NetLobbyScreen.FileTransferFrame?.AddToGUIUpdateList();
} }
serverSettings.AddToGUIUpdateList();
if (serverSettings.ServerLog.LogFrame != null) serverSettings.ServerLog.LogFrame.AddToGUIUpdateList();
GameMain.NetLobbyScreen?.PlayerFrame?.AddToGUIUpdateList();
} }
public void UpdateHUD(float deltaTime) public void UpdateHUD(float deltaTime)
@@ -2580,7 +2627,7 @@ namespace Barotrauma.Networking
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
bool chatKeyHit = PlayerInput.KeyHit(InputType.Chat); bool chatKeyHit = PlayerInput.KeyHit(InputType.Chat);
bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 0); bool radioKeyHit = PlayerInput.KeyHit(InputType.RadioChat) && (Character.Controlled == null || Character.Controlled.SpeechImpediment < 100);
if (chatKeyHit || radioKeyHit) if (chatKeyHit || radioKeyHit)
{ {
@@ -2626,8 +2673,6 @@ namespace Barotrauma.Networking
} }
} }
} }
serverSettings.AddToGUIUpdateList();
if (serverSettings.ServerLog.LogFrame != null) serverSettings.ServerLog.LogFrame.AddToGUIUpdateList();
} }
public virtual void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) public virtual void Draw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
@@ -2740,16 +2785,31 @@ namespace Barotrauma.Networking
}*/ }*/
} }
public virtual bool SelectCrewCharacter(Character character, GUIComponent characterFrame) public virtual bool SelectCrewCharacter(Character character, GUIComponent frame)
{ {
if (character == null) { return false; } if (character == null) return false;
if (character != myCharacter) if (character != myCharacter)
{ {
var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == character); var client = GameMain.NetworkMember.ConnectedClients.Find(c => c.Character == character);
if (client == null) { return false; } if (client == null) return false;
var content = new GUIFrame(new RectTransform(new Vector2(0.9f, 1.0f - characterFrame.RectTransform.RelativeSize.Y), characterFrame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter), CreateSelectionRelatedButtons(client, frame);
}
return true;
}
public virtual bool SelectCrewClient(Client client, GUIComponent frame)
{
if (client == null || client.ID == ID) return false;
CreateSelectionRelatedButtons(client, frame);
return true;
}
private void CreateSelectionRelatedButtons(Client client, GUIComponent frame)
{
var content = new GUIFrame(new RectTransform(new Vector2(1f, 1.0f - frame.RectTransform.RelativeSize.Y), frame.RectTransform, Anchor.BottomCenter, Pivot.TopCenter),
style: null); style: null);
var mute = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform, Anchor.TopCenter), var mute = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform, Anchor.TopCenter),
@@ -2759,14 +2819,35 @@ namespace Barotrauma.Networking
OnSelected = (tickBox) => { client.MutedLocally = tickBox.Selected; return true; } OnSelected = (tickBox) => { client.MutedLocally = tickBox.Selected; return true; }
}; };
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform, Anchor.BottomCenter), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), content.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomLeft)
{ {
RelativeSpacing = 0.05f, RelativeSpacing = 0.05f,
ChildAnchor = Anchor.CenterLeft,
Stretch = true Stretch = true
}; };
if (HasPermission(ClientPermissions.Ban)) if (!GameMain.Client.GameStarted || (GameMain.Client.Character == null || GameMain.Client.Character.IsDead) && (client.Character == null || client.Character.IsDead))
{
var messageButton = new GUIButton(new RectTransform(new Vector2(1f, 0.2f), content.RectTransform, Anchor.BottomCenter) { RelativeOffset = new Vector2(0f, buttonContainer.RectTransform.RelativeSize.Y) },
TextManager.Get("message"), style: "GUIButtonSmall")
{
UserData = client,
OnClicked = (btn, userdata) =>
{
chatBox.InputBox.Text = $"{client.Name}; ";
CoroutineManager.StartCoroutine(selectCoroutine());
return false;
}
};
}
// Need a delayed selection due to the inputbox being deselected when a left click occurs outside of it
IEnumerable<object> selectCoroutine()
{
yield return new WaitForSeconds(0.01f, true);
chatBox.InputBox.Select(chatBox.InputBox.Text.Length);
}
if (HasPermission(ClientPermissions.Ban) && client.AllowKicking)
{ {
var banButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform), var banButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform),
TextManager.Get("Ban"), style: "GUIButtonSmall") TextManager.Get("Ban"), style: "GUIButtonSmall")
@@ -2775,7 +2856,7 @@ namespace Barotrauma.Networking
OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.BanPlayer(client); return false; } OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.BanPlayer(client); return false; }
}; };
} }
if (HasPermission(ClientPermissions.Kick)) if (HasPermission(ClientPermissions.Kick) && client.AllowKicking)
{ {
var kickButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform), var kickButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform),
TextManager.Get("Kick"), style: "GUIButtonSmall") TextManager.Get("Kick"), style: "GUIButtonSmall")
@@ -2784,7 +2865,7 @@ namespace Barotrauma.Networking
OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.KickPlayer(client); return false; } OnClicked = (btn, userdata) => { GameMain.NetLobbyScreen.KickPlayer(client); return false; }
}; };
} }
else if (serverSettings.Voting.AllowVoteKick) else if (serverSettings.Voting.AllowVoteKick && client.AllowKicking)
{ {
var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform), var kickVoteButton = new GUIButton(new RectTransform(new Vector2(0.45f, 0.9f), buttonContainer.RectTransform),
TextManager.Get("VoteToKick"), style: "GUIButtonSmall") TextManager.Get("VoteToKick"), style: "GUIButtonSmall")
@@ -2799,14 +2880,11 @@ namespace Barotrauma.Networking
} }
} }
return true;
}
public void CreateKickReasonPrompt(string clientName, bool ban, bool rangeBan = false) public void CreateKickReasonPrompt(string clientName, bool ban, bool rangeBan = false)
{ {
var banReasonPrompt = new GUIMessageBox( var banReasonPrompt = new GUIMessageBox(
TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"), TextManager.Get(ban ? "BanReasonPrompt" : "KickReasonPrompt"),
"", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, new Vector2(0.25f, 0.2f), new Point(400, 200)); "", new string[] { TextManager.Get("OK"), TextManager.Get("Cancel") }, new Vector2(0.25f, 0.22f), new Point(400, 220));
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center)); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.6f), banReasonPrompt.InnerFrame.RectTransform, Anchor.Center));
var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform)) var banReasonBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.3f), content.RectTransform))
@@ -2820,14 +2898,16 @@ namespace Barotrauma.Networking
if (ban) if (ban)
{ {
new GUITextBlock(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform), TextManager.Get("BanDuration"));
permaBanTickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform) { RelativeOffset = new Vector2(0.05f, 0.0f) }, var labelContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), content.RectTransform), isHorizontal: false);
TextManager.Get("BanPermanent")) new GUITextBlock(new RectTransform(new Vector2(1f, 0.5f), labelContainer.RectTransform), TextManager.Get("BanDuration")) { Padding = Vector4.Zero };
var buttonContent = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), labelContainer.RectTransform), isHorizontal: true);
permaBanTickBox = new GUITickBox(new RectTransform(new Vector2(0.4f, 0.15f), buttonContent.RectTransform), TextManager.Get("BanPermanent"))
{ {
Selected = true Selected = true
}; };
var durationContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.15f), content.RectTransform), isHorizontal: true) var durationContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1f), buttonContent.RectTransform), isHorizontal: true)
{ {
Visible = false Visible = false
}; };
@@ -2930,7 +3010,7 @@ namespace Barotrauma.Networking
} }
if (GameMain.GameSession?.Submarine != null) if (GameMain.GameSession?.Submarine != null)
{ {
errorLines.Add("Submarine: " + GameMain.GameSession.Submarine.Name); errorLines.Add("Submarine: " + GameMain.GameSession.Submarine.Info.Name);
} }
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
@@ -2952,8 +3032,8 @@ namespace Barotrauma.Networking
errorLines.Add(" " + DebugConsole.Messages[i].Time + " - " + DebugConsole.Messages[i].Text); errorLines.Add(" " + DebugConsole.Messages[i].Time + " - " + DebugConsole.Messages[i].Text);
} }
string filePath = "event_error_log_client_" + Name + "_" + ToolBox.RemoveInvalidFileNameChars(DateTime.UtcNow.ToShortTimeString() + ".log"); string filePath = "event_error_log_client_" + Name + "_" + DateTime.UtcNow.ToShortTimeString() + ".log";
filePath = Path.Combine(ServerLog.SavePath, filePath); filePath = Path.Combine(ServerLog.SavePath, ToolBox.RemoveInvalidFileNameChars(filePath));
if (!Directory.Exists(ServerLog.SavePath)) if (!Directory.Exists(ServerLog.SavePath))
{ {
@@ -47,11 +47,30 @@ namespace Barotrauma
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(StructureDamageKarmaDecrease)); CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(StructureDamageKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(DamageFriendlyKarmaDecrease)); CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(DamageFriendlyKarmaDecrease));
//hide these for now if a localized text is not available
if (TextManager.ContainsTag("Karma." + nameof(StunFriendlyKarmaDecrease)))
{
CreateLabeledSlider(parent, 0.0f, 1.0f, 0.01f, nameof(StunFriendlyKarmaDecrease));
}
if (TextManager.ContainsTag("Karma." + nameof(StunFriendlyKarmaDecreaseThreshold)))
{
CreateLabeledSlider(parent, 0.0f, 10.0f, 1.0f, nameof(StunFriendlyKarmaDecreaseThreshold));
}
CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, nameof(ReactorMeltdownKarmaDecrease)); CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, nameof(ReactorMeltdownKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, nameof(ReactorOverheatKarmaDecrease)); CreateLabeledSlider(parent, 0.0f, 10.0f, 0.05f, nameof(ReactorOverheatKarmaDecrease));
CreateLabeledNumberInput(parent, 0, 20, nameof(AllowedWireDisconnectionsPerMinute)); CreateLabeledNumberInput(parent, 0, 20, nameof(AllowedWireDisconnectionsPerMinute));
CreateLabeledSlider(parent, 0.0f, 20.0f, 0.5f, nameof(WireDisconnectionKarmaDecrease)); CreateLabeledSlider(parent, 0.0f, 20.0f, 0.5f, nameof(WireDisconnectionKarmaDecrease));
CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, nameof(SpamFilterKarmaDecrease)); CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, nameof(SpamFilterKarmaDecrease));
//hide these for now if a localized text is not available
if (TextManager.ContainsTag("Karma." + nameof(DangerousItemStealKarmaDecrease)))
{
CreateLabeledSlider(parent, 0.0f, 30.0f, 1.0f, nameof(DangerousItemStealKarmaDecrease));
}
if (TextManager.ContainsTag("Karma." + nameof(DangerousItemStealBots)))
{
CreateLabeledTickBox(parent, nameof(DangerousItemStealBots));
}
} }
private void CreateLabeledSlider(GUIComponent parent, float min, float max, float step, string propertyName) private void CreateLabeledSlider(GUIComponent parent, float min, float max, float step, string propertyName)
@@ -39,7 +39,10 @@ namespace Barotrauma.Networking
contentPackageOrderReceived = false; contentPackageOrderReceived = false;
netPeerConfiguration = new NetPeerConfiguration("barotrauma"); netPeerConfiguration = new NetPeerConfiguration("barotrauma")
{
UseDualModeSockets = GameMain.Config.UseDualModeSockets
};
netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt netPeerConfiguration.DisableMessageType(NetIncomingMessageType.DebugMessage | NetIncomingMessageType.WarningMessage | NetIncomingMessageType.Receipt
| NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error); | NetIncomingMessageType.ErrorMessage | NetIncomingMessageType.Error);
@@ -137,8 +137,9 @@ namespace Barotrauma.Networking
timeout -= deltaTime; timeout -= deltaTime;
heartbeatTimer -= deltaTime; heartbeatTimer -= deltaTime;
while (Steamworks.SteamNetworking.IsP2PPacketAvailable()) for (int i=0;i<100;i++)
{ {
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket(); var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue) if (packet.HasValue)
{ {
@@ -204,8 +204,9 @@ namespace Barotrauma.Networking
} }
} }
while (Steamworks.SteamNetworking.IsP2PPacketAvailable()) for (int i=0;i<100;i++)
{ {
if (!Steamworks.SteamNetworking.IsP2PPacketAvailable()) { break; }
var packet = Steamworks.SteamNetworking.ReadP2PPacket(); var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue) if (packet.HasValue)
{ {
@@ -2,6 +2,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using Barotrauma.Extensions;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -9,9 +11,19 @@ namespace Barotrauma.Networking
{ {
public GUIButton LogFrame; public GUIButton LogFrame;
private GUIListBox listBox; private GUIListBox listBox;
private GUIButton reverseButton;
private string msgFilter; private string msgFilter;
private bool reverseOrder = false;
private bool OnReverseClicked(GUIButton btn, object obj)
{
SetMessageReversal(!reverseOrder);
return false;
}
public void CreateLogFrame() public void CreateLogFrame()
{ {
LogFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker") LogFrame = new GUIButton(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker")
@@ -80,7 +92,17 @@ namespace Barotrauma.Networking
GUI.KeyboardDispatcher.Subscriber = searchBox; GUI.KeyboardDispatcher.Subscriber = searchBox;
filterArea.RectTransform.MinSize = new Point(0, filterArea.RectTransform.Children.Max(c => c.MinSize.Y)); filterArea.RectTransform.MinSize = new Point(0, filterArea.RectTransform.Children.Max(c => c.MinSize.Y));
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), rightColumn.RectTransform)); GUILayoutGroup listBoxLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.95f), rightColumn.RectTransform))
{
Stretch = true,
RelativeSpacing = 0.0f
};
reverseButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), listBoxLayout.RectTransform), style: "UIToggleButtonVertical");
reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None);
reverseButton.OnClicked = OnReverseClicked;
listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), listBoxLayout.RectTransform));
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close")) GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.05f), rightColumn.RectTransform), TextManager.Get("Close"))
{ {
@@ -107,7 +129,7 @@ namespace Barotrauma.Networking
msgFilter = ""; msgFilter = "";
} }
public void AssignLogFrame(GUIListBox inListBox, GUIComponent tickBoxContainer, GUITextBox searchBox) public void AssignLogFrame(GUIButton inReverseButton, GUIListBox inListBox, GUIComponent tickBoxContainer, GUITextBox searchBox)
{ {
searchBox.OnTextChanged += (textBox, text) => searchBox.OnTextChanged += (textBox, text) =>
{ {
@@ -144,6 +166,10 @@ namespace Barotrauma.Networking
inListBox.ClearChildren(); inListBox.ClearChildren();
listBox = inListBox; listBox = inListBox;
reverseButton = inReverseButton;
reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None);
reverseButton.OnClicked = OnReverseClicked;
var currLines = lines.ToList(); var currLines = lines.ToList();
foreach (LogMessage line in currLines) foreach (LogMessage line in currLines)
{ {
@@ -158,8 +184,34 @@ namespace Barotrauma.Networking
{ {
float prevSize = listBox.BarSize; float prevSize = listBox.BarSize;
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), GUIFrame textContainer = null;
line.Text, wrap: true, font: GUI.SmallFont)
Anchor anchor = Anchor.TopLeft;
Pivot pivot = Pivot.TopLeft;
if (line.RichData != null)
{
foreach (var data in line.RichData)
{
UInt64 id = 0;
if (!UInt64.TryParse(data.Metadata, out id)) { return; }
Client client = GameMain.Client.ConnectedClients.Find(c => c.SteamID == id);
client ??= GameMain.Client.ConnectedClients.Find(c => c.ID == id);
if (client != null && client.Karma < 40.0f)
{
textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
style: null, color: new Color(0xff111155))
{
CanBeFocused = false
};
anchor = Anchor.CenterLeft;
pivot = Pivot.CenterLeft;
break;
}
}
}
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), (textContainer ?? listBox.Content).RectTransform, anchor, pivot),
line.RichData, line.SanitizedText, wrap: true, font: GUI.SmallFont)
{ {
TextColor = messageColor[line.Type], TextColor = messageColor[line.Type],
Visible = !msgTypeHidden[(int)line.Type], Visible = !msgTypeHidden[(int)line.Type],
@@ -167,6 +219,38 @@ namespace Barotrauma.Networking
UserData = line UserData = line
}; };
if (textContainer != null)
{
textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
textBlock.SetTextPos();
textBlock.RectTransform.Resize(textContainer.RectTransform.NonScaledSize);
}
if (reverseOrder)
{
textBlock.RectTransform.SetAsFirstChild();
}
if (line.RichData != null)
{
foreach (var data in line.RichData)
{
textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea()
{
Data = data,
OnClick = (component, area) =>
{
UInt64 id = 0;
if (!UInt64.TryParse(area.Data.Metadata, out id)) { return; }
Client client = GameMain.Client.ConnectedClients.Find(c => c.SteamID == id);
client ??= GameMain.Client.ConnectedClients.Find(c => c.ID == id);
if (client == null) { return; }
GameMain.NetLobbyScreen.SelectPlayer(client);
}
});
}
}
if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f; if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f;
} }
@@ -195,6 +279,16 @@ namespace Barotrauma.Networking
return true; return true;
} }
private void SetMessageReversal(bool reverse)
{
if (reverseOrder == reverse) { return; }
reverseOrder = reverse;
reverseButton.Children.ForEach(c => c.SpriteEffects = reverseOrder ? SpriteEffects.FlipVertically : SpriteEffects.None);
listBox.Content.RectTransform.ReverseChildren();
}
public bool ClearFilter(GUIComponent button, object obj) public bool ClearFilter(GUIComponent button, object obj)
{ {
var searchBox = button.UserData as GUITextBox; var searchBox = button.UserData as GUITextBox;
@@ -571,6 +571,9 @@ namespace Barotrauma.Networking
var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton"));
GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox); GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox);
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
GetPropertyData("DisableBotConversations").AssignGUIComponent(disableBotConversationsBox);
/*var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio")); /*var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio"));
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel);
@@ -10,11 +10,14 @@ using RestSharp.Contrib;
using System.Xml.Linq; using System.Xml.Linq;
using System.Xml; using System.Xml;
using Color = Microsoft.Xna.Framework.Color; using Color = Microsoft.Xna.Framework.Color;
using System.Runtime.InteropServices;
namespace Barotrauma.Steam namespace Barotrauma.Steam
{ {
static partial class SteamManager static partial class SteamManager
{ {
private static Dictionary<Steamworks.Data.PublishedFileId, Task> modCopiesInProgress = new Dictionary<Steamworks.Data.PublishedFileId, Task>();
private static void InitializeProjectSpecific() private static void InitializeProjectSpecific()
{ {
if (isInitialized) { return; } if (isInitialized) { return; }
@@ -35,6 +38,11 @@ namespace Barotrauma.Steam
popularTags.Insert(i, commonness.Key); popularTags.Insert(i, commonness.Key);
i++; i++;
} }
LogSteamworksNetworkingDelegate = LogSteamworksNetworking;
IntPtr logSteamworksNetworkingPtr = Marshal.GetFunctionPointerForDelegate(LogSteamworksNetworkingDelegate);
Steamworks.SteamNetworkingUtils.SetDebugOutputFunction(Steamworks.Data.DebugOutputType.Everything, logSteamworksNetworkingPtr);
} }
} }
catch (DllNotFoundException) catch (DllNotFoundException)
@@ -61,22 +69,17 @@ namespace Barotrauma.Steam
} }
} }
private static void UpdateProjectSpecific(float deltaTime) public static bool NetworkingDebugLog = false;
private static Steamworks.Data.FSteamNetworkingSocketsDebugOutput LogSteamworksNetworkingDelegate;
private static void LogSteamworksNetworking(Steamworks.Data.DebugOutputType nType, string pszMsg)
{ {
for (int i=0;i<(ugcResultPageTasks?.Count ?? 0);i++) if (NetworkingDebugLog) { DebugConsole.NewMessage($"({nType}) {pszMsg}", Color.Orange); }
{
var task = ugcResultPageTasks[i];
if (task.IsCompleted)
{
if (!task.IsCompletedSuccessfully)
{
DebugConsole.ThrowError("Failed to retrieve Steam Workshop page info: TaskStatus = "+task.Status.ToString());
}
ugcResultPageTasks.RemoveAt(i);
i--;
}
} }
private static void UpdateProjectSpecific(float deltaTime)
{
if (ugcSubscriptionTasks != null) if (ugcSubscriptionTasks != null)
{ {
var ugcSubscriptionKeys = ugcSubscriptionTasks.Keys.ToList(); var ugcSubscriptionKeys = ugcSubscriptionTasks.Keys.ToList();
@@ -525,7 +528,37 @@ namespace Barotrauma.Steam
} }
} }
private static List<Task> ugcResultPageTasks; private static async Task<List<Steamworks.Ugc.Item>> GetWorkshopItemsAsync(Steamworks.Ugc.Query query, int clampResults = 0, Predicate<Steamworks.Ugc.Item> itemPredicate=null)
{
await Task.Yield();
int pageIndex = 1;
Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
List<Steamworks.Ugc.Item> retVal = new List<Steamworks.Ugc.Item>();
while (resultPage.HasValue && resultPage?.ResultCount > 0)
{
if (itemPredicate != null)
{
retVal.AddRange(resultPage.Value.Entries.Where(it => itemPredicate(it)));
}
else
{
retVal.AddRange(resultPage.Value.Entries);
}
if (clampResults > 0 && retVal.Count >= clampResults)
{
retVal = retVal.Take(clampResults).ToList();
break;
}
pageIndex++;
resultPage = await query.GetPageAsync(pageIndex);
}
return retVal;
}
public static void GetSubscribedWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, List<string> requireTags = null) public static void GetSubscribedWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, List<string> requireTags = null)
{ {
@@ -535,30 +568,9 @@ namespace Barotrauma.Steam
.RankedByTotalUniqueSubscriptions() .RankedByTotalUniqueSubscriptions()
.WhereUserSubscribed() .WhereUserSubscribed()
.WithLongDescription(); .WithLongDescription();
if (requireTags != null) query.WithTags(requireTags); if (requireTags != null) { query = query.WithTags(requireTags); }
ugcResultPageTasks ??= new List<Task>(); TaskPool.Add(GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(task.Result); });
ugcResultPageTasks.Add(Task.Run(async () =>
{
int processedResults = 0; int pageIndex = 1;
Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
while (resultPage.HasValue && resultPage?.ResultCount > 0)
{
onItemsFound?.Invoke(resultPage.Value.Entries.ToList());
processedResults += resultPage.Value.ResultCount;
pageIndex++;
if (processedResults < resultPage?.TotalCount)
{
resultPage = await query.GetPageAsync(pageIndex);
}
else
{
resultPage = null;
}
}
}));
} }
public static void GetPopularWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, int amount, List<string> requireTags = null) public static void GetPopularWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, int amount, List<string> requireTags = null)
@@ -570,15 +582,9 @@ namespace Barotrauma.Steam
.WithLongDescription(); .WithLongDescription();
if (requireTags != null) query.WithTags(requireTags); if (requireTags != null) query.WithTags(requireTags);
ugcResultPageTasks ??= new List<Task>(); TaskPool.Add(GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) => {
ugcResultPageTasks.Add(Task.Run(async () => var entries = task.Result;
{
int processedResults = 0; int pageIndex = 1;
Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
while (resultPage.HasValue && resultPage?.ResultCount > 0)
{
var entries = resultPage.Value.Entries.ToList();
//count the number of each unique tag //count the number of each unique tag
foreach (var item in entries) foreach (var item in entries)
{ {
@@ -608,27 +614,8 @@ namespace Barotrauma.Steam
} }
popularTags.Insert(i, tagCommonnessKVP.Key); popularTags.Insert(i, tagCommonnessKVP.Key);
} }
onItemsFound?.Invoke(task.Result);
var nonSubscribedItems = entries.Where(it => !it.IsSubscribed); });
if (nonSubscribedItems.Count() > (amount-processedResults))
{
nonSubscribedItems = nonSubscribedItems.Take(amount - processedResults);
}
onItemsFound?.Invoke(nonSubscribedItems.ToList());
processedResults += resultPage.Value.ResultCount;
pageIndex++;
if (processedResults < resultPage?.TotalCount && processedResults < amount)
{
resultPage = await query.GetPageAsync(pageIndex);
}
else
{
resultPage = null;
}
}
}));
} }
public static void GetPublishedWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, List<string> requireTags = null) public static void GetPublishedWorkshopItems(Action<IList<Steamworks.Ugc.Item>> onItemsFound, List<string> requireTags = null)
@@ -641,28 +628,7 @@ namespace Barotrauma.Steam
.WithLongDescription(); .WithLongDescription();
if (requireTags != null) query.WithTags(requireTags); if (requireTags != null) query.WithTags(requireTags);
ugcResultPageTasks ??= new List<Task>(); TaskPool.Add(GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(task.Result); });
ugcResultPageTasks.Add(Task.Run(async () =>
{
int processedResults = 0; int pageIndex = 1;
Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
while (resultPage.HasValue && resultPage?.ResultCount > 0)
{
onItemsFound?.Invoke(resultPage.Value.Entries.ToList());
processedResults += resultPage.Value.ResultCount;
pageIndex++;
if (processedResults < resultPage?.TotalCount)
{
resultPage = await query.GetPageAsync(pageIndex);
}
else
{
resultPage = null;
}
}
}));
} }
private static Dictionary<ulong, Task> ugcSubscriptionTasks; private static Dictionary<ulong, Task> ugcSubscriptionTasks;
@@ -926,7 +892,7 @@ namespace Barotrauma.Steam
/// <summary> /// <summary>
/// Enables a workshop item by moving it to the game folder. /// Enables a workshop item by moving it to the game folder.
/// </summary> /// </summary>
public static bool EnableWorkShopItem(Steamworks.Ugc.Item? item, bool allowFileOverwrite, out string errorMsg) public static bool EnableWorkShopItem(Steamworks.Ugc.Item? item, bool allowFileOverwrite, out string errorMsg, bool selectContentPackage = false, bool suppressInstallNotif = false)
{ {
if (!(item?.IsInstalled ?? false)) if (!(item?.IsInstalled ?? false))
{ {
@@ -951,12 +917,20 @@ namespace Barotrauma.Steam
string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage); string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage);
if (ContentPackage.List.Any(cp => cp.Path.CleanUpPath() == newContentPackagePath.CleanUpPath())) if (ContentPackage.List.Any(cp => cp.Path.CleanUpPath() == newContentPackagePath.CleanUpPath()))
{
if (item?.Owner.Id != Steamworks.SteamClient.SteamId)
{ {
errorMsg = TextManager.GetWithVariables("WorkshopErrorSamePathInstalled", errorMsg = TextManager.GetWithVariables("WorkshopErrorSamePathInstalled",
new string[] { "[itemname]", "[itempath]" }, new string[] { "[itemname]", "[itempath]" },
new string[] { item?.Title, Path.GetDirectoryName(newContentPackagePath) }); new string[] { item?.Title, Path.GetDirectoryName(newContentPackagePath) });
return false; return false;
} }
else
{
RemoveMods(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.SteamWorkshopUrl == contentPackage.SteamWorkshopUrl,
false);
}
}
if (!contentPackage.IsCompatible()) if (!contentPackage.IsCompatible())
{ {
@@ -972,11 +946,44 @@ namespace Barotrauma.Steam
return false; return false;
} }
Task<string> newTask = null;
lock (modCopiesInProgress)
{
if (modCopiesInProgress.ContainsKey(item.Value.Id))
{
if (!modCopiesInProgress[item.Value.Id].IsCompleted &&
!modCopiesInProgress[item.Value.Id].IsFaulted &&
!modCopiesInProgress[item.Value.Id].IsCanceled)
{
errorMsg = ""; return true;
}
modCopiesInProgress.Remove(item.Value.Id);
}
newTask = CopyWorkShopItemAsync(item, contentPackage, newContentPackagePath, metaDataFilePath, allowFileOverwrite);
modCopiesInProgress.Add(item.Value.Id, newTask);
}
TaskPool.Add(newTask,
contentPackage,
(task, cp) =>
{
if (task.IsFaulted || task.IsCanceled)
{
DebugConsole.ThrowError($"Failed to copy \"{item?.Title}\"", task.Exception);
GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red);
return;
}
if (!string.IsNullOrWhiteSpace(task.Result))
{
DebugConsole.ThrowError($"Failed to copy \"{item?.Title}\": {task.Result}");
GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Red);
return;
}
GameMain.Config.SuppressModFolderWatcher = true; GameMain.Config.SuppressModFolderWatcher = true;
CopyWorkShopItem(item, contentPackage, newContentPackagePath, metaDataFilePath, allowFileOverwrite, out errorMsg); var newPackage = new ContentPackage(cp.Path, newContentPackagePath)
var newPackage = new ContentPackage(contentPackage.Path, newContentPackagePath)
{ {
SteamWorkshopUrl = item?.Url, SteamWorkshopUrl = item?.Url,
InstallTime = item?.Updated > item?.Created ? item?.Updated : item?.Created InstallTime = item?.Updated > item?.Created ? item?.Updated : item?.Created
@@ -984,7 +991,7 @@ namespace Barotrauma.Steam
foreach (ContentFile contentFile in newPackage.Files) foreach (ContentFile contentFile in newPackage.Files)
{ {
contentFile.Path = CorrectContentFilePath(contentFile.Path, contentPackage, true); contentFile.Path = CorrectContentFilePath(contentFile.Path, cp, true);
} }
if (!Directory.Exists(Path.GetDirectoryName(newContentPackagePath))) if (!Directory.Exists(Path.GetDirectoryName(newContentPackagePath)))
@@ -993,6 +1000,9 @@ namespace Barotrauma.Steam
} }
newPackage.Save(newContentPackagePath); newPackage.Save(newContentPackagePath);
ContentPackage.List.Add(newPackage); ContentPackage.List.Add(newPackage);
if (selectContentPackage)
{
if (newPackage.CorePackage) if (newPackage.CorePackage)
{ {
GameMain.Config.SelectCorePackage(newPackage); GameMain.Config.SelectCorePackage(newPackage);
@@ -1005,24 +1015,47 @@ namespace Barotrauma.Steam
GameMain.Config.WarnIfContentPackageSelectionDirty(); GameMain.Config.WarnIfContentPackageSelectionDirty();
GameMain.Config.SuppressModFolderWatcher = false;
if (newPackage.Files.Any(f => f.Type == ContentType.Submarine)) if (newPackage.Files.Any(f => f.Type == ContentType.Submarine))
{ {
Submarine.RefreshSavedSubs(); SubmarineInfo.RefreshSavedSubs();
} }
}
else if (!suppressInstallNotif)
{
GameMain.MainMenuScreen?.SetEnableModsNotification(true);
}
GameMain.Config.SuppressModFolderWatcher = false;
GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, true, GUI.Style.Green);
});
errorMsg = ""; errorMsg = "";
return true; return true;
} }
private static bool CopyWorkShopItem(Steamworks.Ugc.Item? item, ContentPackage contentPackage, string newContentPackagePath, string metaDataFilePath, bool allowFileOverwrite, out string errorMsg) /// <summary>
/// Asynchronously copies a Workshop item into the Mods folder.
/// </summary>
/// <returns>Returns an empty string on success, otherwise returns an error message.</returns>
private async static Task<string> CopyWorkShopItemAsync(Steamworks.Ugc.Item? item, ContentPackage contentPackage, string newContentPackagePath, string metaDataFilePath, bool allowFileOverwrite)
{ {
errorMsg = ""; await Task.Yield();
string targetPath = Path.GetDirectoryName(GetWorkshopItemContentPackagePath(contentPackage));
string copyingPath = Path.Combine(targetPath, CopyIndicatorFileName);
string errorMsg = "";
if (contentPackage.GameVersion > new Version(0, 9, 1, 0)) if (contentPackage.GameVersion > new Version(0, 9, 1, 0))
{ {
SaveUtil.CopyFolder(item?.Directory, Path.GetDirectoryName(GetWorkshopItemContentPackagePath(contentPackage)), copySubDirs: true, overwriteExisting: true); Directory.CreateDirectory(targetPath);
return true; File.WriteAllText(copyingPath, "TEMPORARY FILE");
SaveUtil.CopyFolder(item?.Directory, targetPath, copySubDirs: true, overwriteExisting: true);
File.Delete(copyingPath);
return "";
} }
var allPackageFiles = Directory.GetFiles(item?.Directory, "*", SearchOption.AllDirectories); var allPackageFiles = Directory.GetFiles(item?.Directory, "*", SearchOption.AllDirectories);
@@ -1042,7 +1075,7 @@ namespace Barotrauma.Steam
{ {
errorMsg = TextManager.GetWithVariables("WorkshopErrorOverwriteOnEnable", new string[2] { "[itemname]", "[filename]" }, new string[2] { item?.Title, newContentPackagePath }); errorMsg = TextManager.GetWithVariables("WorkshopErrorOverwriteOnEnable", new string[2] { "[itemname]", "[filename]" }, new string[2] { item?.Title, newContentPackagePath });
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
return false; return errorMsg;
} }
foreach (ContentFile contentFile in contentPackage.Files) foreach (ContentFile contentFile in contentPackage.Files)
@@ -1053,13 +1086,14 @@ namespace Barotrauma.Steam
{ {
errorMsg = TextManager.GetWithVariables("WorkshopErrorOverwriteOnEnable", new string[2] { "[itemname]", "[filename]" }, new string[2] { item?.Title, contentFile.Path }); errorMsg = TextManager.GetWithVariables("WorkshopErrorOverwriteOnEnable", new string[2] { "[itemname]", "[filename]" }, new string[2] { item?.Title, contentFile.Path });
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
return false; return errorMsg;
} }
} }
} }
try Directory.CreateDirectory(targetPath);
{ File.WriteAllText(copyingPath, "TEMPORARY FILE");
foreach (ContentFile contentFile in contentPackage.Files) foreach (ContentFile contentFile in contentPackage.Files)
{ {
contentFile.Path = contentFile.Path.CleanUpPath(); contentFile.Path = contentFile.Path.CleanUpPath();
@@ -1073,8 +1107,8 @@ namespace Barotrauma.Steam
} }
} }
contentFile.Path = CorrectContentFilePath(contentFile.Path, contentPackage, false); contentFile.Path = CorrectContentFilePath(contentFile.Path, contentPackage,
contentFile.Type != ContentType.Submarine);
//path not allowed -> the content file must be a reference to an external file (such as some vanilla file outside the Mods folder) //path not allowed -> the content file must be a reference to an external file (such as some vanilla file outside the Mods folder)
if (!ContentPackage.IsModFilePathAllowed(contentFile)) if (!ContentPackage.IsModFilePathAllowed(contentFile))
@@ -1083,14 +1117,14 @@ namespace Barotrauma.Steam
if (File.Exists(sourceFile)) if (File.Exists(sourceFile))
{ {
errorMsg = TextManager.GetWithVariable("WorkshopErrorIllegalPathOnEnable", "[filename]", contentFile.Path); errorMsg = TextManager.GetWithVariable("WorkshopErrorIllegalPathOnEnable", "[filename]", contentFile.Path);
return false; return errorMsg;
} }
//not trying to copy anything, so this is a reference to an external file //not trying to copy anything, so this is a reference to an external file
//if the external file doesn't exist, we cannot enable the package //if the external file doesn't exist, we cannot enable the package
else if (!File.Exists(contentFile.Path)) else if (!File.Exists(contentFile.Path))
{ {
errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\""); errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\"");
return false; return errorMsg;
} }
continue; continue;
} }
@@ -1105,7 +1139,7 @@ namespace Barotrauma.Steam
{ {
//file not present in either the mod or the game folder -> cannot enable the package //file not present in either the mod or the game folder -> cannot enable the package
errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\""); errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " " + TextManager.GetWithVariable("WorkshopFileNotFound", "[path]", "\"" + contentFile.Path + "\"");
return false; return errorMsg;
} }
} }
@@ -1122,15 +1156,9 @@ namespace Barotrauma.Steam
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)); Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
CorrectContentFileCopy(contentPackage, sourceFile, destinationPath, overwrite: true); CorrectContentFileCopy(contentPackage, sourceFile, destinationPath, overwrite: true);
} }
}
catch (Exception e)
{
errorMsg = TextManager.GetWithVariable("WorkshopErrorEnableFailed", "[itemname]", item?.Title) + " {" + e.Message + "}";
DebugConsole.NewMessage(errorMsg, Color.Red);
return false;
}
return true; File.Delete(copyingPath);
return "";
} }
private static bool CheckFileEquality(string filePath1, string filePath2) private static bool CheckFileEquality(string filePath1, string filePath2)
@@ -1149,6 +1177,47 @@ namespace Barotrauma.Steam
} }
} }
private static void RemoveMods(Func<ContentPackage, bool> predicate, bool delete = true)
{
var toRemove = ContentPackage.List.Where(predicate).ToList();
var packagesToDeselect = GameMain.Config.SelectedContentPackages.Where(p => toRemove.Contains(p)).ToList();
foreach (var cp in packagesToDeselect)
{
if (cp.CorePackage)
{
GameMain.Config.SelectCorePackage(ContentPackage.List.Find(cpp => cpp.CorePackage && !toRemove.Contains(cpp)));
}
else
{
GameMain.Config.DeselectContentPackage(cp);
}
}
if (delete)
{
foreach (var cp in toRemove)
{
try
{
string path = Path.GetDirectoryName(cp.Path);
if (Directory.Exists(path)) { Directory.Delete(path, true); }
}
catch (Exception e)
{
DebugConsole.ThrowError($"An error occurred while attempting to delete {Path.GetDirectoryName(cp.Path)}", e);
}
}
}
ContentPackage.List.RemoveAll(cp => toRemove.Contains(cp));
GameMain.Config.SelectedContentPackages.RemoveAll(cp => !ContentPackage.List.Contains(cp));
ContentPackage.SortContentPackages();
GameMain.Config.SaveNewPlayerConfig();
GameMain.Config.WarnIfContentPackageSelectionDirty();
}
/// <summary> /// <summary>
/// Disables a workshop item by removing the files from the game folder. /// Disables a workshop item by removing the files from the game folder.
/// </summary> /// </summary>
@@ -1173,44 +1242,11 @@ namespace Barotrauma.Steam
GameMain.Config.SuppressModFolderWatcher = true; GameMain.Config.SuppressModFolderWatcher = true;
try try
{ {
RemoveMods(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.SteamWorkshopUrl == contentPackage.SteamWorkshopUrl);
var toRemove = ContentPackage.List.Where(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.SteamWorkshopUrl == contentPackage.SteamWorkshopUrl).ToList();
var packagesToDeselect = GameMain.Config.SelectedContentPackages.Where(p => toRemove.Contains(p)).ToList();
foreach (var cp in packagesToDeselect)
{
if (cp.CorePackage)
{
GameMain.Config.SelectCorePackage(ContentPackage.List.Find(cpp => cpp.CorePackage && !toRemove.Contains(cpp)));
}
else
{
GameMain.Config.DeselectContentPackage(cp);
}
}
foreach (var cp in toRemove)
{
try
{
Directory.Delete(Path.GetDirectoryName(cp.Path), true);
} }
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError($"An error occurred while attempting to delete {Path.GetDirectoryName(cp.Path)}", e); errorMsg = "Disabling the workshop item \"" + item?.Title + "\" failed. " + e.Message + "\n" + e.StackTrace;
}
}
ContentPackage.List.RemoveAll(cp => toRemove.Contains(cp));
GameMain.Config.SelectedContentPackages.RemoveAll(cp => !ContentPackage.List.Contains(cp));
ContentPackage.SortContentPackages();
GameMain.Config.SaveNewPlayerConfig();
GameMain.Config.WarnIfContentPackageSelectionDirty();
}
catch (Exception e)
{
errorMsg = "Disabling the workshop item \"" + item?.Title + "\" failed. " + e.Message;
if (!noLog) if (!noLog)
{ {
DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red); DebugConsole.NewMessage(errorMsg, Microsoft.Xna.Framework.Color.Red);
@@ -1219,6 +1255,8 @@ namespace Barotrauma.Steam
} }
GameMain.Config.SuppressModFolderWatcher = false; GameMain.Config.SuppressModFolderWatcher = false;
GameMain.SteamWorkshopScreen?.SetReinstallButtonStatus(item, false, null);
errorMsg = ""; errorMsg = "";
return true; return true;
} }
@@ -1314,7 +1352,7 @@ namespace Barotrauma.Steam
return upToDate; return upToDate;
} }
public static async Task<bool> AutoUpdateWorkshopItems() public static async Task<bool> AutoUpdateWorkshopItemsAsync()
{ {
if (!isInitialized) { return false; } if (!isInitialized) { return false; }
@@ -1322,60 +1360,104 @@ namespace Barotrauma.Steam
.WhereUserSubscribed() .WhereUserSubscribed()
.WithLongDescription(); .WithLongDescription();
DateTime startTime = DateTime.Now; List<Steamworks.Ugc.Item> items = await GetWorkshopItemsAsync(query);
DateTime endTime = startTime + new TimeSpan(0, 0, 30);
int processedResults = 0; int pageIndex = 1; GameMain.Config.SuppressModFolderWatcher = true;
Steamworks.Ugc.ResultPage? resultPage = await query.GetPageAsync(pageIndex);
while (resultPage.HasValue && resultPage?.ResultCount > 0) //remove mods that the player is no longer subscribed to
{ RemoveMods(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && !items.Any(it => it.Id == GetWorkshopItemIDFromUrl(cp.SteamWorkshopUrl)));
if (DateTime.Now > endTime)
{ GameMain.Config.SuppressModFolderWatcher = false;
return false;
}
foreach (var item in resultPage.Value.Entries) List<string> updateNotifications = new List<string>();
foreach (var item in items)
{ {
try try
{ {
if (!item.IsInstalled || !CheckWorkshopItemEnabled(item) || CheckWorkshopItemUpToDate(item)) { continue; } if (!item.IsInstalled) { continue; }
if (!UpdateWorkshopItem(item, out string errorMsg))
bool installedSuccessfully = false;
string errorMsg;
if (!CheckWorkshopItemEnabled(item))
{ {
DebugConsole.ThrowError(errorMsg); installedSuccessfully = EnableWorkShopItem(item, true, out errorMsg);
new GUIMessageBox( }
TextManager.Get("Error"), else if (!CheckWorkshopItemUpToDate(item))
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg })); {
installedSuccessfully = UpdateWorkshopItem(item, out errorMsg);
} }
else else
{ {
//TODO: potential race condition continue;
new GUIMessageBox("", TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title)); }
if (!installedSuccessfully)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
DebugConsole.NewMessage(errorMsg, Color.Red);
string errorId = errorMsg;
if (!GUIMessageBox.MessageBoxes.Any(m => m.UserData as string == errorId))
{
new GUIMessageBox(
TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, errorMsg }))
{
UserData = errorId
};
}
});
}
else
{
updateNotifications.Add(TextManager.GetWithVariable("WorkshopItemUpdated", "[itemname]", item.Title));
} }
} }
catch (Exception e) catch (Exception e)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
string errorId = e.Message;
if (!GUIMessageBox.MessageBoxes.Any(m => m.UserData as string == errorId))
{ {
new GUIMessageBox( new GUIMessageBox(
TextManager.Get("Error"), TextManager.Get("Error"),
TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite })); TextManager.GetWithVariables("WorkshopItemUpdateFailed", new string[2] { "[itemname]", "[errormessage]" }, new string[2] { item.Title, e.Message + ", " + e.TargetSite }))
{
UserData = errorId
};
}
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"SteamManager.AutoUpdateWorkshopItems:" + e.Message, "SteamManager.AutoUpdateWorkshopItems:" + e.Message,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
"Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace); "Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace);
});
} }
} }
processedResults += resultPage.Value.ResultCount; if (updateNotifications.Count > 0)
pageIndex++;
if (processedResults < resultPage?.TotalCount)
{ {
resultPage = await query.GetPageAsync(pageIndex); CrossThread.RequestExecutionOnMainThread(() =>
}
else
{ {
resultPage = null; while (updateNotifications.Count > 0)
{
int notificationsPerMsgBox = 20;
new GUIMessageBox("", string.Join('\n', updateNotifications.Take(notificationsPerMsgBox)),
relativeSize: new Microsoft.Xna.Framework.Vector2(0.5f, 0.0f),
minSize: new Microsoft.Xna.Framework.Point(600, 0));
updateNotifications.RemoveRange(0, Math.Min(notificationsPerMsgBox, updateNotifications.Count));
} }
});
} }
List<Task> tasks;
lock (modCopiesInProgress)
{
tasks = modCopiesInProgress.Values.ToList();
}
await Task.WhenAll(tasks);
return true; return true;
} }
@@ -1383,7 +1465,10 @@ namespace Barotrauma.Steam
{ {
errorMsg = ""; errorMsg = "";
if (!(item?.IsInstalled ?? false)) { return false; } if (!(item?.IsInstalled ?? false)) { return false; }
if (item?.Owner.Id != Steamworks.SteamClient.SteamId)
{
if (!DisableWorkShopItem(item, false, out errorMsg)) { return false; } if (!DisableWorkShopItem(item, false, out errorMsg)) { return false; }
}
if (!EnableWorkShopItem(item, allowFileOverwrite: false, errorMsg: out errorMsg)) { return false; } if (!EnableWorkShopItem(item, allowFileOverwrite: false, errorMsg: out errorMsg)) { return false; }
return true; return true;
@@ -1391,8 +1476,9 @@ namespace Barotrauma.Steam
private static string GetWorkshopItemContentPackagePath(ContentPackage contentPackage) private static string GetWorkshopItemContentPackagePath(ContentPackage contentPackage)
{ {
string packageName = contentPackage.Name; string packageName = contentPackage.Name.Trim();
packageName = ToolBox.RemoveInvalidFileNameChars(packageName); packageName = ToolBox.RemoveInvalidFileNameChars(packageName);
while (packageName.Last() == '.') { packageName = packageName.Substring(0, packageName.Length-1); }
//packageName = packageName + "_" + GetWorkshopItemIDFromUrl(contentPackage.SteamWorkshopUrl); //packageName = packageName + "_" + GetWorkshopItemIDFromUrl(contentPackage.SteamWorkshopUrl);
return Path.Combine("Mods", packageName, MetadataFileName); return Path.Combine("Mods", packageName, MetadataFileName);
@@ -1421,7 +1507,7 @@ namespace Barotrauma.Steam
private static void CorrectContentFileCopy(ContentPackage package, string src, string dest, bool overwrite) private static void CorrectContentFileCopy(ContentPackage package, string src, string dest, bool overwrite)
{ {
if (Path.GetExtension(src).ToLowerInvariant() == ".xml") if (Path.GetExtension(src).Equals(".xml", StringComparison.OrdinalIgnoreCase))
{ {
XDocument doc = XMLExtensions.TryLoadXml(src); XDocument doc = XMLExtensions.TryLoadXml(src);
if (doc != null) if (doc != null)
@@ -1471,7 +1557,7 @@ namespace Barotrauma.Steam
{ {
if (checkIfFileExists) if (checkIfFileExists)
{ {
ContentPackage otherContentPackage = ContentPackage.List.Find(cp => cp.Name.ToLowerInvariant() == splitPath[1].ToLowerInvariant()); ContentPackage otherContentPackage = ContentPackage.List.Find(cp => cp.Name.Equals(splitPath[1], StringComparison.OrdinalIgnoreCase));
if (otherContentPackage != null) if (otherContentPackage != null)
{ {
string otherPackageName = Path.GetDirectoryName(otherContentPackage.Path); string otherPackageName = Path.GetDirectoryName(otherContentPackage.Path);
@@ -1483,7 +1569,8 @@ namespace Barotrauma.Steam
} }
} }
} }
newPath = Path.Combine(packageName, string.Join("/", splitPath.Skip(2))); splitPath = splitPath.Skip(Math.Clamp(splitPath.Length-1, 0, 2)).ToArray();
newPath = Path.Combine(packageName, string.Join("/", splitPath));
} }
else else
{ {
@@ -149,9 +149,15 @@ namespace Barotrauma.Networking
} }
} }
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
bool prevCaptured = true;
int captureTimer;
void UpdateCapture() void UpdateCapture()
{ {
short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE]; Array.Copy(uncompressedBuffer, 0, prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE);
Array.Clear(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE);
while (capturing) while (capturing)
{ {
int alcError; int alcError;
@@ -202,6 +208,21 @@ namespace Barotrauma.Networking
bool allowEnqueue = false; bool allowEnqueue = false;
if (GameMain.WindowActive) if (GameMain.WindowActive)
{ {
ForceLocal = captureTimer > 0 ? ForceLocal : false;
bool pttDown = false;
if ((PlayerInput.KeyDown(InputType.Voice) || PlayerInput.KeyDown(InputType.LocalVoice)) &&
GUI.KeyboardDispatcher.Subscriber == null)
{
pttDown = true;
if (PlayerInput.KeyDown(InputType.LocalVoice))
{
ForceLocal = true;
}
else
{
ForceLocal = false;
}
}
if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Activity) if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.Activity)
{ {
if (dB > GameMain.Config.NoiseGateThreshold) if (dB > GameMain.Config.NoiseGateThreshold)
@@ -211,25 +232,38 @@ namespace Barotrauma.Networking
} }
else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.PushToTalk) else if (GameMain.Config.VoiceSetting == GameSettings.VoiceMode.PushToTalk)
{ {
if (PlayerInput.KeyDown(InputType.Voice) && GUI.KeyboardDispatcher.Subscriber == null) if (pttDown)
{ {
allowEnqueue = true; allowEnqueue = true;
} }
} }
} }
if (allowEnqueue) if (allowEnqueue || captureTimer > 0)
{ {
LastEnqueueAudio = DateTime.Now; LastEnqueueAudio = DateTime.Now;
//encode audio and enqueue it //encode audio and enqueue it
lock (buffers) lock (buffers)
{ {
if (!prevCaptured) //enqueue the previous buffer if not sent to avoid cutoff
{
int compressedCountPrev = VoipConfig.Encoder.Encode(prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE);
EnqueueBuffer(compressedCountPrev);
}
int compressedCount = VoipConfig.Encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE); int compressedCount = VoipConfig.Encoder.Encode(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE, BufferToQueue, 0, VoipConfig.MAX_COMPRESSED_SIZE);
EnqueueBuffer(compressedCount); EnqueueBuffer(compressedCount);
} }
captureTimer -= (VoipConfig.BUFFER_SIZE * 1000) / VoipConfig.FREQUENCY;
if (allowEnqueue)
{
captureTimer = GameMain.Config.VoiceChatCutoffPrevention;
}
prevCaptured = true;
} }
else else
{ {
captureTimer = 0;
prevCaptured = false;
//enqueue silence //enqueue silence
lock (buffers) lock (buffers)
{ {
@@ -85,20 +85,20 @@ namespace Barotrauma.Networking
return; return;
} }
if (queue.Read(msg))
{
Client client = gameClient.ConnectedClients.Find(c => c.VoipQueue == queue); Client client = gameClient.ConnectedClients.Find(c => c.VoipQueue == queue);
if (queue.Read(msg, discardData: client.Muted || client.MutedLocally))
{
if (client.Muted || client.MutedLocally) { return; } if (client.Muted || client.MutedLocally) { return; }
if (client.VoipSound == null) if (client.VoipSound == null)
{ {
DebugConsole.Log("Recreating voipsound " + queueId); DebugConsole.Log("Recreating voipsound " + queueId);
client.VoipSound = new VoipSound(GameMain.SoundManager, client.VoipQueue); client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue);
} }
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f) if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
{ {
var messageType = ChatMessage.CanUseRadio(client.Character, out WifiComponent radio) ? ChatMessageType.Radio : ChatMessageType.Default; WifiComponent radio = null;
var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default;
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]); client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio; client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio;
@@ -61,10 +61,10 @@ namespace Barotrauma
foreach (GUIComponent comp in listBox.Content.Children) foreach (GUIComponent comp in listBox.Content.Children)
{ {
if (comp.FindChild("votes") is GUITextBlock voteText) comp.RemoveChild(voteText); if (comp.FindChild("votes") is GUITextBlock voteText) { comp.RemoveChild(voteText); }
} }
if (clients == null) return; if (clients == null) { return; }
List<Pair<object, int>> voteList = GetVoteList(voteType, clients); List<Pair<object, int>> voteList = GetVoteList(voteType, clients);
foreach (Pair<object, int> votable in voteList) foreach (Pair<object, int> votable in voteList)
@@ -73,7 +73,7 @@ namespace Barotrauma
} }
break; break;
case VoteType.StartRound: case VoteType.StartRound:
if (clients == null) return; if (clients == null) { return; }
foreach (Client client in clients) foreach (Client client in clients)
{ {
var clientReady = GameMain.NetLobbyScreen.PlayerList.Content.FindChild(client)?.FindChild("clientready"); var clientReady = GameMain.NetLobbyScreen.PlayerList.Content.FindChild(client)?.FindChild("clientready");
@@ -113,18 +113,18 @@ namespace Barotrauma
switch (voteType) switch (voteType)
{ {
case VoteType.Sub: case VoteType.Sub:
Submarine sub = data as Submarine; SubmarineInfo sub = data as SubmarineInfo;
if (sub == null) return; if (sub == null) { return; }
msg.Write(sub.Name); msg.Write(sub.Name);
break; break;
case VoteType.Mode: case VoteType.Mode:
GameModePreset gameMode = data as GameModePreset; GameModePreset gameMode = data as GameModePreset;
if (gameMode == null) return; if (gameMode == null) { return; }
msg.Write(gameMode.Identifier); msg.Write(gameMode.Identifier);
break; break;
case VoteType.EndRound: case VoteType.EndRound:
if (!(data is bool)) return; if (!(data is bool)) { return; }
msg.Write((bool)data); msg.Write((bool)data);
break; break;
case VoteType.Kick: case VoteType.Kick:
@@ -153,12 +153,12 @@ namespace Barotrauma
{ {
int votes = inc.ReadByte(); int votes = inc.ReadByte();
string subName = inc.ReadString(); string subName = inc.ReadString();
List<Submarine> serversubs = new List<Submarine>(); List<SubmarineInfo> serversubs = new List<SubmarineInfo>();
foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.Content?.Children) foreach (GUIComponent item in GameMain.NetLobbyScreen?.SubList?.Content?.Children)
{ {
if (item.UserData != null && item.UserData is Submarine) serversubs.Add(item.UserData as Submarine); if (item.UserData != null && item.UserData is SubmarineInfo) { serversubs.Add(item.UserData as SubmarineInfo); }
} }
Submarine sub = serversubs.FirstOrDefault(sm => sm.Name == subName); SubmarineInfo sub = serversubs.FirstOrDefault(s => s.Name == subName);
SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes); SetVoteText(GameMain.NetLobbyScreen.SubList, sub, votes);
} }
} }
@@ -11,7 +11,18 @@ namespace Barotrauma.Particles
public string OriginalName { get { return Name; } } public string OriginalName { get { return Name; } }
public string Identifier { get { return Name.ToLowerInvariant(); } } private string _identifier;
public string Identifier
{
get
{
if (_identifier == null)
{
_identifier = Name.ToLowerInvariant();
}
return _identifier;
}
}
public string FilePath { get; private set; } public string FilePath { get; private set; }
@@ -46,7 +57,7 @@ namespace Barotrauma.Particles
foreach (XElement subElement in element.Elements()) foreach (XElement subElement in element.Elements())
{ {
if (subElement.Name.ToString().ToLowerInvariant() == "sprite") if (subElement.Name.ToString().Equals("sprite", StringComparison.OrdinalIgnoreCase))
{ {
Sprites.Add(new Sprite(subElement)); Sprites.Add(new Sprite(subElement));
} }
@@ -108,7 +108,8 @@ namespace Barotrauma.Particles
public readonly bool CopyEntityAngle; public readonly bool CopyEntityAngle;
public readonly bool DrawOnTop; public bool DrawOnTop => forceDrawOnTop || ParticlePrefab.DrawOnTop;
private readonly bool forceDrawOnTop;
public ParticleEmitterPrefab(XElement element) public ParticleEmitterPrefab(XElement element)
{ {
@@ -150,6 +151,12 @@ namespace Barotrauma.Particles
{ {
DistanceMin = DistanceMax = element.GetAttributeFloat("distance", 0.0f); DistanceMin = DistanceMax = element.GetAttributeFloat("distance", 0.0f);
} }
if (DistanceMax < DistanceMin)
{
var temp = DistanceMin;
DistanceMin = DistanceMax;
DistanceMax = temp;
}
if (element.Attribute("velocity") == null) if (element.Attribute("velocity") == null)
{ {
@@ -160,13 +167,19 @@ namespace Barotrauma.Particles
{ {
VelocityMin = VelocityMax = element.GetAttributeFloat("velocity", 0.0f); VelocityMin = VelocityMax = element.GetAttributeFloat("velocity", 0.0f);
} }
if (VelocityMax < VelocityMin)
{
var temp = VelocityMin;
VelocityMin = VelocityMax;
VelocityMax = temp;
}
EmitInterval = element.GetAttributeFloat("emitinterval", 0.0f); EmitInterval = element.GetAttributeFloat("emitinterval", 0.0f);
ParticlesPerSecond = element.GetAttributeInt("particlespersecond", 0); ParticlesPerSecond = element.GetAttributeInt("particlespersecond", 0);
ParticleAmount = element.GetAttributeInt("particleamount", 0); ParticleAmount = element.GetAttributeInt("particleamount", 0);
HighQualityCollisionDetection = element.GetAttributeBool("highqualitycollisiondetection", false); HighQualityCollisionDetection = element.GetAttributeBool("highqualitycollisiondetection", false);
CopyEntityAngle = element.GetAttributeBool("copyentityangle", false); CopyEntityAngle = element.GetAttributeBool("copyentityangle", false);
DrawOnTop = element.GetAttributeBool("drawontop", false); forceDrawOnTop = element.GetAttributeBool("drawontop", false);
} }
} }
} }
@@ -228,14 +228,7 @@ namespace Barotrauma.Particles
if (inSub.HasValue) if (inSub.HasValue)
{ {
bool isOutside = particle.CurrentHull == null; bool isOutside = particle.CurrentHull == null;
if (particle.DrawOnTop) if (!particle.DrawOnTop && isOutside == inSub.Value)
{
if (isOutside != inSub.Value)
{
continue;
}
}
else if (isOutside == inSub.Value)
{ {
continue; continue;
} }
@@ -199,6 +199,9 @@ namespace Barotrauma.Particles
[Editable, Serialize(DrawTargetType.Air, false, description: "Should the particle be rendered in air, water or both.")] [Editable, Serialize(DrawTargetType.Air, false, description: "Should the particle be rendered in air, water or both.")]
public DrawTargetType DrawTarget { get; private set; } public DrawTargetType DrawTarget { get; private set; }
[Editable, Serialize(false, false, description: "Should the particle be always rendered on top of entities?")]
public bool DrawOnTop { get; private set; }
[Editable, Serialize(ParticleBlendState.AlphaBlend, false, description: "The type of blending to use when rendering the particle.")] [Editable, Serialize(ParticleBlendState.AlphaBlend, false, description: "The type of blending to use when rendering the particle.")]
public ParticleBlendState BlendState { get; private set; } public ParticleBlendState BlendState { get; private set; }

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