Merge remote-tracking branch 'origin/dev'
# Conflicts: # Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs # Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs # Barotrauma/BarotraumaClient/ClientSource/StatusEffects/StatusEffect.cs # Barotrauma/BarotraumaClient/LinuxClient.csproj # Barotrauma/BarotraumaClient/MacClient.csproj # Barotrauma/BarotraumaClient/WindowsClient.csproj # Barotrauma/BarotraumaServer/LinuxServer.csproj # Barotrauma/BarotraumaServer/MacServer.csproj # Barotrauma/BarotraumaServer/ServerSource/GameSession/GameModes/CampaignMode.cs # Barotrauma/BarotraumaServer/WindowsServer.csproj # Barotrauma/BarotraumaShared/SharedSource/Map/Map/LocationType.cs # Barotrauma/BarotraumaShared/SharedSource/Networking/ChildServerRelay.cs # Barotrauma/BarotraumaShared/SharedSource/StatusEffects/StatusEffect.cs
This commit is contained in:
@@ -175,11 +175,11 @@ namespace Barotrauma
|
|||||||
position += amount;
|
position += amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientWrite(IWriteMessage msg)
|
public void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
|
||||||
{
|
{
|
||||||
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
|
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
|
||||||
|
|
||||||
msg.WriteByte((byte)ClientNetObject.SPECTATING_POS);
|
segmentTableWriter.StartNewSegment(ClientNetSegment.SpectatingPos);
|
||||||
msg.WriteSingle(position.X);
|
msg.WriteSingle(position.X);
|
||||||
msg.WriteSingle(position.Y);
|
msg.WriteSingle(position.Y);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ namespace Barotrauma
|
|||||||
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
|
GUI.DrawRectangle(spriteBatch, wallTargetPos - new Vector2(10.0f, 10.0f), new Vector2(20.0f, 20.0f), Color.Orange, false);
|
||||||
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
|
GUI.DrawLine(spriteBatch, pos, wallTargetPos, Color.Orange * 0.5f, 0, 5);
|
||||||
}
|
}
|
||||||
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity} ({GetTargetMemory(SelectedAiTarget, false)?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
|
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 60.0f, $"{SelectedAiTarget.Entity}", GUIStyle.Red, Color.Black);
|
||||||
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"({targetValue.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
|
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 40.0f, $"{targetValue.FormatZeroDecimal()} (M: {SelectedTargetMemory?.Priority.FormatZeroDecimal()}, P: {SelectedTargetingParams?.Priority.FormatZeroDecimal()})", GUIStyle.Red, Color.Black);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red);
|
/*GUIStyle.Font.DrawString(spriteBatch, targetValue.ToString(), pos - Vector2.UnitY * 80.0f, GUIStyle.Red);
|
||||||
|
|||||||
@@ -442,8 +442,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (Limb limb in Limbs)
|
foreach (Limb limb in Limbs)
|
||||||
{
|
{
|
||||||
if (limb == null || limb.IsSevered || limb.ActiveSprite == null) { continue; }
|
if (limb == null || limb.IsSevered || limb.ActiveSprite == null || !limb.DoesFlip) { continue; }
|
||||||
|
|
||||||
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
|
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
|
||||||
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
|
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
|
||||||
limb.ActiveSprite.Origin = spriteOrigin;
|
limb.ActiveSprite.Origin = spriteOrigin;
|
||||||
@@ -468,7 +467,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var damageSound = character.GetSound(s => s.Type == CharacterSound.SoundType.Damage);
|
var damageSound = character.GetSound(s => s.Type == CharacterSound.SoundType.Damage);
|
||||||
float range = damageSound != null ? damageSound.Range * 2 : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize().Length() * 10);
|
float range = damageSound != null ? damageSound.Range * 2 : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize().Length() * 10);
|
||||||
SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range);
|
if (!limbJoint.Params.BreakSound.IsNullOrEmpty() && !limbJoint.Params.BreakSound.Equals("none", StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (sound != null)
|
if (sound != null)
|
||||||
{
|
{
|
||||||
SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling);
|
SoundPlayer.PlaySound(sound.Sound, worldPosition, sound.Volume, sound.Range, ignoreMuffling: sound.IgnoreMuffling, freqMult: sound.GetRandomFrequencyMultiplier());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,6 +109,22 @@ namespace Barotrauma
|
|||||||
set => grainStrength = Math.Max(0, value);
|
set => grainStrength = Math.Max(0, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Can be used to set camera shake from status effects
|
||||||
|
/// </summary>
|
||||||
|
public float CameraShake
|
||||||
|
{
|
||||||
|
get { return Screen.Selected?.Cam?.Shake ?? 0.0f; }
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (!MathUtils.IsValid(value)) { return; }
|
||||||
|
if (Screen.Selected?.Cam != null)
|
||||||
|
{
|
||||||
|
Screen.Selected.Cam.Shake = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
|
private readonly List<ParticleEmitter> bloodEmitters = new List<ParticleEmitter>();
|
||||||
public IEnumerable<ParticleEmitter> BloodEmitters
|
public IEnumerable<ParticleEmitter> BloodEmitters
|
||||||
{
|
{
|
||||||
@@ -637,18 +653,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
partial void UpdateProjSpecific(float deltaTime, Camera cam)
|
||||||
{
|
{
|
||||||
if (InvisibleTimer > 0.0f)
|
|
||||||
{
|
|
||||||
if (Controlled == null || Controlled == this || (Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
|
|
||||||
{
|
|
||||||
InvisibleTimer = 0.0f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
InvisibleTimer -= deltaTime;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (GUIMessage message in guiMessages)
|
foreach (GUIMessage message in guiMessages)
|
||||||
{
|
{
|
||||||
bool wasPending = message.Timer < 0.0f;
|
bool wasPending = message.Timer < 0.0f;
|
||||||
@@ -965,7 +969,24 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (IsDead) { return; }
|
if (IsDead) { return; }
|
||||||
|
|
||||||
|
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
|
||||||
|
if (healthBarMode != EnemyHealthBarMode.ShowAll)
|
||||||
|
{
|
||||||
|
if (Controlled == null)
|
||||||
|
{
|
||||||
|
if (!IsOnPlayerTeam) { return; }
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!HumanAIController.IsFriendly(Controlled, this) ||
|
||||||
|
(AIController is HumanAIController humanAi && humanAi.ObjectiveManager.CurrentObjective is AIObjectiveCombat combatObjective && HumanAIController.IsFriendly(Controlled, combatObjective.Enemy)))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
|
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
|
||||||
{
|
{
|
||||||
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
|
hudInfoAlpha = Math.Max(hudInfoAlpha, Math.Min(CharacterHealth.DamageOverlayTimer, 1.0f));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Barotrauma.Tutorials;
|
|
||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
@@ -10,7 +9,7 @@ using System.Linq;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
class CharacterHUD
|
partial class CharacterHUD
|
||||||
{
|
{
|
||||||
const float BossHealthBarDuration = 120.0f;
|
const float BossHealthBarDuration = 120.0f;
|
||||||
|
|
||||||
@@ -100,8 +99,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool ShouldRecreateHudTexts { get; set; } = true;
|
public static bool RecreateHudTexts { get; set; } = true;
|
||||||
private static bool heldDownShiftWhenGotHudTexts;
|
private static bool lastHudTextsContextual;
|
||||||
|
private static float timeHealthWindowClosed;
|
||||||
|
|
||||||
public static bool IsCampaignInterfaceOpen =>
|
public static bool IsCampaignInterfaceOpen =>
|
||||||
GameMain.GameSession?.Campaign != null &&
|
GameMain.GameSession?.Campaign != null &&
|
||||||
@@ -114,7 +114,7 @@ namespace Barotrauma
|
|||||||
return
|
return
|
||||||
character?.Inventory != null &&
|
character?.Inventory != null &&
|
||||||
!character.Removed && !character.IsKnockedDown &&
|
!character.Removed && !character.IsKnockedDown &&
|
||||||
(controller?.User != character || !controller.HideHUD) &&
|
(controller?.User != character || !controller.HideHUD || Screen.Selected.IsEditor) &&
|
||||||
!IsCampaignInterfaceOpen &&
|
!IsCampaignInterfaceOpen &&
|
||||||
!ConversationAction.FadeScreenToBlack;
|
!ConversationAction.FadeScreenToBlack;
|
||||||
}
|
}
|
||||||
@@ -175,7 +175,8 @@ namespace Barotrauma
|
|||||||
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
|
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null && Screen.Selected != GameMain.SubEditorScreen)
|
||||||
{
|
{
|
||||||
bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
|
bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
|
||||||
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
|
bool healthWindowOpen = CharacterHealth.OpenHealthWindow != null || timeHealthWindowClosed < 0.2f;
|
||||||
|
if (mouseOnPortrait && !healthWindowOpen && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
|
||||||
{
|
{
|
||||||
CharacterHealth.OpenHealthWindow = character.CharacterHealth;
|
CharacterHealth.OpenHealthWindow = character.CharacterHealth;
|
||||||
}
|
}
|
||||||
@@ -217,7 +218,7 @@ namespace Barotrauma
|
|||||||
if (focusedItemOverlayTimer <= 0.0f)
|
if (focusedItemOverlayTimer <= 0.0f)
|
||||||
{
|
{
|
||||||
focusedItem = null;
|
focusedItem = null;
|
||||||
ShouldRecreateHudTexts = true;
|
RecreateHudTexts = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,6 +244,15 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (CharacterHealth.OpenHealthWindow != null)
|
||||||
|
{
|
||||||
|
timeHealthWindowClosed = 0.0f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
timeHealthWindowClosed += deltaTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
|
public static void Draw(SpriteBatch spriteBatch, Character character, Camera cam)
|
||||||
@@ -307,7 +317,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (!brokenItem.IsInteractable(character)) { continue; }
|
if (!brokenItem.IsInteractable(character)) { continue; }
|
||||||
float alpha = GetDistanceBasedIconAlpha(brokenItem);
|
float alpha = GetDistanceBasedIconAlpha(brokenItem);
|
||||||
if (alpha <= 0.0f) continue;
|
if (alpha <= 0.0f) { continue; }
|
||||||
GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite,
|
GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite,
|
||||||
Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
|
Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
|
||||||
}
|
}
|
||||||
@@ -330,7 +340,7 @@ namespace Barotrauma
|
|||||||
if (focusedItem != character.FocusedItem)
|
if (focusedItem != character.FocusedItem)
|
||||||
{
|
{
|
||||||
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
|
focusedItemOverlayTimer = Math.Min(1.0f, focusedItemOverlayTimer);
|
||||||
ShouldRecreateHudTexts = true;
|
RecreateHudTexts = true;
|
||||||
}
|
}
|
||||||
focusedItem = character.FocusedItem;
|
focusedItem = character.FocusedItem;
|
||||||
}
|
}
|
||||||
@@ -354,14 +364,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
|
if (!GUI.DisableItemHighlights && !Inventory.DraggingItemToWorld)
|
||||||
{
|
{
|
||||||
bool shiftDown = PlayerInput.IsShiftDown();
|
bool hudTextsContextual = PlayerInput.IsShiftDown();
|
||||||
if (ShouldRecreateHudTexts || heldDownShiftWhenGotHudTexts != shiftDown)
|
if (RecreateHudTexts || lastHudTextsContextual != hudTextsContextual)
|
||||||
{
|
{
|
||||||
ShouldRecreateHudTexts = true;
|
RecreateHudTexts = true;
|
||||||
heldDownShiftWhenGotHudTexts = shiftDown;
|
lastHudTextsContextual = hudTextsContextual;
|
||||||
}
|
}
|
||||||
var hudTexts = focusedItem.GetHUDTexts(character, ShouldRecreateHudTexts);
|
var hudTexts = focusedItem.GetHUDTexts(character, RecreateHudTexts);
|
||||||
ShouldRecreateHudTexts = false;
|
RecreateHudTexts = false;
|
||||||
|
|
||||||
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
|
int dir = Math.Sign(focusedItem.WorldPosition.X - character.WorldPosition.X);
|
||||||
|
|
||||||
@@ -492,7 +502,17 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
var item = character.Inventory.GetItemAt(i);
|
var item = character.Inventory.GetItemAt(i);
|
||||||
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
|
if (item == null || character.Inventory.SlotTypes[i] == InvSlotType.Any) { continue; }
|
||||||
|
//if the item is also equipped in another slot we already went through, don't draw the hud again
|
||||||
|
bool duplicateFound = false;
|
||||||
|
for (int j = 0; j < i; j++)
|
||||||
|
{
|
||||||
|
if (character.Inventory.SlotTypes[j] != InvSlotType.Any && character.Inventory.GetItemAt(j) == item)
|
||||||
|
{
|
||||||
|
duplicateFound = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (duplicateFound) { continue; }
|
||||||
foreach (ItemComponent ic in item.Components)
|
foreach (ItemComponent ic in item.Components)
|
||||||
{
|
{
|
||||||
if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
|
if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
|
||||||
@@ -548,7 +568,7 @@ namespace Barotrauma
|
|||||||
if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
|
if (CharacterHealth.OpenHealthWindow == character.SelectedCharacter.CharacterHealth)
|
||||||
{
|
{
|
||||||
character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
|
character.SelectedCharacter.CharacterHealth.Alignment = Alignment.Left;
|
||||||
character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
|
//character.SelectedCharacter.CharacterHealth.DrawStatusHUD(spriteBatch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (character.Inventory != null)
|
else if (character.Inventory != null)
|
||||||
@@ -644,6 +664,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (character == null || character.IsDead || character.Removed) { return; }
|
if (character == null || character.IsDead || character.Removed) { return; }
|
||||||
|
|
||||||
|
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
|
||||||
|
if (healthBarMode == EnemyHealthBarMode.HideAll)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
var existingBar = bossHealthBars.Find(b => b.Character == character);
|
var existingBar = bossHealthBars.Find(b => b.Character == character);
|
||||||
if (existingBar != null)
|
if (existingBar != null)
|
||||||
{
|
{
|
||||||
@@ -669,6 +695,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void UpdateBossHealthBars(float deltaTime)
|
public static void UpdateBossHealthBars(float deltaTime)
|
||||||
{
|
{
|
||||||
|
var healthBarMode = GameMain.NetworkMember?.ServerSettings.ShowEnemyHealthBars ?? GameSettings.CurrentConfig.ShowEnemyHealthBars;
|
||||||
|
|
||||||
for (int i = 0; i < bossHealthBars.Count; i++)
|
for (int i = 0; i < bossHealthBars.Count; i++)
|
||||||
{
|
{
|
||||||
var bossHealthBar = bossHealthBars[i];
|
var bossHealthBar = bossHealthBars[i];
|
||||||
@@ -710,7 +738,7 @@ namespace Barotrauma
|
|||||||
for (int i = bossHealthBars.Count - 1; i >= 0 ; i--)
|
for (int i = bossHealthBars.Count - 1; i >= 0 ; i--)
|
||||||
{
|
{
|
||||||
var bossHealthBar = bossHealthBars[i];
|
var bossHealthBar = bossHealthBars[i];
|
||||||
if (bossHealthBar.FadeTimer <= 0)
|
if (bossHealthBar.FadeTimer <= 0 || healthBarMode == EnemyHealthBarMode.HideAll)
|
||||||
{
|
{
|
||||||
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
|
bossHealthBar.SideContainer.Parent?.RemoveChild(bossHealthBar.SideContainer);
|
||||||
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
|
bossHealthBar.TopContainer.Parent?.RemoveChild(bossHealthBar.TopContainer);
|
||||||
@@ -762,5 +790,25 @@ namespace Barotrauma
|
|||||||
Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
|
Vector2 drawPos = objectiveEntity.Entity.WorldPosition;// + Vector2.UnitX * objectiveEntity.Sprite.size.X * 1.5f;
|
||||||
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
|
GUI.DrawIndicator(spriteBatch, drawPos, cam, 100.0f, objectiveEntity.Sprite, objectiveEntity.Color * iconAlpha);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static partial void RecreateHudTextsIfControllingProjSpecific(Character character)
|
||||||
|
{
|
||||||
|
if (character == Character.Controlled)
|
||||||
|
{
|
||||||
|
RecreateHudTexts = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static partial void RecreateHudTextsIfFocusedProjSpecific(params Item[] items)
|
||||||
|
{
|
||||||
|
foreach (var item in items)
|
||||||
|
{
|
||||||
|
if (item == Character.Controlled?.FocusedItem)
|
||||||
|
{
|
||||||
|
RecreateHudTexts = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ namespace Barotrauma
|
|||||||
Color? nameColor = null;
|
Color? nameColor = null;
|
||||||
if (Job != null) { nameColor = Job.Prefab.UIColor; }
|
if (Job != null) { nameColor = Job.Prefab.UIColor; }
|
||||||
|
|
||||||
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), ToolBox.LimitString(Name, GUIStyle.Font, headerTextArea.Rect.Width), textColor: nameColor, font: GUIStyle.Font)
|
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform), ToolBox.LimitString(Name, GUIStyle.Font, headerTextArea.Rect.Width), textColor: nameColor, font: GUIStyle.Font)
|
||||||
{
|
{
|
||||||
ForceUpperCase = ForceUpperCase.Yes,
|
ForceUpperCase = ForceUpperCase.Yes,
|
||||||
Padding = Vector4.Zero
|
Padding = Vector4.Zero
|
||||||
@@ -92,8 +92,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (Job != null)
|
if (Job != null)
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform), Job.Name, textColor: Job.Prefab.UIColor, font: font)
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform), Job.Name, textColor: Job.Prefab.UIColor, font: font)
|
||||||
{
|
{
|
||||||
Padding = Vector4.Zero
|
Padding = Vector4.Zero
|
||||||
};
|
};
|
||||||
@@ -101,7 +101,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (PersonalityTrait != null)
|
if (PersonalityTrait != null)
|
||||||
{
|
{
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), headerTextArea.RectTransform),
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform),
|
||||||
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName),
|
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName),
|
||||||
font: font)
|
font: font)
|
||||||
{
|
{
|
||||||
@@ -109,7 +109,23 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Job != null && (Character == null || !Character.IsDead))
|
GUIButton manageTalentButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), headerTextArea.RectTransform),
|
||||||
|
text: TextManager.Get("ClientPermission.ManageBotTalents"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
Enabled = false,
|
||||||
|
UserData = TalentMenu.ManageBotTalentsButtonUserData,
|
||||||
|
TextBlock =
|
||||||
|
{
|
||||||
|
AutoScaleHorizontal = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (TalentMenu.CanManageTalents(this))
|
||||||
|
{
|
||||||
|
manageTalentButton.Enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Job != null && Character is not { IsDead: true })
|
||||||
{
|
{
|
||||||
var skillsArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
|
var skillsArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
|
||||||
{
|
{
|
||||||
@@ -120,7 +136,7 @@ namespace Barotrauma
|
|||||||
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
|
skills.Sort((s1, s2) => -s1.Level.CompareTo(s2.Level));
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero };
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillsArea.RectTransform), TextManager.AddPunctuation(':', TextManager.Get("skills"), string.Empty), font: font) { Padding = Vector4.Zero };
|
||||||
|
|
||||||
foreach (Skill skill in skills)
|
foreach (Skill skill in skills)
|
||||||
{
|
{
|
||||||
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
Color textColor = Color.White * (0.5f + skill.Level / 200.0f);
|
||||||
@@ -144,7 +160,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Character != null && Character.IsDead)
|
else if (Character is { IsDead: true })
|
||||||
{
|
{
|
||||||
var deadArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
|
var deadArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.63f), paddedFrame.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -113,9 +113,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientWriteInput(IWriteMessage msg)
|
public void ClientWriteInput(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ClientNetObject.CHARACTER_INPUT);
|
segmentTableWriter.StartNewSegment(ClientNetSegment.CharacterInput);
|
||||||
|
|
||||||
if (memInput.Count > 60)
|
if (memInput.Count > 60)
|
||||||
{
|
{
|
||||||
@@ -501,7 +501,7 @@ namespace Barotrauma
|
|||||||
info?.ClearSavedStatValues(statType);
|
info?.ClearSavedStatValues(statType);
|
||||||
for (int i = 0; i < savedStatValueCount; i++)
|
for (int i = 0; i < savedStatValueCount; i++)
|
||||||
{
|
{
|
||||||
string statIdentifier = msg.ReadString();
|
Identifier statIdentifier = msg.ReadIdentifier();
|
||||||
float statValue = msg.ReadSingle();
|
float statValue = msg.ReadSingle();
|
||||||
bool removeOnDeath = msg.ReadBoolean();
|
bool removeOnDeath = msg.ReadBoolean();
|
||||||
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
|
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
|
||||||
|
|||||||
@@ -32,12 +32,6 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
|
public static Sprite DamageOverlay => DamageOverlayPrefab.Prefabs.ActivePrefab.DamageOverlay;
|
||||||
|
|
||||||
private readonly static LocalizedString[] strengthTexts = new LocalizedString[]
|
|
||||||
{
|
|
||||||
TextManager.Get("AfflictionStrengthLow"),
|
|
||||||
TextManager.Get("AfflictionStrengthMedium"),
|
|
||||||
TextManager.Get("AfflictionStrengthHigh")
|
|
||||||
};
|
|
||||||
|
|
||||||
private Point screenResolution;
|
private Point screenResolution;
|
||||||
|
|
||||||
@@ -89,11 +83,23 @@ namespace Barotrauma
|
|||||||
private int selectedLimbIndex = -1;
|
private int selectedLimbIndex = -1;
|
||||||
private LimbHealth currentDisplayedLimb;
|
private LimbHealth currentDisplayedLimb;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container for the icons above the health bar
|
||||||
|
/// </summary>
|
||||||
|
private GUIComponent afflictionIconContainer;
|
||||||
|
|
||||||
|
private GUIButton showHiddenAfflictionsButton;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Container for passive afflictions that have been hidden from afflictionIconContainer
|
||||||
|
/// </summary>
|
||||||
|
private GUIComponent hiddenAfflictionIconContainer;
|
||||||
|
|
||||||
private GUIProgressBar healthWindowHealthBar;
|
private GUIProgressBar healthWindowHealthBar;
|
||||||
private GUIProgressBar healthWindowHealthBarShadow;
|
private GUIProgressBar healthWindowHealthBarShadow;
|
||||||
|
|
||||||
private GUITextBlock characterName;
|
private GUITextBlock characterName;
|
||||||
private GUIListBox afflictionIconContainer;
|
private GUIListBox afflictionIconList;
|
||||||
private GUILayoutGroup treatmentLayout;
|
private GUILayoutGroup treatmentLayout;
|
||||||
private GUIListBox recommendedTreatmentContainer;
|
private GUIListBox recommendedTreatmentContainer;
|
||||||
|
|
||||||
@@ -150,7 +156,7 @@ namespace Barotrauma
|
|||||||
Character.Controlled.DeselectCharacter();
|
Character.Controlled.DeselectCharacter();
|
||||||
}
|
}
|
||||||
|
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
if (openHealthWindow != null)
|
if (openHealthWindow != null)
|
||||||
{
|
{
|
||||||
if (value.Character.Info == null || value.Character == Character.Controlled || Character.Controlled.HasEquippedItem("healthscanner".ToIdentifier()))
|
if (value.Character.Info == null || value.Character == Character.Controlled || Character.Controlled.HasEquippedItem("healthscanner".ToIdentifier()))
|
||||||
@@ -331,7 +337,7 @@ namespace Barotrauma
|
|||||||
deadIndicator.AutoScaleHorizontal = true;
|
deadIndicator.AutoScaleHorizontal = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
afflictionIconContainer = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null);
|
afflictionIconList = new GUIListBox(new RectTransform(new Vector2(0.25f, 1.0f), characterIndicatorArea.RectTransform), style: null);
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform),
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.1f), healthWindowVerticalLayout.RectTransform),
|
||||||
TextManager.Get("SuitableTreatments"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter);
|
TextManager.Get("SuitableTreatments"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomCenter);
|
||||||
@@ -379,6 +385,26 @@ namespace Barotrauma
|
|||||||
Enabled = true
|
Enabled = true
|
||||||
};
|
};
|
||||||
|
|
||||||
|
afflictionIconContainer = new GUILayoutGroup(
|
||||||
|
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
|
||||||
|
isHorizontal: true, childAnchor: Anchor.CenterRight)
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = GUI.IntScale(5)
|
||||||
|
};
|
||||||
|
|
||||||
|
showHiddenAfflictionsButton = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: "GUIButtonCircular")
|
||||||
|
{
|
||||||
|
Visible = false,
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
|
||||||
|
hiddenAfflictionIconContainer = new GUILayoutGroup(
|
||||||
|
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.HealthBarAfflictionArea, GUI.Canvas),
|
||||||
|
isHorizontal: true, childAnchor: Anchor.CenterRight)
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = GUI.IntScale(5)
|
||||||
|
};
|
||||||
|
|
||||||
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)
|
||||||
@@ -596,7 +622,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void UpdateHUD(float deltaTime)
|
public void UpdateHUD(float deltaTime)
|
||||||
{
|
{
|
||||||
if (GUI.DisableHUD) return;
|
if (GUI.DisableHUD) { return; }
|
||||||
if (openHealthWindow != null)
|
if (openHealthWindow != null)
|
||||||
{
|
{
|
||||||
if (openHealthWindow != Character.Controlled?.CharacterHealth && openHealthWindow != Character.Controlled?.SelectedCharacter?.CharacterHealth)
|
if (openHealthWindow != Character.Controlled?.CharacterHealth && openHealthWindow != Character.Controlled?.SelectedCharacter?.CharacterHealth)
|
||||||
@@ -700,6 +726,8 @@ namespace Barotrauma
|
|||||||
distortTimer = 0.0f;
|
distortTimer = 0.0f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UpdateStatusHUD(deltaTime);
|
||||||
|
|
||||||
if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null &&
|
if (PlayerInput.KeyHit(InputType.Health) && GUI.KeyboardDispatcher.Subscriber == null &&
|
||||||
Character.Controlled.AllowInput && !toggledThisFrame)
|
Character.Controlled.AllowInput && !toggledThisFrame)
|
||||||
{
|
{
|
||||||
@@ -726,9 +754,9 @@ namespace Barotrauma
|
|||||||
OpenHealthWindow = null;
|
OpenHealthWindow = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (GUIComponent afflictionIcon in afflictionIconContainer.Content.Children)
|
foreach (GUIComponent afflictionIcon in afflictionIconList.Content.Children)
|
||||||
{
|
{
|
||||||
if (!(afflictionIcon.UserData is Affliction affliction)) { continue; }
|
if (afflictionIcon.UserData is not Affliction affliction) { continue; }
|
||||||
if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
|
if (affliction.AppliedAsFailedTreatmentTime > Timing.TotalTime - 1.0 && afflictionIcon.FlashTimer <= 0.0f)
|
||||||
{
|
{
|
||||||
afflictionIcon.Flash(GUIStyle.Red);
|
afflictionIcon.Flash(GUIStyle.Red);
|
||||||
@@ -900,7 +928,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
|
healthBarHolder.CanBeFocused = healthBar.CanBeFocused = healthBarShadow.CanBeFocused = !Character.ShouldLockHud();
|
||||||
if (Character.AllowInput && UseHealthWindow && !Character.DisableHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
|
if (Character.AllowInput && UseHealthWindow && !Character.DisableHealthWindow && healthBar.Enabled && healthBar.CanBeFocused &&
|
||||||
(GUI.IsMouseOn(healthBar) || highlightedAfflictionIcon != null) && Inventory.SelectedSlot == null)
|
(GUI.IsMouseOn(healthBar) || GUI.MouseOn?.UserData is AfflictionPrefab) && Inventory.SelectedSlot == null)
|
||||||
{
|
{
|
||||||
healthBar.State = GUIComponent.ComponentState.Hover;
|
healthBar.State = GUIComponent.ComponentState.Hover;
|
||||||
if (PlayerInput.PrimaryMouseButtonClicked())
|
if (PlayerInput.PrimaryMouseButtonClicked())
|
||||||
@@ -960,7 +988,12 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen)
|
else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen)
|
||||||
{
|
{
|
||||||
healthBarHolder.AddToGUIUpdateList();
|
healthBarHolder.AddToGUIUpdateList();
|
||||||
|
afflictionIconContainer.AddToGUIUpdateList();
|
||||||
|
if (hiddenAfflictionIconContainer.Visible)
|
||||||
|
{
|
||||||
|
hiddenAfflictionIconContainer.AddToGUIUpdateList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (SuicideButton.Visible && Character == Character.Controlled)
|
if (SuicideButton.Visible && Character == Character.Controlled)
|
||||||
{
|
{
|
||||||
@@ -989,7 +1022,7 @@ namespace Barotrauma
|
|||||||
if (affliction.Prefab.AfflictionOverlay != null)
|
if (affliction.Prefab.AfflictionOverlay != null)
|
||||||
{
|
{
|
||||||
Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay;
|
Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay;
|
||||||
ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * (affliction.GetAfflictionOverlayMultiplier()), Vector2.Zero, 0.0f,
|
ScreenAfflictionOverlay?.Draw(spriteBatch, Vector2.Zero, Color.White * affliction.GetAfflictionOverlayMultiplier(), Vector2.Zero, 0.0f,
|
||||||
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
|
new Vector2(GameMain.GraphicsWidth / DamageOverlay.size.X, GameMain.GraphicsHeight / DamageOverlay.size.Y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1021,94 +1054,134 @@ namespace Barotrauma
|
|||||||
// If manning a turret the portrait doesn't get rendered so we push the health bar to remove the empty gap
|
// If manning a turret the portrait doesn't get rendered so we push the health bar to remove the empty gap
|
||||||
healthBarHolder.RectTransform.ScreenSpaceOffset = Character.ShouldLockHud() ? new Point(0, HUDLayoutSettings.PortraitArea.Height) : Point.Zero;
|
healthBarHolder.RectTransform.ScreenSpaceOffset = Character.ShouldLockHud() ? new Point(0, HUDLayoutSettings.PortraitArea.Height) : Point.Zero;
|
||||||
}
|
}
|
||||||
|
|
||||||
DrawStatusHUD(spriteBatch);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private (Affliction Affliction, LocalizedString NameToolTip)? highlightedAfflictionIcon = null;
|
//private (Affliction Affliction, LocalizedString NameToolTip)? highlightedAfflictionIcon = null;
|
||||||
public void DrawStatusHUD(SpriteBatch spriteBatch)
|
|
||||||
|
private readonly List<Affliction> statusIcons = new List<Affliction>();
|
||||||
|
private readonly Dictionary<AfflictionPrefab, float> statusIconVisibleTime = new Dictionary<AfflictionPrefab, float>();
|
||||||
|
private const float HideStatusIconDelay = 5.0f;
|
||||||
|
|
||||||
|
public void UpdateStatusHUD(float deltaTime)
|
||||||
{
|
{
|
||||||
highlightedAfflictionIcon = null;
|
|
||||||
//Rectangle interactArea = healthBar.Rect;
|
|
||||||
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
|
if (Character.Controlled?.SelectedCharacter == null && openHealthWindow == null)
|
||||||
{
|
{
|
||||||
var statusIcons = new List<(Affliction Affliction, LocalizedString Warning)>();
|
statusIcons.Clear();
|
||||||
if (Character.InPressure)
|
if (Character.InPressure)
|
||||||
{
|
{
|
||||||
statusIcons.Add((pressureAffliction, TextManager.Get("PressureHUDWarning")));
|
statusIcons.Add(pressureAffliction);
|
||||||
}
|
}
|
||||||
if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold)
|
if (Character.CurrentHull != null && Character.OxygenAvailable < LowOxygenThreshold && oxygenLowAffliction.Strength < oxygenLowAffliction.Prefab.ShowIconThreshold)
|
||||||
{
|
{
|
||||||
statusIcons.Add((oxygenLowAffliction, TextManager.Get("OxygenHUDWarning")));
|
statusIcons.Add(oxygenLowAffliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (Affliction affliction in currentDisplayedAfflictions)
|
foreach (Affliction affliction in currentDisplayedAfflictions)
|
||||||
{
|
{
|
||||||
statusIcons.Add((affliction, affliction.Prefab.Name));
|
statusIcons.Add(affliction);
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 highlightedIconPos = Vector2.Zero;
|
int spacing = GUI.IntScale(10);
|
||||||
Rectangle afflictionArea = HUDLayoutSettings.AfflictionAreaLeft;
|
|
||||||
|
|
||||||
// Push the icons down since the portrait doesn't get rendered
|
|
||||||
if (Character.ShouldLockHud())
|
if (Character.ShouldLockHud())
|
||||||
{
|
{
|
||||||
afflictionArea.Y += HUDLayoutSettings.PortraitArea.Height;
|
// Push the icons down since the portrait doesn't get rendered
|
||||||
|
afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, HUDLayoutSettings.PortraitArea.Height);
|
||||||
|
hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing + HUDLayoutSettings.PortraitArea.Height);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
afflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, 0);
|
||||||
|
hiddenAfflictionIconContainer.RectTransform.ScreenSpaceOffset = new Point(0, -hiddenAfflictionIconContainer.Rect.Height - spacing);
|
||||||
|
}
|
||||||
|
//remove affliction icons for afflictions that no longer exist
|
||||||
|
|
||||||
bool horizontal = afflictionArea.Width > afflictionArea.Height;
|
RemoveNonExistentIcons(afflictionIconContainer);
|
||||||
int iconSize = horizontal ? afflictionArea.Height : afflictionArea.Width;
|
RemoveNonExistentIcons(hiddenAfflictionIconContainer);
|
||||||
|
void RemoveNonExistentIcons(GUIComponent container)
|
||||||
Point pos = new Point(afflictionArea.Right - iconSize, afflictionArea.Top);
|
{
|
||||||
|
for (int i = container.CountChildren - 1; i >= 0; i--)
|
||||||
|
{
|
||||||
|
var child = container.GetChild(i);
|
||||||
|
if (child.UserData is not AfflictionPrefab afflictionPrefab) { continue; }
|
||||||
|
if (!statusIcons.Any(s => s.Prefab == afflictionPrefab))
|
||||||
|
{
|
||||||
|
container.RemoveChild(child);
|
||||||
|
statusIconVisibleTime.Remove(afflictionPrefab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var statusIcon in statusIcons)
|
foreach (var statusIcon in statusIcons)
|
||||||
{
|
{
|
||||||
Affliction affliction = statusIcon.Affliction;
|
Affliction affliction = statusIcon;
|
||||||
AfflictionPrefab afflictionPrefab = affliction.Prefab;
|
AfflictionPrefab afflictionPrefab = affliction.Prefab;
|
||||||
|
|
||||||
Rectangle afflictionIconRect = new Rectangle(pos, new Point(iconSize));
|
if (!statusIconVisibleTime.ContainsKey(afflictionPrefab)) { statusIconVisibleTime.Add(afflictionPrefab, 0.0f); }
|
||||||
if (afflictionIconRect.Contains(PlayerInput.MousePosition) && !Character.ShouldLockHud() && GUI.MouseOn == null)
|
statusIconVisibleTime[afflictionPrefab] += deltaTime;
|
||||||
|
|
||||||
|
var matchingIcon =
|
||||||
|
afflictionIconContainer.GetChildByUserData(afflictionPrefab) ??
|
||||||
|
hiddenAfflictionIconContainer.GetChildByUserData(afflictionPrefab);
|
||||||
|
if (matchingIcon == null)
|
||||||
{
|
{
|
||||||
highlightedAfflictionIcon = statusIcon;
|
matchingIcon = new GUIButton(new RectTransform(new Point(afflictionIconContainer.Rect.Height), afflictionIconContainer.RectTransform), style: null)
|
||||||
highlightedIconPos = afflictionIconRect.Location.ToVector2();
|
{
|
||||||
|
UserData = afflictionPrefab,
|
||||||
|
ToolTip = affliction.Prefab.Name,
|
||||||
|
CanBeSelected = false
|
||||||
|
};
|
||||||
|
if (affliction == pressureAffliction)
|
||||||
|
{
|
||||||
|
matchingIcon.ToolTip = TextManager.Get("PressureHUDWarning");
|
||||||
|
}
|
||||||
|
else if (affliction == pressureAffliction)
|
||||||
|
{
|
||||||
|
matchingIcon.ToolTip = TextManager.Get("OxygenHUDWarning");
|
||||||
|
}
|
||||||
|
new GUIImage(new RectTransform(Vector2.One, matchingIcon.RectTransform, Anchor.BottomCenter), afflictionPrefab.Icon, scaleToFit: true)
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
if (afflictionPrefab.HideIconAfterDelay && statusIconVisibleTime[afflictionPrefab] > HideStatusIconDelay)
|
||||||
if (affliction.DamagePerSecond > 1.0f)
|
|
||||||
{
|
{
|
||||||
Rectangle glowRect = afflictionIconRect;
|
matchingIcon.RectTransform.Parent = hiddenAfflictionIconContainer.RectTransform;
|
||||||
glowRect.Inflate((int)(20 * GUI.Scale), (int)(20 * GUI.Scale));
|
|
||||||
var glow = GUIStyle.GetComponentStyle("OuterGlowCircular");
|
|
||||||
glow.Sprites[GUIComponent.ComponentState.None][0].Draw(
|
|
||||||
spriteBatch, glowRect,
|
|
||||||
GUIStyle.Red * (float)((Math.Sin(affliction.DamagePerSecondTimer * MathHelper.TwoPi - MathHelper.PiOver2) + 1.0f) * 0.5f));
|
|
||||||
}
|
}
|
||||||
|
var image = matchingIcon.GetChild<GUIImage>();
|
||||||
|
image.Color = GetAfflictionIconColor(afflictionPrefab, affliction);
|
||||||
|
image.HoverColor = Color.Lerp(image.Color, Color.White, 0.5f);
|
||||||
|
|
||||||
float alphaMultiplier = highlightedAfflictionIcon == statusIcon ? 1f : 0.8f;
|
if (affliction.DamagePerSecond > 1.0f && matchingIcon.FlashTimer <= 0.0f)
|
||||||
|
{
|
||||||
afflictionPrefab.Icon?.Draw(spriteBatch,
|
matchingIcon.Flash(useCircularFlash: true, flashDuration: 1.5f, flashRectInflate: Vector2.One * 15.0f * GUI.Scale);
|
||||||
pos.ToVector2(),
|
image.Pulsate(Vector2.One, Vector2.One * 1.2f, 1.0f);
|
||||||
/*highlightedIcon == statusIcon ? statusIcon.First.Prefab.IconColor : statusIcon.First.Prefab.IconColor * 0.8f,*/ // OLD IMPLEMENTATION
|
}
|
||||||
GetAfflictionIconColor(afflictionPrefab, affliction) * alphaMultiplier,
|
|
||||||
rotate: 0,
|
|
||||||
scale: iconSize / afflictionPrefab.Icon.size.X);
|
|
||||||
|
|
||||||
if (horizontal)
|
|
||||||
pos.X -= iconSize + (int)(5 * GUI.Scale);
|
|
||||||
else
|
|
||||||
pos.Y += iconSize + (int)(5 * GUI.Scale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (highlightedAfflictionIcon != null)
|
afflictionIconContainer.RectTransform.SortChildren((r1, r2) =>
|
||||||
{
|
{
|
||||||
LocalizedString nameTooltip = highlightedAfflictionIcon.Value.NameToolTip;
|
if (r1.GUIComponent.UserData is not AfflictionPrefab prefab1) { return -1; }
|
||||||
Vector2 offset = GUIStyle.Font.MeasureString(nameTooltip);
|
if (r2.GUIComponent.UserData is not AfflictionPrefab prefab2) { return 1; }
|
||||||
|
var index1 = statusIcons.IndexOf(s => s.Prefab == prefab1);
|
||||||
|
var index2 = statusIcons.IndexOf(s => s.Prefab == prefab2);
|
||||||
|
return index1.CompareTo(index2);
|
||||||
|
});
|
||||||
|
(afflictionIconContainer as GUILayoutGroup).NeedsToRecalculate = true;
|
||||||
|
|
||||||
GUI.DrawString(spriteBatch,
|
Rectangle hiddenAfflictionHoverArea = showHiddenAfflictionsButton.Rect;
|
||||||
alignment == Alignment.Left ? highlightedIconPos + offset : highlightedIconPos - offset,
|
foreach (GUIComponent child in hiddenAfflictionIconContainer.Children)
|
||||||
nameTooltip,
|
{
|
||||||
Color.White * 0.8f, Color.Black * 0.5f);
|
hiddenAfflictionHoverArea = Rectangle.Union(hiddenAfflictionHoverArea, child.Rect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
afflictionIconContainer.Visible = true;
|
||||||
|
hiddenAfflictionIconContainer.Visible =
|
||||||
|
showHiddenAfflictionsButton.Rect.Contains(PlayerInput.MousePosition) ||
|
||||||
|
(hiddenAfflictionIconContainer.Visible && hiddenAfflictionHoverArea.Contains(PlayerInput.MousePosition));
|
||||||
|
showHiddenAfflictionsButton.Visible = hiddenAfflictionIconContainer.CountChildren > 0;
|
||||||
|
showHiddenAfflictionsButton.IgnoreLayoutGroups = !showHiddenAfflictionsButton.Visible;
|
||||||
|
showHiddenAfflictionsButton.Text = $"+{hiddenAfflictionIconContainer.CountChildren}";
|
||||||
|
|
||||||
if (Vitality > 0.0f)
|
if (Vitality > 0.0f)
|
||||||
{
|
{
|
||||||
float currHealth = healthBar.BarSize;
|
float currHealth = healthBar.BarSize;
|
||||||
@@ -1126,6 +1199,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
afflictionIconContainer.Visible = hiddenAfflictionIconContainer.Visible = false;
|
||||||
if (Vitality > 0.0f)
|
if (Vitality > 0.0f)
|
||||||
{
|
{
|
||||||
float currHealth = healthWindowHealthBar.BarSize;
|
float currHealth = healthWindowHealthBar.BarSize;
|
||||||
@@ -1150,18 +1224,20 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
|
public static Color GetAfflictionIconColor(AfflictionPrefab prefab, float afflictionStrength)
|
||||||
{
|
{
|
||||||
|
//use sqrt to make the color change rapidly when strength is low
|
||||||
|
//(low strength is where seeing the severity of the affliction makes more difference - at high strengths the character is already unconscious or dead)
|
||||||
|
float colorT = MathF.Sqrt(afflictionStrength / prefab.MaxStrength);
|
||||||
// No specific colors, use generic
|
// No specific colors, use generic
|
||||||
if (prefab.IconColors == null)
|
if (prefab.IconColors == null)
|
||||||
{
|
{
|
||||||
if (prefab.IsBuff)
|
if (prefab.IsBuff)
|
||||||
{
|
{
|
||||||
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, GUIStyle.BuffColorLow, GUIStyle.BuffColorMedium, GUIStyle.BuffColorHigh);
|
return ToolBox.GradientLerp(colorT, GUIStyle.BuffColorLow, GUIStyle.BuffColorMedium, GUIStyle.BuffColorHigh);
|
||||||
}
|
}
|
||||||
|
|
||||||
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, GUIStyle.DebuffColorLow, GUIStyle.DebuffColorMedium, GUIStyle.DebuffColorHigh);
|
return ToolBox.GradientLerp(colorT, GUIStyle.DebuffColorLow, GUIStyle.DebuffColorMedium, GUIStyle.DebuffColorHigh);
|
||||||
}
|
}
|
||||||
|
return ToolBox.GradientLerp(colorT, prefab.IconColors);
|
||||||
return ToolBox.GradientLerp(afflictionStrength / prefab.MaxStrength, prefab.IconColors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
|
public static Color GetAfflictionIconColor(Affliction affliction) => GetAfflictionIconColor(affliction.Prefab, affliction);
|
||||||
@@ -1172,7 +1248,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (selectedLimb == null)
|
if (selectedLimb == null)
|
||||||
{
|
{
|
||||||
afflictionIconContainer.Content.ClearChildren();
|
afflictionIconList.Content.ClearChildren();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1207,7 +1283,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
|
private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
|
||||||
{
|
{
|
||||||
afflictionIconContainer.ClearChildren();
|
afflictionIconList.ClearChildren();
|
||||||
displayedAfflictions.Clear();
|
displayedAfflictions.Clear();
|
||||||
|
|
||||||
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault();
|
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault();
|
||||||
@@ -1217,7 +1293,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
displayedAfflictions.Add((affliction, affliction.Strength));
|
displayedAfflictions.Add((affliction, affliction.Strength));
|
||||||
|
|
||||||
var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconContainer.Content.RectTransform), style: "ListBoxElement")
|
var frame = new GUIButton(new RectTransform(new Vector2(1.0f, 0.25f), afflictionIconList.Content.RectTransform), style: "ListBoxElement")
|
||||||
{
|
{
|
||||||
UserData = affliction,
|
UserData = affliction,
|
||||||
OnClicked = SelectAffliction
|
OnClicked = SelectAffliction
|
||||||
@@ -1275,7 +1351,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData);
|
buttonToSelect?.OnClicked(buttonToSelect, buttonToSelect.UserData);
|
||||||
afflictionIconContainer.RecalculateChildren();
|
afflictionIconList.RecalculateChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateRecommendedTreatments()
|
private void CreateRecommendedTreatments()
|
||||||
@@ -1386,7 +1462,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
recommendedTreatmentContainer.RecalculateChildren();
|
recommendedTreatmentContainer.RecalculateChildren();
|
||||||
|
|
||||||
afflictionIconContainer.Content.RectTransform.SortChildren((r1, r2) =>
|
afflictionIconList.Content.RectTransform.SortChildren((r1, r2) =>
|
||||||
{
|
{
|
||||||
var first = r1.GUIComponent.UserData as Affliction;
|
var first = r1.GUIComponent.UserData as Affliction;
|
||||||
var second = r2.GUIComponent.UserData as Affliction;
|
var second = r2.GUIComponent.UserData as Affliction;
|
||||||
@@ -1437,7 +1513,10 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
|
var description = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform),
|
||||||
affliction.Prefab.Description, textAlignment: Alignment.TopLeft, wrap: true)
|
affliction.Prefab.GetDescription(
|
||||||
|
affliction.Strength,
|
||||||
|
Character == Character.Controlled ? AfflictionPrefab.Description.TargetType.Self : AfflictionPrefab.Description.TargetType.OtherCharacter),
|
||||||
|
textAlignment: Alignment.TopLeft, wrap: true)
|
||||||
{
|
{
|
||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
@@ -1449,8 +1528,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Point nameDims = new Point(afflictionName.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f));
|
Point nameDims = new Point(afflictionName.Rect.Width, (int)(GUIStyle.LargeFont.Size * 1.5f));
|
||||||
|
|
||||||
afflictionStrength.Text = strengthTexts[
|
afflictionStrength.Text = affliction.GetStrengthText();
|
||||||
MathHelper.Clamp((int)Math.Floor((affliction.Strength / affliction.Prefab.MaxStrength) * strengthTexts.Length), 0, strengthTexts.Length - 1)];
|
|
||||||
|
|
||||||
Vector2 strengthDims = GUIStyle.SubHeadingFont.MeasureString(afflictionStrength.Text);
|
Vector2 strengthDims = GUIStyle.SubHeadingFont.MeasureString(afflictionStrength.Text);
|
||||||
|
|
||||||
@@ -1482,10 +1560,9 @@ namespace Barotrauma
|
|||||||
private bool SelectAffliction(GUIButton button, object userData)
|
private bool SelectAffliction(GUIButton button, object userData)
|
||||||
{
|
{
|
||||||
bool selected = button.Selected;
|
bool selected = button.Selected;
|
||||||
foreach (var child in afflictionIconContainer.Content.Children)
|
foreach (var child in afflictionIconList.Content.Children)
|
||||||
{
|
{
|
||||||
GUIButton btn = child.GetChild<GUIButton>();
|
if (child is GUIButton btn)
|
||||||
if (btn != null)
|
|
||||||
{
|
{
|
||||||
btn.Selected = btn == button && !selected;
|
btn.Selected = btn == button && !selected;
|
||||||
}
|
}
|
||||||
@@ -1516,7 +1593,7 @@ namespace Barotrauma
|
|||||||
afflictionEffectColor = GUIStyle.Green;
|
afflictionEffectColor = GUIStyle.Green;
|
||||||
}
|
}
|
||||||
|
|
||||||
var child = afflictionIconContainer.Content.FindChild(affliction);
|
var child = afflictionIconList.Content.FindChild(affliction);
|
||||||
|
|
||||||
var afflictionStrengthPredictionBar = child.GetChild<GUILayoutGroup>().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar;
|
var afflictionStrengthPredictionBar = child.GetChild<GUILayoutGroup>().GetChildByUserData("afflictionstrengthprediction") as GUIProgressBar;
|
||||||
afflictionStrengthPredictionBar.BarSize = 0.0f;
|
afflictionStrengthPredictionBar.BarSize = 0.0f;
|
||||||
@@ -1542,6 +1619,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!affliction.Prefab.ShowBarInHealthMenu)
|
||||||
|
{
|
||||||
|
afflictionStrengthBar.BarSize = 1f;
|
||||||
|
}
|
||||||
|
|
||||||
if (afflictionTooltip != null && afflictionTooltip.UserData == affliction)
|
if (afflictionTooltip != null && afflictionTooltip.UserData == affliction)
|
||||||
{
|
{
|
||||||
UpdateAfflictionInfo(afflictionTooltip.Content, affliction);
|
UpdateAfflictionInfo(afflictionTooltip.Content, affliction);
|
||||||
@@ -1581,8 +1663,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock;
|
var strengthText = labelContainer.GetChildByUserData("strength") as GUITextBlock;
|
||||||
|
|
||||||
strengthText.Text = strengthTexts[
|
strengthText.Text = affliction.GetStrengthText();
|
||||||
MathHelper.Clamp((int)Math.Floor((affliction.Strength / affliction.Prefab.MaxStrength) * strengthTexts.Length), 0, strengthTexts.Length - 1)];
|
|
||||||
|
|
||||||
strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
|
strengthText.TextColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red,
|
||||||
affliction.Strength / affliction.Prefab.MaxStrength);
|
affliction.Strength / affliction.Prefab.MaxStrength);
|
||||||
@@ -1836,14 +1917,14 @@ namespace Barotrauma
|
|||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedLimbIndex > -1 && afflictionIconContainer.Content.CountChildren > 0)
|
if (selectedLimbIndex > -1 && afflictionIconList.Content.CountChildren > 0)
|
||||||
{
|
{
|
||||||
LimbHealth limbHealth = limbHealths[selectedLimbIndex];
|
LimbHealth limbHealth = limbHealths[selectedLimbIndex];
|
||||||
if (limbHealth?.IndicatorSprite != null)
|
if (limbHealth?.IndicatorSprite != null)
|
||||||
{
|
{
|
||||||
Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea);
|
Rectangle selectedLimbArea = GetLimbHighlightArea(limbHealth, drawArea);
|
||||||
GUI.DrawLine(spriteBatch,
|
GUI.DrawLine(spriteBatch,
|
||||||
new Vector2(afflictionIconContainer.Rect.X, afflictionIconContainer.Rect.Y),
|
new Vector2(afflictionIconList.Rect.X, afflictionIconList.Rect.Y),
|
||||||
selectedLimbArea.Center.ToVector2(),
|
selectedLimbArea.Center.ToVector2(),
|
||||||
Color.LightGray * 0.5f, width: 4);
|
Color.LightGray * 0.5f, width: 4);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,11 +51,10 @@ namespace Barotrauma
|
|||||||
&& p.InstallTime.TryUnwrap(out var installTime)
|
&& p.InstallTime.TryUnwrap(out var installTime)
|
||||||
&& item.LatestUpdateTime <= installTime))
|
&& item.LatestUpdateTime <= installTime))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
if (needInstalling.Any())
|
if (!needInstalling.Any()) { return Enumerable.Empty<Steamworks.Ugc.Item>(); }
|
||||||
{
|
|
||||||
await Task.WhenAll(
|
await Task.WhenAll(
|
||||||
needInstalling.Select(SteamManager.Workshop.DownloadModThenEnqueueInstall));
|
needInstalling.Select(SteamManager.Workshop.DownloadModThenEnqueueInstall));
|
||||||
}
|
|
||||||
|
|
||||||
return needInstalling;
|
return needInstalling;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void ClientExecute(string[] args)
|
public void ClientExecute(string[] args)
|
||||||
{
|
{
|
||||||
bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is EditorScreen);
|
bool allowCheats = GameMain.NetworkMember == null && (GameMain.GameSession?.GameMode is TestGameMode || Screen.Selected is { IsEditor: true });
|
||||||
if (!allowCheats && !CheatsEnabled && IsCheat)
|
if (!allowCheats && !CheatsEnabled && IsCheat)
|
||||||
{
|
{
|
||||||
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red);
|
NewMessage("You need to enable cheats using the command \"enablecheats\" before you can use the command \"" + names[0] + "\".", Color.Red);
|
||||||
@@ -128,21 +128,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void Update(float deltaTime)
|
public static void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
lock (queuedMessages)
|
while (queuedMessages.TryDequeue(out var newMsg))
|
||||||
{
|
{
|
||||||
while (queuedMessages.Count > 0)
|
AddMessage(newMsg);
|
||||||
{
|
|
||||||
var newMsg = queuedMessages.Dequeue();
|
|
||||||
AddMessage(newMsg);
|
|
||||||
|
|
||||||
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
||||||
|
{
|
||||||
|
unsavedMessages.Add(newMsg);
|
||||||
|
if (unsavedMessages.Count >= messagesPerFile)
|
||||||
{
|
{
|
||||||
unsavedMessages.Add(newMsg);
|
SaveLogs();
|
||||||
if (unsavedMessages.Count >= messagesPerFile)
|
unsavedMessages.Clear();
|
||||||
{
|
|
||||||
SaveLogs();
|
|
||||||
unsavedMessages.Clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -178,9 +174,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Character.DisableControls = true;
|
Character.DisableControls = true;
|
||||||
|
|
||||||
if (PlayerInput.KeyHit(Keys.Tab))
|
if (PlayerInput.KeyHit(Keys.Tab) && !textBox.IsIMEActive)
|
||||||
{
|
{
|
||||||
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
|
textBox.Text = AutoComplete(textBox.Text, increment: string.IsNullOrEmpty(currentAutoCompletedCommand) ? 0 : 1 );
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
if (PlayerInput.KeyDown(Keys.LeftControl) || PlayerInput.KeyDown(Keys.RightControl))
|
||||||
@@ -258,25 +254,21 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DequeueMessages()
|
public static void DequeueMessages()
|
||||||
{
|
{
|
||||||
lock (queuedMessages)
|
while (queuedMessages.TryDequeue(out var newMsg))
|
||||||
{
|
{
|
||||||
while (queuedMessages.Count > 0)
|
if (listBox == null)
|
||||||
{
|
{
|
||||||
var newMsg = queuedMessages.Dequeue();
|
//don't attempt to add to the listbox if it hasn't been created yet
|
||||||
if (listBox == null)
|
Messages.Add(newMsg);
|
||||||
{
|
}
|
||||||
//don't attempt to add to the listbox if it hasn't been created yet
|
else
|
||||||
Messages.Add(newMsg);
|
{
|
||||||
}
|
AddMessage(newMsg);
|
||||||
else
|
}
|
||||||
{
|
|
||||||
AddMessage(newMsg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs || GameSettings.CurrentConfig.VerboseLogging)
|
||||||
{
|
{
|
||||||
unsavedMessages.Add(newMsg);
|
unsavedMessages.Add(newMsg);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1136,6 +1128,17 @@ namespace Barotrauma
|
|||||||
});
|
});
|
||||||
AssignRelayToServer("debugdraw", false);
|
AssignRelayToServer("debugdraw", false);
|
||||||
|
|
||||||
|
AssignOnExecute("debugdrawlocalization", (string[] args) =>
|
||||||
|
{
|
||||||
|
if (args.None() || !bool.TryParse(args[0], out bool state))
|
||||||
|
{
|
||||||
|
state = !TextManager.DebugDraw;
|
||||||
|
}
|
||||||
|
TextManager.DebugDraw = state;
|
||||||
|
NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.White);
|
||||||
|
});
|
||||||
|
AssignRelayToServer("debugdraw", false);
|
||||||
|
|
||||||
AssignOnExecute("togglevoicechatfilters", (string[] args) =>
|
AssignOnExecute("togglevoicechatfilters", (string[] args) =>
|
||||||
{
|
{
|
||||||
if (args.None() || !bool.TryParse(args[0], out bool state))
|
if (args.None() || !bool.TryParse(args[0], out bool state))
|
||||||
@@ -1695,6 +1698,8 @@ namespace Barotrauma
|
|||||||
config.Language = language;
|
config.Language = language;
|
||||||
GameSettings.SetCurrentConfig(config);
|
GameSettings.SetCurrentConfig(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HashSet<string> missingTexts = new HashSet<string>();
|
||||||
|
|
||||||
//key = text tag, value = list of languages the tag is missing from
|
//key = text tag, value = list of languages the tag is missing from
|
||||||
Dictionary<Identifier, HashSet<LanguageIdentifier>> missingTags = new Dictionary<Identifier, HashSet<LanguageIdentifier>>();
|
Dictionary<Identifier, HashSet<LanguageIdentifier>> missingTags = new Dictionary<Identifier, HashSet<LanguageIdentifier>>();
|
||||||
@@ -1755,20 +1760,38 @@ namespace Barotrauma
|
|||||||
|
|
||||||
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
|
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
|
||||||
{
|
{
|
||||||
foreach (var property in itemComponentType.GetProperties())
|
checkSerializableEntityType(itemComponentType);
|
||||||
|
}
|
||||||
|
checkSerializableEntityType(typeof(Item));
|
||||||
|
checkSerializableEntityType(typeof(Hull));
|
||||||
|
checkSerializableEntityType(typeof(Structure));
|
||||||
|
|
||||||
|
void checkSerializableEntityType(Type t)
|
||||||
|
{
|
||||||
|
foreach (var property in t.GetProperties())
|
||||||
{
|
{
|
||||||
if (!property.IsDefined(typeof(InGameEditable), false)) { continue; }
|
if (!property.IsDefined(typeof(Editable), false)) { continue; }
|
||||||
|
|
||||||
string propertyTag = $"{property.DeclaringType.Name}.{property.Name}";
|
string propertyTag = $"{property.DeclaringType.Name}.{property.Name}";
|
||||||
|
|
||||||
addIfMissingAll(language,
|
if (addIfMissingAll(language,
|
||||||
propertyTag.ToIdentifier(),
|
propertyTag.ToIdentifier(),
|
||||||
property.Name.ToIdentifier(),
|
property.Name.ToIdentifier(),
|
||||||
$"sp.{propertyTag}.name".ToIdentifier());
|
$"sp.{property.Name}.name".ToIdentifier(),
|
||||||
|
$"sp.{propertyTag}.name".ToIdentifier()) && language == "English".ToLanguageIdentifier())
|
||||||
|
{
|
||||||
|
missingTexts.Add($"<sp.{propertyTag.ToLower()}.name>{property.Name.FormatCamelCaseWithSpaces()}</sp.{propertyTag.ToLower()}.name>");
|
||||||
|
}
|
||||||
|
|
||||||
addIfMissingAll(language,
|
var description = (property.GetCustomAttributes(true).First(a => a is Serialize) as Serialize).Description;
|
||||||
|
|
||||||
|
if (addIfMissingAll(language,
|
||||||
$"sp.{propertyTag}.description".ToIdentifier(),
|
$"sp.{propertyTag}.description".ToIdentifier(),
|
||||||
$"{property.Name.ToIdentifier()}.description".ToIdentifier());
|
$"sp.{property.Name}.description".ToIdentifier(),
|
||||||
|
$"{property.Name.ToIdentifier()}.description".ToIdentifier()) && language == "English".ToLanguageIdentifier())
|
||||||
|
{
|
||||||
|
missingTexts.Add($"<sp.{propertyTag.ToLower()}.description>{description}</sp.{propertyTag.ToLower()}.description>");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1792,7 +1815,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Identifier afflictionId = affliction.TranslationIdentifier;
|
Identifier afflictionId = affliction.TranslationIdentifier;
|
||||||
addIfMissing($"afflictionname.{afflictionId}".ToIdentifier(), language);
|
addIfMissing($"afflictionname.{afflictionId}".ToIdentifier(), language);
|
||||||
addIfMissing($"afflictiondescription.{afflictionId}".ToIdentifier(), language);
|
|
||||||
|
if (affliction.Descriptions.Any())
|
||||||
|
{
|
||||||
|
foreach (var description in affliction.Descriptions)
|
||||||
|
{
|
||||||
|
addIfMissing(description.TextTag, language);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
addIfMissing($"afflictiondescription.{afflictionId}".ToIdentifier(), language);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var talentTree in TalentTree.JobTalentTrees)
|
foreach (var talentTree in TalentTree.JobTalentTrees)
|
||||||
@@ -1889,6 +1923,23 @@ namespace Barotrauma
|
|||||||
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
|
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
|
||||||
SwapLanguage(TextManager.DefaultLanguage);
|
SwapLanguage(TextManager.DefaultLanguage);
|
||||||
|
|
||||||
|
if (missingTexts.Any())
|
||||||
|
{
|
||||||
|
ShowQuestionPrompt("Dump the property names and descriptions missing from English to a new xml file? Y/N",
|
||||||
|
(option) =>
|
||||||
|
{
|
||||||
|
if (option.ToLowerInvariant() == "y")
|
||||||
|
{
|
||||||
|
string path = "newtexts.txt";
|
||||||
|
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
|
||||||
|
File.WriteAllLines(path, missingTexts);
|
||||||
|
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
|
||||||
|
ToolBox.OpenFileWithShell(Path.GetFullPath(path));
|
||||||
|
SwapLanguage(TextManager.DefaultLanguage);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void addIfMissing(Identifier tag, LanguageIdentifier language)
|
void addIfMissing(Identifier tag, LanguageIdentifier language)
|
||||||
{
|
{
|
||||||
if (!tags[language].Contains(tag))
|
if (!tags[language].Contains(tag))
|
||||||
@@ -1897,15 +1948,90 @@ namespace Barotrauma
|
|||||||
missingTags[tag].Add(language);
|
missingTags[tag].Add(language);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
void addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
|
bool addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
|
||||||
{
|
{
|
||||||
if (!potentialTags.Any(t => tags[language].Contains(t)))
|
if (!potentialTags.Any(t => tags[language].Contains(t)))
|
||||||
{
|
{
|
||||||
var tag = potentialTags.First();
|
var tag = potentialTags.First();
|
||||||
if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet<LanguageIdentifier>(); }
|
if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet<LanguageIdentifier>(); }
|
||||||
missingTags[tag].Add(language);
|
missingTags[tag].Add(language);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
|
||||||
|
commands.Add(new Command("checkduplicateloca", "", (string[] args) =>
|
||||||
|
{
|
||||||
|
if (args.Length < 1)
|
||||||
|
{
|
||||||
|
ThrowError("Please specify a file path.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
XDocument doc1 = XMLExtensions.TryLoadXml(args[0]);
|
||||||
|
if (doc1?.Root == null)
|
||||||
|
{
|
||||||
|
ThrowError($"Could not load the file \"{args[0]}\"");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<(string tag, string text)> texts = new List<(string tag, string text)>();
|
||||||
|
|
||||||
|
bool duplicatesFound = false;
|
||||||
|
foreach (XElement element in doc1.Root.Elements())
|
||||||
|
{
|
||||||
|
string tag = element.Name.ToString();
|
||||||
|
string text = element.ElementInnerText();
|
||||||
|
if (texts.Any(t => t.tag == tag))
|
||||||
|
{
|
||||||
|
ThrowError($"Duplicate tag \"{tag}\".");
|
||||||
|
duplicatesFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (duplicatesFound)
|
||||||
|
{
|
||||||
|
ThrowError($"Aborting, please fix duplicate tags in the file and try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (XElement element in doc1.Root.Elements())
|
||||||
|
{
|
||||||
|
string tag = element.Name.ToString();
|
||||||
|
string text = element.ElementInnerText();
|
||||||
|
if (texts.Any(t => t.text == text))
|
||||||
|
{
|
||||||
|
if (tag.StartsWith("sp."))
|
||||||
|
{
|
||||||
|
string[] split = tag.Split('.');
|
||||||
|
if (split.Length > 3)
|
||||||
|
{
|
||||||
|
texts.RemoveAll(t => t.text == text);
|
||||||
|
string newTag = $"sp.{split[2]}.{split[3]}";
|
||||||
|
texts.Add((newTag, text));
|
||||||
|
NewMessage($"Duplicate text \"{tag}\", merging to \"{newTag}\".");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
NewMessage($"Duplicate text \"{tag}\", using existing one \"{texts.Find(t => t.text == text).tag}\".");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texts.Add((tag, text));
|
||||||
|
ThrowError($"Duplicate text \"{tag}\". Could not determine if the text can be merged with an existing one, please check it manually.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
texts.Add((tag, text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
string filePath = "uniquetexts.xml";
|
||||||
|
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
|
||||||
|
File.WriteAllLines(filePath, texts.Select(t => $"<{t.tag}>{t.text}</{t.tag}>"));
|
||||||
|
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
|
||||||
|
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
|
||||||
}));
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("comparelocafiles", "comparelocafiles [file1] [file2]", (string[] args) =>
|
commands.Add(new Command("comparelocafiles", "comparelocafiles [file1] [file2]", (string[] args) =>
|
||||||
@@ -2085,6 +2211,15 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
commands.Add(new Command("spawnallitems", "", (string[] args) =>
|
||||||
|
{
|
||||||
|
var cursorPos = Screen.Selected.Cam?.ScreenToWorld(PlayerInput.MousePosition) ?? Vector2.Zero;
|
||||||
|
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
|
||||||
|
{
|
||||||
|
Entity.Spawner?.AddItemToSpawnQueue(itemPrefab, cursorPos);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) =>
|
commands.Add(new Command("camerasettings", "camerasettings [defaultzoom] [zoomsmoothness] [movesmoothness] [minzoom] [maxzoom]: debug command for testing camera settings. The values default to 1.1, 8.0, 8.0, 0.1 and 2.0.", (string[] args) =>
|
||||||
{
|
{
|
||||||
float defaultZoom = Screen.Selected.Cam.DefaultZoom;
|
float defaultZoom = Screen.Selected.Cam.DefaultZoom;
|
||||||
@@ -2585,99 +2720,6 @@ namespace Barotrauma
|
|||||||
}));
|
}));
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
commands.Add(new Command("cleanbuild", "", (string[] args) =>
|
|
||||||
{
|
|
||||||
/*GameSettings.CurrentConfig.MusicVolume = 0.5f;
|
|
||||||
GameSettings.CurrentConfig.SoundVolume = 0.5f;
|
|
||||||
GameSettings.CurrentConfig.DynamicRangeCompressionEnabled = true;
|
|
||||||
GameSettings.CurrentConfig.VoipAttenuationEnabled = true;
|
|
||||||
NewMessage("Music and sound volume set to 0.5", Color.Green);
|
|
||||||
|
|
||||||
GameSettings.CurrentConfig.GraphicsWidth = 0;
|
|
||||||
GameSettings.CurrentConfig.GraphicsHeight = 0;
|
|
||||||
GameSettings.CurrentConfig.WindowMode = WindowMode.BorderlessWindowed;
|
|
||||||
NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green);
|
|
||||||
NewMessage("Fullscreen enabled", Color.Green);
|
|
||||||
|
|
||||||
GameSettings.CurrentConfig.VerboseLogging = false;
|
|
||||||
|
|
||||||
if (GameSettings.CurrentConfig.MasterServerUrl != "http://www.undertowgames.com/baromaster")
|
|
||||||
{
|
|
||||||
ThrowError("MasterServerUrl \"" + GameSettings.CurrentConfig.MasterServerUrl + "\"!");
|
|
||||||
}
|
|
||||||
|
|
||||||
GameSettings.SaveCurrentConfig();*/
|
|
||||||
throw new NotImplementedException();
|
|
||||||
#warning TODO: reimplement
|
|
||||||
|
|
||||||
var saveFiles = Barotrauma.IO.Directory.GetFiles(SaveUtil.SaveFolder);
|
|
||||||
|
|
||||||
foreach (string saveFile in saveFiles)
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete(saveFile);
|
|
||||||
NewMessage("Deleted " + saveFile, Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Barotrauma.IO.Directory.Exists(Barotrauma.IO.Path.Combine(SaveUtil.SaveFolder, "temp")))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.Directory.Delete(Barotrauma.IO.Path.Combine(SaveUtil.SaveFolder, "temp"), true);
|
|
||||||
NewMessage("Deleted temp save folder", Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Barotrauma.IO.Directory.Exists(ServerLog.SavePath))
|
|
||||||
{
|
|
||||||
var logFiles = Barotrauma.IO.Directory.GetFiles(ServerLog.SavePath);
|
|
||||||
|
|
||||||
foreach (string logFile in logFiles)
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete(logFile);
|
|
||||||
NewMessage("Deleted " + logFile, Color.Green);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Barotrauma.IO.File.Exists("filelist.xml"))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete("filelist.xml");
|
|
||||||
NewMessage("Deleted filelist", Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Barotrauma.IO.File.Exists("Data/bannedplayers.txt"))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete("Data/bannedplayers.txt");
|
|
||||||
NewMessage("Deleted bannedplayers.txt", Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Barotrauma.IO.File.Exists("Submarines/TutorialSub.sub"))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete("Submarines/TutorialSub.sub");
|
|
||||||
|
|
||||||
NewMessage("Deleted TutorialSub from the submarine folder", Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*if (Barotrauma.IO.File.Exists(GameServer.SettingsFile))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete(GameServer.SettingsFile);
|
|
||||||
NewMessage("Deleted server settings", Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Barotrauma.IO.File.Exists(GameServer.ClientPermissionsFile))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete(GameServer.ClientPermissionsFile);
|
|
||||||
NewMessage("Deleted client permission file", Color.Green);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
if (Barotrauma.IO.File.Exists("crashreport.log"))
|
|
||||||
{
|
|
||||||
Barotrauma.IO.File.Delete("crashreport.log");
|
|
||||||
NewMessage("Deleted crashreport.log", Color.Green);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Barotrauma.IO.File.Exists("Content/Map/TutorialSub.sub"))
|
|
||||||
{
|
|
||||||
ThrowError("TutorialSub.sub not found!");
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
|
commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
|
||||||
{
|
{
|
||||||
if (args.Length < 1)
|
if (args.Length < 1)
|
||||||
|
|||||||
@@ -1,75 +1,22 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
using System;
|
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using System.Text;
|
|
||||||
|
|
||||||
namespace EventInput
|
namespace EventInput
|
||||||
{
|
{
|
||||||
public class CharacterEventArgs : EventArgs
|
public readonly record struct CharacterEventArgs(char Character, long Param)
|
||||||
{
|
{
|
||||||
private readonly char character;
|
public long RepeatCount => Param & 0xffff;
|
||||||
private readonly long lParam;
|
public bool ExtendedKey => (Param & (1 << 24)) > 0;
|
||||||
|
public bool AltPressed => (Param & (1 << 29)) > 0;
|
||||||
public CharacterEventArgs(char character, long lParam)
|
public bool PreviousState => (Param & (1 << 30)) > 0;
|
||||||
{
|
public bool TransitionState => (Param & (1 << 31)) > 0;
|
||||||
this.character = character;
|
|
||||||
this.lParam = lParam;
|
|
||||||
}
|
|
||||||
|
|
||||||
public char Character
|
|
||||||
{
|
|
||||||
get { return character; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public long Param
|
|
||||||
{
|
|
||||||
get { return lParam; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public long RepeatCount
|
|
||||||
{
|
|
||||||
get { return lParam & 0xffff; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool ExtendedKey
|
|
||||||
{
|
|
||||||
get { return (lParam & (1 << 24)) > 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool AltPressed
|
|
||||||
{
|
|
||||||
get { return (lParam & (1 << 29)) > 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool PreviousState
|
|
||||||
{
|
|
||||||
get { return (lParam & (1 << 30)) > 0; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool TransitionState
|
|
||||||
{
|
|
||||||
get { return (lParam & (1 << 31)) > 0; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public class KeyEventArgs : EventArgs
|
public readonly record struct KeyEventArgs(Keys KeyCode, char Character);
|
||||||
{
|
|
||||||
private Keys keyCode;
|
|
||||||
|
|
||||||
public KeyEventArgs(Keys keyCode)
|
|
||||||
{
|
|
||||||
this.keyCode = keyCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Keys KeyCode
|
|
||||||
{
|
|
||||||
get { return keyCode; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
|
public delegate void CharEnteredHandler(object sender, CharacterEventArgs e);
|
||||||
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
|
public delegate void KeyEventHandler(object sender, KeyEventArgs e);
|
||||||
|
public delegate void EditingTextHandler(object sender, TextEditingEventArgs e);
|
||||||
|
|
||||||
public static class EventInput
|
public static class EventInput
|
||||||
{
|
{
|
||||||
@@ -88,6 +35,12 @@ namespace EventInput
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static event KeyEventHandler KeyUp;
|
public static event KeyEventHandler KeyUp;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Raised when the user is editing text and IME is in progress.
|
||||||
|
/// </summary>
|
||||||
|
public static event EditingTextHandler EditingText;
|
||||||
|
|
||||||
|
|
||||||
static bool initialized;
|
static bool initialized;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -100,8 +53,10 @@ namespace EventInput
|
|||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
window.TextInput += ReceiveInput;
|
window.TextInput += ReceiveInput;
|
||||||
|
window.KeyDown += ReceiveKeyDown;
|
||||||
|
window.TextEditing += ReceiveTextEditing;
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
@@ -109,7 +64,16 @@ namespace EventInput
|
|||||||
private static void ReceiveInput(object sender, TextInputEventArgs e)
|
private static void ReceiveInput(object sender, TextInputEventArgs e)
|
||||||
{
|
{
|
||||||
OnCharEntered(e.Character);
|
OnCharEntered(e.Character);
|
||||||
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key));
|
}
|
||||||
|
|
||||||
|
private static void ReceiveKeyDown(object sender, TextInputEventArgs e)
|
||||||
|
{
|
||||||
|
KeyDown?.Invoke(sender, new KeyEventArgs(e.Key, e.Character));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReceiveTextEditing(object sender, TextEditingEventArgs e)
|
||||||
|
{
|
||||||
|
EditingText?.Invoke(sender, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OnCharEntered(char character)
|
public static void OnCharEntered(char character)
|
||||||
|
|||||||
@@ -1,8 +1,4 @@
|
|||||||
using System;
|
using Barotrauma;
|
||||||
using System.Threading;
|
|
||||||
#if WINDOWS
|
|
||||||
using System.Windows;
|
|
||||||
#endif
|
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Input;
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
|
||||||
@@ -14,6 +10,7 @@ namespace EventInput
|
|||||||
void ReceiveTextInput(string text);
|
void ReceiveTextInput(string text);
|
||||||
void ReceiveCommandInput(char command);
|
void ReceiveCommandInput(char command);
|
||||||
void ReceiveSpecialInput(Keys key);
|
void ReceiveSpecialInput(Keys key);
|
||||||
|
void ReceiveEditingInput(string text, int start, int length);
|
||||||
|
|
||||||
bool Selected { get; set; } //or Focused
|
bool Selected { get; set; } //or Focused
|
||||||
}
|
}
|
||||||
@@ -25,47 +22,27 @@ namespace EventInput
|
|||||||
EventInput.Initialize(window);
|
EventInput.Initialize(window);
|
||||||
EventInput.CharEntered += EventInput_CharEntered;
|
EventInput.CharEntered += EventInput_CharEntered;
|
||||||
EventInput.KeyDown += EventInput_KeyDown;
|
EventInput.KeyDown += EventInput_KeyDown;
|
||||||
|
EventInput.EditingText += EventInput_TextEditing;
|
||||||
|
GameMain.ResetIMEWorkaround();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void EventInput_TextEditing(object sender, TextEditingEventArgs e)
|
||||||
|
{
|
||||||
|
_subscriber?.ReceiveEditingInput(e.Text, e.Start, e.Length);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void EventInput_KeyDown(object sender, KeyEventArgs e)
|
public void EventInput_KeyDown(object sender, KeyEventArgs e)
|
||||||
{
|
{
|
||||||
if (_subscriber == null)
|
_subscriber?.ReceiveSpecialInput(e.KeyCode);
|
||||||
return;
|
if (char.IsControl(e.Character))
|
||||||
|
{
|
||||||
_subscriber.ReceiveSpecialInput(e.KeyCode);
|
_subscriber?.ReceiveCommandInput(e.Character);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventInput_CharEntered(object sender, CharacterEventArgs e)
|
void EventInput_CharEntered(object sender, CharacterEventArgs e)
|
||||||
{
|
{
|
||||||
if (_subscriber == null)
|
_subscriber?.ReceiveTextInput(e.Character);
|
||||||
return;
|
|
||||||
if (char.IsControl(e.Character))
|
|
||||||
{
|
|
||||||
_subscriber.ReceiveCommandInput(e.Character);
|
|
||||||
// Doesn't work as expected. Not sure why this should be run in a separate thread.
|
|
||||||
//#if WINDOWS
|
|
||||||
// //ctrl-v
|
|
||||||
// if (e.Character == 0x16) // 22
|
|
||||||
// {
|
|
||||||
// //XNA runs in Multiple Thread Apartment state, which cannot recieve clipboard
|
|
||||||
// Thread thread = new Thread(PasteThread);
|
|
||||||
// thread.SetApartmentState(ApartmentState.STA);
|
|
||||||
// thread.Start();
|
|
||||||
// thread.Join();
|
|
||||||
// _subscriber.ReceiveTextInput(_pasteResult);
|
|
||||||
// }
|
|
||||||
// else
|
|
||||||
// {
|
|
||||||
// _subscriber.ReceiveCommandInput(e.Character);
|
|
||||||
// }
|
|
||||||
//#else
|
|
||||||
// _subscriber.ReceiveCommandInput(e.Character);
|
|
||||||
//#endif
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
_subscriber.ReceiveTextInput(e.Character);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IKeyboardSubscriber _subscriber;
|
IKeyboardSubscriber _subscriber;
|
||||||
@@ -74,12 +51,26 @@ namespace EventInput
|
|||||||
get { return _subscriber; }
|
get { return _subscriber; }
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (_subscriber == value) return;
|
if (_subscriber == value) { return; }
|
||||||
if (_subscriber != null)
|
|
||||||
|
if (_subscriber is GUITextBox)
|
||||||
|
{
|
||||||
|
TextInput.StopTextInput();
|
||||||
_subscriber.Selected = false;
|
_subscriber.Selected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value is GUITextBox box)
|
||||||
|
{
|
||||||
|
TextInput.SetTextInputRect(box.MouseRect);
|
||||||
|
TextInput.StartTextInput();
|
||||||
|
TextInput.SetTextInputRect(box.MouseRect);
|
||||||
|
}
|
||||||
|
|
||||||
_subscriber = value;
|
_subscriber = value;
|
||||||
if (value != null)
|
if (value != null)
|
||||||
|
{
|
||||||
value.Selected = true;
|
value.Selected = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
using Barotrauma.Tutorials;
|
||||||
|
using Segment = Barotrauma.ObjectiveManager.Segment;
|
||||||
|
|
||||||
|
namespace Barotrauma;
|
||||||
|
|
||||||
|
partial class CheckObjectiveAction : BinaryOptionAction
|
||||||
|
{
|
||||||
|
public enum CheckType
|
||||||
|
{
|
||||||
|
Added,
|
||||||
|
Completed
|
||||||
|
}
|
||||||
|
|
||||||
|
[Serialize(CheckType.Completed, IsPropertySaveable.Yes)]
|
||||||
|
public CheckType Type { get; set; }
|
||||||
|
|
||||||
|
[Serialize("", IsPropertySaveable.Yes)]
|
||||||
|
public Identifier Identifier { get; set; }
|
||||||
|
|
||||||
|
partial void DetermineSuccessProjSpecific(ref bool success)
|
||||||
|
{
|
||||||
|
success = false;
|
||||||
|
if (Identifier.IsEmpty)
|
||||||
|
{
|
||||||
|
success = ObjectiveManager.AllActiveObjectivesCompleted();
|
||||||
|
}
|
||||||
|
else if (ObjectiveManager.GetObjective(Identifier) is Segment segment)
|
||||||
|
{
|
||||||
|
success = Type switch
|
||||||
|
{
|
||||||
|
CheckType.Added => true,
|
||||||
|
CheckType.Completed => segment.IsCompleted,
|
||||||
|
_ => false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -323,7 +323,10 @@ namespace Barotrauma
|
|||||||
AlwaysOverrideCursor = true
|
AlwaysOverrideCursor = true
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalizedString translatedText = TextManager.ParseInputTypes(TextManager.Get(text)).Fallback(text);
|
LocalizedString translatedText = speaker?.DisplayName is not null ?
|
||||||
|
TextManager.GetWithVariable(text, "[speakername]", speaker?.DisplayName) :
|
||||||
|
TextManager.Get(text);
|
||||||
|
translatedText = TextManager.ParseInputTypes(translatedText).Fallback(text);
|
||||||
|
|
||||||
if (speaker?.Info != null && drawChathead)
|
if (speaker?.Info != null && drawChathead)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,11 +11,13 @@ partial class MessageBoxAction : EventAction
|
|||||||
if (Type == ActionType.Create || Type == ActionType.ConnectObjective)
|
if (Type == ActionType.Create || Type == ActionType.ConnectObjective)
|
||||||
{
|
{
|
||||||
CreateMessageBox();
|
CreateMessageBox();
|
||||||
if (!ObjectiveTag.IsEmpty && GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
if (!ObjectiveTag.IsEmpty)
|
||||||
{
|
{
|
||||||
Identifier id = Identifier.IfEmpty(Text);
|
Identifier id = Identifier.IfEmpty(Text);
|
||||||
var segment = Tutorial.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
|
var segment = ObjectiveManager.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
|
||||||
tutorialMode.Tutorial?.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
|
segment.CanBeCompleted = ObjectiveCanBeCompleted;
|
||||||
|
segment.ParentId = ParentObjectiveId;
|
||||||
|
ObjectiveManager.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (Type == ActionType.Close)
|
else if (Type == ActionType.Close)
|
||||||
|
|||||||
+23
-30
@@ -1,50 +1,43 @@
|
|||||||
using Barotrauma.Tutorials;
|
|
||||||
|
|
||||||
namespace Barotrauma;
|
namespace Barotrauma;
|
||||||
|
|
||||||
partial class TutorialSegmentAction : EventAction
|
partial class TutorialSegmentAction : EventAction
|
||||||
{
|
{
|
||||||
private Tutorial.Segment segment;
|
private ObjectiveManager.Segment segment;
|
||||||
|
|
||||||
partial void UpdateProjSpecific()
|
partial void UpdateProjSpecific()
|
||||||
{
|
{
|
||||||
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
|
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
|
||||||
if (Type == SegmentActionType.Trigger)
|
if (Type == SegmentActionType.Trigger)
|
||||||
{
|
{
|
||||||
segment = Tutorial.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
|
segment = ObjectiveManager.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
|
||||||
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center),
|
new ObjectiveManager.Segment.Text(TextTag, Width, Height, Anchor.Center),
|
||||||
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height));
|
new ObjectiveManager.Segment.Video(VideoFile, TextTag, Width, Height));
|
||||||
}
|
}
|
||||||
else if (Type == SegmentActionType.Add)
|
else if (Type == SegmentActionType.Add)
|
||||||
{
|
{
|
||||||
segment = Tutorial.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
|
segment = ObjectiveManager.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
|
||||||
}
|
}
|
||||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
if (segment is not null)
|
||||||
{
|
{
|
||||||
if (tutorialMode.Tutorial is Tutorial tutorial)
|
segment.CanBeCompleted = CanBeCompleted;
|
||||||
{
|
segment.ParentId = ParentObjectiveId;
|
||||||
switch (Type)
|
|
||||||
{
|
|
||||||
case SegmentActionType.Trigger:
|
|
||||||
case SegmentActionType.Add:
|
|
||||||
tutorial.TriggerTutorialSegment(segment);
|
|
||||||
break;
|
|
||||||
case SegmentActionType.Complete:
|
|
||||||
tutorial.CompleteTutorialSegment(Identifier);
|
|
||||||
break;
|
|
||||||
case SegmentActionType.Remove:
|
|
||||||
tutorial.RemoveTutorialSegment(Identifier);
|
|
||||||
break;
|
|
||||||
case SegmentActionType.CompleteAndRemove:
|
|
||||||
tutorial.CompleteTutorialSegment(Identifier);
|
|
||||||
tutorial.RemoveTutorialSegment(Identifier);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
switch (Type)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!");
|
case SegmentActionType.Trigger:
|
||||||
|
case SegmentActionType.Add:
|
||||||
|
ObjectiveManager.TriggerTutorialSegment(segment);
|
||||||
|
break;
|
||||||
|
case SegmentActionType.Complete:
|
||||||
|
ObjectiveManager.CompleteTutorialSegment(Identifier);
|
||||||
|
break;
|
||||||
|
case SegmentActionType.Remove:
|
||||||
|
ObjectiveManager.RemoveTutorialSegment(Identifier);
|
||||||
|
break;
|
||||||
|
case SegmentActionType.CompleteAndRemove:
|
||||||
|
ObjectiveManager.CompleteTutorialSegment(Identifier);
|
||||||
|
ObjectiveManager.RemoveTutorialSegment(Identifier);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma;
|
namespace Barotrauma;
|
||||||
@@ -10,37 +11,66 @@ partial class UIHighlightAction : EventAction
|
|||||||
partial void UpdateProjSpecific()
|
partial void UpdateProjSpecific()
|
||||||
{
|
{
|
||||||
bool useCircularFlash = false;
|
bool useCircularFlash = false;
|
||||||
GUIComponent component = null;
|
|
||||||
|
|
||||||
if (Id != ElementId.None)
|
if (Id != ElementId.None)
|
||||||
{
|
{
|
||||||
component = GUI.GetAdditions().FirstOrDefault(c => Equals(Id, c.UserData));
|
FindAndFlashComponents(c => Equals(Id, c.UserData));
|
||||||
}
|
}
|
||||||
else if (!EntityIdentifier.IsEmpty)
|
else if (!EntityIdentifier.IsEmpty)
|
||||||
{
|
{
|
||||||
component = GUI.GetAdditions().FirstOrDefault(c =>
|
FindAndFlashComponents(c =>
|
||||||
c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier);
|
c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier);
|
||||||
}
|
}
|
||||||
else if (!OrderIdentifier.IsEmpty)
|
else if (!OrderIdentifier.IsEmpty)
|
||||||
{
|
{
|
||||||
useCircularFlash = true;
|
useCircularFlash = true;
|
||||||
|
bool foundMinimapNode = false;
|
||||||
if (!OrderTargetTag.IsEmpty)
|
if (!OrderTargetTag.IsEmpty)
|
||||||
{
|
{
|
||||||
component =
|
foundMinimapNode = FindAndFlashComponents(c =>
|
||||||
GUI.GetAdditions().FirstOrDefault(c =>
|
c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order &&
|
||||||
c.UserData is CrewManager.MinimapNodeData nodeData && nodeData.Order is Order order &&
|
order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag));
|
||||||
order.Identifier == OrderIdentifier && order.Option == OrderOption && order.TargetEntity is Item item && item.HasTag(OrderTargetTag));
|
}
|
||||||
|
if (!foundMinimapNode)
|
||||||
|
{
|
||||||
|
FindAndFlashComponents(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption,
|
||||||
|
c => c.UserData is Order order && order.Identifier == OrderIdentifier,
|
||||||
|
c => Equals(OrderCategory, c.UserData));
|
||||||
}
|
}
|
||||||
component ??=
|
|
||||||
GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier && order.Option == OrderOption) ??
|
|
||||||
GUI.GetAdditions().FirstOrDefault(c => c.UserData is Order order && order.Identifier == OrderIdentifier) ??
|
|
||||||
GUI.GetAdditions().FirstOrDefault(c => Equals(OrderCategory, c.UserData));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component != null && component.FlashTimer <= 0.0f)
|
bool FindAndFlashComponents(params Func<GUIComponent, bool>[] predicates)
|
||||||
{
|
{
|
||||||
component.Flash(highlightColor, useCircularFlash: useCircularFlash);
|
foreach (var predicate in predicates)
|
||||||
component.Bounce |= Bounce;
|
{
|
||||||
|
if (HighlightMultiple)
|
||||||
|
{
|
||||||
|
bool found = false;
|
||||||
|
foreach (var component in GUI.GetAdditions())
|
||||||
|
{
|
||||||
|
if (predicate(component))
|
||||||
|
{
|
||||||
|
Flash(component);
|
||||||
|
found = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
else if (GUI.GetAdditions().FirstOrDefault(predicate) is GUIComponent component)
|
||||||
|
{
|
||||||
|
Flash(component);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Flash(GUIComponent component)
|
||||||
|
{
|
||||||
|
if (component.FlashTimer <= 0.0f)
|
||||||
|
{
|
||||||
|
component.Flash(highlightColor, useCircularFlash: useCircularFlash);
|
||||||
|
component.Bounce |= Bounce;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -7,6 +7,7 @@ using System.Collections.Generic;
|
|||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Xml.Linq;
|
||||||
using Barotrauma.Threading;
|
using Barotrauma.Threading;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -37,7 +38,7 @@ namespace Barotrauma
|
|||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsCJK
|
public TextManager.SpeciallyHandledCharCategory SpeciallyHandledCharCategory
|
||||||
{
|
{
|
||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
@@ -84,17 +85,35 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static TextManager.SpeciallyHandledCharCategory ExtractShccFromXElement(XElement element)
|
||||||
|
=> TextManager.SpeciallyHandledCharCategories
|
||||||
|
.Where(category => element.GetAttributeBool($"is{category}", category switch {
|
||||||
|
// CJK isn't supported by default
|
||||||
|
TextManager.SpeciallyHandledCharCategory.CJK => false,
|
||||||
|
|
||||||
|
// For backwards compatibility, we assume that Cyrillic is supported by default
|
||||||
|
TextManager.SpeciallyHandledCharCategory.Cyrillic => true,
|
||||||
|
|
||||||
|
_ => throw new Exception("unreachable")
|
||||||
|
}))
|
||||||
|
.Aggregate(TextManager.SpeciallyHandledCharCategory.None, (current, category) => current | category);
|
||||||
|
|
||||||
public ScalableFont(ContentXElement element, GraphicsDevice gd = null)
|
public ScalableFont(ContentXElement element, GraphicsDevice gd = null)
|
||||||
: this(
|
: this(
|
||||||
element.GetAttributeContentPath("file")?.Value,
|
element.GetAttributeContentPath("file")?.Value,
|
||||||
(uint)element.GetAttributeInt("size", 14),
|
(uint)element.GetAttributeInt("size", 14),
|
||||||
gd,
|
gd,
|
||||||
element.GetAttributeBool("dynamicloading", false),
|
element.GetAttributeBool("dynamicloading", false),
|
||||||
element.GetAttributeBool("iscjk", false))
|
ExtractShccFromXElement(element))
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public ScalableFont(string filename, uint size, GraphicsDevice gd = null, bool dynamicLoading = false, bool isCJK = false)
|
public ScalableFont(
|
||||||
|
string filename,
|
||||||
|
uint size,
|
||||||
|
GraphicsDevice gd = null,
|
||||||
|
bool dynamicLoading = false,
|
||||||
|
TextManager.SpeciallyHandledCharCategory speciallyHandledCharCategory = TextManager.SpeciallyHandledCharCategory.None)
|
||||||
{
|
{
|
||||||
lock (globalMutex)
|
lock (globalMutex)
|
||||||
{
|
{
|
||||||
@@ -120,7 +139,7 @@ namespace Barotrauma
|
|||||||
this.textures = new List<Texture2D>();
|
this.textures = new List<Texture2D>();
|
||||||
this.texCoords = new Dictionary<uint, GlyphData>();
|
this.texCoords = new Dictionary<uint, GlyphData>();
|
||||||
this.DynamicLoading = dynamicLoading;
|
this.DynamicLoading = dynamicLoading;
|
||||||
this.IsCJK = isCJK;
|
this.SpeciallyHandledCharCategory = speciallyHandledCharCategory;
|
||||||
this.graphicsDevice = gd;
|
this.graphicsDevice = gd;
|
||||||
|
|
||||||
if (gd != null && !dynamicLoading)
|
if (gd != null && !dynamicLoading)
|
||||||
|
|||||||
@@ -769,7 +769,7 @@ namespace Barotrauma
|
|||||||
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
|
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
|
||||||
{
|
{
|
||||||
radio.Channel = channel;
|
radio.Channel = channel;
|
||||||
GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()]));
|
GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()], radio));
|
||||||
|
|
||||||
if (setText)
|
if (setText)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -357,6 +357,17 @@ namespace Barotrauma
|
|||||||
string txt = directory;
|
string txt = directory;
|
||||||
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
|
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
|
||||||
if (!txt.EndsWith("/")) { txt += "/"; }
|
if (!txt.EndsWith("/")) { txt += "/"; }
|
||||||
|
//get directory info
|
||||||
|
DirectoryInfo dirInfo = new DirectoryInfo(directory);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
//this will throw an exception if the directory can't be opened
|
||||||
|
Directory.GetDirectories(directory);
|
||||||
|
}
|
||||||
|
catch (UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
|
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
|
||||||
{
|
{
|
||||||
UserData = ItemIsDirectory.Yes
|
UserData = ItemIsDirectory.Yes
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ using System.Collections.Generic;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
|
||||||
using Barotrauma.CharacterEditor;
|
using Barotrauma.CharacterEditor;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
@@ -50,6 +49,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
static class GUI
|
static class GUI
|
||||||
{
|
{
|
||||||
|
// Controls where a line is drawn for given coords.
|
||||||
|
public enum OutlinePosition
|
||||||
|
{
|
||||||
|
Default = 0, // Thickness is inside of top left and outside of bottom right coord
|
||||||
|
Inside = 1, // Thickness is subtracted from the inside
|
||||||
|
Centered = 2, // Thickness is centered on given coords
|
||||||
|
Outside = 3, // Tickness is added to the outside
|
||||||
|
}
|
||||||
public static GUICanvas Canvas => GUICanvas.Instance;
|
public static GUICanvas Canvas => GUICanvas.Instance;
|
||||||
public static CursorState MouseCursor = CursorState.Default;
|
public static CursorState MouseCursor = CursorState.Default;
|
||||||
|
|
||||||
@@ -248,7 +255,17 @@ namespace Barotrauma
|
|||||||
ScreenChanged = false;
|
ScreenChanged = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateList.ForEach(c => c.DrawAuto(spriteBatch));
|
foreach (GUIComponent c in updateList)
|
||||||
|
{
|
||||||
|
c.DrawAuto(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
// always draw IME preview on top of everything else
|
||||||
|
foreach (GUIComponent c in updateList)
|
||||||
|
{
|
||||||
|
if (c is not GUITextBox box) { continue; }
|
||||||
|
box.DrawIMEPreview(spriteBatch);
|
||||||
|
}
|
||||||
|
|
||||||
if (ScreenOverlayColor.A > 0.0f)
|
if (ScreenOverlayColor.A > 0.0f)
|
||||||
{
|
{
|
||||||
@@ -310,7 +327,7 @@ namespace Barotrauma
|
|||||||
DrawString(spriteBatch, new Vector2(10, y),
|
DrawString(spriteBatch, new Vector2(10, y),
|
||||||
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
|
"FPS: " + Math.Round(GameMain.PerformanceCounter.AverageFramesPerSecond),
|
||||||
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
|
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
|
||||||
if (GameMain.GameSession != null && Timing.TotalTime > GameMain.GameSession.RoundStartTime + 1.0)
|
if (GameMain.GameSession != null && GameMain.GameSession.RoundDuration > 1.0)
|
||||||
{
|
{
|
||||||
y += yStep;
|
y += yStep;
|
||||||
DrawString(spriteBatch, new Vector2(10, y),
|
DrawString(spriteBatch, new Vector2(10, y),
|
||||||
@@ -1350,7 +1367,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#region Element drawing
|
#region Element drawing
|
||||||
|
|
||||||
private static readonly List<float> usedIndicatorAngles = new List<float>();
|
private static readonly List<float> usedIndicatorAngles = new List<float>();
|
||||||
|
|
||||||
@@ -1605,6 +1622,54 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void DrawRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 origin, float rotation, Color clr, float depth = 0.0f, float thickness = 1, OutlinePosition outlinePos = OutlinePosition.Centered)
|
||||||
|
{
|
||||||
|
Vector2 topLeft = new Vector2(-origin.X, -origin.Y);
|
||||||
|
Vector2 topRight = new Vector2(-origin.X + size.X, -origin.Y);
|
||||||
|
Vector2 bottomLeft = new Vector2(-origin.X, -origin.Y + size.Y);
|
||||||
|
Vector2 actualSize = size;
|
||||||
|
|
||||||
|
switch(outlinePos)
|
||||||
|
{
|
||||||
|
case OutlinePosition.Default:
|
||||||
|
actualSize += new Vector2(thickness);
|
||||||
|
break;
|
||||||
|
case OutlinePosition.Centered:
|
||||||
|
topLeft -= new Vector2(thickness * 0.5f);
|
||||||
|
topRight -= new Vector2(thickness * 0.5f);
|
||||||
|
bottomLeft -= new Vector2(thickness * 0.5f);
|
||||||
|
actualSize += new Vector2(thickness);
|
||||||
|
break;
|
||||||
|
case OutlinePosition.Inside:
|
||||||
|
topRight -= new Vector2(thickness, 0.0f);
|
||||||
|
bottomLeft -= new Vector2(0.0f, thickness);
|
||||||
|
break;
|
||||||
|
case OutlinePosition.Outside:
|
||||||
|
topLeft -= new Vector2(thickness);
|
||||||
|
topRight -= new Vector2(0.0f, thickness);
|
||||||
|
bottomLeft -= new Vector2(thickness, 0.0f);
|
||||||
|
actualSize += new Vector2(thickness * 2.0f);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
Matrix rotate = Matrix.CreateRotationZ(rotation);
|
||||||
|
topLeft = Vector2.Transform(topLeft, rotate) + position;
|
||||||
|
topRight = Vector2.Transform(topRight, rotate) + position;
|
||||||
|
bottomLeft = Vector2.Transform(bottomLeft, rotate) + position;
|
||||||
|
|
||||||
|
Rectangle srcRect = new Rectangle(0, 0, 1, 1);
|
||||||
|
sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
|
||||||
|
sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
|
||||||
|
sb.Draw(solidWhiteTexture, topRight, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
|
||||||
|
sb.Draw(solidWhiteTexture, bottomLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void DrawFilledRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 pivot, float rotation, Color clr, float depth = 0.0f)
|
||||||
|
{
|
||||||
|
Rectangle srcRect = new Rectangle(0, 0, 1, 1);
|
||||||
|
sb.Draw(solidWhiteTexture, position, srcRect, clr, rotation, (pivot/size), size, SpriteEffects.None, depth);
|
||||||
|
}
|
||||||
|
|
||||||
public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f)
|
public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f)
|
||||||
{
|
{
|
||||||
DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
|
DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
|
||||||
@@ -1788,9 +1853,9 @@ namespace Barotrauma
|
|||||||
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
|
Vector2 pos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) - new Vector2(HUDLayoutSettings.Padding) - 2 * Scale * sheet.FrameSize.ToVector2();
|
||||||
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
|
sheet.Draw(spriteBatch, (int)Math.Floor(savingIndicatorSpriteIndex), pos, savingIndicatorColor, origin: Vector2.Zero, rotate: 0.0f, scale: new Vector2(Scale));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Element creation
|
#region Element creation
|
||||||
|
|
||||||
public static Texture2D CreateCircle(int radius, bool filled = false)
|
public static Texture2D CreateCircle(int radius, bool filled = false)
|
||||||
{
|
{
|
||||||
@@ -2162,9 +2227,9 @@ namespace Barotrauma
|
|||||||
return msgBox;
|
return msgBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Element positioning
|
#region Element positioning
|
||||||
private static List<T> CreateElements<T>(int count, RectTransform parent, Func<RectTransform, T> constructor,
|
private static List<T> CreateElements<T>(int count, RectTransform parent, Func<RectTransform, T> constructor,
|
||||||
Vector2? relativeSize = null, Point? absoluteSize = null,
|
Vector2? relativeSize = null, Point? absoluteSize = null,
|
||||||
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null,
|
Anchor anchor = Anchor.TopLeft, Pivot? pivot = null, Point? minSize = null, Point? maxSize = null,
|
||||||
@@ -2363,9 +2428,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Misc
|
#region Misc
|
||||||
public static void TogglePauseMenu()
|
public static void TogglePauseMenu()
|
||||||
{
|
{
|
||||||
if (Screen.Selected == GameMain.MainMenuScreen) { return; }
|
if (Screen.Selected == GameMain.MainMenuScreen) { return; }
|
||||||
@@ -2603,6 +2668,6 @@ namespace Barotrauma
|
|||||||
if (!isSavingIndicatorEnabled) { return; }
|
if (!isSavingIndicatorEnabled) { return; }
|
||||||
timeUntilSavingIndicatorDisabled = delay;
|
timeUntilSavingIndicatorDisabled = delay;
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using Barotrauma.IO;
|
|||||||
using RestSharp;
|
using RestSharp;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using Barotrauma.Tutorials;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -736,7 +737,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos)
|
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos)
|
||||||
{
|
{
|
||||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) { return; }
|
if (ObjectiveManager.ContentRunning) { return; }
|
||||||
|
|
||||||
int width = (int)(400 * GUI.Scale);
|
int width = (int)(400 * GUI.Scale);
|
||||||
int height = (int)(18 * GUI.Scale);
|
int height = (int)(18 * GUI.Scale);
|
||||||
@@ -757,9 +758,9 @@ namespace Barotrauma
|
|||||||
toolTipBlock.DrawManually(spriteBatch);
|
toolTipBlock.DrawManually(spriteBatch);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement)
|
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Rectangle targetElement, Anchor anchor = Anchor.BottomCenter, Pivot pivot = Pivot.TopLeft)
|
||||||
{
|
{
|
||||||
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning) { return; }
|
if (ObjectiveManager.ContentRunning) { return; }
|
||||||
|
|
||||||
int width = (int)(400 * GUI.Scale);
|
int width = (int)(400 * GUI.Scale);
|
||||||
int height = (int)(18 * GUI.Scale);
|
int height = (int)(18 * GUI.Scale);
|
||||||
@@ -774,7 +775,10 @@ namespace Barotrauma
|
|||||||
toolTipBlock.UserData = toolTip;
|
toolTipBlock.UserData = toolTip;
|
||||||
}
|
}
|
||||||
|
|
||||||
toolTipBlock.RectTransform.AbsoluteOffset = new Point(targetElement.Center.X, targetElement.Bottom);
|
toolTipBlock.RectTransform.AbsoluteOffset =
|
||||||
|
RectTransform.CalculateAnchorPoint(anchor, targetElement) +
|
||||||
|
RectTransform.CalculatePivotOffset(pivot, toolTipBlock.RectTransform.NonScaledSize);
|
||||||
|
|
||||||
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
|
if (toolTipBlock.Rect.Right > GameMain.GraphicsWidth - 10)
|
||||||
{
|
{
|
||||||
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
|
toolTipBlock.RectTransform.AbsoluteOffset -= new Point(toolTipBlock.Rect.Width, 0);
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
public void ReceiveTextInput(string text) { }
|
public void ReceiveTextInput(string text) { }
|
||||||
public void ReceiveCommandInput(char command) { }
|
public void ReceiveCommandInput(char command) { }
|
||||||
|
public void ReceiveEditingInput(string text, int start, int length) { }
|
||||||
|
|
||||||
public void ReceiveSpecialInput(Keys key)
|
public void ReceiveSpecialInput(Keys key)
|
||||||
{
|
{
|
||||||
@@ -121,9 +122,7 @@ namespace Barotrauma
|
|||||||
listBox.ReceiveSpecialInput(key);
|
listBox.ReceiveSpecialInput(key);
|
||||||
GUI.KeyboardDispatcher.Subscriber = this;
|
GUI.KeyboardDispatcher.Subscriber = this;
|
||||||
break;
|
break;
|
||||||
case Keys.Enter:
|
default:
|
||||||
case Keys.Space:
|
|
||||||
case Keys.Escape:
|
|
||||||
GUI.KeyboardDispatcher.Subscriber = null;
|
GUI.KeyboardDispatcher.Subscriber = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: fix implicit hiding
|
|
||||||
public override bool Selected
|
public override bool Selected
|
||||||
{
|
{
|
||||||
get { return isSelected; }
|
get { return isSelected; }
|
||||||
@@ -1348,6 +1347,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
public void ReceiveTextInput(string text) { }
|
public void ReceiveTextInput(string text) { }
|
||||||
public void ReceiveCommandInput(char command) { }
|
public void ReceiveCommandInput(char command) { }
|
||||||
|
public void ReceiveEditingInput(string text, int start, int length) { }
|
||||||
|
|
||||||
public void ReceiveSpecialInput(Keys key)
|
public void ReceiveSpecialInput(Keys key)
|
||||||
{
|
{
|
||||||
@@ -1365,9 +1365,7 @@ namespace Barotrauma
|
|||||||
case Keys.Right:
|
case Keys.Right:
|
||||||
if (isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); }
|
if (isHorizontal && AllowArrowKeyScroll) { SelectNext(playSelectSound: PlaySelectSound.Yes); }
|
||||||
break;
|
break;
|
||||||
case Keys.Enter:
|
default:
|
||||||
case Keys.Space:
|
|
||||||
case Keys.Escape:
|
|
||||||
GUI.KeyboardDispatcher.Subscriber = null;
|
GUI.KeyboardDispatcher.Subscriber = null;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ namespace Barotrauma
|
|||||||
InGame,
|
InGame,
|
||||||
Vote,
|
Vote,
|
||||||
Hint,
|
Hint,
|
||||||
Tutorial
|
Tutorial,
|
||||||
|
Warning // Keep this last so that it's always drawn in front
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial;
|
private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial;
|
||||||
@@ -84,8 +85,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
|
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
|
||||||
|
|
||||||
public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null)
|
public GUIMessageBox(LocalizedString headerText, LocalizedString text, Vector2? relativeSize = null, Point? minSize = null, Type type = Type.Default)
|
||||||
: this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize)
|
: this(headerText, text, new LocalizedString[] { "OK" }, relativeSize, minSize, type: type)
|
||||||
{
|
{
|
||||||
this.Buttons[0].OnClicked = Close;
|
this.Buttons[0].OnClicked = Close;
|
||||||
}
|
}
|
||||||
@@ -147,7 +148,7 @@ namespace Barotrauma
|
|||||||
Tag = tag.ToIdentifier();
|
Tag = tag.ToIdentifier();
|
||||||
|
|
||||||
#warning TODO: These should be broken into separate methods at least
|
#warning TODO: These should be broken into separate methods at least
|
||||||
if (type == Type.Default || type == Type.Vote)
|
if (type == Type.Default || type == Type.Vote || type == Type.Warning)
|
||||||
{
|
{
|
||||||
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
|
Content = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.85f), InnerFrame.RectTransform, Anchor.Center)) { AbsoluteSpacing = 5 };
|
||||||
|
|
||||||
|
|||||||
@@ -179,6 +179,11 @@ namespace Barotrauma
|
|||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If enabled, the value wraps around to Max when you go below Min, and vice versa
|
||||||
|
/// </summary>
|
||||||
|
public bool WrapAround;
|
||||||
|
|
||||||
public float valueStep;
|
public float valueStep;
|
||||||
|
|
||||||
private float pressedTimer;
|
private float pressedTimer;
|
||||||
@@ -403,13 +408,19 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (MinValueFloat != null)
|
if (MinValueFloat != null)
|
||||||
{
|
{
|
||||||
floatValue = Math.Max(floatValue, MinValueFloat.Value);
|
floatValue =
|
||||||
MinusButton.Enabled = floatValue > MinValueFloat;
|
WrapAround && MinValueFloat.HasValue && floatValue < MinValueFloat.Value ?
|
||||||
|
MaxValueFloat.Value :
|
||||||
|
Math.Max(floatValue, MinValueFloat.Value);
|
||||||
|
MinusButton.Enabled = WrapAround || floatValue > MinValueFloat;
|
||||||
}
|
}
|
||||||
if (MaxValueFloat != null)
|
if (MaxValueFloat != null)
|
||||||
{
|
{
|
||||||
floatValue = Math.Min(floatValue, MaxValueFloat.Value);
|
floatValue =
|
||||||
PlusButton.Enabled = floatValue < MaxValueFloat;
|
WrapAround && MaxValueFloat.HasValue && floatValue > MaxValueFloat.Value ?
|
||||||
|
MinValueFloat.Value :
|
||||||
|
Math.Min(floatValue, MaxValueFloat.Value);
|
||||||
|
PlusButton.Enabled = WrapAround || floatValue < MaxValueFloat;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -417,16 +428,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (MinValueInt != null && intValue < MinValueInt.Value)
|
if (MinValueInt != null && intValue < MinValueInt.Value)
|
||||||
{
|
{
|
||||||
intValue = Math.Max(intValue, MinValueInt.Value);
|
intValue = WrapAround && MaxValueInt.HasValue ? MaxValueInt.Value : Math.Max(intValue, MinValueInt.Value);
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
if (MaxValueInt != null && intValue > MaxValueInt.Value)
|
if (MaxValueInt != null && intValue > MaxValueInt.Value)
|
||||||
{
|
{
|
||||||
intValue = Math.Min(intValue, MaxValueInt.Value);
|
intValue = WrapAround && MinValueInt.HasValue ? MinValueInt.Value : Math.Min(intValue, MaxValueInt.Value);
|
||||||
UpdateText();
|
UpdateText();
|
||||||
}
|
}
|
||||||
PlusButton.Enabled = MaxValueInt == null || intValue < MaxValueInt;
|
PlusButton.Enabled = WrapAround || MaxValueInt == null || intValue < MaxValueInt;
|
||||||
MinusButton.Enabled = MinValueInt == null || intValue > MinValueInt;
|
MinusButton.Enabled = WrapAround || MinValueInt == null || intValue > MinValueInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateText()
|
private void UpdateText()
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Xml.Linq;
|
using System.Xml.Linq;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -45,16 +46,17 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScalableFont cjkFont;
|
private ImmutableDictionary<TextManager.SpeciallyHandledCharCategory, ScalableFont> specialHandlingFonts;
|
||||||
|
|
||||||
public ScalableFont CjkFont
|
public ScalableFont GetFontForCategory(TextManager.SpeciallyHandledCharCategory category)
|
||||||
{
|
{
|
||||||
get
|
if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); }
|
||||||
{
|
if (font.SpeciallyHandledCharCategory.HasFlag(category)) { return font; }
|
||||||
if (Language != GameSettings.CurrentConfig.Language) { LoadFont(); }
|
return specialHandlingFonts.TryGetValue(category, out var resultFont)
|
||||||
if (font.IsCJK) { return font; }
|
? resultFont
|
||||||
return cjkFont;
|
: specialHandlingFonts.TryGetValue(TextManager.SpeciallyHandledCharCategory.CJK, out resultFont)
|
||||||
}
|
? resultFont
|
||||||
|
: font;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LanguageIdentifier Language { get; private set; }
|
public LanguageIdentifier Language { get; private set; }
|
||||||
@@ -70,40 +72,68 @@ namespace Barotrauma
|
|||||||
string fontPath = GetFontFilePath(element);
|
string fontPath = GetFontFilePath(element);
|
||||||
uint size = GetFontSize(element);
|
uint size = GetFontSize(element);
|
||||||
bool dynamicLoading = GetFontDynamicLoading(element);
|
bool dynamicLoading = GetFontDynamicLoading(element);
|
||||||
bool isCJK = GetIsCJK(element);
|
var shcc = GetShcc(element);
|
||||||
font?.Dispose();
|
font?.Dispose();
|
||||||
cjkFont?.Dispose();
|
specialHandlingFonts?.Values.ForEach(f => f.Dispose());
|
||||||
font = new ScalableFont(fontPath, size, GameMain.Instance.GraphicsDevice, dynamicLoading, isCJK)
|
font = new ScalableFont(
|
||||||
|
fontPath,
|
||||||
|
size,
|
||||||
|
GameMain.Instance.GraphicsDevice,
|
||||||
|
dynamicLoading,
|
||||||
|
shcc)
|
||||||
{
|
{
|
||||||
ForceUpperCase = element.GetAttributeBool("forceuppercase", false)
|
ForceUpperCase = element.GetAttributeBool("forceuppercase", false)
|
||||||
};
|
};
|
||||||
if (!isCJK)
|
|
||||||
|
var fallbackFonts = new Dictionary<TextManager.SpeciallyHandledCharCategory, ScalableFont>();
|
||||||
|
foreach (var flag in TextManager.SpeciallyHandledCharCategories)
|
||||||
{
|
{
|
||||||
cjkFont = ExtractCjkFont(element)
|
if (shcc.HasFlag(flag)) { continue; }
|
||||||
?? new ScalableFont("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf",
|
|
||||||
font.Size, GameMain.Instance.GraphicsDevice, dynamicLoading: true, isCJK: true);
|
var extractedFont = ExtractFont(flag, element);
|
||||||
cjkFont.ForceUpperCase = font.ForceUpperCase;
|
if (extractedFont is null) { continue; }
|
||||||
|
fallbackFonts.Add(flag, extractedFont);
|
||||||
}
|
}
|
||||||
|
fallbackFonts.Values.ForEach(ff => ff.ForceUpperCase = font.ForceUpperCase);
|
||||||
|
specialHandlingFonts = fallbackFonts.ToImmutableDictionary();
|
||||||
Language = GameSettings.CurrentConfig.Language;
|
Language = GameSettings.CurrentConfig.Language;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
{
|
{
|
||||||
font?.Dispose(); font = null;
|
font?.Dispose();
|
||||||
cjkFont?.Dispose(); cjkFont = null;
|
font = null;
|
||||||
|
specialHandlingFonts?.Values.ForEach(f => f.Dispose());
|
||||||
|
specialHandlingFonts = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScalableFont ExtractCjkFont(ContentXElement element)
|
private ScalableFont ExtractFont(TextManager.SpeciallyHandledCharCategory flag, ContentXElement element)
|
||||||
{
|
{
|
||||||
foreach (var subElement in element.Elements().Reverse())
|
foreach (var subElement in element.Elements().Reverse())
|
||||||
{
|
{
|
||||||
if (subElement.NameAsIdentifier() != "override") { continue; }
|
if (subElement.NameAsIdentifier() != "override") { continue; }
|
||||||
if (subElement.GetAttributeBool("iscjk", false))
|
if (ScalableFont.ExtractShccFromXElement(subElement).HasFlag(flag))
|
||||||
{
|
{
|
||||||
return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice);
|
return new ScalableFont(subElement, GameMain.Instance.GraphicsDevice);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
ScalableFont hardcodedFallback(string path)
|
||||||
|
=> new ScalableFont(
|
||||||
|
path,
|
||||||
|
font.Size,
|
||||||
|
GameMain.Instance.GraphicsDevice,
|
||||||
|
dynamicLoading: true,
|
||||||
|
speciallyHandledCharCategory: flag);
|
||||||
|
|
||||||
|
return flag switch
|
||||||
|
{
|
||||||
|
TextManager.SpeciallyHandledCharCategory.CJK
|
||||||
|
=> hardcodedFallback("Content/Fonts/NotoSans/NotoSansCJKsc-Bold.otf"),
|
||||||
|
TextManager.SpeciallyHandledCharCategory.Cyrillic
|
||||||
|
=> hardcodedFallback("Content/Fonts/Oswald-Bold.ttf"),
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetFontFilePath(ContentXElement element)
|
private string GetFontFilePath(ContentXElement element)
|
||||||
@@ -154,21 +184,21 @@ namespace Barotrauma
|
|||||||
return element.GetAttributeBool("dynamicloading", false);
|
return element.GetAttributeBool("dynamicloading", false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool GetIsCJK(XElement element)
|
private TextManager.SpeciallyHandledCharCategory GetShcc(XElement element)
|
||||||
{
|
{
|
||||||
foreach (var subElement in element.Elements())
|
foreach (var subElement in element.Elements())
|
||||||
{
|
{
|
||||||
if (IsValidOverride(subElement))
|
if (IsValidOverride(subElement))
|
||||||
{
|
{
|
||||||
return subElement.GetAttributeBool("iscjk", false);
|
return ScalableFont.ExtractShccFromXElement(subElement);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return element.GetAttributeBool("iscjk", false);
|
return ScalableFont.ExtractShccFromXElement(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool IsValidOverride(XElement element)
|
private bool IsValidOverride(XElement element)
|
||||||
{
|
{
|
||||||
if (!element.Name.ToString().Equals("override", StringComparison.OrdinalIgnoreCase)) { return false; }
|
if (!element.IsOverride()) { return false; }
|
||||||
var languages = element.GetAttributeIdentifierArray("language", Array.Empty<Identifier>());
|
var languages = element.GetAttributeIdentifierArray("language", Array.Empty<Identifier>());
|
||||||
return languages.Any(l => l.ToLanguageIdentifier() == GameSettings.CurrentConfig.Language);
|
return languages.Any(l => l.ToLanguageIdentifier() == GameSettings.CurrentConfig.Language);
|
||||||
}
|
}
|
||||||
@@ -191,26 +221,26 @@ namespace Barotrauma
|
|||||||
private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
|
private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
|
||||||
|
|
||||||
public ScalableFont GetFontForStr(string str) =>
|
public ScalableFont GetFontForStr(string str) =>
|
||||||
TextManager.IsCJK(str) ? Prefabs.ActivePrefab.CjkFont : Prefabs.ActivePrefab.Font;
|
Prefabs.ActivePrefab.GetFontForCategory(TextManager.GetSpeciallyHandledCategories(str));
|
||||||
|
|
||||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
|
||||||
{
|
{
|
||||||
DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth);
|
DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
|
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects spriteEffects, float layerDepth)
|
||||||
{
|
{
|
||||||
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth);
|
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft)
|
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft)
|
||||||
{
|
{
|
||||||
DrawString(sb, text.Value, position, color, rotation, origin, scale, se, layerDepth, alignment);
|
DrawString(sb, text.Value, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
||||||
{
|
{
|
||||||
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, se, layerDepth, alignment, forceUpperCase);
|
GetFontForStr(text).DrawString(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, alignment, forceUpperCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false)
|
public void DrawString(SpriteBatch sb, LocalizedString text, Vector2 position, Color color, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit, bool italics = false)
|
||||||
@@ -223,9 +253,9 @@ namespace Barotrauma
|
|||||||
GetFontForStr(text).DrawString(sb, text, position, color, forceUpperCase, italics);
|
GetFontForStr(text).DrawString(sb, text, position, color, forceUpperCase, italics);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects se, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
public void DrawStringWithColors(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, float scale, SpriteEffects spriteEffects, float layerDepth, in ImmutableArray<RichTextData>? richTextData, int rtdOffset = 0, Alignment alignment = Alignment.TopLeft, ForceUpperCase forceUpperCase = Barotrauma.ForceUpperCase.Inherit)
|
||||||
{
|
{
|
||||||
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, se, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
|
GetFontForStr(text).DrawStringWithColors(sb, text, position, color, rotation, origin, scale, spriteEffects, layerDepth, richTextData, rtdOffset, alignment, forceUpperCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing = false)
|
public Vector2 MeasureString(LocalizedString str, bool removeExtraSpacing = false)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework.Graphics;
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -285,8 +286,8 @@ namespace Barotrauma
|
|||||||
/// This is the new constructor.
|
/// This is the new constructor.
|
||||||
/// If the rectT height is set 0, the height is calculated from the text.
|
/// If the rectT height is set 0, the height is calculated from the text.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
|
public GUITextBlock(RectTransform rectT, RichString text, Color? textColor = null, GUIFont font = null,
|
||||||
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
|
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null)
|
||||||
: base(style, rectT)
|
: base(style, rectT)
|
||||||
{
|
{
|
||||||
if (color.HasValue)
|
if (color.HasValue)
|
||||||
@@ -551,6 +552,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (TextGetter != null) { Text = TextGetter(); }
|
if (TextGetter != null) { Text = TextGetter(); }
|
||||||
|
|
||||||
|
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
|
||||||
|
|
||||||
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
|
||||||
if (overflowClipActive)
|
if (overflowClipActive)
|
||||||
{
|
{
|
||||||
@@ -561,7 +564,7 @@ namespace Barotrauma
|
|||||||
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!text.IsNullOrEmpty())
|
if (!string.IsNullOrEmpty(textToShow))
|
||||||
{
|
{
|
||||||
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
|
Vector2 pos = rect.Location.ToVector2() + textPos + TextOffset;
|
||||||
if (RoundToNearestPixel)
|
if (RoundToNearestPixel)
|
||||||
@@ -570,7 +573,8 @@ namespace Barotrauma
|
|||||||
pos.Y = (int)pos.Y;
|
pos.Y = (int)pos.Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
Color currentTextColor = State == ComponentState.Hover || State == ComponentState.HoverSelected ? HoverTextColor : TextColor;
|
Color currentTextColor = State is ComponentState.Hover or ComponentState.HoverSelected ? HoverTextColor : TextColor;
|
||||||
|
|
||||||
if (!enabled)
|
if (!enabled)
|
||||||
{
|
{
|
||||||
currentTextColor = disabledTextColor;
|
currentTextColor = disabledTextColor;
|
||||||
@@ -582,8 +586,14 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (!HasColorHighlight)
|
if (!HasColorHighlight)
|
||||||
{
|
{
|
||||||
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
|
|
||||||
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
|
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
|
||||||
|
if (TextManager.DebugDraw)
|
||||||
|
{
|
||||||
|
if (!text.NestedStr.Loaded || text.NestedStr.Language == LanguageIdentifier.None)
|
||||||
|
{
|
||||||
|
colorToShow = Color.Magenta;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (Shadow)
|
if (Shadow)
|
||||||
{
|
{
|
||||||
@@ -597,10 +607,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (OverrideRichTextDataAlpha)
|
if (OverrideRichTextDataAlpha)
|
||||||
{
|
{
|
||||||
RichTextData.Value.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
|
RichTextData?.ForEach(rt => rt.Alpha = currentTextColor.A / 255.0f);
|
||||||
}
|
}
|
||||||
Font.DrawStringWithColors(spriteBatch, Censor ? censoredText : (Wrap ? wrappedText : text.SanitizedString).Value, pos,
|
Font.DrawStringWithColors(spriteBatch, textToShow, pos,
|
||||||
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData.Value, alignment: textAlignment, forceUpperCase: ForceUpperCase);
|
currentTextColor * (currentTextColor.A / 255.0f), 0.0f, origin, TextScale, SpriteEffects.None, textDepth, RichTextData, alignment: textAlignment, forceUpperCase: ForceUpperCase);
|
||||||
}
|
}
|
||||||
|
|
||||||
Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X,
|
Strikethrough?.Draw(spriteBatch, (int)Math.Ceiling(TextSize.X / 2f), pos.X,
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public delegate void TextBoxEvent(GUITextBox sender, Keys key);
|
public delegate void TextBoxEvent(GUITextBox sender, Keys key);
|
||||||
|
|
||||||
public class GUITextBox : GUIComponent, IKeyboardSubscriber
|
public partial class GUITextBox : GUIComponent, IKeyboardSubscriber
|
||||||
{
|
{
|
||||||
public event TextBoxEvent OnSelected;
|
public event TextBoxEvent OnSelected;
|
||||||
public event TextBoxEvent OnDeselected;
|
public event TextBoxEvent OnDeselected;
|
||||||
@@ -67,12 +67,12 @@ namespace Barotrauma
|
|||||||
private int selectionEndIndex;
|
private int selectionEndIndex;
|
||||||
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
|
private bool IsLeftToRight => selectionStartIndex <= selectionEndIndex;
|
||||||
|
|
||||||
private GUICustomComponent caretAndSelectionRenderer;
|
private readonly GUICustomComponent caretAndSelectionRenderer;
|
||||||
|
|
||||||
private bool mouseHeldInside;
|
private bool mouseHeldInside;
|
||||||
|
|
||||||
private readonly Memento<string> memento = new Memento<string>();
|
private readonly Memento<string> memento = new Memento<string>();
|
||||||
|
|
||||||
// Skip one update cycle, fixes Enter key instantly deselecting the chatbox
|
// Skip one update cycle, fixes Enter key instantly deselecting the chatbox
|
||||||
private bool skipUpdate;
|
private bool skipUpdate;
|
||||||
|
|
||||||
@@ -189,6 +189,7 @@ namespace Barotrauma
|
|||||||
base.Font = value;
|
base.Font = value;
|
||||||
if (textBlock == null) { return; }
|
if (textBlock == null) { return; }
|
||||||
textBlock.Font = value;
|
textBlock.Font = value;
|
||||||
|
imePreviewTextHandler.Font = Font;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -253,6 +254,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public override bool PlaySoundOnSelect { get; set; } = true;
|
public override bool PlaySoundOnSelect { get; set; } = true;
|
||||||
|
|
||||||
|
private readonly IMEPreviewTextHandler imePreviewTextHandler;
|
||||||
|
|
||||||
|
public bool IsIMEActive => imePreviewTextHandler is { HasText: true };
|
||||||
|
|
||||||
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
|
public GUITextBox(RectTransform rectT, string text = "", Color? textColor = null, GUIFont font = null,
|
||||||
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
|
Alignment textAlignment = Alignment.Left, bool wrap = false, string style = "", Color? color = null, bool createClearButton = false, bool createPenIcon = true)
|
||||||
: base(style, rectT)
|
: base(style, rectT)
|
||||||
@@ -264,6 +269,7 @@ namespace Barotrauma
|
|||||||
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
|
frame = new GUIFrame(new RectTransform(Vector2.One, rectT, Anchor.Center), style, color);
|
||||||
GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
|
GUIStyle.Apply(frame, style == "" ? "GUITextBox" : style);
|
||||||
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
|
textBlock = new GUITextBlock(new RectTransform(Vector2.One, frame.RectTransform, Anchor.CenterLeft), text ?? "", textColor, font, textAlignment, wrap);
|
||||||
|
imePreviewTextHandler = new IMEPreviewTextHandler(textBlock.Font);
|
||||||
GUIStyle.Apply(textBlock, "", this);
|
GUIStyle.Apply(textBlock, "", this);
|
||||||
if (font != null) { textBlock.Font = font; }
|
if (font != null) { textBlock.Font = font; }
|
||||||
CaretEnabled = true;
|
CaretEnabled = true;
|
||||||
@@ -295,18 +301,17 @@ namespace Barotrauma
|
|||||||
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
|
textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - clearButtonWidth - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue);
|
||||||
}
|
}
|
||||||
Font = textBlock.Font;
|
Font = textBlock.Font;
|
||||||
|
|
||||||
Enabled = true;
|
Enabled = true;
|
||||||
|
|
||||||
rectT.SizeChanged += () =>
|
rectT.SizeChanged += () =>
|
||||||
{
|
{
|
||||||
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
|
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
|
||||||
caretPosDirty = true;
|
caretPosDirty = true;
|
||||||
};
|
};
|
||||||
rectT.ScaleChanged += () =>
|
rectT.ScaleChanged += () =>
|
||||||
{
|
{
|
||||||
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
|
if (icon != null) { textBlock.RectTransform.MaxSize = new Point(frame.Rect.Width - icon.Rect.Height - icon.RectTransform.AbsoluteOffset.X * 2, int.MaxValue); }
|
||||||
caretPosDirty = true;
|
caretPosDirty = true;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,14 +386,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
GUI.KeyboardDispatcher.Subscriber = null;
|
GUI.KeyboardDispatcher.Subscriber = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
OnDeselected?.Invoke(this, Keys.None);
|
OnDeselected?.Invoke(this, Keys.None);
|
||||||
|
imePreviewTextHandler.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
|
public override void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectOffset = null)
|
||||||
{
|
{
|
||||||
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
|
frame.Flash(color, flashDuration, useRectangleFlash, useCircularFlash, flashRectOffset);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update(float deltaTime)
|
protected override void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
if (!Visible) return;
|
if (!Visible) return;
|
||||||
@@ -569,6 +576,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
|
CaretIndex = Math.Min(Text.Length, CaretIndex + input.Length);
|
||||||
OnTextChanged?.Invoke(this, Text);
|
OnTextChanged?.Invoke(this, Text);
|
||||||
|
imePreviewTextHandler?.Reset();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -597,10 +605,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public void ReceiveCommandInput(char command)
|
public void ReceiveCommandInput(char command)
|
||||||
{
|
{
|
||||||
if (Text == null) Text = "";
|
if (IsIMEActive) { return; }
|
||||||
|
|
||||||
|
if (Text == null) { Text = ""; }
|
||||||
|
|
||||||
// Prevent alt gr from triggering any of these as that combination is often needed for special characters
|
// Prevent alt gr from triggering any of these as that combination is often needed for special characters
|
||||||
if (PlayerInput.IsAltDown()) return;
|
if (PlayerInput.IsAltDown()) { return; }
|
||||||
|
|
||||||
switch (command)
|
switch (command)
|
||||||
{
|
{
|
||||||
@@ -674,8 +684,21 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReceiveEditingInput(string text, int start, int length)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
imePreviewTextHandler.Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
imePreviewTextHandler.UpdateText(text, start, length);
|
||||||
|
}
|
||||||
|
|
||||||
public void ReceiveSpecialInput(Keys key)
|
public void ReceiveSpecialInput(Keys key)
|
||||||
{
|
{
|
||||||
|
if (IsIMEActive) { return; }
|
||||||
|
|
||||||
switch (key)
|
switch (key)
|
||||||
{
|
{
|
||||||
case Keys.Left:
|
case Keys.Left:
|
||||||
@@ -864,6 +887,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DrawIMEPreview(SpriteBatch spriteBatch)
|
||||||
|
{
|
||||||
|
imePreviewTextHandler.DrawIMEPreview(spriteBatch, CaretScreenPos, textBlock);
|
||||||
|
}
|
||||||
|
|
||||||
private void CalculateSelection()
|
private void CalculateSelection()
|
||||||
{
|
{
|
||||||
string textDrawn = Censor ? textBlock.CensoredText : WrappedText;
|
string textDrawn = Censor ? textBlock.CensoredText : WrappedText;
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ namespace Barotrauma
|
|||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Rectangle AfflictionAreaLeft
|
public static Rectangle HealthBarAfflictionArea
|
||||||
{
|
{
|
||||||
get; private set;
|
get; private set;
|
||||||
}
|
}
|
||||||
@@ -143,7 +143,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
int healthBarHeight = (int)(50f * GUI.Scale);
|
int healthBarHeight = (int)(50f * GUI.Scale);
|
||||||
HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight);
|
HealthBarArea = new Rectangle(BottomRightInfoArea.Right - healthBarWidth + (int)Math.Floor(1 / GUI.Scale), BottomRightInfoArea.Y - healthBarHeight + GUI.IntScale(10), healthBarWidth, healthBarHeight);
|
||||||
AfflictionAreaLeft = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
|
HealthBarAfflictionArea = new Rectangle(HealthBarArea.X, HealthBarArea.Y - Padding - afflictionAreaHeight, HealthBarArea.Width, afflictionAreaHeight);
|
||||||
|
|
||||||
|
|
||||||
int messageAreaWidth = GameMain.GraphicsWidth / 3;
|
int messageAreaWidth = GameMain.GraphicsWidth / 3;
|
||||||
@@ -173,7 +173,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding;
|
int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding;
|
||||||
int objectiveListAreaY = ButtonAreaTop.Bottom + Padding;
|
int objectiveListAreaY = ButtonAreaTop.Bottom + Padding;
|
||||||
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (AfflictionAreaLeft.Top - Padding) - objectiveListAreaY);
|
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (HealthBarAfflictionArea.Top - Padding) - objectiveListAreaY);
|
||||||
|
|
||||||
int votingAreaWidth = (int)(400 * GUI.Scale);
|
int votingAreaWidth = (int)(400 * GUI.Scale);
|
||||||
int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth;
|
int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth;
|
||||||
@@ -193,7 +193,7 @@ namespace Barotrauma
|
|||||||
DrawRectangle(CrewArea, Color.Blue * 0.5f);
|
DrawRectangle(CrewArea, Color.Blue * 0.5f);
|
||||||
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
|
DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
|
||||||
DrawRectangle(HealthBarArea, Color.Red * 0.5f);
|
DrawRectangle(HealthBarArea, Color.Red * 0.5f);
|
||||||
DrawRectangle(AfflictionAreaLeft, Color.Red * 0.5f);
|
DrawRectangle(HealthBarAfflictionArea, Color.Red * 0.5f);
|
||||||
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
|
DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
|
||||||
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
|
DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
|
||||||
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
|
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
|
||||||
|
public sealed class IMEPreviewTextHandler
|
||||||
|
{
|
||||||
|
public bool HasText => !string.IsNullOrEmpty(previewText);
|
||||||
|
|
||||||
|
// This has to be settable because for some reason we update the font of GUITextBox in some places
|
||||||
|
public GUIFont Font { get; set; }
|
||||||
|
|
||||||
|
private string previewText = string.Empty;
|
||||||
|
private Vector2 textSize;
|
||||||
|
|
||||||
|
private bool isSectioned;
|
||||||
|
private ImmutableArray<RichTextData>? richTextData;
|
||||||
|
|
||||||
|
public IMEPreviewTextHandler(GUIFont font)
|
||||||
|
{
|
||||||
|
Font = font;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
textSize = Vector2.Zero;
|
||||||
|
previewText = string.Empty;
|
||||||
|
richTextData = null;
|
||||||
|
isSectioned = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateText(string text, int start, int length)
|
||||||
|
{
|
||||||
|
isSectioned = start >= 0 && length > 0;
|
||||||
|
richTextData = null;
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(text))
|
||||||
|
{
|
||||||
|
Reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
previewText = text;
|
||||||
|
|
||||||
|
textSize = Font.MeasureString(text);
|
||||||
|
|
||||||
|
if (!isSectioned) { return; }
|
||||||
|
|
||||||
|
string coloredText = ToolBox.ColorSectionOfString(text, start, length, GUIStyle.Orange);
|
||||||
|
|
||||||
|
RichString richString = RichString.Rich(coloredText);
|
||||||
|
|
||||||
|
previewText = richString.SanitizedValue;
|
||||||
|
richTextData = richString.RichTextData;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DrawIMEPreview(SpriteBatch spriteBatch, Vector2 position, GUITextBlock textBlock)
|
||||||
|
{
|
||||||
|
if (!HasText) { return; }
|
||||||
|
|
||||||
|
int inflate = GUI.IntScale(3);
|
||||||
|
|
||||||
|
RectangleF rect = new RectangleF(position, textSize);
|
||||||
|
rect.Inflate(inflate, inflate);
|
||||||
|
|
||||||
|
RectangleF borderRect = rect;
|
||||||
|
borderRect.Inflate(1, 1);
|
||||||
|
|
||||||
|
GUI.DrawFilledRectangle(spriteBatch, borderRect, Color.White, depth: 0.02f);
|
||||||
|
GUI.DrawFilledRectangle(spriteBatch, rect, Color.Black, depth: 0.01f);
|
||||||
|
|
||||||
|
Font.DrawStringWithColors(spriteBatch,
|
||||||
|
text: previewText,
|
||||||
|
position: position,
|
||||||
|
color: isSectioned ? GUIStyle.TextColorNormal : GUIStyle.Orange,
|
||||||
|
rotation: 0.0f,
|
||||||
|
origin: Vector2.Zero,
|
||||||
|
scale: 1f,
|
||||||
|
spriteEffects: SpriteEffects.None,
|
||||||
|
layerDepth: 0,
|
||||||
|
richTextData: richTextData,
|
||||||
|
alignment: textBlock.TextAlignment,
|
||||||
|
forceUpperCase: textBlock.ForceUpperCase);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,7 +12,7 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
|
|||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
[SuppressMessage("ReSharper", "UnusedVariable")]
|
[SuppressMessage("ReSharper", "UnusedVariable")]
|
||||||
internal class MedicalClinicUI
|
internal sealed class MedicalClinicUI
|
||||||
{
|
{
|
||||||
private enum ElementState
|
private enum ElementState
|
||||||
{
|
{
|
||||||
@@ -127,12 +127,14 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public readonly GUIComponent Panel;
|
public readonly GUIComponent Panel;
|
||||||
public readonly GUIListBox HealList;
|
public readonly GUIListBox HealList;
|
||||||
|
public readonly GUIComponent TreatAllButton;
|
||||||
public readonly List<CrewElement> HealElements;
|
public readonly List<CrewElement> HealElements;
|
||||||
|
|
||||||
public CrewHealList(GUIListBox healList, GUIComponent panel)
|
public CrewHealList(GUIListBox healList, GUIComponent panel, GUIComponent treatAllButton)
|
||||||
{
|
{
|
||||||
Panel = panel;
|
Panel = panel;
|
||||||
HealList = healList;
|
HealList = healList;
|
||||||
|
TreatAllButton = treatAllButton;
|
||||||
HealElements = new List<CrewElement>();
|
HealElements = new List<CrewElement>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +181,7 @@ namespace Barotrauma
|
|||||||
private PopupAfflictionList? selectedCrewAfflictionList;
|
private PopupAfflictionList? selectedCrewAfflictionList;
|
||||||
private bool isWaitingForServer;
|
private bool isWaitingForServer;
|
||||||
private const float refreshTimerMax = 3f;
|
private const float refreshTimerMax = 3f;
|
||||||
private float refreshTimer = 0;
|
private float refreshTimer;
|
||||||
|
|
||||||
private PlayerBalanceElement? playerBalanceElement;
|
private PlayerBalanceElement? playerBalanceElement;
|
||||||
|
|
||||||
@@ -196,7 +198,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
new GUIButton(new RectTransform(new Vector2(0.2f, 0.1f), parent.RectTransform, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!")
|
new GUIButton(new RectTransform(new Vector2(0.2f, 0.1f), parent.RectTransform, Anchor.TopCenter), "Recreate UI - NOT PRESENT IN RELEASE!")
|
||||||
{
|
{
|
||||||
OnClicked = (_, __) =>
|
OnClicked = (_, _) =>
|
||||||
{
|
{
|
||||||
parent.ClearChildren();
|
parent.ClearChildren();
|
||||||
CreateUI();
|
CreateUI();
|
||||||
@@ -254,7 +256,7 @@ namespace Barotrauma
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
CreatePendingHealElement(healList.HealList.Content, crewMember, healList, Array.Empty<MedicalClinic.NetAffliction>());
|
CreatePendingHealElement(healList.HealList.Content, crewMember, healList, ImmutableArray<MedicalClinic.NetAffliction>.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
// check if there are elements that the crew doesn't have
|
// check if there are elements that the crew doesn't have
|
||||||
@@ -309,7 +311,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void UpdateCrewPanel()
|
private void UpdateCrewPanel()
|
||||||
{
|
{
|
||||||
if (!(crewHealList is { } healList)) { return; }
|
if (crewHealList is not { } healList) { return; }
|
||||||
|
|
||||||
ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters();
|
ImmutableArray<CharacterInfo> crew = MedicalClinic.GetCrewCharacters();
|
||||||
|
|
||||||
@@ -334,12 +336,21 @@ namespace Barotrauma
|
|||||||
healList.HealList.Content.RemoveChild(element.UIElement);
|
healList.HealList.Content.RemoveChild(element.UIElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(element => element.Target.Character?.HealthPercentage ?? 100);
|
IEnumerable<CrewElement> orderedList = healList.HealElements.OrderBy(static element => element.Target.Character?.HealthPercentage ?? 100);
|
||||||
|
|
||||||
foreach (CrewElement element in orderedList)
|
foreach (CrewElement element in orderedList)
|
||||||
{
|
{
|
||||||
element.UIElement.SetAsLastChild();
|
element.UIElement.SetAsLastChild();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
healList.TreatAllButton.Enabled = false;
|
||||||
|
foreach (CrewElement element in healList.HealElements)
|
||||||
|
{
|
||||||
|
if (element.Afflictions.Count is 0) { continue; }
|
||||||
|
|
||||||
|
healList.TreatAllButton.Enabled = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateAfflictionList(CrewElement healElement)
|
private static void UpdateAfflictionList(CrewElement healElement)
|
||||||
@@ -350,7 +361,7 @@ namespace Barotrauma
|
|||||||
// sum up all the afflictions and their strengths
|
// sum up all the afflictions and their strengths
|
||||||
Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>();
|
Dictionary<AfflictionPrefab, float> afflictionAndStrength = new Dictionary<AfflictionPrefab, float>();
|
||||||
|
|
||||||
foreach (Affliction affliction in health.GetAllAfflictions().Where(a => MedicalClinic.IsHealable(a)))
|
foreach (Affliction affliction in health.GetAllAfflictions().Where(MedicalClinic.IsHealable))
|
||||||
{
|
{
|
||||||
if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength))
|
if (afflictionAndStrength.TryGetValue(affliction.Prefab, out float strength))
|
||||||
{
|
{
|
||||||
@@ -446,8 +457,8 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
GUILayoutGroup clinicLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), clinicContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
GUILayoutGroup clinicLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), clinicContent.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
||||||
GUIImage clinicIcon = new GUIImage(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CrewManagementHeaderIcon", scaleToFit: true);
|
new GUIImage(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform, scaleBasis: ScaleBasis.BothHeight), style: "CrewManagementHeaderIcon", scaleToFit: true);
|
||||||
GUITextBlock clinicLabel = new GUITextBlock(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get("medicalclinic.medicalclinic"), font: GUIStyle.LargeFont);
|
new GUITextBlock(new RectTransform(Vector2.One, clinicLabelLayout.RectTransform), TextManager.Get("medicalclinic.medicalclinic"), font: GUIStyle.LargeFont);
|
||||||
|
|
||||||
GUIFrame clinicBackground = new GUIFrame(new RectTransform(Vector2.One, clinicContent.RectTransform));
|
GUIFrame clinicBackground = new GUIFrame(new RectTransform(Vector2.One, clinicContent.RectTransform));
|
||||||
|
|
||||||
@@ -480,22 +491,24 @@ namespace Barotrauma
|
|||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
|
|
||||||
// GUILayoutGroup sortLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.05f), clinicContainer.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft);
|
|
||||||
|
|
||||||
// new GUITextBlock(new RectTransform(new Vector2(0.2f, 1f), sortLayout.RectTransform), TextManager.Get("campaignstore.sortby"), font: GUI.SubHeadingFont);
|
|
||||||
|
|
||||||
// GUIDropDown sortDropdown = new GUIDropDown(new RectTransform(new Vector2(0.3f, 1f), sortLayout.RectTransform));
|
|
||||||
//
|
|
||||||
// foreach (SortMode mode in Enum.GetValues(typeof(SortMode)).Cast<SortMode>())
|
|
||||||
// {
|
|
||||||
// sortDropdown.AddItem(TextManager.Get($"medicalclinic.sortmode.{mode}"), mode);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// sortDropdown.SelectItem(SortMode.Severity);
|
|
||||||
|
|
||||||
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, clinicContainer.RectTransform));
|
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, clinicContainer.RectTransform));
|
||||||
|
|
||||||
crewHealList = new CrewHealList(crewList, parent);
|
GUIButton treatAllButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.05f), clinicContainer.RectTransform), TextManager.Get("medicalclinic.treateveryone"))
|
||||||
|
{
|
||||||
|
OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
isWaitingForServer = true;
|
||||||
|
medicalClinic.TreatAllButtonAction(OnReceived);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
crewHealList = new CrewHealList(crewList, parent, treatAllButton);
|
||||||
|
|
||||||
|
void OnReceived(MedicalClinic.CallbackOnlyRequest obj)
|
||||||
|
{
|
||||||
|
isWaitingForServer = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
|
private void CreateCrewEntry(GUIComponent parent, CrewHealList healList, CharacterInfo info, GUIComponent panel)
|
||||||
@@ -525,9 +538,9 @@ namespace Barotrauma
|
|||||||
TextColor = GUIStyle.Red
|
TextColor = GUIStyle.Red
|
||||||
};
|
};
|
||||||
|
|
||||||
MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember { CharacterInfo = info, Afflictions = Array.Empty<MedicalClinic.NetAffliction>() };
|
MedicalClinic.NetCrewMember member = new MedicalClinic.NetCrewMember(info);
|
||||||
|
|
||||||
crewBackground.OnClicked = (_, __) =>
|
crewBackground.OnClicked = (_, _) =>
|
||||||
{
|
{
|
||||||
SelectCharacter(member, new Vector2(panel.Rect.Right, crewBackground.Rect.Top));
|
SelectCharacter(member, new Vector2(panel.Rect.Right, crewBackground.Rect.Top));
|
||||||
return true;
|
return true;
|
||||||
@@ -618,7 +631,7 @@ namespace Barotrauma
|
|||||||
pendingHealList = list;
|
pendingHealList = list;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, MedicalClinic.NetAffliction[] afflictions)
|
private void CreatePendingHealElement(GUIComponent parent, MedicalClinic.NetCrewMember crewMember, PendingHealList healList, ImmutableArray<MedicalClinic.NetAffliction> afflictions)
|
||||||
{
|
{
|
||||||
CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
|
CharacterInfo? healInfo = crewMember.FindCharacterInfo(MedicalClinic.GetCrewCharacters());
|
||||||
if (healInfo is null) { return; }
|
if (healInfo is null) { return; }
|
||||||
@@ -803,7 +816,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
allComponents.Add(treatAllButton);
|
allComponents.Add(treatAllButton);
|
||||||
treatAllButton.OnClicked = (_, __) =>
|
treatAllButton.OnClicked = (_, _) =>
|
||||||
{
|
{
|
||||||
ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
|
ImmutableArray<MedicalClinic.NetAffliction> afflictions = request.Afflictions.Where(a => !medicalClinic.IsAfflictionPending(crewMember, a)).ToImmutableArray();
|
||||||
if (!afflictions.Any()) { return true; }
|
if (!afflictions.Any()) { return true; }
|
||||||
@@ -845,9 +858,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), prefab.Name, font: GUIStyle.SubHeadingFont);
|
GUITextBlock prefabBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1f), topTextLayout.RectTransform), prefab.Name, font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, (int)affliction.AfflictionSeverity / 2f);
|
Color textColor = Color.Lerp(GUIStyle.Orange, GUIStyle.Red, affliction.Strength / affliction.Prefab.MaxStrength);
|
||||||
|
|
||||||
LocalizedString vitalityText = TextManager.GetWithVariable("medicalclinic.vitalitydifference", "[amount]", (-affliction.Strength).ToString());
|
LocalizedString vitalityText = affliction.VitalityDecrease == 0 ? string.Empty : TextManager.GetWithVariable("medicalclinic.vitalitydifference", "[amount]", (-affliction.VitalityDecrease).ToString());
|
||||||
GUITextBlock vitalityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), vitalityText, textAlignment: Alignment.Center)
|
GUITextBlock vitalityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), vitalityText, textAlignment: Alignment.Center)
|
||||||
{
|
{
|
||||||
TextColor = textColor,
|
TextColor = textColor,
|
||||||
@@ -856,7 +869,7 @@ namespace Barotrauma
|
|||||||
AutoScaleHorizontal = true
|
AutoScaleHorizontal = true
|
||||||
};
|
};
|
||||||
|
|
||||||
LocalizedString severityText = TextManager.Get($"AfflictionStrength{affliction.AfflictionSeverity}");
|
LocalizedString severityText = Affliction.GetStrengthText(affliction.Strength, affliction.Prefab.MaxStrength);
|
||||||
GUITextBlock severityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), severityText, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
|
GUITextBlock severityBlock = new GUITextBlock(new RectTransform(new Vector2(0.25f, 1f), topTextLayout.RectTransform), severityText, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont)
|
||||||
{
|
{
|
||||||
TextColor = textColor,
|
TextColor = textColor,
|
||||||
@@ -873,9 +886,13 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
RelativeSpacing = 0.05f
|
RelativeSpacing = 0.05f
|
||||||
};
|
};
|
||||||
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform), prefab.Description, font: GUIStyle.SmallFont, wrap: true)
|
LocalizedString description = affliction.Prefab.GetDescription(affliction.Strength, AfflictionPrefab.Description.TargetType.OtherCharacter);
|
||||||
|
GUITextBlock descriptionBlock = new GUITextBlock(new RectTransform(new Vector2(1f, 0.6f), bottomTextLayout.RectTransform),
|
||||||
|
description,
|
||||||
|
font: GUIStyle.SmallFont,
|
||||||
|
wrap: true)
|
||||||
{
|
{
|
||||||
ToolTip = prefab.Description
|
ToolTip = description
|
||||||
};
|
};
|
||||||
bool truncated = false;
|
bool truncated = false;
|
||||||
while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
|
while (descriptionBlock.TextSize.Y > descriptionBlock.Rect.Height && descriptionBlock.WrappedText.Contains('\n'))
|
||||||
@@ -919,10 +936,9 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
MedicalClinic.NetCrewMember newMember = new MedicalClinic.NetCrewMember
|
MedicalClinic.NetCrewMember newMember = crewMember with
|
||||||
{
|
{
|
||||||
CharacterInfoID = crewMember.CharacterInfoID,
|
Afflictions = ImmutableArray<MedicalClinic.NetAffliction>.Empty
|
||||||
Afflictions = Array.Empty<MedicalClinic.NetAffliction>()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
existingMember = newMember;
|
existingMember = newMember;
|
||||||
@@ -936,7 +952,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToArray();
|
existingMember.Afflictions = existingMember.Afflictions.Concat(afflictions).ToImmutableArray();
|
||||||
ToggleElements(ElementState.Disabled, elementsToDisable);
|
ToggleElements(ElementState.Disabled, elementsToDisable);
|
||||||
medicalClinic.AddPendingButtonAction(existingMember, request =>
|
medicalClinic.AddPendingButtonAction(existingMember, request =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -134,7 +134,7 @@ namespace Barotrauma
|
|||||||
set => hadSellSubPermissions = value;
|
set => hadSellSubPermissions = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasPermissionToUseTab(StoreTab tab)
|
private static bool HasPermissionToUseTab(StoreTab tab)
|
||||||
{
|
{
|
||||||
return tab switch
|
return tab switch
|
||||||
{
|
{
|
||||||
@@ -278,6 +278,7 @@ namespace Barotrauma
|
|||||||
RefreshBuying(updateOwned: false);
|
RefreshBuying(updateOwned: false);
|
||||||
RefreshSelling(updateOwned: false);
|
RefreshSelling(updateOwned: false);
|
||||||
RefreshSellingFromSub(updateOwned: false);
|
RefreshSellingFromSub(updateOwned: false);
|
||||||
|
SetConfirmButtonBehavior();
|
||||||
needsRefresh = false;
|
needsRefresh = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -475,6 +476,7 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
List<MapEntityCategory> itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast<MapEntityCategory>().ToList();
|
List<MapEntityCategory> itemCategories = Enum.GetValues(typeof(MapEntityCategory)).Cast<MapEntityCategory>().ToList();
|
||||||
|
itemCategories.Remove(MapEntityCategory.None);
|
||||||
//don't show categories with no buyable items
|
//don't show categories with no buyable items
|
||||||
itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought));
|
itemCategories.RemoveAll(c => !ItemPrefab.Prefabs.Any(ep => ep.Category.HasFlag(c) && ep.CanBeBought));
|
||||||
itemCategoryButtons.Clear();
|
itemCategoryButtons.Clear();
|
||||||
@@ -507,6 +509,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
btn.RectTransform.SizeChanged += () =>
|
btn.RectTransform.SizeChanged += () =>
|
||||||
{
|
{
|
||||||
|
if (btn.Frame.sprites == null) { return; }
|
||||||
var sprite = btn.Frame.sprites[GUIComponent.ComponentState.None].First();
|
var sprite = btn.Frame.sprites[GUIComponent.ComponentState.None].First();
|
||||||
btn.RectTransform.NonScaledSize = new Point(btn.Rect.Width, (int)(btn.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
|
btn.RectTransform.NonScaledSize = new Point(btn.Rect.Width, (int)(btn.Rect.Width * ((float)sprite.Sprite.SourceRect.Height / sprite.Sprite.SourceRect.Width)));
|
||||||
};
|
};
|
||||||
@@ -617,9 +620,9 @@ namespace Barotrauma
|
|||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style: null);
|
var shoppingCrateListContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.8f), shoppingCrateInventoryContainer.RectTransform), style: null);
|
||||||
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
|
shoppingCrateBuyList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true };
|
||||||
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
|
shoppingCrateSellList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true };
|
||||||
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false };
|
shoppingCrateSellFromSubList = new GUIListBox(new RectTransform(Vector2.One, shoppingCrateListContainer.RectTransform)) { Visible = false, KeepSpaceForScrollBar = true };
|
||||||
|
|
||||||
var relevantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
|
var relevantBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), shoppingCrateInventoryContainer.RectTransform), isHorizontal: true)
|
||||||
{
|
{
|
||||||
@@ -745,7 +748,7 @@ namespace Barotrauma
|
|||||||
} ?? Enumerable.Empty<PurchasedItem>();
|
} ?? Enumerable.Empty<PurchasedItem>();
|
||||||
foreach (var button in itemCategoryButtons)
|
foreach (var button in itemCategoryButtons)
|
||||||
{
|
{
|
||||||
if (!(button.UserData is MapEntityCategory category))
|
if (button.UserData is not MapEntityCategory category)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -859,18 +862,17 @@ namespace Barotrauma
|
|||||||
float prevBuyListScroll = storeBuyList.BarScroll;
|
float prevBuyListScroll = storeBuyList.BarScroll;
|
||||||
float prevShoppingCrateScroll = shoppingCrateBuyList.BarScroll;
|
float prevShoppingCrateScroll = shoppingCrateBuyList.BarScroll;
|
||||||
|
|
||||||
int dailySpecialCount = ActiveStore.DailySpecials.Count;
|
int dailySpecialCount = ActiveStore?.DailySpecials.Count(s => s.CanCharacterBuy()) ?? 0;
|
||||||
if ((storeDailySpecialsGroup != null) != ActiveStore.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
|
if ((ActiveStore == null && storeDailySpecialsGroup != null) || (storeDailySpecialsGroup != null) != ActiveStore.DailySpecials.Any() || dailySpecialCount != prevDailySpecialCount)
|
||||||
{
|
{
|
||||||
if (storeDailySpecialsGroup == null || dailySpecialCount != prevDailySpecialCount)
|
storeBuyList.RemoveChild(storeDailySpecialsGroup?.Parent);
|
||||||
|
if (ActiveStore != null && (storeDailySpecialsGroup == null || dailySpecialCount != prevDailySpecialCount))
|
||||||
{
|
{
|
||||||
storeBuyList.RemoveChild(storeDailySpecialsGroup?.Parent);
|
|
||||||
storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, dailySpecialCount);
|
storeDailySpecialsGroup = CreateDealsGroup(storeBuyList, dailySpecialCount);
|
||||||
storeDailySpecialsGroup.Parent.SetAsFirstChild();
|
storeDailySpecialsGroup.Parent.SetAsFirstChild();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
storeBuyList.RemoveChild(storeDailySpecialsGroup.Parent);
|
|
||||||
storeDailySpecialsGroup = null;
|
storeDailySpecialsGroup = null;
|
||||||
}
|
}
|
||||||
storeBuyList.RecalculateChildren();
|
storeBuyList.RecalculateChildren();
|
||||||
@@ -879,20 +881,22 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasPermissions = HasTabPermissions(StoreTab.Buy);
|
bool hasPermissions = HasTabPermissions(StoreTab.Buy);
|
||||||
var existingItemFrames = new HashSet<GUIComponent>();
|
var existingItemFrames = new HashSet<GUIComponent>();
|
||||||
foreach (PurchasedItem item in ActiveStore.Stock)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
foreach (PurchasedItem item in ActiveStore.Stock)
|
||||||
}
|
{
|
||||||
|
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
||||||
foreach (ItemPrefab itemPrefab in ActiveStore.DailySpecials)
|
}
|
||||||
{
|
foreach (ItemPrefab itemPrefab in ActiveStore.DailySpecials)
|
||||||
if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) { continue; }
|
{
|
||||||
CreateOrUpdateItemFrame(itemPrefab, 0);
|
if (ActiveStore.Stock.Any(pi => pi.ItemPrefab == itemPrefab)) { continue; }
|
||||||
|
CreateOrUpdateItemFrame(itemPrefab, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
|
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
|
||||||
{
|
{
|
||||||
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo))
|
if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
|
||||||
{
|
{
|
||||||
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
|
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
|
||||||
var itemFrame = isDailySpecial ?
|
var itemFrame = isDailySpecial ?
|
||||||
@@ -945,11 +949,11 @@ namespace Barotrauma
|
|||||||
float prevSellListScroll = storeSellList.BarScroll;
|
float prevSellListScroll = storeSellList.BarScroll;
|
||||||
float prevShoppingCrateScroll = shoppingCrateSellList.BarScroll;
|
float prevShoppingCrateScroll = shoppingCrateSellList.BarScroll;
|
||||||
|
|
||||||
int requestedGoodsCount = ActiveStore.RequestedGoods.Count;
|
int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
|
||||||
if ((storeRequestedGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
|
if ((ActiveStore == null && storeRequestedGoodGroup != null) || (storeRequestedGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevRequestedGoodsCount)
|
||||||
{
|
{
|
||||||
storeSellList.RemoveChild(storeRequestedGoodGroup?.Parent);
|
storeSellList.RemoveChild(storeRequestedGoodGroup?.Parent);
|
||||||
if (storeRequestedGoodGroup == null || requestedGoodsCount != prevRequestedGoodsCount)
|
if (ActiveStore != null && (storeRequestedGoodGroup == null || requestedGoodsCount != prevRequestedGoodsCount))
|
||||||
{
|
{
|
||||||
storeRequestedGoodGroup = CreateDealsGroup(storeSellList, requestedGoodsCount);
|
storeRequestedGoodGroup = CreateDealsGroup(storeSellList, requestedGoodsCount);
|
||||||
storeRequestedGoodGroup.Parent.SetAsFirstChild();
|
storeRequestedGoodGroup.Parent.SetAsFirstChild();
|
||||||
@@ -964,14 +968,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasPermissions = HasTabPermissions(StoreTab.Sell);
|
bool hasPermissions = HasTabPermissions(StoreTab.Sell);
|
||||||
var existingItemFrames = new HashSet<GUIComponent>();
|
var existingItemFrames = new HashSet<GUIComponent>();
|
||||||
foreach (PurchasedItem item in itemsToSell)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
foreach (PurchasedItem item in itemsToSell)
|
||||||
}
|
{
|
||||||
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
||||||
{
|
}
|
||||||
if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
||||||
CreateOrUpdateItemFrame(requestedGood, 0);
|
{
|
||||||
|
if (itemsToSell.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
||||||
|
CreateOrUpdateItemFrame(requestedGood, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
||||||
@@ -1029,11 +1036,11 @@ namespace Barotrauma
|
|||||||
float prevSellListScroll = storeSellFromSubList.BarScroll;
|
float prevSellListScroll = storeSellFromSubList.BarScroll;
|
||||||
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
|
float prevShoppingCrateScroll = shoppingCrateSellFromSubList.BarScroll;
|
||||||
|
|
||||||
int requestedGoodsCount = ActiveStore.RequestedGoods.Count;
|
int requestedGoodsCount = ActiveStore?.RequestedGoods.Count ?? 0;
|
||||||
if ((storeRequestedSubGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
|
if ((ActiveStore == null && storeRequestedSubGoodGroup != null) || (storeRequestedSubGoodGroup != null) != ActiveStore.RequestedGoods.Any() || requestedGoodsCount != prevSubRequestedGoodsCount)
|
||||||
{
|
{
|
||||||
storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup?.Parent);
|
storeSellFromSubList.RemoveChild(storeRequestedSubGoodGroup?.Parent);
|
||||||
if (storeRequestedSubGoodGroup == null || requestedGoodsCount != prevSubRequestedGoodsCount)
|
if (ActiveStore != null && (storeRequestedSubGoodGroup == null || requestedGoodsCount != prevSubRequestedGoodsCount))
|
||||||
{
|
{
|
||||||
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, requestedGoodsCount);
|
storeRequestedSubGoodGroup = CreateDealsGroup(storeSellFromSubList, requestedGoodsCount);
|
||||||
storeRequestedSubGoodGroup.Parent.SetAsFirstChild();
|
storeRequestedSubGoodGroup.Parent.SetAsFirstChild();
|
||||||
@@ -1048,14 +1055,17 @@ namespace Barotrauma
|
|||||||
|
|
||||||
bool hasPermissions = HasSellSubPermissions;
|
bool hasPermissions = HasSellSubPermissions;
|
||||||
var existingItemFrames = new HashSet<GUIComponent>();
|
var existingItemFrames = new HashSet<GUIComponent>();
|
||||||
foreach (PurchasedItem item in itemsToSellFromSub)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
foreach (PurchasedItem item in itemsToSellFromSub)
|
||||||
}
|
{
|
||||||
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
CreateOrUpdateItemFrame(item.ItemPrefab, item.Quantity);
|
||||||
{
|
}
|
||||||
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
foreach (var requestedGood in ActiveStore.RequestedGoods)
|
||||||
CreateOrUpdateItemFrame(requestedGood, 0);
|
{
|
||||||
|
if (itemsToSellFromSub.Any(pi => pi.ItemPrefab == requestedGood)) { continue; }
|
||||||
|
CreateOrUpdateItemFrame(requestedGood, 0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int itemQuantity)
|
||||||
@@ -1110,7 +1120,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SetPriceGetters(GUIComponent itemFrame, bool buying)
|
private void SetPriceGetters(GUIComponent itemFrame, bool buying)
|
||||||
{
|
{
|
||||||
if (itemFrame == null || !(itemFrame.UserData is PurchasedItem pi)) { return; }
|
if (itemFrame == null || itemFrame.UserData is not PurchasedItem pi) { return; }
|
||||||
|
|
||||||
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
|
if (itemFrame.FindChild("undiscountedprice", recursive: true) is GUITextBlock undiscountedPriceBlock)
|
||||||
{
|
{
|
||||||
@@ -1142,6 +1152,7 @@ namespace Barotrauma
|
|||||||
public void RefreshItemsToSell()
|
public void RefreshItemsToSell()
|
||||||
{
|
{
|
||||||
itemsToSell.Clear();
|
itemsToSell.Clear();
|
||||||
|
if (ActiveStore == null) { return; }
|
||||||
var playerItems = CargoManager.GetSellableItems(Character.Controlled);
|
var playerItems = CargoManager.GetSellableItems(Character.Controlled);
|
||||||
foreach (Item playerItem in playerItems)
|
foreach (Item playerItem in playerItems)
|
||||||
{
|
{
|
||||||
@@ -1172,6 +1183,7 @@ namespace Barotrauma
|
|||||||
public void RefreshItemsToSellFromSub()
|
public void RefreshItemsToSellFromSub()
|
||||||
{
|
{
|
||||||
itemsToSellFromSub.Clear();
|
itemsToSellFromSub.Clear();
|
||||||
|
if (ActiveStore == null) { return; }
|
||||||
var subItems = CargoManager.GetSellableItemsFromSub();
|
var subItems = CargoManager.GetSellableItemsFromSub();
|
||||||
foreach (Item subItem in subItems)
|
foreach (Item subItem in subItems)
|
||||||
{
|
{
|
||||||
@@ -1205,52 +1217,55 @@ namespace Barotrauma
|
|||||||
bool hasPermissions = HasTabPermissions(tab);
|
bool hasPermissions = HasTabPermissions(tab);
|
||||||
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
|
HashSet<GUIComponent> existingItemFrames = new HashSet<GUIComponent>();
|
||||||
int totalPrice = 0;
|
int totalPrice = 0;
|
||||||
foreach (PurchasedItem item in items)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; }
|
foreach (PurchasedItem item in items)
|
||||||
GUINumberInput numInput = null;
|
|
||||||
if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame))
|
|
||||||
{
|
{
|
||||||
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
|
if (!(item.ItemPrefab.GetPriceInfo(ActiveStore) is { } priceInfo)) { continue; }
|
||||||
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
|
GUINumberInput numInput = null;
|
||||||
}
|
if (!(listBox.Content.FindChild(c => c.UserData is PurchasedItem pi && pi.ItemPrefab.Identifier == item.ItemPrefab.Identifier) is { } itemFrame))
|
||||||
else
|
{
|
||||||
{
|
itemFrame = CreateItemFrame(item, listBox, tab, forceDisable: !hasPermissions);
|
||||||
itemFrame.UserData = item;
|
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
|
||||||
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemFrame.UserData = item;
|
||||||
|
numInput = itemFrame.FindChild(c => c is GUINumberInput, recursive: true) as GUINumberInput;
|
||||||
|
if (numInput != null)
|
||||||
|
{
|
||||||
|
numInput.UserData = item;
|
||||||
|
numInput.Enabled = hasPermissions;
|
||||||
|
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
|
||||||
|
}
|
||||||
|
SetOwnedText(itemFrame);
|
||||||
|
SetItemFrameStatus(itemFrame, hasPermissions);
|
||||||
|
}
|
||||||
|
existingItemFrames.Add(itemFrame);
|
||||||
|
|
||||||
|
suppressBuySell = true;
|
||||||
if (numInput != null)
|
if (numInput != null)
|
||||||
{
|
{
|
||||||
numInput.UserData = item;
|
if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUIStyle.Green); }
|
||||||
numInput.Enabled = hasPermissions;
|
numInput.IntValue = item.Quantity;
|
||||||
numInput.MaxValueInt = GetMaxAvailable(item.ItemPrefab, tab);
|
|
||||||
}
|
}
|
||||||
SetOwnedText(itemFrame);
|
suppressBuySell = false;
|
||||||
SetItemFrameStatus(itemFrame, hasPermissions);
|
|
||||||
}
|
|
||||||
existingItemFrames.Add(itemFrame);
|
|
||||||
|
|
||||||
suppressBuySell = true;
|
try
|
||||||
if (numInput != null)
|
|
||||||
{
|
|
||||||
if (numInput.IntValue != item.Quantity) { itemFrame.Flash(GUIStyle.Green); }
|
|
||||||
numInput.IntValue = item.Quantity;
|
|
||||||
}
|
|
||||||
suppressBuySell = false;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
int price = tab switch
|
|
||||||
{
|
{
|
||||||
StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo),
|
int price = tab switch
|
||||||
StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
{
|
||||||
StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
StoreTab.Buy => ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo),
|
||||||
_ => throw new NotImplementedException()
|
StoreTab.Sell => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
||||||
};
|
StoreTab.SellSub => ActiveStore.GetAdjustedItemSellPrice(item.ItemPrefab, priceInfo: priceInfo),
|
||||||
totalPrice += item.Quantity * price;
|
_ => throw new NotImplementedException()
|
||||||
}
|
};
|
||||||
catch (NotImplementedException e)
|
totalPrice += item.Quantity * price;
|
||||||
{
|
}
|
||||||
DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
|
catch (NotImplementedException e)
|
||||||
|
{
|
||||||
|
DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1287,7 +1302,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SortItems(GUIListBox list, SortingMethod sortingMethod)
|
private void SortItems(GUIListBox list, SortingMethod sortingMethod)
|
||||||
{
|
{
|
||||||
if (CurrentLocation == null) { return; }
|
if (CurrentLocation == null || ActiveStore == null) { return; }
|
||||||
|
|
||||||
if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc)
|
if (sortingMethod == SortingMethod.AlphabeticalAsc || sortingMethod == SortingMethod.AlphabeticalDesc)
|
||||||
{
|
{
|
||||||
@@ -1662,13 +1677,15 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
OwnedItems.Clear();
|
OwnedItems.Clear();
|
||||||
|
|
||||||
|
if (ActiveStore == null) { return; }
|
||||||
|
|
||||||
// Add items on the sub(s)
|
// Add items on the sub(s)
|
||||||
if (Submarine.MainSub?.GetItems(true) is List<Item> subItems)
|
if (Submarine.MainSub?.GetItems(true) is List<Item> subItems)
|
||||||
{
|
{
|
||||||
foreach (var subItem in subItems)
|
foreach (var subItem in subItems)
|
||||||
{
|
{
|
||||||
if (!subItem.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { continue; }
|
if (!subItem.Components.All(c => c is not Holdable h || !h.Attachable || !h.Attached)) { continue; }
|
||||||
if (!subItem.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { continue; }
|
if (!subItem.Components.All(c => c is not Wire w || w.Connections.All(c => c == null))) { continue; }
|
||||||
if (!ItemAndAllContainersInteractable(subItem)) { continue; }
|
if (!ItemAndAllContainersInteractable(subItem)) { continue; }
|
||||||
AddOwnedItem(subItem);
|
AddOwnedItem(subItem);
|
||||||
}
|
}
|
||||||
@@ -1701,7 +1718,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
void AddOwnedItem(Item item)
|
void AddOwnedItem(Item item)
|
||||||
{
|
{
|
||||||
if (!(item?.Prefab.GetPriceInfo(ActiveStore) is PriceInfo priceInfo)) { return; }
|
if (item?.Prefab.GetPriceInfo(ActiveStore) is not PriceInfo priceInfo) { return; }
|
||||||
bool isNonEmpty = !priceInfo.DisplayNonEmpty || item.ConditionPercentage > 5.0f;
|
bool isNonEmpty = !priceInfo.DisplayNonEmpty || item.ConditionPercentage > 5.0f;
|
||||||
if (OwnedItems.TryGetValue(item.Prefab, out ItemQuantity itemQuantity))
|
if (OwnedItems.TryGetValue(item.Prefab, out ItemQuantity itemQuantity))
|
||||||
{
|
{
|
||||||
@@ -1729,7 +1746,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
|
private void SetItemFrameStatus(GUIComponent itemFrame, bool enabled)
|
||||||
{
|
{
|
||||||
if (!(itemFrame?.UserData is PurchasedItem pi)) { return; }
|
if (itemFrame?.UserData is not PurchasedItem pi) { return; }
|
||||||
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
|
bool refreshFrameStatus = !pi.IsStoreComponentEnabled.HasValue || pi.IsStoreComponentEnabled.Value != enabled;
|
||||||
if (!refreshFrameStatus) { return; }
|
if (!refreshFrameStatus) { return; }
|
||||||
if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon)
|
if (itemFrame.FindChild("icon", recursive: true) is GUIImage icon)
|
||||||
@@ -1841,11 +1858,7 @@ namespace Barotrauma
|
|||||||
LocalizedString toolTip = string.Empty;
|
LocalizedString toolTip = string.Empty;
|
||||||
if (purchasedItem.ItemPrefab != null)
|
if (purchasedItem.ItemPrefab != null)
|
||||||
{
|
{
|
||||||
toolTip = purchasedItem.ItemPrefab.Name;
|
toolTip = purchasedItem.ItemPrefab.GetTooltip();
|
||||||
if (!purchasedItem.ItemPrefab.Description.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
toolTip += $"\n{purchasedItem.ItemPrefab.Description}";
|
|
||||||
}
|
|
||||||
if (itemQuantity != null)
|
if (itemQuantity != null)
|
||||||
{
|
{
|
||||||
if (itemQuantity.AllNonEmpty)
|
if (itemQuantity.AllNonEmpty)
|
||||||
@@ -1859,7 +1872,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
itemComponent.ToolTip = toolTip;
|
itemComponent.ToolTip = RichString.Rich(toolTip);
|
||||||
}
|
}
|
||||||
if (ownedLabel != null)
|
if (ownedLabel != null)
|
||||||
{
|
{
|
||||||
@@ -1995,11 +2008,23 @@ namespace Barotrauma
|
|||||||
int totalPrice = 0;
|
int totalPrice = 0;
|
||||||
foreach (var item in itemsToPurchase)
|
foreach (var item in itemsToPurchase)
|
||||||
{
|
{
|
||||||
if (item?.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
|
if (item is null) { continue; }
|
||||||
|
|
||||||
|
if (item.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
|
||||||
{
|
{
|
||||||
itemsToRemove.Add(item);
|
itemsToRemove.Add(item);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (item.ItemPrefab.DefaultPrice.RequiresUnlock)
|
||||||
|
{
|
||||||
|
if (!CargoManager.HasUnlockedStoreItem(item.ItemPrefab))
|
||||||
|
{
|
||||||
|
itemsToRemove.Add(item);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo);
|
totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo);
|
||||||
}
|
}
|
||||||
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
|
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
|
||||||
@@ -2054,7 +2079,12 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SetShoppingCrateTotalText()
|
private void SetShoppingCrateTotalText()
|
||||||
{
|
{
|
||||||
if (IsBuying)
|
if (ActiveStore == null)
|
||||||
|
{
|
||||||
|
shoppingCrateTotal.Text = TextManager.FormatCurrency(0);
|
||||||
|
shoppingCrateTotal.TextColor = Color.White;
|
||||||
|
}
|
||||||
|
else if (IsBuying)
|
||||||
{
|
{
|
||||||
shoppingCrateTotal.Text = TextManager.FormatCurrency(buyTotal);
|
shoppingCrateTotal.Text = TextManager.FormatCurrency(buyTotal);
|
||||||
shoppingCrateTotal.TextColor = Balance < buyTotal ? Color.Red : Color.White;
|
shoppingCrateTotal.TextColor = Balance < buyTotal ? Color.Red : Color.White;
|
||||||
@@ -2074,7 +2104,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void SetConfirmButtonBehavior()
|
private void SetConfirmButtonBehavior()
|
||||||
{
|
{
|
||||||
if (IsBuying)
|
if (ActiveStore == null)
|
||||||
|
{
|
||||||
|
confirmButton.OnClicked = null;
|
||||||
|
}
|
||||||
|
else if (IsBuying)
|
||||||
{
|
{
|
||||||
confirmButton.ClickSound = GUISoundType.ConfirmTransaction;
|
confirmButton.ClickSound = GUISoundType.ConfirmTransaction;
|
||||||
confirmButton.Text = TextManager.Get("CampaignStore.Purchase");
|
confirmButton.Text = TextManager.Get("CampaignStore.Purchase");
|
||||||
@@ -2102,6 +2136,7 @@ namespace Barotrauma
|
|||||||
private void SetConfirmButtonStatus()
|
private void SetConfirmButtonStatus()
|
||||||
{
|
{
|
||||||
confirmButton.Enabled =
|
confirmButton.Enabled =
|
||||||
|
ActiveStore != null &&
|
||||||
HasActiveTabPermissions() &&
|
HasActiveTabPermissions() &&
|
||||||
ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
|
ActiveShoppingCrateList.Content.RectTransform.Children.Any() &&
|
||||||
activeTab switch
|
activeTab switch
|
||||||
@@ -2111,6 +2146,7 @@ namespace Barotrauma
|
|||||||
StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= ActiveStore.Balance,
|
StoreTab.SellSub => CurrentLocation != null && sellFromSubTotal <= ActiveStore.Balance,
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
|
confirmButton.Visible = ActiveStore != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetClearAllButtonStatus()
|
private void SetClearAllButtonStatus()
|
||||||
@@ -2169,7 +2205,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
needsRefresh = itemsToSellFromSub.Count != prevSubItems.Count ||
|
needsRefresh = itemsToSellFromSub.Count != prevSubItems.Count ||
|
||||||
itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) ||
|
itemsToSellFromSub.Sum(i => i.Quantity) != prevSubItems.Sum(i => i.Quantity) ||
|
||||||
itemsToSellFromSub.Any(i => !(prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is PurchasedItem prev) || i.Quantity != prev.Quantity) ||
|
itemsToSellFromSub.Any(i => prevSubItems.FirstOrDefault(prev => prev.ItemPrefab == i.ItemPrefab) is not PurchasedItem prev || i.Quantity != prev.Quantity) ||
|
||||||
prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab));
|
prevSubItems.Any(prev => itemsToSellFromSub.None(i => i.ItemPrefab == prev.ItemPrefab));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2184,29 +2220,32 @@ namespace Barotrauma
|
|||||||
prevBalance = currBalance;
|
prevBalance = currBalance;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (needsItemsToSellRefresh)
|
if (ActiveStore != null)
|
||||||
{
|
{
|
||||||
RefreshItemsToSell();
|
if (needsItemsToSellRefresh)
|
||||||
}
|
{
|
||||||
if (needsItemsToSellFromSubRefresh)
|
RefreshItemsToSell();
|
||||||
{
|
}
|
||||||
RefreshItemsToSellFromSub();
|
if (needsItemsToSellFromSubRefresh)
|
||||||
}
|
{
|
||||||
if (needsRefresh)
|
RefreshItemsToSellFromSub();
|
||||||
{
|
}
|
||||||
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
if (needsRefresh)
|
||||||
}
|
{
|
||||||
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
|
Refresh(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
||||||
{
|
}
|
||||||
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
if (needsBuyingRefresh || HavePermissionsChanged(StoreTab.Buy))
|
||||||
}
|
{
|
||||||
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell))
|
RefreshBuying(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
||||||
{
|
}
|
||||||
RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
if (needsSellingRefresh || HavePermissionsChanged(StoreTab.Sell))
|
||||||
}
|
{
|
||||||
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub))
|
RefreshSelling(updateOwned: ownedItemsUpdateTimer > 0.0f);
|
||||||
{
|
}
|
||||||
RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
|
if (needsSellingFromSubRefresh || HavePermissionsChanged(StoreTab.SellSub))
|
||||||
|
{
|
||||||
|
RefreshSellingFromSub(updateOwned: ownedItemsUpdateTimer > 0.0f, updateItemsToSellFromSub: sellableItemsFromSubUpdateTimer > 0.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateStopwatch.Stop();
|
updateStopwatch.Stop();
|
||||||
|
|||||||
@@ -472,7 +472,7 @@ namespace Barotrauma
|
|||||||
if (transferService)
|
if (transferService)
|
||||||
{
|
{
|
||||||
subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines);
|
subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines);
|
||||||
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
|
subsToShow.Sort(ComparePrice);
|
||||||
string currentSubName = CurrentOrPendingSubmarine().Name;
|
string currentSubName = CurrentOrPendingSubmarine().Name;
|
||||||
int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName);
|
int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName);
|
||||||
if (currentIndex != -1)
|
if (currentIndex != -1)
|
||||||
@@ -484,7 +484,11 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs())
|
subsToShow.AddRange((GameMain.Client is null ? SubmarineInfo.SavedSubmarines : MultiPlayerCampaign.GetCampaignSubs())
|
||||||
.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
|
.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
|
||||||
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
|
if (GameMain.GameSession.Campaign?.Map?.CurrentLocation is Location currentLocation)
|
||||||
|
{
|
||||||
|
subsToShow.RemoveAll(sub => !currentLocation.IsSubmarineAvailable(sub));
|
||||||
|
}
|
||||||
|
subsToShow.Sort(ComparePrice);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (transferService)
|
if (transferService)
|
||||||
@@ -492,10 +496,14 @@ namespace Barotrauma
|
|||||||
SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
|
SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
|
|
||||||
pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage));
|
pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage));
|
||||||
UpdatePaging();
|
UpdatePaging();
|
||||||
ContentRefreshRequired = false;
|
ContentRefreshRequired = false;
|
||||||
|
|
||||||
|
static int ComparePrice(SubmarineInfo x, SubmarineInfo y)
|
||||||
|
{
|
||||||
|
return x.Price.CompareTo(y.Price) * 100 + x.Name.CompareTo(y.Name);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private SubmarineInfo GetSubToDisplay(int index)
|
private SubmarineInfo GetSubToDisplay(int index)
|
||||||
@@ -673,7 +681,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
|
if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
|
||||||
{
|
{
|
||||||
return Submarine.MainSub.Info;
|
return Submarine.MainSub?.Info;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace Barotrauma
|
|||||||
private List<CharacterTeamType> teamIDs;
|
private List<CharacterTeamType> teamIDs;
|
||||||
private const string inLobbyString = "\u2022 \u2022 \u2022";
|
private const string inLobbyString = "\u2022 \u2022 \u2022";
|
||||||
|
|
||||||
private GUIFrame pendingChangesFrame = null;
|
public static GUIFrame PendingChangesFrame = null;
|
||||||
|
|
||||||
public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
|
public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
|
||||||
private bool isTransferMenuOpen;
|
private bool isTransferMenuOpen;
|
||||||
@@ -44,6 +44,7 @@ namespace Barotrauma
|
|||||||
private float transferMenuOpenState;
|
private float transferMenuOpenState;
|
||||||
private bool transferMenuStateCompleted;
|
private bool transferMenuStateCompleted;
|
||||||
private readonly HashSet<Identifier> registeredEvents = new HashSet<Identifier>();
|
private readonly HashSet<Identifier> registeredEvents = new HashSet<Identifier>();
|
||||||
|
private readonly TalentMenu talentMenu = new TalentMenu();
|
||||||
|
|
||||||
private class LinkedGUI
|
private class LinkedGUI
|
||||||
{
|
{
|
||||||
@@ -206,15 +207,8 @@ namespace Barotrauma
|
|||||||
transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height);
|
transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height);
|
||||||
}
|
}
|
||||||
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
|
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
|
||||||
if (Character.Controlled?.Info is { } characterInfo && talentResetButton != null && talentApplyButton != null)
|
|
||||||
{
|
talentMenu?.Update();
|
||||||
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
|
|
||||||
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
|
|
||||||
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
|
|
||||||
{
|
|
||||||
talentApplyButton.Flash(GUIStyle.Orange);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (SelectedTab != InfoFrameTab.Crew) { return; }
|
if (SelectedTab != InfoFrameTab.Crew) { return; }
|
||||||
if (linkedGUIList == null) { return; }
|
if (linkedGUIList == null) { return; }
|
||||||
@@ -325,11 +319,11 @@ namespace Barotrauma
|
|||||||
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
|
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
|
||||||
}, style: null);
|
}, style: null);
|
||||||
|
|
||||||
pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
|
PendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
|
||||||
|
|
||||||
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
|
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
|
||||||
{
|
{
|
||||||
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
|
NetLobbyScreen.CreateChangesPendingFrame(PendingChangesFrame);
|
||||||
}
|
}
|
||||||
|
|
||||||
SetBalanceText(balanceText, campaignMode.Bank.Balance);
|
SetBalanceText(balanceText, campaignMode.Bank.Balance);
|
||||||
@@ -403,7 +397,7 @@ namespace Barotrauma
|
|||||||
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
|
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
|
||||||
break;
|
break;
|
||||||
case InfoFrameTab.Talents:
|
case InfoFrameTab.Talents:
|
||||||
CreateCharacterInfo(infoFrameHolder);
|
talentMenu.CreateGUI(infoFrameHolder, Character.Controlled ?? GameMain.Client?.Character);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -957,16 +951,26 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (character != null)
|
if (character != null)
|
||||||
{
|
{
|
||||||
if (GameMain.Client == null)
|
if (GameMain.Client is null)
|
||||||
{
|
{
|
||||||
GUIComponent preview = character.Info.CreateInfoFrame(background, false, null);
|
GUIComponent preview = character.Info.CreateInfoFrame(background, false, null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
|
GUIComponent preview = character.Info.CreateInfoFrame(background, false, GetPermissionIcon(GameMain.Client.ConnectedClients.Find(c => c.Character == character)));
|
||||||
|
|
||||||
GameMain.Client.SelectCrewCharacter(character, preview);
|
GameMain.Client.SelectCrewCharacter(character, preview);
|
||||||
if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); }
|
if (!character.IsBot && GameMain.GameSession?.Campaign is MultiPlayerCampaign mpCampaign) { CreateWalletFrame(background, character, mpCampaign); }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (background.FindChild(TalentMenu.ManageBotTalentsButtonUserData, recursive: true) is GUIButton { Enabled: true } talentButton)
|
||||||
|
{
|
||||||
|
talentButton.OnClicked = (button, o) =>
|
||||||
|
{
|
||||||
|
talentMenu.CreateGUI(infoFrameHolder, character);
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (client != null)
|
else if (client != null)
|
||||||
{
|
{
|
||||||
@@ -1487,7 +1491,11 @@ namespace Barotrauma
|
|||||||
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
||||||
int padding = (int)(0.0245f * missionFrame.Rect.Height);
|
int padding = (int)(0.0245f * missionFrame.Rect.Height);
|
||||||
GUIFrame missionFrameContent = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, missionFrame.Rect.Height - padding * 2), infoFrame.RectTransform, Anchor.Center), style: null);
|
GUIFrame missionFrameContent = new GUIFrame(new RectTransform(new Point(missionFrame.Rect.Width - padding * 2, missionFrame.Rect.Height - padding * 2), infoFrame.RectTransform, Anchor.Center), style: null);
|
||||||
Location location = GameMain.GameSession.EndLocation ?? GameMain.GameSession.StartLocation;
|
Location location = GameMain.GameSession.StartLocation;
|
||||||
|
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection)
|
||||||
|
{
|
||||||
|
location ??= GameMain.GameSession.EndLocation;
|
||||||
|
}
|
||||||
|
|
||||||
GUILayoutGroup locationInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), missionFrameContent.RectTransform))
|
GUILayoutGroup locationInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), missionFrameContent.RectTransform))
|
||||||
{
|
{
|
||||||
@@ -1774,373 +1782,10 @@ namespace Barotrauma
|
|||||||
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
|
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private Color unselectedColor = new Color(240, 255, 255, 225);
|
|
||||||
private Color unselectableColor = new Color(100, 100, 100, 225);
|
|
||||||
private Color pressedColor = new Color(60, 60, 60, 225);
|
|
||||||
|
|
||||||
private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>();
|
|
||||||
private readonly List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>();
|
|
||||||
private List<Identifier> selectedTalents = new List<Identifier>();
|
|
||||||
|
|
||||||
private GUITextBlock experienceText;
|
|
||||||
private GUIProgressBar experienceBar;
|
|
||||||
private GUITextBlock talentPointText;
|
|
||||||
private GUIListBox skillListBox;
|
|
||||||
|
|
||||||
private GUIButton talentApplyButton,
|
|
||||||
talentResetButton;
|
|
||||||
|
|
||||||
private GUIImage talentPointNotification;
|
private GUIImage talentPointNotification;
|
||||||
|
|
||||||
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, GUIComponentStyle> talentStageStyles = new Dictionary<TalentTree.TalentTreeStageState, GUIComponentStyle>
|
public static void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
|
||||||
{
|
|
||||||
{ TalentTree.TalentTreeStageState.Invalid, GUIStyle.GetComponentStyle("TalentTreeLocked") },
|
|
||||||
{ TalentTree.TalentTreeStageState.Locked, GUIStyle.GetComponentStyle("TalentTreeLocked") },
|
|
||||||
{ TalentTree.TalentTreeStageState.Unlocked, GUIStyle.GetComponentStyle("TalentTreePurchased") },
|
|
||||||
{ TalentTree.TalentTreeStageState.Available, GUIStyle.GetComponentStyle("TalentTreeUnlocked") },
|
|
||||||
{ TalentTree.TalentTreeStageState.Highlighted, GUIStyle.GetComponentStyle("TalentTreeAvailable") },
|
|
||||||
}.ToImmutableDictionary();
|
|
||||||
|
|
||||||
private readonly ImmutableDictionary<TalentTree.TalentTreeStageState, Color> talentStageBackgroundColors = new Dictionary<TalentTree.TalentTreeStageState, Color>
|
|
||||||
{
|
|
||||||
{ TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) },
|
|
||||||
{ TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) },
|
|
||||||
{ TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) },
|
|
||||||
{ TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) },
|
|
||||||
{ TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) },
|
|
||||||
}.ToImmutableDictionary();
|
|
||||||
|
|
||||||
private void CreateCharacterInfo(GUIFrame infoFrame)
|
|
||||||
{
|
|
||||||
infoFrame.ClearChildren();
|
|
||||||
talentButtons.Clear();
|
|
||||||
talentCornerIcons.Clear();
|
|
||||||
|
|
||||||
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
|
||||||
int padding = GUI.IntScale(15);
|
|
||||||
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
|
|
||||||
|
|
||||||
GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
|
|
||||||
|
|
||||||
GUIFrame characterSettingsFrame = null;
|
|
||||||
GUILayoutGroup characterLayout = null;
|
|
||||||
if (!(GameMain.NetworkMember is null))
|
|
||||||
{
|
|
||||||
characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: null) { Visible = false };
|
|
||||||
characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
|
|
||||||
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
|
|
||||||
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
|
|
||||||
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
|
|
||||||
}
|
|
||||||
|
|
||||||
Character controlledCharacter = Character.Controlled;
|
|
||||||
CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
|
|
||||||
if (info == null) { return; }
|
|
||||||
|
|
||||||
Job job = info.Job;
|
|
||||||
|
|
||||||
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
|
|
||||||
{
|
|
||||||
AbsoluteSpacing = GUI.IntScale(10),
|
|
||||||
Stretch = true
|
|
||||||
};
|
|
||||||
|
|
||||||
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), contentLayout.RectTransform, Anchor.Center), isHorizontal: true);
|
|
||||||
|
|
||||||
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
|
|
||||||
{
|
|
||||||
float posY = component.Rect.Center.Y - component.Rect.Width / 2;
|
|
||||||
info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
|
|
||||||
});
|
|
||||||
|
|
||||||
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
|
|
||||||
{
|
|
||||||
AbsoluteSpacing = GUI.IntScale(5),
|
|
||||||
CanBeFocused = true
|
|
||||||
};
|
|
||||||
|
|
||||||
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
|
|
||||||
|
|
||||||
if (!info.OmitJobInMenus)
|
|
||||||
{
|
|
||||||
nameBlock.TextColor = job.Prefab.UIColor;
|
|
||||||
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
|
|
||||||
}
|
|
||||||
|
|
||||||
if (info.PersonalityTrait != null)
|
|
||||||
{
|
|
||||||
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
|
|
||||||
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
|
|
||||||
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
|
|
||||||
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
|
|
||||||
if (talentsOutsideTree.Count() > 0)
|
|
||||||
{
|
|
||||||
//spacing
|
|
||||||
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
|
|
||||||
|
|
||||||
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
|
|
||||||
|
|
||||||
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont);
|
|
||||||
talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
|
|
||||||
|
|
||||||
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
|
|
||||||
{
|
|
||||||
AutoHideScrollBar = false,
|
|
||||||
ResizeContentToMakeSpaceForScrollBar = false
|
|
||||||
};
|
|
||||||
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
|
|
||||||
extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
|
|
||||||
extraTalentLayout.Recalculate();
|
|
||||||
extraTalentList.ForceLayoutRecalculation();
|
|
||||||
|
|
||||||
foreach (var extraTalent in talentsOutsideTree)
|
|
||||||
{
|
|
||||||
var img = new GUIImage(new RectTransform(new Point(extraTalentList.Content.Rect.Height), extraTalentList.Content.RectTransform), sprite: extraTalent.Icon, scaleToFit: true)
|
|
||||||
{
|
|
||||||
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
|
|
||||||
Color = GUIStyle.Green
|
|
||||||
};
|
|
||||||
img.RectTransform.SizeChanged += () =>
|
|
||||||
{
|
|
||||||
img.RectTransform.MaxSize = new Point(img.Rect.Height);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
|
|
||||||
{
|
|
||||||
AbsoluteSpacing = GUI.IntScale(5),
|
|
||||||
Stretch = true
|
|
||||||
};
|
|
||||||
|
|
||||||
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
|
|
||||||
|
|
||||||
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
|
|
||||||
CreateSkillList(controlledCharacter, info, skillListBox);
|
|
||||||
|
|
||||||
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
|
|
||||||
|
|
||||||
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
|
|
||||||
|
|
||||||
if (controlledCharacter == null)
|
|
||||||
{
|
|
||||||
talentTreeListBox.Enabled = false;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
|
|
||||||
|
|
||||||
selectedTalents = info.GetUnlockedTalentsInTree().ToList();
|
|
||||||
|
|
||||||
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
|
|
||||||
foreach (var subTree in talentTree.TalentSubTrees)
|
|
||||||
{
|
|
||||||
GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
|
|
||||||
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
|
|
||||||
|
|
||||||
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
|
|
||||||
int elementPadding = GUI.IntScale(8);
|
|
||||||
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
|
|
||||||
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
|
|
||||||
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
|
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++)
|
|
||||||
{
|
|
||||||
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
|
|
||||||
|
|
||||||
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
|
|
||||||
|
|
||||||
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
|
|
||||||
{
|
|
||||||
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
|
|
||||||
};
|
|
||||||
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
|
|
||||||
|
|
||||||
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
|
|
||||||
{
|
|
||||||
CanBeFocused = false,
|
|
||||||
Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
|
|
||||||
};
|
|
||||||
|
|
||||||
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
|
|
||||||
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
|
|
||||||
|
|
||||||
if (subTree.TalentOptionStages.Length <= i) { continue; }
|
|
||||||
|
|
||||||
TalentOption talentOption = subTree.TalentOptionStages[i];
|
|
||||||
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
|
|
||||||
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
|
|
||||||
|
|
||||||
foreach (Identifier talentId in talentOption.TalentIdentifiers.OrderBy(t => t))
|
|
||||||
{
|
|
||||||
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talent)) { continue; }
|
|
||||||
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
|
|
||||||
{
|
|
||||||
CanBeFocused = false
|
|
||||||
};
|
|
||||||
|
|
||||||
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
|
|
||||||
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
|
|
||||||
{
|
|
||||||
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
|
|
||||||
UserData = talent.Identifier,
|
|
||||||
PressedColor = pressedColor,
|
|
||||||
Enabled = controlledCharacter != null,
|
|
||||||
OnClicked = (button, userData) =>
|
|
||||||
{
|
|
||||||
// deselect other buttons in tier by removing their selected talents from pool
|
|
||||||
foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren<GUIButton>())
|
|
||||||
{
|
|
||||||
if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
|
|
||||||
{
|
|
||||||
if (!controlledCharacter.HasTalent(otherTalentIdentifier))
|
|
||||||
{
|
|
||||||
selectedTalents.Remove(otherTalentIdentifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Identifier talentIdentifier = (Identifier)userData;
|
|
||||||
|
|
||||||
if (TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents))
|
|
||||||
{
|
|
||||||
if (!selectedTalents.Contains(talentIdentifier))
|
|
||||||
{
|
|
||||||
selectedTalents.Add(talentIdentifier);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (!controlledCharacter.HasTalent(talentIdentifier))
|
|
||||||
{
|
|
||||||
selectedTalents.Remove(talentIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateTalentInfo();
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
|
|
||||||
|
|
||||||
GUIComponent iconImage;
|
|
||||||
if (talent.Icon is null)
|
|
||||||
{
|
|
||||||
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
|
|
||||||
{
|
|
||||||
OutlineColor = GUIStyle.Red,
|
|
||||||
TextColor = GUIStyle.Red,
|
|
||||||
PressedColor = unselectableColor,
|
|
||||||
DisabledColor = unselectableColor,
|
|
||||||
CanBeFocused = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
|
|
||||||
{
|
|
||||||
PressedColor = unselectableColor,
|
|
||||||
DisabledColor = unselectableColor * 0.5f,
|
|
||||||
CanBeFocused = false,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
iconImage.Enabled = talentButton.Enabled;
|
|
||||||
talentButtons.Add((talentButton, iconImage));
|
|
||||||
}
|
|
||||||
talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
|
|
||||||
|
|
||||||
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true)
|
|
||||||
{
|
|
||||||
RelativeSpacing = 0.01f,
|
|
||||||
Stretch = true
|
|
||||||
};
|
|
||||||
|
|
||||||
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
|
|
||||||
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
|
|
||||||
|
|
||||||
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
|
|
||||||
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
|
|
||||||
{
|
|
||||||
IsHorizontal = true,
|
|
||||||
};
|
|
||||||
|
|
||||||
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
|
|
||||||
{
|
|
||||||
Shadow = true,
|
|
||||||
ToolTip = TextManager.Get("experiencetooltip")
|
|
||||||
};
|
|
||||||
|
|
||||||
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
|
|
||||||
|
|
||||||
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
|
|
||||||
{
|
|
||||||
OnClicked = ResetTalentSelection
|
|
||||||
};
|
|
||||||
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
|
|
||||||
{
|
|
||||||
OnClicked = ApplyTalentSelection,
|
|
||||||
};
|
|
||||||
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(GameMain.NetworkMember is null))
|
|
||||||
{
|
|
||||||
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
|
|
||||||
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
|
|
||||||
{
|
|
||||||
IgnoreLayoutGroups = false
|
|
||||||
};
|
|
||||||
newCharacterBox.TextBlock.AutoScaleHorizontal = true;
|
|
||||||
|
|
||||||
newCharacterBox.OnClicked = (button, o) =>
|
|
||||||
{
|
|
||||||
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
|
|
||||||
{
|
|
||||||
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
|
|
||||||
{
|
|
||||||
newCharacterBox.Text = TextManager.Get("settings");
|
|
||||||
if (pendingChangesFrame != null)
|
|
||||||
{
|
|
||||||
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
|
|
||||||
}
|
|
||||||
OpenMenu();
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpenMenu();
|
|
||||||
return true;
|
|
||||||
|
|
||||||
void OpenMenu()
|
|
||||||
{
|
|
||||||
characterSettingsFrame!.Visible = true;
|
|
||||||
content.Visible = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!(characterLayout is null))
|
|
||||||
{
|
|
||||||
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
|
|
||||||
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
|
|
||||||
{
|
|
||||||
OnClicked = (button, o) =>
|
|
||||||
{
|
|
||||||
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
|
|
||||||
characterSettingsFrame!.Visible = false;
|
|
||||||
content.Visible = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UpdateTalentInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
|
|
||||||
{
|
{
|
||||||
parent.Content.ClearChildren();
|
parent.Content.ClearChildren();
|
||||||
List<GUITextBlock> skillNames = new List<GUITextBlock>();
|
List<GUITextBlock> skillNames = new List<GUITextBlock>();
|
||||||
@@ -2154,10 +1799,10 @@ namespace Barotrauma
|
|||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight);
|
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), Math.Floor(skill.Level).ToString("F0"), textAlignment: Alignment.TopRight);
|
||||||
|
|
||||||
float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level;
|
float modifiedSkillLevel = MathF.Floor(character?.GetSkillLevel(skill.Identifier) ?? skill.Level);
|
||||||
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
|
if (!MathUtils.NearlyEqual(MathF.Floor(modifiedSkillLevel), MathF.Floor(skill.Level)))
|
||||||
{
|
{
|
||||||
int skillChange = (int)MathF.Floor(modifiedSkillLevel - skill.Level);
|
int skillChange = (int)MathF.Floor(modifiedSkillLevel - MathF.Floor(skill.Level));
|
||||||
string stringColor = skillChange switch
|
string stringColor = skillChange switch
|
||||||
{
|
{
|
||||||
> 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
|
> 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
|
||||||
@@ -2168,123 +1813,17 @@ namespace Barotrauma
|
|||||||
RichString changeText = RichString.Rich($"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)");
|
RichString changeText = RichString.Rich($"(‖color:{stringColor}‖{(skillChange > 0 ? "+" : string.Empty) + skillChange}‖color:end‖)");
|
||||||
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText) { Padding = Vector4.Zero };
|
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), skillContainer.RectTransform), changeText) { Padding = Vector4.Zero };
|
||||||
}
|
}
|
||||||
//skillContainer.Recalculate();
|
skillContainer.Recalculate();
|
||||||
}
|
}
|
||||||
|
|
||||||
parent.RecalculateChildren();
|
parent.RecalculateChildren();
|
||||||
GUITextBlock.AutoScaleAndNormalize(skillNames);
|
GUITextBlock.AutoScaleAndNormalize(skillNames);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateTalentInfo()
|
|
||||||
{
|
|
||||||
Character controlledCharacter = Character.Controlled;
|
|
||||||
if (controlledCharacter?.Info == null) { return; }
|
|
||||||
|
|
||||||
if (SelectedTab != InfoFrameTab.Talents) { return; }
|
|
||||||
|
|
||||||
bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
|
|
||||||
|
|
||||||
if (unlockedAllTalents)
|
|
||||||
{
|
|
||||||
experienceText.Text = string.Empty;
|
|
||||||
experienceBar.BarSize = 1f;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
|
|
||||||
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
|
|
||||||
}
|
|
||||||
|
|
||||||
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
|
|
||||||
|
|
||||||
string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString();
|
|
||||||
|
|
||||||
int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
|
|
||||||
|
|
||||||
if (unlockedAllTalents)
|
|
||||||
{
|
|
||||||
talentPointText.SetRichText($"‖color:{XMLExtensions.ToStringHex(Color.Gray)}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
|
|
||||||
}
|
|
||||||
else if (talentCount > 0)
|
|
||||||
{
|
|
||||||
string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
|
|
||||||
LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
|
|
||||||
talentPointText.SetRichText(localizedString);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons)
|
|
||||||
{
|
|
||||||
TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents);
|
|
||||||
GUIComponentStyle newStyle = talentStageStyles[state];
|
|
||||||
icon.ApplyStyle(newStyle);
|
|
||||||
icon.Color = newStyle.Color;
|
|
||||||
frame.Color = talentStageBackgroundColors[state];
|
|
||||||
glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var talentButton in talentButtons)
|
|
||||||
{
|
|
||||||
Identifier talentIdentifier = (Identifier)talentButton.button.UserData;
|
|
||||||
bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
|
|
||||||
Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
|
|
||||||
Color hoverColor = Color.White;
|
|
||||||
|
|
||||||
if (controlledCharacter.HasTalent(talentIdentifier))
|
|
||||||
{
|
|
||||||
newTalentColor = GUIStyle.Green;
|
|
||||||
}
|
|
||||||
else if (selectedTalents.Contains(talentIdentifier))
|
|
||||||
{
|
|
||||||
newTalentColor = GUIStyle.Orange;
|
|
||||||
hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
|
|
||||||
}
|
|
||||||
|
|
||||||
talentButton.icon.Color = newTalentColor;
|
|
||||||
talentButton.icon.HoverColor = hoverColor;
|
|
||||||
}
|
|
||||||
|
|
||||||
CreateSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ApplyTalents(Character controlledCharacter)
|
|
||||||
{
|
|
||||||
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
|
|
||||||
foreach (Identifier talent in selectedTalents)
|
|
||||||
{
|
|
||||||
controlledCharacter.GiveTalent(talent);
|
|
||||||
if (GameMain.Client != null)
|
|
||||||
{
|
|
||||||
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
|
|
||||||
UpdateTalentInfo();
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
|
|
||||||
{
|
|
||||||
Character controlledCharacter = Character.Controlled;
|
|
||||||
ApplyTalents(controlledCharacter);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool ResetTalentSelection(GUIButton guiButton, object userData)
|
|
||||||
{
|
|
||||||
Character controlledCharacter = Character.Controlled;
|
|
||||||
if (controlledCharacter?.Info == null) { return false; }
|
|
||||||
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
|
|
||||||
UpdateTalentInfo();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void OnExperienceChanged(Character character)
|
public void OnExperienceChanged(Character character)
|
||||||
{
|
{
|
||||||
if (character != Character.Controlled) { return; }
|
if (character != Character.Controlled) { return; }
|
||||||
UpdateTalentInfo();
|
talentMenu.UpdateTalentInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnClose()
|
public void OnClose()
|
||||||
|
|||||||
@@ -0,0 +1,817 @@
|
|||||||
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
|
using System.Linq;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.Networking;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using Microsoft.Xna.Framework.Input;
|
||||||
|
using static Barotrauma.TalentTree;
|
||||||
|
using static Barotrauma.TalentTree.TalentStages;
|
||||||
|
|
||||||
|
namespace Barotrauma
|
||||||
|
{
|
||||||
|
internal readonly record struct TalentShowCaseButton(ImmutableHashSet<TalentButton> Buttons,
|
||||||
|
GUIComponent IconComponent);
|
||||||
|
|
||||||
|
internal readonly record struct TalentButton(GUIComponent IconComponent,
|
||||||
|
TalentPrefab Prefab)
|
||||||
|
{
|
||||||
|
public Identifier Identifier => Prefab.Identifier;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal readonly record struct TalentCornerIcon(Identifier TalentTree,
|
||||||
|
int Index,
|
||||||
|
GUIImage IconComponent,
|
||||||
|
GUIFrame BackgroundComponent,
|
||||||
|
GUIFrame GlowComponent);
|
||||||
|
|
||||||
|
internal readonly struct TalentTreeStyle
|
||||||
|
{
|
||||||
|
public readonly GUIComponentStyle ComponentStyle;
|
||||||
|
public readonly Color Color;
|
||||||
|
|
||||||
|
public TalentTreeStyle(string componentStyle, Color color)
|
||||||
|
{
|
||||||
|
ComponentStyle = GUIStyle.GetComponentStyle(componentStyle);
|
||||||
|
Color = color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal sealed class TalentMenu
|
||||||
|
{
|
||||||
|
public const string ManageBotTalentsButtonUserData = "managebottalentsbutton";
|
||||||
|
|
||||||
|
private Character? character;
|
||||||
|
private CharacterInfo? characterInfo;
|
||||||
|
|
||||||
|
private static readonly Color unselectedColor = new Color(240, 255, 255, 225),
|
||||||
|
unselectableColor = new Color(100, 100, 100, 225),
|
||||||
|
pressedColor = new Color(60, 60, 60, 225),
|
||||||
|
lockedColor = new Color(48, 48, 48, 255),
|
||||||
|
unlockedColor = new Color(24, 37, 31, 255),
|
||||||
|
availableColor = new Color(50, 47, 33, 255);
|
||||||
|
|
||||||
|
private static readonly ImmutableDictionary<TalentStages, TalentTreeStyle> talentStageStyles =
|
||||||
|
new Dictionary<TalentStages, TalentTreeStyle>
|
||||||
|
{
|
||||||
|
[Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
|
||||||
|
[Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
|
||||||
|
[Unlocked] = new TalentTreeStyle("TalentTreePurchased", unlockedColor),
|
||||||
|
[Available] = new TalentTreeStyle("TalentTreeUnlocked", availableColor),
|
||||||
|
[Highlighted] = new TalentTreeStyle("TalentTreeAvailable", availableColor)
|
||||||
|
}.ToImmutableDictionary();
|
||||||
|
|
||||||
|
private readonly HashSet<TalentButton> talentButtons = new HashSet<TalentButton>();
|
||||||
|
private readonly HashSet<TalentShowCaseButton> talentShowCaseButtons = new HashSet<TalentShowCaseButton>();
|
||||||
|
private readonly HashSet<GUIComponent> showCaseTalentFrames = new HashSet<GUIComponent>();
|
||||||
|
private readonly HashSet<TalentCornerIcon> talentCornerIcons = new HashSet<TalentCornerIcon>();
|
||||||
|
private HashSet<Identifier> selectedTalents = new HashSet<Identifier>();
|
||||||
|
|
||||||
|
private readonly Queue<Identifier> showCaseClosureQueue = new();
|
||||||
|
|
||||||
|
private GUIListBox? skillListBox;
|
||||||
|
private GUITextBlock? talentPointText;
|
||||||
|
private GUIProgressBar? experienceBar;
|
||||||
|
private GUITextBlock? experienceText;
|
||||||
|
private GUILayoutGroup? skillLayout;
|
||||||
|
|
||||||
|
private GUIButton? talentApplyButton,
|
||||||
|
talentResetButton;
|
||||||
|
|
||||||
|
public void CreateGUI(GUIFrame parent, Character? targetCharacter)
|
||||||
|
{
|
||||||
|
parent.ClearChildren();
|
||||||
|
talentButtons.Clear();
|
||||||
|
talentShowCaseButtons.Clear();
|
||||||
|
talentCornerIcons.Clear();
|
||||||
|
showCaseTalentFrames.Clear();
|
||||||
|
|
||||||
|
character = targetCharacter;
|
||||||
|
characterInfo = targetCharacter?.Info;
|
||||||
|
|
||||||
|
GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
|
||||||
|
int padding = GUI.IntScale(15);
|
||||||
|
GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null);
|
||||||
|
|
||||||
|
GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
|
||||||
|
|
||||||
|
GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = GUI.IntScale(10),
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (characterInfo is null) { return; }
|
||||||
|
|
||||||
|
CreateStatPanel(contentLayout, characterInfo);
|
||||||
|
|
||||||
|
new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
|
||||||
|
|
||||||
|
if (JobTalentTrees.TryGet(characterInfo.Job.Prefab.Identifier, out TalentTree? talentTree))
|
||||||
|
{
|
||||||
|
CreateTalentMenu(contentLayout, characterInfo, talentTree!);
|
||||||
|
}
|
||||||
|
|
||||||
|
CreateFooter(contentLayout, characterInfo);
|
||||||
|
UpdateTalentInfo();
|
||||||
|
|
||||||
|
if (GameMain.NetworkMember != null && IsOwnCharacter(characterInfo))
|
||||||
|
{
|
||||||
|
CreateMultiplayerCharacterSettings(frame, content);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateMultiplayerCharacterSettings(GUIComponent parent, GUIComponent content)
|
||||||
|
{
|
||||||
|
if (skillLayout is null) { return; }
|
||||||
|
|
||||||
|
GUIFrame characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null) { Visible = false };
|
||||||
|
GUILayoutGroup characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
|
||||||
|
GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
|
||||||
|
GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
|
||||||
|
GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
|
||||||
|
|
||||||
|
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
|
||||||
|
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
|
||||||
|
{
|
||||||
|
IgnoreLayoutGroups = false,
|
||||||
|
TextBlock =
|
||||||
|
{
|
||||||
|
AutoScaleHorizontal = true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
newCharacterBox.OnClicked = (button, o) =>
|
||||||
|
{
|
||||||
|
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
|
||||||
|
{
|
||||||
|
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
|
||||||
|
{
|
||||||
|
newCharacterBox.Text = TextManager.Get("settings");
|
||||||
|
if (TabMenu.PendingChangesFrame != null)
|
||||||
|
{
|
||||||
|
NetLobbyScreen.CreateChangesPendingFrame(TabMenu.PendingChangesFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenMenu();
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
OpenMenu();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
void OpenMenu()
|
||||||
|
{
|
||||||
|
characterSettingsFrame!.Visible = true;
|
||||||
|
content.Visible = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
|
||||||
|
new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
|
||||||
|
{
|
||||||
|
OnClicked = (button, o) =>
|
||||||
|
{
|
||||||
|
GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
|
||||||
|
characterSettingsFrame.Visible = false;
|
||||||
|
content.Visible = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateStatPanel(GUIComponent parent, CharacterInfo info)
|
||||||
|
{
|
||||||
|
Job job = info.Job;
|
||||||
|
|
||||||
|
GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform, Anchor.Center), isHorizontal: true);
|
||||||
|
|
||||||
|
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
|
||||||
|
{
|
||||||
|
info.DrawPortrait(batch, component.Rect.Location.ToVector2(), Vector2.Zero, component.Rect.Width, false, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = GUI.IntScale(5),
|
||||||
|
CanBeFocused = true
|
||||||
|
};
|
||||||
|
|
||||||
|
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
|
if (!info.OmitJobInMenus)
|
||||||
|
{
|
||||||
|
nameBlock.TextColor = job.Prefab.UIColor;
|
||||||
|
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (info.PersonalityTrait != null)
|
||||||
|
{
|
||||||
|
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
|
||||||
|
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
|
||||||
|
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
|
||||||
|
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableHashSet<TalentPrefab?> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(static e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)).ToImmutableHashSet();
|
||||||
|
if (talentsOutsideTree.Any())
|
||||||
|
{
|
||||||
|
//spacing
|
||||||
|
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), nameLayout.RectTransform), style: null);
|
||||||
|
|
||||||
|
GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.55f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
|
||||||
|
|
||||||
|
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont)
|
||||||
|
{
|
||||||
|
AutoScaleVertical = true
|
||||||
|
};
|
||||||
|
talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
|
||||||
|
|
||||||
|
var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.7f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
|
||||||
|
{
|
||||||
|
AutoHideScrollBar = false,
|
||||||
|
ResizeContentToMakeSpaceForScrollBar = false
|
||||||
|
};
|
||||||
|
extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
|
||||||
|
extraTalentLayout.Recalculate();
|
||||||
|
extraTalentList.ForceLayoutRecalculation();
|
||||||
|
|
||||||
|
foreach (var extraTalent in talentsOutsideTree)
|
||||||
|
{
|
||||||
|
if (extraTalent is null) { continue; }
|
||||||
|
GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
|
||||||
|
{
|
||||||
|
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(extraTalent.Description.Value)),
|
||||||
|
Color = GUIStyle.Green
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = GUI.IntScale(5),
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
|
||||||
|
|
||||||
|
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
|
||||||
|
TabMenu.CreateSkillList(info.Character, info, skillListBox);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateTalentMenu(GUIComponent parent, CharacterInfo info, TalentTree tree)
|
||||||
|
{
|
||||||
|
GUIListBox mainList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, anchor: Anchor.TopCenter));
|
||||||
|
|
||||||
|
selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
|
||||||
|
|
||||||
|
var specializationCount = tree.TalentSubTrees.Count(t => t.Type == TalentTreeType.Specialization);
|
||||||
|
|
||||||
|
List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
|
||||||
|
foreach (var subTree in tree.TalentSubTrees)
|
||||||
|
{
|
||||||
|
GUIListBox talentList;
|
||||||
|
GUIComponent talentParent;
|
||||||
|
Vector2 treeSize;
|
||||||
|
switch (subTree.Type)
|
||||||
|
{
|
||||||
|
case TalentTreeType.Primary:
|
||||||
|
talentList = mainList;
|
||||||
|
treeSize = new Vector2(1f, 0.5f);
|
||||||
|
break;
|
||||||
|
case TalentTreeType.Specialization:
|
||||||
|
talentList = GetSpecializationList();
|
||||||
|
treeSize = new Vector2(Math.Max(0.333f, 1.0f / tree.TalentSubTrees.Count(t => t.Type == TalentTreeType.Specialization)), 1f);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
|
||||||
|
}
|
||||||
|
talentParent = talentList.Content;
|
||||||
|
|
||||||
|
GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(treeSize, talentParent.RectTransform), isHorizontal: false, childAnchor: Anchor.TopCenter)
|
||||||
|
{
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
if (subTree.Type != TalentTreeType.Primary)
|
||||||
|
{
|
||||||
|
GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.05f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter)
|
||||||
|
{ MinSize = new Point(0, GUI.IntScale(30)) }, style: null);
|
||||||
|
subtreeTitleFrame.RectTransform.IsFixedSize = true;
|
||||||
|
int elementPadding = GUI.IntScale(8);
|
||||||
|
Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
|
||||||
|
GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
|
||||||
|
subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
|
||||||
|
}
|
||||||
|
|
||||||
|
int optionAmount = subTree.TalentOptionStages.Length;
|
||||||
|
for (int i = 0; i < optionAmount; i++)
|
||||||
|
{
|
||||||
|
TalentOption option = subTree.TalentOptionStages[i];
|
||||||
|
CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info, specializationCount);
|
||||||
|
}
|
||||||
|
subTreeLayoutGroup.RectTransform.Resize(new Point(subTreeLayoutGroup.Rect.Width,
|
||||||
|
subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing)));
|
||||||
|
subTreeLayoutGroup.RectTransform.MinSize = new Point(subTreeLayoutGroup.Rect.Width, subTreeLayoutGroup.Rect.Height);
|
||||||
|
subTreeLayoutGroup.Recalculate();
|
||||||
|
|
||||||
|
if (subTree.Type == TalentTreeType.Specialization)
|
||||||
|
{
|
||||||
|
talentList.RectTransform.Resize(new Point(talentList.Rect.Width, Math.Max(subTreeLayoutGroup.Rect.Height, talentList.Rect.Height)));
|
||||||
|
talentList.RectTransform.MinSize = new Point(0, talentList.Rect.Height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var specializationList = GetSpecializationList();
|
||||||
|
//resize (scale up) children if there's less than 3 of them to make them cover the whole width of the menu
|
||||||
|
specializationList.Content.RectTransform.Resize(new Point(specializationList.Content.Children.Sum(static c => c.Rect.Width), specializationList.Rect.Height),
|
||||||
|
resizeChildren: specializationCount < 3);
|
||||||
|
//make room for scrollbar if there's more than the default amount of specializations
|
||||||
|
if (specializationCount > 3)
|
||||||
|
{
|
||||||
|
specializationList.RectTransform.MinSize = new Point(specializationList.Rect.Width, specializationList.Content.Rect.Height + (int)(specializationList.ScrollBar.Rect.Height * 0.9f));
|
||||||
|
}
|
||||||
|
|
||||||
|
GUITextBlock.AutoScaleAndNormalize(subTreeNames);
|
||||||
|
|
||||||
|
GUIListBox GetSpecializationList()
|
||||||
|
{
|
||||||
|
if (mainList.Content.Children.LastOrDefault() is GUIListBox specList)
|
||||||
|
{
|
||||||
|
return specList;
|
||||||
|
}
|
||||||
|
GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
|
||||||
|
return newSpecializationList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info, int specializationCount)
|
||||||
|
{
|
||||||
|
int elementPadding = GUI.IntScale(8);
|
||||||
|
int height = GUI.IntScale((GameMain.GameSession?.Campaign == null ? 65 : 60) * (specializationCount > 3 ? 0.97f : 1.0f));
|
||||||
|
GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.01f), parent.RectTransform, anchor: Anchor.TopCenter)
|
||||||
|
{ MinSize = new Point(0, height) }, style: null);
|
||||||
|
|
||||||
|
Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
|
||||||
|
|
||||||
|
GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center),
|
||||||
|
style: "TalentBackground")
|
||||||
|
{
|
||||||
|
Color = talentStageStyles[Locked].Color
|
||||||
|
};
|
||||||
|
GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
|
||||||
|
|
||||||
|
GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
|
||||||
|
{
|
||||||
|
CanBeFocused = false,
|
||||||
|
Color = talentStageStyles[Locked].Color
|
||||||
|
};
|
||||||
|
|
||||||
|
Point iconSize = cornerIcon.RectTransform.NonScaledSize;
|
||||||
|
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
|
||||||
|
|
||||||
|
GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 0.9f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
|
||||||
|
GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
|
||||||
|
|
||||||
|
HashSet<Identifier> talentOptionIdentifiers = talentOption.TalentIdentifiers.OrderBy(static t => t).ToHashSet();
|
||||||
|
HashSet<TalentButton> buttonsToAdd = new();
|
||||||
|
|
||||||
|
Dictionary<GUILayoutGroup, ImmutableHashSet<Identifier>> showCaseTalentParents = new();
|
||||||
|
Dictionary<Identifier, GUIComponent> showCaseTalentButtonsToAdd = new();
|
||||||
|
|
||||||
|
foreach (var (showCaseTalentIdentifier, talents) in talentOption.ShowCaseTalents)
|
||||||
|
{
|
||||||
|
talentOptionIdentifiers.Add(showCaseTalentIdentifier);
|
||||||
|
Point parentSize = talentBackground.RectTransform.NonScaledSize;
|
||||||
|
GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talents.Count - 1)), parentSize.Y)), style: "GUITooltip")
|
||||||
|
{
|
||||||
|
UserData = showCaseTalentIdentifier,
|
||||||
|
IgnoreLayoutGroups = true,
|
||||||
|
Visible = false
|
||||||
|
};
|
||||||
|
GUILayoutGroup showcaseCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.7f), showCaseFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
|
||||||
|
GUILayoutGroup showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true };
|
||||||
|
showCaseTalentParents.Add(showcaseLayout, talents);
|
||||||
|
showCaseTalentFrames.Add(showCaseFrame);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (Identifier talentId in talentOptionIdentifiers)
|
||||||
|
{
|
||||||
|
if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; }
|
||||||
|
|
||||||
|
bool isShowCaseTalent = talentOption.ShowCaseTalents.ContainsKey(talentId);
|
||||||
|
GUIComponent talentParent = talentOptionLayoutGroup;
|
||||||
|
|
||||||
|
foreach (var (key, value) in showCaseTalentParents)
|
||||||
|
{
|
||||||
|
if (value.Contains(talentId))
|
||||||
|
{
|
||||||
|
talentParent = key;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null)
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
|
||||||
|
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
|
||||||
|
GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
|
||||||
|
{
|
||||||
|
ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + ToolBox.ExtendColorToPercentageSigns(talent.Description.Value)),
|
||||||
|
UserData = talent.Identifier,
|
||||||
|
PressedColor = pressedColor,
|
||||||
|
Enabled = info.Character != null,
|
||||||
|
OnClicked = (button, userData) =>
|
||||||
|
{
|
||||||
|
if (isShowCaseTalent)
|
||||||
|
{
|
||||||
|
foreach (GUIComponent component in showCaseTalentFrames)
|
||||||
|
{
|
||||||
|
if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == talentId)
|
||||||
|
{
|
||||||
|
component.RectTransform.ScreenSpaceOffset = new Point((int)(button.Rect.Location.X - component.Rect.Width / 2f + button.Rect.Width / 2f), button.Rect.Location.Y - component.Rect.Height);
|
||||||
|
component.Visible = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
component.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character is null) { return false; }
|
||||||
|
|
||||||
|
Identifier talentIdentifier = (Identifier)userData;
|
||||||
|
if (talentOption.MaxChosenTalents is 1)
|
||||||
|
{
|
||||||
|
// deselect other buttons in tier by removing their selected talents from pool
|
||||||
|
foreach (Identifier identifier in selectedTalents)
|
||||||
|
{
|
||||||
|
if (character.HasTalent(identifier) || identifier == talentId) { continue; }
|
||||||
|
|
||||||
|
if (talentOptionIdentifiers.Contains(identifier))
|
||||||
|
{
|
||||||
|
selectedTalents.Remove(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character.HasTalent(talentIdentifier))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents))
|
||||||
|
{
|
||||||
|
if (!selectedTalents.Contains(talentIdentifier))
|
||||||
|
{
|
||||||
|
selectedTalents.Add(talentIdentifier);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectedTalents.Remove(talentIdentifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selectedTalents.Remove(talentIdentifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTalentInfo();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
|
||||||
|
|
||||||
|
GUIComponent iconImage;
|
||||||
|
if (talent.Icon is null)
|
||||||
|
{
|
||||||
|
iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
|
||||||
|
{
|
||||||
|
OutlineColor = GUIStyle.Red,
|
||||||
|
TextColor = GUIStyle.Red,
|
||||||
|
PressedColor = unselectableColor,
|
||||||
|
DisabledColor = unselectableColor,
|
||||||
|
CanBeFocused = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
|
||||||
|
{
|
||||||
|
Color = talent.ColorOverride.TryUnwrap(out Color color) ? color : Color.White,
|
||||||
|
PressedColor = unselectableColor,
|
||||||
|
DisabledColor = unselectableColor * 0.5f,
|
||||||
|
CanBeFocused = false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
iconImage.Enabled = talentButton.Enabled;
|
||||||
|
if (isShowCaseTalent)
|
||||||
|
{
|
||||||
|
showCaseTalentButtonsToAdd.Add(talentId, iconImage);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
buttonsToAdd.Add(new TalentButton(iconImage, talent));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TalentButton button in buttonsToAdd)
|
||||||
|
{
|
||||||
|
talentButtons.Add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (key, value) in showCaseTalentButtonsToAdd)
|
||||||
|
{
|
||||||
|
HashSet<TalentButton> buttons = new();
|
||||||
|
foreach (Identifier identifier in talentOption.ShowCaseTalents[key])
|
||||||
|
{
|
||||||
|
if (talentButtons.FirstOrNull(talentButton => talentButton.Identifier == identifier) is not { } button) { continue; }
|
||||||
|
|
||||||
|
buttons.Add(button);
|
||||||
|
}
|
||||||
|
|
||||||
|
talentShowCaseButtons.Add(new TalentShowCaseButton(buttons.ToImmutableHashSet(), value));
|
||||||
|
}
|
||||||
|
|
||||||
|
talentCornerIcons.Add(new TalentCornerIcon(subTree.Identifier, index, cornerIcon, talentBackground, talentBackgroundHighlight));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CreateFooter(GUIComponent parent, CharacterInfo info)
|
||||||
|
{
|
||||||
|
GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), parent.RectTransform, Anchor.TopCenter), isHorizontal: true)
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.01f,
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
|
||||||
|
GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
|
||||||
|
|
||||||
|
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
|
||||||
|
barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
|
||||||
|
{
|
||||||
|
IsHorizontal = true,
|
||||||
|
};
|
||||||
|
|
||||||
|
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
|
||||||
|
{
|
||||||
|
Shadow = true,
|
||||||
|
ToolTip = TextManager.Get("experiencetooltip")
|
||||||
|
};
|
||||||
|
|
||||||
|
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight)
|
||||||
|
{ AutoScaleVertical = true };
|
||||||
|
|
||||||
|
talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
|
||||||
|
{
|
||||||
|
OnClicked = ResetTalentSelection
|
||||||
|
};
|
||||||
|
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
|
||||||
|
{
|
||||||
|
OnClicked = ApplyTalentSelection,
|
||||||
|
};
|
||||||
|
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ResetTalentSelection(GUIButton guiButton, object userData)
|
||||||
|
{
|
||||||
|
if (characterInfo is null) { return false; }
|
||||||
|
selectedTalents = characterInfo.GetUnlockedTalentsInTree().ToHashSet();
|
||||||
|
UpdateTalentInfo();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTalents(Character controlledCharacter)
|
||||||
|
{
|
||||||
|
foreach (Identifier talent in CheckTalentSelection(controlledCharacter, selectedTalents))
|
||||||
|
{
|
||||||
|
controlledCharacter.GiveTalent(talent);
|
||||||
|
if (GameMain.Client != null)
|
||||||
|
{
|
||||||
|
GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateTalentInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool ApplyTalentSelection(GUIButton guiButton, object userData)
|
||||||
|
{
|
||||||
|
if (character is null) { return false; }
|
||||||
|
|
||||||
|
ApplyTalents(character);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateTalentInfo()
|
||||||
|
{
|
||||||
|
if (character is null || characterInfo is null) { return; }
|
||||||
|
|
||||||
|
bool unlockedAllTalents = character.HasUnlockedAllTalents();
|
||||||
|
|
||||||
|
if (experienceBar is null || experienceText is null) { return; }
|
||||||
|
|
||||||
|
if (unlockedAllTalents)
|
||||||
|
{
|
||||||
|
experienceText.Text = string.Empty;
|
||||||
|
experienceBar.BarSize = 1f;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
experienceText.Text = $"{characterInfo.ExperiencePoints - characterInfo.GetExperienceRequiredForCurrentLevel()} / {characterInfo.GetExperienceRequiredToLevelUp() - characterInfo.GetExperienceRequiredForCurrentLevel()}";
|
||||||
|
experienceBar.BarSize = characterInfo.GetProgressTowardsNextLevel();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet();
|
||||||
|
|
||||||
|
string pointsLeft = characterInfo.GetAvailableTalentPoints().ToString();
|
||||||
|
|
||||||
|
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
|
||||||
|
|
||||||
|
if (unlockedAllTalents)
|
||||||
|
{
|
||||||
|
talentPointText?.SetRichText($"‖color:{Color.Gray.ToStringHex()}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
|
||||||
|
}
|
||||||
|
else if (talentCount > 0)
|
||||||
|
{
|
||||||
|
string pointsUsed = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
|
||||||
|
LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
|
||||||
|
talentPointText?.SetRichText(localizedString);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
talentPointText?.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TalentCornerIcon cornerIcon in talentCornerIcons)
|
||||||
|
{
|
||||||
|
TalentStages state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
|
||||||
|
TalentTreeStyle style = talentStageStyles[state];
|
||||||
|
GUIComponentStyle newStyle = style.ComponentStyle;
|
||||||
|
cornerIcon.IconComponent.ApplyStyle(newStyle);
|
||||||
|
cornerIcon.IconComponent.Color = newStyle.Color;
|
||||||
|
cornerIcon.BackgroundComponent.Color = style.Color;
|
||||||
|
cornerIcon.GlowComponent.Visible = state == Highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TalentButton talentButton in talentButtons)
|
||||||
|
{
|
||||||
|
TalentStages stage = GetTalentState(character, talentButton.Identifier, selectedTalents);
|
||||||
|
ApplyTalentIconColor(stage, talentButton.IconComponent, talentButton.Prefab.ColorOverride);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (TalentShowCaseButton showCaseTalentButton in talentShowCaseButtons)
|
||||||
|
{
|
||||||
|
TalentStages collectiveTalentStage = GetCollectiveTalentState(character, showCaseTalentButton.Buttons, selectedTalents);
|
||||||
|
ApplyTalentIconColor(collectiveTalentStage, showCaseTalentButton.IconComponent, Option<Color>.None());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (skillListBox is null) { return; }
|
||||||
|
|
||||||
|
TabMenu.CreateSkillList(character, characterInfo, skillListBox);
|
||||||
|
|
||||||
|
static TalentStages GetTalentState(Character character, Identifier talentIdentifier, IReadOnlyCollection<Identifier> selectedTalents)
|
||||||
|
{
|
||||||
|
bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier);
|
||||||
|
TalentStages stage = unselectable ? Locked : Available;
|
||||||
|
if (unselectable)
|
||||||
|
{
|
||||||
|
stage = Locked;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (character.HasTalent(talentIdentifier))
|
||||||
|
{
|
||||||
|
stage = Unlocked;
|
||||||
|
}
|
||||||
|
else if (selectedTalents.Contains(talentIdentifier))
|
||||||
|
{
|
||||||
|
stage = Highlighted;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stage;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ApplyTalentIconColor(TalentStages stage, GUIComponent component, Option<Color> colorOverride)
|
||||||
|
{
|
||||||
|
Color color = stage switch
|
||||||
|
{
|
||||||
|
Invalid => unselectableColor,
|
||||||
|
Locked => unselectableColor,
|
||||||
|
Unlocked => GetColorOrOverride(GUIStyle.Green, colorOverride),
|
||||||
|
Highlighted => GetColorOrOverride(GUIStyle.Orange, colorOverride),
|
||||||
|
Available => GetColorOrOverride(unselectedColor, colorOverride),
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(stage), stage, null)
|
||||||
|
};
|
||||||
|
|
||||||
|
component.Color = color;
|
||||||
|
component.HoverColor = Color.Lerp(color, Color.White, 0.7f);
|
||||||
|
|
||||||
|
static Color GetColorOrOverride(Color color, Option<Color> colorOverride) => colorOverride.TryUnwrap(out Color overrideColor) ? overrideColor : color;
|
||||||
|
}
|
||||||
|
|
||||||
|
// this could also be reused for setting colors for talentCornerIcons but that's for another time
|
||||||
|
static TalentStages GetCollectiveTalentState(Character character, IReadOnlyCollection<TalentButton> buttons, IReadOnlyCollection<Identifier> selectedTalents)
|
||||||
|
{
|
||||||
|
HashSet<TalentStages> talentStages = new HashSet<TalentStages>();
|
||||||
|
foreach (TalentButton button in buttons)
|
||||||
|
{
|
||||||
|
talentStages.Add(GetTalentState(character, button.Identifier, selectedTalents));
|
||||||
|
}
|
||||||
|
|
||||||
|
TalentStages collectiveStage = talentStages.Any(static stage => stage is Locked)
|
||||||
|
? Locked
|
||||||
|
: Available;
|
||||||
|
|
||||||
|
foreach (TalentStages stage in talentStages)
|
||||||
|
{
|
||||||
|
if (stage is Highlighted)
|
||||||
|
{
|
||||||
|
collectiveStage = Highlighted;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (stage is Unlocked)
|
||||||
|
{
|
||||||
|
collectiveStage = Unlocked;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return collectiveStage;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update()
|
||||||
|
{
|
||||||
|
if (characterInfo is null || talentResetButton is null || talentApplyButton is null) { return; }
|
||||||
|
|
||||||
|
int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
|
||||||
|
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
|
||||||
|
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
|
||||||
|
{
|
||||||
|
talentApplyButton.Flash(GUIStyle.Orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (showCaseClosureQueue.TryDequeue(out Identifier identifier))
|
||||||
|
{
|
||||||
|
foreach (GUIComponent component in showCaseTalentFrames)
|
||||||
|
{
|
||||||
|
if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == identifier)
|
||||||
|
{
|
||||||
|
component.Visible = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool mouseInteracted = PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.ScrollWheelSpeed != 0;
|
||||||
|
bool keyboardInteracted = PlayerInput.KeyHit(Keys.Escape) || GameSettings.CurrentConfig.KeyMap.Bindings[InputType.InfoTab].IsHit();
|
||||||
|
|
||||||
|
foreach (GUIComponent component in showCaseTalentFrames)
|
||||||
|
{
|
||||||
|
if (component.UserData is not Identifier identifier) { continue; }
|
||||||
|
|
||||||
|
component.AddToGUIUpdateList(order: 1);
|
||||||
|
if (!component.Visible) { continue; }
|
||||||
|
|
||||||
|
if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
|
||||||
|
{
|
||||||
|
showCaseClosureQueue.Enqueue(identifier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool IsOwnCharacter(CharacterInfo? info)
|
||||||
|
{
|
||||||
|
if (info is null) { return false; }
|
||||||
|
|
||||||
|
CharacterInfo? ownCharacterInfo = Character.Controlled?.Info ?? GameMain.Client?.CharacterInfo;
|
||||||
|
if (ownCharacterInfo is null) { return false; }
|
||||||
|
|
||||||
|
return info == ownCharacterInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool CanManageTalents(CharacterInfo targetInfo)
|
||||||
|
{
|
||||||
|
// in singleplayer we can do whatever we want
|
||||||
|
if (GameMain.IsSingleplayer) { return true; }
|
||||||
|
|
||||||
|
// always allow managing talents for own character
|
||||||
|
if (IsOwnCharacter(targetInfo)) { return true; }
|
||||||
|
|
||||||
|
// don't allow controlling non-bot characters
|
||||||
|
if (targetInfo.Character is not { IsBot: true }) { return false; }
|
||||||
|
|
||||||
|
// lastly check if we have the permission to do this
|
||||||
|
return GameMain.Client is { } client && client.HasPermission(ClientPermissions.ManageBotTalents);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -278,10 +278,13 @@ namespace Barotrauma
|
|||||||
* | upgrades | maintenance | <- 1/3rd empty space |
|
* | upgrades | maintenance | <- 1/3rd empty space |
|
||||||
* |---------------------------------------------------------------------------------------------------|
|
* |---------------------------------------------------------------------------------------------------|
|
||||||
*/
|
*/
|
||||||
GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.5f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f };
|
GUILayoutGroup leftLayout = new GUILayoutGroup(rectT(0.4f, 1, topHeaderLayout)) { RelativeSpacing = 0.05f };
|
||||||
GUILayoutGroup locationLayout = new GUILayoutGroup(rectT(1, 0.5f, leftLayout), isHorizontal: true);
|
GUILayoutGroup locationLayout = new GUILayoutGroup(rectT(1, 0.5f, leftLayout), isHorizontal: true);
|
||||||
GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true);
|
GUIImage submarineIcon = new GUIImage(rectT(new Point(locationLayout.Rect.Height, locationLayout.Rect.Height), locationLayout), style: "SubmarineIcon", scaleToFit: true);
|
||||||
new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUIStyle.LargeFont);
|
var header = new GUITextBlock(rectT(1.0f - submarineIcon.RectTransform.RelativeSize.X, 1, locationLayout), TextManager.Get("UpgradeUI.Title"), font: GUIStyle.LargeFont);
|
||||||
|
header.RectTransform.MaxSize = new Point((int)(header.TextSize.X + header.Padding.X + header.Padding.Z), int.MaxValue);
|
||||||
|
new GUITextBlock(rectT(1.0f, 1, locationLayout), TextManager.Get("UpgradeUI.AllSubmarinesInfo"), font: GUIStyle.SmallFont, wrap: true);
|
||||||
|
|
||||||
categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true };
|
categoryButtonLayout = new GUILayoutGroup(rectT(0.4f, 0.3f, leftLayout), isHorizontal: true) { Stretch = true };
|
||||||
GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade };
|
GUIButton upgradeButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Upgrades"), style: "GUITabButton") { UserData = UpgradeTab.Upgrade, Selected = selectedUpgradeTab == UpgradeTab.Upgrade };
|
||||||
GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
|
GUIButton repairButton = new GUIButton(rectT(1, 1f, categoryButtonLayout), TextManager.Get("UICategory.Maintenance"), style: "GUITabButton") { UserData = UpgradeTab.Repairs, Selected = selectedUpgradeTab == UpgradeTab.Repairs };
|
||||||
@@ -433,8 +436,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
Location location = Campaign.Map.CurrentLocation;
|
Location location = Campaign.Map.CurrentLocation;
|
||||||
|
|
||||||
int hullRepairCost = Campaign.GetHullRepairCost();
|
int hullRepairCost = CampaignMode.GetHullRepairCost();
|
||||||
int itemRepairCost = Campaign.GetItemRepairCost();
|
int itemRepairCost = CampaignMode.GetItemRepairCost();
|
||||||
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
|
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
|
||||||
if (location != null)
|
if (location != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ namespace Barotrauma
|
|||||||
Data = data,
|
Data = data,
|
||||||
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) =>
|
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) =>
|
||||||
{
|
{
|
||||||
GameMain.Instance.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
|
GameMain.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Barotrauma.Networking;
|
|||||||
using Barotrauma.Particles;
|
using Barotrauma.Particles;
|
||||||
using Barotrauma.Steam;
|
using Barotrauma.Steam;
|
||||||
using Barotrauma.Transition;
|
using Barotrauma.Transition;
|
||||||
|
using Barotrauma.Tutorials;
|
||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using FarseerPhysics.Dynamics;
|
using FarseerPhysics.Dynamics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
@@ -15,6 +16,7 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using Barotrauma.Extensions;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -313,7 +315,7 @@ namespace Barotrauma
|
|||||||
GraphicsDeviceManager.SynchronizeWithVerticalRetrace = GameSettings.CurrentConfig.Graphics.VSync;
|
GraphicsDeviceManager.SynchronizeWithVerticalRetrace = GameSettings.CurrentConfig.Graphics.VSync;
|
||||||
SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode);
|
SetWindowMode(GameSettings.CurrentConfig.Graphics.DisplayMode);
|
||||||
|
|
||||||
defaultViewport = GraphicsDevice.Viewport;
|
defaultViewport = new Viewport(0, 0, GraphicsWidth, GraphicsHeight);
|
||||||
|
|
||||||
if (recalculateFontsAndStyles)
|
if (recalculateFontsAndStyles)
|
||||||
{
|
{
|
||||||
@@ -352,6 +354,7 @@ namespace Barotrauma
|
|||||||
public void ResetViewPort()
|
public void ResetViewPort()
|
||||||
{
|
{
|
||||||
GraphicsDevice.Viewport = defaultViewport;
|
GraphicsDevice.Viewport = defaultViewport;
|
||||||
|
GraphicsDevice.ScissorRectangle = defaultViewport.Bounds;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -374,6 +377,7 @@ namespace Barotrauma
|
|||||||
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
|
Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(typeof(Hull));
|
||||||
|
|
||||||
performanceCounterTimer = Stopwatch.StartNew();
|
performanceCounterTimer = Stopwatch.StartNew();
|
||||||
|
ResetIMEWorkaround();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -463,10 +467,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
LegacySteamUgcTransition.Prepare();
|
LegacySteamUgcTransition.Prepare();
|
||||||
var contentPackageLoadRoutine = ContentPackageManager.Init();
|
var contentPackageLoadRoutine = ContentPackageManager.Init();
|
||||||
foreach (var progress in contentPackageLoadRoutine)
|
foreach (var progress in contentPackageLoadRoutine
|
||||||
|
.Select(p => p.Result).Successes())
|
||||||
{
|
{
|
||||||
const float min = 1f, max = 70f;
|
const float min = 1f, max = 70f;
|
||||||
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress.Value);
|
TitleScreen.LoadState = MathHelper.Lerp(min, max, progress);
|
||||||
yield return CoroutineStatus.Running;
|
yield return CoroutineStatus.Running;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,9 +779,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
|
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
|
||||||
}
|
}
|
||||||
else if (GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning)
|
else if (ObjectiveManager.ContentRunning)
|
||||||
{
|
{
|
||||||
tutorialMode.Tutorial.CloseActiveContentGUI();
|
ObjectiveManager.CloseActiveContentGUI();
|
||||||
}
|
}
|
||||||
else if (GameSession.IsTabMenuOpen)
|
else if (GameSession.IsTabMenuOpen)
|
||||||
{
|
{
|
||||||
@@ -791,6 +796,10 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
GUI.TogglePauseMenu();
|
GUI.TogglePauseMenu();
|
||||||
}
|
}
|
||||||
|
else if (GameSession?.Campaign is { ShowCampaignUI: true, ForceMapUI: false })
|
||||||
|
{
|
||||||
|
GameSession.Campaign.ShowCampaignUI = false;
|
||||||
|
}
|
||||||
//open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC
|
//open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC
|
||||||
else if ((Character.Controlled == null || !itemHudActive())
|
else if ((Character.Controlled == null || !itemHudActive())
|
||||||
&& CharacterHealth.OpenHealthWindow == null
|
&& CharacterHealth.OpenHealthWindow == null
|
||||||
@@ -824,7 +833,7 @@ namespace Barotrauma
|
|||||||
Paused =
|
Paused =
|
||||||
(DebugConsole.IsOpen || DebugConsole.Paused ||
|
(DebugConsole.IsOpen || DebugConsole.Paused ||
|
||||||
GUI.PauseMenuOpen || GUI.SettingsMenuOpen ||
|
GUI.PauseMenuOpen || GUI.SettingsMenuOpen ||
|
||||||
(GameSession?.GameMode is TutorialMode tutoMode && tutoMode.Tutorial.ContentRunning)) &&
|
(GameSession?.GameMode is TutorialMode && ObjectiveManager.ContentRunning)) &&
|
||||||
(NetworkMember == null || !NetworkMember.GameStarted);
|
(NetworkMember == null || !NetworkMember.GameStarted);
|
||||||
if (GameSession?.GameMode != null && GameSession.GameMode.Paused)
|
if (GameSession?.GameMode != null && GameSession.GameMode.Paused)
|
||||||
{
|
{
|
||||||
@@ -858,8 +867,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
Screen.Selected.Update(Timing.Step);
|
Screen.Selected.Update(Timing.Step);
|
||||||
}
|
}
|
||||||
else if (GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial.ContentRunning)
|
else if (ObjectiveManager.ContentRunning && GameSession?.GameMode is TutorialMode tutorialMode)
|
||||||
{
|
{
|
||||||
|
ObjectiveManager.VideoPlayer.Update();
|
||||||
tutorialMode.Update((float)Timing.Step);
|
tutorialMode.Update((float)Timing.Step);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -1051,11 +1061,9 @@ 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 spCampaign && Level.IsLoadedFriendlyOutpost && spCampaign.Map?.CurrentLocation != null && spCampaign.CargoManager != null)
|
if (GameSession?.Campaign is SinglePlayerCampaign spCampaign && Level.IsLoadedFriendlyOutpost)
|
||||||
{
|
{
|
||||||
spCampaign.Map.CurrentLocation.AddStock(spCampaign.CargoManager.SoldItems);
|
spCampaign.UpdateStoreStock();
|
||||||
spCampaign.CargoManager.ClearSoldItemsProjSpecific();
|
|
||||||
spCampaign.Map.CurrentLocation.RemoveStock(spCampaign.CargoManager.PurchasedItems);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaveUtil.SaveGame(GameSession.SavePath);
|
SaveUtil.SaveGame(GameSession.SavePath);
|
||||||
@@ -1071,10 +1079,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (GameSession != null)
|
if (GameSession != null)
|
||||||
{
|
{
|
||||||
double roundDuration = Timing.TotalTime - GameSession.RoundStartTime;
|
|
||||||
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
|
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
|
||||||
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
|
GameSession.GameMode?.Preset.Identifier.Value ?? "none",
|
||||||
roundDuration);
|
GameSession.RoundDuration);
|
||||||
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
|
string eventId = "QuitRound:" + (GameSession.GameMode?.Preset.Identifier.Value ?? "none") + ":";
|
||||||
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
|
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:CurrentIntensity", GameSession.EventManager.CurrentIntensity);
|
||||||
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
|
foreach (var activeEvent in GameSession.EventManager.ActiveEvents)
|
||||||
@@ -1200,7 +1207,7 @@ namespace Barotrauma
|
|||||||
base.OnExiting(sender, args);
|
base.OnExiting(sender, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
|
public static void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(url)) { return; }
|
if (string.IsNullOrEmpty(url)) { return; }
|
||||||
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
|
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
|
||||||
@@ -1218,11 +1225,33 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
|
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
ToolBox.OpenFileWithShell(url);
|
try
|
||||||
|
{
|
||||||
|
ToolBox.OpenFileWithShell(url);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
DebugConsole.ThrowError($"Failed to open the url {url}", e);
|
||||||
|
}
|
||||||
msgBox.Close();
|
msgBox.Close();
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
msgBox.Buttons[1].OnClicked = msgBox.Close;
|
msgBox.Buttons[1].OnClicked = msgBox.Close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* On some systems, IME input is enabled by default, and being able to set the game to a state
|
||||||
|
* where it doesn't accept IME input on game launch seems very inconsistent.
|
||||||
|
* This function quickly cycles through IME input states and is called from couple different places
|
||||||
|
* to ensure that IME input is disabled properly when it's not needed.
|
||||||
|
*/
|
||||||
|
public static void ResetIMEWorkaround()
|
||||||
|
{
|
||||||
|
Rectangle rect = new Rectangle(0, 0, GraphicsWidth, GraphicsHeight);
|
||||||
|
TextInput.SetTextInputRect(rect);
|
||||||
|
TextInput.StartTextInput();
|
||||||
|
TextInput.SetTextInputRect(rect);
|
||||||
|
TextInput.StopTextInput();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,15 @@ namespace Barotrauma
|
|||||||
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
|
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
|
||||||
|
|
||||||
// The bag slot is intentionally left out since we want to be able to sell items from there
|
// The bag slot is intentionally left out since we want to be able to sell items from there
|
||||||
private readonly List<InvSlotType> equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
|
private static readonly HashSet<InvSlotType> equipmentSlots = new HashSet<InvSlotType>()
|
||||||
|
{
|
||||||
|
InvSlotType.Head,
|
||||||
|
InvSlotType.InnerClothes,
|
||||||
|
InvSlotType.OuterClothes,
|
||||||
|
InvSlotType.Headset,
|
||||||
|
InvSlotType.Card,
|
||||||
|
InvSlotType.HealthInterface
|
||||||
|
};
|
||||||
|
|
||||||
public IEnumerable<Item> GetSellableItems(Character character)
|
public IEnumerable<Item> GetSellableItems(Character character)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ using Barotrauma.Networking;
|
|||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@@ -86,6 +87,8 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool IsOwner(Client client) => client != null && client.IsOwner;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
|
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -97,10 +100,8 @@ namespace Barotrauma
|
|||||||
return
|
return
|
||||||
GameMain.Client.HasPermission(permissions) ||
|
GameMain.Client.HasPermission(permissions) ||
|
||||||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
|
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
|
||||||
GameMain.Client.ConnectedClients.Count == 1 ||
|
|
||||||
GameMain.Client.IsServerOwner ||
|
GameMain.Client.IsServerOwner ||
|
||||||
//allow managing if no-one with permissions is alive
|
AnyOneAllowedToManageCampaign(permissions);
|
||||||
GameMain.Client.ConnectedClients.None(c => c.InGame && c.Character is { IsIncapacitated: false, IsDead: false } && (c.IsOwner || c.HasPermission(permissions)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static bool AllowedToManageWallets()
|
public static bool AllowedToManageWallets()
|
||||||
@@ -203,6 +204,10 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (Level.IsLoadedOutpost && !ObjectiveManager.AllActiveObjectivesCompleted())
|
||||||
|
{
|
||||||
|
endRoundButton.Visible = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = endRoundButton.Visible; }
|
if (ReadyCheckButton != null) { ReadyCheckButton.Visible = endRoundButton.Visible; }
|
||||||
|
|
||||||
@@ -266,7 +271,7 @@ namespace Barotrauma
|
|||||||
Rand.ThreadId = Thread.CurrentThread.ManagedThreadId;
|
Rand.ThreadId = Thread.CurrentThread.ManagedThreadId;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror);
|
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -283,6 +288,18 @@ namespace Barotrauma
|
|||||||
return loadTask;
|
return loadTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected SubmarineInfo GetPredefinedStartOutpost()
|
||||||
|
{
|
||||||
|
if (Map?.CurrentLocation?.Type?.GetForcedOutpostGenerationParams() is OutpostGenerationParams parameters && !parameters.OutpostFilePath.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
return new SubmarineInfo(parameters.OutpostFilePath.Value)
|
||||||
|
{
|
||||||
|
OutpostGenerationParams = parameters
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
partial void NPCInteractProjSpecific(Character npc, Character interactor)
|
partial void NPCInteractProjSpecific(Character npc, Character interactor)
|
||||||
{
|
{
|
||||||
if (npc == null || interactor == null) { return; }
|
if (npc == null || interactor == null) { return; }
|
||||||
|
|||||||
+11
-1
@@ -694,8 +694,18 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (ShouldApply(NetFlags.SubList, id, requireUpToDateSave: false))
|
if (ShouldApply(NetFlags.SubList, id, requireUpToDateSave: false))
|
||||||
{
|
{
|
||||||
foreach (int ownedSubIndex in ownedSubIndices)
|
foreach (ushort ownedSubIndex in ownedSubIndices)
|
||||||
{
|
{
|
||||||
|
if (ownedSubIndex >= GameMain.Client.ServerSubmarines.Count)
|
||||||
|
{
|
||||||
|
string errorMsg = $"Error in {nameof(MultiPlayerCampaign.ClientRead)}. Owned submarine index was out of bounds. Index: {ownedSubIndex}, submarines: {string.Join(", ", GameMain.Client.ServerSubmarines.Select(s => s.Name))}";
|
||||||
|
DebugConsole.ThrowError(errorMsg);
|
||||||
|
GameAnalyticsManager.AddErrorEventOnce(
|
||||||
|
"MultiPlayerCampaign.ClientRead.OwnerSubIndexOutOfBounds" + ownedSubIndex,
|
||||||
|
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
SubmarineInfo sub = GameMain.Client.ServerSubmarines[ownedSubIndex];
|
SubmarineInfo sub = GameMain.Client.ServerSubmarines[ownedSubIndex];
|
||||||
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Owned))
|
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, NetLobbyScreen.SubmarineDeliveryData.Owned))
|
||||||
{
|
{
|
||||||
|
|||||||
+8
-4
@@ -265,8 +265,8 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror)
|
private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror)
|
||||||
{
|
{
|
||||||
GameMain.GameSession.StartRound(level,
|
|
||||||
mirrorLevel: mirror);
|
GameMain.GameSession.StartRound(level, mirrorLevel: mirror, startOutpost: GetPredefinedStartOutpost());
|
||||||
GameMain.GameScreen.Select();
|
GameMain.GameScreen.Select();
|
||||||
|
|
||||||
CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition");
|
CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "SinglePlayerCampaign.DoInitialCameraTransition");
|
||||||
@@ -407,6 +407,11 @@ namespace Barotrauma
|
|||||||
GUI.SetSavingIndicatorState(success);
|
GUI.SetSavingIndicatorState(success);
|
||||||
crewDead = false;
|
crewDead = false;
|
||||||
|
|
||||||
|
if (success)
|
||||||
|
{
|
||||||
|
// Event history must be registered before ending the round or it will be cleared
|
||||||
|
GameMain.GameSession.EventManager.RegisterEventHistory();
|
||||||
|
}
|
||||||
GameMain.GameSession.EndRound("", traitorResults, transitionType);
|
GameMain.GameSession.EndRound("", traitorResults, transitionType);
|
||||||
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
|
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
|
||||||
RoundSummary roundSummary = null;
|
RoundSummary roundSummary = null;
|
||||||
@@ -439,7 +444,7 @@ namespace Barotrauma
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map.ProgressWorld(transitionType, (float)(Timing.TotalTime - GameMain.GameSession.RoundStartTime));
|
Map.ProgressWorld(transitionType, GameMain.GameSession.RoundDuration);
|
||||||
|
|
||||||
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,
|
||||||
@@ -466,7 +471,6 @@ namespace Barotrauma
|
|||||||
if (success)
|
if (success)
|
||||||
{
|
{
|
||||||
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
|
||||||
GameMain.GameSession.EventManager.RegisterEventHistory();
|
|
||||||
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
+64
-522
@@ -1,7 +1,5 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.IO;
|
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@@ -25,107 +23,9 @@ namespace Barotrauma.Tutorials
|
|||||||
#region Tutorial variables
|
#region Tutorial variables
|
||||||
|
|
||||||
public readonly Identifier Identifier;
|
public readonly Identifier Identifier;
|
||||||
|
|
||||||
public LocalizedString DisplayName { get; }
|
public LocalizedString DisplayName { get; }
|
||||||
|
public LocalizedString Description { get; }
|
||||||
public bool ContentRunning { get; private set; }
|
|
||||||
|
|
||||||
private GUIComponent infoBox;
|
|
||||||
private Action infoBoxClosedCallback;
|
|
||||||
|
|
||||||
private VideoPlayer videoPlayer;
|
|
||||||
private Point screenResolution;
|
|
||||||
private WindowMode windowMode;
|
|
||||||
private float prevUIScale;
|
|
||||||
|
|
||||||
private GUILayoutGroup objectiveGroup;
|
|
||||||
private readonly LocalizedString objectiveTextTranslated;
|
|
||||||
|
|
||||||
private readonly List<Segment> ActiveObjectives = new List<Segment>();
|
|
||||||
private const float ObjectiveComponentAnimationTime = 1.5f;
|
|
||||||
private Segment ActiveContentSegment { get; set; }
|
|
||||||
|
|
||||||
public class Segment
|
|
||||||
{
|
|
||||||
public readonly record struct Text(
|
|
||||||
Identifier Tag,
|
|
||||||
int Width = DefaultWidth,
|
|
||||||
int Height = DefaultHeight,
|
|
||||||
Anchor Anchor = Anchor.Center);
|
|
||||||
|
|
||||||
public readonly record struct Video(
|
|
||||||
string FullPath,
|
|
||||||
Identifier TextTag,
|
|
||||||
int Width = DefaultWidth,
|
|
||||||
int Height = DefaultHeight)
|
|
||||||
{
|
|
||||||
public string FileName => Path.GetFileName(FullPath.CleanUpPath());
|
|
||||||
public string ContentPath => Path.GetDirectoryName(FullPath.CleanUpPath());
|
|
||||||
}
|
|
||||||
|
|
||||||
private const int DefaultWidth = 450;
|
|
||||||
private const int DefaultHeight = 80;
|
|
||||||
|
|
||||||
public GUIImage ObjectiveStateIndicator;
|
|
||||||
public GUIButton ObjectiveButton;
|
|
||||||
public GUITextBlock LinkedTextBlock;
|
|
||||||
public LocalizedString ObjectiveText;
|
|
||||||
|
|
||||||
public readonly Identifier Id;
|
|
||||||
public readonly Text TextContent;
|
|
||||||
public readonly Video VideoContent;
|
|
||||||
public readonly AutoPlayVideo AutoPlayVideo;
|
|
||||||
|
|
||||||
public Action OnClickObjective;
|
|
||||||
|
|
||||||
public TutorialSegmentType SegmentType { get; private set; }
|
|
||||||
|
|
||||||
public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
|
|
||||||
{
|
|
||||||
return new Segment(id, objectiveTextTag, autoPlayVideo, textContent, videoContent);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
|
|
||||||
{
|
|
||||||
return new Segment(id, objectiveTextTag, onClickObjective);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
|
|
||||||
{
|
|
||||||
return new Segment(id, objectiveTextTag);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
|
||||||
AutoPlayVideo = autoPlayVideo;
|
|
||||||
TextContent = textContent;
|
|
||||||
VideoContent = videoContent;
|
|
||||||
SegmentType = TutorialSegmentType.InfoBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
|
||||||
OnClickObjective = onClickObjective;
|
|
||||||
SegmentType = TutorialSegmentType.MessageBox;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Segment(Identifier id, Identifier objectiveTextTag)
|
|
||||||
{
|
|
||||||
Id = id;
|
|
||||||
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
|
||||||
SegmentType = TutorialSegmentType.Objective;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void ConnectMessageBox(Segment messageBoxSegment)
|
|
||||||
{
|
|
||||||
SegmentType = TutorialSegmentType.MessageBox;
|
|
||||||
OnClickObjective = messageBoxSegment.OnClickObjective;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private bool completed;
|
private bool completed;
|
||||||
public bool Completed
|
public bool Completed
|
||||||
@@ -163,6 +63,8 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
public readonly List<(Entity entity, Identifier iconStyle)> Icons = new List<(Entity entity, Identifier iconStyle)>();
|
public readonly List<(Entity entity, Identifier iconStyle)> Icons = new List<(Entity entity, Identifier iconStyle)>();
|
||||||
|
|
||||||
|
public bool Paused { get; private set; }
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Tutorial Controls
|
#region Tutorial Controls
|
||||||
@@ -171,8 +73,7 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
Identifier = $"tutorial.{prefab.Identifier}".ToIdentifier();
|
Identifier = $"tutorial.{prefab.Identifier}".ToIdentifier();
|
||||||
DisplayName = TextManager.Get(Identifier);
|
DisplayName = TextManager.Get(Identifier);
|
||||||
objectiveTextTranslated = TextManager.Get("Tutorial.Objective");
|
Description = TextManager.Get($"tutorial.{prefab.Identifier}.description");
|
||||||
|
|
||||||
TutorialPrefab = prefab;
|
TutorialPrefab = prefab;
|
||||||
eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier);
|
eventPrefab = EventSet.GetEventPrefab(prefab.EventIdentifier);
|
||||||
}
|
}
|
||||||
@@ -260,35 +161,26 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
|
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
|
||||||
|
|
||||||
Initialize();
|
GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
|
||||||
|
GameMain.GameSession.CrewManager.AutoHideCrewList();
|
||||||
|
|
||||||
|
if (Character.Controlled?.Inventory is CharacterInventory inventory)
|
||||||
|
{
|
||||||
|
foreach (Item item in inventory.AllItemsMod)
|
||||||
|
{
|
||||||
|
if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; }
|
||||||
|
item.Unequip(Character.Controlled);
|
||||||
|
Character.Controlled.Inventory.RemoveItem(item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
yield return CoroutineStatus.Success;
|
yield return CoroutineStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Initialize()
|
|
||||||
{
|
|
||||||
GameMain.GameSession.CrewManager.AllowCharacterSwitch = TutorialPrefab.AllowCharacterSwitch;
|
|
||||||
GameMain.GameSession.CrewManager.AutoHideCrewList();
|
|
||||||
|
|
||||||
if (Character.Controlled is Character character)
|
|
||||||
{
|
|
||||||
foreach (Item item in character.Inventory.AllItemsMod)
|
|
||||||
{
|
|
||||||
if (item.HasTag(TutorialPrefab.StartingItemTags)) { continue; }
|
|
||||||
item.Unequip(character);
|
|
||||||
character.Inventory.RemoveItem(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Start()
|
public void Start()
|
||||||
{
|
{
|
||||||
videoPlayer = new VideoPlayer();
|
|
||||||
GameMain.Instance.ShowLoading(Loading());
|
GameMain.Instance.ShowLoading(Loading());
|
||||||
ActiveObjectives.Clear();
|
ObjectiveManager.ResetObjectives();
|
||||||
ActiveContentSegment = null;
|
|
||||||
|
|
||||||
CreateObjectiveFrame();
|
|
||||||
|
|
||||||
// Setup doors: Clear all requirements, unless the door is setup as locked.
|
// Setup doors: Clear all requirements, unless the door is setup as locked.
|
||||||
foreach (var item in Item.ItemList)
|
foreach (var item in Item.ItemList)
|
||||||
@@ -304,24 +196,8 @@ namespace Barotrauma.Tutorials
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddToGUIUpdateList()
|
|
||||||
{
|
|
||||||
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameSettings.CurrentConfig.Graphics.DisplayMode != windowMode)
|
|
||||||
{
|
|
||||||
CreateObjectiveFrame();
|
|
||||||
}
|
|
||||||
if (ActiveObjectives.Count > 0)
|
|
||||||
{
|
|
||||||
objectiveGroup?.AddToGUIUpdateList(order: -1);
|
|
||||||
}
|
|
||||||
infoBox?.AddToGUIUpdateList(order: 100);
|
|
||||||
videoPlayer?.AddToGUIUpdateList(order: 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Update()
|
public void Update()
|
||||||
{
|
{
|
||||||
videoPlayer?.Update();
|
|
||||||
|
|
||||||
if (character != null)
|
if (character != null)
|
||||||
{
|
{
|
||||||
if (character.Oxygen < 1)
|
if (character.Oxygen < 1)
|
||||||
@@ -342,8 +218,7 @@ namespace Barotrauma.Tutorials
|
|||||||
{
|
{
|
||||||
GUI.PreventPauseMenuToggle = false;
|
GUI.PreventPauseMenuToggle = false;
|
||||||
}
|
}
|
||||||
ContentRunning = false;
|
ObjectiveManager.ClearContent();
|
||||||
infoBox = null;
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -374,18 +249,6 @@ namespace Barotrauma.Tutorials
|
|||||||
yield return CoroutineStatus.Success;
|
yield return CoroutineStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CloseActiveContentGUI()
|
|
||||||
{
|
|
||||||
if (videoPlayer.IsPlaying)
|
|
||||||
{
|
|
||||||
videoPlayer.Stop();
|
|
||||||
}
|
|
||||||
else if (infoBox != null)
|
|
||||||
{
|
|
||||||
CloseInfoFrame();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<CoroutineStatus> UpdateState()
|
public IEnumerable<CoroutineStatus> UpdateState()
|
||||||
{
|
{
|
||||||
while (GameMain.Instance.LoadingScreenOpen || Level.Loaded == null || Level.Loaded.Generating)
|
while (GameMain.Instance.LoadingScreenOpen || Level.Loaded == null || Level.Loaded.Generating)
|
||||||
@@ -432,13 +295,56 @@ namespace Barotrauma.Tutorials
|
|||||||
|
|
||||||
yield return new WaitForSeconds(WaitBeforeFade);
|
yield return new WaitForSeconds(WaitBeforeFade);
|
||||||
|
|
||||||
|
Action onEnd = () => GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
|
||||||
|
|
||||||
|
TutorialPrefab nextTutorialPrefab = null;
|
||||||
|
bool displayEndMessage =
|
||||||
|
TutorialPrefab.EndMessage.EndType == TutorialPrefab.EndType.Restart ||
|
||||||
|
(TutorialPrefab.EndMessage.EndType == TutorialPrefab.EndType.Continue && TutorialPrefab.Prefabs.TryGet(TutorialPrefab.EndMessage.NextTutorialIdentifier, out nextTutorialPrefab));
|
||||||
|
|
||||||
|
if (displayEndMessage)
|
||||||
|
{
|
||||||
|
Paused = true;
|
||||||
|
var endingMessageBox = new GUIMessageBox(
|
||||||
|
headerText: "",
|
||||||
|
text: TextManager.Get($"{Identifier}.completed"),
|
||||||
|
buttons: new LocalizedString[]
|
||||||
|
{
|
||||||
|
TextManager.Get(nextTutorialPrefab is null ? "restart" : "campaigncontinue"),
|
||||||
|
TextManager.Get("pausemenuquit")
|
||||||
|
});
|
||||||
|
|
||||||
|
endingMessageBox.Buttons[0].OnClicked += (_, _) =>
|
||||||
|
{
|
||||||
|
if (nextTutorialPrefab is null)
|
||||||
|
{
|
||||||
|
onEnd = () => Restart(null, null);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
onEnd = () =>
|
||||||
|
{
|
||||||
|
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
|
||||||
|
new Tutorial(nextTutorialPrefab).Start();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
endingMessageBox.Buttons[0].OnClicked += endingMessageBox.Close;
|
||||||
|
endingMessageBox.Buttons[0].OnClicked += (_, _) => Paused = false;
|
||||||
|
endingMessageBox.Buttons[1].OnClicked += endingMessageBox.Close;
|
||||||
|
endingMessageBox.Buttons[1].OnClicked += (_, _) => Paused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (Paused) { yield return CoroutineStatus.Running; }
|
||||||
|
|
||||||
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime);
|
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: FadeOutTime);
|
||||||
Completed = true;
|
Completed = true;
|
||||||
|
|
||||||
while (endCinematic.Running) { yield return CoroutineStatus.Running; }
|
while (endCinematic.Running) { yield return CoroutineStatus.Running; }
|
||||||
|
|
||||||
Stop();
|
Stop();
|
||||||
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
|
onEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -450,379 +356,15 @@ namespace Barotrauma.Tutorials
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerTutorialSegment(Segment segment, bool connectObjective = false)
|
|
||||||
{
|
|
||||||
if (segment.SegmentType != TutorialSegmentType.InfoBox)
|
|
||||||
{
|
|
||||||
ActiveObjectives.Add(segment);
|
|
||||||
AddToObjectiveList(segment, connectObjective);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Inventory.DraggingItems.Clear();
|
|
||||||
ContentRunning = true;
|
|
||||||
ActiveContentSegment = segment;
|
|
||||||
|
|
||||||
var title = TextManager.Get(segment.Id);
|
|
||||||
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag);
|
|
||||||
tutorialText = TextManager.ParseInputTypes(tutorialText);
|
|
||||||
|
|
||||||
switch (segment.AutoPlayVideo)
|
|
||||||
{
|
|
||||||
case AutoPlayVideo.Yes:
|
|
||||||
infoBox = CreateInfoFrame(
|
|
||||||
title,
|
|
||||||
tutorialText,
|
|
||||||
segment.TextContent.Width,
|
|
||||||
segment.TextContent.Height,
|
|
||||||
segment.TextContent.Anchor,
|
|
||||||
hasButton: true,
|
|
||||||
onInfoBoxClosed: LoadActiveContentVideo);
|
|
||||||
break;
|
|
||||||
case AutoPlayVideo.No:
|
|
||||||
infoBox = CreateInfoFrame(
|
|
||||||
title,
|
|
||||||
tutorialText,
|
|
||||||
segment.TextContent.Width,
|
|
||||||
segment.TextContent.Height,
|
|
||||||
segment.TextContent.Anchor,
|
|
||||||
hasButton: true,
|
|
||||||
onInfoBoxClosed: StopCurrentContentSegment,
|
|
||||||
onVideoButtonClicked: LoadActiveContentVideo);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void CompleteTutorialSegment(Identifier segmentId)
|
|
||||||
{
|
|
||||||
if (GetActiveObjective(segmentId) is not Segment segment)
|
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"Warning: tried to complete the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
|
|
||||||
{
|
|
||||||
//return if already completed
|
|
||||||
if (segment.ObjectiveStateIndicator.Style == style) { return; }
|
|
||||||
segment.ObjectiveStateIndicator.ApplyStyle(style);
|
|
||||||
}
|
|
||||||
segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true);
|
|
||||||
segment.ObjectiveButton.OnClicked = null;
|
|
||||||
segment.ObjectiveButton.CanBeFocused = false;
|
|
||||||
GameAnalyticsManager.AddDesignEvent($"Tutorial:{Identifier}:{segmentId}:Completed");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void RemoveTutorialSegment(Identifier segmentId)
|
|
||||||
{
|
|
||||||
if (GetActiveObjective(segmentId) is not Segment segment)
|
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{Identifier}\" but it isn't active!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
|
|
||||||
segment.LinkedTextBlock.FadeOut(ObjectiveComponentAnimationTime, false);
|
|
||||||
var parent = segment.LinkedTextBlock.Parent;
|
|
||||||
parent.FadeOut(ObjectiveComponentAnimationTime, true, onRemove: () =>
|
|
||||||
{
|
|
||||||
ActiveObjectives.Remove(segment);
|
|
||||||
objectiveGroup?.Recalculate();
|
|
||||||
});
|
|
||||||
parent.RectTransform.MoveOverTime(GetObjectiveHiddenPosition(parent.RectTransform), ObjectiveComponentAnimationTime);
|
|
||||||
segment.ObjectiveButton.OnClicked = null;
|
|
||||||
segment.ObjectiveButton.CanBeFocused = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Segment GetActiveObjective(Identifier id) => ActiveObjectives.FirstOrDefault(s => s.Id == id);
|
|
||||||
|
|
||||||
public void Stop()
|
public void Stop()
|
||||||
{
|
{
|
||||||
if (tutorialCoroutine != null)
|
if (tutorialCoroutine != null)
|
||||||
{
|
{
|
||||||
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
CoroutineManager.StopCoroutines(tutorialCoroutine);
|
||||||
}
|
}
|
||||||
ContentRunning = false;
|
ObjectiveManager.ResetUI();
|
||||||
infoBox = null;
|
|
||||||
videoPlayer?.Remove();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Objectives
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Create the objective list that holds the objectives (called on start and on resolution change)
|
|
||||||
/// </summary>
|
|
||||||
private void CreateObjectiveFrame()
|
|
||||||
{
|
|
||||||
var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null);
|
|
||||||
objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform))
|
|
||||||
{
|
|
||||||
AbsoluteSpacing = (int)GUIStyle.Font.LineHeight
|
|
||||||
};
|
|
||||||
for (int i = 0; i < ActiveObjectives.Count; i++)
|
|
||||||
{
|
|
||||||
AddToObjectiveList(ActiveObjectives[i]);
|
|
||||||
}
|
|
||||||
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
|
|
||||||
windowMode = GameSettings.CurrentConfig.Graphics.DisplayMode;
|
|
||||||
prevUIScale = GUI.Scale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Stops content running and adds the active segment to the objective list
|
|
||||||
/// </summary>
|
|
||||||
private void StopCurrentContentSegment()
|
|
||||||
{
|
|
||||||
if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty())
|
|
||||||
{
|
|
||||||
ActiveObjectives.Add(ActiveContentSegment);
|
|
||||||
AddToObjectiveList(ActiveContentSegment);
|
|
||||||
}
|
|
||||||
ContentRunning = false;
|
|
||||||
ActiveContentSegment = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds the segment to the objective list
|
|
||||||
/// </summary>
|
|
||||||
private void AddToObjectiveList(Segment segment, bool connectExisting = false)
|
|
||||||
{
|
|
||||||
if (connectExisting)
|
|
||||||
{
|
|
||||||
if (ActiveObjectives.Find(o => o.Id == segment.Id) is { } existingSegment)
|
|
||||||
{
|
|
||||||
existingSegment.ConnectMessageBox(segment);
|
|
||||||
SetButtonBehavior(existingSegment);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
|
|
||||||
{
|
|
||||||
AbsoluteOffset = GetObjectiveHiddenPosition(),
|
|
||||||
MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
|
|
||||||
};
|
|
||||||
var frame = new GUIFrame(frameRt, style: null)
|
|
||||||
{
|
|
||||||
CanBeFocused = true
|
|
||||||
};
|
|
||||||
objectiveGroup.Recalculate();
|
|
||||||
|
|
||||||
segment.LinkedTextBlock = new GUITextBlock(
|
|
||||||
new RectTransform(new Point(frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing, 0), frame.RectTransform, anchor: Anchor.TopRight),
|
|
||||||
TextManager.ParseInputTypes(segment.ObjectiveText),
|
|
||||||
wrap: true);
|
|
||||||
|
|
||||||
var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height);
|
|
||||||
segment.LinkedTextBlock.RectTransform.NonScaledSize = size;
|
|
||||||
segment.LinkedTextBlock.RectTransform.MinSize = size;
|
|
||||||
segment.LinkedTextBlock.RectTransform.MaxSize = size;
|
|
||||||
segment.LinkedTextBlock.RectTransform.IsFixedSize = true;
|
|
||||||
frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false);
|
|
||||||
frame.RectTransform.IsFixedSize = true;
|
|
||||||
|
|
||||||
var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true);
|
|
||||||
segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");
|
|
||||||
|
|
||||||
SetTransparent(segment.LinkedTextBlock);
|
|
||||||
|
|
||||||
segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
|
|
||||||
{
|
|
||||||
ToolTip = objectiveTextTranslated
|
|
||||||
};
|
|
||||||
SetButtonBehavior(segment);
|
|
||||||
SetTransparent(segment.ObjectiveButton);
|
|
||||||
|
|
||||||
frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate());
|
|
||||||
|
|
||||||
static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
|
|
||||||
|
|
||||||
void SetButtonBehavior(Segment segment)
|
|
||||||
{
|
|
||||||
segment.ObjectiveButton.CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective;
|
|
||||||
segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
|
|
||||||
{
|
|
||||||
if (segment.SegmentType == TutorialSegmentType.InfoBox)
|
|
||||||
{
|
|
||||||
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
|
||||||
{
|
|
||||||
ReplaySegmentVideo(segment);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ShowSegmentText(segment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
|
|
||||||
{
|
|
||||||
segment.OnClickObjective?.Invoke();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ReplaySegmentVideo(Segment segment)
|
|
||||||
{
|
|
||||||
if (ContentRunning) { return; }
|
|
||||||
Inventory.DraggingItems.Clear();
|
|
||||||
ContentRunning = true;
|
|
||||||
LoadVideo(segment);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ShowSegmentText(Segment segment)
|
|
||||||
{
|
|
||||||
if (ContentRunning) { return; }
|
|
||||||
Inventory.DraggingItems.Clear();
|
|
||||||
ContentRunning = true;
|
|
||||||
ActiveContentSegment = segment;
|
|
||||||
infoBox = CreateInfoFrame(
|
|
||||||
TextManager.Get(segment.Id),
|
|
||||||
TextManager.Get(segment.TextContent.Tag),
|
|
||||||
segment.TextContent.Width,
|
|
||||||
segment.TextContent.Height,
|
|
||||||
segment.TextContent.Anchor,
|
|
||||||
hasButton: true,
|
|
||||||
onInfoBoxClosed: () => ContentRunning = false,
|
|
||||||
onVideoButtonClicked: () => LoadVideo(segment));
|
|
||||||
}
|
|
||||||
|
|
||||||
private Point GetObjectiveHiddenPosition(RectTransform rt = null)
|
|
||||||
{
|
|
||||||
return new Point(GameMain.GraphicsWidth - objectiveGroup.Rect.X, rt?.AbsoluteOffset.Y ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region InfoFrame
|
|
||||||
|
|
||||||
private void CloseInfoFrame() => CloseInfoFrame(null, null);
|
|
||||||
|
|
||||||
private bool CloseInfoFrame(GUIButton button, object userData)
|
|
||||||
{
|
|
||||||
infoBox = null;
|
|
||||||
infoBoxClosedCallback?.Invoke();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
// Creates and displays a tutorial info box
|
|
||||||
/// </summary>
|
|
||||||
private GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null)
|
|
||||||
{
|
|
||||||
if (hasButton)
|
|
||||||
{
|
|
||||||
height += 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
width = (int)(width * GUI.Scale);
|
|
||||||
height = (int)(height * GUI.Scale);
|
|
||||||
|
|
||||||
LocalizedString wrappedText = ToolBox.WrapText(text, width, GUIStyle.Font);
|
|
||||||
height += (int)GUIStyle.Font.MeasureString(wrappedText).Y;
|
|
||||||
|
|
||||||
if (title.Length > 0)
|
|
||||||
{
|
|
||||||
height += (int)GUIStyle.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
|
|
||||||
}
|
|
||||||
|
|
||||||
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker");
|
|
||||||
|
|
||||||
var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
|
|
||||||
infoBlock.Flash(GUIStyle.Green);
|
|
||||||
|
|
||||||
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
|
|
||||||
{
|
|
||||||
Stretch = true,
|
|
||||||
AbsoluteSpacing = 5
|
|
||||||
};
|
|
||||||
|
|
||||||
if (title.Length > 0)
|
|
||||||
{
|
|
||||||
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
|
|
||||||
title, font: GUIStyle.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
|
|
||||||
titleBlock.RectTransform.IsFixedSize = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
text = RichString.Rich(text);
|
|
||||||
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
|
|
||||||
|
|
||||||
textBlock.RectTransform.IsFixedSize = true;
|
|
||||||
infoBoxClosedCallback = onInfoBoxClosed;
|
|
||||||
|
|
||||||
if (hasButton)
|
|
||||||
{
|
|
||||||
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
|
|
||||||
{
|
|
||||||
RelativeSpacing = 0.1f
|
|
||||||
};
|
|
||||||
buttonContainer.RectTransform.IsFixedSize = true;
|
|
||||||
|
|
||||||
if (onVideoButtonClicked != null)
|
|
||||||
{
|
|
||||||
buttonContainer.Stretch = true;
|
|
||||||
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
|
||||||
TextManager.Get("Video"), style: "GUIButtonLarge")
|
|
||||||
{
|
|
||||||
OnClicked = (GUIButton button, object obj) =>
|
|
||||||
{
|
|
||||||
onVideoButtonClicked();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
buttonContainer.Stretch = false;
|
|
||||||
buttonContainer.ChildAnchor = Anchor.Center;
|
|
||||||
}
|
|
||||||
|
|
||||||
var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
|
||||||
TextManager.Get("OK"), style: "GUIButtonLarge")
|
|
||||||
{
|
|
||||||
OnClicked = CloseInfoFrame
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y));
|
|
||||||
|
|
||||||
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
|
|
||||||
|
|
||||||
return background;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
#region Video
|
|
||||||
|
|
||||||
private void LoadVideo(Segment segment)
|
|
||||||
{
|
|
||||||
videoPlayer ??= new VideoPlayer();
|
|
||||||
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
|
||||||
{
|
|
||||||
videoPlayer.LoadContent(
|
|
||||||
contentPath: segment.VideoContent.ContentPath,
|
|
||||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
|
|
||||||
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.TextTag, segment.VideoContent.Width),
|
|
||||||
contentId: segment.Id,
|
|
||||||
startPlayback: true,
|
|
||||||
objective: segment.ObjectiveText,
|
|
||||||
onStop: StopCurrentContentSegment);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
videoPlayer.LoadContent(
|
|
||||||
contentPath: segment.VideoContent.ContentPath,
|
|
||||||
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
|
|
||||||
textSettings: null,
|
|
||||||
contentId: segment.Id,
|
|
||||||
startPlayback: true,
|
|
||||||
objective: string.Empty);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment);
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-6
@@ -6,6 +6,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
public Tutorial Tutorial;
|
public Tutorial Tutorial;
|
||||||
|
|
||||||
|
public override bool Paused => Tutorial.Paused;
|
||||||
|
|
||||||
public TutorialMode(GameModePreset preset) : base(preset) { }
|
public TutorialMode(GameModePreset preset) : base(preset) { }
|
||||||
|
|
||||||
public override void Start()
|
public override void Start()
|
||||||
@@ -19,12 +21,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddToGUIUpdateList()
|
|
||||||
{
|
|
||||||
base.AddToGUIUpdateList();
|
|
||||||
Tutorial.AddToGUIUpdateList();
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void Update(float deltaTime)
|
public override void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
base.Update(deltaTime);
|
base.Update(deltaTime);
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Microsoft.Xna.Framework;
|
using Barotrauma.Tutorials;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
@@ -128,8 +129,9 @@ namespace Barotrauma
|
|||||||
if (GUI.DisableHUD) { return; }
|
if (GUI.DisableHUD) { return; }
|
||||||
GameMode?.AddToGUIUpdateList();
|
GameMode?.AddToGUIUpdateList();
|
||||||
tabMenu?.AddToGUIUpdateList();
|
tabMenu?.AddToGUIUpdateList();
|
||||||
|
ObjectiveManager.AddToGUIUpdateList();
|
||||||
|
|
||||||
if ((!(GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
|
if ((GameMode is not CampaignMode campaign || (!campaign.ForceMapUI && !campaign.ShowCampaignUI)) &&
|
||||||
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
|
!CoroutineManager.IsCoroutineRunning("LevelTransition") && !CoroutineManager.IsCoroutineRunning("SubmarineTransition"))
|
||||||
{
|
{
|
||||||
if (topLeftButtonGroup == null)
|
if (topLeftButtonGroup == null)
|
||||||
@@ -223,6 +225,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
HintManager.Update();
|
HintManager.Update();
|
||||||
|
ObjectiveManager.VideoPlayer.Update();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn)
|
public void SetRespawnInfo(bool visible, string text, Color textColor, bool buttonsVisible, bool waitForNextRoundRespawn)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Barotrauma.Extensions;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.IO;
|
using Barotrauma.IO;
|
||||||
using Barotrauma.Items.Components;
|
using Barotrauma.Items.Components;
|
||||||
|
using Barotrauma.Tutorials;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -209,7 +210,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (item.CurrentHull == null) { continue; }
|
if (item.CurrentHull == null) { continue; }
|
||||||
if (item.GetComponent<Pump>() == null) { continue; }
|
if (item.GetComponent<Pump>() == null) { continue; }
|
||||||
if (!item.HasTag("ballast")) { continue; }
|
if (!item.HasTag("ballast") && !item.CurrentHull.RoomName.Contains("ballast", StringComparison.OrdinalIgnoreCase)) { continue; }
|
||||||
BallastHulls.Add(item.CurrentHull);
|
BallastHulls.Add(item.CurrentHull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -383,6 +384,34 @@ namespace Barotrauma
|
|||||||
IgnoreReminder("tabmenu");
|
IgnoreReminder("tabmenu");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void OnObtainedItem(Character character, Item item)
|
||||||
|
{
|
||||||
|
if (!CanDisplayHints()) { return; }
|
||||||
|
if (character != Character.Controlled || item == null) { return; }
|
||||||
|
|
||||||
|
if (DisplayHint($"onobtaineditem.{item.Prefab.Identifier}".ToIdentifier())) { return; }
|
||||||
|
foreach (Identifier tag in item.GetTags())
|
||||||
|
{
|
||||||
|
if (DisplayHint($"onobtaineditem.{tag}".ToIdentifier())) { return; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((item.HasTag("geneticmaterial") && character.Inventory.FindItemByTag("geneticdevice".ToIdentifier(), recursive: true) != null) ||
|
||||||
|
(item.HasTag("geneticdevice") && character.Inventory.FindItemByTag("geneticmaterial".ToIdentifier(), recursive: true) != null))
|
||||||
|
{
|
||||||
|
if (DisplayHint($"geneticmaterial.useinstructions".ToIdentifier())) { return; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void OnStartDeconstructing(Character character, Deconstructor deconstructor)
|
||||||
|
{
|
||||||
|
if (!CanDisplayHints()) { return; }
|
||||||
|
if (character != Character.Controlled || deconstructor == null) { return; }
|
||||||
|
if (deconstructor.InputContainer.Inventory.AllItems.All(it => it.GetComponent<GeneticMaterial>() is not null))
|
||||||
|
{
|
||||||
|
DisplayHint($"geneticmaterial.onrefiningorcombining".ToIdentifier());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void OnStoleItem(Character character, Item item)
|
public static void OnStoleItem(Character character, Item item)
|
||||||
{
|
{
|
||||||
if (!CanDisplayHints()) { return; }
|
if (!CanDisplayHints()) { return; }
|
||||||
@@ -507,7 +536,7 @@ namespace Barotrauma
|
|||||||
if (!CanDisplayHints()) { return; }
|
if (!CanDisplayHints()) { return; }
|
||||||
if (character != Character.Controlled) { return; }
|
if (character != Character.Controlled) { return; }
|
||||||
// Could make this more generic if there will ever be any other status effect related hints
|
// Could make this more generic if there will ever be any other status effect related hints
|
||||||
if (!(component is Repairable) || actionType != ActionType.OnFailure) { return; }
|
if (component is not Repairable || actionType != ActionType.OnFailure) { return; }
|
||||||
DisplayHint("onrepairfailed".ToIdentifier());
|
DisplayHint("onrepairfailed".ToIdentifier());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -563,7 +592,7 @@ namespace Barotrauma
|
|||||||
foreach (var me in gap.linkedTo)
|
foreach (var me in gap.linkedTo)
|
||||||
{
|
{
|
||||||
if (me == Character.Controlled.CurrentHull) { continue; }
|
if (me == Character.Controlled.CurrentHull) { continue; }
|
||||||
if (!(me is Hull adjacentHull)) { continue; }
|
if (me is not Hull adjacentHull) { continue; }
|
||||||
if (!IsOnFriendlySub()) { continue; }
|
if (!IsOnFriendlySub()) { continue; }
|
||||||
if (IsWearingDivingSuit()) { continue; }
|
if (IsWearingDivingSuit()) { continue; }
|
||||||
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure".ToIdentifier())) { return; }
|
if (adjacentHull.LethalPressure > 5.0f && DisplayHint("onadjacenthull.highpressure".ToIdentifier())) { return; }
|
||||||
@@ -720,6 +749,7 @@ namespace Barotrauma
|
|||||||
if (requireControllingCharacter && Character.Controlled == null) { return false; }
|
if (requireControllingCharacter && Character.Controlled == null) { return false; }
|
||||||
var gameMode = GameMain.GameSession?.GameMode;
|
var gameMode = GameMain.GameSession?.GameMode;
|
||||||
if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; }
|
if (!(gameMode is CampaignMode || gameMode is MissionMode)) { return false; }
|
||||||
|
if (ObjectiveManager.AnyObjectives) { return false; }
|
||||||
if (requireGameScreen && Screen.Selected != GameMain.GameScreen) { return false; }
|
if (requireGameScreen && Screen.Selected != GameMain.GameScreen) { return false; }
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ using Barotrauma.Networking;
|
|||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
internal partial class MedicalClinic
|
internal sealed partial class MedicalClinic
|
||||||
{
|
{
|
||||||
public enum RequestResult
|
public enum RequestResult
|
||||||
{
|
{
|
||||||
@@ -19,63 +19,11 @@ namespace Barotrauma
|
|||||||
Timeout
|
Timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly struct RequestAction<T>
|
public readonly record struct RequestAction<T>(Action<T> Callback, DateTimeOffset Timeout);
|
||||||
{
|
public readonly record struct AfflictionRequest(RequestResult Result, ImmutableArray<NetAffliction> Afflictions);
|
||||||
public readonly Action<T> Callback;
|
public readonly record struct PendingRequest(RequestResult Result, NetCollection<NetCrewMember> CrewMembers);
|
||||||
public readonly DateTimeOffset Timeout;
|
public readonly record struct CallbackOnlyRequest(RequestResult Result);
|
||||||
|
public readonly record struct HealRequest(RequestResult Result, HealRequestResult HealResult);
|
||||||
public RequestAction(Action<T> callback, DateTimeOffset timeout)
|
|
||||||
{
|
|
||||||
Callback = callback;
|
|
||||||
Timeout = timeout;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct AfflictionRequest
|
|
||||||
{
|
|
||||||
public readonly RequestResult Result;
|
|
||||||
public readonly ImmutableArray<NetAffliction> Afflictions;
|
|
||||||
|
|
||||||
public AfflictionRequest(RequestResult result, ImmutableArray<NetAffliction> afflictions)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
Afflictions = afflictions;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct PendingRequest
|
|
||||||
{
|
|
||||||
public readonly RequestResult Result;
|
|
||||||
public readonly ImmutableArray<NetCrewMember> CrewMembers;
|
|
||||||
|
|
||||||
public PendingRequest(RequestResult result, ImmutableArray<NetCrewMember> crewMembers)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
CrewMembers = crewMembers;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct CallbackOnlyRequest
|
|
||||||
{
|
|
||||||
public readonly RequestResult Result;
|
|
||||||
|
|
||||||
public CallbackOnlyRequest(RequestResult result)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly struct HealRequest
|
|
||||||
{
|
|
||||||
public readonly RequestResult Result;
|
|
||||||
public readonly HealRequestResult HealResult;
|
|
||||||
|
|
||||||
public HealRequest(RequestResult result, HealRequestResult healResult)
|
|
||||||
{
|
|
||||||
Result = result;
|
|
||||||
HealResult = healResult;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly List<RequestAction<AfflictionRequest>> afflictionRequests = new List<RequestAction<AfflictionRequest>>();
|
private readonly List<RequestAction<AfflictionRequest>> afflictionRequests = new List<RequestAction<AfflictionRequest>>();
|
||||||
private readonly List<RequestAction<PendingRequest>> pendingHealRequests = new List<RequestAction<PendingRequest>>();
|
private readonly List<RequestAction<PendingRequest>> pendingHealRequests = new List<RequestAction<PendingRequest>>();
|
||||||
@@ -96,7 +44,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (!(info is { Character: { CharacterHealth: { } health } }))
|
if (info is not { Character.CharacterHealth: { } health })
|
||||||
{
|
{
|
||||||
onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty));
|
onReceived.Invoke(new AfflictionRequest(RequestResult.Error, ImmutableArray<NetAffliction>.Empty));
|
||||||
return;
|
return;
|
||||||
@@ -123,14 +71,14 @@ namespace Barotrauma
|
|||||||
public void Update(float deltaTime)
|
public void Update(float deltaTime)
|
||||||
{
|
{
|
||||||
DateTimeOffset now = DateTimeOffset.Now;
|
DateTimeOffset now = DateTimeOffset.Now;
|
||||||
UpdateQueue(afflictionRequests, now, onTimeout: callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); });
|
UpdateQueue(afflictionRequests, now, onTimeout: static callback => { callback(new AfflictionRequest(RequestResult.Timeout, ImmutableArray<NetAffliction>.Empty)); });
|
||||||
UpdateQueue(pendingHealRequests, now, onTimeout: callback => { callback(new PendingRequest(RequestResult.Timeout, ImmutableArray<NetCrewMember>.Empty)); });
|
UpdateQueue(pendingHealRequests, now, onTimeout: static callback => { callback(new PendingRequest(RequestResult.Timeout, NetCollection<NetCrewMember>.Empty)); });
|
||||||
UpdateQueue(healAllRequests, now, onTimeout: callback => { callback(new HealRequest(RequestResult.Timeout, HealRequestResult.Unknown)); });
|
UpdateQueue(healAllRequests, now, onTimeout: static callback => { callback(new HealRequest(RequestResult.Timeout, HealRequestResult.Unknown)); });
|
||||||
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
|
UpdateQueue(clearAllRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||||
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
|
UpdateQueue(addRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||||
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
|
UpdateQueue(removeRequests, now, onTimeout: CallbackOnlyTimeout);
|
||||||
|
|
||||||
void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
|
static void CallbackOnlyTimeout(Action<CallbackOnlyRequest> callback) { callback(new CallbackOnlyRequest(RequestResult.Timeout)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsAfflictionPending(NetCrewMember character, NetAffliction affliction)
|
public bool IsAfflictionPending(NetCrewMember character, NetAffliction affliction)
|
||||||
@@ -148,9 +96,9 @@ namespace Barotrauma
|
|||||||
private static bool TryDequeue<T>(List<RequestAction<T>> requestQueue, out Action<T> result)
|
private static bool TryDequeue<T>(List<RequestAction<T>> requestQueue, out Action<T> result)
|
||||||
{
|
{
|
||||||
RequestAction<T>? first = requestQueue.FirstOrNull();
|
RequestAction<T>? first = requestQueue.FirstOrNull();
|
||||||
if (!(first is { } action))
|
if (first is not { } action)
|
||||||
{
|
{
|
||||||
result = _ => { };
|
result = static _ => { };
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,11 +139,25 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private static int GetPing()
|
private static int GetPing()
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer || !(GameMain.Client?.Name is { } ownName) || !(GameMain.NetworkMember?.ConnectedClients is { } clients)) { return 0; }
|
if (GameMain.IsSingleplayer || GameMain.Client?.Name is not { } ownName || GameMain.NetworkMember?.ConnectedClients is not { } clients) { return 0; }
|
||||||
|
|
||||||
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
|
return (from client in clients where client.Name == ownName select client.Ping).FirstOrDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TreatAllButtonAction(Action<CallbackOnlyRequest> onReceived)
|
||||||
|
{
|
||||||
|
if (GameMain.IsSingleplayer)
|
||||||
|
{
|
||||||
|
AddEverythingToPending();
|
||||||
|
onReceived(new CallbackOnlyRequest(RequestResult.Success));
|
||||||
|
OnUpdate?.Invoke();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
addRequests.Add(new RequestAction<CallbackOnlyRequest>(onReceived, GetTimeout()));
|
||||||
|
ClientSend(null, NetworkHeader.ADD_EVERYTHING_TO_PENDING, DeliveryMethod.Reliable);
|
||||||
|
}
|
||||||
|
|
||||||
public void HealAllButtonAction(Action<HealRequest> onReceived)
|
public void HealAllButtonAction(Action<HealRequest> onReceived)
|
||||||
{
|
{
|
||||||
if (GameMain.IsSingleplayer)
|
if (GameMain.IsSingleplayer)
|
||||||
@@ -296,8 +258,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private void NewAdditonReceived(IReadMessage inc, MessageFlag flag)
|
private void NewAdditonReceived(IReadMessage inc, MessageFlag flag)
|
||||||
{
|
{
|
||||||
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
var crewMembers = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
|
||||||
InsertPendingCrewMember(crewMember);
|
foreach (var crewMember in crewMembers)
|
||||||
|
{
|
||||||
|
InsertPendingCrewMember(crewMember);
|
||||||
|
}
|
||||||
if (flag == MessageFlag.Response && TryDequeue(addRequests, out var callback))
|
if (flag == MessageFlag.Response && TryDequeue(addRequests, out var callback))
|
||||||
{
|
{
|
||||||
callback(new CallbackOnlyRequest(RequestResult.Success));
|
callback(new CallbackOnlyRequest(RequestResult.Success));
|
||||||
@@ -318,11 +283,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private static void SendAfflictionRequest(CharacterInfo info)
|
private static void SendAfflictionRequest(CharacterInfo info)
|
||||||
{
|
{
|
||||||
INetSerializableStruct crewMember = new NetCrewMember
|
INetSerializableStruct crewMember = new NetCrewMember(info);
|
||||||
{
|
|
||||||
CharacterInfo = info,
|
|
||||||
Afflictions = Array.Empty<NetAffliction>()
|
|
||||||
};
|
|
||||||
|
|
||||||
ClientSend(crewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable);
|
ClientSend(crewMember, NetworkHeader.REQUEST_AFFLICTIONS, DeliveryMethod.Unreliable);
|
||||||
}
|
}
|
||||||
@@ -337,17 +298,17 @@ namespace Barotrauma
|
|||||||
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
NetCrewMember crewMember = INetSerializableStruct.Read<NetCrewMember>(inc);
|
||||||
if (TryDequeue(afflictionRequests, out var callback))
|
if (TryDequeue(afflictionRequests, out var callback))
|
||||||
{
|
{
|
||||||
RequestResult result = crewMember.CharacterInfoID == 0 ? RequestResult.Error : RequestResult.Success;
|
RequestResult result = crewMember.CharacterInfoID is 0 ? RequestResult.Error : RequestResult.Success;
|
||||||
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
|
callback(new AfflictionRequest(result, crewMember.Afflictions.ToImmutableArray()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PendingRequestReceived(IReadMessage inc)
|
private void PendingRequestReceived(IReadMessage inc)
|
||||||
{
|
{
|
||||||
NetPendingCrew pendingCrew = INetSerializableStruct.Read<NetPendingCrew>(inc);
|
var pendingCrew = INetSerializableStruct.Read<NetCollection<NetCrewMember>>(inc);
|
||||||
if (TryDequeue(pendingHealRequests, out var callback))
|
if (TryDequeue(pendingHealRequests, out var callback))
|
||||||
{
|
{
|
||||||
callback(new PendingRequest(RequestResult.Success, pendingCrew.CrewMembers.ToImmutableArray()));
|
callback(new PendingRequest(RequestResult.Success, pendingCrew));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,599 @@
|
|||||||
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.IO;
|
||||||
|
using Barotrauma.Tutorials;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace Barotrauma;
|
||||||
|
|
||||||
|
static class ObjectiveManager
|
||||||
|
{
|
||||||
|
public class Segment
|
||||||
|
{
|
||||||
|
public readonly record struct Text(
|
||||||
|
Identifier Tag,
|
||||||
|
int Width = DefaultWidth,
|
||||||
|
int Height = DefaultHeight,
|
||||||
|
Anchor Anchor = Anchor.Center);
|
||||||
|
|
||||||
|
public readonly record struct Video(
|
||||||
|
string FullPath,
|
||||||
|
Identifier TextTag,
|
||||||
|
int Width = DefaultWidth,
|
||||||
|
int Height = DefaultHeight)
|
||||||
|
{
|
||||||
|
public string FileName => Path.GetFileName(FullPath.CleanUpPath());
|
||||||
|
public string ContentPath => Path.GetDirectoryName(FullPath.CleanUpPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
private const int DefaultWidth = 450;
|
||||||
|
private const int DefaultHeight = 80;
|
||||||
|
|
||||||
|
public GUIImage ObjectiveStateIndicator;
|
||||||
|
public GUIButton ObjectiveButton;
|
||||||
|
public GUITextBlock LinkedTextBlock;
|
||||||
|
public LocalizedString ObjectiveText;
|
||||||
|
|
||||||
|
public readonly Identifier Id;
|
||||||
|
public readonly Text TextContent;
|
||||||
|
public readonly Video VideoContent;
|
||||||
|
public readonly AutoPlayVideo AutoPlayVideo;
|
||||||
|
|
||||||
|
public Action OnClickObjective;
|
||||||
|
|
||||||
|
public bool IsCompleted { get; set; }
|
||||||
|
|
||||||
|
public bool CanBeCompleted { get; set; }
|
||||||
|
|
||||||
|
public Identifier ParentId { get; set; }
|
||||||
|
|
||||||
|
public TutorialSegmentType SegmentType { get; private set; }
|
||||||
|
|
||||||
|
public static Segment CreateInfoBoxSegment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
|
||||||
|
{
|
||||||
|
return new Segment(id, objectiveTextTag, autoPlayVideo, textContent, videoContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Segment CreateMessageBoxSegment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
|
||||||
|
{
|
||||||
|
return new Segment(id, objectiveTextTag, onClickObjective);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Segment CreateObjectiveSegment(Identifier id, Identifier objectiveTextTag)
|
||||||
|
{
|
||||||
|
return new Segment(id, objectiveTextTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Segment(Identifier id, Identifier objectiveTextTag, AutoPlayVideo autoPlayVideo, Text textContent = default, Video videoContent = default)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||||
|
AutoPlayVideo = autoPlayVideo;
|
||||||
|
TextContent = textContent;
|
||||||
|
VideoContent = videoContent;
|
||||||
|
SegmentType = TutorialSegmentType.InfoBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Segment(Identifier id, Identifier objectiveTextTag, Action onClickObjective)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||||
|
OnClickObjective = onClickObjective;
|
||||||
|
SegmentType = TutorialSegmentType.MessageBox;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Segment(Identifier id, Identifier objectiveTextTag)
|
||||||
|
{
|
||||||
|
Id = id;
|
||||||
|
ObjectiveText = TextManager.ParseInputTypes(TextManager.Get(objectiveTextTag));
|
||||||
|
SegmentType = TutorialSegmentType.Objective;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ConnectMessageBox(Segment messageBoxSegment)
|
||||||
|
{
|
||||||
|
SegmentType = TutorialSegmentType.MessageBox;
|
||||||
|
OnClickObjective = messageBoxSegment.OnClickObjective;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly record struct ScreenSettings(
|
||||||
|
Point ScreenResolution = default,
|
||||||
|
float UiScale = default,
|
||||||
|
WindowMode WindowMode = default)
|
||||||
|
{
|
||||||
|
public bool HaveChanged() =>
|
||||||
|
GameMain.GraphicsWidth != ScreenResolution.X ||
|
||||||
|
GameMain.GraphicsHeight != ScreenResolution.Y ||
|
||||||
|
GUI.Scale != UiScale ||
|
||||||
|
GameSettings.CurrentConfig.Graphics.DisplayMode != WindowMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
private const float ObjectiveComponentAnimationTime = 1.5f;
|
||||||
|
|
||||||
|
public static bool ContentRunning { get; private set; }
|
||||||
|
|
||||||
|
public static VideoPlayer VideoPlayer { get; } = new VideoPlayer();
|
||||||
|
|
||||||
|
private static Segment ActiveContentSegment { get; set; }
|
||||||
|
|
||||||
|
private readonly static List<Segment> activeObjectives = new List<Segment>();
|
||||||
|
private static GUIComponent infoBox;
|
||||||
|
private static Action infoBoxClosedCallback;
|
||||||
|
private static ScreenSettings screenSettings;
|
||||||
|
private static GUILayoutGroup objectiveGroup;
|
||||||
|
private static LocalizedString objectiveTextTranslated;
|
||||||
|
|
||||||
|
public static void AddToGUIUpdateList()
|
||||||
|
{
|
||||||
|
if (screenSettings.HaveChanged())
|
||||||
|
{
|
||||||
|
CreateObjectiveFrame();
|
||||||
|
}
|
||||||
|
if (activeObjectives.Count > 0 && GameMain.GameSession?.Campaign is not { ShowCampaignUI: true })
|
||||||
|
{
|
||||||
|
objectiveGroup?.AddToGUIUpdateList(order: -1);
|
||||||
|
}
|
||||||
|
infoBox?.AddToGUIUpdateList(order: 100);
|
||||||
|
VideoPlayer.AddToGUIUpdateList(order: 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void TriggerTutorialSegment(Segment segment, bool connectObjective = false)
|
||||||
|
{
|
||||||
|
if (segment.SegmentType != TutorialSegmentType.InfoBox)
|
||||||
|
{
|
||||||
|
activeObjectives.Add(segment);
|
||||||
|
AddToObjectiveList(segment, connectObjective);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Inventory.DraggingItems.Clear();
|
||||||
|
ContentRunning = true;
|
||||||
|
ActiveContentSegment = segment;
|
||||||
|
|
||||||
|
var title = TextManager.Get(segment.Id);
|
||||||
|
LocalizedString tutorialText = TextManager.GetFormatted(segment.TextContent.Tag);
|
||||||
|
tutorialText = TextManager.ParseInputTypes(tutorialText);
|
||||||
|
|
||||||
|
switch (segment.AutoPlayVideo)
|
||||||
|
{
|
||||||
|
case AutoPlayVideo.Yes:
|
||||||
|
infoBox = CreateInfoFrame(
|
||||||
|
title,
|
||||||
|
tutorialText,
|
||||||
|
segment.TextContent.Width,
|
||||||
|
segment.TextContent.Height,
|
||||||
|
segment.TextContent.Anchor,
|
||||||
|
hasButton: true,
|
||||||
|
onInfoBoxClosed: LoadActiveContentVideo);
|
||||||
|
break;
|
||||||
|
case AutoPlayVideo.No:
|
||||||
|
infoBox = CreateInfoFrame(
|
||||||
|
title,
|
||||||
|
tutorialText,
|
||||||
|
segment.TextContent.Width,
|
||||||
|
segment.TextContent.Height,
|
||||||
|
segment.TextContent.Anchor,
|
||||||
|
hasButton: true,
|
||||||
|
onInfoBoxClosed: StopCurrentContentSegment,
|
||||||
|
onVideoButtonClicked: LoadActiveContentVideo);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CompleteTutorialSegment(Identifier segmentId)
|
||||||
|
{
|
||||||
|
if (GetActiveObjective(segmentId) is not Segment segment || !segment.CanBeCompleted || segment.IsCompleted)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!MarkSegmentCompleted(segment))
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||||
|
{
|
||||||
|
GameAnalyticsManager.AddDesignEvent($"Tutorial:{tutorialMode.Tutorial?.Identifier}:{segmentId}:Completed");
|
||||||
|
}
|
||||||
|
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
|
||||||
|
{
|
||||||
|
GameAnalyticsManager.AddDesignEvent($"Tutorial:CampaignMode:{segmentId}:Completed");
|
||||||
|
campaign?.CampaignMetadata?.SetValue(segmentId, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool MarkSegmentCompleted(Segment segment, bool flash = true)
|
||||||
|
{
|
||||||
|
segment.IsCompleted = true;
|
||||||
|
if (GUIStyle.GetComponentStyle("ObjectiveIndicatorCompleted") is GUIComponentStyle style)
|
||||||
|
{
|
||||||
|
if (segment.ObjectiveStateIndicator.Style == style)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
segment.ObjectiveStateIndicator.ApplyStyle(style);
|
||||||
|
}
|
||||||
|
if (flash)
|
||||||
|
{
|
||||||
|
segment.ObjectiveStateIndicator.Parent.Flash(color: GUIStyle.Green, flashDuration: 0.35f, useRectangleFlash: true);
|
||||||
|
}
|
||||||
|
segment.ObjectiveButton.OnClicked = null;
|
||||||
|
segment.ObjectiveButton.CanBeFocused = false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void RemoveTutorialSegment(Identifier segmentId)
|
||||||
|
{
|
||||||
|
if (GetActiveObjective(segmentId) is not Segment segment)
|
||||||
|
{
|
||||||
|
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
|
||||||
|
{
|
||||||
|
DebugConsole.AddWarning($"Warning: tried to remove the tutorial segment \"{segmentId}\" in tutorial \"{tutorialMode.Tutorial?.Identifier}\" but it isn't active!");
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
segment.ObjectiveStateIndicator.FadeOut(ObjectiveComponentAnimationTime, false);
|
||||||
|
segment.LinkedTextBlock.FadeOut(ObjectiveComponentAnimationTime, false);
|
||||||
|
var parent = segment.LinkedTextBlock.Parent;
|
||||||
|
parent.FadeOut(ObjectiveComponentAnimationTime, true, onRemove: () =>
|
||||||
|
{
|
||||||
|
activeObjectives.Remove(segment);
|
||||||
|
objectiveGroup?.Recalculate();
|
||||||
|
});
|
||||||
|
parent.RectTransform.MoveOverTime(GetObjectiveHiddenPosition(parent.RectTransform), ObjectiveComponentAnimationTime);
|
||||||
|
segment.ObjectiveButton.OnClicked = null;
|
||||||
|
segment.ObjectiveButton.CanBeFocused = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void CloseActiveContentGUI()
|
||||||
|
{
|
||||||
|
if (VideoPlayer.IsPlaying)
|
||||||
|
{
|
||||||
|
VideoPlayer.Stop();
|
||||||
|
}
|
||||||
|
else if (infoBox != null)
|
||||||
|
{
|
||||||
|
CloseInfoFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ClearContent()
|
||||||
|
{
|
||||||
|
ContentRunning = false;
|
||||||
|
infoBox = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void ResetUI()
|
||||||
|
{
|
||||||
|
ContentRunning = false;
|
||||||
|
infoBox = null;
|
||||||
|
VideoPlayer.Remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Objectives
|
||||||
|
private static Segment GetActiveObjective(Identifier id) => activeObjectives.FirstOrDefault(s => s.Id == id);
|
||||||
|
|
||||||
|
public static void ResetObjectives()
|
||||||
|
{
|
||||||
|
activeObjectives.Clear();
|
||||||
|
ActiveContentSegment = null;
|
||||||
|
CreateObjectiveFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Create the objective list that holds the objectives (called on start and on resolution change)
|
||||||
|
/// </summary>
|
||||||
|
private static void CreateObjectiveFrame()
|
||||||
|
{
|
||||||
|
var objectiveListFrame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.TutorialObjectiveListArea, GUI.Canvas), style: null)
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
objectiveGroup = new GUILayoutGroup(new RectTransform(Vector2.One, objectiveListFrame.RectTransform))
|
||||||
|
{
|
||||||
|
AbsoluteSpacing = (int)GUIStyle.Font.LineHeight
|
||||||
|
};
|
||||||
|
for (int i = 0; i < activeObjectives.Count; i++)
|
||||||
|
{
|
||||||
|
AddToObjectiveList(activeObjectives[i], useExistingIndex: true);
|
||||||
|
}
|
||||||
|
screenSettings = new ScreenSettings(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Scale, GameSettings.CurrentConfig.Graphics.DisplayMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stops content running and adds the active segment to the objective list
|
||||||
|
/// </summary>
|
||||||
|
private static void StopCurrentContentSegment()
|
||||||
|
{
|
||||||
|
if (!ActiveContentSegment.ObjectiveText.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
activeObjectives.Add(ActiveContentSegment);
|
||||||
|
AddToObjectiveList(ActiveContentSegment);
|
||||||
|
}
|
||||||
|
ContentRunning = false;
|
||||||
|
ActiveContentSegment = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds the segment to the objective list
|
||||||
|
/// </summary>
|
||||||
|
private static void AddToObjectiveList(Segment segment, bool connectExisting = false, bool useExistingIndex = false)
|
||||||
|
{
|
||||||
|
if (connectExisting)
|
||||||
|
{
|
||||||
|
if (activeObjectives.Find(o => o.Id == segment.Id) is { } existingSegment)
|
||||||
|
{
|
||||||
|
existingSegment.ConnectMessageBox(segment);
|
||||||
|
SetButtonBehavior(existingSegment);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var frameRt = new RectTransform(new Vector2(1.0f, 0.1f), objectiveGroup.RectTransform)
|
||||||
|
{
|
||||||
|
MinSize = new Point(0, objectiveGroup.AbsoluteSpacing)
|
||||||
|
};
|
||||||
|
Segment parentSegment = activeObjectives.FirstOrDefault(s => s.Id == segment.ParentId);
|
||||||
|
if (parentSegment is not null)
|
||||||
|
{
|
||||||
|
// Add this child as the last child in case there are other existing children already
|
||||||
|
int childIndex = useExistingIndex ? activeObjectives.IndexOf(segment) :
|
||||||
|
activeObjectives.IndexOf(parentSegment) + activeObjectives.Count(s => s.ParentId == segment.ParentId);
|
||||||
|
if (objectiveGroup.RectTransform.GetChildIndex(frameRt) != childIndex)
|
||||||
|
{
|
||||||
|
frameRt.RepositionChildInHierarchy(childIndex);
|
||||||
|
activeObjectives.Remove(segment);
|
||||||
|
activeObjectives.Insert(childIndex, segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
frameRt.AbsoluteOffset = GetObjectiveHiddenPosition();
|
||||||
|
|
||||||
|
var frame = new GUIFrame(frameRt, style: null)
|
||||||
|
{
|
||||||
|
CanBeFocused = true
|
||||||
|
};
|
||||||
|
|
||||||
|
objectiveGroup.Recalculate();
|
||||||
|
|
||||||
|
int textWidth = parentSegment is null ? frameRt.Rect.Width - objectiveGroup.AbsoluteSpacing
|
||||||
|
: frameRt.Rect.Width - 2 * objectiveGroup.AbsoluteSpacing;
|
||||||
|
segment.LinkedTextBlock = new GUITextBlock(
|
||||||
|
new RectTransform(new Point(textWidth, 0), frame.RectTransform, anchor: Anchor.TopRight),
|
||||||
|
TextManager.ParseInputTypes(segment.ObjectiveText),
|
||||||
|
wrap: true);
|
||||||
|
|
||||||
|
var size = new Point(segment.LinkedTextBlock.Rect.Width, segment.LinkedTextBlock.Rect.Height);
|
||||||
|
segment.LinkedTextBlock.RectTransform.NonScaledSize = size;
|
||||||
|
segment.LinkedTextBlock.RectTransform.MinSize = size;
|
||||||
|
segment.LinkedTextBlock.RectTransform.MaxSize = size;
|
||||||
|
segment.LinkedTextBlock.RectTransform.IsFixedSize = true;
|
||||||
|
frame.RectTransform.Resize(new Point(frame.Rect.Width, segment.LinkedTextBlock.RectTransform.Rect.Height), resizeChildren: false);
|
||||||
|
frame.RectTransform.IsFixedSize = true;
|
||||||
|
|
||||||
|
var indicatorRt = new RectTransform(new Point(objectiveGroup.AbsoluteSpacing), frame.RectTransform, isFixedSize: true);
|
||||||
|
if (parentSegment is not null)
|
||||||
|
{
|
||||||
|
indicatorRt.AbsoluteOffset = new Point(objectiveGroup.AbsoluteSpacing, 0);
|
||||||
|
}
|
||||||
|
segment.ObjectiveStateIndicator = new GUIImage(indicatorRt, "ObjectiveIndicatorIncomplete");
|
||||||
|
|
||||||
|
SetTransparent(segment.LinkedTextBlock);
|
||||||
|
|
||||||
|
objectiveTextTranslated ??= TextManager.Get("Tutorial.Objective");
|
||||||
|
segment.ObjectiveButton = new GUIButton(new RectTransform(Vector2.One, segment.LinkedTextBlock.RectTransform, Anchor.TopLeft, Pivot.TopLeft), style: null)
|
||||||
|
{
|
||||||
|
ToolTip = objectiveTextTranslated
|
||||||
|
};
|
||||||
|
SetButtonBehavior(segment);
|
||||||
|
SetTransparent(segment.ObjectiveButton);
|
||||||
|
|
||||||
|
frameRt.MoveOverTime(new Point(0, frameRt.AbsoluteOffset.Y), ObjectiveComponentAnimationTime, onDoneMoving: () => objectiveGroup?.Recalculate());
|
||||||
|
|
||||||
|
// Check if the objective has already been completed in the campaign
|
||||||
|
if (!segment.IsCompleted && GameMain.GameSession?.Campaign?.CampaignMetadata is CampaignMetadata data && data.GetBoolean(segment.Id))
|
||||||
|
{
|
||||||
|
MarkSegmentCompleted(segment, flash: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void SetTransparent(GUIComponent component) => component.Color = component.HoverColor = component.PressedColor = component.SelectedColor = Color.Transparent;
|
||||||
|
|
||||||
|
void SetButtonBehavior(Segment segment)
|
||||||
|
{
|
||||||
|
segment.ObjectiveButton.CanBeFocused = segment.SegmentType != TutorialSegmentType.Objective;
|
||||||
|
segment.ObjectiveButton.OnClicked = (GUIButton btn, object userdata) =>
|
||||||
|
{
|
||||||
|
if (segment.SegmentType == TutorialSegmentType.InfoBox)
|
||||||
|
{
|
||||||
|
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
||||||
|
{
|
||||||
|
ReplaySegmentVideo(segment);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ShowSegmentText(segment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (segment.SegmentType == TutorialSegmentType.MessageBox)
|
||||||
|
{
|
||||||
|
segment.OnClickObjective?.Invoke();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReplaySegmentVideo(Segment segment)
|
||||||
|
{
|
||||||
|
if (ContentRunning) { return; }
|
||||||
|
Inventory.DraggingItems.Clear();
|
||||||
|
ContentRunning = true;
|
||||||
|
LoadVideo(segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ShowSegmentText(Segment segment)
|
||||||
|
{
|
||||||
|
if (ContentRunning) { return; }
|
||||||
|
Inventory.DraggingItems.Clear();
|
||||||
|
ContentRunning = true;
|
||||||
|
ActiveContentSegment = segment;
|
||||||
|
infoBox = CreateInfoFrame(
|
||||||
|
TextManager.Get(segment.Id),
|
||||||
|
TextManager.Get(segment.TextContent.Tag),
|
||||||
|
segment.TextContent.Width,
|
||||||
|
segment.TextContent.Height,
|
||||||
|
segment.TextContent.Anchor,
|
||||||
|
hasButton: true,
|
||||||
|
onInfoBoxClosed: () => ContentRunning = false,
|
||||||
|
onVideoButtonClicked: () => LoadVideo(segment));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Point GetObjectiveHiddenPosition(RectTransform rt = null)
|
||||||
|
{
|
||||||
|
return new Point(GameMain.GraphicsWidth - objectiveGroup.Rect.X, rt?.AbsoluteOffset.Y ?? 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Segment GetObjective(Identifier identifier)
|
||||||
|
{
|
||||||
|
return activeObjectives.FirstOrDefault(o => o.Id == identifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AllActiveObjectivesCompleted()
|
||||||
|
{
|
||||||
|
return activeObjectives.None() || activeObjectives.All(o => !o.CanBeCompleted || o.IsCompleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool AnyObjectives => activeObjectives.Any();
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region InfoFrame
|
||||||
|
|
||||||
|
private static void CloseInfoFrame() => CloseInfoFrame(null, null);
|
||||||
|
|
||||||
|
private static bool CloseInfoFrame(GUIButton button, object userData)
|
||||||
|
{
|
||||||
|
infoBox = null;
|
||||||
|
infoBoxClosedCallback?.Invoke();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
// Creates and displays a tutorial info box
|
||||||
|
/// </summary>
|
||||||
|
private static GUIComponent CreateInfoFrame(LocalizedString title, LocalizedString text, int width = 300, int height = 80, Anchor anchor = Anchor.TopRight, bool hasButton = false, Action onInfoBoxClosed = null, Action onVideoButtonClicked = null)
|
||||||
|
{
|
||||||
|
if (hasButton)
|
||||||
|
{
|
||||||
|
height += 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
width = (int)(width * GUI.Scale);
|
||||||
|
height = (int)(height * GUI.Scale);
|
||||||
|
|
||||||
|
LocalizedString wrappedText = ToolBox.WrapText(text, width, GUIStyle.Font);
|
||||||
|
height += (int)GUIStyle.Font.MeasureString(wrappedText).Y;
|
||||||
|
|
||||||
|
if (title.Length > 0)
|
||||||
|
{
|
||||||
|
height += (int)GUIStyle.Font.MeasureString(title).Y + (int)(150 * GUI.Scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
var background = new GUIFrame(new RectTransform(new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight), GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker");
|
||||||
|
|
||||||
|
var infoBlock = new GUIFrame(new RectTransform(new Point(width, height), background.RectTransform, anchor));
|
||||||
|
infoBlock.Flash(GUIStyle.Green);
|
||||||
|
|
||||||
|
var infoContent = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), infoBlock.RectTransform, Anchor.Center))
|
||||||
|
{
|
||||||
|
Stretch = true,
|
||||||
|
AbsoluteSpacing = 5
|
||||||
|
};
|
||||||
|
|
||||||
|
if (title.Length > 0)
|
||||||
|
{
|
||||||
|
var titleBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform),
|
||||||
|
title, font: GUIStyle.LargeFont, textAlignment: Alignment.Center, textColor: new Color(253, 174, 0));
|
||||||
|
titleBlock.RectTransform.IsFixedSize = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
text = RichString.Rich(text);
|
||||||
|
GUITextBlock textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoContent.RectTransform), text, wrap: true);
|
||||||
|
|
||||||
|
textBlock.RectTransform.IsFixedSize = true;
|
||||||
|
infoBoxClosedCallback = onInfoBoxClosed;
|
||||||
|
|
||||||
|
if (hasButton)
|
||||||
|
{
|
||||||
|
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), infoContent.RectTransform), isHorizontal: true)
|
||||||
|
{
|
||||||
|
RelativeSpacing = 0.1f
|
||||||
|
};
|
||||||
|
buttonContainer.RectTransform.IsFixedSize = true;
|
||||||
|
|
||||||
|
if (onVideoButtonClicked != null)
|
||||||
|
{
|
||||||
|
buttonContainer.Stretch = true;
|
||||||
|
var videoButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
||||||
|
TextManager.Get("Video"), style: "GUIButtonLarge")
|
||||||
|
{
|
||||||
|
OnClicked = (GUIButton button, object obj) =>
|
||||||
|
{
|
||||||
|
onVideoButtonClicked();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
buttonContainer.Stretch = false;
|
||||||
|
buttonContainer.ChildAnchor = Anchor.Center;
|
||||||
|
}
|
||||||
|
|
||||||
|
var okButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), buttonContainer.RectTransform),
|
||||||
|
TextManager.Get("OK"), style: "GUIButtonLarge")
|
||||||
|
{
|
||||||
|
OnClicked = CloseInfoFrame
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
infoBlock.RectTransform.NonScaledSize = new Point(infoBlock.Rect.Width, (int)(infoContent.Children.Sum(c => c.Rect.Height + infoContent.AbsoluteSpacing) / infoContent.RectTransform.RelativeSize.Y));
|
||||||
|
|
||||||
|
SoundPlayer.PlayUISound(GUISoundType.UIMessage);
|
||||||
|
|
||||||
|
return background;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Video
|
||||||
|
|
||||||
|
private static void LoadVideo(Segment segment)
|
||||||
|
{
|
||||||
|
if (segment.AutoPlayVideo == AutoPlayVideo.Yes)
|
||||||
|
{
|
||||||
|
VideoPlayer.LoadContent(
|
||||||
|
contentPath: segment.VideoContent.ContentPath,
|
||||||
|
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
|
||||||
|
textSettings: new VideoPlayer.TextSettings(segment.VideoContent.TextTag, segment.VideoContent.Width),
|
||||||
|
contentId: segment.Id,
|
||||||
|
startPlayback: true,
|
||||||
|
objective: segment.ObjectiveText,
|
||||||
|
onStop: StopCurrentContentSegment);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
VideoPlayer.LoadContent(
|
||||||
|
contentPath: segment.VideoContent.ContentPath,
|
||||||
|
videoSettings: new VideoPlayer.VideoSettings(segment.VideoContent.FileName),
|
||||||
|
textSettings: null,
|
||||||
|
contentId: segment.Id,
|
||||||
|
startPlayback: true,
|
||||||
|
objective: string.Empty);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void LoadActiveContentVideo() => LoadVideo(ActiveContentSegment);
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
@@ -117,7 +118,7 @@ namespace Barotrauma
|
|||||||
private void UpdateBar()
|
private void UpdateBar()
|
||||||
{
|
{
|
||||||
double elapsedTime = (DateTime.Now - startTime).TotalSeconds;
|
double elapsedTime = (DateTime.Now - startTime).TotalSeconds;
|
||||||
if (msgBox != null && !msgBox.Closed && GUIMessageBox.MessageBoxes.Contains(msgBox))
|
if (msgBox is { Closed: false } && GUIMessageBox.MessageBoxes.Contains(msgBox))
|
||||||
{
|
{
|
||||||
if (msgBox.FindChild(TimerData, true) is GUIProgressBar bar)
|
if (msgBox.FindChild(TimerData, true) is GUIProgressBar bar)
|
||||||
{
|
{
|
||||||
@@ -129,7 +130,7 @@ namespace Barotrauma
|
|||||||
int second = (int)Math.Ceiling(elapsedTime);
|
int second = (int)Math.Ceiling(elapsedTime);
|
||||||
if (second > lastSecond)
|
if (second > lastSecond)
|
||||||
{
|
{
|
||||||
if (msgBox != null && !msgBox.Closed)
|
if (msgBox is { Closed: false })
|
||||||
{
|
{
|
||||||
SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
|
SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
|
||||||
}
|
}
|
||||||
@@ -137,6 +138,19 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void CloseLingeringPopups()
|
||||||
|
{
|
||||||
|
foreach (GUIComponent box in GUIMessageBox.MessageBoxes.ToImmutableArray())
|
||||||
|
{
|
||||||
|
if (box is not GUIMessageBox msgBox) { continue; }
|
||||||
|
|
||||||
|
if (msgBox.UserData is PromptData or ResultData)
|
||||||
|
{
|
||||||
|
msgBox.Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static void ClientRead(IReadMessage inc)
|
public static void ClientRead(IReadMessage inc)
|
||||||
{
|
{
|
||||||
ReadyCheckState state = (ReadyCheckState)inc.ReadByte();
|
ReadyCheckState state = (ReadyCheckState)inc.ReadByte();
|
||||||
@@ -154,6 +168,8 @@ namespace Barotrauma
|
|||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ReadyCheckState.Start:
|
case ReadyCheckState.Start:
|
||||||
|
CloseLingeringPopups();
|
||||||
|
|
||||||
bool isOwn = false;
|
bool isOwn = false;
|
||||||
byte authorId = 0;
|
byte authorId = 0;
|
||||||
|
|
||||||
@@ -175,8 +191,8 @@ namespace Barotrauma
|
|||||||
clients.Add(inc.ReadByte());
|
clients.Add(inc.ReadByte());
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadyCheck rCheck = new ReadyCheck(clients,
|
ReadyCheck rCheck = new ReadyCheck(clients,
|
||||||
DateTimeOffset.FromUnixTimeSeconds(startTime).LocalDateTime,
|
DateTimeOffset.FromUnixTimeSeconds(startTime).LocalDateTime,
|
||||||
DateTimeOffset.FromUnixTimeSeconds(endTime).LocalDateTime);
|
DateTimeOffset.FromUnixTimeSeconds(endTime).LocalDateTime);
|
||||||
crewManager.ActiveReadyCheck = rCheck;
|
crewManager.ActiveReadyCheck = rCheck;
|
||||||
|
|
||||||
@@ -224,7 +240,7 @@ namespace Barotrauma
|
|||||||
if (IsFinished) { return; }
|
if (IsFinished) { return; }
|
||||||
IsFinished = true;
|
IsFinished = true;
|
||||||
|
|
||||||
int readyCount = Clients.Count(pair => pair.Value == ReadyStatus.Yes);
|
int readyCount = Clients.Count(static pair => pair.Value == ReadyStatus.Yes);
|
||||||
int totalCount = Clients.Count;
|
int totalCount = Clients.Count;
|
||||||
GameMain.Client.AddChatMessage(ChatMessage.Create(string.Empty, readyCheckStatus(readyCount, totalCount).Value, ChatMessageType.Server, null));
|
GameMain.Client.AddChatMessage(ChatMessage.Create(string.Empty, readyCheckStatus(readyCount, totalCount).Value, ChatMessageType.Server, null));
|
||||||
}
|
}
|
||||||
@@ -238,31 +254,29 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (resultsBox == null || resultsBox.Closed || !GUIMessageBox.MessageBoxes.Contains(resultsBox)) { return; }
|
if (resultsBox == null || resultsBox.Closed || !GUIMessageBox.MessageBoxes.Contains(resultsBox)) { return; }
|
||||||
|
|
||||||
if (resultsBox.Content.FindChild(UserListData) is GUIListBox userList)
|
if (resultsBox.Content.FindChild(UserListData) is not GUIListBox userList) { return; }
|
||||||
|
|
||||||
|
// for some reason FindChild doesn't work here?
|
||||||
|
foreach (GUIComponent child in userList.Content.Children)
|
||||||
{
|
{
|
||||||
// for some reason FindChild doesn't work here?
|
if (child.UserData is not byte b || b != id) { continue; }
|
||||||
foreach (GUIComponent child in userList.Content.Children)
|
|
||||||
|
if (child.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is not GUIImage image) { continue; }
|
||||||
|
|
||||||
|
string style;
|
||||||
|
switch (status)
|
||||||
{
|
{
|
||||||
if (!(child.UserData is byte b) || b != id) { continue; }
|
case ReadyStatus.Yes:
|
||||||
|
style = "MissionCompletedIcon";
|
||||||
if (child.GetChild<GUILayoutGroup>().FindChild(ReadySpriteData) is GUIImage image)
|
break;
|
||||||
{
|
case ReadyStatus.No:
|
||||||
string style;
|
style = "MissionFailedIcon";
|
||||||
switch (status)
|
break;
|
||||||
{
|
default:
|
||||||
case ReadyStatus.Yes:
|
return;
|
||||||
style = "MissionCompletedIcon";
|
|
||||||
break;
|
|
||||||
case ReadyStatus.No:
|
|
||||||
style = "MissionFailedIcon";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
image.ApplyStyle(GUIStyle.GetComponentStyle(style));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -506,7 +506,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
|
private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
|
||||||
{
|
{
|
||||||
string locationName = Submarine.MainSub.AtEndExit ? endLocation?.Name : startLocation?.Name;
|
string locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.Name : startLocation?.Name;
|
||||||
|
|
||||||
string textTag;
|
string textTag;
|
||||||
if (gameOver)
|
if (gameOver)
|
||||||
|
|||||||
@@ -774,7 +774,6 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any);
|
|
||||||
var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
|
var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
|
||||||
|
|
||||||
if (selectedContainer != null &&
|
if (selectedContainer != null &&
|
||||||
@@ -802,8 +801,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (character.HeldItems.Any(i =>
|
else if (character.HeldItems.Any(i =>
|
||||||
i.OwnInventory != null &&
|
i.OwnInventory != null &&
|
||||||
/*disallow putting into equipped item if the item is equippable (equip as the quick action instead)*/
|
(i.OwnInventory.CanBePut(item) || ((i.OwnInventory.Capacity == 1 || i.OwnInventory.Container.HasSubContainers) && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
|
||||||
((i.OwnInventory.CanBePut(item) && (allowInventorySwap || !isEquippable)) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
|
|
||||||
{
|
{
|
||||||
return QuickUseAction.PutToEquippedItem;
|
return QuickUseAction.PutToEquippedItem;
|
||||||
}
|
}
|
||||||
@@ -973,11 +971,11 @@ namespace Barotrauma
|
|||||||
//don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
|
//don't allow swapping if we're moving items into an item with 1 slot holding a stack of items
|
||||||
//(in that case, the quick action should just fill up the stack)
|
//(in that case, the quick action should just fill up the stack)
|
||||||
bool disallowSwapping =
|
bool disallowSwapping =
|
||||||
heldItem.OwnInventory.Capacity == 1 &&
|
(heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) &&
|
||||||
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
|
heldItem.OwnInventory.GetItemAt(0)?.Prefab == item.Prefab &&
|
||||||
heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
|
heldItem.OwnInventory.GetItemsAt(0).Count() > 1;
|
||||||
if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
|
if (heldItem.OwnInventory.TryPutItem(item, Character.Controlled) ||
|
||||||
(heldItem.OwnInventory.Capacity == 1 && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: !disallowSwapping, allowCombine: false, user: Character.Controlled)))
|
((heldItem.OwnInventory.Capacity == 1 || heldItem.OwnInventory.Container.HasSubContainers) && heldItem.OwnInventory.TryPutItem(item, 0, allowSwapping: !disallowSwapping, allowCombine: false, user: Character.Controlled)))
|
||||||
{
|
{
|
||||||
success = true;
|
success = true;
|
||||||
for (int j = 0; j < capacity; j++)
|
for (int j = 0; j < capacity; j++)
|
||||||
@@ -1133,7 +1131,7 @@ namespace Barotrauma
|
|||||||
GUI.DrawRectangle(spriteBatch, inventoryArea, new Color(30,30,30,100), isFilled: true);
|
GUI.DrawRectangle(spriteBatch, inventoryArea, new Color(30,30,30,100), isFilled: true);
|
||||||
var lockIcon = GUIStyle.GetComponentStyle("LockIcon")?.GetDefaultSprite();
|
var lockIcon = GUIStyle.GetComponentStyle("LockIcon")?.GetDefaultSprite();
|
||||||
lockIcon?.Draw(spriteBatch, inventoryArea.Center.ToVector2(), scale: Math.Min(inventoryArea.Height / lockIcon.size.Y * 0.7f, 1.0f));
|
lockIcon?.Draw(spriteBatch, inventoryArea.Center.ToVector2(), scale: Math.Min(inventoryArea.Height / lockIcon.size.Y * 0.7f, 1.0f));
|
||||||
if (inventoryArea.Contains(PlayerInput.MousePosition))
|
if (inventoryArea.Contains(PlayerInput.MousePosition) && character.LockHands)
|
||||||
{
|
{
|
||||||
GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("handcuffed"), new Rectangle(inventoryArea.Center - new Point(inventoryArea.Height / 2), new Point(inventoryArea.Height)));
|
GUIComponent.DrawToolTip(spriteBatch, TextManager.Get("handcuffed"), new Rectangle(inventoryArea.Center - new Point(inventoryArea.Height / 2), new Point(inventoryArea.Height)));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,14 +25,40 @@ namespace Barotrauma.Items.Components
|
|||||||
foreach (Node node in nodes)
|
foreach (Node node in nodes)
|
||||||
{
|
{
|
||||||
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
|
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
|
||||||
|
|
||||||
|
if (node.ParentIndex > -1)
|
||||||
|
{
|
||||||
|
CreateParticlesBetween(nodes[node.ParentIndex].WorldPosition, node.WorldPosition);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach (var character in charactersInRange)
|
||||||
|
{
|
||||||
|
CreateParticlesBetween(character.character.WorldPosition, character.node.WorldPosition);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void CreateParticlesBetween(Vector2 start, Vector2 end)
|
||||||
|
{
|
||||||
|
const float ParticleInterval = 50.0f;
|
||||||
|
Vector2 diff = end - start;
|
||||||
|
float dist = diff.Length();
|
||||||
|
Vector2 normalizedDiff = MathUtils.NearlyEqual(dist, 0.0f) ? Vector2.Zero : diff / dist;
|
||||||
|
for (float x = 0.0f; x < dist; x += ParticleInterval)
|
||||||
|
{
|
||||||
|
var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", start + normalizedDiff * x, Vector2.Zero);
|
||||||
|
if (spark != null)
|
||||||
|
{
|
||||||
|
spark.Size *= 0.3f;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DrawElectricity(SpriteBatch spriteBatch)
|
public void DrawElectricity(SpriteBatch spriteBatch)
|
||||||
{
|
{
|
||||||
|
if (timer <= 0.0f) { return; }
|
||||||
for (int i = 0; i < nodes.Count; i++)
|
for (int i = 0; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
if (nodes[i].Length <= 1.0f) continue;
|
if (nodes[i].Length <= 1.0f) { continue; }
|
||||||
var node = nodes[i];
|
var node = nodes[i];
|
||||||
electricitySprite.Draw(spriteBatch,
|
electricitySprite.Draw(spriteBatch,
|
||||||
(i + frameOffset) % electricitySprite.FrameCount,
|
(i + frameOffset) % electricitySprite.FrameCount,
|
||||||
@@ -46,10 +72,16 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (GameMain.DebugDraw)
|
if (GameMain.DebugDraw)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < nodes.Count; i++)
|
for (int i = 1; i < nodes.Count; i++)
|
||||||
{
|
{
|
||||||
if (nodes[i].Length <= 1.0f) continue;
|
GUI.DrawLine(spriteBatch,
|
||||||
GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 5, Color.LightCyan, isFilled: true);
|
new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y),
|
||||||
|
new Vector2(nodes[nodes[i].ParentIndex].WorldPosition.X, -nodes[nodes[i].ParentIndex].WorldPosition.Y),
|
||||||
|
Color.LightCyan,
|
||||||
|
width: 3);
|
||||||
|
|
||||||
|
if (nodes[i].Length <= 1.0f) { continue; }
|
||||||
|
GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 10, Color.LightCyan, isFilled: true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -114,8 +114,8 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (chargeSound != null)
|
if (chargeSound != null)
|
||||||
{
|
{
|
||||||
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling);
|
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
|
||||||
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
|
if (chargeSoundChannel != null) { chargeSoundChannel.Looping = true; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (chargeSoundChannel != null)
|
else if (chargeSoundChannel != null)
|
||||||
|
|||||||
@@ -224,68 +224,59 @@ namespace Barotrauma.Items.Components
|
|||||||
if (character == null) { return false; }
|
if (character == null) { return false; }
|
||||||
if (character == Character.Controlled)
|
if (character == Character.Controlled)
|
||||||
{
|
{
|
||||||
if (targetSections.Count == 0) { return false; }
|
Spray(character, deltaTime, applyColors: targetSections.Count > 0);
|
||||||
Spray(deltaTime);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
//allow remote players to use the sprayer, but don't actually color the walls (we'll receive the data from the server)
|
//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;
|
Spray(character, deltaTime, applyColors: false);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Spray(float deltaTime)
|
public void Spray(Character user, float deltaTime, bool applyColors)
|
||||||
{
|
{
|
||||||
if (targetSections.Count == 0) { return; }
|
|
||||||
|
|
||||||
Item liquidItem = liquidContainer?.Inventory.FirstOrDefault();
|
Item liquidItem = liquidContainer?.Inventory.FirstOrDefault();
|
||||||
if (liquidItem == null) { return; }
|
if (liquidItem == null) { return; }
|
||||||
|
|
||||||
bool isCleaning = false;
|
bool isCleaning = false;
|
||||||
liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color);
|
liquidColors.TryGetValue(liquidItem.Prefab.Identifier, out color);
|
||||||
|
|
||||||
// Ethanol or other cleaning solvent
|
if (applyColors && targetSections.Any())
|
||||||
if (color.A == 0) { isCleaning = true; }
|
|
||||||
|
|
||||||
float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
|
|
||||||
|
|
||||||
if (!isCleaning)
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < targetSections.Count; i++)
|
// Ethanol or other cleaning solvent
|
||||||
|
if (color.A == 0) { isCleaning = true; }
|
||||||
|
float sizeAdjustedSprayStrength = SprayStrength / targetSections.Count;
|
||||||
|
if (!isCleaning)
|
||||||
{
|
{
|
||||||
targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
|
for (int i = 0; i < targetSections.Count; i++)
|
||||||
|
{
|
||||||
|
targetHull.IncreaseSectionColorOrStrength(targetSections[i], color, sizeAdjustedSprayStrength * deltaTime, true, false);
|
||||||
|
}
|
||||||
|
if (GameMain.GameSession != null)
|
||||||
|
{
|
||||||
|
GameMain.GameSession.TimeSpentCleaning += deltaTime;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (GameMain.GameSession != null)
|
else
|
||||||
{
|
{
|
||||||
GameMain.GameSession.TimeSpentCleaning += deltaTime;
|
for (int i = 0; i < targetSections.Count; i++)
|
||||||
}
|
{
|
||||||
}
|
targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
|
||||||
else
|
}
|
||||||
{
|
if (GameMain.GameSession != null)
|
||||||
for (int i = 0; i < targetSections.Count; i++)
|
{
|
||||||
{
|
GameMain.GameSession.TimeSpentPainting += deltaTime;
|
||||||
targetHull.CleanSection(targetSections[i], -sizeAdjustedSprayStrength * deltaTime, true);
|
}
|
||||||
}
|
|
||||||
if (GameMain.GameSession != null)
|
|
||||||
{
|
|
||||||
GameMain.GameSession.TimeSpentPainting += deltaTime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
|
Vector2 particleStartPos = item.WorldPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
|
||||||
Vector2 particleEndPos = Vector2.Zero;
|
Vector2 particleEndPos = user.CursorWorldPosition;
|
||||||
for (int i = 0; i < targetSections.Count; i++)
|
//the cursor position is not exact for remote players, we only know the direction they're aiming at but not the distance
|
||||||
{
|
// -> use 50% range, looks good enough
|
||||||
particleEndPos += new Vector2(targetSections[i].Rect.Center.X, targetSections[i].Rect.Y - targetSections[i].Rect.Height / 2) + targetHull.Rect.Location.ToVector2();
|
float dist = Math.Min(Vector2.Distance(particleStartPos, particleEndPos), Range * 0.5f);
|
||||||
}
|
|
||||||
particleEndPos /= targetSections.Count;
|
|
||||||
if (targetHull?.Submarine != null)
|
|
||||||
{
|
|
||||||
particleEndPos += targetHull.Submarine.Position;
|
|
||||||
}
|
|
||||||
float dist = Vector2.Distance(particleStartPos, particleEndPos);
|
|
||||||
|
|
||||||
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 + ((item.body.Dir > 0.0f) ? 0.0f : MathHelper.Pi);
|
||||||
|
|||||||
@@ -309,21 +309,53 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
Vector2 currentItemPos = transformedItemPos;
|
Vector2 currentItemPos = transformedItemPos;
|
||||||
|
|
||||||
SpriteEffects spriteEffects = SpriteEffects.None;
|
|
||||||
if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
|
|
||||||
{
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
|
bool isWiringMode = SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode();
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
foreach (Item containedItem in Inventory.AllItems)
|
foreach (Item containedItem in Inventory.AllItems)
|
||||||
{
|
{
|
||||||
|
Vector2 itemPos = currentItemPos;
|
||||||
|
var relatedItem = FindContainableItem(containedItem);
|
||||||
|
if (relatedItem != null)
|
||||||
|
{
|
||||||
|
if (relatedItem.Hide.HasValue && relatedItem.Hide.Value) { continue; }
|
||||||
|
if (relatedItem.ItemPos.HasValue)
|
||||||
|
{
|
||||||
|
Vector2 pos = relatedItem.ItemPos.Value;
|
||||||
|
if (item.body != null)
|
||||||
|
{
|
||||||
|
Matrix transform = Matrix.CreateRotationZ(item.body.DrawRotation);
|
||||||
|
pos.X *= item.body.Dir;
|
||||||
|
itemPos = Vector2.Transform(pos, transform) + item.body.DrawPosition;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
itemPos = pos;
|
||||||
|
// This code is aped based on above. Not tested.
|
||||||
|
if (item.FlippedX)
|
||||||
|
{
|
||||||
|
itemPos.X = -itemPos.X;
|
||||||
|
itemPos.X += item.Rect.Width;
|
||||||
|
}
|
||||||
|
if (item.FlippedY)
|
||||||
|
{
|
||||||
|
itemPos.Y = -itemPos.Y;
|
||||||
|
itemPos.Y -= item.Rect.Height;
|
||||||
|
}
|
||||||
|
itemPos += new Vector2(item.Rect.X, item.Rect.Y);
|
||||||
|
if (item.Submarine != null)
|
||||||
|
{
|
||||||
|
itemPos += item.Submarine.DrawPosition;
|
||||||
|
}
|
||||||
|
if (Math.Abs(item.RotationRad) > 0.01f)
|
||||||
|
{
|
||||||
|
Matrix transform = Matrix.CreateRotationZ(-item.RotationRad);
|
||||||
|
itemPos = Vector2.Transform(itemPos - item.DrawPosition, transform) + item.DrawPosition;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (containedItem?.Sprite == null) { continue; }
|
if (containedItem?.Sprite == null) { continue; }
|
||||||
|
|
||||||
if (AutoInteractWithContained)
|
if (AutoInteractWithContained)
|
||||||
@@ -343,19 +375,34 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
|
containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
|
||||||
|
|
||||||
|
SpriteEffects spriteEffects = SpriteEffects.None;
|
||||||
|
float spriteRotation = ItemRotation;
|
||||||
|
if (relatedItem != null && relatedItem.Rotation != 0)
|
||||||
|
{
|
||||||
|
spriteRotation = relatedItem.Rotation;
|
||||||
|
}
|
||||||
|
if ((item.body != null && item.body.Dir == -1) || item.FlippedX)
|
||||||
|
{
|
||||||
|
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipVertically : SpriteEffects.FlipHorizontally;
|
||||||
|
}
|
||||||
|
if (item.FlippedY)
|
||||||
|
{
|
||||||
|
spriteEffects |= MathUtils.NearlyEqual(spriteRotation % 180, 90.0f) ? SpriteEffects.FlipHorizontally : SpriteEffects.FlipVertically;
|
||||||
|
}
|
||||||
|
|
||||||
containedItem.Sprite.Draw(
|
containedItem.Sprite.Draw(
|
||||||
spriteBatch,
|
spriteBatch,
|
||||||
new Vector2(currentItemPos.X, -currentItemPos.Y),
|
new Vector2(itemPos.X, -itemPos.Y),
|
||||||
isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true),
|
isWiringMode ? containedItem.GetSpriteColor(withHighlight: true) * 0.15f : containedItem.GetSpriteColor(withHighlight: true),
|
||||||
origin,
|
origin,
|
||||||
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation ),
|
-(containedItem.body == null ? 0.0f : containedItem.body.DrawRotation),
|
||||||
containedItem.Scale,
|
containedItem.Scale,
|
||||||
spriteEffects,
|
spriteEffects,
|
||||||
depth: containedSpriteDepth);
|
depth: containedSpriteDepth);
|
||||||
|
|
||||||
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>())
|
foreach (ItemContainer ic in containedItem.GetComponents<ItemContainer>())
|
||||||
{
|
{
|
||||||
if (ic.hideItems) continue;
|
if (ic.hideItems) { continue; }
|
||||||
ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
|
ic.DrawContainedItems(spriteBatch, containedSpriteDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
public override void FlipX(bool relativeToSub)
|
public override void FlipX(bool relativeToSub)
|
||||||
{
|
{
|
||||||
if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX && item.body == null)
|
if (Light?.LightSprite != null && item.Prefab.CanSpriteFlipX)
|
||||||
{
|
{
|
||||||
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
|
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
|
||||||
SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
SpriteEffects.FlipHorizontally : SpriteEffects.None;
|
||||||
|
|||||||
@@ -14,7 +14,10 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private GUIFrame selectedItemFrame;
|
private GUIFrame selectedItemFrame;
|
||||||
private GUIFrame selectedItemReqsFrame;
|
private GUIFrame selectedItemReqsFrame;
|
||||||
|
|
||||||
|
private GUITextBlock amountTextMin, amountTextMax;
|
||||||
|
private GUIScrollBar amountInput;
|
||||||
|
|
||||||
public GUIButton ActivateButton
|
public GUIButton ActivateButton
|
||||||
{
|
{
|
||||||
get { return activateButton; }
|
get { return activateButton; }
|
||||||
@@ -160,14 +163,46 @@ namespace Barotrauma.Items.Components
|
|||||||
new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false };
|
new GUICustomComponent(new RectTransform(Vector2.One, inputInventoryHolder.RectTransform), DrawInputOverLay) { CanBeFocused = false };
|
||||||
|
|
||||||
// === ACTIVATE BUTTON === //
|
// === ACTIVATE BUTTON === //
|
||||||
var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterRight);
|
var buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 0.9f), inputArea.RectTransform))
|
||||||
activateButton = new GUIButton(new RectTransform(new Vector2(1f, 0.6f), buttonFrame.RectTransform),
|
{
|
||||||
TextManager.Get(CreateButtonText), style: "DeviceButtonFixedSize")
|
Stretch = true,
|
||||||
|
RelativeSpacing = 0.05f
|
||||||
|
};
|
||||||
|
|
||||||
|
var amountInputHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.4f), buttonFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft)
|
||||||
|
{
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
amountTextMin = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||||
|
|
||||||
|
amountInput = new GUIScrollBar(new RectTransform(new Vector2(0.7f, 1.0f), amountInputHolder.RectTransform), barSize: 0.1f, style: "GUISlider")
|
||||||
|
{
|
||||||
|
OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
|
||||||
|
{
|
||||||
|
scrollBar.Step = 1.0f / Math.Max(scrollBar.Range.Y - 1, 1);
|
||||||
|
AmountToFabricate = (int)MathF.Round(scrollBar.BarScrollValue);
|
||||||
|
RefreshActivateButtonText();
|
||||||
|
if (GameMain.Client != null)
|
||||||
|
{
|
||||||
|
item.CreateClientEvent(this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
amountTextMax = new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), amountInputHolder.RectTransform), "1", textAlignment: Alignment.Center);
|
||||||
|
|
||||||
|
activateButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.6f), buttonFrame.RectTransform),
|
||||||
|
TextManager.Get(CreateButtonText), style: "DeviceButton")
|
||||||
{
|
{
|
||||||
OnClicked = StartButtonClicked,
|
OnClicked = StartButtonClicked,
|
||||||
UserData = selectedItem,
|
UserData = selectedItem,
|
||||||
Enabled = false
|
Enabled = false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//spacing
|
||||||
|
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), buttonFrame.RectTransform), style: null);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -192,6 +227,21 @@ namespace Barotrauma.Items.Components
|
|||||||
CreateRecipes();
|
CreateRecipes();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void RefreshActivateButtonText()
|
||||||
|
{
|
||||||
|
if (amountInput == null)
|
||||||
|
{
|
||||||
|
activateButton.Text = TextManager.Get(IsActive ? "FabricatorCancel" : CreateButtonText);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
activateButton.Text =
|
||||||
|
IsActive ?
|
||||||
|
$"{TextManager.Get("FabricatorCancel")} ({amountRemaining})" :
|
||||||
|
$"{TextManager.Get(CreateButtonText)} ({AmountToFabricate})";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
partial void CreateRecipes()
|
partial void CreateRecipes()
|
||||||
{
|
{
|
||||||
itemList.Content.RectTransform.ClearChildren();
|
itemList.Content.RectTransform.ClearChildren();
|
||||||
@@ -247,7 +297,7 @@ namespace Barotrauma.Items.Components
|
|||||||
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
|
||||||
}
|
}
|
||||||
|
|
||||||
private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
|
private static LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
|
||||||
{
|
{
|
||||||
if (fabricationRecipe == null) { return ""; }
|
if (fabricationRecipe == null) { return ""; }
|
||||||
if (fabricationRecipe.Amount > 1)
|
if (fabricationRecipe.Amount > 1)
|
||||||
@@ -269,7 +319,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void SelectProjSpecific(Character character)
|
partial void SelectProjSpecific(Character character)
|
||||||
{
|
{
|
||||||
var nonItems = itemList.Content.Children.Where(c => !(c.UserData is FabricationRecipe)).ToList();
|
var nonItems = itemList.Content.Children.Where(c => c.UserData is not FabricationRecipe).ToList();
|
||||||
nonItems.ForEach(i => itemList.Content.RemoveChild(i));
|
nonItems.ForEach(i => itemList.Content.RemoveChild(i));
|
||||||
|
|
||||||
itemList.Content.RectTransform.SortChildren((c1, c2) =>
|
itemList.Content.RectTransform.SortChildren((c1, c2) =>
|
||||||
@@ -277,17 +327,20 @@ namespace Barotrauma.Items.Components
|
|||||||
var item1 = c1.GUIComponent.UserData as FabricationRecipe;
|
var item1 = c1.GUIComponent.UserData as FabricationRecipe;
|
||||||
var item2 = c2.GUIComponent.UserData as FabricationRecipe;
|
var item2 = c2.GUIComponent.UserData as FabricationRecipe;
|
||||||
|
|
||||||
int itemPlacement1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f ? 0 : -1;
|
int itemPlacement1 = calculatePlacement(item1);
|
||||||
int itemPlacement2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f ? 0 : -1;
|
int itemPlacement2 = calculatePlacement(item2);
|
||||||
|
|
||||||
itemPlacement1 += item1.RequiresRecipe && !character.HasRecipeForItem(item1.TargetItem.Identifier) ? -2 : 0;
|
|
||||||
itemPlacement2 += item2.RequiresRecipe && !character.HasRecipeForItem(item2.TargetItem.Identifier) ? -2 : 0;
|
|
||||||
|
|
||||||
if (itemPlacement1 != itemPlacement2)
|
if (itemPlacement1 != itemPlacement2)
|
||||||
{
|
{
|
||||||
return itemPlacement1 > itemPlacement2 ? -1 : 1;
|
return itemPlacement1 > itemPlacement2 ? -1 : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int calculatePlacement(FabricationRecipe recipe)
|
||||||
|
{
|
||||||
|
int placement = FabricationDegreeOfSuccess(character, recipe.RequiredSkills) >= 0.5f ? 0 : -1;
|
||||||
|
placement += recipe.RequiresRecipe && !AnyOneHasRecipeForItem(character, recipe.TargetItem) ? -2 : 0;
|
||||||
|
return placement;
|
||||||
|
}
|
||||||
|
|
||||||
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
|
return string.Compare(item1.DisplayName.Value, item2.DisplayName.Value);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,7 +375,9 @@ namespace Barotrauma.Items.Components
|
|||||||
AutoScaleHorizontal = true,
|
AutoScaleHorizontal = true,
|
||||||
CanBeFocused = false
|
CanBeFocused = false
|
||||||
};
|
};
|
||||||
var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && (fabricableItem.RequiresRecipe && !character.HasRecipeForItem(fabricableItem.TargetItem.Identifier)));
|
var firstRequiresRecipe = itemList.Content.Children.FirstOrDefault(c =>
|
||||||
|
c.UserData is FabricationRecipe fabricableItem &&
|
||||||
|
fabricableItem.RequiresRecipe && !AnyOneHasRecipeForItem(character, fabricableItem.TargetItem));
|
||||||
if (firstRequiresRecipe != null)
|
if (firstRequiresRecipe != null)
|
||||||
{
|
{
|
||||||
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
|
requiresRecipeText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstRequiresRecipe.RectTransform));
|
||||||
@@ -567,7 +622,7 @@ namespace Barotrauma.Items.Components
|
|||||||
bool recipeVisible = false;
|
bool recipeVisible = false;
|
||||||
foreach (GUIComponent child in itemList.Content.Children.Reverse())
|
foreach (GUIComponent child in itemList.Content.Children.Reverse())
|
||||||
{
|
{
|
||||||
if (!(child.UserData is FabricationRecipe recipe))
|
if (child.UserData is not FabricationRecipe recipe)
|
||||||
{
|
{
|
||||||
if (child.Enabled)
|
if (child.Enabled)
|
||||||
{
|
{
|
||||||
@@ -598,9 +653,23 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
this.selectedItem = selectedItem;
|
this.selectedItem = selectedItem;
|
||||||
|
|
||||||
|
int max = Math.Max(selectedItem.TargetItem.MaxStackSize / selectedItem.Amount, 1);
|
||||||
|
|
||||||
|
if (amountInput != null)
|
||||||
|
{
|
||||||
|
float prevBarScroll = amountInput.BarScroll;
|
||||||
|
amountInput.Range = new Vector2(1, max);
|
||||||
|
amountInput.BarScroll = prevBarScroll;
|
||||||
|
|
||||||
|
amountTextMax.Text = max.ToString();
|
||||||
|
amountInput.Enabled = amountTextMax.Enabled = max > 1;
|
||||||
|
AmountToFabricate = Math.Min((int)amountInput.BarScrollValue, max);
|
||||||
|
}
|
||||||
|
RefreshActivateButtonText();
|
||||||
|
|
||||||
selectedItemFrame.ClearChildren();
|
selectedItemFrame.ClearChildren();
|
||||||
selectedItemReqsFrame.ClearChildren();
|
selectedItemReqsFrame.ClearChildren();
|
||||||
|
|
||||||
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
||||||
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
var paddedReqFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), selectedItemReqsFrame.RectTransform, Anchor.Center)) { RelativeSpacing = 0.03f };
|
||||||
|
|
||||||
@@ -734,7 +803,9 @@ namespace Barotrauma.Items.Components
|
|||||||
outputSlot.Flash(GUIStyle.Red);
|
outputSlot.Flash(GUIStyle.Red);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
amountRemaining = AmountToFabricate;
|
||||||
|
|
||||||
if (GameMain.Client != null)
|
if (GameMain.Client != null)
|
||||||
{
|
{
|
||||||
pendingFabricatedItem = fabricatedItem != null ? null : selectedItem;
|
pendingFabricatedItem = fabricatedItem != null ? null : selectedItem;
|
||||||
@@ -777,7 +848,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
foreach (GUIComponent child in itemList.Content.Children)
|
foreach (GUIComponent child in itemList.Content.Children)
|
||||||
{
|
{
|
||||||
if (!(child.UserData is FabricationRecipe recipe)) { continue; }
|
if (child.UserData is not FabricationRecipe recipe) { continue; }
|
||||||
|
|
||||||
if (recipe != selectedItem &&
|
if (recipe != selectedItem &&
|
||||||
(child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y))
|
(child.Rect.Y > itemList.Rect.Bottom || child.Rect.Bottom < itemList.Rect.Y))
|
||||||
@@ -811,11 +882,14 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
|
uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
|
||||||
msg.WriteUInt32(recipeHash);
|
msg.WriteUInt32(recipeHash);
|
||||||
|
msg.WriteRangedInteger(AmountToFabricate, 1, MaxAmountToFabricate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
public void ClientEventRead(IReadMessage msg, float sendingTime)
|
||||||
{
|
{
|
||||||
FabricatorState newState = (FabricatorState)msg.ReadByte();
|
FabricatorState newState = (FabricatorState)msg.ReadByte();
|
||||||
|
int amountToFabricate = msg.ReadRangedInteger(0, MaxAmountToFabricate);
|
||||||
|
int amountRemaining = msg.ReadRangedInteger(0, MaxAmountToFabricate);
|
||||||
float newTimeUntilReady = msg.ReadSingle();
|
float newTimeUntilReady = msg.ReadSingle();
|
||||||
uint recipeHash = msg.ReadUInt32();
|
uint recipeHash = msg.ReadUInt32();
|
||||||
UInt16 userID = msg.ReadUInt16();
|
UInt16 userID = msg.ReadUInt16();
|
||||||
@@ -828,6 +902,8 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
State = newState;
|
State = newState;
|
||||||
|
this.amountToFabricate = amountToFabricate;
|
||||||
|
this.amountRemaining = amountRemaining;
|
||||||
if (newState == FabricatorState.Stopped || recipeHash == 0)
|
if (newState == FabricatorState.Stopped || recipeHash == 0)
|
||||||
{
|
{
|
||||||
CancelFabricating();
|
CancelFabricating();
|
||||||
|
|||||||
@@ -144,6 +144,9 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial class MiniMap : Powered
|
partial class MiniMap : Powered
|
||||||
{
|
{
|
||||||
|
private Dictionary<Hull, HullData> hullDatas;
|
||||||
|
private DateTime resetDataTime;
|
||||||
|
|
||||||
private GUIFrame submarineContainer;
|
private GUIFrame submarineContainer;
|
||||||
|
|
||||||
private GUIFrame? hullInfoFrame;
|
private GUIFrame? hullInfoFrame;
|
||||||
@@ -226,6 +229,8 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
partial void InitProjSpecific()
|
partial void InitProjSpecific()
|
||||||
{
|
{
|
||||||
|
hullDatas = new Dictionary<Hull, HullData>();
|
||||||
|
|
||||||
SetDefaultMode();
|
SetDefaultMode();
|
||||||
|
|
||||||
noPowerTip = TextManager.Get("SteeringNoPowerTip");
|
noPowerTip = TextManager.Get("SteeringNoPowerTip");
|
||||||
@@ -408,6 +413,8 @@ namespace Barotrauma.Items.Components
|
|||||||
var wire = it.GetComponent<Wire>();
|
var wire = it.GetComponent<Wire>();
|
||||||
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
|
if (wire != null && wire.Connections.Any(c => c != null)) { return false; }
|
||||||
|
|
||||||
|
if (it.Container?.GetComponent<ItemContainer>() is { DrawInventory: false }) { return false; }
|
||||||
|
|
||||||
if (it.HasTag("traitormissionitem")) { return false; }
|
if (it.HasTag("traitormissionitem")) { return false; }
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
@@ -549,6 +556,34 @@ namespace Barotrauma.Items.Components
|
|||||||
CreateHUD();
|
CreateHUD();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//reset data if we haven't received anything in a while
|
||||||
|
//(so that outdated hull info won't be shown if detectors stop sending signals)
|
||||||
|
if (DateTime.Now > resetDataTime)
|
||||||
|
{
|
||||||
|
foreach (HullData hullData in hullDatas.Values)
|
||||||
|
{
|
||||||
|
if (!hullData.Distort)
|
||||||
|
{
|
||||||
|
if (Timing.TotalTime > hullData.LastOxygenDataTime + 1.0) { hullData.ReceivedOxygenAmount = null; }
|
||||||
|
if (Timing.TotalTime > hullData.LastWaterDataTime + 1.0) { hullData.ReceivedWaterAmount = null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
resetDataTime = DateTime.Now + new TimeSpan(0, 0, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardRefreshTimer > cardRefreshDelay)
|
||||||
|
{
|
||||||
|
if (item.Submarine is { } sub)
|
||||||
|
{
|
||||||
|
UpdateIDCards(sub);
|
||||||
|
}
|
||||||
|
cardRefreshTimer = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cardRefreshTimer += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
if (scissorComponent != null)
|
if (scissorComponent != null)
|
||||||
{
|
{
|
||||||
if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus)
|
if (PlayerInput.PrimaryMouseButtonDown() && currentMode != MiniMapMode.HullStatus)
|
||||||
@@ -952,11 +987,8 @@ namespace Barotrauma.Items.Components
|
|||||||
component.Color = borderComponent.OutlineColor = NoPowerColor;
|
component.Color = borderComponent.OutlineColor = NoPowerColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Voltage < MinVoltage) { continue; }
|
|
||||||
|
|
||||||
if (!component.Visible) { continue; }
|
if (!component.Visible) { continue; }
|
||||||
if (!(entity is Hull hull)) { continue; }
|
if (entity is not Hull hull) { continue; }
|
||||||
|
|
||||||
if (!submarineContainer.Rect.Contains(component.Rect))
|
if (!submarineContainer.Rect.Contains(component.Rect))
|
||||||
{
|
{
|
||||||
if (hull.Submarine.Info.Type != SubmarineType.Player)
|
if (hull.Submarine.Info.Type != SubmarineType.Player)
|
||||||
@@ -966,6 +998,8 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Voltage < MinVoltage) { continue; }
|
||||||
|
|
||||||
hullDatas.TryGetValue(hull, out HullData? hullData);
|
hullDatas.TryGetValue(hull, out HullData? hullData);
|
||||||
if (hullData is null)
|
if (hullData is null)
|
||||||
{
|
{
|
||||||
@@ -1119,7 +1153,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (it.GetComponent<PowerContainer>() is { } battery)
|
if (it.GetComponent<PowerContainer>() is { } battery)
|
||||||
{
|
{
|
||||||
int batteryCapacity = (int)(battery.Charge / battery.Capacity * 100f);
|
int batteryCapacity = (int)(battery.Charge / battery.GetCapacity() * 100f);
|
||||||
line2 = TextManager.GetWithVariable("statusmonitor.battery.tooltip", "[amount]", batteryCapacity.ToString());
|
line2 = TextManager.GetWithVariable("statusmonitor.battery.tooltip", "[amount]", batteryCapacity.ToString());
|
||||||
}
|
}
|
||||||
else if (it.GetComponent<PowerTransfer>() is { } powerTransfer)
|
else if (it.GetComponent<PowerTransfer>() is { } powerTransfer)
|
||||||
@@ -1734,6 +1768,67 @@ namespace Barotrauma.Items.Components
|
|||||||
return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
|
return new MiniMapHullData(scaledPolygon, worldRect, parentRect.Size, snappedRectangles, hullRefs.ToImmutableArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void ReceiveSignal(Signal signal, Connection connection)
|
||||||
|
{
|
||||||
|
Item source = signal.source;
|
||||||
|
if (source == null || source.CurrentHull == null) { return; }
|
||||||
|
|
||||||
|
Hull sourceHull = source.CurrentHull;
|
||||||
|
if (!hullDatas.TryGetValue(sourceHull, out HullData? hullData))
|
||||||
|
{
|
||||||
|
hullData = new HullData();
|
||||||
|
hullDatas.Add(sourceHull, hullData);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hullData.Distort) { return; }
|
||||||
|
|
||||||
|
switch (connection.Name)
|
||||||
|
{
|
||||||
|
case "water_data_in":
|
||||||
|
//cheating a bit because water detectors don't actually send the water level
|
||||||
|
bool fromWaterDetector = source.GetComponent<WaterDetector>() != null;
|
||||||
|
hullData.ReceivedWaterAmount = null;
|
||||||
|
hullData.LastWaterDataTime = Timing.TotalTime;
|
||||||
|
if (fromWaterDetector)
|
||||||
|
{
|
||||||
|
hullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(sourceHull);
|
||||||
|
}
|
||||||
|
foreach (var linked in sourceHull.linkedTo)
|
||||||
|
{
|
||||||
|
if (linked is not Hull linkedHull) { continue; }
|
||||||
|
if (!hullDatas.TryGetValue(linkedHull, out HullData? linkedHullData))
|
||||||
|
{
|
||||||
|
linkedHullData = new HullData();
|
||||||
|
hullDatas.Add(linkedHull, linkedHullData);
|
||||||
|
}
|
||||||
|
linkedHullData.ReceivedWaterAmount = null;
|
||||||
|
if (fromWaterDetector)
|
||||||
|
{
|
||||||
|
linkedHullData.ReceivedWaterAmount = WaterDetector.GetWaterPercentage(linkedHull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case "oxygen_data_in":
|
||||||
|
if (!float.TryParse(signal.value, System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out float oxy))
|
||||||
|
{
|
||||||
|
oxy = Rand.Range(0.0f, 100.0f);
|
||||||
|
}
|
||||||
|
hullData.ReceivedOxygenAmount = oxy;
|
||||||
|
hullData.LastOxygenDataTime = Timing.TotalTime;
|
||||||
|
foreach (var linked in sourceHull.linkedTo)
|
||||||
|
{
|
||||||
|
if (linked is not Hull linkedHull) { continue; }
|
||||||
|
if (!hullDatas.TryGetValue(linkedHull, out HullData? linkedHullData))
|
||||||
|
{
|
||||||
|
linkedHullData = new HullData();
|
||||||
|
hullDatas.Add(linkedHull, linkedHullData);
|
||||||
|
}
|
||||||
|
linkedHullData.ReceivedOxygenAmount = oxy;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ namespace Barotrauma.Items.Components
|
|||||||
private Sprite tempRangeIndicator;
|
private Sprite tempRangeIndicator;
|
||||||
|
|
||||||
private Sprite graphLine;
|
private Sprite graphLine;
|
||||||
//private GUIFrame graph;
|
private GUICustomComponent graph;
|
||||||
|
|
||||||
|
private GUIFrame inventoryWindow;
|
||||||
|
private GUILayoutGroup buttonArea;
|
||||||
|
private GUIFrame infographic;
|
||||||
|
|
||||||
private Color optimalRangeColor = new Color(74,238,104,255);
|
private Color optimalRangeColor = new Color(74,238,104,255);
|
||||||
private Color offRangeColor = Color.Orange;
|
private Color offRangeColor = Color.Orange;
|
||||||
@@ -66,6 +70,8 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
|
|
||||||
public override bool RecreateGUIOnResolutionChange => true;
|
public override bool RecreateGUIOnResolutionChange => true;
|
||||||
|
|
||||||
|
public bool TriggerInfographic { get; set; }
|
||||||
|
|
||||||
partial void InitProjSpecific(ContentXElement element)
|
partial void InitProjSpecific(ContentXElement element)
|
||||||
{
|
{
|
||||||
@@ -122,7 +128,7 @@ namespace Barotrauma.Items.Components
|
|||||||
//left column
|
//left column
|
||||||
//----------------------------------------------------------
|
//----------------------------------------------------------
|
||||||
|
|
||||||
GUIFrame inventoryWindow = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.75f), GuiFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight)
|
inventoryWindow = new GUIFrame(new RectTransform(new Vector2(0.1f, 0.75f), GuiFrame.RectTransform, Anchor.TopLeft, Pivot.TopRight)
|
||||||
{
|
{
|
||||||
MinSize = new Point(85, 220),
|
MinSize = new Point(85, 220),
|
||||||
RelativeOffset = new Vector2(-0.02f, 0)
|
RelativeOffset = new Vector2(-0.02f, 0)
|
||||||
@@ -255,7 +261,7 @@ namespace Barotrauma.Items.Components
|
|||||||
};
|
};
|
||||||
TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider;
|
TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider;
|
||||||
|
|
||||||
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform))
|
buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform))
|
||||||
{
|
{
|
||||||
Stretch = true,
|
Stretch = true,
|
||||||
RelativeSpacing = 0.02f
|
RelativeSpacing = 0.02f
|
||||||
@@ -390,7 +396,9 @@ namespace Barotrauma.Items.Components
|
|||||||
ToolTip = TextManager.Get("reactor.temperatureboostup"),
|
ToolTip = TextManager.Get("reactor.temperatureboostup"),
|
||||||
OnClicked = (_, __) =>
|
OnClicked = (_, __) =>
|
||||||
{
|
{
|
||||||
applyTemperatureBoost(TemperatureBoostAmount, temperatureBoostSoundUp);
|
unsentChanges = true;
|
||||||
|
sendUpdateTimer = 0.0f;
|
||||||
|
ApplyTemperatureBoost(TemperatureBoostAmount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -401,25 +409,13 @@ namespace Barotrauma.Items.Components
|
|||||||
ToolTip = TextManager.Get("reactor.temperatureboostdown"),
|
ToolTip = TextManager.Get("reactor.temperatureboostdown"),
|
||||||
OnClicked = (_, __) =>
|
OnClicked = (_, __) =>
|
||||||
{
|
{
|
||||||
applyTemperatureBoost(-TemperatureBoostAmount, temperatureBoostSoundDown);
|
unsentChanges = true;
|
||||||
|
sendUpdateTimer = 0.0f;
|
||||||
|
ApplyTemperatureBoost(-TemperatureBoostAmount);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
void applyTemperatureBoost(float amount, RoundSound sound)
|
|
||||||
{
|
|
||||||
temperatureBoost = amount;
|
|
||||||
if (sound != null)
|
|
||||||
{
|
|
||||||
SoundPlayer.PlaySound(
|
|
||||||
sound.Sound,
|
|
||||||
item.WorldPosition,
|
|
||||||
sound.Volume,
|
|
||||||
sound.Range,
|
|
||||||
hullGuess: item.CurrentHull);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var graphArea = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), bottomRightArea.RectTransform))
|
var graphArea = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 1.0f), bottomRightArea.RectTransform))
|
||||||
{
|
{
|
||||||
Stretch = true,
|
Stretch = true,
|
||||||
@@ -436,8 +432,8 @@ namespace Barotrauma.Items.Components
|
|||||||
LocalizedString kW = TextManager.Get("kilowatt");
|
LocalizedString kW = TextManager.Get("kilowatt");
|
||||||
loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)Load).ToString())} {kW}";
|
loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)Load).ToString())} {kW}";
|
||||||
|
|
||||||
var graph = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed");
|
var graphFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed");
|
||||||
new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graph.RectTransform, Anchor.Center), DrawGraph, null);
|
graph = new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graphFrame.RectTransform, Anchor.Center), DrawGraph, null);
|
||||||
|
|
||||||
var outputText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform),
|
var outputText = new GUITextBlock(new RectTransform(relativeTextSize, graphArea.RectTransform),
|
||||||
"Output", textColor: outputColor, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
"Output", textColor: outputColor, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterLeft)
|
||||||
@@ -448,6 +444,41 @@ namespace Barotrauma.Items.Components
|
|||||||
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
|
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
|
||||||
|
|
||||||
InitInventoryUI();
|
InitInventoryUI();
|
||||||
|
|
||||||
|
// Infographic overlay ---------------------
|
||||||
|
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
|
||||||
|
var helpButtonRt = new RectTransform(new Point(buttonHeight), parent: GuiFrame.RectTransform, anchor: Anchor.TopRight)
|
||||||
|
{
|
||||||
|
AbsoluteOffset = new Point(buttonHeight / 4),
|
||||||
|
MinSize = new Point(buttonHeight)
|
||||||
|
};
|
||||||
|
new GUIButton(helpButtonRt, "", style: "HelpIcon")
|
||||||
|
{
|
||||||
|
OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
CreateInfrographic();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
private void ApplyTemperatureBoost(float amount)
|
||||||
|
{
|
||||||
|
if (Math.Abs(temperatureBoost) <= TemperatureBoostAmount * 0.9f &&
|
||||||
|
Math.Abs(amount) > TemperatureBoostAmount * 0.9f)
|
||||||
|
{
|
||||||
|
var sound = amount > 0 ? temperatureBoostSoundUp : temperatureBoostSoundDown;
|
||||||
|
if (sound != null)
|
||||||
|
{
|
||||||
|
SoundPlayer.PlaySound(
|
||||||
|
sound.Sound,
|
||||||
|
item.WorldPosition,
|
||||||
|
sound.Volume,
|
||||||
|
sound.Range,
|
||||||
|
freqMult: sound.GetRandomFrequencyMultiplier(),
|
||||||
|
hullGuess: item.CurrentHull);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
temperatureBoost = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void InitInventoryUI()
|
private void InitInventoryUI()
|
||||||
@@ -469,7 +500,6 @@ namespace Barotrauma.Items.Components
|
|||||||
InitInventoryUI();
|
InitInventoryUI();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
private void DrawTempMeter(SpriteBatch spriteBatch, GUICustomComponent container)
|
||||||
{
|
{
|
||||||
Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y);
|
Vector2 meterPos = new Vector2(container.Rect.X, container.Rect.Y);
|
||||||
@@ -518,7 +548,6 @@ namespace Barotrauma.Items.Components
|
|||||||
DrawGraph(loadGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, loadColor);
|
DrawGraph(loadGraph, spriteBatch, graphRect, Math.Max(10000.0f, maxLoad), xOffset, loadColor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private void UpdateGraph(float deltaTime)
|
private void UpdateGraph(float deltaTime)
|
||||||
{
|
{
|
||||||
graphTimer += deltaTime * 1000.0f;
|
graphTimer += deltaTime * 1000.0f;
|
||||||
@@ -645,6 +674,12 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (TriggerInfographic)
|
||||||
|
{
|
||||||
|
CreateInfrographic();
|
||||||
|
TriggerInfographic = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawMeter(SpriteBatch spriteBatch, Rectangle rect, Sprite meterSprite, float value, Vector2 range, Vector2 optimalRange, Vector2 allowedRange)
|
private void DrawMeter(SpriteBatch spriteBatch, Rectangle rect, Sprite meterSprite, float value, Vector2 range, Vector2 optimalRange, Vector2 allowedRange)
|
||||||
@@ -760,7 +795,96 @@ namespace Barotrauma.Items.Components
|
|||||||
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
|
||||||
spriteBatch.Begin(SpriteSortMode.Deferred);
|
spriteBatch.Begin(SpriteSortMode.Deferred);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private enum InfographicArrowStyle { Straight, Curved };
|
||||||
|
|
||||||
|
private void CreateInfrographic()
|
||||||
|
{
|
||||||
|
if (infographic != null) { return; }
|
||||||
|
var dimColor = Color.Lerp(Color.Black, Color.TransparentBlack, 0.25f);
|
||||||
|
// Dim reactor interface
|
||||||
|
infographic = new GUIFrame(new RectTransform(Vector2.One, GuiFrame.RectTransform))
|
||||||
|
{
|
||||||
|
CanBeFocused = false,
|
||||||
|
Color = dimColor
|
||||||
|
};
|
||||||
|
// Dim inventory window
|
||||||
|
new GUIFrame(new RectTransform(inventoryWindow.Rect.Size, infographic.RectTransform) { AbsoluteOffset = inventoryWindow.Rect.Location - GuiFrame.Rect.Location}, color: dimColor)
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
int arrowSize = (int)(70 * GUI.Scale);
|
||||||
|
var arrows = new Dictionary<string, GUIImage>()
|
||||||
|
{
|
||||||
|
{ "fuelslots", CreateArrow(InfographicArrowStyle.Curved, inventoryWindow, Anchor.TopLeft, Pivot.TopRight, SpriteEffects.FlipVertically) },
|
||||||
|
{ "temperature", CreateArrow(InfographicArrowStyle.Straight, temperatureBoostDownButton, Anchor.Center, Pivot.Center) },
|
||||||
|
{ "automaticcontrol", CreateArrow(InfographicArrowStyle.Curved, AutoTempSwitch, Anchor.TopRight, Pivot.BottomRight, rotationDegrees: 90f) },
|
||||||
|
{ "power", CreateArrow(InfographicArrowStyle.Straight, PowerButton, Anchor.BottomCenter, Pivot.TopCenter) }
|
||||||
|
};
|
||||||
|
CreateArrow(InfographicArrowStyle.Straight, FissionRateScrollBar, Anchor.Center, Pivot.Center);
|
||||||
|
CreateArrow(InfographicArrowStyle.Straight, TurbineOutputScrollBar, Anchor.Center, Pivot.Center);
|
||||||
|
CreateArrow(InfographicArrowStyle.Straight, graph, Anchor.TopLeft, Pivot.TopLeft, SpriteEffects.FlipHorizontally, additionalOffset: new Point(arrowSize / 2, 0));
|
||||||
|
CreateArrow(InfographicArrowStyle.Straight, graph, Anchor.BottomLeft, Pivot.BottomLeft, SpriteEffects.FlipHorizontally | SpriteEffects.FlipVertically, additionalOffset: new Point(arrowSize / 2, 0));
|
||||||
|
new GUICustomComponent(new RectTransform(Vector2.One, infographic.RectTransform),
|
||||||
|
onDraw: (sb, c) =>
|
||||||
|
{
|
||||||
|
DrawToolTip("fuelslots", Anchor.TopLeft, Pivot.BottomCenter, arrows["fuelslots"]);
|
||||||
|
DrawToolTip("fissionrate", Anchor.TopCenter, Pivot.TopCenter, buttonArea);
|
||||||
|
DrawToolTip("temperature", Anchor.BottomLeft, Pivot.TopLeft, arrows["temperature"]);
|
||||||
|
DrawToolTip("automaticcontrol", Anchor.TopLeft, Pivot.CenterRight, arrows["automaticcontrol"]);
|
||||||
|
DrawToolTip("power", Anchor.BottomCenter, Pivot.TopCenter, arrows["power"]);
|
||||||
|
DrawToolTip("load", Anchor.CenterLeft, Pivot.CenterLeft, graph);
|
||||||
|
|
||||||
|
void DrawToolTip(string textTag, Anchor anchor, Pivot pivot, GUIComponent targetComponent)
|
||||||
|
{
|
||||||
|
GUIComponent.DrawToolTip(sb,
|
||||||
|
TextManager.Get($"infographic.reactor.{textTag}"),
|
||||||
|
targetComponent.Rect,
|
||||||
|
anchor: anchor,
|
||||||
|
pivot: pivot);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
{
|
||||||
|
CanBeFocused = false
|
||||||
|
};
|
||||||
|
var closeButtonRt = new RectTransform(new Point(200, 50).Multiply(GUI.Scale), infographic.RectTransform, Anchor.TopRight, Pivot.BottomRight)
|
||||||
|
{
|
||||||
|
AbsoluteOffset = new Point(0, -50).Multiply(GUI.Scale)
|
||||||
|
};
|
||||||
|
new GUIButton(closeButtonRt, TextManager.Get("close"))
|
||||||
|
{
|
||||||
|
OnClicked = (_, _) =>
|
||||||
|
{
|
||||||
|
CloseInfographic(Character.Controlled);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
item.OnDeselect += CloseInfographic;
|
||||||
|
|
||||||
|
GUIImage CreateArrow(InfographicArrowStyle arrowStyle, GUIComponent parent, Anchor anchor, Pivot pivot, SpriteEffects spriteEffects = SpriteEffects.None, float rotationDegrees = 0f, Point? additionalOffset = null)
|
||||||
|
{
|
||||||
|
Point offset = (additionalOffset ?? Point.Zero) + RectTransform.CalculateAnchorPoint(anchor, parent.Rect) - GuiFrame.Rect.Location;
|
||||||
|
var rt = new RectTransform(new Point(arrowSize), infographic.RectTransform, pivot: pivot)
|
||||||
|
{
|
||||||
|
AbsoluteOffset = offset
|
||||||
|
};
|
||||||
|
string style = arrowStyle == InfographicArrowStyle.Straight ? "InfographicArrow" : "InfographicArrowCurved";
|
||||||
|
return new GUIImage(rt, style)
|
||||||
|
{
|
||||||
|
Rotation = MathHelper.ToRadians(rotationDegrees),
|
||||||
|
SpriteEffects = spriteEffects
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
void CloseInfographic(Character character)
|
||||||
|
{
|
||||||
|
if (character != Character.Controlled) { return; }
|
||||||
|
GuiFrame.RemoveChild(infographic);
|
||||||
|
infographic = null;
|
||||||
|
item.OnDeselect -= CloseInfographic;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected override void RemoveComponentSpecific()
|
protected override void RemoveComponentSpecific()
|
||||||
{
|
{
|
||||||
base.RemoveComponentSpecific();
|
base.RemoveComponentSpecific();
|
||||||
@@ -780,6 +904,7 @@ namespace Barotrauma.Items.Components
|
|||||||
msg.WriteBoolean(PowerOn);
|
msg.WriteBoolean(PowerOn);
|
||||||
msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8);
|
msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8);
|
||||||
msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8);
|
msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8);
|
||||||
|
msg.WriteRangedSingle(temperatureBoost, -TemperatureBoostAmount, TemperatureBoostAmount, 8);
|
||||||
|
|
||||||
correctionTimer = CorrectionDelay;
|
correctionTimer = CorrectionDelay;
|
||||||
}
|
}
|
||||||
@@ -788,7 +913,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (correctionTimer > 0.0f)
|
if (correctionTimer > 0.0f)
|
||||||
{
|
{
|
||||||
StartDelayedCorrection(msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8), sendingTime);
|
StartDelayedCorrection(msg.ExtractBits(1 + 1 + 8 + 8 + 8 + 8 + 8), sendingTime);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -798,6 +923,7 @@ namespace Barotrauma.Items.Components
|
|||||||
TargetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
TargetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||||
TargetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
TargetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
|
||||||
degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||||
|
ApplyTemperatureBoost(msg.ReadRangedSingle(-TemperatureBoostAmount, TemperatureBoostAmount, 8));
|
||||||
|
|
||||||
if (Math.Abs(FissionRateScrollBar.BarScroll - TargetFissionRate / 100.0f) > 0.01f)
|
if (Math.Abs(FissionRateScrollBar.BarScroll - TargetFissionRate / 100.0f) > 0.01f)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -802,11 +802,12 @@ namespace Barotrauma.Items.Components
|
|||||||
if (passivePingRadius > 0.0f)
|
if (passivePingRadius > 0.0f)
|
||||||
{
|
{
|
||||||
if (activePingsCount == 0) { disruptedDirections.Clear(); }
|
if (activePingsCount == 0) { disruptedDirections.Clear(); }
|
||||||
|
//emit "pings" from nearby sound-emitting AITargets to reveal what's around them
|
||||||
foreach (AITarget t in AITarget.List)
|
foreach (AITarget t in AITarget.List)
|
||||||
{
|
{
|
||||||
if (t.Entity is Character c && !c.IsUnconscious && c.Params.HideInSonar) { continue; }
|
if (t.Entity is Character c && !c.IsUnconscious && c.Params.HideInSonar) { continue; }
|
||||||
if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
|
if (t.SoundRange <= 0.0f || float.IsNaN(t.SoundRange) || float.IsInfinity(t.SoundRange)) { continue; }
|
||||||
|
|
||||||
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
|
float distSqr = Vector2.DistanceSquared(t.WorldPosition, transducerCenter);
|
||||||
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
|
if (distSqr > t.SoundRange * t.SoundRange * 2) { continue; }
|
||||||
|
|
||||||
@@ -814,9 +815,12 @@ namespace Barotrauma.Items.Components
|
|||||||
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500)
|
if (dist > prevPassivePingRadius * Range && dist <= passivePingRadius * Range && Rand.Int(sonarBlips.Count) < 500)
|
||||||
{
|
{
|
||||||
Ping(t.WorldPosition, transducerCenter,
|
Ping(t.WorldPosition, transducerCenter,
|
||||||
Math.Min(t.SoundRange, range * 0.5f) * displayScale, 0, displayScale, Math.Min(t.SoundRange, range * 0.5f),
|
t.SoundRange * displayScale, 0, displayScale, range,
|
||||||
passive: true, pingStrength: 0.5f);
|
passive: true, pingStrength: 0.5f, needsToBeInSector: t);
|
||||||
sonarBlips.Add(new SonarBlip(t.WorldPosition, 1.0f, 1.0f));
|
if (t.IsWithinSector(transducerCenter))
|
||||||
|
{
|
||||||
|
sonarBlips.Add(new SonarBlip(t.WorldPosition, fadeTimer: 1.0f, scale: MathHelper.Clamp(t.SoundRange / 2000, 1.0f, 5.0f)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1276,7 +1280,7 @@ namespace Barotrauma.Items.Components
|
|||||||
float indicatorSector = sector * 0.75f;
|
float indicatorSector = sector * 0.75f;
|
||||||
float indicatorSectorLength = (float)(midLength / Math.Cos(indicatorSector));
|
float indicatorSectorLength = (float)(midLength / Math.Cos(indicatorSector));
|
||||||
|
|
||||||
bool withinSector =
|
bool withinSector =
|
||||||
(Math.Abs(diff.X) < steering.ActiveDockingSource.DistanceTolerance.X && Math.Abs(diff.Y) < steering.ActiveDockingSource.DistanceTolerance.Y) ||
|
(Math.Abs(diff.X) < steering.ActiveDockingSource.DistanceTolerance.X && Math.Abs(diff.Y) < steering.ActiveDockingSource.DistanceTolerance.Y) ||
|
||||||
Vector2.Dot(normalizedDockingDir, MathUtils.RotatePoint(normalizedDockingDir, indicatorSector)) <
|
Vector2.Dot(normalizedDockingDir, MathUtils.RotatePoint(normalizedDockingDir, indicatorSector)) <
|
||||||
Vector2.Dot(normalizedDockingDir, Vector2.Normalize(dockingDir));
|
Vector2.Dot(normalizedDockingDir, Vector2.Normalize(dockingDir));
|
||||||
@@ -1345,7 +1349,7 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void Ping(Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float displayScale, float range, bool passive,
|
private void Ping(Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius, float displayScale, float range, bool passive,
|
||||||
float pingStrength = 1.0f)
|
float pingStrength = 1.0f, AITarget needsToBeInSector = null)
|
||||||
{
|
{
|
||||||
float prevPingRadiusSqr = prevPingRadius * prevPingRadius;
|
float prevPingRadiusSqr = prevPingRadius * prevPingRadius;
|
||||||
float pingRadiusSqr = pingRadius * pingRadius;
|
float pingRadiusSqr = pingRadius * pingRadius;
|
||||||
@@ -1357,25 +1361,25 @@ namespace Barotrauma.Items.Components
|
|||||||
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y),
|
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y),
|
||||||
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y),
|
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y),
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
|
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
|
||||||
|
|
||||||
CreateBlipsForLine(
|
CreateBlipsForLine(
|
||||||
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
||||||
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
|
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
|
||||||
|
|
||||||
CreateBlipsForLine(
|
CreateBlipsForLine(
|
||||||
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y),
|
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y),
|
||||||
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
new Vector2(item.CurrentHull.WorldRect.X, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
|
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
|
||||||
|
|
||||||
CreateBlipsForLine(
|
CreateBlipsForLine(
|
||||||
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y),
|
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y),
|
||||||
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
new Vector2(item.CurrentHull.WorldRect.Right, item.CurrentHull.WorldRect.Y - item.CurrentHull.Rect.Height),
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive);
|
pingRadius, prevPingRadius, 50.0f, 5.0f, range, 2.0f, passive, needsToBeInSector: needsToBeInSector);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1404,7 +1408,8 @@ namespace Barotrauma.Items.Components
|
|||||||
end + submarine.WorldPosition,
|
end + submarine.WorldPosition,
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius,
|
pingRadius, prevPingRadius,
|
||||||
200.0f, 2.0f, range, 1.0f, passive);
|
200.0f, 2.0f, range, 1.0f, passive,
|
||||||
|
needsToBeInSector: needsToBeInSector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1417,7 +1422,8 @@ namespace Barotrauma.Items.Components
|
|||||||
new Vector2(pingSource.X + range, Level.Loaded.Size.Y),
|
new Vector2(pingSource.X + range, Level.Loaded.Size.Y),
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius,
|
pingRadius, prevPingRadius,
|
||||||
250.0f, 150.0f, range, pingStrength, passive);
|
250.0f, 150.0f, range, pingStrength, passive,
|
||||||
|
needsToBeInSector: needsToBeInSector);
|
||||||
}
|
}
|
||||||
if (pingSource.Y - Level.Loaded.BottomPos < range)
|
if (pingSource.Y - Level.Loaded.BottomPos < range)
|
||||||
{
|
{
|
||||||
@@ -1426,7 +1432,8 @@ namespace Barotrauma.Items.Components
|
|||||||
new Vector2(pingSource.X + range, Level.Loaded.BottomPos),
|
new Vector2(pingSource.X + range, Level.Loaded.BottomPos),
|
||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius,
|
pingRadius, prevPingRadius,
|
||||||
250.0f, 150.0f, range, pingStrength, passive);
|
250.0f, 150.0f, range, pingStrength, passive,
|
||||||
|
needsToBeInSector: needsToBeInSector);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
|
List<Voronoi2.VoronoiCell> cells = Level.Loaded.GetCells(pingSource, 7);
|
||||||
@@ -1448,7 +1455,8 @@ namespace Barotrauma.Items.Components
|
|||||||
pingSource, transducerPos,
|
pingSource, transducerPos,
|
||||||
pingRadius, prevPingRadius,
|
pingRadius, prevPingRadius,
|
||||||
350.0f, 3.0f * (Math.Abs(facingDot) + 1.0f), range, pingStrength, passive,
|
350.0f, 3.0f * (Math.Abs(facingDot) + 1.0f), range, pingStrength, passive,
|
||||||
blipType : cell.IsDestructible ? BlipType.Destructible : BlipType.Default);
|
blipType : cell.IsDestructible ? BlipType.Destructible : BlipType.Default,
|
||||||
|
needsToBeInSector: needsToBeInSector);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1458,14 +1466,13 @@ namespace Barotrauma.Items.Components
|
|||||||
if (item.CurrentHull == null && item.Prefab.SonarSize > 0.0f)
|
if (item.CurrentHull == null && item.Prefab.SonarSize > 0.0f)
|
||||||
{
|
{
|
||||||
float pointDist = ((item.WorldPosition - pingSource) * displayScale).LengthSquared();
|
float pointDist = ((item.WorldPosition - pingSource) * displayScale).LengthSquared();
|
||||||
|
|
||||||
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
|
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
|
||||||
{
|
{
|
||||||
var blip = new SonarBlip(
|
var blip = new SonarBlip(
|
||||||
item.WorldPosition + Rand.Vector(item.Prefab.SonarSize),
|
item.WorldPosition + Rand.Vector(item.Prefab.SonarSize),
|
||||||
MathHelper.Clamp(item.Prefab.SonarSize, 0.1f, pingStrength),
|
MathHelper.Clamp(item.Prefab.SonarSize, 0.1f, pingStrength),
|
||||||
MathHelper.Clamp(item.Prefab.SonarSize * 0.1f, 0.1f, 10.0f));
|
MathHelper.Clamp(item.Prefab.SonarSize * 0.1f, 0.1f, 10.0f));
|
||||||
if (!passive && !CheckBlipVisibility(blip, transducerPos)) continue;
|
if (!IsVisible(blip)) { continue; }
|
||||||
sonarBlips.Add(blip);
|
sonarBlips.Add(blip);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1488,7 +1495,7 @@ namespace Barotrauma.Items.Components
|
|||||||
c.WorldPosition,
|
c.WorldPosition,
|
||||||
MathHelper.Clamp(c.Mass, 0.1f, pingStrength),
|
MathHelper.Clamp(c.Mass, 0.1f, pingStrength),
|
||||||
MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f));
|
MathHelper.Clamp(c.Mass * 0.03f, 0.1f, 2.0f));
|
||||||
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
|
if (!IsVisible(blip)) { continue; }
|
||||||
sonarBlips.Add(blip);
|
sonarBlips.Add(blip);
|
||||||
HintManager.OnSonarSpottedCharacter(Item, c);
|
HintManager.OnSonarSpottedCharacter(Item, c);
|
||||||
}
|
}
|
||||||
@@ -1505,19 +1512,29 @@ namespace Barotrauma.Items.Components
|
|||||||
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
|
if (pointDist > prevPingRadiusSqr && pointDist < pingRadiusSqr)
|
||||||
{
|
{
|
||||||
var blip = new SonarBlip(
|
var blip = new SonarBlip(
|
||||||
limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f),
|
limb.WorldPosition + Rand.Vector(limb.Mass / 10.0f),
|
||||||
MathHelper.Clamp(limb.Mass, 0.1f, pingStrength),
|
MathHelper.Clamp(limb.Mass, 0.1f, pingStrength),
|
||||||
MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f));
|
MathHelper.Clamp(limb.Mass * 0.1f, 0.1f, 2.0f));
|
||||||
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
|
if (!IsVisible(blip)) { continue; }
|
||||||
sonarBlips.Add(blip);
|
sonarBlips.Add(blip);
|
||||||
HintManager.OnSonarSpottedCharacter(Item, c);
|
HintManager.OnSonarSpottedCharacter(Item, c);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool IsVisible(SonarBlip blip)
|
||||||
|
{
|
||||||
|
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { return false; }
|
||||||
|
if (needsToBeInSector != null)
|
||||||
|
{
|
||||||
|
if (!needsToBeInSector.IsWithinSector(blip.Position)) { return false; }
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateBlipsForLine(Vector2 point1, Vector2 point2, Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius,
|
private void CreateBlipsForLine(Vector2 point1, Vector2 point2, Vector2 pingSource, Vector2 transducerPos, float pingRadius, float prevPingRadius,
|
||||||
float lineStep, float zStep, float range, float pingStrength, bool passive, BlipType blipType = BlipType.Default)
|
float lineStep, float zStep, float range, float pingStrength, bool passive, BlipType blipType = BlipType.Default, AITarget needsToBeInSector = null)
|
||||||
{
|
{
|
||||||
lineStep /= zoom;
|
lineStep /= zoom;
|
||||||
zStep /= zoom;
|
zStep /= zoom;
|
||||||
@@ -1563,12 +1580,17 @@ namespace Barotrauma.Items.Components
|
|||||||
Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / displayScale;
|
Vector2 pos = point + Rand.Vector(150.0f / zoom) + pingDirection * z / displayScale;
|
||||||
float fadeTimer = alpha * (1.0f - displayPointDist / range);
|
float fadeTimer = alpha * (1.0f - displayPointDist / range);
|
||||||
|
|
||||||
int minDist = (int)(200 / zoom);
|
if (needsToBeInSector != null)
|
||||||
sonarBlips.RemoveAll(b => b.FadeTimer < fadeTimer && Math.Abs(pos.X - b.Position.X) < minDist && Math.Abs(pos.Y - b.Position.Y) < minDist);
|
{
|
||||||
|
if (!needsToBeInSector.IsWithinSector(pos)) { continue; }
|
||||||
|
}
|
||||||
|
|
||||||
var blip = new SonarBlip(pos, fadeTimer, 1.0f + ((displayPointDist + z) / DisplayRadius), blipType);
|
var blip = new SonarBlip(pos, fadeTimer, 1.0f + ((displayPointDist + z) / DisplayRadius), blipType);
|
||||||
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
|
if (!passive && !CheckBlipVisibility(blip, transducerPos)) { continue; }
|
||||||
|
|
||||||
|
int minDist = (int)(200 / zoom);
|
||||||
|
sonarBlips.RemoveAll(b => b.FadeTimer < fadeTimer && Math.Abs(pos.X - b.Position.X) < minDist && Math.Abs(pos.Y - b.Position.Y) < minDist);
|
||||||
|
|
||||||
sonarBlips.Add(blip);
|
sonarBlips.Add(blip);
|
||||||
zStep += 0.5f / zoom;
|
zStep += 0.5f / zoom;
|
||||||
|
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
private bool? swapDestinationOrder;
|
private bool? swapDestinationOrder;
|
||||||
|
|
||||||
private GUIMessageBox enterOutpostPrompt;
|
private GUIMessageBox enterOutpostPrompt, exitOutpostPrompt;
|
||||||
|
|
||||||
private bool levelStartSelected;
|
private bool levelStartSelected;
|
||||||
public bool LevelStartSelected
|
public bool LevelStartSelected
|
||||||
@@ -382,15 +382,26 @@ namespace Barotrauma.Items.Components
|
|||||||
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
|
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
|
||||||
{
|
{
|
||||||
// Undocking from an outpost
|
// Undocking from an outpost
|
||||||
campaign.ShowCampaignUI = true;
|
if (!ObjectiveManager.AllActiveObjectivesCompleted())
|
||||||
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
|
{
|
||||||
return false;
|
exitOutpostPrompt = new GUIMessageBox("",
|
||||||
|
TextManager.GetWithVariable("CampaignExitTutorialOutpostPrompt", "[locationname]", campaign.Map.CurrentLocation.Name),
|
||||||
|
new LocalizedString[] { TextManager.Get("yes"), TextManager.Get("no") });
|
||||||
|
exitOutpostPrompt.Buttons[0].OnClicked += (_, _) =>
|
||||||
|
{
|
||||||
|
exitOutpostPrompt.Close();
|
||||||
|
return OpenMap(campaign);
|
||||||
|
};
|
||||||
|
exitOutpostPrompt.Buttons[1].OnClicked += exitOutpostPrompt.Close;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return OpenMap(campaign);
|
||||||
}
|
}
|
||||||
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
|
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
|
||||||
!ActiveDockingSource.Docked && DockingTarget?.Item?.Submarine == Level.Loaded.StartOutpost && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false))
|
!ActiveDockingSource.Docked && DockingTarget?.Item?.Submarine == Level.Loaded.StartOutpost && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false))
|
||||||
{
|
{
|
||||||
// Docking to an outpost
|
// Docking to an outpost
|
||||||
var subsToLeaveBehind = campaign.GetSubsToLeaveBehind(Item.Submarine);
|
var subsToLeaveBehind = CampaignMode.GetSubsToLeaveBehind(Item.Submarine);
|
||||||
if (subsToLeaveBehind.Any())
|
if (subsToLeaveBehind.Any())
|
||||||
{
|
{
|
||||||
enterOutpostPrompt = new GUIMessageBox(
|
enterOutpostPrompt = new GUIMessageBox(
|
||||||
@@ -419,6 +430,14 @@ namespace Barotrauma.Items.Components
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool OpenMap(CampaignMode campaign)
|
||||||
|
{
|
||||||
|
campaign.ShowCampaignUI = true;
|
||||||
|
campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
void SendDockingSignal()
|
void SendDockingSignal()
|
||||||
{
|
{
|
||||||
if (GameMain.Client == null)
|
if (GameMain.Client == null)
|
||||||
@@ -431,6 +450,7 @@ namespace Barotrauma.Items.Components
|
|||||||
item.CreateClientEvent(this);
|
item.CreateClientEvent(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dockingButton.Font = GUIStyle.SubHeadingFont;
|
dockingButton.Font = GUIStyle.SubHeadingFont;
|
||||||
dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue);
|
dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue);
|
||||||
dockingButton.TextBlock.AutoScaleHorizontal = true;
|
dockingButton.TextBlock.AutoScaleHorizontal = true;
|
||||||
@@ -913,6 +933,7 @@ namespace Barotrauma.Items.Components
|
|||||||
maintainPosOriginIndicator?.Remove();
|
maintainPosOriginIndicator?.Remove();
|
||||||
steeringIndicator?.Remove();
|
steeringIndicator?.Remove();
|
||||||
enterOutpostPrompt?.Close();
|
enterOutpostPrompt?.Close();
|
||||||
|
exitOutpostPrompt?.Close();
|
||||||
pathFinder = null;
|
pathFinder = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -97,7 +97,7 @@ namespace Barotrauma.Items.Components
|
|||||||
var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), chargeTextContainer.RectTransform, Anchor.CenterRight),
|
var chargeText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1), chargeTextContainer.RectTransform, Anchor.CenterRight),
|
||||||
"", textColor: GUIStyle.TextColorNormal, font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
|
"", textColor: GUIStyle.TextColorNormal, font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
|
||||||
{
|
{
|
||||||
TextGetter = () => $"{(int)MathF.Round(charge)}/{(int)capacity} {kWmin} ({(int)MathF.Round(MathUtils.Percentage(charge, capacity))} %)"
|
TextGetter = () => $"{(int)MathF.Round(charge)}/{(int)adjustedCapacity} {kWmin} ({(int)MathF.Round(MathUtils.Percentage(charge, adjustedCapacity))} %)"
|
||||||
};
|
};
|
||||||
if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUIStyle.SmallFont; }
|
if (chargeText.TextSize.X > chargeText.Rect.Width) { chargeText.Font = GUIStyle.SmallFont; }
|
||||||
|
|
||||||
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
ProgressGetter = () =>
|
ProgressGetter = () =>
|
||||||
{
|
{
|
||||||
return capacity <= 0.0f ? 1.0f : charge / capacity;
|
return adjustedCapacity <= 0.0f ? 1.0f : charge / adjustedCapacity;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -126,42 +126,50 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (chargeIndicator != null)
|
if (chargeIndicator != null)
|
||||||
{
|
{
|
||||||
float chargeRatio = charge / capacity;
|
float chargeRatio = charge / adjustedCapacity;
|
||||||
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
|
chargeIndicator.Color = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
|
||||||
{
|
{
|
||||||
if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; }
|
Vector2 scaledIndicatorSize = indicatorSize * item.Scale;
|
||||||
|
if (scaledIndicatorSize.X <= 2.0f || scaledIndicatorSize.Y <= 2.0f) { return; }
|
||||||
|
|
||||||
|
const float outlineThickness = 1.0f;
|
||||||
Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
|
Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
|
||||||
Vector2 indicatorPos = -itemSize / 2 + indicatorPosition * item.Scale;
|
Vector2 indicatorPos = -itemSize / 2.0f + indicatorPosition * item.Scale;
|
||||||
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; }
|
Vector2 itemPosition = new Vector2(item.DrawPosition.X, -item.DrawPosition.Y);
|
||||||
if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; }
|
Vector2 flip = new Vector2(item.FlippedX && item.Prefab.CanSpriteFlipX ? -1.0f : 1.0f, item.FlippedY && item.Prefab.CanSpriteFlipY ? -1.0f : 1.0f);
|
||||||
|
Matrix rotate = Matrix.CreateRotationZ(item.RotationRad);
|
||||||
|
Vector2 center = Vector2.Transform((indicatorPos + (scaledIndicatorSize * 0.5f)) * flip, rotate) + itemPosition;
|
||||||
|
|
||||||
if (charge > 0 && capacity > 0)
|
if (charge > 0 && adjustedCapacity > 0)
|
||||||
{
|
{
|
||||||
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
|
float chargeRatio = MathHelper.Clamp(charge / adjustedCapacity, 0.0f, 1.0f);
|
||||||
Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
|
Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
|
||||||
if (!isHorizontal)
|
Vector2 indicatorCenter = (indicatorPos + (scaledIndicatorSize * 0.5f)) * flip;
|
||||||
|
Vector2 indicatorSize;
|
||||||
|
|
||||||
|
if (isHorizontal)
|
||||||
{
|
{
|
||||||
GUI.DrawRectangle(spriteBatch,
|
float indicatorLength = (scaledIndicatorSize.X - outlineThickness * 2.0f) * chargeRatio;
|
||||||
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - chargeRatio))) + indicatorPos,
|
indicatorCenter.X += -scaledIndicatorSize.X * 0.5f + (flipIndicator ? scaledIndicatorSize.X - outlineThickness - indicatorLength * 0.5f : outlineThickness + indicatorLength * 0.5f);
|
||||||
new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * chargeRatio), indicatorColor, true,
|
indicatorSize = new Vector2(indicatorLength, scaledIndicatorSize.Y);
|
||||||
depth: item.SpriteDepth - 0.00001f);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
GUI.DrawRectangle(spriteBatch,
|
float indicatorLength = (scaledIndicatorSize.Y - outlineThickness * 2.0f) * chargeRatio;
|
||||||
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
|
indicatorCenter.Y += -scaledIndicatorSize.Y * 0.5f + (flipIndicator ? outlineThickness + indicatorLength * 0.5f : scaledIndicatorSize.Y - outlineThickness - indicatorLength * 0.5f);
|
||||||
new Vector2((indicatorSize.X * item.Scale) * chargeRatio, indicatorSize.Y * item.Scale), indicatorColor, true,
|
indicatorSize = new Vector2(scaledIndicatorSize.X, indicatorLength);
|
||||||
depth: item.SpriteDepth - 0.00001f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
indicatorCenter = Vector2.Transform(indicatorCenter, rotate) + itemPosition;
|
||||||
|
|
||||||
|
GUI.DrawFilledRectangle(spriteBatch, indicatorCenter, indicatorSize, indicatorSize * 0.5f, item.RotationRad, indicatorColor, item.SpriteDepth - 0.00001f);
|
||||||
}
|
}
|
||||||
GUI.DrawRectangle(spriteBatch,
|
|
||||||
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
|
GUI.DrawRectangle(spriteBatch, center, scaledIndicatorSize, scaledIndicatorSize * 0.5f, item.RotationRad, Color.Black, item.SpriteDepth - 0.000015f, outlineThickness, GUI.OutlinePosition.Inside);
|
||||||
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.000015f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData)
|
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData)
|
||||||
@@ -185,7 +193,7 @@ namespace Barotrauma.Items.Components
|
|||||||
rechargeSpeedSlider.BarScroll = rechargeRate;
|
rechargeSpeedSlider.BarScroll = rechargeRate;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
Charge = msg.ReadRangedSingle(0.0f, 1.0f, 8) * capacity;
|
Charge = msg.ReadRangedSingle(0.0f, 1.0f, 8) * adjustedCapacity;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,13 @@ namespace Barotrauma.Items.Components
|
|||||||
if (GuiFrame == null) { return; }
|
if (GuiFrame == null) { return; }
|
||||||
originalMaxSize = GuiFrame.RectTransform.MaxSize;
|
originalMaxSize = GuiFrame.RectTransform.MaxSize;
|
||||||
originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
|
originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
|
||||||
|
CreateGUI();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void CreateGUI()
|
||||||
|
{
|
||||||
|
if (GuiFrame == null) { return; }
|
||||||
|
|
||||||
CheckForLabelOverlap();
|
CheckForLabelOverlap();
|
||||||
var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
|
var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
|
||||||
{
|
{
|
||||||
@@ -43,8 +50,8 @@ namespace Barotrauma.Items.Components
|
|||||||
content.RectTransform.SetAsFirstChild();
|
content.RectTransform.SetAsFirstChild();
|
||||||
|
|
||||||
//prevents inputs from going through the GUICustomComponent to the drag handle
|
//prevents inputs from going through the GUICustomComponent to the drag handle
|
||||||
dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
dragArea = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
|
||||||
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
|
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void TriggerRewiringSound()
|
public void TriggerRewiringSound()
|
||||||
@@ -121,12 +128,6 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnResolutionChanged()
|
|
||||||
{
|
|
||||||
if (GuiFrame == null) { return; }
|
|
||||||
CheckForLabelOverlap();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckForLabelOverlap()
|
private void CheckForLabelOverlap()
|
||||||
{
|
{
|
||||||
GuiFrame.RectTransform.MaxSize = originalMaxSize;
|
GuiFrame.RectTransform.MaxSize = originalMaxSize;
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (Character.Controlled != null)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
Character.Controlled.FocusedItem = null;
|
Character.Controlled.FocusedItem = null;
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
}
|
}
|
||||||
//cancel dragging
|
//cancel dragging
|
||||||
@@ -401,7 +401,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (Character.Controlled != null)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
}
|
}
|
||||||
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out _);
|
int closestSectionIndex = selectedWire.GetClosestSectionIndex(mousePos, sectionSelectDist, out _);
|
||||||
@@ -431,7 +431,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (Character.Controlled != null)
|
if (Character.Controlled != null)
|
||||||
{
|
{
|
||||||
Character.Controlled.ResetInteract = true;
|
Character.Controlled.DisableInteract = true;
|
||||||
Character.Controlled.ClearInputs();
|
Character.Controlled.ClearInputs();
|
||||||
}
|
}
|
||||||
draggingWire = selectedWire;
|
draggingWire = selectedWire;
|
||||||
|
|||||||
@@ -293,7 +293,7 @@ namespace Barotrauma.Items.Components
|
|||||||
|
|
||||||
if (target.Bleeding > 0.0f)
|
if (target.Bleeding > 0.0f)
|
||||||
{
|
{
|
||||||
int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 100.0f) * BleedingTexts.Length, 0, BleedingTexts.Length - 1);
|
int bleedingTextIndex = MathHelper.Clamp((int)Math.Floor(target.Bleeding / 100.0f * BleedingTexts.Length), 0, BleedingTexts.Length - 1);
|
||||||
texts.Add(BleedingTexts[bleedingTextIndex]);
|
texts.Add(BleedingTexts[bleedingTextIndex]);
|
||||||
textColors.Add(Color.Lerp(GUIStyle.Orange, GUIStyle.Red, target.Bleeding / 100.0f));
|
textColors.Add(Color.Lerp(GUIStyle.Orange, GUIStyle.Red, target.Bleeding / 100.0f));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,14 +212,14 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (moveSoundChannel == null && startMoveSound != null)
|
if (moveSoundChannel == null && startMoveSound != null)
|
||||||
{
|
{
|
||||||
moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range, ignoreMuffling: startMoveSound.IgnoreMuffling);
|
moveSoundChannel = SoundPlayer.PlaySound(startMoveSound.Sound, item.WorldPosition, startMoveSound.Volume, startMoveSound.Range, ignoreMuffling: startMoveSound.IgnoreMuffling, freqMult: startMoveSound.GetRandomFrequencyMultiplier());
|
||||||
}
|
}
|
||||||
else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying)
|
else if (moveSoundChannel == null || !moveSoundChannel.IsPlaying)
|
||||||
{
|
{
|
||||||
if (moveSound != null)
|
if (moveSound != null)
|
||||||
{
|
{
|
||||||
moveSoundChannel.FadeOutAndDispose();
|
moveSoundChannel.FadeOutAndDispose();
|
||||||
moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling);
|
moveSoundChannel = SoundPlayer.PlaySound(moveSound.Sound, item.WorldPosition, moveSound.Volume, moveSound.Range, ignoreMuffling: moveSound.IgnoreMuffling, freqMult: moveSound.GetRandomFrequencyMultiplier());
|
||||||
if (moveSoundChannel != null) moveSoundChannel.Looping = true;
|
if (moveSoundChannel != null) moveSoundChannel.Looping = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -231,7 +231,7 @@ namespace Barotrauma.Items.Components
|
|||||||
if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound)
|
if (endMoveSound != null && moveSoundChannel.Sound != endMoveSound.Sound)
|
||||||
{
|
{
|
||||||
moveSoundChannel.FadeOutAndDispose();
|
moveSoundChannel.FadeOutAndDispose();
|
||||||
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling);
|
moveSoundChannel = SoundPlayer.PlaySound(endMoveSound.Sound, item.WorldPosition, endMoveSound.Volume, endMoveSound.Range, ignoreMuffling: endMoveSound.IgnoreMuffling, freqMult: endMoveSound.GetRandomFrequencyMultiplier());
|
||||||
if (moveSoundChannel != null) moveSoundChannel.Looping = false;
|
if (moveSoundChannel != null) moveSoundChannel.Looping = false;
|
||||||
}
|
}
|
||||||
else if (!moveSoundChannel.IsPlaying)
|
else if (!moveSoundChannel.IsPlaying)
|
||||||
@@ -260,7 +260,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
if (chargeSound != null)
|
if (chargeSound != null)
|
||||||
{
|
{
|
||||||
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling);
|
chargeSoundChannel = SoundPlayer.PlaySound(chargeSound.Sound, item.WorldPosition, chargeSound.Volume, chargeSound.Range, ignoreMuffling: chargeSound.IgnoreMuffling, freqMult: chargeSound.GetRandomFrequencyMultiplier());
|
||||||
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
|
if (chargeSoundChannel != null) chargeSoundChannel.Looping = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,6 +411,14 @@ namespace Barotrauma.Items.Components
|
|||||||
SpriteEffects.None, newDepth);
|
SpriteEffects.None, newDepth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (GameMain.DebugDraw)
|
||||||
|
{
|
||||||
|
Vector2 firingPos = GetRelativeFiringPosition();
|
||||||
|
firingPos.Y = -firingPos.Y;
|
||||||
|
GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitX * 5, firingPos + Vector2.UnitX * 5, Color.Red);
|
||||||
|
GUI.DrawLine(spriteBatch, firingPos - Vector2.UnitY * 5, firingPos + Vector2.UnitY * 5, Color.Red);
|
||||||
|
}
|
||||||
|
|
||||||
if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
|
if (!editing || GUI.DisableHUD || !item.IsSelected) { return; }
|
||||||
|
|
||||||
const float widgetRadius = 60.0f;
|
const float widgetRadius = 60.0f;
|
||||||
@@ -581,7 +589,7 @@ namespace Barotrauma.Items.Components
|
|||||||
var battery = recipient.Item?.GetComponent<PowerContainer>();
|
var battery = recipient.Item?.GetComponent<PowerContainer>();
|
||||||
if (battery == null || battery.Item.Condition <= 0.0f) { continue; }
|
if (battery == null || battery.Item.Condition <= 0.0f) { continue; }
|
||||||
availableCharge += battery.Charge;
|
availableCharge += battery.Charge;
|
||||||
availableCapacity += battery.Capacity;
|
availableCapacity += battery.GetCapacity();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using Barotrauma.Networking;
|
using Barotrauma.Networking;
|
||||||
|
|
||||||
@@ -6,7 +7,7 @@ namespace Barotrauma.Items.Components
|
|||||||
{
|
{
|
||||||
partial class Wearable : Pickable, IServerSerializable
|
partial class Wearable : Pickable, IServerSerializable
|
||||||
{
|
{
|
||||||
private void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier)
|
private static void GetDamageModifierText(ref LocalizedString description, DamageModifier damageModifier, Identifier afflictionIdentifier)
|
||||||
{
|
{
|
||||||
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100);
|
int roundedValue = (int)Math.Round((1 - damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier) * 100);
|
||||||
if (roundedValue == 0) { return; }
|
if (roundedValue == 0) { return; }
|
||||||
@@ -19,8 +20,13 @@ namespace Barotrauma.Items.Components
|
|||||||
if (!description.IsNullOrWhiteSpace()) { description += '\n'; }
|
if (!description.IsNullOrWhiteSpace()) { description += '\n'; }
|
||||||
description += $" ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}";
|
description += $" ‖color:{colorStr}‖{roundedValue.ToString("-0;+#")}%‖color:end‖ {afflictionName}";
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
|
public override void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description)
|
||||||
|
{
|
||||||
|
AddTooltipInfo(damageModifiers, SkillModifiers, ref description);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void AddTooltipInfo(IReadOnlyList<DamageModifier> damageModifiers, IReadOnlyDictionary<Identifier, float> skillModifiers, ref LocalizedString description)
|
||||||
{
|
{
|
||||||
if (damageModifiers.Any())
|
if (damageModifiers.Any())
|
||||||
{
|
{
|
||||||
@@ -41,9 +47,9 @@ namespace Barotrauma.Items.Components
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (SkillModifiers.Any())
|
if (skillModifiers.Any())
|
||||||
{
|
{
|
||||||
foreach (var skillModifier in SkillModifiers)
|
foreach (var skillModifier in skillModifiers)
|
||||||
{
|
{
|
||||||
string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green);
|
string colorStr = XMLExtensions.ToStringHex(GUIStyle.Green);
|
||||||
int roundedValue = (int)Math.Round(skillModifier.Value);
|
int roundedValue = (int)Math.Round(skillModifier.Value);
|
||||||
|
|||||||
@@ -1608,22 +1608,79 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
containedState = item.Condition / item.MaxCondition;
|
containedState = item.Condition / item.MaxCondition;
|
||||||
}
|
}
|
||||||
else if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
|
|
||||||
{
|
|
||||||
containedState = itemContainer.Inventory.AllItems.Count() / (float)(itemContainer.GetMaxStackSize(0) * itemContainer.Capacity);
|
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var containedItem = itemContainer.Inventory.slots[Math.Max(itemContainer.ContainedStateIndicatorSlot, 0)].FirstOrDefault();
|
int targetSlot = Math.Max(itemContainer.ContainedStateIndicatorSlot, 0);
|
||||||
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
|
ItemSlot containedItemSlot = null;
|
||||||
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
|
if (targetSlot < itemContainer.Inventory.slots.Length)
|
||||||
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
|
|
||||||
if (containedItem != null && itemContainer.Inventory.Capacity == 1)
|
|
||||||
{
|
{
|
||||||
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(0));
|
containedItemSlot = itemContainer.Inventory.slots[targetSlot];
|
||||||
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
|
}
|
||||||
|
if (containedItemSlot != null)
|
||||||
|
{
|
||||||
|
Item containedItem = containedItemSlot.FirstOrDefault();
|
||||||
|
if (itemContainer.ShowTotalStackCapacityInContainedStateIndicator)
|
||||||
{
|
{
|
||||||
containedState = itemContainer.Inventory.slots[0].Items.Count / (float)maxStackSize;
|
if (containedItem == null)
|
||||||
|
{
|
||||||
|
// No item on the defined slot, check if the items on other slots can be used.
|
||||||
|
containedItem = containedItemSlot.FirstOrDefault() ?? itemContainer.Inventory.AllItems.FirstOrDefault(it => itemContainer.CanBeContained(it, targetSlot));
|
||||||
|
}
|
||||||
|
if (containedItem != null)
|
||||||
|
{
|
||||||
|
int ignoredItemCount = 0;
|
||||||
|
var subContainableItems = itemContainer.AllSubContainableItems;
|
||||||
|
float capacity = itemContainer.GetMaxStackSize(targetSlot);
|
||||||
|
if (subContainableItems != null)
|
||||||
|
{
|
||||||
|
bool useMainContainerCapacity = true;
|
||||||
|
foreach (Item it in itemContainer.Inventory.AllItems)
|
||||||
|
{
|
||||||
|
// Ignore all items in the sub containers.
|
||||||
|
foreach (RelatedItem ri in subContainableItems)
|
||||||
|
{
|
||||||
|
if (ri.MatchesItem(containedItem))
|
||||||
|
{
|
||||||
|
// The target item is in a subcontainer -> inverse the logic.
|
||||||
|
useMainContainerCapacity = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (ri.MatchesItem(it))
|
||||||
|
{
|
||||||
|
ignoredItemCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!useMainContainerCapacity) { break; }
|
||||||
|
}
|
||||||
|
if (useMainContainerCapacity)
|
||||||
|
{
|
||||||
|
capacity *= itemContainer.MainContainerCapacity;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ignore all items in the main container.
|
||||||
|
ignoredItemCount = itemContainer.Inventory.AllItems.Count(it => subContainableItems.Any(ri => !ri.MatchesItem(it)));
|
||||||
|
capacity *= itemContainer.Capacity - itemContainer.MainContainerCapacity;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int itemCount = itemContainer.Inventory.AllItems.Count() - ignoredItemCount;
|
||||||
|
containedState = Math.Min(itemCount / Math.Max(capacity, 1), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
containedState = itemContainer.Inventory.Capacity == 1 || itemContainer.ContainedStateIndicatorSlot > -1 ?
|
||||||
|
(containedItem == null ? 0.0f : containedItem.Condition / containedItem.MaxCondition) :
|
||||||
|
itemContainer.Inventory.slots.Count(i => !i.Empty()) / (float)itemContainer.Inventory.capacity;
|
||||||
|
|
||||||
|
if (containedItem != null && (itemContainer.Inventory.Capacity == 1 || itemContainer.HasSubContainers))
|
||||||
|
{
|
||||||
|
int maxStackSize = Math.Min(containedItem.Prefab.MaxStackSize, itemContainer.GetMaxStackSize(targetSlot));
|
||||||
|
if (maxStackSize > 1 || containedItem.Prefab.HideConditionBar)
|
||||||
|
{
|
||||||
|
containedState = containedItemSlot.Items.Count / (float)maxStackSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,6 +242,11 @@ namespace Barotrauma
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (parentInventory?.Owner is Character character && character.InvisibleTimer > 0.0f)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle extents;
|
Rectangle extents;
|
||||||
if (cachedVisibleExtents.HasValue)
|
if (cachedVisibleExtents.HasValue)
|
||||||
{
|
{
|
||||||
@@ -1251,11 +1256,9 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (ItemComponent ic in components)
|
foreach (ItemComponent ic in components)
|
||||||
{
|
{
|
||||||
if (ic.DisplayMsg.IsNullOrEmpty()) { continue; }
|
|
||||||
if (!ic.CanBePicked && !ic.CanBeSelected) { continue; }
|
if (!ic.CanBePicked && !ic.CanBeSelected) { continue; }
|
||||||
if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; }
|
if (ic is Holdable holdable && !holdable.CanBeDeattached()) { continue; }
|
||||||
if (ic is ConnectionPanel connectionPanel && !connectionPanel.CanRewire()) { continue; }
|
if (ic is ConnectionPanel connectionPanel && !connectionPanel.CanRewire()) { continue; }
|
||||||
|
|
||||||
Color color = Color.Gray;
|
Color color = Color.Gray;
|
||||||
if (ic.HasRequiredItems(character, false))
|
if (ic.HasRequiredItems(character, false))
|
||||||
{
|
{
|
||||||
@@ -1268,6 +1271,7 @@ namespace Barotrauma
|
|||||||
color = Color.Cyan;
|
color = Color.Cyan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (ic.DisplayMsg.IsNullOrEmpty()) { continue; }
|
||||||
texts.Add(new ColoredText(ic.DisplayMsg.Value, color, false, false));
|
texts.Add(new ColoredText(ic.DisplayMsg.Value, color, false, false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1282,7 +1286,8 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
foreach (ItemComponent ic in activeHUDs)
|
foreach (ItemComponent ic in activeHUDs)
|
||||||
{
|
{
|
||||||
if (ic.GuiFrame == null || !ic.CanBeSelected) { continue; }
|
if (ic.GuiFrame == null) { continue; }
|
||||||
|
if (!ic.CanBeSelected && !ic.DrawHudWhenEquipped) { continue; }
|
||||||
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
|
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
|
||||||
if (ic.UseAlternativeLayout)
|
if (ic.UseAlternativeLayout)
|
||||||
{
|
{
|
||||||
@@ -1415,6 +1420,15 @@ namespace Barotrauma
|
|||||||
case EventType.ChangeProperty:
|
case EventType.ChangeProperty:
|
||||||
ReadPropertyChange(msg, false);
|
ReadPropertyChange(msg, false);
|
||||||
break;
|
break;
|
||||||
|
case EventType.ItemStat:
|
||||||
|
byte length = msg.ReadByte();
|
||||||
|
for (int i = 0; i < length; i++)
|
||||||
|
{
|
||||||
|
var statIdentifier = INetSerializableStruct.Read<ItemStatManager.TalentStatIdentifier>(msg);
|
||||||
|
var statValue = msg.ReadSingle();
|
||||||
|
StatManager.ApplyStat(statIdentifier, statValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
case EventType.Upgrade:
|
case EventType.Upgrade:
|
||||||
Identifier identifier = msg.ReadIdentifier();
|
Identifier identifier = msg.ReadIdentifier();
|
||||||
byte level = msg.ReadByte();
|
byte level = msg.ReadByte();
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Barotrauma.IO;
|
using Barotrauma.Extensions;
|
||||||
using Barotrauma.Extensions;
|
using Barotrauma.Items.Components;
|
||||||
using FarseerPhysics;
|
using FarseerPhysics;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
@@ -7,7 +7,6 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Xml.Linq;
|
|
||||||
|
|
||||||
namespace Barotrauma
|
namespace Barotrauma
|
||||||
{
|
{
|
||||||
@@ -73,6 +72,9 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public float UpgradePreviewScale = 1.0f;
|
public float UpgradePreviewScale = 1.0f;
|
||||||
|
|
||||||
|
private IReadOnlyList<DamageModifier> wearableDamageModifiers;
|
||||||
|
private IReadOnlyDictionary<Identifier, float> wearableSkillModifiers;
|
||||||
|
|
||||||
//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", IsPropertySaveable.No)]
|
[Serialize("1.0,1.0,1.0,1.0", IsPropertySaveable.No)]
|
||||||
public Color InventoryIconColor { get; protected set; }
|
public Color InventoryIconColor { get; protected set; }
|
||||||
@@ -101,6 +103,9 @@ namespace Barotrauma
|
|||||||
var containedSprites = new List<ContainedItemSprite>();
|
var containedSprites = new List<ContainedItemSprite>();
|
||||||
var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
|
var decorativeSpriteGroups = new Dictionary<int, List<DecorativeSprite>>();
|
||||||
|
|
||||||
|
var wearableDamageModifiers = new List<DamageModifier>();
|
||||||
|
var wearableSkillModifiers = new Dictionary<Identifier, float>();
|
||||||
|
|
||||||
foreach (var subElement in element.Elements())
|
foreach (var subElement in element.Elements())
|
||||||
{
|
{
|
||||||
switch (subElement.Name.LocalName.ToLowerInvariant())
|
switch (subElement.Name.LocalName.ToLowerInvariant())
|
||||||
@@ -198,8 +203,33 @@ namespace Barotrauma
|
|||||||
containedSprites.Add(containedSprite);
|
containedSprites.Add(containedSprite);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case "wearable":
|
||||||
|
foreach (ContentXElement wearableSubElement in subElement.Elements())
|
||||||
|
{
|
||||||
|
switch (wearableSubElement.Name.LocalName.ToLowerInvariant())
|
||||||
|
{
|
||||||
|
case "damagemodifier":
|
||||||
|
wearableDamageModifiers.Add(new DamageModifier(wearableSubElement, Name.Value + ", Wearable", checkErrors: false));
|
||||||
|
break;
|
||||||
|
case "skillmodifier":
|
||||||
|
Identifier skillIdentifier = wearableSubElement.GetAttributeIdentifier("skillidentifier", Identifier.Empty);
|
||||||
|
float skillValue = wearableSubElement.GetAttributeFloat("skillvalue", 0f);
|
||||||
|
if (wearableSkillModifiers.ContainsKey(skillIdentifier))
|
||||||
|
{
|
||||||
|
wearableSkillModifiers[skillIdentifier] += skillValue;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
wearableSkillModifiers.TryAdd(skillIdentifier, skillValue);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
this.wearableDamageModifiers = wearableDamageModifiers.ToImmutableList();
|
||||||
|
this.wearableSkillModifiers = wearableSkillModifiers.ToImmutableDictionary();
|
||||||
|
|
||||||
UpgradeOverrideSprites = upgradeOverrideSprites.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
UpgradeOverrideSprites = upgradeOverrideSprites.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
||||||
BrokenSprites = brokenSprites.ToImmutableArray();
|
BrokenSprites = brokenSprites.ToImmutableArray();
|
||||||
@@ -208,6 +238,26 @@ namespace Barotrauma
|
|||||||
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool CanCharacterBuy()
|
||||||
|
{
|
||||||
|
if (DefaultPrice == null) { return false; }
|
||||||
|
if (!DefaultPrice.RequiresUnlock) { return true; }
|
||||||
|
return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
|
||||||
|
}
|
||||||
|
public LocalizedString GetTooltip()
|
||||||
|
{
|
||||||
|
LocalizedString tooltip = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.TextColorBright)}‖{Name}‖color:end‖";
|
||||||
|
if (!Description.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
tooltip += $"\n{Description}";
|
||||||
|
}
|
||||||
|
if (wearableDamageModifiers.Any() || wearableSkillModifiers.Any())
|
||||||
|
{
|
||||||
|
Wearable.AddTooltipInfo(wearableDamageModifiers, wearableSkillModifiers, ref tooltip);
|
||||||
|
}
|
||||||
|
return tooltip;
|
||||||
|
}
|
||||||
|
|
||||||
public override void UpdatePlacing(Camera cam)
|
public override void UpdatePlacing(Camera cam)
|
||||||
{
|
{
|
||||||
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
|
||||||
@@ -313,15 +363,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Vector2 position = Submarine.MouseToWorldGrid(Screen.Selected.Cam, Submarine.MainSub);
|
Sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), SpriteColor * 0.8f);
|
||||||
Vector2 placeSize = Size * Scale;
|
|
||||||
if (placePosition != Vector2.Zero)
|
|
||||||
{
|
|
||||||
if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); }
|
|
||||||
if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); }
|
|
||||||
position = placePosition;
|
|
||||||
}
|
|
||||||
Sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-3
@@ -168,11 +168,11 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
else if (position.X < 0.0f)
|
else if (position.X < 0.0f)
|
||||||
{
|
{
|
||||||
obstacleDiff = Vector2.UnitX;
|
obstacleDiff = -Vector2.UnitX;
|
||||||
}
|
}
|
||||||
else if (position.X > Level.Loaded.Size.X)
|
else if (position.X > Level.Loaded.Size.X)
|
||||||
{
|
{
|
||||||
obstacleDiff = -Vector2.UnitX;
|
obstacleDiff = Vector2.UnitX;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -183,7 +183,7 @@ namespace Barotrauma
|
|||||||
foreach (Voronoi2.VoronoiCell cell in cells)
|
foreach (Voronoi2.VoronoiCell cell in cells)
|
||||||
{
|
{
|
||||||
Vector2 diff = cell.Center - position;
|
Vector2 diff = cell.Center - position;
|
||||||
if (diff.LengthSquared() > 5000.0f * 5000.0f) continue;
|
if (diff.LengthSquared() > 5000.0f * 5000.0f) { continue; }
|
||||||
obstacleDiff += diff;
|
obstacleDiff += diff;
|
||||||
cellCount++;
|
cellCount++;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (SoundChannels[i] == null || !SoundChannels[i].IsPlaying)
|
if (SoundChannels[i] == null || !SoundChannels[i].IsPlaying)
|
||||||
{
|
{
|
||||||
SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, soundPos);
|
SoundChannels[i] = roundSound.Sound.Play(roundSound.Volume, roundSound.Range, roundSound.GetRandomFrequencyMultiplier(), soundPos);
|
||||||
}
|
}
|
||||||
SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
|
SoundChannels[i].Position = new Vector3(soundPos.X, soundPos.Y, 0.0f);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -1,4 +1,5 @@
|
|||||||
using Barotrauma.Networking;
|
using Barotrauma.Extensions;
|
||||||
|
using Barotrauma.Networking;
|
||||||
using Microsoft.Xna.Framework;
|
using Microsoft.Xna.Framework;
|
||||||
using Microsoft.Xna.Framework.Graphics;
|
using Microsoft.Xna.Framework.Graphics;
|
||||||
using System;
|
using System;
|
||||||
@@ -178,7 +179,7 @@ namespace Barotrauma
|
|||||||
activeSprite?.Draw(
|
activeSprite?.Draw(
|
||||||
spriteBatch,
|
spriteBatch,
|
||||||
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f,
|
new Vector2(obj.Position.X, -obj.Position.Y) - camDiff * obj.Position.Z / 10000.0f,
|
||||||
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 3000.0f),
|
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 3000.0f),
|
||||||
activeSprite.Origin,
|
activeSprite.Origin,
|
||||||
obj.CurrentRotation,
|
obj.CurrentRotation,
|
||||||
obj.CurrentScale,
|
obj.CurrentScale,
|
||||||
@@ -200,7 +201,7 @@ namespace Barotrauma
|
|||||||
obj.ActivePrefab.DeformableSprite.Origin,
|
obj.ActivePrefab.DeformableSprite.Origin,
|
||||||
obj.CurrentRotation,
|
obj.CurrentRotation,
|
||||||
obj.CurrentScale,
|
obj.CurrentScale,
|
||||||
Color.Lerp(Color.White, Level.Loaded.BackgroundTextureColor, obj.Position.Z / 5000.0f));
|
Color.Lerp(obj.Prefab.SpriteColor, obj.Prefab.SpriteColor.Multiply(Level.Loaded.BackgroundTextureColor), obj.Position.Z / 5000.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -343,38 +343,31 @@ namespace Barotrauma.Lights
|
|||||||
{
|
{
|
||||||
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
|
SolidColorEffect.CurrentTechnique = SolidColorEffect.Techniques["SolidVertexColor"];
|
||||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
|
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
|
||||||
foreach (Character character in Character.CharacterList)
|
DrawCharacters(spriteBatch, cam, drawDeformSprites: false);
|
||||||
{
|
|
||||||
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
|
|
||||||
if (Character.Controlled?.FocusedCharacter == character) { continue; }
|
|
||||||
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
|
|
||||||
Color.Black :
|
|
||||||
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
|
|
||||||
foreach (Limb limb in character.AnimController.Limbs)
|
|
||||||
{
|
|
||||||
if (limb.DeformSprite != null) { continue; }
|
|
||||||
limb.Draw(spriteBatch, cam, lightColor);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spriteBatch.End();
|
spriteBatch.End();
|
||||||
|
|
||||||
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"];
|
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShaderSolidVertexColor"];
|
||||||
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
|
DeformableSprite.Effect.CurrentTechnique.Passes[0].Apply();
|
||||||
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
|
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
|
||||||
|
DrawCharacters(spriteBatch, cam, drawDeformSprites: true);
|
||||||
|
spriteBatch.End();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void DrawCharacters(SpriteBatch spriteBatch, Camera cam, bool drawDeformSprites)
|
||||||
|
{
|
||||||
foreach (Character character in Character.CharacterList)
|
foreach (Character character in Character.CharacterList)
|
||||||
{
|
{
|
||||||
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
|
if (character.CurrentHull == null || !character.Enabled || !character.IsVisible || character.InvisibleTimer > 0.0f) { continue; }
|
||||||
if (Character.Controlled?.FocusedCharacter == character) { continue; }
|
if (Character.Controlled?.FocusedCharacter == character) { continue; }
|
||||||
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
|
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
|
||||||
Color.Black :
|
Color.Black :
|
||||||
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
|
character.CurrentHull.AmbientLight.Multiply(character.CurrentHull.AmbientLight.A / 255.0f).Opaque();
|
||||||
foreach (Limb limb in character.AnimController.Limbs)
|
foreach (Limb limb in character.AnimController.Limbs)
|
||||||
{
|
{
|
||||||
if (limb.DeformSprite == null) { continue; }
|
if (drawDeformSprites == (limb.DeformSprite == null)) { continue; }
|
||||||
limb.Draw(spriteBatch, cam, lightColor);
|
limb.Draw(spriteBatch, cam, lightColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
spriteBatch.End();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
|
DeformableSprite.Effect.CurrentTechnique = DeformableSprite.Effect.Techniques["DeformShader"];
|
||||||
|
|||||||
@@ -291,7 +291,7 @@ namespace Barotrauma
|
|||||||
if (!currentDisplayLocation.Discovered)
|
if (!currentDisplayLocation.Discovered)
|
||||||
{
|
{
|
||||||
RemoveFogOfWar(currentDisplayLocation);
|
RemoveFogOfWar(currentDisplayLocation);
|
||||||
currentDisplayLocation.Discover();
|
Discover(currentDisplayLocation);
|
||||||
if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
if (currentDisplayLocation.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||||
{
|
{
|
||||||
furthestDiscoveredLocation = currentDisplayLocation;
|
furthestDiscoveredLocation = currentDisplayLocation;
|
||||||
@@ -452,7 +452,7 @@ namespace Barotrauma
|
|||||||
Level.Loaded.DebugSetStartLocation(CurrentLocation);
|
Level.Loaded.DebugSetStartLocation(CurrentLocation);
|
||||||
Level.Loaded.DebugSetEndLocation(null);
|
Level.Loaded.DebugSetEndLocation(null);
|
||||||
|
|
||||||
CurrentLocation.Discover();
|
Discover(CurrentLocation);
|
||||||
OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation));
|
OnLocationChanged?.Invoke(new LocationChangeInfo(prevLocation, CurrentLocation));
|
||||||
SelectLocation(-1);
|
SelectLocation(-1);
|
||||||
if (GameMain.Client == null)
|
if (GameMain.Client == null)
|
||||||
@@ -693,7 +693,27 @@ namespace Barotrauma
|
|||||||
pos.Y = (int)pos.Y;
|
pos.Y = (int)pos.Y;
|
||||||
Vector2 nameSize = GUIStyle.LargeFont.MeasureString(HighlightedLocation.Name);
|
Vector2 nameSize = GUIStyle.LargeFont.MeasureString(HighlightedLocation.Name);
|
||||||
Vector2 typeSize = HighlightedLocation.Type.Name.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Type.Name);
|
Vector2 typeSize = HighlightedLocation.Type.Name.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.Font.MeasureString(HighlightedLocation.Type.Name);
|
||||||
Vector2 size = new Vector2(Math.Max(nameSize.X, typeSize.X), nameSize.Y + typeSize.Y);
|
Vector2 descSize = HighlightedLocation.Type.Description.IsNullOrEmpty() ? Vector2.Zero : GUIStyle.SmallFont.MeasureString(HighlightedLocation.Type.Description);
|
||||||
|
Vector2 size = new Vector2(Math.Max(nameSize.X, Math.Max(typeSize.X, descSize.X)), nameSize.Y + typeSize.Y + descSize.Y);
|
||||||
|
|
||||||
|
int highestSubTier = HighlightedLocation.HighestSubmarineTierAvailable();
|
||||||
|
List<(SubmarineClass subClass, int tier)> overrideTiers = null;
|
||||||
|
if (HighlightedLocation.CanHaveSubsForSale())
|
||||||
|
{
|
||||||
|
overrideTiers = new List<(SubmarineClass subClass, int tier)>();
|
||||||
|
foreach (SubmarineClass subClass in Enum.GetValues(typeof(SubmarineClass)))
|
||||||
|
{
|
||||||
|
if (subClass == SubmarineClass.Undefined) { continue; }
|
||||||
|
int highestClassTier = HighlightedLocation.HighestSubmarineTierAvailable(subClass);
|
||||||
|
if (highestClassTier > 0 && highestClassTier > highestSubTier)
|
||||||
|
{
|
||||||
|
overrideTiers.Add((subClass, highestClassTier));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
int subAvailabilityTextCount = (highestSubTier > 0 ? 1 : 0) + (overrideTiers?.Count ?? 0);
|
||||||
|
size.Y += subAvailabilityTextCount * GUIStyle.SmallFont.MeasureString(TextManager.Get("advancedsub.all")).Y;
|
||||||
|
|
||||||
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
|
bool showReputation = hudVisibility > 0.0f && HighlightedLocation.Discovered && HighlightedLocation.Type.HasOutpost && HighlightedLocation.Reputation != null;
|
||||||
LocalizedString repLabelText = null, repValueText = null;
|
LocalizedString repLabelText = null, repValueText = null;
|
||||||
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
|
Vector2 repLabelSize = Vector2.Zero, repBarSize = Vector2.Zero;
|
||||||
@@ -706,21 +726,54 @@ namespace Barotrauma
|
|||||||
repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
|
repValueText = HighlightedLocation.Reputation.GetFormattedReputationText(addColorTags: false);
|
||||||
size.X = Math.Max(size.X, repBarSize.X + GUIStyle.Font.MeasureString(repValueText).X + GUI.IntScale(10));
|
size.X = Math.Max(size.X, repBarSize.X + GUIStyle.Font.MeasureString(repValueText).X + GUI.IntScale(10));
|
||||||
}
|
}
|
||||||
|
|
||||||
GUIStyle.GetComponentStyle("OuterGlow").Sprites[GUIComponent.ComponentState.None][0].Draw(
|
GUIStyle.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);
|
||||||
|
|
||||||
var topLeftPos = pos - new Vector2(0.0f, size.Y / 2);
|
var topLeftPos = pos - new Vector2(0.0f, size.Y / 2);
|
||||||
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: GUIStyle.LargeFont);
|
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: GUIStyle.LargeFont);
|
||||||
topLeftPos += new Vector2(0.0f, nameSize.Y);
|
topLeftPos += new Vector2(0.0f, nameSize.Y);
|
||||||
GUI.DrawString(spriteBatch, topLeftPos, HighlightedLocation.Type.Name, GUIStyle.TextColorNormal * hudVisibility * 1.5f);
|
DrawText(HighlightedLocation.Type.Name);
|
||||||
|
if (!HighlightedLocation.Type.Description.IsNullOrEmpty())
|
||||||
|
{
|
||||||
|
topLeftPos += new Vector2(0.0f, descSize.Y);
|
||||||
|
DrawText(HighlightedLocation.Type.Description, font: GUIStyle.SmallFont);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (highestSubTier > 0)
|
||||||
|
{
|
||||||
|
DrawSubAvailabilityText("advancedsub.all", highestSubTier);
|
||||||
|
}
|
||||||
|
if (overrideTiers != null)
|
||||||
|
{
|
||||||
|
foreach (var (subClass, tier) in overrideTiers)
|
||||||
|
{
|
||||||
|
DrawSubAvailabilityText($"advancedsub.{subClass}", tier);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DrawSubAvailabilityText(string tag, int tier)
|
||||||
|
{
|
||||||
|
topLeftPos += new Vector2(0.0f, typeSize.Y);
|
||||||
|
DrawText(TextManager.GetWithVariable(tag, "[tiernumber]", tier.ToString()), font: GUIStyle.SmallFont);
|
||||||
|
}
|
||||||
|
|
||||||
if (showReputation)
|
if (showReputation)
|
||||||
{
|
{
|
||||||
topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
|
topLeftPos += new Vector2(0.0f, typeSize.Y + repLabelSize.Y);
|
||||||
GUI.DrawString(spriteBatch, topLeftPos, repLabelText.Value, GUIStyle.TextColorNormal * hudVisibility * 1.5f);
|
DrawText(repLabelText.Value);
|
||||||
topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10));
|
topLeftPos += new Vector2(0.0f, repLabelSize.Y + GUI.IntScale(10));
|
||||||
Rectangle repBarRect = new Rectangle(new Point((int)topLeftPos.X, (int)topLeftPos.Y), new Point((int)repBarSize.X, (int)repBarSize.Y));
|
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);
|
RoundSummary.DrawReputationBar(spriteBatch, repBarRect, HighlightedLocation.Reputation.NormalizedValue);
|
||||||
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText.Value, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue));
|
GUI.DrawString(spriteBatch, new Vector2(repBarRect.Right + GUI.IntScale(5), repBarRect.Top), repValueText.Value, Reputation.GetReputationColor(HighlightedLocation.Reputation.NormalizedValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DrawText(LocalizedString text, GUIFont font = null) => GUI.DrawString(spriteBatch, topLeftPos, text, GUIStyle.TextColorNormal * hudVisibility * 1.5f, font: font);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (drawRadiationTooltip)
|
if (drawRadiationTooltip)
|
||||||
|
|||||||
@@ -1042,19 +1042,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
protected static void PositionEditingHUD()
|
protected static void PositionEditingHUD()
|
||||||
{
|
{
|
||||||
int maxHeight = 100;
|
int maxHeight =
|
||||||
if (Screen.Selected == GameMain.SubEditorScreen)
|
Screen.Selected == GameMain.SubEditorScreen ?
|
||||||
{
|
GameMain.GraphicsHeight - GameMain.SubEditorScreen.EntityMenu.Rect.Height - GameMain.SubEditorScreen.TopPanel.Rect.Bottom * 2 - 20 :
|
||||||
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
|
HUDLayoutSettings.InventoryAreaLower.Y - HUDLayoutSettings.CrewArea.Bottom - 10;
|
||||||
editingHUD.RectTransform.AbsoluteOffset = new Point(0, GameMain.SubEditorScreen.TopPanel.Rect.Bottom);
|
|
||||||
maxHeight = (GameMain.GraphicsHeight - GameMain.SubEditorScreen.EntityMenu.Rect.Height) - GameMain.SubEditorScreen.TopPanel.Rect.Bottom * 2 - 20;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
|
|
||||||
editingHUD.RectTransform.RelativeOffset = new Vector2(0.0f, (HUDLayoutSettings.CrewArea.Bottom + 10.0f) / (editingHUD.RectTransform.Parent ?? GUI.Canvas).Rect.Height);
|
|
||||||
maxHeight = HUDLayoutSettings.InventoryAreaLower.Y - HUDLayoutSettings.CrewArea.Bottom - 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
var listBox = editingHUD.GetChild<GUIListBox>();
|
var listBox = editingHUD.GetChild<GUIListBox>();
|
||||||
if (listBox != null)
|
if (listBox != null)
|
||||||
@@ -1074,6 +1066,17 @@ namespace Barotrauma
|
|||||||
MathHelper.Clamp(contentHeight + padding * 2, 50, maxHeight)), resizeChildren: false);
|
MathHelper.Clamp(contentHeight + padding * 2, 50, maxHeight)), resizeChildren: false);
|
||||||
listBox.RectTransform.Resize(new Point(listBox.RectTransform.NonScaledSize.X, editingHUD.RectTransform.NonScaledSize.Y - padding * 2), resizeChildren: false);
|
listBox.RectTransform.Resize(new Point(listBox.RectTransform.NonScaledSize.X, editingHUD.RectTransform.NonScaledSize.Y - padding * 2), resizeChildren: false);
|
||||||
}
|
}
|
||||||
|
editingHUD.RectTransform.SetPosition(Anchor.TopRight);
|
||||||
|
if (Screen.Selected == GameMain.SubEditorScreen)
|
||||||
|
{
|
||||||
|
editingHUD.RectTransform.AbsoluteOffset = new Point(0, GameMain.SubEditorScreen.TopPanel.Rect.Bottom);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
editingHUD.RectTransform.AbsoluteOffset = new Point(
|
||||||
|
0,
|
||||||
|
HUDLayoutSettings.HealthBarAfflictionArea.Y - editingHUD.Rect.Height - GUI.IntScale(10));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
|
public virtual void DrawEditing(SpriteBatch spriteBatch, Camera cam) { }
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
partial class ChatMessage
|
partial class ChatMessage
|
||||||
{
|
{
|
||||||
public virtual void ClientWrite(IWriteMessage msg)
|
public virtual void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
|
segmentTableWriter.StartNewSegment(ClientNetSegment.ChatMessage);
|
||||||
msg.WriteUInt16(NetStateID);
|
msg.WriteUInt16(NetStateID);
|
||||||
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
msg.WriteRangedInteger((int)Type, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||||
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMode, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.IO.Pipes;
|
using System.IO.Pipes;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
static partial class ChildServerRelay
|
static partial class ChildServerRelay
|
||||||
{
|
{
|
||||||
public static Process Process;
|
public static Process Process;
|
||||||
|
public static bool IsProcessAlive => Process is { HasExited: false };
|
||||||
|
|
||||||
private static bool localHandlesDisposed;
|
private static bool localHandlesDisposed;
|
||||||
private static AnonymousPipeServerStream writePipe;
|
private static AnonymousPipeServerStream writePipe;
|
||||||
private static AnonymousPipeServerStream readPipe;
|
private static AnonymousPipeServerStream readPipe;
|
||||||
@@ -44,18 +47,27 @@ namespace Barotrauma.Networking
|
|||||||
localHandlesDisposed = true;
|
localHandlesDisposed = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ClosePipes()
|
public static void AttemptGracefulShutDown(int maxAttempts = 20)
|
||||||
{
|
{
|
||||||
writePipe?.Dispose(); writePipe = null;
|
status = StatusEnum.RequestedShutDown;
|
||||||
readPipe?.Dispose(); readPipe = null;
|
writeManualResetEvent?.Set();
|
||||||
shutDown = true;
|
int checks = 0;
|
||||||
|
while (Process is { HasExited: false })
|
||||||
|
{
|
||||||
|
if (checks >= maxAttempts)
|
||||||
|
{
|
||||||
|
DebugConsole.AddWarning("Server could not be shut down gracefully");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
Thread.Sleep(100);
|
||||||
|
checks++;
|
||||||
|
}
|
||||||
|
ForceShutDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void ShutDown()
|
public static void ForceShutDown()
|
||||||
{
|
{
|
||||||
Process?.Kill(); Process = null;
|
Process?.Kill(); Process = null;
|
||||||
writePipe = null; readPipe = null;
|
|
||||||
|
|
||||||
PrivateShutDown();
|
PrivateShutDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,16 @@ namespace Barotrauma.Networking
|
|||||||
set;
|
set;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SoundChannel radioNoiseChannel;
|
||||||
|
private float radioNoise;
|
||||||
|
|
||||||
|
public float RadioNoise
|
||||||
|
{
|
||||||
|
get { return radioNoise; }
|
||||||
|
set { radioNoise = MathHelper.Clamp(value, 0.0f, 1.0f); }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private bool mutedLocally;
|
private bool mutedLocally;
|
||||||
public bool MutedLocally
|
public bool MutedLocally
|
||||||
{
|
{
|
||||||
@@ -42,35 +52,64 @@ namespace Barotrauma.Networking
|
|||||||
!HasPermission(ClientPermissions.Kick) &&
|
!HasPermission(ClientPermissions.Kick) &&
|
||||||
!HasPermission(ClientPermissions.Unban);
|
!HasPermission(ClientPermissions.Unban);
|
||||||
|
|
||||||
public void UpdateSoundPosition()
|
public void UpdateVoipSound()
|
||||||
{
|
{
|
||||||
if (VoipSound == null) { return; }
|
if (VoipSound == null || !VoipSound.IsPlaying)
|
||||||
|
|
||||||
if (!VoipSound.IsPlaying)
|
|
||||||
{
|
{
|
||||||
DebugConsole.Log("Destroying voipsound");
|
radioNoiseChannel?.Dispose();
|
||||||
VoipSound.Dispose();
|
radioNoiseChannel = null;
|
||||||
|
if (VoipSound != null)
|
||||||
|
{
|
||||||
|
DebugConsole.Log("Destroying voipsound");
|
||||||
|
VoipSound.Dispose();
|
||||||
|
}
|
||||||
VoipSound = null;
|
VoipSound = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Screen.Selected is ModDownloadScreen)
|
||||||
|
{
|
||||||
|
VoipSound.Gain = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
float gain = 1.0f;
|
||||||
|
float noiseGain = 0.0f;
|
||||||
|
Vector3? position = null;
|
||||||
if (character != null)
|
if (character != null)
|
||||||
{
|
{
|
||||||
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
|
if (GameSettings.CurrentConfig.Audio.UseDirectionalVoiceChat)
|
||||||
{
|
{
|
||||||
VoipSound.SetPosition(new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f));
|
position = new Vector3(character.WorldPosition.X, character.WorldPosition.Y, 0.0f);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
VoipSound.SetPosition(null);
|
|
||||||
float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition);
|
float dist = Vector3.Distance(new Vector3(character.WorldPosition, 0.0f), GameMain.SoundManager.ListenerPosition);
|
||||||
VoipSound.Gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
|
gain = 1.0f - MathUtils.InverseLerp(VoipSound.Near, VoipSound.Far, dist);
|
||||||
|
}
|
||||||
|
if (RadioNoise > 0.0f)
|
||||||
|
{
|
||||||
|
noiseGain = gain * RadioNoise;
|
||||||
|
gain *= 1.0f - RadioNoise;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
VoipSound.SetPosition(position);
|
||||||
|
VoipSound.Gain = gain;
|
||||||
|
if (noiseGain > 0.0f)
|
||||||
{
|
{
|
||||||
VoipSound.SetPosition(null);
|
if (radioNoiseChannel == null || !radioNoiseChannel.IsPlaying)
|
||||||
VoipSound.Gain = 1.0f;
|
{
|
||||||
|
radioNoiseChannel = SoundPlayer.PlaySound("radiostatic");
|
||||||
|
radioNoiseChannel.Category = "voip";
|
||||||
|
radioNoiseChannel.Looping = true;
|
||||||
|
}
|
||||||
|
radioNoiseChannel.Near = VoipSound.Near;
|
||||||
|
radioNoiseChannel.Far = VoipSound.Far;
|
||||||
|
radioNoiseChannel.Position = position;
|
||||||
|
radioNoiseChannel.Gain = noiseGain;
|
||||||
|
}
|
||||||
|
else if (radioNoiseChannel != null)
|
||||||
|
{
|
||||||
|
radioNoiseChannel.Gain = 0.0f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,6 +197,11 @@ namespace Barotrauma.Networking
|
|||||||
VoipSound.Dispose();
|
VoipSound.Dispose();
|
||||||
VoipSound = null;
|
VoipSound = null;
|
||||||
}
|
}
|
||||||
|
if (radioNoiseChannel != null)
|
||||||
|
{
|
||||||
|
radioNoiseChannel.Dispose();
|
||||||
|
radioNoiseChannel = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -311,6 +311,12 @@ namespace Barotrauma.Networking
|
|||||||
CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo");
|
CoroutineManager.StartCoroutine(WaitForStartingInfo(), "WaitForStartingInfo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void SetLobbyPublic(bool isPublic)
|
||||||
|
{
|
||||||
|
GameMain.NetLobbyScreen.SetPublic(isPublic);
|
||||||
|
SteamManager.SetLobbyPublic(isPublic);
|
||||||
|
}
|
||||||
|
|
||||||
private ClientPeer CreateNetPeer()
|
private ClientPeer CreateNetPeer()
|
||||||
{
|
{
|
||||||
Networking.ClientPeer.Callbacks callbacks = new ClientPeer.Callbacks(
|
Networking.ClientPeer.Callbacks callbacks = new ClientPeer.Callbacks(
|
||||||
@@ -326,10 +332,27 @@ namespace Barotrauma.Networking
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void CreateServerCrashMessage()
|
||||||
|
{
|
||||||
|
// Close any message boxes that say "The server has crashed."
|
||||||
|
var basicServerCrashMsg = TextManager.Get($"{nameof(DisconnectReason)}.{nameof(DisconnectReason.ServerCrashed)}");
|
||||||
|
GUIMessageBox.MessageBoxes
|
||||||
|
.OfType<GUIMessageBox>()
|
||||||
|
.Where(mb => mb.Text?.Text == basicServerCrashMsg)
|
||||||
|
.ToArray()
|
||||||
|
.ForEach(mb => mb.Close());
|
||||||
|
|
||||||
|
// Open a new message box with the crash report path
|
||||||
|
if (GUIMessageBox.MessageBoxes.All(
|
||||||
|
mb => (mb as GUIMessageBox)?.Text?.Text != ChildServerRelay.CrashMessage))
|
||||||
|
{
|
||||||
|
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
||||||
|
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool ReturnToPreviousMenu(GUIButton button, object obj)
|
private bool ReturnToPreviousMenu(GUIButton button, object obj)
|
||||||
{
|
{
|
||||||
Quit();
|
|
||||||
|
|
||||||
Submarine.Unload();
|
Submarine.Unload();
|
||||||
GameMain.Client = null;
|
GameMain.Client = null;
|
||||||
GameMain.GameSession = null;
|
GameMain.GameSession = null;
|
||||||
@@ -443,7 +466,7 @@ namespace Barotrauma.Networking
|
|||||||
foreach (Client c in ConnectedClients)
|
foreach (Client c in ConnectedClients)
|
||||||
{
|
{
|
||||||
if (c.Character != null && c.Character.Removed) { c.Character = null; }
|
if (c.Character != null && c.Character.Removed) { c.Character = null; }
|
||||||
c.UpdateSoundPosition();
|
c.UpdateVoipSound();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (VoipCapture.Instance != null)
|
if (VoipCapture.Instance != null)
|
||||||
@@ -479,15 +502,11 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string errorMsg = "Error while reading a message from server. {" + e + "}. ";
|
string errorMsg = "Error while reading a message from server. ";
|
||||||
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
|
if (GameMain.Client == null) { errorMsg += "Client disposed."; }
|
||||||
errorMsg += "\n" + e.StackTrace.CleanupStackTrace();
|
AppendExceptionInfo(ref errorMsg, e);
|
||||||
if (e.InnerException != null)
|
|
||||||
{
|
|
||||||
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
|
|
||||||
}
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
DebugConsole.ThrowError("Error while reading a message from server.", e);
|
DebugConsole.ThrowError(errorMsg);
|
||||||
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
|
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", ("[message]", e.Message), ("[targetsite]", e.TargetSite.ToString())))
|
||||||
{
|
{
|
||||||
DisplayInLoadingScreens = true
|
DisplayInLoadingScreens = true
|
||||||
@@ -529,14 +548,10 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (GameMain.WindowActive)
|
if (GameMain.WindowActive)
|
||||||
{
|
{
|
||||||
if (ChildServerRelay.Process?.HasExited ?? true)
|
if (!ChildServerRelay.IsProcessAlive)
|
||||||
{
|
{
|
||||||
Quit();
|
Quit();
|
||||||
if (!GUIMessageBox.MessageBoxes.Any(mb => (mb as GUIMessageBox)?.Text?.Text == ChildServerRelay.CrashMessage))
|
CreateServerCrashMessage();
|
||||||
{
|
|
||||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
|
||||||
msgBox.Buttons[0].OnClicked += ReturnToPreviousMenu;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -630,14 +645,8 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
string errorMsg = "Error while reading an ingame update message from server. {" + e + "}\n" + e.StackTrace.CleanupStackTrace();
|
string errorMsg = "Error while reading an ingame update message from server.";
|
||||||
if (e.InnerException != null)
|
AppendExceptionInfo(ref errorMsg, e);
|
||||||
{
|
|
||||||
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
|
|
||||||
}
|
|
||||||
#if DEBUG
|
|
||||||
DebugConsole.ThrowError("Error while reading an ingame update message from server.", e);
|
|
||||||
#endif
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
@@ -866,22 +875,31 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
|
|
||||||
byte missionCount = inc.ReadByte();
|
byte missionCount = inc.ReadByte();
|
||||||
if (missionCount != GameMain.GameSession.Missions.Count())
|
|
||||||
{
|
|
||||||
string errorMsg = $"Mission equality check failed. Mission count doesn't match the server (server: {missionCount}, client: {GameMain.GameSession.Missions.Count()})";
|
|
||||||
throw new Exception(errorMsg);
|
|
||||||
}
|
|
||||||
List<Identifier> serverMissionIdentifiers = new List<Identifier>();
|
List<Identifier> serverMissionIdentifiers = new List<Identifier>();
|
||||||
for (int i = 0; i < missionCount; i++)
|
for (int i = 0; i < missionCount; i++)
|
||||||
{
|
{
|
||||||
serverMissionIdentifiers.Add(inc.ReadIdentifier());
|
serverMissionIdentifiers.Add(inc.ReadIdentifier());
|
||||||
}
|
}
|
||||||
|
if (missionCount != GameMain.GameSession.GameMode.Missions.Count())
|
||||||
|
{
|
||||||
|
string errorMsg =
|
||||||
|
$"Mission equality check failed. Mission count doesn't match the server. " +
|
||||||
|
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
|
||||||
|
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
|
||||||
|
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
|
||||||
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsCountMismatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
|
throw new Exception(errorMsg);
|
||||||
|
}
|
||||||
|
|
||||||
if (missionCount > 0)
|
if (missionCount > 0)
|
||||||
{
|
{
|
||||||
if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
|
if (!GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
|
||||||
{
|
{
|
||||||
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
|
string errorMsg =
|
||||||
|
$"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server " +
|
||||||
|
$"Server: {string.Join(", ", serverMissionIdentifiers)}, " +
|
||||||
|
$"client: {string.Join(", ", GameMain.GameSession.GameMode.Missions.Select(m => m.Prefab.Identifier))}, " +
|
||||||
|
$"game session: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
throw new Exception(errorMsg);
|
throw new Exception(errorMsg);
|
||||||
}
|
}
|
||||||
@@ -937,13 +955,6 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
GUI.ClearCursorWait();
|
GUI.ClearCursorWait();
|
||||||
|
|
||||||
ChildServerRelay.ShutDown();
|
|
||||||
|
|
||||||
if (SteamManager.IsInitialized)
|
|
||||||
{
|
|
||||||
Steamworks.SteamFriends.ClearRichPresence();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (disconnectPacket.ShouldCreateAnalyticsEvent)
|
if (disconnectPacket.ShouldCreateAnalyticsEvent)
|
||||||
{
|
{
|
||||||
GameAnalyticsManager.AddErrorEventOnce(
|
GameAnalyticsManager.AddErrorEventOnce(
|
||||||
@@ -969,11 +980,43 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
ReturnToPreviousMenu(null, null);
|
if (ClientPeer is SteamP2PClientPeer or SteamP2POwnerPeer)
|
||||||
new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage)
|
|
||||||
{
|
{
|
||||||
DisplayInLoadingScreens = true
|
SteamManager.LeaveLobby();
|
||||||
};
|
}
|
||||||
|
|
||||||
|
GameMain.ModDownloadScreen.Reset();
|
||||||
|
ContentPackageManager.EnabledPackages.Restore();
|
||||||
|
|
||||||
|
CampaignMode.StartRoundCancellationToken?.Cancel();
|
||||||
|
|
||||||
|
if (SteamManager.IsInitialized)
|
||||||
|
{
|
||||||
|
Steamworks.SteamFriends.ClearRichPresence();
|
||||||
|
}
|
||||||
|
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
|
||||||
|
{
|
||||||
|
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
ChildServerRelay.AttemptGracefulShutDown();
|
||||||
|
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
|
||||||
|
|
||||||
|
characterInfo?.Remove();
|
||||||
|
|
||||||
|
VoipClient?.Dispose();
|
||||||
|
VoipClient = null;
|
||||||
|
GameMain.Client = null;
|
||||||
|
GameMain.GameSession = null;
|
||||||
|
|
||||||
|
ReturnToPreviousMenu(null, null);
|
||||||
|
if (disconnectPacket.DisconnectReason != DisconnectReason.Disconnected)
|
||||||
|
{
|
||||||
|
new GUIMessageBox(TextManager.Get(wasConnected ? "ConnectionLost" : "CouldNotConnectToServer"), disconnectPacket.PopupMessage)
|
||||||
|
{
|
||||||
|
DisplayInLoadingScreens = true
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1319,6 +1362,7 @@ namespace Barotrauma.Networking
|
|||||||
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
|
ServerSettings.MaximumMoneyTransferRequest = inc.ReadInt32();
|
||||||
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
|
bool usingShuttle = GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
|
||||||
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
|
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
|
||||||
|
ServerSettings.ShowEnemyHealthBars = (EnemyHealthBarMode)inc.ReadByte();
|
||||||
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
|
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
|
||||||
GameMain.LightManager.LightingEnabled = true;
|
GameMain.LightManager.LightingEnabled = true;
|
||||||
|
|
||||||
@@ -1876,12 +1920,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
private void ReadLobbyUpdate(IReadMessage inc)
|
private void ReadLobbyUpdate(IReadMessage inc)
|
||||||
{
|
{
|
||||||
ServerNetObject objHeader;
|
SegmentTableReader<ServerNetSegment>.Read(inc, (segment, inc) =>
|
||||||
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
|
|
||||||
{
|
{
|
||||||
switch (objHeader)
|
switch (segment)
|
||||||
{
|
{
|
||||||
case ServerNetObject.SYNC_IDS:
|
case ServerNetSegment.SyncIds:
|
||||||
bool lobbyUpdated = inc.ReadBoolean();
|
bool lobbyUpdated = inc.ReadBoolean();
|
||||||
inc.ReadPadBits();
|
inc.ReadPadBits();
|
||||||
|
|
||||||
@@ -2013,17 +2056,19 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
lastSentChatMsgID = inc.ReadUInt16();
|
lastSentChatMsgID = inc.ReadUInt16();
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.CLIENT_LIST:
|
case ServerNetSegment.ClientList:
|
||||||
ReadClientList(inc);
|
ReadClientList(inc);
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.CHAT_MESSAGE:
|
case ServerNetSegment.ChatMessage:
|
||||||
ChatMessage.ClientRead(inc);
|
ChatMessage.ClientRead(inc);
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.VOTE:
|
case ServerNetSegment.Vote:
|
||||||
Voting.ClientRead(inc);
|
Voting.ClientRead(inc);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
readonly List<IServerSerializable> debugEntityList = new List<IServerSerializable>();
|
readonly List<IServerSerializable> debugEntityList = new List<IServerSerializable>();
|
||||||
@@ -2033,117 +2078,106 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
|
float sendingTime = inc.ReadSingle() - 0.0f;//TODO: reimplement inc.SenderConnection.RemoteTimeOffset;
|
||||||
|
|
||||||
ServerNetObject? prevObjHeader = null;
|
SegmentTableReader<ServerNetSegment>.Read(inc,
|
||||||
long prevBitPos = 0;
|
segmentDataReader: (segment, inc) =>
|
||||||
long prevBytePos = 0;
|
|
||||||
|
|
||||||
long prevBitLength = 0;
|
|
||||||
long prevByteLength = 0;
|
|
||||||
|
|
||||||
ServerNetObject? objHeader = null;
|
|
||||||
try
|
|
||||||
{
|
{
|
||||||
while ((objHeader = (ServerNetObject)inc.ReadByte()) != ServerNetObject.END_OF_MESSAGE)
|
switch (segment)
|
||||||
{
|
{
|
||||||
switch (objHeader)
|
case ServerNetSegment.SyncIds:
|
||||||
{
|
lastSentChatMsgID = inc.ReadUInt16();
|
||||||
case ServerNetObject.SYNC_IDS:
|
LastSentEntityEventID = inc.ReadUInt16();
|
||||||
lastSentChatMsgID = inc.ReadUInt16();
|
|
||||||
LastSentEntityEventID = inc.ReadUInt16();
|
|
||||||
|
|
||||||
bool campaignUpdated = inc.ReadBoolean();
|
bool campaignUpdated = inc.ReadBoolean();
|
||||||
inc.ReadPadBits();
|
inc.ReadPadBits();
|
||||||
if (campaignUpdated)
|
if (campaignUpdated)
|
||||||
{
|
{
|
||||||
MultiPlayerCampaign.ClientRead(inc);
|
MultiPlayerCampaign.ClientRead(inc);
|
||||||
}
|
}
|
||||||
else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
|
else if (GameMain.NetLobbyScreen.SelectedMode != GameModePreset.MultiPlayerCampaign)
|
||||||
{
|
{
|
||||||
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
|
GameMain.NetLobbyScreen.SetCampaignCharacterInfo(null);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case ServerNetObject.ENTITY_POSITION:
|
case ServerNetSegment.EntityPosition:
|
||||||
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
|
inc.ReadPadBits(); //padding is required here to make sure any padding bits within tempBuffer are read correctly
|
||||||
|
|
||||||
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
|
bool isItem = inc.ReadBoolean(); inc.ReadPadBits();
|
||||||
UInt32 incomingUintIdentifier = inc.ReadUInt32();
|
UInt32 incomingUintIdentifier = inc.ReadUInt32();
|
||||||
UInt16 id = inc.ReadUInt16();
|
UInt16 id = inc.ReadUInt16();
|
||||||
uint msgLength = inc.ReadVariableUInt32();
|
uint msgLength = inc.ReadVariableUInt32();
|
||||||
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
|
int msgEndPos = (int)(inc.BitPosition + msgLength * 8);
|
||||||
|
|
||||||
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
|
var entity = Entity.FindEntityByID(id) as IServerPositionSync;
|
||||||
if (msgEndPos > inc.LengthBits)
|
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.");
|
DebugConsole.ThrowError($"Error while reading a position update for the entity \"({entity?.ToString() ?? "null"})\". Message length exceeds the size of the buffer.");
|
||||||
return;
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
|
||||||
}
|
}
|
||||||
|
|
||||||
debugEntityList.Add(entity);
|
debugEntityList.Add(entity);
|
||||||
if (entity != null)
|
if (entity != null)
|
||||||
|
{
|
||||||
|
if (entity is Item != isItem)
|
||||||
{
|
{
|
||||||
if (entity is Item != isItem)
|
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message. Entity type does not match (server entity is {(isItem ? "an item" : "not an item")}, client entity is {(entity?.GetType().ToString() ?? "null")}). Ignoring the message...");
|
|
||||||
}
|
|
||||||
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
|
|
||||||
uintIdentifier != incomingUintIdentifier)
|
|
||||||
{
|
|
||||||
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
|
|
||||||
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
|
|
||||||
+$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
entity.ClientReadPosition(inc, sendingTime);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (entity is MapEntity { Prefab: { UintIdentifier: { } uintIdentifier } } me &&
|
||||||
//force to the correct position in case the entity doesn't exist
|
uintIdentifier != incomingUintIdentifier)
|
||||||
//or the message wasn't read correctly for whatever reason
|
|
||||||
inc.BitPosition = msgEndPos;
|
|
||||||
inc.ReadPadBits();
|
|
||||||
break;
|
|
||||||
case ServerNetObject.CLIENT_LIST:
|
|
||||||
ReadClientList(inc);
|
|
||||||
break;
|
|
||||||
case ServerNetObject.ENTITY_EVENT:
|
|
||||||
case ServerNetObject.ENTITY_EVENT_INITIAL:
|
|
||||||
if (!EntityEventManager.Read(objHeader.Value, inc, sendingTime, debugEntityList))
|
|
||||||
{
|
{
|
||||||
return;
|
DebugConsole.AddWarning($"Received a potentially invalid ENTITY_POSITION message."
|
||||||
|
+$"Entity identifier does not match (server entity is {MapEntityPrefab.List.FirstOrDefault(p => p.UintIdentifier == incomingUintIdentifier)?.Identifier.Value ?? "[not found]"}, "
|
||||||
|
+$"client entity is {me.Prefab.Identifier}). Ignoring the message...");
|
||||||
}
|
}
|
||||||
break;
|
else
|
||||||
case ServerNetObject.CHAT_MESSAGE:
|
{
|
||||||
ChatMessage.ClientRead(inc);
|
entity.ClientReadPosition(inc, sendingTime);
|
||||||
break;
|
}
|
||||||
default:
|
}
|
||||||
throw new Exception($"Unknown object header \"{objHeader}\"!)");
|
|
||||||
}
|
|
||||||
prevBitLength = inc.BitPosition - prevBitPos;
|
|
||||||
prevByteLength = inc.BytePosition - prevBytePos;
|
|
||||||
|
|
||||||
prevObjHeader = objHeader;
|
//force to the correct position in case the entity doesn't exist
|
||||||
prevBitPos = inc.BitPosition;
|
//or the message wasn't read correctly for whatever reason
|
||||||
prevBytePos = inc.BytePosition;
|
inc.BitPosition = msgEndPos;
|
||||||
|
inc.ReadPadBits();
|
||||||
|
break;
|
||||||
|
case ServerNetSegment.ClientList:
|
||||||
|
ReadClientList(inc);
|
||||||
|
break;
|
||||||
|
case ServerNetSegment.EntityEvent:
|
||||||
|
case ServerNetSegment.EntityEventInitial:
|
||||||
|
if (!EntityEventManager.Read(segment, inc, sendingTime, debugEntityList))
|
||||||
|
{
|
||||||
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.Yes;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case ServerNetSegment.ChatMessage:
|
||||||
|
ChatMessage.ClientRead(inc);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception($"Unknown segment \"{segment}\"!)");
|
||||||
}
|
}
|
||||||
}
|
|
||||||
catch (Exception ex)
|
return SegmentTableReader<ServerNetSegment>.BreakSegmentReading.No;
|
||||||
|
},
|
||||||
|
exceptionHandler: (segment, prevSegments, ex) =>
|
||||||
{
|
{
|
||||||
List<string> errorLines = new List<string>
|
List<string> errorLines = new List<string>
|
||||||
{
|
{
|
||||||
ex.Message,
|
ex.Message,
|
||||||
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
|
"Message length: " + inc.LengthBits + " (" + inc.LengthBytes + " bytes)",
|
||||||
"Read position: " + inc.BitPosition,
|
"Read position: " + inc.BitPosition,
|
||||||
"Header: " + (objHeader != null ? objHeader.Value.ToString() : "Error occurred on the very first header!"),
|
$"Segment with error: {segment}"
|
||||||
prevObjHeader != null ? "Previous header: " + prevObjHeader : "Error occurred on the very first header!",
|
|
||||||
"Previous object was " + (prevBitLength) + " bits long (" + (prevByteLength) + " bytes)",
|
|
||||||
" "
|
|
||||||
};
|
};
|
||||||
|
if (prevSegments.Any())
|
||||||
|
{
|
||||||
|
errorLines.Add("Prev segments: " + string.Join(", ", prevSegments));
|
||||||
|
errorLines.Add(" ");
|
||||||
|
}
|
||||||
errorLines.Add(ex.StackTrace.CleanupStackTrace());
|
errorLines.Add(ex.StackTrace.CleanupStackTrace());
|
||||||
errorLines.Add(" ");
|
errorLines.Add(" ");
|
||||||
if (prevObjHeader == ServerNetObject.ENTITY_EVENT || prevObjHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
|
if (prevSegments.Concat(segment.ToEnumerable()).Any(s => s.Identifier
|
||||||
objHeader == ServerNetObject.ENTITY_EVENT || objHeader == ServerNetObject.ENTITY_EVENT_INITIAL ||
|
is ServerNetSegment.EntityPosition
|
||||||
objHeader == ServerNetObject.ENTITY_POSITION || prevObjHeader == ServerNetObject.ENTITY_POSITION)
|
or ServerNetSegment.EntityEvent
|
||||||
|
or ServerNetSegment.EntityEventInitial))
|
||||||
{
|
{
|
||||||
foreach (IServerSerializable ent in debugEntityList)
|
foreach (IServerSerializable ent in debugEntityList)
|
||||||
{
|
{
|
||||||
@@ -2157,34 +2191,18 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (string line in errorLines)
|
|
||||||
{
|
|
||||||
DebugConsole.ThrowError(line);
|
|
||||||
}
|
|
||||||
errorLines.Add("Last console messages:");
|
errorLines.Add("Last console messages:");
|
||||||
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
|
for (int i = DebugConsole.Messages.Count - 1; i > Math.Max(0, DebugConsole.Messages.Count - 20); i--)
|
||||||
{
|
{
|
||||||
errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
|
errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
|
||||||
}
|
}
|
||||||
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
|
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
|
||||||
|
|
||||||
DebugConsole.ThrowError("Writing object data to \"networkerror_data.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues");
|
throw new Exception(
|
||||||
|
$"Exception thrown while reading segment {segment.Identifier} at position {segment.Pointer}." +
|
||||||
using (FileStream fl = File.Open("networkerror_data.log", System.IO.FileMode.Create))
|
(prevSegments.Any() ? $" Previous segments: {string.Join(", ", prevSegments)}" : ""),
|
||||||
{
|
ex);
|
||||||
using (System.IO.BinaryWriter bw = new System.IO.BinaryWriter(fl))
|
});
|
||||||
using (System.IO.StreamWriter sw = new System.IO.StreamWriter(fl))
|
|
||||||
{
|
|
||||||
bw.Write(inc.Buffer, (int)(prevBytePos - prevByteLength), (int)(prevByteLength));
|
|
||||||
sw.WriteLine("");
|
|
||||||
foreach (string line in errorLines)
|
|
||||||
{
|
|
||||||
sw.WriteLine(line);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Exception("Read error: please send us \"networkerror_data.log\"!");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SendLobbyUpdate()
|
private void SendLobbyUpdate()
|
||||||
@@ -2192,50 +2210,51 @@ namespace Barotrauma.Networking
|
|||||||
IWriteMessage outmsg = new WriteOnlyMessage();
|
IWriteMessage outmsg = new WriteOnlyMessage();
|
||||||
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
outmsg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
|
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
|
||||||
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
|
|
||||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
|
||||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
|
||||||
outmsg.WriteUInt16(nameId);
|
|
||||||
outmsg.WriteString(Name);
|
|
||||||
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
|
||||||
if (jobPreferences.Count > 0)
|
|
||||||
{
|
{
|
||||||
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
|
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
||||||
}
|
outmsg.WriteUInt16(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
else
|
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||||
{
|
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||||
outmsg.WriteIdentifier(Identifier.Empty);
|
outmsg.WriteUInt16(nameId);
|
||||||
}
|
outmsg.WriteString(Name);
|
||||||
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
|
var jobPreferences = GameMain.NetLobbyScreen.JobPreferences;
|
||||||
|
if (jobPreferences.Count > 0)
|
||||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
|
||||||
{
|
|
||||||
outmsg.WriteUInt16((UInt16)0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outmsg.WriteUInt16(campaign.LastSaveID);
|
|
||||||
outmsg.WriteByte(campaign.CampaignID);
|
|
||||||
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
|
||||||
{
|
{
|
||||||
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
|
outmsg.WriteIdentifier(jobPreferences[0].Prefab.Identifier);
|
||||||
}
|
}
|
||||||
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
else
|
||||||
}
|
|
||||||
|
|
||||||
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
|
||||||
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
|
||||||
{
|
|
||||||
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
|
|
||||||
{
|
{
|
||||||
//no more room in this packet
|
outmsg.WriteIdentifier(Identifier.Empty);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
chatMsgQueue[i].ClientWrite(outmsg);
|
outmsg.WriteByte((byte)MultiplayerPreferences.Instance.TeamPreference);
|
||||||
}
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
|
|
||||||
|
|
||||||
|
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
||||||
|
{
|
||||||
|
outmsg.WriteUInt16((UInt16)0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
outmsg.WriteUInt16(campaign.LastSaveID);
|
||||||
|
outmsg.WriteByte(campaign.CampaignID);
|
||||||
|
foreach (MultiPlayerCampaign.NetFlags netFlag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
||||||
|
{
|
||||||
|
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(netFlag));
|
||||||
|
}
|
||||||
|
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
||||||
|
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
||||||
|
{
|
||||||
|
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
|
||||||
|
{
|
||||||
|
//no more room in this packet
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
chatMsgQueue[i].ClientWrite(segmentTable, outmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (outmsg.LengthBytes > MsgConstants.MTU)
|
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||||
{
|
{
|
||||||
DebugConsole.ThrowError($"Maximum packet size exceeded ({outmsg.LengthBytes} > {MsgConstants.MTU})");
|
DebugConsole.ThrowError($"Maximum packet size exceeded ({outmsg.LengthBytes} > {MsgConstants.MTU})");
|
||||||
@@ -2251,44 +2270,47 @@ namespace Barotrauma.Networking
|
|||||||
outmsg.WriteBoolean(EntityEventManager.MidRoundSyncingDone);
|
outmsg.WriteBoolean(EntityEventManager.MidRoundSyncingDone);
|
||||||
outmsg.WritePadBits();
|
outmsg.WritePadBits();
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.SYNC_IDS);
|
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(outmsg))
|
||||||
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
{
|
||||||
outmsg.WriteUInt16(ChatMessage.LastID);
|
segmentTable.StartNewSegment(ClientNetSegment.SyncIds);
|
||||||
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
|
//outmsg.Write(GameMain.NetLobbyScreen.LastUpdateID);
|
||||||
outmsg.WriteUInt16(LastClientListUpdateID);
|
outmsg.WriteUInt16(ChatMessage.LastID);
|
||||||
|
outmsg.WriteUInt16(EntityEventManager.LastReceivedID);
|
||||||
|
outmsg.WriteUInt16(LastClientListUpdateID);
|
||||||
|
|
||||||
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaign.LastSaveID == 0)
|
||||||
{
|
|
||||||
outmsg.WriteUInt16((UInt16)0);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
outmsg.WriteUInt16(campaign.LastSaveID);
|
|
||||||
outmsg.WriteByte(campaign.CampaignID);
|
|
||||||
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
|
||||||
{
|
{
|
||||||
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
|
outmsg.WriteUInt16((UInt16)0);
|
||||||
}
|
}
|
||||||
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
else
|
||||||
}
|
|
||||||
|
|
||||||
Character.Controlled?.ClientWriteInput(outmsg);
|
|
||||||
GameMain.GameScreen.Cam?.ClientWrite(outmsg);
|
|
||||||
|
|
||||||
EntityEventManager.Write(outmsg, ClientPeer?.ServerConnection);
|
|
||||||
|
|
||||||
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
|
||||||
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
|
||||||
{
|
|
||||||
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
|
|
||||||
{
|
{
|
||||||
//not enough room in this packet
|
outmsg.WriteUInt16(campaign.LastSaveID);
|
||||||
break;
|
outmsg.WriteByte(campaign.CampaignID);
|
||||||
}
|
foreach (MultiPlayerCampaign.NetFlags flag in Enum.GetValues(typeof(MultiPlayerCampaign.NetFlags)))
|
||||||
chatMsgQueue[i].ClientWrite(outmsg);
|
{
|
||||||
}
|
outmsg.WriteUInt16(campaign.GetLastUpdateIdForFlag(flag));
|
||||||
|
}
|
||||||
|
|
||||||
outmsg.WriteByte((byte)ClientNetObject.END_OF_MESSAGE);
|
outmsg.WriteBoolean(GameMain.NetLobbyScreen.CampaignCharacterDiscarded);
|
||||||
|
}
|
||||||
|
|
||||||
|
Character.Controlled?.ClientWriteInput(segmentTable, outmsg);
|
||||||
|
GameMain.GameScreen.Cam?.ClientWrite(segmentTable, outmsg);
|
||||||
|
|
||||||
|
EntityEventManager.Write(segmentTable, outmsg, ClientPeer?.ServerConnection);
|
||||||
|
|
||||||
|
chatMsgQueue.RemoveAll(cMsg => !NetIdUtils.IdMoreRecent(cMsg.NetStateID, lastSentChatMsgID));
|
||||||
|
for (int i = 0; i < chatMsgQueue.Count && i < ChatMessage.MaxMessagesPerPacket; i++)
|
||||||
|
{
|
||||||
|
if (outmsg.LengthBytes + chatMsgQueue[i].EstimateLengthBytesClient() > MsgConstants.MTU - 5)
|
||||||
|
{
|
||||||
|
//not enough room in this packet
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
chatMsgQueue[i].ClientWrite(segmentTable, outmsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (outmsg.LengthBytes > MsgConstants.MTU)
|
if (outmsg.LengthBytes > MsgConstants.MTU)
|
||||||
{
|
{
|
||||||
@@ -2521,7 +2543,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null)
|
public override void CreateEntityEvent(INetSerializable entity, NetEntityEvent.IData extraData = null)
|
||||||
{
|
{
|
||||||
if (!(entity is IClientSerializable clientSerializable))
|
if (entity is not IClientSerializable clientSerializable)
|
||||||
{
|
{
|
||||||
throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}");
|
throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}");
|
||||||
}
|
}
|
||||||
@@ -2554,46 +2576,9 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public void Quit()
|
public void Quit()
|
||||||
{
|
{
|
||||||
if (ClientPeer is SteamP2PClientPeer || ClientPeer is SteamP2POwnerPeer)
|
|
||||||
{
|
|
||||||
SteamManager.LeaveLobby();
|
|
||||||
}
|
|
||||||
|
|
||||||
GameMain.ModDownloadScreen.Reset();
|
|
||||||
ContentPackageManager.EnabledPackages.Restore();
|
|
||||||
|
|
||||||
CampaignMode.StartRoundCancellationToken?.Cancel();
|
|
||||||
|
|
||||||
ClientPeer?.Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
ClientPeer?.Close(PeerDisconnectPacket.WithReason(DisconnectReason.Disconnected));
|
||||||
ClientPeer = null;
|
|
||||||
|
|
||||||
foreach (var fileTransfer in FileReceiver.ActiveTransfers.ToArray())
|
|
||||||
{
|
|
||||||
FileReceiver.StopTransfer(fileTransfer, deleteFile: true);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ChildServerRelay.Process != null)
|
|
||||||
{
|
|
||||||
int checks = 0;
|
|
||||||
while (ChildServerRelay.Process is { HasExited: false })
|
|
||||||
{
|
|
||||||
if (checks > 10)
|
|
||||||
{
|
|
||||||
ChildServerRelay.ShutDown();
|
|
||||||
}
|
|
||||||
Thread.Sleep(100);
|
|
||||||
checks++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ChildServerRelay.ShutDown();
|
|
||||||
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
|
GUIMessageBox.MessageBoxes.RemoveAll(c => c?.UserData is RoundSummary);
|
||||||
|
|
||||||
characterInfo?.Remove();
|
|
||||||
|
|
||||||
VoipClient?.Dispose();
|
|
||||||
VoipClient = null;
|
|
||||||
GameMain.Client = null;
|
|
||||||
GameMain.GameSession = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendCharacterInfo(string newName = null)
|
public void SendCharacterInfo(string newName = null)
|
||||||
@@ -2601,7 +2586,6 @@ namespace Barotrauma.Networking
|
|||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
|
msg.WriteByte((byte)ClientPacketHeader.UPDATE_CHARACTERINFO);
|
||||||
WriteCharacterInfo(msg, newName);
|
WriteCharacterInfo(msg, newName);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer?.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2641,9 +2625,11 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
IWriteMessage msg = new WriteOnlyMessage();
|
IWriteMessage msg = new WriteOnlyMessage();
|
||||||
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
msg.WriteByte((byte)ClientPacketHeader.UPDATE_LOBBY);
|
||||||
msg.WriteByte((byte)ClientNetObject.VOTE);
|
using (var segmentTable = SegmentTableWriter<ClientNetSegment>.StartWriting(msg))
|
||||||
Voting.ClientWrite(msg, voteType, data);
|
{
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
segmentTable.StartNewSegment(ClientNetSegment.Vote);
|
||||||
|
Voting.ClientWrite(msg, voteType, data);
|
||||||
|
}
|
||||||
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
@@ -2756,7 +2742,6 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||||
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
|
msg.WriteUInt16((UInt16)ClientPermissions.ManageCampaign);
|
||||||
campaign.ClientWrite(msg);
|
campaign.ClientWrite(msg);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2805,7 +2790,6 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
|
msg.WriteUInt16((UInt16)ClientPermissions.SelectSub);
|
||||||
msg.WriteBoolean(isShuttle); msg.WritePadBits();
|
msg.WriteBoolean(isShuttle); msg.WritePadBits();
|
||||||
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
msg.WriteString(sub.MD5Hash.StringRepresentation);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2825,7 +2809,6 @@ namespace Barotrauma.Networking
|
|||||||
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
msg.WriteByte((byte)ClientPacketHeader.SERVER_COMMAND);
|
||||||
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
|
msg.WriteUInt16((UInt16)ClientPermissions.SelectMode);
|
||||||
msg.WriteUInt16((UInt16)modeIndex);
|
msg.WriteUInt16((UInt16)modeIndex);
|
||||||
msg.WriteByte((byte)ServerNetObject.END_OF_MESSAGE);
|
|
||||||
|
|
||||||
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
ClientPeer.Send(msg, DeliveryMethod.Reliable);
|
||||||
}
|
}
|
||||||
@@ -3531,6 +3514,23 @@ namespace Barotrauma.Networking
|
|||||||
eventErrorWritten = true;
|
eventErrorWritten = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void AppendExceptionInfo(ref string errorMsg, Exception e)
|
||||||
|
{
|
||||||
|
if (!errorMsg.EndsWith("\n")) { errorMsg += "\n"; }
|
||||||
|
errorMsg += e.Message + "\n";
|
||||||
|
var innermostException = e.GetInnermost();
|
||||||
|
if (innermostException != e)
|
||||||
|
{
|
||||||
|
// If available, only append the stacktrace of the innermost exception,
|
||||||
|
// because that's the most important one to fix
|
||||||
|
errorMsg += "Inner exception: " + innermostException.Message + "\n" + innermostException.StackTrace.CleanupStackTrace();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
errorMsg += e.StackTrace.CleanupStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
public void ForceTimeOut()
|
public void ForceTimeOut()
|
||||||
{
|
{
|
||||||
|
|||||||
+22
-45
@@ -66,7 +66,7 @@ namespace Barotrauma.Networking
|
|||||||
events.Add(newEvent);
|
events.Add(newEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Write(IWriteMessage msg, NetworkConnection serverConnection)
|
public void Write(in SegmentTableWriter<ClientNetSegment> segmentTable, IWriteMessage msg, NetworkConnection serverConnection)
|
||||||
{
|
{
|
||||||
if (events.Count == 0 || serverConnection == null) return;
|
if (events.Count == 0 || serverConnection == null) return;
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@ namespace Barotrauma.Networking
|
|||||||
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
|
eventLastSent[entityEvent.ID] = (float)Lidgren.Network.NetTime.Now;
|
||||||
}
|
}
|
||||||
|
|
||||||
msg.WriteByte((byte)ClientNetObject.ENTITY_STATE);
|
segmentTable.StartNewSegment(ClientNetSegment.EntityState);
|
||||||
Write(msg, eventsToSync, out _);
|
Write(msg, eventsToSync, out _);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,11 +112,11 @@ namespace Barotrauma.Networking
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
|
/// Read the events from the message, ignoring ones we've already received. Returns false if reading the events fails.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Read(ServerNetObject type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
|
public bool Read(ServerNetSegment type, IReadMessage msg, float sendingTime, List<IServerSerializable> entities)
|
||||||
{
|
{
|
||||||
UInt16 unreceivedEntityEventCount = 0;
|
UInt16 unreceivedEntityEventCount = 0;
|
||||||
|
|
||||||
if (type == ServerNetObject.ENTITY_EVENT_INITIAL)
|
if (type == ServerNetSegment.EntityEventInitial)
|
||||||
{
|
{
|
||||||
unreceivedEntityEventCount = msg.ReadUInt16();
|
unreceivedEntityEventCount = msg.ReadUInt16();
|
||||||
firstNewID = msg.ReadUInt16();
|
firstNewID = msg.ReadUInt16();
|
||||||
@@ -218,43 +218,20 @@ namespace Barotrauma.Networking
|
|||||||
Microsoft.Xna.Framework.Color.Green);
|
Microsoft.Xna.Framework.Color.Green);
|
||||||
}
|
}
|
||||||
lastReceivedID++;
|
lastReceivedID++;
|
||||||
try
|
ReadEvent(msg, entity, sendingTime);
|
||||||
|
msg.ReadPadBits();
|
||||||
|
|
||||||
|
if (msg.BitPosition != msgPosition + msgLength * 8)
|
||||||
{
|
{
|
||||||
ReadEvent(msg, entity, sendingTime);
|
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
|
||||||
msg.ReadPadBits();
|
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
|
||||||
|
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
|
||||||
|
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
|
||||||
|
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
|
||||||
|
|
||||||
if (msg.BitPosition != msgPosition + msgLength * 8)
|
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
||||||
{
|
|
||||||
var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
|
throw new Exception(errorMsg);
|
||||||
ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
|
|
||||||
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
|
|
||||||
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
|
|
||||||
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
|
|
||||||
|
|
||||||
DebugConsole.ThrowError(errorMsg);
|
|
||||||
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
|
||||||
|
|
||||||
//TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick
|
|
||||||
//msg.BitPosition = (int)(msgPosition + msgLength * 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
string errorMsg = $"Failed to read event {thisEventID} for entity \"{entity}\"" +
|
|
||||||
$"{(entity is Entity { ID: var entityId } ? $", id {entityId}" : "")} ";
|
|
||||||
DebugConsole.ThrowError(errorMsg, e);
|
|
||||||
|
|
||||||
errorMsg += $"({e.Message})! (MidRoundSyncing: {thisClient.MidRoundSyncing})\n{e.StackTrace.CleanupStackTrace()}";
|
|
||||||
errorMsg += "\nPrevious entities:";
|
|
||||||
for (int j = entities.Count - 2; j >= 0; j--)
|
|
||||||
{
|
|
||||||
errorMsg += "\n" + (entities[j] == null ? "NULL" : entities[j].ToString());
|
|
||||||
}
|
|
||||||
|
|
||||||
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
|
|
||||||
GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
|
|
||||||
msg.BitPosition = (int)(msgPosition + msgLength * 8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -277,16 +254,12 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public void Clear()
|
public void Clear()
|
||||||
{
|
{
|
||||||
ID = 0;
|
|
||||||
|
|
||||||
lastReceivedID = 0;
|
lastReceivedID = 0;
|
||||||
|
|
||||||
firstNewID = null;
|
firstNewID = null;
|
||||||
|
|
||||||
events.Clear();
|
|
||||||
eventLastSent.Clear();
|
eventLastSent.Clear();
|
||||||
|
|
||||||
MidRoundSyncingDone = false;
|
MidRoundSyncingDone = false;
|
||||||
|
|
||||||
|
ClearSelf();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -297,6 +270,10 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
ID = 0;
|
ID = 0;
|
||||||
events.Clear();
|
events.Clear();
|
||||||
|
if (thisClient != null)
|
||||||
|
{
|
||||||
|
thisClient.LastSentEntityEventID = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
partial class OrderChatMessage : ChatMessage
|
partial class OrderChatMessage : ChatMessage
|
||||||
{
|
{
|
||||||
public override void ClientWrite(IWriteMessage msg)
|
public override void ClientWrite(in SegmentTableWriter<ClientNetSegment> segmentTableWriter, IWriteMessage msg)
|
||||||
{
|
{
|
||||||
msg.WriteByte((byte)ClientNetObject.CHAT_MESSAGE);
|
segmentTableWriter.StartNewSegment(ClientNetSegment.ChatMessage);
|
||||||
msg.WriteUInt16(NetStateID);
|
msg.WriteUInt16(NetStateID);
|
||||||
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMessageType.Order, 0, Enum.GetValues(typeof(ChatMessageType)).Length - 1);
|
||||||
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
msg.WriteRangedInteger((int)ChatMode.None, 0, Enum.GetValues(typeof(ChatMode)).Length - 1);
|
||||||
|
|||||||
+4
-8
@@ -91,15 +91,11 @@ namespace Barotrauma.Networking
|
|||||||
ToolBox.ThrowIfNull(netClient);
|
ToolBox.ThrowIfNull(netClient);
|
||||||
ToolBox.ThrowIfNull(incomingLidgrenMessages);
|
ToolBox.ThrowIfNull(incomingLidgrenMessages);
|
||||||
|
|
||||||
if (isOwner && !(ChildServerRelay.Process is { HasExited: false }))
|
if (isOwner && !ChildServerRelay.IsProcessAlive)
|
||||||
{
|
{
|
||||||
|
var gameClient = GameMain.Client;
|
||||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
gameClient?.CreateServerCrashMessage();
|
||||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
|
||||||
{
|
|
||||||
GameMain.MainMenuScreen.Select();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +107,7 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
|
foreach (NetIncomingMessage inc in incomingLidgrenMessages)
|
||||||
{
|
{
|
||||||
if (!inc.SenderConnection.RemoteEndPoint.Equals(lidgrenEndpoint.NetEndpoint))
|
if (!inc.SenderConnection.RemoteEndPoint.EquivalentTo(lidgrenEndpoint.NetEndpoint))
|
||||||
{
|
{
|
||||||
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
DebugConsole.AddWarning($"Mismatched endpoint: expected {lidgrenEndpoint.NetEndpoint}, got {inc.SenderConnection.RemoteEndPoint}");
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
+3
-9
@@ -187,15 +187,11 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
if (!isActive) { return; }
|
if (!isActive) { return; }
|
||||||
|
|
||||||
if (ChildServerRelay.HasShutDown || !(ChildServerRelay.Process is { HasExited: false }))
|
if (ChildServerRelay.HasShutDown || !ChildServerRelay.IsProcessAlive)
|
||||||
{
|
{
|
||||||
|
var gameClient = GameMain.Client;
|
||||||
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
Close(PeerDisconnectPacket.WithReason(DisconnectReason.ServerCrashed));
|
||||||
var msgBox = new GUIMessageBox(TextManager.Get("ConnectionLost"), ChildServerRelay.CrashMessage);
|
gameClient?.CreateServerCrashMessage();
|
||||||
msgBox.Buttons[0].OnClicked += (btn, obj) =>
|
|
||||||
{
|
|
||||||
GameMain.MainMenuScreen.Select();
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -401,8 +397,6 @@ namespace Barotrauma.Networking
|
|||||||
ClosePeerSession(remotePeers[i]);
|
ClosePeerSession(remotePeers[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
ChildServerRelay.ClosePipes();
|
|
||||||
|
|
||||||
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
callbacks.OnDisconnect.Invoke(peerDisconnectPacket);
|
||||||
|
|
||||||
SteamManager.LeaveLobby();
|
SteamManager.LeaveLobby();
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
static class PingUtils
|
static class PingUtils
|
||||||
{
|
{
|
||||||
private static readonly Dictionary<IPAddress, int> activePings = new Dictionary<IPAddress, int>();
|
private static readonly Dictionary<IPEndPoint, int> activePings = new Dictionary<IPEndPoint, int>();
|
||||||
|
|
||||||
private static bool steamPingInfoReady;
|
private static bool steamPingInfoReady;
|
||||||
|
|
||||||
@@ -36,9 +36,9 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
switch (serverInfo.Endpoint)
|
switch (serverInfo.Endpoint)
|
||||||
{
|
{
|
||||||
case LidgrenEndpoint { NetEndpoint: { Address: var address } }:
|
case LidgrenEndpoint { NetEndpoint: var endPoint }:
|
||||||
|
|
||||||
GetIPAddressPing(serverInfo, address, onPingDiscovered);
|
GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
|
||||||
break;
|
break;
|
||||||
case SteamP2PEndpoint steamP2PEndpoint:
|
case SteamP2PEndpoint steamP2PEndpoint:
|
||||||
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
|
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
|
||||||
@@ -131,9 +131,9 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action<ServerInfo> onPingDiscovered)
|
private static void GetIPAddressPing(ServerInfo serverInfo, IPEndPoint endPoint, Action<ServerInfo> onPingDiscovered)
|
||||||
{
|
{
|
||||||
if (IPAddress.IsLoopback(address))
|
if (IPAddress.IsLoopback(endPoint.Address))
|
||||||
{
|
{
|
||||||
serverInfo.Ping = Option<int>.Some(0);
|
serverInfo.Ping = Option<int>.Some(0);
|
||||||
onPingDiscovered(serverInfo);
|
onPingDiscovered(serverInfo);
|
||||||
@@ -142,24 +142,24 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
lock (activePings)
|
lock (activePings)
|
||||||
{
|
{
|
||||||
if (activePings.ContainsKey(address)) { return; }
|
if (activePings.ContainsKey(endPoint)) { return; }
|
||||||
activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0);
|
activePings.Add(endPoint, activePings.Any() ? activePings.Values.Max() + 1 : 0);
|
||||||
}
|
}
|
||||||
serverInfo.Ping = Option<int>.None();
|
serverInfo.Ping = Option<int>.None();
|
||||||
TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000),
|
TaskPool.Add($"PingServerAsync ({endPoint})", PingServerAsync(endPoint, 1000),
|
||||||
rtt =>
|
rtt =>
|
||||||
{
|
{
|
||||||
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); }
|
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option<int>.None(); }
|
||||||
onPingDiscovered(serverInfo);
|
onPingDiscovered(serverInfo);
|
||||||
lock (activePings)
|
lock (activePings)
|
||||||
{
|
{
|
||||||
activePings.Remove(address);
|
activePings.Remove(endPoint);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<Option<int>> PingServerAsync(IPAddress ipAddress, int timeOut)
|
private static async Task<Option<int>> PingServerAsync(IPEndPoint endPoint, int timeOut)
|
||||||
{
|
{
|
||||||
await Task.Yield();
|
await Task.Yield();
|
||||||
bool shouldGo = false;
|
bool shouldGo = false;
|
||||||
@@ -167,21 +167,21 @@ namespace Barotrauma.Networking
|
|||||||
{
|
{
|
||||||
lock (activePings)
|
lock (activePings)
|
||||||
{
|
{
|
||||||
shouldGo = activePings.Count(kvp => kvp.Value < activePings[ipAddress]) < 25;
|
shouldGo = activePings.Count(kvp => kvp.Value < activePings[endPoint]) < 25;
|
||||||
}
|
}
|
||||||
await Task.Delay(25);
|
await Task.Delay(25);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ipAddress == null) { return Option<int>.None(); }
|
if (endPoint?.Address == null) { return Option<int>.None(); }
|
||||||
|
|
||||||
//don't attempt to ping if the address is IPv6 and it's not supported
|
//don't attempt to ping if the address is IPv6 and it's not supported
|
||||||
if (ipAddress.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
|
if (endPoint.Address.AddressFamily == AddressFamily.InterNetworkV6 && !Socket.OSSupportsIPv6) { return Option<int>.None(); }
|
||||||
|
|
||||||
Ping ping = new Ping();
|
Ping ping = new Ping();
|
||||||
byte[] buffer = new byte[32];
|
byte[] buffer = new byte[32];
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PingReply pingReply = await ping.SendPingAsync(ipAddress, timeOut, buffer, new PingOptions(128, true));
|
PingReply pingReply = await ping.SendPingAsync(endPoint.Address, timeOut, buffer, new PingOptions(128, true));
|
||||||
|
|
||||||
return pingReply.Status switch
|
return pingReply.Status switch
|
||||||
{
|
{
|
||||||
@@ -191,9 +191,9 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + ipAddress, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
|
GameAnalyticsManager.AddErrorEventOnce("ServerListScreen.PingServer:PingException" + endPoint.Address, GameAnalyticsManager.ErrorSeverity.Warning, "Failed to ping a server - " + (ex?.InnerException?.Message ?? ex.Message));
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
DebugConsole.NewMessage("Failed to ping a server (" + ipAddress + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
|
DebugConsole.NewMessage("Failed to ping a server (" + endPoint.Address + ") - " + (ex?.InnerException?.Message ?? ex.Message), Color.Red);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return Option<int>.None();
|
return Option<int>.None();
|
||||||
|
|||||||
@@ -140,7 +140,7 @@ namespace Barotrauma.Networking
|
|||||||
MaxPlayers = incMsg.ReadByte();
|
MaxPlayers = incMsg.ReadByte();
|
||||||
HasPassword = incMsg.ReadBoolean();
|
HasPassword = incMsg.ReadBoolean();
|
||||||
IsPublic = incMsg.ReadBoolean();
|
IsPublic = incMsg.ReadBoolean();
|
||||||
GameMain.NetLobbyScreen.SetPublic(IsPublic);
|
GameMain.Client?.SetLobbyPublic(IsPublic);
|
||||||
AllowFileTransfers = incMsg.ReadBoolean();
|
AllowFileTransfers = incMsg.ReadBoolean();
|
||||||
incMsg.ReadPadBits();
|
incMsg.ReadPadBits();
|
||||||
TickRate = incMsg.ReadRangedInteger(1, 60);
|
TickRate = incMsg.ReadRangedInteger(1, 60);
|
||||||
@@ -367,6 +367,17 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
//***********************************************
|
//***********************************************
|
||||||
|
|
||||||
|
//changing server visibility on the fly is not supported in dedicated servers
|
||||||
|
if (GameMain.Client?.ClientPeer is not LidgrenClientPeer)
|
||||||
|
{
|
||||||
|
var isPublic = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
|
||||||
|
TextManager.Get("publicserver"))
|
||||||
|
{
|
||||||
|
ToolTip = TextManager.Get("publicservertooltip")
|
||||||
|
};
|
||||||
|
GetPropertyData(nameof(IsPublic)).AssignGUIComponent(isPublic);
|
||||||
|
}
|
||||||
|
|
||||||
// Sub Selection
|
// Sub Selection
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsSubSelection"), font: GUIStyle.SubHeadingFont);
|
||||||
var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true)
|
var selectionFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), isHorizontal: true)
|
||||||
@@ -475,9 +486,10 @@ namespace Barotrauma.Networking
|
|||||||
// game settings
|
// game settings
|
||||||
//--------------------------------------------------------------------------------
|
//--------------------------------------------------------------------------------
|
||||||
|
|
||||||
var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center)) { };
|
var roundsTab = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), settingsTabs[(int)SettingsTab.Rounds].RectTransform, Anchor.Center));
|
||||||
|
var roundsContent = new GUIListBox(new RectTransform(Vector2.One, roundsTab.RectTransform, Anchor.Center), style: "GUIListBoxNoBorder").Content;
|
||||||
|
|
||||||
GUILayoutGroup playStyleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), roundsTab.RectTransform));
|
GUILayoutGroup playStyleLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), roundsContent.RectTransform));
|
||||||
// Play Style Selection
|
// Play Style Selection
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), playStyleLayout.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUIStyle.SubHeadingFont);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), playStyleLayout.RectTransform), TextManager.Get("ServerSettingsPlayStyle"), font: GUIStyle.SubHeadingFont);
|
||||||
var playstyleList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), playStyleLayout.RectTransform))
|
var playstyleList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), playStyleLayout.RectTransform))
|
||||||
@@ -502,7 +514,7 @@ 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));
|
||||||
|
|
||||||
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), roundsTab.RectTransform))
|
GUILayoutGroup sliderLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.35f), roundsContent.RectTransform))
|
||||||
{
|
{
|
||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
@@ -608,7 +620,7 @@ namespace Barotrauma.Networking
|
|||||||
};
|
};
|
||||||
slider.OnMoved(slider, slider.BarScroll);
|
slider.OnMoved(slider, slider.BarScroll);
|
||||||
|
|
||||||
GUILayoutGroup losModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsTab.RectTransform));
|
GUILayoutGroup losModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsContent.RectTransform));
|
||||||
|
|
||||||
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), losModeLayout.RectTransform),
|
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), losModeLayout.RectTransform),
|
||||||
TextManager.Get("LosEffect"));
|
TextManager.Get("LosEffect"));
|
||||||
@@ -629,7 +641,30 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
GetPropertyData(nameof(LosMode)).AssignGUIComponent(losModeRadioButtonGroup);
|
GetPropertyData(nameof(LosMode)).AssignGUIComponent(losModeRadioButtonGroup);
|
||||||
|
|
||||||
GUILayoutGroup numberLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), roundsTab.RectTransform))
|
GUILayoutGroup healthBarModeLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.14f), roundsContent.RectTransform));
|
||||||
|
|
||||||
|
var healthBarModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.4f), healthBarModeLayout.RectTransform),
|
||||||
|
TextManager.Get("ShowEnemyHealthBars"));
|
||||||
|
|
||||||
|
var healthBarModeRadioButtonLayout
|
||||||
|
= new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.6f), healthBarModeLayout.RectTransform),
|
||||||
|
isHorizontal: true)
|
||||||
|
{
|
||||||
|
Stretch = true
|
||||||
|
};
|
||||||
|
|
||||||
|
var healthBarModeRadioButtonGroup = new GUIRadioButtonGroup();
|
||||||
|
EnemyHealthBarMode[] healthBarModeModes = Enum.GetValues<EnemyHealthBarMode>();
|
||||||
|
for (int i = 0; i < healthBarModeModes.Length; i++)
|
||||||
|
{
|
||||||
|
var losTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), healthBarModeRadioButtonLayout.RectTransform),
|
||||||
|
TextManager.Get($"ShowEnemyHealthBars.{healthBarModeModes[i]}"),
|
||||||
|
font: GUIStyle.SmallFont, style: "GUIRadioButton");
|
||||||
|
healthBarModeRadioButtonGroup.AddRadioButton(i, losTick);
|
||||||
|
}
|
||||||
|
GetPropertyData(nameof(ShowEnemyHealthBars)).AssignGUIComponent(healthBarModeRadioButtonGroup);
|
||||||
|
|
||||||
|
GUILayoutGroup numberLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.3f), roundsContent.RectTransform))
|
||||||
{
|
{
|
||||||
Stretch = true
|
Stretch = true
|
||||||
};
|
};
|
||||||
@@ -651,7 +686,7 @@ namespace Barotrauma.Networking
|
|||||||
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), numberLayout.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
|
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), numberLayout.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
|
||||||
GetPropertyData(nameof(DisableBotConversations)).AssignGUIComponent(disableBotConversationsBox);
|
GetPropertyData(nameof(DisableBotConversations)).AssignGUIComponent(disableBotConversationsBox);
|
||||||
|
|
||||||
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsTab.RectTransform), isHorizontal: true)
|
GUILayoutGroup buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsContent.RectTransform), isHorizontal: true)
|
||||||
{
|
{
|
||||||
Stretch = true,
|
Stretch = true,
|
||||||
RelativeSpacing = 0.05f
|
RelativeSpacing = 0.05f
|
||||||
|
|||||||
@@ -17,9 +17,7 @@ namespace Barotrauma.Networking
|
|||||||
get;
|
get;
|
||||||
private set;
|
private set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<string> CaptureDeviceNames =>
|
|
||||||
Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
|
|
||||||
|
|
||||||
private readonly IntPtr captureDevice;
|
private readonly IntPtr captureDevice;
|
||||||
|
|
||||||
@@ -169,6 +167,11 @@ namespace Barotrauma.Networking
|
|||||||
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, storedBufferID);
|
Create(GameSettings.CurrentConfig.Audio.VoiceCaptureDevice, storedBufferID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IReadOnlyList<string> GetCaptureDeviceNames()
|
||||||
|
{
|
||||||
|
return Alc.GetStringList(IntPtr.Zero, OpenAL.Alc.CaptureDeviceSpecifier);
|
||||||
|
}
|
||||||
|
|
||||||
IntPtr nativeBuffer;
|
IntPtr nativeBuffer;
|
||||||
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
readonly short[] uncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||||
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
readonly short[] prevUncompressedBuffer = new short[VoipConfig.BUFFER_SIZE];
|
||||||
@@ -260,6 +263,13 @@ namespace Barotrauma.Networking
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Screen.Selected is ModDownloadScreen)
|
||||||
|
{
|
||||||
|
allowEnqueue = false;
|
||||||
|
captureTimer = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (allowEnqueue || captureTimer > 0)
|
if (allowEnqueue || captureTimer > 0)
|
||||||
{
|
{
|
||||||
LastEnqueueAudio = DateTime.Now;
|
LastEnqueueAudio = DateTime.Now;
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
using Barotrauma.Sounds;
|
using Barotrauma.Items.Components;
|
||||||
|
using Barotrauma.Sounds;
|
||||||
|
using Microsoft.Xna.Framework;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using Microsoft.Xna.Framework;
|
|
||||||
using Barotrauma.Items.Components;
|
|
||||||
|
|
||||||
namespace Barotrauma.Networking
|
namespace Barotrauma.Networking
|
||||||
{
|
{
|
||||||
class VoipClient : IDisposable
|
class VoipClient : IDisposable
|
||||||
{
|
{
|
||||||
private GameClient gameClient;
|
/// <summary>
|
||||||
private ClientPeer netClient;
|
/// The "near" range of the voice chat (a percentage of either SpeakRange or radio range), further than this the volume starts to diminish
|
||||||
|
/// </summary>
|
||||||
|
const float RangeNear = 0.4f;
|
||||||
|
|
||||||
|
private readonly GameClient gameClient;
|
||||||
|
private readonly ClientPeer netClient;
|
||||||
private DateTime lastSendTime;
|
private DateTime lastSendTime;
|
||||||
private List<VoipQueue> queues;
|
private readonly List<VoipQueue> queues;
|
||||||
|
|
||||||
private UInt16 storedBufferID = 0;
|
private UInt16 storedBufferID = 0;
|
||||||
|
|
||||||
@@ -32,13 +35,13 @@ namespace Barotrauma.Networking
|
|||||||
|
|
||||||
public void RegisterQueue(VoipQueue queue)
|
public void RegisterQueue(VoipQueue queue)
|
||||||
{
|
{
|
||||||
if (queue == VoipCapture.Instance) return;
|
if (queue == VoipCapture.Instance) { return; }
|
||||||
if (!queues.Contains(queue)) queues.Add(queue);
|
if (!queues.Contains(queue)) { queues.Add(queue); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UnregisterQueue(VoipQueue queue)
|
public void UnregisterQueue(VoipQueue queue)
|
||||||
{
|
{
|
||||||
if (queues.Contains(queue)) queues.Remove(queue);
|
if (queues.Contains(queue)) { queues.Remove(queue); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SendToServer()
|
public void SendToServer()
|
||||||
@@ -85,6 +88,7 @@ namespace Barotrauma.Networking
|
|||||||
public void Read(IReadMessage msg)
|
public void Read(IReadMessage msg)
|
||||||
{
|
{
|
||||||
byte queueId = msg.ReadByte();
|
byte queueId = msg.ReadByte();
|
||||||
|
float distanceFactor = msg.ReadRangedSingle(0.0f, 1.0f, 8);
|
||||||
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
|
VoipQueue queue = queues.Find(q => q.QueueID == queueId);
|
||||||
|
|
||||||
if (queue == null)
|
if (queue == null)
|
||||||
@@ -105,9 +109,12 @@ namespace Barotrauma.Networking
|
|||||||
client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue);
|
client.VoipSound = new VoipSound(client.Name, GameMain.SoundManager, client.VoipQueue);
|
||||||
}
|
}
|
||||||
GameMain.SoundManager.ForceStreamUpdate();
|
GameMain.SoundManager.ForceStreamUpdate();
|
||||||
|
client.RadioNoise = 0.0f;
|
||||||
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
|
if (client.Character != null && !client.Character.IsDead && !client.Character.Removed && client.Character.SpeechImpediment <= 100.0f)
|
||||||
{
|
{
|
||||||
|
float speechImpedimentMultiplier = 1.0f - client.Character.SpeechImpediment / 100.0f;
|
||||||
|
bool spectating = Character.Controlled == null;
|
||||||
|
float rangeMultiplier = spectating ? 2.0f : 1.0f;
|
||||||
WifiComponent radio = null;
|
WifiComponent radio = null;
|
||||||
var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default;
|
var messageType = !client.VoipQueue.ForceLocal && ChatMessage.CanUseRadio(client.Character, out radio) ? ChatMessageType.Radio : ChatMessageType.Default;
|
||||||
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
client.Character.ShowSpeechBubble(1.25f, ChatMessage.MessageColor[(int)messageType]);
|
||||||
@@ -115,11 +122,17 @@ namespace Barotrauma.Networking
|
|||||||
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
client.VoipSound.UseRadioFilter = messageType == ChatMessageType.Radio && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters;
|
||||||
if (messageType == ChatMessageType.Radio)
|
if (messageType == ChatMessageType.Radio)
|
||||||
{
|
{
|
||||||
client.VoipSound.SetRange(radio.Range * 0.8f, radio.Range);
|
client.VoipSound.SetRange(radio.Range * RangeNear * speechImpedimentMultiplier * rangeMultiplier, radio.Range * speechImpedimentMultiplier * rangeMultiplier);
|
||||||
|
if (distanceFactor > RangeNear && !spectating)
|
||||||
|
{
|
||||||
|
//noise starts increasing exponentially after 40% range
|
||||||
|
client.RadioNoise = MathF.Pow(MathUtils.InverseLerp(RangeNear, 1.0f, distanceFactor), 2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
client.VoipSound.SetRange(ChatMessage.SpeakRange * 0.4f, ChatMessage.SpeakRange);
|
|
||||||
|
client.VoipSound.SetRange(ChatMessage.SpeakRange * RangeNear * speechImpedimentMultiplier * rangeMultiplier, ChatMessage.SpeakRange * speechImpedimentMultiplier * rangeMultiplier);
|
||||||
}
|
}
|
||||||
client.VoipSound.UseMuffleFilter =
|
client.VoipSound.UseMuffleFilter =
|
||||||
messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters &&
|
messageType != ChatMessageType.Radio && Character.Controlled != null && !GameSettings.CurrentConfig.Audio.DisableVoiceChatFilters &&
|
||||||
|
|||||||
@@ -82,6 +82,9 @@ namespace Barotrauma.Particles
|
|||||||
[Editable, Serialize(false, IsPropertySaveable.Yes)]
|
[Editable, Serialize(false, IsPropertySaveable.Yes)]
|
||||||
public bool CopyEntityAngle { get; set; }
|
public bool CopyEntityAngle { get; set; }
|
||||||
|
|
||||||
|
[Editable, Serialize(true, IsPropertySaveable.Yes, description: "Should the entity heading direction be applied to the particle rotation? Only affects after flipping the texture and when CopyEntityAngle is true.")]
|
||||||
|
public bool CopyEntityDir { get; set; }
|
||||||
|
|
||||||
[Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)]
|
[Editable, Serialize("1,1,1,1", IsPropertySaveable.Yes)]
|
||||||
public Color ColorMultiplier { get; set; }
|
public Color ColorMultiplier { get; set; }
|
||||||
|
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ namespace Barotrauma
|
|||||||
switch (MouseButton)
|
switch (MouseButton)
|
||||||
{
|
{
|
||||||
case MouseButton.None:
|
case MouseButton.None:
|
||||||
|
if (Key == Keys.None) { return false; }
|
||||||
return PlayerInput.KeyDown(Key);
|
return PlayerInput.KeyDown(Key);
|
||||||
case MouseButton.PrimaryMouse:
|
case MouseButton.PrimaryMouse:
|
||||||
return PlayerInput.PrimaryMouseButtonHeld();
|
return PlayerInput.PrimaryMouseButtonHeld();
|
||||||
@@ -88,6 +89,7 @@ namespace Barotrauma
|
|||||||
switch (MouseButton)
|
switch (MouseButton)
|
||||||
{
|
{
|
||||||
case MouseButton.None:
|
case MouseButton.None:
|
||||||
|
if (Key == Keys.None) { return false; }
|
||||||
return PlayerInput.KeyHit(Key);
|
return PlayerInput.KeyHit(Key);
|
||||||
case MouseButton.PrimaryMouse:
|
case MouseButton.PrimaryMouse:
|
||||||
return PlayerInput.PrimaryMouseButtonClicked();
|
return PlayerInput.PrimaryMouseButtonClicked();
|
||||||
|
|||||||
@@ -112,10 +112,23 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
|
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
|
|
||||||
sb.AppendLine("Barotrauma Client crash report (generated on " + DateTime.Now + ")");
|
sb.AppendLine("Barotrauma Client crash report (generated on " + DateTime.Now + ")");
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
|
sb.AppendLine("Barotrauma seems to have crashed. Sorry for the inconvenience! ");
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
|
|
||||||
|
string dxgiErrorHelpText =
|
||||||
|
#if WINDOWS
|
||||||
|
GetDXGIErrorHelpText(game, exception);
|
||||||
|
#else
|
||||||
|
string.Empty;
|
||||||
|
#endif
|
||||||
|
if (!string.IsNullOrEmpty(dxgiErrorHelpText))
|
||||||
|
{
|
||||||
|
sb.AppendLine(dxgiErrorHelpText);
|
||||||
|
sb.AppendLine();
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -135,7 +148,7 @@ namespace Barotrauma
|
|||||||
XDocument newDoc = new XDocument(newElement);
|
XDocument newDoc = new XDocument(newElement);
|
||||||
newDoc.Save(GameSettings.PlayerConfigPath);
|
newDoc.Save(GameSettings.PlayerConfigPath);
|
||||||
sb.AppendLine("To prevent further startup errors, installed mods will be disabled the next time you launch the game.");
|
sb.AppendLine("To prevent further startup errors, installed mods will be disabled the next time you launch the game.");
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -148,7 +161,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
sb.AppendLine(exeHash.StringRepresentation);
|
sb.AppendLine(exeHash.StringRepresentation);
|
||||||
}
|
}
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
sb.AppendLine("Game version " + GameMain.Version +
|
sb.AppendLine("Game version " + GameMain.Version +
|
||||||
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
|
" (" + AssemblyInfo.BuildString + ", branch " + AssemblyInfo.GitBranch + ", revision " + AssemblyInfo.GitRevision + ")");
|
||||||
sb.AppendLine($"Graphics mode: {GameSettings.CurrentConfig.Graphics.Width}x{GameSettings.CurrentConfig.Graphics.Height} ({GameSettings.CurrentConfig.Graphics.DisplayMode})");
|
sb.AppendLine($"Graphics mode: {GameSettings.CurrentConfig.Graphics.Width}x{GameSettings.CurrentConfig.Graphics.Height} ({GameSettings.CurrentConfig.Graphics.DisplayMode})");
|
||||||
@@ -171,7 +184,7 @@ namespace Barotrauma
|
|||||||
sb.AppendLine("Client (" + (GameMain.Client.GameStarted ? "Round had started)" : "Round hadn't been started)"));
|
sb.AppendLine("Client (" + (GameMain.Client.GameStarted ? "Round had started)" : "Round hadn't been started)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
sb.AppendLine("System info:");
|
sb.AppendLine("System info:");
|
||||||
sb.AppendLine(" Operating system: " + System.Environment.OSVersion + (System.Environment.Is64BitOperatingSystem ? " 64 bit" : " x86"));
|
sb.AppendLine(" Operating system: " + System.Environment.OSVersion + (System.Environment.Is64BitOperatingSystem ? " 64 bit" : " x86"));
|
||||||
|
|
||||||
@@ -201,13 +214,14 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
sb.AppendLine("Exception: " + exception.Message + " (" + exception.GetType().ToString() + ")");
|
sb.AppendLine($"Exception: {exception.Message} ({exception.GetType()})");
|
||||||
#if WINDOWS
|
#if WINDOWS
|
||||||
if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
|
if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
|
||||||
{
|
{
|
||||||
var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
|
var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
|
||||||
sb.AppendLine("Device removed reason: " + dxDevice.DeviceRemovedReason.ToString());
|
var descriptor = ResultDescriptor.Find(dxDevice.DeviceRemovedReason)?.ApiCode ?? "UNKNOWN";
|
||||||
|
sb.AppendLine($"Device removed reason: {descriptor} ({dxDevice.DeviceRemovedReason})");
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (exception.TargetSite != null)
|
if (exception.TargetSite != null)
|
||||||
@@ -219,7 +233,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
sb.AppendLine("Stack trace: ");
|
sb.AppendLine("Stack trace: ");
|
||||||
sb.AppendLine(exception.StackTrace.CleanupStackTrace());
|
sb.AppendLine(exception.StackTrace.CleanupStackTrace());
|
||||||
sb.AppendLine("\n");
|
sb.AppendLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (exception.InnerException != null)
|
if (exception.InnerException != null)
|
||||||
@@ -260,18 +274,43 @@ namespace Barotrauma
|
|||||||
|
|
||||||
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs
|
if (GameSettings.CurrentConfig.SaveDebugConsoleLogs
|
||||||
|| GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); }
|
|| GameSettings.CurrentConfig.VerboseLogging) { DebugConsole.SaveLogs(); }
|
||||||
|
|
||||||
|
string msg = string.Empty;
|
||||||
if (GameAnalyticsManager.SendUserStatistics)
|
if (GameAnalyticsManager.SendUserStatistics)
|
||||||
{
|
{
|
||||||
CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.", filePath);
|
msg = "A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.";
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" +
|
msg = "A crash report (\"" + filePath + "\") was saved in the root folder of the game. The error was not sent to the developers because user statistics have been disabled, but" +
|
||||||
" if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/", filePath);
|
" if you'd like to help fix this bug, you may post it on Barotrauma's GitHub issue tracker: https://github.com/Regalis11/Barotrauma/issues/";
|
||||||
}
|
}
|
||||||
|
if (string.IsNullOrEmpty(dxgiErrorHelpText))
|
||||||
|
{
|
||||||
|
msg += "\n\n" + dxgiErrorHelpText;
|
||||||
|
}
|
||||||
|
CrashMessageBox(msg, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if WINDOWS
|
||||||
|
private static string GetDXGIErrorHelpText(GameMain game, Exception exception)
|
||||||
|
{
|
||||||
|
string text = string.Empty;
|
||||||
|
if (exception is SharpDXException sharpDxException && ((uint)sharpDxException.HResult) == 0x887A0005)
|
||||||
|
{
|
||||||
|
var dxDevice = (SharpDX.Direct3D11.Device)game.GraphicsDevice.Handle;
|
||||||
|
var descriptor = ResultDescriptor.Find(dxDevice.DeviceRemovedReason)?.ApiCode ?? "UNKNOWN";
|
||||||
|
|
||||||
|
text +=
|
||||||
|
$"The crash was caused by the DirectX error {descriptor} ({dxDevice.DeviceRemovedReason}). " +
|
||||||
|
"This is a common DirectX error that can be related to various different issues, such as outdated drivers, RAM problems or an overclocked or otherwise overstressed GPU. " +
|
||||||
|
"There are several potential ways to fix the issue: ensuring your graphics drivers and DirectX installation are up-to-date, disabling overclocking and adjusting various GPU-specific settings. " +
|
||||||
|
$"You may also be able to find potential solutions to the problem by using the error code {descriptor} ({dxDevice.DeviceRemovedReason}) and your GPU manufacturer as search terms.";
|
||||||
|
}
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private static IntPtr nvApi64Dll = IntPtr.Zero;
|
private static IntPtr nvApi64Dll = IntPtr.Zero;
|
||||||
private static void EnableNvOptimus()
|
private static void EnableNvOptimus()
|
||||||
{
|
{
|
||||||
@@ -287,11 +326,11 @@ namespace Barotrauma
|
|||||||
|
|
||||||
private static void FreeNvOptimus()
|
private static void FreeNvOptimus()
|
||||||
{
|
{
|
||||||
#warning TODO: determine if we can do this safely
|
#warning TODO: determine if we can do this safely
|
||||||
//NativeLibrary.Free(nvApi64Dll);
|
//NativeLibrary.Free(nvApi64Dll);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
public struct CampaignSettingElements
|
public struct CampaignSettingElements
|
||||||
{
|
{
|
||||||
|
public SettingValue<bool> TutorialEnabled;
|
||||||
public SettingValue<bool> RadiationEnabled;
|
public SettingValue<bool> RadiationEnabled;
|
||||||
public SettingValue<int> MaxMissionCount;
|
public SettingValue<int> MaxMissionCount;
|
||||||
public SettingValue<StartingBalanceAmount> StartingFunds;
|
public SettingValue<StartingBalanceAmount> StartingFunds;
|
||||||
@@ -114,6 +115,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
return new CampaignSettings(element: null)
|
return new CampaignSettings(element: null)
|
||||||
{
|
{
|
||||||
|
TutorialEnabled = TutorialEnabled.GetValue(),
|
||||||
RadiationEnabled = RadiationEnabled.GetValue(),
|
RadiationEnabled = RadiationEnabled.GetValue(),
|
||||||
MaxMissionCount = MaxMissionCount.GetValue(),
|
MaxMissionCount = MaxMissionCount.GetValue(),
|
||||||
StartingBalanceAmount = StartingFunds.GetValue(),
|
StartingBalanceAmount = StartingFunds.GetValue(),
|
||||||
@@ -159,7 +161,7 @@ namespace Barotrauma
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings)
|
protected static CampaignSettingElements CreateCampaignSettingList(GUIComponent parent, CampaignSettings prevSettings, bool isSinglePlayer)
|
||||||
{
|
{
|
||||||
const float verticalSize = 0.14f;
|
const float verticalSize = 0.14f;
|
||||||
|
|
||||||
@@ -180,6 +182,9 @@ namespace Barotrauma
|
|||||||
Spacing = GUI.IntScale(5)
|
Spacing = GUI.IntScale(5)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
SettingValue<bool> tutorialEnabled = isSinglePlayer ?
|
||||||
|
CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableTutorial"), TextManager.Get("campaignoption.enabletutorial.tooltip"), prevSettings.TutorialEnabled, verticalSize) :
|
||||||
|
new SettingValue<bool>(() => false, b => { });
|
||||||
SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize);
|
SettingValue<bool> radiationEnabled = CreateTickbox(settingsList.Content, TextManager.Get("CampaignOption.EnableRadiation"), TextManager.Get("campaignoption.enableradiation.tooltip"), prevSettings.RadiationEnabled, verticalSize);
|
||||||
|
|
||||||
ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray();
|
ImmutableArray<SettingCarouselElement<Identifier>> startingSetOptions = StartItemSet.Sets.OrderBy(s => s.Order).Select(set => new SettingCarouselElement<Identifier>(set.Identifier, $"startitemset.{set.Identifier}")).ToImmutableArray();
|
||||||
@@ -214,6 +219,7 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
if (o is CampaignSettings settings)
|
if (o is CampaignSettings settings)
|
||||||
{
|
{
|
||||||
|
tutorialEnabled.SetValue(isSinglePlayer && settings.TutorialEnabled);
|
||||||
radiationEnabled.SetValue(settings.RadiationEnabled);
|
radiationEnabled.SetValue(settings.RadiationEnabled);
|
||||||
maxMissionCountInput.SetValue(settings.MaxMissionCount);
|
maxMissionCountInput.SetValue(settings.MaxMissionCount);
|
||||||
startingFundsInput.SetValue(settings.StartingBalanceAmount);
|
startingFundsInput.SetValue(settings.StartingBalanceAmount);
|
||||||
@@ -226,6 +232,7 @@ namespace Barotrauma
|
|||||||
|
|
||||||
return new CampaignSettingElements
|
return new CampaignSettingElements
|
||||||
{
|
{
|
||||||
|
TutorialEnabled = tutorialEnabled,
|
||||||
RadiationEnabled = radiationEnabled,
|
RadiationEnabled = radiationEnabled,
|
||||||
MaxMissionCount = maxMissionCountInput,
|
MaxMissionCount = maxMissionCountInput,
|
||||||
StartingFunds = startingFundsInput,
|
StartingFunds = startingFundsInput,
|
||||||
|
|||||||
+5
-5
@@ -35,18 +35,18 @@ namespace Barotrauma
|
|||||||
};
|
};
|
||||||
|
|
||||||
// New game
|
// New game
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(24)) }, TextManager.Get("SaveName"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
||||||
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
|
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform), string.Empty)
|
||||||
{
|
{
|
||||||
textFilterFunction = ToolBox.RemoveInvalidFileNameChars
|
textFilterFunction = ToolBox.RemoveInvalidFileNameChars
|
||||||
};
|
};
|
||||||
|
|
||||||
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, GUI.IntScale(24)) }, TextManager.Get("MapSeed"), font: GUIStyle.SubHeadingFont, textAlignment: Alignment.BottomLeft);
|
||||||
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
|
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), nameSeedLayout.RectTransform), ToolBox.RandomSeed(8));
|
||||||
|
|
||||||
nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y));
|
nameSeedLayout.RectTransform.MinSize = new Point(0, nameSeedLayout.Children.Sum(c => c.RectTransform.MinSize.Y));
|
||||||
|
|
||||||
CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingLayout, CampaignSettings.Empty);
|
CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingLayout, CampaignSettings.Empty, false);
|
||||||
|
|
||||||
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f),
|
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f),
|
||||||
verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true);
|
verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true);
|
||||||
|
|||||||
+5
-4
@@ -370,7 +370,7 @@ namespace Barotrauma
|
|||||||
GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter));
|
GUILayoutGroup campaignSettingContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.8f), CampaignCustomizeSettings.Content.RectTransform, Anchor.TopCenter));
|
||||||
|
|
||||||
|
|
||||||
CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingContent, prevSettings);
|
CampaignSettingElements elements = CreateCampaignSettingList(campaignSettingContent, prevSettings, true);
|
||||||
CampaignCustomizeSettings.Buttons[0].OnClicked += (button, o) =>
|
CampaignCustomizeSettings.Buttons[0].OnClicked += (button, o) =>
|
||||||
{
|
{
|
||||||
|
|
||||||
@@ -608,15 +608,16 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
OnClicked = (btn, userdata) =>
|
OnClicked = (btn, userdata) =>
|
||||||
{
|
{
|
||||||
|
var saveFolder = SaveUtil.GetSaveFolder(SaveUtil.SaveType.Singleplayer);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ToolBox.OpenFileWithShell(SaveUtil.SaveFolder);
|
ToolBox.OpenFileWithShell(saveFolder);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
new GUIMessageBox(
|
new GUIMessageBox(
|
||||||
TextManager.Get("error"),
|
TextManager.Get("error"),
|
||||||
TextManager.GetWithVariables("showinfoldererror", ("[folder]", SaveUtil.SaveFolder), ("[errormessage]", e.Message)));
|
TextManager.GetWithVariables("showinfoldererror", ("[folder]", saveFolder), ("[errormessage]", e.Message)));
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -472,7 +472,12 @@ namespace Barotrauma
|
|||||||
{
|
{
|
||||||
TextGetter = () =>
|
TextGetter = () =>
|
||||||
{
|
{
|
||||||
return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{Campaign.NumberOfMissionsAtLocation(destination)}/{Campaign.Settings.TotalMaxMissionCount}");
|
int missionCount = 0;
|
||||||
|
if (GameMain.GameSession != null && Campaign.Map?.CurrentLocation?.SelectedMissions != null)
|
||||||
|
{
|
||||||
|
missionCount = Campaign.Map.CurrentLocation.SelectedMissions.Count(m => m.Locations.Contains(location) && !GameMain.GameSession.Missions.Contains(m));
|
||||||
|
}
|
||||||
|
return TextManager.AddPunctuation(':', TextManager.Get("Missions"), $"{missionCount}/{Campaign.Settings.TotalMaxMissionCount}");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+21
-9
@@ -39,6 +39,7 @@ namespace Barotrauma.CharacterEditor
|
|||||||
|
|
||||||
private bool ShowExtraRagdollControls => editLimbs || editJoints;
|
private bool ShowExtraRagdollControls => editLimbs || editJoints;
|
||||||
|
|
||||||
|
public Character SpawnedCharacter => character;
|
||||||
private Character character;
|
private Character character;
|
||||||
private Vector2 spawnPosition;
|
private Vector2 spawnPosition;
|
||||||
|
|
||||||
@@ -1513,7 +1514,7 @@ namespace Barotrauma.CharacterEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Character SpawnCharacter(Identifier speciesName, RagdollParams ragdoll = null)
|
public Character SpawnCharacter(Identifier speciesName, RagdollParams ragdoll = null)
|
||||||
{
|
{
|
||||||
DebugConsole.NewMessage(GetCharacterEditorTranslation("TryingToSpawnCharacter").Replace("[config]", speciesName.ToString()), Color.HotPink);
|
DebugConsole.NewMessage(GetCharacterEditorTranslation("TryingToSpawnCharacter").Replace("[config]", speciesName.ToString()), Color.HotPink);
|
||||||
OnPreSpawn();
|
OnPreSpawn();
|
||||||
@@ -1765,9 +1766,15 @@ namespace Barotrauma.CharacterEditor
|
|||||||
var modProject = new ModProject(contentPackage);
|
var modProject = new ModProject(contentPackage);
|
||||||
var newFile = ModProject.File.FromPath<CharacterFile>(configFilePath);
|
var newFile = ModProject.File.FromPath<CharacterFile>(configFilePath);
|
||||||
modProject.AddFile(newFile);
|
modProject.AddFile(newFile);
|
||||||
|
|
||||||
modProject.Save(contentPackage.Path);
|
modProject.Save(contentPackage.Path);
|
||||||
contentPackage = ContentPackageManager.ReloadContentPackage(contentPackage);
|
|
||||||
|
var reloadResult = ContentPackageManager.ReloadContentPackage(contentPackage);
|
||||||
|
if (!reloadResult.TryUnwrapSuccess(out var newPackage))
|
||||||
|
{
|
||||||
|
throw new Exception($"Failed to reload package",
|
||||||
|
reloadResult.TryUnwrapFailure(out var exception) ? exception : null);
|
||||||
|
}
|
||||||
|
contentPackage = newPackage;
|
||||||
|
|
||||||
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
|
DebugConsole.NewMessage(GetCharacterEditorTranslation("ContentPackageSaved").Replace("[path]", contentPackage.Path));
|
||||||
|
|
||||||
@@ -3181,10 +3188,7 @@ namespace Barotrauma.CharacterEditor
|
|||||||
OnClicked = (button, data) =>
|
OnClicked = (button, data) =>
|
||||||
{
|
{
|
||||||
ResetView();
|
ResetView();
|
||||||
CharacterParams.Serialize();
|
PrepareCharacterCopy();
|
||||||
RagdollParams.Serialize();
|
|
||||||
AnimParams.ForEach(a => a.Serialize());
|
|
||||||
Wizard.Instance.CopyExisting(CharacterParams, RagdollParams, AnimParams);
|
|
||||||
Wizard.Instance.SelectTab(Wizard.Tab.Character);
|
Wizard.Instance.SelectTab(Wizard.Tab.Character);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -3209,9 +3213,17 @@ namespace Barotrauma.CharacterEditor
|
|||||||
|
|
||||||
fileEditPanel.RectTransform.MinSize = new Point(0, (int)(layoutGroup.RectTransform.Children.Sum(c => c.MinSize.Y + layoutGroup.AbsoluteSpacing) * 1.2f));
|
fileEditPanel.RectTransform.MinSize = new Point(0, (int)(layoutGroup.RectTransform.Children.Sum(c => c.MinSize.Y + layoutGroup.AbsoluteSpacing) * 1.2f));
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region ToggleButtons
|
public void PrepareCharacterCopy()
|
||||||
|
{
|
||||||
|
CharacterParams.Serialize();
|
||||||
|
RagdollParams.Serialize();
|
||||||
|
AnimParams.ForEach(a => a.Serialize());
|
||||||
|
Wizard.Instance.CopyExisting(CharacterParams, RagdollParams, AnimParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
#region ToggleButtons
|
||||||
private enum Direction
|
private enum Direction
|
||||||
{
|
{
|
||||||
Left,
|
Left,
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ namespace Barotrauma.CharacterEditor
|
|||||||
{
|
{
|
||||||
bool isSamePackage = contentPackage.GetFiles<CharacterFile>().Any(f => Path.GetFileNameWithoutExtension(f.Path.Value) == name);
|
bool isSamePackage = contentPackage.GetFiles<CharacterFile>().Any(f => Path.GetFileNameWithoutExtension(f.Path.Value) == name);
|
||||||
LocalizedString verificationText = isSamePackage ? GetCharacterEditorTranslation("existingcharacterfoundreplaceverification") : GetCharacterEditorTranslation("existingcharacterfoundoverrideverification");
|
LocalizedString verificationText = isSamePackage ? GetCharacterEditorTranslation("existingcharacterfoundreplaceverification") : GetCharacterEditorTranslation("existingcharacterfoundoverrideverification");
|
||||||
var msgBox = new GUIMessageBox("", verificationText, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") })
|
var msgBox = new GUIMessageBox("", verificationText, new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") }, type: GUIMessageBox.Type.Warning)
|
||||||
{
|
{
|
||||||
UserData = "verificationprompt"
|
UserData = "verificationprompt"
|
||||||
};
|
};
|
||||||
@@ -356,7 +356,7 @@ namespace Barotrauma.CharacterEditor
|
|||||||
}
|
}
|
||||||
if (ContentPackageManager.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
|
if (ContentPackageManager.AllPackages.Any(cp => cp.Name.ToLower() == contentPackageNameElement.Text.ToLower()))
|
||||||
{
|
{
|
||||||
new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", "leveleditorlevelobjnametaken"));
|
new GUIMessageBox("", TextManager.Get("charactereditor.contentpackagenameinuse", "leveleditorlevelobjnametaken"), type: GUIMessageBox.Type.Warning);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
string modName = contentPackageNameElement.Text;
|
string modName = contentPackageNameElement.Text;
|
||||||
@@ -428,17 +428,26 @@ namespace Barotrauma.CharacterEditor
|
|||||||
texturePathElement.Flash(useRectangleFlash: true);
|
texturePathElement.Flash(useRectangleFlash: true);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (Name == CharacterPrefab.HumanSpeciesName && !IsCopy)
|
||||||
|
{
|
||||||
|
// Force a copy when trying to override a human, because handling the crash would be very difficult (we require humans to have certain definitions).
|
||||||
|
if (!CharacterEditorScreen.Instance.SpawnedCharacter.IsHuman)
|
||||||
|
{
|
||||||
|
CharacterEditorScreen.Instance.SpawnCharacter(CharacterPrefab.HumanSpeciesName);
|
||||||
|
}
|
||||||
|
CharacterEditorScreen.Instance.PrepareCharacterCopy();
|
||||||
|
}
|
||||||
if (IsCopy)
|
if (IsCopy)
|
||||||
{
|
{
|
||||||
SourceRagdoll.Texture = evaluatedTexturePath;
|
SourceRagdoll.Texture = evaluatedTexturePath;
|
||||||
SourceRagdoll.CanEnterSubmarine = CanEnterSubmarine;
|
SourceRagdoll.CanEnterSubmarine = CanEnterSubmarine;
|
||||||
SourceRagdoll.CanWalk = CanWalk;
|
SourceRagdoll.CanWalk = CanWalk;
|
||||||
SourceRagdoll.Serialize();
|
SourceRagdoll.Serialize();
|
||||||
Wizard.Instance.CreateCharacter(SourceRagdoll.MainElement, SourceCharacter.MainElement, SourceAnimations);
|
Instance.CreateCharacter(SourceRagdoll.MainElement, SourceCharacter.MainElement, SourceAnimations);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Wizard.Instance.SelectTab(Tab.Ragdoll);
|
Instance.SelectTab(Tab.Ragdoll);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
@@ -470,9 +479,6 @@ namespace Barotrauma.CharacterEditor
|
|||||||
Stretch = true,
|
Stretch = true,
|
||||||
RelativeSpacing = 0.02f
|
RelativeSpacing = 0.02f
|
||||||
};
|
};
|
||||||
// HTML
|
|
||||||
GUIMessageBox htmlBox = null;
|
|
||||||
var loadHtmlButton = new GUIButton(new RectTransform(new Point(content.Rect.Width / 3, elementSize), content.RectTransform), GetCharacterEditorTranslation("LoadFromHTML"));
|
|
||||||
// Limbs
|
// Limbs
|
||||||
var limbsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
|
var limbsElement = new GUIFrame(new RectTransform(new Vector2(1, 0.05f), content.RectTransform), style: null) { CanBeFocused = false };
|
||||||
|
|
||||||
@@ -689,69 +695,6 @@ namespace Barotrauma.CharacterEditor
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
loadHtmlButton.OnClicked = (b, d) =>
|
|
||||||
{
|
|
||||||
if (htmlBox == null)
|
|
||||||
{
|
|
||||||
htmlBox = new GUIMessageBox(GetCharacterEditorTranslation("LoadHTML"), string.Empty, new LocalizedString[] { TextManager.Get("Close"), TextManager.Get("Load") }, new Vector2(0.65f, 1f));
|
|
||||||
htmlBox.Header.Font = GUIStyle.LargeFont;
|
|
||||||
var element = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.05f), htmlBox.Content.RectTransform), style: null, color: Color.Gray * 0.25f);
|
|
||||||
//new GUITextBlock(new RectTransform(new Vector2(0.3f, 1), element.RectTransform), GetCharacterEditorTranslation("HTMLPath"));
|
|
||||||
var htmlPathElement = new GUITextBox(new RectTransform(new Vector2(0.7f, 1), element.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("HTMLPath").Value);
|
|
||||||
LocalizedString title = GetCharacterEditorTranslation("SelectFile");
|
|
||||||
new GUIButton(new RectTransform(new Vector2(0.3f, 1), element.RectTransform), title)
|
|
||||||
{
|
|
||||||
OnClicked = (button, data) =>
|
|
||||||
{
|
|
||||||
FileSelection.OnFileSelected = (file) =>
|
|
||||||
{
|
|
||||||
htmlPathElement.Text = file;
|
|
||||||
};
|
|
||||||
FileSelection.ClearFileTypeFilters();
|
|
||||||
FileSelection.AddFileTypeFilter("HTML", "*.html, *.htm");
|
|
||||||
FileSelection.AddFileTypeFilter("All files", "*.*");
|
|
||||||
FileSelection.SelectFileTypeFilter("*.html, *.htm");
|
|
||||||
FileSelection.Open = true;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
var list = new GUIListBox(new RectTransform(new Vector2(1, 0.8f), htmlBox.Content.RectTransform));
|
|
||||||
var htmlOutput = new GUITextBlock(new RectTransform(Vector2.One, list.Content.RectTransform), string.Empty) { CanBeFocused = false };
|
|
||||||
htmlBox.Buttons[0].OnClicked += (_b, _d) =>
|
|
||||||
{
|
|
||||||
htmlBox.Close();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
htmlBox.Buttons[1].OnClicked += (_b, _d) =>
|
|
||||||
{
|
|
||||||
LimbGUIElements.ForEach(l => l.RectTransform.Parent = null);
|
|
||||||
LimbGUIElements.Clear();
|
|
||||||
JointGUIElements.ForEach(j => j.RectTransform.Parent = null);
|
|
||||||
JointGUIElements.Clear();
|
|
||||||
LimbXElements.Clear();
|
|
||||||
JointXElements.Clear();
|
|
||||||
ParseRagdollFromHTML(htmlPathElement.Text, (id, limbName, limbType, rect) =>
|
|
||||||
{
|
|
||||||
CreateLimbGUIElement(limbsList.Content.RectTransform, elementSize, id, limbName, limbType, rect);
|
|
||||||
}, (id1, id2, anchor1, anchor2, jointName) =>
|
|
||||||
{
|
|
||||||
CreateJointGUIElement(jointsList.Content.RectTransform, elementSize, id1, id2, anchor1, anchor2, jointName);
|
|
||||||
});
|
|
||||||
htmlOutput.Text = new XDocument(new XElement("Ragdoll", new object[]
|
|
||||||
{
|
|
||||||
new XAttribute("type", Name), LimbXElements.Values, JointXElements
|
|
||||||
})).ToString();
|
|
||||||
htmlOutput.CalculateHeightFromText();
|
|
||||||
list.UpdateScrollBarSize();
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
GUIMessageBox.MessageBoxes.Add(htmlBox);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
// Previous
|
// Previous
|
||||||
box.Buttons[0].OnClicked += (b, d) =>
|
box.Buttons[0].OnClicked += (b, d) =>
|
||||||
{
|
{
|
||||||
@@ -1070,7 +1013,6 @@ namespace Barotrauma.CharacterEditor
|
|||||||
// Rectangles
|
// Rectangles
|
||||||
colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f)));
|
colliderAttributes.Add(new XAttribute("height", (int)(height * 0.85f)));
|
||||||
colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f)));
|
colliderAttributes.Add(new XAttribute("width", (int)(width * 0.85f)));
|
||||||
idToCodeName.TryGetValue(id, out string notes);
|
|
||||||
LimbXElements.Add(id.ToString(), new XElement("limb",
|
LimbXElements.Add(id.ToString(), new XElement("limb",
|
||||||
new XAttribute("id", id),
|
new XAttribute("id", id),
|
||||||
new XAttribute("name", limbName),
|
new XAttribute("name", limbName),
|
||||||
@@ -1107,188 +1049,6 @@ namespace Barotrauma.CharacterEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Dictionary<int, string> idToCodeName = new Dictionary<int, string>();
|
|
||||||
protected void ParseRagdollFromHTML(string path, Action<int, string, LimbType, Rectangle> limbCallback = null, Action<int, int, Vector2, Vector2, string> jointCallback = null)
|
|
||||||
{
|
|
||||||
// TODO: parse as xml files -> allows to load ragdolls onto the wizard.
|
|
||||||
//XDocument doc = XMLExtensions.TryLoadXml(path);
|
|
||||||
//var xElements = doc.Elements().ToArray();
|
|
||||||
string html = string.Empty;
|
|
||||||
try
|
|
||||||
{
|
|
||||||
html = File.ReadAllText(path);
|
|
||||||
}
|
|
||||||
catch (Exception e)
|
|
||||||
{
|
|
||||||
DebugConsole.ThrowError(GetCharacterEditorTranslation("FailedToReadHTML").Replace("[path]", path), e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
var lines = html.Split(new string[] { "<div", "</div>", Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries)
|
|
||||||
.Where(s => s.Contains("left") && s.Contains("top") && s.Contains("width") && s.Contains("height"));
|
|
||||||
int id = 0;
|
|
||||||
Dictionary<string, int> hierarchyToID = new Dictionary<string, int>();
|
|
||||||
Dictionary<int, string> idToHierarchy = new Dictionary<int, string>();
|
|
||||||
Dictionary<int, string> idToPositionCode = new Dictionary<int, string>();
|
|
||||||
Dictionary<int, string> idToName = new Dictionary<int, string>();
|
|
||||||
idToCodeName.Clear();
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
var codeNames = new string(line.SkipWhile(c => c != '>').Skip(1).ToArray()).Split(',');
|
|
||||||
for (int i = 0; i < codeNames.Length; i++)
|
|
||||||
{
|
|
||||||
string codeName = codeNames[i].Trim();
|
|
||||||
if (string.IsNullOrWhiteSpace(codeName)) { continue; }
|
|
||||||
idToCodeName.Add(id, codeName);
|
|
||||||
string limbName = new string(codeName.SkipWhile(c => c != '_').Skip(1).ToArray());
|
|
||||||
if (string.IsNullOrWhiteSpace(limbName)) { continue; }
|
|
||||||
idToName.Add(id, limbName);
|
|
||||||
var parts = line.Split(' ');
|
|
||||||
int ParseToInt(string selector)
|
|
||||||
{
|
|
||||||
string part = parts.First(p => p.Contains(selector));
|
|
||||||
string s = new string(part.SkipWhile(c => c != ':').Skip(1).TakeWhile(c => char.IsNumber(c)).ToArray());
|
|
||||||
int.TryParse(s, out int v);
|
|
||||||
return v;
|
|
||||||
};
|
|
||||||
// example: 111311cr -> 111311
|
|
||||||
string hierarchy = new string(codeName.TakeWhile(c => char.IsNumber(c)).ToArray());
|
|
||||||
if (hierarchyToID.ContainsKey(hierarchy))
|
|
||||||
{
|
|
||||||
DebugConsole.ThrowError(GetCharacterEditorTranslation("MultipleItemsWithSameHierarchy").Replace("[hierarchy]", hierarchy).Replace("[name]", codeName));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
hierarchyToID.Add(hierarchy, id);
|
|
||||||
idToHierarchy.Add(id, hierarchy);
|
|
||||||
string positionCode = new string(codeName.SkipWhile(c => char.IsNumber(c)).TakeWhile(c => c != '_').ToArray());
|
|
||||||
idToPositionCode.Add(id, positionCode.ToLowerInvariant());
|
|
||||||
int x = ParseToInt("left");
|
|
||||||
int y = ParseToInt("top");
|
|
||||||
int width = ParseToInt("width");
|
|
||||||
int height = ParseToInt("height");
|
|
||||||
// This is overridden when the data is loaded from the gui fields.
|
|
||||||
LimbXElements.Add(hierarchy, new XElement("limb",
|
|
||||||
new XAttribute("id", id),
|
|
||||||
new XAttribute("name", limbName),
|
|
||||||
new XAttribute("type", ParseLimbType(limbName).ToString()),
|
|
||||||
new XElement("sprite",
|
|
||||||
new XAttribute("texture", ""),
|
|
||||||
new XAttribute("sourcerect", $"{x}, {y}, {width}, {height}"))
|
|
||||||
));
|
|
||||||
limbCallback?.Invoke(id, limbName, ParseLimbType(limbName), new Rectangle(x, y, width, height));
|
|
||||||
id++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = 0; i < id; i++)
|
|
||||||
{
|
|
||||||
if (idToHierarchy.TryGetValue(i, out string hierarchy))
|
|
||||||
{
|
|
||||||
if (hierarchy != "0")
|
|
||||||
{
|
|
||||||
// NEW LOGIC: if hierarchy length == 1, parent to 0
|
|
||||||
// Else parent to the last bone in the current hierarchy (11 is parented to 1, 212 is parented to 21 etc)
|
|
||||||
string parent = hierarchy.Length > 1 ? hierarchy.Remove(hierarchy.Length - 1, 1) : "0";
|
|
||||||
if (hierarchyToID.TryGetValue(parent, out int parentID))
|
|
||||||
{
|
|
||||||
Vector2 anchor1 = Vector2.Zero;
|
|
||||||
Vector2 anchor2 = Vector2.Zero;
|
|
||||||
idToName.TryGetValue(parentID, out string parentName);
|
|
||||||
idToName.TryGetValue(i, out string limbName);
|
|
||||||
string jointName = $"{GetCharacterEditorTranslation("Joint")} {parentName} - {limbName}";
|
|
||||||
if (idToPositionCode.TryGetValue(i, out string positionCode))
|
|
||||||
{
|
|
||||||
float scalar = 0.8f;
|
|
||||||
if (LimbXElements.TryGetValue(parent, out XElement parentElement))
|
|
||||||
{
|
|
||||||
Rectangle parentSourceRect = parentElement.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
|
|
||||||
float parentWidth = parentSourceRect.Width / 2 * scalar;
|
|
||||||
float parentHeight = parentSourceRect.Height / 2 * scalar;
|
|
||||||
switch (positionCode)
|
|
||||||
{
|
|
||||||
case "tl": // -1, 1
|
|
||||||
anchor1 = new Vector2(-parentWidth, parentHeight);
|
|
||||||
break;
|
|
||||||
case "tc": // 0, 1
|
|
||||||
anchor1 = new Vector2(0, parentHeight);
|
|
||||||
break;
|
|
||||||
case "tr": // -1, 1
|
|
||||||
anchor1 = new Vector2(-parentWidth, parentHeight);
|
|
||||||
break;
|
|
||||||
case "cl": // -1, 0
|
|
||||||
anchor1 = new Vector2(-parentWidth, 0);
|
|
||||||
break;
|
|
||||||
case "cr": // 1, 0
|
|
||||||
anchor1 = new Vector2(parentWidth, 0);
|
|
||||||
break;
|
|
||||||
case "bl": // -1, -1
|
|
||||||
anchor1 = new Vector2(-parentWidth, -parentHeight);
|
|
||||||
break;
|
|
||||||
case "bc": // 0, -1
|
|
||||||
anchor1 = new Vector2(0, -parentHeight);
|
|
||||||
break;
|
|
||||||
case "br": // 1, -1
|
|
||||||
anchor1 = new Vector2(parentWidth, -parentHeight);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (LimbXElements.TryGetValue(hierarchy, out XElement element))
|
|
||||||
{
|
|
||||||
Rectangle sourceRect = element.Element("sprite").GetAttributeRect("sourcerect", Rectangle.Empty);
|
|
||||||
float width = sourceRect.Width / 2 * scalar;
|
|
||||||
float height = sourceRect.Height / 2 * scalar;
|
|
||||||
switch (positionCode)
|
|
||||||
{
|
|
||||||
// Inverse
|
|
||||||
case "tl":
|
|
||||||
// br
|
|
||||||
anchor2 = new Vector2(-width, -height);
|
|
||||||
break;
|
|
||||||
case "tc":
|
|
||||||
// bc
|
|
||||||
anchor2 = new Vector2(0, -height);
|
|
||||||
break;
|
|
||||||
case "tr":
|
|
||||||
// bl
|
|
||||||
anchor2 = new Vector2(-width, -height);
|
|
||||||
break;
|
|
||||||
case "cl":
|
|
||||||
// cr
|
|
||||||
anchor2 = new Vector2(width, 0);
|
|
||||||
break;
|
|
||||||
case "cr":
|
|
||||||
// cl
|
|
||||||
anchor2 = new Vector2(-width, 0);
|
|
||||||
break;
|
|
||||||
case "bl":
|
|
||||||
// tr
|
|
||||||
anchor2 = new Vector2(-width, height);
|
|
||||||
break;
|
|
||||||
case "bc":
|
|
||||||
// tc
|
|
||||||
anchor2 = new Vector2(0, height);
|
|
||||||
break;
|
|
||||||
case "br":
|
|
||||||
// tl
|
|
||||||
anchor2 = new Vector2(-width, height);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// This is overridden when the data is loaded from the gui fields.
|
|
||||||
JointXElements.Add(new XElement("joint",
|
|
||||||
new XAttribute("name", jointName),
|
|
||||||
new XAttribute("limb1", parentID),
|
|
||||||
new XAttribute("limb2", i),
|
|
||||||
new XAttribute("limb1anchor", $"{anchor1.X.Format(2)}, {anchor1.Y.Format(2)}"),
|
|
||||||
new XAttribute("limb2anchor", $"{anchor2.X.Format(2)}, {anchor2.Y.Format(2)}")
|
|
||||||
));
|
|
||||||
jointCallback?.Invoke(parentID, i, anchor1, anchor2, jointName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected LimbType ParseLimbType(string limbName)
|
protected LimbType ParseLimbType(string limbName)
|
||||||
{
|
{
|
||||||
var limbType = LimbType.None;
|
var limbType = LimbType.None;
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ namespace Barotrauma
|
|||||||
GameMain.LightManager.LosEnabled = true;
|
GameMain.LightManager.LosEnabled = true;
|
||||||
Hull.EditFire = false;
|
Hull.EditFire = false;
|
||||||
Hull.EditWater = false;
|
Hull.EditWater = false;
|
||||||
#endif
|
|
||||||
HumanAIController.DisableCrewAI = false;
|
HumanAIController.DisableCrewAI = false;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void DeselectEditorSpecific() { }
|
protected virtual void DeselectEditorSpecific() { }
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user