Merge branch 'unstable'

This commit is contained in:
EvilFactory
2022-10-05 12:22:18 -03:00
611 changed files with 19862 additions and 16305 deletions
+1 -1
View File
@@ -52,7 +52,7 @@ body:
label: Version label: Version
description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu. description: Which version of the game did the bug happen in? You can see the current version number in the bottom left corner of your screen in the main menu.
options: options:
- 0.18.15.0 - 0.19.10.0
- Other - Other
validations: validations:
required: true required: true
@@ -6,7 +6,7 @@ using System;
namespace Barotrauma namespace Barotrauma
{ {
public class Camera : IDisposable class Camera : IDisposable
{ {
public static bool FollowSub = true; public static bool FollowSub = true;
@@ -179,9 +179,9 @@ namespace Barotrauma
{ {
if (Character.Controlled != null && !Character.Controlled.IsDead) { return; } if (Character.Controlled != null && !Character.Controlled.IsDead) { return; }
msg.Write((byte)ClientNetObject.SPECTATING_POS); msg.WriteByte((byte)ClientNetObject.SPECTATING_POS);
msg.Write(position.X); msg.WriteSingle(position.X);
msg.Write(position.Y); msg.WriteSingle(position.Y);
} }
private void CreateMatrices() private void CreateMatrices()
@@ -159,7 +159,7 @@ namespace Barotrauma
GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosEnabled = true;
} }
#endif #endif
timer += CoroutineManager.UnscaledDeltaTime; timer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
@@ -73,6 +73,11 @@ namespace Barotrauma
} }
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black); GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 80.0f, State.ToString(), stateColor, Color.Black);
if (State == AIState.Attack && selectedTargetingParams != null && selectedTargetingParams.AttackPattern == AttackPattern.Circle)
{
GUI.DrawString(spriteBatch, pos - Vector2.UnitY * 100.0f, CirclePhase.ToString(), stateColor, Color.Black);
}
if (LatchOntoAI != null && (State == AIState.Idle || LatchOntoAI.IsAttachedToSub)) if (LatchOntoAI != null && (State == AIState.Idle || LatchOntoAI.IsAttachedToSub))
{ {
foreach (Joint attachJoint in LatchOntoAI.AttachJoints) foreach (Joint attachJoint in LatchOntoAI.AttachJoints)
@@ -1,15 +1,14 @@
using Barotrauma.Items.Components; using Barotrauma.Extensions;
using Barotrauma.Items.Components;
using Barotrauma.Particles;
using Barotrauma.SpriteDeformations; using Barotrauma.SpriteDeformations;
using Barotrauma.Extensions;
using FarseerPhysics; using FarseerPhysics;
using FarseerPhysics.Dynamics; using FarseerPhysics.Dynamics;
using FarseerPhysics.Dynamics.Joints;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.Particles; using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -55,21 +54,34 @@ namespace Barotrauma
if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed) if (character.MemState[0].SelectedItem == null || character.MemState[0].SelectedItem.Removed)
{ {
character.SelectedConstruction = null; character.SelectedItem = null;
} }
else else if (character.SelectedItem != character.MemState[0].SelectedItem)
{ {
if (character.SelectedConstruction != character.MemState[0].SelectedItem) foreach (var ic in character.MemState[0].SelectedItem.Components)
{ {
foreach (var ic in character.MemState[0].SelectedItem.Components) if (ic.CanBeSelected)
{ {
if (ic.CanBeSelected) ic.Select(character);
{
ic.Select(character);
}
} }
} }
character.SelectedConstruction = character.MemState[0].SelectedItem; character.SelectedItem = character.MemState[0].SelectedItem;
}
if (character.MemState[0].SelectedSecondaryItem == null || character.MemState[0].SelectedSecondaryItem.Removed)
{
character.SelectedSecondaryItem = null;
}
else if (character.SelectedSecondaryItem != character.MemState[0].SelectedSecondaryItem)
{
foreach (var ic in character.MemState[0].SelectedSecondaryItem.Components)
{
if (ic.CanBeSelected)
{
ic.Select(character);
}
}
character.SelectedSecondaryItem = character.MemState[0].SelectedSecondaryItem;
} }
if (character.MemState[0].Animation == AnimController.Animation.CPR) if (character.MemState[0].Animation == AnimController.Animation.CPR)
@@ -201,15 +213,24 @@ namespace Barotrauma
{ {
if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed) if (serverPos.SelectedItem == null || serverPos.SelectedItem.Removed)
{ {
character.SelectedConstruction = null; character.SelectedItem = null;
} }
else if (serverPos.SelectedItem != null) else if (character.SelectedItem != serverPos.SelectedItem)
{ {
if (character.SelectedConstruction != serverPos.SelectedItem) serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true);
{ character.SelectedItem = serverPos.SelectedItem;
serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true); }
} }
character.SelectedConstruction = serverPos.SelectedItem; if (localPos.SelectedSecondaryItem != serverPos.SelectedSecondaryItem)
{
if (serverPos.SelectedSecondaryItem == null || serverPos.SelectedSecondaryItem.Removed)
{
character.SelectedSecondaryItem = null;
}
else if (character.SelectedSecondaryItem != serverPos.SelectedSecondaryItem)
{
serverPos.SelectedSecondaryItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true);
character.SelectedSecondaryItem = serverPos.SelectedSecondaryItem;
} }
} }
@@ -496,12 +517,11 @@ namespace Barotrauma
float maxDepth = 0.0f; float maxDepth = 0.0f;
float minDepth = 1.0f; float minDepth = 1.0f;
float depthOffset = 0.0f; float depthOffset = 0.0f;
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
if (ladder != null) if (character.SelectedSecondaryItem?.GetComponent<Ladder>() is Ladder ladder)
{ {
CalculateLimbDepths(); CalculateLimbDepths();
if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X) if (character.WorldPosition.X < character.SelectedSecondaryItem.WorldPosition.X)
{ {
//at the left side of the ladder, needs to be drawn in front of the rungs //at the left side of the ladder, needs to be drawn in front of the rungs
if (maxDepth > ladder.BackgroundSpriteDepth) if (maxDepth > ladder.BackgroundSpriteDepth)
@@ -522,16 +542,21 @@ namespace Barotrauma
else else
{ {
CalculateLimbDepths(); CalculateLimbDepths();
var controller = character.SelectedConstruction?.GetComponent<Controller>(); AdjustDepthOffset(character.SelectedItem);
if (controller != null && controller.ControlCharacterPose && controller.User == character && controller.UserInCorrectPosition) AdjustDepthOffset(character.SelectedSecondaryItem);
void AdjustDepthOffset(Item item)
{ {
if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind) if (item?.GetComponent<Controller>() is { ControlCharacterPose: true, UserInCorrectPosition: true } controller && controller.User == character)
{ {
depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth); if (controller.Item.SpriteDepth <= maxDepth || controller.DrawUserBehind)
} {
else depthOffset = Math.Max(controller.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
{ }
depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f); else
{
depthOffset = Math.Max(controller.Item.GetDrawDepth() - 0.0001f - maxDepth, 0.0f);
}
} }
} }
} }
@@ -9,7 +9,6 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions; using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
@@ -323,8 +322,8 @@ namespace Barotrauma
{ {
cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected * item.OffsetOnSelectedMultiplier; cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected * item.OffsetOnSelectedMultiplier;
} }
else if (SelectedConstruction != null && ViewTarget == null && else if (SelectedItem != null && ViewTarget == null &&
SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this))) SelectedItem.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this)))
{ {
cam.OffsetAmount = targetOffsetAmount = 0.0f; cam.OffsetAmount = targetOffsetAmount = 0.0f;
cursorPosition = cursorPosition =
@@ -368,21 +367,20 @@ namespace Barotrauma
if (!GUI.InputBlockingMenuOpen) if (!GUI.InputBlockingMenuOpen)
{ {
if (SelectedConstruction != null && if (SelectedItem != null &&
(SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) || (SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null && HUD.CloseHUD(ic.GuiFrame.Rect)) ||
((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))) ((ViewTarget as Item)?.Prefab.FocusOnSelected ?? false) && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)))
{ {
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
//emulate a Select input to get the character to deselect the item server-side //emulate a Deselect input to get the character to deselect the item server-side
//keys[(int)InputType.Select].Hit = true; EmulateInput(InputType.Deselect);
keys[(int)InputType.Deselect].Hit = true;
} }
//reset focus to prevent us from accidentally interacting with another entity //reset focus to prevent us from accidentally interacting with another entity
focusedItem = null; focusedItem = null;
FocusedCharacter = null; FocusedCharacter = null;
findFocusedTimer = 0.2f; findFocusedTimer = 0.2f;
SelectedConstruction = null; SelectedItem = null;
} }
} }
@@ -426,6 +424,11 @@ namespace Barotrauma
} }
} }
public void EmulateInput(InputType input)
{
keys[(int)input].Hit = true;
}
partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun) partial void OnAttackedProjSpecific(Character attacker, AttackResult attackResult, float stun)
{ {
if (IsDead) { return; } if (IsDead) { return; }
@@ -518,7 +521,7 @@ namespace Barotrauma
//reduce the amount of aim assist if an item has been selected //reduce the amount of aim assist if an item has been selected
//= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item //= can't switch selection to another item without deselecting the current one first UNLESS the cursor is directly on the item
//otherwise it would be too easy to accidentally switch the selected item when rewiring items //otherwise it would be too easy to accidentally switch the selected item when rewiring items
float aimAssistAmount = SelectedConstruction == null ? 100.0f * aimAssistModifier : 1.0f; float aimAssistAmount = SelectedItem == null ? 100.0f * aimAssistModifier : 1.0f;
Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition); Vector2 displayPosition = ConvertUnits.ToDisplayUnits(simPosition);
@@ -623,12 +626,12 @@ namespace Barotrauma
{ {
if (this != controlled) { return false; } if (this != controlled) { return false; }
if (GameMain.GameSession?.Campaign != null && GameMain.GameSession.Campaign.ShowCampaignUI) { return true; } if (GameMain.GameSession?.Campaign != null && GameMain.GameSession.Campaign.ShowCampaignUI) { return true; }
var controller = SelectedConstruction?.GetComponent<Controller>(); var controller = SelectedItem?.GetComponent<Controller>();
//lock if using a controller, except if we're also using a connection panel in the same item //lock if using a controller, except if we're also using a connection panel in the same item
return return
SelectedConstruction != null && SelectedItem != null &&
controller?.User == this && controller.HideHUD && controller?.User == this && controller.HideHUD &&
SelectedConstruction?.GetComponent<ConnectionPanel>()?.User != this; SelectedItem?.GetComponent<ConnectionPanel>()?.User != this;
} }
@@ -900,7 +903,14 @@ namespace Barotrauma
if (info != null) if (info != null)
{ {
LocalizedString name = Info.DisplayName; LocalizedString name = Info.DisplayName;
if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); } if (controlled == null && name != Info.Name)
{
name += " " + TextManager.Get("Disguised");
}
else if (Info.Title != null)
{
name += '\n' + Info.Title;
}
Vector2 nameSize = GUIStyle.Font.MeasureString(name); Vector2 nameSize = GUIStyle.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom; Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
@@ -1,5 +1,6 @@
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;
@@ -108,7 +109,7 @@ namespace Barotrauma
private static bool ShouldDrawInventory(Character character) private static bool ShouldDrawInventory(Character character)
{ {
var controller = character.SelectedConstruction?.GetComponent<Controller>(); var controller = character.SelectedItem?.GetComponent<Controller>();
return return
character?.Inventory != null && character?.Inventory != null &&
@@ -173,8 +174,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 = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && GUI.MouseOn == null; bool mouseOnPortrait = MouseOnCharacterPortrait() && GUI.MouseOn == null;
if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked()) if (mouseOnPortrait && PlayerInput.PrimaryMouseButtonClicked() && Inventory.DraggingItems.None())
{ {
CharacterHealth.OpenHealthWindow = character.CharacterHealth; CharacterHealth.OpenHealthWindow = character.CharacterHealth;
} }
@@ -398,32 +399,63 @@ namespace Barotrauma
progressBar.Draw(spriteBatch, cam); progressBar.Draw(spriteBatch, cam);
} }
foreach (Character npc in Character.CharacterList) void DrawInteractionIcon(Entity entity, Identifier iconStyle)
{ {
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None || npc.Submarine != character.Submarine || npc.IsDead || npc.IsIncapacitated) { continue; } if (entity == null || entity.Removed) { return; }
var iconStyle = GUIStyle.GetComponentStyle("CampaignInteractionIcon." + npc.CampaignInteractionType); Hull currentHull = entity switch
if (iconStyle == null) { continue; }
Range<float> visibleRange = new Range<float>(npc.CurrentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Examine)
{ {
//TODO: we could probably do better than just hardcoding Character character => character.CurrentHull,
//a check for InteractionType.Examine here. Item item => item.CurrentHull,
_ => null
};
Range<float> visibleRange = new Range<float>(currentHull == Character.Controlled.CurrentHull ? 500.0f : 100.0f, float.PositiveInfinity);
LocalizedString label = null;
if (entity is Character characterEntity)
{
if (characterEntity.IsDead || characterEntity.IsIncapacitated) { return; }
if (characterEntity?.CampaignInteractionType == CampaignMode.InteractionType.Examine)
{
//TODO: we could probably do better than just hardcoding
//a check for InteractionType.Examine here.
if (Vector2.DistanceSquared(character.Position, npc.Position) > 500f * 500f) { continue; } if (Vector2.DistanceSquared(character.Position, entity.Position) > 500f * 500f) { return; }
var body = Submarine.CheckVisibility(character.SimPosition, npc.SimPosition, ignoreLevel: true); var body = Submarine.CheckVisibility(character.SimPosition, entity.SimPosition, ignoreLevel: true);
if (body != null && body.UserData as Character != npc) { continue; } if (body != null && body.UserData != entity) { return; }
visibleRange = new Range<float>(-100f, 500f); visibleRange = new Range<float>(-100f, 500f);
}
label = characterEntity?.Info?.Title;
} }
if (GUIStyle.GetComponentStyle(iconStyle) is not GUIComponentStyle style) { return; }
float dist = Vector2.Distance(character.WorldPosition, entity.WorldPosition);
float distFactor = 1.0f - MathUtils.InverseLerp(1000.0f, 3000.0f, dist);
float alpha = MathHelper.Lerp(0.3f, 1.0f, distFactor);
GUI.DrawIndicator( GUI.DrawIndicator(
spriteBatch, spriteBatch,
npc.WorldPosition, entity.WorldPosition,
cam, cam,
visibleRange, visibleRange,
iconStyle.GetDefaultSprite(), style.GetDefaultSprite(),
iconStyle.Color); style.Color * alpha,
label: label);
}
foreach (Character npc in Character.CharacterList)
{
if (npc.CampaignInteractionType == CampaignMode.InteractionType.None) { continue; }
DrawInteractionIcon(npc, ("CampaignInteractionIcon." + npc.CampaignInteractionType).ToIdentifier());
}
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode && tutorialMode.Tutorial is not null)
{
foreach (var (entity, iconStyle) in tutorialMode.Tutorial.Icons)
{
DrawInteractionIcon(entity, iconStyle);
}
} }
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
@@ -436,10 +468,10 @@ namespace Barotrauma
} }
} }
if (character.SelectedConstruction != null && if (character.SelectedItem != null &&
(character.CanInteractWith(character.SelectedConstruction) || Screen.Selected == GameMain.SubEditorScreen)) (character.CanInteractWith(character.SelectedItem) || Screen.Selected == GameMain.SubEditorScreen))
{ {
character.SelectedConstruction.DrawHUD(spriteBatch, cam, character); character.SelectedItem.DrawHUD(spriteBatch, cam, character);
} }
if (character.Inventory != null) if (character.Inventory != null)
{ {
@@ -487,7 +519,7 @@ namespace Barotrauma
character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, yOffset), targetWidth: HUDLayoutSettings.PortraitArea.Width, true, character.Info.IsDisguisedAsAnother); character.Info.DrawPortrait(spriteBatch, HUDLayoutSettings.PortraitArea.Location.ToVector2(), new Vector2(-12 * GUI.Scale, yOffset), targetWidth: HUDLayoutSettings.PortraitArea.Width, true, character.Info.IsDisguisedAsAnother);
character.Info.DrawForeground(spriteBatch); character.Info.DrawForeground(spriteBatch);
} }
mouseOnPortrait = HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) && !character.ShouldLockHud(); mouseOnPortrait = MouseOnCharacterPortrait() && !character.ShouldLockHud();
if (mouseOnPortrait) if (mouseOnPortrait)
{ {
GUIStyle.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUIStyle.Green * 0.5f); GUIStyle.UIGlow.Draw(spriteBatch, HUDLayoutSettings.BottomRightInfoArea, GUIStyle.Green * 0.5f);
@@ -534,6 +566,13 @@ namespace Barotrauma
} }
} }
public static bool MouseOnCharacterPortrait()
{
if (Character.Controlled == null) { return false; }
if (CharacterHealth.OpenHealthWindow != null || Character.Controlled.SelectedCharacter != null) { return false; }
return HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition);
}
private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character) private static void DrawCharacterHoverTexts(SpriteBatch spriteBatch, Camera cam, Character character)
{ {
var allItems = character.Inventory?.AllItems; var allItems = character.Inventory?.AllItems;
@@ -561,9 +600,15 @@ namespace Barotrauma
Color nameColor = character.FocusedCharacter.GetNameColor(); Color nameColor = character.FocusedCharacter.GetNameColor();
GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No); GUI.DrawString(spriteBatch, textPos, focusName, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
textPos.X += 10.0f * GUI.Scale;
textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y; textPos.Y += GUIStyle.SubHeadingFont.MeasureString(focusName).Y;
if (character.FocusedCharacter.Info?.Title != null && !character.FocusedCharacter.Info.Title.IsNullOrEmpty())
{
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.Info.Title, nameColor, Color.Black * 0.7f, 2, GUIStyle.SubHeadingFont, ForceUpperCase.No);
textPos.Y += GUIStyle.SubHeadingFont.MeasureString(character.FocusedCharacter.Info.Title.Value).Y;
}
textPos.X += 10.0f * GUI.Scale;
if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet) if (!character.FocusedCharacter.IsIncapacitated && character.FocusedCharacter.IsPet)
{ {
GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use), GUI.DrawString(spriteBatch, textPos, GetCachedHudText("PlayHint", InputType.Use),
@@ -102,7 +102,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.0f), headerTextArea.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + PersonalityTrait.Name.Replace(" ".ToIdentifier(), "".ToIdentifier()))), TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), PersonalityTrait.DisplayName),
font: font) font: font)
{ {
Padding = Vector4.Zero Padding = Vector4.Zero
@@ -521,29 +521,28 @@ namespace Barotrauma
Color skinColor = inc.ReadColorR8G8B8(); Color skinColor = inc.ReadColorR8G8B8();
Color hairColor = inc.ReadColorR8G8B8(); Color hairColor = inc.ReadColorR8G8B8();
Color facialHairColor = inc.ReadColorR8G8B8(); Color facialHairColor = inc.ReadColorR8G8B8();
string ragdollFile = inc.ReadString();
string jobIdentifier = inc.ReadString(); string ragdollFile = inc.ReadString();
Identifier npcId = inc.ReadIdentifier();
uint jobIdentifier = inc.ReadUInt32();
int variant = inc.ReadByte(); int variant = inc.ReadByte();
JobPrefab jobPrefab = null; JobPrefab jobPrefab = null;
Dictionary<Identifier, float> skillLevels = new Dictionary<Identifier, float>(); Dictionary<Identifier, float> skillLevels = new Dictionary<Identifier, float>();
if (!string.IsNullOrEmpty(jobIdentifier)) if (jobIdentifier > 0)
{ {
jobPrefab = JobPrefab.Get(jobIdentifier); jobPrefab = JobPrefab.Prefabs.Find(jp => jp.UintIdentifier == jobIdentifier);
byte skillCount = inc.ReadByte(); foreach (SkillPrefab skillPrefab in jobPrefab.Skills.OrderBy(s => s.Identifier))
for (int i = 0; i < skillCount; i++)
{ {
Identifier skillIdentifier = inc.ReadIdentifier();
float skillLevel = inc.ReadSingle(); float skillLevel = inc.ReadSingle();
skillLevels.Add(skillIdentifier, skillLevel); skillLevels.Add(skillPrefab.Identifier, skillLevel);
} }
} }
// TODO: animations // TODO: animations
CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant) CharacterInfo ch = new CharacterInfo(speciesName, newName, originalName, jobPrefab, ragdollFile, variant, npcIdentifier: npcId)
{ {
ID = infoID, ID = infoID
}; };
ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex); ch.RecreateHead(tagSet.ToImmutableHashSet(), hairIndex, beardIndex, moustacheIndex, faceAttachmentIndex);
ch.Head.SkinColor = skinColor; ch.Head.SkinColor = skinColor;
@@ -777,7 +776,21 @@ namespace Barotrauma
createColorSelector($"Customization.{nameof(info.Head.SkinColor)}".ToIdentifier(), info.SkinColors, () => info.Head.SkinColor, createColorSelector($"Customization.{nameof(info.Head.SkinColor)}".ToIdentifier(), info.SkinColors, () => info.Head.SkinColor,
(color) => info.Head.SkinColor = color); (color) => info.Head.SkinColor = color);
#if DEBUG
new GUIButton(new RectTransform(Vector2.One * 0.12f,
parentComponent.RectTransform,
anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest)
{ RelativeOffset = new Vector2(0.01f, 0.005f) }, style: "SaveButton", color: Color.Magenta)
{
ToolTip = "DEBUG ONLY: copy the character info XML to clipboard",
OnClicked = (button, o) =>
{
XElement element = info.Save(null);
Clipboard.SetText(element.ToString());
return false;
}
};
#endif
RandomizeButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, RandomizeButton = new GUIButton(new RectTransform(Vector2.One * 0.12f,
parentComponent.RectTransform, parentComponent.RectTransform,
anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest) anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest)
@@ -1,8 +1,8 @@
using Barotrauma.Extensions; using Barotrauma.Items.Components;
using Barotrauma.Items.Components;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -44,7 +44,8 @@ namespace Barotrauma
LastNetworkUpdateID, LastNetworkUpdateID,
AnimController.TargetDir, AnimController.TargetDir,
SelectedCharacter, SelectedCharacter,
SelectedConstruction, SelectedItem,
SelectedSecondaryItem,
AnimController.Anim); AnimController.Anim);
memLocalState.Add(posInfo); memLocalState.Add(posInfo);
@@ -114,34 +115,34 @@ namespace Barotrauma
public void ClientWriteInput(IWriteMessage msg) public void ClientWriteInput(IWriteMessage msg)
{ {
msg.Write((byte)ClientNetObject.CHARACTER_INPUT); msg.WriteByte((byte)ClientNetObject.CHARACTER_INPUT);
if (memInput.Count > 60) if (memInput.Count > 60)
{ {
memInput.RemoveRange(60, memInput.Count - 60); memInput.RemoveRange(60, memInput.Count - 60);
} }
msg.Write(LastNetworkUpdateID); msg.WriteUInt16(LastNetworkUpdateID);
byte inputCount = Math.Min((byte)memInput.Count, (byte)60); byte inputCount = Math.Min((byte)memInput.Count, (byte)60);
msg.Write(inputCount); msg.WriteByte(inputCount);
for (int i = 0; i < inputCount; i++) for (int i = 0; i < inputCount; i++)
{ {
msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal); msg.WriteRangedInteger((int)memInput[i].states, 0, (int)InputNetFlags.MaxVal);
msg.Write(memInput[i].intAim); msg.WriteUInt16(memInput[i].intAim);
if (memInput[i].states.HasFlag(InputNetFlags.Select) || if (memInput[i].states.HasFlag(InputNetFlags.Select) ||
memInput[i].states.HasFlag(InputNetFlags.Deselect) || memInput[i].states.HasFlag(InputNetFlags.Deselect) ||
memInput[i].states.HasFlag(InputNetFlags.Use) || memInput[i].states.HasFlag(InputNetFlags.Use) ||
memInput[i].states.HasFlag(InputNetFlags.Health) || memInput[i].states.HasFlag(InputNetFlags.Health) ||
memInput[i].states.HasFlag(InputNetFlags.Grab)) memInput[i].states.HasFlag(InputNetFlags.Grab))
{ {
msg.Write(memInput[i].interact); msg.WriteUInt16(memInput[i].interact);
} }
} }
} }
public virtual void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public virtual void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
if (!(extraData is IEventData eventData)) { throw new Exception($"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}"); } if (extraData is not IEventData eventData) { throw new Exception($"Malformed character event: expected {nameof(Character)}.{nameof(IEventData)}"); }
msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue); msg.WriteRangedInteger((int)eventData.EventType, (int)EventType.MinValue, (int)EventType.MaxValue);
switch (eventData) switch (eventData)
@@ -150,16 +151,16 @@ namespace Barotrauma
Inventory.ClientEventWrite(msg, inventoryStateEventData); Inventory.ClientEventWrite(msg, inventoryStateEventData);
break; break;
case TreatmentEventData _: case TreatmentEventData _:
msg.Write(AnimController.Anim == AnimController.Animation.CPR); msg.WriteBoolean(AnimController.Anim == AnimController.Animation.CPR);
break; break;
case CharacterStatusEventData _: case CharacterStatusEventData _:
//do nothing //do nothing
break; break;
case UpdateTalentsEventData _: case UpdateTalentsEventData _:
msg.Write((ushort)characterTalents.Count); msg.WriteUInt16((ushort)characterTalents.Count);
foreach (var unlockedTalent in characterTalents) foreach (var unlockedTalent in characterTalents)
{ {
msg.Write(unlockedTalent.Prefab.UintIdentifier); msg.WriteUInt32(unlockedTalent.Prefab.UintIdentifier);
} }
break; break;
default: default:
@@ -175,6 +176,11 @@ namespace Barotrauma
AnimController.Frozen = false; AnimController.Frozen = false;
Enabled = true; Enabled = true;
//if we start receiving position updates, it means the character's no longer disabled
if (DisabledByEvent && !Removed)
{
DisabledByEvent = false;
}
UInt16 networkUpdateID = 0; UInt16 networkUpdateID = 0;
if (msg.ReadBoolean()) if (msg.ReadBoolean())
@@ -219,15 +225,17 @@ namespace Barotrauma
bool entitySelected = msg.ReadBoolean(); bool entitySelected = msg.ReadBoolean();
Character selectedCharacter = null; Character selectedCharacter = null;
Item selectedItem = null; Item selectedItem = null, selectedSecondaryItem = null;
AnimController.Animation animation = AnimController.Animation.None; AnimController.Animation animation = AnimController.Animation.None;
if (entitySelected) if (entitySelected)
{ {
ushort characterID = msg.ReadUInt16(); ushort characterID = msg.ReadUInt16();
ushort itemID = msg.ReadUInt16(); ushort itemID = msg.ReadUInt16();
ushort secondaryItemID = msg.ReadUInt16();
selectedCharacter = FindEntityByID(characterID) as Character; selectedCharacter = FindEntityByID(characterID) as Character;
selectedItem = FindEntityByID(itemID) as Item; selectedItem = FindEntityByID(itemID) as Item;
selectedSecondaryItem = FindEntityByID(secondaryItemID) as Item;
if (characterID != NullEntityID) if (characterID != NullEntityID)
{ {
bool doingCpr = msg.ReadBoolean(); bool doingCpr = msg.ReadBoolean();
@@ -274,7 +282,7 @@ namespace Barotrauma
pos, rotation, pos, rotation,
networkUpdateID, networkUpdateID,
facingRight ? Direction.Right : Direction.Left, facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation); selectedCharacter, selectedItem, selectedSecondaryItem, animation);
while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID)) while (index < memState.Count && NetIdUtils.IdMoreRecent(posInfo.ID, memState[index].ID))
index++; index++;
@@ -286,7 +294,7 @@ namespace Barotrauma
pos, rotation, pos, rotation,
linearVelocity, angularVelocity, linearVelocity, angularVelocity,
sendingTime, facingRight ? Direction.Right : Direction.Left, sendingTime, facingRight ? Direction.Right : Direction.Left,
selectedCharacter, selectedItem, animation); selectedCharacter, selectedItem, selectedSecondaryItem, animation);
while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp) while (index < memState.Count && posInfo.Timestamp > memState[index].Timestamp)
index++; index++;
@@ -375,9 +383,15 @@ namespace Barotrauma
if (attackLimbIndex == 255 || Removed) { break; } if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length) if (attackLimbIndex >= AnimController.Limbs.Length)
{ {
string errorMsg = $"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})"; //it's possible to get these errors when mid-round syncing, as the client may not
DebugConsole.ThrowError(errorMsg); //yet know about afflictions that have given the character extra limbs (e.g. spineling genes)
GameAnalyticsManager.AddErrorEventOnce("Character.ClientEventRead:AttackLimbOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); //ignoring the error should be safe though, not executing the attack should not cause any further issues
if (!GameMain.Client.MidRoundSyncing)
{
string errorMsg = $"Received invalid {(eventType == EventType.SetAttackTarget ? "SetAttackTarget" : "ExecuteAttack")} message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Character.ClientEventRead:AttackLimbOutOfBounds", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
}
break; break;
} }
Limb attackLimb = AnimController.Limbs[attackLimbIndex]; Limb attackLimb = AnimController.Limbs[attackLimbIndex];
@@ -458,25 +472,11 @@ namespace Barotrauma
break; break;
case EventType.AddToCrew: case EventType.AddToCrew:
GameMain.GameSession.CrewManager.AddCharacter(this); GameMain.GameSession.CrewManager.AddCharacter(this);
CharacterTeamType teamID = (CharacterTeamType)msg.ReadByte(); ReadItemTeamChange(msg, true);
ushort itemCount = msg.ReadUInt16(); break;
for (int i = 0; i < itemCount; i++) case EventType.RemoveFromCrew:
{ GameMain.GameSession.CrewManager.RemoveCharacter(this, removeInfo: true);
ushort itemID = msg.ReadUInt16(); ReadItemTeamChange(msg, false);
if (!(Entity.FindEntityByID(itemID) is Item item)) { continue; }
item.AllowStealing = true;
var wifiComponent = item.GetComponent<WifiComponent>();
if (wifiComponent != null)
{
wifiComponent.TeamID = teamID;
}
var idCard = item.GetComponent<IdCard>();
if (idCard != null)
{
idCard.TeamID = teamID;
idCard.SubmarineSpecificID = 0;
}
}
break; break;
case EventType.UpdateExperience: case EventType.UpdateExperience:
int experienceAmount = msg.ReadInt32(); int experienceAmount = msg.ReadInt32();
@@ -507,9 +507,27 @@ namespace Barotrauma
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true); info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
} }
break; break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
static void ReadItemTeamChange(IReadMessage msg, bool allowStealing)
{
var itemTeamChange = INetSerializableStruct.Read<ItemTeamChange>(msg);
foreach (var itemID in itemTeamChange.ItemIds)
{
if (FindEntityByID(itemID) is not Item item) { continue; }
item.AllowStealing = allowStealing;
if (item.GetComponent<WifiComponent>() is { } wifiComponent)
{
wifiComponent.TeamID = itemTeamChange.TeamId;
}
if (item.GetComponent<IdCard>() is { } idCard)
{
idCard.TeamID = itemTeamChange.TeamId;
idCard.SubmarineSpecificID = 0;
}
}
}
} }
public static Character ReadSpawnData(IReadMessage inc) public static Character ReadSpawnData(IReadMessage inc)
@@ -526,6 +544,7 @@ namespace Barotrauma
Vector2 position = new Vector2(inc.ReadSingle(), inc.ReadSingle()); Vector2 position = new Vector2(inc.ReadSingle(), inc.ReadSingle());
bool enabled = inc.ReadBoolean(); bool enabled = inc.ReadBoolean();
bool disabledByEvent = inc.ReadBoolean();
DebugConsole.Log("Received spawn data for " + speciesName); DebugConsole.Log("Received spawn data for " + speciesName);
@@ -561,7 +580,7 @@ namespace Barotrauma
CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc); CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc);
try try
{ {
character = Create(speciesName, position, seed, characterInfo: info, id: id, isRemotePlayer: ownerId > 0 && GameMain.Client.ID != ownerId, hasAi: hasAi); character = Create(speciesName, position, seed, characterInfo: info, id: id, isRemotePlayer: ownerId > 0 && GameMain.Client.SessionId != ownerId, hasAi: hasAi);
} }
catch (Exception e) catch (Exception e)
{ {
@@ -642,7 +661,7 @@ namespace Barotrauma
GameMain.GameSession.CrewManager.AddCharacter(character); GameMain.GameSession.CrewManager.AddCharacter(character);
} }
if (GameMain.Client.ID == ownerId) if (GameMain.Client.SessionId == ownerId)
{ {
GameMain.Client.HasSpawned = true; GameMain.Client.HasSpawned = true;
GameMain.Client.Character = character; GameMain.Client.Character = character;
@@ -659,7 +678,14 @@ namespace Barotrauma
} }
} }
character.Enabled = Controlled == character || enabled; if (disabledByEvent)
{
character.DisabledByEvent = true;
}
else
{
character.Enabled = Controlled == character || enabled;
}
return character; return character;
} }
@@ -673,16 +699,17 @@ namespace Barotrauma
AfflictionPrefab causeOfDeathAffliction = null; AfflictionPrefab causeOfDeathAffliction = null;
if (causeOfDeathType == CauseOfDeathType.Affliction) if (causeOfDeathType == CauseOfDeathType.Affliction)
{ {
string afflictionName = msg.ReadString(); uint afflictionId = msg.ReadUInt32();
if (!AfflictionPrefab.Prefabs.ContainsKey(afflictionName)) AfflictionPrefab afflictionPrefab = AfflictionPrefab.Prefabs.Find(p => p.UintIdentifier == afflictionId);
if (afflictionPrefab == null)
{ {
string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})"; string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found (id {afflictionId})";
causeOfDeathType = CauseOfDeathType.Unknown; causeOfDeathType = CauseOfDeathType.Unknown;
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionNotFound", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
} }
else else
{ {
causeOfDeathAffliction = AfflictionPrefab.Prefabs[afflictionName]; causeOfDeathAffliction = afflictionPrefab;
} }
} }
bool containsAfflictionData = msg.ReadBoolean(); bool containsAfflictionData = msg.ReadBoolean();
@@ -162,11 +162,7 @@ namespace Barotrauma
openHealthWindow.characterName.Text = value.Character.Info.DisplayName; openHealthWindow.characterName.Text = value.Character.Info.DisplayName;
value.Character.Info.CheckDisguiseStatus(false); value.Character.Info.CheckDisguiseStatus(false);
} }
Character.Controlled.SelectedItem = null;
if (Character.Controlled.SelectedConstruction != null && Character.Controlled.SelectedConstruction.GetComponent<Ladder>() == null)
{
Character.Controlled.SelectedConstruction = null;
}
} }
HintManager.OnShowHealthInterface(); HintManager.OnShowHealthInterface();
@@ -284,6 +280,7 @@ namespace Barotrauma
cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton") cprButton = new GUIButton(new RectTransform(new Vector2(0.17f, 0.17f), characterIndicatorArea.RectTransform, Anchor.BottomLeft, scaleBasis: ScaleBasis.Smallest), text: "", style: "CPRButton")
{ {
UserData = UIHighlightAction.ElementId.CPRButton,
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
Character selectedCharacter = Character.Controlled?.SelectedCharacter; Character selectedCharacter = Character.Controlled?.SelectedCharacter;
@@ -694,7 +691,7 @@ namespace Barotrauma
{ {
distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi; distortTimer = (distortTimer + deltaTime * distortSpeed) % MathHelper.TwoPi;
Character.BlurStrength = (float)(Math.Sin(distortTimer) + 1.5f) * 0.25f * blurStrength; Character.BlurStrength = (float)(Math.Sin(distortTimer) + 1.5f) * 0.25f * blurStrength;
Character.DistortStrength = (float)(Math.Sin(distortTimer) + 1.0f) * 0.1f * distortStrength; Character.DistortStrength = (float)(Math.Sin(distortTimer) + 1.0f) * 0.05f * distortStrength;
} }
else else
{ {
@@ -724,7 +721,7 @@ namespace Barotrauma
//emulate a Health input to get the character to deselect the item server-side //emulate a Health input to get the character to deselect the item server-side
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
Character.Controlled.Keys[(int)InputType.Health].Hit = true; Character.Controlled.EmulateInput(InputType.Health);
} }
OpenHealthWindow = null; OpenHealthWindow = null;
} }
@@ -1298,6 +1295,7 @@ namespace Barotrauma
Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>(); Dictionary<Identifier, float> treatmentSuitability = new Dictionary<Identifier, float>();
GetSuitableTreatments(treatmentSuitability, GetSuitableTreatments(treatmentSuitability,
normalize: true, normalize: true,
user: Character.Controlled,
ignoreHiddenAfflictions: true, ignoreHiddenAfflictions: true,
limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex)); limb: selectedLimbIndex == -1 ? null : Character.AnimController.Limbs.Find(l => l.HealthIndex == selectedLimbIndex));
@@ -1347,7 +1345,7 @@ namespace Barotrauma
PlaySoundOnSelect = false, PlaySoundOnSelect = false,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!(userdata is ItemPrefab itemPrefab)) { return false; } if (userdata is not ItemPrefab itemPrefab) { return false; }
var item = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true); var item = Character.Controlled.Inventory.FindItem(it => it.Prefab == itemPrefab, recursive: true);
if (item == null) { return false; } if (item == null) { return false; }
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex);
@@ -1465,7 +1463,7 @@ namespace Barotrauma
description.RectTransform.Resize(new Point(description.Rect.Width, (int)(description.TextSize.Y + 10))); description.RectTransform.Resize(new Point(description.Rect.Width, (int)(description.TextSize.Y + 10)));
int vitalityDecrease = (int)affliction.GetVitalityDecrease(this); int vitalityDecrease = (int)GetVitalityDecreaseWithVitalityMultipliers(affliction);
if (vitalityDecrease == 0) if (vitalityDecrease == 0)
{ {
vitality.Visible = false; vitality.Visible = false;
@@ -1507,7 +1505,7 @@ namespace Barotrauma
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
{ {
float afflictionVitalityDecrease = affliction.GetVitalityDecrease(this); float afflictionVitalityDecrease = GetVitalityDecreaseWithVitalityMultipliers(affliction);
Color afflictionEffectColor = Color.White; Color afflictionEffectColor = Color.White;
if (afflictionVitalityDecrease > 0.0f) if (afflictionVitalityDecrease > 0.0f)
{ {
@@ -1590,7 +1588,7 @@ namespace Barotrauma
affliction.Strength / affliction.Prefab.MaxStrength); affliction.Strength / affliction.Prefab.MaxStrength);
var vitalityText = labelContainer.GetChildByUserData("vitality") as GUITextBlock; var vitalityText = labelContainer.GetChildByUserData("vitality") as GUITextBlock;
int vitalityDecrease = (int)affliction.GetVitalityDecrease(this); int vitalityDecrease = (int)GetVitalityDecreaseWithVitalityMultipliers(affliction);
if (vitalityDecrease == 0) if (vitalityDecrease == 0)
{ {
vitalityText.Visible = false; vitalityText.Visible = false;
@@ -1608,7 +1606,7 @@ namespace Barotrauma
{ {
//items can be dropped outside the health window //items can be dropped outside the health window
if (!ignoreMousePos && if (!ignoreMousePos &&
!healthWindow.Rect.Contains(PlayerInput.MousePosition) ) !healthWindow.Rect.Contains(PlayerInput.MousePosition))
{ {
return false; return false;
} }
@@ -1624,10 +1622,10 @@ namespace Barotrauma
} }
} }
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); Limb targetLimb =
Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex) ??
Character.AnimController.MainLimb;
item.ApplyTreatment(Character.Controlled, Character, targetLimb); item.ApplyTreatment(Character.Controlled, Character, targetLimb);
return true; return true;
} }
private void UpdateLimbIndicators(float deltaTime, Rectangle drawArea) private void UpdateLimbIndicators(float deltaTime, Rectangle drawArea)
@@ -1690,7 +1688,7 @@ namespace Barotrauma
if (!affliction.ShouldShowIcon(Character)) { continue; } if (!affliction.ShouldShowIcon(Character)) { continue; }
if (!affliction.Prefab.IsBuff) if (!affliction.Prefab.IsBuff)
{ {
negativeEffect += affliction.Strength; negativeEffect += affliction.Strength * GetVitalityMultiplier(affliction, limbHealth);
} }
else else
{ {
@@ -1904,6 +1902,7 @@ namespace Barotrauma
public void ClientRead(IReadMessage inc) public void ClientRead(IReadMessage inc)
{ {
newAfflictions.Clear(); newAfflictions.Clear();
newPeriodicEffects.Clear();
byte afflictionCount = inc.ReadByte(); byte afflictionCount = inc.ReadByte();
for (int i = 0; i < afflictionCount; i++) for (int i = 0; i < afflictionCount; i++)
{ {
@@ -1925,7 +1924,7 @@ namespace Barotrauma
int periodicAfflictionCount = inc.ReadByte(); int periodicAfflictionCount = inc.ReadByte();
for (int j = 0; j < periodicAfflictionCount; j++) for (int j = 0; j < periodicAfflictionCount; j++)
{ {
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); float periodicAfflictionTimer = inc.ReadRangedSingle(0, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
} }
newAfflictions.Add((null, afflictionPrefab, afflictionStrength)); newAfflictions.Add((null, afflictionPrefab, afflictionStrength));
@@ -1995,13 +1994,13 @@ namespace Barotrauma
//timer has wrapped around, apply the effect //timer has wrapped around, apply the effect
if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2) if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {
existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
{ {
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb)); Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb));
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb); existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
} }
} }
existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
} }
} }
@@ -2014,7 +2013,7 @@ namespace Barotrauma
FaceTint = DefaultFaceTint; FaceTint = DefaultFaceTint;
BodyTint = Color.TransparentBlack; BodyTint = Color.TransparentBlack;
if (!(Character?.Params?.Health.ApplyAfflictionColors ?? false)) { return; } if (!Character.Params.Health.ApplyAfflictionColors) { return; }
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions) foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{ {
@@ -2031,15 +2030,21 @@ namespace Barotrauma
foreach (Limb limb in Character.AnimController.Limbs) foreach (Limb limb in Character.AnimController.Limbs)
{ {
if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; } if (limb.HealthIndex < 0 || limb.HealthIndex >= limbHealths.Count) { continue; }
limb.BurnOverlayStrength = 0.0f; limb.BurnOverlayStrength = 0.0f;
limb.DamageOverlayStrength = 0.0f; limb.DamageOverlayStrength = 0.0f;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions) foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{ {
if (kvp.Value != limbHealths[limb.HealthIndex]) { continue; }
var affliction = kvp.Key; var affliction = kvp.Key;
limb.BurnOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha; float burnStrength = affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha; if (kvp.Value == limbHealths[limb.HealthIndex])
{
limb.BurnOverlayStrength += burnStrength;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
}
else
{
limb.BurnOverlayStrength += burnStrength / 2;
}
} }
} }
} }
@@ -481,7 +481,7 @@ namespace Barotrauma
{ {
ContentPath texturePath = ContentPath texturePath =
character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage) character.Params.VariantFile?.Root?.GetAttributeContentPath("texture", character.Prefab.ContentPackage)
?? ContentPath.FromRaw(character.Prefab.ContentPackage, spriteParams.GetTexturePath()); ?? ContentPath.FromRaw(spriteParams.Element.ContentPackage ?? character.Prefab.ContentPackage, spriteParams.GetTexturePath());
path = GetSpritePath(texturePath); path = GetSpritePath(texturePath);
} }
else else
@@ -63,7 +63,7 @@ namespace Barotrauma
files = contentPackage.Files.Select(File.FromContentFile).ToList(); files = contentPackage.Files.Select(File.FromContentFile).ToList();
ModVersion = IncrementModVersion(contentPackage.ModVersion); ModVersion = IncrementModVersion(contentPackage.ModVersion);
IsCore = contentPackage is CorePackage; IsCore = contentPackage is CorePackage;
SteamWorkshopId = contentPackage.SteamWorkshopId; UgcId = contentPackage.UgcId;
ExpectedHash = contentPackage.Hash; ExpectedHash = contentPackage.Hash;
InstallTime = contentPackage.InstallTime; InstallTime = contentPackage.InstallTime;
} }
@@ -74,7 +74,7 @@ namespace Barotrauma
get => name; get => name;
set set
{ {
var charsToRemove = Path.GetInvalidFileNameChars(); var charsToRemove = Path.GetInvalidFileNameCharsCrossPlatform();
name = string.Concat(value.Where(c => !charsToRemove.Contains(c))); name = string.Concat(value.Where(c => !charsToRemove.Contains(c)));
} }
} }
@@ -90,9 +90,9 @@ namespace Barotrauma
public bool IsCore = false; public bool IsCore = false;
public UInt64 SteamWorkshopId = 0; public Option<ContentPackageId> UgcId = Option<ContentPackageId>.None();
public DateTime? InstallTime = null; public Option<DateTime> InstallTime = Option<DateTime>.None();
public bool HasFile(File file) public bool HasFile(File file)
=> Files.Any(f => => Files.Any(f =>
@@ -120,7 +120,7 @@ namespace Barotrauma
public void DiscardHashAndInstallTime() public void DiscardHashAndInstallTime()
{ {
ExpectedHash = null; ExpectedHash = null;
InstallTime = null; InstallTime = Option<DateTime>.None();
} }
public static string IncrementModVersion(string modVersion) public static string IncrementModVersion(string modVersion)
@@ -155,11 +155,11 @@ namespace Barotrauma
addRootAttribute("name", Name); addRootAttribute("name", Name);
if (!ModVersion.IsNullOrEmpty()) { addRootAttribute("modversion", ModVersion); } if (!ModVersion.IsNullOrEmpty()) { addRootAttribute("modversion", ModVersion); }
addRootAttribute("corepackage", IsCore); addRootAttribute("corepackage", IsCore);
if (SteamWorkshopId != 0) { addRootAttribute("steamworkshopid", SteamWorkshopId); } if (UgcId.TryUnwrap(out var ugcId) && ugcId is SteamWorkshopId steamWorkshopId) { addRootAttribute("steamworkshopid", steamWorkshopId.Value); }
addRootAttribute("gameversion", GameMain.Version); addRootAttribute("gameversion", GameMain.Version);
if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); } if (AltNames.Any()) { addRootAttribute("altnames", string.Join(",", AltNames)); }
if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); } if (ExpectedHash != null) { addRootAttribute("expectedhash", ExpectedHash.StringRepresentation); }
if (InstallTime != null) { addRootAttribute("installtime", ToolBox.Epoch.FromDateTime(InstallTime.Value)); } if (InstallTime.TryUnwrap(out var installTime)) { addRootAttribute("installtime", ToolBox.Epoch.FromDateTime(installTime)); }
files.ForEach(f => rootElement.Add(f.ToXElement())); files.ForEach(f => rootElement.Add(f.ToXElement()));
@@ -45,9 +45,11 @@ namespace Barotrauma
var needInstalling = subscribedItems.Where(item var needInstalling = subscribedItems.Where(item
=> !WorkshopPackages.Any(p => !WorkshopPackages.Any(p
=> item.Id == p.SteamWorkshopId => p.UgcId.TryUnwrap(out var ugcId)
&& p.InstallTime.HasValue && ugcId is SteamWorkshopId workshopId
&& item.LatestUpdateTime <= p.InstallTime)) && item.Id == workshopId.Value
&& p.InstallTime.TryUnwrap(out var installTime)
&& item.LatestUpdateTime <= installTime))
.ToArray(); .ToArray();
if (needInstalling.Any()) if (needInstalling.Any())
{ {
@@ -16,7 +16,7 @@ namespace Barotrauma.Transition
/// Class dedicated to transitioning away from the old, shitty /// Class dedicated to transitioning away from the old, shitty
/// Mods + Submarines folders to the new LocalMods folder /// Mods + Submarines folders to the new LocalMods folder
/// </summary> /// </summary>
public static class UgcTransition public static class LegacySteamUgcTransition
{ {
private const string readmeName = "LOCALMODS_README.txt"; private const string readmeName = "LOCALMODS_README.txt";
@@ -168,7 +168,11 @@ namespace Barotrauma.Transition
addHeader(TextManager.Get("SubscribedMods")); addHeader(TextManager.Get("SubscribedMods"));
foreach (var mod in mods.Mods) foreach (var mod in mods.Mods)
{ {
addTickbox(mod.Dir, mod.Name, ticked: !ContentPackageManager.LocalPackages.Any(p => p.SteamWorkshopId != 0 && p.SteamWorkshopId == mod.Item?.Id)); addTickbox(mod.Dir, mod.Name,
ticked: !(mod.Item is { } item && ContentPackageManager.LocalPackages.Any(p =>
p.UgcId.TryUnwrap(out var ugcId)
&& ugcId is SteamWorkshopId workshopId
&& workshopId.Value == item.Id)));
} }
} }
@@ -1,21 +1,18 @@
using Barotrauma.Items.Components; using Barotrauma.ClientSource.Settings;
using Barotrauma.Extensions;
using Barotrauma.IO;
using Barotrauma.Items.Components;
using Barotrauma.MapCreatures.Behavior;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Steam;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using System.Globalization;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using System.Xml.Linq; using System.Xml.Linq;
using System.Globalization;
using FarseerPhysics;
using Barotrauma.Extensions;
using Barotrauma.Steam;
using System.Threading.Tasks;
using Barotrauma.ClientSource.Settings;
using Barotrauma.MapCreatures.Behavior;
using static Barotrauma.FabricationRecipe; using static Barotrauma.FabricationRecipe;
namespace Barotrauma namespace Barotrauma
@@ -229,7 +226,7 @@ namespace Barotrauma
return client.HasPermission(ClientPermissions.Kick); return client.HasPermission(ClientPermissions.Kick);
case "ban": case "ban":
case "banip": case "banip":
case "banendpoint": case "banaddress":
return client.HasPermission(ClientPermissions.Ban); return client.HasPermission(ClientPermissions.Ban);
case "unban": case "unban":
case "unbanip": case "unbanip":
@@ -432,24 +429,6 @@ namespace Barotrauma
} }
})); }));
commands.Add(new Command("startlidgrenclient", "", (string[] args) =>
{
if (args.Length == 0) return;
if (GameMain.Client == null)
{
GameMain.Client = new GameClient("Name", args[0], 0);
}
}));
commands.Add(new Command("startsteamp2pclient", "", (string[] args) =>
{
if (GameMain.Client == null)
{
GameMain.Client = new GameClient("Name", null, 76561198977850505); //this is juan's alt account, feel free to abuse this one
}
}));
commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) => commands.Add(new Command("enablecheats", "enablecheats: Enables cheat commands and disables Steam achievements during this play session.", (string[] args) =>
{ {
CheatsEnabled = true; CheatsEnabled = true;
@@ -744,7 +723,7 @@ namespace Barotrauma
AssignOnExecute("explosion", (string[] args) => AssignOnExecute("explosion", (string[] args) =>
{ {
Vector2 explosionPos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 explosionPos = Screen.Selected.Cam.ScreenToWorld(PlayerInput.MousePosition);
float range = 500, force = 10, damage = 50, structureDamage = 20, itemDamage = 100, empStrength = 0.0f, ballastFloraStrength = 50f; float range = 500, force = 10, damage = 50, structureDamage = 20, itemDamage = 100, empStrength = 0.0f, ballastFloraStrength = 50f;
if (args.Length > 0) float.TryParse(args[0], out range); if (args.Length > 0) float.TryParse(args[0], out range);
if (args.Length > 1) float.TryParse(args[1], out force); if (args.Length > 1) float.TryParse(args[1], out force);
@@ -1299,7 +1278,7 @@ namespace Barotrauma
int? fabricationCost = null; int? fabricationCost = null;
int? deconstructProductCost = null; int? deconstructProductCost = null;
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab); var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == itemPrefab && f.RequiredItems.Any());
if (fabricationRecipe != null) if (fabricationRecipe != null)
{ {
foreach (var ingredient in fabricationRecipe.RequiredItems) foreach (var ingredient in fabricationRecipe.RequiredItems)
@@ -1334,6 +1313,21 @@ namespace Barotrauma
if (fabricationRecipe != null) if (fabricationRecipe != null)
{ {
var ingredient = fabricationRecipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem)); var ingredient = fabricationRecipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem));
if (ingredient == null)
{
foreach (var requiredItem in fabricationRecipe.RequiredItems)
{
foreach (var itemPrefab2 in requiredItem.ItemPrefabs)
{
foreach (var recipe in itemPrefab2.FabricationRecipes.Values)
{
ingredient ??= recipe.RequiredItems.Find(r => r.ItemPrefabs.Contains(targetItem));
}
}
}
}
if (ingredient == null) if (ingredient == null)
{ {
NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red); NewMessage("Deconstructing \"" + itemPrefab.Name + "\" produces \"" + deconstructItem.ItemIdentifier + "\", which isn't required in the fabrication recipe of the item.", Color.Red);
@@ -2077,7 +2071,17 @@ namespace Barotrauma
var prefab = MapEntityPrefab.Find(null, args[0]); var prefab = MapEntityPrefab.Find(null, args[0]);
if (prefab != null) if (prefab != null)
{ {
DebugConsole.NewMessage(prefab.Name + " " + prefab.Identifier + " " + prefab.GetType().ToString()); NewMessage(prefab.Name + " " + prefab.Identifier + " " + prefab.GetType().ToString());
}
}));
commands.Add(new Command("copycharacterinfotoclipboard", "", (string[] args) =>
{
if (Character.Controlled?.Info != null)
{
XElement element = Character.Controlled?.Info.Save(null);
Clipboard.SetText(element.ToString());
DebugConsole.NewMessage($"Copied the characterinfo of {Character.Controlled.Name} to clipboard.");
} }
})); }));
@@ -2485,29 +2489,11 @@ namespace Barotrauma
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
})); }));
#if DEBUG #if DEBUG
commands.Add(new Command("playovervc", "Plays a sound over voice chat.", (args) =>
{
VoipCapture.Instance?.SetOverrideSound(args.Length > 0 ? args[0] : null);
}));
commands.Add(new Command("querylobbies", "Queries all SteamP2P lobbies", (args) =>
{
TaskPool.Add("DebugQueryLobbies",
SteamManager.LobbyQueryRequest(), (t) =>
{
t.TryGetResult(out List<Steamworks.Data.Lobby> lobbies);
foreach (var lobby in lobbies)
{
NewMessage(lobby.GetData("name") + ", " + lobby.GetData("lobbyowner"), Color.Yellow);
}
NewMessage($"Retrieved a total of {lobbies.Count} lobbies", Color.Lime);
});
}));
commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) => commands.Add(new Command("checkduplicates", "Checks the given language for duplicate translation keys and writes to file.", (string[] args) =>
{ {
if (args.Length != 1) return; if (args.Length != 1) { return; }
TextManager.CheckForDuplicates(args[0].ToIdentifier().ToLanguageIdentifier()); TextManager.CheckForDuplicates(args[0].ToIdentifier().ToLanguageIdentifier());
})); }));
@@ -2517,9 +2503,20 @@ namespace Barotrauma
NPCConversation.WriteToCSV(); NPCConversation.WriteToCSV();
})); }));
commands.Add(new Command("csvtoxml", "csvtoxml [language] -> Converts .csv localization files in Content/NPCConversations & Content/Texts to .xml for use in-game.", (string[] args) => commands.Add(new Command("csvtoxml", "csvtoxml -> Converts .csv localization files Content/Texts/Texts.csv and Content/Texts/NPCConversations.csv to .xml for use in-game.", (string[] args) =>
{ {
LocalizationCSVtoXML.Convert(); ShowQuestionPrompt("Do you want to save the text files to the project folder (../../../BarotraumaShared/Content/Texts/)? If not, they are saved in the current working directory. Y/N",
(option1) =>
{
ShowQuestionPrompt("Do you want to convert the NPC conversations as well? Y/N",
(option2) =>
{
LocalizationCSVtoXML.ConvertMasterLocalizationKit(
option1.ToLowerInvariant() == "y" ? "../../../BarotraumaShared/Content/Texts/" : "Content/Texts",
option1.ToLowerInvariant() == "y" ? "../../../BarotraumaShared/Content/NPCConversations/" : "Content/NPCConversations",
convertConversations: option2.ToLowerInvariant() == "y");
});
});
})); }));
commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) => commands.Add(new Command("printproperties", "Goes through the currently collected property list for missing localizations and writes them to a file.", (string[] args) =>
@@ -2816,7 +2813,7 @@ namespace Barotrauma
); );
AssignOnClientExecute( AssignOnClientExecute(
"banendpoint|banip", "banaddress|banip",
(string[] args) => (string[] args) =>
{ {
if (GameMain.Client == null || args.Length == 0) return; if (GameMain.Client == null || args.Length == 0) return;
@@ -2838,7 +2835,7 @@ namespace Barotrauma
} }
GameMain.Client?.SendConsoleCommand( GameMain.Client?.SendConsoleCommand(
"banendpoint " + "banaddress " +
args[0] + " " + args[0] + " " +
(banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " + (banDuration.HasValue ? banDuration.Value.TotalSeconds.ToString() : "0") + " " +
reason); reason);
@@ -2851,13 +2848,16 @@ namespace Barotrauma
{ {
if (GameMain.Client == null || args.Length == 0) return; if (GameMain.Client == null || args.Length == 0) return;
string clientName = string.Join(" ", args); string clientName = string.Join(" ", args);
GameMain.Client.UnbanPlayer(clientName, ""); GameMain.Client.UnbanPlayer(clientName);
})); }));
commands.Add(new Command("unbanip", "unbanip [ip]: Unban a specific IP.", (string[] args) => commands.Add(new Command("unbanaddress", "unbanaddress [endpoint]: Unban a specific endpoint.", (string[] args) =>
{ {
if (GameMain.Client == null || args.Length == 0) return; if (GameMain.Client == null || args.Length == 0) return;
GameMain.Client.UnbanPlayer("", args[0]); if (Endpoint.Parse(args[0]).TryUnwrap(out var endpoint))
{
GameMain.Client.UnbanPlayer(endpoint);
}
})); }));
AssignOnClientExecute( AssignOnClientExecute(
@@ -32,12 +32,12 @@ namespace Barotrauma
private static bool shouldFadeToBlack; private static bool shouldFadeToBlack;
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> _) private bool IsBlockedByAnotherConversation(IEnumerable<Entity> _, float duration)
{ {
return return
lastActiveAction != null && lastActiveAction != null &&
lastActiveAction.ParentEvent != ParentEvent && lastActiveAction.ParentEvent != ParentEvent &&
Timing.TotalTime < lastActiveAction.lastActiveTime + BlockOtherConversationsDuration; Timing.TotalTime < lastActiveAction.lastActiveTime + duration;
} }
partial void ShowDialog(Character speaker, Character targetCharacter) partial void ShowDialog(Character speaker, Character targetCharacter)
@@ -63,33 +63,45 @@ namespace Barotrauma
shouldFadeToBlack = fadeToBlack; shouldFadeToBlack = fadeToBlack;
Sprite eventSprite = EventSet.GetEventSprite(spriteIdentifier);
if (lastMessageBox != null && !lastMessageBox.Closed && GUIMessageBox.MessageBoxes.Contains(lastMessageBox)) if (lastMessageBox != null && !lastMessageBox.Closed && GUIMessageBox.MessageBoxes.Contains(lastMessageBox))
{ {
if (actionId != null && lastMessageBox.UserData is Pair<string, ushort> userData) if (eventSprite != null && lastMessageBox.BackgroundIcon == null)
{ {
if (userData.Second == actionId) { return; } //no background icon in the last message box: we need to create a new one
lastMessageBox.UserData = new Pair<string, ushort>("ConversationAction", actionId.Value); lastMessageBox.Close();
} }
else
GUIListBox conversationList = lastMessageBox.FindChild("conversationlist", true) as GUIListBox;
Debug.Assert(conversationList != null);
// gray out the last text block
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
{ {
if (lastElement.FindChild("text", true) is GUITextBlock textLayout) if (actionId != null && lastMessageBox.UserData is Pair<string, ushort> userData)
{ {
textLayout.OverrideTextColor(Color.DarkGray * 0.8f); if (userData.Second == actionId) { return; }
lastMessageBox.UserData = new Pair<string, ushort>("ConversationAction", actionId.Value);
} }
GUIListBox conversationList = lastMessageBox.FindChild("conversationlist", true) as GUIListBox;
Debug.Assert(conversationList != null);
// gray out the last text block
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
{
if (lastElement.FindChild("text", true) is GUITextBlock textLayout)
{
textLayout.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
float prevSize = conversationList.TotalSize;
List<GUIButton> extraButtons = CreateConversation(conversationList, text, speaker, options, string.IsNullOrWhiteSpace(spriteIdentifier));
AssignActionsToButtons(extraButtons, lastMessageBox);
RecalculateLastMessage(conversationList, true);
conversationList.BarScroll = (prevSize - conversationList.Content.Rect.Height) / (conversationList.TotalSize - conversationList.Content.Rect.Height);
conversationList.ScrollToEnd(duration: 0.5f);
lastMessageBox.SetBackgroundIcon(eventSprite);
return;
} }
List<GUIButton> extraButtons = CreateConversation(conversationList, text, speaker, options, string.IsNullOrWhiteSpace(spriteIdentifier));
AssignActionsToButtons(extraButtons, lastMessageBox);
RecalculateLastMessage(conversationList, true);
conversationList.ScrollToEnd(0.5f);
lastMessageBox.SetBackgroundIcon(EventSet.GetEventSprite(spriteIdentifier));
return;
} }
var (relative, min) = GetSizes(dialogType); var (relative, min) = GetSizes(dialogType);
@@ -100,7 +112,10 @@ namespace Barotrauma
{ {
UserData = "ConversationAction" UserData = "ConversationAction"
}; };
messageBox.OnAddedToGUIUpdateList += (GUIComponent component) =>
{
if (Screen.Selected is not GameScreen) { messageBox.Close(); }
};
lastMessageBox = messageBox; lastMessageBox = messageBox;
messageBox.InnerFrame.ClearChildren(); messageBox.InnerFrame.ClearChildren();
@@ -308,7 +323,7 @@ namespace Barotrauma
AlwaysOverrideCursor = true AlwaysOverrideCursor = true
}; };
LocalizedString translatedText = TextManager.Get(text).Fallback(text); LocalizedString translatedText = TextManager.ParseInputTypes(TextManager.Get(text)).Fallback(text);
if (speaker?.Info != null && drawChathead) if (speaker?.Info != null && drawChathead)
{ {
@@ -368,18 +383,18 @@ namespace Barotrauma
private static void SendResponse(UInt16 actionId, int selectedOption) private static void SendResponse(UInt16 actionId, int selectedOption)
{ {
IWriteMessage outmsg = new WriteOnlyMessage(); IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); outmsg.WriteByte((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
outmsg.Write(actionId); outmsg.WriteUInt16(actionId);
outmsg.Write((byte)selectedOption); outmsg.WriteByte((byte)selectedOption);
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
} }
private static void SendIgnore(UInt16 actionId) private static void SendIgnore(UInt16 actionId)
{ {
IWriteMessage outmsg = new WriteOnlyMessage(); IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE); outmsg.WriteByte((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
outmsg.Write(actionId); outmsg.WriteUInt16(actionId);
outmsg.Write(byte.MaxValue); outmsg.WriteByte(byte.MaxValue);
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable); GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
} }
@@ -0,0 +1,58 @@
using Microsoft.Xna.Framework;
using System.Linq;
namespace Barotrauma;
partial class InventoryHighlightAction : EventAction
{
private static readonly Color highlightColor = Color.Orange;
partial void UpdateProjSpecific()
{
foreach (var target in ParentEvent.GetTargets(TargetTag))
{
SetHighlight(target);
}
}
private void SetHighlight(Entity entity)
{
if (entity is Item item)
{
int i = 0;
foreach (var itemContainer in item.GetComponents<Items.Components.ItemContainer>())
{
if (ItemContainerIndex == -1 || i == ItemContainerIndex)
{
SetHighlight(itemContainer.Inventory);
}
i++;
}
}
else if (entity is Character c)
{
SetHighlight(c.Inventory);
}
}
private void SetHighlight(Inventory inventory)
{
if (inventory?.visualSlots == null) { return; }
for (int i = 0; i < inventory.visualSlots.Length; i++)
{
if (inventory.visualSlots[i].HighlightTimer > 0) { continue; }
Item item = inventory.GetItemAt(i);
if (IsSuitableItem(item) ||
(Recursive && item?.OwnInventory != null && item.OwnInventory.FindAllItems(it => IsSuitableItem(it), recursive: true).Any()))
{
inventory.visualSlots[i].ShowBorderHighlight(highlightColor, 0.5f, 0.5f, 0.1f);
}
}
}
private bool IsSuitableItem(Item item)
{
return (ItemIdentifier.IsEmpty && item == null) ||
(item != null && (item.Prefab.Identifier == ItemIdentifier || item.HasTag(ItemIdentifier)));
}
}
@@ -0,0 +1,77 @@
using Barotrauma.Tutorials;
using System;
using System.Linq;
namespace Barotrauma;
partial class MessageBoxAction : EventAction
{
partial void UpdateProjSpecific()
{
if (Type == ActionType.Create || Type == ActionType.ConnectObjective)
{
CreateMessageBox();
if (!ObjectiveTag.IsEmpty && GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
Identifier id = Identifier.IfEmpty(Text);
var segment = Tutorial.Segment.CreateMessageBoxSegment(id, ObjectiveTag, CreateMessageBox);
tutorialMode.Tutorial?.TriggerTutorialSegment(segment, connectObjective: Type == ActionType.ConnectObjective);
}
}
else if (Type == ActionType.Close)
{
GUIMessageBox.Close(Tag);
}
else if (Type == ActionType.Clear)
{
GUIMessageBox.CloseAll();
}
}
public void CreateMessageBox()
{
new GUIMessageBox(
headerText: TextManager.Get(Header),
text: RichString.Rich(TextManager.ParseInputTypes(TextManager.Get(Text).Fallback(Text.ToString()), useColorHighlight: true)),
buttons: Array.Empty<LocalizedString>(),
type: GUIMessageBox.Type.Tutorial,
tag: Tag,
iconStyle: IconStyle,
autoCloseCondition: GetAutoCloseCondition(),
hideCloseButton: HideCloseButton)
{
FlashOnAutoCloseCondition = true
};
}
private Func<bool> GetAutoCloseCondition()
{
var character = ParentEvent.GetTargets(TargetTag).FirstOrDefault() as Character;
Func<bool> autoCloseCondition = null;
if (!string.IsNullOrEmpty(CloseOnInput) && Enum.TryParse(CloseOnInput, true, out InputType closeOnInput))
{
autoCloseCondition = () => PlayerInput.KeyDown(closeOnInput);
}
else if (!CloseOnSelectTag.IsEmpty)
{
autoCloseCondition = () => character?.SelectedItem != null && character.SelectedItem.HasTag(CloseOnSelectTag);
}
else if (!CloseOnPickUpTag.IsEmpty)
{
autoCloseCondition = () => character?.Inventory != null && character.Inventory.FindItemByTag(CloseOnPickUpTag, recursive: true) != null;
}
else if (!CloseOnEquipTag.IsEmpty)
{
autoCloseCondition = () => character != null && character.HasEquippedItem(CloseOnEquipTag);
}
else if (!CloseOnExitRoomName.IsEmpty)
{
autoCloseCondition = () => character?.CurrentHull == null || character.CurrentHull.RoomName.ToIdentifier() != CloseOnExitRoomName;
}
else if (!CloseOnInRoomName.IsEmpty)
{
autoCloseCondition = () => character?.CurrentHull != null && character.CurrentHull.RoomName.ToIdentifier() == CloseOnInRoomName;
}
return autoCloseCondition;
}
}
@@ -0,0 +1,51 @@
using Microsoft.Xna.Framework;
namespace Barotrauma;
partial class TutorialHighlightAction : EventAction
{
private static readonly Color highlightColor = Color.Orange;
partial void UpdateProjSpecific()
{
if (GameMain.GameSession?.GameMode is not TutorialMode) { return; }
foreach (var target in ParentEvent.GetTargets(TargetTag))
{
SetHighlight(target);
}
}
private void SetHighlight(Entity entity)
{
if (entity is Item i)
{
SetItemHighlight(i);
}
else if (entity is Structure s)
{
SetStructureHighlight(s);
}
else if (entity is Character c)
{
SetCharacterHighlight(c);
}
}
private void SetItemHighlight(Item item)
{
if (item.ExternalHighlight == State) { return; }
item.HighlightColor = State ? highlightColor : null;
item.ExternalHighlight = State;
}
private void SetStructureHighlight(Structure structure)
{
structure.SpriteColor = State ? highlightColor : Color.White;
structure.ExternalHighlight = State;
}
private void SetCharacterHighlight(Character character)
{
character.ExternalHighlight = State;
}
}
@@ -0,0 +1,50 @@
using Barotrauma.Tutorials;
namespace Barotrauma;
partial class TutorialSegmentAction : EventAction
{
private Tutorial.Segment segment;
partial void UpdateProjSpecific()
{
// Only need to create the segment when it's being triggered (otherwise the tutorial already has the segment instance)
if (Type == SegmentActionType.Trigger)
{
segment = Tutorial.Segment.CreateInfoBoxSegment(Identifier, ObjectiveTag, AutoPlayVideo ? Tutorials.AutoPlayVideo.Yes : Tutorials.AutoPlayVideo.No,
new Tutorial.Segment.Text(TextTag, Width, Height, Anchor.Center),
new Tutorial.Segment.Video(VideoFile, TextTag, Width, Height));
}
else if (Type == SegmentActionType.Add)
{
segment = Tutorial.Segment.CreateObjectiveSegment(Identifier, !ObjectiveTag.IsEmpty ? ObjectiveTag : Identifier);
}
if (GameMain.GameSession?.GameMode is TutorialMode tutorialMode)
{
if (tutorialMode.Tutorial is Tutorial tutorial)
{
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
{
DebugConsole.ThrowError($"Error in event \"{ParentEvent.Prefab.Identifier}\": attempting to use TutorialSegmentAction during a non-Tutorial game mode!");
}
}
}
@@ -0,0 +1,46 @@
using Microsoft.Xna.Framework;
using System.Linq;
namespace Barotrauma;
partial class UIHighlightAction : EventAction
{
private static readonly Color highlightColor = Color.Orange;
partial void UpdateProjSpecific()
{
bool useCircularFlash = false;
GUIComponent component = null;
if (Id != ElementId.None)
{
component = GUI.GetAdditions().FirstOrDefault(c => Equals(Id, c.UserData));
}
else if (!EntityIdentifier.IsEmpty)
{
component = GUI.GetAdditions().FirstOrDefault(c =>
c.UserData is MapEntityPrefab mep && mep.Identifier == EntityIdentifier || c.UserData is MapEntity me && me.Prefab.Identifier == EntityIdentifier);
}
else if (!OrderIdentifier.IsEmpty)
{
useCircularFlash = true;
if (!OrderTargetTag.IsEmpty)
{
component =
GUI.GetAdditions().FirstOrDefault(c =>
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));
}
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)
{
component.Flash(highlightColor, useCircularFlash: useCircularFlash);
component.Bounce |= Bounce;
}
}
}
@@ -651,11 +651,11 @@ namespace Barotrauma
break; break;
case NetworkEventType.MISSION: case NetworkEventType.MISSION:
Identifier missionIdentifier = msg.ReadIdentifier(); Identifier missionIdentifier = msg.ReadIdentifier();
string missionName = msg.ReadString();
MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier); MissionPrefab? prefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == missionIdentifier);
if (prefab != null) if (prefab != null)
{ {
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name), new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", missionName),
Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128)) Array.Empty<LocalizedString>(), type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
{ {
IconColor = prefab.IconColor IconColor = prefab.IconColor
@@ -1,6 +1,4 @@
using Barotrauma.Networking; namespace Barotrauma
namespace Barotrauma
{ {
partial class CombatMission : Mission partial class CombatMission : Mission
{ {
@@ -8,7 +6,7 @@ namespace Barotrauma
{ {
get get
{ {
if (descriptions == null) return ""; if (descriptions == null) { return ""; }
if (GameMain.Client?.Character == null) if (GameMain.Client?.Character == null)
{ {
@@ -80,6 +80,10 @@ namespace Barotrauma
LocalizedString header = messageIndex < Headers.Length ? Headers[messageIndex] : ""; LocalizedString header = messageIndex < Headers.Length ? Headers[messageIndex] : "";
LocalizedString message = messageIndex < Messages.Length ? Messages[messageIndex] : ""; LocalizedString message = messageIndex < Messages.Length ? Messages[messageIndex] : "";
if (!message.IsNullOrEmpty())
{
message = ModifyMessage(message);
}
CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message)); CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message));
} }
@@ -6,23 +6,26 @@ 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; using System.Threading;
using Barotrauma.Threading;
namespace Barotrauma namespace Barotrauma
{ {
public class ScalableFont : IDisposable public class ScalableFont : IDisposable
{ {
private static List<ScalableFont> FontList = new List<ScalableFont>(); private static readonly List<ScalableFont> FontList = new List<ScalableFont>();
private static Library Lib = null; private static Library Lib = null;
private readonly object mutex = new object(); private static readonly object globalMutex = new object();
private string filename; private readonly ReaderWriterLockSlim rwl = new ReaderWriterLockSlim();
private Face face;
private readonly string filename;
private readonly Face face;
private uint size; private uint size;
private int baseHeight; private int baseHeight;
private Dictionary<uint, GlyphData> texCoords; private readonly Dictionary<uint, GlyphData> texCoords;
private List<Texture2D> textures; private readonly List<Texture2D> textures;
private GraphicsDevice graphicsDevice; private readonly GraphicsDevice graphicsDevice;
private Vector2 currentDynamicAtlasCoords; private Vector2 currentDynamicAtlasCoords;
private int currentDynamicAtlasNextY; private int currentDynamicAtlasNextY;
@@ -49,7 +52,7 @@ namespace Barotrauma
set set
{ {
size = value; size = value;
if (graphicsDevice != null) RenderAtlas(graphicsDevice, charRanges, texDims, baseChar); if (graphicsDevice != null) { RenderAtlas(graphicsDevice, charRanges, texDims, baseChar); }
} }
} }
@@ -93,11 +96,15 @@ namespace Barotrauma
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, bool isCJK = false)
{ {
lock (mutex) lock (globalMutex)
{
Lib ??= new Library();
}
this.filename = filename;
this.face = null;
using (new ReadLock(rwl))
{ {
if (Lib == null) Lib = new Library();
this.filename = filename;
this.face = null;
foreach (ScalableFont font in FontList) foreach (ScalableFont font in FontList)
{ {
if (font.filename == filename) if (font.filename == filename)
@@ -106,19 +113,23 @@ namespace Barotrauma
break; break;
} }
} }
this.face ??= new Face(Lib, filename); }
this.size = size;
this.textures = new List<Texture2D>();
this.texCoords = new Dictionary<uint, GlyphData>();
this.DynamicLoading = dynamicLoading;
this.IsCJK = isCJK;
this.graphicsDevice = gd;
if (gd != null && !dynamicLoading) this.face ??= new Face(Lib, filename);
{ this.size = size;
RenderAtlas(gd); this.textures = new List<Texture2D>();
} this.texCoords = new Dictionary<uint, GlyphData>();
this.DynamicLoading = dynamicLoading;
this.IsCJK = isCJK;
this.graphicsDevice = gd;
if (gd != null && !dynamicLoading)
{
RenderAtlas(gd);
}
lock (globalMutex)
{
FontList.Add(this); FontList.Add(this);
} }
} }
@@ -162,7 +173,7 @@ namespace Barotrauma
Vector2 currentCoords = Vector2.Zero; Vector2 currentCoords = Vector2.Zero;
int nextY = 0; int nextY = 0;
lock (mutex) using (new WriteLock(rwl))
{ {
face.SetPixelSizes(0, size); face.SetPixelSizes(0, size);
face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal); face.LoadGlyph(face.GetCharIndex(baseChar), LoadFlags.Default, LoadTarget.Normal);
@@ -175,19 +186,22 @@ namespace Barotrauma
for (uint j = start; j <= end; j++) for (uint j = start; j <= end; j++)
{ {
uint glyphIndex = face.GetCharIndex(j); uint glyphIndex = face.GetCharIndex(j);
if (glyphIndex == 0) continue; if (glyphIndex == 0)
{
texCoords.Add(j, new GlyphData(
advance: 0,
texIndex: -1));
continue;
}
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal); face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0) if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
{ {
if (face.Glyph.Metrics.HorizontalAdvance > 0) //glyph is empty, but char might still apply advance
{ GlyphData blankData = new GlyphData(
//glyph is empty, but char still applies advance advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
GlyphData blankData = new GlyphData( texIndex: -1); //indicates no texture because the glyph is empty
advance: (float)face.Glyph.Metrics.HorizontalAdvance,
texIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(j, blankData); texCoords.Add(j, blankData);
}
continue; continue;
} }
//stacktrace doesn't really work that well when RenderGlyph throws an exception //stacktrace doesn't really work that well when RenderGlyph throws an exception
@@ -257,7 +271,7 @@ namespace Barotrauma
private void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54) private void DynamicRenderAtlas(GraphicsDevice gd, uint character, int texDims = 1024, uint baseChar = 0x54)
{ {
bool missingCharacterFound = false; bool missingCharacterFound = false;
lock (mutex) using (new ReadLock(rwl))
{ {
missingCharacterFound = !texCoords.ContainsKey(character); missingCharacterFound = !texCoords.ContainsKey(character);
} }
@@ -268,10 +282,9 @@ namespace Barotrauma
private void DynamicRenderAtlas(GraphicsDevice gd, string str, int texDims = 1024, uint baseChar = 0x54) private void DynamicRenderAtlas(GraphicsDevice gd, string str, int texDims = 1024, uint baseChar = 0x54)
{ {
bool missingCharacterFound = false; bool missingCharacterFound = false;
var distinctChrs = str.Distinct().Select(c => (uint)c).ToArray(); using (new ReadLock(rwl))
lock (mutex)
{ {
foreach (var character in distinctChrs) foreach (var character in str)
{ {
if (texCoords.ContainsKey(character)) { continue; } if (texCoords.ContainsKey(character)) { continue; }
@@ -280,7 +293,7 @@ namespace Barotrauma
} }
} }
if (!missingCharacterFound) { return; } if (!missingCharacterFound) { return; }
DynamicRenderAtlas(gd, distinctChrs, texDims, baseChar); DynamicRenderAtlas(gd, str.Select(c => (uint)c), texDims, baseChar);
} }
private void DynamicRenderAtlas(GraphicsDevice gd, IEnumerable<uint> characters, int texDims = 1024, uint baseChar = 0x54) private void DynamicRenderAtlas(GraphicsDevice gd, IEnumerable<uint> characters, int texDims = 1024, uint baseChar = 0x54)
@@ -299,7 +312,7 @@ namespace Barotrauma
Fixed26Dot6 horizontalAdvance; Fixed26Dot6 horizontalAdvance;
Vector2 drawOffset; Vector2 drawOffset;
lock (mutex) using (new WriteLock(rwl))
{ {
if (textures.Count == 0) if (textures.Count == 0)
{ {
@@ -318,20 +331,23 @@ namespace Barotrauma
if (texCoords.ContainsKey(character)) { continue; } if (texCoords.ContainsKey(character)) { continue; }
uint glyphIndex = face.GetCharIndex(character); uint glyphIndex = face.GetCharIndex(character);
if (glyphIndex == 0) { continue; } if (glyphIndex == 0)
{
texCoords.Add(character, new GlyphData(
advance: 0,
texIndex: -1));
continue;
}
face.SetPixelSizes(0, size); face.SetPixelSizes(0, size);
face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal); face.LoadGlyph(glyphIndex, LoadFlags.Default, LoadTarget.Normal);
if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0) if (face.Glyph.Metrics.Width == 0 || face.Glyph.Metrics.Height == 0)
{ {
if (face.Glyph.Metrics.HorizontalAdvance > 0) //glyph is empty, but char might still apply advance
{ GlyphData blankData = new GlyphData(
//glyph is empty, but char still applies advance advance: Math.Max((float)face.Glyph.Metrics.HorizontalAdvance, 0f),
GlyphData blankData = new GlyphData( texIndex: -1); //indicates no texture because the glyph is empty
advance: (float)face.Glyph.Metrics.HorizontalAdvance, texCoords.Add(character, blankData);
texIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(character, blankData);
}
continue; continue;
} }
@@ -325,6 +325,12 @@ namespace Barotrauma
ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen; ToggleOpen = PreferChatBoxOpen = GameSettings.CurrentConfig.ChatOpen;
} }
public void Toggle()
{
ToggleOpen = !ToggleOpen;
CloseAfterMessageSent = false;
}
public bool TypingChatMessage(GUITextBox textBox, string text) public bool TypingChatMessage(GUITextBox textBox, string text)
{ {
string command = ChatMessage.GetChatMessageCommand(text, out _); string command = ChatMessage.GetChatMessageCommand(text, out _);
@@ -611,22 +617,29 @@ namespace Barotrauma
showNewMessagesButton.Visible = false; showNewMessagesButton.Visible = false;
} }
if (PlayerInput.KeyHit(InputType.ToggleChatMode) && GUI.KeyboardDispatcher.Subscriber == null && Screen.Selected == GameMain.GameScreen) if (Screen.Selected == GameMain.GameScreen && GUI.KeyboardDispatcher.Subscriber == null)
{ {
try if (PlayerInput.KeyHit(InputType.ToggleChatMode))
{ {
var mode = GameMain.ActiveChatMode switch try
{ {
ChatMode.Local => ChatMode.Radio, var mode = GameMain.ActiveChatMode switch
ChatMode.Radio => ChatMode.Local, {
_ => throw new NotImplementedException() ChatMode.Local => ChatMode.Radio,
}; ChatMode.Radio => ChatMode.Local,
ChatModeDropDown.SelectItem(mode); _ => throw new NotImplementedException()
// TODO: Play a sound? };
ChatModeDropDown.SelectItem(mode);
// TODO: Play a sound?
}
catch (NotImplementedException)
{
DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\"");
}
} }
catch (NotImplementedException) else if (PlayerInput.KeyHit(InputType.ChatBox))
{ {
DebugConsole.ThrowError($"Error toggling chat mode: not implemented for current mode \"{GameMain.ActiveChatMode}\""); Toggle();
} }
} }
@@ -128,10 +128,11 @@ namespace Barotrauma
var sortGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), hireablesGroup.RectTransform), isHorizontal: true) var sortGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), hireablesGroup.RectTransform), isHorizontal: true)
{ {
RelativeSpacing = 0.015f RelativeSpacing = 0.015f,
Stretch = true
}; };
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sortGroup.RectTransform), text: TextManager.Get("campaignstore.sortby")); new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sortGroup.RectTransform), text: TextManager.Get("campaignstore.sortby"));
sortingDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), sortGroup.RectTransform), elementCount: 5) sortingDropDown = new GUIDropDown(new RectTransform(new Vector2(0.5f, 1.0f), sortGroup.RectTransform), elementCount: 5)
{ {
OnSelected = (child, userData) => OnSelected = (child, userData) =>
{ {
@@ -193,19 +194,20 @@ namespace Barotrauma
{ {
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
validateHiresButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaigncrew.validate")) validateHiresButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), group.RectTransform), text: TextManager.Get("campaigncrew.validate"))
{ {
ClickSound = GUISoundType.ConfirmTransaction, ClickSound = GUISoundType.ConfirmTransaction,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
OnClicked = (b, o) => ValidateHires(PendingHires, true) OnClicked = (b, o) => ValidateHires(PendingHires, true)
}; };
clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall")) clearAllButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall"))
{ {
ClickSound = GUISoundType.Cart, ClickSound = GUISoundType.Cart,
ForceUpperCase = ForceUpperCase.Yes, ForceUpperCase = ForceUpperCase.Yes,
Enabled = HasPermission, Enabled = HasPermission,
OnClicked = (b, o) => RemoveAllPendingHires() OnClicked = (b, o) => RemoveAllPendingHires()
}; };
GUITextBlock.AutoScaleAndNormalize(validateHiresButton.TextBlock, clearAllButton.TextBlock);
resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
} }
@@ -528,7 +530,7 @@ namespace Barotrauma
if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait) if (characterInfo.PersonalityTrait is NPCPersonalityTrait trait)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait")); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("PersonalityTrait"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get("personalitytrait." + trait.Name.Replace(" ".ToIdentifier(), Identifier.Empty))); new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), trait.DisplayName);
} }
infoLabelGroup.Recalculate(); infoLabelGroup.Recalculate();
infoValueGroup.Recalculate(); infoValueGroup.Recalculate();
@@ -887,35 +889,35 @@ namespace Barotrauma
if (campaign is MultiPlayerCampaign) if (campaign is MultiPlayerCampaign)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.CREW); msg.WriteByte((byte)ClientPacketHeader.CREW);
msg.Write(updatePending); msg.WriteBoolean(updatePending);
if (updatePending) if (updatePending)
{ {
msg.Write((ushort)PendingHires.Count); msg.WriteUInt16((ushort)PendingHires.Count);
foreach (CharacterInfo pendingHire in PendingHires) foreach (CharacterInfo pendingHire in PendingHires)
{ {
msg.Write(pendingHire.GetIdentifierUsingOriginalName()); msg.WriteInt32(pendingHire.GetIdentifierUsingOriginalName());
} }
} }
msg.Write(validateHires); msg.WriteBoolean(validateHires);
bool validRenaming = renameCharacter.info != null && !string.IsNullOrEmpty(renameCharacter.newName); bool validRenaming = renameCharacter.info != null && !string.IsNullOrEmpty(renameCharacter.newName);
msg.Write(validRenaming); msg.WriteBoolean(validRenaming);
if (validRenaming) if (validRenaming)
{ {
int identifier = renameCharacter.info.GetIdentifierUsingOriginalName(); int identifier = renameCharacter.info.GetIdentifierUsingOriginalName();
msg.Write(identifier); msg.WriteInt32(identifier);
msg.Write(renameCharacter.newName); msg.WriteString(renameCharacter.newName);
bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false; bool existingCrewMember = campaign.CrewManager?.GetCharacterInfos().Any(ci => ci.GetIdentifierUsingOriginalName() == identifier) ?? false;
msg.Write(existingCrewMember); msg.WriteBoolean(existingCrewMember);
} }
msg.Write(firedCharacter != null); msg.WriteBoolean(firedCharacter != null);
if (firedCharacter != null) if (firedCharacter != null)
{ {
msg.Write(firedCharacter.GetIdentifier()); msg.WriteInt32(firedCharacter.GetIdentifier());
} }
GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable); GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
@@ -48,7 +48,7 @@ namespace Barotrauma
WaitingBackground = 6, // Cursor + Hourglass WaitingBackground = 6, // Cursor + Hourglass
} }
public static class GUI static class GUI
{ {
public static GUICanvas Canvas => GUICanvas.Instance; public static GUICanvas Canvas => GUICanvas.Instance;
public static CursorState MouseCursor = CursorState.Default; public static CursorState MouseCursor = CursorState.Default;
@@ -410,7 +410,7 @@ namespace Barotrauma
{ {
y += yStep; y += yStep;
DrawString(spriteBatch, new Vector2(10, y), DrawString(spriteBatch, new Vector2(10, y),
"Sub pos: " + Submarine.MainSub.Position.ToPoint(), "Sub pos: " + Submarine.MainSub.WorldPosition.ToPoint(),
Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont); Color.White, Color.Black * 0.5f, 0, GUIStyle.SmallFont);
} }
@@ -879,6 +879,11 @@ namespace Barotrauma
} }
} }
} }
public static IEnumerable<GUIComponent> GetAdditions()
{
return additions;
}
#endregion #endregion
public static GUIComponent MouseOn { get; private set; } public static GUIComponent MouseOn { get; private set; }
@@ -958,7 +963,7 @@ namespace Barotrauma
// Wire cursors // Wire cursors
if (Character.Controlled != null) if (Character.Controlled != null)
{ {
if (Character.Controlled.SelectedConstruction?.GetComponent<ConnectionPanel>() != null) if (Character.Controlled.SelectedItem?.GetComponent<ConnectionPanel>() != null)
{ {
if (Connection.DraggingConnected != null) if (Connection.DraggingConnected != null)
{ {
@@ -981,7 +986,7 @@ namespace Barotrauma
return editor.GetMouseCursorState(); return editor.GetMouseCursorState();
// Portrait area during gameplay // Portrait area during gameplay
case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true): case GameScreen _ when !(Character.Controlled?.ShouldLockHud() ?? true):
if (HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) || CharacterHealth.IsMouseOnHealthBar()) if (CharacterHUD.MouseOnCharacterPortrait() || CharacterHealth.IsMouseOnHealthBar())
{ {
return CursorState.Hand; return CursorState.Hand;
} }
@@ -1018,6 +1023,11 @@ namespace Barotrauma
if (c.Enabled) if (c.Enabled)
{ {
var dragHandle = c as GUIDragHandle ?? parent as GUIDragHandle;
if (dragHandle != null)
{
return dragHandle.Dragging ? CursorState.Dragging : CursorState.Hand;
}
// Some parent elements take priority // Some parent elements take priority
// but not when the child is a GUIButton or GUITickBox // but not when the child is a GUIButton or GUITickBox
if (!(parent is GUIButton) && !(parent is GUIListBox) || if (!(parent is GUIButton) && !(parent is GUIListBox) ||
@@ -1027,6 +1037,7 @@ namespace Barotrauma
} }
} }
// Children in list boxes can be interacted with despite not having // Children in list boxes can be interacted with despite not having
// a GUIButton inside of them so instead of hard coding we check if // a GUIButton inside of them so instead of hard coding we check if
// the children can be interacted with by checking their hover state // the children can be interacted with by checking their hover state
@@ -1097,6 +1108,8 @@ namespace Barotrauma
return list; return list;
case GUIScrollBar bar: case GUIScrollBar bar:
return bar; return bar;
case GUIDragHandle dragHandle:
return dragHandle;
} }
} }
component = parent; component = parent;
@@ -1344,7 +1357,7 @@ namespace Barotrauma
/// <param name="createOffset">Should the indicator move based on the camera position?</param> /// <param name="createOffset">Should the indicator move based on the camera position?</param>
/// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param> /// <param name="overrideAlpha">Override the distance-based alpha value with the specified alpha value</param>
public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Range<float> visibleRange, Sprite sprite, in Color color, public static void DrawIndicator(SpriteBatch spriteBatch, in Vector2 worldPosition, Camera cam, in Range<float> visibleRange, Sprite sprite, in Color color,
bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null) bool createOffset = true, float scaleMultiplier = 1.0f, float? overrideAlpha = null, LocalizedString label = null)
{ {
Vector2 diff = worldPosition - cam.WorldViewCenter; Vector2 diff = worldPosition - cam.WorldViewCenter;
float dist = diff.Length(); float dist = diff.Length();
@@ -1394,10 +1407,6 @@ namespace Barotrauma
angle = MathHelper.Lerp(originalAngle, angle, MathHelper.Clamp(((screenDist + 10f) - iconDiff.Length()) / 10f, 0f, 1f)); angle = MathHelper.Lerp(originalAngle, angle, MathHelper.Clamp(((screenDist + 10f) - iconDiff.Length()) / 10f, 0f, 1f));
/*Vector2 unclampedDiff = new Vector2(
(float)Math.Cos(angle) * screenDist,
(float)-Math.Sin(angle) * screenDist);*/
iconDiff = new Vector2( iconDiff = new Vector2(
(float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist), (float)Math.Cos(angle) * Math.Min(GameMain.GraphicsWidth * 0.4f, screenDist),
(float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist)); (float)-Math.Sin(angle) * Math.Min(GameMain.GraphicsHeight * 0.4f, screenDist));
@@ -1405,7 +1414,20 @@ namespace Barotrauma
Vector2 iconPos = cam.WorldToScreen(cam.WorldViewCenter) + iconDiff; Vector2 iconPos = cam.WorldToScreen(cam.WorldViewCenter) + iconDiff;
sprite.Draw(spriteBatch, iconPos, color * alpha, rotate: 0.0f, scale: symbolScale); sprite.Draw(spriteBatch, iconPos, color * alpha, rotate: 0.0f, scale: symbolScale);
if (/*unclampedDiff.Length()*/ screenDist - 10 > iconDiff.Length()) if (label != null)
{
float cursorDist = Vector2.Distance(PlayerInput.MousePosition, iconPos);
if (cursorDist < sprite.size.X * symbolScale)
{
Vector2 textSize = GUIStyle.Font.MeasureString(label);
Vector2 textPos = iconPos + new Vector2(sprite.size.X * symbolScale * 0.7f * Math.Sign(-iconDiff.X), -textSize.Y / 2);
if (iconDiff.X > 0) { textPos.X -= textSize.X; }
DrawString(spriteBatch, textPos + Vector2.One, label, Color.Black);
DrawString(spriteBatch, textPos, label, color);
}
}
if (screenDist - 10 > iconDiff.Length())
{ {
Vector2 normalizedDiff = Vector2.Normalize(targetScreenPos - iconPos); Vector2 normalizedDiff = Vector2.Normalize(targetScreenPos - iconPos);
Vector2 arrowOffset = normalizedDiff * sprite.size.X * symbolScale * 0.7f; Vector2 arrowOffset = normalizedDiff * sprite.size.X * symbolScale * 0.7f;
@@ -1465,9 +1487,9 @@ namespace Barotrauma
depth); depth);
} }
public static void DrawString(SpriteBatch sb, Vector2 pos, LocalizedString text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null) public static void DrawString(SpriteBatch sb, Vector2 pos, LocalizedString text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null, ForceUpperCase forceUpperCase = ForceUpperCase.Inherit)
{ {
DrawString(sb, pos, text.Value, color, backgroundColor, backgroundPadding, font); DrawString(sb, pos, text.Value, color, backgroundColor, backgroundPadding, font, forceUpperCase);
} }
public static void DrawString(SpriteBatch sb, Vector2 pos, string text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null, ForceUpperCase forceUpperCase = ForceUpperCase.Inherit) public static void DrawString(SpriteBatch sb, Vector2 pos, string text, Color color, Color? backgroundColor = null, int backgroundPadding = 0, GUIFont font = null, ForceUpperCase forceUpperCase = ForceUpperCase.Inherit)
@@ -486,7 +486,7 @@ namespace Barotrauma
{ {
if (bounceTimer > 3.0f || bounceDown) if (bounceTimer > 3.0f || bounceDown)
{ {
RectTransform.ScreenSpaceOffset = new Point(RectTransform.ScreenSpaceOffset.X, (int) -(bounceJump * 10f)); RectTransform.ScreenSpaceOffset = new Point(RectTransform.ScreenSpaceOffset.X, (int) -(bounceJump * 15f * GUI.Scale));
if (!bounceDown) if (!bounceDown)
{ {
bounceJump += deltaTime * 4; bounceJump += deltaTime * 4;
@@ -503,6 +503,7 @@ namespace Barotrauma
bounceJump = 0.0f; bounceJump = 0.0f;
bounceTimer = 0.0f; bounceTimer = 0.0f;
bounceDown = false; bounceDown = false;
Bounce = false;
} }
} }
} }
@@ -730,7 +731,7 @@ namespace Barotrauma
public void DrawToolTip(SpriteBatch spriteBatch) public void DrawToolTip(SpriteBatch spriteBatch)
{ {
if (!Visible) { return; } if (!Visible) { return; }
DrawToolTip(spriteBatch, ToolTip, GUI.MouseOn.Rect); DrawToolTip(spriteBatch, ToolTip, Rect);
} }
public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos) public static void DrawToolTip(SpriteBatch spriteBatch, RichString toolTip, Vector2 pos)
@@ -781,7 +782,7 @@ namespace Barotrauma
if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10) if (toolTipBlock.Rect.Bottom > GameMain.GraphicsHeight - 10)
{ {
toolTipBlock.RectTransform.AbsoluteOffset -= new Point( toolTipBlock.RectTransform.AbsoluteOffset -= new Point(
(targetElement.Width / 2) * Math.Sign(targetElement.Center.X - toolTipBlock.Center.X), 0,
toolTipBlock.Rect.Bottom - (GameMain.GraphicsHeight - 10)); toolTipBlock.Rect.Bottom - (GameMain.GraphicsHeight - 10));
} }
toolTipBlock.SetTextPos(); toolTipBlock.SetTextPos();
@@ -806,9 +807,16 @@ namespace Barotrauma
flashColor = (color == null) ? GUIStyle.Red : (Color)color; flashColor = (color == null) ? GUIStyle.Red : (Color)color;
} }
public void FadeOut(float duration, bool removeAfter, float wait = 0.0f) public void ImmediateFlash(Color? color = null)
{ {
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait)); flashTimer = MathHelper.Pi / 4.0f * 0.1f;
flashDuration = 1.0f *0.1f;
flashColor = (color == null) ? GUIStyle.Red : (Color)color;
}
public void FadeOut(float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null)
{
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter, wait, onRemove));
} }
public void FadeIn(float wait, float duration) public void FadeIn(float wait, float duration)
@@ -870,7 +878,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<CoroutineStatus> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f) private IEnumerable<CoroutineStatus> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f, Action onRemove = null)
{ {
State = ComponentState.None; State = ComponentState.None;
float t = 0.0f; float t = 0.0f;
@@ -895,6 +903,7 @@ namespace Barotrauma
if (removeAfter && Parent != null) if (removeAfter && Parent != null)
{ {
Parent.RemoveChild(this); Parent.RemoveChild(this);
onRemove?.Invoke();
} }
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
@@ -1156,7 +1165,7 @@ namespace Barotrauma
try try
{ {
#if USE_STEAM #if USE_STEAM
Steam.SteamManager.OverlayCustomURL(url); Steam.SteamManager.OverlayCustomUrl(url);
#else #else
ToolBox.OpenFileWithShell(url); ToolBox.OpenFileWithShell(url);
#endif #endif
@@ -62,7 +62,7 @@ namespace Barotrauma
GUIFont headerFont = GUIStyle.SubHeadingFont; GUIFont headerFont = GUIStyle.SubHeadingFont;
GUIFont font = GUIStyle.SmallFont; // font the context menu options use GUIFont font = GUIStyle.SmallFont; // font the context menu options use
Vector4 padding = new Vector4(4), headerPadding = new Vector4(8); Vector4 padding = new Vector4(4), headerPadding = new Vector4(8);
int horizontalPadding = (int) (padding.X + padding.Z), verticalPadding = (int) (padding.Y + padding.W); int horizontalPadding = (int)(padding.X + padding.Z), verticalPadding = (int)(padding.Y + padding.W);
bool hasHeader = !header.IsNullOrWhiteSpace(); bool hasHeader = !header.IsNullOrWhiteSpace();
//---------------------------------------------------------------------------------- //----------------------------------------------------------------------------------
@@ -131,9 +131,15 @@ namespace Barotrauma
optionElement.ToolTip = option.Tooltip; optionElement.ToolTip = option.Tooltip;
} }
if (!option.IsEnabled) //option doesn't do anything, make it a label
if (option.OnSelected == null)
{ {
optionElement.TextColor *= 0.5f; optionElement.TextAlignment = Alignment.BottomLeft;
optionElement.TextColor = optionElement.DisabledTextColor = GUIStyle.Green;
}
else if (!option.IsEnabled)
{
optionElement.TextColor *= 0.5f;
} }
} }
@@ -146,7 +152,10 @@ namespace Barotrauma
// Resize all children to the size of their text // Resize all children to the size of their text
foreach (GUITextBlock block in children.Where(c => c is GUITextBlock).Cast<GUITextBlock>()) foreach (GUITextBlock block in children.Where(c => c is GUITextBlock).Cast<GUITextBlock>())
{ {
block.RectTransform.NonScaledSize = new Point((int) (block.TextSize.X + (block.Padding.X + block.Padding.Z)), (int) (18 * GUI.Scale)); bool isLabel = block.UserData is ContextMenuOption option && option.OnSelected == null;
block.RectTransform.NonScaledSize = new Point(
(int)(block.TextSize.X + (block.Padding.X + block.Padding.Z)),
(int)Math.Max(block.TextSize.Y * 1.2f, 18 * GUI.Scale));
} }
int largestWidth = children.Max(c => c.Rect.Width + horizontalPadding); int largestWidth = children.Max(c => c.Rect.Width + horizontalPadding);
@@ -155,7 +164,7 @@ namespace Barotrauma
if (HeaderLabel != null) if (HeaderLabel != null)
{ {
RectTransform headerTransform = HeaderLabel.RectTransform; RectTransform headerTransform = HeaderLabel.RectTransform;
headerTransform.MinSize = new Point((int) (HeaderLabel.TextSize.X + (headerPadding.X + headerPadding.Z)), headerTransform.NonScaledSize.Y); headerTransform.MinSize = new Point((int)(HeaderLabel.TextSize.X + (headerPadding.X + headerPadding.Z)), headerTransform.NonScaledSize.Y);
if (largestWidth < headerTransform.MinSize.X) if (largestWidth < headerTransform.MinSize.X)
{ {
largestWidth = headerTransform.MinSize.X; largestWidth = headerTransform.MinSize.X;
@@ -171,7 +180,7 @@ namespace Barotrauma
// the cropped size of the option list // the cropped size of the option list
Point newSize = new Point(largestWidth, children.Sum(c => c.Rect.Height) + verticalPadding); Point newSize = new Point(largestWidth, children.Sum(c => c.Rect.Height) + verticalPadding);
// resize the menu itself taking into account the option menus relative Y size // resize the menu itself taking into account the option menus relative Y size
RectTransform.NonScaledSize = new Point(newSize.X, (int) (newSize.Y / optionList.RectTransform.RelativeSize.Y)); RectTransform.NonScaledSize = new Point(newSize.X, (int)(newSize.Y / optionList.RectTransform.RelativeSize.Y));
optionList.RectTransform.NonScaledSize = newSize; optionList.RectTransform.NonScaledSize = newSize;
// move the context menu if it would go outside of screen // move the context menu if it would go outside of screen
@@ -227,8 +236,8 @@ namespace Barotrauma
private Vector2 InflateSize(ref Point size, LocalizedString label, ScalableFont font) private Vector2 InflateSize(ref Point size, LocalizedString label, ScalableFont font)
{ {
Vector2 textSize = font.MeasureString(label); Vector2 textSize = font.MeasureString(label);
size.X = Math.Max((int) Math.Ceiling(textSize.X), size.X); size.X = Math.Max((int)Math.Ceiling(textSize.X), size.X);
size.Y += (int) Math.Ceiling(textSize.Y); size.Y += (int)Math.Ceiling(textSize.Y);
return textSize; return textSize;
} }
@@ -0,0 +1,97 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
public class GUIDragHandle : GUIComponent
{
private readonly RectTransform elementToMove;
private Point originalOffset;
private Vector2 dragStart;
private bool dragStarted;
public Rectangle DragArea;
public Func<RectTransform, bool> ValidatePosition;
public bool Dragging => dragStarted;
public GUIDragHandle(RectTransform rectT, RectTransform elementToMove, string style = "GUIDragIndicator")
: base(style, rectT)
{
enabled = true;
this.elementToMove = elementToMove;
DragArea = new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight);
}
protected override void Update(float deltaTime)
{
if (!Visible) { return; }
base.Update(deltaTime);
if (enabled)
{
if (dragStarted)
{
Point moveAmount = (PlayerInput.MousePosition - dragStart).ToPoint() - elementToMove.ScreenSpaceOffset;
Rectangle rect = elementToMove.Rect;
rect.Location += moveAmount;
moveAmount.X += Math.Max(DragArea.X - rect.X, 0);
moveAmount.X -= Math.Max(rect.Right - DragArea.Right, 0);
moveAmount.Y += Math.Max(DragArea.Y - rect.Y, 0);
moveAmount.Y -= Math.Max(rect.Bottom - DragArea.Bottom, 0);
if (moveAmount != Point.Zero)
{
elementToMove.ScreenSpaceOffset += moveAmount;
}
bool isPositionValid = ValidatePosition == null || ValidatePosition.Invoke(elementToMove);
if (!PlayerInput.PrimaryMouseButtonHeld())
{
if (!isPositionValid)
{
elementToMove.ScreenSpaceOffset = originalOffset;
elementToMove.GUIComponent?.Flash();
SoundPlayer.PlayUISound(GUISoundType.PickItemFail);
}
dragStarted = false;
}
}
else if (Rect.Contains(PlayerInput.MousePosition) && CanBeFocused && Enabled && GUI.IsMouseOn(this) && !(GUI.MouseOn is GUIButton))
{
State = Selected ? ComponentState.HoverSelected : ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonDown())
{
originalOffset = elementToMove.ScreenSpaceOffset;
dragStart = PlayerInput.MousePosition - elementToMove.ScreenSpaceOffset.ToVector2();
dragStarted = true;
}
}
else
{
if (!ExternalHighlight)
{
State = Selected ? ComponentState.Selected : ComponentState.None;
}
else
{
State = ComponentState.Hover;
}
}
}
foreach (GUIComponent child in Children)
{
//allow buttons to handle their states themselves
if (child is GUIButton) { continue; }
child.State = State;
child.Enabled = enabled;
}
}
}
}
@@ -204,15 +204,7 @@ namespace Barotrauma
currentHighestParent = FindHighestParent(); currentHighestParent = FindHighestParent();
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList; currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
rectT.ParentChanged += (RectTransform newParent) => rectT.ParentChanged += _ => RefreshListBoxParent();
{
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
if (newParent != null)
{
currentHighestParent = FindHighestParent();
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
}
};
} }
@@ -396,6 +388,15 @@ namespace Barotrauma
return true; return true;
} }
public void RefreshListBoxParent()
{
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
if (RectTransform.Parent == null) { return; }
currentHighestParent = FindHighestParent();
currentHighestParent.GUIComponent.OnAddedToGUIUpdateList += AddListBoxToGUIUpdateList;
}
private void AddListBoxToGUIUpdateList(GUIComponent parent) private void AddListBoxToGUIUpdateList(GUIComponent parent)
{ {
//the parent is not our parent anymore :( //the parent is not our parent anymore :(
@@ -403,11 +404,13 @@ namespace Barotrauma
//and somewhere between this component and the higher parent a component was removed //and somewhere between this component and the higher parent a component was removed
for (int i = 1; i < parentHierarchy.Count; i++) for (int i = 1; i < parentHierarchy.Count; i++)
{ {
if (!parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false)) if (parentHierarchy[i].IsParentOf(parentHierarchy[i - 1], recursive: false))
{ {
parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList; continue;
return;
} }
parent.OnAddedToGUIUpdateList -= AddListBoxToGUIUpdateList;
return;
} }
if (Dropped) if (Dropped)
@@ -85,11 +85,11 @@ namespace Barotrauma
get { return sprite; } get { return sprite; }
set set
{ {
if (sprite == value) return; if (sprite == value) { return; }
sprite = value; sprite = value;
sourceRect = value == null ? Rectangle.Empty : value.SourceRect; sourceRect = value == null ? Rectangle.Empty : value.SourceRect;
origin = value == null ? Vector2.Zero : value.size / 2; origin = value == null ? Vector2.Zero : value.size / 2;
if (scaleToFit) RecalculateScale(); if (scaleToFit) { RecalculateScale(); }
} }
} }
@@ -49,7 +49,7 @@ namespace Barotrauma
//scaling the bar linearly with the resolution tends to make them too large on large resolutions //scaling the bar linearly with the resolution tends to make them too large on large resolutions
float desiredSize = 25.0f; float desiredSize = 25.0f;
float scaledSize = desiredSize * GUI.Scale; float scaledSize = desiredSize * GUI.Scale;
return (int)((desiredSize + scaledSize) / 2.0f); return (int)Math.Min((desiredSize + scaledSize) / 2.0f, Rect.Height / 3);
} }
} }
@@ -57,7 +57,8 @@ namespace Barotrauma
{ {
SelectSingle, SelectSingle,
SelectMultiple, SelectMultiple,
RequireShiftToSelectMultiple RequireShiftToSelectMultiple,
None
} }
public SelectMode CurrentSelectMode = SelectMode.SelectSingle; public SelectMode CurrentSelectMode = SelectMode.SelectSingle;
@@ -73,6 +74,8 @@ namespace Barotrauma
public bool HideChildrenOutsideFrame = true; public bool HideChildrenOutsideFrame = true;
public bool ResizeContentToMakeSpaceForScrollBar = true;
private bool useGridLayout; private bool useGridLayout;
private GUIComponent scrollToElement; private GUIComponent scrollToElement;
@@ -419,7 +422,7 @@ namespace Barotrauma
{ {
dimensionsNeedsRecalculation = false; dimensionsNeedsRecalculation = false;
ContentBackground.RectTransform.Resize(Rect.Size); ContentBackground.RectTransform.Resize(Rect.Size);
bool reduceScrollbarSize = KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible; bool reduceScrollbarSize = ResizeContentToMakeSpaceForScrollBar && (KeepSpaceForScrollBar ? ScrollBarEnabled : ScrollBarVisible);
Point contentSize = reduceScrollbarSize ? CalculateFrameSize(ScrollBar.IsHorizontal, ScrollBarSize) : Rect.Size; Point contentSize = reduceScrollbarSize ? CalculateFrameSize(ScrollBar.IsHorizontal, ScrollBarSize) : Rect.Size;
Content.RectTransform.Resize(new Point((int)(contentSize.X - Padding.X - Padding.Z), (int)(contentSize.Y - Padding.Y - Padding.W))); Content.RectTransform.Resize(new Point((int)(contentSize.X - Padding.X - Padding.Z), (int)(contentSize.Y - Padding.Y - Padding.W)));
if (!IsScrollBarOnDefaultSide) { Content.RectTransform.SetPosition(Anchor.BottomRight); } if (!IsScrollBarOnDefaultSide) { Content.RectTransform.SetPosition(Anchor.BottomRight); }
@@ -598,16 +601,13 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
float t = 0.0f; float t = 0.0f;
float startScroll = BarScroll * BarSize; float startScroll = BarScroll;
float distanceToTravel = ScrollBar.MaxValue - startScroll; float distanceToTravel = ScrollBar.MaxValue - startScroll;
float progress = startScroll;
float speed = distanceToTravel / duration; float speed = distanceToTravel / duration;
while (t < duration && !MathUtils.NearlyEqual(ScrollBar.MaxValue, BarScroll))
while (t < duration && !MathUtils.NearlyEqual(ScrollBar.MaxValue, progress))
{ {
t += CoroutineManager.DeltaTime; t += CoroutineManager.DeltaTime;
progress += speed * CoroutineManager.DeltaTime; BarScroll += speed * CoroutineManager.DeltaTime;
BarScroll = progress;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
@@ -1056,7 +1056,7 @@ namespace Barotrauma
public void Select(int childIndex, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No) public void Select(int childIndex, Force force = Force.No, AutoScroll autoScroll = AutoScroll.Enabled, TakeKeyBoardFocus takeKeyBoardFocus = TakeKeyBoardFocus.No, PlaySelectSound playSelectSound = PlaySelectSound.No)
{ {
if (childIndex >= Content.CountChildren || childIndex < 0) { return; } if (childIndex >= Content.CountChildren || childIndex < 0 || CurrentSelectMode == SelectMode.None) { return; }
GUIComponent child = Content.GetChild(childIndex); GUIComponent child = Content.GetChild(childIndex);
if (child is null) { return; } if (child is null) { return; }
@@ -1155,6 +1155,7 @@ namespace Barotrauma
public void Select(IEnumerable<GUIComponent> children) public void Select(IEnumerable<GUIComponent> children)
{ {
if (CurrentSelectMode == SelectMode.None) { return; }
Selected = true; Selected = true;
selected.Clear(); selected.Clear();
selected.AddRange(children.Where(c => Content.Children.Contains(c))); selected.AddRange(children.Where(c => Content.Children.Contains(c)));
@@ -1,9 +1,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Barotrauma.Networking;
using Barotrauma.Extensions; using Barotrauma.Extensions;
namespace Barotrauma namespace Barotrauma
@@ -25,15 +23,18 @@ namespace Barotrauma
Default, Default,
InGame, InGame,
Vote, Vote,
Hint Hint,
Tutorial
} }
private bool IsAnimated => type == Type.InGame || type == Type.Hint || type == Type.Tutorial;
public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>(); public List<GUIButton> Buttons { get; private set; } = new List<GUIButton>();
public GUILayoutGroup Content { get; private set; } public GUILayoutGroup Content { get; private set; }
public GUIFrame InnerFrame { get; private set; } public GUIFrame InnerFrame { get; private set; }
public GUITextBlock Header { get; private set; } public GUITextBlock Header { get; private set; }
public GUITextBlock Text { get; private set; } public GUITextBlock Text { get; private set; }
public string Tag { get; private set; } public Identifier Tag { get; private set; }
public bool Closed { get; private set; } public bool Closed { get; private set; }
public bool DisplayInLoadingScreens; public bool DisplayInLoadingScreens;
@@ -60,6 +61,9 @@ namespace Barotrauma
public GUIImage BackgroundIcon { get; private set; } public GUIImage BackgroundIcon { get; private set; }
private GUIImage newBackgroundIcon; private GUIImage newBackgroundIcon;
/// <summary>
/// Close the message box automatically after enough time has passed (<see cref="inGameCloseTime"/>)
/// </summary>
public bool AutoClose; public bool AutoClose;
private float openState; private float openState;
@@ -69,6 +73,13 @@ namespace Barotrauma
private readonly Type type; private readonly Type type;
/// <summary>
/// Close the message box automatically if the condition is met
/// </summary>
private readonly Func<bool> autoCloseCondition;
public bool FlashOnAutoCloseCondition { get; set; }
public Type MessageBoxType => type; public Type MessageBoxType => type;
public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault(); public static GUIComponent VisibleBox => MessageBoxes.LastOrDefault();
@@ -79,7 +90,9 @@ namespace Barotrauma
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
} }
public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null) public GUIMessageBox(RichString headerText, RichString text, LocalizedString[] buttons,
Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "",
Sprite icon = null, string iconStyle = "", Sprite backgroundIcon = null, Func<bool> autoCloseCondition = null, bool hideCloseButton = false)
: base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUIStyle.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox") : base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUIStyle.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
{ {
int width = (int)(DefaultWidth * type switch int width = (int)(DefaultWidth * type switch
@@ -116,6 +129,7 @@ namespace Barotrauma
Anchor anchor = type switch Anchor anchor = type switch
{ {
Type.InGame => Anchor.TopCenter, Type.InGame => Anchor.TopCenter,
Type.Tutorial => Anchor.TopCenter,
Type.Hint => Anchor.TopRight, Type.Hint => Anchor.TopRight,
Type.Vote => Anchor.TopRight, Type.Vote => Anchor.TopRight,
_ => Anchor.Center _ => Anchor.Center
@@ -130,7 +144,7 @@ namespace Barotrauma
} }
GUIStyle.Apply(InnerFrame, "", this); GUIStyle.Apply(InnerFrame, "", this);
this.type = type; this.type = type;
Tag = tag; 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)
@@ -187,12 +201,13 @@ namespace Barotrauma
var button = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f / buttons.Length), buttonContainer.RectTransform), buttons[i]); var button = new GUIButton(new RectTransform(new Vector2(0.6f, 1.0f / buttons.Length), buttonContainer.RectTransform), buttons[i]);
Buttons.Add(button); Buttons.Add(button);
} }
GUITextBlock.AutoScaleAndNormalize(Buttons.Select(btn => btn.TextBlock));
} }
else if (type == Type.InGame) else if (type == Type.InGame || type == Type.Tutorial)
{ {
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
CanBeFocused = false; CanBeFocused = false;
AutoClose = true; AutoClose = type == Type.InGame;
GUIStyle.Apply(InnerFrame, "", this); GUIStyle.Apply(InnerFrame, "", this);
var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), InnerFrame.RectTransform, Anchor.Center), var horizontalLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.98f, 0.95f), InnerFrame.RectTransform, Anchor.Center),
@@ -212,37 +227,43 @@ namespace Barotrauma
Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform)); Content = new GUILayoutGroup(new RectTransform(new Vector2(Icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform));
var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null); if (!hideCloseButton)
Buttons = new List<GUIButton>(1)
{ {
new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center), var buttonContainer = new GUIFrame(new RectTransform(new Vector2(0.15f, 1.0f), horizontalLayoutGroup.RectTransform), style: null);
style: "UIToggleButton") Buttons = new List<GUIButton>(1)
{ {
OnClicked = Close new GUIButton(new RectTransform(new Vector2(0.3f, 0.5f), buttonContainer.RectTransform, Anchor.Center),
} style: "UIToggleButton")
};
InputType? closeInput = null;
if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Use].MouseButton == MouseButton.None)
{
closeInput = InputType.Use;
}
else if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select].MouseButton == MouseButton.None)
{
closeInput = InputType.Select;
}
if (closeInput.HasValue)
{
Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("Close")} ([InputType.{closeInput.Value}])");
Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) =>
{
if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value))
{ {
GUIButton btn = component as GUIButton; OnClicked = Close
btn?.OnClicked(btn, btn.UserData);
btn?.Flash(GUIStyle.Green);
} }
}; };
InputType? closeInput = null;
if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Use].MouseButton == MouseButton.None)
{
closeInput = InputType.Use;
}
else if (GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select].MouseButton == MouseButton.None)
{
closeInput = InputType.Select;
}
if (closeInput.HasValue)
{
Buttons[0].ToolTip = TextManager.ParseInputTypes($"{TextManager.Get("Close")} ([InputType.{closeInput.Value}])");
Buttons[0].OnAddedToGUIUpdateList += (GUIComponent component) =>
{
if (!closing && openState >= 1.0f && PlayerInput.KeyHit(closeInput.Value))
{
GUIButton btn = component as GUIButton;
btn?.OnClicked(btn, btn.UserData);
btn?.Flash(GUIStyle.Green);
}
};
}
}
else
{
Buttons.Clear();
} }
Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true); Header = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), Content.RectTransform), headerText, wrap: true);
@@ -274,7 +295,10 @@ namespace Barotrauma
Content.RectTransform.NonScaledSize = Content.RectTransform.NonScaledSize =
new Point(Content.Rect.Width, height); new Point(Content.Rect.Width, height);
} }
Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y); if (!hideCloseButton)
{
Buttons[0].RectTransform.MaxSize = new Point((int)(0.4f * Buttons[0].Rect.Y), Buttons[0].Rect.Y);
}
} }
else if (type == Type.Hint) else if (type == Type.Hint)
{ {
@@ -408,6 +432,8 @@ namespace Barotrauma
} }
} }
this.autoCloseCondition = autoCloseCondition;
MessageBoxes.Add(this); MessageBoxes.Add(this);
} }
@@ -448,7 +474,7 @@ namespace Barotrauma
{ {
// Message box not of type GUIMessageBox is likely the round summary // Message box not of type GUIMessageBox is likely the round summary
MessageBoxes[i].AddToGUIUpdateList(); MessageBoxes[i].AddToGUIUpdateList();
break; if (!(MessageBoxes[i].UserData is RoundSummary)) { break; }
} }
continue; continue;
} }
@@ -471,6 +497,7 @@ namespace Barotrauma
public void SetBackgroundIcon(Sprite icon) public void SetBackgroundIcon(Sprite icon)
{ {
if (icon == null) { return; } if (icon == null) { return; }
if (icon == BackgroundIcon?.Sprite) { return; }
GUIImage newIcon = new GUIImage(new RectTransform(icon.size.ToPoint(), RectTransform), icon) GUIImage newIcon = new GUIImage(new RectTransform(icon.size.ToPoint(), RectTransform), icon)
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
@@ -510,10 +537,10 @@ namespace Barotrauma
} }
} }
if (type == Type.InGame || type == Type.Hint) if (IsAnimated)
{ {
Vector2 initialPos, defaultPos, endPos; Vector2 initialPos, defaultPos, endPos;
if (type == Type.InGame) if (type == Type.InGame || type == Type.Tutorial)
{ {
initialPos = new Vector2(0.0f, GameMain.GraphicsHeight); initialPos = new Vector2(0.0f, GameMain.GraphicsHeight);
defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale); defaultPos = new Vector2(0.0f, HUDLayoutSettings.InventoryAreaLower.Y - InnerFrame.Rect.Height - 20 * GUI.Scale);
@@ -552,6 +579,14 @@ namespace Barotrauma
{ {
Close(); Close();
} }
else if (autoCloseCondition != null && autoCloseCondition())
{
Close();
if (FlashOnAutoCloseCondition)
{
InnerFrame.Flash(GUIStyle.Green);
}
}
} }
else else
{ {
@@ -593,7 +628,7 @@ namespace Barotrauma
{ {
newBackgroundIcon.SetAsFirstChild(); newBackgroundIcon.SetAsFirstChild();
newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int)(newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2); newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int)(newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2);
newBackgroundIcon.Color = ToolBox.GradientLerp(iconState, Color.Transparent, Color.White); newBackgroundIcon.Color = Color.Lerp(Color.Transparent, Color.White, iconState);
if (newBackgroundIcon.Color.A == 255) if (newBackgroundIcon.Color.A == 255)
{ {
BackgroundIcon = newBackgroundIcon; BackgroundIcon = newBackgroundIcon;
@@ -608,10 +643,9 @@ namespace Barotrauma
} }
} }
public void Close() public void Close()
{ {
if (type == Type.InGame || type == Type.Hint) if (IsAnimated)
{ {
closing = true; closing = true;
} }
@@ -636,6 +670,19 @@ namespace Barotrauma
MessageBoxes.Clear(); MessageBoxes.Clear();
} }
public static void Close(Identifier tag)
{
foreach (var messageBox in MessageBoxes)
{
if (messageBox is GUIMessageBox mb && mb.Tag == tag)
{
mb.Close();
}
}
}
public static void Close(string tag) => Close(tag.ToIdentifier());
/// <summary> /// <summary>
/// Parent does not matter. It's overridden. /// Parent does not matter. It's overridden.
/// </summary> /// </summary>
@@ -178,19 +178,19 @@ namespace Barotrauma
{ {
public GUIFont(string identifier) : base(identifier) { } public GUIFont(string identifier) : base(identifier) { }
public bool HasValue => Prefabs.Any(); public bool HasValue => !Prefabs.IsEmpty;
public ScalableFont Value => Prefabs.ActivePrefab.Font; public ScalableFont Value => Prefabs.ActivePrefab.Font;
public static implicit operator ScalableFont(GUIFont reference) => reference.Value; public static implicit operator ScalableFont(GUIFont reference) => reference.Value;
public bool ForceUpperCase => HasValue && Value.ForceUpperCase; public bool ForceUpperCase => Prefabs.ActivePrefab?.Font is { ForceUpperCase: true };
public uint Size => HasValue ? Value.Size : 0; public uint Size => HasValue ? Value.Size : 0;
private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value); private ScalableFont GetFontForStr(LocalizedString str) => GetFontForStr(str.Value);
private ScalableFont GetFontForStr(string str) => public ScalableFont GetFontForStr(string str) =>
TextManager.IsCJK(str) ? Prefabs.ActivePrefab.CjkFont : Prefabs.ActivePrefab.Font; TextManager.IsCJK(str) ? Prefabs.ActivePrefab.CjkFont : Prefabs.ActivePrefab.Font;
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 se, float layerDepth)
@@ -141,11 +141,32 @@ namespace Barotrauma
public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium"); public readonly static GUIColor HealthBarColorMedium = new GUIColor("HealthBarColorMedium");
public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh"); public readonly static GUIColor HealthBarColorHigh = new GUIColor("HealthBarColorHigh");
public static Point ItemFrameMargin => new Point(50, 56).Multiply(GUI.SlicedSpriteScale); public static Point ItemFrameMargin
{
get
{
Point size = new Point(50, 56).Multiply(GUI.SlicedSpriteScale);
var style = GetComponentStyle("ItemUI");
var sprite = style?.Sprites[GUIComponent.ComponentState.None].First();
if (sprite != null)
{
size.X = Math.Min(sprite.Slices[0].Width + sprite.Slices[2].Width, size.X);
size.Y = Math.Min(sprite.Slices[0].Height + sprite.Slices[6].Height, size.Y);
}
return size;
}
}
public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale); public static Point ItemFrameOffset => new Point(0, 3).Multiply(GUI.SlicedSpriteScale);
public static GUIComponentStyle GetComponentStyle(string name) public static GUIComponentStyle GetComponentStyle(string styleName)
=> ComponentStyles.ContainsKey(name) ? ComponentStyles[name] : null; {
return GetComponentStyle(styleName.ToIdentifier());
}
public static GUIComponentStyle GetComponentStyle(Identifier identifier)
=> ComponentStyles.TryGet(identifier, out var style) ? style : null;
public static void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null) public static void Apply(GUIComponent targetComponent, string styleName = "", GUIComponent parent = null)
{ {
@@ -587,7 +587,7 @@ namespace Barotrauma
if (Shadow) if (Shadow)
{ {
Vector2 shadowOffset = new Vector2(GUI.IntScale(2)); Vector2 shadowOffset = new Vector2(Math.Max(GUI.IntScale(2), 1));
Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth, alignment: textAlignment, forceUpperCase: ForceUpperCase); Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth, alignment: textAlignment, forceUpperCase: ForceUpperCase);
} }
@@ -412,7 +412,8 @@ namespace Barotrauma
return; return;
} }
if (MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this)))) bool isMouseOn = MouseRect.Contains(PlayerInput.MousePosition) && (GUI.MouseOn == null || (!(GUI.MouseOn is GUIButton) && GUI.IsMouseOn(this)));
if (isMouseOn || isSelecting)
{ {
State = ComponentState.Hover; State = ComponentState.Hover;
if (PlayerInput.PrimaryMouseButtonDown()) if (PlayerInput.PrimaryMouseButtonDown())
@@ -448,10 +449,6 @@ namespace Barotrauma
isSelecting = false; isSelecting = false;
State = ComponentState.None; State = ComponentState.None;
} }
if (!isSelecting)
{
isSelecting = PlayerInput.IsShiftDown();
}
if (mouseHeldInside && !PlayerInput.PrimaryMouseButtonHeld()) if (mouseHeldInside && !PlayerInput.PrimaryMouseButtonHeld())
{ {
@@ -177,7 +177,7 @@ namespace Barotrauma
radioButtonGroup = rbg; radioButtonGroup = rbg;
} }
private void ResizeBox() public void ResizeBox()
{ {
Vector2 textBlockScale = new Vector2(Math.Max(Rect.Width - box.Rect.Width, 0.0f) / Math.Max(Rect.Width, 1.0f), 1.0f); Vector2 textBlockScale = new Vector2(Math.Max(Rect.Width - box.Rect.Width, 0.0f) / Math.Max(Rect.Width, 1.0f), 1.0f);
text.RectTransform.RelativeSize = textBlockScale; text.RectTransform.RelativeSize = textBlockScale;
@@ -25,6 +25,10 @@ namespace Barotrauma
{ {
get; private set; get; private set;
} }
public static Rectangle TutorialObjectiveListArea
{
get; private set;
}
public static Rectangle MessageAreaTop public static Rectangle MessageAreaTop
{ {
@@ -81,6 +85,11 @@ namespace Barotrauma
get; private set; get; private set;
} }
public static Rectangle ItemHUDArea
{
get; private set;
}
public static int Padding public static int Padding
{ {
get; private set; get; private set;
@@ -162,25 +171,35 @@ namespace Barotrauma
HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight); HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight);
int objectiveListAreaX = HealthWindowAreaLeft.Right + Padding;
int objectiveListAreaY = ButtonAreaTop.Bottom + Padding;
TutorialObjectiveListArea = new Rectangle(objectiveListAreaX, objectiveListAreaY, (GameMain.GraphicsWidth - Padding) - objectiveListAreaX, (AfflictionAreaLeft.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;
int votingAreaY = Padding + ButtonAreaTop.Height; int votingAreaY = Padding + ButtonAreaTop.Height;
// Height is based on text content // Height is based on text content
VotingArea = new Rectangle(votingAreaX, votingAreaY, votingAreaWidth, 0); VotingArea = new Rectangle(votingAreaX, votingAreaY, votingAreaWidth, 0);
ItemHUDArea = new Rectangle(0, ButtonAreaTop.Bottom, GameMain.GraphicsWidth, GameMain.GraphicsHeight - ButtonAreaTop.Bottom - InventoryAreaLower.Height);
} }
public static void Draw(SpriteBatch spriteBatch) public static void Draw(SpriteBatch spriteBatch)
{ {
GUI.DrawRectangle(spriteBatch, ButtonAreaTop, Color.White * 0.5f); DrawRectangle(ButtonAreaTop, Color.White * 0.5f);
GUI.DrawRectangle(spriteBatch, MessageAreaTop, GUIStyle.Orange * 0.5f); DrawRectangle(TutorialObjectiveListArea, GUIStyle.Blue * 0.5f);
GUI.DrawRectangle(spriteBatch, CrewArea, Color.Blue * 0.5f); DrawRectangle(MessageAreaTop, GUIStyle.Orange * 0.5f);
GUI.DrawRectangle(spriteBatch, ChatBoxArea, Color.Cyan * 0.5f); DrawRectangle(CrewArea, Color.Blue * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthBarArea, Color.Red * 0.5f); DrawRectangle(ChatBoxArea, Color.Cyan * 0.5f);
GUI.DrawRectangle(spriteBatch, AfflictionAreaLeft, Color.Red * 0.5f); DrawRectangle(HealthBarArea, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, InventoryAreaLower, Color.Yellow * 0.5f); DrawRectangle(AfflictionAreaLeft, Color.Red * 0.5f);
GUI.DrawRectangle(spriteBatch, HealthWindowAreaLeft, Color.Red * 0.5f); DrawRectangle(InventoryAreaLower, Color.Yellow * 0.5f);
GUI.DrawRectangle(spriteBatch, BottomRightInfoArea, Color.Green * 0.5f); DrawRectangle(HealthWindowAreaLeft, Color.Red * 0.5f);
DrawRectangle(BottomRightInfoArea, Color.Green * 0.5f);
DrawRectangle(ItemHUDArea, Color.Magenta * 0.3f);
void DrawRectangle(Rectangle r, Color c) => GUI.DrawRectangle(spriteBatch, r, c);
} }
} }
@@ -233,7 +233,9 @@ namespace Barotrauma
get get
{ {
Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor); Point absoluteOffset = ConvertOffsetRelativeToAnchor(AbsoluteOffset, Anchor);
Point relativeOffset = NonScaledParentRect.MultiplySize(RelativeOffset); Point relativeOffset = new Point(
(int)(NonScaledParentSize.X * RelativeOffset.X),
(int)(NonScaledParentSize.Y * RelativeOffset.Y));
relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor); relativeOffset = ConvertOffsetRelativeToAnchor(relativeOffset, Anchor);
return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset; return AnchorPoint + PivotOffset + absoluteOffset + relativeOffset + ScreenSpaceOffset;
} }
@@ -256,6 +258,7 @@ namespace Barotrauma
public Rectangle ParentRect => Parent != null ? Parent.Rect : UIRect; public Rectangle ParentRect => Parent != null ? Parent.Rect : UIRect;
protected Rectangle NonScaledRect => new Rectangle(NonScaledTopLeft, NonScaledSize); protected Rectangle NonScaledRect => new Rectangle(NonScaledTopLeft, NonScaledSize);
protected virtual Rectangle NonScaledUIRect => NonScaledRect; protected virtual Rectangle NonScaledUIRect => NonScaledRect;
protected Point NonScaledParentSize => parent?.NonScaledSize ?? new Point(GUI.UIWidth, GameMain.GraphicsHeight);
protected Rectangle NonScaledParentRect => parent != null ? Parent.NonScaledRect : UIRect; protected Rectangle NonScaledParentRect => parent != null ? Parent.NonScaledRect : UIRect;
protected Rectangle NonScaledParentUIRect => parent != null ? Parent.NonScaledUIRect : UIRect; protected Rectangle NonScaledParentUIRect => parent != null ? Parent.NonScaledUIRect : UIRect;
protected Rectangle UIRect => new Rectangle(0, 0, GUI.UIWidth, GameMain.GraphicsHeight); protected Rectangle UIRect => new Rectangle(0, 0, GUI.UIWidth, GameMain.GraphicsHeight);
@@ -336,6 +339,11 @@ namespace Barotrauma
public event Action<RectTransform> ChildrenChanged; public event Action<RectTransform> ChildrenChanged;
public event Action ScaleChanged; public event Action ScaleChanged;
public event Action SizeChanged; public event Action SizeChanged;
public void ResetSizeChanged()
{
SizeChanged = null;
}
#endregion #endregion
#region Initialization #region Initialization
@@ -730,17 +738,17 @@ namespace Barotrauma
get { return animTargetPos ?? AbsoluteOffset; } get { return animTargetPos ?? AbsoluteOffset; }
} }
public void MoveOverTime(Point targetPos, float duration) public void MoveOverTime(Point targetPos, float duration, Action onDoneMoving = null)
{ {
animTargetPos = targetPos; animTargetPos = targetPos;
CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration)); CoroutineManager.StartCoroutine(DoMoveAnimation(targetPos, duration, onDoneMoving));
} }
public void ScaleOverTime(Point targetSize, float duration) public void ScaleOverTime(Point targetSize, float duration)
{ {
CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration)); CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration));
} }
private IEnumerable<CoroutineStatus> DoMoveAnimation(Point targetPos, float duration) private IEnumerable<CoroutineStatus> DoMoveAnimation(Point targetPos, float duration, Action onDoneMoving = null)
{ {
Vector2 startPos = AbsoluteOffset.ToVector2(); Vector2 startPos = AbsoluteOffset.ToVector2();
float t = 0.0f; float t = 0.0f;
@@ -752,6 +760,7 @@ namespace Barotrauma
} }
AbsoluteOffset = targetPos; AbsoluteOffset = targetPos;
animTargetPos = null; animTargetPos = null;
onDoneMoving?.Invoke();
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<CoroutineStatus> DoScaleAnimation(Point targetSize, float duration) private IEnumerable<CoroutineStatus> DoScaleAnimation(Point targetSize, float duration)
@@ -238,7 +238,7 @@ namespace Barotrauma
errorId = "Store.SelectStore:StoreDoesntExist"; errorId = "Store.SelectStore:StoreDoesntExist";
msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location."; msg = $"Error selecting store with identifier \"{identifier}\" at {CurrentLocation}: store with the identifier doesn't exist at the location.";
} }
DebugConsole.ShowError(msg); DebugConsole.LogError(msg);
GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
} }
} }
@@ -263,7 +263,7 @@ namespace Barotrauma
} }
if (!msg.IsNullOrEmpty()) if (!msg.IsNullOrEmpty())
{ {
DebugConsole.ShowError(msg); DebugConsole.LogError(msg);
GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg); GameAnalyticsManager.AddErrorEventOnce(errorId, GameAnalyticsManager.ErrorSeverity.Error, msg);
} }
} }
@@ -1250,7 +1250,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.ShowError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error getting item price: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
} }
} }
@@ -1808,7 +1808,7 @@ namespace Barotrauma
{ {
string errorMsg = $"Error creating a store quantity label text: unknown store tab.\n{e.StackTrace.CleanupStackTrace()}"; string errorMsg = $"Error creating a store quantity label text: unknown store tab.\n{e.StackTrace.CleanupStackTrace()}";
#if DEBUG #if DEBUG
DebugConsole.ShowError(errorMsg); DebugConsole.LogError(errorMsg);
#else #else
DebugConsole.AddWarning(errorMsg); DebugConsole.AddWarning(errorMsg);
#endif #endif
@@ -1882,7 +1882,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.ShowError($"Error getting item availability: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error getting item availability: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
} }
if (list != null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item) if (list != null && list.Find(i => i.ItemPrefab == itemPrefab) is PurchasedItem item)
{ {
@@ -1962,7 +1962,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.ShowError($"Error adding an item to the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error adding an item to the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
return false; return false;
} }
} }
@@ -1982,7 +1982,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.ShowError($"Error clearing the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error clearing the shopping crate: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
return false; return false;
} }
} }
@@ -2029,7 +2029,7 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.ShowError($"Error confirming the store transaction: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error confirming the store transaction: Uknown store tab type. {e.StackTrace.CleanupStackTrace()}");
return false; return false;
} }
var itemsToRemove = new List<PurchasedItem>(); var itemsToRemove = new List<PurchasedItem>();
@@ -56,6 +56,7 @@ namespace Barotrauma
public SubmarineInfo displayedSubmarine; public SubmarineInfo displayedSubmarine;
public GUITextBlock submarineName; public GUITextBlock submarineName;
public GUITextBlock submarineClass; public GUITextBlock submarineClass;
public GUITextBlock submarineTier;
public GUITextBlock submarineFee; public GUITextBlock submarineFee;
public GUIButton selectSubmarineButton; public GUIButton selectSubmarineButton;
public GUITextBlock middleTextBlock; public GUITextBlock middleTextBlock;
@@ -162,7 +163,12 @@ namespace Barotrauma
IgnoreLayoutGroups = true IgnoreLayoutGroups = true
}; };
new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)) { IgnoreLayoutGroups = true, CanBeFocused = false }; new GUIListBox(new RectTransform(Vector2.One, infoFrame.RectTransform)) { IgnoreLayoutGroups = true, CanBeFocused = false };
specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null) { Spacing = GUI.IntScale(5), Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0) }; specsFrame = new GUIListBox(new RectTransform(new Vector2(0.39f, 1f), infoFrame.RectTransform), style: null)
{
CurrentSelectMode = GUIListBox.SelectMode.None,
Spacing = GUI.IntScale(5),
Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding, 0, 0)
};
new GUIFrame(new RectTransform(new Vector2(0.02f, 0.8f), infoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "VerticalLine"); new GUIFrame(new RectTransform(new Vector2(0.02f, 0.8f), infoFrame.RectTransform) { RelativeOffset = new Vector2(0.0f, 0.1f) }, style: "VerticalLine");
GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) }; GUIListBox descriptionFrame = new GUIListBox(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform), style: null) { Padding = new Vector4(HUDLayoutSettings.Padding / 2f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding * 1.5f, HUDLayoutSettings.Padding / 2f) };
descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false }; descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUIStyle.Font, wrap: true) { CanBeFocused = false };
@@ -220,16 +226,18 @@ namespace Barotrauma
}; };
submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true); submarineDisplayElement.submarineImage = new GUIImage(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), null, true);
submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center); submarineDisplayElement.middleTextBlock = new GUITextBlock(new RectTransform(new Vector2(0.8f, 1f), submarineDisplayElement.background.RectTransform, Anchor.Center), string.Empty, textAlignment: Alignment.Center);
submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont); submarineDisplayElement.submarineName = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter, Pivot.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Center); submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
submarineDisplayElement.submarineFee = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.BottomCenter, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUIStyle.SubHeadingFont);
submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null); submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null);
submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, pivot: Pivot.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton") submarineDisplayElement.previewButton = new GUIButton(new RectTransform(Vector2.One * 0.12f, submarineDisplayElement.background.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { AbsoluteOffset = new Point((int)(0.03f * background.Rect.Height)) }, style: "ExpandButton")
{ {
Color = Color.White, Color = Color.White,
HoverColor = Color.White, HoverColor = Color.White,
PressedColor = Color.White PressedColor = Color.White
}; };
submarineDisplayElement.submarineClass = new GUITextBlock(new RectTransform(new Vector2(1f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Left);
submarineDisplayElement.submarineTier = new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.1f), submarineDisplayElement.background.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding + (int)GUIStyle.Font.MeasureString(submarineDisplayElement.submarineName.Text).Y) }, string.Empty, textAlignment: Alignment.Right);
submarineDisplays[i] = submarineDisplayElement; submarineDisplays[i] = submarineDisplayElement;
} }
@@ -355,6 +363,7 @@ namespace Barotrauma
submarineDisplays[i].submarineName.Text = string.Empty; submarineDisplays[i].submarineName.Text = string.Empty;
submarineDisplays[i].submarineFee.Text = string.Empty; submarineDisplays[i].submarineFee.Text = string.Empty;
submarineDisplays[i].submarineClass.Text = string.Empty; submarineDisplays[i].submarineClass.Text = string.Empty;
submarineDisplays[i].submarineTier.Text = string.Empty;
submarineDisplays[i].selectSubmarineButton.Enabled = false; submarineDisplays[i].selectSubmarineButton.Enabled = false;
submarineDisplays[i].selectSubmarineButton.OnClicked = null; submarineDisplays[i].selectSubmarineButton.OnClicked = null;
submarineDisplays[i].displayedSubmarine = null; submarineDisplays[i].displayedSubmarine = null;
@@ -388,7 +397,12 @@ namespace Barotrauma
}; };
submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName; submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName;
submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}")); submarineDisplays[i].submarineClass.Text = TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"));
submarineDisplays[i].submarineClass.ToolTip = TextManager.Get("submarineclass.description") + "\n\n" + TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}.description");
submarineDisplays[i].submarineTier.Text = TextManager.Get($"submarinetier.{subToDisplay.Tier}");
submarineDisplays[i].submarineTier.ToolTip = TextManager.Get("submarinetier.description");
if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay)) if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay))
{ {
@@ -847,7 +861,7 @@ namespace Barotrauma
msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext", msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext",
("[submarinename]", selectedSubmarine.DisplayName), ("[submarinename]", selectedSubmarine.DisplayName),
("[amount]", selectedSubmarine.Price.ToString()), ("[amount]", selectedSubmarine.Price.ToString()),
("[currencyname]", currencyName)), messageBoxOptions); ("[currencyname]", currencyName)) + '\n' + TextManager.Get("submarineswitchinstruction"), messageBoxOptions);
msgBox.Buttons[0].OnClicked = (applyButton, obj) => msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{ {
@@ -206,9 +206,9 @@ 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 is { } controlled && talentResetButton != null && talentApplyButton != null) if (Character.Controlled?.Info is { } characterInfo && talentResetButton != null && talentApplyButton != null)
{ {
int talentCount = selectedTalents.Count - controlled.Info.GetUnlockedTalentsInTree().Count(); int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0; talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f) if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
{ {
@@ -403,7 +403,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub); CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break; break;
case InfoFrameTab.Talents: case InfoFrameTab.Talents:
CreateTalentInfo(infoFrameHolder); CreateCharacterInfo(infoFrameHolder);
break; break;
} }
} }
@@ -631,7 +631,7 @@ namespace Barotrauma
linkedGUIList = new List<LinkedGUI>(); linkedGUIList = new List<LinkedGUI>();
List<Client> connectedClients = GameMain.Client.ConnectedClients; var connectedClients = GameMain.Client.ConnectedClients;
for (int i = 0; i < teamIDs.Count; i++) for (int i = 0; i < teamIDs.Count; i++)
{ {
@@ -1722,7 +1722,11 @@ namespace Barotrauma
var subInfoTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedFrame.RectTransform)); var subInfoTextLayout = new GUILayoutGroup(new RectTransform(Vector2.One, paddedFrame.RectTransform));
LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}") : TextManager.Get("shuttle"); LocalizedString className = !sub.Info.HasTag(SubmarineTag.Shuttle) ?
TextManager.GetWithVariables("submarine.classandtier",
("[class]", TextManager.Get($"submarineclass.{sub.Info.SubmarineClass}")),
("[tier]", TextManager.Get($"submarinetier.{sub.Info.Tier}"))) :
TextManager.Get("shuttle");
int nameHeight = (int)GUIStyle.LargeFont.MeasureString(sub.Info.DisplayName, true).Y; int nameHeight = (int)GUIStyle.LargeFont.MeasureString(sub.Info.DisplayName, true).Y;
int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y; int classHeight = (int)GUIStyle.SubHeadingFont.MeasureString(className).Y;
@@ -1763,7 +1767,10 @@ namespace Barotrauma
} }
else else
{ {
var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft)); var specsListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.57f), paddedFrame.RectTransform, Anchor.BottomLeft, Pivot.BottomLeft))
{
CurrentSelectMode = GUIListBox.SelectMode.None
};
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true); sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
} }
} }
@@ -1803,162 +1810,126 @@ namespace Barotrauma
{ TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) }, { TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) },
}.ToImmutableDictionary(); }.ToImmutableDictionary();
private void CreateTalentInfo(GUIFrame infoFrame) private void CreateCharacterInfo(GUIFrame infoFrame)
{ {
infoFrame.ClearChildren(); infoFrame.ClearChildren();
talentButtons.Clear(); talentButtons.Clear();
talentCornerIcons.Clear(); talentCornerIcons.Clear();
GUIFrame talentFrameBackground = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = GUI.IntScale(15); int padding = GUI.IntScale(15);
GUIFrame talentFrameContent = new GUIFrame(new RectTransform(new Point(talentFrameBackground.Rect.Width - padding, talentFrameBackground.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null); GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
GUIFrame paddedTalentFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), talentFrameContent.RectTransform, Anchor.Center), style: null); GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
GUIFrame talentFrameMain = new GUIFrame(new RectTransform(Vector2.One, paddedTalentFrame.RectTransform), style: null);
GUIFrame characterSettingsFrame = null; GUIFrame characterSettingsFrame = null;
GUILayoutGroup characterLayout = null; GUILayoutGroup characterLayout = null;
if (!(GameMain.NetworkMember is null)) if (!(GameMain.NetworkMember is null))
{ {
characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrameContent.RectTransform), style: null) { Visible = false }; characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: null) { Visible = false };
characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform)); 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 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); 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); GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
} }
/*Character controlledCharacter = Character.Controlled;
if (controlledCharacter == null) { return; }
if (controlledCharacter.Info is null)
{
DebugConsole.ThrowError("No character info found for talent UI");
return;
}*/
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo; CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
if (info == null) { return; } if (info == null) { return; }
Job job = info.Job; Job job = info.Job;
GUILayoutGroup talentFrameLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), talentFrameMain.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter) GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
AbsoluteSpacing = GUI.IntScale(5) AbsoluteSpacing = GUI.IntScale(10),
Stretch = true
}; };
GUILayoutGroup talentInfoLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.25f), talentFrameLayoutGroup.RectTransform, Anchor.Center), isHorizontal: 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) =>
new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), talentInfoLayoutGroup.RectTransform), onDraw: (batch, component) =>
{ {
float posY = component.Rect.Center.Y - component.Rect.Width / 2; 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); 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), talentInfoLayoutGroup.RectTransform)) { RelativeSpacing = 0.05f }; GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
{
AbsoluteSpacing = GUI.IntScale(5),
CanBeFocused = true
};
Vector2 nameSize = GUIStyle.SubHeadingFont.MeasureString(info.Name); GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
nameBlock.RectTransform.NonScaledSize = nameSize.Pad(nameBlock.Padding).ToPoint();
if (!info.OmitJobInMenus) if (!info.OmitJobInMenus)
{ {
nameBlock.TextColor = job.Prefab.UIColor; nameBlock.TextColor = job.Prefab.UIColor;
Vector2 jobSize = GUIStyle.SmallFont.MeasureString(job.Name); GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
jobBlock.RectTransform.NonScaledSize = jobSize.Pad(jobBlock.Padding).ToPoint();
} }
LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), TextManager.Get("personalitytrait." + info.PersonalityTrait.Name.Replace(" ", ""))); LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString); Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont); GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint(); traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
GUIFrame talentsOutsideTreeFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.35f), nameLayout.RectTransform, Anchor.BottomCenter), style: null); IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
if (talentsOutsideTree.Count() > 0)
if (!(GameMain.NetworkMember is null))
{ {
GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.675f, 1f), talentsOutsideTreeFrame.RectTransform, Anchor.TopLeft), //spacing
text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew")) 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)
{ {
IgnoreLayoutGroups = true AutoHideScrollBar = false,
ResizeContentToMakeSpaceForScrollBar = false
}; };
newCharacterBox.TextBlock.AutoScaleHorizontal = true; extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
extraTalentLayout.Recalculate();
extraTalentList.ForceLayoutRecalculation();
newCharacterBox.OnClicked = (button, o) => foreach (var extraTalent in talentsOutsideTree)
{ {
if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded) var img = new GUIImage(new RectTransform(new Point(extraTalentList.Content.Rect.Height), extraTalentList.Content.RectTransform), sprite: extraTalent.Icon, scaleToFit: true)
{ {
GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() => ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
{ Color = GUIStyle.Green
newCharacterBox.Text = TextManager.Get("settings");
if (pendingChangesFrame != null)
{
NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
}
OpenMenu();
});
return true;
}
OpenMenu();
return true;
void OpenMenu()
{
characterSettingsFrame!.Visible = true;
talentFrameMain.Visible = false;
}
};
if (!(characterLayout is null))
{
GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomRight);
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;
talentFrameMain.Visible = true;
return true;
}
}; };
img.RectTransform.SizeChanged += () =>
{
img.RectTransform.MaxSize = new Point(img.Rect.Height);
};
} }
} }
IEnumerable<TalentPrefab> talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)); GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
if (talentsOutsideTree.Count() > 0)
{ {
//TODO: replace with something more generic AbsoluteSpacing = GUI.IntScale(5),
GUIImage endocrineIcon = new GUIImage(new RectTransform(new Vector2(0.275f, 1f), talentsOutsideTreeFrame.RectTransform, anchor: Anchor.TopRight, scaleBasis: ScaleBasis.Normal), style: "EndocrineReminderIcon") Stretch = true
{ };
ToolTip = $"{TextManager.Get("afflictionname.endocrineboost")}\n\n{string.Join(", ", talentsOutsideTree.Select(e => e.DisplayName))}"
};
}
GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), talentInfoLayoutGroup.RectTransform)) { Stretch = true }; GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
LocalizedString skillString = TextManager.Get("skills");
Vector2 skillSize = GUIStyle.SubHeadingFont.MeasureString(skillString);
GUITextBlock skillBlock = new GUITextBlock(new RectTransform(Vector2.One, skillLayout.RectTransform), skillString, font: GUIStyle.SubHeadingFont);
skillBlock.RectTransform.NonScaledSize = skillSize.Pad(skillBlock.Padding).ToPoint();
skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null); skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
CreateTalentSkillList(controlledCharacter, info, skillListBox); CreateSkillList(controlledCharacter, info, skillListBox);
if (controlledCharacter != null) 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; } if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
new GUIFrame(new RectTransform(new Vector2(1f, 1f), talentFrameLayoutGroup.RectTransform), style: "HorizontalLine");
GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.7f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
selectedTalents = info.GetUnlockedTalentsInTree().ToList(); selectedTalents = info.GetUnlockedTalentsInTree().ToList();
List<GUITextBlock> subTreeNames = new List<GUITextBlock>(); List<GUITextBlock> subTreeNames = new List<GUITextBlock>();
@@ -1994,24 +1965,24 @@ namespace Barotrauma
Point iconSize = cornerIcon.RectTransform.NonScaledSize; Point iconSize = cornerIcon.RectTransform.NonScaledSize;
cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2); cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
if (subTree.TalentOptionStages.Count <= i) { continue; } if (subTree.TalentOptionStages.Length <= i) { continue; }
TalentOption talentOption = subTree.TalentOptionStages[i]; 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 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 }; GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
foreach (TalentPrefab talent in talentOption.Talents.OrderBy(t => t.Identifier)) 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) GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
{ {
CanBeFocused = false CanBeFocused = false
}; };
GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null); 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) GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
{ {
ToolTip = RichString.Rich(talent.DisplayName + "\n\n" + talent.Description), ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
UserData = talent.Identifier, UserData = talent.Identifier,
PressedColor = pressedColor, PressedColor = pressedColor,
Enabled = controlledCharacter != null, Enabled = controlledCharacter != null,
@@ -2078,9 +2049,13 @@ namespace Barotrauma
} }
GUITextBlock.AutoScaleAndNormalize(subTreeNames); GUITextBlock.AutoScaleAndNormalize(subTreeNames);
GUILayoutGroup talentBottomFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), talentFrameLayoutGroup.RectTransform, Anchor.TopCenter), isHorizontal: true) { RelativeSpacing = 0.01f }; 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), talentBottomFrame.RectTransform)); 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); 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), experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
@@ -2097,47 +2072,100 @@ namespace Barotrauma
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; 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), talentBottomFrame.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale") talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
{ {
OnClicked = ResetTalentSelection OnClicked = ResetTalentSelection
}; };
talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), talentBottomFrame.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale") talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
{ {
OnClicked = ApplyTalentSelection, OnClicked = ApplyTalentSelection,
}; };
GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock); 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(); UpdateTalentInfo();
} }
private void CreateTalentSkillList(Character character, CharacterInfo info, GUIListBox parent) 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>();
foreach (Skill skill in info.Job.GetSkills()) foreach (Skill skill in info.Job.GetSkills())
{ {
GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.2f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = false }; GUILayoutGroup skillContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.0f), parent.Content.RectTransform), isHorizontal: true) { CanBeFocused = true };
var skillName = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value));
skillNames.Add(skillName);
skillName.RectTransform.MinSize = new Point(0, skillName.Rect.Height);
skillContainer.RectTransform.MinSize = new Point(0, skillName.Rect.Height);
skillNames.Add(new GUITextBlock(new RectTransform(new Vector2(0.7f, 1f), skillContainer.RectTransform), TextManager.Get($"skillname.{skill.Identifier}").Fallback(skill.Identifier.Value))); 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.CenterRight) { Padding = new Vector4(0, 0, 4, 0) };
float modifiedSkillLevel = character?.GetSkillLevel(skill.Identifier) ?? skill.Level; float modifiedSkillLevel = 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 - skill.Level);
//TODO: if/when we upgrade to C# 9, do neater pattern matching here string stringColor = skillChange switch
string stringColor = true switch
{ {
true when skillChange > 0 => XMLExtensions.ColorToString(GUIStyle.Green), > 0 => XMLExtensions.ToStringHex(GUIStyle.Green),
true when skillChange < 0 => XMLExtensions.ColorToString(GUIStyle.Red), < 0 => XMLExtensions.ToStringHex(GUIStyle.Red),
_ => XMLExtensions.ColorToString(GUIStyle.TextColorNormal) _ => XMLExtensions.ToStringHex(GUIStyle.TextColorNormal)
}; };
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();
@@ -2216,7 +2244,7 @@ namespace Barotrauma
talentButton.icon.HoverColor = hoverColor; talentButton.icon.HoverColor = hoverColor;
} }
CreateTalentSkillList(controlledCharacter, controlledCharacter.Info, skillListBox); CreateSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
} }
private void ApplyTalents(Character controlledCharacter) private void ApplyTalents(Character controlledCharacter)
@@ -17,7 +17,7 @@ using PlayerBalanceElement = Barotrauma.CampaignUI.PlayerBalanceElement;
namespace Barotrauma namespace Barotrauma
{ {
internal class UpgradeStore internal sealed class UpgradeStore
{ {
public readonly struct CategoryData public readonly struct CategoryData
{ {
@@ -432,13 +432,21 @@ namespace Barotrauma
}; };
Location location = Campaign.Map.CurrentLocation; Location location = Campaign.Map.CurrentLocation;
int hullRepairCost = location?.GetAdjustedMechanicalCost(CampaignMode.HullRepairCost) ?? CampaignMode.HullRepairCost;
int itemRepairCost = location?.GetAdjustedMechanicalCost(CampaignMode.ItemRepairCost) ?? CampaignMode.ItemRepairCost; int hullRepairCost = Campaign.GetHullRepairCost();
int shuttleRetrieveCost = location?.GetAdjustedMechanicalCost(CampaignMode.ShuttleReplaceCost) ?? CampaignMode.ShuttleReplaceCost; int itemRepairCost = Campaign.GetItemRepairCost();
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
if (location != null)
{
hullRepairCost = location.GetAdjustedMechanicalCost(hullRepairCost);
itemRepairCost = location.GetAdjustedMechanicalCost(itemRepairCost);
shuttleRetrieveCost = location.GetAdjustedMechanicalCost(shuttleRetrieveCost);
}
CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallwalls"), "RepairHullButton", hullRepairCost, (button, o) => CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallwalls"), "RepairHullButton", hullRepairCost, (button, o) =>
{ {
if (Campaign.PurchasedHullRepairs) //cost is zero = nothing to repair
if (Campaign.PurchasedHullRepairs || hullRepairCost <= 0)
{ {
button.Enabled = false; button.Enabled = false;
return false; return false;
@@ -471,7 +479,7 @@ namespace Barotrauma
return false; return false;
} }
return true; return true;
}, Campaign.PurchasedHullRepairs || !HasPermission, isHovered => }, Campaign.PurchasedHullRepairs || !HasPermission || hullRepairCost <= 0, isHovered =>
{ {
highlightWalls = isHovered; highlightWalls = isHovered;
return true; return true;
@@ -479,7 +487,8 @@ namespace Barotrauma
CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) => CreateRepairEntry(currentStoreLayout.Content, TextManager.Get("repairallitems"), "RepairItemsButton", itemRepairCost, (button, o) =>
{ {
if (PlayerBalance >= itemRepairCost && !Campaign.PurchasedItemRepairs) //cost is zero = nothing to repair
if (PlayerBalance >= itemRepairCost && !Campaign.PurchasedItemRepairs && itemRepairCost > 0)
{ {
LocalizedString body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString()); LocalizedString body = TextManager.GetWithVariable("ItemRepairs.PurchasePromptBody", "[amount]", itemRepairCost.ToString());
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () => currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), body, () =>
@@ -505,9 +514,8 @@ namespace Barotrauma
button.Enabled = false; button.Enabled = false;
return false; return false;
} }
return true; return true;
}, Campaign.PurchasedItemRepairs || !HasPermission, isHovered => }, Campaign.PurchasedItemRepairs || !HasPermission || itemRepairCost <= 0, isHovered =>
{ {
foreach (var (item, itemFrame) in itemPreviews) foreach (var (item, itemFrame) in itemPreviews)
{ {
@@ -839,7 +847,8 @@ namespace Barotrauma
foreach (UpgradePrefab prefab in prefabs) foreach (UpgradePrefab prefab in prefabs)
{ {
CreateUpgradeEntry(prefab, category, parent.Content, entitiesOnSub); if (prefab.MaxLevel is 0) { continue; }
CreateUpgradeEntry(prefab, category, parent.Content, submarine, entitiesOnSub);
} }
} }
@@ -865,7 +874,7 @@ namespace Barotrauma
itemPrefab.SwappableItem.CanBeBought && itemPrefab.SwappableItem.CanBeBought &&
itemPrefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase)).Cast<ItemPrefab>(); itemPrefab.SwappableItem.SwapIdentifier.Equals(item.Prefab.SwappableItem.SwapIdentifier, StringComparison.OrdinalIgnoreCase)).Cast<ItemPrefab>();
var linkedItems = Campaign.UpgradeManager.GetLinkedItemsToSwap(item) ?? new List<Item>() { item }; var linkedItems = UpgradeManager.GetLinkedItemsToSwap(item) ?? new List<Item>() { item };
//create the swap entry only for one of the items (the one with the smallest ID) //create the swap entry only for one of the items (the one with the smallest ID)
if (linkedItems.Min(it => it.ID) < item.ID) { return; } if (linkedItems.Min(it => it.ID) < item.ID) { return; }
@@ -901,7 +910,7 @@ namespace Barotrauma
GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1f, 1f, toggleButton.Frame), isHorizontal: true); GUILayoutGroup buttonLayout = new GUILayoutGroup(rectT(1f, 1f, toggleButton.Frame), isHorizontal: true);
LocalizedString slotText = ""; LocalizedString slotText = "";
if (linkedItems.Count > 1) if (linkedItems.Count() > 1)
{ {
slotText = TextManager.GetWithVariable("weaponslot", "[number]", string.Join(", ", linkedItems.Select(it => (swappableEntities.IndexOf(it) + 1).ToString()))); slotText = TextManager.GetWithVariable("weaponslot", "[number]", string.Join(", ", linkedItems.Select(it => (swappableEntities.IndexOf(it) + 1).ToString())));
} }
@@ -973,7 +982,7 @@ namespace Barotrauma
bool isPurchased = item.AvailableSwaps.Contains(replacement); bool isPurchased = item.AvailableSwaps.Contains(replacement);
int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count; int price = isPurchased || replacement == item.Prefab ? 0 : replacement.SwappableItem.GetPrice(Campaign.Map?.CurrentLocation) * linkedItems.Count();
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description, frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), replacement.UpgradePreviewSprite, replacement.Name, replacement.Description,
price, replacement, price, replacement,
@@ -989,7 +998,7 @@ namespace Barotrauma
{ {
LocalizedString promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody", LocalizedString promptBody = TextManager.GetWithVariables(isPurchased ? "upgrades.itemswappromptbody" : "upgrades.purchaseitemswappromptbody",
("[itemtoinstall]", replacement.Name), ("[itemtoinstall]", replacement.Name),
("[amount]", (replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation) * linkedItems.Count).ToString())); ("[amount]", (replacement.SwappableItem.GetPrice(Campaign?.Map?.CurrentLocation) * linkedItems.Count()).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () => currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
{ {
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
@@ -1033,7 +1042,7 @@ namespace Barotrauma
} }
if (toggleButton.Selected) if (toggleButton.Selected)
{ {
var linkedItems = Campaign.UpgradeManager.GetLinkedItemsToSwap(item); var linkedItems = UpgradeManager.GetLinkedItemsToSwap(item);
foreach (var itemPreview in itemPreviews) foreach (var itemPreview in itemPreviews)
{ {
itemPreview.Value.OutlineColor = itemPreview.Value.Color = linkedItems.Contains(itemPreview.Key) ? GUIStyle.Orange : previewWhite; itemPreview.Value.OutlineColor = itemPreview.Value.Color = linkedItems.Contains(itemPreview.Key) ? GUIStyle.Orange : previewWhite;
@@ -1116,7 +1125,11 @@ namespace Barotrauma
//negative price = refund //negative price = refund
if (price < 0) { formattedPrice = "+" + formattedPrice; } if (price < 0) { formattedPrice = "+" + formattedPrice; }
buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" }; buyButtonLayout = new GUILayoutGroup(rectT(0.2f, 1, prefabLayout), childAnchor: Anchor.TopCenter) { UserData = "buybutton" };
var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center); var priceText = new GUITextBlock(rectT(1, 0.2f, buyButtonLayout), formattedPrice, textAlignment: Alignment.Center)
{
//prices on swappable items are always visible, upgrade prices are enabled in UpdateUpgradeEntry for purchasable upgrades
Visible = userData is ItemPrefab
};
if (price < 0) if (price < 0)
{ {
priceText.TextColor = GUIStyle.Green; priceText.TextColor = GUIStyle.Green;
@@ -1175,9 +1188,10 @@ namespace Barotrauma
} }
} }
private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, List<Item>? itemsOnSubmarine) private void CreateUpgradeEntry(UpgradePrefab prefab, UpgradeCategory category, GUIComponent parent, Submarine submarine, List<Item>? itemsOnSubmarine)
{ {
if (Campaign is null) { return; } Submarine? sub = GameMain.GameSession?.Submarine ?? Submarine.MainSub;
if (Campaign is null || sub is null) { return; }
GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent)); GUIFrame prefabFrame = CreateUpgradeFrame(prefab, category, Campaign, rectT(1f, 0.25f, parent));
var prefabLayout = prefabFrame.GetChild<GUILayoutGroup>(); var prefabLayout = prefabFrame.GetChild<GUILayoutGroup>();
@@ -1193,7 +1207,7 @@ namespace Barotrauma
var buyButtonLayout = childLayouts[2]; var buyButtonLayout = childLayouts[2];
var buyButton = buyButtonLayout.GetChild<GUIButton>(); var buyButton = buyButtonLayout.GetChild<GUIButton>();
if (!HasPermission || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab)))) if (!HasPermission || !prefab.IsApplicable(submarine.Info) || (itemsOnSubmarine != null && !itemsOnSubmarine.Any(it => category.CanBeApplied(it, prefab))))
{ {
prefabFrame.Enabled = false; prefabFrame.Enabled = false;
description.Enabled = false; description.Enabled = false;
@@ -1244,11 +1258,16 @@ namespace Barotrauma
int upgradesCount = upgrades.Count; int upgradesCount = upgrades.Count;
const int maxUpgrades = 4; const int maxUpgrades = 4;
itemName.Text = entity is Item ? entity.Name : TextManager.Get("upgradecategory.walls"); Item? item = entity as Item;
itemName.Text = item?.Name ?? TextManager.Get("upgradecategory.walls");
if (slotIndex > -1) if (slotIndex > -1)
{ {
itemName.Text = TextManager.GetWithVariables("weaponslotwithname", ("[number]", slotIndex.ToString()), ("[weaponname]", itemName.Text)); itemName.Text = TextManager.GetWithVariables("weaponslotwithname", ("[number]", slotIndex.ToString()), ("[weaponname]", itemName.Text));
} }
if (item?.PendingItemSwap != null)
{
itemName.Text = RichString.Rich(itemName.Text + "\n" + TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", item.PendingItemSwap.Name));
}
upgradeList.Content.ClearChildren(); upgradeList.Content.ClearChildren();
for (var i = 0; i < upgrades.Count && i < maxUpgrades; i++) for (var i = 0; i < upgrades.Count && i < maxUpgrades; i++)
{ {
@@ -1261,7 +1280,7 @@ namespace Barotrauma
// include pending upgrades into the tooltip // include pending upgrades into the tooltip
foreach (var (prefab, category, level) in upgradeManager.PendingUpgrades) foreach (var (prefab, category, level) in upgradeManager.PendingUpgrades)
{ {
if (entity is Item item && category.CanBeApplied(item, prefab) || entity is Structure && category.IsWallUpgrade) if (item != null && category.CanBeApplied(item, prefab) || entity is Structure && category.IsWallUpgrade)
{ {
bool found = false; bool found = false;
foreach (GUITextBlock textBlock in upgradeList.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>()) foreach (GUITextBlock textBlock in upgradeList.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>())
@@ -1305,6 +1324,8 @@ namespace Barotrauma
Item[] entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToArray(); Item[] entitiesOnSub = drawnSubmarine.GetItems(true).Where(i => drawnSubmarine.IsEntityFoundOnThisSub(i, true)).ToArray();
foreach (UpgradeCategory category in UpgradeCategory.Categories) foreach (UpgradeCategory category in UpgradeCategory.Categories)
{ {
//hide categories with no upgrades in them
if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category))) { continue; }
if (entitiesOnSub.Any(item => category.CanBeApplied(item, null))) if (entitiesOnSub.Any(item => category.CanBeApplied(item, null)))
{ {
yield return category; yield return category;
@@ -1370,12 +1391,16 @@ namespace Barotrauma
{ {
if (selectedUpgradeCategoryLayout != null) if (selectedUpgradeCategoryLayout != null)
{ {
var linkedItems = HoveredEntity is Item hoveredItem ? Campaign.UpgradeManager.GetLinkedItemsToSwap(hoveredItem) : new List<Item>(); var linkedItems = HoveredEntity is Item hoveredItem ? UpgradeManager.GetLinkedItemsToSwap(hoveredItem) : new List<Item>();
if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData is Item item && (item == HoveredEntity || linkedItems.Contains(item)), recursive: true) is GUIButton itemElement) if (selectedUpgradeCategoryLayout.FindChild(c => c.UserData is Item item && (item == HoveredEntity || linkedItems.Contains(item)), recursive: true) is GUIButton itemElement)
{ {
if (!itemElement.Selected) { itemElement.OnClicked(itemElement, itemElement.UserData); } if (!itemElement.Selected) { itemElement.OnClicked(itemElement, itemElement.UserData); }
(itemElement.Parent?.Parent?.Parent as GUIListBox)?.ScrollToElement(itemElement); (itemElement.Parent?.Parent?.Parent as GUIListBox)?.ScrollToElement(itemElement);
} }
else
{
ScrollToCategory(data => data.Category.CanBeApplied(item, null));
}
} }
} }
else else
@@ -1436,7 +1461,7 @@ namespace Barotrauma
* |--------------------------------------------------| * |--------------------------------------------------|
* | name | * | name |
* |--------------------------------------------------| * |--------------------------------------------------|
* | class | * | class + tier |
* |--------------------------------------------------| * |--------------------------------------------------|
* | description | * | description |
* | | * | |
@@ -1446,13 +1471,24 @@ namespace Barotrauma
submarineInfoFrame = new GUILayoutGroup(rectT(0.25f, 0.2f, mainStoreLayout, Anchor.TopRight)) { IgnoreLayoutGroups = true }; submarineInfoFrame = new GUILayoutGroup(rectT(0.25f, 0.2f, mainStoreLayout, Anchor.TopRight)) { IgnoreLayoutGroups = true };
// submarine name // submarine name
new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.DisplayName, textAlignment: Alignment.Right, font: GUIStyle.LargeFont); new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.DisplayName, textAlignment: Alignment.Right, font: GUIStyle.LargeFont);
// submarine class
new GUITextBlock(rectT(1, 0, submarineInfoFrame), $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}", textAlignment: Alignment.Right, font: GUIStyle.Font); LocalizedString classText = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}"))}";
// submarine class + tier
new GUITextBlock(rectT(1.0f, 0.15f, submarineInfoFrame), classText, textAlignment: Alignment.Right, font: GUIStyle.Font)
{
ToolTip = TextManager.Get("submarineclass.description") + "\n\n" + TextManager.Get($"submarineclass.{submarine.Info.SubmarineClass}.description")
};
new GUITextBlock(rectT(1.0f, 0.15f, submarineInfoFrame), TextManager.Get($"submarinetier.{submarine.Info.Tier}"), textAlignment: Alignment.Right, font: GUIStyle.Font)
{
ToolTip = TextManager.Get("submarinetier.description")
};
var description = new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.Description, textAlignment: Alignment.Right, wrap: true); var description = new GUITextBlock(rectT(1, 0, submarineInfoFrame), submarine.Info.Description, textAlignment: Alignment.Right, wrap: true);
submarineInfoFrame.RectTransform.ScreenSpaceOffset = new Point(0, (int)(16 * GUI.Scale)); submarineInfoFrame.RectTransform.ScreenSpaceOffset = new Point(0, (int)(16 * GUI.Scale));
description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W); description.Padding = new Vector4(description.Padding.X, 24 * GUI.Scale, description.Padding.Z, description.Padding.W);
List<Entity> pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs) where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast<Entity>().ToList(); List<Entity> pointsOfInterest = (from category in UpgradeCategory.Categories from item in submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs)
where category.CanBeApplied(item, null) && item.IsPlayerTeamInteractable select item).Cast<Entity>().Distinct().ToList();
List<ushort> ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List<ushort>(); List<ushort> ids = GameMain.GameSession.SubmarineInfo?.LeftBehindDockingPortIDs ?? new List<ushort>();
pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID))); pointsOfInterest.AddRange(submarine.GetItems(UpgradeManager.UpgradeAlsoConnectedSubs).Where(item => ids.Contains(item.ID)));
@@ -1600,6 +1636,7 @@ namespace Barotrauma
List<GUITextBlock> textBlocks = buttonParent.GetAllChildren<GUITextBlock>().ToList(); List<GUITextBlock> textBlocks = buttonParent.GetAllChildren<GUITextBlock>().ToList();
GUITextBlock priceLabel = textBlocks[0]; GUITextBlock priceLabel = textBlocks[0];
priceLabel.Visible = true;
int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation); int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate) if (priceLabel != null && !WaitForServerUpdate)
@@ -1638,9 +1675,15 @@ namespace Barotrauma
IEnumerable<UpgradeCategory> applicableCategories) IEnumerable<UpgradeCategory> applicableCategories)
{ {
// Disables the parent and only re-enables if the submarine contains valid items // Disables the parent and only re-enables if the submarine contains valid items
if (!category.IsWallUpgrade && drawnSubmarine != null) if (!category.IsWallUpgrade && drawnSubmarine?.Info != null)
{ {
if (applicableCategories.Contains(category)) if (UpgradePrefab.Prefabs.None(p => p.UpgradeCategories.Contains(category) && p.GetMaxLevel(drawnSubmarine.Info) > 0))
{
parent.ToolTip = TextManager.Get("upgradecategorynotapplicable");
parent.Enabled = false;
parent.SelectedColor = GUIStyle.Red * 0.5f;
}
else if (applicableCategories.Contains(category))
{ {
parent.Enabled = true; parent.Enabled = true;
parent.SelectedColor = parent.Style.SelectedColor; parent.SelectedColor = parent.Style.SelectedColor;
@@ -1660,14 +1703,27 @@ namespace Barotrauma
{ {
if (component.UserData != prefab) { continue; } if (component.UserData != prefab) { continue; }
if (prefab.MaxLevel is 0)
{
component.Visible = false;
continue;
}
Dictionary<Identifier, GUIComponentStyle> styles = GUIStyle.GetComponentStyle("upgradeindicator").ChildStyles; Dictionary<Identifier, GUIComponentStyle> styles = GUIStyle.GetComponentStyle("upgradeindicator").ChildStyles;
if (!styles.ContainsKey("upgradeindicatoron") || !styles.ContainsKey("upgradeindicatordim") || !styles.ContainsKey("upgradeindicatoroff")) { continue; } if (!styles.ContainsKey("upgradeindicatoron") || !styles.ContainsKey("upgradeindicatordim") || !styles.ContainsKey("upgradeindicatoroff")) { continue; }
GUIComponentStyle onStyle = styles["upgradeindicatoron".ToIdentifier()]; GUIComponentStyle onStyle = styles["upgradeindicatoron".ToIdentifier()];
GUIComponentStyle dimStyle = styles["upgradeindicatordim".ToIdentifier()]; GUIComponentStyle dimStyle = styles["upgradeindicatordim".ToIdentifier()];
GUIComponentStyle offStyle = styles["upgradeindicatoroff".ToIdentifier()]; GUIComponentStyle offStyle = styles["upgradeindicatoroff".ToIdentifier()];
int maxLevel = prefab.MaxLevel;
if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= prefab.MaxLevel) if (maxLevel == 0)
{
SetOff();
continue;
}
if (campaign.UpgradeManager.GetUpgradeLevel(prefab, category) >= maxLevel)
{ {
// we check this to avoid flickering from re-applying the same style // we check this to avoid flickering from re-applying the same style
if (image.Style == onStyle) { continue; } if (image.Style == onStyle) { continue; }
@@ -1680,7 +1736,12 @@ namespace Barotrauma
} }
else else
{ {
if (image.Style == offStyle) { continue; } SetOff();
}
void SetOff()
{
if (image.Style == offStyle) { return; }
image.ApplyStyle(offStyle); image.ApplyStyle(offStyle);
} }
} }
@@ -1691,14 +1752,26 @@ namespace Barotrauma
{ {
if (currentStoreLayout == null) { return; } if (currentStoreLayout == null) { return; }
CategoryData? mostAppropriateCategory = null;
GUIComponent? mostAppropriateChild = null;
foreach (GUIComponent child in currentStoreLayout.Content.Children) foreach (GUIComponent child in currentStoreLayout.Content.Children)
{ {
if (child.UserData is CategoryData data && predicate(data)) if (child.UserData is CategoryData data && predicate(data))
{ {
currentStoreLayout.ScrollToElement(child, playSelectSound); //choose the category with least items in it as the "most appropriate"
break; //e.g. when selecting junction boxes, we want to select the "junction boxes" category instead of "electrical repairs" which contains many electrical devices
if (mostAppropriateCategory == null ||
data.Category.ItemTags.Count() < mostAppropriateCategory.Value.Category.ItemTags.Count())
{
mostAppropriateCategory = data;
mostAppropriateChild = child;
}
} }
} }
if (mostAppropriateChild != null)
{
currentStoreLayout.ScrollToElement(mostAppropriateChild, playSelectSound);
}
} }
/// <summary> /// <summary>
@@ -131,9 +131,9 @@ namespace Barotrauma
LoadContent(contentPath, videoSettings, textSettings, contentId, startPlayback, new RawLString(""), null); LoadContent(contentPath, videoSettings, textSettings, contentId, startPlayback, new RawLString(""), null);
} }
public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action callback = null) public void LoadContent(string contentPath, VideoSettings videoSettings, TextSettings textSettings, Identifier contentId, bool startPlayback, LocalizedString objective, Action onStop = null)
{ {
callbackOnStop = callback; callbackOnStop = onStop;
filePath = contentPath + videoSettings.File; filePath = contentPath + videoSettings.File;
if (!File.Exists(filePath)) if (!File.Exists(filePath))
@@ -66,7 +66,7 @@ namespace Barotrauma
{ {
currentVoteType = type; currentVoteType = type;
CreateVotingGUI(); CreateVotingGUI();
if (starter.ID == GameMain.Client.ID) { SetGUIToVotedState(2); } if (starter.SessionId == GameMain.Client.SessionId) { SetGUIToVotedState(2); }
VoteRunning = true; VoteRunning = true;
} }
@@ -111,9 +111,7 @@ namespace Barotrauma
private readonly GameTime fixedTime; private readonly GameTime fixedTime;
public string ConnectName; public Option<ConnectCommand> ConnectCommand = Option<ConnectCommand>.None();
public string ConnectEndpoint;
public UInt64 ConnectLobby;
private static SpriteBatch spriteBatch; private static SpriteBatch spriteBatch;
@@ -236,20 +234,14 @@ namespace Barotrauma
ConsoleArguments = args; ConsoleArguments = args;
ConnectName = null;
ConnectEndpoint = null;
ConnectLobby = 0;
try try
{ {
ToolBox.ParseConnectCommand(ConsoleArguments, out ConnectName, out ConnectEndpoint, out ConnectLobby); ConnectCommand = ToolBox.ParseConnectCommand(ConsoleArguments);
} }
catch (IndexOutOfRangeException e) catch (IndexOutOfRangeException e)
{ {
DebugConsole.ThrowError($"Failed to parse console arguments ({string.Join(' ', ConsoleArguments)})", e); DebugConsole.ThrowError($"Failed to parse console arguments ({string.Join(' ', ConsoleArguments)})", e);
ConnectName = null; ConnectCommand = Option<ConnectCommand>.None();
ConnectEndpoint = null;
ConnectLobby = 0;
} }
GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window); GUI.KeyboardDispatcher = new EventInput.KeyboardDispatcher(Window);
@@ -473,7 +465,7 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
UgcTransition.Prepare(); LegacySteamUgcTransition.Prepare();
var contentPackageLoadRoutine = ContentPackageManager.Init(); var contentPackageLoadRoutine = ContentPackageManager.Init();
foreach (var progress in contentPackageLoadRoutine) foreach (var progress in contentPackageLoadRoutine)
{ {
@@ -569,6 +561,8 @@ namespace Barotrauma
new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError)); new GUIMessageBox(TextManager.Get("Error"), TextManager.Get(steamError));
} }
GameSettings.OnGameMainHasLoaded?.Invoke();
TitleScreen.LoadState = 100.0f; TitleScreen.LoadState = 100.0f;
HasLoaded = true; HasLoaded = true;
if (GameSettings.CurrentConfig.VerboseLogging) if (GameSettings.CurrentConfig.VerboseLogging)
@@ -604,7 +598,7 @@ namespace Barotrauma
{ {
try try
{ {
ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCommand), out ConnectName, out ConnectEndpoint, out ConnectLobby); ConnectCommand = ToolBox.ParseConnectCommand(ToolBox.SplitCommand(connectCommand));
} }
catch (IndexOutOfRangeException e) catch (IndexOutOfRangeException e)
{ {
@@ -613,12 +607,8 @@ namespace Barotrauma
#else #else
DebugConsole.Log($"Failed to parse a Steam friend's connect invitation command ({connectCommand})\n" + e.StackTrace.CleanupStackTrace()); DebugConsole.Log($"Failed to parse a Steam friend's connect invitation command ({connectCommand})\n" + e.StackTrace.CleanupStackTrace());
#endif #endif
ConnectName = null; ConnectCommand = Option<ConnectCommand>.None();
ConnectEndpoint = null;
ConnectLobby = 0;
} }
DebugConsole.NewMessage(ConnectName + ", " + ConnectEndpoint, Color.Yellow);
} }
public void OnLobbyJoinRequested(Steamworks.Data.Lobby lobby, Steamworks.SteamId friendId) public void OnLobbyJoinRequested(Steamworks.Data.Lobby lobby, Steamworks.SteamId friendId)
@@ -734,7 +724,7 @@ namespace Barotrauma
} }
#endif #endif
NetworkMember?.Update((float)Timing.Step); Client?.Update((float)Timing.Step);
if (!HasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine)) if (!HasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
{ {
@@ -743,38 +733,29 @@ namespace Barotrauma
} }
else if (HasLoaded) else if (HasLoaded)
{ {
if (ConnectLobby != 0) if (ConnectCommand is Some<ConnectCommand> { Value: var connectCommand })
{ {
if (Client != null) if (Client != null)
{ {
Client.Disconnect(); Client.Quit();
Client = null; Client = null;
MainMenuScreen.Select();
GameMain.MainMenuScreen.Select();
} }
Steam.SteamManager.JoinLobby(ConnectLobby, true);
ConnectLobby = 0; if (connectCommand.EndpointOrLobby.TryGet(out ulong lobbyId))
ConnectEndpoint = null;
ConnectName = null;
}
else if (!string.IsNullOrWhiteSpace(ConnectEndpoint))
{
if (Client != null)
{ {
Client.Disconnect(); SteamManager.JoinLobby(lobbyId, joinServer: true);
Client = null;
GameMain.MainMenuScreen.Select();
} }
UInt64 serverSteamId = SteamManager.SteamIDStringToUInt64(ConnectEndpoint); else if (connectCommand.EndpointOrLobby.TryGet(out ConnectCommand.NameAndEndpoint nameAndEndpoint)
Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()), && nameAndEndpoint is { ServerName: var serverName, Endpoint: var endpoint })
serverSteamId != 0 ? null : ConnectEndpoint, {
serverSteamId, Client = new GameClient(MultiplayerPreferences.Instance.PlayerName.FallbackNullOrEmpty(SteamManager.GetUsername()),
string.IsNullOrWhiteSpace(ConnectName) ? ConnectEndpoint : ConnectName); endpoint,
ConnectLobby = 0; string.IsNullOrWhiteSpace(serverName) ? endpoint.StringRepresentation : serverName,
ConnectEndpoint = null; Option<int>.None());
ConnectName = null; }
ConnectCommand = Option<ConnectCommand>.None();
} }
SoundPlayer.Update((float)Timing.Step); SoundPlayer.Update((float)Timing.Step);
@@ -823,7 +804,7 @@ namespace Barotrauma
else if ((Character.Controlled == null || !itemHudActive()) else if ((Character.Controlled == null || !itemHudActive())
&& CharacterHealth.OpenHealthWindow == null && CharacterHealth.OpenHealthWindow == null
&& !CrewManager.IsCommandInterfaceOpen && !CrewManager.IsCommandInterfaceOpen
&& !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedConstruction != null)) && !(Screen.Selected is SubEditorScreen editor && !editor.WiringMode && Character.Controlled?.SelectedItem != null))
{ {
// Otherwise toggle pausing, unless another window/interface is open. // Otherwise toggle pausing, unless another window/interface is open.
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
@@ -831,9 +812,9 @@ namespace Barotrauma
static bool itemHudActive() static bool itemHudActive()
{ {
if (Character.Controlled?.SelectedConstruction == null) { return false; } if (Character.Controlled?.SelectedItem == null) { return false; }
return return
Character.Controlled.SelectedConstruction.ActiveHUDs.Any(ic => ic.GuiFrame != null) || Character.Controlled.SelectedItem.ActiveHUDs.Any(ic => ic.GuiFrame != null) ||
((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false); ((Character.Controlled.ViewTarget as Item)?.Prefab?.FocusOnSelected ?? false);
} }
} }
@@ -902,7 +883,7 @@ namespace Barotrauma
} }
} }
NetworkMember?.Update((float)Timing.Step); Client?.Update((float)Timing.Step);
GUI.Update((float)Timing.Step); GUI.Update((float)Timing.Step);
@@ -928,7 +909,7 @@ namespace Barotrauma
#endif #endif
} }
CoroutineManager.Update((float)Timing.Step, Paused ? 0.0f : (float)Timing.Step); CoroutineManager.Update(Paused, (float)Timing.Step);
SteamManager.Update((float)Timing.Step); SteamManager.Update((float)Timing.Step);
@@ -1102,7 +1083,7 @@ namespace Barotrauma
if (Client != null) if (Client != null)
{ {
Client.Disconnect(); Client.Quit();
Client = null; Client = null;
} }
@@ -1118,7 +1099,7 @@ namespace Barotrauma
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)
{ {
GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.ToString()); GameAnalyticsManager.AddDesignEvent(eventId + "EventManager:ActiveEvents:" + activeEvent.Prefab.Identifier);
} }
GameSession.LogEndRoundStats(eventId); GameSession.LogEndRoundStats(eventId);
if (GameSession.GameMode is TutorialMode tutorialMode) if (GameSession.GameMode is TutorialMode tutorialMode)
@@ -1184,7 +1165,7 @@ namespace Barotrauma
UserData = "https://steamcommunity.com/app/602960/discussions/1/", UserData = "https://steamcommunity.com/app/602960/discussions/1/",
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!SteamManager.OverlayCustomURL(userdata as string)) if (!SteamManager.OverlayCustomUrl(userdata as string))
{ {
ShowOpenUrlInWebBrowserPrompt(userdata as string); ShowOpenUrlInWebBrowserPrompt(userdata as string);
} }
@@ -1222,7 +1203,7 @@ namespace Barotrauma
{ {
exiting = true; exiting = true;
DebugConsole.NewMessage("Exiting..."); DebugConsole.NewMessage("Exiting...");
NetworkMember?.Disconnect(); Client?.Quit();
SteamManager.ShutDown(); SteamManager.ShutDown();
try try
@@ -103,7 +103,7 @@ namespace Barotrauma
{ {
if (soldItem.ItemPrefab != soldEntity.ItemPrefab) { return false; } if (soldItem.ItemPrefab != soldEntity.ItemPrefab) { return false; }
if (matchId && (soldEntity.Item == null || soldItem.ID != soldEntity.Item.ID)) { return false; } if (matchId && (soldEntity.Item == null || soldItem.ID != soldEntity.Item.ID)) { return false; }
if (soldItem.Origin == SoldItem.SellOrigin.Character && GameMain.Client != null && soldItem.SellerID != GameMain.Client.ID) { return false; } if (soldItem.Origin == SoldItem.SellOrigin.Character && GameMain.Client != null && soldItem.SellerID != GameMain.Client.SessionId) { return false; }
return true; return true;
} }
} }
@@ -139,16 +139,16 @@ namespace Barotrauma
} }
catch (NotImplementedException e) catch (NotImplementedException e)
{ {
DebugConsole.ShowError($"Error selling items: uknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error selling items: uknown store tab type \"{sellingMode}\".\n{e.StackTrace.CleanupStackTrace()}");
return; return;
} }
bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null; bool canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
byte sellerId = GameMain.Client?.ID ?? 0; byte sellerId = GameMain.Client?.SessionId ?? 0;
// Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction // Check all the prices before starting the transaction to make sure the modifiers stay the same for the whole transaction
var sellValues = GetSellValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab)); var sellValues = GetSellValuesAtCurrentLocation(storeIdentifier, itemsToSell.Select(i => i.ItemPrefab));
if (!(Location.GetStore(storeIdentifier) is { } store)) if (!(Location.GetStore(storeIdentifier) is { } store))
{ {
DebugConsole.ShowError($"Error selling items at {Location}: no store with identifier \"{storeIdentifier}\" exists.\n{Environment.StackTrace.CleanupStackTrace()}"); DebugConsole.LogError($"Error selling items at {Location}: no store with identifier \"{storeIdentifier}\" exists.\n{Environment.StackTrace.CleanupStackTrace()}");
return; return;
} }
var storeSpecificSoldItems = GetSoldItems(storeIdentifier, create: true); var storeSpecificSoldItems = GetSoldItems(storeIdentifier, create: true);
@@ -183,14 +183,13 @@ namespace Barotrauma
{ {
chatBox.ToggleButton = new GUIButton(new RectTransform(new Point((int)(182f * GUI.Scale * 0.4f), (int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style: "ChatToggleButton") chatBox.ToggleButton = new GUIButton(new RectTransform(new Point((int)(182f * GUI.Scale * 0.4f), (int)(99f * GUI.Scale * 0.4f)), chatBox.GUIFrame.Parent.RectTransform), style: "ChatToggleButton")
{ {
ToolTip = TextManager.Get("chat"), ToolTip = TextManager.GetWithVariable("hudbutton.chatbox", "[key]", GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.ChatBox)),
ClampMouseRectToParent = false ClampMouseRectToParent = false
}; };
chatBox.ToggleButton.RectTransform.AbsoluteOffset = new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height); chatBox.ToggleButton.RectTransform.AbsoluteOffset = new Point(0, HUDLayoutSettings.ChatBoxArea.Height - chatBox.ToggleButton.Rect.Height);
chatBox.ToggleButton.OnClicked += (GUIButton btn, object userdata) => chatBox.ToggleButton.OnClicked += (GUIButton btn, object userdata) =>
{ {
chatBox.ToggleOpen = !chatBox.ToggleOpen; chatBox.Toggle();
chatBox.CloseAfterMessageSent = false;
return true; return true;
}; };
} }
@@ -292,29 +291,13 @@ namespace Barotrauma
return crewArea.Rect; return crewArea.Rect;
} }
/// <summary>
/// Remove the character from the crew (and crew menus).
/// </summary>
/// <param name="character">The character to remove</param>
/// <param name="removeInfo">If the character info is also removed, the character will not be visible in the round summary.</param>
public void RemoveCharacter(Character character, bool removeInfo = false, bool resetCrewListIndex = true)
{
if (character == null)
{
DebugConsole.ThrowError("Tried to remove a null character from CrewManager.\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
characters.Remove(character);
if (removeInfo) { characterInfos.Remove(character.Info); }
if (resetCrewListIndex) { ResetCrewListIndex(character); }
}
/// <summary> /// <summary>
/// Add character to the list without actually adding it to the crew /// Add character to the list without actually adding it to the crew
/// </summary> /// </summary>
public GUIComponent AddCharacterToCrewList(Character character) public GUIComponent AddCharacterToCrewList(Character character)
{ {
if (character == null) { return null; } if (character == null) { return null; }
if (crewList.Content.Children.Any(c => c.UserData as Character == character)) { return null; }
var background = new GUIFrame( var background = new GUIFrame(
new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight), new RectTransform(crewListEntrySize, parent: crewList.Content.RectTransform, anchor: Anchor.TopRight),
@@ -510,7 +493,15 @@ namespace Barotrauma
return background; return background;
} }
private void SetCharacterComponentTooltip(GUIComponent characterComponent) public void RemoveCharacterFromCrewList(Character character)
{
if (crewList?.Content.GetChildByUserData(character) is { } component)
{
crewList.RemoveChild(component);
}
}
private static void SetCharacterComponentTooltip(GUIComponent characterComponent)
{ {
if (!(characterComponent?.UserData is Character character)) { return; } if (!(characterComponent?.UserData is Character character)) { return; }
if (character.Info?.Job?.Prefab == null) { return; } if (character.Info?.Job?.Prefab == null) { return; }
@@ -1390,7 +1381,7 @@ namespace Barotrauma
} }
else else
{ {
CreateCommandUI(HUDLayoutSettings.BottomRightInfoArea.Contains(PlayerInput.MousePosition) ? Character.Controlled : GUI.MouseOn?.UserData as Character); CreateCommandUI(CharacterHUD.MouseOnCharacterPortrait() ? Character.Controlled : GUI.MouseOn?.UserData as Character);
} }
SoundPlayer.PlayUISound(GUISoundType.PopupMenu); SoundPlayer.PlayUISound(GUISoundType.PopupMenu);
clicklessSelectionActive = isOpeningClick = true; clicklessSelectionActive = isOpeningClick = true;
@@ -2412,7 +2403,7 @@ namespace Barotrauma
float reactorOutput = -reactor.CurrPowerConsumption; float reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
// --> Create shortcut node for "Operate Reactor" order's "Power Up" option // --> Create shortcut node for "Operate Reactor" order's "Power Up" option
if (ShouldDelegateOrder("operatereactor") && reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) if (ShouldDelegateOrder("operatereactor") && reactorOutput < float.Epsilon && characters.None(c => c.SelectedItem == reactor.Item))
{ {
var orderPrefab = OrderPrefab.Prefabs["operatereactor"]; var orderPrefab = OrderPrefab.Prefabs["operatereactor"];
var order = new Order(orderPrefab, orderPrefab.Options[0], reactor.Item, reactor); var order = new Order(orderPrefab, orderPrefab.Options[0], reactor.Item, reactor);
@@ -2426,7 +2417,7 @@ namespace Barotrauma
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order // --> Create shortcut node for Steer order
if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) && if (CanFitMoreNodes() && ShouldDelegateOrder("steer") && IsNonDuplicateOrderPrefab(OrderPrefab.Prefabs["steer"]) &&
subItems.Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && subItems.Find(i => i.HasTag("navterminal") && i.IsPlayerTeamInteractable) is Item nav && characters.None(c => c.SelectedItem == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage) nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{ {
var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering); var order = new Order(OrderPrefab.Prefabs["steer"], steering.Item, steering);
@@ -2822,7 +2813,7 @@ namespace Barotrauma
return node; return node;
} }
private struct MinimapNodeData public struct MinimapNodeData
{ {
public Order Order; public Order Order;
} }
@@ -3517,9 +3508,9 @@ namespace Barotrauma
if (node == null || characterContext != null) { return false; } if (node == null || characterContext != null) { return false; }
if (node.UserData is Order nodeOrder) if (node.UserData is Order nodeOrder)
{ {
return !nodeOrder.TargetAllCharacters && !nodeOrder.Prefab.HasOptions && return !nodeOrder.TargetAllCharacters &&
(!nodeOrder.MustSetTarget || itemContext != null || (!nodeOrder.Prefab.HasOptions || !nodeOrder.Option.IsEmpty) &&
nodeOrder.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: Character.Controlled).Count < 2); (!nodeOrder.MustSetTarget || itemContext != null || nodeOrder.GetMatchingItems(GetTargetSubmarine(), true, interactableFor: Character.Controlled).Count < 2);
} }
return false; return false;
} }
@@ -3,8 +3,8 @@ namespace Barotrauma
{ {
partial class GameMode partial class GameMode
{ {
public virtual void Draw(SpriteBatch spriteBatch) public virtual void HUDScaleChanged() { }
{
} public virtual void Draw(SpriteBatch spriteBatch) { }
} }
} }
@@ -115,12 +115,16 @@ namespace Barotrauma
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUI.Canvas), CreateButtons();
isHorizontal: true, childAnchor: Anchor.CenterRight) }
{
CanBeFocused = false
};
public override void HUDScaleChanged()
{
CreateButtons();
}
private void CreateButtons()
{
int buttonHeight = (int) (GUI.Scale * 40), int buttonHeight = (int) (GUI.Scale * 40),
buttonWidth = GUI.IntScale(450), buttonWidth = GUI.IntScale(450),
buttonCenter = buttonHeight / 2, buttonCenter = buttonHeight / 2,
@@ -166,8 +170,6 @@ namespace Barotrauma
}, },
UserData = "ReadyCheckButton" UserData = "ReadyCheckButton"
}; };
buttonContainer.Recalculate();
} }
private void InitCampaignUI() private void InitCampaignUI()
@@ -311,7 +313,7 @@ namespace Barotrauma
if (prevControlled != null) if (prevControlled != null)
{ {
prevControlled.SelectedConstruction = null; prevControlled.SelectedItem = prevControlled.SelectedSecondaryItem = null;
if (prevControlled.AIController != null) if (prevControlled.AIController != null)
{ {
prevControlled.AIController.Enabled = true; prevControlled.AIController.Enabled = true;
@@ -362,7 +364,7 @@ namespace Barotrauma
float t = 0.0f; float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running) while (t < fadeOutDuration || endTransition.Running)
{ {
t += CoroutineManager.UnscaledDeltaTime; t += CoroutineManager.DeltaTime;
overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration); overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
@@ -469,7 +471,6 @@ namespace Barotrauma
{ {
base.End(transitionType); base.End(transitionType);
ForceMapUI = ShowCampaignUI = false; ForceMapUI = ShowCampaignUI = false;
UpgradeManager.CanUpgrade = true;
// remove all event dialogue boxes // remove all event dialogue boxes
GUIMessageBox.MessageBoxes.ForEachMod(mb => GUIMessageBox.MessageBoxes.ForEachMod(mb =>
@@ -539,37 +540,37 @@ namespace Barotrauma
{ {
System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue); System.Diagnostics.Debug.Assert(map.Locations.Count < UInt16.MaxValue);
msg.Write(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex); msg.WriteUInt16(map.CurrentLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.CurrentLocationIndex);
msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); msg.WriteUInt16(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
var selectedMissionIndices = map.GetSelectedMissionIndices(); var selectedMissionIndices = map.GetSelectedMissionIndices();
msg.Write((byte)selectedMissionIndices.Count()); msg.WriteByte((byte)selectedMissionIndices.Count());
foreach (int selectedMissionIndex in selectedMissionIndices) foreach (int selectedMissionIndex in selectedMissionIndices)
{ {
msg.Write((byte)selectedMissionIndex); msg.WriteByte((byte)selectedMissionIndex);
} }
msg.Write(PurchasedHullRepairs); msg.WriteBoolean(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs); msg.WriteBoolean(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles); msg.WriteBoolean(PurchasedLostShuttles);
WriteItems(msg, CargoManager.ItemsInBuyCrate); WriteItems(msg, CargoManager.ItemsInBuyCrate);
WriteItems(msg, CargoManager.ItemsInSellFromSubCrate); WriteItems(msg, CargoManager.ItemsInSellFromSubCrate);
WriteItems(msg, CargoManager.PurchasedItems); WriteItems(msg, CargoManager.PurchasedItems);
WriteItems(msg, CargoManager.SoldItems); WriteItems(msg, CargoManager.SoldItems);
msg.Write((ushort)UpgradeManager.PurchasedUpgrades.Count); msg.WriteUInt16((ushort)UpgradeManager.PurchasedUpgrades.Count);
foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades) foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades)
{ {
msg.Write(prefab.Identifier); msg.WriteIdentifier(prefab.Identifier);
msg.Write(category.Identifier); msg.WriteIdentifier(category.Identifier);
msg.Write((byte)level); msg.WriteByte((byte)level);
} }
msg.Write((ushort)UpgradeManager.PurchasedItemSwaps.Count); msg.WriteUInt16((ushort)UpgradeManager.PurchasedItemSwaps.Count);
foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps) foreach (var itemSwap in UpgradeManager.PurchasedItemSwaps)
{ {
msg.Write(itemSwap.ItemToRemove.ID); msg.WriteUInt16(itemSwap.ItemToRemove.ID);
msg.Write(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty); msg.WriteIdentifier(itemSwap.ItemToInstall?.Identifier ?? Identifier.Empty);
} }
} }
@@ -760,6 +761,7 @@ namespace Barotrauma
item.PendingItemSwap = null; item.PendingItemSwap = null;
} }
} }
campaign.CampaignUI?.UpgradeStore?.RequestRefresh();
} }
} }
@@ -1,12 +1,8 @@
using Barotrauma.Extensions; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -49,6 +45,31 @@ namespace Barotrauma
private bool showCampaignResetText; private bool showCampaignResetText;
public override bool PurchasedHullRepairs
{
get { return PurchasedHullRepairsInLatestSave; }
set
{
PurchasedHullRepairsInLatestSave = value;
}
}
public override bool PurchasedLostShuttles
{
get { return PurchasedLostShuttlesInLatestSave; }
set
{
PurchasedLostShuttlesInLatestSave = value;
}
}
public override bool PurchasedItemRepairs
{
get { return PurchasedItemRepairsInLatestSave; }
set
{
PurchasedItemRepairsInLatestSave = value;
}
}
#region Constructors/initialization #region Constructors/initialization
/// <summary> /// <summary>
@@ -132,9 +153,9 @@ namespace Barotrauma
}; };
} }
PurchasedLostShuttles = element.GetAttributeBool("purchasedlostshuttles", false); PurchasedLostShuttlesInLatestSave = element.GetAttributeBool("purchasedlostshuttles", false);
PurchasedHullRepairs = element.GetAttributeBool("purchasedhullrepairs", false); PurchasedHullRepairsInLatestSave = element.GetAttributeBool("purchasedhullrepairs", false);
PurchasedItemRepairs = element.GetAttributeBool("purchaseditemrepairs", false); PurchasedItemRepairsInLatestSave = element.GetAttributeBool("purchaseditemrepairs", false);
CheatsEnabled = element.GetAttributeBool("cheatsenabled", false); CheatsEnabled = element.GetAttributeBool("cheatsenabled", false);
if (CheatsEnabled) if (CheatsEnabled)
{ {
@@ -169,10 +190,20 @@ namespace Barotrauma
public static SinglePlayerCampaign Load(XElement element) => new SinglePlayerCampaign(element); public static SinglePlayerCampaign Load(XElement element) => new SinglePlayerCampaign(element);
private void InitUI() private void InitUI()
{
CreateEndRoundButton();
campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
CampaignUI = new CampaignUI(this, campaignUIContainer)
{
StartRound = () => { TryEndRound(); }
};
}
private void CreateEndRoundButton()
{ {
int buttonHeight = (int)(GUI.Scale * 40); int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(450); int buttonWidth = GUI.IntScale(450);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUI.Canvas), endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUI.Canvas),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton") TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{ {
@@ -190,12 +221,11 @@ namespace Barotrauma
return true; return true;
} }
}; };
}
campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black); public override void HUDScaleChanged()
CampaignUI = new CampaignUI(this, campaignUIContainer) {
{ CreateEndRoundButton();
StartRound = () => { TryEndRound(); }
};
} }
#endregion #endregion
@@ -205,7 +235,7 @@ namespace Barotrauma
base.Start(); base.Start();
CargoManager.CreatePurchasedItems(); CargoManager.CreatePurchasedItems();
UpgradeManager.ApplyUpgrades(); UpgradeManager.ApplyUpgrades();
UpgradeManager.SanityCheckUpgrades(Submarine.MainSub); UpgradeManager.SanityCheckUpgrades();
if (!savedOnStart) if (!savedOnStart)
{ {
@@ -292,7 +322,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration); overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.UnscaledDeltaTime, textDuration); timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
var outpost = GameMain.GameSession.Level.StartOutpost; var outpost = GameMain.GameSession.Level.StartOutpost;
@@ -320,7 +350,7 @@ namespace Barotrauma
while (timer < fadeInDuration) while (timer < fadeInDuration)
{ {
overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration); overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration);
timer += CoroutineManager.UnscaledDeltaTime; timer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
overlayColor = Color.Transparent; overlayColor = Color.Transparent;
@@ -353,7 +383,7 @@ namespace Barotrauma
if (prevControlled != null) if (prevControlled != null)
{ {
prevControlled.SelectedConstruction = null; prevControlled.SelectedItem = prevControlled.SelectedSecondaryItem = null;
if (prevControlled.AIController != null) if (prevControlled.AIController != null)
{ {
prevControlled.AIController.Enabled = true; prevControlled.AIController.Enabled = true;
@@ -424,7 +454,7 @@ namespace Barotrauma
float t = 0.0f; float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running) while (t < fadeOutDuration || endTransition.Running)
{ {
t += CoroutineManager.UnscaledDeltaTime; t += CoroutineManager.DeltaTime;
overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration); overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration);
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
} }
@@ -436,6 +466,7 @@ 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
@@ -1,346 +0,0 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Tutorials
{
class CaptainTutorial : ScenarioTutorial
{
// Room 1
private float shakeTimer = 1f;
private float shakeAmount = 20f;
// Room 2
private Door captain_firstDoor;
private LightComponent captain_firstDoorLight;
// Room 3
private Character captain_medic;
private MotionSensor captain_medicObjectiveSensor;
private Vector2 captain_medicSpawnPos;
private Door tutorial_submarineDoor;
private LightComponent tutorial_submarineDoorLight;
// Submarine
private MotionSensor captain_enteredSubmarineSensor;
private Steering captain_navConsole;
private Sonar captain_sonar;
private Item captain_statusMonitor;
private Character captain_security;
private Character captain_mechanic;
private Character captain_engineer;
private Reactor tutorial_submarineReactor;
private Door tutorial_lockedDoor_1;
private Door tutorial_lockedDoor_2;
// Variables
private Character captain;
private LocalizedString radioSpeakerName;
private Sprite captain_steerIcon;
private Color captain_steerIconColor;
public CaptainTutorial() : base("tutorial.captaintraining".ToIdentifier(),
new Segment(
"Captain.CommandMedic".ToIdentifier(),
"Captain.CommandMedicObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Captain.CommandMedicText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_command.webm", TextTag = "Captain.CommandMedicText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Captain.CommandMechanic".ToIdentifier(),
"Captain.CommandMechanicObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Captain.CommandMechanicText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Captain.CommandSecurity".ToIdentifier(),
"Captain.CommandSecurityObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Captain.CommandSecurityText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Captain.CommandEngineer".ToIdentifier(),
"Captain.CommandEngineerObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Captain.CommandEngineerText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Captain.Undock".ToIdentifier(),
"Captain.UndockObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Captain.UndockText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_undock.webm", TextTag = "Captain.UndockText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Captain.Navigate".ToIdentifier(),
"Captain.NavigateObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Captain.NavigateText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_navigation.webm", TextTag = "Captain.NavigateText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Captain.Dock".ToIdentifier(),
"Captain.DockObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Captain.DockText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_docking.webm", TextTag = "Captain.DockText".ToIdentifier(), Width = 450, Height = 80 }))
{ }
protected override CharacterInfo GetCharacterInfo()
{
return new CharacterInfo(
CharacterPrefab.HumanSpeciesName,
jobOrJobPrefab: new Job(
JobPrefab.Prefabs["captain"], Rand.RandSync.Unsynced, 0,
new Skill("medical".ToIdentifier(), 20),
new Skill("weapons".ToIdentifier(), 20),
new Skill("mechanical".ToIdentifier(), 20),
new Skill("electrical".ToIdentifier(), 20),
new Skill("helm".ToIdentifier(), 70)));
}
protected override void Initialize()
{
captain = Character.Controlled;
radioSpeakerName = TextManager.Get("Tutorial.Radio.Watchman");
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
foreach (Item item in captain.Inventory.AllItemsMod)
{
if (item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(captain);
captain.Inventory.RemoveItem(item);
}
var steerOrder = OrderPrefab.Prefabs["steer"];
captain_steerIcon = steerOrder.SymbolSprite;
captain_steerIconColor = steerOrder.Color;
// Room 2
captain_firstDoor = Item.ItemList.Find(i => i.HasTag("captain_firstdoor")).GetComponent<Door>();
captain_firstDoorLight = Item.ItemList.Find(i => i.HasTag("captain_firstdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(captain_firstDoor, captain_firstDoorLight, true);
// Room 3
captain_medicObjectiveSensor = Item.ItemList.Find(i => i.HasTag("captain_medicobjectivesensor")).GetComponent<MotionSensor>();
captain_medicSpawnPos = Item.ItemList.Find(i => i.HasTag("captain_medicspawnpos")).WorldPosition;
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("medicaldoctor"))
{
TeamID = CharacterTeamType.Team1
};
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
captain_medic.GiveJobItems(null);
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
// Submarine
captain_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("captain_enteredsubmarinesensor")).GetComponent<MotionSensor>();
tutorial_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
captain_navConsole = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
captain_sonar = captain_navConsole.Item.GetComponent<Sonar>();
captain_statusMonitor = Item.ItemList.Find(i => i.HasTag("captain_statusmonitor"));
tutorial_submarineReactor.CanBeSelected = false;
tutorial_submarineReactor.IsActive = tutorial_submarineReactor.AutoTemp = false;
tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent<Door>();
tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent<Door>();
SetDoorAccess(tutorial_lockedDoor_1, null, false);
SetDoorAccess(tutorial_lockedDoor_2, null, false);
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("mechanic"))
{
TeamID = CharacterTeamType.Team1
};
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer"))
{
TeamID = CharacterTeamType.Team1
};
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer"))
{
TeamID = CharacterTeamType.Team1
};
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.GiveJobItems();
captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false;
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false;
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Started");
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
}
public override IEnumerable<CoroutineStatus> UpdateState()
{
while (GameMain.Instance.LoadingScreenOpen) yield return null;
// Room 1
while (shakeTimer > 0.0f) // Wake up, shake
{
shakeTimer -= 0.1f;
GameMain.GameScreen.Cam.Shake = shakeAmount;
yield return new WaitForSeconds(0.1f, false);
}
// Room 2
do { yield return null; } while (!captain_firstDoor.IsOpen);
captain_medic.AIController.Enabled = true;
// Room 3
do { yield return null; } while (!captain_medicObjectiveSensor.MotionDetected);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(captain_medic.Info.DisplayName, TextManager.Get("Captain.Radio.Medic"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
GameMain.GameSession.CrewManager.AutoShowCrewList();
GameMain.GameSession.CrewManager.AddCharacter(captain_medic);
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
do
{
yield return null;
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_medic, "follow", highlightColor, new Vector2(5, 5));
}
while (!HasOrder(captain_medic, "follow"));
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
RemoveCompletedObjective(0);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective0");
// Submarine
do { yield return null; } while (!captain_enteredSubmarineSensor.MotionDetected);
yield return new WaitForSeconds(3f, false);
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = true;
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
GameMain.GameSession.CrewManager.AddCharacter(captain_mechanic);
do
{
yield return null;
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_mechanic, "repairsystems", highlightColor, new Vector2(5, 5));
//HighlightOrderOption("jobspecific");
} while (!HasOrder(captain_mechanic, "repairsystems") && !HasOrder(captain_mechanic, "repairmechanical") && !HasOrder(captain_mechanic, "repairelectrical"));
RemoveCompletedObjective(1);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective1");
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
GameMain.GameSession.CrewManager.AddCharacter(captain_security);
do
{
yield return null;
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
HighlightOrderOption("fireatwill");
}
while (!HasOrder(captain_security, "operateweapons"));
RemoveCompletedObjective(2);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective2");
yield return new WaitForSeconds(4f, false);
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command));
GameMain.GameSession.CrewManager.AddCharacter(captain_engineer);
tutorial_submarineReactor.CanBeSelected = true;
//recreate autonomous objectives to make sure the engineer didn't abandon the operate reactor objective because it was not selectable
(captain_engineer.AIController as HumanAIController).ObjectiveManager.CreateAutonomousObjectives();
do
{
yield return null;
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_engineer, "operatereactor", highlightColor, new Vector2(5, 5));
HighlightOrderOption("powerup");
}
while (!HasOrder(captain_engineer, "operatereactor", "powerup"));
RemoveCompletedObjective(3);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective3");
do { yield return null; } while (!tutorial_submarineReactor.IsActive); // Wait until reactor on
TriggerTutorialSegment(4);
while (ContentRunning) yield return null;
captain.AddActiveObjectiveEntity(captain_navConsole.Item, captain_steerIcon, captain_steerIconColor);
SetHighlight(captain_navConsole.Item, true);
SetHighlight(captain_sonar.Item, true);
SetHighlight(captain_statusMonitor, true);
do
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
} while (Submarine.MainSub.DockedTo.Any());
captain_navConsole.UseAutoDocking = false;
RemoveCompletedObjective(4);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective4");
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(5); // Navigate to destination
do
{
if (IsSelectedItem(captain_navConsole.Item))
{
if (captain_sonar.SonarModeSwitch.Frame.FlashTimer <= 0)
{
captain_sonar.SonarModeSwitch.Frame.Flash(highlightColor, 1.5f, false, false, new Vector2(2.5f, 2.5f));
}
}
yield return null;
} while (captain_sonar.CurrentMode != Sonar.Mode.Active);
do { yield return null; } while (Vector2.Distance(Submarine.MainSub.WorldPosition, Level.Loaded.EndPosition) > 4000f);
RemoveCompletedObjective(5);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective5");
captain_navConsole.UseAutoDocking = true;
yield return new WaitForSeconds(4f, false);
TriggerTutorialSegment(6); // Docking
do
{
//captain_navConsoleCustomInterface.HighlightElement(0, uiHighlightColor, duration: 1.0f, pulsateAmount: 0.0f);
yield return new WaitForSeconds(1.0f, false);
} while (!Submarine.MainSub.AtEndExit || !Submarine.MainSub.DockedTo.Any());
RemoveCompletedObjective(6);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Objective6");
yield return new WaitForSeconds(3f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.GetWithVariable("Captain.Radio.Complete", "[OUTPOSTNAME]", GameMain.GameSession.EndLocation.Name), ChatMessageType.Radio, null);
SetHighlight(captain_navConsole.Item, false);
SetHighlight(captain_sonar.Item, false);
SetHighlight(captain_statusMonitor, false);
captain.RemoveActiveObjectiveEntity(captain_navConsole.Item);
GameAnalyticsManager.AddDesignEvent("Tutorial:CaptainTutorial:Completed");
CoroutineManager.StartCoroutine(TutorialCompleted());
}
private void HighlightOrderOption(string option)
{
if (GameMain.GameSession.CrewManager.OrderOptionButtons.Count == 0) return;
var order = GameMain.GameSession.CrewManager.OrderOptionButtons[0].UserData as Order;
int orderIndex = 0;
for (int i = 0; i < GameMain.GameSession.CrewManager.OrderOptionButtons.Count; i++)
{
if (orderIndex >= order.Options.Length)
{
orderIndex = 0;
}
if (order.Options[orderIndex] == option)
{
if (GameMain.GameSession.CrewManager.OrderOptionButtons[i].FlashTimer <= 0)
{
GameMain.GameSession.CrewManager.OrderOptionButtons[i].Flash(highlightColor);
}
}
orderIndex++;
}
}
private bool IsSelectedItem(Item item)
{
return
captain?.SelectedConstruction == item ||
(captain?.SelectedConstruction?.linkedTo?.Contains(item) ?? false);
}
}
}
@@ -1,521 +0,0 @@
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Tutorials
{
class DoctorTutorial : ScenarioTutorial
{
// Room 1
private float shakeTimer = 1f;
private float shakeAmount = 20f;
private LocalizedString radioSpeakerName;
private Character doctor;
private ItemContainer doctor_suppliesCabinet;
private ItemContainer doctor_medBayCabinet;
private Character patient1, patient2;
private List<Character> subPatients;
private Hull medBay;
private Door doctor_firstDoor;
private Door doctor_secondDoor;
private Door doctor_thirdDoor;
private Door tutorial_upperFinalDoor;
private Door tutorial_lockedDoor_2;
private LightComponent doctor_firstDoorLight;
private LightComponent doctor_secondDoorLight;
private LightComponent doctor_thirdDoorLight;
private Door tutorial_submarineDoor;
private LightComponent tutorial_submarineDoorLight;
// Variables
private Sprite doctor_firstAidIcon;
private Color doctor_firstAidIconColor;
public DoctorTutorial() : base("tutorial.medicaldoctortraining".ToIdentifier(),
new Segment(
"Doctor.Supplies".ToIdentifier(),
"Doctor.SuppliesObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Doctor.SuppliesText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Doctor.OpenMedicalInterface".ToIdentifier(),
"Doctor.OpenMedicalInterfaceObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Doctor.OpenMedicalInterfaceText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_medinterface1.webm", TextTag = "Doctor.OpenMedicalInterfaceText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Doctor.FirstAidSelf".ToIdentifier(),
"Doctor.FirstAidSelfObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Doctor.FirstAidSelfText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_medinterface1.webm", TextTag = "Doctor.FirstAidSelfText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Doctor.Medbay".ToIdentifier(),
"Doctor.MedbayObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Doctor.MedbayText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_command.webm", TextTag = "Doctor.MedbayText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Doctor.TreatBurns".ToIdentifier(),
"Doctor.TreatBurnsObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Doctor.TreatBurnsText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_medinterface2.webm", TextTag = "Doctor.TreatBurnsText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Doctor.CPR".ToIdentifier(),
"Doctor.CPRObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Doctor.CPRText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_cpr.webm", TextTag = "Doctor.CPRText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Doctor.Submarine".ToIdentifier(),
"Doctor.SubmarineObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Doctor.SubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }))
{ }
protected override CharacterInfo GetCharacterInfo()
{
return new CharacterInfo(
CharacterPrefab.HumanSpeciesName,
jobOrJobPrefab: new Job(
JobPrefab.Prefabs["medicaldoctor"], Rand.RandSync.Unsynced, 0,
new Skill("medical".ToIdentifier(), 70),
new Skill("weapons".ToIdentifier(), 20),
new Skill("mechanical".ToIdentifier(), 20),
new Skill("electrical".ToIdentifier(), 20),
new Skill("helm".ToIdentifier(), 20)));
}
protected override void Initialize()
{
var firstAidOrder = OrderPrefab.Prefabs["requestfirstaid"];
doctor_firstAidIcon = firstAidOrder.SymbolSprite;
doctor_firstAidIconColor = firstAidOrder.Color;
subPatients = new List<Character>();
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
doctor = Character.Controlled;
foreach (Item item in doctor.Inventory.AllItemsMod)
{
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(doctor);
doctor.Inventory.RemoveItem(item);
}
doctor_suppliesCabinet = Item.ItemList.Find(i => i.HasTag("doctor_suppliescabinet"))?.GetComponent<ItemContainer>();
doctor_medBayCabinet = Item.ItemList.Find(i => i.HasTag("doctor_medbaycabinet"))?.GetComponent<ItemContainer>();
var patientHull1 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "waitingroom").CurrentHull;
var patientHull2 = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "airlock").CurrentHull;
medBay = WayPoint.WayPointList.Find(wp => wp.IdCardDesc == "medbay").CurrentHull;
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant"))
{
TeamID = CharacterTeamType.Team1
};
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
patient1.GiveJobItems(null);
patient1.CanSpeak = false;
patient1.Params.Health.BurnReduction = 0;
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 15.0f) }, stun: 0, playSound: false);
patient1.AIController.Enabled = false;
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("assistant"))
{
TeamID = CharacterTeamType.Team1
};
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
patient2.GiveJobItems(null);
patient2.CanSpeak = false;
patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer"))
{
TeamID = CharacterTeamType.Team1
};
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient1.Params.Health.BurnReduction = 0;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = CharacterTeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: JobPrefab.Get("engineer"))
{
TeamID = CharacterTeamType.Team1
};
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job?.Prefab, Submarine.MainSub).WorldPosition, "3");
subPatient3.Params.Health.BurnReduction = 0;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3);
doctor_firstDoor = Item.ItemList.Find(i => i.HasTag("doctor_firstdoor")).GetComponent<Door>();
doctor_secondDoor = Item.ItemList.Find(i => i.HasTag("doctor_seconddoor")).GetComponent<Door>();
doctor_thirdDoor = Item.ItemList.Find(i => i.HasTag("doctor_thirddoor")).GetComponent<Door>();
tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent<Door>();
doctor_firstDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_firstdoorlight")).GetComponent<LightComponent>();
doctor_secondDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_seconddoorlight")).GetComponent<LightComponent>();
doctor_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("doctor_thirddoorlight")).GetComponent<LightComponent>();
SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, false);
SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, false);
SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, false);
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
tutorial_lockedDoor_2 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_2")).GetComponent<Door>();
SetDoorAccess(tutorial_lockedDoor_2, null, true);
foreach (var patient in subPatients)
{
patient.CanSpeak = false;
patient.AIController.Enabled = false;
patient.GiveJobItems();
}
Item reactorItem = Item.ItemList.Find(i => i.Submarine == Submarine.MainSub && i.GetComponent<Reactor>() != null);
reactorItem.GetComponent<Reactor>().AutoTemp = true;
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Started");
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
}
public override IEnumerable<CoroutineStatus> UpdateState()
{
while (GameMain.Instance.LoadingScreenOpen) yield return null;
// explosions and radio messages ------------------------------------------------------
yield return new WaitForSeconds(3.0f, false);
//SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
//// Room 1
//while (shakeTimer > 0.0f) // Wake up, shake
//{
// shakeTimer -= 0.1f;
// GameMain.GameScreen.Cam.Shake = shakeAmount;
// yield return new WaitForSeconds(0.1f);
//}
//yield return new WaitForSeconds(2.5f);
//GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
//yield return new WaitForSeconds(2.5f);
doctor.SetStun(1.5f);
var explosion = new Explosion(range: 100, force: 10, damage: 0, structureDamage: 0, itemDamage: 0);
explosion.DisableParticles();
GameMain.GameScreen.Cam.Shake = shakeAmount;
explosion.Explode(Character.Controlled.WorldPosition - Vector2.UnitX * 25, null);
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition - Vector2.UnitX * 25);
yield return new WaitForSeconds(0.5f, false);
doctor.DamageLimb(
Character.Controlled.WorldPosition,
doctor.AnimController.GetLimb(LimbType.Torso),
new List<Affliction> { new Affliction(AfflictionPrefab.InternalDamage, 10.0f) },
stun: 3.0f, playSound: true, attackImpulse: 0.0f);
shakeTimer = 0.5f;
while (shakeTimer > 0.0f) // Wake up, shake
{
shakeTimer -= 0.1f;
GameMain.GameScreen.Cam.Shake = shakeAmount;
yield return new WaitForSeconds(0.1f, false);
}
yield return new WaitForSeconds(3.0f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.KnockedDown"), ChatMessageType.Radio, null);
// first tutorial segment, get medical supplies ------------------------------------------------------
yield return new WaitForSeconds(1.5f, false);
SetHighlight(doctor_suppliesCabinet.Item, true);
/*while (doctor.CurrentHull != doctor_suppliesCabinet.Item.CurrentHull)
{
yield return new WaitForSeconds(2.0f);
}*/
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect), "None"); // Medical supplies objective
do
{
for (int i = 0; i < doctor_suppliesCabinet.Inventory.Capacity; i++)
{
if (doctor_suppliesCabinet.Inventory.GetItemAt(i) != null)
{
HighlightInventorySlot(doctor_suppliesCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
}
}
if (doctor.SelectedConstruction == doctor_suppliesCabinet.Item)
{
for (int i = 0; i < doctor.Inventory.Capacity; i++)
{
if (doctor.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
}
yield return null;
} while (doctor.Inventory.FindItemByIdentifier("antidama1".ToIdentifier()) == null); // Wait until looted
yield return new WaitForSeconds(1.0f, false);
SetHighlight(doctor_suppliesCabinet.Item, false);
RemoveCompletedObjective(0);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective0");
yield return new WaitForSeconds(1.0f, false);
// 2nd tutorial segment, treat self -------------------------------------------------------------------------
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // Open health interface
while (CharacterHealth.OpenHealthWindow == null)
{
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
yield return null;
}
yield return null;
RemoveCompletedObjective(1);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective1");
yield return new WaitForSeconds(1.0f, false);
TriggerTutorialSegment(2); //Treat self
while (doctor.CharacterHealth.GetAfflictionStrength("damage") > 0.01f)
{
if (CharacterHealth.OpenHealthWindow == null)
{
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
}
else
{
HighlightInventorySlot(doctor.Inventory, "antidama1".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
yield return null;
}
RemoveCompletedObjective(2);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective2");
SetDoorAccess(doctor_firstDoor, doctor_firstDoorLight, true);
while (CharacterHealth.OpenHealthWindow != null)
{
yield return new WaitForSeconds(1.0f, false);
}
// treat patient --------------------------------------------------------------------------------------------
//patient 1 requests first aid
var newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], patient1.CurrentHull, null, orderGiver: patient1);
doctor.AddActiveObjectiveEntity(patient1, doctor_firstAidIcon, doctor_firstAidIconColor);
//GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient1.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName?.Value, givingOrderToSelf: false), ChatMessageType.Order, null);
while (doctor.CurrentHull != patient1.CurrentHull)
{
yield return new WaitForSeconds(1.0f, false);
}
yield return new WaitForSeconds(0.0f, false);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurns"), ChatMessageType.Radio, null);
GameMain.GameSession.CrewManager.AllowCharacterSwitch = false;
GameMain.GameSession.CrewManager.AddCharacter(doctor);
GameMain.GameSession.CrewManager.AddCharacter(patient1);
GameMain.GameSession.CrewManager.AutoShowCrewList();
patient1.CharacterHealth.UseHealthWindow = false;
yield return new WaitForSeconds(3.0f, false);
patient1.AIController.Enabled = true;
doctor.RemoveActiveObjectiveEntity(patient1);
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Command)); // Get the patient to medbay
while (patient1.GetCurrentOrderWithTopPriority()?.Identifier != "follow")
{
// TODO: Rework order highlighting for new command UI
// GameMain.GameSession.CrewManager.HighlightOrderButton(patient1, "follow", highlightColor, new Vector2(5, 5));
yield return null;
}
SetDoorAccess(doctor_secondDoor, doctor_secondDoorLight, true);
while (patient1.CurrentHull != medBay)
{
yield return new WaitForSeconds(1.0f, false);
}
RemoveCompletedObjective(3);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective3");
SetHighlight(doctor_medBayCabinet.Item, true);
SetDoorAccess(doctor_thirdDoor, doctor_thirdDoorLight, true);
patient1.CharacterHealth.UseHealthWindow = true;
yield return new WaitForSeconds(2.0f, false);
TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // treat burns
do
{
for (int i = 0; i < 3; i++)
{
if (doctor_medBayCabinet.Inventory.GetItemAt(i) != null)
{
HighlightInventorySlot(doctor_medBayCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
}
}
if (doctor.SelectedConstruction == doctor_medBayCabinet.Item)
{
for (int i = 0; i < doctor.Inventory.Capacity; i++)
{
if (doctor.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(doctor.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
}
yield return null;
} while (doctor.Inventory.FindItemByIdentifier("antibleeding1".ToIdentifier()) == null); // Wait until looted
SetHighlight(doctor_medBayCabinet.Item, false);
SetHighlight(patient1, true);
while (patient1.CharacterHealth.GetAfflictionStrength("burn") > 0.01f)
{
if (CharacterHealth.OpenHealthWindow == null)
{
doctor.CharacterHealth.HealthBarPulsateTimer = 1.0f;
}
else
{
HighlightInventorySlot(doctor.Inventory, "antibleeding1".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
yield return null;
}
RemoveCompletedObjective(4);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective4");
SetHighlight(patient1, false);
yield return new WaitForSeconds(1.0f, false);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.AssistantBurnsHealed"), ChatMessageType.Radio, null);
// treat unconscious patient ------------------------------------------------------
//patient calls for help
//patient2.CanSpeak = true;
yield return new WaitForSeconds(2.0f, false);
newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], patient2.CurrentHull, null, orderGiver: patient2);
doctor.AddActiveObjectiveEntity(patient2, doctor_firstAidIcon, doctor_firstAidIconColor);
//GameMain.GameSession.CrewManager.AddOrder(newOrder, newOrder.FadeOutTime);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(patient2.Name, newOrder.GetChatMessage("", patient1.CurrentHull?.DisplayName?.Value, givingOrderToSelf: false), ChatMessageType.Order, null);
patient2.AIController.Enabled = true;
patient2.Oxygen = -50;
CoroutineManager.StartCoroutine(KeepPatientAlive(patient2), "KeepPatient2Alive");
/*while (doctor.CurrentHull != patient2.CurrentHull)
{
yield return new WaitForSeconds(1.0f);
}*/
do { yield return null; } while (!tutorial_upperFinalDoor.IsOpen);
yield return new WaitForSeconds(2.0f, false);
TriggerTutorialSegment(5, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // perform CPR
SetHighlight(patient2, true);
while (patient2.IsUnconscious)
{
if (CharacterHealth.OpenHealthWindow != null && doctor.AnimController.Anim != AnimController.Animation.CPR)
{
//Disabled pulse until it's replaced by a better effect
//CharacterHealth.OpenHealthWindow.CPRButton.Pulsate(Vector2.One, Vector2.One * 1.5f, 1.0f);
if (CharacterHealth.OpenHealthWindow.CPRButton.FlashTimer <= 0.0f)
{
CharacterHealth.OpenHealthWindow.CPRButton.Flash(highlightColor);
}
}
yield return null;
}
RemoveCompletedObjective(5);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective5");
SetHighlight(patient2, false);
doctor.RemoveActiveObjectiveEntity(patient2);
CoroutineManager.StopCoroutines("KeepPatient2Alive");
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
while (doctor.Submarine != Submarine.MainSub)
{
yield return new WaitForSeconds(1.0f, false);
}
subPatients[2].Oxygen = -50;
CoroutineManager.StartCoroutine(KeepPatientAlive(subPatients[2]), "KeepPatient3Alive");
yield return new WaitForSeconds(5.0f, false);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Doctor.Radio.EnteredSub"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(3.0f, false);
TriggerTutorialSegment(6, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Health)); // give treatment to anyone in need
foreach (var patient in subPatients)
{
//patient.CanSpeak = true;
patient.AIController.Enabled = true;
SetHighlight(patient, true);
}
double subEnterTime = Timing.TotalTime;
bool[] patientCalledHelp = new bool[] { false, false, false };
while (subPatients.Any(p => p.Vitality < p.MaxVitality * 0.9f && !p.IsDead))
{
for (int i = 0; i < subPatients.Count; i++)
{
//make patients call for help to make sure the player finds them
//(within 1 minute intervals of entering the sub)
if (!patientCalledHelp[i] && Timing.TotalTime > subEnterTime + 60 * (i + 1))
{
doctor.AddActiveObjectiveEntity(subPatients[i], doctor_firstAidIcon, doctor_firstAidIconColor);
newOrder = new Order(OrderPrefab.Prefabs["requestfirstaid"], subPatients[i].CurrentHull, null, orderGiver: subPatients[i]);
string message = newOrder.GetChatMessage("", subPatients[i].CurrentHull?.DisplayName?.Value, givingOrderToSelf: false);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(subPatients[i].Name, message, ChatMessageType.Order, null);
patientCalledHelp[i] = true;
}
if (subPatients[i].ExternalHighlight && subPatients[i].Vitality >= subPatients[i].MaxVitality * 0.9f)
{
doctor.RemoveActiveObjectiveEntity(subPatients[i]);
SetHighlight(subPatients[i], false);
}
}
yield return new WaitForSeconds(1.0f, false);
}
RemoveCompletedObjective(6);
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Objective6");
foreach (var patient in subPatients)
{
SetHighlight(patient, false);
doctor.RemoveActiveObjectiveEntity(patient);
}
// END TUTORIAL
GameAnalyticsManager.AddDesignEvent("Tutorial:DoctorTutorial:Completed");
CoroutineManager.StartCoroutine(TutorialCompleted());
}
public IEnumerable<CoroutineStatus> KeepPatientAlive(Character patient)
{
while (patient != null && !patient.Removed)
{
patient.Oxygen = Math.Max(patient.Oxygen, -50);
yield return null;
}
}
}
}
@@ -1,664 +0,0 @@
using System;
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma.Tutorials
{
class EngineerTutorial : ScenarioTutorial
{
// Other tutorial items
private LightComponent tutorial_securityFinalDoorLight;
private LightComponent tutorial_mechanicFinalDoorLight;
private Steering tutorial_submarineSteering;
// Room 1
private float shakeTimer = 1f;
private float shakeAmount = 20f;
// Room 2
private MotionSensor engineer_equipmentObjectiveSensor;
private ItemContainer engineer_equipmentCabinet;
private Door engineer_firstDoor;
private LightComponent engineer_firstDoorLight;
// Room 3
private Powered tutorial_oxygenGenerator;
private Reactor engineer_reactor;
private Door engineer_secondDoor;
private LightComponent engineer_secondDoorLight;
// Room 4
private Item engineer_brokenJunctionBox;
private Door engineer_thirdDoor;
private LightComponent engineer_thirdDoorLight;
// Room 5
private PowerTransfer[] engineer_disconnectedJunctionBoxes;
private ConnectionPanel[] engineer_disconnectedConnectionPanels;
private Item engineer_wire_1;
private Powered engineer_lamp_1;
private Item engineer_wire_2;
private Powered engineer_lamp_2;
private Door engineer_fourthDoor;
private LightComponent engineer_fourthDoorLight;
// Room 6
private Pump engineer_workingPump;
private Door tutorial_lockedDoor_1;
// Submarine
private Door tutorial_submarineDoor;
private LightComponent tutorial_submarineDoorLight;
private MotionSensor tutorial_enteredSubmarineSensor;
private Item engineer_submarineJunctionBox_1;
private Item engineer_submarineJunctionBox_2;
private Item engineer_submarineJunctionBox_3;
private Reactor engineer_submarineReactor;
// Variables
private LocalizedString radioSpeakerName;
private Character engineer;
private int[] reactorLoads = new int[5] { 1500, 3000, 2000, 5000, 3500 };
private float reactorLoadChangeTime = 2f;
private float reactorLoadError = 200f;
private bool reactorOperatedProperly;
private const float waterVolumeBeforeOpening = 15f;
private Sprite engineer_repairIcon;
private Color engineer_repairIconColor;
private Sprite engineer_reactorIcon;
private Color engineer_reactorIconColor;
private bool wiringActive = false;
public EngineerTutorial() : base("tutorial.engineertraining".ToIdentifier(),
new Segment(
"Mechanic.Equipment".ToIdentifier(),
"Mechanic.EquipmentObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Engineer.Reactor".ToIdentifier(),
"Engineer.ReactorObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_reactor.webm", TextTag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80 }),
new Segment(
"Engineer.OperateReactor".ToIdentifier(),
"Engineer.OperateReactorObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Engineer.OperateReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_reactor.webm", TextTag = "Engineer.ReactorText".ToIdentifier(), Width = 700, Height = 80 }),
new Segment(
"Engineer.RepairJunctionBox".ToIdentifier(),
"Engineer.RepairJunctionBoxObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Engineer.RepairJunctionBoxText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Engineer.WireJunctionBoxes".ToIdentifier(),
"Engineer.WireJunctionBoxesObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Engineer.WireJunctionBoxesText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_wiring.webm", TextTag = "Engineer.WireJunctionBoxesText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Engineer.RepairElectricalRoom".ToIdentifier(),
"Engineer.RepairElectricalRoomObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Engineer.RepairElectricalRoomText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Engineer.PowerUpReactor".ToIdentifier(),
"Engineer.PowerUpReactorObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Engineer.PowerUpReactorText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center }))
{ }
protected override CharacterInfo GetCharacterInfo()
{
return new CharacterInfo(
CharacterPrefab.HumanSpeciesName,
jobOrJobPrefab: new Job(
JobPrefab.Prefabs["engineer"], Rand.RandSync.Unsynced, 0,
new Skill("medical".ToIdentifier(), 0),
new Skill("weapons".ToIdentifier(), 0),
new Skill("mechanical".ToIdentifier(), 20),
new Skill("electrical".ToIdentifier(), 60),
new Skill("helm".ToIdentifier(), 0)));
}
protected override void Initialize()
{
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
engineer = Character.Controlled;
foreach (Item item in engineer.Inventory.AllItemsMod)
{
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(engineer);
engineer.Inventory.RemoveItem(item);
}
var repairOrder = OrderPrefab.Prefabs["repairsystems"];
engineer_repairIcon = repairOrder.SymbolSprite;
engineer_repairIconColor = repairOrder.Color;
var reactorOrder = OrderPrefab.Prefabs["operatereactor"];
engineer_reactorIcon = reactorOrder.SymbolSprite;
engineer_reactorIconColor = reactorOrder.Color;
// Other tutorial items
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
tutorial_submarineSteering.CanBeSelected = false;
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
{
ic.CanBeSelected = false;
}
SetDoorAccess(null, tutorial_securityFinalDoorLight, false);
SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false);
// Room 2
engineer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("engineer_equipmentobjectivesensor")).GetComponent<MotionSensor>();
engineer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("engineer_equipmentcabinet")).GetComponent<ItemContainer>();
engineer_firstDoor = Item.ItemList.Find(i => i.HasTag("engineer_firstdoor")).GetComponent<Door>();
engineer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_firstdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, false);
// Room 3
tutorial_oxygenGenerator = Item.ItemList.Find(i => i.HasTag("tutorial_oxygengenerator")).GetComponent<OxygenGenerator>();
engineer_reactor = Item.ItemList.Find(i => i.HasTag("engineer_reactor")).GetComponent<Reactor>();
engineer_reactor.FireDelay = engineer_reactor.MeltdownDelay = float.PositiveInfinity;
engineer_reactor.FuelConsumptionRate = 0.0f;
engineer_reactor.PowerOn = true;
reactorOperatedProperly = false;
engineer_secondDoor = Item.ItemList.Find(i => i.HasTag("engineer_seconddoor")).GetComponent<Door>(); ;
engineer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_seconddoorlight")).GetComponent<LightComponent>();
SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, false);
// Room 4
engineer_brokenJunctionBox = Item.ItemList.Find(i => i.HasTag("engineer_brokenjunctionbox"));
engineer_thirdDoor = Item.ItemList.Find(i => i.HasTag("engineer_thirddoor")).GetComponent<Door>();
engineer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_thirddoorlight")).GetComponent<LightComponent>();
engineer_brokenJunctionBox.Indestructible = false;
engineer_brokenJunctionBox.Condition = 0f;
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, false);
// Room 5
engineer_disconnectedJunctionBoxes = new PowerTransfer[4];
engineer_disconnectedConnectionPanels = new ConnectionPanel[4];
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
{
engineer_disconnectedJunctionBoxes[i] = Item.ItemList.Find(item => item.HasTag($"engineer_disconnectedjunctionbox_{i + 1}")).GetComponent<PowerTransfer>();
engineer_disconnectedConnectionPanels[i] = engineer_disconnectedJunctionBoxes[i].Item.GetComponent<ConnectionPanel>();
engineer_disconnectedConnectionPanels[i].Locked = false;
for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++)
{
foreach (Wire wire in engineer_disconnectedJunctionBoxes[i].PowerConnections[j].Wires)
{
if (wire == null) continue;
wire.Locked = true;
}
}
}
engineer_wire_1 = Item.ItemList.Find(i => i.HasTag("engineer_wire_1"));
engineer_wire_1.SpriteColor = Color.Transparent;
engineer_wire_2 = Item.ItemList.Find(i => i.HasTag("engineer_wire_2"));
engineer_wire_2.SpriteColor = Color.Transparent;
engineer_lamp_1 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_1")).GetComponent<Powered>();
engineer_lamp_2 = Item.ItemList.Find(i => i.HasTag("engineer_lamp_2")).GetComponent<Powered>();
engineer_fourthDoor = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoor")).GetComponent<Door>();
engineer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("engineer_fourthdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, false);
// Room 6
engineer_workingPump = Item.ItemList.Find(i => i.HasTag("engineer_workingpump")).GetComponent<Pump>();
engineer_workingPump.Item.CurrentHull.WaterVolume += engineer_workingPump.Item.CurrentHull.Volume;
engineer_workingPump.IsActive = true;
tutorial_lockedDoor_1 = Item.ItemList.Find(i => i.HasTag("tutorial_lockeddoor_1")).GetComponent<Door>();
SetDoorAccess(tutorial_lockedDoor_1, null, true);
// Submarine
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
engineer_submarineJunctionBox_1 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_1"));
engineer_submarineJunctionBox_2 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_2"));
engineer_submarineJunctionBox_3 = Item.ItemList.Find(i => i.HasTag("engineer_submarinejunctionbox_3"));
engineer_submarineReactor = Item.ItemList.Find(i => i.HasTag("engineer_submarinereactor")).GetComponent<Reactor>();
engineer_submarineReactor.PowerOn = true;
engineer_submarineReactor.IsActive = engineer_submarineReactor.AutoTemp = false;
engineer_submarineJunctionBox_1.Indestructible = false;
engineer_submarineJunctionBox_1.Condition = 0f;
engineer_submarineJunctionBox_2.Indestructible = false;
engineer_submarineJunctionBox_2.Condition = 0f;
engineer_submarineJunctionBox_3.Indestructible = false;
engineer_submarineJunctionBox_3.Condition = 0f;
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Started");
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
}
public override IEnumerable<CoroutineStatus> UpdateState()
{
while (GameMain.Instance.LoadingScreenOpen) { yield return null; }
// Room 1
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
while (shakeTimer > 0.0f) // Wake up, shake
{
shakeTimer -= 0.1f;
GameMain.GameScreen.Cam.Shake = shakeAmount;
yield return new WaitForSeconds(0.1f, false);
}
//// Remove
//for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
//{
// SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true);
//}
//do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
//CheckGhostWires();
//// Remove
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.WakeUp"), ChatMessageType.Radio, null);
SetHighlight(engineer_equipmentCabinet.Item, true);
// Room 2
do { yield return null; } while (!engineer_equipmentObjectiveSensor.MotionDetected);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Equipment"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(0.5f, false);
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Retrieve equipment
bool firstSlotRemoved = false;
bool secondSlotRemoved = false;
bool thirdSlotRemoved = false;
bool fourthSlotRemoved = false;
do
{
if (IsSelectedItem(engineer_equipmentCabinet.Item))
{
if (!firstSlotRemoved)
{
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
if (engineer_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; }
}
if (!secondSlotRemoved)
{
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
if (engineer_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; }
}
if (!thirdSlotRemoved)
{
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
if (engineer_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; }
}
if (!fourthSlotRemoved)
{
HighlightInventorySlot(engineer_equipmentCabinet.Inventory, 3, highlightColor, .5f, .5f, 0f);
if (engineer_equipmentCabinet.Inventory.GetItemAt(2) == null) { fourthSlotRemoved = true; }
}
for (int i = 0; i < engineer.Inventory.visualSlots.Length; i++)
{
if (engineer.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(engineer.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
}
yield return null;
} while (!engineer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted
RemoveCompletedObjective(0);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective0");
SetHighlight(engineer_equipmentCabinet.Item, false);
SetHighlight(engineer_reactor.Item, true);
SetDoorAccess(engineer_firstDoor, engineer_firstDoorLight, true);
// Room 3
do { yield return null; } while (!IsSelectedItem(engineer_reactor.Item));
yield return new WaitForSeconds(0.5f, false);
TriggerTutorialSegment(1);
do
{
if (IsSelectedItem(engineer_reactor.Item))
{
engineer_reactor.AutoTemp = false;
if (engineer_reactor.PowerButton.FlashTimer <= 0)
{
engineer_reactor.PowerButton.Flash(highlightColor, 1.5f, false);
}
}
yield return null;
} while (!engineer_reactor.PowerOn);
do
{
if (IsSelectedItem(engineer_reactor.Item) && engineer_reactor.Item.OwnInventory.visualSlots != null)
{
engineer_reactor.AutoTemp = false;
HighlightInventorySlot(engineer.Inventory, "fuelrod".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
for (int i = 0; i < engineer_reactor.Item.OwnInventory.visualSlots.Length; i++)
{
HighlightInventorySlot(engineer_reactor.Item.OwnInventory, i, highlightColor, 0.5f, 0.5f, 0f);
}
}
yield return null;
} while (engineer_reactor.AvailableFuel == 0);
RemoveCompletedObjective(1);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective1");
TriggerTutorialSegment(2);
CoroutineManager.StartCoroutine(ReactorOperatedProperly());
do
{
if (IsSelectedItem(engineer_reactor.Item))
{
engineer_reactor.AutoTemp = false;
if (engineer_reactor.FissionRateScrollBar.FlashTimer <= 0)
{
engineer_reactor.FissionRateScrollBar.Flash(highlightColor, 1.5f);
}
if (engineer_reactor.TurbineOutputScrollBar.FlashTimer <= 0)
{
engineer_reactor.TurbineOutputScrollBar.Flash(highlightColor, 1.5f);
}
}
yield return null;
} while (!reactorOperatedProperly);
yield return new WaitForSeconds(2f, false);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ReactorStable"), ChatMessageType.Radio, null);
do
{
if (IsSelectedItem(engineer_reactor.Item))
{
if (engineer_reactor.AutoTempSwitch.FlashTimer <= 0)
{
engineer_reactor.AutoTempSwitch.Flash(highlightColor, 1.5f, false, false, new Vector2(10, 10));
}
}
yield return null;
} while (!engineer_reactor.AutoTemp);
float wait = 1.5f;
do
{
yield return new WaitForSeconds(0.1f, false);
wait -= 0.1f;
engineer_reactor.AutoTemp = true;
} while (wait > 0.0f);
engineer.SelectedConstruction = null;
engineer_reactor.CanBeSelected = false;
RemoveCompletedObjective(2);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective2");
SetHighlight(engineer_reactor.Item, false);
SetHighlight(engineer_brokenJunctionBox, true);
SetDoorAccess(engineer_secondDoor, engineer_secondDoorLight, true);
// Room 4
do { yield return null; } while (!engineer_secondDoor.IsOpen);
yield return new WaitForSeconds(1f, false);
Repairable repairableJunctionBoxComponent = engineer_brokenJunctionBox.GetComponent<Repairable>();
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Repair the junction box
do
{
if (!engineer.HasEquippedItem("screwdriver".ToIdentifier()))
{
HighlightInventorySlot(engineer.Inventory, "screwdriver".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
else if (IsSelectedItem(engineer_brokenJunctionBox) && repairableJunctionBoxComponent.CurrentFixer == null)
{
if (repairableJunctionBoxComponent.RepairButton.FlashTimer <= 0)
{
repairableJunctionBoxComponent.RepairButton.Flash();
}
}
yield return null;
} while (repairableJunctionBoxComponent.IsBelowRepairThreshold); // Wait until repaired
SetHighlight(engineer_brokenJunctionBox, false);
RemoveCompletedObjective(3);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective3");
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
{
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, true);
}
// Room 5
do { yield return null; } while (!engineer_thirdDoor.IsOpen);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.FaultyWiring"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Connect the junction boxes
do { CheckGhostWires(); HandleJunctionBoxWiringHighlights(); yield return null; } while (engineer_workingPump.Voltage < engineer_workingPump.MinVoltage); // Wait until connected all the way to the pump
CheckGhostWires();
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
{
SetHighlight(engineer_disconnectedJunctionBoxes[i].Item, false);
}
RemoveCompletedObjective(4);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective4");
do { yield return null; } while (engineer_workingPump.Item.CurrentHull.WaterPercentage > waterVolumeBeforeOpening); // Wait until drained
wiringActive = false;
SetDoorAccess(engineer_fourthDoor, engineer_fourthDoorLight, true);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.ChangeOfPlans"), ChatMessageType.Radio, null);
// Submarine
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Submarine"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(5); // Repair junction box
while (ContentRunning) yield return null;
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, engineer_repairIcon, engineer_repairIconColor);
SetHighlight(engineer_submarineJunctionBox_1, true);
SetHighlight(engineer_submarineJunctionBox_2, true);
SetHighlight(engineer_submarineJunctionBox_3, true);
Repairable repairableJunctionBoxComponent1 = engineer_submarineJunctionBox_1.GetComponent<Repairable>();
Repairable repairableJunctionBoxComponent2 = engineer_submarineJunctionBox_2.GetComponent<Repairable>();
Repairable repairableJunctionBoxComponent3 = engineer_submarineJunctionBox_3.GetComponent<Repairable>();
// Remove highlights when each individual machine is repaired
do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (repairableJunctionBoxComponent1.IsBelowRepairThreshold || repairableJunctionBoxComponent2.IsBelowRepairThreshold || repairableJunctionBoxComponent3.IsBelowRepairThreshold);
CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3);
RemoveCompletedObjective(5);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective5");
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(6); // Powerup reactor
SetHighlight(engineer_submarineReactor.Item, true);
engineer.AddActiveObjectiveEntity(engineer_submarineReactor.Item, engineer_reactorIcon, engineer_reactorIconColor);
do { yield return null; } while (!IsReactorPoweredUp(engineer_submarineReactor)); // Wait until ~matches load
engineer.RemoveActiveObjectiveEntity(engineer_submarineReactor.Item);
SetHighlight(engineer_submarineReactor.Item, false);
RemoveCompletedObjective(6);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Objective6");
GameMain.GameSession.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Engineer.Radio.Complete"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(4f, false);
GameAnalyticsManager.AddDesignEvent("Tutorial:EngineerTutorial:Completed");
CoroutineManager.StartCoroutine(TutorialCompleted());
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (wiringActive)
{
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
{
for (int j = 0; j < engineer_disconnectedJunctionBoxes[i].PowerConnections.Count; j++)
{
engineer_disconnectedJunctionBoxes[i].PowerConnections[j].UpdateFlashTimer(deltaTime);
}
}
}
}
private bool IsSelectedItem(Item item)
{
return engineer?.SelectedConstruction == item;
}
private IEnumerable<CoroutineStatus> ReactorOperatedProperly()
{
float timer;
for (int i = 0; i < reactorLoads.Length; i++)
{
timer = reactorLoadChangeTime;
tutorial_oxygenGenerator.PowerConsumption = reactorLoads[i];
while (timer > 0)
{
yield return CoroutineStatus.Running;
if (CoroutineManager.DeltaTime > 0.0f && IsReactorPoweredUp(engineer_reactor))
{
timer -= CoroutineManager.DeltaTime;
}
}
}
reactorOperatedProperly = true;
}
private void CheckGhostWires()
{
Color wireColor =
Color.Orange *
MathHelper.Lerp(0.25f, 0.75f, (float)(Math.Sin((Timing.TotalTime * 4.0f)) + 1.0f) / 2.0f);
if (engineer_wire_1 != null)
{
engineer_wire_1.SpriteColor = wireColor;
if (engineer_lamp_1.Voltage > engineer_lamp_1.MinVoltage)
{
engineer_wire_1.Remove();
engineer_wire_1 = null;
}
}
if (engineer_wire_2 != null)
{
engineer_wire_2.SpriteColor = wireColor;
if (engineer_lamp_2.Voltage > engineer_lamp_2.MinVoltage)
{
engineer_wire_2.Remove();
engineer_wire_2 = null;
}
}
}
private void HandleJunctionBoxWiringHighlights()
{
Item selected = engineer.SelectedConstruction;
if (!engineer.HasEquippedItem("screwdriver".ToIdentifier()))
{
HighlightInventorySlot(engineer.Inventory, "screwdriver".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
}
int selectedIndex = -1;
if (selected != null)
{
for (int i = 0; i < engineer_disconnectedJunctionBoxes.Length; i++)
{
if (selected == engineer_disconnectedJunctionBoxes[i].Item)
{
selectedIndex = i;
break;
}
}
}
wiringActive = selectedIndex != -1;
if (!engineer.HasEquippedItem("wire".ToIdentifier()))
{
HighlightInventorySlotWithTag(engineer.Inventory, "wire".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
}
else
{
if (!wiringActive) return;
for (int i = 0; i < engineer_disconnectedConnectionPanels[selectedIndex].Connections.Count; i++)
{
var connection = engineer_disconnectedConnectionPanels[selectedIndex].Connections[i];
if (connection.IsPower && connection.FlashTimer <= 0)
{
foreach (Wire wire in engineer_disconnectedConnectionPanels[selectedIndex].Connections[i].Wires)
{
if (wire == null) continue;
if (!wire.Locked)
{
return;
}
}
connection.Flash(highlightColor);
}
}
}
}
private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{
if (!comp1.IsBelowRepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight)
{
SetHighlight(engineer_submarineJunctionBox_1, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1);
}
if (!comp2.IsBelowRepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight)
{
SetHighlight(engineer_submarineJunctionBox_2, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2);
}
if (!comp3.IsBelowRepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight)
{
SetHighlight(engineer_submarineJunctionBox_3, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3);
}
}
private bool IsReactorPoweredUp(Reactor reactor)
{
float load = 0.0f;
List<Connection> connections = reactor.Item.Connections;
if (connections != null && connections.Count > 0)
{
foreach (Connection connection in connections)
{
if (!connection.IsPower) continue;
foreach (Connection recipient in connection.Recipients)
{
if (!(recipient.Item is Item it)) continue;
PowerTransfer pt = it.GetComponent<PowerTransfer>();
if (pt == null) continue;
load = Math.Max(load, pt.PowerLoad);
}
}
}
return Math.Abs(load + reactor.CurrPowerConsumption) < reactorLoadError;
}
}
}
@@ -1,737 +0,0 @@
using System.Collections.Generic;
using System.Xml.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma.Tutorials
{
class MechanicTutorial : ScenarioTutorial
{
// Other tutorial items
private LightComponent tutorial_securityFinalDoorLight;
private Door tutorial_upperFinalDoor;
private Steering tutorial_submarineSteering;
// Room 1
private float shakeTimer = 1f;
private float shakeAmount = 20f;
private Door mechanic_firstDoor;
private LightComponent mechanic_firstDoorLight;
// Room 2
private MotionSensor mechanic_equipmentObjectiveSensor;
private ItemContainer mechanic_equipmentCabinet;
private Door mechanic_secondDoor;
private LightComponent mechanic_secondDoorLight;
// Room 3
private MotionSensor mechanic_weldingObjectiveSensor;
private Pump mechanic_workingPump;
private Door mechanic_thirdDoor;
private LightComponent mechanic_thirdDoorLight;
private Structure mechanic_brokenWall_1;
private Hull mechanic_brokenhull_1;
// Room 4
private MotionSensor mechanic_craftingObjectiveSensor;
private Deconstructor mechanic_deconstructor;
private Fabricator mechanic_fabricator;
private ItemContainer mechanic_craftingCabinet;
private Door mechanic_fourthDoor;
private LightComponent mechanic_fourthDoorLight;
// Room 5
private MotionSensor mechanic_fireSensor;
private DummyFireSource mechanic_fire;
private Door mechanic_fifthDoor;
private LightComponent mechanic_fifthDoorLight;
// Room 6
private MotionSensor mechanic_divingSuitObjectiveSensor;
private ItemContainer mechanic_divingSuitContainer;
private ItemContainer mechanic_oxygenContainer;
private Door tutorial_mechanicFinalDoor;
private LightComponent tutorial_mechanicFinalDoorLight;
// Room 7
private Pump mechanic_brokenPump;
private Structure mechanic_brokenWall_2;
private Hull mechanic_brokenhull_2;
private Door tutorial_submarineDoor;
private LightComponent tutorial_submarineDoorLight;
// Submarine
private MotionSensor tutorial_enteredSubmarineSensor;
private Engine mechanic_submarineEngine;
private Pump mechanic_ballastPump_1;
private Pump mechanic_ballastPump_2;
// Variables
private const float waterVolumeBeforeOpening = 15f;
private LocalizedString radioSpeakerName;
private Character mechanic;
private Sprite mechanic_repairIcon;
private Color mechanic_repairIconColor;
private Sprite mechanic_weldIcon;
public MechanicTutorial() : base("tutorial.mechanictraining".ToIdentifier(),
new Segment(
"Mechanic.OpenDoor".ToIdentifier(),
"Mechanic.OpenDoorObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.OpenDoorText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Mechanic.Equipment".ToIdentifier(),
"Mechanic.EquipmentObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_inventory.webm", TextTag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Mechanic.Welding".ToIdentifier(),
"Mechanic.WeldingObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Mechanic.WeldingText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_equip.webm", TextTag = "Mechanic.WeldingText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Mechanic.Drain".ToIdentifier(),
"Mechanic.DrainObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.DrainText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Mechanic.Deconstruct".ToIdentifier(),
"Mechanic.DeconstructObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Mechanic.DeconstructText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_deconstruct.webm", TextTag = "Mechanic.DeconstructText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Mechanic.Fabricate".ToIdentifier(),
"Mechanic.FabricateObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Mechanic.FabricateText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_fabricate.webm", TextTag = "Mechanic.FabricateText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Mechanic.Extinguisher".ToIdentifier(),
"Mechanic.ExtinguisherObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.ExtinguisherText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Mechanic.DropExtinguisher".ToIdentifier(),
"Mechanic.DropExtinguisherObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.DropExtinguisherText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Mechanic.Diving".ToIdentifier(),
"Mechanic.DivingObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.DivingText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Mechanic.RepairPump".ToIdentifier(),
"Mechanic.RepairPumpObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.RepairPumpText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Mechanic.RepairSubmarine".ToIdentifier(),
"Mechanic.RepairSubmarineObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.RepairSubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"tutorial.laddertitle".ToIdentifier(),
"tutorial.laddertitle".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "tutorial.ladderdescription".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }))
{ }
protected override CharacterInfo GetCharacterInfo()
{
return new CharacterInfo(
CharacterPrefab.HumanSpeciesName,
jobOrJobPrefab: new Job(
JobPrefab.Prefabs["mechanic"], Rand.RandSync.Unsynced, 0,
new Skill("medical".ToIdentifier(), 0),
new Skill("weapons".ToIdentifier(), 0),
new Skill("mechanical".ToIdentifier(), 50),
new Skill("electrical".ToIdentifier(), 20),
new Skill("helm".ToIdentifier(), 0)));
}
protected override void Initialize()
{
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
mechanic = Character.Controlled;
foreach (Item item in mechanic.Inventory.AllItemsMod)
{
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(mechanic);
mechanic.Inventory.RemoveItem(item);
}
var repairOrder = OrderPrefab.Prefabs["repairsystems"];
mechanic_repairIcon = repairOrder.SymbolSprite;
mechanic_repairIconColor = repairOrder.Color;
mechanic_weldIcon = new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(1, 256, 127, 127), new Vector2(0.5f, 0.5f));
// Other tutorial items
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
tutorial_upperFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_upperfinaldoor")).GetComponent<Door>();
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
tutorial_submarineSteering.CanBeSelected = false;
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
{
ic.CanBeSelected = false;
}
SetDoorAccess(null, tutorial_securityFinalDoorLight, false);
SetDoorAccess(tutorial_upperFinalDoor, null, false);
// Room 1
mechanic_firstDoor = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoor")).GetComponent<Door>();
mechanic_firstDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_firstdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, false);
// Room 2
mechanic_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentobjectivesensor")).GetComponent<MotionSensor>();
mechanic_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_equipmentcabinet")).GetComponent<ItemContainer>();
mechanic_secondDoor = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoor")).GetComponent<Door>();
mechanic_secondDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_seconddoorlight")).GetComponent<LightComponent>();
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, false);
// Room 3
mechanic_weldingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_weldingobjectivesensor")).GetComponent<MotionSensor>();
mechanic_workingPump = Item.ItemList.Find(i => i.HasTag("mechanic_workingpump")).GetComponent<Pump>();
mechanic_thirdDoor = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoor")).GetComponent<Door>();
mechanic_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_thirddoorlight")).GetComponent<LightComponent>();
mechanic_brokenWall_1 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_1");
//mechanic_ladderSensor = Item.ItemList.Find(i => i.HasTag("mechanic_laddersensor")).GetComponent<MotionSensor>();
SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, false);
mechanic_brokenWall_1.Indestructible = false;
mechanic_brokenWall_1.SpriteColor = Color.White;
for (int i = 0; i < mechanic_brokenWall_1.SectionCount; i++)
{
mechanic_brokenWall_1.AddDamage(i, 85);
}
mechanic_brokenhull_1 = mechanic_brokenWall_1.Sections[0].gap.FlowTargetHull;
// Room 4
mechanic_craftingObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_craftingobjectivesensor")).GetComponent<MotionSensor>();
mechanic_deconstructor = Item.ItemList.Find(i => i.HasTag("mechanic_deconstructor")).GetComponent<Deconstructor>();
mechanic_fabricator = Item.ItemList.Find(i => i.HasTag("mechanic_fabricator")).GetComponent<Fabricator>();
mechanic_craftingCabinet = Item.ItemList.Find(i => i.HasTag("mechanic_craftingcabinet")).GetComponent<ItemContainer>();
mechanic_fourthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoor")).GetComponent<Door>();
mechanic_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fourthdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, false);
// Room 5
mechanic_fifthDoor = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoor")).GetComponent<Door>();
mechanic_fifthDoorLight = Item.ItemList.Find(i => i.HasTag("mechanic_fifthdoorlight")).GetComponent<LightComponent>();
mechanic_fireSensor = Item.ItemList.Find(i => i.HasTag("mechanic_firesensor")).GetComponent<MotionSensor>();
SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, false);
// Room 6
mechanic_divingSuitObjectiveSensor = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitobjectivesensor")).GetComponent<MotionSensor>();
mechanic_divingSuitContainer = Item.ItemList.Find(i => i.HasTag("mechanic_divingsuitcontainer")).GetComponent<ItemContainer>();
foreach (Item item in mechanic_divingSuitContainer.Inventory.AllItems)
{
foreach (ItemComponent ic in item.Components)
{
ic.CanBePicked = true;
}
}
mechanic_oxygenContainer = Item.ItemList.Find(i => i.HasTag("mechanic_oxygencontainer")).GetComponent<ItemContainer>();
foreach (Item item in mechanic_oxygenContainer.Inventory.AllItems)
{
foreach (ItemComponent ic in item.Components)
{
ic.CanBePicked = true;
}
}
tutorial_mechanicFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoor")).GetComponent<Door>();
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, false);
// Room 7
mechanic_brokenPump = Item.ItemList.Find(i => i.HasTag("mechanic_brokenpump")).GetComponent<Pump>();
mechanic_brokenPump.Item.Indestructible = false;
mechanic_brokenPump.Item.Condition = 0;
mechanic_brokenPump.CanBeSelected = false;
mechanic_brokenPump.Item.GetComponent<Repairable>().CanBeSelected = false;
mechanic_brokenWall_2 = Structure.WallList.Find(i => i.SpecialTag == "mechanic_brokenwall_2");
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
mechanic_brokenWall_2.Indestructible = false;
mechanic_brokenWall_2.SpriteColor = Color.White;
for (int i = 0; i < mechanic_brokenWall_2.SectionCount; i++)
{
mechanic_brokenWall_2.AddDamage(i, 85);
}
mechanic_brokenhull_2 = mechanic_brokenWall_2.Sections[0].gap.FlowTargetHull;
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
// Submarine
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
mechanic_submarineEngine = Item.ItemList.Find(i => i.HasTag("mechanic_submarineengine")).GetComponent<Engine>();
mechanic_submarineEngine.Item.Indestructible = false;
mechanic_submarineEngine.Item.Condition = 0f;
mechanic_ballastPump_1 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_1")).GetComponent<Pump>();
mechanic_ballastPump_1.Item.Indestructible = false;
mechanic_ballastPump_1.Item.Condition = 0f;
mechanic_ballastPump_2 = Item.ItemList.Find(i => i.HasTag("mechanic_ballastpump_2")).GetComponent<Pump>();
mechanic_ballastPump_2.Item.Indestructible = false;
mechanic_ballastPump_2.Item.Condition = 0f;
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Started");
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
}
public override void Update(float deltaTime)
{
if (mechanic_brokenhull_1 != null)
{
mechanic_brokenhull_1.WaterVolume = MathHelper.Clamp(mechanic_brokenhull_1.WaterVolume, 0, mechanic_brokenhull_1.Volume * 0.85f);
}
base.Update(deltaTime);
}
public override IEnumerable<CoroutineStatus> UpdateState()
{
while (GameMain.Instance.LoadingScreenOpen) yield return null;
// Room 1
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
while (shakeTimer > 0.0f) // Wake up, shake
{
shakeTimer -= 0.1f;
GameMain.GameScreen.Cam.Shake = shakeAmount;
yield return new WaitForSeconds(0.1f, false);
}
yield return new WaitForSeconds(2.5f, false);
mechanic_fabricator.RemoveFabricationRecipes(allowedIdentifiers:
new[] { "extinguisher", "wrench", "weldingtool", "weldingfuel", "divingmask", "railgunshell", "nuclearshell", "uex", "harpoongun" }.ToIdentifiers());
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.WakeUp"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2.5f, false);
TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Up), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Left), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Down), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Right), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Open door objective
yield return new WaitForSeconds(0.0f, false);
SetDoorAccess(mechanic_firstDoor, mechanic_firstDoorLight, true);
SetHighlight(mechanic_firstDoor.Item, true);
do { yield return null; } while (!mechanic_firstDoor.IsOpen);
SetHighlight(mechanic_firstDoor.Item, false);
yield return new WaitForSeconds(1.5f, false);
RemoveCompletedObjective(0);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective0");
// Room 2
yield return new WaitForSeconds(0.0f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Equipment"), ChatMessageType.Radio, null);
do { yield return null; } while (!mechanic_equipmentObjectiveSensor.MotionDetected);
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Equipment & inventory objective
SetHighlight(mechanic_equipmentCabinet.Item, true);
bool firstSlotRemoved = false;
bool secondSlotRemoved = false;
bool thirdSlotRemoved = false;
do
{
if (IsSelectedItem(mechanic_equipmentCabinet.Item))
{
if (!firstSlotRemoved)
{
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
if (mechanic_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; }
}
if (!secondSlotRemoved)
{
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
if (mechanic_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; }
}
if (!thirdSlotRemoved)
{
HighlightInventorySlot(mechanic_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
if (mechanic_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; }
}
for (int i = 0; i < mechanic.Inventory.Capacity; i++)
{
if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
}
yield return null;
} while (mechanic.Inventory.FindItemByIdentifier("divingmask".ToIdentifier()) == null || mechanic.Inventory.FindItemByIdentifier("weldingtool".ToIdentifier()) == null || mechanic.Inventory.FindItemByIdentifier("wrench".ToIdentifier()) == null); // Wait until looted
SetHighlight(mechanic_equipmentCabinet.Item, false);
yield return new WaitForSeconds(1.5f, false);
RemoveCompletedObjective(1);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective1");
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Breach"), ChatMessageType.Radio, null);
// Room 3
do { yield return null; } while (!mechanic_weldingObjectiveSensor.MotionDetected);
TriggerTutorialSegment(2, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Welding objective
do
{
if (!mechanic.HasEquippedItem("divingmask".ToIdentifier()))
{
HighlightInventorySlot(mechanic.Inventory, "divingmask".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
if (!mechanic.HasEquippedItem("weldingtool".ToIdentifier()))
{
HighlightInventorySlot(mechanic.Inventory, "weldingtool".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
yield return null;
} while (!mechanic.HasEquippedItem("divingmask".ToIdentifier()) || !mechanic.HasEquippedItem("weldingtool".ToIdentifier())); // Wait until equipped
SetDoorAccess(mechanic_secondDoor, mechanic_secondDoorLight, true);
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_1, mechanic_weldIcon, mechanic_repairIconColor);
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_1)); // Highlight until repaired
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_1);
RemoveCompletedObjective(2);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective2");
yield return new WaitForSeconds(1f, false);
TriggerTutorialSegment(3, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select)); // Pump objective
SetHighlight(mechanic_workingPump.Item, true);
do
{
yield return null;
if (IsSelectedItem(mechanic_workingPump.Item))
{
if (mechanic_workingPump.PowerButton.FlashTimer <= 0)
{
mechanic_workingPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
}
}
} while (mechanic_workingPump.FlowPercentage >= 0 || !mechanic_workingPump.IsActive); // Highlight until draining
SetHighlight(mechanic_workingPump.Item, false);
do { yield return null; } while (mechanic_brokenhull_1 != null && mechanic_brokenhull_1.WaterPercentage > waterVolumeBeforeOpening); // Unlock door once drained
RemoveCompletedObjective(3);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective3");
SetDoorAccess(mechanic_thirdDoor, mechanic_thirdDoorLight, true);
//TriggerTutorialSegment(11, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Up], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Down], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select]); // Ladder objective
//do { yield return null; } while (!mechanic_ladderSensor.MotionDetected);
//RemoveCompletedObjective(segments[11]);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.News"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(1f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Fire"), ChatMessageType.Radio, null);
// Room 4
do { yield return null; } while (!mechanic_thirdDoor.IsOpen);
yield return new WaitForSeconds(1f, false);
mechanic_fire = new DummyFireSource(new Vector2(20f, 2f), Item.ItemList.Find(i => i.HasTag("mechanic_fire")).WorldPosition);
//do { yield return null; } while (!mechanic_craftingObjectiveSensor.MotionDetected);
TriggerTutorialSegment(4); // Deconstruct
SetHighlight(mechanic_craftingCabinet.Item, true);
bool gotOxygenTank = false;
bool gotSodium = false;
do
{
if (mechanic.SelectedConstruction == mechanic_craftingCabinet.Item)
{
for (int i = 0; i < mechanic.Inventory.Capacity; i++)
{
if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
if (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) == null && mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null)
{
for (int i = 0; i < mechanic_craftingCabinet.Capacity; i++)
{
Item item = mechanic_craftingCabinet.Inventory.GetItemAt(i);
if (item != null && item.Prefab.Identifier == "oxygentank")
{
HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
}
}
}
if (mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) == null)
{
for (int i = 0; i < mechanic_craftingCabinet.Inventory.Capacity; i++)
{
Item item = mechanic_craftingCabinet.Inventory.GetItemAt(i);
if (item != null && item.Prefab.Identifier == "sodium")
{
HighlightInventorySlot(mechanic_craftingCabinet.Inventory, i, highlightColor, .5f, .5f, 0f);
}
}
}
}
if (!gotOxygenTank && (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null ||
mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null))
{
gotOxygenTank = true;
}
if (!gotSodium && mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null)
{
gotSodium = true;
}
yield return null;
} while (!gotOxygenTank || !gotSodium); // Wait until looted
yield return new WaitForSeconds(1.0f, false);
SetHighlight(mechanic_craftingCabinet.Item, false);
SetHighlight(mechanic_deconstructor.Item, true);
do
{
if (IsSelectedItem(mechanic_deconstructor.Item))
{
if (mechanic_deconstructor.OutputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null)
{
HighlightInventorySlot(mechanic_deconstructor.OutputContainer.Inventory, "aluminium".ToIdentifier(), highlightColor, .5f, .5f, 0f);
for (int i = 0; i < mechanic.Inventory.Capacity; i++)
{
if (mechanic.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
}
else
{
if (mechanic.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null && mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) == null)
{
HighlightInventorySlot(mechanic.Inventory, "oxygentank".ToIdentifier(), highlightColor, .5f, .5f, 0f);
for (int i = 0; i < mechanic_deconstructor.InputContainer.Inventory.Capacity; i++)
{
HighlightInventorySlot(mechanic_deconstructor.InputContainer.Inventory, i, highlightColor, .5f, .5f, 0f);
}
}
if (mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank".ToIdentifier()) != null && !mechanic_deconstructor.IsActive)
{
if (mechanic_deconstructor.ActivateButton.FlashTimer <= 0)
{
mechanic_deconstructor.ActivateButton.Flash(highlightColor, 1.5f, false);
}
}
}
}
yield return null;
} while (
mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null &&
mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) == null); // Wait until aluminium obtained
SetHighlight(mechanic_deconstructor.Item, false);
RemoveCompletedObjective(4);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective4");
yield return new WaitForSeconds(1f, false);
TriggerTutorialSegment(5); // Fabricate
SetHighlight(mechanic_fabricator.Item, true);
do
{
if (IsSelectedItem(mechanic_fabricator.Item))
{
if (mechanic_fabricator.SelectedItem?.TargetItem.Identifier != "extinguisher")
{
mechanic_fabricator.HighlightRecipe("extinguisher", highlightColor);
}
else
{
if (mechanic_fabricator.OutputContainer.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) != null)
{
HighlightInventorySlot(mechanic_fabricator.OutputContainer.Inventory, "extinguisher".ToIdentifier(), highlightColor, .5f, .5f, 0f);
/*for (int i = 0; i < mechanic.Inventory.Capacity; i++)
{
if (mechanic.Inventory.Items[i] == null) HighlightInventorySlot(mechanic.Inventory, i, highlightColor, .5f, .5f, 0f);
}*/
}
else if (mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null && mechanic_fabricator.InputContainer.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null && !mechanic_fabricator.IsActive)
{
if (mechanic_fabricator.ActivateButton.FlashTimer <= 0)
{
mechanic_fabricator.ActivateButton.Flash(highlightColor, 1.5f, false);
}
}
else if (mechanic.Inventory.FindItemByIdentifier("aluminium".ToIdentifier()) != null || mechanic.Inventory.FindItemByIdentifier("sodium".ToIdentifier()) != null)
{
HighlightInventorySlot(mechanic.Inventory, "aluminium".ToIdentifier(), highlightColor, .5f, .5f, 0f);
HighlightInventorySlot(mechanic.Inventory, "sodium".ToIdentifier(), highlightColor, .5f, .5f, 0f);
if (mechanic_fabricator.InputContainer.Inventory.GetItemAt(0) == null)
{
HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 0, highlightColor, .5f, .5f, 0f);
}
if (mechanic_fabricator.InputContainer.Inventory.GetItemAt(1) == null)
{
HighlightInventorySlot(mechanic_fabricator.InputContainer.Inventory, 1, highlightColor, .5f, .5f, 0f);
}
}
}
}
yield return null;
} while (mechanic.Inventory.FindItemByIdentifier("extinguisher".ToIdentifier()) == null); // Wait until extinguisher is created
RemoveCompletedObjective(5);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective5");
SetHighlight(mechanic_fabricator.Item, false);
SetDoorAccess(mechanic_fourthDoor, mechanic_fourthDoorLight, true);
// Room 5
do { yield return null; } while (!mechanic_fireSensor.MotionDetected);
TriggerTutorialSegment(6, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Using the extinguisher
do { yield return null; } while (!mechanic_fire.Removed); // Wait until extinguished
yield return new WaitForSeconds(3f, false);
RemoveCompletedObjective(6);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective6");
if (mechanic.HasEquippedItem("extinguisher".ToIdentifier())) // do not trigger if dropped already
{
TriggerTutorialSegment(7);
do
{
HighlightInventorySlot(mechanic.Inventory, "extinguisher".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
yield return null;
} while (mechanic.HasEquippedItem("extinguisher".ToIdentifier()));
RemoveCompletedObjective(7);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective7");
}
SetDoorAccess(mechanic_fifthDoor, mechanic_fifthDoorLight, true);
// Room 6
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Diving"), ChatMessageType.Radio, null);
do { yield return null; } while (!mechanic_divingSuitObjectiveSensor.MotionDetected);
TriggerTutorialSegment(8); // Dangers of pressure, equip diving suit objective
SetHighlight(mechanic_divingSuitContainer.Item, true);
do
{
if (IsSelectedItem(mechanic_divingSuitContainer.Item))
{
if (mechanic_divingSuitContainer.Inventory.visualSlots != null)
{
for (int i = 0; i < mechanic_divingSuitContainer.Inventory.Capacity; i++)
{
HighlightInventorySlot(mechanic_divingSuitContainer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
}
}
}
yield return null;
} while (!mechanic.HasEquippedItem("divingsuit".ToIdentifier(), slotType: InvSlotType.OuterClothes));
SetHighlight(mechanic_divingSuitContainer.Item, false);
RemoveCompletedObjective(8);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective8");
SetDoorAccess(tutorial_mechanicFinalDoor, tutorial_mechanicFinalDoorLight, true);
// Room 7
mechanic.AddActiveObjectiveEntity(mechanic_brokenWall_2, mechanic_weldIcon, mechanic_repairIconColor);
do { yield return null; } while (WallHasDamagedSections(mechanic_brokenWall_2));
mechanic.RemoveActiveObjectiveEntity(mechanic_brokenWall_2);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(9, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Use)); // Repairing machinery (pump)
SetHighlight(mechanic_brokenPump.Item, true);
mechanic_brokenPump.CanBeSelected = true;
Repairable repairablePumpComponent = mechanic_brokenPump.Item.GetComponent<Repairable>();
repairablePumpComponent.CanBeSelected = true;
do
{
yield return null;
if (repairablePumpComponent.IsBelowRepairThreshold)
{
if (!mechanic.HasEquippedItem("wrench".ToIdentifier()))
{
HighlightInventorySlot(mechanic.Inventory, "wrench".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
}
else if (IsSelectedItem(mechanic_brokenPump.Item) && repairablePumpComponent.CurrentFixer == null)
{
if (repairablePumpComponent.RepairButton.FlashTimer <= 0)
{
repairablePumpComponent.RepairButton.Flash();
}
}
}
else
{
if (IsSelectedItem(mechanic_brokenPump.Item))
{
if (mechanic_brokenPump.PowerButton.FlashTimer <= 0)
{
mechanic_brokenPump.PowerButton.Flash(uiHighlightColor, 1.5f, true);
}
}
}
} while (repairablePumpComponent.IsBelowRepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive);
RemoveCompletedObjective(9);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective9");
SetHighlight(mechanic_brokenPump.Item, false);
do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening);
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
// Submarine
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Submarine"), ChatMessageType.Radio, null);
TriggerTutorialSegment(10); // Repairing ballast pumps, engine
while (ContentRunning) yield return null;
mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_1.Item, mechanic_repairIcon, mechanic_repairIconColor);
mechanic.AddActiveObjectiveEntity(mechanic_ballastPump_2.Item, mechanic_repairIcon, mechanic_repairIconColor);
mechanic.AddActiveObjectiveEntity(mechanic_submarineEngine.Item, mechanic_repairIcon, mechanic_repairIconColor);
SetHighlight(mechanic_ballastPump_1.Item, true);
SetHighlight(mechanic_ballastPump_2.Item, true);
SetHighlight(mechanic_submarineEngine.Item, true);
Repairable repairablePumpComponent1 = mechanic_ballastPump_1.Item.GetComponent<Repairable>();
Repairable repairablePumpComponent2 = mechanic_ballastPump_2.Item.GetComponent<Repairable>();
Repairable repairableEngineComponent = mechanic_submarineEngine.Item.GetComponent<Repairable>();
// Remove highlights when each individual machine is repaired
do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (repairablePumpComponent1.IsBelowRepairThreshold || repairablePumpComponent2.IsBelowRepairThreshold || repairableEngineComponent.IsBelowRepairThreshold);
CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent);
RemoveCompletedObjective(10);
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Objective10");
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null);
// END TUTORIAL
GameAnalyticsManager.AddDesignEvent("Tutorial:MechanicTutorial:Completed");
CoroutineManager.StartCoroutine(TutorialCompleted());
}
private bool IsSelectedItem(Item item)
{
return mechanic?.SelectedConstruction == item;
}
private bool WallHasDamagedSections(Structure wall)
{
for (int i = 0; i < wall.SectionCount; i++)
{
if (wall.Sections[i].damage > 0) return true;
}
return false;
}
private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{
if (!comp1.IsBelowRepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight)
{
SetHighlight(mechanic_ballastPump_1.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item);
}
if (!comp2.IsBelowRepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight)
{
SetHighlight(mechanic_ballastPump_2.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item);
}
if (!comp3.IsBelowRepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight)
{
SetHighlight(mechanic_submarineEngine.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item);
}
}
}
}
@@ -1,526 +0,0 @@
using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Xml.Linq;
using System.Linq;
using Barotrauma.Items.Components;
using Barotrauma.Networking;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
namespace Barotrauma.Tutorials
{
class OfficerTutorial : ScenarioTutorial
{
// Other tutorial items
private LightComponent tutorial_mechanicFinalDoorLight;
private Steering tutorial_submarineSteering;
// Room 1
private float shakeTimer = 1f;
private float shakeAmount = 20f;
// Room 2
private MotionSensor officer_equipmentObjectiveSensor;
private ItemContainer officer_equipmentCabinet;
private Door officer_firstDoor;
private LightComponent officer_firstDoorLight;
// Room 3
private MotionSensor officer_crawlerSensor;
private Character officer_crawler;
private Vector2 officer_crawlerSpawnPos;
private Door officer_secondDoor;
private LightComponent officer_secondDoorLight;
// Room 4
private MotionSensor officer_somethingBigSensor;
private ItemContainer officer_coilgunLoader;
private ItemContainer officer_ammoShelf_1;
private ItemContainer officer_ammoShelf_2;
private PowerContainer officer_superCapacitor;
private Item officer_coilgunPeriscope;
private Character officer_hammerhead;
private Vector2 officer_hammerheadSpawnPos;
private Door officer_thirdDoor;
private LightComponent officer_thirdDoorLight;
// Room 5
private MotionSensor officer_rangedWeaponSensor;
private ItemContainer officer_rangedWeaponCabinet;
private ItemContainer officer_rangedWeaponHolder;
private Door officer_fourthDoor;
private LightComponent officer_fourthDoorLight;
// Room 6
private MotionSensor officer_mudraptorObjectiveSensor;
private Vector2 officer_mudraptorSpawnPos;
private Character officer_mudraptor;
private Door tutorial_securityFinalDoor;
private LightComponent tutorial_securityFinalDoorLight;
// Submarine
private Door tutorial_submarineDoor;
private LightComponent tutorial_submarineDoorLight;
private MotionSensor tutorial_enteredSubmarineSensor;
private Item officer_subAmmoBox_1;
private Item officer_subAmmoBox_2;
private ItemContainer officer_subAmmoShelf;
private ItemContainer officer_subLoader_1;
private ItemContainer officer_subLoader_2;
private PowerContainer officer_subSuperCapacitor_1;
private PowerContainer officer_subSuperCapacitor_2;
// Variables
private LocalizedString radioSpeakerName;
private Character officer;
private float superCapacitorRechargeRate = 10;
private Sprite officer_gunIcon;
private Color officer_gunIconColor;
public OfficerTutorial() : base("tutorial.securityofficertraining".ToIdentifier(),
new Segment(
"Mechanic.Equipment".ToIdentifier(),
"Mechanic.EquipmentObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Mechanic.EquipmentText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Officer.MeleeWeapon".ToIdentifier(),
"Officer.MeleeWeaponObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Officer.MeleeWeaponText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Officer.Crawler".ToIdentifier(),
"Officer.CrawlerObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Officer.CrawlerText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Officer.SomethingBig".ToIdentifier(),
"Officer.SomethingBigObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Officer.SomethingBigText".ToIdentifier(), Width = 700, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_loaders.webm", TextTag = "Officer.SomethingBigText".ToIdentifier(), Width = 700, Height = 80 }),
new Segment(
"Officer.Hammerhead".ToIdentifier(),
"Officer.HammerheadObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Officer.HammerheadText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Officer.RangedWeapon".ToIdentifier(),
"Officer.RangedWeaponObjective".ToIdentifier(),
TutorialContentType.ManualVideo,
textContent: new Segment.Text { Tag = "Officer.RangedWeaponText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center },
videoContent: new Segment.Video { File = "tutorial_ranged.webm", TextTag = "Officer.RangedWeaponText".ToIdentifier(), Width = 450, Height = 80 }),
new Segment(
"Officer.Mudraptor".ToIdentifier(),
"Officer.MudraptorObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Officer.MudraptorText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }),
new Segment(
"Officer.ArmSubmarine".ToIdentifier(),
"Officer.ArmSubmarineObjective".ToIdentifier(),
TutorialContentType.TextOnly,
textContent: new Segment.Text { Tag = "Officer.ArmSubmarineText".ToIdentifier(), Width = 450, Height = 80, Anchor = Anchor.Center }))
{ }
protected override CharacterInfo GetCharacterInfo()
{
return new CharacterInfo(
CharacterPrefab.HumanSpeciesName,
jobOrJobPrefab: new Job(
JobPrefab.Prefabs["securityofficer"], Rand.RandSync.Unsynced, 0,
new Skill("medical".ToIdentifier(), 20),
new Skill("weapons".ToIdentifier(), 70),
new Skill("mechanical".ToIdentifier(), 20),
new Skill("electrical".ToIdentifier(), 20),
new Skill("helm".ToIdentifier(), 20)));
}
protected override void Initialize()
{
radioSpeakerName = TextManager.Get("Tutorial.Radio.Speaker");
officer = Character.Controlled;
foreach (Item item in officer.Inventory.AllItemsMod)
{
if (item.HasTag("clothing") || item.HasTag("identitycard") || item.HasTag("mobileradio")) { continue; }
item.Unequip(officer);
officer.Inventory.RemoveItem(item);
}
var gunOrder = OrderPrefab.Prefabs["operateweapons"];
officer_gunIcon = gunOrder.SymbolSprite;
officer_gunIconColor = gunOrder.Color;
var bandage = FindOrGiveItem(officer, "antibleeding1".ToIdentifier());
bandage.Unequip(officer);
officer.Inventory.RemoveItem(bandage);
FindOrGiveItem(officer, "antibleeding1".ToIdentifier());
// Other tutorial items
tutorial_mechanicFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_mechanicfinaldoorlight")).GetComponent<LightComponent>();
tutorial_submarineSteering = Item.ItemList.Find(i => i.HasTag("command")).GetComponent<Steering>();
tutorial_submarineSteering.CanBeSelected = false;
foreach (ItemComponent ic in tutorial_submarineSteering.Item.Components)
{
ic.CanBeSelected = false;
}
SetDoorAccess(null, tutorial_mechanicFinalDoorLight, false);
// Room 2
officer_equipmentObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_equipmentobjectivesensor")).GetComponent<MotionSensor>();
officer_equipmentCabinet = Item.ItemList.Find(i => i.HasTag("officer_equipmentcabinet")).GetComponent<ItemContainer>();
officer_firstDoor = Item.ItemList.Find(i => i.HasTag("officer_firstdoor")).GetComponent<Door>();
officer_firstDoorLight = Item.ItemList.Find(i => i.HasTag("officer_firstdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, false);
// Room 3
officer_crawlerSensor = Item.ItemList.Find(i => i.HasTag("officer_crawlerobjectivesensor")).GetComponent<MotionSensor>();
officer_crawlerSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_crawlerspawn")).WorldPosition;
officer_secondDoor = Item.ItemList.Find(i => i.HasTag("officer_seconddoor")).GetComponent<Door>();
officer_secondDoorLight = Item.ItemList.Find(i => i.HasTag("officer_seconddoorlight")).GetComponent<LightComponent>();
SetDoorAccess(officer_secondDoor, officer_secondDoorLight, false);
// Room 4
officer_somethingBigSensor = Item.ItemList.Find(i => i.HasTag("officer_somethingbigobjectivesensor")).GetComponent<MotionSensor>();
officer_coilgunLoader = Item.ItemList.Find(i => i.HasTag("officer_coilgunloader")).GetComponent<ItemContainer>();
officer_superCapacitor = Item.ItemList.Find(i => i.HasTag("officer_supercapacitor")).GetComponent<PowerContainer>();
officer_coilgunPeriscope = Item.ItemList.Find(i => i.HasTag("officer_coilgunperiscope"));
officer_hammerheadSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_hammerheadspawn")).WorldPosition;
officer_thirdDoor = Item.ItemList.Find(i => i.HasTag("officer_thirddoor")).GetComponent<Door>();
officer_thirdDoorLight = Item.ItemList.Find(i => i.HasTag("officer_thirddoorlight")).GetComponent<LightComponent>();
officer_ammoShelf_1 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_1")).GetComponent<ItemContainer>();
officer_ammoShelf_2 = Item.ItemList.Find(i => i.HasTag("officer_ammoshelf_2")).GetComponent<ItemContainer>();
SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, false);
// Room 5
officer_rangedWeaponSensor = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponobjectivesensor")).GetComponent<MotionSensor>();
officer_rangedWeaponCabinet = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponcabinet")).GetComponent<ItemContainer>();
officer_rangedWeaponHolder = Item.ItemList.Find(i => i.HasTag("officer_rangedweaponholder")).GetComponent<ItemContainer>();
officer_fourthDoor = Item.ItemList.Find(i => i.HasTag("officer_fourthdoor")).GetComponent<Door>();
officer_fourthDoorLight = Item.ItemList.Find(i => i.HasTag("officer_fourthdoorlight")).GetComponent<LightComponent>();
SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, false);
// Room 6
officer_mudraptorObjectiveSensor = Item.ItemList.Find(i => i.HasTag("officer_mudraptorobjectivesensor")).GetComponent<MotionSensor>();
officer_mudraptorSpawnPos = Item.ItemList.Find(i => i.HasTag("officer_mudraptorspawn")).WorldPosition;
tutorial_securityFinalDoor = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoor")).GetComponent<Door>();
tutorial_securityFinalDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_securityfinaldoorlight")).GetComponent<LightComponent>();
SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, false);
// Submarine
tutorial_submarineDoor = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoor")).GetComponent<Door>();
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
tutorial_enteredSubmarineSensor = Item.ItemList.Find(i => i.HasTag("tutorial_enteredsubmarinesensor")).GetComponent<MotionSensor>();
officer_subAmmoBox_1 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_1"));
officer_subAmmoBox_2 = Item.ItemList.Find(i => i.HasTag("officer_subammobox_2"));
officer_subLoader_1 = Item.ItemList.Find(i => i.HasTag("officer_subloader_1")).GetComponent<ItemContainer>();
officer_subLoader_2 = Item.ItemList.Find(i => i.HasTag("officer_subloader_2")).GetComponent<ItemContainer>();
officer_subSuperCapacitor_1 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_1")).GetComponent<PowerContainer>();
officer_subSuperCapacitor_2 = Item.ItemList.Find(i => i.HasTag("officer_subsupercapacitor_2")).GetComponent<PowerContainer>();
officer_subAmmoShelf = Item.ItemList.Find(i => i.HasTag("officer_subammoshelf")).GetComponent<ItemContainer>();
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Started");
GameAnalyticsManager.AddDesignEvent("Tutorial:Started");
}
public override IEnumerable<CoroutineStatus> UpdateState()
{
while (GameMain.Instance.LoadingScreenOpen) yield return null;
yield return new WaitForSeconds(0.01f);
// Room 1
SoundPlayer.PlayDamageSound("StructureBlunt", 10, Character.Controlled.WorldPosition);
while (shakeTimer > 0.0f) // Wake up, shake
{
shakeTimer -= 0.1f;
GameMain.GameScreen.Cam.Shake = shakeAmount;
yield return new WaitForSeconds(0.1f, false);
}
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.WakeUp"), ChatMessageType.Radio, null);
// Room 2
do { yield return null; } while (!officer_equipmentObjectiveSensor.MotionDetected);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Equipment"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(3f, false);
//TriggerTutorialSegment(0, GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Select], GameSettings.CurrentConfig.KeyMap.Bindings[InputType.Deselect]); // Retrieve equipment
SetHighlight(officer_equipmentCabinet.Item, true);
bool firstSlotRemoved = false;
bool secondSlotRemoved = false;
bool thirdSlotRemoved = false;
do
{
if (IsSelectedItem(officer_equipmentCabinet.Item))
{
if (!firstSlotRemoved)
{
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 0, highlightColor, .5f, .5f, 0f);
if (officer_equipmentCabinet.Inventory.GetItemAt(0) == null) { firstSlotRemoved = true; }
}
if (!secondSlotRemoved)
{
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 1, highlightColor, .5f, .5f, 0f);
if (officer_equipmentCabinet.Inventory.GetItemAt(1) == null) { secondSlotRemoved = true; }
}
if (!thirdSlotRemoved)
{
HighlightInventorySlot(officer_equipmentCabinet.Inventory, 2, highlightColor, .5f, .5f, 0f);
if (officer_equipmentCabinet.Inventory.GetItemAt(2) == null) { thirdSlotRemoved = true; }
}
for (int i = 0; i < officer.Inventory.visualSlots.Length; i++)
{
if (officer.Inventory.GetItemAt(i) == null) { HighlightInventorySlot(officer.Inventory, i, highlightColor, .5f, .5f, 0f); }
}
}
yield return null;
} while (!officer_equipmentCabinet.Inventory.IsEmpty()); // Wait until looted
//RemoveCompletedObjective(segments[0]);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective0");
SetHighlight(officer_equipmentCabinet.Item, false);
do { yield return null; } while (IsSelectedItem(officer_equipmentCabinet.Item));
TriggerTutorialSegment(1, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Equip melee weapon & armor
do
{
if (!officer.HasEquippedItem("stunbaton".ToIdentifier()))
{
HighlightInventorySlot(officer.Inventory, "stunbaton".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
if (!officer.HasEquippedItem("bodyarmor".ToIdentifier()))
{
HighlightInventorySlot(officer.Inventory, "bodyarmor".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
if (!officer.HasEquippedItem("ballistichelmet1".ToIdentifier()))
{
HighlightInventorySlot(officer.Inventory, "ballistichelmet1".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
yield return new WaitForSeconds(1f, false);
} while (!officer.HasEquippedItem("stunbaton".ToIdentifier()) || !officer.HasEquippedItem("bodyarmor".ToIdentifier()) || !officer.HasEquippedItem("ballistichelmet1".ToIdentifier()));
RemoveCompletedObjective(1);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective1");
SetDoorAccess(officer_firstDoor, officer_firstDoorLight, true);
// Room 3
do { yield return null; } while (!officer_crawlerSensor.MotionDetected);
TriggerTutorialSegment(2);
officer_crawler = SpawnMonster("crawler", officer_crawlerSpawnPos);
do { yield return null; } while (!officer_crawler.IsDead);
RemoveCompletedObjective(2);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective2");
Heal(officer);
yield return new WaitForSeconds(1f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.CrawlerDead"), ChatMessageType.Radio, null);
SetDoorAccess(officer_secondDoor, officer_secondDoorLight, true);
// Room 4
do { yield return null; } while (!officer_somethingBigSensor.MotionDetected);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.SomethingBig"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(3); // Arm coilgun
do
{
SetHighlight(officer_coilgunLoader.Item, officer_coilgunLoader.Inventory.GetItemAt(0) == null || officer_coilgunLoader.Inventory.GetItemAt(0).Condition == 0);
HighlightInventorySlot(officer_coilgunLoader.Inventory, 0, highlightColor, .5f, .5f, 0f);
SetHighlight(officer_superCapacitor.Item, officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate);
SetHighlight(officer_ammoShelf_1.Item, officer_coilgunLoader.Item.ExternalHighlight );
SetHighlight(officer_ammoShelf_2.Item, officer_coilgunLoader.Item.ExternalHighlight );
if (IsSelectedItem(officer_coilgunLoader.Item))
{
HighlightInventorySlot(officer.Inventory, "coilgunammobox".ToIdentifier(), highlightColor, .5f, .5f, 0f);
}
yield return null;
} while (officer_coilgunLoader.Inventory.GetItemAt(0) == null || officer_superCapacitor.RechargeSpeed < superCapacitorRechargeRate || officer_coilgunLoader.Inventory.GetItemAt(0).Condition == 0);
SetHighlight(officer_coilgunLoader.Item, false);
SetHighlight(officer_superCapacitor.Item, false);
SetHighlight(officer_ammoShelf_1.Item, false);
SetHighlight(officer_ammoShelf_2.Item, false);
RemoveCompletedObjective(3);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective3");
yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Select), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Deselect)); // Kill hammerhead
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
officer_hammerhead.Params.AI.AvoidAbyss = false;
officer_hammerhead.Params.AI.StayInAbyss = false;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
SetHighlight(officer_coilgunPeriscope, true);
float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos);
do
{
float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition);
if (distance > originalDistance * 1.5f || officer_hammerhead.WorldPosition.Y > officer_coilgunPeriscope.WorldPosition.Y)
{
// Don't let the Hammerhead go too far.
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000));
}
if (distance > originalDistance)
{
// Ensure that the Hammerhead targets the player
officer.AiTarget.SoundRange = float.MaxValue;
officer.AiTarget.SightRange = float.MaxValue;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
if ((officer_hammerhead.AIController as EnemyAIController)?.SelectedTargetingParams != null)
{
((EnemyAIController)officer_hammerhead.AIController).SelectedTargetingParams.ReactDistance = 5000.0f;
}
/*var ai = officer_hammerhead.AIController as EnemyAIController;
ai.sight = 2.0f;*/
}
yield return null;
}
while(!officer_hammerhead.IsDead);
Heal(officer);
SetHighlight(officer_coilgunPeriscope, false);
RemoveCompletedObjective(4);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective4");
yield return new WaitForSeconds(1f, false);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.HammerheadDead"), ChatMessageType.Radio, null);
SetDoorAccess(officer_thirdDoor, officer_thirdDoorLight, true);
// Room 5
//do { yield return null; } while (!officer_rangedWeaponSensor.MotionDetected);
do { yield return null; } while (!officer_thirdDoor.IsOpen);
yield return new WaitForSeconds(3f, false);
TriggerTutorialSegment(5, GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Shoot)); // Ranged weapons
SetHighlight(officer_rangedWeaponHolder.Item, true);
do { yield return null; } while (!officer_rangedWeaponHolder.Inventory.IsEmpty()); // Wait until looted
SetHighlight(officer_rangedWeaponHolder.Item, false);
do
{
HighlightInventorySlot(officer.Inventory, "shotgun".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
yield return null;
} while (!officer.HasEquippedItem("shotgun".ToIdentifier())); // Wait until equipped
ItemContainer shotGunChamber = officer.Inventory.FindItemByIdentifier("shotgun".ToIdentifier()).GetComponent<ItemContainer>();
SetHighlight(officer_rangedWeaponCabinet.Item, true);
do
{
if (IsSelectedItem(officer_rangedWeaponCabinet.Item))
{
if (officer_rangedWeaponCabinet.Inventory.visualSlots != null)
{
for (int i = 0; i < officer_rangedWeaponCabinet.Inventory.Capacity; i++)
{
if (officer_rangedWeaponCabinet.Inventory.GetItemAt(i)?.Prefab.Identifier == "shotgunshell")
{
HighlightInventorySlot(officer_rangedWeaponCabinet.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
}
}
}
}
for (int i = 0; i < officer.Inventory.Capacity; i++)
{
if (officer.Inventory.GetItemAt(i)?.Prefab.Identifier == "shotgunshell")
{
HighlightInventorySlot(officer.Inventory, i, highlightColor, 0.5f, 0.5f, 0f);
}
}
if (officer.Inventory.FindItemByIdentifier("shotgunshell".ToIdentifier()) != null || (IsSelectedItem(officer_rangedWeaponCabinet.Item) && officer_rangedWeaponCabinet.Inventory.FindItemByIdentifier("shotgunshell".ToIdentifier()) != null))
{
HighlightInventorySlot(officer.Inventory, "shotgun".ToIdentifier(), highlightColor, 0.5f, 0.5f, 0f);
}
yield return null;
} while (!shotGunChamber.Inventory.IsFull(takeStacksIntoAccount: true)); // Wait until all six harpoons loaded
RemoveCompletedObjective(5);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective5");
SetHighlight(officer_rangedWeaponCabinet.Item, false);
SetDoorAccess(officer_fourthDoor, officer_fourthDoorLight, true);
// Room 6
do { yield return null; } while (!officer_mudraptorObjectiveSensor.MotionDetected);
TriggerTutorialSegment(6);
officer_mudraptor = SpawnMonster("mudraptor", officer_mudraptorSpawnPos);
do { yield return null; } while (!officer_mudraptor.IsDead);
Heal(officer);
RemoveCompletedObjective(6);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective6");
SetDoorAccess(tutorial_securityFinalDoor, tutorial_securityFinalDoorLight, true);
// Submarine
do { yield return null; } while (!tutorial_enteredSubmarineSensor.MotionDetected);
TriggerTutorialSegment(7);
while (ContentRunning) yield return null;
officer.AddActiveObjectiveEntity(officer_subAmmoBox_1, officer_gunIcon, officer_gunIconColor);
officer.AddActiveObjectiveEntity(officer_subAmmoBox_2, officer_gunIcon, officer_gunIconColor);
officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_1.Item, officer_gunIcon, officer_gunIconColor);
officer.AddActiveObjectiveEntity(officer_subSuperCapacitor_2.Item, officer_gunIcon, officer_gunIconColor);
SetHighlight(officer_subSuperCapacitor_1.Item, true);
SetHighlight(officer_subSuperCapacitor_2.Item, true);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Submarine"), ChatMessageType.Radio, null);
do
{
SetHighlight(officer_subLoader_1.Item, officer_subLoader_1.Inventory.GetItemAt(0) == null || officer_subLoader_1.Inventory.GetItemAt(0).Condition == 0);
SetHighlight(officer_subLoader_2.Item, officer_subLoader_2.Inventory.GetItemAt(0) == null || officer_subLoader_2.Inventory.GetItemAt(0).Condition == 0);
HighlightInventorySlot(officer_subLoader_1.Inventory, 0, highlightColor, .5f, .5f, 0f);
HighlightInventorySlot(officer_subLoader_2.Inventory, 0, highlightColor, .5f, .5f, 0f);
if (officer_subSuperCapacitor_1.Item.ExternalHighlight && officer_subSuperCapacitor_1.RechargeSpeed >= superCapacitorRechargeRate)
{
SetHighlight(officer_subSuperCapacitor_1.Item, false);
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item);
}
if (officer_subSuperCapacitor_2.Item.ExternalHighlight && officer_subSuperCapacitor_2.RechargeSpeed >= superCapacitorRechargeRate)
{
SetHighlight(officer_subSuperCapacitor_2.Item, false);
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item);
}
SetHighlight(officer_subAmmoBox_1, officer_subAmmoBox_1.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_1.ParentInventory != officer_subLoader_2.Inventory);
SetHighlight(officer_subAmmoBox_2, officer_subAmmoBox_2.ParentInventory != officer_subLoader_1.Inventory && officer_subAmmoBox_2.ParentInventory != officer_subLoader_2.Inventory);
SetHighlight(officer_subAmmoShelf.Item, officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight);
if (officer_subAmmoBox_1.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_1.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1);
if (officer_subAmmoBox_2.ParentInventory == officer_subLoader_1.Inventory || officer_subAmmoBox_2.ParentInventory == officer_subLoader_2.Inventory) officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2);
yield return null;
} while (officer_subLoader_1.Item.ExternalHighlight || officer_subLoader_2.Item.ExternalHighlight || officer_subSuperCapacitor_1.Item.ExternalHighlight || officer_subSuperCapacitor_2.Item.ExternalHighlight);
SetHighlight(officer_subLoader_1.Item, false);
SetHighlight(officer_subLoader_2.Item, false);
SetHighlight(officer_subSuperCapacitor_1.Item, false);
SetHighlight(officer_subSuperCapacitor_2.Item, false);
SetHighlight(officer_subAmmoBox_1, false);
SetHighlight(officer_subAmmoBox_2, false);
SetHighlight(officer_subAmmoShelf.Item, false);
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_1.Item);
officer.RemoveActiveObjectiveEntity(officer_subSuperCapacitor_2.Item);
officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_1);
officer.RemoveActiveObjectiveEntity(officer_subAmmoBox_2);
RemoveCompletedObjective(7);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Objective7");
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Officer.Radio.Complete"), ChatMessageType.Radio, null);
yield return new WaitForSeconds(4f, false);
GameAnalyticsManager.AddDesignEvent("Tutorial:OfficerTutorial:Completed");
CoroutineManager.StartCoroutine(TutorialCompleted());
}
private bool IsSelectedItem(Item item)
{
return officer?.SelectedConstruction == item;
}
private Character SpawnMonster(string speciesName, Vector2 pos)
{
var character = Character.Create(speciesName, pos, ToolBox.RandomSeed(8));
var ai = character.AIController as EnemyAIController;
ai.TargetOutposts = true;
character.CharacterHealth.SetVitality(character.Health / 2);
character.AnimController.Limbs.Where(l => l.attack != null).Select(l => l.attack).ForEach(a => a.AfterAttack = AIBehaviorAfterAttack.FallBack);
return character;
}
}
}
@@ -1,307 +0,0 @@
using Barotrauma.Items.Components;
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma.Tutorials
{
abstract class ScenarioTutorial : Tutorial
{
private CoroutineHandle tutorialCoroutine;
private Character character;
private const string submarinePath = "Content/Tutorials/Dugong_Tutorial.sub";
private const string startOutpostPath = "Content/Tutorials/TutorialOutpost.sub";
//private const string endOutpostPath = "";
private const string levelSeed = "nLoZLLtza";
private const string levelParams = "ColdCavernsTutorial";
//private const string spawnSub = "startoutpost";
private const SpawnType spawnPointType = SpawnType.Human;
private SubmarineInfo startOutpost = null;
private SubmarineInfo endOutpost = null;
private bool currentTutorialCompleted = false;
private float fadeOutTime = 3f;
protected float waitBeforeFade = 4f;
// Colors
protected Color highlightColor = Color.OrangeRed;
protected Color uiHighlightColor = new Color(150, 50, 0);
protected Color buttonHighlightColor = new Color(255, 100, 0);
protected Color inaccessibleColor = GUIStyle.Red;
protected Color accessibleColor = GUIStyle.Green;
protected ScenarioTutorial(Identifier identifier, params Segment[] segments) : base(identifier, segments) { }
protected abstract void Initialize();
protected override IEnumerable<CoroutineStatus> Loading()
{
SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier == levelParams);
yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefabs: null);
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams != null)
{
Biome biome =
Biome.Prefabs.FirstOrDefault(b => generationParams.AllowedBiomeIdentifiers.Contains(b.Identifier)) ??
Biome.Prefabs.First();
if (!string.IsNullOrEmpty(startOutpostPath))
{
startOutpost = new SubmarineInfo(startOutpostPath);
}
/*if (!string.IsNullOrEmpty(endOutpostPath))
{
endOutpost = new SubmarineInfo(endOutpostPath);
}*/
LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome);
GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost, endOutpost: endOutpost);
}
else
{
GameMain.GameSession.StartRound(levelSeed);
}
GameMain.GameSession.EventManager.ActiveEvents.Clear();
GameMain.GameSession.EventManager.Enabled = false;
GameMain.GameScreen.Select();
Submarine.MainSub.GodMode = true;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != null && wall.Submarine.Info.IsOutpost)
{
wall.Indestructible = true;
}
}
CharacterInfo charInfo = GetCharacterInfo();
WayPoint wayPoint = GetSpawnPoint(charInfo);
if (wayPoint == null)
{
DebugConsole.ThrowError("A waypoint with the spawntype \"" + spawnPointType + "\" is required for the tutorial event");
yield return CoroutineStatus.Failure;
yield break;
}
character = Character.Create(charInfo, wayPoint.WorldPosition, "", isRemotePlayer: false, hasAi: false);
character.TeamID = CharacterTeamType.Team1;
Character.Controlled = character;
character.GiveJobItems(null);
var idCard = character.Inventory.FindItemByTag("identitycard".ToIdentifier());
if (idCard == null)
{
DebugConsole.ThrowError("Item prefab \"ID Card\" not found!");
yield return CoroutineStatus.Failure;
yield break;
}
idCard.AddTag("com");
idCard.AddTag("eng");
foreach (Item item in Item.ItemList)
{
Door door = item.GetComponent<Door>();
if (door != null)
{
door.CanBeWelded = false;
}
}
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
Initialize();
yield return CoroutineStatus.Success;
}
protected abstract CharacterInfo GetCharacterInfo();
public override void AddToGUIUpdateList()
{
if (!currentTutorialCompleted)
{
base.AddToGUIUpdateList();
}
}
private WayPoint GetSpawnPoint(CharacterInfo charInfo)
{
/*Submarine spawnSub = null;
if (this.spawnSub != string.Empty)
{
switch (this.spawnSub)
{
case "startoutpost":
spawnSub = Level.Loaded.StartOutpost;
break;
case "endoutpost":
spawnSub = Level.Loaded.EndOutpost;
break;
default:
spawnSub = Submarine.MainSub;
break;
}
}*/
Submarine spawnSub = Level.Loaded.StartOutpost;
return WayPoint.GetRandom(spawnPointType, charInfo.Job?.Prefab, spawnSub);
}
protected bool HasOrder(Character character, string identifier, string option = null)
{
var currentOrderInfo = character.GetCurrentOrderWithTopPriority();
if (currentOrderInfo?.Identifier == identifier)
{
if (option == null)
{
return true;
}
else
{
return currentOrderInfo?.Option == option;
}
}
return false;
}
protected void SetHighlight(Item item, bool state)
{
if (item.ExternalHighlight == state) return;
item.SpriteColor = (state) ? highlightColor : Color.White;
item.ExternalHighlight = state;
}
protected void SetHighlight(Structure structure, bool state)
{
structure.SpriteColor = (state) ? highlightColor : Color.White;
structure.ExternalHighlight = state;
}
protected void SetHighlight(Character character, bool state)
{
character.ExternalHighlight = state;
}
protected void SetDoorAccess(Door door, LightComponent light, bool state)
{
if (state && door != null) door.requiredItems.Clear();
if (light != null) light.LightColor = (state) ? accessibleColor : inaccessibleColor;
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (character != null)
{
if (character.Oxygen < 1)
{
character.Oxygen = 1;
}
if (character.IsDead)
{
CoroutineManager.StartCoroutine(Dead());
}
else if (Character.Controlled == null)
{
if (tutorialCoroutine != null)
{
CoroutineManager.StopCoroutines(tutorialCoroutine);
}
GUI.PreventPauseMenuToggle = false;
ContentRunning = false;
infoBox = null;
}
else if (Character.Controlled.IsDead)
{
CoroutineManager.StartCoroutine(Dead());
}
}
}
public override void Stop()
{
if (tutorialCoroutine != null)
{
CoroutineManager.StopCoroutines(tutorialCoroutine);
}
base.Stop();
}
private IEnumerable<CoroutineStatus> Dead()
{
GUI.PreventPauseMenuToggle = true;
Character.Controlled = character = null;
Stop();
GameAnalyticsManager.AddDesignEvent("Tutorial:Died");
yield return new WaitForSeconds(3.0f);
var messageBox = new GUIMessageBox(TextManager.Get("Tutorial.TryAgainHeader"), TextManager.Get("Tutorial.TryAgain"), new LocalizedString[] { TextManager.Get("Yes"), TextManager.Get("No") });
messageBox.Buttons[0].OnClicked += Restart;
messageBox.Buttons[0].OnClicked += messageBox.Close;
messageBox.Buttons[1].OnClicked = GameMain.MainMenuScreen.ReturnToMainMenu;
messageBox.Buttons[1].OnClicked += messageBox.Close;
yield return CoroutineStatus.Success;
}
protected IEnumerable<CoroutineStatus> TutorialCompleted()
{
GUI.PreventPauseMenuToggle = true;
Character.Controlled.ClearInputs();
Character.Controlled = null;
GameAnalyticsManager.AddDesignEvent("Tutorial:Completed");
yield return new WaitForSeconds(waitBeforeFade);
var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, panDuration: fadeOutTime);
currentTutorialCompleted = Completed = true;
while (endCinematic.Running) yield return null;
Stop();
GameMain.MainMenuScreen.ReturnToMainMenu(null, null);
}
protected void Heal(Character character)
{
character.SetAllDamage(0.0f, 0.0f, 0.0f);
character.Oxygen = 100.0f;
character.Bloodloss = 0.0f;
character.SetStun(0.0f, true);
}
protected Item FindOrGiveItem(Character character, Identifier identifier)
{
var item = character.Inventory.FindItemByIdentifier(identifier);
if (item != null && !item.Removed) { return item; }
ItemPrefab itemPrefab = MapEntityPrefab.Find(name: null, identifier: identifier) as ItemPrefab;
item = new Item(itemPrefab, Vector2.Zero, submarine: null);
character.Inventory.TryPutItem(item, character, item.AllowedSlots);
return item;
}
}
}
File diff suppressed because it is too large Load Diff
@@ -6,10 +6,7 @@ namespace Barotrauma
{ {
public Tutorial Tutorial; public Tutorial Tutorial;
public TutorialMode(GameModePreset preset) public TutorialMode(GameModePreset preset) : base(preset) { }
: base(preset)
{
}
public override void Start() public override void Start()
{ {
@@ -31,7 +28,7 @@ namespace Barotrauma
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
base.Update(deltaTime); base.Update(deltaTime);
Tutorial.Update(deltaTime); Tutorial.Update();
} }
} }
} }
@@ -14,6 +14,7 @@ namespace Barotrauma
public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null; public static bool IsTabMenuOpen => GameMain.GameSession?.tabMenu != null;
public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu; public static TabMenu TabMenuInstance => GameMain.GameSession?.tabMenu;
private float prevHudScale;
private TabMenu tabMenu; private TabMenu tabMenu;
@@ -119,6 +120,7 @@ namespace Barotrauma
return true; return true;
} }
}; };
prevHudScale = GameSettings.CurrentConfig.Graphics.HUDScale;
} }
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
@@ -178,6 +180,12 @@ namespace Barotrauma
} }
} }
public void HUDScaleChanged()
{
CreateTopLeftButtons();
GameMode?.HUDScaleChanged();
}
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
@@ -112,19 +112,19 @@ namespace Barotrauma
CheckReminders(); CheckReminders();
} }
public static void OnSetSelectedConstruction(Character character, Item oldConstruction, Item newConstruction) public static void OnSetSelectedItem(Character character, Item oldItem, Item newItem)
{ {
if (oldConstruction == newConstruction) { return; } if (oldItem == newItem) { return; }
if (Character.Controlled != null && Character.Controlled == character && oldConstruction != null && oldConstruction.GetComponent<Ladder>() == null) if (Character.Controlled != null && Character.Controlled == character && oldItem != null && !oldItem.IsLadder)
{ {
TimeStoppedInteracting = Timing.TotalTime; TimeStoppedInteracting = Timing.TotalTime;
} }
if (newConstruction == null) { return; } if (newItem == null) { return; }
if (newConstruction.GetComponent<Ladder>() != null) { return; } if (newItem.IsLadder) { return; }
if (newConstruction.GetComponent<ConnectionPanel>() is ConnectionPanel cp && cp.User == character) { return; } if (newItem.GetComponent<ConnectionPanel>() is ConnectionPanel cp && cp.User == character) { return; }
OnStartedInteracting(character, newConstruction); OnStartedInteracting(character, newItem);
} }
private static void OnStartedInteracting(Character character, Item item) private static void OnStartedInteracting(Character character, Item item)
@@ -177,10 +177,10 @@ namespace Barotrauma
private static void CheckIsInteracting() private static void CheckIsInteracting()
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (Character.Controlled?.SelectedConstruction == null) { return; } if (Character.Controlled?.SelectedItem == null) { return; }
if (Character.Controlled.SelectedConstruction.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn && if (Character.Controlled.SelectedItem.GetComponent<Reactor>() is Reactor reactor && reactor.PowerOn &&
Character.Controlled.SelectedConstruction.OwnInventory?.AllItems is IEnumerable<Item> containedItems && Character.Controlled.SelectedItem.OwnInventory?.AllItems is IEnumerable<Item> containedItems &&
containedItems.Count(i => i.HasTag("reactorfuel")) > 1) containedItems.Count(i => i.HasTag("reactorfuel")) > 1)
{ {
if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; } if (DisplayHint("onisinteracting.reactorwithextrarods".ToIdentifier())) { return; }
@@ -272,7 +272,7 @@ namespace Barotrauma
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (sonar == null || sonar.Removed) { return; } if (sonar == null || sonar.Removed) { return; }
if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; } if (spottedCharacter == null || spottedCharacter.Removed || spottedCharacter.IsDead) { return; }
if (Character.Controlled.SelectedConstruction != sonar) { return; } if (Character.Controlled.SelectedItem != sonar) { return; }
if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; } if (HumanAIController.IsFriendly(Character.Controlled, spottedCharacter)) { return; }
DisplayHint("onsonarspottedenemy".ToIdentifier()); DisplayHint("onsonarspottedenemy".ToIdentifier());
} }
@@ -305,7 +305,7 @@ namespace Barotrauma
{ {
if (!CanDisplayHints()) { return; } if (!CanDisplayHints()) { return; }
if (character != Character.Controlled) { return; } if (character != Character.Controlled) { return; }
if (character.SelectedConstruction != null || character.FocusedItem != null) { return; } if (character.HasSelectedAnyItem || character.FocusedItem != null) { return; }
if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; } if (item == null || !item.IsShootable || !item.RequireAimToUse) { return; }
if (TimeStoppedInteracting + 1 > Timing.TotalTime) { return; } if (TimeStoppedInteracting + 1 > Timing.TotalTime) { return; }
if (GUI.MouseOn != null) { return; } if (GUI.MouseOn != null) { return; }
@@ -317,7 +317,7 @@ namespace Barotrauma
variables: new[] { ("[key]".ToIdentifier(), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim)) }, variables: new[] { ("[key]".ToIdentifier(), GameSettings.CurrentConfig.KeyMap.KeyBindText(InputType.Aim)) },
onUpdate: () => onUpdate: () =>
{ {
if (character.SelectedConstruction == null && GUI.MouseOn == null && PlayerInput.KeyDown(InputType.Aim)) if (character.SelectedItem == null && GUI.MouseOn == null && PlayerInput.KeyDown(InputType.Aim))
{ {
ActiveHintMessageBox.Close(); ActiveHintMessageBox.Close();
} }
@@ -354,14 +354,14 @@ namespace Barotrauma
private static IWriteMessage StartSending() private static IWriteMessage StartSending()
{ {
IWriteMessage writeMessage = new WriteOnlyMessage(); IWriteMessage writeMessage = new WriteOnlyMessage();
writeMessage.Write((byte)ClientPacketHeader.MEDICAL); writeMessage.WriteByte((byte)ClientPacketHeader.MEDICAL);
return writeMessage; return writeMessage;
} }
private static void ClientSend(INetSerializableStruct? netStruct, NetworkHeader header, DeliveryMethod deliveryMethod) private static void ClientSend(INetSerializableStruct? netStruct, NetworkHeader header, DeliveryMethod deliveryMethod)
{ {
IWriteMessage msg = StartSending(); IWriteMessage msg = StartSending();
msg.Write((byte)header); msg.WriteByte((byte)header);
netStruct?.Write(msg); netStruct?.Write(msg);
GameMain.Client.ClientPeer?.Send(msg, deliveryMethod); GameMain.Client.ClientPeer?.Send(msg, deliveryMethod);
} }
@@ -83,7 +83,7 @@ namespace Barotrauma
foreach (var (id, _) in Clients) foreach (var (id, _) in Clients)
{ {
Client? client = GameMain.Client.ConnectedClients.FirstOrDefault(c => c.ID == id); Client? client = GameMain.Client.ConnectedClients.FirstOrDefault(c => c.SessionId == id);
GUIFrame container = new GUIFrame(new RectTransform(new Vector2(1f, 0.15f), listBox.Content.RectTransform), style: "ListBoxElement") { UserData = id }; GUIFrame container = new GUIFrame(new RectTransform(new Vector2(1f, 0.15f), listBox.Content.RectTransform), style: "ListBoxElement") { UserData = id };
GUILayoutGroup frame = new GUILayoutGroup(new RectTransform(Vector2.One, container.RectTransform), isHorizontal: true) { Stretch = true }; GUILayoutGroup frame = new GUILayoutGroup(new RectTransform(Vector2.One, container.RectTransform), isHorizontal: true) { Stretch = true };
@@ -93,7 +93,7 @@ namespace Barotrauma
if (client == null) if (client == null)
{ {
string list = GameMain.Client.ConnectedClients.Aggregate("Available clients:\n", (current, c) => current + $"{c.ID}: {c.Name}\n"); string list = GameMain.Client.ConnectedClients.Aggregate("Available clients:\n", (current, c) => current + $"{c.SessionId}: {c.Name}\n");
DebugConsole.ThrowError($"Client ID {id} was reported in ready check but was not found.\n" + list.TrimEnd('\n')); DebugConsole.ThrowError($"Client ID {id} was reported in ready check but was not found.\n" + list.TrimEnd('\n'));
} }
@@ -139,9 +139,9 @@ namespace Barotrauma
public static void ClientRead(IReadMessage inc) public static void ClientRead(IReadMessage inc)
{ {
ReadyCheckState state = (ReadyCheckState) inc.ReadByte(); ReadyCheckState state = (ReadyCheckState)inc.ReadByte();
CrewManager? crewManager = GameMain.GameSession?.CrewManager; CrewManager? crewManager = GameMain.GameSession?.CrewManager;
List<Client> otherClients = GameMain.Client.ConnectedClients; var otherClients = GameMain.Client.ConnectedClients;
if (crewManager == null || otherClients == null) if (crewManager == null || otherClients == null)
{ {
if (state == ReadyCheckState.Start) if (state == ReadyCheckState.Start)
@@ -165,7 +165,7 @@ namespace Barotrauma
if (hasAuthor) if (hasAuthor)
{ {
authorId = inc.ReadByte(); authorId = inc.ReadByte();
isOwn = authorId == GameMain.Client.ID; isOwn = authorId == GameMain.Client.SessionId;
} }
ushort clientCount = inc.ReadUInt16(); ushort clientCount = inc.ReadUInt16();
@@ -196,7 +196,7 @@ namespace Barotrauma
} }
break; break;
case ReadyCheckState.Update: case ReadyCheckState.Update:
ReadyStatus newState = (ReadyStatus) inc.ReadByte(); ReadyStatus newState = (ReadyStatus)inc.ReadByte();
byte targetId = inc.ReadByte(); byte targetId = inc.ReadByte();
if (crewManager.ActiveReadyCheck != null) if (crewManager.ActiveReadyCheck != null)
{ {
@@ -208,7 +208,7 @@ namespace Barotrauma
for (int i = 0; i < count; i++) for (int i = 0; i < count; i++)
{ {
byte id = inc.ReadByte(); byte id = inc.ReadByte();
ReadyStatus status = (ReadyStatus) inc.ReadByte(); ReadyStatus status = (ReadyStatus)inc.ReadByte();
crewManager.ActiveReadyCheck?.UpdateState(id, status); crewManager.ActiveReadyCheck?.UpdateState(id, status);
} }
@@ -269,9 +269,9 @@ namespace Barotrauma
private static void SendState(ReadyStatus status) private static void SendState(ReadyStatus status)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte) ClientPacketHeader.READY_CHECK); msg.WriteByte((byte)ClientPacketHeader.READY_CHECK);
msg.Write((byte) ReadyCheckState.Update); msg.WriteByte((byte)ReadyCheckState.Update);
msg.Write((byte) status); msg.WriteByte((byte)status);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
} }
@@ -283,8 +283,8 @@ namespace Barotrauma
ReadyCheckCooldown = DateTime.Now.AddMinutes(1); ReadyCheckCooldown = DateTime.Now.AddMinutes(1);
#endif #endif
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte) ClientPacketHeader.READY_CHECK); msg.WriteByte((byte)ClientPacketHeader.READY_CHECK);
msg.Write((byte) ReadyCheckState.Start); msg.WriteByte((byte)ReadyCheckState.Start);
GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable); GameMain.Client?.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
return; return;
} }
@@ -562,7 +562,7 @@ namespace Barotrauma
break; break;
} }
//if putting an item to a container with a max stack size of 1, only put one item from the stack //if putting an item to a container with a max stack size of 1, only put one item from the stack
if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedConstruction?.GetComponent<ItemContainer>()?.MaxStackSize ?? 0) <= 1) if (quickUseAction == QuickUseAction.PutToContainer && (character.SelectedItem?.GetComponent<ItemContainer>()?.MaxStackSize ?? 0) <= 1)
{ {
break; break;
} }
@@ -595,14 +595,14 @@ namespace Barotrauma
if (rootInventory != null && if (rootInventory != null &&
rootInventory.Owner != Character.Controlled && rootInventory.Owner != Character.Controlled &&
rootInventory.Owner != Character.Controlled.SelectedConstruction && rootInventory.Owner != Character.Controlled.SelectedItem &&
rootInventory.Owner != Character.Controlled.SelectedCharacter) rootInventory.Owner != Character.Controlled.SelectedCharacter)
{ {
//allow interacting if the container is linked to the item the character is interacting with //allow interacting if the container is linked to the item the character is interacting with
if (!(rootContainer != null && if (!(rootContainer != null &&
rootContainer.DisplaySideBySideWhenLinked && rootContainer.DisplaySideBySideWhenLinked &&
Character.Controlled.SelectedConstruction != null && Character.Controlled.SelectedItem != null &&
rootContainer.linkedTo.Contains(Character.Controlled.SelectedConstruction))) rootContainer.linkedTo.Contains(Character.Controlled.SelectedItem)))
{ {
DraggingItems.Clear(); DraggingItems.Clear();
} }
@@ -756,7 +756,7 @@ namespace Barotrauma
} }
else else
{ {
var selectedContainer = character.SelectedConstruction?.GetComponent<ItemContainer>(); var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
if (selectedContainer != null && if (selectedContainer != null &&
selectedContainer.Inventory != null && selectedContainer.Inventory != null &&
!selectedContainer.Inventory.Locked) !selectedContainer.Inventory.Locked)
@@ -775,7 +775,8 @@ namespace Barotrauma
else else
{ {
bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any); bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any);
var selectedContainer = character.SelectedConstruction?.GetComponent<ItemContainer>(); var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
if (selectedContainer != null && if (selectedContainer != null &&
selectedContainer.Inventory != null && selectedContainer.Inventory != null &&
!selectedContainer.Inventory.Locked && !selectedContainer.Inventory.Locked &&
@@ -930,7 +931,7 @@ namespace Barotrauma
} }
break; break;
case QuickUseAction.PutToContainer: case QuickUseAction.PutToContainer:
var selectedContainer = character.SelectedConstruction?.GetComponent<ItemContainer>(); var selectedContainer = character.SelectedItem?.GetComponent<ItemContainer>();
if (selectedContainer != null && selectedContainer.Inventory != null) if (selectedContainer != null && selectedContainer.Inventory != null)
{ {
//player has selected the inventory of another item -> attempt to move the item there //player has selected the inventory of another item -> attempt to move the item there
@@ -965,8 +966,8 @@ namespace Barotrauma
} }
break; break;
case QuickUseAction.PutToEquippedItem: case QuickUseAction.PutToEquippedItem:
//order by the condition of the contained item to prefer putting into the item with the emptiest ammo/battery/tank
foreach (Item heldItem in character.HeldItems) foreach (Item heldItem in character.HeldItems.OrderBy(it => it.ContainedItems.FirstOrDefault()?.Condition ?? 0.0f))
{ {
if (heldItem.OwnInventory == null) { continue; } if (heldItem.OwnInventory == null) { continue; }
//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
@@ -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.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -177,7 +178,7 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing, float itemDepth = -1)
{ {
Color color = item.SpriteColor; Color color = item.GetSpriteColor(withHighlight: true);
if (brokenSprite == null) if (brokenSprite == null)
{ {
//broken doors turn black if no broken sprite has been configured //broken doors turn black if no broken sprite has been configured
@@ -220,11 +221,15 @@ namespace Barotrauma.Items.Components
color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth); color, 0.0f, doorSprite.Origin, item.Scale, item.SpriteEffects, doorSprite.Depth);
} }
if (brokenSprite != null && item.Health < item.MaxCondition) float maxCondition = item.Repairables.Any() ?
item.Repairables.Min(r => r.RepairThreshold) / 100.0f * item.MaxCondition :
item.MaxCondition;
float healthRatio = item.Health / maxCondition;
if (brokenSprite != null && healthRatio < 1.0f)
{ {
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.MaxCondition) : Vector2.One; Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - healthRatio) : Vector2.One;
if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; } if (IsHorizontal) { scale.X = 1; } else { scale.Y = 1; }
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f; float alpha = fadeBrokenSprite ? 1.0f - healthRatio : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos, spriteBatch.Draw(brokenSprite.Texture, pos,
getSourceRect(brokenSprite, openState, IsHorizontal), getSourceRect(brokenSprite, openState, IsHorizontal),
color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, item.SpriteEffects, color * alpha, 0.0f, brokenSprite.Origin, scale * item.Scale, item.SpriteEffects,
@@ -166,10 +166,10 @@ namespace Barotrauma.Items.Components
List<VineTile> tiles = new List<VineTile>(); List<VineTile> tiles = new List<VineTile>();
for (int i = 0; i < vineCount; i++) for (int i = 0; i < vineCount; i++)
{ {
VineTileType vineType = (VineTileType) msg.ReadRangedInteger(0b0000, 0b1111); VineTileType vineType = (VineTileType)msg.ReadRangedInteger(0b0000, 0b1111);
int flowerConfig = msg.ReadRangedInteger(0, 0xFFF); int flowerConfig = msg.ReadRangedInteger(0, 0xFFF);
int leafConfig = msg.ReadRangedInteger(0, 0xFFF); int leafConfig = msg.ReadRangedInteger(0, 0xFFF);
sbyte posX = (sbyte) msg.ReadByte(), posY = (sbyte) msg.ReadByte(); sbyte posX = (sbyte)msg.ReadByte(), posY = (sbyte)msg.ReadByte();
Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size); Vector2 pos = new Vector2(posX * VineTile.Size, posY * VineTile.Size);
tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig))); tiles.Add(new VineTile(this, pos, vineType, FoliageConfig.Deserialize(flowerConfig), FoliageConfig.Deserialize(leafConfig)));
@@ -69,8 +69,8 @@ namespace Barotrauma.Items.Components
var eventData = ExtractEventData<EventData>(extraData); var eventData = ExtractEventData<EventData>(extraData);
Vector2 attachPos = eventData.AttachPos; Vector2 attachPos = eventData.AttachPos;
msg.Write(attachPos.X); msg.WriteSingle(attachPos.X);
msg.Write(attachPos.Y); msg.WriteSingle(attachPos.Y);
} }
public override void ClientEventRead(IReadMessage msg, float sendingTime) public override void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -24,10 +24,9 @@ namespace Barotrauma.Items.Components
public void ExtractJobPrefab(IReadOnlyDictionary<Identifier, string> tags) public void ExtractJobPrefab(IReadOnlyDictionary<Identifier, string> tags)
{ {
if (!tags.TryGetValue("jobid".ToIdentifier(), out string jobId)) { return; } if (!tags.TryGetValue("jobid".ToIdentifier(), out string jobId)) { return; }
if (!jobId.IsNullOrEmpty()) if (!jobId.IsNullOrEmpty())
{ {
JobPrefab = JobPrefab.Get(jobId); JobPrefab = JobPrefab.Get(jobId.ToIdentifier());
} }
} }
@@ -153,7 +153,7 @@ namespace Barotrauma.Items.Components
GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused; GUI.MouseOn == null && !Inventory.IsMouseOnInventory && !GameMain.Instance.Paused;
if (GUI.HideCursor) if (GUI.HideCursor)
{ {
crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale); crosshairSprite?.Draw(spriteBatch, crosshairPos, ReloadTimer <= 0.0f ? Color.White : Color.White * 0.2f, 0, currentCrossHairScale);
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale); crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale);
} }
@@ -66,6 +66,8 @@ namespace Barotrauma.Items.Components
public float IsActiveTimer; public float IsActiveTimer;
public virtual bool RecreateGUIOnResolutionChange => false;
public GUILayoutSettings DefaultLayout { get; protected set; } public GUILayoutSettings DefaultLayout { get; protected set; }
public GUILayoutSettings AlternativeLayout { get; protected set; } public GUILayoutSettings AlternativeLayout { get; protected set; }
@@ -148,6 +150,8 @@ namespace Barotrauma.Items.Components
public GUIFrame GuiFrame { get; set; } public GUIFrame GuiFrame { get; set; }
public bool LockGuiFramePosition;
[Serialize(false, IsPropertySaveable.No)] [Serialize(false, IsPropertySaveable.No)]
public bool AllowUIOverlap public bool AllowUIOverlap
{ {
@@ -365,6 +369,7 @@ namespace Barotrauma.Items.Components
loopingSoundChannel = loopingSound.RoundSound.Sound.Play( loopingSoundChannel = loopingSound.RoundSound.Sound.Play(
new Vector3(position.X, position.Y, 0.0f), new Vector3(position.X, position.Y, 0.0f),
0.01f, 0.01f,
freqMult: itemSound.RoundSound.GetRandomFrequencyMultiplier(),
muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull)); muffle: SoundPlayer.ShouldMuffleSound(Character.Controlled, position, loopingSound.Range, Character.Controlled?.CurrentHull));
loopingSoundChannel.Looping = true; loopingSoundChannel.Looping = true;
//TODO: tweak //TODO: tweak
@@ -522,13 +527,11 @@ namespace Barotrauma.Items.Components
if (soundSelectionModes == null) soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>(); if (soundSelectionModes == null) soundSelectionModes = new Dictionary<ActionType, SoundSelectionMode>();
if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random) if (!soundSelectionModes.ContainsKey(type) || soundSelectionModes[type] == SoundSelectionMode.Random)
{ {
SoundSelectionMode selectionMode = SoundSelectionMode.Random; Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out SoundSelectionMode selectionMode);
Enum.TryParse(subElement.GetAttributeString("selectionmode", "Random"), out selectionMode);
soundSelectionModes[type] = selectionMode; soundSelectionModes[type] = selectionMode;
} }
List<ItemSound> soundList = null; if (!sounds.TryGetValue(itemSound.Type, out List<ItemSound> soundList))
if (!sounds.TryGetValue(itemSound.Type, out soundList))
{ {
soundList = new List<ItemSound>(); soundList = new List<ItemSound>();
sounds.Add(itemSound.Type, soundList); sounds.Add(itemSound.Type, soundList);
@@ -566,12 +569,86 @@ namespace Barotrauma.Items.Components
} }
string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", ""); string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color); GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas, Anchor.Center), style, color);
TryCreateDragHandle();
DefaultLayout = GUILayoutSettings.Load(GuiFrameSource); DefaultLayout = GUILayoutSettings.Load(GuiFrameSource);
if (GuiFrame != null) if (GuiFrame != null)
{ {
GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged; GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged;
} }
GameMain.Instance.ResolutionChanged += OnResolutionChanged; GameMain.Instance.ResolutionChanged += OnResolutionChangedPrivate;
}
protected virtual void TryCreateDragHandle()
{
if (GuiFrame != null && GuiFrameSource.GetAttributeBool("draggable", true))
{
var handle = new GUIDragHandle(new RectTransform(Vector2.One, GuiFrame.RectTransform, Anchor.Center),
GuiFrame.RectTransform, style: null)
{
DragArea = HUDLayoutSettings.ItemHUDArea
};
int iconHeight = GUIStyle.ItemFrameMargin.Y / 4;
var dragIcon = new GUIImage(new RectTransform(new Point(GuiFrame.Rect.Width, iconHeight), handle.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, iconHeight / 2) },
style: "GUIDragIndicatorHorizontal");
dragIcon.RectTransform.MinSize = new Point(0, iconHeight);
handle.ValidatePosition = (RectTransform rectT) =>
{
var activeHuds = Character.Controlled?.SelectedItem?.ActiveHUDs ?? item.ActiveHUDs;
foreach (ItemComponent ic in activeHuds)
{
if (ic == this || ic.GuiFrame == null || !ic.CanBeSelected) { continue; }
if (ic.GuiFrame.Rect.Width > GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height > GameMain.GraphicsHeight * 0.9f)
{
//a full-screen GUIFrame (or at least close to one) - this component is doing something weird,
//an ItemContainer with no GUIFrame definition that positions itself in some other GUIFrame, some kind of an overlay?
// -> allow intersecting
continue;
}
if (dragIcon.Rect.Intersects(ic.GuiFrame.Rect))
{
GuiFrame.ImmediateFlash();
return false;
}
}
foreach (ItemComponent ic in activeHuds)
{
//refresh slots to ensure they're rendered at the correct position
(ic as ItemContainer)?.Inventory.CreateSlots();
}
return true;
};
int buttonHeight = (int)(GUIStyle.ItemFrameMargin.Y * 0.4f);
new GUIButton(new RectTransform(new Point(buttonHeight), handle.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(buttonHeight / 4), MinSize = new Point(buttonHeight) },
style: "GUIButtonSettings")
{
OnClicked = (btn, userdata) =>
{
GUIContextMenu.CreateContextMenu(
new ContextMenuOption("item.resetuiposition", isEnabled: true, onSelected: () =>
{
if (Character.Controlled?.SelectedItem != null && item != Character.Controlled.SelectedItem)
{
Character.Controlled.SelectedItem.ForceHUDLayoutUpdate(ignoreLocking: true);
}
else
{
item.ForceHUDLayoutUpdate(ignoreLocking: true);
}
}),
new ContextMenuOption(TextManager.Get(LockGuiFramePosition ? "item.unlockuiposition" : "item.lockuiposition"), isEnabled: true, onSelected: () =>
{
LockGuiFramePosition = !LockGuiFramePosition;
handle.Enabled = !LockGuiFramePosition;
}));
return true;
}
};
}
} }
/// <summary> /// <summary>
@@ -631,6 +708,11 @@ namespace Barotrauma.Items.Components
CreateGUI(); CreateGUI();
} }
OnResolutionChanged(); OnResolutionChanged();
item.ForceHUDLayoutUpdate(ignoreLocking: true);
if (GuiFrame != null && GuiFrame.GetChild<GUIDragHandle>() is GUIDragHandle dragHandle)
{
dragHandle.DragArea = HUDLayoutSettings.ItemHUDArea;
}
} }
public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { } public virtual void AddTooltipInfo(ref LocalizedString name, ref LocalizedString description) { }
@@ -45,6 +45,8 @@ namespace Barotrauma.Items.Components
private set; private set;
} }
public override bool RecreateGUIOnResolutionChange => true;
/// <summary> /// <summary>
/// Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used. /// Depth at which the contained sprites are drawn. If not set, the original depth of the item sprites is used.
/// </summary> /// </summary>
@@ -189,7 +191,7 @@ namespace Barotrauma.Items.Components
onDraw: (SpriteBatch spriteBatch, GUICustomComponent component) => { Inventory.Draw(spriteBatch); }, onDraw: (SpriteBatch spriteBatch, GUICustomComponent component) => { Inventory.Draw(spriteBatch); },
onUpdate: null) onUpdate: null)
{ {
CanBeFocused = false CanBeFocused = true
}; };
// Expand the frame vertically if it's too small to fit the text // Expand the frame vertically if it's too small to fit the text
@@ -322,6 +324,8 @@ namespace Barotrauma.Items.Components
int i = 0; int i = 0;
foreach (Item containedItem in Inventory.AllItems) foreach (Item containedItem in Inventory.AllItems)
{ {
if (containedItem?.Sprite == null) { continue; }
if (AutoInteractWithContained) if (AutoInteractWithContained)
{ {
containedItem.IsHighlighted = item.IsHighlighted; containedItem.IsHighlighted = item.IsHighlighted;
@@ -342,7 +346,7 @@ namespace Barotrauma.Items.Components
containedItem.Sprite.Draw( containedItem.Sprite.Draw(
spriteBatch, spriteBatch,
new Vector2(currentItemPos.X, -currentItemPos.Y), new Vector2(currentItemPos.X, -currentItemPos.Y),
isWiringMode ? containedItem.GetSpriteColor() * 0.15f : containedItem.GetSpriteColor(), 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,
@@ -381,9 +385,14 @@ namespace Barotrauma.Items.Components
guiCustomComponent.RectTransform.Parent = Inventory.RectTransform; guiCustomComponent.RectTransform.Parent = Inventory.RectTransform;
} }
if (item.ParentInventory?.Owner == character && character.SelectedItem == item)
{
character.SelectedItem = null;
}
//if the item is in the character's inventory, no need to update the item's inventory //if the item is in the character's inventory, no need to update the item's inventory
//because the player can see it by hovering the cursor over the item //because the player can see it by hovering the cursor over the item
guiCustomComponent.Visible = item.ParentInventory?.Owner != character && DrawInventory; guiCustomComponent.Visible = DrawInventory && item.ParentInventory?.Owner != character;
if (!guiCustomComponent.Visible) { return; } if (!guiCustomComponent.Visible) { return; }
Inventory.Update(deltaTime, cam); Inventory.Update(deltaTime, cam);
@@ -90,8 +90,8 @@ namespace Barotrauma.Items.Components
{ {
Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ? Light.LightSpriteEffect = Light.LightSpriteEffect == SpriteEffects.None ?
SpriteEffects.FlipHorizontally : SpriteEffects.None; SpriteEffects.FlipHorizontally : SpriteEffects.None;
SetLightSourceTransformProjSpecific();
} }
SetLightSourceTransformProjSpecific();
} }
partial void OnStateChanged() partial void OnStateChanged()
@@ -1,9 +1,11 @@
using Barotrauma.Extensions; using System.Collections.Generic;
using System.Collections.Immutable;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -15,6 +17,7 @@ namespace Barotrauma.Items.Components
} }
private GUIButton activateButton; private GUIButton activateButton;
private GUIComponent inputInventoryHolder, outputInventoryHolder; private GUIComponent inputInventoryHolder, outputInventoryHolder;
private GUIListBox outputDisplayListBox;
private GUIComponent inSufficientPowerWarning; private GUIComponent inSufficientPowerWarning;
@@ -31,42 +34,57 @@ namespace Barotrauma.Items.Components
[Serialize(0.0f, IsPropertySaveable.Yes)] [Serialize(0.0f, IsPropertySaveable.Yes)]
public float InfoAreaWidth { get; set; } public float InfoAreaWidth { get; set; }
partial void InitProjSpecific(XElement element) [Serialize(true, IsPropertySaveable.Yes)]
public bool ShowOutput { get; set; }
partial void InitProjSpecific(XElement _)
{ {
CreateGUI(); CreateGUI();
} }
public override bool RecreateGUIOnResolutionChange => true;
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
{ {
base.OnResolutionChanged();
OnItemLoadedProjSpecific(); OnItemLoadedProjSpecific();
} }
protected override void CreateGUI() protected override void CreateGUI()
{ {
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.90f, 0.80f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.88f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.08f RelativeSpacing = 0.08f
}; };
new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform), item.Name, font: GUIStyle.SubHeadingFont) new GUITextBlock(new RectTransform(new Vector2(1f, 0.07f), paddedFrame.RectTransform) { MinSize = new Point(0, GUI.IntScale(25)) }, item.Name, font: GUIStyle.SubHeadingFont)
{ {
TextAlignment = Alignment.Center, TextAlignment = Alignment.Center,
AutoScaleHorizontal = true AutoScaleHorizontal = true
}; };
var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); var topFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null);
// === INPUT LABEL === // // === INPUT LABEL === //
var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true) var inputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), topFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true);
{
Stretch = true, var queueLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.43f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
RelativeSpacing = 0.05f {
}; Stretch = true,
var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelArea.RectTransform), TextManager.Get("deconstructor.input", "uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero }; RelativeSpacing = 0.05f
inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height)); };
new GUIFrame(new RectTransform(Vector2.One, inputLabelArea.RectTransform), style: "HorizontalLine"); var queueLabel = new GUITextBlock(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), TextManager.Get("deconstructor.inputqueue"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
queueLabel.RectTransform.Resize(new Point((int) queueLabel.Font.MeasureString(queueLabel.Text).X, queueLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, queueLabelLayout.RectTransform), style: "HorizontalLine");
var inputLabelLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.57f, 1f), inputLabelArea.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
Stretch = true,
RelativeSpacing = 0.05f
};
var inputLabel = new GUITextBlock(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), TextManager.Get("deconstructor.input", "uilabel.input"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
inputLabel.RectTransform.Resize(new Point((int) inputLabel.Font.MeasureString(inputLabel.Text).X, inputLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, inputLabelLayout.RectTransform), style: "HorizontalLine");
var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; var inputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), topFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
@@ -78,6 +96,7 @@ namespace Barotrauma.Items.Components
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft); var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 0.8f), inputArea.RectTransform), childAnchor: Anchor.CenterLeft);
activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton") activateButton = new GUIButton(new RectTransform(new Vector2(0.95f, 0.8f), buttonContainer.RectTransform), TextManager.Get("DeconstructorDeconstruct"), style: "DeviceButton")
{ {
UserData = UIHighlightAction.ElementId.DeconstructButton,
TextBlock = { AutoScaleHorizontal = true }, TextBlock = { AutoScaleHorizontal = true },
OnClicked = OnActivateButtonClicked OnClicked = OnActivateButtonClicked
}; };
@@ -92,7 +111,7 @@ namespace Barotrauma.Items.Components
}; };
// === OUTPUT AREA === // // === OUTPUT AREA === //
var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), paddedFrame.RectTransform), style: null); var bottomFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.375f), paddedFrame.RectTransform), style: null);
// === OUTPUT LABEL === // // === OUTPUT LABEL === //
var outputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true) var outputLabelArea = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.15f), bottomFrame.RectTransform, Anchor.TopCenter), childAnchor: Anchor.CenterLeft, isHorizontal: true)
@@ -104,10 +123,19 @@ namespace Barotrauma.Items.Components
outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height)); outputLabel.RectTransform.Resize(new Point((int) outputLabel.Font.MeasureString(outputLabel.Text).X, outputLabel.RectTransform.Rect.Height));
new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(Vector2.One, outputLabelArea.RectTransform), style: "HorizontalLine");
var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f }; var outputArea = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), bottomFrame.RectTransform, Anchor.CenterLeft), childAnchor: Anchor.BottomLeft, isHorizontal: true) { Stretch = true, RelativeSpacing = 0.05f };
// === OUTPUT SLOTS === // // === OUTPUT SLOTS === //
outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null); outputInventoryHolder = new GUIFrame(new RectTransform(new Vector2(1f - InfoAreaWidth, 1f), outputArea.RectTransform, Anchor.CenterLeft), style: null);
if (ShowOutput)
{
GUILayoutGroup outputDisplayLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.25f), paddedFrame.RectTransform), childAnchor: Anchor.TopCenter);
GUILayoutGroup outDisplayTopGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.2f), outputDisplayLayout.RectTransform), isHorizontal: true);
GUITextBlock outDisplayBlock = new GUITextBlock(new RectTransform(Vector2.One, outDisplayTopGroup.RectTransform), TextManager.Get("deconstructor.output"), font: GUIStyle.SubHeadingFont) { Padding = Vector4.Zero };
GUILayoutGroup outDisplayBottomGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.975f, 0.8f), outputDisplayLayout.RectTransform), isHorizontal: true);
outputDisplayListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f), outDisplayBottomGroup.RectTransform), isHorizontal: true, style: null);
}
if (InfoAreaWidth >= 0.0f) if (InfoAreaWidth >= 0.0f)
{ {
@@ -195,12 +223,178 @@ namespace Barotrauma.Items.Components
return base.Select(character); return base.Select(character);
} }
partial void OnItemSlotsChanged(ItemContainer container)
{
if (container.Inventory is null) { return; }
RefreshOutputDisplay(container.Inventory.AllItems.ToImmutableArray());
}
private void RefreshOutputDisplay(ImmutableArray<Item> items)
{
const string outputItemCountUserData = "OutputItemCount";
const string questionMarkUserData = "UnknownItemOutput";
if (outputDisplayListBox is null || inputContainer.Inventory is null) { return; }
Dictionary<Identifier, int> itemCounts = new Dictionary<Identifier, int>();
Dictionary<Identifier, GUIComponent> children = new Dictionary<Identifier, GUIComponent>();
bool addQuestionMark = false;
foreach (GUIComponent child in outputDisplayListBox.Content.Children)
{
if (child.UserData is Identifier it)
{
children.Add(it, child);
}
}
if (outputDisplayListBox.Content.FindChild(questionMarkUserData) is { } foundChild)
{
outputDisplayListBox.RemoveChild(foundChild);
}
foreach (Item it in items)
{
if (it.Prefab.RandomDeconstructionOutput)
{
addQuestionMark = true;
continue;
}
foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems)
{
if (!deconstructItem.IsValidDeconstructor(item)) { continue; }
RegisterItem(deconstructItem.ItemIdentifier, deconstructItem.Amount);
}
/*if (it.OwnInventory is { } inventory)
{
foreach (Item inventoryItems in inventory.AllItems)
{
RegisterItem(inventoryItems.Prefab.Identifier);
}
}*/
void RegisterItem(Identifier identifier, int amount = 1)
{
if (itemCounts.ContainsKey(identifier))
{
itemCounts[identifier] += amount;
return;
}
itemCounts.Add(identifier, amount);
}
}
foreach (var (it, child) in children)
{
if (!itemCounts.ContainsKey(it))
{
outputDisplayListBox.RemoveChild(child);
}
}
foreach (var (it, amount) in itemCounts)
{
if (!children.TryGetValue(it, out GUIComponent child))
{
child = CreateOutputDisplayItem(it, outputDisplayListBox.Content);
}
if (child is null) { continue; }
UpdateOutputDisplayItemCount(child, amount);
}
if (addQuestionMark)
{
CreateQuestionMark(outputDisplayListBox.Content);
}
static void CreateQuestionMark(GUIComponent parent)
{
GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
{
UserData = questionMarkUserData,
ToolTip = TextManager.Get("deconstructor.unknownitemsoutput")
};
GUIFrame questionMarkFrame = new GUIFrame(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), style: "GUIFrameListBox")
{
CanBeFocused = false,
};
// question mark text
new GUITextBlock(new RectTransform(Vector2.One, questionMarkFrame.RectTransform, anchor: Anchor.Center), text: "?", textAlignment: Alignment.Center, font: GUIStyle.LargeFont)
{
CanBeFocused = false
};
}
static GUIComponent CreateOutputDisplayItem(Identifier identifier, GUIComponent parent)
{
ItemPrefab prefab = ItemPrefab.Find(null, identifier);
if (prefab is null) { return null; }
GUIFrame itemFrame = new GUIFrame(new RectTransform(new Vector2(0.1f, 1f), parent.RectTransform), style: null)
{
UserData = identifier,
ToolTip = GetTooltip(prefab)
};
Sprite icon = prefab.InventoryIcon ?? prefab.Sprite;
Color iconColor = prefab.InventoryIcon is null ? prefab.SpriteColor : prefab.InventoryIconColor;
GUIImage itemIcon = new GUIImage(new RectTransform(Vector2.One, itemFrame.RectTransform, scaleBasis: ScaleBasis.Smallest, anchor: Anchor.Center), sprite: icon, scaleToFit: true)
{
Color = iconColor,
CanBeFocused = false
};
// item count text
new GUITextBlock(new RectTransform(new Vector2(0.5f, 0.5f), itemIcon.RectTransform, anchor: Anchor.BottomRight), "", font: GUIStyle.Font, textAlignment: Alignment.BottomRight)
{
UserData = outputItemCountUserData,
Shadow = true,
CanBeFocused = false,
Padding = Vector4.Zero,
TextColor = Color.White,
};
return itemFrame;
}
static void UpdateOutputDisplayItemCount(GUIComponent component, int count)
{
if (!(component.FindChild(outputItemCountUserData, recursive: true) is GUITextBlock textBlock)) { return; }
textBlock.Text = TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString());
}
static RichString GetTooltip(ItemPrefab prefab)
{
LocalizedString toolTip = $"‖color:{Color.White.ToStringHex()}‖{prefab.Name}‖color:end‖";
LocalizedString description = prefab.Description;
if (!description.IsNullOrEmpty()) { toolTip += '\n' + description; }
if (prefab.ContentPackage != GameMain.VanillaContent && prefab.ContentPackage != null)
{
toolTip += $"\n‖color:{Color.MediumPurple.ToStringHex()}‖{prefab.ContentPackage.Name}‖color:end‖";
}
return RichString.Rich(toolTip);
}
}
partial void OnItemLoadedProjSpecific() partial void OnItemLoadedProjSpecific()
{ {
inputContainer.AllowUIOverlap = true; inputContainer.AllowUIOverlap = true;
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform; inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
outputContainer.AllowUIOverlap = true; outputContainer.AllowUIOverlap = true;
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform; outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
inputContainer.Inventory.Locked = IsActive;
} }
private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
@@ -239,17 +433,22 @@ namespace Barotrauma.Items.Components
private bool OnActivateButtonClicked(GUIButton button, object obj) private bool OnActivateButtonClicked(GUIButton button, object obj)
{ {
var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false); if (!IsActive)
if (disallowedItem != null && !DeconstructItemsSimultaneously)
{ {
int index = inputContainer.Inventory.FindIndex(disallowedItem); //don't allow turning on if there's non-deconstructable items in the queue
if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length) var disallowedItem = inputContainer.Inventory.FindItem(i => !i.AllowDeconstruct, recursive: false);
if (disallowedItem != null && !DeconstructItemsSimultaneously)
{ {
var slot = inputContainer.Inventory.visualSlots[index]; int index = inputContainer.Inventory.FindIndex(disallowedItem);
slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f); if (index >= 0 && index < inputContainer.Inventory.visualSlots.Length)
{
var slot = inputContainer.Inventory.visualSlots[index];
slot?.ShowBorderHighlight(GUIStyle.Red, 0.1f, 0.9f);
}
return true;
} }
return true;
} }
if (GameMain.Client != null) if (GameMain.Client != null)
{ {
pendingState = !IsActive; pendingState = !IsActive;
@@ -264,7 +463,7 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
msg.Write(pendingState); msg.WriteBoolean(pendingState);
} }
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -32,6 +32,8 @@ namespace Barotrauma.Items.Components
} }
private FabricationRecipe selectedItem; private FabricationRecipe selectedItem;
public Identifier SelectedItemIdentifier => SelectedItem?.TargetItem.Identifier ?? Identifier.Empty;
private GUIComponent inSufficientPowerWarning; private GUIComponent inSufficientPowerWarning;
private FabricationRecipe pendingFabricatedItem; private FabricationRecipe pendingFabricatedItem;
@@ -51,11 +53,13 @@ namespace Barotrauma.Items.Components
[Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)] [Serialize("vendingmachine.outofstock", IsPropertySaveable.Yes)]
public string FabricationLimitReachedText { get; set; } public string FabricationLimitReachedText { get; set; }
public override bool RecreateGUIOnResolutionChange => true;
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
{ {
if (GuiFrame != null) if (GuiFrame != null)
{ {
OnItemLoadedProjSpecific(); InitInventoryUIs();
} }
} }
@@ -232,6 +236,17 @@ namespace Barotrauma.Items.Components
} }
} }
private void InitInventoryUIs()
{
if (inputInventoryHolder != null)
{
inputContainer.AllowUIOverlap = true;
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
}
outputContainer.AllowUIOverlap = true;
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
}
private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe) private LocalizedString GetRecipeNameAndAmount(FabricationRecipe fabricationRecipe)
{ {
if (fabricationRecipe == null) { return ""; } if (fabricationRecipe == null) { return ""; }
@@ -249,13 +264,7 @@ namespace Barotrauma.Items.Components
partial void OnItemLoadedProjSpecific() partial void OnItemLoadedProjSpecific()
{ {
CreateGUI(); CreateGUI();
if (inputInventoryHolder != null) InitInventoryUIs();
{
inputContainer.AllowUIOverlap = true;
inputContainer.Inventory.RectTransform = inputInventoryHolder.RectTransform;
}
outputContainer.AllowUIOverlap = true;
outputContainer.Inventory.RectTransform = outputInventoryHolder.RectTransform;
} }
partial void SelectProjSpecific(Character character) partial void SelectProjSpecific(Character character)
@@ -328,76 +337,112 @@ namespace Barotrauma.Items.Components
} }
} }
private readonly Dictionary<FabricationRecipe.RequiredItem, int> missingIngredientCounts = new Dictionary<FabricationRecipe.RequiredItem, int>();
private float ingredientHighlightTimer;
private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent) private void DrawInputOverLay(SpriteBatch spriteBatch, GUICustomComponent overlayComponent)
{ {
overlayComponent.RectTransform.SetAsLastChild(); overlayComponent.RectTransform.SetAsLastChild();
missingIngredientCounts.Clear();
FabricationRecipe targetItem = fabricatedItem ?? selectedItem; FabricationRecipe targetItem = fabricatedItem ?? selectedItem;
if (targetItem != null) if (targetItem != null)
{ {
int slotIndex = 0;
var missingItems = new List<FabricationRecipe.RequiredItem>();
foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems) foreach (FabricationRecipe.RequiredItem requiredItem in targetItem.RequiredItems)
{ {
for (int i = 0; i < requiredItem.Amount; i++) if (missingIngredientCounts.ContainsKey(requiredItem))
{ {
missingItems.Add(requiredItem); missingIngredientCounts[requiredItem] += requiredItem.Amount;
}
else
{
missingIngredientCounts[requiredItem] = requiredItem.Amount;
} }
} }
foreach (Item item in inputContainer.Inventory.AllItems) foreach (Item item in inputContainer.Inventory.AllItems)
{ {
missingItems.Remove(missingItems.FirstOrDefault(mi => mi.ItemPrefabs.Contains(item.Prefab))); var missingIngredient = missingIngredientCounts.Keys.FirstOrDefault(mi => mi.MatchesItem(item));
} if (missingIngredient == null) { continue; }
var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count());
missingItems = missingItems.Distinct().ToList();
foreach (FabricationRecipe.RequiredItem requiredItem in missingItems) if (missingIngredientCounts[missingIngredient] == 1)
{
missingIngredientCounts.Remove(missingIngredient);
}
else
{
missingIngredientCounts[missingIngredient]--;
}
}
if (ingredientHighlightTimer <= 0.0f)
{ {
//highlight inventory slots that contain suitable ingredients in linked inventories
foreach (var inventory in linkedInventories)
{
if (inventory.visualSlots == null) { continue; }
for (int i = 0; i < inventory.Capacity; i++)
{
if (inventory.visualSlots[i].HighlightTimer > 0.0f) { continue; }
var availableItem = inventory.GetItemAt(i);
if (availableItem == null) { continue; }
if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableItem)))
{
inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
continue;
}
if (availableItem.OwnInventory != null)
{
for (int j = 0; j < availableItem.OwnInventory.Capacity; j++)
{
var availableContainedItem = availableItem.OwnInventory.GetItemAt(i);
if (availableContainedItem == null) { continue; }
if (missingIngredientCounts.Keys.Any(it => it.MatchesItem(availableContainedItem)))
{
inventory.visualSlots[i].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
break;
}
}
}
}
}
ingredientHighlightTimer = 1.0f;
}
int slotIndex = 0;
foreach (var kvp in missingIngredientCounts)
{
var requiredItem = kvp.Key;
int missingCount = kvp.Value;
while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null) while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null)
{ {
slotIndex++; slotIndex++;
} }
requiredItem.ItemPrefabs
.Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier))
.ForEach(requiredPrefab => {
var availableItems = availableIngredients[requiredPrefab.Identifier];
foreach (Item it in availableItems)
{
if (it.ParentInventory == inputContainer.Inventory) { continue; }
var rootInventoryOwner = it.GetRootInventoryOwner();
Inventory rootInventory = (rootInventoryOwner as Item)?.OwnInventory as Inventory ?? (rootInventoryOwner as Character)?.Inventory;
if (rootInventory?.visualSlots == null) { continue; }
int availableSlotIndex = rootInventory.FindIndex((it.Container != rootInventoryOwner ? it.Container : it) ?? it);
if (availableSlotIndex < 0) { continue; }
if (rootInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f)
{
rootInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
if (slotIndex < inputContainer.Capacity)
{
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
}
}
}
});
if (slotIndex >= inputContainer.Capacity) { break; } if (slotIndex >= inputContainer.Capacity) { break; }
var itemIcon = requiredItem.ItemPrefabs.First().InventoryIcon ?? requiredItem.ItemPrefabs.First().Sprite; if (slotIndex < inputContainer.Capacity &&
inputContainer.Inventory.visualSlots[slotIndex].HighlightTimer <= 0.0f &&
availableIngredients.Any(i => i.Value.Any() && requiredItem.MatchesItem(i.Value.First())))
{
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUIStyle.Green, 0.5f, 0.5f, 0.2f);
}
var requiredItemPrefab = requiredItem.FirstMatchingPrefab;
var itemIcon = requiredItemPrefab.InventoryIcon ?? requiredItemPrefab.Sprite;
Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect; Rectangle slotRect = inputContainer.Inventory.visualSlots[slotIndex].Rect;
itemIcon.Draw( itemIcon.Draw(
spriteBatch, spriteBatch,
slotRect.Center.ToVector2(), slotRect.Center.ToVector2(),
color: requiredItem.ItemPrefabs.First().InventoryIconColor * 0.3f, color: requiredItemPrefab.InventoryIconColor * 0.3f,
scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y)); scale: Math.Min(slotRect.Width / itemIcon.size.X, slotRect.Height / itemIcon.size.Y));
if (missingCount > 1)
if (missingCounts[requiredItem] > 1)
{ {
Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom); Vector2 stackCountPos = new Vector2(slotRect.Right, slotRect.Bottom);
string stackCountText = "x" + missingCounts[requiredItem]; string stackCountText = "x" + missingCount;
stackCountPos -= GUIStyle.SmallFont.MeasureString(stackCountText) + new Vector2(4, 2); stackCountPos -= GUIStyle.SmallFont.MeasureString(stackCountText) + new Vector2(4, 2);
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black); GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos + Vector2.One, Color.Black);
GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White); GUIStyle.SmallFont.DrawString(spriteBatch, stackCountText, stackCountPos, Color.White);
@@ -446,9 +491,9 @@ namespace Barotrauma.Items.Components
{ {
toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText); toolTipText = TextManager.GetWithVariable("displayname.emptyitem", "[itemname]", toolTipText);
} }
if (!requiredItem.ItemPrefabs.First().Description.IsNullOrEmpty()) if (!requiredItemPrefab.Description.IsNullOrEmpty())
{ {
toolTipText += '\n' + requiredItem.ItemPrefabs.First().Description; toolTipText += '\n' + requiredItemPrefab.Description;
} }
tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText }; tooltip = new ToolTip { TargetElement = slotRect, Tooltip = toolTipText };
} }
@@ -715,6 +760,8 @@ namespace Barotrauma.Items.Components
activateButton.Enabled = false; activateButton.Enabled = false;
inSufficientPowerWarning.Visible = IsActive && !hasPower; inSufficientPowerWarning.Visible = IsActive && !hasPower;
ingredientHighlightTimer -= deltaTime;
if (!IsActive) if (!IsActive)
{ {
//only check ingredients if the fabricator isn't active (if it is, this is done in Update) //only check ingredients if the fabricator isn't active (if it is, this is done in Update)
@@ -763,7 +810,7 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0; uint recipeHash = pendingFabricatedItem?.RecipeHash ?? 0;
msg.Write(recipeHash); msg.WriteUInt32(recipeHash);
} }
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -246,6 +246,7 @@ namespace Barotrauma.Items.Components
protected override void CreateGUI() protected override void CreateGUI()
{ {
GuiFrame.ClearChildren(); GuiFrame.ClearChildren();
TryCreateDragHandle();
GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f);
GuiFrame.CanBeFocused = true; GuiFrame.CanBeFocused = true;
@@ -509,7 +510,7 @@ namespace Barotrauma.Items.Components
Vector2 origin = weaponSprite.Origin; Vector2 origin = weaponSprite.Origin;
float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y); float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y);
Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green; Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? Color.DimGray : GUIStyle.Green;
weaponSprite.Draw(batch, center, color, origin, rotation, scale, it.SpriteEffects); weaponSprite.Draw(batch, center, color, origin, rotation, scale, SpriteEffects.None);
} }
}); });
@@ -1012,12 +1013,14 @@ namespace Barotrauma.Items.Components
} }
else if (hullData.LinkedHulls.Any()) else if (hullData.LinkedHulls.Any())
{ {
hullData.HullWaterAmount = 0.0f; float waterVolume = 0.0f;
float totalVolume = 0.0f;
foreach (Hull linkedHull in hullData.LinkedHulls) foreach (Hull linkedHull in hullData.LinkedHulls)
{ {
hullData.HullWaterAmount += WaterDetector.GetWaterPercentage(linkedHull); waterVolume += linkedHull.WaterVolume;
totalVolume += linkedHull.Volume;
} }
hullData.HullWaterAmount /= hullData.LinkedHulls.Count; hullData.HullWaterAmount = MathHelper.Clamp((int)Math.Ceiling(waterVolume / totalVolume * 100), 0, 100);
} }
else else
{ {
@@ -25,7 +25,7 @@ namespace Barotrauma.Items.Components
public override void Update(float deltaTime, Camera cam) public override void Update(float deltaTime, Camera cam)
{ {
if (Character.Controlled?.SelectedConstruction != item) if (Character.Controlled?.SelectedItem != item)
{ {
IsActive = false; IsActive = false;
return; return;
@@ -1,10 +1,8 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -58,6 +56,7 @@ namespace Barotrauma.Items.Components
RelativeOffset = new Vector2(0, 0.1f) RelativeOffset = new Vector2(0, 0.1f)
}, style: "PowerButton") }, style: "PowerButton")
{ {
UserData = UIHighlightAction.ElementId.PowerButton,
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
TargetLevel = null; TargetLevel = null;
@@ -114,6 +113,7 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
pumpSpeedSlider.Frame.UserData = UIHighlightAction.ElementId.PumpSpeedSlider;
var textsArea = new GUIFrame(new RectTransform(new Vector2(1, 0.25f), sliderArea.RectTransform, Anchor.BottomCenter), style: null); var textsArea = new GUIFrame(new RectTransform(new Vector2(1, 0.25f), sliderArea.RectTransform, Anchor.BottomCenter), style: null);
var outLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textsArea.RectTransform, Anchor.CenterLeft), TextManager.Get("PumpOut"), var outLabel = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textsArea.RectTransform, Anchor.CenterLeft), TextManager.Get("PumpOut"),
textColor: GUIStyle.TextColorNormal, textAlignment: Alignment.CenterLeft, wrap: false, font: GUIStyle.SubHeadingFont); textColor: GUIStyle.TextColorNormal, textAlignment: Alignment.CenterLeft, wrap: false, font: GUIStyle.SubHeadingFont);
@@ -219,7 +219,7 @@ namespace Barotrauma.Items.Components
{ {
//flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this //flowpercentage can only be adjusted at 10% intervals -> no need for more accuracy than this
msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10); msg.WriteRangedInteger((int)(flowPercentage / 10.0f), -10, 10);
msg.Write(IsActive); msg.WriteBoolean(IsActive);
} }
public void ClientEventRead(IReadMessage msg, float sendingTime) public void ClientEventRead(IReadMessage msg, float sendingTime)
@@ -1,11 +1,11 @@
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
using Barotrauma.Sounds;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -34,12 +34,14 @@ namespace Barotrauma.Items.Components
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;
private Color warningColor = Color.Red; private Color warningColor = Color.Red;
private Color coldColor = Color.LightBlue;
private Color warmColor = Color.Orange; private readonly Color[] temperatureColors = new Color[] { Color.Blue, Color.LightBlue, Color.Orange, Color.Red };
private Color hotColor = Color.Red;
private Color outputColor = Color.Goldenrod; private Color outputColor = Color.Goldenrod;
private Color loadColor = Color.LightSteelBlue; private Color loadColor = Color.LightSteelBlue;
private RoundSound temperatureBoostSoundUp, temperatureBoostSoundDown;
private GUIButton temperatureBoostUpButton, temperatureBoostDownButton;
public GUIScrollBar FissionRateScrollBar { get; private set; } public GUIScrollBar FissionRateScrollBar { get; private set; }
public GUIScrollBar TurbineOutputScrollBar { get; private set; } public GUIScrollBar TurbineOutputScrollBar { get; private set; }
@@ -63,10 +65,11 @@ namespace Barotrauma.Items.Components
"ReactorWarningOverheating", "ReactorWarningHighOutput", "ReactorWarningFuelOut", "ReactorWarningSCRAM" "ReactorWarningOverheating", "ReactorWarningHighOutput", "ReactorWarningFuelOut", "ReactorWarningSCRAM"
}; };
public override bool RecreateGUIOnResolutionChange => true;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
// TODO: need to recreate the gui when the resolution changes CreateGUI();
fissionRateMeter = new Sprite(element.GetChildElement("fissionratemeter")?.GetChildElement("sprite")); fissionRateMeter = new Sprite(element.GetChildElement("fissionratemeter")?.GetChildElement("sprite"));
turbineOutputMeter = new Sprite(element.GetChildElement("turbineoutputmeter")?.GetChildElement("sprite")); turbineOutputMeter = new Sprite(element.GetChildElement("turbineoutputmeter")?.GetChildElement("sprite"));
meterPointer = new Sprite(element.GetChildElement("meterpointer")?.GetChildElement("sprite")); meterPointer = new Sprite(element.GetChildElement("meterpointer")?.GetChildElement("sprite"));
@@ -76,10 +79,28 @@ namespace Barotrauma.Items.Components
tempRangeIndicator = new Sprite(element.GetChildElement("temprangeindicator")?.GetChildElement("sprite")); tempRangeIndicator = new Sprite(element.GetChildElement("temprangeindicator")?.GetChildElement("sprite"));
graphLine = new Sprite(element.GetChildElement("graphline")?.GetChildElement("sprite")); graphLine = new Sprite(element.GetChildElement("graphline")?.GetChildElement("sprite"));
foreach (var subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "temperatureboostsoundup":
temperatureBoostSoundUp = RoundSound.Load(subElement, false);
break;
case "temperatureboostsounddown":
temperatureBoostSoundDown = RoundSound.Load(subElement, false);
break;
}
}
}
protected override void CreateGUI()
{
warningButtons.Clear();
paddedFrame = new GUILayoutGroup(new RectTransform( paddedFrame = new GUILayoutGroup(new RectTransform(
GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center)
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, { AbsoluteOffset = GUIStyle.ItemFrameOffset },
isHorizontal: true) isHorizontal: true)
{ {
RelativeSpacing = 0.012f, RelativeSpacing = 0.012f,
Stretch = true Stretch = true
@@ -213,6 +234,7 @@ namespace Barotrauma.Items.Components
return false; return false;
} }
}; };
FissionRateScrollBar.Frame.UserData = UIHighlightAction.ElementId.FissionRateSlider;
TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter) TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter)
{ {
@@ -231,6 +253,7 @@ namespace Barotrauma.Items.Components
return false; return false;
} }
}; };
TurbineOutputScrollBar.Frame.UserData = UIHighlightAction.ElementId.TurbineOutputSlider;
var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform)) var buttonArea = new GUILayoutGroup(new RectTransform(new Vector2(1, 0.2f), columnLeft.RectTransform))
{ {
@@ -284,6 +307,7 @@ namespace Barotrauma.Items.Components
AutoTempSwitch = new GUIButton(new RectTransform(new Vector2(0.15f, 0.9f), topRightArea.RectTransform), AutoTempSwitch = new GUIButton(new RectTransform(new Vector2(0.15f, 0.9f), topRightArea.RectTransform),
style: "SwitchVertical") style: "SwitchVertical")
{ {
UserData = UIHighlightAction.ElementId.AutoTempSwitch,
Enabled = false, Enabled = false,
Selected = AutoTemp, Selected = AutoTemp,
ClickSound = GUISoundType.UISwitch, ClickSound = GUISoundType.UISwitch,
@@ -326,6 +350,7 @@ namespace Barotrauma.Items.Components
RelativeOffset = new Vector2(0, 0.1f) RelativeOffset = new Vector2(0, 0.1f)
}, style: "PowerButton") }, style: "PowerButton")
{ {
UserData = UIHighlightAction.ElementId.PowerButton,
OnClicked = (button, data) => OnClicked = (button, data) =>
{ {
PowerOn = !PowerOn; PowerOn = !PowerOn;
@@ -354,7 +379,46 @@ namespace Barotrauma.Items.Components
new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), bottomRightArea.RectTransform), style: "VerticalLine"); new GUIFrame(new RectTransform(new Vector2(0.01f, 1.0f), bottomRightArea.RectTransform), style: "VerticalLine");
new GUICustomComponent(new RectTransform(new Vector2(0.1f, 1), bottomRightArea.RectTransform, Anchor.Center), DrawTempMeter, null); var temperatureArea = new GUILayoutGroup(new RectTransform(new Vector2(0.1f, 1), bottomRightArea.RectTransform, Anchor.Center), isHorizontal: false)
{
Stretch = true,
RelativeSpacing = 0.01f
};
temperatureBoostUpButton = new GUIButton(new RectTransform(Vector2.One, temperatureArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: "GUIPlusButton")
{
ToolTip = TextManager.Get("reactor.temperatureboostup"),
OnClicked = (_, __) =>
{
applyTemperatureBoost(TemperatureBoostAmount, temperatureBoostSoundUp);
return true;
}
};
new GUICustomComponent(new RectTransform(Vector2.One, temperatureArea.RectTransform, Anchor.Center), DrawTempMeter, null);
temperatureBoostDownButton = new GUIButton(new RectTransform(Vector2.One, temperatureArea.RectTransform, scaleBasis: ScaleBasis.BothWidth), style: "GUIMinusButton")
{
ToolTip = TextManager.Get("reactor.temperatureboostdown"),
OnClicked = (_, __) =>
{
applyTemperatureBoost(-TemperatureBoostAmount, temperatureBoostSoundDown);
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))
{ {
@@ -382,6 +446,19 @@ namespace Barotrauma.Items.Components
}; };
LocalizedString outputStr = TextManager.Get("ReactorOutput"); LocalizedString outputStr = TextManager.Get("ReactorOutput");
outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}"; outputText.TextGetter += () => $"{outputStr.Replace("[kw]", ((int)-currPowerConsumption).ToString())} {kW}";
InitInventoryUI();
}
private void InitInventoryUI()
{
var itemContainer = item.GetComponent<ItemContainer>();
if (itemContainer != null)
{
itemContainer.UILabel = "";
itemContainer.AllowUIOverlap = true;
itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform;
}
} }
public override void OnItemLoaded() public override void OnItemLoaded()
@@ -389,23 +466,10 @@ namespace Barotrauma.Items.Components
base.OnItemLoaded(); base.OnItemLoaded();
TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f; TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f; FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
var itemContainer = item.GetComponent<ItemContainer>(); InitInventoryUI();
if (itemContainer != null)
{
itemContainer.UILabel = "";
itemContainer.AllowUIOverlap = true;
itemContainer.Inventory.RectTransform = inventoryContainer.RectTransform;
/*var inventoryLabel = inventoryContainer.Parent?.GetChild<GUITextBlock>();
if (inventoryLabel != null)
{
inventoryLabel.RectTransform.MinSize = new Point(100, 0);
inventoryLabel.Text = itemContainer.GetUILabel();
inventoryLabel.CalculateHeightFromText();
(inventoryLabel.Parent as GUILayoutGroup).Recalculate();
}*/
}
} }
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);
@@ -418,7 +482,7 @@ namespace Barotrauma.Items.Components
while (meterBarPos.Y > container.Rect.Bottom + (int)(5 * GUI.yScale) - container.Rect.Height * tempFill) while (meterBarPos.Y > container.Rect.Bottom + (int)(5 * GUI.yScale) - container.Rect.Height * tempFill)
{ {
float tempRatio = 1.0f - ((meterBarPos.Y - container.Rect.Y) / container.Rect.Height); float tempRatio = 1.0f - ((meterBarPos.Y - container.Rect.Y) / container.Rect.Height);
Color color = ToolBox.GradientLerp(tempRatio, coldColor, optimalRangeColor, warmColor, hotColor); Color color = ToolBox.GradientLerp(tempRatio, temperatureColors);
tempMeterBar.Draw(spriteBatch, meterBarPos, color: color, scale: meterBarScale); tempMeterBar.Draw(spriteBatch, meterBarPos, color: color, scale: meterBarScale);
int spacing = 2; int spacing = 2;
meterBarPos.Y -= tempMeterBar.size.Y * meterBarScale + spacing; meterBarPos.Y -= tempMeterBar.size.Y * meterBarScale + spacing;
@@ -635,7 +699,6 @@ namespace Barotrauma.Items.Components
float normalizedValue = (value - range.X) / (range.Y - range.X); float normalizedValue = (value - range.X) / (range.Y - range.X);
float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue); float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue);
Vector2 offset = new Vector2(0, 40) * scale;
meterPointer.Draw(spriteBatch, pointerPos, valueRad, scale); meterPointer.Draw(spriteBatch, pointerPos, valueRad, scale);
} }
@@ -713,8 +776,8 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
msg.Write(autoTemp); msg.WriteBoolean(autoTemp);
msg.Write(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);
@@ -140,8 +140,7 @@ namespace Barotrauma.Items.Components
set; set;
} }
private bool AllowUsingMineralScanner => public override bool RecreateGUIOnResolutionChange => true;
HasMineralScanner && !isConnectedToSteering;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
@@ -195,7 +194,6 @@ namespace Barotrauma.Items.Components
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
{ {
base.OnResolutionChanged();
UpdateGUIElements(); UpdateGUIElements();
} }
@@ -218,6 +216,7 @@ namespace Barotrauma.Items.Components
var sonarModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f + extraHeight), paddedControlContainer.RectTransform, Anchor.TopCenter), style: null); var sonarModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f + extraHeight), paddedControlContainer.RectTransform, Anchor.TopCenter), style: null);
SonarModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), sonarModeArea.RectTransform), string.Empty, style: "SwitchVertical") SonarModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), sonarModeArea.RectTransform), string.Empty, style: "SwitchVertical")
{ {
UserData = UIHighlightAction.ElementId.SonarModeSwitch,
Selected = false, Selected = false,
Enabled = true, Enabled = true,
ClickSound = GUISoundType.UISwitch, ClickSound = GUISoundType.UISwitch,
@@ -240,6 +239,7 @@ namespace Barotrauma.Items.Components
passiveTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), sonarModeRightSide.RectTransform, Anchor.TopLeft), passiveTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), sonarModeRightSide.RectTransform, Anchor.TopLeft),
TextManager.Get("SonarPassive"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRedSmall") TextManager.Get("SonarPassive"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRedSmall")
{ {
UserData = UIHighlightAction.ElementId.PassiveSonarIndicator,
ToolTip = TextManager.Get("SonarTipPassive"), ToolTip = TextManager.Get("SonarTipPassive"),
Selected = true, Selected = true,
Enabled = false Enabled = false
@@ -247,6 +247,7 @@ namespace Barotrauma.Items.Components
activeTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), sonarModeRightSide.RectTransform, Anchor.BottomLeft), activeTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.45f), sonarModeRightSide.RectTransform, Anchor.BottomLeft),
TextManager.Get("SonarActive"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRedSmall") TextManager.Get("SonarActive"), font: GUIStyle.SubHeadingFont, style: "IndicatorLightRedSmall")
{ {
UserData = UIHighlightAction.ElementId.ActiveSonarIndicator,
ToolTip = TextManager.Get("SonarTipActive"), ToolTip = TextManager.Get("SonarTipActive"),
Selected = false, Selected = false,
Enabled = false Enabled = false
@@ -281,9 +282,14 @@ namespace Barotrauma.Items.Components
}; };
new GUIFrame(new RectTransform(new Vector2(0.8f, 0.01f), paddedControlContainer.RectTransform, Anchor.Center), style: "HorizontalLine") new GUIFrame(new RectTransform(new Vector2(0.8f, 0.01f), paddedControlContainer.RectTransform, Anchor.Center), style: "HorizontalLine")
{ UserData = "horizontalline" }; {
UserData = "horizontalline"
};
var directionalModeFrame = new GUIFrame(new RectTransform(new Vector2(1, 0.45f), lowerAreaFrame.RectTransform, Anchor.BottomCenter), style: null); var directionalModeFrame = new GUIFrame(new RectTransform(new Vector2(1, 0.45f), lowerAreaFrame.RectTransform, Anchor.BottomCenter), style: null)
{
UserData = UIHighlightAction.ElementId.DirectionalSonarFrame
};
directionalModeSwitch = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), directionalModeFrame.RectTransform, Anchor.CenterLeft), string.Empty, style: "SwitchHorizontal") directionalModeSwitch = new GUIButton(new RectTransform(new Vector2(0.3f, 0.8f), directionalModeFrame.RectTransform, Anchor.CenterLeft), string.Empty, style: "SwitchHorizontal")
{ {
OnClicked = (button, data) => OnClicked = (button, data) =>
@@ -302,7 +308,7 @@ namespace Barotrauma.Items.Components
TextManager.Get("SonarDirectionalPing"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft); TextManager.Get("SonarDirectionalPing"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
textBlocksToScaleAndNormalize.Add(directionalModeSwitchText); textBlocksToScaleAndNormalize.Add(directionalModeSwitchText);
if (AllowUsingMineralScanner) if (HasMineralScanner)
{ {
AddMineralScannerSwitchToGUI(); AddMineralScannerSwitchToGUI();
} }
@@ -336,6 +342,18 @@ namespace Barotrauma.Items.Components
sonarView.RectTransform.RelativeOffset = new Vector2(0.13f * GUI.RelativeHorizontalAspectRatio, 0); sonarView.RectTransform.RelativeOffset = new Vector2(0.13f * GUI.RelativeHorizontalAspectRatio, 0);
sonarView.RectTransform.SetPosition(Anchor.BottomRight); sonarView.RectTransform.SetPosition(Anchor.BottomRight);
} }
var handle = GuiFrame.GetChild<GUIDragHandle>();
if (handle != null)
{
handle.RectTransform.Parent = controlContainer.RectTransform;
handle.RectTransform.Resize(Vector2.One);
handle.RectTransform.SetAsFirstChild();
}
}
protected override void TryCreateDragHandle()
{
base.TryCreateDragHandle();
} }
private void SetPingDirection(Vector2 direction) private void SetPingDirection(Vector2 direction)
@@ -373,7 +391,7 @@ namespace Barotrauma.Items.Components
{ {
base.OnItemLoaded(); base.OnItemLoaded();
zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom); zoomSlider.BarScroll = MathUtils.InverseLerp(MinZoom, MaxZoom, zoom);
if (AllowUsingMineralScanner && mineralScannerSwitch == null) if (HasMineralScanner && mineralScannerSwitch == null)
{ {
AddMineralScannerSwitchToGUI(); AddMineralScannerSwitchToGUI();
GUITextBlock.AutoScaleAndNormalize(textBlocksToScaleAndNormalize); GUITextBlock.AutoScaleAndNormalize(textBlocksToScaleAndNormalize);
@@ -417,12 +435,32 @@ namespace Barotrauma.Items.Components
unsentChanges = true; unsentChanges = true;
correctionTimer = CorrectionDelay; correctionTimer = CorrectionDelay;
} }
return true; return true;
} }
}; };
var mineralScannerSwitchText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1), mineralScannerFrame.RectTransform, Anchor.CenterRight), var mineralScannerSwitchText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1), mineralScannerFrame.RectTransform, Anchor.CenterRight),
TextManager.Get("SonarMineralScanner"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft); TextManager.Get("SonarMineralScanner"), GUIStyle.TextColorNormal, GUIStyle.SubHeadingFont, Alignment.CenterLeft);
textBlocksToScaleAndNormalize.Add(mineralScannerSwitchText); textBlocksToScaleAndNormalize.Add(mineralScannerSwitchText);
PreventMineralScannerOverlap();
}
private void PreventMineralScannerOverlap()
{
if (item.GetComponent<Steering>() is { } steering && controlContainer is { } container)
{
int containerBottom = container.Rect.Y + container.Rect.Height,
steeringTop = steering.ControlContainer.Rect.Top;
int amountRaised = 0;
while (GetContainerBottom() > steeringTop) { amountRaised++; }
container.RectTransform.AbsoluteOffset = new Point(0, -amountRaised);
int GetContainerBottom() => containerBottom - amountRaised;
}
} }
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
@@ -502,7 +540,7 @@ namespace Barotrauma.Items.Components
Vector2.DistanceSquared(sonarView.Rect.Center.ToVector2(), PlayerInput.MousePosition) < Vector2.DistanceSquared(sonarView.Rect.Center.ToVector2(), PlayerInput.MousePosition) <
(sonarView.Rect.Width / 2 * sonarView.Rect.Width / 2); (sonarView.Rect.Width / 2 * sonarView.Rect.Width / 2);
if (AllowUsingMineralScanner && Level.Loaded != null && !Level.Loaded.Generating) if (HasMineralScanner && Level.Loaded != null && !Level.Loaded.Generating)
{ {
if (MineralClusters == null) if (MineralClusters == null)
{ {
@@ -985,7 +1023,7 @@ namespace Barotrauma.Items.Components
missionIndex++; missionIndex++;
} }
if (AllowUsingMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null && if (HasMineralScanner && useMineralScanner && CurrentMode == Mode.Active && MineralClusters != null &&
(item.CurrentHull == null || !DetectSubmarineWalls)) (item.CurrentHull == null || !DetectSubmarineWalls))
{ {
foreach (var c in MineralClusters) foreach (var c in MineralClusters)
@@ -995,6 +1033,19 @@ namespace Barotrauma.Items.Components
if (!CheckResourceMarkerVisibility(c.center, transducerCenter)) { continue; } if (!CheckResourceMarkerVisibility(c.center, transducerCenter)) { continue; }
var i = unobtainedMinerals.FirstOrDefault(); var i = unobtainedMinerals.FirstOrDefault();
if (i == null) { continue; } if (i == null) { continue; }
bool disrupted = false;
foreach ((Vector2 disruptPos, float disruptStrength) in disruptedDirections)
{
float dot = Vector2.Dot(Vector2.Normalize(c.center - transducerCenter), disruptPos);
if (dot > 1.0f - disruptStrength)
{
disrupted = true;
break;
}
}
if (disrupted) { continue; }
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
i.Name, "mineral".ToIdentifier(), "mineralcluster" + i, i.Name, "mineral".ToIdentifier(), "mineralcluster" + i,
c.center, transducerCenter, c.center, transducerCenter,
@@ -1754,17 +1805,17 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
msg.Write(currentMode == Mode.Active); msg.WriteBoolean(currentMode == Mode.Active);
if (currentMode == Mode.Active) if (currentMode == Mode.Active)
{ {
msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8); msg.WriteRangedSingle(zoom, MinZoom, MaxZoom, 8);
msg.Write(useDirectionalPing); msg.WriteBoolean(useDirectionalPing);
if (useDirectionalPing) if (useDirectionalPing)
{ {
float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection)); float pingAngle = MathUtils.WrapAngleTwoPi(MathUtils.VectorToAngle(pingDirection));
msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8); msg.WriteRangedSingle(MathUtils.InverseLerp(0.0f, MathHelper.TwoPi, pingAngle), 0.0f, 1.0f, 8);
} }
msg.Write(useMineralScanner); msg.WriteBoolean(useMineralScanner);
} }
} }
@@ -1,13 +1,11 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using FarseerPhysics; using FarseerPhysics;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using System.Xml.Linq; using System.Linq;
using Barotrauma.Extensions;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -25,7 +23,9 @@ namespace Barotrauma.Items.Components
private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox; private GUITickBox maintainPosTickBox, levelEndTickBox, levelStartTickBox;
private GUIComponent statusContainer, dockingContainer, controlContainer; private GUIComponent statusContainer, dockingContainer;
public GUIComponent ControlContainer { get; private set; }
private bool dockingNetworkMessagePending; private bool dockingNetworkMessagePending;
@@ -90,6 +90,24 @@ namespace Barotrauma.Items.Components
} }
} }
private bool disableControls;
/// <summary>
/// Can be used by status effects to disable all the UI controls
/// </summary>
public bool DisableControls
{
get { return disableControls; }
set
{
if (disableControls == value) { return; }
disableControls = value;
UpdateGUIElements();
}
}
public override bool RecreateGUIOnResolutionChange => true;
partial void InitProjSpecific(ContentXElement element) partial void InitProjSpecific(ContentXElement element)
{ {
foreach (var subElement in element.Elements()) foreach (var subElement in element.Elements())
@@ -112,8 +130,8 @@ namespace Barotrauma.Items.Components
protected override void CreateGUI() protected override void CreateGUI()
{ {
controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterRight), "ItemUI"); ControlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterRight), "ItemUI");
var paddedControlContainer = new GUIFrame(new RectTransform(controlContainer.Rect.Size - GUIStyle.ItemFrameMargin, controlContainer.RectTransform, Anchor.Center) var paddedControlContainer = new GUIFrame(new RectTransform(ControlContainer.Rect.Size - GUIStyle.ItemFrameMargin, ControlContainer.RectTransform, Anchor.Center)
{ {
AbsoluteOffset = GUIStyle.ItemFrameOffset AbsoluteOffset = GUIStyle.ItemFrameOffset
}, style: null); }, style: null);
@@ -121,6 +139,7 @@ namespace Barotrauma.Items.Components
var steeringModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedControlContainer.RectTransform, Anchor.TopLeft), style: null); var steeringModeArea = new GUIFrame(new RectTransform(new Vector2(1, 0.4f), paddedControlContainer.RectTransform, Anchor.TopLeft), style: null);
steeringModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), steeringModeArea.RectTransform), string.Empty, style: "SwitchVertical") steeringModeSwitch = new GUIButton(new RectTransform(new Vector2(0.2f, 1), steeringModeArea.RectTransform), string.Empty, style: "SwitchVertical")
{ {
UserData = UIHighlightAction.ElementId.SteeringModeSwitch,
Selected = autoPilot, Selected = autoPilot,
Enabled = true, Enabled = true,
ClickSound = GUISoundType.UISwitch, ClickSound = GUISoundType.UISwitch,
@@ -162,6 +181,7 @@ namespace Barotrauma.Items.Components
maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter), maintainPosTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.TopCenter),
TextManager.Get("SteeringMaintainPos"), font: GUIStyle.SmallFont, style: "GUIRadioButton") TextManager.Get("SteeringMaintainPos"), font: GUIStyle.SmallFont, style: "GUIRadioButton")
{ {
UserData = UIHighlightAction.ElementId.MaintainPosTickBox,
Enabled = autoPilot, Enabled = autoPilot,
Selected = maintainPos, Selected = maintainPos,
OnSelected = tickBox => OnSelected = tickBox =>
@@ -472,7 +492,6 @@ namespace Barotrauma.Items.Components
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
{ {
base.OnResolutionChanged();
UpdateGUIElements(); UpdateGUIElements();
} }
@@ -658,6 +677,11 @@ namespace Barotrauma.Items.Components
} }
} }
if (DisableControls)
{
dockingModeEnabled = false;
}
dockingContainer.Visible = DockingModeEnabled; dockingContainer.Visible = DockingModeEnabled;
statusContainer.Visible = !DockingModeEnabled; statusContainer.Visible = !DockingModeEnabled;
if (!DockingModeEnabled) if (!DockingModeEnabled)
@@ -745,7 +769,7 @@ namespace Barotrauma.Items.Components
iceSpireWarningText.Visible = item.Submarine != null && !pressureWarningText.Visible && showIceSpireWarning && Timing.TotalTime % 1.0f < 0.8f; iceSpireWarningText.Visible = item.Submarine != null && !pressureWarningText.Visible && showIceSpireWarning && Timing.TotalTime % 1.0f < 0.8f;
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) if (!disableControls && Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
{ {
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen && if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen &&
(!GameMain.GameSession?.Campaign?.ShowCampaignUI ?? true) && !GUIMessageBox.MessageBoxes.Any(msgBox => msgBox is GUIMessageBox { MessageBoxType: GUIMessageBox.Type.Default })) (!GameMain.GameSession?.Campaign?.ShowCampaignUI ?? true) && !GUIMessageBox.MessageBoxes.Any(msgBox => msgBox is GUIMessageBox { MessageBoxType: GUIMessageBox.Type.Default }))
@@ -815,7 +839,7 @@ namespace Barotrauma.Items.Components
keyboardInput = Vector2.Zero; keyboardInput = Vector2.Zero;
} }
if (!UseAutoDocking) { return; } if (!UseAutoDocking || DisableControls) { return; }
if (checkConnectedPortsTimer <= 0.0f) if (checkConnectedPortsTimer <= 0.0f)
{ {
@@ -871,13 +895,11 @@ namespace Barotrauma.Items.Components
posToMaintain = item.Submarine.WorldPosition; posToMaintain = item.Submarine.WorldPosition;
} }
MaintainPos = true; MaintainPos = true;
if (userdata is Vector2) if (userdata is Vector2 nudgeAmount)
{ {
Sonar sonar = item.GetComponent<Sonar>(); if (item.GetComponent<Sonar>() is Sonar sonar)
Vector2 nudgeAmount = (Vector2)userdata;
if (sonar != null)
{ {
nudgeAmount *= sonar == null ? 500.0f : 500.0f / sonar.Zoom; nudgeAmount *= 500.0f / sonar.Zoom;
} }
PosToMaintain += nudgeAmount; PosToMaintain += nudgeAmount;
} }
@@ -896,27 +918,27 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
msg.Write(AutoPilot); msg.WriteBoolean(AutoPilot);
msg.Write(dockingNetworkMessagePending); msg.WriteBoolean(dockingNetworkMessagePending);
dockingNetworkMessagePending = false; dockingNetworkMessagePending = false;
if (!AutoPilot) if (!AutoPilot)
{ {
//no need to write steering info if autopilot is controlling //no need to write steering info if autopilot is controlling
msg.Write(steeringInput.X); msg.WriteSingle(steeringInput.X);
msg.Write(steeringInput.Y); msg.WriteSingle(steeringInput.Y);
} }
else else
{ {
msg.Write(posToMaintain != null); msg.WriteBoolean(posToMaintain != null);
if (posToMaintain != null) if (posToMaintain != null)
{ {
msg.Write(((Vector2)posToMaintain).X); msg.WriteSingle(((Vector2)posToMaintain).X);
msg.Write(((Vector2)posToMaintain).Y); msg.WriteSingle(((Vector2)posToMaintain).Y);
} }
else else
{ {
msg.Write(LevelStartSelected); msg.WriteBoolean(LevelStartSelected);
} }
} }
} }
@@ -1000,9 +1022,20 @@ namespace Barotrauma.Items.Components
steeringModeSwitch.Selected = AutoPilot; steeringModeSwitch.Selected = AutoPilot;
autopilotIndicator.Selected = AutoPilot; autopilotIndicator.Selected = AutoPilot;
manualPilotIndicator.Selected = !AutoPilot; manualPilotIndicator.Selected = !AutoPilot;
maintainPosTickBox.Enabled = AutoPilot; if (DisableControls)
levelEndTickBox.Enabled = AutoPilot; {
levelStartTickBox.Enabled = AutoPilot; steeringModeSwitch.Enabled = false;
maintainPosTickBox.Enabled = false;
levelEndTickBox.Enabled = false;
levelStartTickBox.Enabled = false;
}
else
{
steeringModeSwitch.Enabled = true;
maintainPosTickBox.Enabled = AutoPilot;
levelEndTickBox.Enabled = AutoPilot;
levelStartTickBox.Enabled = AutoPilot;
}
} }
} }
} }
@@ -83,6 +83,7 @@ namespace Barotrauma.Items.Components
} }
}; };
rechargeSpeedSlider.Bar.RectTransform.MaxSize = new Point(rechargeSpeedSlider.Bar.Rect.Height); rechargeSpeedSlider.Bar.RectTransform.MaxSize = new Point(rechargeSpeedSlider.Bar.Rect.Height);
rechargeSpeedSlider.Frame.UserData = UIHighlightAction.ElementId.RechargeSpeedSlider;
// lower area -------------------------- // lower area --------------------------
@@ -1,8 +1,4 @@
using Barotrauma.Sounds; namespace Barotrauma.Items.Components
using System.Collections.Generic;
using System.Xml.Linq;
namespace Barotrauma.Items.Components
{ {
partial class Powered : ItemComponent partial class Powered : ItemComponent
{ {
@@ -22,6 +22,8 @@ namespace Barotrauma.Items.Components
private GUILayoutGroup extraButtonContainer; private GUILayoutGroup extraButtonContainer;
private GUIComponent skillTextContainer;
private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>(); private readonly List<ParticleEmitter> particleEmitters = new List<ParticleEmitter>();
//the corresponding particle emitter is active when the condition is within this range //the corresponding particle emitter is active when the condition is within this range
private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>(); private readonly List<Vector2> particleEmitterConditionRanges = new List<Vector2>();
@@ -59,7 +61,7 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) public override bool ShouldDrawHUD(Character character)
{ {
if (item.HiddenInGame) { return false; } if (item.HiddenInGame) { return false; }
if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; } if (!HasRequiredItems(character, false) || character.SelectedItem != item) { return false; }
if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; } if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier; float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
@@ -110,6 +112,7 @@ namespace Barotrauma.Items.Components
if (GuiFrame != null) if (GuiFrame != null)
{ {
GuiFrame.ClearChildren(); GuiFrame.ClearChildren();
TryCreateDragHandle();
CreateGUI(); CreateGUI();
} }
} }
@@ -131,9 +134,10 @@ namespace Barotrauma.Items.Components
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont); TextManager.Get("RequiredRepairSkills"), font: GUIStyle.SubHeadingFont);
skillTextContainer = paddedFrame;
for (int i = 0; i < requiredSkills.Count; i++) for (int i = 0; i < requiredSkills.Count; i++)
{ {
var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform), var skillText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillTextContainer.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) Math.Round(requiredSkills[i].Level * SkillRequirementMultiplier)).ToString()), " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) Math.Round(requiredSkills[i].Level * SkillRequirementMultiplier)).ToString()),
font: GUIStyle.SmallFont) font: GUIStyle.SmallFont)
{ {
@@ -161,6 +165,7 @@ namespace Barotrauma.Items.Components
repairingText = TextManager.Get("Repairing"); repairingText = TextManager.Get("Repairing");
RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText)
{ {
UserData = UIHighlightAction.ElementId.RepairButton,
OnClicked = (btn, obj) => OnClicked = (btn, obj) =>
{ {
requestStartFixAction = FixActions.Repair; requestStartFixAction = FixActions.Repair;
@@ -265,7 +270,7 @@ namespace Barotrauma.Items.Components
} }
} }
if (CurrentFixer != null && CurrentFixer.SelectedConstruction == item) if (CurrentFixer != null && CurrentFixer.SelectedItem == item)
{ {
if (repairSoundChannel == null || !repairSoundChannel.IsPlaying) if (repairSoundChannel == null || !repairSoundChannel.IsPlaying)
{ {
@@ -353,24 +358,16 @@ namespace Barotrauma.Items.Components
tinkerButtonText : tinkerButtonText :
tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); tinkeringText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts"); //System.Diagnostics.Debug.Assert(GuiFrame.GetChild(0) is GUILayoutGroup, "Repair UI hierarchy has changed, could not find skill texts");
extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible; extraButtonContainer.Visible = SabotageButton.Visible || TinkerButton.Visible;
extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible; extraButtonContainer.IgnoreLayoutGroups = !extraButtonContainer.Visible;
foreach (GUIComponent c in GuiFrame.GetChild(0).Children) foreach (GUIComponent c in skillTextContainer.Children)
{ {
if (!(c.UserData is Skill skill)) continue; if (c.UserData is not Skill skill) { continue; }
GUITextBlock textBlock = (GUITextBlock)c; GUITextBlock textBlock = (GUITextBlock)c;
if (character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier)) textBlock.TextColor = character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier) ? GUIStyle.Red : GUIStyle.TextColorNormal;
{
textBlock.TextColor = GUIStyle.Red;
}
else
{
textBlock.TextColor = Color.White;
}
} }
} }
@@ -450,7 +447,7 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
msg.WriteRangedInteger((int)requestStartFixAction, 0, 2); msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
msg.Write(qteSuccess); msg.WriteBoolean(qteSuccess);
} }
} }
} }
@@ -94,7 +94,6 @@ namespace Barotrauma.Items.Components
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
{ {
base.OnResolutionChanged();
OnItemLoadedProjSpecific(); OnItemLoadedProjSpecific();
} }
@@ -21,12 +21,13 @@ namespace Barotrauma.Items.Components
public float FlashTimer { get; private set; } public float FlashTimer { get; private set; }
public static Wire DraggingConnected { get; private set; } public static Wire DraggingConnected { get; private set; }
public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Character character) public static void DrawConnections(SpriteBatch spriteBatch, ConnectionPanel panel, Rectangle dragArea, Character character)
{ {
if (DraggingConnected?.Item?.Removed ?? true) if (DraggingConnected?.Item?.Removed ?? true)
{ {
DraggingConnected = null; DraggingConnected = null;
} }
Rectangle panelRect = panel.GuiFrame.Rect; Rectangle panelRect = panel.GuiFrame.Rect;
int x = panelRect.X, y = panelRect.Y; int x = panelRect.X, y = panelRect.Y;
int width = panelRect.Width, height = panelRect.Height; int width = panelRect.Width, height = panelRect.Height;
@@ -131,7 +132,10 @@ namespace Barotrauma.Items.Components
{ {
if (mouseInRect) if (mouseInRect)
{ {
DrawWire(spriteBatch, DraggingConnected, PlayerInput.MousePosition, new Vector2(x + width / 2, y + height - 10), null, panel, ""); Vector2 wireDragPos = new Vector2(
MathHelper.Clamp(PlayerInput.MousePosition.X, dragArea.X, dragArea.Right),
MathHelper.Clamp(PlayerInput.MousePosition.Y, dragArea.Y, dragArea.Bottom));
DrawWire(spriteBatch, DraggingConnected, wireDragPos, new Vector2(x + width / 2, y + height - 10), null, panel, "");
} }
panel.TriggerRewiringSound(); panel.TriggerRewiringSound();
@@ -317,6 +321,7 @@ namespace Barotrauma.Items.Components
bool mouseOn = bool mouseOn =
canDrag && canDrag &&
!(GUI.MouseOn is GUIDragHandle) &&
((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) && ((PlayerInput.MousePosition.X > Math.Min(start.X, end.X) &&
PlayerInput.MousePosition.X < Math.Max(start.X, end.X) && PlayerInput.MousePosition.X < Math.Max(start.X, end.X) &&
MathUtils.LineToPointDistanceSquared(start, end, PlayerInput.MousePosition) < 36) || MathUtils.LineToPointDistanceSquared(start, end, PlayerInput.MousePosition) < 36) ||
@@ -5,7 +5,6 @@ using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -27,16 +26,25 @@ namespace Barotrauma.Items.Components
private Point originalMaxSize; private Point originalMaxSize;
private Vector2 originalRelativeSize; private Vector2 originalRelativeSize;
private GUIComponent dragArea;
public override bool RecreateGUIOnResolutionChange => true;
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
if (GuiFrame == null) { return; } if (GuiFrame == null) { return; }
originalMaxSize = GuiFrame.RectTransform.MaxSize; originalMaxSize = GuiFrame.RectTransform.MaxSize;
originalRelativeSize = GuiFrame.RectTransform.RelativeSize; originalRelativeSize = GuiFrame.RectTransform.RelativeSize;
CheckForLabelOverlap(); CheckForLabelOverlap();
new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null) var content = new GUICustomComponent(new RectTransform(Vector2.One, GuiFrame.RectTransform), DrawConnections, null)
{ {
UserData = this UserData = this
}; };
content.RectTransform.SetAsFirstChild();
//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)
{ AbsoluteOffset = GUIStyle.ItemFrameOffset }, style: null);
} }
public void TriggerRewiringSound() public void TriggerRewiringSound()
@@ -62,7 +70,7 @@ namespace Barotrauma.Items.Components
} }
rewireSoundTimer -= deltaTime; rewireSoundTimer -= deltaTime;
if (user != null && user.SelectedConstruction == item && rewireSoundTimer > 0.0f) if (user != null && user.SelectedItem == item && rewireSoundTimer > 0.0f)
{ {
if (rewireSoundChannel == null || !rewireSoundChannel.IsPlaying) if (rewireSoundChannel == null || !rewireSoundChannel.IsPlaying)
{ {
@@ -85,12 +93,12 @@ namespace Barotrauma.Items.Components
public override bool ShouldDrawHUD(Character character) public override bool ShouldDrawHUD(Character character)
{ {
return character == Character.Controlled && character == user && character.SelectedConstruction == item; return character == Character.Controlled && character == user && character.SelectedItem == item;
} }
public override void UpdateHUD(Character character, float deltaTime, Camera cam) public override void UpdateHUD(Character character, float deltaTime, Camera cam)
{ {
if (character != Character.Controlled || character != user || character.SelectedConstruction != item) { return; } if (character != Character.Controlled || character != user || character.SelectedItem != item) { return; }
if (HighlightedWire != null) if (HighlightedWire != null)
{ {
@@ -105,7 +113,7 @@ namespace Barotrauma.Items.Components
if (user != Character.Controlled || user == null) { return; } if (user != Character.Controlled || user == null) { return; }
HighlightedWire = null; HighlightedWire = null;
Connection.DrawConnections(spriteBatch, this, user); Connection.DrawConnections(spriteBatch, this, dragArea.Rect, user);
foreach (UISprite sprite in GUIStyle.GetComponentStyle("ConnectionPanelFront").Sprites[GUIComponent.ComponentState.None]) foreach (UISprite sprite in GUIStyle.GetComponentStyle("ConnectionPanelFront").Sprites[GUIComponent.ComponentState.None])
{ {
@@ -115,7 +123,6 @@ namespace Barotrauma.Items.Components
protected override void OnResolutionChanged() protected override void OnResolutionChanged()
{ {
base.OnResolutionChanged();
if (GuiFrame == null) { return; } if (GuiFrame == null) { return; }
CheckForLabelOverlap(); CheckForLabelOverlap();
} }
@@ -14,6 +14,8 @@ namespace Barotrauma.Items.Components
private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale)); private Point ElementMaxSize => new Point(uiElementContainer.Rect.Width, (int)(65 * GUI.yScale));
public override bool RecreateGUIOnResolutionChange => true;
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
CreateGUI(); CreateGUI();
@@ -133,7 +135,7 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
DebugConsole.ShowError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\""); DebugConsole.LogError($"Error creating a CustomInterface component: unexpected NumberType \"{(ciElement.NumberType.HasValue ? ciElement.NumberType.Value.ToString() : "none")}\"");
} }
if (numberInput != null) if (numberInput != null)
{ {
@@ -354,29 +356,29 @@ namespace Barotrauma.Items.Components
{ {
if (!element.IsNumberInput) if (!element.IsNumberInput)
{ {
msg.Write(((GUITextBox)uiElements[i]).Text); msg.WriteString(((GUITextBox)uiElements[i]).Text);
} }
else else
{ {
switch (element.NumberType) switch (element.NumberType)
{ {
case NumberType.Float: case NumberType.Float:
msg.Write(((GUINumberInput)uiElements[i]).FloatValue.ToString()); msg.WriteString(((GUINumberInput)uiElements[i]).FloatValue.ToString());
break; break;
case NumberType.Int: case NumberType.Int:
default: default:
msg.Write(((GUINumberInput)uiElements[i]).IntValue.ToString()); msg.WriteString(((GUINumberInput)uiElements[i]).IntValue.ToString());
break; break;
} }
} }
} }
else if (element.ContinuousSignal) else if (element.ContinuousSignal)
{ {
msg.Write(((GUITickBox)uiElements[i]).Selected); msg.WriteBoolean(((GUITickBox)uiElements[i]).Selected);
} }
else else
{ {
msg.Write(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]); msg.WriteBoolean(extraData is Item.ComponentStateEventData { ComponentData: EventData eventData } && eventData.BtnElement == customInterfaceElementList[i]);
} }
} }
} }
@@ -149,7 +149,7 @@ namespace Barotrauma.Items.Components
{ {
if (TryExtractEventData(extraData, out ClientEventData eventData)) if (TryExtractEventData(extraData, out ClientEventData eventData))
{ {
msg.Write(eventData.Text); msg.WriteString(eventData.Text);
} }
} }
@@ -311,7 +311,7 @@ namespace Barotrauma.Items.Components
Wire equippedWire = Character.Controlled.HeldItems.FirstOrDefault(it => it.GetComponent<Wire>() != null)?.GetComponent<Wire>(); Wire equippedWire = Character.Controlled.HeldItems.FirstOrDefault(it => it.GetComponent<Wire>() != null)?.GetComponent<Wire>();
if (equippedWire != null && GUI.MouseOn == null) if (equippedWire != null && GUI.MouseOn == null)
{ {
if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedConstruction == null) if (PlayerInput.PrimaryMouseButtonClicked() && Character.Controlled.SelectedItem == null)
{ {
equippedWire.Use(1.0f, Character.Controlled); equippedWire.Use(1.0f, Character.Controlled);
} }
@@ -603,11 +603,11 @@ namespace Barotrauma.Items.Components
{ {
var eventData = ExtractEventData<ClientEventData>(extraData); var eventData = ExtractEventData<ClientEventData>(extraData);
int nodeCount = eventData.NodeCount; int nodeCount = eventData.NodeCount;
msg.Write((byte)nodeCount); msg.WriteByte((byte)nodeCount);
if (nodeCount > 0) if (nodeCount > 0)
{ {
msg.Write(nodes.Last().X); msg.WriteSingle(nodes.Last().X);
msg.Write(nodes.Last().Y); msg.WriteSingle(nodes.Last().Y);
} }
} }
} }
@@ -5,9 +5,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -94,14 +92,6 @@ namespace Barotrauma.Items.Components
private set; private set;
} }
[Serialize(false, IsPropertySaveable.No, description: "Use firing offset for muzzleflash? This field shouldn't be needed but I'm using it for prototyping")]
public bool UseFiringOffsetForMuzzleFlash
{
get;
private set;
}
public Vector2 DrawSize public Vector2 DrawSize
{ {
get get
@@ -188,7 +178,7 @@ namespace Barotrauma.Items.Components
recoilTimer /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed); recoilTimer /= 1 + user.GetStatValue(StatTypes.TurretAttackSpeed);
} }
PlaySound(ActionType.OnUse); PlaySound(ActionType.OnUse);
Vector2 particlePos = GetRelativeFiringPosition(UseFiringOffsetForMuzzleFlash); Vector2 particlePos = GetRelativeFiringPosition();
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
{ {
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation); emitter.Emit(1.0f, particlePos, hullGuess: null, angle: -rotation, particleRotation: rotation);
@@ -248,7 +238,6 @@ namespace Barotrauma.Items.Components
{ {
moveSoundChannel.FadeOutAndDispose(); moveSoundChannel.FadeOutAndDispose();
moveSoundChannel = null; moveSoundChannel = null;
} }
} }
} }
@@ -26,7 +26,7 @@ namespace Barotrauma.Items.Components
{ {
foreach (DamageModifier damageModifier in damageModifiers) foreach (DamageModifier damageModifier in damageModifiers)
{ {
if (MathUtils.NearlyEqual(damageModifier.DamageMultiplier, 1f)) if (MathUtils.NearlyEqual(damageModifier.DamageMultiplier * damageModifier.ProbabilityMultiplier, 1f))
{ {
continue; continue;
} }
@@ -185,7 +185,7 @@ namespace Barotrauma.Items.Components
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null) public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData = null)
{ {
msg.Write((byte)allowOutpostAutoDocking); msg.WriteByte((byte)allowOutpostAutoDocking);
} }
} }
} }
@@ -364,7 +364,7 @@ namespace Barotrauma
get get
{ {
return Character.Controlled != null && return Character.Controlled != null &&
Character.Controlled.SelectedConstruction == null && !Character.Controlled.HasSelectedAnyItem &&
CharacterHealth.OpenHealthWindow == null && CharacterHealth.OpenHealthWindow == null &&
DraggingItems.Any(); DraggingItems.Any();
} }
@@ -924,9 +924,9 @@ namespace Barotrauma
} }
} }
if (Character.Controlled.SelectedConstruction != null) if (Character.Controlled.SelectedItem != null)
{ {
foreach (var ic in Character.Controlled.SelectedConstruction.ActiveHUDs) foreach (var ic in Character.Controlled.SelectedItem.ActiveHUDs)
{ {
var itemContainer = ic as ItemContainer; var itemContainer = ic as ItemContainer;
if (itemContainer?.Inventory?.visualSlots == null) { continue; } if (itemContainer?.Inventory?.visualSlots == null) { continue; }
@@ -1003,9 +1003,9 @@ namespace Barotrauma
} }
} }
if (character.SelectedConstruction != null) if (character.SelectedItem != null)
{ {
foreach (var ic in character.SelectedConstruction.ActiveHUDs) foreach (var ic in character.SelectedItem.ActiveHUDs)
{ {
var itemContainer = ic as ItemContainer; var itemContainer = ic as ItemContainer;
if (itemContainer?.Inventory?.visualSlots == null) { continue; } if (itemContainer?.Inventory?.visualSlots == null) { continue; }
@@ -1147,15 +1147,16 @@ namespace Barotrauma
{ {
Character.Controlled.ClearInputs(); Character.Controlled.ClearInputs();
bool mouseOnPortrait = CharacterHUD.MouseOnCharacterPortrait();
if (!DetermineMouseOnInventory(ignoreDraggedItem: true) && if (!DetermineMouseOnInventory(ignoreDraggedItem: true) &&
CharacterHealth.OpenHealthWindow != null) (CharacterHealth.OpenHealthWindow != null || mouseOnPortrait))
{ {
bool dropSuccessful = false; bool dropSuccessful = false;
foreach (Item item in DraggingItems) foreach (Item item in DraggingItems)
{ {
var inventory = item.ParentInventory; var inventory = item.ParentInventory;
var indices = inventory?.FindIndices(item); var indices = inventory?.FindIndices(item);
dropSuccessful |= CharacterHealth.OpenHealthWindow.OnItemDropped(item, false); dropSuccessful |= (CharacterHealth.OpenHealthWindow ?? Character.Controlled.CharacterHealth).OnItemDropped(item, ignoreMousePos: mouseOnPortrait);
if (dropSuccessful) if (dropSuccessful)
{ {
if (indices != null && inventory.visualSlots != null) if (indices != null && inventory.visualSlots != null)
@@ -1167,7 +1168,6 @@ namespace Barotrauma
} }
break; break;
} }
} }
if (dropSuccessful) if (dropSuccessful)
{ {
@@ -1341,27 +1341,29 @@ namespace Barotrauma
} }
else else
{ {
var rootOwner = (selectedSlot.ParentInventory?.Owner as Item)?.GetRootInventoryOwner(); static bool OwnerInaccessible(Entity owner) =>
if (selectedSlot.ParentInventory?.Owner != Character.Controlled && owner != Character.Controlled &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedCharacter && owner != Character.Controlled.SelectedCharacter &&
selectedSlot.ParentInventory?.Owner != Character.Controlled.SelectedConstruction && owner != Character.Controlled.SelectedItem &&
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(selectedSlot.ParentInventory?.Owner) ?? false) && (Character.Controlled.SelectedItem == null || !Character.Controlled.SelectedItem.linkedTo.Contains(owner));
rootOwner != Character.Controlled &&
rootOwner != Character.Controlled.SelectedCharacter && Entity owner = selectedSlot.ParentInventory?.Owner;
rootOwner != Character.Controlled.SelectedConstruction && Entity rootOwner = (owner as Item)?.GetRootInventoryOwner();
!(Character.Controlled.SelectedConstruction?.linkedTo.Contains(rootOwner) ?? false)) if (OwnerInaccessible(owner) && (rootOwner == owner || OwnerInaccessible(rootOwner)))
{ {
return false; return false;
} }
var parentItem = (selectedSlot?.ParentInventory?.Owner as Item) ?? selectedSlot?.Item; Item parentItem = (owner as Item) ?? selectedSlot?.Item;
if ((parentItem?.GetRootInventoryOwner() is Character ownerCharacter) && if (parentItem?.GetRootInventoryOwner() is Character ownerCharacter)
ownerCharacter == Character.Controlled &&
CharacterHealth.OpenHealthWindow?.Character != ownerCharacter &&
ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface) &&
Screen.Selected != GameMain.SubEditorScreen)
{ {
highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem); if (ownerCharacter == Character.Controlled &&
return false; CharacterHealth.OpenHealthWindow?.Character != ownerCharacter &&
ownerCharacter.Inventory.IsInLimbSlot(parentItem, InvSlotType.HealthInterface) &&
Screen.Selected != GameMain.SubEditorScreen)
{
highlightedSubInventorySlots.RemoveWhere(s => s.Item == parentItem);
return false;
}
} }
} }
return true; return true;
@@ -1442,7 +1444,10 @@ namespace Barotrauma
float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f); float scale = Math.Min(Math.Min(iconSize / sprite.size.X, iconSize / sprite.size.Y), 1.5f);
Vector2 itemPos = PlayerInput.MousePosition; Vector2 itemPos = PlayerInput.MousePosition;
bool mouseOnHealthInterface = CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement && DraggingItems.Any(it => it.UseInHealthInterface); bool mouseOnHealthInterface =
(CharacterHealth.OpenHealthWindow != null && CharacterHealth.OpenHealthWindow.MouseOnElement)||
CharacterHUD.MouseOnCharacterPortrait();
mouseOnHealthInterface = mouseOnHealthInterface && DraggingItems.Any(it => it.UseInHealthInterface);
if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null) if ((GUI.MouseOn == null || mouseOnHealthInterface) && selectedSlot == null)
{ {
@@ -1451,13 +1456,25 @@ namespace Barotrauma
Character.Controlled.FocusedItem != null ? Character.Controlled.FocusedItem != null ?
TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, FormatCapitals.Yes) : TextManager.GetWithVariable("PutItemIn", "[itemname]", Character.Controlled.FocusedItem.Name, FormatCapitals.Yes) :
TextManager.Get(Screen.Selected is SubEditorScreen editor && editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition) ? "Delete" : "DropItem"); TextManager.Get(Screen.Selected is SubEditorScreen editor && editor.EntityMenu.Rect.Contains(PlayerInput.MousePosition) ? "Delete" : "DropItem");
int textWidth = (int)Math.Max(GUIStyle.Font.MeasureString(DraggingItems.First().Name).X, GUIStyle.SmallFont.MeasureString(toolTip).X);
Vector2 nameSize = GUIStyle.Font.MeasureString(DraggingItems.First().Name);
Vector2 toolTipSize = GUIStyle.SmallFont.MeasureString(toolTip);
int textWidth = (int)Math.Max(nameSize.X, toolTipSize.X);
int textSpacing = (int)(15 * GUI.Scale); int textSpacing = (int)(15 * GUI.Scale);
Point shadowBorders = (new Point(40, 10)).Multiply(GUI.Scale);
Vector2 textPos = itemPos;
int textDir = textPos.X + textWidth * 1.5f > GameMain.GraphicsWidth ? -1 : 1;
int textOffset = textDir == 1 ? 0 : -1;
textPos += new Vector2((iconSize / 2 + textSpacing) * textDir, 0);
Point shadowPadding = new Point(40, 20).Multiply(GUI.Scale);
Point shadowSize = new Point(iconSize + textWidth + textSpacing, iconSize) + shadowPadding.Multiply(2);
shadowSprite.Draw(spriteBatch, shadowSprite.Draw(spriteBatch,
new Rectangle(itemPos.ToPoint() - new Point(iconSize / 2) - shadowBorders, new Point(iconSize + textWidth + textSpacing, iconSize) + shadowBorders.Multiply(2)), Color.Black * 0.8f); new Rectangle(itemPos.ToPoint() - new Point((iconSize / 2 - shadowPadding.X) * textDir - shadowSize.X * textOffset, iconSize / 2 + shadowPadding.Y), shadowSize), Color.Black * 0.8f);
GUI.DrawString(spriteBatch, new Vector2(itemPos.X + iconSize / 2 + textSpacing, itemPos.Y - iconSize / 2), DraggingItems.First().Name, Color.White);
GUI.DrawString(spriteBatch, new Vector2(itemPos.X + iconSize / 2 + textSpacing, itemPos.Y), toolTip, GUI.DrawString(spriteBatch, textPos + new Vector2(nameSize.X * textOffset, -iconSize / 2), DraggingItems.First().Name, Color.White);
GUI.DrawString(spriteBatch, textPos + new Vector2(toolTipSize.X * textOffset, 0), toolTip,
color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? GUIStyle.Red : Color.LightGreen, color: Character.Controlled.FocusedItem == null && !mouseOnHealthInterface ? GUIStyle.Red : Color.LightGreen,
font: GUIStyle.SmallFont); font: GUIStyle.SmallFont);
} }
@@ -1725,7 +1742,8 @@ namespace Barotrauma
if (inventory != null && if (inventory != null &&
!inventory.Locked && !inventory.Locked &&
Character.Controlled?.Inventory == inventory && Character.Controlled?.Inventory == inventory &&
slot.InventoryKeyIndex != -1) slot.InventoryKeyIndex != -1 &&
slot.InventoryKeyIndex < GameSettings.CurrentConfig.InventoryKeyMap.Bindings.Length)
{ {
spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor); spriteBatch.Draw(slotHotkeySprite.Texture, rect.ScaleSize(1.15f), slotHotkeySprite.SourceRect, slotColor);
GUI.DrawString(spriteBatch, rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)), GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name, Color.Black, font: GUIStyle.HotkeyFont); GUI.DrawString(spriteBatch, rect.Location.ToVector2() + new Vector2((int)(4.25f * UIScale), (int)Math.Ceiling(-1.5f * UIScale)), GameSettings.CurrentConfig.InventoryKeyMap.Bindings[slot.InventoryKeyIndex].Name, Color.Black, font: GUIStyle.HotkeyFont);
@@ -118,7 +118,7 @@ namespace Barotrauma
return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite); return GetDrawDepth(SpriteDepth + DrawDepthOffset, Sprite);
} }
public Color GetSpriteColor() public Color GetSpriteColor(bool withHighlight = false)
{ {
Color color = spriteColor; Color color = spriteColor;
if (Prefab.UseContainedSpriteColor && ownInventory != null) if (Prefab.UseContainedSpriteColor && ownInventory != null)
@@ -129,6 +129,17 @@ namespace Barotrauma
break; break;
} }
} }
if (withHighlight)
{
if (IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen)
{
color = GUIStyle.Orange * Math.Max(GetSpriteColor().A / (float)byte.MaxValue, 0.1f);
}
else if (IsHighlighted && HighlightColor.HasValue)
{
color = Color.Lerp(color, HighlightColor.Value, (MathF.Sin((float)Timing.TotalTime * 3.0f) + 1.0f) / 2.0f);
}
}
return color; return color;
} }
@@ -281,9 +292,7 @@ namespace Barotrauma
else if (!ShowItems) { return; } else if (!ShowItems) { return; }
} }
Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : IsHighlighted && !GUI.DisableItemHighlights && Screen.Selected != GameMain.GameScreen ? GUIStyle.Orange * Math.Max(GetSpriteColor().A / (float) byte.MaxValue, 0.1f) : GetSpriteColor(); Color color = IsIncludedInSelection && editing ? GUIStyle.Blue : GetSpriteColor(withHighlight: true);
//if (IsSelected && editing) color = Color.Lerp(color, Color.Gold, 0.5f);
bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null; bool isWiringMode = editing && SubEditorScreen.TransparentWiringMode && SubEditorScreen.IsWiringMode() && !isWire && parentInventory == null;
bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null; bool renderTransparent = isWiringMode && GetComponent<ConnectionPanel>() == null;
@@ -569,6 +578,39 @@ namespace Barotrauma
} }
} }
partial void Splash()
{
if (body == null || CurrentHull == null) { return; }
//create a splash particle
float massFactor = MathHelper.Clamp(body.Mass, 0.5f, 20.0f);
for (int i = 0; i < MathHelper.Clamp(Math.Abs(body.LinearVelocity.Y), 1.0f, 10.0f); i++)
{
var splash = GameMain.ParticleManager.CreateParticle("watersplash",
new Vector2(WorldPosition.X, CurrentHull.WorldSurface),
new Vector2(0.0f, Math.Abs(-body.LinearVelocity.Y * massFactor)) + Rand.Vector(Math.Abs(body.LinearVelocity.Y * 10)),
Rand.Range(0.0f, MathHelper.TwoPi), CurrentHull);
if (splash != null)
{
splash.Size *= MathHelper.Clamp(Math.Abs(body.LinearVelocity.Y) * 0.1f * massFactor, 1.0f, 4.0f);
}
}
GameMain.ParticleManager.CreateParticle("bubbles",
new Vector2(WorldPosition.X, CurrentHull.WorldSurface),
body.LinearVelocity * massFactor,
0.0f, CurrentHull);
//create a wave
if (body.LinearVelocity.Y < 0.0f)
{
int n = (int)((Position.X - CurrentHull.Rect.X) / Hull.WaveWidth);
if (n >= 0 && n < currentHull.WaveVel.Length)
{
CurrentHull.WaveVel[n] += MathHelper.Clamp(body.LinearVelocity.Y * massFactor, -5.0f, 5.0f);
}
}
SoundPlayer.PlaySplashSound(WorldPosition, Math.Abs(body.LinearVelocity.Y) + Rand.Range(-10.0f, -5.0f));
}
public void CheckNeedsSoundUpdate(ItemComponent ic) public void CheckNeedsSoundUpdate(ItemComponent ic)
{ {
if (ic.NeedsSoundUpdate()) if (ic.NeedsSoundUpdate())
@@ -790,7 +832,7 @@ namespace Barotrauma
reloadTextureButton.OnClicked += (button, data) => reloadTextureButton.OnClicked += (button, data) =>
{ {
Sprite.ReloadXML(); Sprite.ReloadXML();
Sprite.ReloadTexture(updateAllSprites: true); Sprite.ReloadTexture();
return true; return true;
}; };
} }
@@ -976,13 +1018,13 @@ namespace Barotrauma
/// <summary> /// <summary>
/// Reposition currently active item interfaces to make sure they don't overlap with each other /// Reposition currently active item interfaces to make sure they don't overlap with each other
/// </summary> /// </summary>
private void SetHUDLayout() private void SetHUDLayout(bool ignoreLocking = false)
{ {
//reset positions first //reset positions first
List<GUIComponent> elementsToMove = new List<GUIComponent>(); List<GUIComponent> elementsToMove = new List<GUIComponent>();
if (editingHUD != null && editingHUD.UserData == this && if (editingHUD != null && editingHUD.UserData == this &&
((HasInGameEditableProperties && Character.Controlled?.SelectedConstruction == this) || Screen.Selected == GameMain.SubEditorScreen)) ((HasInGameEditableProperties && Character.Controlled?.SelectedItem == this) || Screen.Selected == GameMain.SubEditorScreen))
{ {
elementsToMove.Add(editingHUD); elementsToMove.Add(editingHUD);
} }
@@ -991,6 +1033,7 @@ namespace Barotrauma
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
{ {
if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; } if (ic.GuiFrame == null || ic.AllowUIOverlap || ic.GetLinkUIToComponent() != null) { continue; }
if (!ignoreLocking && ic.LockGuiFramePosition) { continue; }
//if the frame covers nearly all of the screen, don't trying to prevent overlaps because it'd fail anyway //if the frame covers nearly all of the screen, don't trying to prevent overlaps because it'd fail anyway
if (ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f) { continue; } if (ic.GuiFrame.Rect.Width >= GameMain.GraphicsWidth * 0.9f && ic.GuiFrame.Rect.Height >= GameMain.GraphicsHeight * 0.9f) { continue; }
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero; ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
@@ -1015,11 +1058,7 @@ namespace Barotrauma
disallowedAreas.Add(editor.ToggleEntityMenuButton.Rect); disallowedAreas.Add(editor.ToggleEntityMenuButton.Rect);
} }
GUI.PreventElementOverlap(elementsToMove, disallowedAreas, GUI.PreventElementOverlap(elementsToMove, disallowedAreas, clampArea: HUDLayoutSettings.ItemHUDArea);
new Rectangle(
0, 20,
GameMain.GraphicsWidth,
HUDLayoutSettings.InventoryTopY > 0 ? HUDLayoutSettings.InventoryTopY - 40 : GameMain.GraphicsHeight - 80));
//System.Diagnostics.Debug.WriteLine("after: " + elementsToMove[0].Rect.ToString() + " " + elementsToMove[1].Rect.ToString()); //System.Diagnostics.Debug.WriteLine("after: " + elementsToMove[0].Rect.ToString() + " " + elementsToMove[1].Rect.ToString());
foreach (ItemComponent ic in activeHUDs) foreach (ItemComponent ic in activeHUDs)
@@ -1042,7 +1081,7 @@ namespace Barotrauma
public void UpdateHUD(Camera cam, Character character, float deltaTime) public void UpdateHUD(Camera cam, Character character, float deltaTime)
{ {
bool editingHUDCreated = false; bool editingHUDCreated = false;
if ((HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) || if ((HasInGameEditableProperties && (character.SelectedItem == this || EditableWhenEquipped)) ||
Screen.Selected == GameMain.SubEditorScreen) Screen.Selected == GameMain.SubEditorScreen)
{ {
GUIComponent prevEditingHUD = editingHUD; GUIComponent prevEditingHUD = editingHUD;
@@ -1126,7 +1165,7 @@ namespace Barotrauma
foreach (Character otherCharacter in Character.CharacterList) foreach (Character otherCharacter in Character.CharacterList)
{ {
if (otherCharacter != character && if (otherCharacter != character &&
otherCharacter.SelectedConstruction == this) otherCharacter.SelectedItem == this)
{ {
ItemInUseWarning.Visible = true; ItemInUseWarning.Visible = true;
if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); } if (mergedHUDRect.Width > GameMain.GraphicsWidth / 2) { mergedHUDRect.Inflate(-GameMain.GraphicsWidth / 4, 0); }
@@ -1145,7 +1184,7 @@ namespace Barotrauma
public void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character) public void DrawHUD(SpriteBatch spriteBatch, Camera cam, Character character)
{ {
if (HasInGameEditableProperties && (character.SelectedConstruction == this || EditableWhenEquipped)) if (HasInGameEditableProperties && (character.SelectedItem == this || EditableWhenEquipped))
{ {
DrawEditing(spriteBatch, cam); DrawEditing(spriteBatch, cam);
} }
@@ -1215,6 +1254,7 @@ namespace Barotrauma
if (ic.DisplayMsg.IsNullOrEmpty()) { continue; } 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; }
Color color = Color.Gray; Color color = Color.Gray;
if (ic.HasRequiredItems(character, false)) if (ic.HasRequiredItems(character, false))
@@ -1238,6 +1278,24 @@ namespace Barotrauma
return texts; return texts;
} }
public void ForceHUDLayoutUpdate(bool ignoreLocking = false)
{
foreach (ItemComponent ic in activeHUDs)
{
if (ic.GuiFrame == null || !ic.CanBeSelected) { continue; }
ic.GuiFrame.RectTransform.ScreenSpaceOffset = Point.Zero;
if (ic.UseAlternativeLayout)
{
ic.AlternativeLayout?.ApplyTo(ic.GuiFrame.RectTransform);
}
else
{
ic.DefaultLayout?.ApplyTo(ic.GuiFrame.RectTransform);
}
}
SetHUDLayout(ignoreLocking);
}
public override void AddToGUIUpdateList(int order = 0) public override void AddToGUIUpdateList(int order = 0)
{ {
if (Screen.Selected is SubEditorScreen) if (Screen.Selected is SubEditorScreen)
@@ -1246,15 +1304,15 @@ namespace Barotrauma
} }
else else
{ {
if (HasInGameEditableProperties && Character.Controlled != null && (Character.Controlled.SelectedConstruction == this || EditableWhenEquipped)) if (HasInGameEditableProperties && Character.Controlled != null && (Character.Controlled.SelectedItem == this || EditableWhenEquipped))
{ {
if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(); } if (editingHUD != null && editingHUD.UserData == this) { editingHUD.AddToGUIUpdateList(); }
} }
} }
if (Character.Controlled != null && Character.Controlled.SelectedConstruction != this && GetComponent<RemoteController>() == null) if (Character.Controlled != null && Character.Controlled.SelectedItem != this && GetComponent<RemoteController>() == null)
{ {
if (Character.Controlled.SelectedConstruction?.GetComponent<RemoteController>()?.TargetItem != this && if (Character.Controlled.SelectedItem?.GetComponent<RemoteController>()?.TargetItem != this &&
!Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this)) !Character.Controlled.HeldItems.Any(it => it.GetComponent<RemoteController>()?.TargetItem == this))
{ {
return; return;
@@ -1423,8 +1481,8 @@ namespace Barotrauma
case TreatmentEventData treatmentEventData: case TreatmentEventData treatmentEventData:
Character targetCharacter = treatmentEventData.TargetCharacter; Character targetCharacter = treatmentEventData.TargetCharacter;
msg.Write(targetCharacter.ID); msg.WriteUInt16(targetCharacter.ID);
msg.Write(treatmentEventData.LimbIndex); msg.WriteByte(treatmentEventData.LimbIndex);
break; break;
case ChangePropertyEventData changePropertyEventData: case ChangePropertyEventData changePropertyEventData:
WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: true); WritePropertyChange(msg, changePropertyEventData, inGameEditableOnly: true);
@@ -1432,7 +1490,7 @@ namespace Barotrauma
break; break;
case CombineEventData combineEventData: case CombineEventData combineEventData:
Item combineTarget = combineEventData.CombineTarget; Item combineTarget = combineEventData.CombineTarget;
msg.Write(combineTarget.ID); msg.WriteUInt16(combineTarget.ID);
break; break;
default: default:
throw error($"Unsupported event type {eventData.GetType().Name}"); throw error($"Unsupported event type {eventData.GetType().Name}");
@@ -1,5 +1,4 @@
using System; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
@@ -17,7 +16,7 @@ namespace Barotrauma
{ {
if (PlayerInput.KeyHit(InputType.Select)) if (PlayerInput.KeyHit(InputType.Select))
{ {
Character.Controlled.SelectedConstruction = null; Character.Controlled.SelectedItem = null;
} }
}*/ }*/
} }
@@ -222,13 +222,14 @@ namespace Barotrauma
if (!ResizeHorizontal && !ResizeVertical) if (!ResizeHorizontal && !ResizeVertical)
{ {
if (PlayerInput.PrimaryMouseButtonClicked()) if (PlayerInput.PrimaryMouseButtonClicked() && GUI.MouseOn == null)
{ {
var item = new Item(new Rectangle((int)position.X, (int)position.Y, (int)(Sprite.size.X * Scale), (int)(Sprite.size.Y * Scale)), this, Submarine.MainSub) var item = new Item(new Rectangle((int)position.X, (int)position.Y, (int)(Sprite.size.X * Scale), (int)(Sprite.size.Y * Scale)), this, Submarine.MainSub)
{ {
Submarine = Submarine.MainSub Submarine = Submarine.MainSub
}; };
item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f); item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
item.GetComponent<Items.Components.Door>()?.RefreshLinkedGap();
item.FindHull(); item.FindHull();
item.Submarine = Submarine.MainSub; item.Submarine = Submarine.MainSub;
@@ -252,7 +253,7 @@ namespace Barotrauma
if (placePosition == Vector2.Zero) if (placePosition == Vector2.Zero)
{ {
if (PlayerInput.PrimaryMouseButtonHeld()) placePosition = position; if (PlayerInput.PrimaryMouseButtonHeld() && GUI.MouseOn == null) { placePosition = position; }
} }
else else
{ {
@@ -270,11 +271,10 @@ namespace Barotrauma
item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f); item.SetTransform(ConvertUnits.ToSimUnits(Submarine.MainSub == null ? item.Position : item.Position - Submarine.MainSub.Position), 0.0f);
item.FindHull(); item.FindHull();
//selected = null; SubEditorScreen.StoreCommand(new AddOrDeleteCommand(new List<MapEntity> { item }, false));
return; return;
} }
position = placePosition;
} }
} }
@@ -282,22 +282,12 @@ namespace Barotrauma
{ {
potentialContainer.IsHighlighted = true; potentialContainer.IsHighlighted = true;
} }
//if (PlayerInput.GetMouseState.RightButton == ButtonState.Pressed) selected = null;
} }
public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam) public override void DrawPlacing(SpriteBatch spriteBatch, Camera cam)
{ {
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub); Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
if (PlayerInput.SecondaryMouseButtonClicked())
{
Selected = null;
return;
}
if (!ResizeHorizontal && !ResizeVertical) if (!ResizeHorizontal && !ResizeVertical)
{ {
Sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y) + Sprite.size / 2.0f * Scale, SpriteColor, scale: Scale); Sprite.Draw(spriteBatch, new Vector2(position.X, -position.Y) + Sprite.size / 2.0f * Scale, SpriteColor, scale: Scale);
@@ -264,10 +264,6 @@ namespace Barotrauma
{ {
Vector2 velocity = flowForce; Vector2 velocity = flowForce;
if (!IsHorizontal) if (!IsHorizontal)
{
velocity.X = Rand.Range(-100.0f, 100.0f) * open;
}
else
{ {
velocity.X *= Rand.Range(1.0f, 3.0f); velocity.X *= Rand.Range(1.0f, 3.0f);
} }

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