v0.10.5.1

This commit is contained in:
Juan Pablo Arce
2020-09-22 11:31:56 -03:00
parent 44032d0ae0
commit 0002ad2c50
343 changed files with 12276 additions and 5023 deletions
+8 -1
View File
@@ -7,11 +7,18 @@ assignees: ''
--- ---
**QA**
- [ ] Play through tutorials and verify that they are working.
- [ ] Do a smoketest on all game modes
- [ ] Smoketest server hosting (dedicated and client)
- [ ] Install Trick or Trauma and check you can start a round with no obvious issues/errors (to make sure we didn't unintentionally break compatibility with older mods).
- [ ] Play through one single player campaign round.
- [ ] Do a smoketest in a language other than English.
**Code:** **Code:**
- [ ] Build and upload dedicated server - [ ] Build and upload dedicated server
- [ ] Verify that Vanilla content package hashes match between Windows/Mac/Linux - [ ] Verify that Vanilla content package hashes match between Windows/Mac/Linux
- [ ] Run "checkmissingloca" command to make sure localization files are up-to-date. - [ ] Run "checkmissingloca" command to make sure localization files are up-to-date.
- [ ] Install Trick or Trauma and check you can start a round with no obvious issues/errors (to make sure we didn't unintentionally break compatibility with older mods).
- [ ] Prepare new main menu content (changelog) - [ ] Prepare new main menu content (changelog)
- [ ] Prepare public github repo for pushing the new changes - [ ] Prepare public github repo for pushing the new changes
@@ -261,7 +261,7 @@ namespace Barotrauma
if (targetPos == Vector2.Zero) if (targetPos == Vector2.Zero)
{ {
Vector2 moveInput = Vector2.Zero; Vector2 moveInput = Vector2.Zero;
if (allowMove) if (allowMove && !Freeze)
{ {
if (GUI.KeyboardDispatcher.Subscriber == null) if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
@@ -1,6 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using FarseerPhysics; using FarseerPhysics;
using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -64,6 +63,27 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60 + offset), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black); GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(0, 60 + offset), $"ACTIVE OBJECTIVE: {activeObjective.DebugTag} ({activeObjective.Priority.FormatZeroDecimal()})", Color.White, Color.Black);
} }
} }
for (int i = 0; i < ObjectiveManager.Objectives.Count; i++)
{
var objective = ObjectiveManager.Objectives[i];
int offsetMultiplier;
if (ObjectiveManager.CurrentOrder == null)
{
if (i == 0)
{
continue;
}
else
{
offsetMultiplier = i - 1;
}
}
else
{
offsetMultiplier = i + 1;
}
GUI.DrawString(spriteBatch, pos + textOffset + new Vector2(120, offsetMultiplier * 18 + 100), $"{objective.DebugTag} ({objective.Priority.FormatZeroDecimal()})", Color.White, Color.Black * 0.5f);
}
} }
if (steeringManager is IndoorsSteeringManager pathSteering) if (steeringManager is IndoorsSteeringManager pathSteering)
@@ -409,11 +409,6 @@ namespace Barotrauma
emitter.Emit(1.0f, limb.WorldPosition, character.CurrentHull, amountMultiplier: gibParticleAmount); emitter.Emit(1.0f, limb.WorldPosition, character.CurrentHull, amountMultiplier: gibParticleAmount);
} }
if (!string.IsNullOrEmpty(character.BloodDecalName))
{
character.CurrentHull?.AddDecal(character.BloodDecalName, limb.WorldPosition, MathHelper.Clamp(limb.Mass, 0.5f, 2.0f));
}
} }
if (playSound) if (playSound)
@@ -97,8 +97,6 @@ namespace Barotrauma
set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); } set { chromaticAberrationStrength = MathHelper.Clamp(value, 0.0f, 100.0f); }
} }
public string BloodDecalName => Params.BloodDecal;
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
public IEnumerable<ParticleEmitter> BloodEmitters public IEnumerable<ParticleEmitter> BloodEmitters
{ {
@@ -617,7 +615,7 @@ namespace Barotrauma
partial void SetOrderProjSpecific(Order order, string orderOption) partial void SetOrderProjSpecific(Order order, string orderOption)
{ {
GameMain.GameSession?.CrewManager?.DisplayCharacterOrder(this, order, orderOption); GameMain.GameSession?.CrewManager?.AddCurrentOrderIcon(this, order, orderOption);
} }
public static void AddAllToGUIUpdateList() public static void AddAllToGUIUpdateList()
@@ -786,7 +784,7 @@ namespace Barotrauma
} }
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract) if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{ {
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + CampaignInteractionType); var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionBubble." + CampaignInteractionType);
if (iconStyle != null) if (iconStyle != null)
{ {
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f; Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f;
@@ -825,15 +823,19 @@ namespace Barotrauma
/// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object) /// Creates a progress bar that's "linked" to the specified object (or updates an existing one if there's one already linked to the object)
/// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time /// The progress bar will automatically fade out after 1 sec if the method hasn't been called during that time
/// </summary> /// </summary>
public HUDProgressBar UpdateHUDProgressBar(object linkedObject, Vector2 worldPosition, float progress, Color emptyColor, Color fullColor) public HUDProgressBar UpdateHUDProgressBar(object linkedObject, Vector2 worldPosition, float progress, Color emptyColor, Color fullColor, string textTag = "")
{ {
if (controlled != this) return null; if (controlled != this) { return null; }
if (!hudProgressBars.TryGetValue(linkedObject, out HUDProgressBar progressBar)) if (!hudProgressBars.TryGetValue(linkedObject, out HUDProgressBar progressBar))
{ {
progressBar = new HUDProgressBar(worldPosition, Submarine, emptyColor, fullColor); progressBar = new HUDProgressBar(worldPosition, Submarine, emptyColor, fullColor, textTag);
hudProgressBars.Add(linkedObject, progressBar); hudProgressBars.Add(linkedObject, progressBar);
} }
else
{
progressBar.TextTag = textTag;
}
progressBar.WorldPosition = worldPosition; progressBar.WorldPosition = worldPosition;
progressBar.FadeTimer = Math.Max(progressBar.FadeTimer, 1.0f); progressBar.FadeTimer = Math.Max(progressBar.FadeTimer, 1.0f);
@@ -859,7 +861,7 @@ namespace Barotrauma
} }
var selectedSound = matchingSounds.GetRandom(); var selectedSound = matchingSounds.GetRandom();
if (selectedSound?.Sound == null) { return; } 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, hullGuess: CurrentHull);
soundTimer = soundInterval; soundTimer = soundInterval;
} }
@@ -52,7 +52,6 @@ namespace Barotrauma
return return
character?.Inventory != null && character?.Inventory != null &&
character.AllowInput && character.AllowInput &&
!character.LockHands &&
(controller?.User != character || !controller.HideHUD) && (controller?.User != character || !controller.HideHUD) &&
!IsCampaignInterfaceOpen && !IsCampaignInterfaceOpen &&
!ConversationAction.FadeScreenToBlack; !ConversationAction.FadeScreenToBlack;
@@ -227,7 +226,7 @@ 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.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen) if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen && (!character.IsKeyDown(InputType.Aim) || character.SelectedItems.Any(it => it?.GetComponent<Sprayer>() == null)))
{ {
if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected) if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
{ {
@@ -307,6 +306,14 @@ namespace Barotrauma
{ {
progressBar.Draw(spriteBatch, cam); progressBar.Draw(spriteBatch, cam);
} }
foreach (Character npc in Character.CharacterList)
{
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; }
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType);
GUI.DrawIndicator(spriteBatch, npc.WorldPosition, cam, 500.0f, iconStyle.GetDefaultSprite(), iconStyle.Color);
}
} }
if (character.SelectedConstruction != null && if (character.SelectedConstruction != null &&
@@ -355,7 +362,7 @@ namespace Barotrauma
} }
if (ShouldDrawInventory(character)) if (ShouldDrawInventory(character))
{ {
character.Inventory.Locked = LockInventory(character); character.Inventory.Locked = character == Character.Controlled && LockInventory(character);
character.Inventory.DrawOwn(spriteBatch); character.Inventory.DrawOwn(spriteBatch);
character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ? character.Inventory.CurrentLayout = CharacterHealth.OpenHealthWindow == null && character.SelectedCharacter == null ?
CharacterInventory.Layout.Default : CharacterInventory.Layout.Default :
@@ -369,14 +376,10 @@ namespace Barotrauma
{ {
if (character.SelectedCharacter.CanInventoryBeAccessed) if (character.SelectedCharacter.CanInventoryBeAccessed)
{ {
///character.Inventory.CurrentLayout = Alignment.Left; character.SelectedCharacter.Inventory.Locked = false;
character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left; character.SelectedCharacter.Inventory.CurrentLayout = CharacterInventory.Layout.Left;
character.SelectedCharacter.Inventory.DrawOwn(spriteBatch); character.SelectedCharacter.Inventory.DrawOwn(spriteBatch);
} }
else
{
//character.Inventory.CurrentLayout = (CharacterHealth.OpenHealthWindow == null) ? Alignment.Center : Alignment.Left;
}
if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth) if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
{ {
character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left; character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
@@ -450,7 +453,6 @@ namespace Barotrauma
private static bool LockInventory(Character character) private static bool LockInventory(Character character)
{ {
if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; } if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; }
return character.ShouldLockHud(); return character.ShouldLockHud();
} }
@@ -14,7 +14,8 @@ namespace Barotrauma
{ {
if (this != Controlled) if (this != Controlled)
{ {
if (GameMain.Client.EndCinematic != null) // Freezes the characters during the ending cinematic if (GameMain.Client.EndCinematic != null &&
GameMain.Client.EndCinematic.Running) // Freezes the characters during the ending cinematic
{ {
AnimController.Frozen = true; AnimController.Frozen = true;
memState.Clear(); memState.Clear();
@@ -38,25 +38,43 @@ namespace Barotrauma
public Vector2 Size; public Vector2 Size;
private Submarine parentSub; private readonly Submarine parentSub;
public string Text
{
get;
private set;
}
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine = null) private string textTag;
: this(worldPosition, parentSubmarine, GUI.Style.Red, GUI.Style.Green) public string TextTag
{
get { return textTag; }
set
{
if (textTag == value) { return; }
textTag = value;
Text = string.IsNullOrEmpty(textTag) ? string.Empty : TextManager.Get(textTag);
}
}
public HUDProgressBar(Vector2 worldPosition, string textTag, Submarine parentSubmarine = null)
: this(worldPosition, parentSubmarine, GUI.Style.Red, GUI.Style.Green, textTag)
{ {
} }
public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine, Color emptyColor, Color fullColor) public HUDProgressBar(Vector2 worldPosition, Submarine parentSubmarine, Color emptyColor, Color fullColor, string textTag)
{ {
this.emptyColor = emptyColor; this.emptyColor = emptyColor;
this.fullColor = fullColor; this.fullColor = fullColor;
parentSub = parentSubmarine; parentSub = parentSubmarine;
WorldPosition = worldPosition; WorldPosition = worldPosition;
Size = new Vector2(100.0f, 20.0f); Size = new Vector2(100.0f, 20.0f);
FadeTimer = 1.0f; FadeTimer = 1.0f;
if (!string.IsNullOrEmpty(textTag))
{
textTag = textTag;
Text = TextManager.Get(textTag);
}
} }
public void Update(float deltatime) public void Update(float deltatime)
@@ -76,12 +94,21 @@ namespace Barotrauma
} }
pos = cam.WorldToScreen(pos); pos = cam.WorldToScreen(pos);
Color color = Color.Lerp(emptyColor, fullColor, progress);
GUI.DrawProgressBar(spriteBatch, GUI.DrawProgressBar(spriteBatch,
new Vector2(pos.X, -pos.Y), new Vector2(pos.X, -pos.Y),
Size, progress, Size, progress,
Color.Lerp(emptyColor, fullColor, progress) * a, color * a,
Color.White * a * 0.8f); Color.White * a * 0.8f);
if (!string.IsNullOrEmpty(Text))
{
Vector2 textSize = GUI.SmallFont.MeasureString(Text);
Vector2 textPos = new Vector2(pos.X + (Size.X - textSize.X) / 2, pos.Y - textSize.Y * 1.2f);
GUI.DrawString(spriteBatch, textPos - Vector2.One, Text, Color.Black * a, font: GUI.SmallFont);
GUI.DrawString(spriteBatch, textPos, Text, Color.White * a, font: GUI.SmallFont);
}
} }
} }
} }
@@ -36,7 +36,7 @@ namespace Barotrauma
} }
} }
private GUIButton suicideButton; public GUIButton SuicideButton { get; private set; }
// healthbars // healthbars
private GUIProgressBar healthBar; private GUIProgressBar healthBar;
@@ -247,22 +247,6 @@ namespace Barotrauma
private GUIFrame healthBarHolder; private GUIFrame healthBarHolder;
private Point healthBarOffset
{
get
{
return new Point(Math.Max(2, GUI.IntScaleCeiling(1.5f)), Math.Min(GUI.IntScaleFloor(18f), 19));
}
}
private Point healthBarSize
{
get
{
return new Point(healthBarHolder.Rect.Width - Math.Min(GUI.IntScale(45f), 47), GUI.IntScale(15f));
}
}
partial void InitProjSpecific(XElement element, Character character) partial void InitProjSpecific(XElement element, Character character)
{ {
DisplayedVitality = MaxVitality; DisplayedVitality = MaxVitality;
@@ -290,29 +274,22 @@ namespace Barotrauma
healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size; healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero; healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;
GUIFrame healthBarBG = new GUIFrame(new RectTransform(Vector2.One, healthBarHolder.RectTransform), style: "CharacterHealthBarBG") healthBarShadow = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight),
{ barSize: 1.0f, color: Color.Green, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical", showFrame: false)
CanBeFocused = false
};
healthBarShadow = new GUIProgressBar(new RectTransform(healthBarSize, healthBarHolder.RectTransform, Anchor.BottomRight),
barSize: 1.0f, color: Color.Green, style: horizontal ? "CharacterHealthBarSlider" : "GUIProgressBarVertical", showFrame: false)
{ {
IsHorizontal = horizontal IsHorizontal = horizontal
}; };
healthBarShadow.Visible = false; healthBarShadow.Visible = false;
healthShadowSize = 1.0f; healthShadowSize = 1.0f;
healthBar = new GUIProgressBar(new RectTransform(healthBarSize, healthBarHolder.RectTransform, Anchor.BottomRight), healthBar = new GUIProgressBar(new RectTransform(Vector2.One, healthBarHolder.RectTransform, Anchor.BottomRight),
barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: horizontal ? "CharacterHealthBarSlider" : "GUIProgressBarVertical", showFrame: false) barSize: 1.0f, color: GUI.Style.HealthBarColorHigh, style: horizontal ? "CharacterHealthBar" : "GUIProgressBarVertical")
{ {
HoverCursor = CursorState.Hand, HoverCursor = CursorState.Hand,
Enabled = true, Enabled = true,
IsHorizontal = horizontal IsHorizontal = horizontal
}; };
healthBar.RectTransform.AbsoluteOffset = healthBarShadow.RectTransform.AbsoluteOffset = healthBarOffset;
healthInterfaceFrame = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.55f), GUI.Canvas, anchor: Anchor.Center, scaleBasis: ScaleBasis.Smallest), style: "ItemUI"); healthInterfaceFrame = new GUIFrame(new RectTransform(new Vector2(0.7f, 0.55f), GUI.Canvas, anchor: Anchor.Center, scaleBasis: ScaleBasis.Smallest), style: "ItemUI");
var healthInterfaceLayout = new GUILayoutGroup(new RectTransform(Vector2.One / 1.05f, healthInterfaceFrame.RectTransform, anchor: Anchor.Center), true); var healthInterfaceLayout = new GUILayoutGroup(new RectTransform(Vector2.One / 1.05f, healthInterfaceFrame.RectTransform, anchor: Anchor.Center), true);
@@ -519,7 +496,7 @@ namespace Barotrauma
UpdateAlignment(); UpdateAlignment();
suicideButton = new GUIButton(new RectTransform(new Vector2(0.1f, 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)
}, },
@@ -546,7 +523,7 @@ namespace Barotrauma
return true; return true;
} }
}; };
suicideButton.TextBlock.AutoScaleHorizontal = true; SuicideButton.TextBlock.AutoScaleHorizontal = true;
if (element != null) if (element != null)
{ {
@@ -591,9 +568,6 @@ namespace Barotrauma
healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size; healthBarHolder.RectTransform.NonScaledSize = HUDLayoutSettings.HealthBarArea.Size;
healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero; healthBarHolder.RectTransform.RelativeOffset = Vector2.Zero;
healthBar.RectTransform.NonScaledSize = healthBarShadow.RectTransform.NonScaledSize = healthBarSize;
healthBar.RectTransform.AbsoluteOffset = healthBarShadow.RectTransform.AbsoluteOffset = healthBarOffset;
switch (alignment) switch (alignment)
{ {
case Alignment.Left: case Alignment.Left:
@@ -943,23 +917,7 @@ namespace Barotrauma
healthBar.State = GUIComponent.ComponentState.None; healthBar.State = GUIComponent.ComponentState.None;
} }
suicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated; SuicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated;
if (GameMain.GameSession?.Campaign is { } campaign)
{
RectTransform endRoundButton = campaign?.EndRoundButton.RectTransform;
if (endRoundButton != null)
{
if (suicideButton.Visible)
{
endRoundButton.ScreenSpaceOffset = new Point(0, suicideButton.Rect.Height);
}
else if (endRoundButton.ScreenSpaceOffset != Point.Zero)
{
endRoundButton.ScreenSpaceOffset = Point.Zero;
}
}
}
cprButton.Visible = cprButton.Visible =
Character == Character.Controlled?.SelectedCharacter Character == Character.Controlled?.SelectedCharacter
@@ -992,9 +950,9 @@ namespace Barotrauma
{ {
healthBarHolder.AddToGUIUpdateList(); healthBarHolder.AddToGUIUpdateList();
} }
if (suicideButton.Visible && Character == Character.Controlled) if (SuicideButton.Visible && Character == Character.Controlled)
{ {
suicideButton.AddToGUIUpdateList(); SuicideButton.AddToGUIUpdateList();
} }
if (cprButton != null && cprButton.Visible) if (cprButton != null && cprButton.Visible)
{ {
@@ -1907,6 +1865,8 @@ namespace Barotrauma
private readonly List<Pair<AfflictionPrefab, float>> newAfflictions = new List<Pair<AfflictionPrefab, float>>(); 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>>(); private readonly List<Triplet<LimbHealth, AfflictionPrefab, float>> newLimbAfflictions = new List<Triplet<LimbHealth, AfflictionPrefab, float>>();
private readonly List<Pair<AfflictionPrefab.PeriodicEffect, float>> newPeriodicEffects = new List<Pair<AfflictionPrefab.PeriodicEffect, float>>();
public void ClientRead(IReadMessage inc) public void ClientRead(IReadMessage inc)
{ {
newAfflictions.Clear(); newAfflictions.Clear();
@@ -1920,9 +1880,20 @@ namespace Barotrauma
DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found."); DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found.");
//read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message //read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message
_ = inc.ReadRangedSingle(0.0f, 100.0f, 8); _ = inc.ReadRangedSingle(0.0f, 100.0f, 8);
int _periodicAfflictionCount = inc.ReadByte();
for (int j = 0; j < _periodicAfflictionCount; j++)
{
_ = inc.ReadByte();
}
continue; continue;
} }
float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8); float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8);
int periodicAfflictionCount = inc.ReadByte();
for (int j = 0; j < periodicAfflictionCount; j++)
{
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add(new Pair<AfflictionPrefab.PeriodicEffect, float>(afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
}
newAfflictions.Add(new Pair<AfflictionPrefab, float>(afflictionPrefab, afflictionStrength)); newAfflictions.Add(new Pair<AfflictionPrefab, float>(afflictionPrefab, afflictionStrength));
} }
@@ -1940,12 +1911,26 @@ namespace Barotrauma
Affliction existingAffliction = afflictions.Find(a => a.Prefab == newAffliction.First); Affliction existingAffliction = afflictions.Find(a => a.Prefab == newAffliction.First);
if (existingAffliction == null) if (existingAffliction == null)
{ {
afflictions.Add(newAffliction.First.Instantiate(newAffliction.Second)); existingAffliction = newAffliction.First.Instantiate(newAffliction.Second);
afflictions.Add(existingAffliction);
} }
else existingAffliction.SetStrength(newAffliction.Second);
if (existingAffliction == stunAffliction)
{ {
existingAffliction.Strength = newAffliction.Second; Character.SetStun(existingAffliction.Strength, true, true);
if (existingAffliction == stunAffliction) Character.SetStun(existingAffliction.Strength, true, true); }
foreach (var periodicEffect in newPeriodicEffects)
{
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.First)) { continue; }
//timer has wrapped around, apply the effect
if (periodicEffect.Second - existingAffliction.PeriodicEffectTimers[periodicEffect.First] > periodicEffect.First.MinInterval / 2)
{
existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second;
foreach (StatusEffect effect in periodicEffect.First.StatusEffects)
{
existingAffliction.ApplyStatusEffect(effect, deltaTime: 1.0f, this, targetLimb: null);
}
}
} }
} }
@@ -1961,9 +1946,20 @@ namespace Barotrauma
DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found."); DebugConsole.ThrowError("Error while reading character health data: affliction with the uint ID " + afflictionID + " not found.");
//read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message //read the 8 bytes for affliction strength anyway to prevent messing up reading rest of the message
_ = inc.ReadRangedSingle(0.0f, 100.0f, 8); _ = inc.ReadRangedSingle(0.0f, 100.0f, 8);
int _periodicAfflictionCount = inc.ReadByte();
for (int j = 0; j < _periodicAfflictionCount; j++)
{
_ = inc.ReadByte();
}
continue; continue;
} }
float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8); float afflictionStrength = inc.ReadRangedSingle(0.0f, afflictionPrefab.MaxStrength, 8);
int periodicAfflictionCount = inc.ReadByte();
for (int j = 0; j < periodicAfflictionCount; j++)
{
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add(new Pair<AfflictionPrefab.PeriodicEffect, float>(afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
}
newLimbAfflictions.Add(new Triplet<LimbHealth, AfflictionPrefab, float>(limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); newLimbAfflictions.Add(new Triplet<LimbHealth, AfflictionPrefab, float>(limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
} }
@@ -1980,15 +1976,28 @@ namespace Barotrauma
foreach (Triplet<LimbHealth, AfflictionPrefab, float> newAffliction in newLimbAfflictions) foreach (Triplet<LimbHealth, AfflictionPrefab, float> newAffliction in newLimbAfflictions)
{ {
if (newAffliction.First != limbHealth) continue; if (newAffliction.First != limbHealth) { continue; }
Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == newAffliction.Second); Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == newAffliction.Second);
if (existingAffliction == null) if (existingAffliction == null)
{ {
limbHealth.Afflictions.Add(newAffliction.Second.Instantiate(newAffliction.Third)); existingAffliction = newAffliction.Second.Instantiate(newAffliction.Third);
limbHealth.Afflictions.Add(existingAffliction);
} }
else existingAffliction.SetStrength(newAffliction.Third);
foreach (var periodicEffect in newPeriodicEffects)
{ {
existingAffliction.Strength = newAffliction.Third; if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.First)) { continue; }
//timer has wrapped around, apply the effect
if (periodicEffect.Second - existingAffliction.PeriodicEffectTimers[periodicEffect.First] > periodicEffect.First.MinInterval / 2)
{
existingAffliction.PeriodicEffectTimers[periodicEffect.First] = periodicEffect.Second;
foreach (StatusEffect effect in periodicEffect.First.StatusEffects)
{
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(newAffliction.First));
existingAffliction.ApplyStatusEffect(effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
}
}
} }
} }
} }
@@ -446,7 +446,7 @@ namespace Barotrauma
{ {
if (affliction is AfflictionBleeding) if (affliction is AfflictionBleeding)
{ {
bleedingDamage += affliction.GetVitalityDecrease(character.CharacterHealth); bleedingDamage += affliction.GetVitalityDecrease(null);
} }
} }
} }
@@ -455,7 +455,7 @@ namespace Barotrauma
{ {
if (affliction.Prefab.AfflictionType == "damage") if (affliction.Prefab.AfflictionType == "damage")
{ {
damage += affliction.GetVitalityDecrease(character.CharacterHealth); damage += affliction.GetVitalityDecrease(null);
} }
} }
float damageMultiplier = 1; float damageMultiplier = 1;
@@ -488,7 +488,7 @@ namespace Barotrauma
} }
// spawn damage particles // spawn damage particles
float damageParticleAmount = Math.Min(damage / 10, 1.0f) * damageMultiplier; float damageParticleAmount = Math.Min(damage / 5, 1.0f) * damageMultiplier;
if (damageParticleAmount > 0.001f) if (damageParticleAmount > 0.001f)
{ {
foreach (ParticleEmitter emitter in character.DamageEmitters) foreach (ParticleEmitter emitter in character.DamageEmitters)
@@ -510,11 +510,6 @@ namespace Barotrauma
if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; } if (!inWater && emitter.Prefab.ParticlePrefab.DrawTarget == ParticlePrefab.DrawTargetType.Water) { continue; }
emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount); emitter.Emit(1.0f, WorldPosition, character.CurrentHull, sizeMultiplier: bloodParticleSize, amountMultiplier: bloodParticleAmount);
} }
if (bloodParticleAmount > 0 && character.CurrentHull != null && !string.IsNullOrEmpty(character.BloodDecalName))
{
character.CurrentHull.AddDecal(character.BloodDecalName, WorldPosition, MathHelper.Clamp(bloodParticleSize, 0.5f, 1.0f));
}
} }
} }
@@ -1086,17 +1086,6 @@ namespace Barotrauma
AssignRelayToServer("water|editwater", false); AssignRelayToServer("water|editwater", false);
AssignRelayToServer("fire|editfire", false); AssignRelayToServer("fire|editfire", false);
commands.Add(new Command("togglecampaignteleport", "togglecampaignteleport: Toggle on/off teleportation between campaign locations by double clicking on the campaign map.", (string[] args) =>
{
if (GameMain.GameSession?.Campaign == null)
{
ThrowError("No campaign active.");
return;
}
GameMain.GameSession.Map.AllowDebugTeleport = !GameMain.GameSession.Map.AllowDebugTeleport;
NewMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", Color.White);
}, isCheat: true));
commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.", commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.",
null, null,
() => () =>
@@ -1136,7 +1125,7 @@ namespace Barotrauma
{ {
foreach (var ingredient in fabricationRecipe.RequiredItems) foreach (var ingredient in fabricationRecipe.RequiredItems)
{ {
int? ingredientPrice = ingredient.ItemPrefab.GetMinPrice(); int? ingredientPrice = ingredient.ItemPrefabs.Min(ip => ip.GetMinPrice());
if (ingredientPrice.HasValue) if (ingredientPrice.HasValue)
{ {
if (!fabricationCost.HasValue) { fabricationCost = 0; } if (!fabricationCost.HasValue) { fabricationCost = 0; }
@@ -1163,14 +1152,14 @@ namespace Barotrauma
if (fabricationRecipe != null) if (fabricationRecipe != null)
{ {
var ingredient = fabricationRecipe.RequiredItems.Find(r => r.ItemPrefab == targetItem); var ingredient = fabricationRecipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem));
if (ingredient == null) if (ingredient == null)
{ {
NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red); NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red);
} }
else if (ingredient.UseCondition && ingredient.MinCondition < deconstructItem.OutCondition) else if (ingredient.UseCondition && ingredient.MinCondition < deconstructItem.OutCondition)
{ {
NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {ingredient.ItemPrefab.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(deconstructItem.OutCondition * 100)}%)", Color.Red); NewMessage($"Deconstructing \"{itemPrefab.Name}\" produces more \"{deconstructItem.ItemIdentifier}\", than what's required to fabricate the item (required: {targetItem.Name} {(int)(ingredient.MinCondition * 100)}%, output: {deconstructItem.ItemIdentifier} {(int)(deconstructItem.OutCondition * 100)}%)", Color.Red);
} }
} }
} }
@@ -1389,6 +1378,39 @@ namespace Barotrauma
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
#if DEBUG #if DEBUG
commands.Add(new Command("setplanthealth", "setplanthealth [value]: Sets the health of the selected plant in sub editor.", (string[] args) =>
{
if (1 > args.Length || Screen.Selected != GameMain.SubEditorScreen) { return; }
string arg = args[0];
if (!float.TryParse(arg, out float value))
{
ThrowError($"{arg} is not a valid value.");
return;
}
foreach (MapEntity me in MapEntity.SelectedList)
{
if (me is Item it)
{
if (it.GetComponent<Planter>() is { } planter)
{
foreach (Growable seed in planter.GrowableSeeds.Where(s => s != null))
{
NewMessage($"Set the health of {seed.Name} to {value} (from {seed.Health})");
seed.Health = value;
}
}
else if (it.GetComponent<Growable>() is { } seed)
{
NewMessage($"Set the health of {seed.Name} to {value} (from {seed.Health})");
seed.Health = value;
}
}
}
}));
commands.Add(new Command("printreceivertransfers", "", (string[] args) => commands.Add(new Command("printreceivertransfers", "", (string[] args) =>
{ {
GameMain.Client.PrintReceiverTransters(); GameMain.Client.PrintReceiverTransters();
@@ -2012,7 +2034,7 @@ namespace Barotrauma
return; return;
} }
GameMain.Config.SelectCorePackage(GameMain.Config.SelectedContentPackages.First(cp => cp.CorePackage), true); GameMain.Config.SelectCorePackage(GameMain.Config.CurrentCorePackage, true);
})); }));
commands.Add(new Command("ingamemodswap", "", (string[] args) => commands.Add(new Command("ingamemodswap", "", (string[] args) =>
@@ -2041,7 +2063,7 @@ namespace Barotrauma
} }
ShowQuestionPrompt("Permission to grant to client " + args[0] + "?", (perm) => ShowQuestionPrompt("Permission to grant to client " + args[0] + "?", (perm) =>
{ {
GameMain.Client?.SendConsoleCommand("giveperm " + args[0] + " " + perm); GameMain.Client?.SendConsoleCommand("giveperm \"" + args[0] + "\" " + perm);
}, args, 1); }, args, 1);
} }
); );
@@ -2060,7 +2082,7 @@ namespace Barotrauma
ShowQuestionPrompt("Permission to revoke from client " + args[0] + "?", (perm) => ShowQuestionPrompt("Permission to revoke from client " + args[0] + "?", (perm) =>
{ {
GameMain.Client?.SendConsoleCommand("revokeperm " + args[0] + " " + perm); GameMain.Client?.SendConsoleCommand("revokeperm \"" + args[0] + "\" " + perm);
}, args, 1); }, args, 1);
} }
); );
@@ -2078,7 +2100,7 @@ namespace Barotrauma
} }
ShowQuestionPrompt("Rank to grant to client " + args[0] + "?", (rank) => ShowQuestionPrompt("Rank to grant to client " + args[0] + "?", (rank) =>
{ {
GameMain.Client?.SendConsoleCommand("giverank " + args[0] + " " + rank); GameMain.Client?.SendConsoleCommand("giverank \"" + args[0] + "\" " + rank);
}, args, 1); }, args, 1);
} }
); );
@@ -2091,7 +2113,7 @@ namespace Barotrauma
ShowQuestionPrompt("Console command permissions to grant to client " + args[0] + "? You may enter multiple commands separated with a space or use \"all\" to give the permission to use all console commands.", (commandNames) => ShowQuestionPrompt("Console command permissions to grant to client " + args[0] + "? You may enter multiple commands separated with a space or use \"all\" to give the permission to use all console commands.", (commandNames) =>
{ {
GameMain.Client?.SendConsoleCommand("givecommandperm " + args[0] + " " + commandNames); GameMain.Client?.SendConsoleCommand("givecommandperm \"" + args[0] + "\" " + commandNames);
}, args, 1); }, args, 1);
} }
); );
@@ -2104,7 +2126,7 @@ namespace Barotrauma
ShowQuestionPrompt("Console command permissions to revoke from client " + args[0] + "? You may enter multiple commands separated with a space or use \"all\" to revoke the permission to use any console commands.", (commandNames) => ShowQuestionPrompt("Console command permissions to revoke from client " + args[0] + "? You may enter multiple commands separated with a space or use \"all\" to revoke the permission to use any console commands.", (commandNames) =>
{ {
GameMain.Client?.SendConsoleCommand("revokecommandperm " + args[0] + " " + commandNames); GameMain.Client?.SendConsoleCommand("revokecommandperm \"" + args[0] + "\" " + commandNames);
}, args, 1); }, args, 1);
} }
); );
@@ -2505,7 +2527,7 @@ namespace Barotrauma
} }
}, isCheat: true)); }, isCheat: true));
commands.Add(new Command("spawnsub", "spawnsub [subname]: Spawn a submarine at the position of the cursor", (string[] args) => commands.Add(new Command("spawnsub", "spawnsub [subname] [is thalamus]: Spawn a submarine at the position of the cursor", (string[] args) =>
{ {
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
@@ -2528,6 +2550,25 @@ namespace Barotrauma
{ {
Submarine spawnedSub = Submarine.Load(subInfo, false); Submarine spawnedSub = Submarine.Load(subInfo, false);
spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition)); spawnedSub.SetPosition(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
if (subInfo.Type == SubmarineType.Wreck)
{
spawnedSub.MakeWreck();
if (args.Length > 1 && bool.TryParse(args[1], out bool isThalamus))
{
if (isThalamus)
{
spawnedSub.CreateWreckAI();
}
else
{
spawnedSub.DisableWreckAI();
}
}
else
{
spawnedSub.DisableWreckAI();
}
}
} }
} }
catch (Exception e) catch (Exception e)
@@ -0,0 +1,33 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
namespace Barotrauma
{
partial class Decal
{
public void Draw(SpriteBatch spriteBatch, Hull hull, float depth)
{
Vector2 drawPos = position + hull.Rect.Location.ToVector2();
if (hull.Submarine != null) { drawPos += hull.Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;
spriteBatch.Draw(Sprite.Texture, drawPos, clippedSourceRect, Color * GetAlpha(), 0, Vector2.Zero, Scale, SpriteEffects.None, depth);
if (GameMain.DebugDraw && affectedSections != null && affectedSections.Count > 0)
{
Vector2 drawOffset = hull.Submarine == null ? Vector2.Zero : hull.Submarine.DrawPosition;
Point sectionSize = affectedSections.First().Rect.Size;
Rectangle drawPositionRect = new Rectangle((int)(drawOffset.X + hull.Rect.X), (int)(drawOffset.Y + hull.Rect.Y), sectionSize.X, sectionSize.Y);
foreach (var section in affectedSections)
{
// Draw colors
GUI.DrawRectangle(spriteBatch, new Vector2(drawPositionRect.X + section.Rect.X, -(drawPositionRect.Y + section.Rect.Y)), new Vector2(sectionSize.X, sectionSize.Y), Color.Red, false, 0.0f, (int)Math.Max(1.5f / Screen.Selected.Cam.Zoom, 1.0f));
}
}
}
}
}
@@ -132,17 +132,19 @@ namespace Barotrauma
}; };
shadow.SetAsFirstChild(); shadow.SetAsFirstChild();
void RecalculateLastMessage(GUIListBox conversationList, bool append) static void RecalculateLastMessage(GUIListBox conversationList, bool append)
{ {
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement) if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
{ {
GUILayoutGroup textLayout = lastElement.GetChild<GUILayoutGroup>(); GUILayoutGroup textLayout = lastElement.GetChild<GUILayoutGroup>();
if (lastElement.Rect.Size.Y < textLayout.Rect.Size.Y && !append)
{
lastElement.RectTransform.MinSize = textLayout.Rect.Size;
}
if (textLayout != null) if (textLayout != null)
{ {
if (lastElement.Rect.Size.Y < textLayout.Rect.Size.Y && !append)
{
lastElement.RectTransform.MinSize = textLayout.Rect.Size;
}
int textHeight = textLayout.Children.Sum(c => c.Rect.Height); int textHeight = textLayout.Children.Sum(c => c.Rect.Height);
textLayout.RectTransform.MaxSize = new Point(lastElement.RectTransform.MaxSize.X, textHeight); textLayout.RectTransform.MaxSize = new Point(lastElement.RectTransform.MaxSize.X, textHeight);
textLayout.Recalculate(); textLayout.Recalculate();
@@ -323,7 +325,10 @@ namespace Barotrauma
} }
textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16)); textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16));
// content.RectTransform.MinSize = new Point(0, textContent.Rect.Height);
// Recalculate the text size as it is scaled up and no longer matching the text height due to the textContent's minSize increasing
textBlock.CalculateHeightFromText();
//content.RectTransform.MinSize = new Point(0, textContent.Rect.Height);
return buttons; return buttons;
} }
@@ -17,10 +17,11 @@ namespace Barotrauma
private float intensityGraphUpdateInterval; private float intensityGraphUpdateInterval;
private float lastIntensityUpdate; private float lastIntensityUpdate;
private Event? pinnedEvent;
private Vector2 pinnedPosition = new Vector2(256, 128); private Vector2 pinnedPosition = new Vector2(256, 128);
private bool isDragging; private bool isDragging;
public Event? PinnedEvent { get; set; }
public void DebugDraw(SpriteBatch spriteBatch) public void DebugDraw(SpriteBatch spriteBatch)
{ {
foreach (Event ev in activeEvents) foreach (Event ev in activeEvents)
@@ -104,12 +105,21 @@ namespace Barotrauma
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12; y += 12;
if ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) && if (eventSet.PerWreck)
roundDuration < eventSet.MinMissionTime) {
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), " submarine near the wreck", Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12;
}
if (eventSet.PerRuin)
{
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), " submarine near the ruins", Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12;
}
if (roundDuration < eventSet.MinMissionTime)
{ {
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
" " + (int) (eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int) (distanceTraveled * 100.0f) + " %)", " " + (int) (eventSet.MinDistanceTraveled * 100.0f) + "% travelled (current: " + (int) (distanceTraveled * 100.0f) + " %)",
Color.Orange * 0.8f, null, 0, GUI.SmallFont); ((Submarine.MainSub == null || distanceTraveled < eventSet.MinDistanceTraveled) ? Color.Lerp(GUI.Style.Yellow, GUI.Style.Red, eventSet.MinDistanceTraveled - distanceTraveled) : GUI.Style.Green) * 0.8f, null, 0, GUI.SmallFont);
y += 12; y += 12;
} }
@@ -125,7 +135,7 @@ namespace Barotrauma
{ {
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
" " + (int) (eventSet.MinMissionTime - roundDuration) + " s", " " + (int) (eventSet.MinMissionTime - roundDuration) + " s",
Color.Orange * 0.8f, null, 0, GUI.SmallFont); Color.Lerp(GUI.Style.Yellow, GUI.Style.Red, (eventSet.MinMissionTime - roundDuration)), null, 0, GUI.SmallFont);
} }
y += 15; y += 15;
@@ -143,25 +153,25 @@ namespace Barotrauma
Rectangle outlineRect = new Rectangle(rect.Location, rect.Size); Rectangle outlineRect = new Rectangle(rect.Location, rect.Size);
outlineRect.Inflate(4, 4); outlineRect.Inflate(4, 4);
if (pinnedEvent == ev) { GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); } if (PinnedEvent == ev) { GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); }
if (rect.Contains(PlayerInput.MousePosition)) if (rect.Contains(PlayerInput.MousePosition))
{ {
GUI.MouseCursor = CursorState.Hand; GUI.MouseCursor = CursorState.Hand;
GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); GUI.DrawRectangle(spriteBatch, outlineRect, Color.White);
if (ev != pinnedEvent) if (ev != PinnedEvent)
{ {
DrawEvent(spriteBatch, ev, rect); DrawEvent(spriteBatch, ev, rect);
} }
else if (PlayerInput.SecondaryMouseButtonHeld() || PlayerInput.SecondaryMouseButtonDown()) else if (PlayerInput.SecondaryMouseButtonHeld() || PlayerInput.SecondaryMouseButtonDown())
{ {
pinnedEvent = null; PinnedEvent = null;
} }
if (PlayerInput.PrimaryMouseButtonHeld() || PlayerInput.PrimaryMouseButtonDown()) if (PlayerInput.PrimaryMouseButtonHeld() || PlayerInput.PrimaryMouseButtonDown())
{ {
pinnedEvent = ev; PinnedEvent = ev;
} }
} }
@@ -171,9 +181,9 @@ namespace Barotrauma
public void DrawPinnedEvent(SpriteBatch spriteBatch) public void DrawPinnedEvent(SpriteBatch spriteBatch)
{ {
if (pinnedEvent != null) if (PinnedEvent != null)
{ {
Rectangle rect = DrawEvent(spriteBatch, pinnedEvent, null); Rectangle rect = DrawEvent(spriteBatch, PinnedEvent, null);
if (rect != Rectangle.Empty) if (rect != Rectangle.Empty)
{ {
@@ -187,7 +197,7 @@ namespace Barotrauma
if (PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonHeld()) if (PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonHeld())
{ {
pinnedEvent = null; PinnedEvent = null;
isDragging = false; isDragging = false;
} }
} }
@@ -2,11 +2,9 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma namespace Barotrauma
{ {
@@ -14,7 +12,7 @@ namespace Barotrauma
{ {
public const string RadioChatString = "r; "; public const string RadioChatString = "r; ";
private GUIListBox chatBox; private readonly GUIListBox chatBox;
private Point screenResolution; private Point screenResolution;
public readonly ChatManager ChatManager = new ChatManager(); public readonly ChatManager ChatManager = new ChatManager();
@@ -37,10 +35,17 @@ namespace Barotrauma
private float prevUIScale; private float prevUIScale;
private readonly GUIFrame channelSettingsFrame;
private readonly GUITextBox channelText;
private readonly GUILayoutGroup channelPickerContent;
private readonly GUIButton memButton;
private WifiComponent prevRadio;
private bool channelMemPending;
//individual message texts that pop up when the chatbox is hidden //individual message texts that pop up when the chatbox is hidden
const float PopupMessageDuration = 5.0f; const float PopupMessageDuration = 5.0f;
private float popupMessageTimer; private readonly List<GUIComponent> popupMessages = new List<GUIComponent>();
private Queue<GUIComponent> popupMessages = new Queue<GUIComponent>();
public GUITextBox.OnEnterHandler OnEnterMessage public GUITextBox.OnEnterHandler OnEnterMessage
{ {
@@ -67,9 +72,9 @@ namespace Barotrauma
} }
} }
private GUIButton showNewMessagesButton; private readonly GUIButton showNewMessagesButton;
private GUIFrame hideableElements; private readonly GUIFrame hideableElements;
public const int ToggleButtonWidthRaw = 30; public const int ToggleButtonWidthRaw = 30;
private int popupMessageOffset; private int popupMessageOffset;
@@ -88,6 +93,133 @@ namespace Barotrauma
var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox"); var chatBoxHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.875f), hideableElements.RectTransform), style: "ChatBox");
chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null); chatBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.95f), chatBoxHolder.RectTransform, Anchor.CenterRight), style: null);
// channel settings -----------------------------------------------------------------------------
channelSettingsFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), chatBoxHolder.RectTransform, Anchor.TopCenter, Pivot.BottomCenter) { MinSize = new Point(0, 25) }, style: "GUIFrameBottom");
var channelSettingsContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), channelSettingsFrame.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true,
RelativeSpacing = 0.01f
};
var buttonLeft = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
{
OnClicked = (btn, userdata) =>
{
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
SetChannel(radio.Channel - 1, setText: true);
GUI.PlayUISound(GUISoundType.PopupMenu);
}
return true;
}
};
var arrowIcon = new GUIImage(new RectTransform(new Vector2(0.4f), buttonLeft.RectTransform, Anchor.Center), style: "GUIButtonHorizontalArrow", scaleToFit: true)
{
Color = new Color(51, 59, 46),
SpriteEffects = Microsoft.Xna.Framework.Graphics.SpriteEffects.FlipHorizontally
};
arrowIcon.HoverColor = arrowIcon.PressedColor = arrowIcon.PressedColor = arrowIcon.Color;
channelText = new GUITextBox(new RectTransform(new Vector2(0.25f, 0.8f), channelSettingsContent.RectTransform), style: "DigitalFrameLight", textAlignment: Alignment.Center, font: GUI.DigitalFont)
{
textFilterFunction = text =>
{
var str = new string(text.Where(c => char.IsNumber(c)).ToArray());
if (str.Length > 4) { str = str.Substring(0, 4); }
return str;
},
OnEnterPressed = (tb, text) =>
{
tb.Deselect();
return true;
}
};
Vector2 textSize = channelText.Font.MeasureString("0000");
channelText.TextBlock.ToolTip = TextManager.Get("currentradiochannel");
channelText.TextBlock.TextScale = Math.Min(channelText.Rect.Height / textSize.Y * 0.9f, 1.0f);
channelText.OnDeselected += (sender, key) =>
{
int.TryParse(channelText.Text, out int newChannel);
SetChannel(newChannel, setText: true);
GUI.PlayUISound(GUISoundType.PopupMenu);
};
var buttonRight = new GUIButton(new RectTransform(new Vector2(0.1f, 0.8f), channelSettingsContent.RectTransform), style: "DeviceButton")
{
OnClicked = (btn, userdata) =>
{
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
SetChannel(radio.Channel + 1, setText: true);
GUI.PlayUISound(GUISoundType.PopupMenu);
}
return true;
}
};
arrowIcon = new GUIImage(new RectTransform(new Vector2(0.4f), buttonRight.RectTransform, Anchor.Center), style: "GUIButtonHorizontalArrow", scaleToFit: true)
{
Color = new Color(51, 59, 46)
};
arrowIcon.HoverColor = arrowIcon.PressedColor = arrowIcon.PressedColor = arrowIcon.Color;
var channelPicker = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.6f), channelSettingsContent.RectTransform), "InnerFrame");
channelPickerContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), channelPicker.RectTransform, Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true
};
for (int i = 0; i < 10; i++)
{
new GUIButton(new RectTransform(new Vector2(0.1f, 1.0f), channelPickerContent.RectTransform), i.ToString(), style: "GUITextBlock")
{
TextColor = new Color(51, 59, 46),
SelectedTextColor = GUI.Style.Green,
UserData = i,
OnClicked = (btn, userdata) =>
{
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
int index = (int)userdata;
if (channelMemPending)
{
int.TryParse(channelText.Text, out int newChannel);
radio.SetChannelMemory(index, newChannel);
btn.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
new string[] { "[index]", "[channel]" },
new string[] { index.ToString(), radio.GetChannelMemory(index).ToString() });
channelMemPending = false;
channelPickerContent.Children.First().CanBeFocused = true;
memButton.Enabled = true;
channelPickerContent.Flash(GUI.Style.Green);
channelText.Flash(GUI.Style.Green);
}
SetChannel(radio.GetChannelMemory(index), setText: true);
GUI.PlayUISound(GUISoundType.PopupMenu);
}
return true;
}
};
}
memButton = new GUIButton(new RectTransform(new Vector2(0.2f, 0.9f), channelSettingsContent.RectTransform), style: "DeviceButton", text: TextManager.Get("saveradiochannelbutton"))
{
ToolTip = TextManager.Get("saveradiochannelbuttontooltip"),
OnClicked = (btn, userdata) =>
{
channelMemPending = true;
//don't allow storing a value in the first preset
channelPickerContent.Children.First().CanBeFocused = false;
foreach (GUIComponent channelButton in channelPickerContent.Children)
{
channelButton.Selected = false;
}
btn.Enabled = false;
return true;
}
};
// ---------------------------------------------------------------------------------------------
InputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft), InputBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.125f), hideableElements.RectTransform, Anchor.BottomLeft),
style: "ChatTextBox") style: "ChatTextBox")
{ {
@@ -148,7 +280,7 @@ namespace Barotrauma
} }
} }
Color textColor = textBox.Color; Color textColor;
switch (command) switch (command)
{ {
case "r": case "r":
@@ -244,7 +376,7 @@ namespace Barotrauma
} }
} }
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), senderNameTimestamp == null ? 0 : senderNameTimestamp.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)
@@ -296,7 +428,7 @@ namespace Barotrauma
{ {
var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip") var popupMsg = new GUIFrame(new RectTransform(Vector2.One, GUIFrame.RectTransform), style: "GUIToolTip")
{ {
Visible = false, UserData = 0.0f,
CanBeFocused = false CanBeFocused = false
}; };
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center)); var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), popupMsg.RectTransform, Anchor.Center));
@@ -322,10 +454,10 @@ namespace Barotrauma
popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true); popupMsg.RectTransform.Resize(new Point((int)(textWidth / content.RectTransform.RelativeSize.X) , (int)((senderTextSize.Y + msgSize.Y) / content.RectTransform.RelativeSize.Y)), resizeChildren: true);
popupMsg.RectTransform.IsFixedSize = true; popupMsg.RectTransform.IsFixedSize = true;
content.Recalculate(); content.Recalculate();
popupMessages.Enqueue(popupMsg); popupMessages.Add(popupMsg);
} }
if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) chatBox.BarScroll = 1.0f; if ((prevSize == 1.0f && chatBox.BarScroll == 0.0f) || (prevSize < 1.0f && chatBox.BarScroll == 1.0f)) { chatBox.BarScroll = 1.0f; }
GUISoundType soundType = GUISoundType.ChatMessage; GUISoundType soundType = GUISoundType.ChatMessage;
if (message.Type == ChatMessageType.Radio) if (message.Type == ChatMessageType.Radio)
@@ -372,7 +504,7 @@ namespace Barotrauma
GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0); GUIFrame.RectTransform.NonScaledSize -= new Point(toggleButtonWidth, 0);
GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0); GUIFrame.RectTransform.AbsoluteOffset += new Point(toggleButtonWidth, 0);
popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(20 * GUI.Scale); popupMessageOffset = GameMain.GameSession.CrewManager.ReportButtonFrame.Rect.Width + GUIFrame.Rect.Width + (int)(35 * GUI.Scale);
} }
public void Update(float deltaTime) public void Update(float deltaTime)
@@ -394,22 +526,93 @@ namespace Barotrauma
ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height); ToggleButton.RectTransform.AbsoluteOffset = new Point(GUIFrame.Rect.Right, GUIFrame.Rect.Y + HUDLayoutSettings.ChatBoxArea.Height - ToggleButton.Rect.Height);
} }
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
if (prevRadio != radio)
{
foreach (GUIComponent presetButton in channelPickerContent.Children)
{
int index = (int)presetButton.UserData;
presetButton.ToolTip = TextManager.GetWithVariables("radiochannelpreset",
new string[] { "[index]", "[channel]" },
new string[] { index.ToString(), radio.GetChannelMemory(index).ToString() });
}
SetChannel(radio.Channel, setText: true);
prevRadio = radio;
}
if (channelMemPending)
{
if (channelPickerContent.FlashTimer <= 0)
{
channelPickerContent.Flash(GUI.Style.Green, flashRectInflate: new Vector2(GUI.Scale * 5.0f));
}
if (PlayerInput.PrimaryMouseButtonClicked() && !GUI.IsMouseOn(channelPickerContent))
{
channelPickerContent.Children.First().CanBeFocused = true;
channelMemPending = false;
memButton.Enabled = true;
SetChannel(radio.Channel, setText: true);
}
}
channelSettingsFrame.Visible = true;
}
else
{
channelSettingsFrame.Visible = false;
channelPickerContent.Children.First().CanBeFocused = true;
channelMemPending = false;
memButton.Enabled = true;
}
if (ToggleOpen) if (ToggleOpen)
{ {
openState += deltaTime * 5.0f; openState += deltaTime * 5.0f;
//delete all popup messages when the chatbox is open //delete all popup messages when the chatbox is open
while (popupMessages.Count > 0) foreach (var popupMsg in popupMessages)
{ {
var popupMsg = popupMessages.Dequeue();
popupMsg.Parent.RemoveChild(popupMsg); popupMsg.Parent.RemoveChild(popupMsg);
} }
popupMessages.Clear();
} }
else else
{ {
openState -= deltaTime * 5.0f; openState -= deltaTime * 5.0f;
int yOffset = 0;
foreach (var popupMsg in popupMessages)
{
float msgTimer = (float)popupMsg.UserData;
int targetYOffset = (int)MathHelper.Lerp(popupMsg.RectTransform.ScreenSpaceOffset.Y, yOffset, deltaTime * 10.0f);
if (popupMsg == popupMessages.First())
{
popupMsg.UserData = msgTimer + deltaTime * Math.Max(popupMessages.Count / 2, 1);
if (msgTimer > PopupMessageDuration)
{
//move the message out of the screen and delete it
popupMsg.RectTransform.ScreenSpaceOffset =
new Point((int)MathHelper.SmoothStep(popupMessageOffset, 10, (msgTimer - PopupMessageDuration) * 5.0f), targetYOffset);
if (msgTimer > PopupMessageDuration + 0.2f)
{
popupMsg.Parent?.RemoveChild(popupMsg);
}
}
}
if (msgTimer < PopupMessageDuration)
{
if (popupMsg != popupMessages.First()) { popupMsg.UserData = Math.Min(msgTimer + deltaTime, 1.0f); }
//move the message on the screen
popupMsg.RectTransform.ScreenSpaceOffset = new Point(
(int)MathHelper.SmoothStep(0, popupMessageOffset, msgTimer * 5.0f), targetYOffset);
}
yOffset += popupMsg.Rect.Height + GUI.IntScale(10);
}
popupMessages.RemoveAll(p => p.Parent == null);
//make the first popup message visible //make the first popup message visible
var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null; /*var popupMsg = popupMessages.Count > 0 ? popupMessages.Peek() : null;
if (popupMsg != null) if (popupMsg != null)
{ {
popupMsg.Visible = true; popupMsg.Visible = true;
@@ -433,7 +636,7 @@ namespace Barotrauma
popupMsg.RectTransform.ScreenSpaceOffset = new Point( popupMsg.RectTransform.ScreenSpaceOffset = new Point(
(int)MathHelper.SmoothStep(0, popupMessageOffset, popupMessageTimer * 5.0f), 0); (int)MathHelper.SmoothStep(0, popupMessageOffset, popupMessageTimer * 5.0f), 0);
} }
} }*/
} }
openState = MathHelper.Clamp(openState, 0.0f, 1.0f); openState = MathHelper.Clamp(openState, 0.0f, 1.0f);
int hiddenBoxOffset = -(GUIFrame.Rect.Width); int hiddenBoxOffset = -(GUIFrame.Rect.Width);
@@ -441,5 +644,27 @@ namespace Barotrauma
new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0); new Point((int)MathHelper.SmoothStep(hiddenBoxOffset, 0, openState), 0);
hideableElements.Visible = openState > 0.0f; hideableElements.Visible = openState > 0.0f;
} }
private void SetChannel(int channel, bool setText)
{
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
radio.Channel = channel;
if (setText)
{
string text = radio.Channel.ToString().PadLeft(4, '0');
if (channelText.Text != text) { channelText.Text = text; }
}
if (!channelMemPending)
{
foreach (GUIComponent channelButton in channelPickerContent.Children)
{
int buttonIndex = (int)channelButton.UserData;
channelButton.Selected = radio.GetChannelMemory(buttonIndex) == channel;
}
}
}
}
} }
} }
@@ -323,7 +323,7 @@ namespace Barotrauma
if (Screen.Selected == GameMain.GameScreen) if (Screen.Selected == GameMain.GameScreen)
{ {
yOffset = -HUDLayoutSettings.ChatBoxArea.Height; yOffset = (int)(-HUDLayoutSettings.ChatBoxArea.Height * 1.2f);
watermarkRect.Y += yOffset; watermarkRect.Y += yOffset;
} }
@@ -609,7 +609,7 @@ namespace Barotrauma
if (Character.Controlled?.Inventory != null) if (Character.Controlled?.Inventory != null)
{ {
if (!Character.Controlled.LockHands && Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead) if (Character.Controlled.Stun < 0.1f && !Character.Controlled.IsDead)
{ {
Inventory.DrawFront(spriteBatch); Inventory.DrawFront(spriteBatch);
} }
@@ -1267,7 +1267,7 @@ namespace Barotrauma
Vector2 diff = worldPosition - cam.WorldViewCenter; Vector2 diff = worldPosition - cam.WorldViewCenter;
float dist = diff.Length(); float dist = diff.Length();
float symbolScale = 64.0f / sprite.size.X; float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f);
if (dist > hideDist) if (dist > hideDist)
{ {
@@ -2090,7 +2090,7 @@ namespace Barotrauma
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
var button = new GUIButton(new RectTransform(new Vector2(0.1f, 0.1f), pauseMenuInner.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point((int)(15 * GUI.Scale)) }, new GUIButton(new RectTransform(new Vector2(0.1f, 0.1f), pauseMenuInner.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point((int)(15 * GUI.Scale)) },
"", style: "GUIBugButton") "", style: "GUIBugButton")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -2098,12 +2098,12 @@ namespace Barotrauma
OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; } OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
}; };
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuResume")) new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuResume"))
{ {
OnClicked = TogglePauseMenu OnClicked = TogglePauseMenu
}; };
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSettings")) new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSettings"))
{ {
OnClicked = (btn, userData) => OnClicked = (btn, userData) =>
{ {
@@ -2117,8 +2117,8 @@ namespace Barotrauma
{ {
if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode) if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
{ {
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuRetry")); var retryButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuRetry"));
button.OnClicked += (btn, userData) => retryButton.OnClicked += (btn, userData) =>
{ {
var msgBox = new GUIMessageBox("", TextManager.Get("PauseMenuRetryVerification"), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }) var msgBox = new GUIMessageBox("", TextManager.Get("PauseMenuRetryVerification"), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") })
{ {
@@ -2131,6 +2131,7 @@ namespace Barotrauma
GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame); GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame);
} }
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction");
TogglePauseMenu(btn, userData); TogglePauseMenu(btn, userData);
GameMain.GameSession.LoadPreviousSave(); GameMain.GameSession.LoadPreviousSave();
return true; return true;
@@ -2144,24 +2145,57 @@ namespace Barotrauma
}; };
return true; return true;
}; };
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit")) var saveAndQuitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
{ {
UserData = "save" UserData = "save"
}; };
button.OnClicked += QuitClicked; saveAndQuitButton.OnClicked += (btn, userdata) =>
button.OnClicked += TogglePauseMenu; {
//Only allow saving mid-round in outpost levels. Quitting in the middle of a mission reset progress to the start of the round.
if (GameMain.GameSession == null)
{
pauseMenuOpen = false;
}
else if (GameMain.GameSession?.Campaign == null || Level.IsLoadedOutpost)
{
pauseMenuOpen = false;
GameMain.QuitToMainMenu(save: true);
}
else
{
var msgBox = new GUIMessageBox("", TextManager.Get("PauseMenuSaveAndQuitVerification", fallBackTag: "pausemenuquitverification"), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") })
{
UserData = "verificationprompt"
};
msgBox.Buttons[0].OnClicked = (_, userdata) =>
{
pauseMenuOpen = false;
GameMain.QuitToMainMenu(save: false);
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = (_, userdata) =>
{
pauseMenuOpen = false;
msgBox.Close();
return true;
};
}
return true;
};
} }
else if (GameMain.GameSession.GameMode is TestGameMode) else if (GameMain.GameSession.GameMode is TestGameMode)
{ {
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor")) new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor"))
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
GameMain.GameSession.EndRound(""); GameMain.GameSession.EndRound("");
pauseMenuOpen = false;
return true; 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))
{ {
@@ -2181,7 +2215,7 @@ namespace Barotrauma
}; };
msgBox.Buttons[0].OnClicked = (_, __) => msgBox.Buttons[0].OnClicked = (_, __) =>
{ {
TogglePauseMenu(btn, userdata); pauseMenuOpen = false;
GameMain.Client.RequestRoundEnd(); GameMain.Client.RequestRoundEnd();
return true; return true;
}; };
@@ -2190,7 +2224,7 @@ namespace Barotrauma
} }
else else
{ {
TogglePauseMenu(btn, userdata); pauseMenuOpen = false;
GameMain.Client.RequestRoundEnd(); GameMain.Client.RequestRoundEnd();
} }
return true; return true;
@@ -2199,10 +2233,9 @@ namespace Barotrauma
} }
} }
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuQuit")); var quitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuQuit"));
button.OnClicked += (btn, userData) => quitButton.OnClicked += (btn, userData) =>
{ {
var quitButton = button;
if (GameMain.GameSession != null || (Screen.Selected is CharacterEditorScreen || Screen.Selected is SubEditorScreen)) if (GameMain.GameSession != null || (Screen.Selected is CharacterEditorScreen || Screen.Selected is SubEditorScreen))
{ {
string text = GameMain.GameSession == null ? "PauseMenuQuitVerificationEditor" : "PauseMenuQuitVerification"; string text = GameMain.GameSession == null ? "PauseMenuQuitVerificationEditor" : "PauseMenuQuitVerification";
@@ -2212,21 +2245,21 @@ namespace Barotrauma
}; };
msgBox.Buttons[0].OnClicked = (yesBtn, userdata) => msgBox.Buttons[0].OnClicked = (yesBtn, userdata) =>
{ {
QuitClicked(quitButton, quitButton.UserData); GameMain.QuitToMainMenu(save: false);
pauseMenuOpen = false; pauseMenuOpen = false;
return true; return true;
}; };
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = (_, userdata) => msgBox.Buttons[1].OnClicked = (_, userdata) =>
{ {
TogglePauseMenu(btn, userData); pauseMenuOpen = false;
msgBox.Close(); msgBox.Close();
return true; return true;
}; };
} }
else else
{ {
QuitClicked(quitButton, quitButton.UserData); GameMain.QuitToMainMenu(save: false);
pauseMenuOpen = false; pauseMenuOpen = false;
} }
return true; return true;
@@ -2242,12 +2275,6 @@ namespace Barotrauma
return true; return true;
} }
public static bool QuitClicked(GUIButton button, object obj)
{
GameMain.QuitToMainMenu(button.UserData as string == "save");
return true;
}
/// <summary> /// <summary>
/// Displays a message at the center of the screen, automatically preventing overlapping with other centered messages. TODO: Allow to show messages at the middle of the screen (instead of the top center). /// Displays a message at the center of the screen, automatically preventing overlapping with other centered messages. TODO: Allow to show messages at the middle of the screen (instead of the top center).
/// </summary> /// </summary>
@@ -104,6 +104,12 @@ namespace Barotrauma
set { textBlock.HoverTextColor = value; } set { textBlock.HoverTextColor = value; }
} }
public Color SelectedTextColor
{
get { return textBlock.SelectedTextColor; }
set { textBlock.SelectedTextColor = value; }
}
public override float FlashTimer public override float FlashTimer
{ {
get { return Frame.FlashTimer; } get { return Frame.FlashTimer; }
@@ -756,9 +756,9 @@ namespace Barotrauma
flashColor = (color == null) ? GUI.Style.Red : (Color)color; flashColor = (color == null) ? GUI.Style.Red : (Color)color;
} }
public void FadeOut(float duration, bool removeAfter) public void FadeOut(float duration, bool removeAfter, float wait = 0.0f)
{ {
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter)); CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait));
} }
public void FadeIn(float wait, float duration) public void FadeIn(float wait, float duration)
@@ -239,7 +239,7 @@ namespace Barotrauma
{ {
activeTextureLoads.Add(Sprite.FullPath); activeTextureLoads.Add(Sprite.FullPath);
} }
Sprite.EnsureLazyLoaded(); await Sprite.LazyLoadAsync();
} }
finally finally
{ {
@@ -446,7 +446,7 @@ namespace Barotrauma
//dragging //dragging
if (CanDragElements && draggedElement != null) if (CanDragElements && draggedElement != null)
{ {
if (!PlayerInput.LeftButtonHeld()) if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
OnRearranged?.Invoke(this, draggedElement.UserData); OnRearranged?.Invoke(this, draggedElement.UserData);
draggedElement = null; draggedElement = null;
@@ -533,7 +533,7 @@ namespace Barotrauma
} }
} }
if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child) if (CanDragElements && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child)
{ {
draggedElement = child; draggedElement = child;
draggedReferenceRectangle = child.Rect; draggedReferenceRectangle = child.Rect;
@@ -970,7 +970,6 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
var children = Content.Children;
int lastVisible = 0; int lastVisible = 0;
int i = 0; int i = 0;
@@ -180,13 +180,13 @@ namespace Barotrauma
Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), iconStyle, scaleToFit: true); Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), iconStyle, scaleToFit: true);
} }
Content = new GUILayoutGroup(new RectTransform(new Vector2(icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform)); Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform));
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null); var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null);
Buttons = new List<GUIButton>(1) Buttons = new List<GUIButton>(1)
{ {
new GUIButton(new RectTransform(new Vector2(0.5f, 0.5f), buttonContainer.RectTransform, Anchor.Center), new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
style: "GUIButtonHorizontalArrow") style: "UIToggleButton")
{ {
OnClicked = Close OnClicked = Close
} }
@@ -221,7 +221,7 @@ namespace Barotrauma
Content.RectTransform.NonScaledSize = Content.RectTransform.NonScaledSize =
new Point(Content.Rect.Width, height); new Point(Content.Rect.Width, height);
} }
Buttons[0].RectTransform.MaxSize = new Point(Math.Min(Buttons[0].Rect.Width, Buttons[0].Rect.Height)); Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y);
} }
MessageBoxes.Add(this); MessageBoxes.Add(this);
@@ -271,99 +271,92 @@ namespace Barotrauma
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
if (type == Type.InGame) if (type != Type.InGame) { return; }
Vector2 initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
Vector2 defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
Vector2 endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y);
if (!closing)
{ {
Vector2 initialPos = new Vector2(0.0f, GameMain.GraphicsHeight); Point step = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
Vector2 defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale); InnerFrame.RectTransform.AbsoluteOffset = step;
Vector2 endPos = new Vector2(GameMain.GraphicsWidth, defaultPos.Y); if (BackgroundIcon != null)
/*for (int i = MessageBoxes.IndexOf(this); i >= 0; i--)
{ {
if (MessageBoxes[i] is GUIMessageBox otherMsgBox && otherMsgBox != this && otherMsgBox.type == type && !otherMsgBox.closing) BackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (BackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - BackgroundIcon.Rect.Size.Y / 2);
if (!MathUtils.NearlyEqual(openState, 1.0f))
{ {
defaultPos = new Vector2( BackgroundIcon.Color = ToolBox.GradientLerp(openState, Color.Transparent, Color.White);
Math.Max(otherMsgBox.InnerFrame.RectTransform.AbsoluteOffset.X + 10 * GUI.Scale, defaultPos.X),
Math.Max(otherMsgBox.InnerFrame.RectTransform.AbsoluteOffset.Y + 10 * GUI.Scale, defaultPos.Y));
}
}*/
if (!closing)
{
Point step = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
InnerFrame.RectTransform.AbsoluteOffset = step;
if (BackgroundIcon != null)
{
BackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (BackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - BackgroundIcon.Rect.Size.Y / 2);
if (!MathUtils.NearlyEqual(openState, 1.0f))
{
BackgroundIcon.Color = ToolBox.GradientLerp(openState, Color.Transparent, Color.White);
}
}
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn) && AutoClose)
{
inGameCloseTimer += deltaTime;
}
if (inGameCloseTimer >= inGameCloseTime)
{
Close();
} }
} }
else if (!(Screen.Selected is RoundSummaryScreen) && !MessageBoxes.Any(mb => mb.UserData is RoundSummary))
{
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
}
if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn) && AutoClose)
{
inGameCloseTimer += deltaTime;
}
if (inGameCloseTimer >= inGameCloseTime)
{
Close();
}
}
else
{
openState += deltaTime * 2.0f;
Point step = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint();
InnerFrame.RectTransform.AbsoluteOffset = step;
if (BackgroundIcon != null)
{
BackgroundIcon.Color *= 0.9f;
}
if (openState >= 2.0f)
{
if (Parent != null) { Parent.RemoveChild(this); }
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
}
}
if (newBackgroundIcon != null)
{
if (!iconSwitching)
{ {
openState += deltaTime * 2.0f;
Point step = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint();
InnerFrame.RectTransform.AbsoluteOffset = step;
if (BackgroundIcon != null) if (BackgroundIcon != null)
{ {
BackgroundIcon.Color *= 0.9f; BackgroundIcon.Color *= 0.9f;
} if (BackgroundIcon.Color.A == 0)
if (openState >= 2.0f)
{
if (Parent != null) { Parent.RemoveChild(this); }
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
}
}
if (newBackgroundIcon != null)
{
if (!iconSwitching)
{
if (BackgroundIcon != null)
{
BackgroundIcon.Color *= 0.9f;
if (BackgroundIcon.Color.A == 0)
{
BackgroundIcon = null;
iconSwitching = true;
RemoveChild(BackgroundIcon);
}
}
else
{ {
BackgroundIcon = null;
iconSwitching = true; iconSwitching = true;
RemoveChild(BackgroundIcon);
} }
iconState = 0;
} }
else else
{ {
newBackgroundIcon.SetAsFirstChild(); iconSwitching = true;
newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2);
newBackgroundIcon.Color = ToolBox.GradientLerp(iconState, Color.Transparent, Color.White);
if (newBackgroundIcon.Color.A == 255)
{
BackgroundIcon = newBackgroundIcon;
BackgroundIcon.SetAsFirstChild();
newBackgroundIcon = null;
iconSwitching = false;
}
iconState = Math.Min(iconState + deltaTime * 2.0f, 1.0f);
} }
iconState = 0;
}
else
{
newBackgroundIcon.SetAsFirstChild();
newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2);
newBackgroundIcon.Color = ToolBox.GradientLerp(iconState, Color.Transparent, Color.White);
if (newBackgroundIcon.Color.A == 255)
{
BackgroundIcon = newBackgroundIcon;
BackgroundIcon.SetAsFirstChild();
newBackgroundIcon = null;
iconSwitching = false;
}
iconState = Math.Min(iconState + deltaTime * 2.0f, 1.0f);
} }
} }
} }
@@ -380,16 +380,18 @@ namespace Barotrauma
private void ClampIntValue() private void ClampIntValue()
{ {
if (MinValueInt != null) if (MinValueInt != null && intValue < MinValueInt.Value)
{ {
intValue = Math.Max(intValue, MinValueInt.Value); intValue = Math.Max(intValue, MinValueInt.Value);
minusButton.Enabled = intValue > MinValueInt; UpdateText();
} }
if (MaxValueInt != null) if (MaxValueInt != null && intValue > MaxValueInt.Value)
{ {
intValue = Math.Min(intValue, MaxValueInt.Value); intValue = Math.Min(intValue, MaxValueInt.Value);
plusButton.Enabled = intValue < MaxValueInt; UpdateText();
} }
plusButton.Enabled = intValue < MaxValueInt;
minusButton.Enabled = intValue > MinValueInt;
} }
private void UpdateText() private void UpdateText()
@@ -462,7 +462,7 @@ namespace Barotrauma
protected List<Tuple<Vector2, int>> GetAllPositions() protected List<Tuple<Vector2, int>> GetAllPositions()
{ {
float halfHeight = Font.MeasureString("T").Y * 0.5f; float halfHeight = Font.MeasureString("T").Y * 0.5f * textScale;
string textDrawn = Censor ? CensoredText : WrappedText; string textDrawn = Censor ? CensoredText : WrappedText;
var positions = new List<Tuple<Vector2, int>>(); var positions = new List<Tuple<Vector2, int>>();
if (textDrawn.Contains("\n")) if (textDrawn.Contains("\n"))
@@ -474,10 +474,10 @@ namespace Barotrauma
{ {
string line = lines[i]; string line = lines[i];
totalIndex += line.Length; totalIndex += line.Length;
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y; float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y * textScale;
for (int j = 0; j <= line.Length; j++) for (int j = 0; j <= line.Length; j++)
{ {
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j)); Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j)) * textScale;
Vector2 indexPos = new Vector2(lineTextSize.X + Padding.X, totalTextHeight + Padding.Y - halfHeight); Vector2 indexPos = new Vector2(lineTextSize.X + Padding.X, totalTextHeight + Padding.Y - halfHeight);
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue); //DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(indexPos, index + j)); positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
@@ -490,8 +490,8 @@ namespace Barotrauma
textDrawn = Censor ? CensoredText : Text; textDrawn = Censor ? CensoredText : Text;
for (int i = 0; i <= Text.Length; i++) for (int i = 0; i <= Text.Length; i++)
{ {
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i)); Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i)) * textScale;
Vector2 indexPos = new Vector2(textSize.X + Padding.X, textSize.Y + Padding.Y - halfHeight) + TextPos - Origin; Vector2 indexPos = new Vector2(textSize.X + Padding.X, textSize.Y + Padding.Y - halfHeight) + TextPos - Origin * textScale;
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke); //DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(indexPos, i)); positions.Add(new Tuple<Vector2, int>(indexPos, i));
} }
@@ -508,7 +508,7 @@ namespace Barotrauma
{ {
var positions = GetAllPositions(); var positions = GetAllPositions();
if (positions.Count == 0) { return 0; } if (positions.Count == 0) { return 0; }
float halfHeight = Font.MeasureString("T").Y * 0.5f; float halfHeight = Font.MeasureString("T").Y * 0.5f * textScale;
var currPosition = positions[0]; var currPosition = positions[0];
@@ -522,7 +522,8 @@ namespace Barotrauma
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y); float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
if (diffY < -3.0f) if (diffY < -3.0f)
{ {
currPosition = p1; continue; currPosition = p1;
continue;
} }
else if (diffY > 3.0f) else if (diffY > 3.0f)
{ {
@@ -325,7 +325,7 @@ namespace Barotrauma
} }
else else
{ {
while (ClampText && textBlock.Text.Length>0 && Font.MeasureString(textBlock.Text).X > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z)) while (ClampText && textBlock.Text.Length > 0 && Font.MeasureString(textBlock.Text).X * TextBlock.TextScale > (int)(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z))
{ {
textBlock.Text = textBlock.Text.Substring(0, textBlock.Text.Length - 1); textBlock.Text = textBlock.Text.Substring(0, textBlock.Text.Length - 1);
} }
@@ -354,10 +354,10 @@ namespace Barotrauma
{ {
int diff = totalIndex - CaretIndex; int diff = totalIndex - CaretIndex;
int index = currentLineLength - diff; int index = currentLineLength - diff;
Vector2 lineTextSize = Font.MeasureString(lines[i].Substring(0, index)); Vector2 lineTextSize = Font.MeasureString(lines[i].Substring(0, index)) * TextBlock.TextScale;
Vector2 lastLineSize = Font.MeasureString(lines[i]); Vector2 lastLineSize = Font.MeasureString(lines[i]) * TextBlock.TextScale;
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y; float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y * TextBlock.TextScale;
caretPos = new Vector2(lineTextSize.X, totalTextHeight - lastLineSize.Y) + textBlock.TextPos - textBlock.Origin; caretPos = new Vector2(lineTextSize.X, totalTextHeight - lastLineSize.Y) + textBlock.TextPos - textBlock.Origin * TextBlock.TextScale;
break; break;
} }
} }
@@ -366,8 +366,8 @@ namespace Barotrauma
{ {
CaretIndex = Math.Min(CaretIndex, textDrawn.Length); CaretIndex = Math.Min(CaretIndex, textDrawn.Length);
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text; textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex)); Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex)) * TextBlock.TextScale;
caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin; caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin * TextBlock.TextScale;
} }
caretPosDirty = false; caretPosDirty = false;
} }
@@ -506,7 +506,7 @@ namespace Barotrauma
{ {
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + 3), new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + 3),
new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + Font.MeasureString("I").Y - 3), new Vector2(Rect.X + (int)caretPos.X + 2, Rect.Y + caretPos.Y + Font.MeasureString("I").Y * textBlock.TextScale - 3),
CaretColor ?? textBlock.TextColor * (textBlock.TextColor.A / 255.0f)); CaretColor ?? textBlock.TextColor * (textBlock.TextColor.A / 255.0f));
} }
if (selectedCharacters > 0) if (selectedCharacters > 0)
@@ -544,7 +544,7 @@ namespace Barotrauma
: selectionEndIndex < totalIndex && selectionStartIndex > previousCharacters; : selectionEndIndex < totalIndex && selectionStartIndex > previousCharacters;
if (containsSelection) if (containsSelection)
{ {
Vector2 currentLineSize = Font.MeasureString(currentLine); Vector2 currentLineSize = Font.MeasureString(currentLine) * TextBlock.TextScale;
if ((IsLeftToRight && selectionStartIndex < previousCharacters && selectionEndIndex > totalIndex) if ((IsLeftToRight && selectionStartIndex < previousCharacters && selectionEndIndex > totalIndex)
|| !IsLeftToRight && selectionEndIndex < previousCharacters && selectionStartIndex > totalIndex) || !IsLeftToRight && selectionEndIndex < previousCharacters && selectionStartIndex > totalIndex)
{ {
@@ -560,7 +560,7 @@ namespace Barotrauma
int startIndex = selectFromTheBeginning ? 0 : Math.Abs(selectionStartIndex - previousCharacters); int startIndex = selectFromTheBeginning ? 0 : Math.Abs(selectionStartIndex - previousCharacters);
int endIndex = Math.Abs(selectionEndIndex - previousCharacters); int endIndex = Math.Abs(selectionEndIndex - previousCharacters);
int characters = Math.Min(endIndex - startIndex, currentLineLength - startIndex); int characters = Math.Min(endIndex - startIndex, currentLineLength - startIndex);
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(startIndex, characters)); Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(startIndex, characters)) * TextBlock.TextScale;
Vector2 topLeft = selectFromTheBeginning Vector2 topLeft = selectFromTheBeginning
? new Vector2(offset.X, offset.Y + currentLineSize.Y * i) ? new Vector2(offset.X, offset.Y + currentLineSize.Y * i)
: new Vector2(selectionStartPos.X, offset.Y + currentLineSize.Y * i); : new Vector2(selectionStartPos.X, offset.Y + currentLineSize.Y * i);
@@ -573,7 +573,7 @@ namespace Barotrauma
int startIndex = selectFromTheBeginning ? currentLineLength : Math.Abs(selectionStartIndex - previousCharacters); int startIndex = selectFromTheBeginning ? currentLineLength : Math.Abs(selectionStartIndex - previousCharacters);
int endIndex = selectFromTheStart ? 0 : Math.Abs(selectionEndIndex - previousCharacters); int endIndex = selectFromTheStart ? 0 : Math.Abs(selectionEndIndex - previousCharacters);
int characters = Math.Min(Math.Abs(endIndex - startIndex), currentLineLength); int characters = Math.Min(Math.Abs(endIndex - startIndex), currentLineLength);
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(endIndex, characters)); Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(endIndex, characters)) * TextBlock.TextScale;
Vector2 topLeft = selectFromTheBeginning Vector2 topLeft = selectFromTheBeginning
? new Vector2(offset.X + currentLineSize.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i) ? new Vector2(offset.X + currentLineSize.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i)
: new Vector2(selectionStartPos.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i); : new Vector2(selectionStartPos.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i);
@@ -612,7 +612,7 @@ namespace Barotrauma
OnTextChanged?.Invoke(this, Text); OnTextChanged?.Invoke(this, Text);
if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos)) if (textBlock.OverflowClipActive && wasOverflowClipActive && !MathUtils.NearlyEqual(textBlock.TextPos, textPos))
{ {
textBlock.TextPos = textPos + Vector2.UnitX * Font.MeasureString(input).X; textBlock.TextPos = textPos + Vector2.UnitX * Font.MeasureString(input).X * TextBlock.TextScale;
} }
} }
} }
@@ -714,8 +714,8 @@ namespace Barotrauma
{ {
InitSelectionStart(); InitSelectionStart();
} }
float lineHeight = Font.MeasureString("T").Y; float lineHeight = Font.MeasureString("T").Y * TextBlock.TextScale;
int newIndex = textBlock.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();
@@ -725,8 +725,8 @@ namespace Barotrauma
{ {
InitSelectionStart(); InitSelectionStart();
} }
lineHeight = Font.MeasureString("T").Y; lineHeight = Font.MeasureString("T").Y * TextBlock.TextScale;
newIndex = textBlock.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();
@@ -865,18 +865,18 @@ namespace Barotrauma
{ {
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText; string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText;
InitSelectionStart(); InitSelectionStart();
selectionEndIndex = CaretIndex; selectionEndIndex = Math.Min(CaretIndex, textDrawn.Length);
selectionEndPos = caretPos; selectionEndPos = caretPos;
selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex); selectedCharacters = Math.Abs(selectionStartIndex - selectionEndIndex);
if (IsLeftToRight) if (IsLeftToRight)
{ {
selectedText = Text.Substring(selectionStartIndex, selectedCharacters); selectedText = Text.Substring(selectionStartIndex, selectedCharacters);
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, selectedCharacters)); selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionStartIndex, selectedCharacters)) * TextBlock.TextScale;
} }
else else
{ {
selectedText = Text.Substring(selectionEndIndex, selectedCharacters); selectedText = Text.Substring(selectionEndIndex, Math.Min(selectedCharacters, textDrawn.Length - selectionEndIndex));
selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, selectedCharacters)); selectionRectSize = Font.MeasureString(textDrawn.Substring(selectionEndIndex, selectedCharacters)) * TextBlock.TextScale;
} }
} }
} }
@@ -68,6 +68,10 @@ namespace Barotrauma
CreateUI(); CreateUI();
campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation; campaignUI.Campaign.Map.OnLocationChanged += UpdateLocation;
if (CurrentLocation?.Reputation != null)
{
CurrentLocation.Reputation.OnReputationValueChanged += Refresh;
}
campaignUI.Campaign.CargoManager.OnItemsInBuyCrateChanged += RefreshBuying; campaignUI.Campaign.CargoManager.OnItemsInBuyCrateChanged += RefreshBuying;
campaignUI.Campaign.CargoManager.OnPurchasedItemsChanged += RefreshBuying; campaignUI.Campaign.CargoManager.OnPurchasedItemsChanged += RefreshBuying;
campaignUI.Campaign.CargoManager.OnItemsInSellCrateChanged += RefreshSelling; campaignUI.Campaign.CargoManager.OnItemsInSellCrateChanged += RefreshSelling;
@@ -378,9 +382,13 @@ namespace Barotrauma
{ {
Stretch = true Stretch = true
}; };
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), totalContainer.RectTransform), TextManager.Get("campaignstore.total"), font: GUI.Font); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), totalContainer.RectTransform), TextManager.Get("campaignstore.total"), font: GUI.Font)
{
CanBeFocused = false
};
shoppingCrateTotal = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), totalContainer.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right) shoppingCrateTotal = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), totalContainer.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right)
{ {
CanBeFocused = false,
TextScale = 1.1f TextScale = 1.1f
}; };
@@ -412,11 +420,20 @@ namespace Barotrauma
{ {
if (prevLocation == newLocation) { return; } if (prevLocation == newLocation) { return; }
if (prevLocation?.Reputation != null)
{
prevLocation.Reputation.OnReputationValueChanged -= Refresh;
}
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{ {
if (itemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _)) if (itemPrefab.CanBeBoughtAtLocation(CurrentLocation, out PriceInfo _))
{ {
ChangeStoreTab(StoreTab.Buy); ChangeStoreTab(StoreTab.Buy);
if (newLocation?.Reputation != null)
{
newLocation.Reputation.OnReputationValueChanged += Refresh;
}
return; return;
} }
} }
@@ -717,9 +734,15 @@ namespace Barotrauma
private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIListBox listBox, bool forceDisable = false) private GUIComponent CreateItemFrame(PurchasedItem pi, PriceInfo priceInfo, GUIListBox listBox, bool forceDisable = false)
{ {
var tooltip = pi.ItemPrefab.Name;
if (!string.IsNullOrWhiteSpace(pi.ItemPrefab.Description))
{
tooltip += "\n" + pi.ItemPrefab.Description;
}
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, (int)(GUI.yScale * 60)), parent: listBox.Content.RectTransform), style: "ListBoxElement") GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, (int)(GUI.yScale * 60)), parent: listBox.Content.RectTransform), style: "ListBoxElement")
{ {
ToolTip = pi.ItemPrefab.Description, ToolTip = tooltip,
UserData = pi UserData = pi
}; };
@@ -740,6 +763,7 @@ namespace Barotrauma
iconRelativeWidth = (0.9f * mainGroup.Rect.Height) / mainGroup.Rect.Width; iconRelativeWidth = (0.9f * mainGroup.Rect.Height) / mainGroup.Rect.Width;
GUIImage img = new GUIImage(new RectTransform(new Vector2(iconRelativeWidth, 0.9f), mainGroup.RectTransform), itemIcon, scaleToFit: true) GUIImage img = new GUIImage(new RectTransform(new Vector2(iconRelativeWidth, 0.9f), mainGroup.RectTransform), itemIcon, scaleToFit: true)
{ {
CanBeFocused = false,
Color = (itemIcon == pi.ItemPrefab.InventoryIcon ? pi.ItemPrefab.InventoryIconColor : pi.ItemPrefab.SpriteColor) * (forceDisable ? 0.5f : 1.0f), Color = (itemIcon == pi.ItemPrefab.InventoryIcon ? pi.ItemPrefab.InventoryIconColor : pi.ItemPrefab.SpriteColor) * (forceDisable ? 0.5f : 1.0f),
UserData = "icon" UserData = "icon"
}; };
@@ -748,8 +772,8 @@ namespace Barotrauma
GUILayoutGroup nameAndQuantityGroup = new GUILayoutGroup(new RectTransform(new Vector2(nameAndIconRelativeWidth - iconRelativeWidth, 1.0f), mainGroup.RectTransform)) GUILayoutGroup nameAndQuantityGroup = new GUILayoutGroup(new RectTransform(new Vector2(nameAndIconRelativeWidth - iconRelativeWidth, 1.0f), mainGroup.RectTransform))
{ {
Stretch = true, CanBeFocused = false,
ToolTip = pi.ItemPrefab.Description Stretch = true
}; };
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndQuantityGroup.RectTransform), GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndQuantityGroup.RectTransform),
pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft) pi.ItemPrefab.Name, font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft)
@@ -801,8 +825,8 @@ namespace Barotrauma
var priceBlock = new GUITextBlock(new RectTransform(new Vector2(priceAndButtonRelativeWidth - buttonRelativeWidth, 1.0f), mainGroup.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right) var priceBlock = new GUITextBlock(new RectTransform(new Vector2(priceAndButtonRelativeWidth - buttonRelativeWidth, 1.0f), mainGroup.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right)
{ {
CanBeFocused = false,
TextColor = Color.White * (forceDisable ? 0.5f : 1.0f), TextColor = Color.White * (forceDisable ? 0.5f : 1.0f),
ToolTip = pi.ItemPrefab.Description,
UserData = "price" UserData = "price"
}; };
if(listBox == storeSellList || listBox == shoppingCrateSellList) if(listBox == storeSellList || listBox == shoppingCrateSellList)
@@ -788,17 +788,12 @@ namespace Barotrauma
string msg = ChatMessage.GetTimeStamp() + message.TextWithSender; string msg = ChatMessage.GetTimeStamp() + message.TextWithSender;
storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType)); storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType));
if (GameSession.IsTabMenuOpen) if (GameSession.IsTabMenuOpen && selectedTab == InfoFrameTab.Crew)
{ {
TabMenu instance = GameSession.TabMenuInstance; TabMenu instance = GameSession.TabMenuInstance;
instance.AddLineToLog(msg, message.ChangeType); instance.AddLineToLog(msg, message.ChangeType);
instance.RemoveCurrentElements();
// Update crew instance.CreateMultiPlayerList(true);
if (selectedTab == InfoFrameTab.Crew)
{
instance.RemoveCurrentElements();
instance.CreateMultiPlayerList(true);
}
} }
} }
@@ -835,14 +830,15 @@ namespace Barotrauma
break; break;
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), logList.Content.RectTransform), line, wrap: true, font: GUI.SmallFont) if (logList != null)
{ {
TextColor = textColor, new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), logList.Content.RectTransform), line, wrap: true, font: GUI.SmallFont)
CanBeFocused = false, {
UserData = line TextColor = textColor,
}.CalculateHeightFromText(); CanBeFocused = false,
UserData = line
//if ((prevSize == 1.0f && listBox.BarScroll == 0.0f) || (prevSize < 1.0f && listBox.BarScroll == 1.0f)) listBox.BarScroll = 1.0f; }.CalculateHeightFromText();
}
} }
private void CreateMissionInfo(GUIFrame infoFrame) private void CreateMissionInfo(GUIFrame infoFrame)
@@ -56,11 +56,6 @@ namespace Barotrauma
public static Thread MainThread { get; private set; } public static Thread MainThread { get; private set; }
public static IEnumerable<ContentPackage> SelectedPackages
{
get { return Config?.SelectedContentPackages; }
}
private static ContentPackage vanillaContent; private static ContentPackage vanillaContent;
public static ContentPackage VanillaContent public static ContentPackage VanillaContent
{ {
@@ -69,7 +64,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).Equals("vanilla 0.9.xml", StringComparison.OrdinalIgnoreCase)); vanillaContent = ContentPackage.CorePackages.SingleOrDefault(cp => Path.GetFileName(cp.Path).Equals("vanilla 0.9.xml", StringComparison.OrdinalIgnoreCase));
} }
return vanillaContent; return vanillaContent;
} }
@@ -454,40 +449,31 @@ namespace Barotrauma
} }
} }
GUI.Init(Window, Config.SelectedContentPackages, GraphicsDevice); GUI.Init(Window, Config.AllEnabledPackages, GraphicsDevice);
DebugConsole.Init(); DebugConsole.Init();
if (Config.AutoUpdateWorkshopItems) if (Config.AutoUpdateWorkshopItems)
{ {
bool waitingForWorkshopUpdates = true; Config.WaitingForAutoUpdate = true;
bool result = false;
TaskPool.Add("AutoUpdateWorkshopItemsAsync", TaskPool.Add("AutoUpdateWorkshopItemsAsync",
SteamManager.AutoUpdateWorkshopItemsAsync(), (task) => SteamManager.AutoUpdateWorkshopItemsAsync(), (task) =>
{ {
result = ((Task<bool>)task).Result; bool result = ((Task<bool>)task).Result;
waitingForWorkshopUpdates = false;
Config.WaitingForAutoUpdate = false;
}); });
while (waitingForWorkshopUpdates) { yield return CoroutineStatus.Running; } while (Config.WaitingForAutoUpdate) { yield return CoroutineStatus.Running; }
if (result)
{
CrossThread.RequestExecutionOnMainThread(() =>
{
ContentPackage.LoadAll();
Config.ReloadContentPackages();
});
}
} }
if (SelectedPackages.None()) if (Config.AllEnabledPackages.None())
{ {
DebugConsole.Log("No content packages selected"); DebugConsole.Log("No content packages selected");
} }
else else
{ {
DebugConsole.Log("Selected content packages: " + string.Join(", ", SelectedPackages.Select(cp => cp.Name))); DebugConsole.Log("Selected content packages: " + string.Join(", ", Config.AllEnabledPackages.Select(cp => cp.Name)));
} }
#if DEBUG #if DEBUG
@@ -542,6 +528,7 @@ namespace Barotrauma
OutpostGenerationParams.LoadPresets(); OutpostGenerationParams.LoadPresets();
WreckAIConfig.LoadAll(); WreckAIConfig.LoadAll();
EventSet.LoadPrefabs(); EventSet.LoadPrefabs();
ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item));
AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions));
SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings));
Order.Init(); Order.Init();
@@ -550,10 +537,6 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure)); StructurePrefab.LoadAll(GetFilesOfType(ContentType.Structure));
TitleScreen.LoadState = 53.0f;
yield return CoroutineStatus.Running;
ItemPrefab.LoadAll(GetFilesOfType(ContentType.Item));
TitleScreen.LoadState = 55.0f; TitleScreen.LoadState = 55.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -628,8 +611,6 @@ namespace Barotrauma
LocationType.Init(); LocationType.Init();
MainMenuScreen.Select(); MainMenuScreen.Select();
CheckContentPackage();
foreach (string steamError in SteamManager.InitializationErrors) foreach (string steamError in SteamManager.InitializationErrors)
{ {
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError)); new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError));
@@ -645,29 +626,6 @@ namespace Barotrauma
} }
private void CheckContentPackage()
{
foreach (ContentPackage contentPackage in Config.SelectedContentPackages)
{
var exePaths = contentPackage.GetFilesOfType(ContentType.Executable);
if (exePaths.Any() && AppDomain.CurrentDomain.FriendlyName != Path.GetFileNameWithoutExtension(exePaths.First()))
{
var msgBox = new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("IncorrectExe",
new string[2] { "[selectedpackage]", "[exename]" }, new string[2] { contentPackage.Name, exePaths.First() }),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked += (_, userdata) =>
{
string fullPath = Path.GetFullPath(exePaths.First());
ToolBox.OpenFileWithShell(fullPath);
Exit();
return true;
};
msgBox.Buttons[1].OnClicked = msgBox.Close;
break;
}
}
}
/// <summary> /// <summary>
/// UnloadContent will be called once per game and is the place to unload /// UnloadContent will be called once per game and is the place to unload
/// all content. /// all content.
@@ -690,11 +648,11 @@ namespace Barotrauma
{ {
if (searchAllContentPackages) if (searchAllContentPackages)
{ {
return ContentPackage.GetFilesOfType(ContentPackage.List, type); return ContentPackage.GetFilesOfType(ContentPackage.AllPackages, type);
} }
else else
{ {
return ContentPackage.GetFilesOfType(SelectedPackages, type); return ContentPackage.GetFilesOfType(Config.AllEnabledPackages, type);
} }
} }
@@ -938,6 +896,7 @@ namespace Barotrauma
if (GameSession?.GameMode != null && GameSession.GameMode.Paused) if (GameSession?.GameMode != null && GameSession.GameMode.Paused)
{ {
Paused = true; Paused = true;
GameSession.GameMode.UpdateWhilePaused((float)Timing.Step);
} }
#if !DEBUG #if !DEBUG
@@ -960,7 +919,6 @@ namespace Barotrauma
DebugConsole.AddToGUIUpdateList(); DebugConsole.AddToGUIUpdateList();
DebugConsole.Update((float)Timing.Step); DebugConsole.Update((float)Timing.Step);
Paused = Paused || (DebugConsole.IsOpen && (NetworkMember == null || !NetworkMember.GameStarted));
if (!Paused) if (!Paused)
{ {
@@ -1088,11 +1046,11 @@ namespace Barotrauma
} }
// Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called) // Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called)
if (GameSession?.Campaign is SinglePlayerCampaign campaign && Level.IsLoadedOutpost && campaign.Map?.CurrentLocation != null && campaign.CargoManager != null) if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null)
{ {
campaign.Map.CurrentLocation.AddToStock(campaign.CargoManager.SoldItems); spCampaign.Map.CurrentLocation.AddToStock(spCampaign.CargoManager.SoldItems);
campaign.CargoManager.ClearSoldItemsProjSpecific(); spCampaign.CargoManager.ClearSoldItemsProjSpecific();
campaign.Map.CurrentLocation.RemoveFromStock(campaign.CargoManager.PurchasedItems); spCampaign.Map.CurrentLocation.RemoveFromStock(spCampaign.CargoManager.PurchasedItems);
} }
SaveUtil.SaveGame(GameSession.SavePath); SaveUtil.SaveGame(GameSession.SavePath);
@@ -10,8 +10,17 @@ namespace Barotrauma
{ {
public enum SellStatus public enum SellStatus
{ {
/// <summary>
/// Entity sold in SP. Or, entity sold by client and confirmed by server in MP.
/// </summary>
Confirmed, Confirmed,
/// <summary>
/// Entity sold by client in MP. Client has received at least one update from server after selling, but this entity wasn't yet confirmed.
/// </summary>
Unconfirmed, Unconfirmed,
/// <summary>
/// Entity sold by client in MP. Client hasn't yet received an update from server after selling.
/// </summary>
Local Local
} }
@@ -36,28 +45,41 @@ namespace Barotrauma
// Only consider items which have been: // Only consider items which have been:
// a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or // a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or
// b) sold locally in multiplier (SellStatus.Local), but the client has not received a campaing state update yet after selling them // b) sold locally in multiplayer (SellStatus.Local), but the client has not received a campaing state update yet after selling them
var soldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed); var soldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
var sellables = Item.ItemList.FindAll(i => i?.Prefab != null && !i.Removed && var sellables = Item.ItemList.FindAll(i => i?.Prefab != null && !i.Removed &&
i.GetRootInventoryOwner() == character && i.GetRootInventoryOwner() == character &&
!i.SpawnedInOutpost && !i.SpawnedInOutpost &&
(i.ContainedItems == null || i.ContainedItems.None() || i.ContainedItems.All(ci => soldEntities.Any(se => se.Item == ci))) && (i.ContainedItems == null || i.ContainedItems.None() || i.ContainedItems.All(ci => soldEntities.Any(se => se.Item == ci))) &&
i.IsFullCondition && soldEntities.None(se => se.Item == i)); i.Condition >= 0.9f * i.MaxCondition && soldEntities.None(se => se.Item == i));
// Prevent selling things like battery cells from headsets and oxygen tanks from diving masks // Prevent selling items in equipment slots
var slots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.OuterClothes, InvSlotType.Headset }; var slots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
foreach (InvSlotType slot in slots) foreach (InvSlotType slot in slots)
{ {
var index = character.Inventory.FindLimbSlot(slot); var index = character.Inventory.FindLimbSlot(slot);
if (character.Inventory.Items[index] is Item item && item.ContainedItems != null) if (character.Inventory.Items[index] is Item item)
{
// Don't prevent selling of items which can only be put in equipment slots (like diving suits)
if (item.AllowedSlots.Contains(InvSlotType.Any))
{
sellables.Remove(item);
}
}
}
// Prevent selling items contained in certain equipped items (like battery cell in equipped headset or oxygen tank in equipped diving mask)
slots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.OuterClothes, InvSlotType.Headset };
foreach (InvSlotType slot in slots)
{
var index = character.Inventory.FindLimbSlot(slot);
if (character.Inventory.Items[index] is Item item &&
item.ContainedItems != null && item.AllowedSlots.Contains(InvSlotType.Any))
{ {
foreach (Item containedItem in item.ContainedItems) foreach (Item containedItem in item.ContainedItems)
{ {
if (containedItem != null) sellables.Remove(containedItem);
{
sellables.Remove(containedItem);
}
} }
} }
} }
@@ -123,7 +145,7 @@ namespace Barotrauma
var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab, quantity: item.Quantity); var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab, quantity: item.Quantity);
// check if the store can afford the item // check if the store can afford the item
if (location.StoreCurrentBalance < itemValue) { continue; } if (Location.StoreCurrentBalance < itemValue) { continue; }
var matchingItems = itemsInInventory.FindAll(i => i.Prefab == item.ItemPrefab); var matchingItems = itemsInInventory.FindAll(i => i.Prefab == item.ItemPrefab);
if (matchingItems.Count <= item.Quantity) if (matchingItems.Count <= item.Quantity)
@@ -147,7 +169,7 @@ namespace Barotrauma
} }
// Exchange money // Exchange money
campaign.Map.CurrentLocation.StoreCurrentBalance -= itemValue; Location.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue; campaign.Money += itemValue;
// Remove from the sell crate // Remove from the sell crate
@@ -15,11 +15,6 @@ namespace Barotrauma
{ {
partial class CrewManager partial class CrewManager
{ {
/// <summary>
/// How long the previously selected character waits doing nothing when switching to another character. Only affects idling.
/// </summary>
const float CharacterWaitOnSwitch = 10.0f;
private Point screenResolution; private Point screenResolution;
#region UI #region UI
@@ -63,6 +58,8 @@ namespace Barotrauma
private Sprite jobIndicatorBackground, previousOrderArrow, cancelIcon; private Sprite jobIndicatorBackground, previousOrderArrow, cancelIcon;
private const int MaxOrderIcons = 3;
#endregion #endregion
#region Constructors #region Constructors
@@ -363,9 +360,10 @@ namespace Barotrauma
UserData = character UserData = character
}; };
// "Padding" to prevent member-specific command button from overlapping job indicator
var commandButtonAbsoluteHeight = Math.Min(40.0f, 0.67f * background.Rect.Height); var commandButtonAbsoluteHeight = Math.Min(40.0f, 0.67f * background.Rect.Height);
var paddingRelativeWidth = 0.35f * commandButtonAbsoluteHeight / background.Rect.Width; var paddingRelativeWidth = 0.35f * commandButtonAbsoluteHeight / background.Rect.Width;
// "Padding" to prevent member-specific command button from overlapping job indicator
new GUIFrame(new RectTransform(new Vector2(paddingRelativeWidth, 1.0f), layoutGroup.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(paddingRelativeWidth, 1.0f), layoutGroup.RectTransform), style: null);
var jobIconBackground = new GUIImage( var jobIconBackground = new GUIImage(
@@ -392,7 +390,16 @@ namespace Barotrauma
}; };
} }
var nameRelativeWidth = 1.0f - paddingRelativeWidth - 3.7f * iconRelativeWidth; var nameRelativeWidth = 1.0f
// Start padding
- paddingRelativeWidth
// 5 icons (job, 3 orders, sound)
- (5 * 0.8f * iconRelativeWidth)
// Vertical line
- (0.1f * iconRelativeWidth)
// Spacing
- (7 * layoutGroup.RelativeSpacing);
var font = layoutGroup.Rect.Width < 150 ? GUI.SmallFont : GUI.Font; var font = layoutGroup.Rect.Width < 150 ? GUI.SmallFont : GUI.Font;
var nameBlock = new GUITextBlock( var nameBlock = new GUITextBlock(
new RectTransform( new RectTransform(
@@ -417,6 +424,7 @@ namespace Barotrauma
{ {
UserData = character UserData = character
}; };
// Only create a tooltip if the name doesn't fit the name block // Only create a tooltip if the name doesn't fit the name block
if (nameBlock.Text.EndsWith("...")) if (nameBlock.Text.EndsWith("..."))
{ {
@@ -432,6 +440,7 @@ namespace Barotrauma
}}; }};
} }
} }
if (IsSinglePlayer) if (IsSinglePlayer)
{ {
characterButton.OnClicked = CharacterClicked; characterButton.OnClicked = CharacterClicked;
@@ -443,7 +452,7 @@ namespace Barotrauma
} }
new GUIImage( new GUIImage(
new RectTransform(new Vector2(0.5f * iconRelativeWidth, 0.5f), layoutGroup.RectTransform), new RectTransform(new Vector2(0.1f * iconRelativeWidth, 0.5f), layoutGroup.RectTransform),
style: "VerticalLine") style: "VerticalLine")
{ {
CanBeFocused = false CanBeFocused = false
@@ -697,7 +706,7 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Displays the specified order in the crew UI next to the character. /// Displays the specified order in the crew UI next to the character.
/// </summary> /// </summary>
public void DisplayCharacterOrder(Character character, Order order, string option) public void AddCurrentOrderIcon(Character character, Order order, string option)
{ {
if (character == null) { return; } if (character == null) { return; }
@@ -706,34 +715,44 @@ namespace Barotrauma
if (characterFrame == null) { return; } if (characterFrame == null) { return; }
GUILayoutGroup layoutGroup = (GUILayoutGroup)characterFrame.FindChild(c => c is GUILayoutGroup); GUILayoutGroup layoutGroup = (GUILayoutGroup)characterFrame.FindChild(c => c is GUILayoutGroup);
var currentOrderComponent = GetCurrentOrderComponent(layoutGroup);
if (order != null) // Get the OrderInfo from the current order icon
var currentOrderIcon = GetCurrentOrderIcon(layoutGroup);
OrderInfo? currentOrderInfo = null;
if (currentOrderIcon?.UserData is OrderInfo)
{ {
var prevOrderComponent = GetPreviousOrderComponent(layoutGroup); currentOrderInfo = (OrderInfo)currentOrderIcon.UserData;
if (currentOrderComponent?.UserData is OrderInfo currentOrderInfo) // No need to recreate icons if the current order matches the new order
{ if (currentOrderInfo.Value.MatchesOrder(order, option)) { return; }
if (order.Identifier == currentOrderInfo.Order.Identifier && }
option == currentOrderInfo.OrderOption &&
order.TargetEntity == currentOrderInfo.Order.TargetEntity) { return; }
layoutGroup.RemoveChild(prevOrderComponent); // Remove the current order icon
DisplayPreviousCharacterOrder(character, layoutGroup, currentOrderInfo); layoutGroup.RemoveChild(currentOrderIcon);
}
else if (order.Identifier != dismissedOrderPrefab.Identifier && // Remove a previous order icon if it matches the new order
prevOrderComponent?.UserData is OrderInfo prevOrderInfo && // We don't want the same order as both a current order and a previous order
order.Identifier == prevOrderInfo.Order.Identifier && foreach (GUIComponent icon in GetPreviousOrderIcons(layoutGroup))
option == prevOrderInfo.OrderOption && {
order.TargetEntity == prevOrderInfo.Order.TargetEntity) if (icon?.UserData is OrderInfo info && info.MatchesOrder(order, option))
{ {
layoutGroup.RemoveChild(prevOrderComponent); layoutGroup.RemoveChild(icon);
break;
} }
} }
layoutGroup.RemoveChild(currentOrderComponent); // Create a new previous order icon from the current order icon's OrderInfo
if (currentOrderInfo.HasValue)
{
AddPreviousOrderIcon(character, layoutGroup, currentOrderInfo.Value);
}
if (order == null || order.Identifier == dismissedOrderPrefab.Identifier) { return; } if (order == null || order.Identifier == dismissedOrderPrefab.Identifier) { return; }
if (GetPreviousOrderIcons(layoutGroup).Count() >= MaxOrderIcons)
{
RemoveLastPreviousOrderIcon(layoutGroup);
}
var orderFrame = new GUIButton( var orderFrame = new GUIButton(
new RectTransform( new RectTransform(
layoutGroup.GetChildByUserData("job").RectTransform.RelativeSize, layoutGroup.GetChildByUserData("job").RectTransform.RelativeSize,
@@ -748,25 +767,31 @@ namespace Barotrauma
return true; return true;
} }
}; };
CreateNodeIcon(orderFrame.RectTransform, order.SymbolSprite, order.Color, tooltip: order.Name); CreateNodeIcon(orderFrame.RectTransform, order.SymbolSprite, order.Color, tooltip: order.Name);
new GUIImage(
new RectTransform(Vector2.One, orderFrame.RectTransform), new GUIImage(new RectTransform(Vector2.One, orderFrame.RectTransform), cancelIcon, scaleToFit: true)
cancelIcon,
scaleToFit: true)
{ {
CanBeFocused = false, CanBeFocused = false,
UserData = "cancel", UserData = "cancel",
Visible = false Visible = false
}; };
orderFrame.RectTransform.RepositionChildInHierarchy(4); orderFrame.RectTransform.RepositionChildInHierarchy(4);
characterFrame.SetAsFirstChild();
} }
private void DisplayPreviousCharacterOrder(Character character, GUILayoutGroup characterComponent, OrderInfo orderInfo) private void AddPreviousOrderIcon(Character character, GUILayoutGroup characterComponent, OrderInfo orderInfo)
{ {
if (orderInfo.Order == null || orderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; } if (orderInfo.Order == null || orderInfo.Order.Identifier == dismissedOrderPrefab.Identifier) { return; }
var maxPreviousOrderIcons = GetCurrentOrderIcon(characterComponent) != null ? MaxOrderIcons - 1 : MaxOrderIcons;
if (GetPreviousOrderIcons(characterComponent).Count() >= maxPreviousOrderIcons)
{
RemoveLastPreviousOrderIcon(characterComponent);
}
var previousOrderInfo = new OrderInfo(orderInfo); var previousOrderInfo = new OrderInfo(orderInfo);
var prevOrderFrame = new GUIButton( var prevOrderFrame = new GUIButton(
new RectTransform( new RectTransform(
characterComponent.GetChildByUserData("job").RectTransform.RelativeSize, characterComponent.GetChildByUserData("job").RectTransform.RelativeSize,
@@ -786,17 +811,20 @@ namespace Barotrauma
var prevOrderIconFrame = new GUIFrame( var prevOrderIconFrame = new GUIFrame(
new RectTransform(new Vector2(0.8f), prevOrderFrame.RectTransform, anchor: Anchor.BottomLeft), new RectTransform(new Vector2(0.8f), prevOrderFrame.RectTransform, anchor: Anchor.BottomLeft),
style: null); style: null);
CreateNodeIcon( CreateNodeIcon(
prevOrderIconFrame.RectTransform, prevOrderIconFrame.RectTransform,
previousOrderInfo.Order.SymbolSprite, previousOrderInfo.Order.SymbolSprite,
previousOrderInfo.Order.Color, previousOrderInfo.Order.Color,
tooltip: previousOrderInfo.Order.Name); tooltip: previousOrderInfo.Order.Name);
foreach (GUIComponent c in prevOrderIconFrame.Children) foreach (GUIComponent c in prevOrderIconFrame.Children)
{ {
c.HoverColor = c.Color; c.HoverColor = c.Color;
c.PressedColor = c.Color; c.PressedColor = c.Color;
c.SelectedColor = c.Color; c.SelectedColor = c.Color;
} }
new GUIImage( new GUIImage(
new RectTransform(new Vector2(0.8f), prevOrderFrame.RectTransform, anchor: Anchor.TopRight), new RectTransform(new Vector2(0.8f), prevOrderFrame.RectTransform, anchor: Anchor.TopRight),
previousOrderArrow, previousOrderArrow,
@@ -804,19 +832,63 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
prevOrderFrame.RectTransform.RepositionChildInHierarchy(GetCurrentOrderComponent(characterComponent) != null ? 5 : 4);
var positionInHierarchy = GetCurrentOrderIcon(characterComponent) != null ? 5 : 4;
prevOrderFrame.RectTransform.RepositionChildInHierarchy(positionInHierarchy);
} }
private GUIComponent GetCurrentOrderComponent(GUILayoutGroup characterComponent) private void AddOldPreviousOrderIcons(Character character, GUILayoutGroup oldCharacterComponent)
{ {
return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder"); var prevOrderIcons = GetPreviousOrderIcons(oldCharacterComponent).ToList();
if (prevOrderIcons.None()) { return; }
if (prevOrderIcons.Count() > 1)
{
prevOrderIcons.Sort((x, y) => oldCharacterComponent.GetChildIndex(x).CompareTo(oldCharacterComponent.GetChildIndex(y)));
prevOrderIcons.Reverse();
}
if (crewList.Content.Children.FirstOrDefault(c => c?.UserData == character)?.GetChild<GUILayoutGroup>() is GUILayoutGroup newCharacterComponent)
{
foreach (GUIComponent icon in prevOrderIcons)
{
if (icon.UserData is OrderInfo orderInfo)
{
AddPreviousOrderIcon(character, newCharacterComponent, orderInfo);
}
}
}
} }
private GUIComponent GetPreviousOrderComponent(GUILayoutGroup characterComponent) private void RemoveLastPreviousOrderIcon(GUILayoutGroup characterComponent)
{ {
return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder"); var prevOrderIcons = GetPreviousOrderIcons(characterComponent);
if (prevOrderIcons.None()) { return; }
if (prevOrderIcons.Count() == 1)
{
characterComponent.RemoveChild(prevOrderIcons.First());
}
else
{
int highestIndex = 0;
GUIComponent oldestPreviousOrderIcon = null;
foreach (GUIComponent icon in prevOrderIcons)
{
int i = characterComponent.GetChildIndex(icon);
if (i > highestIndex || oldestPreviousOrderIcon == null)
{
highestIndex = i;
oldestPreviousOrderIcon = icon;
}
}
characterComponent.RemoveChild(oldestPreviousOrderIcon);
}
} }
private GUIComponent GetCurrentOrderIcon(GUILayoutGroup characterComponent) =>
characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "currentorder");
private IEnumerable<GUIComponent> GetPreviousOrderIcons(GUILayoutGroup characterComponent) =>
characterComponent?.FindChildren(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
#endregion #endregion
#region Updating and drawing the UI #region Updating and drawing the UI
@@ -854,7 +926,7 @@ namespace Barotrauma
{ {
if (IsSinglePlayer || client == null || (GameMain.NetworkMember?.ConnectedClients?.All(match => match != client) ?? true)) { return; } if (IsSinglePlayer || client == null || (GameMain.NetworkMember?.ConnectedClients?.All(match => match != client) ?? true)) { return; }
contextMenu = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.12f), GUI.Canvas) { ScreenSpaceOffset = mousePos }, style: "GUIToolTip") { UserData = client }; contextMenu = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.15f), GUI.Canvas) { ScreenSpaceOffset = mousePos }, style: "GUIToolTip") { UserData = client };
var nameLabel = new GUITextBlock(new RectTransform(new Vector2(1f, 0.2f), contextMenu.RectTransform), client.Name, font: GUI.SubHeadingFont) var nameLabel = new GUITextBlock(new RectTransform(new Vector2(1f, 0.2f), contextMenu.RectTransform), client.Name, font: GUI.SubHeadingFont)
{ {
@@ -864,7 +936,9 @@ namespace Barotrauma
var optionsList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), contextMenu.RectTransform, Anchor.BottomLeft), style: null) var optionsList = new GUIListBox(new RectTransform(new Vector2(1f, 0.8f), contextMenu.RectTransform, Anchor.BottomLeft), style: null)
{ {
Padding = new Vector4(4, 0, 4, 4) Padding = new Vector4(4, 0, 4, 4),
AutoHideScrollBar = false,
ScrollBarVisible = false
}; };
bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized, bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized,
@@ -886,6 +960,13 @@ namespace Barotrauma
UserData = "steam" UserData = "steam"
}; };
new GUITextBlock(new RectTransform(Point.Zero, parent), TextManager.Get("moderationmenu.userdetails"), font: GUI.SmallFont)
{
Padding = new Vector4(4),
Enabled = true,
UserData = "user"
};
new GUITextBlock(new RectTransform(Point.Zero, parent), TextManager.Get("permissions"), font: GUI.SmallFont) new GUITextBlock(new RectTransform(Point.Zero, parent), TextManager.Get("permissions"), font: GUI.SmallFont)
{ {
Padding = new Vector4(4), Padding = new Vector4(4),
@@ -971,6 +1052,9 @@ namespace Barotrauma
case "ban": case "ban":
GameMain.Client?.CreateKickReasonPrompt(client.Name, true); GameMain.Client?.CreateKickReasonPrompt(client.Name, true);
break; break;
case "user":
GameMain.NetLobbyScreen?.SelectPlayer(client);
break;
} }
contextMenu = null; contextMenu = null;
return true; return true;
@@ -983,7 +1067,11 @@ namespace Barotrauma
{ {
if (client == null ) { return; } if (client == null ) { return; }
subContextMenu = new GUIListBox(new RectTransform(new Vector2(0.1f, 0.1f), GUI.Canvas) { ScreenSpaceOffset = pos }, style: "GUIToolTip"); subContextMenu = new GUIListBox(new RectTransform(new Vector2(0.1f, 0.1f), GUI.Canvas) { ScreenSpaceOffset = pos }, style: "GUIToolTip")
{
AutoHideScrollBar = false,
ScrollBarVisible = false
};
foreach (var rank in PermissionPreset.List) foreach (var rank in PermissionPreset.List)
{ {
@@ -1073,11 +1161,7 @@ namespace Barotrauma
{ {
if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; } if (!(c.UserData is Character character) || character.IsDead || character.Removed) { continue; }
AddCharacter(character); AddCharacter(character);
if (GetPreviousOrderComponent(c.GetChild<GUILayoutGroup>())?.UserData is OrderInfo prevInfo && AddOldPreviousOrderIcons(character, c.GetChild<GUILayoutGroup>());
crewList.Content.Children.FirstOrDefault(c => c?.UserData == character)?.GetChild<GUILayoutGroup>() is GUILayoutGroup newLayoutGroup)
{
DisplayPreviousCharacterOrder(character, newLayoutGroup, prevInfo);
}
} }
} }
@@ -1112,9 +1196,10 @@ namespace Barotrauma
if (!AllowCharacterSwitch) { return; } if (!AllowCharacterSwitch) { return; }
//make the previously selected character wait in place for some time //make the previously selected character wait in place for some time
//(so they don't immediately start idling and walking away from their station) //(so they don't immediately start idling and walking away from their station)
if (Character.Controlled?.AIController?.ObjectiveManager != null) var aiController = Character.Controlled?.AIController;
if (aiController != null)
{ {
Character.Controlled.AIController.ObjectiveManager.WaitTimer = CharacterWaitOnSwitch; aiController.Reset();
} }
DisableCommandUI(); DisableCommandUI();
Character.Controlled = character; Character.Controlled = character;
@@ -1419,7 +1504,7 @@ namespace Barotrauma
} }
if (child.FindChild(c => c is GUILayoutGroup) is GUILayoutGroup layoutGroup) if (child.FindChild(c => c is GUILayoutGroup) is GUILayoutGroup layoutGroup)
{ {
if (GetCurrentOrderComponent(layoutGroup) is GUIComponent orderButton && if (GetCurrentOrderIcon(layoutGroup) is GUIComponent orderButton &&
orderButton.GetChildByUserData("colorsource") is GUIComponent orderIcon && orderButton.GetChildByUserData("colorsource") is GUIComponent orderIcon &&
orderButton.GetChildByUserData("cancel") is GUIComponent cancelIcon) orderButton.GetChildByUserData("cancel") is GUIComponent cancelIcon)
{ {
@@ -1504,8 +1589,8 @@ namespace Barotrauma
private Hull hullContext; private Hull hullContext;
private bool isContextual; private bool isContextual;
private readonly List<Order> contextualOrders = new List<Order>(); private readonly List<Order> contextualOrders = new List<Order>();
private Point shorcutCenterNodeOffset; private Point shortcutCenterNodeOffset;
private const int maxShorcutNodeCount = 4; private const int maxShortcutNodeCount = 4;
private bool WasCommandInterfaceDisabledThisUpdate { get; set; } private bool WasCommandInterfaceDisabledThisUpdate { get; set; }
private bool CanIssueOrders private bool CanIssueOrders
@@ -1688,7 +1773,7 @@ namespace Barotrauma
returnNodeMargin = returnNodeSize.X * 0.5f; returnNodeMargin = returnNodeSize.X * 0.5f;
nodeDistance = (int)(150 * GUI.Scale); nodeDistance = (int)(150 * GUI.Scale);
shorcutCenterNodeOffset = new Point(0, (int)(1.25f * nodeDistance)); shortcutCenterNodeOffset = new Point(0, (int)(1.25f * nodeDistance));
} }
private List<OrderCategory> GetAvailableCategories() private List<OrderCategory> GetAvailableCategories()
@@ -1972,7 +2057,7 @@ namespace Barotrauma
shortcutNodes.Clear(); shortcutNodes.Clear();
if (shortcutNodes.Count < maxShorcutNodeCount && sub.GetItems(false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor) if (shortcutNodes.Count < maxShortcutNodeCount && sub.GetItems(false).Find(i => i.HasTag("reactor") && !i.NonInteractable)?.GetComponent<Reactor>() is Reactor reactor)
{ {
var reactorOutput = -reactor.CurrPowerConsumption; var reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
@@ -1990,7 +2075,7 @@ namespace Barotrauma
// TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it // TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order // --> Create shortcut node for Steer order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) && if (shortcutNodes.Count < maxShortcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) &&
sub.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && sub.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage) nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{ {
@@ -2000,7 +2085,7 @@ namespace Barotrauma
// If player is not a security officer AND invaders are reported // If player is not a security officer AND invaders are reported
// --> Create shorcut node for Fight Intruders order // --> Create shorcut node for Fight Intruders order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) && if (shortcutNodes.Count < maxShortcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) &&
(Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders))) (Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders)))
{ {
shortcutNodes.Add( shortcutNodes.Add(
@@ -2009,7 +2094,7 @@ namespace Barotrauma
// If player is not a mechanic AND a breach has been reported // If player is not a mechanic AND a breach has been reported
// --> Create shorcut node for Fix Leaks order // --> Create shorcut node for Fix Leaks order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) && if (shortcutNodes.Count < maxShortcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) &&
(Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach))) (Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)))
{ {
shortcutNodes.Add( shortcutNodes.Add(
@@ -2018,7 +2103,7 @@ namespace Barotrauma
// If player is not an engineer AND broken devices have been reported // If player is not an engineer AND broken devices have been reported
// --> Create shortcut node for Repair Damaged Systems order // --> Create shortcut node for Repair Damaged Systems order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) && if (shortcutNodes.Count < maxShortcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
(Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices))) (Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)))
{ {
shortcutNodes.Add( shortcutNodes.Add(
@@ -2027,13 +2112,13 @@ namespace Barotrauma
// If fire is reported // If fire is reported
// --> Create shortcut node for Extinguish Fires order // --> Create shortcut node for Extinguish Fires order
if (shortcutNodes.Count < maxShorcutNodeCount && ActiveOrders.Any(o=> o.First.Prefab == Order.GetPrefab("reportfire"))) if (shortcutNodes.Count < maxShortcutNodeCount && ActiveOrders.Any(o=> o.First.Prefab == Order.GetPrefab("reportfire")))
{ {
shortcutNodes.Add( shortcutNodes.Add(
CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"), -1)); CreateOrderNode(shortcutNodeSize, null, Point.Zero, Order.GetPrefab("extinguishfires"), -1));
} }
if (shortcutNodes.Count < maxShorcutNodeCount && characterContext?.Info?.Job?.Prefab?.AppropriateOrders != null) if (shortcutNodes.Count < maxShortcutNodeCount && characterContext?.Info?.Job?.Prefab?.AppropriateOrders != null)
{ {
foreach (string orderIdentifier in characterContext.Info.Job.Prefab.AppropriateOrders) foreach (string orderIdentifier in characterContext.Info.Job.Prefab.AppropriateOrders)
{ {
@@ -2046,7 +2131,7 @@ namespace Barotrauma
{ {
shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, orderPrefab, -1)); shortcutNodes.Add(CreateOrderNode(shortcutNodeSize, null, Point.Zero, orderPrefab, -1));
} }
if (shortcutNodes.Count >= maxShorcutNodeCount) { break; } if (shortcutNodes.Count >= maxShortcutNodeCount) { break; }
} }
} }
} }
@@ -2063,7 +2148,7 @@ namespace Barotrauma
c.PressedColor = c.Color; c.PressedColor = c.Color;
c.SelectedColor = c.Color; c.SelectedColor = c.Color;
} }
shortcutCenterNode.RectTransform.MoveOverTime(shorcutCenterNodeOffset, CommandNodeAnimDuration); shortcutCenterNode.RectTransform.MoveOverTime(shortcutCenterNodeOffset, CommandNodeAnimDuration);
var nodeCountForCalculations = shortcutNodes.Count * 2 + 2; var nodeCountForCalculations = shortcutNodes.Count * 2 + 2;
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 0.75f * nodeDistance, nodeCountForCalculations); var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 0.75f * nodeDistance, nodeCountForCalculations);
@@ -2071,7 +2156,7 @@ namespace Barotrauma
for (int i = 0; i < shortcutNodes.Count; i++) for (int i = 0; i < shortcutNodes.Count; i++)
{ {
shortcutNodes[i].RectTransform.Parent = commandFrame.RectTransform; shortcutNodes[i].RectTransform.Parent = commandFrame.RectTransform;
shortcutNodes[i].RectTransform.MoveOverTime(shorcutCenterNodeOffset + offsets[firstOffsetIndex - i].ToPoint(), CommandNodeAnimDuration); shortcutNodes[i].RectTransform.MoveOverTime(shortcutCenterNodeOffset + offsets[firstOffsetIndex - i].ToPoint(), CommandNodeAnimDuration);
} }
} }
@@ -2100,6 +2185,8 @@ namespace Barotrauma
{ {
if (contextualOrders.None()) if (contextualOrders.None())
{ {
string orderIdentifier;
// Check if targeting an item or a hull // Check if targeting an item or a hull
if (itemContext != null && !itemContext.NonInteractable) if (itemContext != null && !itemContext.NonInteractable)
{ {
@@ -2114,7 +2201,7 @@ namespace Barotrauma
} }
// If targeting a periscope connected to a turret, show the 'operateweapons' order // If targeting a periscope connected to a turret, show the 'operateweapons' order
var orderIdentifier = "operateweapons"; orderIdentifier = "operateweapons";
var operateWeaponsPrefab = Order.GetPrefab(orderIdentifier); var operateWeaponsPrefab = Order.GetPrefab(orderIdentifier);
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller)) if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller))
{ {
@@ -2123,9 +2210,9 @@ namespace Barotrauma
if (turret != null) { contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled)); } if (turret != null) { contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled)); }
} }
// If targeting a repairable item, show the 'repairsystems' order // If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order
orderIdentifier = "repairsystems"; orderIdentifier = "repairsystems";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Repairables.Any()) if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Repairables.Any(r => itemContext.ConditionPercentage < r.RepairThreshold))
{ {
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, null, Character.Controlled)); contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, null, Character.Controlled));
if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical")))) if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical"))))
@@ -2138,13 +2225,6 @@ namespace Barotrauma
} }
} }
// Always show the 'wait' order if there are other crew members alive
orderIdentifier = "wait";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && characters.Any(c => c != Character.Controlled))
{
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, null, Character.Controlled));
}
// Remove the 'pumpwater' order if the target pump is auto-controlled (as it will immediately overwrite the work done by the bot) // Remove the 'pumpwater' order if the target pump is auto-controlled (as it will immediately overwrite the work done by the bot)
orderIdentifier = "pumpwater"; orderIdentifier = "pumpwater";
if (contextualOrders.FirstOrDefault(o => o.Identifier.Equals(orderIdentifier)) is Order o && if (contextualOrders.FirstOrDefault(o => o.Identifier.Equals(orderIdentifier)) is Order o &&
@@ -2152,20 +2232,34 @@ namespace Barotrauma
{ {
if (pump.IsAutoControlled) { contextualOrders.Remove(o); } if (pump.IsAutoControlled) { contextualOrders.Remove(o); }
} }
if (contextualOrders.None())
{
// If there are other crew members alive and there are no other contextual orders available, show the 'wait' order
orderIdentifier = "wait";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && characters.Any(c => c != Character.Controlled))
{
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, null, Character.Controlled));
}
}
} }
else if(hullContext != null) else if(hullContext != null)
{ {
contextualOrders.Add(new Order(Order.GetPrefab("fixleaks"), hullContext, null, Character.Controlled)); contextualOrders.Add(new Order(Order.GetPrefab("fixleaks"), hullContext, null, Character.Controlled));
} }
// Show the 'follow' and 'dismissed' orders if there are other crew members alive
if (characters.Any(c => c != Character.Controlled)) if (characters.Any(c => c != Character.Controlled))
{ {
var orderIdentifier = "follow"; // Show 'follow' order only when there are no orders of other categories available
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) if (contextualOrders.None(o => o.Category != OrderCategory.Movement))
{ {
contextualOrders.Add(Order.GetPrefab(orderIdentifier)); orderIdentifier = "follow";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)))
{
contextualOrders.Add(Order.GetPrefab(orderIdentifier));
}
} }
// Show 'dismissed' order only when there are crew members with active orders // Show 'dismissed' order only when there are crew members with active orders
orderIdentifier = "dismissed"; orderIdentifier = "dismissed";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) &&
@@ -2192,7 +2286,7 @@ namespace Barotrauma
if (Order.PrefabList.Any(o => item.HasTag(o.ItemIdentifiers))) { return true; } if (Order.PrefabList.Any(o => item.HasTag(o.ItemIdentifiers))) { return true; }
if (Order.PrefabList.Any(o => o.ItemComponentType != null && item.Components.Any(c => c?.GetType() == o.ItemComponentType))) { return true; } if (Order.PrefabList.Any(o => o.ItemComponentType != null && item.Components.Any(c => c?.GetType() == o.ItemComponentType))) { return true; }
if (item.Repairables.Any()) { return true; } if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold)) { return true; }
var operateWeaponsPrefab = Order.GetPrefab("operateweapons"); var operateWeaponsPrefab = Order.GetPrefab("operateweapons");
return item.Components.Any(c => c is Controller) && return item.Components.Any(c => c is Controller) &&
(item.GetConnectedComponents<Turret>().Any(c => operateWeaponsPrefab.ItemIdentifiers.Contains(c.Item.Prefab.Identifier)) || (item.GetConnectedComponents<Turret>().Any(c => operateWeaponsPrefab.ItemIdentifiers.Contains(c.Item.Prefab.Identifier)) ||
@@ -2209,8 +2303,30 @@ namespace Barotrauma
node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration); node.RectTransform.MoveOverTime(offset, CommandNodeAnimDuration);
if (checkIfOrderCanBeHeard && !disableNode) { disableNode = !CanSomeoneHearCharacter(); } if (checkIfOrderCanBeHeard && !disableNode)
var mustSetOptionOrTarget = order.HasOptions || (order.MustSetTarget && itemContext == null); {
disableNode = !CanSomeoneHearCharacter();
}
var mustSetOptionOrTarget = order.HasOptions;
Item orderTargetEntity = null;
// If the order doesn't have options, but must set a target,
// we have to check if there's only one possible target available
// so we know to directly target that with the order
if (!mustSetOptionOrTarget && order.MustSetTarget && itemContext == null)
{
var matchingItems = order.GetMatchingItems(GetTargetSubmarine(), true);
if (matchingItems.Count > 1)
{
mustSetOptionOrTarget = true;
}
else
{
orderTargetEntity = matchingItems.FirstOrDefault();
}
}
node.OnClicked = (button, userData) => node.OnClicked = (button, userData) =>
{ {
if (disableNode || !CanIssueOrders) { return false; } if (disableNode || !CanIssueOrders) { return false; }
@@ -2225,6 +2341,10 @@ namespace Barotrauma
} }
else else
{ {
if (orderTargetEntity != null)
{
o = new Order(o.Prefab, orderTargetEntity, orderTargetEntity.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: order.OrderGiver);
}
SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o), o, null, Character.Controlled); SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o), o, null, Character.Controlled);
DisableCommandUI(); DisableCommandUI();
} }
@@ -2382,6 +2502,10 @@ namespace Barotrauma
Sprite icon = null; Sprite icon = null;
order.MinimapIcons?.TryGetValue(item.Prefab.Identifier, out icon); order.MinimapIcons?.TryGetValue(item.Prefab.Identifier, out icon);
if (item.Prefab.MinimapIcon != null)
{
icon = item.Prefab.MinimapIcon;
}
var colorMultiplier = characters.Any(c => c.CurrentOrder != null && var colorMultiplier = characters.Any(c => c.CurrentOrder != null &&
c.CurrentOrder.Identifier == userData.Item1.Identifier && c.CurrentOrder.Identifier == userData.Item1.Identifier &&
c.CurrentOrder.TargetEntity == userData.Item1.TargetEntity) ? 0.5f : 1f; c.CurrentOrder.TargetEntity == userData.Item1.TargetEntity) ? 0.5f : 1f;
@@ -2601,7 +2725,7 @@ namespace Barotrauma
var availableNodePositions = 20; var availableNodePositions = 20;
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 2.7f * this.nodeDistance, availableNodePositions, var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, 2.7f * this.nodeDistance, availableNodePositions,
firstAngle: MathHelper.ToRadians(-90f - ((extraOptionCharacters.Count - 1) * 0.5f * (360f / availableNodePositions)))); firstAngle: MathHelper.ToRadians(-90f - ((extraOptionCharacters.Count - 1) * 0.5f * (360f / availableNodePositions))));
for (int i = 0; i < extraOptionCharacters.Count; i++) for (int i = 0; i < extraOptionCharacters.Count && i < availableNodePositions; i++)
{ {
CreateAssignmentNode(userData as Tuple<Order, string>, extraOptionCharacters[i], offsets[i].ToPoint(), -1, nameLabelScale: 1.15f); CreateAssignmentNode(userData as Tuple<Order, string>, extraOptionCharacters[i], offsets[i].ToPoint(), -1, nameLabelScale: 1.15f);
} }
@@ -2786,7 +2910,7 @@ namespace Barotrauma
{ {
bearing = GetBearing( bearing = GetBearing(
centerNode.RectTransform.AnimTargetPos.ToVector2(), centerNode.RectTransform.AnimTargetPos.ToVector2(),
shorcutCenterNodeOffset.ToVector2()); shortcutCenterNodeOffset.ToVector2());
} }
return nodeCount % 2 > 0 ? return nodeCount % 2 > 0 ?
MathHelper.ToRadians(bearing + 360.0f / nodeCount / 2) : MathHelper.ToRadians(bearing + 360.0f / nodeCount / 2) :
@@ -1,4 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@@ -30,11 +31,6 @@ namespace Barotrauma
protected set; protected set;
} }
public override bool Paused
{
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
}
private bool showCampaignUI; private bool showCampaignUI;
private bool wasChatBoxOpen; private bool wasChatBoxOpen;
public bool ShowCampaignUI public bool ShowCampaignUI
@@ -165,7 +161,6 @@ namespace Barotrauma
} }
break; break;
case TransitionType.LeaveLocation: case TransitionType.LeaveLocation:
// not sure why this can happen at an outpost but it apparently can in multiplayer
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]"); buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI; endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
break; break;
@@ -195,14 +190,26 @@ namespace Barotrauma
if (endRoundButton.Visible) if (endRoundButton.Visible)
{ {
if (!AllowedToEndRound()) { buttonText = TextManager.Get("map"); }
endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5); endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5);
if (endRoundButton.Text != buttonText) if (endRoundButton.Text != buttonText)
{ {
endRoundButton.ToolTip = buttonText; endRoundButton.ToolTip = buttonText;
} }
endRoundButton.Enabled = AllowedToEndRound(); if (Character.Controlled?.ViewTarget is Item item)
{
Turret turret = item.GetComponent<Turret>();
endRoundButton.RectTransform.ScreenSpaceOffset = turret == null ? Point.Zero : new Point(0, (int)(turret.UIElementHeight * 1.25f));
}
else if (Character.Controlled?.CharacterHealth?.SuicideButton?.Visible ?? false)
{
endRoundButton.RectTransform.ScreenSpaceOffset = new Point(0, Character.Controlled.CharacterHealth.SuicideButton.Rect.Height);
}
else
{
endRoundButton.RectTransform.ScreenSpaceOffset = Point.Zero;
}
} }
endRoundButton.DrawManually(spriteBatch); endRoundButton.DrawManually(spriteBatch);
} }
@@ -216,7 +223,14 @@ namespace Barotrauma
{ {
await Task.Yield(); await Task.Yield();
Rand.ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId; Rand.ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror); try
{
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror);
}
catch (Exception e)
{
roundSummaryScreen.LoadException = e;
}
Rand.ThreadId = 0; Rand.ThreadId = 0;
}); });
TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) => TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
@@ -3,7 +3,7 @@ using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -13,6 +13,11 @@ namespace Barotrauma
{ {
public bool SuppressStateSending = false; public bool SuppressStateSending = false;
public override bool Paused
{
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
}
private UInt16 pendingSaveID = 1; private UInt16 pendingSaveID = 1;
public UInt16 PendingSaveID public UInt16 PendingSaveID
{ {
@@ -100,7 +105,7 @@ namespace Barotrauma
}; };
int buttonHeight = (int)(GUI.Scale * 40); int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(200); int buttonWidth = GUI.IntScale(450);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance), endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
@@ -209,6 +214,7 @@ namespace Barotrauma
Character.Controlled = null; Character.Controlled = null;
prevControlled.ClearInputs(); prevControlled.ClearInputs();
} }
GameMain.GameScreen.Cam.Freeze = true;
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration); timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -357,7 +363,7 @@ namespace Barotrauma
base.Update(deltaTime); base.Update(deltaTime);
if (PlayerInput.RightButtonClicked() || if (PlayerInput.SecondaryMouseButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
@@ -403,6 +409,14 @@ namespace Barotrauma
if (CampaignUI == null) { InitCampaignUI(); } if (CampaignUI == null) { InitCampaignUI(); }
} }
else
{
var transitionType = GetAvailableTransition(out _, out _);
if (transitionType == TransitionType.None && CampaignUI?.SelectedTab == InteractionType.Map)
{
ShowCampaignUI = false;
}
}
} }
public override void End(TransitionType transitionType = TransitionType.None) public override void End(TransitionType transitionType = TransitionType.None)
{ {
@@ -528,6 +542,7 @@ namespace Barotrauma
UInt16 currentLocIndex = msg.ReadUInt16(); UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16(); UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte(); byte selectedMissionIndex = msg.ReadByte();
bool allowDebugTeleport = msg.ReadBoolean();
float? reputation = null; float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); } if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
@@ -640,6 +655,7 @@ namespace Barotrauma
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex); campaign.Map.SelectMission(selectedMissionIndex);
campaign.Map.AllowDebugTeleport = allowDebugTeleport;
campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems); campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems);
campaign.CargoManager.SetPurchasedItems(purchasedItems); campaign.CargoManager.SetPurchasedItems(purchasedItems);
campaign.CargoManager.SetSoldItems(soldItems); campaign.CargoManager.SetSoldItems(soldItems);
@@ -11,6 +11,35 @@ namespace Barotrauma
{ {
class SinglePlayerCampaign : CampaignMode class SinglePlayerCampaign : CampaignMode
{ {
public override bool Paused
{
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition") || ShowCampaignUI && CampaignUI.SelectedTab == InteractionType.Map; }
}
public override void UpdateWhilePaused(float deltaTime)
{
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition") || gameOver) { return; }
if (PlayerInput.RightButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{
ShowCampaignUI = false;
if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary &&
roundSummary.ContinueButton != null &&
roundSummary.ContinueButton.Visible)
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
}
if (CrewManager.ChatBox != null)
{
CrewManager.ChatBox.Update(deltaTime);
}
CrewManager.UpdateReports();
}
private float endTimer; private float endTimer;
private bool savedOnStart; private bool savedOnStart;
@@ -124,7 +153,7 @@ namespace Barotrauma
private void InitUI() private void InitUI()
{ {
int buttonHeight = (int)(GUI.Scale * 40); int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(200); int buttonWidth = GUI.IntScale(450);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance), endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
@@ -357,10 +386,11 @@ namespace Barotrauma
break; break;
case TransitionType.ProgressToNextLocation: case TransitionType.ProgressToNextLocation:
Map.MoveToNextLocation(); Map.MoveToNextLocation();
Map.ProgressWorld();
break; break;
} }
Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center, transitionType == TransitionType.LeaveLocation ? Alignment.BottomCenter : Alignment.Center,
fadeOut: false, fadeOut: false,
@@ -383,6 +413,8 @@ namespace Barotrauma
//-------------------------------------- //--------------------------------------
bool save = false;
if (success) if (success)
{ {
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub)) if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
@@ -398,21 +430,33 @@ namespace Barotrauma
} }
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine); GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
if (PendingSubmarineSwitch != null)
{
SubmarineInfo previousSub = GameMain.GameSession.SubmarineInfo;
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
PendingSubmarineSwitch = null;
for (int i = 0; i < GameMain.GameSession.OwnedSubmarines.Count; i++)
{
if (GameMain.GameSession.OwnedSubmarines[i].Name == previousSub.Name)
{
GameMain.GameSession.OwnedSubmarines[i] = previousSub;
break;
}
}
}
SaveUtil.SaveGame(GameMain.GameSession.SavePath); SaveUtil.SaveGame(GameMain.GameSession.SavePath);
} }
else else
{ {
PendingSubmarineSwitch = null;
EnableRoundSummaryGameOverState(); EnableRoundSummaryGameOverState();
} }
//-------------------------------------- //--------------------------------------
if (PendingSubmarineSwitch != null)
{
GameMain.GameSession.SubmarineInfo = PendingSubmarineSwitch;
PendingSubmarineSwitch = null;
}
SelectSummaryScreen(roundSummary, newLevel, mirror, () => SelectSummaryScreen(roundSummary, newLevel, mirror, () =>
{ {
GameMain.GameScreen.Select(); GameMain.GameScreen.Select();
@@ -473,7 +517,7 @@ namespace Barotrauma
base.Update(deltaTime); base.Update(deltaTime);
if (PlayerInput.RightButtonClicked() || if (PlayerInput.SecondaryMouseButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
ShowCampaignUI = false; ShowCampaignUI = false;
@@ -581,7 +625,7 @@ namespace Barotrauma
} }
else if (transitionType == TransitionType.ProgressToNextEmptyLocation) else if (transitionType == TransitionType.ProgressToNextEmptyLocation)
{ {
Map.SetLocation(Map.Locations.IndexOf(Level.Loaded.EndLocation)); Map.SetLocation(Map.Locations.IndexOf(Level.Loaded.EndLocation ?? Map.CurrentLocation));
} }
var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub); var subsToLeaveBehind = GetSubsToLeaveBehind(leavingSub);
@@ -1,5 +1,9 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
namespace Barotrauma namespace Barotrauma
{ {
@@ -7,6 +11,17 @@ namespace Barotrauma
{ {
public Action OnRoundEnd; public Action OnRoundEnd;
public bool SpawnOutpost;
public OutpostGenerationParams OutpostParams;
public LocationType OutpostType;
public EventPrefab TriggeredEvent;
private List<Event> scriptedEvent;
private GUIButton createEventButton;
public TestGameMode(GameModePreset preset) : base(preset) public TestGameMode(GameModePreset preset) : base(preset)
{ {
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs) foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
@@ -24,11 +39,96 @@ namespace Barotrauma
base.Start(); base.Start();
CrewManager.InitSinglePlayerRound(); CrewManager.InitSinglePlayerRound();
if (SpawnOutpost)
{
GenerateOutpost(Submarine.MainSub);
}
if (TriggeredEvent != null)
{
scriptedEvent = new List<Event> { TriggeredEvent.CreateInstance() };
GameMain.GameSession.EventManager.PinnedEvent = scriptedEvent.Last();
createEventButton = new GUIButton(new RectTransform(new Point(128, 64), GUI.Canvas, Anchor.TopCenter) { ScreenSpaceOffset = new Point(0, 32) }, TextManager.Get("create"))
{
OnClicked = delegate
{
scriptedEvent.Add(TriggeredEvent.CreateInstance());
GameMain.GameSession.EventManager.PinnedEvent = scriptedEvent.Last();
return true;
}
};
}
}
public override void AddToGUIUpdateList()
{
base.AddToGUIUpdateList();
createEventButton?.AddToGUIUpdateList();
} }
public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{ {
GameMain.GameSession.EventManager.PinnedEvent = null;
OnRoundEnd?.Invoke(); OnRoundEnd?.Invoke();
} }
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (scriptedEvent != null)
{
foreach (Event sEvent in scriptedEvent.Where(sEvent => !sEvent.IsFinished))
{
sEvent.Update(deltaTime);
}
}
}
private void GenerateOutpost(Submarine submarine)
{
Submarine outpost = OutpostGenerator.Generate(OutpostParams ?? OutpostGenerationParams.Params.GetRandom(), OutpostType ?? LocationType.List.GetRandom());
outpost.SetPosition(Vector2.Zero);
float closestDistance = 0.0f;
DockingPort myPort = null, outPostPort = null;
foreach (DockingPort port in DockingPort.List)
{
if (port.IsHorizontal || port.Docked) { continue; }
if (port.Item.Submarine == outpost)
{
outPostPort = port;
continue;
}
if (port.Item.Submarine != submarine) { continue; }
//the submarine port has to be at the top of the sub
if (port.Item.WorldPosition.Y < submarine.WorldPosition.Y) { continue; }
float dist = Vector2.DistanceSquared(port.Item.WorldPosition, outpost.WorldPosition);
if ((myPort == null || dist < closestDistance || port.MainDockingPort) && !(myPort?.MainDockingPort ?? false))
{
myPort = port;
closestDistance = dist;
}
}
if (myPort != null && outPostPort != null)
{
Vector2 portDiff = myPort.Item.WorldPosition - submarine.WorldPosition;
Vector2 spawnPos = (outPostPort.Item.WorldPosition - portDiff) - Vector2.UnitY * outPostPort.DockedDistance;
submarine.SetPosition(spawnPos);
myPort.Dock(outPostPort);
myPort.Lock(true);
}
if (Character.Controlled != null)
{
Character.Controlled.TeleportTo(outpost.GetWaypoints(false).GetRandom(point => point.SpawnType == SpawnType.Human).WorldPosition);
}
}
} }
} }
@@ -293,7 +293,9 @@ namespace Barotrauma.Tutorials
// Room 4 // Room 4
do { yield return null; } while (!officer_somethingBigSensor.MotionDetected); do { yield return null; } while (!officer_somethingBigSensor.MotionDetected);
TriggerTutorialSegment(3); // Arm railgun GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.SomethingBig"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(3); // Arm coilgun
do do
{ {
SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.Items[0] == null || officer_coilgunLoader.Inventory.Items[0].Condition == 0); SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.Items[0] == null || officer_coilgunLoader.Inventory.Items[0].Condition == 0);
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization; using System.Globalization;
@@ -373,15 +374,6 @@ namespace Barotrauma
} }
} }
if (!(gameSession.GameMode is CampaignMode))
{
var shadow = new GUIFrame(new RectTransform(new Point((int)(totalWidth * 1.2f), GameMain.GraphicsHeight * 2), background.RectTransform, Anchor.Center), style: "OuterGlow")
{
Color = Color.Black
};
shadow.RectTransform.SetAsFirstChild();
}
Frame = background; Frame = background;
return background; return background;
} }
@@ -511,7 +503,7 @@ namespace Barotrauma
Character character = characterInfo.Character; Character character = characterInfo.Character;
if (character == null || character.IsDead) if (character == null || character.IsDead)
{ {
if (characterInfo.IsNewHire) if (character == null && characterInfo.IsNewHire)
{ {
statusText = TextManager.Get("CampaignCrew.NewHire"); statusText = TextManager.Get("CampaignCrew.NewHire");
statusColor = GUI.Style.Blue; statusColor = GUI.Style.Blue;
@@ -616,21 +608,8 @@ namespace Barotrauma
sliderHolder.RectTransform.MaxSize = new Point(int.MaxValue, GUI.IntScale(25.0f)); sliderHolder.RectTransform.MaxSize = new Point(int.MaxValue, GUI.IntScale(25.0f));
factionTextContent.Recalculate(); factionTextContent.Recalculate();
new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), onDraw: (sb, customComponent) => new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform),
{ onDraw: (sb, customComponent) => DrawReputationBar(sb, customComponent.Rect, normalizedReputation));
GUI.DrawRectangle(sb, customComponent.Rect, GUI.Style.ColorInventoryBackground, isFilled: true);
if (normalizedReputation < 0.5f)
{
int barWidth = (int)((0.5f - normalizedReputation) * customComponent.Rect.Width);
GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X - barWidth, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Red, isFilled: true);
}
else if (normalizedReputation > 0.5f)
{
int barWidth = (int)((normalizedReputation - 0.5f) * customComponent.Rect.Width);
GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Green, isFilled: true);
}
GUI.DrawLine(sb, new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Y - 2), new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Bottom + 2), factionDescription.TextColor, width: 1);
});
string reputationText = ((int)Math.Round(reputation)).ToString(); string reputationText = ((int)Math.Round(reputation)).ToString();
int reputationChange = (int)Math.Round( reputation - initialReputation); int reputationChange = (int)Math.Round( reputation - initialReputation);
@@ -650,5 +629,21 @@ namespace Barotrauma
textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont); textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont);
} }
} }
public static void DrawReputationBar(SpriteBatch sb, Rectangle rect, float normalizedReputation)
{
GUI.DrawRectangle(sb, rect, GUI.Style.ColorInventoryBackground, isFilled: true);
if (normalizedReputation < 0.5f)
{
int barWidth = (int)((0.5f - normalizedReputation) * rect.Width);
GUI.DrawRectangle(sb, new Rectangle(rect.Center.X - barWidth, rect.Y, barWidth, rect.Height), GUI.Style.Red, isFilled: true);
}
else if (normalizedReputation > 0.5f)
{
int barWidth = (int)((normalizedReputation - 0.5f) * rect.Width);
GUI.DrawRectangle(sb, new Rectangle(rect.Center.X, rect.Y, barWidth, rect.Height), GUI.Style.Green, isFilled: true);
}
GUI.DrawLine(sb, new Vector2(rect.Center.X, rect.Y - 2), new Vector2(rect.Center.X, rect.Bottom + 2), GUI.Style.TextColor);
}
} }
} }
@@ -65,6 +65,10 @@ namespace Barotrauma
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.LocalVoice] = new KeyOrMouse(Keys.B);
keyMapping[(int)InputType.Command] = new KeyOrMouse(MouseButton.MiddleMouse); keyMapping[(int)InputType.Command] = new KeyOrMouse(MouseButton.MiddleMouse);
#if DEBUG
keyMapping[(int)InputType.PreviousFireMode] = new KeyOrMouse(MouseButton.MouseWheelDown);
keyMapping[(int)InputType.NextFireMode] = new KeyOrMouse(MouseButton.MouseWheelUp);
#endif
if (Language == "French") if (Language == "French")
{ {
@@ -314,7 +318,7 @@ namespace Barotrauma
var corePackageDropdown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform)) var corePackageDropdown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform))
{ {
ButtonEnabled = ContentPackage.List.Count(cp => cp.CorePackage) > 1 ButtonEnabled = ContentPackage.CorePackages.Count > 1
}; };
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), isHorizontal: true) var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), isHorizontal: true)
@@ -342,7 +346,7 @@ namespace Barotrauma
ScrollBarVisible = true ScrollBarVisible = true
}; };
foreach (ContentPackage contentPackage in ContentPackage.List.Where(cp => cp.CorePackage)) foreach (ContentPackage contentPackage in ContentPackage.CorePackages)
{ {
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), corePackageDropdown.ListBox.Content.RectTransform), style: "ListBoxElement") var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), corePackageDropdown.ListBox.Content.RectTransform), style: "ListBoxElement")
{ {
@@ -358,7 +362,7 @@ namespace Barotrauma
TextManager.GetWithVariables(contentPackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage", TextManager.GetWithVariables(contentPackage.GameVersion <= new Version(0, 0, 0, 0) ? "IncompatibleContentPackageUnknownVersion" : "IncompatibleContentPackage",
new string[3] { "[packagename]", "[packageversion]", "[gameversion]" }, new string[3] { contentPackage.Name, contentPackage.GameVersion.ToString(), GameMain.Version.ToString() }); new string[3] { "[packagename]", "[packageversion]", "[gameversion]" }, new string[3] { contentPackage.Name, contentPackage.GameVersion.ToString(), GameMain.Version.ToString() });
} }
else if (contentPackage.CorePackage && !contentPackage.ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes)) else if (!contentPackage.ContainsRequiredCorePackageFiles(out List<ContentType> missingContentTypes))
{ {
frame.UserData = null; frame.UserData = null;
text.TextColor = GUI.Style.Red * 0.6f; text.TextColor = GUI.Style.Red * 0.6f;
@@ -374,7 +378,7 @@ namespace Barotrauma
"\n" + string.Join("\n", contentPackage.ErrorMessages); "\n" + string.Join("\n", contentPackage.ErrorMessages);
} }
if (SelectedContentPackages.Contains(contentPackage)) if (contentPackage == CurrentCorePackage)
{ {
corePackageDropdown.Select(corePackageDropdown.ListBox.Content.GetChildIndex(frame)); corePackageDropdown.Select(corePackageDropdown.ListBox.Content.GetChildIndex(frame));
} }
@@ -382,10 +386,7 @@ namespace Barotrauma
corePackageDropdown.OnSelected = SelectCorePackage; corePackageDropdown.OnSelected = SelectCorePackage;
corePackageDropdown.ListBox.CanBeFocused = CanHotswapPackages(true); corePackageDropdown.ListBox.CanBeFocused = CanHotswapPackages(true);
foreach (ContentPackage contentPackage in ContentPackage.List foreach (ContentPackage contentPackage in ContentPackage.RegularPackages)
.Where(cp => !cp.CorePackage)
.OrderBy(cp => !SelectedContentPackages.Contains(cp))
.ThenBy(cp => -SelectedContentPackages.IndexOf(cp)))
{ {
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, tickBoxScale.Y), contentPackageList.Content.RectTransform), style: "ListBoxElement") var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, tickBoxScale.Y), contentPackageList.Content.RectTransform), style: "ListBoxElement")
{ {
@@ -407,7 +408,7 @@ namespace Barotrauma
style: "GUITickBox") style: "GUITickBox")
{ {
UserData = contentPackage, UserData = contentPackage,
Selected = SelectedContentPackages.Contains(contentPackage), Selected = EnabledRegularPackages.Contains(contentPackage),
OnSelected = SelectContentPackage, OnSelected = SelectContentPackage,
Enabled = CanHotswapPackages(false) Enabled = CanHotswapPackages(false)
}; };
@@ -1595,10 +1596,10 @@ namespace Barotrauma
if (userData is ContentPackage contentPackage) if (userData is ContentPackage contentPackage)
{ {
if (!SelectedContentPackages.Contains(contentPackage)) { return; } if (!EnabledRegularPackages.Contains(contentPackage)) { return; }
} }
ReorderSelectedContentPackages(cp => -listBox.Content.GetChildIndex(listBox.Content.GetChildByUserData(cp))); ContentPackage.SortContentPackages(cp => listBox.Content.GetChildIndex(listBox.Content.GetChildByUserData(cp)), true);
UnsavedSettings = true; UnsavedSettings = true;
} }
@@ -1609,18 +1610,13 @@ namespace Barotrauma
var contentPackage = tickBox.UserData as ContentPackage; var contentPackage = tickBox.UserData as ContentPackage;
ContentPackage.List = ContentPackage.List
.OrderByDescending(p => p.CorePackage)
.ThenBy(cp => -contentPackageList.Content.GetChildIndex(contentPackageList.Content.GetChildByUserData(cp)))
.ToList();
if (tickBox.Selected) if (tickBox.Selected)
{ {
SelectContentPackage(contentPackage); EnableRegularPackage(contentPackage);
} }
else else
{ {
DeselectContentPackage(contentPackage); DisableRegularPackage(contentPackage);
} }
UnsavedSettings = true; UnsavedSettings = true;
@@ -717,7 +717,7 @@ namespace Barotrauma
{ {
slot.QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ? slot.QuickUseButtonToolTip = quickUseAction == QuickUseAction.None ?
"" : TextManager.GetWithVariable("QuickUseAction." + quickUseAction.ToString(), "[equippeditem]", character.SelectedItems.FirstOrDefault(i => i != null)?.Name); "" : TextManager.GetWithVariable("QuickUseAction." + quickUseAction.ToString(), "[equippeditem]", character.SelectedItems.FirstOrDefault(i => i != null)?.Name);
if (PlayerInput.PrimaryMouseButtonDown()) slot.EquipButtonState = GUIComponent.ComponentState.Pressed; if (PlayerInput.PrimaryMouseButtonDown()) { slot.EquipButtonState = GUIComponent.ComponentState.Pressed; }
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked())
{ {
QuickUseItem(item, allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false); QuickUseItem(item, allowEquip: true, allowInventorySwap: false, allowApplyTreatment: false);
@@ -937,28 +937,52 @@ namespace Barotrauma
switch (quickUseAction) switch (quickUseAction)
{ {
case QuickUseAction.Equip: case QuickUseAction.Equip:
//attempt to put in a free slot first if (string.IsNullOrEmpty(item.Prefab.EquipConfirmationText) || character != Character.Controlled)
for (int i = capacity - 1; i >= 0; i--)
{ {
if (Items[i] != null) { continue; } Equip();
if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) { continue; } }
success = TryPutItem(item, i, true, false, Character.Controlled, true); else
if (success) { break; } {
if (GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "equipconfirmation")) { return; }
var equipConfirmation = new GUIMessageBox(string.Empty, TextManager.Get(item.Prefab.EquipConfirmationText),
new string[] { TextManager.Get("yes"), TextManager.Get("no") })
{
UserData = "equipconfirmation"
};
equipConfirmation.Buttons[0].OnClicked = (btn, userdata) =>
{
Equip();
equipConfirmation.Close();
return true;
};
equipConfirmation.Buttons[1].OnClicked = equipConfirmation.Close;
} }
if (!success) void Equip()
{ {
//attempt to put in a free slot first
for (int i = capacity - 1; i >= 0; i--) for (int i = capacity - 1; i >= 0; i--)
{ {
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; }
// something else already equipped in a hand slot, attempt to unequip it so items aren't unnecessarily swapped to it
if (Items[i] != null && Items[i].AllowedSlots.Contains(InvSlotType.Any) && (SlotTypes[i] == InvSlotType.LeftHand || SlotTypes[i] == InvSlotType.RightHand))
{
TryPutItem(Items[i], Character.Controlled, new List<InvSlotType>() { InvSlotType.Any }, true);
}
success = TryPutItem(item, i, true, false, Character.Controlled, true); success = TryPutItem(item, i, true, false, Character.Controlled, true);
if (success) { break; } if (success) { break; }
} }
if (!success)
{
for (int i = capacity - 1; i >= 0; i--)
{
if (SlotTypes[i] == InvSlotType.Any || !item.AllowedSlots.Any(a => a.HasFlag(SlotTypes[i]))) { continue; }
// something else already equipped in a hand slot, attempt to unequip it so items aren't unnecessarily swapped to it
if (Items[i] != null && Items[i].AllowedSlots.Contains(InvSlotType.Any) && (SlotTypes[i] == InvSlotType.LeftHand || SlotTypes[i] == InvSlotType.RightHand))
{
TryPutItem(Items[i], Character.Controlled, new List<InvSlotType>() { InvSlotType.Any }, true);
}
success = TryPutItem(item, i, true, false, Character.Controlled, true);
if (success) { break; }
}
}
} }
break; break;
case QuickUseAction.Unequip: case QuickUseAction.Unequip:
@@ -1046,8 +1070,8 @@ namespace Barotrauma
public void DrawOwn(SpriteBatch spriteBatch) public void DrawOwn(SpriteBatch spriteBatch)
{ {
if (!AccessibleWhenAlive && !character.IsDead) return; if (!AccessibleWhenAlive && !character.IsDead) { return; }
if (slots == null) CreateSlots(); if (slots == null) { CreateSlots(); }
if (GameMain.GraphicsWidth != screenResolution.X || if (GameMain.GraphicsWidth != screenResolution.X ||
GameMain.GraphicsHeight != screenResolution.Y || GameMain.GraphicsHeight != screenResolution.Y ||
prevUIScale != UIScale || prevUIScale != UIScale ||
@@ -1070,7 +1094,7 @@ namespace Barotrauma
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i)) { continue; }
Rectangle interactRect = slots[i].InteractRect; Rectangle interactRect = slots[i].InteractRect;
interactRect.Location += slots[i].DrawOffset.ToPoint(); interactRect.Location += slots[i].DrawOffset.ToPoint();
@@ -1086,9 +1110,13 @@ namespace Barotrauma
} }
InventorySlot highlightedQuickUseSlot = null; InventorySlot highlightedQuickUseSlot = null;
Rectangle inventoryArea = Rectangle.Empty;
for (int i = 0; i < capacity; i++) for (int i = 0; i < capacity; i++)
{ {
if (HideSlot(i)) continue; if (HideSlot(i)) { continue; }
inventoryArea = inventoryArea == Rectangle.Empty ? slots[i].InteractRect : Rectangle.Union(inventoryArea, slots[i].InteractRect);
if (Items[i] == null || if (Items[i] == null ||
(draggingItem == Items[i] && !slots[i].InteractRect.Contains(PlayerInput.MousePosition)) || (draggingItem == Items[i] && !slots[i].InteractRect.Contains(PlayerInput.MousePosition)) ||
@@ -1102,7 +1130,7 @@ namespace Barotrauma
} }
continue; continue;
} }
if (draggingItem == Items[i] && !slots[i].IsHighlighted) continue; if (draggingItem == Items[i] && !slots[i].IsHighlighted) { continue; }
//draw hand icons if the item is equipped in a hand slot //draw hand icons if the item is equipped in a hand slot
if (IsInLimbSlot(Items[i], InvSlotType.LeftHand)) if (IsInLimbSlot(Items[i], InvSlotType.LeftHand))
@@ -1169,7 +1197,17 @@ namespace Barotrauma
} }
} }
if (highlightedQuickUseSlot != null && !string.IsNullOrEmpty(highlightedQuickUseSlot.QuickUseButtonToolTip)) if (Locked)
{
GUI.DrawRectangle(spriteBatch, inventoryArea, new Color(30,30,30,100), isFilled: true);
var lockIcon = GUI.Style.GetComponentStyle("LockIcon")?.GetDefaultSprite();
lockIcon?.Draw(spriteBatch, inventoryArea.Center.ToVector2(), scale: Math.Min(inventoryArea.Height / lockIcon.size.Y * 0.7f, 1.0f));
if (inventoryArea.Contains(PlayerInput.MousePosition))
{
GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("handcuffed"), new Rectangle(inventoryArea.Center - new Point(inventoryArea.Height / 2), new Point(inventoryArea.Height)));
}
}
else if (highlightedQuickUseSlot != null && !string.IsNullOrEmpty(highlightedQuickUseSlot.QuickUseButtonToolTip))
{ {
GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect); GUIComponent.DrawToolTip(spriteBatch, highlightedQuickUseSlot.QuickUseButtonToolTip, highlightedQuickUseSlot.EquipButtonRect);
} }
@@ -68,29 +68,52 @@ namespace Barotrauma.Items.Components
if (Window.Height > 0 && Window.Width > 0) if (Window.Height > 0 && Window.Width > 0)
{ {
rect.Height = -(int)(Window.Y * item.Scale); if (IsHorizontal)
rect.Y += (int)(doorRect.Height * openState);
rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
rect.Y = Math.Min(doorRect.Y, rect.Y);
if (convexHull2 != null)
{ {
Rectangle rect2 = doorRect; rect.Width = (int)(Window.X * item.Scale);
rect2.Y += (int)(Window.Y * item.Scale - Window.Height * item.Scale); rect.X -= (int)(doorRect.Width * openState);
rect.Width = Math.Max(rect.Width - (doorRect.X - rect.X), 0);
rect2.Y += (int)(doorRect.Height * openState); rect.X = Math.Max(doorRect.X, rect.X);
rect2.Y = Math.Min(doorRect.Y, rect2.Y); if (convexHull2 != null)
rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
if (rect2.Height == 0)
{ {
convexHull2.Enabled = false; Rectangle rect2 = doorRect;
rect2.X += (int)(Window.Right * item.Scale);
rect2.X -= (int)(doorRect.Width * openState);
rect2.X = Math.Max(doorRect.X, rect2.X);
rect2.Width = doorRect.Right - (int)(doorRect.Width * openState) - rect2.X;
if (rect2.Width == 0)
{
convexHull2.Enabled = false;
}
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2));
}
} }
else }
else
{
rect.Height = -(int)(Window.Y * item.Scale);
rect.Y += (int)(doorRect.Height * openState);
rect.Height = Math.Max(rect.Height - (rect.Y - doorRect.Y), 0);
rect.Y = Math.Min(doorRect.Y, rect.Y);
if (convexHull2 != null)
{ {
convexHull2.Enabled = true; Rectangle rect2 = doorRect;
convexHull2.SetVertices(GetConvexHullCorners(rect2)); rect2.Y += (int)(Window.Y * item.Scale - Window.Height * item.Scale);
rect2.Y += (int)(doorRect.Height * openState);
rect2.Y = Math.Min(doorRect.Y, rect2.Y);
rect2.Height = rect2.Y - (doorRect.Y - (int)(doorRect.Height * (1.0f - openState)));
if (rect2.Height == 0)
{
convexHull2.Enabled = false;
}
else
{
convexHull2.Enabled = true;
convexHull2.SetVertices(GetConvexHullCorners(rect2));
}
} }
} }
} }
@@ -251,8 +274,9 @@ namespace Barotrauma.Items.Components
bool open = msg.ReadBoolean(); bool open = msg.ReadBoolean();
bool broken = msg.ReadBoolean(); bool broken = msg.ReadBoolean();
bool forcedOpen = msg.ReadBoolean(); bool forcedOpen = msg.ReadBoolean();
bool isStuck = msg.ReadBoolean();
SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen); SetState(open, isNetworkMessage: true, sendNetworkMessage: false, forcedOpen: forcedOpen);
Stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8); stuck = msg.ReadRangedSingle(0.0f, 100.0f, 8);
UInt16 lastUserID = msg.ReadUInt16(); UInt16 lastUserID = msg.ReadUInt16();
Character user = lastUserID == 0 ? null : Entity.FindEntityByID(lastUserID) as Character; Character user = lastUserID == 0 ? null : Entity.FindEntityByID(lastUserID) as Character;
if (user != lastUser) if (user != lastUser)
@@ -260,7 +284,7 @@ namespace Barotrauma.Items.Components
lastUser = user; lastUser = user;
toggleCooldownTimer = ToggleCoolDown; toggleCooldownTimer = ToggleCoolDown;
} }
this.isStuck = isStuck;
if (isStuck) { OpenState = 0.0f; } if (isStuck) { OpenState = 0.0f; }
IsBroken = broken; IsBroken = broken;
PredictedState = null; PredictedState = null;
@@ -0,0 +1,360 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
internal partial class VineTile
{
public void Draw(SpriteBatch spriteBatch, Vector2 position, float depth, float leafDepth)
{
Vector2 pos = position + Position;
pos.Y = -pos.Y;
VineSprite vineSprite = Parent.VineSprites[Type];
Color color = Parent.Decayed ? Parent.DeadTint : Parent.VineTint;
float layer1 = depth + 0.01f, // flowers
layer2 = depth + 0.02f, // decay atlas
layer3 = depth + 0.03f; // branches and leaves
float scale = Parent.VineScale * VineStep;
if (Parent.VineAtlas != null)
{
spriteBatch.Draw(Parent.VineAtlas.Texture, pos + offset, vineSprite.SourceRect, color, 0f, vineSprite.AbsoluteOrigin, scale, SpriteEffects.None, layer3);
}
if (Parent.DecayAtlas != null)
{
spriteBatch.Draw(Parent.DecayAtlas.Texture, pos, vineSprite.SourceRect, HealthColor, 0f, vineSprite.AbsoluteOrigin, scale, SpriteEffects.None, layer2);
}
if (FlowerConfig.Variant >= 0 && !Parent.Decayed)
{
Sprite flowerSprite = Parent.FlowerSprites[FlowerConfig.Variant];
flowerSprite.Draw(spriteBatch, pos, Parent.FlowerTint, flowerSprite.Origin, scale: Parent.BaseFlowerScale * FlowerConfig.Scale * FlowerStep, rotate: FlowerConfig.Rotation, depth: layer1);
}
if (LeafConfig.Variant >= 0)
{
Sprite leafSprite = Parent.LeafSprites[LeafConfig.Variant];
leafSprite.Draw(spriteBatch, pos, Parent.Decayed ? Parent.DeadTint : Parent.LeafTint, leafSprite.Origin, scale: Parent.BaseLeafScale * LeafConfig.Scale * FlowerStep, rotate: LeafConfig.Rotation, depth: layer3 + leafDepth);
}
}
}
internal class VineSprite
{
[Serialize("0,0,0,0", false)]
public Rectangle SourceRect { get; private set; }
[Serialize("0.5,0.5", false)]
public Vector2 Origin { get; private set; }
public Vector2 AbsoluteOrigin;
public VineSprite(XElement element)
{
SerializableProperty.DeserializeProperties(this, element);
AbsoluteOrigin = new Vector2(SourceRect.Width * Origin.X, SourceRect.Height * Origin.Y);
}
}
internal partial class Growable
{
public readonly Dictionary<VineTileType, VineSprite> VineSprites = new Dictionary<VineTileType, VineSprite>();
public readonly List<Sprite> FlowerSprites = new List<Sprite>();
public readonly List<Sprite> LeafSprites = new List<Sprite>();
public Sprite? VineAtlas, DecayAtlas;
protected override void RemoveComponentSpecific()
{
VineAtlas?.Remove();
DecayAtlas?.Remove();
foreach (Sprite sprite in FlowerSprites)
{
sprite.Remove();
}
foreach (Sprite sprite in LeafSprites)
{
sprite.Remove();
}
}
public void Draw(SpriteBatch spriteBatch, Planter planter, Vector2 offset, float depth)
{
const float zStep = 0.0001f;
float leafDepth = 0f;
foreach (VineTile vine in Vines)
{
leafDepth += zStep;
vine.Draw(spriteBatch, planter.Item.DrawPosition + offset, depth, leafDepth);
}
if (GameMain.DebugDraw)
{
foreach (Rectangle rect in FailedRectangles)
{
Rectangle wRect = rect;
wRect.Y = -wRect.Y;
wRect.Y -= wRect.Height;
GUI.DrawRectangle(spriteBatch, wRect, Color.Red);
}
}
}
partial void LoadVines(XElement element)
{
string? vineAtlasPath = element.GetAttributeString("vineatlas", null);
string? decayAtlasPath = element.GetAttributeString("decayatlas", null);
if (vineAtlasPath != null)
{
VineAtlas = new Sprite(vineAtlasPath, Rectangle.Empty);
}
if (decayAtlasPath != null)
{
DecayAtlas = new Sprite(decayAtlasPath, Rectangle.Empty);
}
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "vinesprite":
var tileType = subElement.GetAttributeString("type", null);
VineTileType type = Enum.Parse<VineTileType>(tileType);
VineSprites.Add(type, new VineSprite(subElement));
break;
case "flowersprite":
FlowerSprites.Add(new Sprite(subElement));
break;
case "leafsprite":
LeafSprites.Add(new Sprite(subElement));
break;
}
flowerVariants = FlowerSprites.Count;
leafVariants = LeafSprites.Count;
}
foreach (VineTileType type in Enum.GetValues(typeof(VineTileType)))
{
if (!VineSprites.ContainsKey(type))
{
DebugConsole.ThrowError($"Vine sprite missing from {item.prefab.Identifier}: {type}");
}
}
}
private readonly object mutex = new object();
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Health = msg.ReadRangedSingle(0, MaxHealth, 8);
int startOffset = msg.ReadRangedInteger(-1, MaximumVines);
if (startOffset > -1)
{
int vineCount = msg.ReadRangedInteger(0, VineChunkSize);
List<VineTile> tiles = new List<VineTile>();
for (int i = 0; i < vineCount; i++)
{
VineTileType vineType = (VineTileType) msg.ReadRangedInteger(0b0000, 0b1111);
int flowerConfig = msg.ReadRangedInteger(0, 0xFFF);
int leafConfig = msg.ReadRangedInteger(0, 0xFFF);
sbyte posX = (sbyte) msg.ReadByte(), posY = (sbyte) msg.ReadByte();
Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size);
tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig)));
}
// is this even needed??
lock (mutex)
{
for (var i = 0; i < vineCount; i++)
{
int index = i + startOffset;
if (index >= Vines.Count)
{
Vines.Add(tiles[i]);
continue;
}
VineTile oldVine = Vines[index];
VineTile newVine = tiles[i];
newVine.GrowthStep = oldVine.GrowthStep;
Vines[index] = newVine;
}
}
}
UpdateBranchHealth();
ResetPlanterSize();
}
private void ResetPlanterSize()
{
if (item.ParentInventory is ItemInventory itemInventory && itemInventory.Owner is Item parentItem)
{
if (parentItem.GetComponent<Planter>() is { } planter)
{
planter.Item.ResetCachedVisibleSize();
}
}
}
#if DEBUG
private int seed;
// Huge bowl of spaghetti
public void CreateDebugHUD(Planter planter, PlantSlot slot)
{
Vector2 relativeSize = new Vector2(0.3f, 0.6f);
GUIMessageBox msgBox = new GUIMessageBox(item.Name, "", new[] { TextManager.Get("applysettingsbutton") }, relativeSize);
GUILayoutGroup content = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.85f), msgBox.Content.RectTransform)) { Stretch = true };
GUINumberInput seedInput = CreateIntEntry("Random Seed", seed, content.RectTransform);
GUINumberInput vineTileSizeInput = CreateIntEntry("Vine Tile Size (Global)", VineTile.Size, content.RectTransform);
GUINumberInput[] leafScaleRangeInput = CreateMinMaxEntry("Leaf Scale Range (Global)", new []{ MinLeafScale, MaxLeafScale }, 1.5f, content.RectTransform);
GUINumberInput[] flowerScaleRangeInput = CreateMinMaxEntry("Flower Scale Range (Global)", new []{ MinFlowerScale, MaxFlowerScale }, 1.5f, content.RectTransform);
GUINumberInput vineCountInput = CreateIntEntry("Vine Count", MaximumVines, content.RectTransform);
GUINumberInput vineScaleInput = CreateFloatEntry("Vine Scale", VineScale, content.RectTransform);
GUINumberInput flowerInput = CreateIntEntry("Flower Quantity", FlowerQuantity, content.RectTransform);
GUINumberInput flowerScaleInput = CreateFloatEntry("Flower Scale", BaseFlowerScale, content.RectTransform);
GUINumberInput leafScaleInput = CreateFloatEntry("Leaf Scale", BaseLeafScale, content.RectTransform);
GUINumberInput leafProbabilityInput = CreateFloatEntry("Leaf Probability", LeafProbability, content.RectTransform);
GUINumberInput[] leafTintInputs = CreateMinMaxEntry("Leaf Tint", new []{ LeafTint.R / 255f, LeafTint.G / 255f, LeafTint.B / 255f }, 1.0f, content.RectTransform);
GUINumberInput[] flowerTintInputs = CreateMinMaxEntry("Flower Tint", new []{ FlowerTint.R / 255f, FlowerTint.G / 255f, FlowerTint.B / 255f }, 1.0f, content.RectTransform);
GUINumberInput[] vineTintInputs = CreateMinMaxEntry("Branch Tint", new []{ VineTint.R / 255f, VineTint.G / 255f, VineTint.B / 255f }, 1.0f, content.RectTransform);
// Apply
msgBox.Buttons[0].OnClicked = (button, o) =>
{
seed = seedInput.IntValue;
MaximumVines = vineCountInput.IntValue;
FlowerQuantity = flowerInput.IntValue;
BaseFlowerScale = flowerScaleInput.FloatValue;
VineScale = vineScaleInput.FloatValue;
BaseLeafScale = leafScaleInput.FloatValue;
LeafProbability = leafProbabilityInput.FloatValue;
VineTile.Size = vineTileSizeInput.IntValue;
MinFlowerScale = flowerScaleRangeInput[0].FloatValue;
MaxFlowerScale = flowerScaleRangeInput[1].FloatValue;
MinLeafScale = leafScaleRangeInput[0].FloatValue;
MaxLeafScale = leafScaleRangeInput[1].FloatValue;
LeafTint = new Color(leafTintInputs[0].FloatValue, leafTintInputs[1].FloatValue, leafTintInputs[2].FloatValue);
FlowerTint = new Color(flowerTintInputs[0].FloatValue, flowerTintInputs[1].FloatValue, flowerTintInputs[2].FloatValue);
VineTint = new Color(vineTintInputs[0].FloatValue, vineTintInputs[1].FloatValue, vineTintInputs[2].FloatValue);
if (FlowerQuantity >= MaximumVines - 1)
{
vineCountInput.Flash(Color.Red);
flowerInput.Flash(Color.Red);
return false;
}
if (MinFlowerScale > MaxFlowerScale)
{
foreach (GUINumberInput input in flowerScaleRangeInput)
{
input.Flash(Color.Red);
}
return false;
}
if (MinLeafScale > MaxLeafScale)
{
foreach (GUINumberInput input in leafScaleRangeInput)
{
input.Flash(Color.Red);
}
return false;
}
msgBox.Close();
Random random = new Random(seed);
Random flowerRandom = new Random(seed);
Vines.Clear();
GenerateFlowerTiles(flowerRandom);
GenerateStem();
Decayed = false;
FullyGrown = false;
while (MaximumVines > Vines.Count)
{
if (!CanGrowMore())
{
Decayed = true;
break;
}
TryGenerateBranches(planter, slot, random, flowerRandom);
}
if (!Decayed)
{
FullyGrown = true;
}
foreach (VineTile vineTile in Vines)
{
vineTile.GrowthStep = 2.0f;
}
return true;
};
}
private static GUINumberInput CreateIntEntry(string label, int defaultValue, RectTransform parent)
{
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), GUINumberInput.NumberType.Int) { IntValue = defaultValue };
return input;
}
private static GUINumberInput CreateFloatEntry(string label, float defaultValue, RectTransform parent)
{
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), GUINumberInput.NumberType.Float) { FloatValue = defaultValue, DecimalsToDisplay = 2 };
return input;
}
private static GUINumberInput[] CreateMinMaxEntry(string label, float[] values, float max, RectTransform parent, float min = 0f)
{
GUILayoutGroup layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.08f), parent), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), layout.RectTransform), label);
GUINumberInput[] inputs = new GUINumberInput[values.Length];
for (var i = 0; i < values.Length; i++)
{
float value = values[i];
GUINumberInput input = new GUINumberInput(new RectTransform(new Vector2(0.5f / values.Length, 1f), layout.RectTransform), GUINumberInput.NumberType.Float)
{
FloatValue = value, DecimalsToDisplay = 2,
MinValueFloat = min,
MaxValueFloat = max
};
inputs[i] = input;
}
return inputs;
}
#endif
}
}
@@ -12,11 +12,11 @@ namespace Barotrauma.Items.Components
{ {
partial class RangedWeapon : ItemComponent partial class RangedWeapon : ItemComponent
{ {
private Sprite crosshairSprite, crosshairPointerSprite; protected Sprite crosshairSprite, crosshairPointerSprite;
private Vector2 crosshairPos, crosshairPointerPos; protected Vector2 crosshairPos, crosshairPointerPos;
private float currentCrossHairScale, currentCrossHairPointerScale; protected float currentCrossHairScale, currentCrossHairPointerScale;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
@@ -86,7 +86,6 @@ namespace Barotrauma.Items.Components
public override void DrawHUD(SpriteBatch spriteBatch, Character character) public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{ {
if (crosshairSprite == null) { return; }
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; } if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
//camera focused on some other item/device, don't draw the crosshair //camera focused on some other item/device, don't draw the crosshair
@@ -0,0 +1,338 @@
using Barotrauma.Networking;
using Barotrauma.Particles;
using FarseerPhysics;
using FarseerPhysics.Dynamics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{
partial class Sprayer : RangedWeapon, IDrawableComponent
{
#if DEBUG
private Vector2 debugRayStartPos, debugRayEndPos;
#endif
public Vector2 DrawSize
{
get { return Vector2.Zero; }
}
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
private Hull targetHull;
private Vector2 rayStartWorldPosition;
private Color color;
partial void InitProjSpecific(XElement element)
{
currentCrossHairPointerScale = element.GetAttributeFloat("crosshairscale", 0.1f);
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
break;
}
}
}
private readonly List<BackgroundSection> targetSections = new List<BackgroundSection>();
// 0 = 1x1, 1 = 2x2, 2 = 3x3
private int spraySetting = 0;
private readonly Point[] sprayArray = new Point[8];
public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) return;
#if DEBUG
if (PlayerInput.KeyHit(InputType.PreviousFireMode))
#else
if (PlayerInput.MouseWheelDownClicked())
#endif
{
if (spraySetting > 0)
{
spraySetting--;
}
else
{
spraySetting = 2;
}
targetSections.Clear();
}
#if DEBUG
if (PlayerInput.KeyHit(InputType.NextFireMode))
#else
if (PlayerInput.MouseWheelUpClicked())
#endif
{
if (spraySetting < 2)
{
spraySetting++;
}
else
{
spraySetting = 0;
}
targetSections.Clear();
}
crosshairPointerPos = PlayerInput.MousePosition;
Vector2 rayStart;
Vector2 sourcePos = character?.AnimController == null ? item.SimPosition : character.AnimController.AimSourceSimPos;
Vector2 barrelPos = item.SimPosition + TransformedBarrelPos;
//make sure there's no obstacles between the base of the item (or the shoulder of the character) and the end of the barrel
if (Submarine.PickBody(sourcePos, barrelPos, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall) == null)
{
//no obstacles -> we start the raycast at the end of the barrel
rayStart = ConvertUnits.ToSimUnits(item.WorldPosition) + TransformedBarrelPos;
}
else
{
targetHull = null;
targetSections.Clear();
return;
}
Vector2 pos = character.CursorWorldPosition;
Vector2 rayEnd = ConvertUnits.ToSimUnits(pos);
rayStartWorldPosition = ConvertUnits.ToDisplayUnits(rayStart);
if (Vector2.Distance(rayStartWorldPosition, pos) > Range)
{
targetHull = null;
targetSections.Clear();
return;
}
#if DEBUG
debugRayStartPos = ConvertUnits.ToDisplayUnits(rayStart);
debugRayEndPos = ConvertUnits.ToDisplayUnits(rayEnd);
#endif
Submarine parentSub = character.Submarine ?? item.Submarine;
if (parentSub != null)
{
rayStart -= parentSub.SimPosition;
rayEnd -= parentSub.SimPosition;
}
var obstacles = Submarine.PickBodies(rayStart, rayEnd, collisionCategory: Physics.CollisionItem | Physics.CollisionItemBlocking | Physics.CollisionWall);
foreach (var body in obstacles)
{
if (body.UserData is Item item)
{
var door = item.GetComponent<Door>();
if (door != null && door.IsOpen || door.IsBroken) continue;
}
targetHull = null;
targetSections.Clear();
return;
}
targetHull = Hull.GetCleanTarget(pos);
if (targetHull == null)
{
targetSections.Clear();
return;
}
BackgroundSection mousedOverSection = targetHull.GetBackgroundSection(pos);
if (mousedOverSection == null)
{
targetSections.Clear();
return;
}
// No need to refresh
if (targetSections.Count > 0 && mousedOverSection == targetSections[0])
{
return;
}
targetSections.Clear();
targetSections.Add(mousedOverSection);
int mousedOverIndex = mousedOverSection.Index;
// Start with 2x2
if (spraySetting > 0)
{
sprayArray[0].X = mousedOverIndex + 1;
sprayArray[0].Y = mousedOverSection.RowIndex;
sprayArray[1].X = mousedOverIndex + targetHull.xBackgroundMax;
sprayArray[1].Y = mousedOverSection.RowIndex + 1;
sprayArray[2].X = sprayArray[1].X + 1;
sprayArray[2].Y = sprayArray[1].Y;
for (int i = 0; i < 3; i++)
{
if (targetHull.DoesSectionMatch(sprayArray[i].X, sprayArray[i].Y))
{
targetSections.Add(targetHull.BackgroundSections[sprayArray[i].X]);
}
}
// Add more if it's 3x3
if (spraySetting == 2)
{
sprayArray[3].X = mousedOverIndex - 1;
sprayArray[3].Y = mousedOverSection.RowIndex;
sprayArray[4].X = sprayArray[1].X - 1;
sprayArray[4].Y = sprayArray[1].Y;
sprayArray[5].X = sprayArray[3].X - targetHull.xBackgroundMax;
sprayArray[5].Y = sprayArray[3].Y - 1;
sprayArray[6].X = sprayArray[5].X + 1;
sprayArray[6].Y = sprayArray[5].Y;
sprayArray[7].X = sprayArray[6].X + 1;
sprayArray[7].Y = sprayArray[6].Y;
for (int i = 3; i < sprayArray.Length; i++)
{
if (targetHull.DoesSectionMatch(sprayArray[i].X, sprayArray[i].Y))
{
targetSections.Add(targetHull.BackgroundSections[sprayArray[i].X]);
}
}
}
}
}
public override void DrawHUD(SpriteBatch spriteBatch, Character character)
{
if (character == null || !character.IsKeyDown(InputType.Aim)) { return; }
GUI.HideCursor = targetSections.Count > 0;
}
public override bool Use(float deltaTime, Character character = null)
{
if (character == null) { return false; }
if (character == Character.Controlled)
{
if (targetSections.Count == 0) { return false; }
Spray(deltaTime);
return true;
}
else
{
//allow remote players to use the sprayer, but don't actually color the walls (we'll receive the data from the server)
return character.IsRemotePlayer;
}
}
public void Spray(float deltaTime)
{
if (targetSections.Count == 0) { return; }
Item liquidItem = liquidContainer?.Inventory.Items[0];
if (liquidItem == null) { return; }
bool isCleaning = false;
liquidColors.TryGetValue(liquidItem.prefab.Identifier, out color);
// Ethanol or other cleaning solvent
if (color.A == 0) { isCleaning = true; }
float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
if (!isCleaning)
{
for (int i = 0; i < targetSections.Count; i++)
{
targetHull.SetSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
}
}
else
{
for (int i = 0; i < targetSections.Count; i++)
{
targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
}
}
Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
Vector2 particleEndPos = Vector2.Zero;
for (int i = 0; i < targetSections.Count; i++)
{
particleEndPos += new Vector2(targetSections[i].Rect.Center.X, targetSections[i].Rect.Y - targetSections[i].Rect.Height / 2) + targetHull.Rect.Location.ToVector2();
}
particleEndPos /= targetSections.Count;
if (targetHull?.Submarine != null)
{
particleEndPos += targetHull.Submarine.Position;
}
float dist = Vector2.Distance(particleStartPos, particleEndPos);
foreach (ParticleEmitter particleEmitter in particleEmitters)
{
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
float particleRange = particleEmitter.Prefab.VelocityMax * particleEmitter.Prefab.ParticlePrefab.LifeTime;
particleEmitter.Emit(
deltaTime, particleStartPos,
item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0, velocityMultiplier: dist / particleRange * 1.5f,
colorMultiplier: new Color(color.R, color.G, color.B, (byte)255));
}
}
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
#if DEBUG
if (GameMain.DebugDraw && Character.Controlled != null && Character.Controlled.IsKeyDown(InputType.Aim))
{
GUI.DrawLine(spriteBatch,
new Vector2(debugRayStartPos.X, -debugRayStartPos.Y),
new Vector2(debugRayEndPos.X, -debugRayEndPos.Y),
Color.Yellow);
}
#endif
if (Character.Controlled == null || !Character.Controlled.HasEquippedItem(item) || !Character.Controlled.IsKeyDown(InputType.Aim) || targetHull == null || targetSections.Count == 0) return;
Vector2 drawOffset = targetHull.Submarine == null ? Vector2.Zero : targetHull.Submarine.DrawPosition;
Point sectionSize = targetSections[0].Rect.Size;
Rectangle drawPositionRect = new Rectangle((int)(drawOffset.X + targetHull.Rect.X), (int)(drawOffset.Y + targetHull.Rect.Y), sectionSize.X, sectionSize.Y);
if (crosshairSprite == null && crosshairPointerSprite == null)
{
for (int i = 0; i < targetSections.Count; i++)
{
GUI.DrawRectangle(spriteBatch, new Vector2(drawPositionRect.X + targetSections[i].Rect.X, -(drawPositionRect.Y + targetSections[i].Rect.Y)), new Vector2(sectionSize.X, sectionSize.Y), Color.White, false, 0.0f, 1);
}
}
else if (targetSections.Count > 0)
{
Vector2 drawPos = Vector2.Zero;
for (int i = 0; i < targetSections.Count; i++)
{
drawPos += new Vector2(drawPositionRect.X + targetSections[i].Rect.X + sectionSize.X / 2, -(drawPositionRect.Y + targetSections[i].Rect.Y - sectionSize.Y / 2));
}
drawPos /= targetSections.Count;
crosshairSprite?.Draw(spriteBatch, drawPos, scale: sectionSize.X * 3 / crosshairSprite.size.X);
crosshairPointerSprite?.Draw(spriteBatch, drawPos, scale: sectionSize.X * (spraySetting + 1) / crosshairPointerSprite.size.X);
}
}
}
}
@@ -256,6 +256,7 @@ namespace Barotrauma.Items.Components
loopingSoundChannel = loopingSound.RoundSound.Sound.Play( loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(item.WorldPosition, 0.0f), new Vector3(item.WorldPosition, 0.0f),
0.01f, 0.01f,
loopingSound.RoundSound.GetRandomFrequencyMultiplier(),
SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull)); SoundPlayer.ShouldMuffleSound(Character.Controlled, item.WorldPosition, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true; loopingSoundChannel.Looping = true;
//TODO: tweak //TODO: tweak
@@ -326,7 +327,7 @@ namespace Barotrauma.Items.Components
{ {
float volume = GetSoundVolume(itemSound); float volume = GetSoundVolume(itemSound);
if (volume <= 0.0001f) { return; } if (volume <= 0.0001f) { return; }
var channel = SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, item.CurrentHull); var channel = SoundPlayer.PlaySound(itemSound.RoundSound.Sound, position, volume, itemSound.Range, itemSound.RoundSound.GetRandomFrequencyMultiplier(), item.CurrentHull);
if (channel != null) { playingOneshotSoundChannels.Add(channel); } if (channel != null) { playingOneshotSoundChannels.Add(channel); }
} }
} }
@@ -172,6 +172,8 @@ namespace Barotrauma.Items.Components
{ {
Vector2 transformedItemPos = ItemPos * item.Scale; Vector2 transformedItemPos = ItemPos * item.Scale;
Vector2 transformedItemInterval = ItemInterval * item.Scale; Vector2 transformedItemInterval = ItemInterval * item.Scale;
Vector2 transformedItemIntervalHorizontal = new Vector2(transformedItemInterval.X, 0.0f);
Vector2 transformedItemIntervalVertical = new Vector2(0.0f, transformedItemInterval.Y);
if (item.body == null) if (item.body == null)
{ {
@@ -180,15 +182,26 @@ namespace Barotrauma.Items.Components
transformedItemPos.X = -transformedItemPos.X; transformedItemPos.X = -transformedItemPos.X;
transformedItemPos.X += item.Rect.Width; transformedItemPos.X += item.Rect.Width;
transformedItemInterval.X = -transformedItemInterval.X; transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
} }
if (item.FlippedY) if (item.FlippedY)
{ {
transformedItemPos.Y = -transformedItemPos.Y; transformedItemPos.Y = -transformedItemPos.Y;
transformedItemPos.Y -= item.Rect.Height; transformedItemPos.Y -= item.Rect.Height;
transformedItemInterval.Y = -transformedItemInterval.Y; transformedItemInterval.Y = -transformedItemInterval.Y;
transformedItemIntervalVertical.Y = -transformedItemIntervalVertical.Y;
} }
transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y); transformedItemPos += new Vector2(item.Rect.X, item.Rect.Y);
if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; } if (item.Submarine != null) { transformedItemPos += item.Submarine.DrawPosition; }
if (Math.Abs(item.Rotation) > 0.01f)
{
Matrix transform = Matrix.CreateRotationZ(MathHelper.ToRadians(-item.Rotation));
transformedItemPos = Vector2.Transform(transformedItemPos - item.DrawPosition, transform) + item.DrawPosition;
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemIntervalVertical = Vector2.Transform(transformedItemIntervalVertical, transform);
}
} }
else else
{ {
@@ -197,9 +210,12 @@ namespace Barotrauma.Items.Components
{ {
transformedItemPos.X = -transformedItemPos.X; transformedItemPos.X = -transformedItemPos.X;
transformedItemInterval.X = -transformedItemInterval.X; transformedItemInterval.X = -transformedItemInterval.X;
transformedItemIntervalHorizontal.X = -transformedItemIntervalHorizontal.X;
} }
transformedItemPos = Vector2.Transform(transformedItemPos, transform); transformedItemPos = Vector2.Transform(transformedItemPos, transform);
transformedItemInterval = Vector2.Transform(transformedItemInterval, transform); transformedItemInterval = Vector2.Transform(transformedItemInterval, transform);
transformedItemIntervalHorizontal = Vector2.Transform(transformedItemIntervalHorizontal, transform);
transformedItemPos += item.DrawPosition; transformedItemPos += item.DrawPosition;
} }
@@ -207,8 +223,14 @@ namespace Barotrauma.Items.Components
Vector2 currentItemPos = transformedItemPos; Vector2 currentItemPos = transformedItemPos;
SpriteEffects spriteEffects = SpriteEffects.None; SpriteEffects spriteEffects = SpriteEffects.None;
if ((item.body != null && item.body.Dir == -1) || item.FlippedX) { spriteEffects |= SpriteEffects.FlipHorizontally; } if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
if (item.FlippedY) { spriteEffects |= SpriteEffects.FlipVertically; } {
spriteEffects |= MathUtils.NearlyEqual(ItemRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
}
if (item.FlippedY)
{
spriteEffects |= MathUtils.NearlyEqual(ItemRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
}
int i = 0; int i = 0;
foreach (Item containedItem in Inventory.Items) foreach (Item containedItem in Inventory.Items)
@@ -233,7 +255,7 @@ namespace Barotrauma.Items.Components
new Vector2(currentItemPos.X, -currentItemPos.Y), new Vector2(currentItemPos.X, -currentItemPos.Y),
containedItem.GetSpriteColor(), containedItem.GetSpriteColor(),
origin, origin,
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation), -(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation + MathHelper.ToRadians(-item.Rotation)),
containedItem.Scale, containedItem.Scale,
spriteEffects, spriteEffects,
depth: containedSpriteDepth); depth: containedSpriteDepth);
@@ -248,11 +270,11 @@ namespace Barotrauma.Items.Components
if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f) if (Math.Abs(ItemInterval.X) > 0.001f && Math.Abs(ItemInterval.Y) > 0.001f)
{ {
//interval set on both axes -> use a grid layout //interval set on both axes -> use a grid layout
currentItemPos.X += transformedItemInterval.X; currentItemPos += transformedItemIntervalHorizontal;
if (i % ItemsPerRow == 0) if (i % ItemsPerRow == 0)
{ {
currentItemPos.X = transformedItemPos.X; currentItemPos = transformedItemPos;
currentItemPos.Y += transformedItemInterval.Y; currentItemPos += transformedItemIntervalVertical * (i / ItemsPerRow);
} }
} }
else else
@@ -264,6 +286,7 @@ namespace Barotrauma.Items.Components
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
if (item.NonInteractable) { return; }
if (Inventory.RectTransform != null) if (Inventory.RectTransform != null)
{ {
guiCustomComponent.RectTransform.Parent = Inventory.RectTransform; guiCustomComponent.RectTransform.Parent = Inventory.RectTransform;
@@ -272,7 +295,7 @@ namespace Barotrauma.Items.Components
//if the item is in the character's inventory, no need to update the item's inventory //if the item is in the character's inventory, no need to update the item's inventory
//because the player can see it by hovering the cursor over the item //because the player can see it by hovering the cursor over the item
guiCustomComponent.Visible = item.ParentInventory?.Owner != character && DrawInventory; guiCustomComponent.Visible = item.ParentInventory?.Owner != character && DrawInventory;
if (!guiCustomComponent.Visible) return; if (!guiCustomComponent.Visible) { return; }
Inventory.Update(deltaTime, cam); Inventory.Update(deltaTime, cam);
} }
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Text; using System.Text;
@@ -225,5 +226,10 @@ namespace Barotrauma.Items.Components
textBlock.TextOffset = drawPos - textBlock.Rect.Location.ToVector2() + new Vector2(scrollAmount + scrollPadding, 0.0f); textBlock.TextOffset = drawPos - textBlock.Rect.Location.ToVector2() + new Vector2(scrollAmount + scrollPadding, 0.0f);
textBlock.DrawManually(spriteBatch); textBlock.DrawManually(spriteBatch);
} }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Text = msg.ReadString();
}
} }
} }
@@ -49,8 +49,8 @@ namespace Barotrauma.Items.Components
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)
{ {
Vector2 origin = light.LightSprite.Origin; Vector2 origin = light.LightSprite.Origin;
if (light.LightSpriteEffect == SpriteEffects.FlipHorizontally) { origin.X = light.LightSprite.SourceRect.Width - origin.X; } if ((light.LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally) { origin.X = light.LightSprite.SourceRect.Width - origin.X; }
if (light.LightSpriteEffect == SpriteEffects.FlipVertically) { origin.Y = light.LightSprite.SourceRect.Height - origin.Y; } if ((light.LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically) { origin.Y = light.LightSprite.SourceRect.Height - origin.Y; }
light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, origin, -light.Rotation, item.Scale, light.LightSpriteEffect, item.SpriteDepth - 0.0001f); light.LightSprite.Draw(spriteBatch, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y), lightColor * lightBrightness, origin, -light.Rotation, item.Scale, light.LightSpriteEffect, item.SpriteDepth - 0.0001f);
} }
} }
@@ -52,7 +52,9 @@ namespace Barotrauma.Items.Components
RelativeOffset = new Vector2(0.05f, 0) RelativeOffset = new Vector2(0.05f, 0)
}, TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow") }, TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow")
{ {
CanBeFocused = false Selected = false,
Enabled = false,
ToolTip = TextManager.Get("AutoControlTip")
}; };
powerIndicator.TextBlock.Wrap = autoControlIndicator.TextBlock.Wrap = true; powerIndicator.TextBlock.Wrap = autoControlIndicator.TextBlock.Wrap = true;
powerIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor); powerIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
@@ -75,6 +77,7 @@ namespace Barotrauma.Items.Components
if (Math.Abs(newTargetForce - targetForce) < 0.01) { return false; } if (Math.Abs(newTargetForce - targetForce) < 0.01) { return false; }
targetForce = newTargetForce; targetForce = newTargetForce;
User = Character.Controlled;
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
@@ -145,7 +148,7 @@ namespace Barotrauma.Items.Components
propellerSprite.Draw(spriteBatch, (int)Math.Floor(spriteIndex), drawPos, Color.White, propellerSprite.Origin, 0.0f, Vector2.One); propellerSprite.Draw(spriteBatch, (int)Math.Floor(spriteIndex), drawPos, Color.White, propellerSprite.Origin, 0.0f, Vector2.One);
} }
if (editing) if (editing && !GUI.DisableHUD)
{ {
Vector2 drawPos = item.DrawPosition; Vector2 drawPos = item.DrawPosition;
drawPos += PropellerPos; drawPos += PropellerPos;
@@ -164,11 +167,16 @@ namespace Barotrauma.Items.Components
{ {
if (correctionTimer > 0.0f) if (correctionTimer > 0.0f)
{ {
StartDelayedCorrection(type, msg.ExtractBits(5), sendingTime); StartDelayedCorrection(type, msg.ExtractBits(5 + 16), sendingTime);
return; return;
} }
targetForce = msg.ReadRangedInteger(-10, 10) * 10.0f; targetForce = msg.ReadRangedInteger(-10, 10) * 10.0f;
UInt16 userID = msg.ReadUInt16();
if (userID != Entity.NullEntityID)
{
User = Entity.FindEntityByID(userID) as Character;
}
} }
} }
} }
@@ -100,7 +100,7 @@ namespace Barotrauma.Items.Components
OnSelected = (component, userdata) => OnSelected = (component, userdata) =>
{ {
selectedItem = userdata as FabricationRecipe; selectedItem = userdata as FabricationRecipe;
if (selectedItem != null) SelectItem(Character.Controlled, selectedItem); if (selectedItem != null) { SelectItem(Character.Controlled, selectedItem); }
return true; return true;
} }
}; };
@@ -292,7 +292,7 @@ namespace Barotrauma.Items.Components
foreach (Item item in inputContainer.Inventory.Items) foreach (Item item in inputContainer.Inventory.Items)
{ {
if (item == null) { continue; } if (item == null) { continue; }
missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefab == item.prefab)); missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.prefab)));
} }
var availableIngredients = GetAvailableIngredients(); var availableIngredients = GetAvailableIngredients();
@@ -328,12 +328,12 @@ namespace Barotrauma.Items.Components
if (slotIndex >= inputContainer.Capacity) { break; } if (slotIndex >= inputContainer.Capacity) { break; }
var itemIcon = requiredItem.ItemPrefab.InventoryIcon ?? requiredItem.ItemPrefab.sprite; var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().sprite;
Rectangle slotRect = inputContainer.Inventory.slots[slotIndex].Rect; Rectangle slotRect = inputContainer.Inventory.slots[slotIndex].Rect;
itemIcon.Draw( itemIcon.Draw(
spriteBatch, spriteBatch,
slotRect.Center.ToVector2(), slotRect.Center.ToVector2(),
color: requiredItem.ItemPrefab.InventoryIconColor * 0.3f, color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f,
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y)); scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f) if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
@@ -346,14 +346,16 @@ namespace Barotrauma.Items.Components
if (slotRect.Contains(PlayerInput.MousePosition)) if (slotRect.Contains(PlayerInput.MousePosition))
{ {
string toolTipText = requiredItem.ItemPrefab.Name; var suitableIngredients = requiredItem.ItemPrefabs.Select(ip => ip.Name);
string toolTipText = string.Join(", ", suitableIngredients.Count() > 3 ? suitableIngredients.SkipLast(suitableIngredients.Count() - 3) : suitableIngredients);
if (suitableIngredients.Count() > 3) { toolTipText += "..."; }
if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f) if (requiredItem.UseCondition && requiredItem.MinCondition < 1.0f)
{ {
toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%"; toolTipText += " " + (int)Math.Round(requiredItem.MinCondition * 100) + "%";
} }
if (!string.IsNullOrEmpty(requiredItem.ItemPrefab.Description)) if (!string.IsNullOrEmpty(requiredItem.ItemPrefabs.First().Description))
{ {
toolTipText += '\n' + requiredItem.ItemPrefab.Description; toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description;
} }
tooltip = new Pair<Rectangle, string>(slotRect, toolTipText); tooltip = new Pair<Rectangle, string>(slotRect, toolTipText);
} }
@@ -378,10 +380,11 @@ namespace Barotrauma.Items.Components
if (fabricatedItem != null) if (fabricatedItem != null)
{ {
float clampedProgressState = Math.Clamp(progressState, 0f, 1f);
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Rectangle( new Rectangle(
slotRect.X, slotRect.Y + (int)(slotRect.Height * (1.0f - progressState)), slotRect.X, slotRect.Y + (int)(slotRect.Height * (1.0f - clampedProgressState)),
slotRect.Width, (int)(slotRect.Height * progressState)), slotRect.Width, (int)(slotRect.Height * clampedProgressState)),
GUI.Style.Green * 0.5f, isFilled: true); GUI.Style.Green * 0.5f, isFilled: true);
} }
@@ -429,8 +432,10 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
private bool SelectItem(Character user, FabricationRecipe selectedItem) private bool SelectItem(Character user, FabricationRecipe selectedItem, float? overrideRequiredTime = null)
{ {
this.selectedItem = selectedItem;
selectedItemFrame.ClearChildren(); selectedItemFrame.ClearChildren();
selectedItemReqsFrame.ClearChildren(); selectedItemReqsFrame.ClearChildren();
@@ -496,7 +501,8 @@ namespace Barotrauma.Items.Components
float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills); float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; } if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
float requiredTime = user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user); float requiredTime = overrideRequiredTime ??
(user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform),
TextManager.Get("FabricatorRequiredTime") , textColor: ToolBox.GradientLerp(degreeOfSuccess, GUI.Style.Red, Color.Yellow, GUI.Style.Green), font: GUI.SubHeadingFont) TextManager.Get("FabricatorRequiredTime") , textColor: ToolBox.GradientLerp(degreeOfSuccess, GUI.Style.Red, Color.Yellow, GUI.Style.Green), font: GUI.SubHeadingFont)
@@ -605,7 +611,7 @@ namespace Barotrauma.Items.Components
State = newState; State = newState;
timeUntilReady = newTimeUntilReady; timeUntilReady = newTimeUntilReady;
if (newState == FabricatorState.Stopped || itemIndex == -1 || user == null) if (newState == FabricatorState.Stopped || itemIndex == -1)
{ {
CancelFabricating(); CancelFabricating();
} }
@@ -81,7 +81,9 @@ namespace Barotrauma.Items.Components
autoControlIndicator = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), rightArea.RectTransform, Anchor.TopLeft), autoControlIndicator = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.25f), rightArea.RectTransform, Anchor.TopLeft),
TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow") TextManager.Get("PumpAutoControl", fallBackTag: "ReactorAutoControl"), font: GUI.SubHeadingFont, style: "IndicatorLightYellow")
{ {
CanBeFocused = false Selected = false,
Enabled = false,
ToolTip = TextManager.Get("AutoControlTip")
}; };
autoControlIndicator.TextBlock.AutoScaleHorizontal = true; autoControlIndicator.TextBlock.AutoScaleHorizontal = true;
autoControlIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor); autoControlIndicator.TextBlock.OverrideTextColor(GUI.Style.TextColor);
@@ -131,17 +131,23 @@ namespace Barotrauma.Items.Components
criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize }, criticalHeatWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed") TextManager.Get("ReactorWarningCriticalTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{ {
CanBeFocused = false Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorHeatTip")
}; };
lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize }, lowTemperatureWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed") TextManager.Get("ReactorWarningCriticalLowTemp"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{ {
CanBeFocused = false Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorTempTip")
}; };
criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize }, criticalOutputWarning = new GUITickBox(new RectTransform(new Vector2(0.33f, 1.0f), topLeftArea.RectTransform) { MaxSize = maxIndicatorSize },
TextManager.Get("ReactorWarningCriticalOutput"), font: GUI.SubHeadingFont, style: "IndicatorLightRed") TextManager.Get("ReactorWarningCriticalOutput"), font: GUI.SubHeadingFont, style: "IndicatorLightRed")
{ {
CanBeFocused = false Selected = false,
Enabled = false,
ToolTip = TextManager.Get("ReactorOutputTip")
}; };
List<GUITickBox> indicatorLights = new List<GUITickBox>() { criticalHeatWarning, lowTemperatureWarning, criticalOutputWarning }; List<GUITickBox> indicatorLights = new List<GUITickBox>() { criticalHeatWarning, lowTemperatureWarning, criticalOutputWarning };
indicatorLights.ForEach(l => l.TextBlock.OverrideTextColor(GUI.Style.TextColor)); indicatorLights.ForEach(l => l.TextBlock.OverrideTextColor(GUI.Style.TextColor));
@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -20,7 +19,7 @@ namespace Barotrauma.Items.Components
private PathFinder pathFinder; private PathFinder pathFinder;
private bool dynamicDockingIndicator = true; private readonly bool dynamicDockingIndicator = true;
private bool unsentChanges; private bool unsentChanges;
private float networkUpdateTimer; private float networkUpdateTimer;
@@ -378,7 +377,6 @@ namespace Barotrauma.Items.Components
float edgeDist = Rand.Range(0.0f, 1.0f); float edgeDist = Rand.Range(0.0f, 1.0f);
Vector2 blipPos = trigger.WorldPosition + Rand.Vector(trigger.ColliderRadius * edgeDist); Vector2 blipPos = trigger.WorldPosition + Rand.Vector(trigger.ColliderRadius * edgeDist);
Vector2 blipVel = flow; Vector2 blipVel = flow;
if (trigger.ForceFalloff) flow *= (1.0f - edgeDist);
//go through other triggers in range and add the flows of the ones that the blip is inside //go through other triggers in range and add the flows of the ones that the blip is inside
foreach (KeyValuePair<LevelTrigger, Vector2> triggerFlow2 in levelTriggerFlows) foreach (KeyValuePair<LevelTrigger, Vector2> triggerFlow2 in levelTriggerFlows)
@@ -387,7 +385,7 @@ namespace Barotrauma.Items.Components
if (trigger2 != trigger && Vector2.DistanceSquared(blipPos, trigger2.WorldPosition) < trigger2.ColliderRadius * trigger2.ColliderRadius) if (trigger2 != trigger && Vector2.DistanceSquared(blipPos, trigger2.WorldPosition) < trigger2.ColliderRadius * trigger2.ColliderRadius)
{ {
Vector2 trigger2flow = triggerFlow2.Value; Vector2 trigger2flow = triggerFlow2.Value;
if (trigger2.ForceFalloff) trigger2flow *= (1.0f - Vector2.Distance(blipPos, trigger2.WorldPosition) / trigger2.ColliderRadius); if (trigger2.ForceFalloff) trigger2flow *= 1.0f - Vector2.Distance(blipPos, trigger2.WorldPosition) / trigger2.ColliderRadius;
blipVel += trigger2flow; blipVel += trigger2flow;
} }
} }
@@ -507,7 +505,7 @@ namespace Barotrauma.Items.Components
float passivePingRadius = (float)(Timing.TotalTime % 1.0f); float passivePingRadius = (float)(Timing.TotalTime % 1.0f);
if (passivePingRadius > 0.0f) if (passivePingRadius > 0.0f)
{ {
disruptedDirections.Clear(); if (activePingsCount == 0) { 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.Entity is Character c && c.Params.HideInSonar) { continue; }
@@ -581,14 +579,14 @@ namespace Barotrauma.Items.Components
} }
} }
if (currentMode == Mode.Active && currentPingIndex != -1) if (currentPingIndex != -1)
{ {
var activePing = activePings[currentPingIndex]; var activePing = activePings[currentPingIndex];
if (activePing.IsDirectional && directionalPingCircle != null) if (activePing.IsDirectional && directionalPingCircle != null)
{ {
directionalPingCircle.Draw(spriteBatch, center, Color.White * (1.0f - activePing.State), directionalPingCircle.Draw(spriteBatch, center, Color.White * (1.0f - activePing.State),
rotate: MathUtils.VectorToAngle(activePing.Direction), rotate: MathUtils.VectorToAngle(activePing.Direction),
scale: (DisplayRadius / directionalPingCircle.size.X) * activePing.State); scale: DisplayRadius / directionalPingCircle.size.X * activePing.State);
} }
else else
{ {
@@ -611,13 +609,13 @@ namespace Barotrauma.Items.Components
if (sonarBlips.Count > 0) if (sonarBlips.Count > 0)
{ {
zoomSqrt = (float)Math.Sqrt(zoom); float blipScale = 0.08f * (float)Math.Sqrt(zoom) * (rect.Width / 700.0f);
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.Additive);
foreach (SonarBlip sonarBlip in sonarBlips) foreach (SonarBlip sonarBlip in sonarBlips)
{ {
DrawBlip(spriteBatch, sonarBlip, transducerCenter, center, sonarBlip.FadeTimer / 2.0f * signalStrength); DrawBlip(spriteBatch, sonarBlip, transducerCenter, center, sonarBlip.FadeTimer / 2.0f * signalStrength, blipScale);
} }
spriteBatch.End(); spriteBatch.End();
@@ -1297,7 +1295,7 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
private void DrawBlip(SpriteBatch spriteBatch, SonarBlip blip, Vector2 transducerPos, Vector2 center, float strength) private void DrawBlip(SpriteBatch spriteBatch, SonarBlip blip, Vector2 transducerPos, Vector2 center, float strength, float blipScale)
{ {
strength = MathHelper.Clamp(strength, 0.0f, 1.0f); strength = MathHelper.Clamp(strength, 0.0f, 1.0f);
@@ -1306,8 +1304,8 @@ namespace Barotrauma.Items.Components
Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom; Vector2 pos = (blip.Position - transducerPos) * displayScale * zoom;
pos.Y = -pos.Y; pos.Y = -pos.Y;
if (Rand.Range(0.5f, 2.0f) < distort) pos.X = -pos.X; if (Rand.Range(0.5f, 2.0f) < distort) { pos.X = -pos.X; }
if (Rand.Range(0.5f, 2.0f) < distort) pos.Y = -pos.Y; if (Rand.Range(0.5f, 2.0f) < distort) { pos.Y = -pos.Y; }
float posDistSqr = pos.LengthSquared(); float posDistSqr = pos.LengthSquared();
if (posDistSqr > DisplayRadius * DisplayRadius) if (posDistSqr > DisplayRadius * DisplayRadius)
@@ -1324,15 +1322,15 @@ namespace Barotrauma.Items.Components
Vector2 dir = pos / (float)Math.Sqrt(posDistSqr); Vector2 dir = pos / (float)Math.Sqrt(posDistSqr);
Vector2 normal = new Vector2(dir.Y, -dir.X); Vector2 normal = new Vector2(dir.Y, -dir.X);
float scale = (strength + 3.0f) * blip.Scale * zoomSqrt; float scale = (strength + 3.0f) * blip.Scale * blipScale;
Color color = ToolBox.GradientLerp(strength, blipColorGradient[blip.BlipType]); Color color = ToolBox.GradientLerp(strength, blipColorGradient[blip.BlipType]);
sonarBlip.Draw(spriteBatch, center + pos, color, sonarBlip.Origin, blip.Rotation ?? MathUtils.VectorToAngle(pos), sonarBlip.Draw(spriteBatch, center + pos, color, sonarBlip.Origin, blip.Rotation ?? MathUtils.VectorToAngle(pos),
blip.Size * scale * 0.04f, SpriteEffects.None, 0); blip.Size * scale * 0.5f, SpriteEffects.None, 0);
pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal; pos += Rand.Range(0.0f, 1.0f) * dir + Rand.Range(-scale, scale) * normal;
sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f, sonarBlip.Origin, 0, scale * 0.08f, SpriteEffects.None, 0); sonarBlip.Draw(spriteBatch, center + pos, color * 0.5f, sonarBlip.Origin, 0, scale, SpriteEffects.None, 0);
} }
private void DrawMarker(SpriteBatch spriteBatch, string label, string iconIdentifier, object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, float scale, Vector2 center, float radius) private void DrawMarker(SpriteBatch spriteBatch, string label, string iconIdentifier, object targetIdentifier, Vector2 worldPosition, Vector2 transducerPosition, float scale, Vector2 center, float radius)
@@ -22,6 +22,7 @@ namespace Barotrauma.Items.Components
LevelEnd, LevelEnd,
LevelStart LevelStart
}; };
private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox; private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox;
private GUIComponent statusContainer, dockingContainer, controlContainer; private GUIComponent statusContainer, dockingContainer, controlContainer;
@@ -349,20 +350,34 @@ namespace Barotrauma.Items.Components
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (GameMain.GameSession?.Campaign is CampaignMode campaign)
if (GameMain.GameSession?.Campaign != null)
{ {
if (Level.IsLoadedOutpost && if (Level.IsLoadedOutpost &&
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false))) DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
{ {
GameMain.GameSession.Campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map); // Undocking from an outpost
GameMain.GameSession.Campaign.ShowCampaignUI = true; campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
campaign.ShowCampaignUI = true;
return false; return false;
} }
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null && else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
!ActiveDockingSource.Docked && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false)) !ActiveDockingSource.Docked && DockingTarget?.Item?.Submarine == Level.Loaded.StartOutpost && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false))
{ {
enterOutpostPrompt = new GUIMessageBox("", TextManager.GetWithVariable("campaignenteroutpostprompt", "[locationname]", DockingTarget.Item.Submarine.Info.Name), new string[] { TextManager.Get("yes"), TextManager.Get("no") }); // Docking to an outpost
var subsToLeaveBehind = campaign.GetSubsToLeaveBehind(Item.Submarine);
if (subsToLeaveBehind.Any())
{
enterOutpostPrompt = new GUIMessageBox(
TextManager.GetWithVariable("enterlocation", "[locationname]", DockingTarget.Item.Submarine.Info.Name),
TextManager.Get(subsToLeaveBehind.Count == 1 ? "LeaveSubBehind" : "LeaveSubsBehind"),
new string[] { TextManager.Get("yes"), TextManager.Get("no") });
}
else
{
enterOutpostPrompt = new GUIMessageBox("",
TextManager.GetWithVariable("campaignenteroutpostprompt", "[locationname]", DockingTarget.Item.Submarine.Info.Name),
new string[] { TextManager.Get("yes"), TextManager.Get("no") });
}
enterOutpostPrompt.Buttons[0].OnClicked += (btn, userdata) => enterOutpostPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{ {
SendDockingSignal(); SendDockingSignal();
@@ -780,10 +795,16 @@ namespace Barotrauma.Items.Components
} }
checkConnectedPortsTimer = CheckConnectedPortsInterval; checkConnectedPortsTimer = CheckConnectedPortsInterval;
} }
else
{
checkConnectedPortsTimer -= deltaTime;
}
float closestDist = DockingAssistThreshold * DockingAssistThreshold;
DockingModeEnabled = false; DockingModeEnabled = false;
if (connectedPorts.None()) { return; }
float closestDist = DockingAssistThreshold * DockingAssistThreshold;
foreach (DockingPort sourcePort in connectedPorts) foreach (DockingPort sourcePort in connectedPorts)
{ {
if (sourcePort.Docked || sourcePort.Item.Submarine == null) { continue; } if (sourcePort.Docked || sourcePort.Item.Submarine == null) { continue; }
@@ -795,15 +816,14 @@ namespace Barotrauma.Items.Components
{ {
if (targetPort.Docked || targetPort.Item.Submarine == null) { continue; } if (targetPort.Docked || targetPort.Item.Submarine == null) { continue; }
if (targetPort.Item.Submarine == controlledSub || targetPort.IsHorizontal != sourcePort.IsHorizontal) { continue; } if (targetPort.Item.Submarine == controlledSub || targetPort.IsHorizontal != sourcePort.IsHorizontal) { continue; }
if (targetPort.Item.Submarine.DockedTo?.Contains(sourcePort.Item.Submarine) ?? false) { continue; }
if (Level.Loaded != null && targetPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; } if (Level.Loaded != null && targetPort.Item.Submarine.WorldPosition.Y > Level.Loaded.Size.Y) { continue; }
if (sourceDir == targetPort.GetDir()) { continue; }
int targetDir = targetPort.GetDir();
if (sourceDir == targetDir) { continue; }
float dist = Vector2.DistanceSquared(sourcePort.Item.WorldPosition, targetPort.Item.WorldPosition); float dist = Vector2.DistanceSquared(sourcePort.Item.WorldPosition, targetPort.Item.WorldPosition);
if (dist < closestDist) if (dist < closestDist)
{ {
closestDist = dist;
DockingModeEnabled = true; DockingModeEnabled = true;
ActiveDockingSource = sourcePort; ActiveDockingSource = sourcePort;
DockingTarget = targetPort; DockingTarget = targetPort;
@@ -0,0 +1,55 @@
using System;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma.Items.Components
{
internal partial class Planter
{
public Vector2 DrawSize => CalculateSize();
private Vector2 CalculateSize()
{
if (GrowableSeeds.All(s => s == null)) { return Vector2.Zero; }
Point pos = item.DrawPosition.ToPoint();
Rectangle rect = new Rectangle(pos, Point.Zero);
for (int i = 0; i < GrowableSeeds.Length; i++)
{
Growable seed = GrowableSeeds[i];
PlantSlot slot = PlantSlots.ContainsKey(i) ? PlantSlots[i] : NullSlot;
if (seed == null) { continue; }
foreach (VineTile vine in seed.Vines)
{
Rectangle worldRect = vine.Rect;
worldRect.Location += slot.Offset.ToPoint();
worldRect.Location += pos;
rect = Rectangle.Union(rect, worldRect);
}
}
Vector2 result = new Vector2(MaxDistance(pos.X, rect.Left, rect.Right) * 2, MaxDistance(pos.Y, rect.Top, rect.Bottom) * 2);
return result;
static float MaxDistance(float origin, float x, float y)
{
return Math.Max(Math.Abs(origin - x), Math.Abs(origin - y));
}
}
private LightComponent lightComponent;
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{
for (var i = 0; i < GrowableSeeds.Length; i++)
{
Growable growable = GrowableSeeds[i];
PlantSlot slot = PlantSlots.ContainsKey(i) ? PlantSlots[i] : NullSlot;
growable?.Draw(spriteBatch, this, slot.Offset, itemDepth);
}
}
}
}
@@ -29,6 +29,7 @@ namespace Barotrauma.Items.Components
private List<Pair<RelatedItem, ParticleEmitter>> particleEmitterHitItem = new List<Pair<RelatedItem, ParticleEmitter>>(); private List<Pair<RelatedItem, ParticleEmitter>> particleEmitterHitItem = new List<Pair<RelatedItem, ParticleEmitter>>();
private float prevProgressBarState; private float prevProgressBarState;
private Item prevProgressBarTarget = null;
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
@@ -65,7 +66,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (ParticleEmitter particleEmitter in particleEmitters) foreach (ParticleEmitter particleEmitter in particleEmitters)
{ {
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
particleEmitter.Emit( particleEmitter.Emit(
deltaTime, ConvertUnits.ToDisplayUnits(raystart), deltaTime, ConvertUnits.ToDisplayUnits(raystart),
item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0); item.CurrentHull, particleAngle, particleEmitter.Prefab.CopyEntityAngle ? -particleAngle : 0);
@@ -92,7 +93,7 @@ namespace Barotrauma.Items.Components
if (targetStructure.Submarine != null) particlePos += targetStructure.Submarine.DrawPosition; if (targetStructure.Submarine != null) particlePos += targetStructure.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitStructure) foreach (var emitter in particleEmitterHitStructure)
{ {
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
} }
} }
@@ -103,7 +104,7 @@ namespace Barotrauma.Items.Components
if (targetCharacter.Submarine != null) particlePos += targetCharacter.Submarine.DrawPosition; if (targetCharacter.Submarine != null) particlePos += targetCharacter.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitCharacter) foreach (var emitter in particleEmitterHitCharacter)
{ {
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); emitter.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
} }
} }
@@ -111,7 +112,7 @@ namespace Barotrauma.Items.Components
partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem) partial void FixItemProjSpecific(Character user, float deltaTime, Item targetItem)
{ {
float progressBarState = targetItem.ConditionPercentage / 100.0f; float progressBarState = targetItem.ConditionPercentage / 100.0f;
if (!MathUtils.NearlyEqual(progressBarState, prevProgressBarState)) if (!MathUtils.NearlyEqual(progressBarState, prevProgressBarState) || prevProgressBarTarget != targetItem)
{ {
var door = targetItem.GetComponent<Door>(); var door = targetItem.GetComponent<Door>();
if (door == null || door.Stuck <= 0) if (door == null || door.Stuck <= 0)
@@ -121,18 +122,20 @@ namespace Barotrauma.Items.Components
targetItem, targetItem,
progressBarPos, progressBarPos,
progressBarState, progressBarState,
GUI.Style.Red, GUI.Style.Green); GUI.Style.Red, GUI.Style.Green,
progressBarState < prevProgressBarState ? "progressbar.cutting" : "");
if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); } if (progressBar != null) { progressBar.Size = new Vector2(60.0f, 20.0f); }
} }
prevProgressBarState = progressBarState; prevProgressBarState = progressBarState;
prevProgressBarTarget = targetItem;
} }
Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition); Vector2 particlePos = ConvertUnits.ToDisplayUnits(pickedPosition);
if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition; if (targetItem.Submarine != null) particlePos += targetItem.Submarine.DrawPosition;
foreach (var emitter in particleEmitterHitItem) foreach (var emitter in particleEmitterHitItem)
{ {
if (!emitter.First.MatchesItem(targetItem)) continue; if (!emitter.First.MatchesItem(targetItem)) { continue; }
float particleAngle = item.body.Rotation + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi); float particleAngle = item.body.Rotation + MathHelper.ToRadians(BarrelRotation) + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi); emitter.Second.Emit(deltaTime, particlePos, item.CurrentHull, particleAngle + MathHelper.Pi, -particleAngle + MathHelper.Pi);
} }
} }
@@ -192,6 +192,7 @@ namespace Barotrauma.Items.Components
foreach (Wire wire in panel.DisconnectedWires) foreach (Wire wire in panel.DisconnectedWires)
{ {
if (wire == DraggingConnected && mouseInRect) { continue; } if (wire == DraggingConnected && mouseInRect) { continue; }
if (wire.HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
Connection recipient = wire.OtherConnection(null); Connection recipient = wire.OtherConnection(null);
string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})"; string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
@@ -238,6 +239,7 @@ namespace Barotrauma.Items.Components
for (int i = 0; i < MaxLinked; i++) for (int i = 0; i < MaxLinked; i++)
{ {
if (wires[i] == null || wires[i].Hidden || (DraggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; } if (wires[i] == null || wires[i].Hidden || (DraggingConnected == wires[i] && (mouseIn || Screen.Selected == GameMain.SubEditorScreen))) { continue; }
if (wires[i].HiddenInGame && Screen.Selected == GameMain.GameScreen) { continue; }
Connection recipient = wires[i].OtherConnection(this); Connection recipient = wires[i].OtherConnection(this);
string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})"; string label = recipient == null ? "" : recipient.item.Name + $" ({recipient.DisplayName})";
@@ -289,7 +291,7 @@ namespace Barotrauma.Items.Components
flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale); flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale);
} }
if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden)) if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden && (!w.HiddenInGame || Screen.Selected != GameMain.GameScreen)))
{ {
int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count;
screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale); screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale);
@@ -325,7 +327,7 @@ namespace Barotrauma.Items.Components
canDrag && canDrag &&
((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) && ((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
PlayerInput.MousePosition.X < Math.Max(start.X, end.X) && PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
MathUtils.LineToPointDistance(start, end, PlayerInput.MousePosition) < 6) || MathUtils.LineToPointDistanceSquared(start, end, PlayerInput.MousePosition) < 36) ||
Vector2.Distance(end, PlayerInput.MousePosition) < 20.0f || Vector2.Distance(end, PlayerInput.MousePosition) < 20.0f ||
new Rectangle((start.X < end.X) ? textX - 100 : textX, (int)start.Y - 5, 100, 14).Contains(PlayerInput.MousePosition)); new Rectangle((start.X < end.X) ? textX - 100 : textX, (int)start.Y - 5, 100, 14).Contains(PlayerInput.MousePosition));
@@ -0,0 +1,12 @@
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
partial class MemoryComponent : ItemComponent
{
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
Value = msg.ReadString();
}
}
}
@@ -165,6 +165,12 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
if (!string.IsNullOrEmpty(target.customInteractHUDText) && target.AllowCustomInteract)
{
texts.Add(target.customInteractHUDText);
textColors.Add(GUI.Style.Green);
}
if (target.IsUnconscious) if (target.IsUnconscious)
{ {
texts.Add(TextManager.Get("Unconscious")); texts.Add(TextManager.Get("Unconscious"));
@@ -17,6 +17,17 @@ namespace Barotrauma.Items.Components
private GUIProgressBar powerIndicator; private GUIProgressBar powerIndicator;
public int UIElementHeight
{
get
{
int height = 0;
if (ShowChargeIndicator) { height += powerIndicator.Rect.Height; }
if (ShowProjectileIndicator) { height += (int)(Inventory.SlotSpriteSmall.size.Y * Inventory.UIScale) + 5; }
return height;
}
}
private float recoilTimer; private float recoilTimer;
private float RetractionTime => Math.Max(Reload * RetractionDurationMultiplier, RecoilTime); private float RetractionTime => Math.Max(Reload * RetractionDurationMultiplier, RecoilTime);
@@ -235,12 +246,11 @@ 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); if (!MathUtils.NearlyEqual(item.Rotation, prevBaseRotation))
if (item.Submarine != null)
{ {
drawPos += item.Submarine.DrawPosition; UpdateTransformedBarrelPos();
} }
drawPos.Y = -drawPos.Y; Vector2 drawPos = GetDrawPos();
float recoilOffset = 0.0f; float recoilOffset = 0.0f;
if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f) if (Math.Abs(RecoilDistance) > 0.0f && recoilTimer > 0.0f)
@@ -275,7 +285,7 @@ namespace Barotrauma.Items.Components
rotation + MathHelper.PiOver2, item.Scale, rotation + MathHelper.PiOver2, item.Scale,
SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth)); SpriteEffects.None, item.SpriteDepth + (barrelSprite.Depth - item.Sprite.Depth));
if (!editing) { return; } if (!editing || GUI.DisableHUD) { return; }
float widgetRadius = 60.0f; float widgetRadius = 60.0f;
@@ -310,7 +320,7 @@ namespace Barotrauma.Items.Components
}; };
widget.MouseHeld += (deltaTime) => widget.MouseHeld += (deltaTime) =>
{ {
minRotation = GetRotationAngle(drawPos); minRotation = GetRotationAngle(GetDrawPos());
if (minRotation > maxRotation) if (minRotation > maxRotation)
{ {
float temp = minRotation; float temp = minRotation;
@@ -332,7 +342,7 @@ namespace Barotrauma.Items.Components
widget.PreDraw += (sprtBtch, deltaTime) => widget.PreDraw += (sprtBtch, deltaTime) =>
{ {
widget.tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation); widget.tooltip = "Min: " + (int)MathHelper.ToDegrees(minRotation);
widget.DrawPos = drawPos + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * widgetRadius; widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(minRotation), (float)Math.Sin(minRotation)) * widgetRadius;
widget.Update(deltaTime); widget.Update(deltaTime);
}; };
}); });
@@ -351,7 +361,7 @@ namespace Barotrauma.Items.Components
}; };
widget.MouseHeld += (deltaTime) => widget.MouseHeld += (deltaTime) =>
{ {
maxRotation = GetRotationAngle(drawPos); maxRotation = GetRotationAngle(GetDrawPos());
if (minRotation > maxRotation) if (minRotation > maxRotation)
{ {
float temp = minRotation; float temp = minRotation;
@@ -373,12 +383,20 @@ namespace Barotrauma.Items.Components
widget.PreDraw += (sprtBtch, deltaTime) => widget.PreDraw += (sprtBtch, deltaTime) =>
{ {
widget.tooltip = "Max: " + (int)MathHelper.ToDegrees(maxRotation); widget.tooltip = "Max: " + (int)MathHelper.ToDegrees(maxRotation);
widget.DrawPos = drawPos + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * widgetRadius; widget.DrawPos = GetDrawPos() + new Vector2((float)Math.Cos(maxRotation), (float)Math.Sin(maxRotation)) * widgetRadius;
widget.Update(deltaTime); widget.Update(deltaTime);
}; };
}); });
minRotationWidget.Draw(spriteBatch, (float)Timing.Step); minRotationWidget.Draw(spriteBatch, (float)Timing.Step);
maxRotationWidget.Draw(spriteBatch, (float)Timing.Step); maxRotationWidget.Draw(spriteBatch, (float)Timing.Step);
Vector2 GetDrawPos()
{
Vector2 drawPos = new Vector2(item.Rect.X + transformedBarrelPos.X, item.Rect.Y - transformedBarrelPos.Y);
if (item.Submarine != null) { drawPos += item.Submarine.DrawPosition; }
drawPos.Y = -drawPos.Y;
return drawPos;
}
} }
private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, Action<Widget> initMethod = null) private Widget GetWidget(string id, SpriteBatch spriteBatch, int size = 5, Action<Widget> initMethod = null)
@@ -715,7 +715,12 @@ namespace Barotrauma
/// <returns></returns> /// <returns></returns>
public static bool IsMouseOnInventory(bool ignoreDraggedItem = false) public static bool IsMouseOnInventory(bool ignoreDraggedItem = false)
{ {
var isSubEditor = Screen.Selected is SubEditorScreen editor && !editor.WiringMode; if (GameMain.GameSession?.Campaign != null &&
(GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI))
{
return false;
}
if (Character.Controlled == null) { return false; } if (Character.Controlled == null) { return false; }
if (!ignoreDraggedItem) if (!ignoreDraggedItem)
@@ -723,6 +728,8 @@ namespace Barotrauma
if (draggingItem != null || DraggingInventory != null) { return true; } if (draggingItem != null || DraggingInventory != null) { return true; }
} }
var isSubEditor = Screen.Selected is SubEditorScreen editor && !editor.WiringMode;
if (Character.Controlled.Inventory != null && !isSubEditor) if (Character.Controlled.Inventory != null && !isSubEditor)
{ {
var inv = Character.Controlled.Inventory; var inv = Character.Controlled.Inventory;
@@ -840,7 +847,8 @@ namespace Barotrauma
foreach (var ic in character.SelectedConstruction.ActiveHUDs) foreach (var ic in character.SelectedConstruction.ActiveHUDs)
{ {
var itemContainer = ic as ItemContainer; var itemContainer = ic as ItemContainer;
if (itemContainer?.Inventory?.slots == null) continue; if (itemContainer?.Inventory?.slots == null) { continue; }
if (ic.Item.NonInteractable) { continue; }
foreach (var slot in itemContainer.Inventory.slots) foreach (var slot in itemContainer.Inventory.slots)
{ {
@@ -1127,7 +1135,6 @@ namespace Barotrauma
return hoverArea; return hoverArea;
} }
public static void DrawFront(SpriteBatch spriteBatch) public static void DrawFront(SpriteBatch spriteBatch)
{ {
if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; } if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; }
@@ -1202,6 +1209,7 @@ namespace Barotrauma
} }
Color slotColor = Color.White; Color slotColor = Color.White;
if ((inventory?.Owner as Item)?.NonInteractable ?? false) { slotColor = Color.Gray; }
var itemContainer = item?.GetComponent<ItemContainer>(); var itemContainer = item?.GetComponent<ItemContainer>();
if (itemContainer != null && (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null)) if (itemContainer != null && (itemContainer.InventoryTopSprite != null || itemContainer.InventoryBottomSprite != null))
{ {
@@ -1470,18 +1478,19 @@ namespace Barotrauma
{ {
if (receivedItemIDs[i] == 0 || (Entity.FindEntityByID(receivedItemIDs[i]) as Item != Items[i])) if (receivedItemIDs[i] == 0 || (Entity.FindEntityByID(receivedItemIDs[i]) as Item != Items[i]))
{ {
if (Items[i] != null) Items[i].Drop(null); Items[i]?.Drop(null);
System.Diagnostics.Debug.Assert(Items[i] == null); System.Diagnostics.Debug.Assert(Items[i] == null);
} }
} }
for (int i = 0; i < capacity; i++) //iterate backwards to get the item to the Any slots first
for (int i = capacity - 1; i >= 0; i--)
{ {
if (receivedItemIDs[i] > 0) if (receivedItemIDs[i] > 0)
{ {
if (!(Entity.FindEntityByID(receivedItemIDs[i]) is Item item) || Items[i] == item) { continue; } if (!(Entity.FindEntityByID(receivedItemIDs[i]) is Item item) || Items[i] == item) { continue; }
TryPutItem(item, i, true, true, null, false); TryPutItem(item, i, false, false, null, false);
for (int j = 0; j < capacity; j++) for (int j = 0; j < capacity; j++)
{ {
if (Items[j] == item && receivedItemIDs[j] != item.ID) if (Items[j] == item && receivedItemIDs[j] != item.ID)
@@ -93,8 +93,6 @@ namespace Barotrauma
} }
} }
public float SpriteRotation;
public Color GetSpriteColor() public Color GetSpriteColor()
{ {
Color color = spriteColor; Color color = spriteColor;
@@ -297,7 +295,7 @@ namespace Barotrauma
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, -rotationRad) * Scale;
decorativeSprite.Sprite.DrawTiled(spriteBatch, decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)), new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
size, color: color, size, color: color,
@@ -307,15 +305,24 @@ namespace Barotrauma
} }
else else
{ {
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, SpriteRotation + rotation, Scale, activeSprite.effects, depth); Vector2 origin = activeSprite.Origin;
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, SpriteRotation + rotation, Scale, activeSprite.effects, depth - 0.000001f); if ((activeSprite.effects & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
{
origin.X = activeSprite.SourceRect.Width - origin.X;
}
if ((activeSprite.effects & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically)
{
origin.Y = activeSprite.SourceRect.Height - origin.Y;
}
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, origin, rotationRad, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, origin, rotationRad, Scale, activeSprite.effects, depth - 0.000001f);
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, -rotationRad) * 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 + rot, decorativeSprite.Scale * Scale, activeSprite.effects, rotationRad + rot, 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));
} }
} }
@@ -358,7 +365,7 @@ namespace Barotrauma
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
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, -rotationRad) * 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);
@@ -378,7 +385,7 @@ namespace Barotrauma
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
var (xOff, yOff) = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; var (xOff, yOff) = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState, -rotationRad) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + xOff, -(DrawPosition.Y + yOff)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + xOff, -(DrawPosition.Y + yOff)), color,
rotation, decorativeSprite.Scale * Scale, activeSprite.effects, rotation, decorativeSprite.Scale * Scale, activeSprite.effects,
@@ -437,7 +444,7 @@ namespace Barotrauma
} }
} }
if (!ShowLinks) return; if (!ShowLinks || GUI.DisableHUD) { return; }
foreach (MapEntity e in linkedTo) foreach (MapEntity e in linkedTo)
{ {
@@ -455,21 +462,6 @@ namespace Barotrauma
} }
} }
private void DrawDecorativeSprite(SpriteBatch spriteBatch, DecorativeSprite decorativeSprite, Color color, float depth)
{
if (!spriteAnimState[decorativeSprite].IsActive) { return; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
var ca = (float)Math.Cos(-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);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color,
-body.Rotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
partial void OnCollisionProjSpecific(float impact) partial void OnCollisionProjSpecific(float impact)
{ {
if (impact > 1.0f && if (impact > 1.0f &&
@@ -1154,7 +1146,6 @@ namespace Barotrauma
editingHUDRefreshPending = true; editingHUDRefreshPending = true;
break; break;
case NetEntityEvent.Type.Upgrade: case NetEntityEvent.Type.Upgrade:
{
string identifier = msg.ReadString(); string identifier = msg.ReadString();
byte level = msg.ReadByte(); byte level = msg.ReadByte();
if (UpgradePrefab.Find(identifier) is { } upgradePrefab) if (UpgradePrefab.Find(identifier) is { } upgradePrefab)
@@ -1175,7 +1166,6 @@ namespace Barotrauma
AddUpgrade(upgrade, false); AddUpgrade(upgrade, false);
} }
break; break;
}
case NetEntityEvent.Type.Invalid: case NetEntityEvent.Type.Invalid:
break; break;
} }
@@ -1391,7 +1381,7 @@ namespace Barotrauma
if (itemPrefab == null) if (itemPrefab == null)
{ {
string errorMsg = "Failed to spawn item, prefab not found (name: " + (itemName ?? "null") + ", identifier: " + (itemIdentifier ?? "null") + ")"; string errorMsg = "Failed to spawn item, prefab not found (name: " + (itemName ?? "null") + ", identifier: " + (itemIdentifier ?? "null") + ")";
errorMsg += "\n" + string.Join(", ", GameMain.Config.SelectedContentPackages.Select(cp => cp.Name)); errorMsg += "\n" + string.Join(", ", GameMain.Config.AllEnabledPackages.Select(cp => cp.Name));
GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:PrefabNotFound" + (itemName ?? "null") + (itemIdentifier ?? "null"), GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:PrefabNotFound" + (itemName ?? "null") + (itemIdentifier ?? "null"),
GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, GameAnalyticsSDK.Net.EGAErrorSeverity.Critical,
errorMsg); errorMsg);
@@ -55,6 +55,7 @@ namespace Barotrauma
public List<ContainedItemSprite> ContainedSprites = new List<ContainedItemSprite>(); public List<ContainedItemSprite> ContainedSprites = new List<ContainedItemSprite>();
public Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>(); public Dictionary<int, List<DecorativeSprite>> DecorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
public Sprite InventoryIcon; public Sprite InventoryIcon;
public Sprite MinimapIcon;
//only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis //only used to display correct color in the sub editor, item instances have their own property that can be edited on a per-item basis
[Serialize("1.0,1.0,1.0,1.0", false)] [Serialize("1.0,1.0,1.0,1.0", false)]
@@ -90,6 +91,7 @@ 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();
item.Submarine = Submarine.MainSub;
if (PlayerInput.IsShiftDown()) if (PlayerInput.IsShiftDown())
{ {
@@ -14,7 +14,7 @@ namespace Barotrauma
Vector2.Zero, 0.0f, hull); Vector2.Zero, 0.0f, hull);
} }
hull = hull ?? Hull.FindHull(worldPosition, useWorldCoordinates: true); hull ??= Hull.FindHull(worldPosition, useWorldCoordinates: true);
bool underwater = hull == null || worldPosition.Y < hull.WorldSurface; bool underwater = hull == null || worldPosition.Y < hull.WorldSurface;
if (underwater && underwaterBubble) if (underwater && underwaterBubble)
@@ -44,7 +44,7 @@ namespace Barotrauma
} }
if (smoke) if (smoke)
{ {
var smokeParticle = GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke", GameMain.ParticleManager.CreateParticle(Rand.Range(0.0f, 1.0f) < 0.5f ? "explosionsmoke" : "smoke",
ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull), ClampParticlePos(worldPosition + Rand.Vector((float)System.Math.Sqrt(Rand.Range(0.0f, attack.Range))), hull),
Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull); Rand.Vector(Rand.Range(0.0f, particleSpeed)), 0.0f, hull);
} }
@@ -75,11 +75,6 @@ namespace Barotrauma
} }
} }
if (hull != null && !string.IsNullOrWhiteSpace(decal) && decalSize > 0.0f)
{
hull.AddDecal(decal, worldPosition, decalSize);
}
if (flash) if (flash)
{ {
float displayRange = flashRange.HasValue ? flashRange.Value : attack.Range; float displayRange = flashRange.HasValue ? flashRange.Value : attack.Range;
@@ -15,32 +15,7 @@ namespace Barotrauma
lightSource.Color = new Color(1.0f, 0.45f, 0.3f) * Rand.Range(0.8f, 1.0f); lightSource.Color = new Color(1.0f, 0.45f, 0.3f) * Rand.Range(0.8f, 1.0f);
if (Math.Abs((lightSource.Range * 0.2f) - Math.Max(size.X, size.Y)) > 1.0f) lightSource.Range = Math.Max(size.X, size.Y) * 5.0f; if (Math.Abs((lightSource.Range * 0.2f) - Math.Max(size.X, size.Y)) > 1.0f) lightSource.Range = Math.Max(size.X, size.Y) * 5.0f;
if (Vector2.DistanceSquared(lightSource.Position,position) > 5.0f) lightSource.Position = position + Vector2.UnitY * 30.0f; if (Vector2.DistanceSquared(lightSource.Position, position) > 5.0f) lightSource.Position = position + Vector2.UnitY * 30.0f;
if (size.X > 256.0f)
{
if (burnDecals.Count == 0)
{
var newDecal = hull.AddDecal("burnt", WorldPosition + size/2);
if (newDecal != null) burnDecals.Add(newDecal);
}
else if (WorldPosition.X < burnDecals[0].WorldPosition.X - 256.0f)
{
var newDecal = hull.AddDecal("burnt", WorldPosition);
if (newDecal != null) burnDecals.Insert(0, newDecal);
}
else if (WorldPosition.X + size.X > burnDecals[burnDecals.Count-1].WorldPosition.X + 256.0f)
{
var newDecal = hull.AddDecal("burnt", WorldPosition + Vector2.UnitX * size.X);
if (newDecal != null) burnDecals.Add(newDecal);
}
}
foreach (Decal d in burnDecals)
{
//prevent the decals from fading out as long as the firesource is alive
d.FadeTimer = Math.Min(d.FadeTimer, d.FadeInTime);
}
} }
public static void EmitParticles(Vector2 size, Vector2 worldPosition, Hull hull, float growModifier, Particle.OnChangeHullHandler onChangeHull = null) public static void EmitParticles(Vector2 size, Vector2 worldPosition, Hull hull, float growModifier, Particle.OnChangeHullHandler onChangeHull = null)
@@ -1,6 +1,4 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
@@ -12,16 +10,10 @@ namespace Barotrauma
{ {
partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable partial class Hull : MapEntity, ISerializableEntity, IServerSerializable, IClientSerializable
{ {
public const int MaxDecalsPerHull = 10;
private readonly List<Decal> decals = new List<Decal>();
private float serverUpdateDelay; private float serverUpdateDelay;
private float remoteWaterVolume, remoteOxygenPercentage; private float remoteWaterVolume, remoteOxygenPercentage;
private List<Vector3> remoteFireSources; private List<Vector3> remoteFireSources;
private readonly List<BackgroundSection> remoteBackgroundSections = new List<BackgroundSection>();
private bool networkUpdatePending;
private float networkUpdateTimer;
private double lastAmbientLightEditTime; private double lastAmbientLightEditTime;
@@ -49,11 +41,14 @@ namespace Barotrauma
} }
} }
private float paintAmount = 0.0f;
private float minimumPaintAmountToDraw;
public override bool IsVisible(Rectangle worldView) public override bool IsVisible(Rectangle worldView)
{ {
if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw) if (Screen.Selected != GameMain.SubEditorScreen && !GameMain.DebugDraw)
{ {
if (decals.Count == 0) { return false; } if (decals.Count == 0 && paintAmount < minimumPaintAmountToDraw) { return false; }
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; }
@@ -70,20 +65,6 @@ namespace Barotrauma
!Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -8), position)); !Submarine.RectContains(MathUtils.ExpandRect(WorldRect, -8), position));
} }
public Decal AddDecal(string decalName, Vector2 worldPosition, float scale = 1.0f)
{
if (decals.Count >= MaxDecalsPerHull) return null;
var decal = GameMain.DecalManager.CreateDecal(decalName, scale, worldPosition, this);
if (decal != null)
{
decals.Add(decal);
}
return decal;
}
private GUIComponent CreateEditingHUD(bool inGame = false) private GUIComponent CreateEditingHUD(bool inGame = false)
{ {
editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this }; editingHUD = new GUIFrame(new RectTransform(new Vector2(0.3f, 0.25f), GUI.Canvas, Anchor.CenterRight) { MinSize = new Point(400, 0) }) { UserData = this };
@@ -102,38 +83,31 @@ namespace Barotrauma
editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen); editingHUD = CreateEditingHUD(Screen.Selected != GameMain.SubEditorScreen);
} }
if (!PlayerInput.KeyDown(Keys.Space)) return; if (!PlayerInput.KeyDown(Keys.Space)) { return; }
bool lClick = PlayerInput.PrimaryMouseButtonClicked(); bool lClick = PlayerInput.PrimaryMouseButtonClicked();
bool rClick = PlayerInput.SecondaryMouseButtonClicked(); bool rClick = PlayerInput.SecondaryMouseButtonClicked();
if (!lClick && !rClick) return; if (!lClick && !rClick) { return; }
Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = cam.ScreenToWorld(PlayerInput.MousePosition);
if (lClick) foreach (MapEntity entity in mapEntityList)
{ {
foreach (MapEntity entity in mapEntityList) if (entity == this || !entity.IsHighlighted) { continue; }
if (!entity.IsMouseOn(position)) { continue; }
if (entity.linkedTo != null && entity.linkedTo.Contains(this))
{ {
if (entity == this || !entity.IsHighlighted) continue; if (entity == this || !entity.IsHighlighted) continue;
if (!entity.IsMouseOn(position)) continue; if (!entity.IsMouseOn(position)) continue;
if (entity.Linkable && entity.linkedTo != null) if (entity.Linkable && entity.linkedTo != null && !entity.linkedTo.Contains(this))
{ {
entity.linkedTo.Add(this); entity.linkedTo.Add(this);
linkedTo.Add(entity); linkedTo.Add(entity);
} }
} }
} else if (entity.Linkable && entity.linkedTo != null)
else
{
foreach (MapEntity entity in mapEntityList)
{ {
if (entity == this || !entity.IsHighlighted) continue; entity.linkedTo.Add(this);
if (!entity.IsMouseOn(position)) continue; linkedTo.Add(entity);
if (entity.linkedTo != null && entity.linkedTo.Contains(this))
{
entity.linkedTo.Remove(this);
linkedTo.Remove(entity);
}
} }
} }
} }
@@ -151,7 +125,15 @@ namespace Barotrauma
networkUpdateTimer += deltaTime; networkUpdateTimer += deltaTime;
if (networkUpdateTimer > 0.2f) if (networkUpdateTimer > 0.2f)
{ {
GameMain.NetworkMember?.CreateEntityEvent(this); if (!pendingSectionUpdates.Any())
{
GameMain.NetworkMember?.CreateEntityEvent(this);
}
foreach (int pendingSectionUpdate in pendingSectionUpdates)
{
GameMain.NetworkMember?.CreateEntityEvent(this, new object[] { pendingSectionUpdate });
}
pendingSectionUpdates.Clear();
networkUpdatePending = false; networkUpdatePending = false;
networkUpdateTimer = 0.0f; networkUpdateTimer = 0.0f;
} }
@@ -190,13 +172,6 @@ namespace Barotrauma
} }
} }
foreach (Decal decal in decals)
{
decal.Update(deltaTime);
}
decals.RemoveAll(d => d.FadeTimer >= d.LifeTime);
if (waterVolume < 1.0f) return; if (waterVolume < 1.0f) return;
for (int i = 1; i < waveY.Length - 1; i++) for (int i = 1; i < waveY.Length - 1; i++)
{ {
@@ -324,6 +299,35 @@ namespace Barotrauma
} }
} }
public void DrawSectionColors(SpriteBatch spriteBatch)
{
Vector2 drawOffset = Submarine == null ? Vector2.Zero : Submarine.DrawPosition;
Point sectionSize = BackgroundSections[0].Rect.Size;
Vector2 drawPos = drawOffset + new Vector2(rect.Location.X + sectionSize.X / 2, rect.Location.Y - sectionSize.Y / 2);
for (int i = 0; i < BackgroundSections.Count; i++)
{
BackgroundSection section = BackgroundSections[i];
if (section.ColorStrength < 0.01f || section.Color.A < 1) { continue; }
if (GameMain.DecalManager.GrimeSprites.Count == 0)
{
GUI.DrawRectangle(spriteBatch,
new Vector2(drawOffset.X + rect.X + section.Rect.X, -(drawOffset.Y + rect.Y + section.Rect.Y)),
new Vector2(sectionSize.X, sectionSize.Y),
section.GetStrengthAdjustedColor(), true, 0.0f, (int)Math.Max(1.5f / Screen.Selected.Cam.Zoom, 1.0f));
}
else
{
Vector2 sectionPos = new Vector2(drawPos.X + section.Rect.Location.X, -(drawPos.Y + section.Rect.Location.Y));
Vector2 randomOffset = new Vector2(section.Noise.X - 0.5f, section.Noise.Y - 0.5f) * 15.0f;
var sprite = GameMain.DecalManager.GrimeSprites[i % GameMain.DecalManager.GrimeSprites.Count];
sprite.Draw(spriteBatch, sectionPos + randomOffset, section.GetStrengthAdjustedColor(), scale: 1.25f);
}
}
}
public static void UpdateVertices(Camera cam, WaterRenderer renderer) public static void UpdateVertices(Camera cam, WaterRenderer renderer)
{ {
foreach (EntityGrid entityGrid in EntityGrids) foreach (EntityGrid entityGrid in EntityGrids)
@@ -526,22 +530,38 @@ namespace Barotrauma
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8); msg.Write(extraData != null);
if (extraData == null)
msg.Write(FireSources.Count > 0);
if (FireSources.Count > 0)
{ {
msg.WriteRangedInteger(Math.Min(FireSources.Count, 16), 0, 16); msg.WriteRangedSingle(MathHelper.Clamp(waterVolume / Volume, 0.0f, 1.5f), 0.0f, 1.5f, 8);
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{
var fireSource = FireSources[i];
Vector2 normalizedPos = new Vector2(
(fireSource.Position.X - rect.X) / rect.Width,
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8); msg.Write(FireSources.Count > 0);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8); if (FireSources.Count > 0)
msg.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8); {
msg.WriteRangedInteger(Math.Min(FireSources.Count, 16), 0, 16);
for (int i = 0; i < Math.Min(FireSources.Count, 16); i++)
{
var fireSource = FireSources[i];
Vector2 normalizedPos = new Vector2(
(fireSource.Position.X - rect.X) / rect.Width,
(fireSource.Position.Y - (rect.Y - rect.Height)) / rect.Height);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.X, 0.0f, 1.0f), 0.0f, 1.0f, 8);
msg.WriteRangedSingle(MathHelper.Clamp(normalizedPos.Y, 0.0f, 1.0f), 0.0f, 1.0f, 8);
msg.WriteRangedSingle(MathHelper.Clamp(fireSource.Size.X / rect.Width, 0.0f, 1.0f), 0, 1.0f, 8);
}
}
}
else
{
int sectorToUpdate = (int)extraData[0];
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
msg.WriteRangedInteger(sectorToUpdate, 0, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
msg.WriteRangedSingle(BackgroundSections[i].ColorStrength, 0.0f, 1.0f, 8);
msg.Write(BackgroundSections[i].Color.PackedValue);
} }
} }
} }
@@ -565,6 +585,54 @@ namespace Barotrauma
} }
} }
bool hasExtraData = message.ReadBoolean();
if (hasExtraData)
{
bool hasSectionUpdate = message.ReadBoolean();
if (hasSectionUpdate)
{
int sectorToUpdate = message.ReadRangedInteger(0, BackgroundSections.Count - 1);
int start = sectorToUpdate * BackgroundSectionsPerNetworkEvent;
int end = Math.Min((sectorToUpdate + 1) * BackgroundSectionsPerNetworkEvent, BackgroundSections.Count - 1);
for (int i = start; i < end; i++)
{
float colorStrength = message.ReadRangedSingle(0.0f, 1.0f, 8);
Color color = new Color(message.ReadUInt32());
float prevColorStrength = BackgroundSections[i].ColorStrength;
BackgroundSections[i].SetColorStrength(colorStrength);
BackgroundSections[i].SetColor(color);
paintAmount = Math.Max(0, paintAmount + (BackgroundSections[i].ColorStrength - prevColorStrength) / BackgroundSections.Count);
var remoteBackgroundSection = remoteBackgroundSections.Find(s => s.Index == i);
if (remoteBackgroundSection != null)
{
remoteBackgroundSection.SetColorStrength(colorStrength);
remoteBackgroundSection.SetColor(color);
}
else
{
remoteBackgroundSections.Add(new BackgroundSection(new Rectangle(0, 0, 1, 1), i, colorStrength, color, 0));
}
}
paintAmount = BackgroundSections.Sum(s => s.ColorStrength);
}
else
{
int decalCount = message.ReadRangedInteger(0, MaxDecalsPerHull);
decals.Clear();
for (int i = 0; i < decalCount; i++)
{
UInt32 decalId = message.ReadUInt32();
float normalizedXPos = message.ReadRangedSingle(0.0f, 1.0f, 8);
float normalizedYPos = message.ReadRangedSingle(0.0f, 1.0f, 8);
float decalPosX = MathHelper.Lerp(rect.X, rect.Right, normalizedXPos);
float decalPosY = MathHelper.Lerp(rect.Y - rect.Height, rect.Y, normalizedYPos);
float decalScale = message.ReadRangedSingle(0.0f, 2.0f, 12);
AddDecal(decalId, new Vector2(decalPosX, decalPosY), decalScale, isNetworkEvent: true);
}
}
}
if (serverUpdateDelay > 0.0f) { return; } if (serverUpdateDelay > 0.0f) { return; }
ApplyRemoteState(); ApplyRemoteState();
@@ -572,10 +640,14 @@ namespace Barotrauma
private void ApplyRemoteState() private void ApplyRemoteState()
{ {
if (remoteFireSources == null) foreach (BackgroundSection remoteBackgroundSection in remoteBackgroundSections)
{ {
return; BackgroundSections[remoteBackgroundSection.Index].SetColor(remoteBackgroundSection.Color);
BackgroundSections[remoteBackgroundSection.Index].SetColorStrength(remoteBackgroundSection.ColorStrength);
} }
remoteBackgroundSections.Clear();
if (remoteFireSources == null) { return; }
WaterVolume = remoteWaterVolume; WaterVolume = remoteWaterVolume;
OxygenPercentage = remoteOxygenPercentage; OxygenPercentage = remoteOxygenPercentage;
@@ -609,7 +681,6 @@ namespace Barotrauma
FireSources.RemoveAt(i); FireSources.RemoveAt(i);
} }
} }
remoteFireSources = null; remoteFireSources = null;
} }
} }
@@ -34,8 +34,8 @@ namespace Barotrauma
public const int DefaultBufferSize = 2000; public const int DefaultBufferSize = 2000;
public const int DefaultIndoorsBufferSize = 3000; public const int DefaultIndoorsBufferSize = 3000;
public static Vector2 DistortionScale = new Vector2(2f, 2f); public static Vector2 DistortionScale = new Vector2(2f, 1.5f);
public static Vector2 DistortionStrength = new Vector2(0.25f, 0.25f); public static Vector2 DistortionStrength = new Vector2(0.01f, 0.33f);
public static float BlurAmount = 0.0f; public static float BlurAmount = 0.0f;
public Vector2 WavePos public Vector2 WavePos
@@ -2,6 +2,7 @@
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using SharpFont;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@@ -87,6 +88,12 @@ namespace Barotrauma.Lights
} }
} }
class VectorPair
{
public Vector2? A = null;
public Vector2? B = null;
}
class ConvexHull class ConvexHull
{ {
public static List<ConvexHullList> HullLists = new List<ConvexHullList>(); public static List<ConvexHullList> HullLists = new List<ConvexHullList>();
@@ -96,6 +103,7 @@ namespace Barotrauma.Lights
private readonly Segment[] segments = new Segment[4]; private readonly Segment[] segments = new Segment[4];
private readonly SegmentPoint[] vertices = new SegmentPoint[4]; private readonly SegmentPoint[] vertices = new SegmentPoint[4];
private readonly SegmentPoint[] losVertices = new SegmentPoint[4]; private readonly SegmentPoint[] losVertices = new SegmentPoint[4];
private readonly VectorPair[] losOffsets = new VectorPair[4];
private readonly bool[] backFacing; private readonly bool[] backFacing;
private readonly bool[] ignoreEdge; private readonly bool[] ignoreEdge;
@@ -105,6 +113,7 @@ namespace Barotrauma.Lights
public VertexPositionColor[] ShadowVertices { get; private set; } public VertexPositionColor[] ShadowVertices { get; private set; }
public VertexPositionTexture[] PenumbraVertices { get; private set; } public VertexPositionTexture[] PenumbraVertices { get; private set; }
public int ShadowVertexCount { get; private set; } public int ShadowVertexCount { get; private set; }
public int PenumbraVertexCount { get; private set; }
private readonly HashSet<ConvexHull> overlappingHulls = new HashSet<ConvexHull>(); private readonly HashSet<ConvexHull> overlappingHulls = new HashSet<ConvexHull>();
@@ -157,15 +166,24 @@ namespace Barotrauma.Lights
ParentEntity = parent; ParentEntity = parent;
ShadowVertices = new VertexPositionColor[6 * 2]; ShadowVertices = new VertexPositionColor[6 * 4];
PenumbraVertices = new VertexPositionTexture[6]; PenumbraVertices = new VertexPositionTexture[6 * 4];
backFacing = new bool[4]; backFacing = new bool[4];
ignoreEdge = new bool[4]; ignoreEdge = new bool[4];
SetVertices(points); float minX = points[0].X, minY = points[0].Y, maxX = points[0].X, maxY = points[0].Y;
Enabled = true; for (int i = 1; i < vertices.Length; i++)
{
if (points[i].X < minX) minX = points[i].X;
if (points[i].Y < minY) minY = points[i].Y;
if (points[i].X > maxX) maxX = points[i].X;
if (points[i].Y > minY) maxY = points[i].Y;
}
BoundingBox = new Rectangle((int)minX, (int)minY, (int)(maxX - minX), (int)(maxY - minY));
isHorizontal = BoundingBox.Width > BoundingBox.Height; isHorizontal = BoundingBox.Width > BoundingBox.Height;
if (ParentEntity is Structure structure) if (ParentEntity is Structure structure)
@@ -180,6 +198,10 @@ namespace Barotrauma.Lights
if (door != null) { isHorizontal = door.IsHorizontal; } if (door != null) { isHorizontal = door.IsHorizontal; }
} }
SetVertices(points);
Enabled = true;
var chList = HullLists.Find(h => h.Submarine == parent.Submarine); var chList = HullLists.Find(h => h.Submarine == parent.Submarine);
if (chList == null) if (chList == null)
{ {
@@ -202,11 +224,17 @@ namespace Barotrauma.Lights
if (isHorizontal == ch.isHorizontal) if (isHorizontal == ch.isHorizontal)
{ {
if (BoundingBox == ch.BoundingBox) { return; }
//hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces) //hide segments that are roughly at the some position as some other segment (e.g. the ends of two adjacent wall pieces)
float mergeDist = 32; float mergeDist = 16;
float mergeDistSqr = mergeDist * mergeDist; float mergeDistSqr = mergeDist * mergeDist;
Rectangle intersection = Rectangle.Intersect(BoundingBox, ch.BoundingBox);
int intersectionArea = intersection.Width * intersection.Height;
int bboxArea = BoundingBox.Width * BoundingBox.Height;
int otherBboxArea = ch.BoundingBox.Width * ch.BoundingBox.Height;
if (Math.Abs(intersectionArea - bboxArea) < mergeDistSqr) { return; }
if (Math.Abs(intersectionArea - otherBboxArea) < mergeDistSqr) { return; }
for (int i = 0; i < segments.Length; i++) for (int i = 0; i < segments.Length; i++)
{ {
for (int j = 0; j < ch.segments.Length; j++) for (int j = 0; j < ch.segments.Length; j++)
@@ -237,21 +265,67 @@ namespace Barotrauma.Lights
} }
} }
} }
else
for (int i = 0; i < segments.Length; i++)
{ {
//TODO: do something to corner areas where a vertical wall meets a horizontal one if (ignoreEdge[i]) { continue; }
if (Vector2.DistanceSquared(segments[i].Start.Pos, segments[i].End.Pos) < 1.0f) { continue; }
for (int j = 0; j < ch.segments.Length; j++)
{
if (ch.ignoreEdge[j]) { continue; }
if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, ch.segments[j].End.Pos) < 1.0f) { continue; }
if (IsSegmentAInB(segments[i], ch.segments[j]))
{
ignoreEdge[i] = true;
if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, segments[i].Start.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, false, segments[i].End.Pos);
}
else if (Vector2.DistanceSquared(ch.segments[j].Start.Pos, segments[i].End.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, false, segments[i].Start.Pos);
}
if (Vector2.DistanceSquared(ch.segments[j].End.Pos, segments[i].Start.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, true, segments[i].End.Pos);
}
else if (Vector2.DistanceSquared(ch.segments[j].End.Pos, segments[i].End.Pos) < 4.0f)
{
ch.ShiftSegmentPoint(j, true, segments[i].Start.Pos);
}
}
else if (IsSegmentAInB(ch.segments[j], segments[i]))
{
ch.ignoreEdge[j] = true;
if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].Start.Pos) < 4.0f)
{
ShiftSegmentPoint(i, false, ch.segments[j].End.Pos);
}
else if (Vector2.DistanceSquared(segments[i].Start.Pos, ch.segments[j].End.Pos) < 4.0f)
{
ShiftSegmentPoint(i, false, ch.segments[j].Start.Pos);
}
if (Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].Start.Pos) < 4.0f)
{
ShiftSegmentPoint(i, true, ch.segments[j].End.Pos);
}
else if (Vector2.DistanceSquared(segments[i].End.Pos, ch.segments[j].End.Pos) < 4.0f)
{
ShiftSegmentPoint(i, true, ch.segments[j].Start.Pos);
}
}
}
} }
//ignore edges that are inside some other convex hull //ignore edges that are inside some other convex hull
for (int i = 0; i < vertices.Length; i++) for (int i = 0; i < vertices.Length; i++)
{ {
if (vertices[i].Pos.X >= ch.BoundingBox.X && vertices[i].Pos.X <= ch.BoundingBox.Right && if (ch.IsPointInside(vertices[i].Pos))
vertices[i].Pos.Y >= ch.BoundingBox.Y && vertices[i].Pos.Y <= ch.BoundingBox.Bottom)
{ {
Vector2 p = vertices[(i + 1) % vertices.Length].Pos; if (ch.IsPointInside(vertices[(i + 1) % vertices.Length].Pos))
if (p.X >= ch.BoundingBox.X && p.X <= ch.BoundingBox.Right &&
p.Y >= ch.BoundingBox.Y && p.Y <= ch.BoundingBox.Bottom)
{ {
ignoreEdge[i] = true; ignoreEdge[i] = true;
overlappingHulls.Add(ch); overlappingHulls.Add(ch);
@@ -260,6 +334,75 @@ namespace Barotrauma.Lights
} }
} }
private void ShiftSegmentPoint(int segmentIndex, bool end, Vector2 newPos)
{
var segment = segments[segmentIndex];
losOffsets[segmentIndex] ??= new VectorPair();
bool flipped = false;
if (Vector2.DistanceSquared(vertices[segmentIndex].Pos, segment.Start.Pos) > Vector2.DistanceSquared(vertices[segmentIndex].Pos, segment.End.Pos))
{
flipped = true;
}
if (end == !flipped)
{
losOffsets[segmentIndex].B = newPos;
}
else
{
losOffsets[segmentIndex].A = newPos;
}
}
public bool IsSegmentAInB(Segment a, Segment b)
{
if (Vector2.DistanceSquared(a.Start.Pos, a.End.Pos) > Vector2.DistanceSquared(b.Start.Pos, b.End.Pos))
{
return false;
}
Vector2 min = new Vector2(Math.Min(b.Start.Pos.X, b.End.Pos.X), Math.Min(b.Start.Pos.Y, b.End.Pos.Y));
Vector2 max = new Vector2(Math.Max(b.Start.Pos.X, b.End.Pos.X), Math.Max(b.Start.Pos.Y, b.End.Pos.Y));
min.X -= 1.0f; min.Y -= 1.0f;
max.X += 1.0f; max.Y += 1.0f;
if (a.Start.Pos.X < min.X) { return false; }
if (a.Start.Pos.Y < min.Y) { return false; }
if (a.End.Pos.X < min.X) { return false; }
if (a.End.Pos.Y < min.Y) { return false; }
if (a.Start.Pos.X > max.X) { return false; }
if (a.Start.Pos.Y > max.Y) { return false; }
if (a.End.Pos.X > max.X) { return false; }
if (a.End.Pos.Y > max.Y) { return false; }
float startDist = MathUtils.LineToPointDistanceSquared(b.Start.Pos, b.End.Pos, a.Start.Pos);
if (startDist > 1.0f) { return false; }
float endDist = MathUtils.LineToPointDistanceSquared(b.Start.Pos, b.End.Pos, a.End.Pos);
if (endDist > 1.0f) { return false; }
return true;
}
public bool IsPointInside(Vector2 point)
{
if (!BoundingBox.Contains(point)) { return false; }
Vector2 center = (vertices[0].Pos + vertices[1].Pos + vertices[2].Pos + vertices[3].Pos) * 0.25f;
for (int i=0;i<4;i++)
{
Vector2 segmentVector = vertices[(i + 1) % 4].Pos - vertices[i].Pos;
Vector2 centerToVertex = center - vertices[i].Pos;
Vector2 pointToVertex = point - vertices[i].Pos;
float dotCenter = Vector2.Dot(centerToVertex, segmentVector);
float dotPoint = Vector2.Dot(pointToVertex, segmentVector);
if ((dotCenter > 0f && dotPoint < 0f) || (dotCenter < 0f && dotPoint > 0f)) { return false; }
}
return true;
}
private void MergeSegments(Segment segment1, Segment segment2, bool startPointsMatch) private void MergeSegments(Segment segment1, Segment segment2, bool startPointsMatch)
{ {
int startPointIndex = -1, endPointIndex = -1; int startPointIndex = -1, endPointIndex = -1;
@@ -344,6 +487,8 @@ namespace Barotrauma.Lights
vertices[i].Pos += amount; vertices[i].Pos += amount;
losVertices[i].Pos += amount; losVertices[i].Pos += amount;
losOffsets[i] = null;
segments[i].Start.Pos += amount; segments[i].Start.Pos += amount;
segments[i].End.Pos += amount; segments[i].End.Pos += amount;
} }
@@ -406,6 +551,7 @@ namespace Barotrauma.Lights
{ {
vertices[i] = new SegmentPoint(points[i], this); vertices[i] = new SegmentPoint(points[i], this);
losVertices[i] = new SegmentPoint(points[i], this); losVertices[i] = new SegmentPoint(points[i], this);
losOffsets[i] = null;
} }
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
@@ -517,7 +663,7 @@ namespace Barotrauma.Lights
} }
} }
public void CalculateShadowVertices(Vector2 lightSourcePos, bool los = true) public void CalculateLosVertices(Vector2 lightSourcePos)
{ {
Vector3 offset = Vector3.Zero; Vector3 offset = Vector3.Zero;
if (ParentEntity != null && ParentEntity.Submarine != null) if (ParentEntity != null && ParentEntity.Submarine != null)
@@ -527,8 +673,6 @@ namespace Barotrauma.Lights
ShadowVertexCount = 0; ShadowVertexCount = 0;
var vertices = los ? losVertices : this.vertices;
//compute facing of each edge, using N*L //compute facing of each edge, using N*L
for (int i = 0; i < 4; i++) for (int i = 0; i < 4; i++)
{ {
@@ -538,8 +682,8 @@ namespace Barotrauma.Lights
continue; continue;
} }
Vector2 firstVertex = vertices[i].Pos; Vector2 firstVertex = losVertices[i].Pos;
Vector2 secondVertex = vertices[(i+1) % 4].Pos; Vector2 secondVertex = losVertices[(i+1) % 4].Pos;
Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f); Vector2 L = lightSourcePos - ((firstVertex + secondVertex) / 2.0f);
@@ -547,121 +691,144 @@ namespace Barotrauma.Lights
-(secondVertex.Y - firstVertex.Y), -(secondVertex.Y - firstVertex.Y),
secondVertex.X - firstVertex.X); secondVertex.X - firstVertex.X);
backFacing[i] = (Vector2.Dot(N, L) < 0) == los; backFacing[i] = (Vector2.Dot(N, L) < 0);
} }
//find beginning and ending vertices which ShadowVertexCount = 0;
//belong to the shadow for (int i=0;i<4;i++)
int startingIndex = -1;
int endingIndex = -1;
for (int i = 0; i < 4; i++)
{ {
int currentEdge = i; if (!backFacing[i]) { continue; }
int nextEdge = (i + 1) % 4; int currentIndex = i;
Vector3 vertexPos0 = new Vector3(losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos, 0.0f);
Vector3 vertexPos1 = new Vector3(losOffsets[currentIndex]?.B ?? losVertices[(currentIndex + 1) % 4].Pos, 0.0f);
if (backFacing[currentEdge] && !backFacing[nextEdge]) if (Vector3.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
endingIndex = nextEdge;
if (!backFacing[currentEdge] && backFacing[nextEdge]) Vector3 L2P0 = vertexPos0 - new Vector3(lightSourcePos, 0);
startingIndex = nextEdge; L2P0.Normalize();
} Vector3 extruded0 = new Vector3(lightSourcePos, 0) + L2P0 * 9000;
if (startingIndex == -1 || endingIndex == -1) { return; } Vector3 L2P1 = vertexPos1 - new Vector3(lightSourcePos, 0);
L2P1.Normalize();
Vector3 extruded1 = new Vector3(lightSourcePos, 0) + L2P1 * 9000;
//nr of vertices that are in the shadow ShadowVertices[ShadowVertexCount + 0] = new VertexPositionColor
if (endingIndex > startingIndex)
ShadowVertexCount = endingIndex - startingIndex + 1;
else
ShadowVertexCount = 4 + 1 - startingIndex + endingIndex;
//shadowVertices = new VertexPositionColor[shadowVertexCount * 2];
//create a triangle strip that has the shape of the shadow
int currentIndex = startingIndex;
int svCount = 0;
while (svCount != ShadowVertexCount * 2)
{
Vector3 vertexPos = new Vector3(vertices[currentIndex].Pos, 0.0f);
int i = los ? svCount : svCount + 1;
int j = los ? svCount + 1 : svCount;
//one vertex on the hull
ShadowVertices[i] = new VertexPositionColor
{ {
Color = los ? Color.Black : Color.Transparent, Color = Color.Black,
Position = vertexPos + offset Position = vertexPos1 + offset
}; };
//one extruded by the light direction ShadowVertices[ShadowVertexCount + 1] = new VertexPositionColor
ShadowVertices[j] = new VertexPositionColor
{ {
Color = ShadowVertices[i].Color Color = Color.Black,
Position = vertexPos0 + offset
}; };
Vector3 L2P = vertexPos - new Vector3(lightSourcePos, 0); ShadowVertices[ShadowVertexCount + 2] = new VertexPositionColor
L2P.Normalize(); {
Color = Color.Black,
Position = extruded0 + offset
};
ShadowVertices[j].Position = new Vector3(lightSourcePos, 0) + L2P * 9000 + offset; ShadowVertices[ShadowVertexCount + 3] = new VertexPositionColor
{
Color = Color.Black,
Position = vertexPos1 + offset
};
svCount += 2; ShadowVertices[ShadowVertexCount + 4] = new VertexPositionColor
currentIndex = (currentIndex + 1) % 4; {
Color = Color.Black,
Position = extruded0 + offset
};
ShadowVertices[ShadowVertexCount + 5] = new VertexPositionColor
{
Color = Color.Black,
Position = extruded1 + offset
};
ShadowVertexCount += 6;
} }
if (los) CalculateLosPenumbraVertices(lightSourcePos);
{
CalculatePenumbraVertices(startingIndex, endingIndex, lightSourcePos, los);
}
} }
private void CalculatePenumbraVertices(int startingIndex, int endingIndex, Vector2 lightSourcePos, bool los) private void CalculateLosPenumbraVertices(Vector2 lightSourcePos)
{ {
var vertices = los ? losVertices : this.vertices;
Vector3 offset = Vector3.Zero; Vector3 offset = Vector3.Zero;
if (ParentEntity != null && ParentEntity.Submarine != null) if (ParentEntity != null && ParentEntity.Submarine != null)
{ {
offset = new Vector3(ParentEntity.Submarine.DrawPosition.X, ParentEntity.Submarine.DrawPosition.Y, 0.0f); offset = new Vector3(ParentEntity.Submarine.DrawPosition.X, ParentEntity.Submarine.DrawPosition.Y, 0.0f);
} }
for (int n = 0; n < 4; n += 3) PenumbraVertexCount = 0;
for (int i = 0; i < 4; i++)
{ {
Vector3 penumbraStart = new Vector3((n == 0) ? vertices[startingIndex].Pos : vertices[endingIndex].Pos, 0.0f); int currentIndex = i;
int prevIndex = (i + 3) % 4;
int nextIndex = (i + 1) % 4;
bool disjointed = losOffsets[i]?.A != null;
Vector2 vertexPos0 = losOffsets[currentIndex]?.A ?? losVertices[currentIndex].Pos;
Vector2 vertexPos1 = losOffsets[currentIndex]?.B ?? losVertices[nextIndex].Pos;
PenumbraVertices[n] = new VertexPositionTexture if (Vector2.DistanceSquared(vertexPos0, vertexPos1) < 1.0f) { continue; }
if (backFacing[currentIndex] && (disjointed || (!backFacing[prevIndex])))
{ {
Position = penumbraStart + offset, Vector3 penumbraStart = new Vector3(vertexPos0, 0.0f);
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int i = 0; i < 2; i++) PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{
PenumbraVertices[n + i + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (i == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
if (n > 0) normal = -normal;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[n + i + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
if (los)
{ {
PenumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f); Position = penumbraStart + offset,
} TextureCoordinate = new Vector2(0.0f, 1.0f)
else };
for (int j = 0; j < 2; j++)
{ {
PenumbraVertices[n + i + 1].TextureCoordinate = (i == 0) ? new Vector2(1.0f, 0.0f) : Vector2.Zero; PenumbraVertices[PenumbraVertexCount + j + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) - normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + j + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
PenumbraVertices[PenumbraVertexCount + j + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
} }
PenumbraVertexCount += 3;
} }
if (n > 0) disjointed = losOffsets[i]?.B != null;
if (backFacing[currentIndex] && (disjointed || (!backFacing[nextIndex])))
{ {
var temp = PenumbraVertices[4]; Vector3 penumbraStart = new Vector3(vertexPos1, 0.0f);
PenumbraVertices[4] = PenumbraVertices[5];
PenumbraVertices[5] = temp; PenumbraVertices[PenumbraVertexCount] = new VertexPositionTexture
{
Position = penumbraStart + offset,
TextureCoordinate = new Vector2(0.0f, 1.0f)
};
for (int j = 0; j < 2; j++)
{
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1] = new VertexPositionTexture();
Vector3 vertexDir = penumbraStart - new Vector3(lightSourcePos, 0);
vertexDir.Normalize();
Vector3 normal = (j == 0) ? new Vector3(-vertexDir.Y, vertexDir.X, 0.0f) : new Vector3(vertexDir.Y, -vertexDir.X, 0.0f) * 0.05f;
vertexDir = penumbraStart - (new Vector3(lightSourcePos, 0) + normal * 20.0f);
vertexDir.Normalize();
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].Position = new Vector3(lightSourcePos, 0) + vertexDir * 9000 + offset;
PenumbraVertices[PenumbraVertexCount + (1 - j) + 1].TextureCoordinate = (j == 0) ? new Vector2(0.05f, 0.0f) : new Vector2(1.0f, 0.0f);
}
PenumbraVertexCount += 3;
} }
} }
} }
@@ -381,7 +381,7 @@ namespace Barotrauma.Lights
if (GUI.DisableItemHighlights) { return false; } if (GUI.DisableItemHighlights) { return false; }
highlightedEntities.Clear(); highlightedEntities.Clear();
if (Character.Controlled != null) if (Character.Controlled != null && (!Character.Controlled.IsKeyDown(InputType.Aim) || Character.Controlled.SelectedItems.Any(it => it?.GetComponent<Sprayer>() == null)))
{ {
if (Character.Controlled.FocusedItem != null) if (Character.Controlled.FocusedItem != null)
{ {
@@ -532,28 +532,16 @@ namespace Barotrauma.Lights
Vector2 relativeLightPos = pos; Vector2 relativeLightPos = pos;
if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position; if (convexHull.ParentEntity?.Submarine != null) relativeLightPos -= convexHull.ParentEntity.Submarine.Position;
convexHull.CalculateShadowVertices(relativeLightPos, true); convexHull.CalculateLosVertices(relativeLightPos);
//convert triangle strips to a triangle list for (int i = 0; i < convexHull.ShadowVertexCount; i++)
for (int i = 0; i < convexHull.ShadowVertexCount * 2 - 2; i++)
{ {
if (i % 2 == 0) shadowVerts.Add(convexHull.ShadowVertices[i]);
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
shadowVerts.Add(convexHull.ShadowVertices[i + 1]);
shadowVerts.Add(convexHull.ShadowVertices[i + 2]);
}
else
{
shadowVerts.Add(convexHull.ShadowVertices[i]);
shadowVerts.Add(convexHull.ShadowVertices[i + 2]);
shadowVerts.Add(convexHull.ShadowVertices[i + 1]);
}
} }
if (convexHull.ShadowVertexCount > 0) for (int i = 0; i < convexHull.PenumbraVertexCount; i++)
{ {
penumbraVerts.AddRange(convexHull.PenumbraVertices); penumbraVerts.Add(convexHull.PenumbraVertices[i]);
} }
} }
@@ -300,6 +300,14 @@ namespace Barotrauma.Lights
} }
} }
public float TextureRange
{
get
{
return lightSourceParams.TextureRange;
}
}
/// <summary> /// <summary>
/// Background lights are drawn behind submarines and they don't cast shadows. /// Background lights are drawn behind submarines and they don't cast shadows.
/// </summary> /// </summary>
@@ -366,7 +374,7 @@ namespace Barotrauma.Lights
var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub); var fullChList = ConvexHull.HullLists.Find(x => x.Submarine == sub);
if (fullChList == null) return; if (fullChList == null) return;
chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, Range, ch.BoundingBox)); chList.List = fullChList.List.FindAll(ch => ch.Enabled && MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, ch.BoundingBox));
NeedsHullCheck = true; NeedsHullCheck = true;
} }
@@ -418,7 +426,7 @@ namespace Barotrauma.Lights
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height); subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//only draw if the light overlaps with the sub //only draw if the light overlaps with the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, Range, subBorders)) if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, subBorders))
{ {
if (chList.List.Count > 0) NeedsRecalculation = true; if (chList.List.Count > 0) NeedsRecalculation = true;
chList.List.Clear(); chList.List.Clear();
@@ -452,7 +460,7 @@ namespace Barotrauma.Lights
subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height); subBorders.Location += sub.HiddenSubPosition.ToPoint() - new Point(0, sub.Borders.Height);
//don't draw any shadows if the light doesn't overlap with the borders of the sub //don't draw any shadows if the light doesn't overlap with the borders of the sub
if (!MathUtils.CircleIntersectsRectangle(lightPos, Range, subBorders)) if (!MathUtils.CircleIntersectsRectangle(lightPos, TextureRange, subBorders))
{ {
if (chList.List.Count > 0) NeedsRecalculation = true; if (chList.List.Count > 0) NeedsRecalculation = true;
chList.List.Clear(); chList.List.Clear();
@@ -495,7 +503,7 @@ namespace Barotrauma.Lights
{ {
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
if (!chList.IsHidden.Contains(hull)) hulls.Add(hull); if (!chList.IsHidden.Contains(hull)) { hulls.Add(hull); }
} }
foreach (ConvexHull hull in chList.List) foreach (ConvexHull hull in chList.List)
{ {
@@ -503,7 +511,7 @@ namespace Barotrauma.Lights
} }
} }
float bounds = Range * 2; float bounds = TextureRange;
//find convexhull segments that are close enough and facing towards the light source //find convexhull segments that are close enough and facing towards the light source
List<Segment> visibleSegments = new List<Segment>(); List<Segment> visibleSegments = new List<Segment>();
List<SegmentPoint> points = new List<SegmentPoint>(); List<SegmentPoint> points = new List<SegmentPoint>();
@@ -513,6 +521,49 @@ namespace Barotrauma.Lights
hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false); hull.GetVisibleSegments(drawPos, visibleSegments, ignoreEdges: false);
} }
//add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source
//(might be more effective to calculate if we actually need these extra points)
Vector2 drawOffset = Vector2.Zero;
float boundsExtended = bounds;
if (OverrideLightTexture != null)
{
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTexture.Origin;
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
origin -= Vector2.One * 0.5f;
if (Math.Abs(origin.X) >= 0.45f || Math.Abs(origin.Y) >= 0.45f)
{
boundsExtended += 5.0f;
}
origin *= TextureRange;
drawOffset.X = -origin.X * cosAngle - origin.Y * sinAngle;
drawOffset.Y = origin.X * sinAngle + origin.Y * cosAngle;
}
var boundaryCorners = new SegmentPoint[] {
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X + boundsExtended, drawPos.Y + drawOffset.Y + boundsExtended), null),
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X + boundsExtended, drawPos.Y + drawOffset.Y - boundsExtended), null),
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X - boundsExtended, drawPos.Y + drawOffset.Y - boundsExtended), null),
new SegmentPoint(new Vector2(drawPos.X + drawOffset.X - boundsExtended, drawPos.Y + drawOffset.Y + boundsExtended), null)
};
for (int i = 0; i < 4; i++)
{
var s = new Segment(boundaryCorners[i], boundaryCorners[(i + 1) % 4], null);
visibleSegments.Add(s);
}
//Generate new points at the intersections between segments //Generate new points at the intersections between segments
//This is necessary for the light volume to generate properly on some subs //This is necessary for the light volume to generate properly on some subs
for (int i = 0; i < visibleSegments.Count; i++) for (int i = 0; i < visibleSegments.Count; i++)
@@ -532,10 +583,10 @@ namespace Barotrauma.Lights
Vector2 p2a = visibleSegments[j].Start.WorldPos; Vector2 p2a = visibleSegments[j].Start.WorldPos;
Vector2 p2b = visibleSegments[j].End.WorldPos; Vector2 p2b = visibleSegments[j].End.WorldPos;
if (Vector2.DistanceSquared(p1a, p2a) < 25.0f || if (Vector2.DistanceSquared(p1a, p2a) < 5.0f ||
Vector2.DistanceSquared(p1a, p2b) < 25.0f || Vector2.DistanceSquared(p1a, p2b) < 5.0f ||
Vector2.DistanceSquared(p1b, p2a) < 25.0f || Vector2.DistanceSquared(p1b, p2a) < 5.0f ||
Vector2.DistanceSquared(p1b, p2b) < 25.0f) Vector2.DistanceSquared(p1b, p2b) < 5.0f)
{ {
continue; continue;
} }
@@ -565,8 +616,8 @@ namespace Barotrauma.Lights
mid.Pos -= visibleSegments[i].ConvexHull.ParentEntity.Submarine.DrawPosition; mid.Pos -= visibleSegments[i].ConvexHull.ParentEntity.Submarine.DrawPosition;
} }
if (Vector2.DistanceSquared(start.WorldPos, mid.WorldPos) < 25.0f || if (Vector2.DistanceSquared(start.WorldPos, mid.WorldPos) < 5.0f ||
Vector2.DistanceSquared(end.WorldPos, mid.WorldPos) < 25.0f) Vector2.DistanceSquared(end.WorldPos, mid.WorldPos) < 5.0f)
{ {
continue; continue;
} }
@@ -580,6 +631,7 @@ namespace Barotrauma.Lights
{ {
IsHorizontal = visibleSegments[i].IsHorizontal IsHorizontal = visibleSegments[i].IsHorizontal
}; };
visibleSegments[i] = seg1; visibleSegments[i] = seg1;
visibleSegments.Insert(i + 1, seg2); visibleSegments.Insert(i + 1, seg2);
i--; i--;
@@ -588,33 +640,26 @@ namespace Barotrauma.Lights
} }
} }
foreach (Segment s in visibleSegments) //remove segments that fall out of bounds
for (int i = 0; i < visibleSegments.Count; i++)
{ {
points.Add(s.Start); Segment s = visibleSegments[i];
points.Add(s.End); if (Math.Abs(s.Start.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
if (Math.Abs(s.Start.WorldPos.X - drawPos.X) > bounds) bounds = Math.Abs(s.Start.WorldPos.X - drawPos.X); Math.Abs(s.Start.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f ||
if (Math.Abs(s.Start.WorldPos.Y - drawPos.Y) > bounds) bounds = Math.Abs(s.Start.WorldPos.Y - drawPos.Y); Math.Abs(s.End.WorldPos.X - drawPos.X - drawOffset.X) > boundsExtended + 1.0f ||
if (Math.Abs(s.End.WorldPos.X - drawPos.X) > bounds) bounds = Math.Abs(s.End.WorldPos.X - drawPos.X); Math.Abs(s.End.WorldPos.Y - drawPos.Y - drawOffset.Y) > boundsExtended + 1.0f)
if (Math.Abs(s.End.WorldPos.Y - drawPos.Y) > bounds) bounds = Math.Abs(s.End.WorldPos.Y - drawPos.Y); {
visibleSegments.RemoveAt(i);
i--;
}
else
{
points.Add(s.Start);
points.Add(s.End);
}
} }
//add a square-shaped boundary to make sure we've got something to construct the triangles from visibleSegments = visibleSegments.OrderBy(s => MathUtils.LineToPointDistanceSquared(s.Start.WorldPos, s.End.WorldPos, drawPos)).ToList();
//even if there aren't enough hull segments around the light source
//(might be more effective to calculate if we actually need these extra points)
var boundaryCorners = new List<SegmentPoint> {
new SegmentPoint(new Vector2(drawPos.X + bounds, drawPos.Y + bounds), null),
new SegmentPoint(new Vector2(drawPos.X + bounds, drawPos.Y - bounds), null),
new SegmentPoint(new Vector2(drawPos.X - bounds, drawPos.Y - bounds), null),
new SegmentPoint(new Vector2(drawPos.X - bounds, drawPos.Y + bounds), null)
};
points.AddRange(boundaryCorners);
for (int i = 0; i < 4; i++)
{
visibleSegments.Add(new Segment(boundaryCorners[i], boundaryCorners[(i + 1) % 4], null));
}
var compareCCW = new CompareSegmentPointCW(drawPos); var compareCCW = new CompareSegmentPointCW(drawPos);
try try
@@ -635,13 +680,15 @@ namespace Barotrauma.Lights
//List<Pair<int, Vector2>> preOutput = new List<Pair<int, Vector2>>(); //List<Pair<int, Vector2>> preOutput = new List<Pair<int, Vector2>>();
//remove points that are very close to each other //remove points that are very close to each other
for (int i = 0; i < points.Count - 1; i++) for (int i = 0; i < points.Count; i++)
{ {
if (Math.Abs(points[i].WorldPos.X - points[i + 1].WorldPos.X) < 6 && for (int j = Math.Min(i + 4, points.Count-1); j > i; j--)
Math.Abs(points[i].WorldPos.Y - points[i + 1].WorldPos.Y) < 6)
{ {
points.RemoveAt(i + 1); if (Math.Abs(points[i].WorldPos.X - points[j].WorldPos.X) < 6 &&
i--; Math.Abs(points[i].WorldPos.Y - points[j].WorldPos.Y) < 6)
{
points.RemoveAt(j);
}
} }
} }
@@ -651,16 +698,16 @@ namespace Barotrauma.Lights
Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3; Vector2 dirNormal = new Vector2(-dir.Y, dir.X) * 3;
//do two slightly offset raycasts to hit the segment itself and whatever's behind it //do two slightly offset raycasts to hit the segment itself and whatever's behind it
Pair<int,Vector2> intersection1 = RayCast(drawPos, drawPos + dir * bounds * 2 - dirNormal, visibleSegments); Pair<int,Vector2> intersection1 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 - dirNormal, visibleSegments);
Pair<int,Vector2> intersection2 = RayCast(drawPos, drawPos + dir * bounds * 2 + dirNormal, visibleSegments); Pair<int,Vector2> intersection2 = RayCast(drawPos, drawPos + dir * boundsExtended * 2 + dirNormal, visibleSegments);
if (intersection1.First < 0) return new List<Vector2>(); if (intersection1.First < 0) return null;
if (intersection2.First < 0) return new List<Vector2>(); if (intersection2.First < 0) return null;
Segment seg1 = visibleSegments[intersection1.First]; Segment seg1 = visibleSegments[intersection1.First];
Segment seg2 = visibleSegments[intersection2.First]; Segment seg2 = visibleSegments[intersection2.First];
bool isPoint1 = MathUtils.LineToPointDistance(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 5.0f; bool isPoint1 = MathUtils.LineToPointDistanceSquared(seg1.Start.WorldPos, seg1.End.WorldPos, p.WorldPos) < 25.0f;
bool isPoint2 = MathUtils.LineToPointDistance(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 5.0f; bool isPoint2 = MathUtils.LineToPointDistanceSquared(seg2.Start.WorldPos, seg2.End.WorldPos, p.WorldPos) < 25.0f;
if (isPoint1 && isPoint2) if (isPoint1 && isPoint2)
{ {
@@ -696,11 +743,13 @@ namespace Barotrauma.Lights
//remove points that are very close to each other //remove points that are very close to each other
for (int i = 0; i < output.Count - 1; i++) for (int i = 0; i < output.Count - 1; i++)
{ {
if (Math.Abs(output[i].X - output[i + 1].X) < 6 && for (int j = Math.Min(i + 4, output.Count - 1); j > i; j--)
Math.Abs(output[i].Y - output[i + 1].Y) < 6)
{ {
output.RemoveAt(i + 1); if (Math.Abs(output[i].X - output[j].X) < 6 &&
i--; Math.Abs(output[i].Y - output[j].Y) < 6)
{
output.RemoveAt(j);
}
} }
} }
@@ -709,7 +758,6 @@ namespace Barotrauma.Lights
private Pair<int, Vector2> RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments) private Pair<int, Vector2> RayCast(Vector2 rayStart, Vector2 rayEnd, List<Segment> segments)
{ {
float closestDist = float.PositiveInfinity;
Vector2? closestIntersection = null; Vector2? closestIntersection = null;
int segment = -1; int segment = -1;
@@ -724,11 +772,11 @@ namespace Barotrauma.Lights
//segment's end position always has a higher or equal y coordinate than the start position //segment's end position always has a higher or equal y coordinate than the start position
//so we can do this comparison and skip segments that are at the wrong side of the ray //so we can do this comparison and skip segments that are at the wrong side of the ray
if (s.End.WorldPos.Y < s.Start.WorldPos.Y) /*if (s.End.WorldPos.Y < s.Start.WorldPos.Y)
{ {
System.Diagnostics.Debug.Assert(s.End.WorldPos.Y >= s.Start.WorldPos.Y, System.Diagnostics.Debug.Assert(s.End.WorldPos.Y >= s.Start.WorldPos.Y,
"LightSource raycast failed. Segment's end positions should never be below the start position. Parent entity: " + (s.ConvexHull?.ParentEntity == null ? "null" : s.ConvexHull.ParentEntity.ToString())); "LightSource raycast failed. Segment's end positions should never be below the start position. Parent entity: " + (s.ConvexHull?.ParentEntity == null ? "null" : s.ConvexHull.ParentEntity.ToString()));
} }*/
if (s.Start.WorldPos.Y > maxY || s.End.WorldPos.Y < minY) { continue; } if (s.Start.WorldPos.Y > maxY || s.End.WorldPos.Y < minY) { continue; }
//same for the x-axis //same for the x-axis
if (s.Start.WorldPos.X > s.End.WorldPos.X) if (s.Start.WorldPos.X > s.End.WorldPos.X)
@@ -742,17 +790,28 @@ namespace Barotrauma.Lights
if (s.Start.WorldPos.X > maxX) continue; if (s.Start.WorldPos.X > maxX) continue;
} }
if (s.IsAxisAligned ? bool intersects;
MathUtils.GetAxisAlignedLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, s.IsHorizontal, out Vector2 intersection) : Vector2 intersection;
MathUtils.GetLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, out intersection)) if (s.IsAxisAligned)
{ {
float dist = Vector2.DistanceSquared(intersection, rayStart); intersects = MathUtils.GetAxisAlignedLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, s.IsHorizontal, out intersection);
if (dist < closestDist) }
{ else
closestDist = dist; {
closestIntersection = intersection; intersects = MathUtils.GetLineIntersection(rayStart, rayEnd, s.Start.WorldPos, s.End.WorldPos, out intersection);
segment = i; }
}
if (intersects)
{
closestIntersection = intersection;
rayEnd = intersection;
minX = Math.Min(rayStart.X, rayEnd.X);
maxX = Math.Max(rayStart.X, rayEnd.X);
minY = Math.Min(rayStart.Y, rayEnd.Y);
maxY = Math.Max(rayStart.Y, rayEnd.Y);
segment = i;
} }
} }
@@ -797,7 +856,7 @@ namespace Barotrauma.Lights
//hacky fix to exc excessively large light volumes (they used to be up to 4x the range of the light if there was nothing to block the rays). //hacky fix to exc excessively large light volumes (they used to be up to 4x the range of the light if there was nothing to block the rays).
//might want to tweak the raycast logic in a way that this isn't necessary //might want to tweak the raycast logic in a way that this isn't necessary
float boundRadius = Range * 1.1f / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y))); /*float boundRadius = Range * 1.1f / (1.0f - Math.Max(Math.Abs(uvOffset.X), Math.Abs(uvOffset.Y)));
Rectangle boundArea = new Rectangle((int)(drawPos.X - boundRadius), (int)(drawPos.Y + boundRadius), (int)(boundRadius * 2), (int)(boundRadius * 2)); Rectangle boundArea = new Rectangle((int)(drawPos.X - boundRadius), (int)(drawPos.Y + boundRadius), (int)(boundRadius * 2), (int)(boundRadius * 2));
for (int i = 0; i < rayCastHits.Count; i++) for (int i = 0; i < rayCastHits.Count; i++)
{ {
@@ -805,7 +864,7 @@ namespace Barotrauma.Lights
{ {
rayCastHits[i] = intersection; rayCastHits[i] = intersection;
} }
} }*/
// Add all the other encounter points as vertices // Add all the other encounter points as vertices
// storing their world position as UV coordinates // storing their world position as UV coordinates
@@ -839,9 +898,15 @@ namespace Barotrauma.Lights
//add the normals together and use some magic numbers to create //add the normals together and use some magic numbers to create
//a somewhat useful/good-looking blur //a somewhat useful/good-looking blur
Vector2 nDiff = nDiff1 + nDiff2; Vector2 nDiff = nDiff1 * 40.0f;
nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y)); if (MathUtils.GetLineIntersection(vertex + (nDiff1 * 40.0f), nextVertex + (nDiff1 * 40.0f), vertex + (nDiff2 * 40.0f), prevVertex + (nDiff2 * 40.0f), true, out Vector2 intersection))
nDiff *= 50.0f; {
nDiff = intersection - vertex;
if (nDiff.LengthSquared() > 10000.0f)
{
nDiff /= Math.Max(Math.Abs(nDiff.X), Math.Abs(nDiff.Y)); nDiff *= 100.0f;
}
}
Vector2 diff = rawDiff; Vector2 diff = rawDiff;
diff /= Range * 2.0f; diff /= Range * 2.0f;
@@ -948,6 +1013,50 @@ namespace Barotrauma.Lights
/// <param name="spriteBatch"></param> /// <param name="spriteBatch"></param>
public void DrawSprite(SpriteBatch spriteBatch, Camera cam) public void DrawSprite(SpriteBatch spriteBatch, Camera cam)
{ {
if (GameMain.DebugDraw)
{
Vector2 drawPos = position;
if (ParentSub != null)
{
drawPos += ParentSub.DrawPosition;
}
drawPos.Y = -drawPos.Y;
float cosAngle = (float)Math.Cos(Rotation);
float sinAngle = -(float)Math.Sin(Rotation);
float bounds = TextureRange;
if (OverrideLightTexture != null)
{
var overrideTextureDims = new Vector2(OverrideLightTexture.SourceRect.Width, OverrideLightTexture.SourceRect.Height);
Vector2 origin = OverrideLightTexture.Origin;
origin /= Math.Max(overrideTextureDims.X, overrideTextureDims.Y);
origin *= TextureRange;
drawPos.X += origin.X * sinAngle + origin.Y * cosAngle;
drawPos.Y += origin.X * cosAngle + origin.Y * sinAngle;
}
//add a square-shaped boundary to make sure we've got something to construct the triangles from
//even if there aren't enough hull segments around the light source
//(might be more effective to calculate if we actually need these extra points)
var boundaryCorners = new SegmentPoint[] {
new SegmentPoint(new Vector2(drawPos.X + bounds, drawPos.Y + bounds), null),
new SegmentPoint(new Vector2(drawPos.X + bounds, drawPos.Y - bounds), null),
new SegmentPoint(new Vector2(drawPos.X - bounds, drawPos.Y - bounds), null),
new SegmentPoint(new Vector2(drawPos.X - bounds, drawPos.Y + bounds), null)
};
for (int i=0;i<4;i++)
{
GUI.DrawLine(spriteBatch, boundaryCorners[i].Pos, boundaryCorners[(i + 1) % 4].Pos, Color.White, 0, 3);
}
}
if (DeformableLightSprite != null) if (DeformableLightSprite != null)
{ {
Vector2 origin = DeformableLightSprite.Origin; Vector2 origin = DeformableLightSprite.Origin;
@@ -976,11 +1085,11 @@ namespace Barotrauma.Lights
if (LightSprite != null) if (LightSprite != null)
{ {
Vector2 origin = LightSprite.Origin; Vector2 origin = LightSprite.Origin;
if (LightSpriteEffect == SpriteEffects.FlipHorizontally) if ((LightSpriteEffect & SpriteEffects.FlipHorizontally) == SpriteEffects.FlipHorizontally)
{ {
origin.X = LightSprite.SourceRect.Width - origin.X; origin.X = LightSprite.SourceRect.Width - origin.X;
} }
if (LightSpriteEffect == SpriteEffects.FlipVertically) if ((LightSpriteEffect & SpriteEffects.FlipVertically) == SpriteEffects.FlipVertically)
{ {
origin.Y = LightSprite.SourceRect.Height - origin.Y; origin.Y = LightSprite.SourceRect.Height - origin.Y;
} }
@@ -9,8 +9,6 @@ namespace Barotrauma
{ {
partial class Map partial class Map
{ {
public bool AllowDebugTeleport;
class MapAnim class MapAnim
{ {
public Location StartLocation; public Location StartLocation;
@@ -380,6 +378,7 @@ namespace Barotrauma
Location prevLocation = CurrentDisplayLocation; Location prevLocation = CurrentDisplayLocation;
CurrentLocation = HighlightedLocation; CurrentLocation = HighlightedLocation;
Level.Loaded.DebugSetStartLocation(CurrentLocation); Level.Loaded.DebugSetStartLocation(CurrentLocation);
Level.Loaded.DebugSetEndLocation(null);
CurrentLocation.Discovered = true; CurrentLocation.Discovered = true;
CurrentLocation.CreateStore(); CurrentLocation.CreateStore();
@@ -573,7 +572,7 @@ namespace Barotrauma
} }
} }
if (GameMain.DebugDraw && location == HighlightedLocation) if (GameMain.DebugDraw && location == HighlightedLocation && (!location.Discovered || !location.Type.HasOutpost))
{ {
if (location.Reputation != null) if (location.Reputation != null)
{ {
@@ -589,7 +588,7 @@ namespace Barotrauma
Color barColor = ToolBox.GradientLerp(location.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen); Color barColor = ToolBox.GradientLerp(location.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.8f, isFilled: true); GUI.DrawRectangle(spriteBatch, bgRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, (int)(location.Reputation.NormalizedValue * 255), 32), barColor, isFilled: true); GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, (int)(location.Reputation.NormalizedValue * 255), 32), barColor, isFilled: true);
string reputationValue = location.Reputation.Value.ToString(CultureInfo.InvariantCulture); string reputationValue = ((int)location.Reputation.Value).ToString();
Vector2 repValueSize = GUI.SubHeadingFont.MeasureString(reputationValue); Vector2 repValueSize = GUI.SubHeadingFont.MeasureString(reputationValue);
GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUI.SubHeadingFont); GUI.DrawString(spriteBatch, dPos + (new Vector2(256, 32) / 2) - (repValueSize / 2), reputationValue, Color.White, Color.Black, font: GUI.SubHeadingFont);
GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White); GUI.DrawRectangle(spriteBatch, new Rectangle((int)dPos.X, (int)dPos.Y, 256, 32), Color.White);
@@ -607,12 +606,34 @@ namespace Barotrauma
Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name); Vector2 nameSize = GUI.LargeFont.MeasureString(HighlightedLocation.Name);
Vector2 typeSize = GUI.Font.MeasureString(HighlightedLocation.Type.Name); Vector2 typeSize = GUI.Font.MeasureString(HighlightedLocation.Type.Name);
Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y); Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y);
bool showReputation = HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
string repLabelText = null, repValueText = null;
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
if (showReputation)
{
repLabelText = TextManager.Get("reputation");
repLabelSize = GUI.Font.MeasureString(repLabelText);
size.X = Math.Max(size.X, repLabelSize.X);
repBarSize = new Vector2(Math.Max(0.75f * size.X, 100), repLabelSize.Y);
size.X = Math.Max(size.X, (4.0f / 3.0f) * repBarSize.X);
size.Y += 2 * repLabelSize.Y + 4 + repBarSize.Y;
repValueText = ((int)HighlightedLocation.Reputation.Value).ToString();
}
GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw( GUI.Style.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
spriteBatch, new Rectangle((int)(pos.X - 60 * GUI.Scale), (int)(pos.Y - size.Y), (int)(size.X + 120 * GUI.Scale), (int)(size.Y * 2.2f)), Color.Black * hudVisibility); spriteBatch, new Rectangle((int)(pos.X - 60 * GUI.Scale), (int)(pos.Y - size.Y), (int)(size.X + 120 * GUI.Scale), (int)(size.Y * 2.2f)), Color.Black * hudVisibility);
GUI.DrawString(spriteBatch, pos - new Vector2(0.0f, size.Y / 2), var topLeftPos = pos - new Vector2(0.0f, size.Y / 2);
HighlightedLocation.Name, GUI.Style.TextColor * hudVisibility * 1.5f, font: GUI.LargeFont); GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUI.Style.TextColor * hudVisibility * 1.5f, font: GUI.LargeFont);
GUI.DrawString(spriteBatch, pos + new Vector2(0.0f, size.Y / 2 - GUI.Font.MeasureString(HighlightedLocation.Type.Name).Y), topLeftPos += new Vector2(0.0f, nameSize.Y);
HighlightedLocation.Type.Name, GUI.Style.TextColor * hudVisibility * 1.5f); GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Type.Name, GUI.Style.TextColor * hudVisibility * 1.5f);
if (showReputation)
{
topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
GUI.DrawString(spriteBatch, topLeftPos, repLabelText, GUI.Style.TextColor * hudVisibility * 1.5f);
topLeftPos += new Vector2(0.0f, repLabelSize.Y + 4);
Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y));
RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue);
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + 4, repBarRect.Top), repValueText, GUI.Style.TextColor);
}
} }
if (tooltip != null) if (tooltip != null)
{ {
@@ -159,6 +159,31 @@ namespace Barotrauma
if (PlayerInput.IsCtrlDown()) if (PlayerInput.IsCtrlDown())
{ {
#if DEBUG
if (PlayerInput.KeyHit(Keys.D))
{
bool terminate = false;
foreach (MapEntity entity in selectedList)
{
if (entity is Item item && item.GetComponent<Planter>() is { } planter)
{
planter.Update(1.0f, cam);
for (var i = 0; i < planter.GrowableSeeds.Length; i++)
{
Growable seed = planter.GrowableSeeds[i];
PlantSlot slot = planter.PlantSlots.ContainsKey(i) ? planter.PlantSlots[i] : Planter.NullSlot;
if (seed == null) { continue; }
seed.CreateDebugHUD(planter, slot);
terminate = true;
break;
}
}
if (terminate) { break; }
}
}
#endif
if (PlayerInput.KeyHit(Keys.C)) if (PlayerInput.KeyHit(Keys.C))
{ {
Copy(selectedList); Copy(selectedList);
@@ -897,8 +922,9 @@ namespace Barotrauma
Clone(copiedList); Clone(copiedList);
var clones = mapEntityList.Except(prevEntities).ToList(); var clones = mapEntityList.Except(prevEntities).ToList();
var nonWireClones = clones.Where(c => !(c is Item item) || item.GetComponent<Wire>() == null); var nonWireClones = clones.Where(c => !(c is Item item) || item.GetComponent<Wire>() == null);
if (!nonWireClones.Any()) { nonWireClones = clones; }
Vector2 center = Vector2.Zero; Vector2 center = Vector2.Zero;
nonWireClones.ForEach(c => center += c.WorldPosition); nonWireClones.ForEach(c => center += c.WorldPosition);
center = Submarine.VectorToWorldGrid(center / nonWireClones.Count()); center = Submarine.VectorToWorldGrid(center / nonWireClones.Count());
@@ -185,9 +185,20 @@ namespace Barotrauma
public override bool IsVisible(Rectangle worldView) public override bool IsVisible(Rectangle worldView)
{ {
Rectangle worldRect = WorldRect; Rectangle worldRect = WorldRect;
Vector2 worldPos = WorldPosition;
if (worldRect.X > worldView.Right || worldRect.Right < worldView.X) { return false; } Vector2 min = new Vector2(worldRect.X, worldRect.Y - worldRect.Height);
if (worldRect.Y < worldView.Y - worldView.Height || worldRect.Y - worldRect.Height > worldView.Y) { return false; } Vector2 max = new Vector2(worldRect.Right, worldRect.Y);
foreach (DecorativeSprite decorativeSprite in Prefab.DecorativeSprites)
{
min.X = Math.Min(worldPos.X - decorativeSprite.Sprite.size.X * decorativeSprite.Sprite.RelativeOrigin.X * decorativeSprite.Scale * Scale, min.X);
max.X = Math.Max(worldPos.X + decorativeSprite.Sprite.size.X * (1.0f - decorativeSprite.Sprite.RelativeOrigin.X) * decorativeSprite.Scale * Scale, max.X);
min.Y = Math.Min(worldPos.Y - decorativeSprite.Sprite.size.Y * (1.0f - decorativeSprite.Sprite.RelativeOrigin.Y) * decorativeSprite.Scale * Scale, min.Y);
max.Y = Math.Max(worldPos.Y + decorativeSprite.Sprite.size.Y * decorativeSprite.Sprite.RelativeOrigin.Y * decorativeSprite.Scale * Scale, max.Y);
}
if (min.X > worldView.Right || max.X < worldView.X) { return false; }
if ( min.Y > worldView.Y || max.Y < worldView.Y - worldView.Height) { return false; }
return true; return true;
} }
@@ -455,8 +466,16 @@ namespace Barotrauma
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)
bool invalidMessage = false;
if (type != ServerNetObject.ENTITY_EVENT && type != ServerNetObject.ENTITY_EVENT_INITIAL)
{ {
DebugConsole.NewMessage($"Error while reading a network event for the structure \"{Name} ({ID})\". Invalid event type ({type}).", Color.Red);
return;
}
else if (sectionCount != Sections.Length)
{
invalidMessage = true;
string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". 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);
@@ -465,7 +484,7 @@ namespace Barotrauma
for (int i = 0; i < sectionCount; i++) for (int i = 0; i < sectionCount; i++)
{ {
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth; float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
if (i < Sections.Length) if (!invalidMessage && i < Sections.Length)
{ {
SetDamage(i, damage); SetDamage(i, damage);
} }
@@ -11,6 +11,7 @@ using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Items.Components; using Barotrauma.Items.Components;
using System.Globalization;
namespace Barotrauma namespace Barotrauma
{ {
@@ -19,6 +20,7 @@ namespace Barotrauma
public Sound Sound; public Sound Sound;
public readonly float Volume; public readonly float Volume;
public readonly float Range; public readonly float Range;
public readonly Vector2 FrequencyMultiplierRange;
public readonly bool Stream; public readonly bool Stream;
public string Filename public string Filename
@@ -32,8 +34,34 @@ namespace Barotrauma
Stream = sound.Stream; Stream = sound.Stream;
Range = element.GetAttributeFloat("range", 1000.0f); Range = element.GetAttributeFloat("range", 1000.0f);
Volume = element.GetAttributeFloat("volume", 1.0f); Volume = element.GetAttributeFloat("volume", 1.0f);
FrequencyMultiplierRange = new Vector2(1.0f);
string freqMultAttr = element.GetAttributeString("frequencymultiplier", element.GetAttributeString("frequency", "1.0"));
if (!freqMultAttr.Contains(','))
{
if (float.TryParse(freqMultAttr, NumberStyles.Any, CultureInfo.InvariantCulture, out float freqMult))
{
FrequencyMultiplierRange = new Vector2(freqMult);
}
}
else
{
var freqMult = XMLExtensions.ParseVector2(freqMultAttr, false);
if (freqMult.Y >= 0.25f)
{
FrequencyMultiplierRange = freqMult;
}
}
if (FrequencyMultiplierRange.Y > 4.0f)
{
DebugConsole.ThrowError($"Loaded frequency range exceeds max value: {FrequencyMultiplierRange} (original string was \"{freqMultAttr}\")");
}
sound.IgnoreMuffling = element.GetAttributeBool("dontmuffle", false); sound.IgnoreMuffling = element.GetAttributeBool("dontmuffle", false);
} }
public float GetRandomFrequencyMultiplier()
{
return Rand.Range(FrequencyMultiplierRange.X, FrequencyMultiplierRange.Y);
}
} }
partial class Submarine : Entity, IServerSerializable partial class Submarine : Entity, IServerSerializable
@@ -288,6 +316,27 @@ namespace Barotrauma
} }
} }
public static void DrawPaintedColors(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
foreach (MapEntity e in entitiesToRender)
{
if (e is Hull hull)
{
if (hull.SupportsPaintedColors)
{
if (predicate != null)
{
if (!predicate(e)) continue;
}
hull.DrawSectionColors(spriteBatch);
}
}
}
}
public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null) public static void DrawBack(SpriteBatch spriteBatch, bool editing = false, Predicate<MapEntity> predicate = null)
{ {
var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList; var entitiesToRender = !editing && visibleEntities != null ? visibleEntities : MapEntity.mapEntityList;
@@ -511,6 +560,11 @@ namespace Barotrauma
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
if (type != ServerNetObject.ENTITY_POSITION)
{
DebugConsole.NewMessage($"Error while reading a network event for the submarine \"{Info.Name} ({ID})\". Invalid event type ({type}).", Color.Red);
}
var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Info.Name); var posInfo = PhysicsBody.ClientRead(type, msg, sendingTime, parentDebugName: Info.Name);
msg.ReadPadBits(); msg.ReadPadBits();
@@ -238,42 +238,7 @@ namespace Barotrauma
return true; return true;
} }
private bool EnterIDCardDesc(GUITextBox textBox, string text) private GUIComponent CreateEditingHUD()
{
IdCardDesc = text;
textBox.Text = text;
textBox.Color = GUI.Style.Green;
textBox.Deselect();
return true;
}
private bool EnterIDCardTags(GUITextBox textBox, string text)
{
IdCardTags = text.Split(',');
textBox.Text = string.Join(",", IdCardTags);
textBox.Flash(GUI.Style.Green);
textBox.Deselect();
return true;
}
private bool EnterTags(GUITextBox textBox, string text)
{
tags = text.Split(',').ToList();
textBox.Text = string.Join(",", Tags);
textBox.Flash(GUI.Style.Green);
textBox.Deselect();
return true;
}
private bool TextBoxChanged(GUITextBox textBox, string text)
{
textBox.Color = GUI.Style.Red;
return true;
}
private GUIComponent CreateEditingHUD(bool inGame = false)
{ {
int width = 500; int width = 500;
int height = spawnType == SpawnType.Path ? 80 : 200; int height = spawnType == SpawnType.Path ? 80 : 200;
@@ -326,21 +291,48 @@ namespace Barotrauma
GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), IdCardDesc) GUITextBox propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), descText.RectTransform, Anchor.CenterRight), IdCardDesc)
{ {
MaxTextLength = 150, MaxTextLength = 150,
OnEnterPressed = EnterIDCardDesc,
ToolTip = TextManager.Get("IDCardDescriptionTooltip") ToolTip = TextManager.Get("IDCardDescriptionTooltip")
}; };
propertyBox.OnTextChanged += TextBoxChanged; propertyBox.OnTextChanged += (textBox, text) =>
{
IdCardDesc = text;
return true;
};
propertyBox.OnEnterPressed += (textBox, text) =>
{
IdCardDesc = text;
textBox.Flash(GUI.Style.Green);
return true;
};
propertyBox.OnDeselected += (textBox, keys) =>
{
IdCardDesc = textBox.Text;
textBox.Flash(GUI.Style.Green);
};
var idCardTagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), var idCardTagsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("IDCardTags"), font: GUI.SmallFont); TextManager.Get("IDCardTags"), font: GUI.SmallFont);
propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), idCardTagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags)) propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), idCardTagsText.RectTransform, Anchor.CenterRight), string.Join(", ", idCardTags))
{ {
MaxTextLength = 60, MaxTextLength = 60,
OnEnterPressed = EnterIDCardTags,
ToolTip = TextManager.Get("IDCardTagsTooltip") ToolTip = TextManager.Get("IDCardTagsTooltip")
}; };
propertyBox.OnTextChanged += TextBoxChanged; propertyBox.OnTextChanged += (textBox, text) =>
{
IdCardTags = text.Split(',');
return true;
};
propertyBox.OnEnterPressed += (textBox, text) =>
{
textBox.Text = string.Join(",", IdCardTags);
textBox.Flash(GUI.Style.Green);
return true;
};
propertyBox.OnDeselected += (textBox, keys) =>
{
textBox.Text = string.Join(",", IdCardTags);
textBox.Flash(GUI.Style.Green);
};
var jobsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform), var jobsText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), paddedFrame.RectTransform),
TextManager.Get("SpawnpointJobs"), font: GUI.SmallFont) TextManager.Get("SpawnpointJobs"), font: GUI.SmallFont)
@@ -368,10 +360,24 @@ namespace Barotrauma
propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), tagsText.RectTransform, Anchor.CenterRight), string.Join(", ", tags)) propertyBox = new GUITextBox(new RectTransform(new Vector2(0.5f, 1.0f), tagsText.RectTransform, Anchor.CenterRight), string.Join(", ", tags))
{ {
MaxTextLength = 60, MaxTextLength = 60,
OnEnterPressed = EnterTags,
ToolTip = TextManager.Get("spawnpointtagstooltip") ToolTip = TextManager.Get("spawnpointtagstooltip")
}; };
propertyBox.OnTextChanged += TextBoxChanged; propertyBox.OnTextChanged += (textBox, text) =>
{
tags = text.Split(',').ToList();
return true;
};
propertyBox.OnEnterPressed += (textBox, text) =>
{
textBox.Text = string.Join(",", tags);
textBox.Flash(GUI.Style.Green);
return true;
};
propertyBox.OnDeselected += (textBox, keys) =>
{
textBox.Text = string.Join(",", tags);
textBox.Flash(GUI.Style.Green);
};
} }
PositionEditingHUD(); PositionEditingHUD();
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using Barotrauma.Steam;
using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@@ -7,12 +8,13 @@ namespace Barotrauma.Networking
{ {
partial class BannedPlayer partial class BannedPlayer
{ {
public BannedPlayer(string name, UInt16 uniqueIdentifier, bool isRangeBan, string ip, ulong steamID) public BannedPlayer(string name, UInt16 uniqueIdentifier, bool isRangeBan, string endPoint, ulong steamID)
{ {
this.Name = name; this.Name = name;
this.EndPoint = endPoint;
this.SteamID = steamID; this.SteamID = steamID;
ParseEndPointAsSteamId();
this.IsRangeBan = isRangeBan; this.IsRangeBan = isRangeBan;
this.IP = ip;
this.UniqueIdentifier = uniqueIdentifier; this.UniqueIdentifier = uniqueIdentifier;
} }
} }
@@ -66,13 +68,13 @@ namespace Barotrauma.Networking
RelativeSpacing = 0.02f RelativeSpacing = 0.02f
}; };
string ip = bannedPlayer.IP; string endPoint = bannedPlayer.EndPoint;
if (localRangeBans.Contains(bannedPlayer.UniqueIdentifier)) ip = ToRange(ip); if (localRangeBans.Contains(bannedPlayer.UniqueIdentifier)) endPoint = ToRange(endPoint);
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), topArea.RectTransform), GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.0f), topArea.RectTransform),
bannedPlayer.Name + " (" + ip + ")"); bannedPlayer.Name + " (" + endPoint + ")");
textBlock.RectTransform.MinSize = new Point(textBlock.Rect.Width, 0); textBlock.RectTransform.MinSize = new Point(textBlock.Rect.Width, 0);
if (bannedPlayer.IP.IndexOf(".x") <= -1) if (bannedPlayer.EndPoint.IndexOf(".x") <= -1)
{ {
var rangeBanButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.4f), topArea.RectTransform), var rangeBanButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.4f), topArea.RectTransform),
TextManager.Get("BanRange"), style: "GUIButtonSmall") TextManager.Get("BanRange"), style: "GUIButtonSmall")
@@ -156,19 +158,19 @@ namespace Barotrauma.Networking
UInt16 uniqueIdentifier = incMsg.ReadUInt16(); UInt16 uniqueIdentifier = incMsg.ReadUInt16();
bool isRangeBan = incMsg.ReadBoolean(); incMsg.ReadPadBits(); bool isRangeBan = incMsg.ReadBoolean(); incMsg.ReadPadBits();
string ip = ""; string endPoint = "";
UInt64 steamID = 0; UInt64 steamID = 0;
if (isOwner) if (isOwner)
{ {
ip = incMsg.ReadString(); endPoint = incMsg.ReadString();
steamID = incMsg.ReadUInt64(); steamID = incMsg.ReadUInt64();
} }
else else
{ {
ip = "IP concealed by host"; endPoint = "Endpoint concealed by host";
steamID = 0; steamID = 0;
} }
bannedPlayers.Add(new BannedPlayer(name, uniqueIdentifier, isRangeBan, ip, steamID)); bannedPlayers.Add(new BannedPlayer(name, uniqueIdentifier, isRangeBan, endPoint, steamID));
} }
if (banFrame != null) if (banFrame != null)
@@ -520,11 +520,13 @@ namespace Barotrauma.Networking
msgBox.Content.Parent.RectTransform.MinSize = new Point(0, (int)(msgBox.Content.RectTransform.MinSize.Y / msgBox.Content.RectTransform.RelativeSize.Y)); 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];
okButton.OnClicked += msgBox.Close;
var cancelButton = msgBox.Buttons[1]; var cancelButton = msgBox.Buttons[1];
cancelButton.OnClicked += msgBox.Close;
okButton.OnClicked += (GUIButton button, object obj) => okButton.OnClicked += (GUIButton button, object obj) =>
{ {
clientPeer.SendPassword(passwordBox.Text); clientPeer?.SendPassword(passwordBox.Text);
requiresPw = false; requiresPw = false;
return true; return true;
}; };
@@ -855,22 +857,6 @@ namespace Barotrauma.Networking
traitorResults.Add(new TraitorMissionResult(inc)); traitorResults.Add(new TraitorMissionResult(inc));
} }
if (GameMain.GameSession?.GameMode is CampaignMode mpCampaign)
{
if (inc.ReadBoolean())
{
Dictionary<string, int> clientUpgrades = UpgradeManager.GetMetadataLevels(mpCampaign.CampaignMetadata);
Dictionary<string, int> serverUpgrades = new Dictionary<string, int>();
int length = inc.ReadUInt16();
for (int i = 0; i < length; i++)
{
serverUpgrades.Add(inc.ReadString(), inc.ReadByte());
}
UpgradeManager.CompareUpgrades(clientUpgrades, serverUpgrades);
}
}
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
CoroutineManager.StartCoroutine(EndGame(endMessage, traitorResults, transitionType), "EndGame"); CoroutineManager.StartCoroutine(EndGame(endMessage, traitorResults, transitionType), "EndGame");
break; break;
@@ -934,7 +920,7 @@ namespace Barotrauma.Networking
private void ReadStartGameFinalize(IReadMessage inc) private void ReadStartGameFinalize(IReadMessage inc)
{ {
TaskPool.ListTasks(null); TaskPool.ListTasks();
ushort contentToPreloadCount = inc.ReadUInt16(); ushort contentToPreloadCount = inc.ReadUInt16();
List<ContentFile> contentToPreload = new List<ContentFile>(); List<ContentFile> contentToPreload = new List<ContentFile>();
for (int i = 0; i < contentToPreloadCount; i++) for (int i = 0; i < contentToPreloadCount; i++)
@@ -1006,8 +992,19 @@ namespace Barotrauma.Networking
} }
private void OnDisconnect() private void OnDisconnect(bool disableReconnect)
{ {
CoroutineManager.StopCoroutines("WaitForStartingInfo");
reconnectBox?.Close();
reconnectBox = null;
GameMain.Config.RestoreBackupPackages();
GUI.ClearCursorWait();
if (disableReconnect) { allowReconnect = false; }
if (!this.allowReconnect) { CancelConnect(); }
if (SteamManager.IsInitialized) if (SteamManager.IsInitialized)
{ {
Steamworks.SteamFriends.ClearRichPresence(); Steamworks.SteamFriends.ClearRichPresence();
@@ -1107,8 +1104,9 @@ namespace Barotrauma.Networking
reconnectBox?.Close(); reconnectBox?.Close();
reconnectBox = new GUIMessageBox( reconnectBox = new GUIMessageBox(
TextManager.Get("ConnectionLost"), TextManager.Get("ConnectionLost"), msg,
msg, new string[0]); new string[] { TextManager.Get("Cancel") });
reconnectBox.Buttons[0].OnClicked += (btn, userdata) => { CancelConnect(); return true; };
connected = false; connected = false;
ConnectToServer(serverEndpoint, serverName); ConnectToServer(serverEndpoint, serverName);
} }
@@ -1385,6 +1383,7 @@ namespace Barotrauma.Networking
if (gameMode == null) if (gameMode == null)
{ {
DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!"); DebugConsole.ThrowError("Game mode \"" + modeIdentifier + "\" not found!");
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -1415,11 +1414,13 @@ namespace Barotrauma.Networking
int missionIndex = inc.ReadInt16(); int missionIndex = inc.ReadInt16();
if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList)) if (!GameMain.NetLobbyScreen.TrySelectSub(subName, subHash, GameMain.NetLobbyScreen.SubList))
{ {
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox)) if (!GameMain.NetLobbyScreen.TrySelectSub(shuttleName, shuttleHash, GameMain.NetLobbyScreen.ShuttleList.ListBox))
{ {
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -1448,6 +1449,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
if (GameMain.NetLobbyScreen.SelectedShuttle == null || if (GameMain.NetLobbyScreen.SelectedShuttle == null ||
@@ -1459,6 +1461,7 @@ namespace Barotrauma.Networking
string errorMsg = "Failed to select shuttle \"" + shuttleName + "\" (hash: " + shuttleHash + ")."; string errorMsg = "Failed to select shuttle \"" + shuttleName + "\" (hash: " + shuttleHash + ").";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -1487,6 +1490,7 @@ namespace Barotrauma.Networking
gameStarted = true; gameStarted = true;
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
else if (campaign.Map == null) else if (campaign.Map == null)
@@ -1495,6 +1499,7 @@ namespace Barotrauma.Networking
gameStarted = true; gameStarted = true;
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -1515,6 +1520,11 @@ namespace Barotrauma.Networking
} }
} }
if (GameMain.Client?.ServerSettings?.Voting != null)
{
GameMain.Client.ServerSettings.Voting.ResetVotes(GameMain.Client.ConnectedClients);
}
if (loadTask != null) if (loadTask != null)
{ {
while (!loadTask.IsCompleted && !loadTask.IsFaulted && !loadTask.IsCanceled) while (!loadTask.IsCompleted && !loadTask.IsFaulted && !loadTask.IsCanceled)
@@ -1626,7 +1636,10 @@ namespace Barotrauma.Networking
} }
} }
if (respawnAllowed) { respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle ? GameMain.NetLobbyScreen.SelectedShuttle : null); } if (respawnAllowed)
{
respawnManager = new RespawnManager(this, GameMain.NetLobbyScreen.UsingShuttle && gameMode != GameModePreset.MultiPlayerCampaign ? GameMain.NetLobbyScreen.SelectedShuttle : null);
}
gameStarted = true; gameStarted = true;
ServerSettings.ServerDetailsChanged = true; ServerSettings.ServerDetailsChanged = true;
@@ -1645,6 +1658,17 @@ namespace Barotrauma.Networking
public IEnumerable<object> EndGame(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) public IEnumerable<object> EndGame(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{ {
//round starting up, wait for it to finish
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 60);
while (TaskPool.IsTaskRunning("AsyncCampaignStartRound"))
{
if (DateTime.Now > timeOut)
{
throw new Exception("Failed to end a round (async campaign round start timed out).");
}
yield return new WaitForSeconds(1.0f);
}
if (!gameStarted) if (!gameStarted)
{ {
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
@@ -2086,7 +2110,6 @@ namespace Barotrauma.Networking
{ {
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE) while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
{ {
bool eventReadFailed = false;
switch (objHeader) switch (objHeader)
{ {
case ServerNetObject.SYNC_IDS: case ServerNetObject.SYNC_IDS:
@@ -2110,7 +2133,13 @@ namespace Barotrauma.Networking
int msgEndPos = (int)(inc.BitPosition + msgLength * 8); int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
var entity = Entity.FindEntityByID(id) as IServerSerializable; var entity = Entity.FindEntityByID(id) as IServerSerializable;
if (entity != null) if (msgEndPos > inc.LengthBits)
{
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
return;
}
if (entity != null && (entity is Item || entity is Character || entity is Submarine))
{ {
entity.ClientRead(objHeader.Value, inc, sendingTime); entity.ClientRead(objHeader.Value, inc, sendingTime);
} }
@@ -2127,8 +2156,7 @@ namespace Barotrauma.Networking
case ServerNetObject.ENTITY_EVENT_INITIAL: case ServerNetObject.ENTITY_EVENT_INITIAL:
if (!entityEventManager.Read(objHeader.Value, inc, sendingTime, entities)) if (!entityEventManager.Read(objHeader.Value, inc, sendingTime, entities))
{ {
eventReadFailed = true; return;
break;
} }
break; break;
case ServerNetObject.CHAT_MESSAGE: case ServerNetObject.CHAT_MESSAGE:
@@ -2138,16 +2166,11 @@ namespace Barotrauma.Networking
throw new Exception($"Unknown object header \"{objHeader}\"!)"); throw new Exception($"Unknown object header \"{objHeader}\"!)");
} }
prevBitLength = inc.BitPosition - prevBitPos; prevBitLength = inc.BitPosition - prevBitPos;
prevByteLength = inc.BytePosition - prevByteLength; prevByteLength = inc.BytePosition - prevBytePos;
prevObjHeader = objHeader; prevObjHeader = objHeader;
prevBitPos = inc.BitPosition; prevBitPos = inc.BitPosition;
prevBytePos = inc.BytePosition; prevBytePos = inc.BytePosition;
if (eventReadFailed)
{
break;
}
} }
} }
@@ -2618,7 +2641,7 @@ namespace Barotrauma.Networking
public void VoteForKick(Client votedClient) public void VoteForKick(Client votedClient)
{ {
if (votedClient == null) { return; } if (votedClient == null) { return; }
votedClient.AddKickVote(ConnectedClients.First(c => c.ID == ID)); votedClient.AddKickVote(ConnectedClients.FirstOrDefault(c => c.ID == myID));
Vote(VoteType.Kick, votedClient); Vote(VoteType.Kick, votedClient);
} }
@@ -164,6 +164,19 @@ namespace Barotrauma.Networking
for (int i = 0; i < eventCount; i++) for (int i = 0; i < eventCount; i++)
{ {
//16 = entity ID, 8 = msg length
if (msg.BitPosition + 16 + 8 > msg.LengthBits)
{
string errorMsg = $"Error while reading a message from the server. Entity event data exceeds the size of the buffer (current position: {msg.BitPosition}, length: {msg.LengthBits}).";
errorMsg += "\nPrevious entities:";
for (int j = entities.Count - 1; j >= 0; j--)
{
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
}
DebugConsole.ThrowError(errorMsg);
return false;
}
UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i); UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
UInt16 entityID = msg.ReadUInt16(); UInt16 entityID = msg.ReadUInt16();
@@ -176,7 +189,7 @@ namespace Barotrauma.Networking
} }
msg.ReadPadBits(); msg.ReadPadBits();
entities.Add(null); entities.Add(null);
if (thisEventID == (UInt16)(lastReceivedID + 1)) lastReceivedID++; if (thisEventID == (UInt16)(lastReceivedID + 1)) { lastReceivedID++; }
continue; continue;
} }
@@ -248,10 +261,8 @@ namespace Barotrauma.Networking
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString()); errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
} }
if (GameSettings.VerboseLogging) DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e);
{
DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e);
}
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8); msg.BitPosition = (int)(msgPosition + msgLength * 8);
@@ -1,13 +1,56 @@
using System; using Barotrauma.Extensions;
using Barotrauma.Steam;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text; using System.Text;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
abstract class ClientPeer abstract class ClientPeer
{ {
protected class ServerContentPackage
{
public string Name;
public string Hash;
public UInt64 WorkshopId;
public ContentPackage RegularPackage
{
get
{
return ContentPackage.RegularPackages.Find(p => p.MD5hash.Hash.Equals(Hash));
}
}
public ContentPackage CorePackage
{
get
{
return ContentPackage.CorePackages.Find(p => p.MD5hash.Hash.Equals(Hash));
}
}
public ServerContentPackage(string name, string hash, UInt64 workshopId)
{
Name = name;
Hash = hash;
WorkshopId = workshopId;
}
}
protected string GetPackageStr(ContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + contentPackage.MD5hash.ShortHash + ")";
}
protected string GetPackageStr(ServerContentPackage contentPackage)
{
return "\"" + contentPackage.Name + "\" (hash " + Md5Hash.GetShortHash(contentPackage.Hash) + ")";
}
public delegate void MessageCallback(IReadMessage message); public delegate void MessageCallback(IReadMessage message);
public delegate void DisconnectCallback(); public delegate void DisconnectCallback(bool disableReconnect);
public delegate void DisconnectMessageCallback(string message); public delegate void DisconnectMessageCallback(string message);
public delegate void PasswordCallback(int salt, int retries); public delegate void PasswordCallback(int salt, int retries);
public delegate void InitializationCompleteCallback(); public delegate void InitializationCompleteCallback();
@@ -25,11 +68,150 @@ namespace Barotrauma.Networking
public NetworkConnection ServerConnection { get; protected set; } public NetworkConnection ServerConnection { get; protected set; }
public abstract void Start(object endPoint, int ownerKey); public abstract void Start(object endPoint, int ownerKey);
public abstract void Close(string msg = null); public abstract void Close(string msg = null, bool disableReconnect = false);
public abstract void Update(float deltaTime); public abstract void Update(float deltaTime);
public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod); public abstract void Send(IWriteMessage msg, DeliveryMethod deliveryMethod);
public abstract void SendPassword(string password); public abstract void SendPassword(string password);
protected abstract void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg);
protected ConnectionInitialization initializationStep;
protected bool contentPackageOrderReceived;
protected int ownerKey = 0;
protected int passwordSalt;
protected Steamworks.AuthTicket steamAuthTicket;
protected void ReadConnectionInitializationStep(IReadMessage inc)
{
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
IWriteMessage outMsg;
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(Name);
outMsg.Write(ownerKey);
outMsg.Write(SteamManager.GetSteamID());
if (steamAuthTicket == null)
{
outMsg.Write((UInt16)0);
}
else
{
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
}
outMsg.Write(GameMain.Version.ToString());
outMsg.Write(GameMain.Config.Language);
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
break;
case ConnectionInitialization.ContentPackageOrder:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
string serverName = inc.ReadString();
UInt32 cpCount = inc.ReadVariableUInt32();
ServerContentPackage corePackage = null;
List<ServerContentPackage> regularPackages = new List<ServerContentPackage>();
List<ServerContentPackage> missingPackages = new List<ServerContentPackage>();
for (int i = 0; i < cpCount; i++)
{
string name = inc.ReadString();
string hash = inc.ReadString();
UInt64 workshopId = inc.ReadUInt64();
var pkg = new ServerContentPackage(name, hash, workshopId);
if (pkg.CorePackage != null)
{
corePackage = pkg;
}
else if (pkg.RegularPackage != null)
{
regularPackages.Add(pkg);
}
else
{
missingPackages.Add(pkg);
}
}
if (missingPackages.Count > 0)
{
var nonDownloadable = missingPackages.Where(p => p.WorkshopId == 0);
if (nonDownloadable.Any())
{
string disconnectMsg;
if (nonDownloadable.Count() == 1)
{
disconnectMsg = $"DisconnectMessage.MissingContentPackage~[missingcontentpackage]={GetPackageStr(missingPackages[0])}";
}
else
{
List<string> packageStrs = new List<string>();
nonDownloadable.ForEach(cp => packageStrs.Add(GetPackageStr(cp)));
disconnectMsg = $"DisconnectMessage.MissingContentPackages~[missingcontentpackages]={string.Join(", ", packageStrs)}";
}
Close(disconnectMsg, disableReconnect: true);
}
else
{
Close(disableReconnect: true);
string missingModNames = "\n- " + string.Join("\n\n- ", missingPackages.Select(p => GetPackageStr(p))) + "\n\n";
var msgBox = new GUIMessageBox(
TextManager.Get("WorkshopItemDownloadTitle"),
TextManager.GetWithVariable("WorkshopItemDownloadPrompt", "[items]", missingModNames),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
msgBox.Buttons[0].OnClicked = (yesBtn, userdata) =>
{
GameMain.ServerListScreen.Select();
GameMain.ServerListScreen.DownloadWorkshopItems(missingPackages.Select(p => p.WorkshopId), serverName, ServerConnection.EndPointString);
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
return;
}
if (!contentPackageOrderReceived)
{
GameMain.Config.SwapPackages(corePackage.CorePackage, regularPackages.Select(p => p.RegularPackage).ToList());
contentPackageOrderReceived = true;
}
SendMsgInternal(DeliveryMethod.Reliable, outMsg);
break;
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
#if DEBUG #if DEBUG
public abstract void ForceTimeOut(); public abstract void ForceTimeOut();
#endif #endif
@@ -14,11 +14,6 @@ namespace Barotrauma.Networking
private NetClient netClient; private NetClient netClient;
private NetPeerConfiguration netPeerConfiguration; private NetPeerConfiguration netPeerConfiguration;
private ConnectionInitialization initializationStep;
private bool contentPackageOrderReceived;
private int ownerKey;
private int passwordSalt;
private Steamworks.AuthTicket steamAuthTicket;
List<NetIncomingMessage> incomingLidgrenMessages; List<NetIncomingMessage> incomingLidgrenMessages;
public LidgrenClientPeer(string name) public LidgrenClientPeer(string name)
@@ -128,7 +123,7 @@ namespace Barotrauma.Networking
if (isConnectionInitializationStep && initializationStep != ConnectionInitialization.Success) if (isConnectionInitializationStep && initializationStep != ConnectionInitialization.Success)
{ {
ReadConnectionInitializationStep(inc); ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
} }
else else
{ {
@@ -158,99 +153,6 @@ namespace Barotrauma.Networking
} }
} }
private void ReadConnectionInitializationStep(NetIncomingMessage inc)
{
if (!isActive) { return; }
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
//Console.WriteLine(step + " " + initializationStep);
NetOutgoingMessage outMsg; NetSendResult result;
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(Name);
outMsg.Write(ownerKey);
outMsg.Write(SteamManager.GetSteamID());
if (steamAuthTicket == null)
{
outMsg.Write((UInt16)0);
}
else
{
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
}
outMsg.Write(GameMain.Version.ToString());
IEnumerable<ContentPackage> mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
outMsg.WriteVariableInt32(mpContentPackages.Count());
foreach (ContentPackage contentPackage in mpContentPackages)
{
outMsg.Write(contentPackage.Name);
outMsg.Write(contentPackage.MD5hash.Hash);
}
result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send "+initializationStep.ToString()+" message to host: " + result);
}
break;
case ConnectionInitialization.ContentPackageOrder:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = netClient.CreateMessage();
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
Int32 cpCount = inc.ReadVariableInt32();
List<ContentPackage> serverContentPackages = new List<ContentPackage>();
for (int i = 0; i < cpCount; i++)
{
string hash = inc.ReadString();
serverContentPackages.Add(GameMain.Config.SelectedContentPackages.Find(cp => cp.MD5hash.Hash == hash));
}
if (!contentPackageOrderReceived)
{
GameMain.Config.ReorderSelectedContentPackages(cp => serverContentPackages.Contains(cp) ?
serverContentPackages.IndexOf(cp) :
serverContentPackages.Count + GameMain.Config.SelectedContentPackages.IndexOf(cp));
contentPackageOrderReceived = true;
}
result = netClient.SendMessage(outMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send " + initializationStep.ToString() + " message to host: " + result);
}
break;
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
public override void SendPassword(string password) public override void SendPassword(string password)
{ {
if (!isActive) { return; } if (!isActive) { return; }
@@ -269,7 +171,7 @@ namespace Barotrauma.Networking
} }
} }
public override void Close(string msg = null) public override void Close(string msg = null, bool disableReconnect = false)
{ {
if (!isActive) { return; } if (!isActive) { return; }
@@ -278,7 +180,7 @@ namespace Barotrauma.Networking
netClient.Shutdown(msg ?? TextManager.Get("Disconnecting")); netClient.Shutdown(msg ?? TextManager.Get("Disconnecting"));
netClient = null; netClient = null;
steamAuthTicket?.Cancel(); steamAuthTicket = null; steamAuthTicket?.Cancel(); steamAuthTicket = null;
OnDisconnect?.Invoke(); OnDisconnect?.Invoke(disableReconnect);
} }
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod) public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
@@ -320,6 +222,18 @@ namespace Barotrauma.Networking
} }
} }
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
{
NetOutgoingMessage lidgrenMsg = netClient.CreateMessage();
lidgrenMsg.Write(msg.Buffer, 0, msg.LengthBytes);
NetSendResult result = netClient.SendMessage(lidgrenMsg, NetDeliveryMethod.ReliableUnordered);
if (result != NetSendResult.Queued && result != NetSendResult.Sent)
{
DebugConsole.NewMessage("Failed to send message to host: " + result + "\n" + Environment.StackTrace);
}
}
#if DEBUG #if DEBUG
public override void ForceTimeOut() public override void ForceTimeOut()
{ {
@@ -12,10 +12,6 @@ namespace Barotrauma.Networking
{ {
private bool isActive; private bool isActive;
private UInt64 hostSteamId; private UInt64 hostSteamId;
private ConnectionInitialization initializationStep;
private bool contentPackageOrderReceived;
private int passwordSalt;
private Steamworks.AuthTicket steamAuthTicket;
private double timeout; private double timeout;
private double heartbeatTimer; private double heartbeatTimer;
private double connectionStatusTimer; private double connectionStatusTimer;
@@ -89,6 +85,7 @@ namespace Barotrauma.Networking
Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId); Steamworks.SteamNetworking.AcceptP2PSessionWithUser(steamId);
} }
else if (initializationStep != ConnectionInitialization.Password && else if (initializationStep != ConnectionInitialization.Password &&
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success) initializationStep != ConnectionInitialization.Success)
{ {
DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+ DebugConsole.ThrowError($"Connection from incorrect SteamID was rejected: "+
@@ -105,7 +102,7 @@ namespace Barotrauma.Networking
OnDisconnectMessageReceived?.Invoke($"SteamP2P connection failed: {error}"); OnDisconnectMessageReceived?.Invoke($"SteamP2P connection failed: {error}");
} }
private void OnP2PData(ulong steamId, byte[] data, int dataLength, int channel) private void OnP2PData(ulong steamId, byte[] data, int dataLength)
{ {
if (!isActive) { return; } if (!isActive) { return; }
if (steamId != hostSteamId) { return; } if (steamId != hostSteamId) { return; }
@@ -165,6 +162,7 @@ namespace Barotrauma.Networking
heartbeatTimer -= deltaTime; heartbeatTimer -= deltaTime;
if (initializationStep != ConnectionInitialization.Password && if (initializationStep != ConnectionInitialization.Password &&
initializationStep != ConnectionInitialization.ContentPackageOrder &&
initializationStep != ConnectionInitialization.Success) initializationStep != ConnectionInitialization.Success)
{ {
connectionStatusTimer -= deltaTime; connectionStatusTimer -= deltaTime;
@@ -194,7 +192,7 @@ namespace Barotrauma.Networking
var packet = Steamworks.SteamNetworking.ReadP2PPacket(); var packet = Steamworks.SteamNetworking.ReadP2PPacket();
if (packet.HasValue) if (packet.HasValue)
{ {
OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0, 0); OnP2PData(packet?.SteamId ?? 0, packet?.Data, packet?.Data.Length ?? 0);
receivedBytes += packet?.Data.Length ?? 0; receivedBytes += packet?.Data.Length ?? 0;
} }
} }
@@ -249,88 +247,6 @@ namespace Barotrauma.Networking
incomingDataMessages.Clear(); incomingDataMessages.Clear();
} }
private void ReadConnectionInitializationStep(IReadMessage inc)
{
if (!isActive) { return; }
ConnectionInitialization step = (ConnectionInitialization)inc.ReadByte();
IWriteMessage outMsg;
//DebugConsole.NewMessage(step + " " + initializationStep);
switch (step)
{
case ConnectionInitialization.SteamTicketAndVersion:
if (initializationStep != ConnectionInitialization.SteamTicketAndVersion) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.SteamTicketAndVersion);
outMsg.Write(Name);
outMsg.Write(SteamManager.GetSteamID());
outMsg.Write((UInt16)steamAuthTicket.Data.Length);
outMsg.Write(steamAuthTicket.Data, 0, steamAuthTicket.Data.Length);
outMsg.Write(GameMain.Version.ToString());
IEnumerable<ContentPackage> mpContentPackages = GameMain.SelectedPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
outMsg.WriteVariableUInt32((UInt32)mpContentPackages.Count());
foreach (ContentPackage contentPackage in mpContentPackages)
{
outMsg.Write(contentPackage.Name);
outMsg.Write(contentPackage.MD5hash.Hash);
}
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
break;
case ConnectionInitialization.ContentPackageOrder:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion ||
initializationStep == ConnectionInitialization.Password) { initializationStep = ConnectionInitialization.ContentPackageOrder; }
if (initializationStep != ConnectionInitialization.ContentPackageOrder) { return; }
outMsg = new WriteOnlyMessage();
outMsg.Write((byte)DeliveryMethod.Reliable);
outMsg.Write((byte)PacketHeader.IsConnectionInitializationStep);
outMsg.Write((byte)ConnectionInitialization.ContentPackageOrder);
UInt32 cpCount = inc.ReadVariableUInt32();
List<ContentPackage> serverContentPackages = new List<ContentPackage>();
for (int i = 0; i < cpCount; i++)
{
string hash = inc.ReadString();
serverContentPackages.Add(GameMain.Config.SelectedContentPackages.Find(cp => cp.MD5hash.Hash == hash));
}
if (!contentPackageOrderReceived)
{
GameMain.Config.ReorderSelectedContentPackages(cp => serverContentPackages.Contains(cp) ?
serverContentPackages.IndexOf(cp) :
serverContentPackages.Count + GameMain.Config.SelectedContentPackages.IndexOf(cp));
contentPackageOrderReceived = true;
}
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, outMsg.Buffer, outMsg.LengthBytes, 0, Steamworks.P2PSend.Reliable);
sentBytes += outMsg.LengthBytes;
break;
case ConnectionInitialization.Password:
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) { initializationStep = ConnectionInitialization.Password; }
if (initializationStep != ConnectionInitialization.Password) { return; }
bool incomingSalt = inc.ReadBoolean(); inc.ReadPadBits();
int retries = 0;
if (incomingSalt)
{
passwordSalt = inc.ReadInt32();
}
else
{
retries = inc.ReadInt32();
}
OnRequestPassword?.Invoke(passwordSalt, retries);
break;
}
}
public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod) public override void Send(IWriteMessage msg, DeliveryMethod deliveryMethod)
{ {
if (!isActive) { return; } if (!isActive) { return; }
@@ -339,8 +255,7 @@ namespace Barotrauma.Networking
buf[0] = (byte)deliveryMethod; buf[0] = (byte)deliveryMethod;
byte[] bufAux = new byte[msg.LengthBytes]; byte[] bufAux = new byte[msg.LengthBytes];
bool isCompressed; int length; msg.PrepareForSending(ref bufAux, out bool isCompressed, out int length);
msg.PrepareForSending(ref bufAux, out isCompressed, out length);
buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None); buf[1] = (byte)(isCompressed ? PacketHeader.IsCompressed : PacketHeader.None);
@@ -426,7 +341,7 @@ namespace Barotrauma.Networking
sentBytes += outMsg.LengthBytes; sentBytes += outMsg.LengthBytes;
} }
public override void Close(string msg = null) public override void Close(string msg = null, bool disableReconnect = false)
{ {
if (!isActive) { return; } if (!isActive) { return; }
@@ -451,7 +366,7 @@ namespace Barotrauma.Networking
steamAuthTicket?.Cancel(); steamAuthTicket = null; steamAuthTicket?.Cancel(); steamAuthTicket = null;
hostSteamId = 0; hostSteamId = 0;
OnDisconnect?.Invoke(); OnDisconnect?.Invoke(disableReconnect);
} }
~SteamP2PClientPeer() ~SteamP2PClientPeer()
@@ -460,6 +375,31 @@ namespace Barotrauma.Networking
Close(); Close();
} }
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
{
Steamworks.P2PSend sendType;
switch (deliveryMethod)
{
case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type
//enforces packet order (TODO: verify)
sendType = Steamworks.P2PSend.Reliable;
break;
default:
sendType = Steamworks.P2PSend.Unreliable;
break;
}
IWriteMessage msgToSend = new WriteOnlyMessage();
msgToSend.Write((byte)deliveryMethod);
msgToSend.Write(msg.Buffer, 0, msg.LengthBytes);
heartbeatTimer = 5.0;
Steamworks.SteamNetworking.SendP2PPacket(hostSteamId, msgToSend.Buffer, msgToSend.LengthBytes, 0, sendType);
sentBytes += msg.LengthBytes;
}
#if DEBUG #if DEBUG
public override void ForceTimeOut() public override void ForceTimeOut()
{ {
@@ -1,8 +1,6 @@
using System; using Barotrauma.Steam;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Net;
using System.Text;
using Barotrauma.Steam;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -12,7 +10,6 @@ namespace Barotrauma.Networking
{ {
private bool isActive; private bool isActive;
private ConnectionInitialization initializationStep;
private readonly UInt64 selfSteamID; private readonly UInt64 selfSteamID;
private long sentBytes, receivedBytes; private long sentBytes, receivedBytes;
@@ -61,7 +58,7 @@ namespace Barotrauma.Networking
initializationStep = ConnectionInitialization.SteamTicketAndVersion; initializationStep = ConnectionInitialization.SteamTicketAndVersion;
ServerConnection = new PipeConnection(); ServerConnection = new PipeConnection(selfSteamID);
ServerConnection.Status = NetworkConnectionStatus.Connected; ServerConnection.Status = NetworkConnectionStatus.Connected;
remotePeers = new List<RemotePeer>(); remotePeers = new List<RemotePeer>();
@@ -161,6 +158,7 @@ namespace Barotrauma.Networking
remotePeer.Authenticating = true; remotePeer.Authenticating = true;
authMsg.ReadString(); //skip name authMsg.ReadString(); //skip name
authMsg.ReadInt32(); //skip owner key
authMsg.ReadUInt64(); //skip steamid authMsg.ReadUInt64(); //skip steamid
UInt16 ticketLength = authMsg.ReadUInt16(); UInt16 ticketLength = authMsg.ReadUInt16();
byte[] ticket = authMsg.ReadBytes(ticketLength); byte[] ticket = authMsg.ReadBytes(ticketLength);
@@ -395,7 +393,7 @@ namespace Barotrauma.Networking
return; //owner doesn't send passwords return; //owner doesn't send passwords
} }
public override void Close(string msg = null) public override void Close(string msg = null, bool disableReconnect = false)
{ {
if (!isActive) { return; } if (!isActive) { return; }
@@ -415,7 +413,7 @@ namespace Barotrauma.Networking
ChildServerRelay.ClosePipes(); ChildServerRelay.ClosePipes();
OnDisconnect?.Invoke(); OnDisconnect?.Invoke(disableReconnect);
SteamManager.LeaveLobby(); SteamManager.LeaveLobby();
Steamworks.SteamNetworking.ResetActions(); Steamworks.SteamNetworking.ResetActions();
@@ -445,6 +443,12 @@ namespace Barotrauma.Networking
Close(); Close();
} }
protected override void SendMsgInternal(DeliveryMethod deliveryMethod, IWriteMessage msg)
{
//not currently used by SteamP2POwnerPeer
throw new NotImplementedException();
}
#if DEBUG #if DEBUG
public override void ForceTimeOut() public override void ForceTimeOut()
{ {
@@ -73,26 +73,25 @@ namespace Barotrauma.Networking
get; get;
private set; private set;
} = new List<string>(); } = new List<string>();
public List<string> ContentPackageWorkshopUrls public List<ulong> ContentPackageWorkshopIds
{ {
get; get;
private set; private set;
} = new List<string>(); } = new List<ulong>();
public bool ContentPackagesMatch(IEnumerable<ContentPackage> myContentPackages) public bool ContentPackagesMatch()
{ {
var myContentPackages = ContentPackage.AllPackages;
//make sure we have all the packages the server requires //make sure we have all the packages the server requires
foreach (string hash in ContentPackageHashes) if (ContentPackageHashes.Count != ContentPackageWorkshopIds.Count) { return false; }
for (int i = 0; i < ContentPackageWorkshopIds.Count; i++)
{ {
if (!myContentPackages.Any(myPackage => myPackage.MD5hash.Hash == hash)) { return false; } string hash = ContentPackageHashes[i];
} UInt64 id = ContentPackageWorkshopIds[i];
if (!myContentPackages.Any(myPackage => myPackage.MD5hash.Hash == hash))
//make sure the server isn't missing any of our packages that cause multiplayer incompatibility
foreach (ContentPackage myPackage in myContentPackages)
{
if (myPackage.HasMultiplayerIncompatibleContent)
{ {
if (!ContentPackageHashes.Any(hash => hash == myPackage.MD5hash.Hash)) { return false; } if (myContentPackages.Any(p => p.SteamWorkshopId == id)) { return false; }
if (id == 0) { return false; }
} }
} }
@@ -145,19 +144,22 @@ namespace Barotrauma.Networking
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), previewContainer.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion)); TextManager.AddPunctuation(':', TextManager.Get("ServerListVersion"), string.IsNullOrEmpty(GameVersion) ? TextManager.Get("Unknown") : GameVersion));
PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious; bool hidePlaystyleBanner = previewContainer.Rect.Height < 380 || !PlayStyle.HasValue;
if (!hidePlaystyleBanner)
{
PlayStyle playStyle = PlayStyle ?? Networking.PlayStyle.Serious;
Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle];
float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height;
var playStyleBanner = new GUIImage(new RectTransform(new Point(previewContainer.Rect.Width, (int)(previewContainer.Rect.Width / playStyleBannerAspectRatio)), previewContainer.RectTransform),
playStyleBannerSprite, null, true);
Sprite playStyleBannerSprite = ServerListScreen.PlayStyleBanners[(int)playStyle]; var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.06f) },
float playStyleBannerAspectRatio = playStyleBannerSprite.SourceRect.Width / playStyleBannerSprite.SourceRect.Height; TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag."+ playStyle)), textColor: Color.White,
var playStyleBanner = new GUIImage(new RectTransform(new Point(previewContainer.Rect.Width, (int)(previewContainer.Rect.Width / playStyleBannerAspectRatio)), previewContainer.RectTransform), font: GUI.SmallFont, textAlignment: Alignment.Center,
playStyleBannerSprite, null, true); color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader");
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
var playStyleName = new GUITextBlock(new RectTransform(new Vector2(0.15f, 0.0f), playStyleBanner.RectTransform) { RelativeOffset = new Vector2(0.01f, 0.06f) }, playStyleName.RectTransform.IsFixedSize = true;
TextManager.AddPunctuation(':', TextManager.Get("serverplaystyle"), TextManager.Get("servertag."+ playStyle)), textColor: Color.White, }
font: GUI.SmallFont, textAlignment: Alignment.Center,
color: ServerListScreen.PlayStyleColors[(int)playStyle], style: "GUISlopedHeader");
playStyleName.RectTransform.NonScaledSize = (playStyleName.Font.MeasureString(playStyleName.Text) + new Vector2(20, 5) * GUI.Scale).ToPoint();
playStyleName.RectTransform.IsFixedSize = true;
var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.RectTransform)) var content = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), previewContainer.RectTransform))
{ {
@@ -207,8 +209,13 @@ namespace Barotrauma.Networking
TextManager.Get(string.IsNullOrEmpty(GameMode) ? "Unknown" : "GameMode." + GameMode, returnNull: true) ?? GameMode, TextManager.Get(string.IsNullOrEmpty(GameMode) ? "Unknown" : "GameMode." + GameMode, returnNull: true) ?? GameMode,
textAlignment: Alignment.Right); textAlignment: Alignment.Right);
/*var traitors = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), bodyContainer.RectTransform), TextManager.Get("Traitors")); GUITextBlock playStyleText = null;
new GUITextBlock(new RectTransform(Vector2.One, traitors.RectTransform), TextManager.Get(!TraitorsEnabled.HasValue ? "Unknown" : TraitorsEnabled.Value.ToString()), textAlignment: Alignment.Right);*/ if (hidePlaystyleBanner && PlayStyle.HasValue)
{
PlayStyle playStyle = PlayStyle.Value;
playStyleText = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("serverplaystyle"));
new GUITextBlock(new RectTransform(Vector2.One, playStyleText.RectTransform), TextManager.Get("servertag." + playStyle), textAlignment: Alignment.Right);
}
var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection")); var subSelection = new GUITextBlock(new RectTransform(new Vector2(1.0f, elementHeight), content.RectTransform), TextManager.Get("ServerListSubSelection"));
new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(!SubSelectionMode.HasValue ? "Unknown" : SubSelectionMode.Value.ToString()), textAlignment: Alignment.Right); new GUITextBlock(new RectTransform(Vector2.One, subSelection.RectTransform), TextManager.Get(!SubSelectionMode.HasValue ? "Unknown" : SubSelectionMode.Value.ToString()), textAlignment: Alignment.Right);
@@ -222,6 +229,10 @@ namespace Barotrauma.Networking
{ {
gameMode.Font = subSelection.Font = modeSelection.Font = GUI.SmallFont; gameMode.Font = subSelection.Font = modeSelection.Font = GUI.SmallFont;
gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUI.SmallFont; gameMode.GetChild<GUITextBlock>().Font = subSelection.GetChild<GUITextBlock>().Font = modeSelection.GetChild<GUITextBlock>().Font = GUI.SmallFont;
if (playStyleText != null)
{
playStyleText.Font = playStyleText.GetChild<GUITextBlock>().Font = GUI.SmallFont;
}
} }
var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating")) var allowSpectating = new GUITickBox(new RectTransform(new Vector2(1, elementHeight), content.RectTransform), TextManager.Get("ServerListAllowSpectating"))
@@ -279,7 +290,6 @@ namespace Barotrauma.Networking
} }
else else
{ {
List<string> availableWorkshopUrls = new List<string>();
for (int i = 0; i < ContentPackageNames.Count; i++) for (int i = 0; i < ContentPackageNames.Count; i++)
{ {
var packageText = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) }, var packageText = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.15f), contentPackageList.Content.RectTransform) { MinSize = new Point(0, 15) },
@@ -289,22 +299,15 @@ namespace Barotrauma.Networking
}; };
if (i < ContentPackageHashes.Count) if (i < ContentPackageHashes.Count)
{ {
if (GameMain.Config.SelectedContentPackages.Any(cp => cp.MD5hash.Hash == ContentPackageHashes[i])) if (ContentPackage.AllPackages.Any(cp => cp.MD5hash.Hash == ContentPackageHashes[i]))
{ {
packageText.Selected = true; packageText.Selected = true;
continue; continue;
} }
//matching content package found, but it hasn't been enabled
if (ContentPackage.List.Any(cp => cp.MD5hash.Hash == ContentPackageHashes[i]))
{
packageText.TextColor = GUI.Style.Orange;
packageText.ToolTip = TextManager.GetWithVariable("ServerListContentPackageNotEnabled", "[contentpackage]", ContentPackageNames[i]);
}
//workshop download link found //workshop download link found
else if (i < ContentPackageWorkshopUrls.Count && !string.IsNullOrEmpty(ContentPackageWorkshopUrls[i])) if (i < ContentPackageWorkshopIds.Count && ContentPackageWorkshopIds[i] != 0)
{ {
availableWorkshopUrls.Add(ContentPackageWorkshopUrls[i]);
packageText.TextColor = Color.Yellow; packageText.TextColor = Color.Yellow;
packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]); packageText.ToolTip = TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", ContentPackageNames[i]);
} }
@@ -316,21 +319,6 @@ namespace Barotrauma.Networking
} }
} }
} }
if (availableWorkshopUrls.Count > 0)
{
var workshopBtn = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), content.RectTransform), TextManager.Get("ServerListSubscribeMissingPackages"))
{
ToolTip = TextManager.Get(SteamManager.IsInitialized ? "ServerListSubscribeMissingPackagesTooltip" : "ServerListSubscribeMissingPackagesTooltipNoSteam"),
Enabled = SteamManager.IsInitialized,
OnClicked = (btn, userdata) =>
{
GameMain.SteamWorkshopScreen.SubscribeToPackages(availableWorkshopUrls);
GameMain.SteamWorkshopScreen.Select();
return true;
}
};
workshopBtn.TextBlock.AutoScaleHorizontal = true;
}
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
@@ -391,7 +379,7 @@ namespace Barotrauma.Networking
if (bool.TryParse(element.GetAttributeString("UsingWhiteList", ""), out bool whitelistTemp)) { info.UsingWhiteList = whitelistTemp; } if (bool.TryParse(element.GetAttributeString("UsingWhiteList", ""), out bool whitelistTemp)) { info.UsingWhiteList = whitelistTemp; }
if (Enum.TryParse(element.GetAttributeString("TraitorsEnabled", ""), out YesNoMaybe traitorsTemp)) { info.TraitorsEnabled = traitorsTemp; } if (Enum.TryParse(element.GetAttributeString("TraitorsEnabled", ""), out YesNoMaybe traitorsTemp)) { info.TraitorsEnabled = traitorsTemp; }
if (Enum.TryParse(element.GetAttributeString("SubSelectionMode", ""), out SelectionMode subSelectionTemp)) { info.SubSelectionMode = subSelectionTemp; } if (Enum.TryParse(element.GetAttributeString("SubSelectionMode", ""), out SelectionMode subSelectionTemp)) { info.SubSelectionMode = subSelectionTemp; }
if (Enum.TryParse(element.GetAttributeString("ModeSelectionMode", ""), out SelectionMode modeSelectionTemp)) { info.ModeSelectionMode = subSelectionTemp; } if (Enum.TryParse(element.GetAttributeString("ModeSelectionMode", ""), out SelectionMode modeSelectionTemp)) { info.ModeSelectionMode = modeSelectionTemp; }
if (bool.TryParse(element.GetAttributeString("VoipEnabled", ""), out bool voipTemp)) { info.VoipEnabled = voipTemp; } if (bool.TryParse(element.GetAttributeString("VoipEnabled", ""), out bool voipTemp)) { info.VoipEnabled = voipTemp; }
if (bool.TryParse(element.GetAttributeString("KarmaEnabled", ""), out bool karmaTemp)) { info.KarmaEnabled = karmaTemp; } if (bool.TryParse(element.GetAttributeString("KarmaEnabled", ""), out bool karmaTemp)) { info.KarmaEnabled = karmaTemp; }
if (bool.TryParse(element.GetAttributeString("FriendlyFireEnabled", ""), out bool friendlyFireTemp)) { info.FriendlyFireEnabled = friendlyFireTemp; } if (bool.TryParse(element.GetAttributeString("FriendlyFireEnabled", ""), out bool friendlyFireTemp)) { info.FriendlyFireEnabled = friendlyFireTemp; }
@@ -477,10 +477,6 @@ namespace Barotrauma.Networking
GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock)); GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock));
playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W)); playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W));
var endBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsEndRoundWhenDestReached"));
GetPropertyData("EndRoundAtLevelEnd").AssignGUIComponent(endBox);
var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsEndRoundVoting")); TextManager.Get("ServerSettingsEndRoundVoting"));
GetPropertyData("AllowEndVoting").AssignGUIComponent(endVoteBox); GetPropertyData("AllowEndVoting").AssignGUIComponent(endVoteBox);
@@ -569,6 +565,8 @@ namespace Barotrauma.Networking
}; };
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
var traitorsMinPlayerCount = CreateLabeledNumberInput(roundsTab, "ServerSettingsTraitorsMinPlayerCount", 1, 16, "ServerSettingsTraitorsMinPlayerCountToolTip");
GetPropertyData("TraitorsMinPlayerCount").AssignGUIComponent(traitorsMinPlayerCount);
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);
@@ -576,53 +574,6 @@ namespace Barotrauma.Networking
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations")); var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
GetPropertyData("DisableBotConversations").AssignGUIComponent(disableBotConversationsBox); GetPropertyData("DisableBotConversations").AssignGUIComponent(disableBotConversationsBox);
/*var traitorRatioBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsUseTraitorRatio"));
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel);
var traitorRatioSlider = slider;
traitorRatioBox.OnSelected = (GUITickBox) =>
{
traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll);
return true;
};
if (TraitorUseRatio)
{
traitorRatioSlider.Range = new Vector2(0.1f, 1.0f);
}
else
{
traitorRatioSlider.Range = new Vector2(1.0f, maxPlayers);
}
string traitorRatioLabel = TextManager.Get("ServerSettingsTraitorRatio") + " ";
string traitorCountLabel = TextManager.Get("ServerSettingsTraitorCount") + " ";
traitorRatioSlider.Range = new Vector2(0.1f, 1.0f);
traitorRatioSlider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{
GUITextBlock traitorText = scrollBar.UserData as GUITextBlock;
if (traitorRatioBox.Selected)
{
scrollBar.Step = 0.01f;
scrollBar.Range = new Vector2(0.1f, 1.0f);
traitorText.Text = traitorRatioLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 1.0f) + " %";
}
else
{
scrollBar.Step = 1f / (maxPlayers - 1);
scrollBar.Range = new Vector2(1.0f, maxPlayers);
traitorText.Text = traitorCountLabel + scrollBar.BarScrollValue;
}
return true;
};
GetPropertyData("TraitorUseRatio").AssignGUIComponent(traitorRatioBox);
GetPropertyData("TraitorRatio").AssignGUIComponent(traitorRatioSlider);
traitorRatioSlider.OnMoved(traitorRatioSlider, traitorRatioSlider.BarScroll);
traitorRatioBox.OnSelected(traitorRatioBox);*/
var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true) var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -919,7 +870,7 @@ namespace Barotrauma.Networking
slider.UserData = label; slider.UserData = label;
} }
private GUINumberInput CreateLabeledNumberInput(GUIComponent parent, string labelTag, int min, int max) private GUINumberInput CreateLabeledNumberInput(GUIComponent parent, string labelTag, int min, int max, string toolTipTag = null)
{ {
var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true) var container = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), isHorizontal: true)
{ {
@@ -928,11 +879,15 @@ namespace Barotrauma.Networking
ToolTip = TextManager.Get(labelTag) ToolTip = TextManager.Get(labelTag)
}; };
new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform), var label = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), container.RectTransform),
TextManager.Get(labelTag), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont) TextManager.Get(labelTag), textAlignment: Alignment.CenterLeft, font: GUI.SmallFont)
{ {
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
if (!string.IsNullOrEmpty(toolTipTag))
{
label.ToolTip = TextManager.Get(toolTipTag);
}
var input = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), GUINumberInput.NumberType.Int) var input = new GUINumberInput(new RectTransform(new Vector2(0.3f, 1.0f), container.RectTransform), GUINumberInput.NumberType.Int)
{ {
MinValueInt = min, MinValueInt = min,
@@ -1,22 +1,18 @@
using Barotrauma.Networking; using Barotrauma.IO;
using Barotrauma.Networking;
using RestSharp; using RestSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using RestSharp.Contrib;
using System.Xml.Linq; using System.Xml.Linq;
using Color = Microsoft.Xna.Framework.Color; using Color = Microsoft.Xna.Framework.Color;
using System.Runtime.InteropServices;
using NLog.Fluent;
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 readonly Dictionary<Steamworks.Data.PublishedFileId, Task> modCopiesInProgress = new Dictionary<Steamworks.Data.PublishedFileId, Task>();
private static void InitializeProjectSpecific() private static void InitializeProjectSpecific()
{ {
@@ -49,6 +45,9 @@ namespace Barotrauma.Steam
} }
catch (Exception e) catch (Exception e)
{ {
#if !DEBUG
DebugConsole.ThrowError("SteamManager initialization threw an exception", e);
#endif
isInitialized = false; isInitialized = false;
initializationErrors.Add("SteamClientInitFailed"); initializationErrors.Add("SteamClientInitFailed");
} }
@@ -85,28 +84,6 @@ namespace Barotrauma.Steam
} }
} }
private static void UpdateProjectSpecific(float deltaTime)
{
if (ugcSubscriptionTasks != null)
{
var ugcSubscriptionKeys = ugcSubscriptionTasks.Keys.ToList();
foreach (var key in ugcSubscriptionKeys)
{
var task = ugcSubscriptionTasks[key];
if (task.IsCompleted)
{
if (!task.IsCompletedSuccessfully)
{
DebugConsole.ThrowError("Failed to subscribe to a Steam Workshop item with id " + key.ToString() + ": TaskStatus = " + task.Status.ToString());
}
ugcSubscriptionTasks.Remove(key);
}
}
}
}
public static async Task InitRelayNetworkAccess() public static async Task InitRelayNetworkAccess()
{ {
if (!IsInitialized) { return; } if (!IsInitialized) { return; }
@@ -209,13 +186,13 @@ namespace Barotrauma.Steam
return; return;
} }
var contentPackages = GameMain.Config.SelectedContentPackages.Where(cp => cp.HasMultiplayerIncompatibleContent); var contentPackages = GameMain.Config.AllEnabledPackages.Where(cp => cp.HasMultiplayerIncompatibleContent);
currentLobby?.SetData("name", serverSettings.ServerName); currentLobby?.SetData("name", serverSettings.ServerName);
currentLobby?.SetData("playercount", (GameMain.Client?.ConnectedClients?.Count ?? 0).ToString()); currentLobby?.SetData("playercount", (GameMain.Client?.ConnectedClients?.Count ?? 0).ToString());
currentLobby?.SetData("maxplayernum", serverSettings.MaxPlayers.ToString()); currentLobby?.SetData("maxplayernum", serverSettings.MaxPlayers.ToString());
//currentLobby?.SetData("hostipaddress", lobbyIP); //currentLobby?.SetData("hostipaddress", lobbyIP);
string pingLocation = Steamworks.SteamNetworkingUtils.LocalPingLocation.ToString(); string pingLocation = Steamworks.SteamNetworkingUtils.LocalPingLocation?.ToString();
currentLobby?.SetData("pinglocation", pingLocation ?? ""); currentLobby?.SetData("pinglocation", pingLocation ?? "");
currentLobby?.SetData("lobbyowner", SteamIDUInt64ToString(GetSteamID())); currentLobby?.SetData("lobbyowner", SteamIDUInt64ToString(GetSteamID()));
currentLobby?.SetData("haspassword", serverSettings.HasPassword.ToString()); currentLobby?.SetData("haspassword", serverSettings.HasPassword.ToString());
@@ -225,7 +202,7 @@ namespace Barotrauma.Steam
currentLobby?.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name))); currentLobby?.SetData("contentpackage", string.Join(",", contentPackages.Select(cp => cp.Name)));
currentLobby?.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.MD5hash.Hash))); currentLobby?.SetData("contentpackagehash", string.Join(",", contentPackages.Select(cp => cp.MD5hash.Hash)));
currentLobby?.SetData("contentpackageurl", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopUrl ?? ""))); currentLobby?.SetData("contentpackageid", string.Join(",", contentPackages.Select(cp => cp.SteamWorkshopId)));
currentLobby?.SetData("usingwhitelist", (serverSettings.Whitelist != null && serverSettings.Whitelist.Enabled).ToString()); currentLobby?.SetData("usingwhitelist", (serverSettings.Whitelist != null && serverSettings.Whitelist.Enabled).ToString());
currentLobby?.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString()); currentLobby?.SetData("modeselectionmode", serverSettings.ModeSelectionMode.ToString());
currentLobby?.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString()); currentLobby?.SetData("subselectionmode", serverSettings.SubSelectionMode.ToString());
@@ -280,9 +257,8 @@ namespace Barotrauma.Steam
{ {
if (!isInitialized) { return false; } if (!isInitialized) { return false; }
int doneTasks = 0; int doneTasks = 0;
Action taskDone = () => void taskDone()
{ {
doneTasks++; doneTasks++;
if (doneTasks >= 2) if (doneTasks >= 2)
@@ -290,52 +266,64 @@ namespace Barotrauma.Steam
serverQueryFinished?.Invoke(); serverQueryFinished?.Invoke();
serverQueryFinished = null; serverQueryFinished = null;
} }
}; }
//TODO: find a better strategy to fetch all lobbies, this is gonna take forever if we actually have 10000 lobbies //TODO: find a better strategy to fetch all lobbies, this is gonna take forever if we actually have 10000 lobbies
Steamworks.Data.LobbyQuery lobbyQuery = Steamworks.SteamMatchmaking.CreateLobbyQuery().FilterDistanceWorldwide().WithMaxResults(10000); Steamworks.Data.LobbyQuery lobbyQuery = Steamworks.SteamMatchmaking.CreateLobbyQuery().FilterDistanceWorldwide().WithMaxResults(10000);
Steamworks.Dispatch.OnDebugCallback = (callbackType, contents, isServer) =>
{
DebugConsole.NewMessage($"{callbackType}: " + contents, Color.Yellow);
};
TaskPool.Add("LobbyQueryRequest", lobbyQuery.RequestAsync(), TaskPool.Add("LobbyQueryRequest", lobbyQuery.RequestAsync(),
(t) => (t) =>
{ {
var lobbies = ((Task<Steamworks.Data.Lobby[]>)t).Result; Steamworks.Dispatch.OnDebugCallback = null;
foreach (var lobby in lobbies)
{
if (string.IsNullOrEmpty(lobby.GetData("name"))) { continue; }
ServerInfo serverInfo = new ServerInfo();
serverInfo.ServerName = lobby.GetData("name");
serverInfo.OwnerID = SteamIDStringToUInt64(lobby.GetData("lobbyowner"));
serverInfo.LobbyID = lobby.Id;
bool.TryParse(lobby.GetData("haspassword"), out serverInfo.HasPassword);
serverInfo.PlayerCount = int.TryParse(lobby.GetData("playercount"), out int playerCount) ? playerCount : 0;
serverInfo.MaxPlayers = int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers) ? maxPlayers : 1;
serverInfo.RespondedToSteamQuery = true;
AssignLobbyDataToServerInfo(lobby, serverInfo);
addToServerList(serverInfo);
}
taskDone();
if (t.Status == TaskStatus.Faulted) if (t.Status == TaskStatus.Faulted)
{ {
TaskPool.PrintTaskExceptions(t, "Failed to retrieve SteamP2P lobbies"); TaskPool.PrintTaskExceptions(t, "Failed to retrieve SteamP2P lobbies");
taskDone();
return; return;
} }
var lobbies = ((Task<Steamworks.Data.Lobby[]>)t).Result;
if (lobbies != null)
{
foreach (var lobby in lobbies)
{
if (string.IsNullOrEmpty(lobby.GetData("name"))) { continue; }
ServerInfo serverInfo = new ServerInfo();
serverInfo.ServerName = lobby.GetData("name");
serverInfo.OwnerID = SteamIDStringToUInt64(lobby.GetData("lobbyowner"));
serverInfo.LobbyID = lobby.Id;
bool.TryParse(lobby.GetData("haspassword"), out serverInfo.HasPassword);
serverInfo.PlayerCount = int.TryParse(lobby.GetData("playercount"), out int playerCount) ? playerCount : 0;
serverInfo.MaxPlayers = int.TryParse(lobby.GetData("maxplayernum"), out int maxPlayers) ? maxPlayers : 1;
serverInfo.RespondedToSteamQuery = true;
AssignLobbyDataToServerInfo(lobby, serverInfo);
addToServerList(serverInfo);
}
}
taskDone();
}); });
Steamworks.ServerList.Internet serverQuery = new Steamworks.ServerList.Internet(); Steamworks.ServerList.Internet serverQuery = new Steamworks.ServerList.Internet();
Action<Steamworks.Data.ServerInfo, bool> onServer = (Steamworks.Data.ServerInfo info, bool responsive) => void onServer(Steamworks.Data.ServerInfo info, bool responsive)
{ {
if (string.IsNullOrEmpty(info.Name)) { return; } if (string.IsNullOrEmpty(info.Name)) { return; }
ServerInfo serverInfo = new ServerInfo(); ServerInfo serverInfo = new ServerInfo
serverInfo.ServerName = info.Name; {
serverInfo.IP = info.Address.ToString(); ServerName = info.Name,
serverInfo.Port = info.ConnectionPort.ToString(); HasPassword = info.Passworded,
serverInfo.PlayerCount = info.Players; IP = info.Address.ToString(),
serverInfo.MaxPlayers = info.MaxPlayers; Port = info.ConnectionPort.ToString(),
serverInfo.RespondedToSteamQuery = responsive; PlayerCount = info.Players,
MaxPlayers = info.MaxPlayers,
RespondedToSteamQuery = responsive
};
if (responsive) if (responsive)
{ {
@@ -344,11 +332,11 @@ namespace Barotrauma.Steam
{ {
if (t.Status == TaskStatus.Faulted) if (t.Status == TaskStatus.Faulted)
{ {
TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for "+info.Name); TaskPool.PrintTaskExceptions(t, "Failed to retrieve rules for " + info.Name);
return; return;
} }
var rules = ((Task<Dictionary<string,string>>)t).Result; var rules = ((Task<Dictionary<string, string>>)t).Result;
AssignServerRulesToServerInfo(rules, serverInfo); AssignServerRulesToServerInfo(rules, serverInfo);
CrossThread.RequestExecutionOnMainThread(() => CrossThread.RequestExecutionOnMainThread(() =>
@@ -365,7 +353,7 @@ namespace Barotrauma.Steam
}); });
} }
}; }
serverQuery.OnResponsiveServer += (info) => onServer(info, true); serverQuery.OnResponsiveServer += (info) => onServer(info, true);
serverQuery.OnUnresponsiveServer += (info) => onServer(info, false); serverQuery.OnUnresponsiveServer += (info) => onServer(info, false);
@@ -393,11 +381,20 @@ namespace Barotrauma.Steam
serverInfo.ContentPackageNames.AddRange(lobby.GetData("contentpackage").Split(',')); serverInfo.ContentPackageNames.AddRange(lobby.GetData("contentpackage").Split(','));
serverInfo.ContentPackageHashes.AddRange(lobby.GetData("contentpackagehash").Split(',')); serverInfo.ContentPackageHashes.AddRange(lobby.GetData("contentpackagehash").Split(','));
serverInfo.ContentPackageWorkshopUrls.AddRange(lobby.GetData("contentpackageurl").Split(','));
string workshopIdData = lobby.GetData("contentpackageid");
if (!string.IsNullOrEmpty(workshopIdData))
{
serverInfo.ContentPackageWorkshopIds.AddRange(ParseWorkshopIds(workshopIdData));
}
else
{
string[] workshopUrls = lobby.GetData("contentpackageurl").Split(',');
serverInfo.ContentPackageWorkshopIds.AddRange(WorkshopUrlsToIds(workshopUrls));
}
serverInfo.UsingWhiteList = getLobbyBool("usingwhitelist"); serverInfo.UsingWhiteList = getLobbyBool("usingwhitelist");
SelectionMode selectionMode; if (Enum.TryParse(lobby.GetData("modeselectionmode"), out SelectionMode selectionMode)) { serverInfo.ModeSelectionMode = selectionMode; }
if (Enum.TryParse(lobby.GetData("modeselectionmode"), out selectionMode)) { serverInfo.ModeSelectionMode = selectionMode; }
if (Enum.TryParse(lobby.GetData("subselectionmode"), out selectionMode)) { serverInfo.SubSelectionMode = selectionMode; } if (Enum.TryParse(lobby.GetData("subselectionmode"), out selectionMode)) { serverInfo.SubSelectionMode = selectionMode; }
serverInfo.AllowSpectating = getLobbyBool("allowspectating"); serverInfo.AllowSpectating = getLobbyBool("allowspectating");
@@ -412,11 +409,12 @@ namespace Barotrauma.Steam
if (Enum.TryParse(lobby.GetData("playstyle"), out PlayStyle playStyle)) serverInfo.PlayStyle = playStyle; if (Enum.TryParse(lobby.GetData("playstyle"), out PlayStyle playStyle)) serverInfo.PlayStyle = playStyle;
if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count || if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count ||
serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopUrls.Count) serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopIds.Count)
{ {
//invalid contentpackage info //invalid contentpackage info
serverInfo.ContentPackageNames.Clear(); serverInfo.ContentPackageNames.Clear();
serverInfo.ContentPackageHashes.Clear(); serverInfo.ContentPackageHashes.Clear();
serverInfo.ContentPackageWorkshopIds.Clear();
} }
string pingLocation = lobby.GetData("pinglocation"); string pingLocation = lobby.GetData("pinglocation");
@@ -449,10 +447,18 @@ namespace Barotrauma.Steam
serverInfo.ContentPackageNames.Clear(); serverInfo.ContentPackageNames.Clear();
serverInfo.ContentPackageHashes.Clear(); serverInfo.ContentPackageHashes.Clear();
serverInfo.ContentPackageWorkshopUrls.Clear(); serverInfo.ContentPackageWorkshopIds.Clear();
if (rules.ContainsKey("contentpackage")) serverInfo.ContentPackageNames.AddRange(rules["contentpackage"].Split(',')); if (rules.ContainsKey("contentpackage")) serverInfo.ContentPackageNames.AddRange(rules["contentpackage"].Split(','));
if (rules.ContainsKey("contentpackagehash")) serverInfo.ContentPackageHashes.AddRange(rules["contentpackagehash"].Split(',')); if (rules.ContainsKey("contentpackagehash")) serverInfo.ContentPackageHashes.AddRange(rules["contentpackagehash"].Split(','));
if (rules.ContainsKey("contentpackageurl")) serverInfo.ContentPackageWorkshopUrls.AddRange(rules["contentpackageurl"].Split(',')); if (rules.ContainsKey("contentpackageid"))
{
serverInfo.ContentPackageWorkshopIds.AddRange(ParseWorkshopIds(rules["contentpackageid"]));
}
else if (rules.ContainsKey("contentpackageurl"))
{
string[] workshopUrls = rules["contentpackageurl"].Split(',');
serverInfo.ContentPackageWorkshopIds.AddRange(WorkshopUrlsToIds(workshopUrls));
}
if (rules.ContainsKey("usingwhitelist")) serverInfo.UsingWhiteList = rules["usingwhitelist"] == "True"; if (rules.ContainsKey("usingwhitelist")) serverInfo.UsingWhiteList = rules["usingwhitelist"] == "True";
if (rules.ContainsKey("modeselectionmode")) if (rules.ContainsKey("modeselectionmode"))
@@ -479,37 +485,16 @@ namespace Barotrauma.Steam
} }
if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count || if (serverInfo.ContentPackageNames.Count != serverInfo.ContentPackageHashes.Count ||
serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopUrls.Count) serverInfo.ContentPackageHashes.Count != serverInfo.ContentPackageWorkshopIds.Count)
{ {
//invalid contentpackage info //invalid contentpackage info
serverInfo.ContentPackageNames.Clear(); serverInfo.ContentPackageNames.Clear();
serverInfo.ContentPackageHashes.Clear(); serverInfo.ContentPackageHashes.Clear();
serverInfo.ContentPackageWorkshopIds.Clear();
} }
} }
public static ulong GetWorkshopItemIDFromUrl(string url) #region Connecting to servers
{
try
{
Uri uri = new Uri(url);
string idStr = HttpUtility.ParseQueryString(uri.Query).Get("id");
if (ulong.TryParse(idStr, out ulong id))
{
return id;
}
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to get Workshop item ID from the url \"" + url + "\"!", e);
}
return 0;
}
#region Connecting to servers
//TODO: reimplement server list queries
private static Steamworks.AuthTicket currentTicket = null; private static Steamworks.AuthTicket currentTicket = null;
public static Steamworks.AuthTicket GetAuthSessionTicket() public static Steamworks.AuthTicket GetAuthSessionTicket()
{ {
@@ -545,9 +530,9 @@ namespace Barotrauma.Steam
Steamworks.SteamUser.EndAuthSession(clientSteamID); Steamworks.SteamUser.EndAuthSession(clientSteamID);
} }
#endregion #endregion
#region Workshop #region Workshop
public const string WorkshopItemPreviewImageFolder = "Workshop"; public const string WorkshopItemPreviewImageFolder = "Workshop";
public const string PreviewImageName = "PreviewImage.png"; public const string PreviewImageName = "PreviewImage.png";
@@ -620,7 +605,8 @@ namespace Barotrauma.Steam
.WithLongDescription(); .WithLongDescription();
if (requireTags != null) query.WithTags(requireTags); if (requireTags != null) query.WithTags(requireTags);
TaskPool.Add("GetPopularWorkshopItems", GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) => { TaskPool.Add("GetPopularWorkshopItems", GetWorkshopItemsAsync(query, amount, (item) => !item.IsSubscribed), (task) =>
{
var entries = ((Task<List<Steamworks.Ugc.Item>>)task).Result; var entries = ((Task<List<Steamworks.Ugc.Item>>)task).Result;
//count the number of each unique tag //count the number of each unique tag
@@ -669,47 +655,82 @@ namespace Barotrauma.Steam
TaskPool.Add("GetPublishedWorkshopItems", GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(((Task<List<Steamworks.Ugc.Item>>)task).Result); }); TaskPool.Add("GetPublishedWorkshopItems", GetWorkshopItemsAsync(query), (task) => { onItemsFound?.Invoke(((Task<List<Steamworks.Ugc.Item>>)task).Result); });
} }
private static Dictionary<ulong, Task> ugcSubscriptionTasks; private static readonly HashSet<ulong> pendingWorkshopSubscriptions = new HashSet<ulong>();
public static void SubscribeToWorkshopItem(string itemUrl) public static void SubscribeToWorkshopItem(ulong id, Action onInstalled = null)
{
if (!isInitialized) return;
ulong id = GetWorkshopItemIDFromUrl(itemUrl);
SubscribeToWorkshopItem(id);
}
public static void SubscribeToWorkshopItem(ulong id)
{ {
if (!isInitialized) return; if (!isInitialized) return;
if (id == 0) { return; } if (id == 0) { return; }
if (ugcSubscriptionTasks?.ContainsKey(id) ?? false) { return; } if (pendingWorkshopSubscriptions.Contains(id)) { return; }
ugcSubscriptionTasks ??= new Dictionary<ulong, Task>(); pendingWorkshopSubscriptions.Add(id);
ugcSubscriptionTasks.Add(id, Task.Run(async () => TaskPool.Add(
{ $"SubscribeToWorkshopItem({id})",
Steamworks.Ugc.Item? item = await Steamworks.SteamUGC.QueryFileAsync(id); Task.Run(async () =>
{
Steamworks.Ugc.Item? item = await Steamworks.SteamUGC.QueryFileAsync(id);
if (!item.HasValue) if (!item.HasValue)
{ {
DebugConsole.ThrowError("Failed to find a Steam Workshop item with the ID " + id.ToString() + "."); DebugConsole.ThrowError($"Failed to find a Steam Workshop item with the ID {id}.");
return; return null;
} }
bool subscribed = await item?.Subscribe(); if (!(item?.IsSubscribed ?? false))
if (!subscribed) {
bool subscribed = await item?.Subscribe();
if (!subscribed)
{
DebugConsole.ThrowError($"Failed to subscribe to Steam Workshop item with the ID {id}.");
return null;
}
}
return item;
}),
(t) =>
{ {
DebugConsole.ThrowError("Failed to subscribe to Steam Workshop item with the ID " + id.ToString() + "."); bool shouldCleanup = true;
} if (t.IsFaulted)
bool downloading = item?.Download() ?? false; {
if (!downloading) TaskPool.PrintTaskExceptions(t, $"Workshop subscription task {id} faulted");
{ }
DebugConsole.ThrowError("Failed to start downloading Steam Workshop item with the ID " + id.ToString() + "."); else
} {
})); var item = ((Task<Steamworks.Ugc.Item?>)t).Result;
if (item != null)
{
if (item?.IsInstalled ?? false)
{
onInstalled?.Invoke();
}
else
{
void _onInstalled()
{
onInstalled?.Invoke();
pendingWorkshopSubscriptions.Remove(id);
}
bool downloading = item?.Download(_onInstalled) ?? false;
if (!downloading)
{
DebugConsole.ThrowError($"Failed to start downloading Steam Workshop item with the ID {id}.");
}
else
{
shouldCleanup = false;
}
}
}
if (shouldCleanup)
{
pendingWorkshopSubscriptions.Remove(id);
}
}
});
} }
public static void CreateWorkshopItemStaging(ContentPackage contentPackage, out Steamworks.Ugc.Editor? itemEditor) public static void CreateWorkshopItemStaging(ContentPackage contentPackage, out Steamworks.Ugc.Editor? itemEditor)
@@ -790,9 +811,9 @@ namespace Barotrauma.Steam
itemEditor = itemEditor?.WithPrivateVisibility(); itemEditor = itemEditor?.WithPrivateVisibility();
} }
if (!CheckWorkshopItemEnabled(existingItem)) if (!CheckWorkshopItemInstalled(existingItem))
{ {
if (!EnableWorkShopItem(existingItem, out string errorMsg)) if (!InstallWorkshopItem(existingItem, out string errorMsg))
{ {
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
new GUIMessageBox( new GUIMessageBox(
@@ -804,9 +825,9 @@ namespace Barotrauma.Steam
} }
} }
ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem?.Directory, MetadataFileName)) { SteamWorkshopUrl = existingItem.Value.Url }; ContentPackage tempContentPackage = new ContentPackage(Path.Combine(existingItem?.Directory, MetadataFileName)) { SteamWorkshopId = existingItem.Value.Id };
string installedContentPackagePath = Path.GetFullPath(GetWorkshopItemContentPackagePath(tempContentPackage)); string installedContentPackagePath = Path.GetFullPath(GetWorkshopItemContentPackagePath(tempContentPackage));
contentPackage = ContentPackage.List.Find(cp => Path.GetFullPath(cp.Path) == installedContentPackagePath); contentPackage = ContentPackage.AllPackages.FirstOrDefault(cp => Path.GetFullPath(cp.Path) == installedContentPackagePath);
itemEditor = itemEditor?.WithContent(Path.GetDirectoryName(installedContentPackagePath)); itemEditor = itemEditor?.WithContent(Path.GetDirectoryName(installedContentPackagePath));
@@ -923,7 +944,7 @@ namespace Barotrauma.Steam
workshopPublishStatus.Result = task.Result; workshopPublishStatus.Result = task.Result;
DebugConsole.NewMessage("Published workshop item " + item?.Title + " successfully.", Microsoft.Xna.Framework.Color.LightGreen); DebugConsole.NewMessage("Published workshop item " + item?.Title + " successfully.", Microsoft.Xna.Framework.Color.LightGreen);
contentPackage.SteamWorkshopUrl = $"http://steamcommunity.com/sharedfiles/filedetails/?source=Facepunch.Steamworks&id={task.Result.FileId.Value}"; contentPackage.SteamWorkshopId = task.Result.FileId.Value;
//NOTE: This sets InstallTime one hour into the future to guarantee //NOTE: This sets InstallTime one hour into the future to guarantee
//that the published content package won't be autoupdated incorrectly. //that the published content package won't be autoupdated incorrectly.
//Change if it causes issues. //Change if it causes issues.
@@ -937,9 +958,9 @@ namespace Barotrauma.Steam
} }
/// <summary> /// <summary>
/// Enables a workshop item by moving it to the game folder. /// Installs a workshop item by moving it to the game folder.
/// </summary> /// </summary>
public static bool EnableWorkShopItem(Steamworks.Ugc.Item? item, out string errorMsg, bool selectContentPackage = false, bool suppressInstallNotif = false) public static bool InstallWorkshopItem(Steamworks.Ugc.Item? item, out string errorMsg, bool enableContentPackage = false, bool suppressInstallNotif = false)
{ {
if (!(item?.IsInstalled ?? false)) if (!(item?.IsInstalled ?? false))
{ {
@@ -959,11 +980,11 @@ namespace Barotrauma.Steam
ContentPackage contentPackage = new ContentPackage(metaDataFilePath) ContentPackage contentPackage = new ContentPackage(metaDataFilePath)
{ {
SteamWorkshopUrl = item?.Url SteamWorkshopId = item?.Id ?? 0
}; };
string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage); string newContentPackagePath = GetWorkshopItemContentPackagePath(contentPackage);
List<ContentPackage> existingPackages = ContentPackage.List.Where(cp => cp.Path.CleanUpPath() == newContentPackagePath.CleanUpPath()).ToList(); List<ContentPackage> existingPackages = ContentPackage.AllPackages.Where(cp => cp.Path.CleanUpPath() == newContentPackagePath.CleanUpPath()).ToList();
if (existingPackages.Any()) if (existingPackages.Any())
{ {
if (item?.Owner.Id != Steamworks.SteamClient.SteamId) if (item?.Owner.Id != Steamworks.SteamClient.SteamId)
@@ -975,7 +996,7 @@ namespace Barotrauma.Steam
} }
else else
{ {
RemoveMods(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.SteamWorkshopUrl == contentPackage.SteamWorkshopUrl, RemoveMods(cp => cp.SteamWorkshopId != 0 && cp.SteamWorkshopId == contentPackage.SteamWorkshopId,
false); false);
} }
} }
@@ -1024,7 +1045,7 @@ namespace Barotrauma.Steam
var newPackage = new ContentPackage(cp.Path, newContentPackagePath) var newPackage = new ContentPackage(cp.Path, newContentPackagePath)
{ {
SteamWorkshopUrl = item?.Url, SteamWorkshopId = item?.Id ?? 0,
InstallTime = item?.Updated > item?.Created ? item?.Updated : item?.Created InstallTime = item?.Updated > item?.Created ? item?.Updated : item?.Created
}; };
@@ -1045,17 +1066,17 @@ namespace Barotrauma.Steam
Directory.CreateDirectory(Path.GetDirectoryName(newContentPackagePath)); Directory.CreateDirectory(Path.GetDirectoryName(newContentPackagePath));
} }
newPackage.Save(newContentPackagePath); newPackage.Save(newContentPackagePath);
ContentPackage.List.Add(newPackage); ContentPackage.AddPackage(newPackage);
if (selectContentPackage) if (enableContentPackage)
{ {
if (newPackage.CorePackage) if (newPackage.IsCorePackage)
{ {
GameMain.Config.SelectCorePackage(newPackage); GameMain.Config.SelectCorePackage(newPackage);
} }
else else
{ {
GameMain.Config.SelectContentPackage(newPackage); GameMain.Config.EnableRegularPackage(newPackage);
} }
GameMain.Config.SaveNewPlayerConfig(); GameMain.Config.SaveNewPlayerConfig();
@@ -1213,40 +1234,21 @@ namespace Barotrauma.Steam
return ""; return "";
} }
private static bool CheckFileEquality(string filePath1, string filePath2)
{
if (filePath1 == filePath2)
{
return true;
}
using (FileStream fs1 = File.OpenRead(filePath1))
using (FileStream fs2 = File.OpenRead(filePath2))
{
Md5Hash hash1 = new Md5Hash(fs1);
Md5Hash hash2 = new Md5Hash(fs2);
return hash1.Hash == hash2.Hash;
}
}
private static void RemoveMods(Func<ContentPackage, bool> predicate, bool delete = true) private static void RemoveMods(Func<ContentPackage, bool> predicate, bool delete = true)
{ {
var toRemove = ContentPackage.List.Where(predicate).ToList(); var toRemoveCore = ContentPackage.CorePackages.Where(predicate).ToList();
var packagesToDeselect = GameMain.Config.SelectedContentPackages.Where(p => toRemove.Contains(p)).ToList(); if (toRemoveCore.Contains(GameMain.Config.CurrentCorePackage)) { GameMain.Config.AutoSelectCorePackage(toRemoveCore); }
var toRemoveRegular = ContentPackage.RegularPackages.Where(predicate).ToList();
var packagesToDeselect = GameMain.Config.EnabledRegularPackages.Where(p => toRemoveRegular.Contains(p)).ToList();
foreach (var cp in packagesToDeselect) foreach (var cp in packagesToDeselect)
{ {
if (cp.CorePackage) GameMain.Config.DisableRegularPackage(cp);
{
GameMain.Config.AutoSelectCorePackage(toRemove);
}
else
{
GameMain.Config.DeselectContentPackage(cp);
}
} }
if (delete) if (delete)
{ {
var toRemove = toRemoveCore.Concat(toRemoveRegular);
foreach (var cp in toRemove) foreach (var cp in toRemove)
{ {
try try
@@ -1258,22 +1260,19 @@ namespace Barotrauma.Steam
{ {
DebugConsole.ThrowError($"An error occurred while attempting to delete {Path.GetDirectoryName(cp.Path)}", e); DebugConsole.ThrowError($"An error occurred while attempting to delete {Path.GetDirectoryName(cp.Path)}", e);
} }
ContentPackage.RemovePackage(cp);
} }
} }
ContentPackage.List.RemoveAll(cp => toRemove.Contains(cp));
GameMain.Config.SelectedContentPackages.RemoveAll(cp => !ContentPackage.List.Contains(cp));
ContentPackage.SortContentPackages();
GameMain.Config.SaveNewPlayerConfig(); GameMain.Config.SaveNewPlayerConfig();
GameMain.Config.WarnIfContentPackageSelectionDirty(); GameMain.Config.WarnIfContentPackageSelectionDirty();
} }
/// <summary> /// <summary>
/// Disables a workshop item by removing the files from the game folder. /// Uninstalls a workshop item by removing the files from the game folder.
/// </summary> /// </summary>
public static bool DisableWorkShopItem(Steamworks.Ugc.Item? item, bool noLog, out string errorMsg) public static bool UninstallWorkshopItem(Steamworks.Ugc.Item? item, bool noLog, out string errorMsg)
{ {
errorMsg = null; errorMsg = null;
if (!(item?.IsInstalled ?? false)) if (!(item?.IsInstalled ?? false))
@@ -1288,13 +1287,13 @@ namespace Barotrauma.Steam
ContentPackage contentPackage = new ContentPackage(Path.Combine(item?.Directory, MetadataFileName)) ContentPackage contentPackage = new ContentPackage(Path.Combine(item?.Directory, MetadataFileName))
{ {
SteamWorkshopUrl = item?.Url SteamWorkshopId = item?.Id ?? 0
}; };
GameMain.Config.SuppressModFolderWatcher = true; GameMain.Config.SuppressModFolderWatcher = true;
try try
{ {
RemoveMods(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.SteamWorkshopUrl == contentPackage.SteamWorkshopUrl); RemoveMods(cp => cp.SteamWorkshopId != 0 && cp.SteamWorkshopId == contentPackage.SteamWorkshopId);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -1330,7 +1329,7 @@ namespace Barotrauma.Steam
return contentPackage.IsCompatible(); return contentPackage.IsCompatible();
} }
public static bool CheckWorkshopItemEnabled(Steamworks.Ugc.Item? item) public static bool CheckWorkshopItemInstalled(Steamworks.Ugc.Item? item)
{ {
if (!(item?.IsInstalled ?? false)) { return false; } if (!(item?.IsInstalled ?? false)) { return false; }
@@ -1359,7 +1358,7 @@ namespace Barotrauma.Steam
string errorMessage = "Metadata file for the Workshop item \"" + item?.Title + string errorMessage = "Metadata file for the Workshop item \"" + item?.Title +
"\" not found. Could not combine path (" + (item?.Directory ?? "directory name empty") + ")."; "\" not found. Could not combine path (" + (item?.Directory ?? "directory name empty") + ").";
DebugConsole.ThrowError(errorMessage); DebugConsole.ThrowError(errorMessage);
GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemEnabled:PathCombineException" + item?.Title, GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemInstalled:PathCombineException" + item?.Title,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMessage); errorMessage);
return false; return false;
@@ -1373,12 +1372,12 @@ namespace Barotrauma.Steam
ContentPackage contentPackage = new ContentPackage(metaDataPath) ContentPackage contentPackage = new ContentPackage(metaDataPath)
{ {
SteamWorkshopUrl = item?.Url SteamWorkshopId = item?.Id ?? 0
}; };
//make sure the contentpackage file is present //make sure the contentpackage file is present
if (!File.Exists(GetWorkshopItemContentPackagePath(contentPackage)) || if (!File.Exists(GetWorkshopItemContentPackagePath(contentPackage)) ||
!ContentPackage.List.Any(cp => cp.SteamWorkshopUrl == contentPackage.SteamWorkshopUrl || !ContentPackage.AllPackages.Any(cp => cp.SteamWorkshopId == contentPackage.SteamWorkshopId ||
(string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.Name == contentPackage.Name))) (cp.SteamWorkshopId == 0 && cp.Name == contentPackage.Name)))
{ {
return false; return false;
} }
@@ -1399,9 +1398,9 @@ namespace Barotrauma.Steam
ContentPackage steamPackage = new ContentPackage(metaDataPath) ContentPackage steamPackage = new ContentPackage(metaDataPath)
{ {
SteamWorkshopUrl = item?.Url SteamWorkshopId = item?.Id ?? 0
}; };
ContentPackage myPackage = ContentPackage.List.Find(cp => cp.SteamWorkshopUrl == steamPackage.SteamWorkshopUrl); ContentPackage myPackage = ContentPackage.AllPackages.FirstOrDefault(cp => cp.SteamWorkshopId == steamPackage.SteamWorkshopId);
if (myPackage?.InstallTime == null) if (myPackage?.InstallTime == null)
{ {
@@ -1427,7 +1426,7 @@ namespace Barotrauma.Steam
GameMain.Config.SuppressModFolderWatcher = true; GameMain.Config.SuppressModFolderWatcher = true;
//remove mods that the player is no longer subscribed to //remove mods that the player is no longer subscribed to
RemoveMods(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && !items.Any(it => it.Id == GetWorkshopItemIDFromUrl(cp.SteamWorkshopUrl))); RemoveMods(cp => cp.SteamWorkshopId != 0 && !items.Any(it => it.Id == cp.SteamWorkshopId));
GameMain.Config.SuppressModFolderWatcher = false; GameMain.Config.SuppressModFolderWatcher = false;
@@ -1441,9 +1440,9 @@ namespace Barotrauma.Steam
bool installedSuccessfully = false; bool installedSuccessfully = false;
string errorMsg; string errorMsg;
if (!CheckWorkshopItemEnabled(item)) if (!CheckWorkshopItemInstalled(item))
{ {
installedSuccessfully = EnableWorkShopItem(item, out errorMsg); installedSuccessfully = InstallWorkshopItem(item, out errorMsg);
} }
else if (!CheckWorkshopItemUpToDate(item)) else if (!CheckWorkshopItemUpToDate(item))
{ {
@@ -1529,12 +1528,12 @@ namespace Barotrauma.Steam
{ {
errorMsg = ""; errorMsg = "";
if (!(item?.IsInstalled ?? false)) { return false; } if (!(item?.IsInstalled ?? false)) { return false; }
bool reenable = GameMain.Config.SelectedContentPackages.Any(p => !string.IsNullOrEmpty(p.SteamWorkshopUrl) && GetWorkshopItemIDFromUrl(p.SteamWorkshopUrl) == item?.Id); bool reenable = GameMain.Config.AllEnabledPackages.Any(p => p.SteamWorkshopId != 0 && p.SteamWorkshopId == item?.Id);
if (item?.Owner.Id != Steamworks.SteamClient.SteamId) if (item?.Owner.Id != Steamworks.SteamClient.SteamId)
{ {
if (!DisableWorkShopItem(item, false, out errorMsg)) { return false; } if (!UninstallWorkshopItem(item, false, out errorMsg)) { return false; }
} }
if (!EnableWorkShopItem(item, errorMsg: out errorMsg, selectContentPackage: reenable)) { return false; } if (!InstallWorkshopItem(item, errorMsg: out errorMsg, enableContentPackage: reenable)) { return false; }
return true; return true;
} }
@@ -1543,7 +1542,7 @@ namespace Barotrauma.Steam
string packageName = contentPackage.Name.Trim(); string packageName = contentPackage.Name.Trim();
packageName = ToolBox.RemoveInvalidFileNameChars(packageName); packageName = ToolBox.RemoveInvalidFileNameChars(packageName);
while (packageName.Last() == '.') { packageName = packageName.Substring(0, packageName.Length-1); } while (packageName.Last() == '.') { packageName = packageName.Substring(0, packageName.Length-1); }
//packageName = packageName + "_" + GetWorkshopItemIDFromUrl(contentPackage.SteamWorkshopUrl); //packageName = packageName + "_" + contentPackage.SteamWorkshopId.ToString();
return Path.Combine("Mods", packageName, MetadataFileName); return Path.Combine("Mods", packageName, MetadataFileName);
} }
@@ -1559,8 +1558,7 @@ namespace Barotrauma.Steam
attr.Name.ToString() == "characterfile") && attr.Name.ToString() == "characterfile") &&
attr.Value.CleanUpPath().Contains("/")) attr.Value.CleanUpPath().Contains("/"))
{ {
ContentType type = ContentType.None; Enum.TryParse(attr.Name.LocalName, true, out ContentType type);
Enum.TryParse(attr.Name.LocalName, true, out type);
attr.Value = CorrectContentFilePath(attr.Value, type, package, true); attr.Value = CorrectContentFilePath(attr.Value, type, package, true);
} }
} }
@@ -1615,8 +1613,7 @@ namespace Barotrauma.Steam
if (checkIfFileExists) if (checkIfFileExists)
{ {
bool exists = File.Exists(contentFilePath); bool exists = File.Exists(contentFilePath);
if (type == ContentType.Executable || if (type == ContentType.ServerExecutable)
type == ContentType.ServerExecutable)
{ {
exists |= File.Exists(Path.GetFileNameWithoutExtension(contentFilePath) + ".dll"); exists |= File.Exists(Path.GetFileNameWithoutExtension(contentFilePath) + ".dll");
} }
@@ -1634,7 +1631,7 @@ namespace Barotrauma.Steam
{ {
if (checkIfFileExists) if (checkIfFileExists)
{ {
ContentPackage otherContentPackage = ContentPackage.List.Find(cp => cp.Name.Equals(splitPath[1], StringComparison.OrdinalIgnoreCase)); ContentPackage otherContentPackage = ContentPackage.AllPackages.FirstOrDefault(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);
@@ -167,7 +167,7 @@ namespace Barotrauma.Networking
bool prevCaptured = true; bool prevCaptured = true;
int captureTimer; int captureTimer;
void UpdateCapture() private void UpdateCapture()
{ {
Array.Copy(uncompressedBuffer, 0, prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE); Array.Copy(uncompressedBuffer, 0, prevUncompressedBuffer, 0, VoipConfig.BUFFER_SIZE);
Array.Clear(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE); Array.Clear(uncompressedBuffer, 0, VoipConfig.BUFFER_SIZE);
@@ -273,6 +273,11 @@ namespace Barotrauma.Networking
if (allowEnqueue || captureTimer > 0) if (allowEnqueue || captureTimer > 0)
{ {
LastEnqueueAudio = DateTime.Now; LastEnqueueAudio = DateTime.Now;
if (GameMain.Client?.Character != null)
{
var messageType = !ForceLocal && ChatMessage.CanUseRadio(GameMain.Client.Character, out _) ? ChatMessageType.Radio : ChatMessageType.Default;
GameMain.Client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
}
//encode audio and enqueue it //encode audio and enqueue it
lock (buffers) lock (buffers)
{ {
@@ -91,13 +91,13 @@ namespace Barotrauma
if (userData == null) return; if (userData == null) return;
foreach (GUIComponent comp in listBox.Content.Children) foreach (GUIComponent comp in listBox.Content.Children)
{ {
if (comp.UserData != userData) continue; if (comp.UserData != userData) { continue; }
GUITextBlock voteText = comp.FindChild("votes") as GUITextBlock; if (!(comp.FindChild("votes") is GUITextBlock voteText))
if (voteText == null)
{ {
voteText = new GUITextBlock(new RectTransform(new Point(30, comp.Rect.Height), comp.RectTransform, Anchor.CenterRight), voteText = new GUITextBlock(new RectTransform(new Point(30, comp.Rect.Height), comp.RectTransform, Anchor.CenterRight),
"", textAlignment: Alignment.CenterRight) "", textAlignment: Alignment.CenterRight)
{ {
Padding = Vector4.Zero,
UserData = "votes" UserData = "votes"
}; };
} }
@@ -3,6 +3,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Reflection.Metadata;
namespace Barotrauma.Particles namespace Barotrauma.Particles
{ {
@@ -61,6 +62,8 @@ namespace Barotrauma.Particles
public bool HighQualityCollisionDetection; public bool HighQualityCollisionDetection;
public Vector4 ColorMultiplier;
public bool DrawOnTop { get; private set; } public bool DrawOnTop { get; private set; }
public ParticlePrefab.DrawTargetType DrawTarget public ParticlePrefab.DrawTargetType DrawTarget
@@ -141,6 +144,7 @@ namespace Barotrauma.Particles
color = prefab.StartColor; color = prefab.StartColor;
changeColor = prefab.StartColor != prefab.EndColor; changeColor = prefab.StartColor != prefab.EndColor;
ColorMultiplier = Vector4.One;
velocityChange = prefab.VelocityChangeDisplay; velocityChange = prefab.VelocityChangeDisplay;
velocityChangeWater = prefab.VelocityChangeWaterDisplay; velocityChangeWater = prefab.VelocityChangeWaterDisplay;
@@ -287,22 +291,42 @@ namespace Barotrauma.Particles
Vector2 collisionNormal = Vector2.Zero; Vector2 collisionNormal = Vector2.Zero;
if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height) if (velocity.Y < 0.0f && position.Y - prefab.CollisionRadius * size.Y < hullRect.Y - hullRect.Height)
{ {
if (prefab.DeleteOnCollision) return false; if (prefab.DeleteOnCollision) { return false; }
collisionNormal = new Vector2(0.0f, 1.0f); collisionNormal = new Vector2(0.0f, 1.0f);
} }
else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y) else if (velocity.Y > 0.0f && position.Y + prefab.CollisionRadius * size.Y > hullRect.Y)
{ {
if (prefab.DeleteOnCollision) return false; if (prefab.DeleteOnCollision) { return false; }
collisionNormal = new Vector2(0.0f, -1.0f); collisionNormal = new Vector2(0.0f, -1.0f);
} }
else if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
if (collisionNormal != Vector2.Zero)
{ {
if (prefab.DeleteOnCollision) return false; bool gapFound = false;
foreach (Gap gap in hullGaps)
{
if (gap.Open <= 0.9f || gap.IsHorizontal) { continue; }
if (gap.WorldRect.X > position.X || gap.WorldRect.Right < position.X) { continue; }
float hullCenterY = currentHull.WorldRect.Y - currentHull.WorldRect.Height / 2;
int gapDir = Math.Sign(gap.WorldRect.Y - hullCenterY);
if (Math.Sign(velocity.Y) != gapDir || Math.Sign(position.Y - hullCenterY) != gapDir) { continue; }
gapFound = true;
break;
}
handleCollision(gapFound, collisionNormal);
}
if (velocity.X < 0.0f && position.X - prefab.CollisionRadius * size.X < hullRect.X)
{
if (prefab.DeleteOnCollision) { return false; }
collisionNormal = new Vector2(1.0f, 0.0f); collisionNormal = new Vector2(1.0f, 0.0f);
} }
else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right) else if (velocity.X > 0.0f && position.X + prefab.CollisionRadius * size.X > hullRect.Right)
{ {
if (prefab.DeleteOnCollision) return false; if (prefab.DeleteOnCollision) { return false; }
collisionNormal = new Vector2(-1.0f, 0.0f); collisionNormal = new Vector2(-1.0f, 0.0f);
} }
@@ -311,26 +335,21 @@ namespace Barotrauma.Particles
bool gapFound = false; bool gapFound = false;
foreach (Gap gap in hullGaps) foreach (Gap gap in hullGaps)
{ {
if (gap.Open <= 0.9f || gap.IsHorizontal != (collisionNormal.X != 0.0f)) continue; if (gap.Open <= 0.9f || !gap.IsHorizontal) { continue; }
if (gap.IsHorizontal) if (gap.WorldRect.Y < position.Y || gap.WorldRect.Y - gap.WorldRect.Height > position.Y) { continue; }
{ int gapDir = Math.Sign(gap.WorldRect.Center.X - currentHull.WorldRect.Center.X);
if (gap.WorldRect.Y < position.Y || gap.WorldRect.Y - gap.WorldRect.Height > position.Y) continue; if (Math.Sign(velocity.X) != gapDir || Math.Sign(position.X - currentHull.WorldRect.Center.X) != gapDir) { continue; }
int gapDir = Math.Sign(gap.WorldRect.Center.X - currentHull.WorldRect.Center.X);
if (Math.Sign(velocity.X) != gapDir || Math.Sign(position.X - currentHull.WorldRect.Center.X) != gapDir) continue;
}
else
{
if (gap.WorldRect.X > position.X || gap.WorldRect.Right < position.X) continue;
float hullCenterY = currentHull.WorldRect.Y - currentHull.WorldRect.Height / 2;
int gapDir = Math.Sign(gap.WorldRect.Y - hullCenterY);
if (Math.Sign(velocity.Y) != gapDir || Math.Sign(position.Y - hullCenterY) != gapDir) continue;
}
gapFound = true; gapFound = true;
break; break;
} }
handleCollision(gapFound, collisionNormal);
}
void handleCollision(bool gapFound, Vector2 collisionNormal)
{
if (!gapFound) if (!gapFound)
{ {
OnWallCollisionInside(currentHull, collisionNormal); OnWallCollisionInside(currentHull, collisionNormal);
@@ -378,6 +397,8 @@ namespace Barotrauma.Particles
private void OnWallCollisionInside(Hull prevHull, Vector2 collisionNormal) private void OnWallCollisionInside(Hull prevHull, Vector2 collisionNormal)
{ {
if (prevHull == null) { return; }
Rectangle prevHullRect = prevHull.WorldRect; Rectangle prevHullRect = prevHull.WorldRect;
Vector2 subVel = prevHull?.Submarine != null ? ConvertUnits.ToDisplayUnits(prevHull.Submarine.Velocity) : Vector2.Zero; Vector2 subVel = prevHull?.Submarine != null ? ConvertUnits.ToDisplayUnits(prevHull.Submarine.Velocity) : Vector2.Zero;
@@ -465,12 +486,14 @@ namespace Barotrauma.Particles
drawSize *= ((totalLifeTime - lifeTime) / prefab.GrowTime); drawSize *= ((totalLifeTime - lifeTime) / prefab.GrowTime);
} }
Color currColor = new Color(color.ToVector4() * ColorMultiplier);
if (prefab.Sprites[spriteIndex] is SpriteSheet) if (prefab.Sprites[spriteIndex] is SpriteSheet)
{ {
((SpriteSheet)prefab.Sprites[spriteIndex]).Draw( ((SpriteSheet)prefab.Sprites[spriteIndex]).Draw(
spriteBatch, animFrame, spriteBatch, animFrame,
new Vector2(drawPosition.X, -drawPosition.Y), new Vector2(drawPosition.X, -drawPosition.Y),
color * (color.A / 255.0f), currColor * (currColor.A / 255.0f),
prefab.Sprites[spriteIndex].Origin, drawRotation, prefab.Sprites[spriteIndex].Origin, drawRotation,
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth); drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
} }
@@ -478,7 +501,7 @@ namespace Barotrauma.Particles
{ {
prefab.Sprites[spriteIndex].Draw(spriteBatch, prefab.Sprites[spriteIndex].Draw(spriteBatch,
new Vector2(drawPosition.X, -drawPosition.Y), new Vector2(drawPosition.X, -drawPosition.Y),
color * (color.A / 255.0f), currColor * (currColor.A / 255.0f),
prefab.Sprites[spriteIndex].Origin, drawRotation, prefab.Sprites[spriteIndex].Origin, drawRotation,
drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth); drawSize, SpriteEffects.None, prefab.Sprites[spriteIndex].Depth);
} }
@@ -23,7 +23,7 @@ namespace Barotrauma.Particles
Prefab = prefab; Prefab = prefab;
} }
public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f) public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null)
{ {
emitTimer += deltaTime * amountMultiplier; emitTimer += deltaTime * amountMultiplier;
burstEmitTimer -= deltaTime; burstEmitTimer -= deltaTime;
@@ -33,7 +33,7 @@ namespace Barotrauma.Particles
float emitInterval = 1.0f / Prefab.ParticlesPerSecond; float emitInterval = 1.0f / Prefab.ParticlesPerSecond;
while (emitTimer > emitInterval) while (emitTimer > emitInterval)
{ {
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier); Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier);
emitTimer -= emitInterval; emitTimer -= emitInterval;
} }
} }
@@ -43,11 +43,11 @@ namespace Barotrauma.Particles
burstEmitTimer = Prefab.EmitInterval; burstEmitTimer = Prefab.EmitInterval;
for (int i = 0; i < Prefab.ParticleAmount * amountMultiplier; i++) for (int i = 0; i < Prefab.ParticleAmount * amountMultiplier; i++)
{ {
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier); Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier);
} }
} }
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier) private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null)
{ {
angle += Rand.Range(Prefab.AngleMin, Prefab.AngleMax); angle += Rand.Range(Prefab.AngleMin, Prefab.AngleMax);
@@ -61,6 +61,7 @@ namespace Barotrauma.Particles
{ {
particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax) * sizeMultiplier; particle.Size *= Rand.Range(Prefab.ScaleMin, Prefab.ScaleMax) * sizeMultiplier;
particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection; particle.HighQualityCollisionDetection = Prefab.HighQualityCollisionDetection;
if (colorMultiplier.HasValue) { particle.ColorMultiplier = colorMultiplier.Value.ToVector4(); }
} }
} }
@@ -157,9 +157,9 @@ namespace Barotrauma
sb.AppendLine("VSync " + (GameMain.Config.VSyncEnabled ? "ON" : "OFF")); sb.AppendLine("VSync " + (GameMain.Config.VSyncEnabled ? "ON" : "OFF"));
sb.AppendLine("Language: " + (GameMain.Config.Language ?? "none")); sb.AppendLine("Language: " + (GameMain.Config.Language ?? "none"));
} }
if (GameMain.SelectedPackages != null) if (GameMain.Config.AllEnabledPackages != null)
{ {
sb.AppendLine("Selected content packages: " + (!GameMain.SelectedPackages.Any() ? "None" : string.Join(", ", GameMain.SelectedPackages.Select(c => c.Name)))); sb.AppendLine("Selected content packages: " + (!GameMain.Config.AllEnabledPackages.Any() ? "None" : string.Join(", ", GameMain.Config.AllEnabledPackages.Select(c => c.Name))));
} }
sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed)); sb.AppendLine("Level seed: " + ((Level.Loaded == null) ? "no level loaded" : Level.Loaded.Seed));
sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")")); sb.AppendLine("Loaded submarine: " + ((Submarine.MainSub == null) ? "None" : Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash + ")"));
@@ -26,6 +26,9 @@ namespace Barotrauma
public Action<SubmarineInfo, string, string> StartNewGame; public Action<SubmarineInfo, string, string> StartNewGame;
public Action<string> LoadGame; public Action<string> LoadGame;
private enum CategoryFilter { All = 0, Vanilla = 1, Custom = 2 };
private CategoryFilter subFilter = CategoryFilter.All;
public GUIButton StartButton public GUIButton StartButton
{ {
get; get;
@@ -74,6 +77,12 @@ namespace Barotrauma
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SelectedSub"), font: GUI.SubHeadingFont);
var moddedDropdown = new GUIDropDown(new RectTransform(new Vector2(1f, 0.02f), leftColumn.RectTransform), "", 3);
moddedDropdown.AddItem(TextManager.Get("clientpermission.all"), CategoryFilter.All);
moddedDropdown.AddItem(TextManager.Get("servertag.modded.false"), CategoryFilter.Vanilla);
moddedDropdown.AddItem(TextManager.Get("customrank"), CategoryFilter.Custom);
moddedDropdown.Select(0);
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true) var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform), isHorizontal: true)
{ {
Stretch = true Stretch = true
@@ -88,6 +97,14 @@ namespace Barotrauma
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; }; searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; }; searchBox.OnTextChanged += (textBox, text) => { FilterSubs(subList, text); return true; };
moddedDropdown.OnSelected = (component, data) =>
{
searchBox.Text = string.Empty;
subFilter = (CategoryFilter)data;
UpdateSubList(SubmarineInfo.SavedSubmarines);
return true;
};
subList.OnSelected = OnSubSelected; subList.OnSelected = OnSubSelected;
} }
else // Spacing to fix the multiplayer campaign setup layout else // Spacing to fix the multiplayer campaign setup layout
@@ -234,7 +251,7 @@ namespace Barotrauma
Stretch = true Stretch = true
}; };
var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Language == "English" ? "Purchasable submarines" : TextManager.Get("workshoplabelsubmarines"), font: GUI.SubHeadingFont); var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont);
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true) var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true)
{ {
@@ -395,7 +412,16 @@ namespace Barotrauma
public void UpdateSubList(IEnumerable<SubmarineInfo> submarines) public void UpdateSubList(IEnumerable<SubmarineInfo> submarines)
{ {
var subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass).ToList(); List<SubmarineInfo> subsToShow;
if (!isMultiplayer && subFilter != CategoryFilter.All)
{
subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && s.IsVanillaSubmarine() == (subFilter == CategoryFilter.Vanilla)).ToList();
}
else
{
subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass).ToList();
}
subsToShow.Sort((s1, s2) => subsToShow.Sort((s1, s2) =>
{ {
int p1 = s1.Price > CampaignMode.MaxInitialSubmarinePrice ? 10 : 0; int p1 = s1.Price > CampaignMode.MaxInitialSubmarinePrice ? 10 : 0;
@@ -428,18 +454,22 @@ namespace Barotrauma
ToolTip = textBlock.ToolTip ToolTip = textBlock.ToolTip
}; };
#if !DEBUG #if !DEBUG
if (sub.Price > CampaignMode.MaxInitialSubmarinePrice && !GameMain.DebugDraw) if (!GameMain.DebugDraw)
{ {
textBlock.CanBeFocused = false; if (sub.Price > CampaignMode.MaxInitialSubmarinePrice || !sub.IsCampaignCompatible)
{
textBlock.CanBeFocused = false;
textBlock.TextColor *= 0.5f;
}
} }
#endif #endif
} }
if (SubmarineInfo.SavedSubmarines.Any()) if (SubmarineInfo.SavedSubmarines.Any())
{ {
var nonShuttles = subsToShow.Where(s => s.Type == SubmarineType.Player && !s.HasTag(SubmarineTag.Shuttle) && s.Price <= CampaignMode.MaxInitialSubmarinePrice).ToList(); var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignMode.MaxInitialSubmarinePrice).ToList();
if (nonShuttles.Count > 0) if (validSubs.Count > 0)
{ {
subList.Select(nonShuttles[Rand.Int(nonShuttles.Count)]); subList.Select(validSubs[Rand.Int(validSubs.Count)]);
} }
} }
} }
@@ -448,6 +478,7 @@ namespace Barotrauma
public void UpdateLoadMenu(IEnumerable<string> saveFiles = null) public void UpdateLoadMenu(IEnumerable<string> saveFiles = null)
{ {
prevSaveFiles?.Clear(); prevSaveFiles?.Clear();
prevSaveFiles = null;
loadGameContainer.ClearChildren(); loadGameContainer.ClearChildren();
if (saveFiles == null) if (saveFiles == null)
@@ -504,6 +535,7 @@ namespace Barotrauma
}; };
bool isCompatible = true; bool isCompatible = true;
prevSaveFiles ??= new List<string>();
if (!isMultiplayer) if (!isMultiplayer)
{ {
nameText.Text = Path.GetFileNameWithoutExtension(saveFile); nameText.Text = Path.GetFileNameWithoutExtension(saveFile);
@@ -529,10 +561,10 @@ namespace Barotrauma
} }
else else
{ {
prevSaveFiles?.Add(saveFile);
string[] splitSaveFile = saveFile.Split(';'); string[] splitSaveFile = saveFile.Split(';');
saveFrame.UserData = splitSaveFile[0]; saveFrame.UserData = splitSaveFile[0];
fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]); fileName = nameText.Text = Path.GetFileNameWithoutExtension(splitSaveFile[0]);
prevSaveFiles?.Add(fileName);
if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; } if (splitSaveFile.Length > 1) { subName = splitSaveFile[1]; }
if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; } if (splitSaveFile.Length > 2) { saveTime = splitSaveFile[2]; }
if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; } if (splitSaveFile.Length > 3) { contentPackageStr = splitSaveFile[3]; }
@@ -545,7 +577,7 @@ namespace Barotrauma
if (!string.IsNullOrEmpty(contentPackageStr)) if (!string.IsNullOrEmpty(contentPackageStr))
{ {
List<string> contentPackagePaths = contentPackageStr.Split('|').ToList(); List<string> contentPackagePaths = contentPackageStr.Split('|').ToList();
if (!GameSession.IsCompatibleWithSelectedContentPackages(contentPackagePaths, out string errorMsg)) if (!GameSession.IsCompatibleWithEnabledContentPackages(contentPackagePaths, out string errorMsg))
{ {
nameText.TextColor = GUI.Style.Red; nameText.TextColor = GUI.Style.Red;
saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning")); saveFrame.ToolTip = string.Join("\n", errorMsg, TextManager.Get("campaignmode.contentpackagemismatchwarning"));
@@ -696,9 +728,16 @@ namespace Barotrauma
string saveFile = obj as string; string saveFile = obj as string;
if (obj == null) { return false; } if (obj == null) { return false; }
SaveUtil.DeleteSave(saveFile); string header = TextManager.Get("deletedialoglabel");
prevSaveFiles?.Remove(saveFile); string body = TextManager.GetWithVariable("deletedialogquestion", "[file]", Path.GetFileNameWithoutExtension(saveFile));
UpdateLoadMenu(prevSaveFiles);
EventEditorScreen.AskForConfirmation(header, body, () =>
{
SaveUtil.DeleteSave(saveFile);
prevSaveFiles?.RemoveAll(s => s.StartsWith(saveFile));
UpdateLoadMenu(prevSaveFiles.ToList());
return true;
});
return true; return true;
} }
@@ -497,7 +497,7 @@ namespace Barotrauma
}; };
if (Level.Loaded != null && if (Level.Loaded != null &&
connection.LevelData == Level.Loaded.LevelData && connection?.LevelData == Level.Loaded.LevelData &&
currentDisplayLocation == Campaign.Map?.CurrentLocation) currentDisplayLocation == Campaign.Map?.CurrentLocation)
{ {
StartButton.Visible = false; StartButton.Visible = false;
@@ -1655,9 +1655,9 @@ namespace Barotrauma.CharacterEditor
if (contentPackage == null) if (contentPackage == null)
{ {
#if DEBUG #if DEBUG
contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault(); contentPackage = GameMain.Config.AllEnabledPackages.LastOrDefault();
#else #else
contentPackage = GameMain.Config.SelectedContentPackages.LastOrDefault(cp => cp != vanilla); contentPackage = GameMain.Config.AllEnabledPackages.LastOrDefault(cp => cp != vanilla);
#endif #endif
} }
if (contentPackage == null) if (contentPackage == null)
@@ -1674,9 +1674,9 @@ namespace Barotrauma.CharacterEditor
} }
#endif #endif
// Content package // Content package
if (!GameMain.Config.SelectedContentPackages.Contains(contentPackage)) if (!GameMain.Config.AllEnabledPackages.Contains(contentPackage))
{ {
GameMain.Config.SelectContentPackage(contentPackage); GameMain.Config.EnableRegularPackage(contentPackage);
} }
GameMain.Config.SaveNewPlayerConfig(); GameMain.Config.SaveNewPlayerConfig();
@@ -1757,9 +1757,10 @@ namespace Barotrauma.CharacterEditor
#endif #endif
// Add to the selected content package // Add to the selected content package
contentPackage.AddFile(configFilePath, ContentType.Character); contentPackage.AddFile(configFilePath, ContentType.Character);
Barotrauma.IO.Validation.DevException = true;
contentPackage.Save(contentPackage.Path); contentPackage.Save(contentPackage.Path);
Barotrauma.IO.Validation.DevException = false;
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path)); DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
CharacterPrefab.LoadFromFile(configFilePath, contentPackage, forceOverride: true);
// Ragdoll // Ragdoll
RagdollParams.ClearCache(); RagdollParams.ClearCache();
@@ -1783,7 +1784,11 @@ namespace Barotrauma.CharacterEditor
element.SetAttributeValue("type", name); element.SetAttributeValue("type", name);
string fullPath = AnimationParams.GetDefaultFile(name, animation.AnimationType, contentPackage); string fullPath = AnimationParams.GetDefaultFile(name, animation.AnimationType, contentPackage);
element.Name = AnimationParams.GetDefaultFileName(name, animation.AnimationType); element.Name = AnimationParams.GetDefaultFileName(name, animation.AnimationType);
#if DEBUG
element.Save(fullPath); element.Save(fullPath);
#else
element.SaveSafe(fullPath);
#endif
} }
} }
else else
@@ -305,7 +305,7 @@ namespace Barotrauma.CharacterEditor
new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), TextManager.Get("ContentPackage")); new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), mainElement.RectTransform, Anchor.CenterLeft), TextManager.Get("ContentPackage"));
var rightContainer = new GUIFrame(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), style: null); var rightContainer = new GUIFrame(new RectTransform(new Vector2(0.7f, 1), mainElement.RectTransform, Anchor.CenterRight), style: null);
contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight)); contentPackageDropDown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.5f), rightContainer.RectTransform, Anchor.TopRight));
foreach (ContentPackage cp in ContentPackage.List) foreach (ContentPackage cp in ContentPackage.AllPackages)
{ {
#if !DEBUG #if !DEBUG
if (cp == GameMain.VanillaContent) { continue; } if (cp == GameMain.VanillaContent) { continue; }
@@ -334,15 +334,15 @@ namespace Barotrauma.CharacterEditor
contentPackageNameElement.Flash(); contentPackageNameElement.Flash();
return false; return false;
} }
if (ContentPackage.List.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower())) if (ContentPackage.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
{ {
new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", fallBackTag: "leveleditorlevelobjnametaken")); new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", fallBackTag: "leveleditorlevelobjnametaken"));
return false; return false;
} }
string modName = ToolBox.RemoveInvalidFileNameChars(contentPackageNameElement.Text); string modName = ToolBox.RemoveInvalidFileNameChars(contentPackageNameElement.Text);
ContentPackage = ContentPackage.CreatePackage(contentPackageNameElement.Text, Path.Combine("Mods", modName, Steam.SteamManager.MetadataFileName), false); ContentPackage = ContentPackage.CreatePackage(contentPackageNameElement.Text, Path.Combine("Mods", modName, Steam.SteamManager.MetadataFileName), false);
ContentPackage.List.Add(ContentPackage); ContentPackage.AddPackage(ContentPackage);
GameMain.Config.SelectContentPackage(ContentPackage); GameMain.Config.EnableRegularPackage(ContentPackage);
contentPackageDropDown.AddItem(ContentPackage.Name, ContentPackage, ContentPackage.Path); contentPackageDropDown.AddItem(ContentPackage.Name, ContentPackage, ContentPackage.Path);
contentPackageDropDown.SelectItem(ContentPackage); contentPackageDropDown.SelectItem(ContentPackage);
contentPackageNameElement.Text = ""; contentPackageNameElement.Text = "";
@@ -32,10 +32,13 @@ namespace Barotrauma
private EditorNode? draggedNode; private EditorNode? draggedNode;
private Vector2 dragOffset; private Vector2 dragOffset;
private Dictionary<EditorNode, Vector2> markedNodes = new Dictionary<EditorNode, Vector2>(); private readonly Dictionary<EditorNode, Vector2> markedNodes = new Dictionary<EditorNode, Vector2>();
private static string projectName = string.Empty; private static string projectName = string.Empty;
private OutpostGenerationParams? lastTestParam;
private LocationType? lastTestType;
private int CreateID() private int CreateID()
{ {
int maxId = nodeList.Any() ? nodeList.Max(node => node.ID) : 0; int maxId = nodeList.Any() ? nodeList.Max(node => node.ID) : 0;
@@ -292,6 +295,8 @@ namespace Barotrauma
string filePath = System.IO.Path.Combine(directory, $"{projectName}.sevproj"); string filePath = System.IO.Path.Combine(directory, $"{projectName}.sevproj");
File.WriteAllText(Path.Combine(directory, $"{projectName}.sevproj"), save.ToString()); File.WriteAllText(Path.Combine(directory, $"{projectName}.sevproj"), save.ToString());
GUI.AddMessage($"Project saved to {filePath}", GUI.Style.Green); GUI.AddMessage($"Project saved to {filePath}", GUI.Style.Green);
AskForConfirmation(TextManager.Get("EventEditor.TestPromptHeader"), TextManager.Get("EventEditor.TestPromptBody"), CreateTestSetupMenu);
return true; return true;
}; };
return true; return true;
@@ -520,15 +525,12 @@ namespace Barotrauma
public override void Select() public override void Select()
{ {
Cam.Position = Vector2.Zero;
nodeList.Clear();
projectName = TextManager.Get("EventEditor.Unnamed"); projectName = TextManager.Get("EventEditor.Unnamed");
base.Select(); base.Select();
} }
public override void Deselect() public override void Deselect()
{ {
nodeList.Clear();
base.Deselect(); base.Deselect();
} }
@@ -597,9 +599,9 @@ namespace Barotrauma
optionElement.Add(new XAttribute("text", text)); optionElement.Add(new XAttribute("text", text));
if (end) { optionElement.Add(new XAttribute("endconversation", true)); } if (end) { optionElement.Add(new XAttribute("endconversation", true)); }
if (node != null) if (node is EventNode eventNode)
{ {
ExportChildNodes((EventNode) node, optionElement); ExportChildNodes(eventNode, optionElement);
} }
newElement.Add(optionElement); newElement.Add(optionElement);
@@ -749,6 +751,57 @@ namespace Barotrauma
}; };
} }
private bool CreateTestSetupMenu()
{
var msgBox = new GUIMessageBox(TextManager.Get("EventEditor.TestPromptHeader"), "", new[] { TextManager.Get("Cancel"), TextManager.Get("OK") },
relativeSize: new Vector2(0.2f, 0.3f), minSize: new Point(300, 175));
var layout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.5f), msgBox.Content.RectTransform));
new GUITextBlock(new RectTransform(new Vector2(1, 0.25f), layout.RectTransform), TextManager.Get("EventEditor.OutpostGenParams"), font: GUI.SubHeadingFont);
GUIDropDown paramInput = new GUIDropDown(new RectTransform(new Vector2(1, 0.25f), layout.RectTransform), string.Empty, OutpostGenerationParams.Params.Count);
foreach (OutpostGenerationParams param in OutpostGenerationParams.Params)
{
paramInput.AddItem(param.Identifier, param);
}
paramInput.OnSelected = (_, param) =>
{
lastTestParam = param as OutpostGenerationParams;
return true;
};
paramInput.SelectItem(lastTestParam ?? OutpostGenerationParams.Params.FirstOrDefault());
new GUITextBlock(new RectTransform(new Vector2(1, 0.25f), layout.RectTransform), TextManager.Get("EventEditor.LocationType"), font: GUI.SubHeadingFont);
GUIDropDown typeInput = new GUIDropDown(new RectTransform(new Vector2(1, 0.25f), layout.RectTransform), string.Empty, LocationType.List.Count);
foreach (LocationType type in LocationType.List)
{
typeInput.AddItem(type.Identifier, type);
}
typeInput.OnSelected = (_, type) =>
{
lastTestType = type as LocationType;
return true;
};
typeInput.SelectItem(lastTestType ?? LocationType.List.FirstOrDefault());
// Cancel button
msgBox.Buttons[0].OnClicked = (button, o) =>
{
msgBox.Close();
return true;
};
// Ok button
msgBox.Buttons[1].OnClicked = (button, o) =>
{
TestEvent(lastTestParam, lastTestType);
msgBox.Close();
return true;
};
return true;
}
private static void CreateEditMenu(ValueNode? node, NodeConnection? connection = null) private static void CreateEditMenu(ValueNode? node, NodeConnection? connection = null)
{ {
object? newValue; object? newValue;
@@ -860,6 +913,40 @@ namespace Barotrauma
}; };
} }
private bool TestEvent(OutpostGenerationParams? param, LocationType? type)
{
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(info => info.HasTag(SubmarineTag.Shuttle));
XElement? eventXml = ExportXML();
EventPrefab? prefab;
if (eventXml != null)
{
prefab = new EventPrefab(eventXml);
}
else
{
GUI.AddMessage("Unable to open test enviroment because the event contains errors.", GUI.Style.Red);
return false;
}
GameSession gameSession = new GameSession(subInfo, "", GameModePreset.TestMode, null);
TestGameMode gameMode = (TestGameMode) gameSession.GameMode;
gameMode.SpawnOutpost = true;
gameMode.OutpostParams = param;
gameMode.OutpostType = type;
gameMode.TriggeredEvent = prefab;
gameMode.OnRoundEnd = () =>
{
Submarine.Unload();
GameMain.EventEditorScreen.Select();
};
GameMain.GameScreen.Select();
gameSession.StartRound(null, false);
return true;
}
public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch) public override void Draw(double deltaTime, GraphicsDevice graphics, SpriteBatch spriteBatch)
{ {
DrawnTooltip = string.Empty; DrawnTooltip = string.Empty;
@@ -171,6 +171,7 @@ namespace Barotrauma
//These will be visible through the LOS effect. //These will be visible through the LOS effect.
spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform); spriteBatch.Begin(SpriteSortMode.BackToFront, BlendState.NonPremultiplied, null, DepthStencilState.None, null, null, cam.Transform);
Submarine.DrawBack(spriteBatch, false, e => e is Structure s && (e.SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite != null)); Submarine.DrawBack(spriteBatch, false, e => e is Structure s && (e.SpriteDepth >= 0.9f || s.Prefab.BackgroundSprite != null));
Submarine.DrawPaintedColors(spriteBatch, false);
spriteBatch.End(); spriteBatch.End();
graphics.SetRenderTarget(null); graphics.SetRenderTarget(null);
@@ -274,9 +274,9 @@ namespace Barotrauma
} }
//TODO: hacky workaround to check for wrecks and outposts, refactor SubmarineInfo and ContentType at some point //TODO: hacky workaround to check for wrecks and outposts, refactor SubmarineInfo and ContentType at some point
var nonPlayerFiles = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Wreck).ToList(); var nonPlayerFiles = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.Wreck).ToList();
nonPlayerFiles.AddRange(ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.Outpost)); nonPlayerFiles.AddRange(ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.Outpost));
nonPlayerFiles.AddRange(ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.OutpostModule)); nonPlayerFiles.AddRange(ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.OutpostModule));
SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.Equals(GameMain.Config.QuickStartSubmarineName, StringComparison.InvariantCultureIgnoreCase)); SubmarineInfo subInfo = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name.Equals(GameMain.Config.QuickStartSubmarineName, StringComparison.InvariantCultureIgnoreCase));
subInfo ??= SubmarineInfo.SavedSubmarines.GetRandom(s => subInfo ??= SubmarineInfo.SavedSubmarines.GetRandom(s =>
s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) && s.IsPlayer && !s.HasTag(SubmarineTag.Shuttle) &&
@@ -41,7 +41,6 @@ namespace Barotrauma
private Tab selectedTab; private Tab selectedTab;
private Sprite backgroundSprite; private Sprite backgroundSprite;
private Sprite backgroundVignette;
private readonly GUIComponent titleText; private readonly GUIComponent titleText;
@@ -65,8 +64,6 @@ namespace Barotrauma
CreateCampaignSetupUI(); CreateCampaignSetupUI();
}; };
backgroundVignette = new Sprite("Content/UI/MainMenuVignette.png", Vector2.Zero);
new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight) new GUIImage(new RectTransform(new Vector2(0.4f, 0.25f), Frame.RectTransform, Anchor.BottomRight)
{ RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) }, { RelativeOffset = new Vector2(0.08f, 0.05f), AbsoluteOffset = new Point(-8, -8) },
style: "TitleText") style: "TitleText")
@@ -863,7 +860,7 @@ namespace Barotrauma
GameMain.NetLobbyScreen = new NetLobbyScreen(); GameMain.NetLobbyScreen = new NetLobbyScreen();
try try
{ {
string exeName = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.ServerExecutable)?.FirstOrDefault()?.Path; string exeName = ContentPackage.GetFilesOfType(GameMain.Config.AllEnabledPackages, ContentType.ServerExecutable)?.FirstOrDefault()?.Path;
if (string.IsNullOrEmpty(exeName)) if (string.IsNullOrEmpty(exeName))
{ {
DebugConsole.ThrowError("No server executable defined in the selected content packages. Attempting to use the default executable..."); DebugConsole.ThrowError("No server executable defined in the selected content packages. Attempting to use the default executable...");
@@ -947,14 +944,11 @@ namespace Barotrauma
#if USE_STEAM #if USE_STEAM
if (GameMain.Config.UseSteamMatchmaking) if (GameMain.Config.UseSteamMatchmaking)
{ {
joinServerButton.Enabled = Steam.SteamManager.IsInitialized; hostServerButton.Enabled = Steam.SteamManager.IsInitialized;
hostServerButton.Enabled = Steam.SteamManager.IsInitialized;
} }
steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized; steamWorkshopButton.Enabled = Steam.SteamManager.IsInitialized;
#endif #endif
#else #else
joinServerButton.Enabled = true;
hostServerButton.Enabled = true;
#if USE_STEAM #if USE_STEAM
steamWorkshopButton.Enabled = true; steamWorkshopButton.Enabled = true;
#endif #endif
@@ -976,10 +970,14 @@ namespace Barotrauma
aberrationStrength: 0.0f); aberrationStrength: 0.0f);
} }
spriteBatch.Begin(blendState: BlendState.NonPremultiplied); var vignette = GUI.Style.GetComponentStyle("mainmenuvignette")?.GetDefaultSprite();
backgroundVignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f, if (vignette != null)
new Vector2(GameMain.GraphicsWidth / backgroundVignette.size.X, GameMain.GraphicsHeight / backgroundVignette.size.Y)); {
spriteBatch.End(); spriteBatch.Begin(blendState: BlendState.NonPremultiplied);
vignette.Draw(spriteBatch, Vector2.Zero, Color.White, Vector2.Zero, 0.0f,
new Vector2(GameMain.GraphicsWidth / vignette.size.X, GameMain.GraphicsHeight / vignette.size.Y));
spriteBatch.End();
}
} }
readonly string[] legalCrap = new string[] readonly string[] legalCrap = new string[]
@@ -15,20 +15,20 @@ namespace Barotrauma
private readonly List<Sprite> characterSprites = new List<Sprite>(); private readonly List<Sprite> characterSprites = new List<Sprite>();
//private readonly List<Sprite> jobPreferenceSprites = new List<Sprite>(); //private readonly List<Sprite> jobPreferenceSprites = new List<Sprite>();
private GUIFrame infoFrame, modeFrame; private readonly GUIFrame infoFrame, modeFrame;
private GUILayoutGroup infoFrameContent; private readonly GUILayoutGroup infoFrameContent;
private GUIFrame myCharacterFrame; private readonly GUIFrame myCharacterFrame;
private GUIListBox subList, modeList; private readonly GUIListBox subList, modeList;
private GUIListBox chatBox, playerList; private readonly GUIListBox chatBox, playerList;
private GUIButton serverLogReverseButton; private readonly GUIButton serverLogReverseButton;
private GUIListBox serverLogBox, serverLogFilterTicks; private readonly GUIListBox serverLogBox, serverLogFilterTicks;
private GUIComponent jobVariantTooltip; private GUIComponent jobVariantTooltip;
private GUITextBox chatInput; private readonly GUITextBox chatInput;
private GUITextBox serverLogFilter; private readonly GUITextBox serverLogFilter;
public GUITextBox ChatInput public GUITextBox ChatInput
{ {
get get
@@ -82,10 +82,10 @@ namespace Barotrauma
private readonly GUITickBox autoRestartBox; private readonly GUITickBox autoRestartBox;
private readonly GUITextBlock autoRestartText; private readonly GUITextBlock autoRestartText;
private GUIDropDown shuttleList; private readonly GUIDropDown shuttleList;
private GUITickBox shuttleTickBox; private readonly GUITickBox shuttleTickBox;
private GUIComponent settingsBlocker; private readonly GUIComponent settingsBlocker;
private Sprite backgroundSprite; private Sprite backgroundSprite;
@@ -123,15 +123,6 @@ namespace Barotrauma
public GUIProgressBar FileTransferProgressBar { get; private set; } public GUIProgressBar FileTransferProgressBar { get; private set; }
public GUITextBlock FileTransferProgressText { get; private set; } public GUITextBlock FileTransferProgressText { get; private set; }
private bool AllowSubSelection
{
get
{
return GameMain.NetworkMember.ServerSettings.Voting.AllowSubVoting ||
(GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub));
}
}
public GUITextBox ServerName public GUITextBox ServerName
{ {
get; get;
@@ -150,8 +141,8 @@ namespace Barotrauma
private set; private set;
} }
private GUIButton showChatButton; private readonly GUIButton showChatButton;
private GUIButton showLogButton; private readonly GUIButton showLogButton;
public GUIListBox SubList public GUIListBox SubList
{ {
@@ -268,9 +259,7 @@ namespace Barotrauma
foreach (MissionType type in Enum.GetValues(typeof(MissionType))) foreach (MissionType type in Enum.GetValues(typeof(MissionType)))
{ {
if (type == MissionType.None || type == MissionType.All) { continue; } if (type == MissionType.None || type == MissionType.All) { continue; }
missionTypeTickBoxes[index].Selected = ((int)type & (int)value) != 0;
missionTypeTickBoxes[index].Selected = (((int)type & (int)value) != 0);
index++; index++;
} }
} }
@@ -290,8 +279,7 @@ namespace Barotrauma
List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>(); List<Pair<JobPrefab, int>> jobPreferences = new List<Pair<JobPrefab, int>>();
foreach (GUIComponent child in JobList.Content.Children) foreach (GUIComponent child in JobList.Content.Children)
{ {
var jobPrefab = child.UserData as Pair<JobPrefab, int>; if (!(child.UserData is Pair<JobPrefab, int> jobPrefab)) { continue; }
if (jobPrefab == null) { continue; }
jobPreferences.Add(jobPrefab); jobPreferences.Add(jobPrefab);
} }
return jobPreferences; return jobPreferences;
@@ -743,7 +731,7 @@ namespace Barotrauma
foreach (GUIComponent child in subList.Content.Children) foreach (GUIComponent child in subList.Content.Children)
{ {
if (!(child.UserData is SubmarineInfo sub)) { continue; } if (!(child.UserData is SubmarineInfo sub)) { continue; }
child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower()); child.Visible = string.IsNullOrEmpty(text) || sub.DisplayName.ToLower().Contains(text.ToLower());
} }
return true; return true;
}; };
@@ -887,7 +875,12 @@ namespace Barotrauma
ContinueCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform), ContinueCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform),
TextManager.Get("campaigncontinue"), textAlignment: Alignment.Center) TextManager.Get("campaigncontinue"), textAlignment: Alignment.Center)
{ {
OnClicked = (_, __) => { GameMain.Client?.RequestStartRound(true); return true; } OnClicked = (_, __) =>
{
CoroutineManager.StartCoroutine(WaitForStartRound(ContinueCampaignButton), "WaitForStartRound");
GameMain.Client?.RequestStartRound(true);
return true;
}
}; };
QuitCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform), QuitCampaignButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.3f), campaignContent.RectTransform),
TextManager.Get("pausemenusavequit"), textAlignment: Alignment.Center) TextManager.Get("pausemenusavequit"), textAlignment: Alignment.Center)
@@ -1356,6 +1349,7 @@ namespace Barotrauma
if (GameMain.Client == null) { return; } if (GameMain.Client == null) { return; }
string newName = Client.SanitizeName(tb.Text); string newName = Client.SanitizeName(tb.Text);
newName = newName.Replace(":", "").Replace(";", ""); newName = newName.Replace(":", "").Replace(";", "");
if (newName == GameMain.Client.Name) return;
if (string.IsNullOrWhiteSpace(newName)) if (string.IsNullOrWhiteSpace(newName))
{ {
tb.Text = GameMain.Client.Name; tb.Text = GameMain.Client.Name;
@@ -1365,6 +1359,8 @@ namespace Barotrauma
if (isGameRunning) if (isGameRunning)
{ {
GameMain.Client.PendingName = tb.Text; GameMain.Client.PendingName = tb.Text;
TabMenu.PendingChanges = true;
CreateChangesPendingText();
} }
else else
{ {
@@ -1603,13 +1599,13 @@ namespace Barotrauma
private void AddSubmarine(GUIComponent subList, SubmarineInfo sub) private void AddSubmarine(GUIComponent subList, SubmarineInfo sub)
{ {
if (subList is GUIListBox) if (subList is GUIListBox listBox)
{ {
subList = ((GUIListBox)subList).Content; subList = listBox.Content;
} }
else if (subList is GUIDropDown) else if (subList is GUIDropDown dropDown)
{ {
subList = ((GUIDropDown)subList).ListBox.Content; subList = dropDown.ListBox.Content;
} }
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), subList.RectTransform) { MinSize = new Point(0, 20) }, var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.1f), subList.RectTransform) { MinSize = new Point(0, 20) },
@@ -1655,7 +1651,7 @@ namespace Barotrauma
if (sub.HasTag(SubmarineTag.Shuttle)) if (sub.HasTag(SubmarineTag.Shuttle))
{ {
var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), var shuttleText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) TextManager.Get("Shuttle", fallBackTag: "RespawnShuttle"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{ {
TextColor = subTextBlock.TextColor * 0.8f, TextColor = subTextBlock.TextColor * 0.8f,
@@ -1665,16 +1661,16 @@ namespace Barotrauma
//make shuttles more dim in the sub list (selecting a shuttle as the main sub is allowed but not recommended) //make shuttles more dim in the sub list (selecting a shuttle as the main sub is allowed but not recommended)
if (subList == this.subList.Content) if (subList == this.subList.Content)
{ {
subTextBlock.TextColor *= 0.5f; subTextBlock.TextColor *= 0.8f;
foreach (GUIComponent child in frame.Children) foreach (GUIComponent child in frame.Children)
{ {
child.Color *= 0.5f; child.Color *= 0.8f;
} }
} }
} }
else else
{ {
var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight), var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight) { AbsoluteOffset = new Point(GUI.IntScale(20), 0) },
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont) TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{ {
UserData = "classtext", UserData = "classtext",
@@ -1816,20 +1812,20 @@ namespace Barotrauma
public void SetPlayerNameAndJobPreference(Client client) public void SetPlayerNameAndJobPreference(Client client)
{ {
var PlayerFrame = (GUITextBlock)PlayerList.Content.FindChild(client); var playerFrame = (GUITextBlock)PlayerList.Content.FindChild(client);
if (PlayerFrame == null) { return; } if (playerFrame == null) { return; }
PlayerFrame.Text = client.Name; playerFrame.Text = client.Name;
Color color = Color.White; Color color = Color.White;
if (JobPrefab.Prefabs.ContainsKey(client.PreferredJob)) if (JobPrefab.Prefabs.ContainsKey(client.PreferredJob))
{ {
color = JobPrefab.Prefabs[client.PreferredJob].UIColor; color = JobPrefab.Prefabs[client.PreferredJob].UIColor;
} }
PlayerFrame.Color = color * 0.4f; playerFrame.Color = color * 0.4f;
PlayerFrame.HoverColor = color * 0.6f; playerFrame.HoverColor = color * 0.6f;
PlayerFrame.SelectedColor = color * 0.8f; playerFrame.SelectedColor = color * 0.8f;
PlayerFrame.OutlineColor = color * 0.5f; playerFrame.OutlineColor = color * 0.5f;
PlayerFrame.TextColor = color; playerFrame.TextColor = color;
} }
public void SetPlayerVoiceIconState(Client client, bool muted, bool mutedLocally) public void SetPlayerVoiceIconState(Client client, bool muted, bool mutedLocally)
@@ -2671,7 +2667,7 @@ namespace Barotrauma
return false; return false;
} }
private bool SwitchJob(GUIButton button, object obj) private bool SwitchJob(GUIButton _, object obj)
{ {
if (JobList == null) { return false; } if (JobList == null) { return false; }
@@ -2724,7 +2720,7 @@ namespace Barotrauma
return false; return false;
} }
private bool OpenJobSelection(GUIComponent child, object userData) private bool OpenJobSelection(GUIComponent _, object __)
{ {
if (JobSelectionFrame != null) if (JobSelectionFrame != null)
{ {
@@ -2870,7 +2866,9 @@ namespace Barotrauma
{ {
Color = Color.Black, Color = Color.Black,
HoverColor = Color.Black, HoverColor = Color.Black,
SelectedColor = Color.Black PressedColor = Color.Black,
SelectedColor = Color.Black,
CanBeFocused = false
}; };
var textBlock = new GUITextBlock( var textBlock = new GUITextBlock(
@@ -2883,6 +2881,7 @@ namespace Barotrauma
HoverColor = Color.Transparent, HoverColor = Color.Transparent,
SelectedColor = Color.Transparent, SelectedColor = Color.Transparent,
TextColor = jobPrefab.UIColor, TextColor = jobPrefab.UIColor,
HoverTextColor = Color.Lerp(jobPrefab.UIColor, Color.White, 0.5f),
CanBeFocused = false, CanBeFocused = false,
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
@@ -2938,7 +2937,7 @@ namespace Barotrauma
info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, info.BeardIndex, index, info.FaceAttachmentIndex); info.Head = new CharacterInfo.HeadInfo(info.HeadSpriteId, info.Gender, info.Race, info.HairIndex, info.BeardIndex, index, info.FaceAttachmentIndex);
break; break;
default: default:
DebugConsole.ThrowError($"Wearable type not implemented: {type.ToString()}"); DebugConsole.ThrowError($"Wearable type not implemented: {type}");
return false; return false;
} }
info.ReloadHeadAttachments(); info.ReloadHeadAttachments();
@@ -13,6 +13,8 @@ namespace Barotrauma
private RectTransform prevGuiElementParent; private RectTransform prevGuiElementParent;
public Exception LoadException;
public static RoundSummaryScreen Select(Sprite backgroundSprite, RoundSummary roundSummary) public static RoundSummaryScreen Select(Sprite backgroundSprite, RoundSummary roundSummary)
{ {
var summaryScreen = new RoundSummaryScreen() var summaryScreen = new RoundSummaryScreen()
@@ -51,5 +53,16 @@ namespace Barotrauma
spriteBatch.End(); spriteBatch.End();
} }
public override void Update(double deltaTime)
{
base.Update(deltaTime);
if (LoadException != null)
{
var temp = LoadException;
LoadException = null;
throw temp;
}
}
} }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Steam; using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -6,13 +7,10 @@ using Microsoft.Xna.Framework.Graphics;
using RestSharp; using RestSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Net; using System.Net;
using System.Net.NetworkInformation; using System.Net.NetworkInformation;
using System.Net.Sockets; using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
@@ -21,7 +19,7 @@ namespace Barotrauma
class ServerListScreen : Screen class ServerListScreen : Screen
{ {
//how often the client is allowed to refresh servers //how often the client is allowed to refresh servers
private TimeSpan AllowedRefreshInterval = new TimeSpan(0, 0, 3); private readonly TimeSpan AllowedRefreshInterval = new TimeSpan(0, 0, 3);
private GUIFrame menu; private GUIFrame menu;
@@ -31,12 +29,20 @@ namespace Barotrauma
private GUIButton joinButton; private GUIButton joinButton;
private ServerInfo selectedServer; private ServerInfo selectedServer;
private GUIButton scanServersButton;
//friends list //friends list
private GUILayoutGroup friendsButtonHolder; private GUILayoutGroup friendsButtonHolder;
private GUIButton friendsDropdownButton; private GUIButton friendsDropdownButton;
private GUIListBox friendsDropdown; private GUIListBox friendsDropdown;
//Workshop downloads
private GUIFrame workshopDownloadsFrame = null;
private Steamworks.Ugc.Item? currentlyDownloadingWorkshopItem = null;
private Dictionary<ulong, Steamworks.Ugc.Item?> pendingWorkshopDownloads = null;
private string autoConnectName; private string autoConnectEndpoint;
private class FriendInfo private class FriendInfo
{ {
public UInt64 SteamID; public UInt64 SteamID;
@@ -558,7 +564,7 @@ namespace Barotrauma
OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu
}; };
new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform), scanServersButton = new GUIButton(new RectTransform(new Vector2(0.25f, 0.9f), buttonContainer.RectTransform),
TextManager.Get("ServerListRefresh")) TextManager.Get("ServerListRefresh"))
{ {
OnClicked = (btn, userdata) => { RefreshServers(); return true; } OnClicked = (btn, userdata) => { RefreshServers(); return true; }
@@ -761,7 +767,7 @@ namespace Barotrauma
info.GameStarted = Screen.Selected != GameMain.NetLobbyScreen; info.GameStarted = Screen.Selected != GameMain.NetLobbyScreen;
info.GameVersion = GameMain.Version.ToString(); info.GameVersion = GameMain.Version.ToString();
info.MaxPlayers = serverSettings.MaxPlayers; info.MaxPlayers = serverSettings.MaxPlayers;
info.PlayStyle = PlayStyle.SomethingDifferent; info.PlayStyle = serverSettings.PlayStyle;
info.RespondedToSteamQuery = true; info.RespondedToSteamQuery = true;
info.UsingWhiteList = serverSettings.Whitelist.Enabled; info.UsingWhiteList = serverSettings.Whitelist.Enabled;
info.TraitorsEnabled = serverSettings.TraitorsEnabled; info.TraitorsEnabled = serverSettings.TraitorsEnabled;
@@ -892,11 +898,11 @@ namespace Barotrauma
case "ServerListCompatible": case "ServerListCompatible":
bool? s1Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s1.GameVersion); bool? s1Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s1.GameVersion);
if (!s1.ContentPackageHashes.Any()) { s1Compatible = null; } if (!s1.ContentPackageHashes.Any()) { s1Compatible = null; }
if (s1Compatible.HasValue) { s1Compatible = s1Compatible.Value && s1.ContentPackagesMatch(GameMain.SelectedPackages); }; if (s1Compatible.HasValue) { s1Compatible = s1Compatible.Value && s1.ContentPackagesMatch(); };
bool? s2Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s2.GameVersion); bool? s2Compatible = NetworkMember.IsCompatible(GameMain.Version.ToString(), s2.GameVersion);
if (!s2.ContentPackageHashes.Any()) { s2Compatible = null; } if (!s2.ContentPackageHashes.Any()) { s2Compatible = null; }
if (s2Compatible.HasValue) { s2Compatible = s2Compatible.Value && s2.ContentPackagesMatch(GameMain.SelectedPackages); }; if (s2Compatible.HasValue) { s2Compatible = s2Compatible.Value && s2.ContentPackagesMatch(); };
//convert to int to make sorting easier //convert to int to make sorting easier
//1 Compatible //1 Compatible
@@ -946,6 +952,9 @@ namespace Barotrauma
public override void Deselect() public override void Deselect()
{ {
base.Deselect(); base.Deselect();
pendingWorkshopDownloads?.Clear();
workshopDownloadsFrame = null;
} }
public override void Update(double deltaTime) public override void Update(double deltaTime)
@@ -965,6 +974,43 @@ namespace Barotrauma
friendsDropdown.Visible = false; friendsDropdown.Visible = false;
} }
} }
if (currentlyDownloadingWorkshopItem?.IsInstalled ?? true)
{
if (pendingWorkshopDownloads?.Any() ?? false)
{
Steamworks.Ugc.Item? item = pendingWorkshopDownloads.Values.FirstOrDefault(it => it != null);
if (item != null)
{
ulong itemId = item.Value.Id;
currentlyDownloadingWorkshopItem = item;
SteamManager.SubscribeToWorkshopItem(itemId, () =>
{
pendingWorkshopDownloads.Remove(itemId);
if (SteamManager.CheckWorkshopItemInstalled(item))
{
SteamManager.UninstallWorkshopItem(item, false, out _);
}
if (SteamManager.InstallWorkshopItem(item, out string errorMsg, enableContentPackage: false, suppressInstallNotif: true))
{
workshopDownloadsFrame?.FindChild((c) => c.UserData is ulong l && l == itemId, true)?.Flash(GUI.Style.Green);
}
else
{
workshopDownloadsFrame?.FindChild((c) => c.UserData is ulong l && l == itemId, true)?.Flash(GUI.Style.Red);
DebugConsole.ThrowError(errorMsg);
}
});
}
}
else if (!string.IsNullOrEmpty(autoConnectEndpoint))
{
JoinServer(autoConnectEndpoint, autoConnectName);
autoConnectEndpoint = null;
}
}
} }
private void FilterServers() private void FilterServers()
@@ -992,7 +1038,7 @@ namespace Barotrauma
else else
{ {
bool incompatible = bool incompatible =
(!serverInfo.ContentPackageHashes.Any() && serverInfo.ContentPackagesMatch(GameMain.Config.SelectedContentPackages)) || (!serverInfo.ContentPackageHashes.Any() && serverInfo.ContentPackagesMatch()) ||
(remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion)); (remoteVersion != null && !NetworkMember.IsCompatible(GameMain.Version, remoteVersion));
child.Visible = child.Visible =
@@ -1018,7 +1064,7 @@ namespace Barotrauma
{ {
var playStyle = (PlayStyle)tickBox.UserData; var playStyle = (PlayStyle)tickBox.UserData;
if (!tickBox.Selected && serverInfo.PlayStyle == playStyle) if (!tickBox.Selected && (serverInfo.PlayStyle == playStyle || !serverInfo.PlayStyle.HasValue))
{ {
child.Visible = false; child.Visible = false;
break; break;
@@ -1136,7 +1182,7 @@ namespace Barotrauma
Port = port.ToString(), Port = port.ToString(),
QueryPort = NetConfig.DefaultQueryPort.ToString(), QueryPort = NetConfig.DefaultQueryPort.ToString(),
GameVersion = GameMain.Version.ToString(), GameVersion = GameMain.Version.ToString(),
PlayStyle = PlayStyle.Serious PlayStyle = null
}; };
var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) && var serverFrame = serverList.Content.FindChild(d => (d.UserData is ServerInfo info) &&
@@ -1546,6 +1592,7 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
scanServersButton.Enabled = false;
} }
else else
{ {
@@ -1555,6 +1602,7 @@ namespace Barotrauma
AddToServerList(info); AddToServerList(info);
QueueInfoQuery(info); QueueInfoQuery(info);
} }
scanServersButton.Enabled = true;
} }
} }
else else
@@ -1712,7 +1760,7 @@ namespace Barotrauma
CanBeFocused = false, CanBeFocused = false,
Selected = Selected =
(NetworkMember.IsCompatible(GameMain.Version.ToString(), serverInfo.GameVersion) ?? true) && (NetworkMember.IsCompatible(GameMain.Version.ToString(), serverInfo.GameVersion) ?? true) &&
serverInfo.ContentPackagesMatch(GameMain.SelectedPackages), serverInfo.ContentPackagesMatch(),
UserData = "compatible" UserData = "compatible"
}; };
@@ -1818,7 +1866,17 @@ namespace Barotrauma
for (int i = 0; i < serverInfo.ContentPackageNames.Count; i++) for (int i = 0; i < serverInfo.ContentPackageNames.Count; i++)
{ {
if (!GameMain.SelectedPackages.Any(cp => cp.MD5hash.Hash == serverInfo.ContentPackageHashes[i])) bool listAsIncompatible = false;
if (serverInfo.ContentPackageWorkshopIds[i] == 0)
{
listAsIncompatible = !GameMain.Config.AllEnabledPackages.Any(cp => cp.MD5hash.Hash == serverInfo.ContentPackageHashes[i]);
}
else
{
listAsIncompatible = GameMain.Config.AllEnabledPackages.Any(cp => cp.MD5hash.Hash != serverInfo.ContentPackageHashes[i] &&
cp.SteamWorkshopId == serverInfo.ContentPackageWorkshopIds[i]);
}
if (listAsIncompatible)
{ {
if (toolTip != "") toolTip += "\n"; if (toolTip != "") toolTip += "\n";
toolTip += TextManager.GetWithVariables("ServerListIncompatibleContentPackage", new string[2] { "[contentpackage]", "[hash]" }, toolTip += TextManager.GetWithVariables("ServerListIncompatibleContentPackage", new string[2] { "[contentpackage]", "[hash]" },
@@ -1831,6 +1889,20 @@ namespace Barotrauma
serverName.TextColor *= 0.5f; serverName.TextColor *= 0.5f;
serverPlayers.TextColor *= 0.5f; serverPlayers.TextColor *= 0.5f;
} }
else
{
string toolTip = "";
for (int i = 0; i < serverInfo.ContentPackageNames.Count; i++)
{
if (!GameMain.Config.AllEnabledPackages.Any(cp => cp.MD5hash.Hash == serverInfo.ContentPackageHashes[i]))
{
if (toolTip != "") toolTip += "\n";
toolTip += TextManager.GetWithVariable("ServerListIncompatibleContentPackageWorkshopAvailable", "[contentpackage]", serverInfo.ContentPackageNames[i]);
break;
}
}
serverContent.Children.ForEach(c => c.ToolTip = toolTip);
}
serverContent.Recalculate(); serverContent.Recalculate();
@@ -1921,17 +1993,17 @@ namespace Barotrauma
serverList.ClearChildren(); serverList.ClearChildren();
new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.GetWithVariable("MasterServerErrorException", "[error]", masterServerResponse.ErrorException.ToString())); new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), TextManager.GetWithVariable("MasterServerErrorException", "[error]", masterServerResponse.ErrorException.ToString()));
} }
else if (masterServerResponse.StatusCode != System.Net.HttpStatusCode.OK) else if (masterServerResponse.StatusCode != HttpStatusCode.OK)
{ {
serverList.ClearChildren(); serverList.ClearChildren();
switch (masterServerResponse.StatusCode) switch (masterServerResponse.StatusCode)
{ {
case System.Net.HttpStatusCode.NotFound: case HttpStatusCode.NotFound:
new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"),
TextManager.GetWithVariable("MasterServerError404", "[masterserverurl]", NetConfig.MasterServerUrl)); TextManager.GetWithVariable("MasterServerError404", "[masterserverurl]", NetConfig.MasterServerUrl));
break; break;
case System.Net.HttpStatusCode.ServiceUnavailable: case HttpStatusCode.ServiceUnavailable:
new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"), new GUIMessageBox(TextManager.Get("MasterServerErrorLabel"),
TextManager.Get("MasterServerErrorUnavailable")); TextManager.Get("MasterServerErrorUnavailable"));
break; break;
@@ -1958,6 +2030,79 @@ namespace Barotrauma
masterServerResponded = true; masterServerResponded = true;
} }
public void DownloadWorkshopItems(IEnumerable<ulong> ids, string serverName, string endPointString)
{
if (workshopDownloadsFrame != null) { return; }
int rowCount = ids.Count() + 2;
autoConnectName = serverName; autoConnectEndpoint = endPointString;
workshopDownloadsFrame = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), null, Color.Black * 0.5f);
pendingWorkshopDownloads = new Dictionary<ulong, Steamworks.Ugc.Item?>();
var innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f + 0.03f * rowCount), workshopDownloadsFrame.RectTransform, Anchor.Center, Pivot.Center));
var innerLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, (float)rowCount / (float)(rowCount + 3)), innerFrame.RectTransform, Anchor.Center, Pivot.Center));
foreach (ulong id in ids)
{
pendingWorkshopDownloads.Add(id, null);
var itemLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f / rowCount), innerLayout.RectTransform), true, Anchor.CenterLeft)
{
UserData = id
};
TaskPool.Add("RetrieveWorkshopItemData", Steamworks.SteamUGC.QueryFileAsync(id), (t) =>
{
if (t.IsFaulted)
{
TaskPool.PrintTaskExceptions(t, $"Failed to retrieve Workshop item info (ID {id})");
return;
}
Steamworks.Ugc.Item? item = ((Task<Steamworks.Ugc.Item?>)t).Result;
if (!item.HasValue)
{
DebugConsole.ThrowError($"Failed to find a Steam Workshop item with the ID {id}.");
return;
}
if (pendingWorkshopDownloads.ContainsKey(id))
{
pendingWorkshopDownloads[id] = item;
new GUITextBlock(new RectTransform(new Vector2(0.4f, 0.67f), itemLayout.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), item.Value.Title);
new GUIProgressBar(new RectTransform(new Vector2(0.6f, 0.67f), itemLayout.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), 0f, Color.Lime)
{
ProgressGetter = () =>
{
if (item.Value.IsInstalled) { return 1.0f; }
else if (!item.Value.IsDownloading) { return 0.0f; }
return item.Value.DownloadAmount;
}
};
}
});
}
var buttonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 2.0f / rowCount), innerLayout.RectTransform), true, Anchor.CenterLeft)
{
UserData = "buttons"
};
new GUIButton(new RectTransform(new Vector2(0.3f, 0.67f), buttonLayout.RectTransform, Anchor.CenterLeft, Pivot.CenterLeft), TextManager.Get("Cancel"))
{
OnClicked = (btn, obj) =>
{
autoConnectEndpoint = null;
autoConnectName = null;
pendingWorkshopDownloads.Clear();
workshopDownloadsFrame = null;
return true;
}
};
}
private bool JoinServer(string endpoint, string serverName) private bool JoinServer(string endpoint, string serverName)
{ {
if (string.IsNullOrWhiteSpace(ClientNameBox.Text)) if (string.IsNullOrWhiteSpace(ClientNameBox.Text))
@@ -2113,6 +2258,8 @@ namespace Barotrauma
friendPopup?.AddToGUIUpdateList(); friendPopup?.AddToGUIUpdateList();
friendsDropdown?.AddToGUIUpdateList(); friendsDropdown?.AddToGUIUpdateList();
workshopDownloadsFrame?.AddToGUIUpdateList();
} }
} }
@@ -325,7 +325,7 @@ namespace Barotrauma
{ {
loadedSprites.ForEach(s => s.Remove()); loadedSprites.ForEach(s => s.Remove());
loadedSprites.Clear(); loadedSprites.Clear();
var contentPackages = GameMain.Config.SelectedContentPackages.ToList(); var contentPackages = GameMain.Config.AllEnabledPackages.ToList();
#if !DEBUG #if !DEBUG
var vanilla = GameMain.VanillaContent; var vanilla = GameMain.VanillaContent;
@@ -1,12 +1,11 @@
using Barotrauma.Steam; using Barotrauma.IO;
using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using RestSharp; using RestSharp;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Barotrauma namespace Barotrauma
@@ -44,7 +43,7 @@ namespace Barotrauma
public int PendingLoads = 1; public int PendingLoads = 1;
} }
private readonly Dictionary<ulong, PendingPreviewImageDownload> pendingPreviewImageDownloads = new Dictionary<ulong, PendingPreviewImageDownload>(); private readonly Dictionary<ulong, PendingPreviewImageDownload> pendingPreviewImageDownloads = new Dictionary<ulong, PendingPreviewImageDownload>();
private Dictionary<string, Sprite> itemPreviewSprites = new Dictionary<string, Sprite>(); private readonly Dictionary<string, Sprite> itemPreviewSprites = new Dictionary<string, Sprite>();
private enum Tab private enum Tab
{ {
@@ -237,7 +236,7 @@ namespace Barotrauma
SelectTab(Tab.Mods); SelectTab(Tab.Mods);
subscribedCoroutine = CoroutineManager.StartCoroutine(PollSubscribedItems()); CoroutineManager.StartCoroutine(PollSubscribedItems());
} }
private GUITextBox CreateFilterBox(GUIComponent parent, GUIListBox listbox) private GUITextBox CreateFilterBox(GUIComponent parent, GUIListBox listbox)
@@ -283,7 +282,7 @@ namespace Barotrauma
RefreshSubscribedItems(); RefreshSubscribedItems();
} }
CoroutineHandle subscribedCoroutine; float subscribePollAdditionalWait = 0.0f;
private IEnumerable<object> PollSubscribedItems() private IEnumerable<object> PollSubscribedItems()
{ {
@@ -293,6 +292,13 @@ namespace Barotrauma
while (true) while (true)
{ {
while (CoroutineManager.IsCoroutineRunning("Load")) { yield return new WaitForSeconds(1.0f); } while (CoroutineManager.IsCoroutineRunning("Load")) { yield return new WaitForSeconds(1.0f); }
while (subscribePollAdditionalWait > 0.01f)
{
subscribePollAdditionalWait = Math.Min(subscribePollAdditionalWait, 3.0f);
float wait = subscribePollAdditionalWait;
yield return new WaitForSeconds(wait);
subscribePollAdditionalWait -= wait;
}
uint newNumSubscribed = Steamworks.SteamUGC.NumSubscribedItems; uint newNumSubscribed = Steamworks.SteamUGC.NumSubscribedItems;
if (newNumSubscribed != numSubscribed) if (newNumSubscribed != numSubscribed)
{ {
@@ -338,15 +344,6 @@ namespace Barotrauma
} }
} }
public void SubscribeToPackages(List<string> packageUrls)
{
foreach (string url in packageUrls)
{
SteamManager.SubscribeToWorkshopItem(url);
}
GameMain.SteamWorkshopScreen.Select();
}
public IEnumerable<object> RefreshDownloadState() public IEnumerable<object> RefreshDownloadState()
{ {
bool isDownloading = true; bool isDownloading = true;
@@ -420,14 +417,14 @@ namespace Barotrauma
continue; continue;
} }
//ignore subs that are part of a workshop content package //ignore subs that are part of a workshop content package
if (ContentPackage.List.Any(cp => !string.IsNullOrEmpty(cp.SteamWorkshopUrl) && if (ContentPackage.AllPackages.Any(cp => cp.SteamWorkshopId != 0 &&
cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath))) cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath)))
{ {
continue; continue;
} }
//ignore subs that are defined in a content package with more files than just the sub //ignore subs that are defined in a content package with more files than just the sub
//(these will be listed in the "content packages" section) //(these will be listed in the "content packages" section)
if (ContentPackage.List.Any(cp => cp.Files.Count > 1 && if (ContentPackage.AllPackages.Any(cp => cp.Files.Count > 1 &&
cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath))) cp.Files.Any(f => f.Type == ContentType.Submarine && Path.GetFullPath(f.Path) == subPath)))
{ {
continue; continue;
@@ -441,9 +438,9 @@ namespace Barotrauma
{ {
CanBeFocused = false CanBeFocused = false
}; };
foreach (ContentPackage contentPackage in ContentPackage.List) foreach (ContentPackage contentPackage in ContentPackage.AllPackages)
{ {
if (!string.IsNullOrEmpty(contentPackage.SteamWorkshopUrl) || contentPackage.HideInWorkshopMenu) { continue; } if (contentPackage.SteamWorkshopId != 0 || contentPackage.HideInWorkshopMenu) { continue; }
if (contentPackage == GameMain.VanillaContent) { continue; } if (contentPackage == GameMain.VanillaContent) { continue; }
//don't list content packages that only define one sub (they're visible in the "Submarines" section) //don't list content packages that only define one sub (they're visible in the "Submarines" section)
if (contentPackage.Files.Count == 1 && contentPackage.Files[0].Type == ContentType.Submarine) { continue; } if (contentPackage.Files.Count == 1 && contentPackage.Files[0].Type == ContentType.Submarine) { continue; }
@@ -488,7 +485,7 @@ namespace Barotrauma
text = topItemFilter.Text; text = topItemFilter.Text;
} }
bool visible = string.IsNullOrEmpty(text) ? true : (item?.Title?.ToLower().Contains(text.ToLower()) ?? false); bool visible = string.IsNullOrEmpty(text) || (item?.Title?.ToLower().Contains(text.ToLower()) ?? false);
int prevIndex = -1; int prevIndex = -1;
var existingFrame = listBox.Content.FindChild((component) => { return (component.UserData is Steamworks.Ugc.Item?) && (component.UserData as Steamworks.Ugc.Item?)?.Id == item?.Id; }); var existingFrame = listBox.Content.FindChild((component) => { return (component.UserData is Steamworks.Ugc.Item?) && (component.UserData as Steamworks.Ugc.Item?)?.Id == item?.Id; });
@@ -611,7 +608,7 @@ namespace Barotrauma
if ((item?.IsSubscribed ?? false) && (item?.IsInstalled ?? false) && Directory.Exists(item?.Directory)) if ((item?.IsSubscribed ?? false) && (item?.IsInstalled ?? false) && Directory.Exists(item?.Directory))
{ {
bool installed = SteamManager.CheckWorkshopItemEnabled(item); bool installed = SteamManager.CheckWorkshopItemInstalled(item);
if (!installed) if (!installed)
{ {
@@ -626,7 +623,7 @@ namespace Barotrauma
} }
else else
{ {
installed = SteamManager.EnableWorkShopItem(item, out string errorMsg, Selected == this); installed = SteamManager.InstallWorkshopItem(item, out string errorMsg, Selected == this);
if (!installed) if (!installed)
{ {
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
@@ -660,7 +657,7 @@ namespace Barotrauma
{ {
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), rightColumn.RectTransform), TextManager.Get("WorkshopItemDownloadPending")); new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), rightColumn.RectTransform), TextManager.Get("WorkshopItemDownloadPending"));
} }
else if (!(item?.IsSubscribed ?? false)) else if (!(item?.IsSubscribed ?? false) && (listBox != subscribedItemList))
{ {
var downloadBtn = new GUIButton(new RectTransform(new Point((int)(32 * GUI.Scale)), rightColumn.RectTransform), "", style: "GUIPlusButton") var downloadBtn = new GUIButton(new RectTransform(new Point((int)(32 * GUI.Scale)), rightColumn.RectTransform), "", style: "GUIPlusButton")
{ {
@@ -684,9 +681,9 @@ namespace Barotrauma
var elem = subscribedItemList.Content.GetChildByUserData(item); var elem = subscribedItemList.Content.GetChildByUserData(item);
try try
{ {
bool reselect = GameMain.Config.SelectedContentPackages.Any(cp => !string.IsNullOrWhiteSpace(cp.SteamWorkshopUrl) && cp.SteamWorkshopUrl == item?.Url); bool reselect = GameMain.Config.AllEnabledPackages.Any(cp => cp.SteamWorkshopId != 0 && cp.SteamWorkshopId == item?.Id);
if (!SteamManager.DisableWorkShopItem(item, false, out string errorMsg) || if (!SteamManager.UninstallWorkshopItem(item, false, out string errorMsg) ||
!SteamManager.EnableWorkShopItem(item, out errorMsg, reselect, true)) !SteamManager.InstallWorkshopItem(item, out errorMsg, reselect, true))
{ {
DebugConsole.ThrowError($"Failed to reinstall \"{item?.Title}\": {errorMsg}", null, true); DebugConsole.ThrowError($"Failed to reinstall \"{item?.Title}\": {errorMsg}", null, true);
elem.Flash(GUI.Style.Red); elem.Flash(GUI.Style.Red);
@@ -707,8 +704,9 @@ namespace Barotrauma
}; };
unsubBtn.OnClicked = (btn, userdata) => unsubBtn.OnClicked = (btn, userdata) =>
{ {
SteamManager.DisableWorkShopItem(item, true, out _); subscribePollAdditionalWait += 1.0f;
item?.Unsubscribe(); item?.Unsubscribe();
SteamManager.UninstallWorkshopItem(item, true, out _);
subscribedItemList.RemoveChild(subscribedItemList.Content.GetChildByUserData(item)); subscribedItemList.RemoveChild(subscribedItemList.Content.GetChildByUserData(item));
return true; return true;
}; };
@@ -819,31 +817,34 @@ namespace Barotrauma
new Tuple<Steamworks.Ugc.Item?, GUIListBox>(item, listBox), new Tuple<Steamworks.Ugc.Item?, GUIListBox>(item, listBox),
(task, tuple) => (task, tuple) =>
{ {
(var it, var lb) = tuple; //must be done in the main thread because creating/removing GUI elements is not thread-safe
var previewImage = lb.Content.FindChild(item)?.GetChildByUserData("previewimage") as GUIImage; CrossThread.RequestExecutionOnMainThread(() =>
if (previewImage != null)
{ {
previewImage.Sprite = ((Task<Sprite>)task).Result; (var it, var lb) = tuple;
} if (lb.Content.FindChild(item)?.GetChildByUserData("previewimage") is GUIImage previewImage)
else {
{ previewImage.Sprite = ((Task<Sprite>)task).Result;
CreateWorkshopItemFrame(it, lb); }
} else
{
CreateWorkshopItemFrame(it, lb);
}
if (modsPreviewFrame.FindChild(it) != null) if (modsPreviewFrame.FindChild(it) != null)
{ {
ShowItemPreview(it, modsPreviewFrame); ShowItemPreview(it, modsPreviewFrame);
} }
if (browsePreviewFrame.FindChild(item) != null) if (browsePreviewFrame.FindChild(item) != null)
{ {
ShowItemPreview(it, browsePreviewFrame); ShowItemPreview(it, browsePreviewFrame);
} }
lock (pendingPreviewImageDownloads) lock (pendingPreviewImageDownloads)
{ {
pendingPreviewImageDownloads[it.Value.Id].PendingLoads--; pendingPreviewImageDownloads[it.Value.Id].PendingLoads--;
if (pendingPreviewImageDownloads[it.Value.Id].PendingLoads <= 0) { pendingPreviewImageDownloads.Remove(it.Value.Id); } if (pendingPreviewImageDownloads[it.Value.Id].PendingLoads <= 0) { pendingPreviewImageDownloads.Remove(it.Value.Id); }
} }
});
}); });
} }
@@ -872,15 +873,13 @@ namespace Barotrauma
{ {
if (item == null) { return false; } if (item == null) { return false; }
if (!(item?.IsSubscribed ?? false)) { item?.Subscribe(); }
var parentElement = downloadButton.Parent; var parentElement = downloadButton.Parent;
parentElement.RemoveChild(downloadButton); parentElement.RemoveChild(downloadButton);
var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), parentElement.RectTransform), TextManager.Get("WorkshopItemDownloading")); var textBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), parentElement.RectTransform), TextManager.Get("WorkshopItemDownloading"));
item?.Download(onInstalled: () => SteamManager.SubscribeToWorkshopItem(item.Value.Id, () =>
{ {
if (SteamManager.EnableWorkShopItem(item, out _)) if (SteamManager.InstallWorkshopItem(item, out _))
{ {
textBlock.Text = TextManager.Get("workshopiteminstalled"); textBlock.Text = TextManager.Get("workshopiteminstalled");
frame.Flash(GUI.Style.Green); frame.Flash(GUI.Style.Green);
@@ -1022,8 +1021,9 @@ namespace Barotrauma
UserData = item, UserData = item,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
SteamManager.DisableWorkShopItem(item, true, out _); subscribePollAdditionalWait += 1.0f;
item?.Unsubscribe(); item?.Unsubscribe();
SteamManager.UninstallWorkshopItem(item, true, out _);
subscribedItemList.RemoveChild(subscribedItemList.Content.GetChildByUserData(item)); subscribedItemList.RemoveChild(subscribedItemList.Content.GetChildByUserData(item));
itemPreviewFrame.ClearChildren(); itemPreviewFrame.ClearChildren();
return true; return true;
@@ -1294,7 +1294,7 @@ namespace Barotrauma
new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), topLeftColumn.RectTransform), TextManager.Get("WorkshopItemCorePackage")) new GUITickBox(new RectTransform(new Vector2(1.0f, 0.1f), topLeftColumn.RectTransform), TextManager.Get("WorkshopItemCorePackage"))
{ {
ToolTip = TextManager.Get("WorkshopItemCorePackageTooltip"), ToolTip = TextManager.Get("WorkshopItemCorePackageTooltip"),
Selected = itemContentPackage.CorePackage, Selected = itemContentPackage.IsCorePackage,
OnSelected = (tickbox) => OnSelected = (tickbox) =>
{ {
if (tickbox.Selected) if (tickbox.Selected)
@@ -1309,12 +1309,12 @@ namespace Barotrauma
} }
else else
{ {
itemContentPackage.CorePackage = tickbox.Selected; itemContentPackage.IsCorePackage = tickbox.Selected;
} }
} }
else else
{ {
itemContentPackage.CorePackage = false; itemContentPackage.IsCorePackage = false;
} }
return true; return true;
} }
@@ -1478,7 +1478,7 @@ namespace Barotrauma
SelectTab(Tab.Browse); SelectTab(Tab.Browse);
deleteVerification.Close(); deleteVerification.Close();
createItemFrame.ClearChildren(); createItemFrame.ClearChildren();
itemContentPackage.SteamWorkshopUrl = ""; itemContentPackage.SteamWorkshopId = 0;
itemContentPackage.Save(itemContentPackage.Path); itemContentPackage.Save(itemContentPackage.Path);
return true; return true;
}; };
@@ -1535,9 +1535,17 @@ namespace Barotrauma
return; return;
} }
if (filePath != previewImagePath) if (Path.GetFullPath(filePath) != previewImagePath)
{ {
File.Copy(filePath, previewImagePath, overwrite: true); try
{
File.Copy(filePath, previewImagePath, overwrite: true);
}
catch (System.IO.IOException e)
{
DebugConsole.ThrowError("Failed to copy the preview image \"{previewImagePath}\" to the mod folder.", e);
return;
}
} }
if (itemPreviewSprites.ContainsKey(previewImagePath)) if (itemPreviewSprites.ContainsKey(previewImagePath))
@@ -1560,13 +1568,12 @@ namespace Barotrauma
string modFolder = Path.GetDirectoryName(itemContentPackage.Path); string modFolder = Path.GetDirectoryName(itemContentPackage.Path);
string filePathRelativeToModFolder = UpdaterUtil.GetRelativePath(file, Path.Combine(Environment.CurrentDirectory, modFolder)); string filePathRelativeToModFolder = UpdaterUtil.GetRelativePath(file, Path.Combine(Environment.CurrentDirectory, modFolder));
string destinationPath;
//file is not inside the mod folder, we need to move it //file is not inside the mod folder, we need to move it
if (filePathRelativeToModFolder.StartsWith("..") || if (filePathRelativeToModFolder.StartsWith("..") ||
Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file)) Path.GetPathRoot(Environment.CurrentDirectory) != Path.GetPathRoot(file))
{ {
destinationPath = Path.Combine(modFolder, Path.GetFileName(file)); string destinationPath = Path.Combine(modFolder, Path.GetFileName(file));
//add a number to the filename if a file with the same name already exists //add a number to the filename if a file with the same name already exists
i = 2; i = 2;
while (File.Exists(destinationPath)) while (File.Exists(destinationPath))
@@ -1584,10 +1591,6 @@ namespace Barotrauma
return; return;
} }
} }
else
{
destinationPath = Path.Combine(modFolder, filePathRelativeToModFolder);
}
} }
RefreshCreateItemFileList(); RefreshCreateItemFileList();
} }
@@ -1605,7 +1608,7 @@ namespace Barotrauma
if (itemContentPackage == null) return; if (itemContentPackage == null) return;
var contentTypes = Enum.GetValues(typeof(ContentType)); var contentTypes = Enum.GetValues(typeof(ContentType));
List<ContentFile> files = itemContentPackage.Files.ToList(); List<ContentFile> files = itemContentPackage.FilesUnsaved.ToList();
for (int i = files.Count - 1; i >= 0; i--) for (int i = files.Count - 1; i >= 0; i--)
{ {
@@ -1613,15 +1616,14 @@ namespace Barotrauma
bool fileExists = File.Exists(contentFile.Path); bool fileExists = File.Exists(contentFile.Path);
if (contentFile.Type == ContentType.Executable || if (contentFile.Type == ContentType.ServerExecutable)
contentFile.Type == ContentType.ServerExecutable)
{ {
fileExists |= File.Exists(Path.GetFileNameWithoutExtension(contentFile.Path) + ".dll"); fileExists |= File.Exists(Path.GetFileNameWithoutExtension(contentFile.Path) + ".dll");
} }
if (!fileExists) if (!fileExists)
{ {
itemContentPackage.Files.Remove(contentFile); itemContentPackage.RemoveFile(contentFile);
files.RemoveAt(i); files.RemoveAt(i);
} }
} }
@@ -1653,8 +1655,7 @@ namespace Barotrauma
bool illegalPath = !ContentPackage.IsModFilePathAllowed(contentFile); bool illegalPath = !ContentPackage.IsModFilePathAllowed(contentFile);
bool fileExists = File.Exists(contentFile.Path); bool fileExists = File.Exists(contentFile.Path);
if (contentFile.Type == ContentType.Executable || if (contentFile.Type == ContentType.ServerExecutable)
contentFile.Type == ContentType.ServerExecutable)
{ {
fileExists |= File.Exists(Path.GetFileNameWithoutExtension(contentFile.Path) + ".dll"); fileExists |= File.Exists(Path.GetFileNameWithoutExtension(contentFile.Path) + ".dll");
} }
@@ -1674,7 +1675,7 @@ namespace Barotrauma
var tickBox = new GUITickBox(new RectTransform(Vector2.One, content.RectTransform, scaleBasis: ScaleBasis.BothHeight), "") var tickBox = new GUITickBox(new RectTransform(Vector2.One, content.RectTransform, scaleBasis: ScaleBasis.BothHeight), "")
{ {
Selected = itemContentPackage.Files.Contains(contentFile), Selected = itemContentPackage.FilesUnsaved.Contains(contentFile),
UserData = contentFile UserData = contentFile
}; };
@@ -1683,11 +1684,11 @@ namespace Barotrauma
ContentFile f = tb.UserData as ContentFile; ContentFile f = tb.UserData as ContentFile;
if (tb.Selected) if (tb.Selected)
{ {
if (!itemContentPackage.Files.Contains(f)) { itemContentPackage.Files.Add(f); } if (!itemContentPackage.FilesUnsaved.Contains(f)) { itemContentPackage.AddFile(f); }
} }
else else
{ {
if (itemContentPackage.Files.Contains(f)) { itemContentPackage.Files.Remove(f); } if (itemContentPackage.FilesUnsaved.Contains(f)) { itemContentPackage.RemoveFile(f); }
} }
return true; return true;
@@ -1702,7 +1703,7 @@ namespace Barotrauma
nameText.TextColor = GUI.Style.Red; nameText.TextColor = GUI.Style.Red;
tickBox.ToolTip = TextManager.Get("WorkshopItemFileNotFound"); tickBox.ToolTip = TextManager.Get("WorkshopItemFileNotFound");
} }
else if (illegalPath && !ContentPackage.List.Any(cp => cp.Files.Any(f => Path.GetFullPath(f.Path) == Path.GetFullPath(contentFile.Path)))) else if (illegalPath && !ContentPackage.AllPackages.Any(cp => cp.FilesUnsaved.Any(f => Path.GetFullPath(f.Path) == Path.GetFullPath(contentFile.Path))))
{ {
nameText.TextColor = GUI.Style.Red; nameText.TextColor = GUI.Style.Red;
tickBox.ToolTip = TextManager.Get("WorkshopItemIllegalPath"); tickBox.ToolTip = TextManager.Get("WorkshopItemIllegalPath");
@@ -1839,7 +1840,10 @@ namespace Barotrauma
{ {
new GUIMessageBox( new GUIMessageBox(
TextManager.Get("Error"), TextManager.Get("Error"),
TextManager.GetWithVariable("WorkshopItemPublishFailed", "[itemname]", item?.Title) + " Task ended with status "+workshopPublishStatus?.TaskStatus?.ToString()); TextManager.GetWithVariable("WorkshopItemPublishFailed", "[itemname]", item?.Title) +
(workshopPublishStatus?.TaskStatus != null ?
" Task ended with status " +workshopPublishStatus?.TaskStatus?.ToString() :
" Publish failed with result "+ workshopPublishStatus.Result?.Result.ToString()));
} }
else else
{ {

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