(965c31410a) Unstable v0.10.4.0

This commit is contained in:
Juan Pablo Arce
2020-07-21 08:57:50 -03:00
parent 4f8bd39789
commit 33d3a41104
546 changed files with 45952 additions and 25762 deletions
@@ -163,7 +163,8 @@ namespace Barotrauma
position = Vector2.Zero; position = Vector2.Zero;
CreateMatrices(); CreateMatrices();
GameMain.Instance.OnResolutionChanged += () => { CreateMatrices(); }; // TODO: Needs to unregister if ever destroy cameras.
GameMain.Instance.ResolutionChanged += CreateMatrices;
UpdateTransform(false); UpdateTransform(false);
} }
@@ -260,7 +261,9 @@ namespace Barotrauma
if (targetPos == Vector2.Zero) if (targetPos == Vector2.Zero)
{ {
Vector2 moveInput = Vector2.Zero; Vector2 moveInput = Vector2.Zero;
if (allowMove && GUI.KeyboardDispatcher.Subscriber == null) if (allowMove)
{
if (GUI.KeyboardDispatcher.Subscriber == null)
{ {
if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f; if (PlayerInput.KeyDown(Keys.LeftShift)) moveSpeed *= 2.0f;
if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f; if (PlayerInput.KeyDown(Keys.LeftControl)) moveSpeed *= 0.5f;
@@ -282,6 +285,7 @@ namespace Barotrauma
moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime); moveCam += FarseerPhysics.ConvertUnits.ToDisplayUnits(closestSub.Velocity * deltaTime);
} }
} }
}
if (allowZoom && GUI.MouseOn == null) if (allowZoom && GUI.MouseOn == null)
{ {
@@ -311,7 +315,7 @@ namespace Barotrauma
{ {
Freeze = true; Freeze = true;
} }
if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen) if (CharacterHealth.OpenHealthWindow != null || CrewManager.IsCommandInterfaceOpen || ConversationAction.IsDialogOpen)
{ {
offset *= 0; offset *= 0;
Freeze = false; Freeze = false;
@@ -6,6 +6,8 @@ namespace Barotrauma
{ {
partial class HumanAIController : AIController partial class HumanAIController : AIController
{ {
public static bool debugai;
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
/*if (GameMain.GameSession != null && GameMain.GameSession.CrewManager != null) /*if (GameMain.GameSession != null && GameMain.GameSession.CrewManager != null)
@@ -18,6 +20,8 @@ namespace Barotrauma
public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch) public override void DebugDraw(Microsoft.Xna.Framework.Graphics.SpriteBatch spriteBatch)
{ {
if (Character == Character.Controlled) { return; }
if (!debugai) { return; }
Vector2 pos = Character.WorldPosition; Vector2 pos = Character.WorldPosition;
pos.Y = -pos.Y; pos.Y = -pos.Y;
Vector2 textOffset = new Vector2(-40, -160); Vector2 textOffset = new Vector2(-40, -160);
@@ -268,7 +268,7 @@ namespace Barotrauma
} }
else if (body.UserData is Limb || body == Collider.FarseerBody) else if (body.UserData is Limb || body == Collider.FarseerBody)
{ {
if (!character.IsRemotePlayer && impact > ImpactTolerance) if (!character.IsRemotelyControlled && impact > ImpactTolerance)
{ {
SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider); SoundPlayer.PlayDamageSound("LimbBlunt", strongestImpact, Collider);
} }
@@ -459,22 +459,15 @@ namespace Barotrauma
/// Offset added to the default draw depth of the character's limbs. For example, climbing on ladders affects the depth of the character to get it to render behind the ladders. /// Offset added to the default draw depth of the character's limbs. For example, climbing on ladders affects the depth of the character to get it to render behind the ladders.
/// </summary> /// </summary>
public float GetDepthOffset() public float GetDepthOffset()
{
float depthOffset = 0.0f;
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
if (ladder != null)
{ {
float maxDepth = 0.0f; float maxDepth = 0.0f;
float minDepth = 1.0f; float minDepth = 1.0f;
foreach (Limb limb in Limbs) float depthOffset = 0.0f;
var ladder = character.SelectedConstruction?.GetComponent<Ladder>();
if (ladder != null)
{ {
var activeSprite = limb.ActiveSprite; CalculateLimbDepths();
if (activeSprite != null)
{
maxDepth = Math.Max(activeSprite.Depth, maxDepth);
minDepth = Math.Min(activeSprite.Depth, minDepth);
}
}
if (character.WorldPosition.X < character.SelectedConstruction.WorldPosition.X) if (character.WorldPosition.X < character.SelectedConstruction.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
@@ -486,6 +479,36 @@ namespace Barotrauma
depthOffset = Math.Max(ladder.BackgroundSpriteDepth + 0.01f - minDepth, 0.0f); depthOffset = Math.Max(ladder.BackgroundSpriteDepth + 0.01f - minDepth, 0.0f);
} }
} }
else
{
CalculateLimbDepths();
var controller = character.SelectedConstruction?.GetComponent<Controller>();
if (controller != null && controller.ControlCharacterPose && controller.User == character)
{
if (controller.Item.SpriteDepth > maxDepth)
{
depthOffset = Math.Max(controller.Item.SpriteDepth - 0.0001f - maxDepth, 0.0f);
}
else
{
depthOffset = Math.Max(controller.Item.SpriteDepth + 0.0001f - minDepth, -minDepth);
}
}
}
void CalculateLimbDepths()
{
foreach (Limb limb in Limbs)
{
var activeSprite = limb.ActiveSprite;
if (activeSprite != null)
{
maxDepth = Math.Max(activeSprite.Depth, maxDepth);
minDepth = Math.Min(activeSprite.Depth, minDepth);
}
}
}
return depthOffset; return depthOffset;
} }
@@ -498,10 +521,15 @@ namespace Barotrauma
{ {
if (limb.PullJointEnabled) if (limb.PullJointEnabled)
{ {
Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA); Vector2 pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorB);
if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition; if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition;
pos.Y = -pos.Y; pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), GUI.Style.Red, true, 0.01f); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), GUI.Style.Red, true, 0.01f);
pos = ConvertUnits.ToDisplayUnits(limb.PullJointWorldAnchorA);
if (currentHull?.Submarine != null) pos += currentHull.Submarine.DrawPosition;
pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)pos.Y, 5, 5), Color.Cyan, true, 0.01f);
} }
limb.body.DebugDraw(spriteBatch, inWater ? (currentHull == null ? Color.Blue : Color.Cyan) : Color.White); limb.body.DebugDraw(spriteBatch, inWater ? (currentHull == null ? Color.Blue : Color.Cyan) : Color.White);
@@ -22,8 +22,8 @@ namespace Barotrauma
protected float soundTimer; protected float soundTimer;
protected float soundInterval; protected float soundInterval;
protected float hudInfoTimer; protected float hudInfoTimer = 1.0f;
protected bool hudInfoVisible; protected bool hudInfoVisible = false;
private float pressureParticleTimer; private float pressureParticleTimer;
@@ -31,7 +31,7 @@ namespace Barotrauma
protected float lastRecvPositionUpdateTime; protected float lastRecvPositionUpdateTime;
private float hudInfoHeight; private float hudInfoHeight = 100.0f;
private List<CharacterSound> sounds; private List<CharacterSound> sounds;
@@ -264,7 +264,7 @@ namespace Barotrauma
} }
else if (Lights.LightManager.ViewTarget is Item item && item.Prefab.FocusOnSelected) else if (Lights.LightManager.ViewTarget is Item item && item.Prefab.FocusOnSelected)
{ {
cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected; cam.OffsetAmount = targetOffsetAmount = item.Prefab.OffsetOnSelected * item.OffsetOnSelectedMultiplier;
} }
else if (SelectedConstruction != null && ViewTarget == null && else if (SelectedConstruction != null && ViewTarget == null &&
SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this))) SelectedConstruction.Components.Any(ic => ic?.GuiFrame != null && ic.ShouldDrawHUD(this)))
@@ -549,11 +549,12 @@ namespace Barotrauma
public bool ShouldLockHud() public bool ShouldLockHud()
{ {
if (this != controlled) { return false; } if (this != controlled) { return false; }
if (GameMain.GameSession?.Campaign != null && GameMain.GameSession.Campaign.ShowCampaignUI) { return true; }
var controller = SelectedConstruction?.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 && SelectedConstruction != null &&
SelectedConstruction?.GetComponent<Controller>()?.User == this && controller?.User == this && controller.HideHUD &&
SelectedConstruction?.GetComponent<ConnectionPanel>()?.User != this; SelectedConstruction?.GetComponent<ConnectionPanel>()?.User != this;
} }
@@ -661,7 +662,7 @@ namespace Barotrauma
public void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth = true) public void DrawHUD(SpriteBatch spriteBatch, Camera cam, bool drawHealth = true)
{ {
CharacterHUD.Draw(spriteBatch, this, cam); CharacterHUD.Draw(spriteBatch, this, cam);
if (drawHealth) CharacterHealth.DrawHUD(spriteBatch); if (drawHealth && !CharacterHUD.IsCampaignInterfaceOpen) { CharacterHealth.DrawHUD(spriteBatch); }
} }
public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam) public virtual void DrawFront(SpriteBatch spriteBatch, Camera cam)
@@ -673,7 +674,7 @@ namespace Barotrauma
AnimController.DebugDraw(spriteBatch); AnimController.DebugDraw(spriteBatch);
} }
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
if (Controlled != null && if (Controlled != null &&
Controlled != this && Controlled != this &&
@@ -756,15 +757,19 @@ namespace Barotrauma
float hoverRange = 300.0f; float hoverRange = 300.0f;
float fadeOutRange = 200.0f; float fadeOutRange = 200.0f;
float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition)); float cursorDist = Vector2.Distance(WorldPosition, cam.ScreenToWorld(PlayerInput.MousePosition));
float hudInfoAlpha = MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f); float hudInfoAlpha =
CampaignInteractionType == CampaignMode.InteractionType.None ?
MathHelper.Clamp(1.0f - (cursorDist - (hoverRange - fadeOutRange)) / fadeOutRange, 0.2f, 1.0f) :
1.0f;
if (!GUI.DisableCharacterNames && hudInfoVisible && info != null && if (!GUI.DisableCharacterNames && hudInfoVisible && info != null &&
(controlled == null || this != controlled.FocusedCharacter)) (controlled == null || this != controlled.FocusedCharacter) && cam.Zoom > 0.4f)
{ {
string name = Info.DisplayName; string name = Info.DisplayName;
if (controlled == null && name != Info.Name) name += " " + TextManager.Get("Disguised"); if (controlled == null && name != Info.Name) { name += " " + TextManager.Get("Disguised"); }
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - GUI.Font.MeasureString(name) * 0.5f / cam.Zoom; Vector2 nameSize = GUI.Font.MeasureString(name);
Vector2 namePos = new Vector2(pos.X, pos.Y - 10.0f - (5.0f / cam.Zoom)) - nameSize * 0.5f / cam.Zoom;
Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight); Vector2 screenSize = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height); Vector2 viewportSize = new Vector2(cam.WorldView.Width, cam.WorldView.Height);
@@ -779,16 +784,30 @@ namespace Barotrauma
{ {
nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red; nameColor = TeamID == TeamType.FriendlyNPC ? Color.SkyBlue : GUI.Style.Red;
} }
if (CampaignInteractionType != CampaignMode.InteractionType.None && AllowCustomInteract)
{
var iconStyle = GUI.Style.GetComponentStyle("CampaignInteractionIcon." + CampaignInteractionType);
if (iconStyle != null)
{
Vector2 headPos = AnimController.GetLimb(LimbType.Head)?.WorldPosition ?? WorldPosition + Vector2.UnitY * 100.0f;
Vector2 iconPos = headPos;
iconPos.Y = -iconPos.Y;
nameColor = iconStyle.Color;
var icon = iconStyle.Sprites[GUIComponent.ComponentState.None].First();
float iconScale = 30.0f / icon.Sprite.size.X / cam.Zoom;
icon.Sprite.Draw(spriteBatch, iconPos + new Vector2(-35.0f, -25.0f), iconStyle.Color * hudInfoAlpha, scale: iconScale);
}
}
GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f); GUI.Font.DrawString(spriteBatch, name, namePos + new Vector2(1.0f / cam.Zoom, 1.0f / cam.Zoom), Color.Black, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.001f);
GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f); GUI.Font.DrawString(spriteBatch, name, namePos, nameColor * hudInfoAlpha, 0.0f, Vector2.Zero, 1.0f / cam.Zoom, SpriteEffects.None, 0.0f);
if (GameMain.DebugDraw) if (GameMain.DebugDraw)
{ {
GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White); GUI.Font.DrawString(spriteBatch, ID.ToString(), namePos - new Vector2(0.0f, 20.0f), Color.White);
} }
} }
if (IsDead) return; if (IsDead) { return; }
if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible) if (CharacterHealth.DisplayedVitality < MaxVitality * 0.98f && hudInfoVisible)
{ {
@@ -41,13 +41,21 @@ namespace Barotrauma
private static bool shouldRecreateHudTexts = true; private static bool shouldRecreateHudTexts = true;
private static bool heldDownShiftWhenGotHudTexts; private static bool heldDownShiftWhenGotHudTexts;
public static bool IsCampaignInterfaceOpen =>
GameMain.GameSession?.Campaign != null &&
(GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI);
private static bool ShouldDrawInventory(Character character) private static bool ShouldDrawInventory(Character character)
{ {
var controller = character.SelectedConstruction?.GetComponent<Controller>();
return return
character?.Inventory != null && character?.Inventory != null &&
character.AllowInput && character.AllowInput &&
!character.LockHands && !character.LockHands &&
character.SelectedConstruction?.GetComponent<Controller>()?.User != character; (controller?.User != character || !controller.HideHUD) &&
!IsCampaignInterfaceOpen &&
!ConversationAction.FadeScreenToBlack;
} }
private static string GetCachedHudText(string textTag, string keyBind) private static string GetCachedHudText(string textTag, string keyBind)
@@ -65,7 +73,7 @@ namespace Barotrauma
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) return;
if (!character.IsIncapacitated && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
{ {
if (character.Inventory != null) if (character.Inventory != null)
{ {
@@ -92,9 +100,17 @@ namespace Barotrauma
public static void Update(float deltaTime, Character character, Camera cam) public static void Update(float deltaTime, Character character, Camera cam)
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD)
{
if (character.Inventory != null && !LockInventory(character))
{
character.Inventory.UpdateSlotInput();
}
if (!character.IsIncapacitated && character.Stun <= 0.0f) return;
}
if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
{ {
if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null) if (character.Info != null && !character.ShouldLockHud() && character.SelectedCharacter == null)
{ {
@@ -163,7 +179,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; } if (item.Submarine == null || item.Submarine.TeamID != character.TeamID || item.Submarine.Info.IsWreck) { continue; }
if (!item.Repairables.Any(r => item.ConditionPercentage <= r.RepairThreshold)) { continue; } if (!item.Repairables.Any(r => item.ConditionPercentage <= r.RepairIconThreshold)) { continue; }
if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; } if (Submarine.VisibleEntities != null && !Submarine.VisibleEntities.Contains(item)) { continue; }
Vector2 diff = item.WorldPosition - character.WorldPosition; Vector2 diff = item.WorldPosition - character.WorldPosition;
@@ -211,7 +227,7 @@ namespace Barotrauma
Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha); Color.Lerp(GUI.Style.Red, GUI.Style.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
} }
if (!character.IsIncapacitated && character.Stun <= 0.0f) if (!character.IsIncapacitated && character.Stun <= 0.0f && !IsCampaignInterfaceOpen)
{ {
if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected) if (character.FocusedCharacter != null && character.FocusedCharacter.CanBeSelected)
{ {
@@ -299,6 +315,8 @@ namespace Barotrauma
character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled); character.SelectedConstruction.DrawHUD(spriteBatch, cam, Character.Controlled);
} }
if (IsCampaignInterfaceOpen) { return; }
if (character.Inventory != null) if (character.Inventory != null)
{ {
for (int i = 0; i < character.Inventory.Items.Length - 1; i++) for (int i = 0; i < character.Inventory.Items.Length - 1; i++)
@@ -308,10 +326,11 @@ namespace Barotrauma
foreach (ItemComponent ic in item.Components) foreach (ItemComponent ic in item.Components)
{ {
if (ic.DrawHudWhenEquipped) ic.DrawHUD(spriteBatch, character); if (ic.DrawHudWhenEquipped) { ic.DrawHUD(spriteBatch, character); }
} }
} }
} }
bool mouseOnPortrait = false; bool mouseOnPortrait = false;
if (character.Stun <= 0.1f && !character.IsDead) if (character.Stun <= 0.1f && !character.IsDead)
{ {
@@ -421,7 +440,7 @@ namespace Barotrauma
GUI.Style.Green, Color.Black, 2, GUI.SmallFont); GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
textPos.Y += textSize.Y; textPos.Y += textSize.Y;
} }
if (!string.IsNullOrEmpty(character.FocusedCharacter.customInteractHUDText)) if (!string.IsNullOrEmpty(character.FocusedCharacter.customInteractHUDText) && character.FocusedCharacter.AllowCustomInteract)
{ {
GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.customInteractHUDText, GUI.Style.Green, Color.Black, 2, GUI.SmallFont); GUI.DrawString(spriteBatch, textPos, character.FocusedCharacter.customInteractHUDText, GUI.Style.Green, Color.Black, 2, GUI.SmallFont);
textPos.Y += textSize.Y; textPos.Y += textSize.Y;
@@ -430,7 +449,7 @@ namespace Barotrauma
private static bool LockInventory(Character character) private static bool LockInventory(Character character)
{ {
if (character?.Inventory == null || !character.AllowInput || character.LockHands) { return true; } if (character?.Inventory == null || !character.AllowInput || character.LockHands || IsCampaignInterfaceOpen) { return true; }
return character.ShouldLockHud(); return character.ShouldLockHud();
} }
@@ -5,6 +5,7 @@ using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -12,9 +13,11 @@ namespace Barotrauma
{ {
private static Sprite infoAreaPortraitBG; private static Sprite infoAreaPortraitBG;
public bool LastControlled;
public static void Init() public static void Init()
{ {
infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.Sprites[GUIComponent.ComponentState.None][0].Sprite; infoAreaPortraitBG = GUI.Style.GetComponentStyle("InfoAreaPortraitBG")?.GetDefaultSprite();
new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0); new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(833, 298, 142, 98), null, 0);
} }
@@ -117,6 +120,7 @@ namespace Barotrauma
private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect) private void DrawInfoFrameCharacterIcon(SpriteBatch sb, Rectangle componentRect)
{ {
if (headSprite == null) { return; }
Vector2 targetAreaSize = componentRect.Size.ToVector2(); Vector2 targetAreaSize = componentRect.Size.ToVector2();
float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y); float scale = Math.Min(targetAreaSize.X / headSprite.size.X, targetAreaSize.Y / headSprite.size.Y);
DrawIcon(sb, componentRect.Location.ToVector2() + headSprite.size / 2 * scale, targetAreaSize); DrawIcon(sb, componentRect.Location.ToVector2() + headSprite.size / 2 * scale, targetAreaSize);
@@ -140,6 +144,9 @@ namespace Barotrauma
partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos) partial void OnSkillChanged(string skillIdentifier, float prevLevel, float newLevel, Vector2 textPopupPos)
{ {
if (TeamID == Character.TeamType.FriendlyNPC) { return; }
if (Character.Controlled != null && Character.Controlled.TeamID != TeamID) { return; }
if (newLevel - prevLevel > 0.1f) if (newLevel - prevLevel > 0.1f)
{ {
GUI.AddMessage( GUI.AddMessage(
@@ -75,8 +75,15 @@ namespace Barotrauma
states = newInput, states = newInput,
intAim = intAngle intAim = intAngle
}; };
if (focusedItem != null && !CharacterInventory.DraggingItemToWorld &&
(!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health))) if (FocusedCharacter != null &&
FocusedCharacter.CampaignInteractionType != CampaignMode.InteractionType.None &&
newMem.states.HasFlag(InputNetFlags.Use))
{
newMem.interact = FocusedCharacter.ID;
}
else if (focusedItem != null && !CharacterInventory.DraggingItemToWorld &&
!newMem.states.HasFlag(InputNetFlags.Grab) && !newMem.states.HasFlag(InputNetFlags.Health))
{ {
newMem.interact = focusedItem.ID; newMem.interact = focusedItem.ID;
} }
@@ -278,10 +285,10 @@ namespace Barotrauma
break; break;
case ServerNetObject.ENTITY_EVENT: case ServerNetObject.ENTITY_EVENT:
int eventType = msg.ReadRangedInteger(0, 4); int eventType = msg.ReadRangedInteger(0, 5);
switch (eventType) switch (eventType)
{ {
case 0: case 0: //NetEntityEvent.Type.InventoryState
if (Inventory == null) if (Inventory == null)
{ {
string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")"; string errorMsg = "Received an inventory update message for an entity with no inventory (" + Name + ", removed: " + Removed + ")";
@@ -301,7 +308,7 @@ namespace Barotrauma
Inventory.ClientRead(type, msg, sendingTime); Inventory.ClientRead(type, msg, sendingTime);
} }
break; break;
case 1: case 1: //NetEntityEvent.Type.Control
byte ownerID = msg.ReadByte(); byte ownerID = msg.ReadByte();
ResetNetState(); ResetNetState();
if (ownerID == GameMain.Client.ID) if (ownerID == GameMain.Client.ID)
@@ -326,10 +333,10 @@ namespace Barotrauma
} }
} }
break; break;
case 2: case 2: //NetEntityEvent.Type.Status
ReadStatus(msg); ReadStatus(msg);
break; break;
case 3: case 3: //NetEntityEvent.Type.UpdateSkills
int skillCount = msg.ReadByte(); int skillCount = msg.ReadByte();
for (int i = 0; i < skillCount; i++) for (int i = 0; i < skillCount; i++)
{ {
@@ -338,17 +345,17 @@ namespace Barotrauma
info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f); info?.SetSkillLevel(skillIdentifier, skillLevel, WorldPosition + Vector2.UnitY * 150.0f);
} }
break; break;
case 4: case 4: //NetEntityEvent.Type.ExecuteAttack
int attackLimbIndex = msg.ReadByte(); int attackLimbIndex = msg.ReadByte();
UInt16 targetEntityID = msg.ReadUInt16(); UInt16 targetEntityID = msg.ReadUInt16();
int targetLimbIndex = msg.ReadByte(); int targetLimbIndex = msg.ReadByte();
//255 = entity already removed, no need to do anything //255 = entity already removed, no need to do anything
if (attackLimbIndex == 255) { break; } if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length) if (attackLimbIndex >= AnimController.Limbs.Length)
{ {
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds ({attackLimbIndex})"); DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})");
break; break;
} }
Limb attackLimb = AnimController.Limbs[attackLimbIndex]; Limb attackLimb = AnimController.Limbs[attackLimbIndex];
@@ -362,7 +369,7 @@ namespace Barotrauma
{ {
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length) if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{ {
DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds ({targetLimbIndex})"); DebugConsole.ThrowError($"Received invalid ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})");
break; break;
} }
targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex]; targetLimb = targetCharacter.AnimController.Limbs[targetLimbIndex];
@@ -372,6 +379,10 @@ namespace Barotrauma
attackLimb.ExecuteAttack(targetEntity, targetLimb, out _); attackLimb.ExecuteAttack(targetEntity, targetLimb, out _);
} }
break; break;
case 5: //NetEntityEvent.Type.AssignCampaignInteraction
byte campaignInteractionType = msg.ReadByte();
(GameMain.GameSession?.GameMode as CampaignMode)?.AssignNPCMenuInteraction(this, (CampaignMode.InteractionType)campaignInteractionType);
break;
} }
msg.ReadPadBits(); msg.ReadPadBits();
break; break;
@@ -398,7 +409,7 @@ namespace Barotrauma
Character character = null; Character character = null;
if (noInfo) if (noInfo)
{ {
character = Create(speciesName, position, seed, null, true); character = Create(speciesName, position, seed, null, false);
character.ID = id; character.ID = id;
bool containsStatusData = inc.ReadBoolean(); bool containsStatusData = inc.ReadBoolean();
if (containsStatusData) if (containsStatusData)
@@ -416,9 +427,14 @@ namespace Barotrauma
CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc); CharacterInfo info = CharacterInfo.ClientRead(infoSpeciesName, inc);
character = Create(speciesName, position, seed, info, GameMain.Client.ID != ownerId, hasAi); character = Create(speciesName, position, seed, info, ownerId > 0 && GameMain.Client.ID != ownerId, hasAi);
character.ID = id; character.ID = id;
character.TeamID = (TeamType)teamID; character.TeamID = (TeamType)teamID;
character.CampaignInteractionType = (CampaignMode.InteractionType)inc.ReadByte();
if (character.CampaignInteractionType != CampaignMode.InteractionType.None)
{
(GameMain.GameSession.GameMode as CampaignMode)?.AssignNPCMenuInteraction(character, character.CampaignInteractionType);
}
// Check if the character has a current order // Check if the character has a current order
if (inc.ReadBoolean()) if (inc.ReadBoolean())
@@ -525,6 +525,7 @@ namespace Barotrauma
}, },
TextManager.Get("GiveInButton"), style: "GUIButtonLarge") TextManager.Get("GiveInButton"), style: "GUIButtonLarge")
{ {
Visible = false,
ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"), ToolTip = TextManager.Get(GameMain.NetworkMember == null ? "GiveInHelpSingleplayer" : "GiveInHelpMultiplayer"),
OnClicked = (button, userData) => OnClicked = (button, userData) =>
{ {
@@ -944,6 +945,22 @@ namespace Barotrauma
suicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated; suicideButton.Visible = Character == Character.Controlled && !Character.IsDead && Character.IsIncapacitated;
if (GameMain.GameSession?.Campaign is { } campaign)
{
RectTransform endRoundButton = campaign?.EndRoundButton.RectTransform;
if (endRoundButton != null)
{
if (suicideButton.Visible)
{
endRoundButton.ScreenSpaceOffset = new Point(0, suicideButton.Rect.Height);
}
else if (endRoundButton.ScreenSpaceOffset != Point.Zero)
{
endRoundButton.ScreenSpaceOffset = Point.Zero;
}
}
}
cprButton.Visible = cprButton.Visible =
Character == Character.Controlled?.SelectedCharacter Character == Character.Controlled?.SelectedCharacter
&& (Character.IsUnconscious || Character.Stun > 0.0f) && (Character.IsUnconscious || Character.Stun > 0.0f)
@@ -965,23 +982,29 @@ namespace Barotrauma
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
if (OpenHealthWindow == this) if (OpenHealthWindow == this)
{ {
healthInterfaceFrame.AddToGUIUpdateList(); healthInterfaceFrame.AddToGUIUpdateList();
afflictionTooltip?.AddToGUIUpdateList(); afflictionTooltip?.AddToGUIUpdateList();
} }
else if (Character.Controlled == Character) else if (Character.Controlled == Character && !CharacterHUD.IsCampaignInterfaceOpen)
{ {
healthBarHolder.AddToGUIUpdateList(); healthBarHolder.AddToGUIUpdateList();
} }
if (suicideButton.Visible && Character == Character.Controlled) suicideButton.AddToGUIUpdateList(); if (suicideButton.Visible && Character == Character.Controlled)
if (cprButton != null && cprButton.Visible) cprButton.AddToGUIUpdateList(); {
suicideButton.AddToGUIUpdateList();
}
if (cprButton != null && cprButton.Visible)
{
cprButton.AddToGUIUpdateList();
}
} }
public void DrawHUD(SpriteBatch spriteBatch) public void DrawHUD(SpriteBatch spriteBatch)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
if (GameMain.GraphicsWidth != screenResolution.X || if (GameMain.GraphicsWidth != screenResolution.X ||
GameMain.GraphicsHeight != screenResolution.Y || GameMain.GraphicsHeight != screenResolution.Y ||
Math.Abs(inventoryScale - Inventory.UIScale) > 0.01f || Math.Abs(inventoryScale - Inventory.UIScale) > 0.01f ||
@@ -8,7 +8,7 @@ namespace Barotrauma
{ {
partial class JobPrefab : IPrefab, IDisposable partial class JobPrefab : IPrefab, IDisposable
{ {
public GUIButton CreateInfoFrame(int variant) public GUIButton CreateInfoFrame(out GUIComponent buttonContainer)
{ {
int width = 500, height = 400; int width = 500, height = 400;
@@ -34,6 +34,8 @@ namespace Barotrauma
font: GUI.SmallFont); font: GUI.SmallFont);
} }
buttonContainer = paddedFrame;
/*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; } /*if (!ItemIdentifiers.TryGetValue(variant, out var itemIdentifiers)) { return backFrame; }
var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight) var itemContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 0.5f), paddedFrame.RectTransform, Anchor.TopRight)
{ RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) }) { RelativeOffset = new Vector2(0.0f, 0.2f + descriptionBlock.RectTransform.RelativeSize.Y) })
@@ -54,7 +56,6 @@ namespace Barotrauma
return frameHolder; return frameHolder;
} }
public class OutfitPreview public class OutfitPreview
{ {
/// <summary> /// <summary>
@@ -50,7 +50,12 @@ namespace Barotrauma
} }
private static bool isOpen; private static bool isOpen;
public static bool IsOpen => isOpen; public static bool IsOpen
{
get => isOpen;
set => isOpen = value;
}
public static bool Paused = false; public static bool Paused = false;
private static GUITextBlock activeQuestionText; private static GUITextBlock activeQuestionText;
@@ -66,6 +71,8 @@ namespace Barotrauma
public static void Init() public static void Init()
{ {
OpenAL.Alc.SetErrorReasonCallback((string msg) => NewMessage(msg, Color.Orange));
frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.45f), GUI.Canvas) { MinSize = new Point(400, 300), AbsoluteOffset = new Point(10, 10) }, frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.45f), GUI.Canvas) { MinSize = new Point(400, 300), AbsoluteOffset = new Point(10, 10) },
color: new Color(0.4f, 0.4f, 0.4f, 0.8f)); color: new Color(0.4f, 0.4f, 0.4f, 0.8f));
@@ -262,6 +269,23 @@ namespace Barotrauma
} }
try try
{
if (msg.IsError)
{
var textContainer = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), style: "InnerFrame", color: Color.White)
{
CanBeFocused = false
};
var textBlock = new GUITextBlock(new RectTransform(new Point(listBox.Content.Rect.Width - 5, 0), textContainer.RectTransform, Anchor.TopLeft) { AbsoluteOffset = new Point(2, 2) },
msg.Text, textAlignment: Alignment.TopLeft, font: GUI.SmallFont, wrap: true)
{
CanBeFocused = false,
TextColor = msg.Color
};
textContainer.RectTransform.NonScaledSize = new Point(textContainer.RectTransform.NonScaledSize.X, textBlock.RectTransform.NonScaledSize.Y + 5);
textBlock.SetTextPos();
}
else
{ {
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform), var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), listBox.Content.RectTransform),
msg.Text, font: GUI.SmallFont, wrap: true) msg.Text, font: GUI.SmallFont, wrap: true)
@@ -269,6 +293,8 @@ namespace Barotrauma
CanBeFocused = false, CanBeFocused = false,
TextColor = msg.Color TextColor = msg.Color
}; };
}
listBox.UpdateScrollBarSize(); listBox.UpdateScrollBarSize();
listBox.BarScroll = 1.0f; listBox.BarScroll = 1.0f;
} }
@@ -447,6 +473,11 @@ namespace Barotrauma
GameMain.SpriteEditorScreen.Select(); GameMain.SpriteEditorScreen.Select();
})); }));
commands.Add(new Command("editevents|eventeditor", "editevents/eventeditor: Switch to the Event Editor to edit scripted events.", (string[] args) =>
{
GameMain.EventEditorScreen.Select();
}));
commands.Add(new Command("editcharacters|charactereditor", "editcharacters/charactereditor: Switch to the Character Editor to edit/create the ragdolls and animations of characters.", (string[] args) => commands.Add(new Command("editcharacters|charactereditor", "editcharacters/charactereditor: Switch to the Character Editor to edit/create the ragdolls and animations of characters.", (string[] args) =>
{ {
if (Screen.Selected == GameMain.GameScreen) if (Screen.Selected == GameMain.GameScreen)
@@ -456,9 +487,11 @@ namespace Barotrauma
GameMain.CharacterEditorScreen.Select(); GameMain.CharacterEditorScreen.Select();
})); }));
commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks debug logging.", (string[] args) => commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) =>
{ {
SteamManager.NetworkingDebugLog = !SteamManager.NetworkingDebugLog; SteamManager.NetworkingDebugLog = !SteamManager.NetworkingDebugLog;
SteamManager.SetSteamworksNetworkingDebugLog(SteamManager.NetworkingDebugLog);
})); }));
AssignRelayToServer("kick", false); AssignRelayToServer("kick", false);
@@ -493,6 +526,7 @@ namespace Barotrauma
commands.Add(new Command("traitorlist", "", (string[] args) => { })); commands.Add(new Command("traitorlist", "", (string[] args) => { }));
AssignRelayToServer("traitorlist", true); AssignRelayToServer("traitorlist", true);
AssignRelayToServer("money", true); AssignRelayToServer("money", true);
AssignRelayToServer("setskill", true);
AssignOnExecute("control", (string[] args) => AssignOnExecute("control", (string[] args) =>
{ {
@@ -568,16 +602,46 @@ namespace Barotrauma
AssignOnExecute("ambientlight", (string[] args) => AssignOnExecute("ambientlight", (string[] args) =>
{ {
Color color = XMLExtensions.ParseColor(string.Join(",", args)); bool add = string.Equals(args.LastOrDefault(), "add");
string colorString = string.Join(",", add ? args.SkipLast(1) : args);
if (colorString.Equals("restore", StringComparison.OrdinalIgnoreCase))
{
foreach (Hull hull in Hull.hullList)
{
if (hull.OriginalAmbientLight != null)
{
hull.AmbientLight = hull.OriginalAmbientLight.Value;
hull.OriginalAmbientLight = null;
}
}
NewMessage("Restored all hull ambient lights", Color.White);
return;
}
Color color = XMLExtensions.ParseColor(colorString);
if (Level.Loaded != null) if (Level.Loaded != null)
{ {
Level.Loaded.GenerationParams.AmbientLightColor = color; Level.Loaded.GenerationParams.AmbientLightColor = color;
} }
else else
{ {
GameMain.LightManager.AmbientLight = color; GameMain.LightManager.AmbientLight = add ? GameMain.LightManager.AmbientLight.Add(color) : color;
}
foreach (Hull hull in Hull.hullList)
{
hull.OriginalAmbientLight ??= hull.AmbientLight;
hull.AmbientLight = add ? hull.AmbientLight.Add(color) : color;
}
if (add)
{
NewMessage($"Set ambient light color to {color}.", Color.White);
}
else
{
NewMessage($"Increased ambient light by {color}.", Color.White);
} }
NewMessage("Set ambient light color to " + color + ".", Color.White);
}); });
AssignRelayToServer("ambientlight", false); AssignRelayToServer("ambientlight", false);
@@ -849,17 +913,6 @@ namespace Barotrauma
TutorialMode.StartTutorial(Tutorials.Tutorial.Tutorials[0]); TutorialMode.StartTutorial(Tutorials.Tutorial.Tutorials[0]);
})); }));
commands.Add(new Command("lobby|lobbyscreen", "", (string[] args) =>
{
if (GameMain.Client != null)
{
ThrowError("This command cannot be used in multiplayer.");
return;
}
GameMain.LobbyScreen.Select();
}));
commands.Add(new Command("save|savesub", "save [submarine name]: Save the currently loaded submarine using the specified name.", (string[] args) => commands.Add(new Command("save|savesub", "save [submarine name]: Save the currently loaded submarine using the specified name.", (string[] args) =>
{ {
if (args.Length < 1) { return; } if (args.Length < 1) { return; }
@@ -1011,9 +1064,39 @@ namespace Barotrauma
}); });
AssignRelayToServer("toggleaitargets|aitargets", false); AssignRelayToServer("toggleaitargets|aitargets", false);
AssignOnExecute("debugai", (string[] args) =>
{
HumanAIController.debugai = !HumanAIController.debugai;
if (HumanAIController.debugai)
{
GameMain.DebugDraw = true;
GameMain.LightManager.LightingEnabled = false;
GameMain.LightManager.LosEnabled = false;
}
else
{
GameMain.DebugDraw = false;
GameMain.LightManager.LightingEnabled = true;
GameMain.LightManager.LosEnabled = true;
}
NewMessage(HumanAIController.debugai ? "AI debug info visible" : "AI debug info hidden", Color.White);
});
AssignRelayToServer("debugai", false);
AssignRelayToServer("water|editwater", false); AssignRelayToServer("water|editwater", false);
AssignRelayToServer("fire|editfire", false); AssignRelayToServer("fire|editfire", false);
commands.Add(new Command("togglecampaignteleport", "togglecampaignteleport: Toggle on/off teleportation between campaign locations by double clicking on the campaign map.", (string[] args) =>
{
if (GameMain.GameSession?.Campaign == null)
{
ThrowError("No campaign active.");
return;
}
GameMain.GameSession.Map.AllowDebugTeleport = !GameMain.GameSession.Map.AllowDebugTeleport;
NewMessage((GameMain.GameSession.Map.AllowDebugTeleport ? "Enabled" : "Disabled") + " teleportation on the campaign map.", Color.White);
}, isCheat: true));
commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.", commands.Add(new Command("mute", "mute [name]: Prevent the client from speaking to anyone through the voice chat. Using this command requires a permission from the server host.",
null, null,
() => () =>
@@ -1044,7 +1127,7 @@ namespace Barotrauma
} }
foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs) foreach (ItemPrefab itemPrefab in ItemPrefab.Prefabs)
{ {
int? minCost = itemPrefab.GetPrices()?.Min(p => p.BuyPrice); int? minCost = itemPrefab.GetMinPrice();
int? fabricationCost = null; int? fabricationCost = null;
int? deconstructProductCost = null; int? deconstructProductCost = null;
@@ -1053,7 +1136,7 @@ namespace Barotrauma
{ {
foreach (var ingredient in fabricationRecipe.RequiredItems) foreach (var ingredient in fabricationRecipe.RequiredItems)
{ {
int? ingredientPrice = ingredient.ItemPrefab.GetPrices()?.Min(p => p.BuyPrice); int? ingredientPrice = ingredient.ItemPrefab.GetMinPrice();
if (ingredientPrice.HasValue) if (ingredientPrice.HasValue)
{ {
if (!fabricationCost.HasValue) { fabricationCost = 0; } if (!fabricationCost.HasValue) { fabricationCost = 0; }
@@ -1071,7 +1154,7 @@ namespace Barotrauma
continue; continue;
} }
int? deconstructProductPrice = targetItem.GetPrices()?.Min(p => p.BuyPrice); int? deconstructProductPrice = targetItem.GetMinPrice();
if (deconstructProductPrice.HasValue) if (deconstructProductPrice.HasValue)
{ {
if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; } if (!deconstructProductCost.HasValue) { deconstructProductCost = 0; }
@@ -1300,7 +1383,7 @@ namespace Barotrauma
commands.Add(new Command("eventstats", "", (string[] args) => commands.Add(new Command("eventstats", "", (string[] args) =>
{ {
var debugLines = ScriptedEventSet.GetDebugStatistics(); var debugLines = EventSet.GetDebugStatistics();
string filePath = "eventstats.txt"; string filePath = "eventstats.txt";
File.WriteAllLines(filePath, debugLines); File.WriteAllLines(filePath, debugLines);
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath)); ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
@@ -1537,6 +1620,76 @@ namespace Barotrauma
File.WriteAllLines(filePath, lines); File.WriteAllLines(filePath, lines);
})); }));
commands.Add(new Command("dumpeventtexts", "dumpeventtexts [filepath]: gets the texts from event files and and writes them into a file along with xml tags that can be used in translation files. If the filepath is omitted, the file is written to Content/Texts/EventTexts.txt", (string[] args) =>
{
string filePath = args.Length > 0 ? args[0] : "Content/Texts/EventTexts.txt";
List<string> lines = new List<string>();
HashSet<XDocument> docs = new HashSet<XDocument>();
HashSet<string> textIds = new HashSet<string>();
foreach (EventPrefab eventPrefab in EventSet.GetAllEventPrefabs())
{
if (string.IsNullOrEmpty(eventPrefab.Identifier))
{
continue;
}
docs.Add(eventPrefab.ConfigElement.Document);
getTextsFromElement(eventPrefab.ConfigElement, lines, eventPrefab.Identifier);
}
File.WriteAllLines(filePath, lines);
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
System.Xml.XmlWriterSettings settings = new System.Xml.XmlWriterSettings
{
Indent = true,
NewLineOnAttributes = false
};
foreach (XDocument doc in docs)
{
using (var writer = XmlWriter.Create(new System.Uri(doc.BaseUri).LocalPath, settings))
{
doc.WriteTo(writer);
writer.Flush();
}
}
void getTextsFromElement(XElement element, List<string> list, string parentName)
{
string text = element.GetAttributeString("text", null);
string textId = $"EventText.{parentName}";
if (!string.IsNullOrEmpty(text) && !text.Contains("EventText.", StringComparison.OrdinalIgnoreCase))
{
list.Add($"<{textId}>{text}</{textId}>");
element.SetAttributeValue("text", textId);
}
int i = 1;
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "conversationaction":
while (textIds.Contains(parentName+".c"+i))
{
i++;
}
parentName += ".c" + i;
break;
case "option":
while (textIds.Contains(parentName.Substring(0, parentName.Length - 3) + ".o" + i))
{
i++;
}
parentName = parentName.Substring(0, parentName.Length - 3) + ".o" + i;
break;
}
textIds.Add(parentName);
getTextsFromElement(subElement, list, parentName);
}
}
}));
commands.Add(new Command("itemcomponentdocumentation", "", (string[] args) => commands.Add(new Command("itemcomponentdocumentation", "", (string[] args) =>
{ {
@@ -0,0 +1,365 @@
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Barotrauma.Extensions;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
partial class ConversationAction : EventAction
{
private GUIMessageBox dialogBox;
private static ConversationAction lastActiveAction;
private static GUIMessageBox lastMessageBox;
public static bool IsDialogOpen
{
get
{
return GUIMessageBox.MessageBoxes.Any(mb =>
mb.UserData as string == "ConversationAction" ||
(mb.UserData is Pair<string, UInt16> pair && pair.First == "ConversationAction"));
}
}
public static bool FadeScreenToBlack
{
get { return IsDialogOpen && shouldFadeToBlack; }
}
private static bool shouldFadeToBlack;
private bool IsBlockedByAnotherConversation(IEnumerable<Entity> _)
{
return
lastActiveAction != null &&
lastActiveAction.ParentEvent != ParentEvent &&
Timing.TotalTime < lastActiveAction.lastActiveTime + BlockOtherConversationsDuration;
}
partial void ShowDialog(Character speaker, Character targetCharacter)
{
CreateDialog(Text, speaker, Options.Select(opt => opt.Text), GetEndingOptions(), actionInstance: this, spriteIdentifier: EventSprite, fadeToBlack: FadeToBlack, dialogType: DialogType, continueConversation: ContinueConversation);
}
public static void CreateDialog(string text, Character speaker, IEnumerable<string> options, int[] closingOptions, string eventSprite, UInt16 actionId, bool fadeToBlack, DialogTypes dialogType, bool continueConversation = false)
{
CreateDialog(text, speaker, options, closingOptions, actionInstance: null, actionId: actionId, spriteIdentifier: eventSprite, fadeToBlack: fadeToBlack, dialogType: dialogType, continueConversation: continueConversation);
}
private static void CreateDialog(string text, Character speaker, IEnumerable<string> options, int[] closingOptions, string spriteIdentifier = null,
ConversationAction actionInstance = null, UInt16? actionId = null, bool fadeToBlack = false, DialogTypes dialogType = DialogTypes.Regular, bool continueConversation = false)
{
Debug.Assert(actionInstance == null || actionId == null);
shouldFadeToBlack = fadeToBlack;
if (lastMessageBox != null && !lastMessageBox.Closed && GUIMessageBox.MessageBoxes.Contains(lastMessageBox))
{
if (actionId != null && lastMessageBox.UserData is Pair<string, ushort> userData)
{
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);
}
}
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);
GUIMessageBox messageBox = new GUIMessageBox(string.Empty, string.Empty, new string[0],
relativeSize: relative, minSize: min,
type: GUIMessageBox.Type.InGame, backgroundIcon: EventSet.GetEventSprite(spriteIdentifier))
{
UserData = "ConversationAction"
};
lastMessageBox = messageBox;
messageBox.InnerFrame.ClearChildren();
messageBox.AutoClose = false;
GUI.Style.Apply(messageBox.InnerFrame, "DialogBox");
if (actionInstance != null)
{
lastActiveAction = actionInstance;
actionInstance.dialogBox = messageBox;
}
else
{
messageBox.UserData = new Pair<string, UInt16>("ConversationAction", actionId.Value);
}
int padding = GUI.IntScale(16);
GUIListBox listBox = new GUIListBox(new RectTransform(messageBox.InnerFrame.Rect.Size - new Point(padding * 2), messageBox.InnerFrame.RectTransform, Anchor.Center), style: null)
{
KeepSpaceForScrollBar = true,
HoverCursor = CursorState.Default,
UserData = "conversationlist"
};
List<GUIButton> buttons = CreateConversation(listBox, text, speaker, options, string.IsNullOrWhiteSpace(spriteIdentifier));
AssignActionsToButtons(buttons, messageBox);
RecalculateLastMessage(listBox, false);
messageBox.InnerFrame.RectTransform.MinSize = new Point(0, Math.Max(listBox.RectTransform.MinSize.Y + padding * 2, (int)(100 * GUI.yScale)));
var shadow = new GUIFrame(new RectTransform(messageBox.InnerFrame.Rect.Size + new Point(padding * 4), messageBox.InnerFrame.RectTransform, Anchor.Center), style: "OuterGlow")
{
Color = Color.Black * 0.7f
};
shadow.SetAsFirstChild();
void RecalculateLastMessage(GUIListBox conversationList, bool append)
{
if (conversationList.Content.Children.LastOrDefault() is GUILayoutGroup lastElement)
{
GUILayoutGroup textLayout = lastElement.GetChild<GUILayoutGroup>();
if (lastElement.Rect.Size.Y < textLayout.Rect.Size.Y && !append)
{
lastElement.RectTransform.MinSize = textLayout.Rect.Size;
}
if (textLayout != null)
{
int textHeight = textLayout.Children.Sum(c => c.Rect.Height);
textLayout.RectTransform.MaxSize = new Point(lastElement.RectTransform.MaxSize.X, textHeight);
textLayout.Recalculate();
}
int sumHeight = lastElement.Children.Sum(c => c.Rect.Height);
lastElement.RectTransform.MaxSize = new Point(lastElement.RectTransform.MaxSize.X, sumHeight);
lastElement.Recalculate();
conversationList.RecalculateChildren();
if (!append || textLayout == null) { return; }
foreach (GUIComponent child in textLayout.Children)
{
conversationList.UpdateScrollBarSize();
float wait = conversationList.BarSize < 1.0f ? 0.5f : 0.0f;
if (child is GUITextBlock) { child.FadeIn(wait, 0.5f); }
if (child is GUIButton btn)
{
btn.FadeIn(wait, 1.0f);
btn.TextBlock.FadeIn(wait, 0.5f);
}
}
}
}
void AssignActionsToButtons(List<GUIButton> optionButtons, GUIMessageBox target)
{
if (!options.Any())
{
GUIButton closeButton = new GUIButton(new RectTransform(Vector2.One, target.InnerFrame.RectTransform, Anchor.BottomRight, scaleBasis: ScaleBasis.Smallest)
{
MaxSize = new Point(GUI.IntScale(24)),
MinSize = new Point(24),
AbsoluteOffset = new Point(GUI.IntScale(48), GUI.IntScale(16))
}, style: "GUIButtonVerticalArrow")
{
UserData = "ContinueButton",
IgnoreLayoutGroups = true,
Bounce = true,
OnClicked = (btn, userdata) =>
{
if (actionInstance != null)
{
actionInstance.selectedOption = 0;
}
else if (actionId.HasValue)
{
SendResponse(actionId.Value, 0);
}
if (!continueConversation)
{
target.Close();
}
else
{
btn.Frame.FadeOut(0.33f, true);
}
return true;
}
};
closeButton.Children.ForEach(child => child.SpriteEffects = SpriteEffects.FlipVertically);
closeButton.Frame.FadeIn(0.5f, 0.5f);
closeButton.SlideIn(0.5f, 0.33f, 16, SlideDirection.Down);
}
for (int i = 0; i < optionButtons.Count; i++)
{
optionButtons[i].UserData = i;
optionButtons[i].OnClicked += (btn, userdata) =>
{
int selectedOption = (userdata as int?) ?? 0;
if (actionInstance != null)
{
actionInstance.selectedOption = selectedOption;
foreach (GUIButton otherButton in optionButtons)
{
otherButton.CanBeFocused = false;
if (otherButton != btn)
{
otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
btn.ExternalHighlight = true;
return true;
}
if (actionId.HasValue)
{
SendResponse(actionId.Value, selectedOption);
btn.CanBeFocused = false;
btn.ExternalHighlight = true;
foreach (GUIButton otherButton in optionButtons)
{
otherButton.CanBeFocused = false;
if (otherButton != btn)
{
otherButton.TextBlock.OverrideTextColor(Color.DarkGray * 0.8f);
}
}
return true;
}
//should not happen
return false;
};
if (closingOptions.Contains(i)) { optionButtons[i].OnClicked += target.Close; }
}
}
}
private static Tuple<Vector2, Point> GetSizes(DialogTypes dialogTypes)
{
return dialogTypes switch
{
DialogTypes.Regular => Tuple.Create(new Vector2(0.3f, 0.2f), new Point(512, 256)),
_ => Tuple.Create(new Vector2(0.3f, 0.15f), new Point(512, 128))
};
}
private static List<GUIButton> CreateConversation(GUIListBox parentBox, string text, Character speaker, IEnumerable<string> options, bool drawChathead = true)
{
var content = new GUILayoutGroup(new RectTransform(Vector2.One, parentBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
Stretch = true,
CanBeFocused = true,
AlwaysOverrideCursor = true
};
string translatedText = TextManager.Get(text, returnNull: true) ?? text;
if (speaker?.Info != null && drawChathead)
{
// chathead
new GUICustomComponent(new RectTransform(new Vector2(0.15f, 0.8f), content.RectTransform), onDraw: (sb, customComponent) =>
{
speaker.Info.DrawIcon(sb, customComponent.Rect.Center.ToVector2(), customComponent.Rect.Size.ToVector2());
});
}
var textContent = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform), childAnchor: Anchor.TopCenter)
{
AbsoluteSpacing = GUI.IntScale(5)
};
var textBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform), translatedText, wrap: true)
{
AlwaysOverrideCursor = true,
UserData = "text"
};
List<GUIButton> buttons = new List<GUIButton>();
if (options.Any())
{
foreach (string option in options)
{
var btn = new GUIButton(new RectTransform(new Vector2(0.9f, 0.01f), textContent.RectTransform), TextManager.Get(option, returnNull: true) ?? option, style: "ListBoxElement");
btn.TextBlock.TextAlignment = Alignment.CenterLeft;
btn.TextColor = btn.HoverTextColor = GUI.Style.Green;
btn.TextBlock.Wrap = true;
buttons.Add(btn);
}
}
content.Recalculate();
textContent.Recalculate();
textBlock.CalculateHeightFromText();
textBlock.RectTransform.MinSize = new Point(0, (int)(textBlock.Rect.Height * 1.2f));
foreach (GUIButton btn in buttons)
{
btn.TextBlock.SetTextPos();
btn.TextBlock.CalculateHeightFromText();
btn.RectTransform.MinSize = new Point(0, (int)(btn.TextBlock.Rect.Height * 1.2f));
}
textContent.RectTransform.MinSize = new Point(0, textContent.Children.Sum(c => c.Rect.Height + textContent.AbsoluteSpacing) + GUI.IntScale(16));
// content.RectTransform.MinSize = new Point(0, textContent.Rect.Height);
return buttons;
}
private static void SendResponse(UInt16 actionId, int selectedOption)
{
IWriteMessage outmsg = new WriteOnlyMessage();
outmsg.Write((byte)ClientPacketHeader.EVENTMANAGER_RESPONSE);
outmsg.Write(actionId);
outmsg.Write((byte)selectedOption);
GameMain.Client?.ClientPeer?.Send(outmsg, DeliveryMethod.Reliable);
}
// Too broken, left it here if I ever want to come back to it
private static List<RichTextData> GetQuoteHighlights(string text, Color color)
{
char[] quotes = { '“', '”', '\"', '\'', '「', '」'};
List<RichTextData> textColors = new List<RichTextData> { new RichTextData { StartIndex = 0 } };
bool start = true;
for (int i = 0; i < text.Length; i++)
{
char c = text[i];
if (quotes.Contains(c))
{
textColors.Last().EndIndex = i - 1;
textColors.Add(new RichTextData { StartIndex = i, Color = start ? color : (Color?) null });
start = !start;
}
}
if (textColors.LastOrDefault() is { } last && last.EndIndex == 0)
{
last.EndIndex = text.Length;
}
return textColors;
}
}
}
@@ -1,6 +1,12 @@
using Microsoft.Xna.Framework; #nullable enable
using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -11,21 +17,30 @@ namespace Barotrauma
private float intensityGraphUpdateInterval; private float intensityGraphUpdateInterval;
private float lastIntensityUpdate; private float lastIntensityUpdate;
private Event? pinnedEvent;
private Vector2 pinnedPosition = new Vector2(256, 128);
private bool isDragging;
public void DebugDraw(SpriteBatch spriteBatch) public void DebugDraw(SpriteBatch spriteBatch)
{ {
foreach (ScriptedEvent ev in activeEvents) foreach (Event ev in activeEvents)
{ {
Vector2 drawPos = ev.DebugDrawPos; Vector2 drawPos = ev.DebugDrawPos;
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
var textOffset = new Vector2(-150, 0); var textOffset = new Vector2(-150, 0);
ShapeExtensions.DrawCircle(spriteBatch, drawPos, 600, 6, Color.White, thickness: 20); spriteBatch.DrawCircle(drawPos, 600, 6, Color.White, thickness: 20);
GUI.DrawString(spriteBatch, drawPos + textOffset, ev.ToString(), Color.White, Color.Black, 0, GUI.LargeFont); GUI.DrawString(spriteBatch, drawPos + textOffset, ev.ToString(), Color.White, Color.Black, 0, GUI.LargeFont);
} }
} }
public void DebugDrawHUD(SpriteBatch spriteBatch, int y) public void DebugDrawHUD(SpriteBatch spriteBatch, int y)
{ {
foreach (ScriptedEvent scriptedEvent in activeEvents.Where(ev => !ev.IsFinished && ev is ScriptedEvent).Cast<ScriptedEvent>())
{
DrawEventTargetTags(spriteBatch, scriptedEvent);
}
GUI.DrawString(spriteBatch, new Vector2(10, y), "EventManager", Color.White, Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(10, y), "EventManager", Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 20), "Event cooldown: " + eventCoolDown, Color.White, Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 20), "Event cooldown: " + eventCoolDown, Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 35), "Current intensity: " + (int) Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, currentIntensity), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 35), "Current intensity: " + (int) Math.Round(currentIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, currentIntensity), Color.Black * 0.6f, 0, GUI.SmallFont);
@@ -81,11 +96,10 @@ namespace Barotrauma
"Intensity too high for new events: " + (int) (currentIntensity * 100) + "%/" + (int) (eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUI.SmallFont); "Intensity too high for new events: " + (int) (currentIntensity * 100) + "%/" + (int) (eventThreshold * 100) + "%", Color.LightGreen * 0.8f, null, 0, GUI.SmallFont);
y += 15; y += 15;
} }
foreach (ScriptedEventSet eventSet in pendingEventSets)
foreach (EventSet eventSet in pendingEventSets)
{ {
float distanceTraveled = MathHelper.Clamp( if (Submarine.MainSub == null) { break; }
(Submarine.MainSub.WorldPosition.X - level.StartPosition.X) / (level.EndPosition.X - level.StartPosition.X),
0.0f, 1.0f);
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "New event (ID " + eventSet.DebugIdentifier + ") after: ", Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12; y += 12;
@@ -98,6 +112,7 @@ namespace Barotrauma
Color.Orange * 0.8f, null, 0, GUI.SmallFont); Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12; y += 12;
} }
if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity) if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
{ {
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
@@ -105,6 +120,7 @@ namespace Barotrauma
Color.Orange * 0.8f, null, 0, GUI.SmallFont); Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12; y += 12;
} }
if (roundDuration < eventSet.MinMissionTime) if (roundDuration < eventSet.MinMissionTime)
{ {
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y),
@@ -115,14 +131,397 @@ namespace Barotrauma
y += 15; y += 15;
} }
GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(graphRect.X, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont);
y += 12; y += 15;
foreach (ScriptedEvent scriptedEvent in activeEvents)
foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
{ {
if (scriptedEvent.IsFinished) { continue; } GUI.DrawString(spriteBatch, new Vector2(graphRect.X + 5, y), ev.ToString(), (!ev.IsFinished ? Color.White : Color.Red) * 0.8f, null, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(graphRect.X + 5, y), scriptedEvent.ToString(), Color.White * 0.8f, null, 0, GUI.SmallFont);
y += 12; Rectangle rect = new Rectangle(new Point(graphRect.X + 5, y), GUI.SmallFont.MeasureString(ev.ToString()).ToPoint());
Rectangle outlineRect = new Rectangle(rect.Location, rect.Size);
outlineRect.Inflate(4, 4);
if (pinnedEvent == ev) { GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); }
if (rect.Contains(PlayerInput.MousePosition))
{
GUI.MouseCursor = CursorState.Hand;
GUI.DrawRectangle(spriteBatch, outlineRect, Color.White);
if (ev != pinnedEvent)
{
DrawEvent(spriteBatch, ev, rect);
}
else if (PlayerInput.SecondaryMouseButtonHeld() || PlayerInput.SecondaryMouseButtonDown())
{
pinnedEvent = null;
}
if (PlayerInput.PrimaryMouseButtonHeld() || PlayerInput.PrimaryMouseButtonDown())
{
pinnedEvent = ev;
}
}
y += 18;
}
}
public void DrawPinnedEvent(SpriteBatch spriteBatch)
{
if (pinnedEvent != null)
{
Rectangle rect = DrawEvent(spriteBatch, pinnedEvent, null);
if (rect != Rectangle.Empty)
{
if (rect.Contains(PlayerInput.MousePosition) && !isDragging)
{
GUI.MouseCursor = CursorState.Move;
if (PlayerInput.PrimaryMouseButtonDown() || PlayerInput.PrimaryMouseButtonHeld())
{
isDragging = true;
}
if (PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonHeld())
{
pinnedEvent = null;
isDragging = false;
}
}
}
if (isDragging)
{
GUI.MouseCursor = CursorState.Dragging;
pinnedPosition = PlayerInput.MousePosition - (new Vector2(rect.Width / 2.0f, -24));
if (!PlayerInput.PrimaryMouseButtonHeld())
{
isDragging = false;
}
}
}
}
private static void DrawEventTargetTags(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent)
{
if (Screen.Selected is GameScreen screen)
{
Camera cam = screen.Cam;
Dictionary<Entity, List<string>> tagsDictionary = new Dictionary<Entity, List<string>>();
foreach ((string key, List<Entity> value) in scriptedEvent.Targets)
{
foreach (Entity entity in value)
{
if (tagsDictionary.ContainsKey(entity))
{
tagsDictionary[entity].Add(key);
}
else
{
tagsDictionary.Add(entity, new List<string> { key });
}
}
}
string identifier = scriptedEvent.Prefab.Identifier;
foreach ((Entity entity, List<string> tags) in tagsDictionary)
{
if (entity.Removed) { continue; }
string text = tags.Aggregate("Tags:\n", (current, tag) => current + $" {tag.ColorizeObject()}\n").TrimEnd('\r', '\n');
if (!string.IsNullOrWhiteSpace(identifier)) { text = $"Event: {identifier.ColorizeObject()}\n{text}"; }
List<RichTextData> richTextData = RichTextData.GetRichTextData(text, out text);
Vector2 entityPos = cam.WorldToScreen(entity.WorldPosition);
Vector2 infoSize = GUI.SmallFont.MeasureString(text);
Vector2 infoPos = entityPos + new Vector2(128 * cam.Zoom, -(128 * cam.Zoom));
infoPos.Y -= infoSize.Y / 2;
Rectangle infoRect = new Rectangle(infoPos.ToPoint(), infoSize.ToPoint());
infoRect.Inflate(4, 4);
GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, infoRect, Color.White, isFilled: false);
GUI.DrawStringWithColors(spriteBatch, infoPos, text, Color.White, richTextData, font: GUI.SmallFont);
GUI.DrawLine(spriteBatch, entityPos, new Vector2(infoRect.Location.X, infoRect.Location.Y + infoRect.Height / 2), Color.White);
}
}
}
private readonly struct DebugLine
{
public readonly Vector2 Position;
public readonly Color Color;
public DebugLine(Vector2 position, Color color)
{
Position = position;
Color = color;
}
}
private Rectangle DrawEvent(SpriteBatch spriteBatch, Event ev, Rectangle? parentRect = null)
{
return ev switch
{
ScriptedEvent scriptedEvent => DrawScriptedEvent(spriteBatch, scriptedEvent, parentRect),
ArtifactEvent artifactEvent => DrawArtifactEvent(spriteBatch, artifactEvent, parentRect),
MonsterEvent monsterEvent => DrawMonsterEvent(spriteBatch, monsterEvent, parentRect),
_ => Rectangle.Empty
};
}
private Rectangle DrawScriptedEvent(SpriteBatch spriteBatch, ScriptedEvent scriptedEvent, Rectangle? parentRect = null)
{
EventAction? currentEvent = !scriptedEvent.IsFinished ? scriptedEvent.Actions[scriptedEvent.CurrentActionIndex] : null;
List<DebugLine> positions = new List<DebugLine>();
string text = $"Finished: {scriptedEvent.IsFinished.ColorizeObject()}\n" +
$"Action index: {scriptedEvent.CurrentActionIndex.ColorizeObject()}\n" +
$"Current action: {currentEvent?.ToDebugString() ?? ToolBox.ColorizeObject(null)}\n";
text += "All actions:\n";
text += FindActions(scriptedEvent).Aggregate(string.Empty, (current, action) => current + $"{new string(' ', action.Item1 * 6)}{action.Item2.ToDebugString()}\n");
text += "Targets:\n";
foreach (var (key, value) in scriptedEvent.Targets)
{
text += $" {key.ColorizeObject()}: {value.Aggregate(string.Empty, (current, entity) => current + $"{entity.ColorizeObject()} ")}\n";
}
if (scriptedEvent.Targets != null)
{
foreach ((_, List<Entity> entities) in scriptedEvent.Targets)
{
if (entities == null || !entities.Any()) { continue; }
foreach (var entity in entities)
{
positions.Add(new DebugLine(entity.WorldPosition, Color.White));
}
}
}
return DrawInfoRectangle(spriteBatch, scriptedEvent, text, parentRect, positions);
}
private Rectangle DrawArtifactEvent(SpriteBatch spriteBatch, ArtifactEvent artifactEvent, Rectangle? parentRect = null)
{
List<DebugLine> positions = new List<DebugLine>();
string text = $"Finished: {artifactEvent.IsFinished.ColorizeObject()}\n" +
$"Item: {artifactEvent.Item.ColorizeObject()}\n" +
$"Spawn pending: {artifactEvent.SpawnPending.ColorizeObject()}\n" +
$"Spawn position: {artifactEvent.SpawnPos.ColorizeObject()}\n";
if (artifactEvent.Item != null)
{
Vector2 pos = artifactEvent.Item.WorldPosition;
positions.Add(new DebugLine(pos, Color.White));
}
return DrawInfoRectangle(spriteBatch, artifactEvent, text, parentRect, positions);
}
private Rectangle DrawMonsterEvent(SpriteBatch spriteBatch, MonsterEvent monsterEvent, Rectangle? parentRect = null)
{
List<DebugLine> positions = new List<DebugLine>();
string text = $"Finished: {monsterEvent.IsFinished.ColorizeObject()}\n" +
$"Amount: {monsterEvent.MinAmount.ColorizeObject()} - {monsterEvent.MaxAmount.ColorizeObject()}\n" +
$"Spawn pending: {monsterEvent.SpawnPending.ColorizeObject()}\n" +
$"Spawn position: {monsterEvent.SpawnPos.ColorizeObject()}\n";
if (monsterEvent.SpawnPos != null && Submarine.MainSub != null)
{
Vector2 pos = monsterEvent.SpawnPos.Value;
text += $"Distance from submarine: {Vector2.Distance(pos, Submarine.MainSub.WorldPosition).ColorizeObject()}\n";
positions.Add(new DebugLine(pos, Color.White));
}
if (monsterEvent.Monsters != null)
{
text += !monsterEvent.Monsters.Any() ? $"Monsters: {"None".ColorizeObject()}" : "Monsters:\n";
foreach (Character monster in monsterEvent.Monsters)
{
text += $" {monster.ColorizeObject()} -> (Dead: {monster.IsDead.ColorizeObject()}, Health: {monster.HealthPercentage.ColorizeObject()}%, AIState: {(monster.AIController?.State).ColorizeObject()})\n";
positions.Add(new DebugLine(monster.WorldPosition, Color.Red));
}
}
return DrawInfoRectangle(spriteBatch, monsterEvent, text, parentRect, positions);
}
private Rectangle DrawInfoRectangle(SpriteBatch spriteBatch, Event @event, string text, Rectangle? parentRect = null, List<DebugLine>? drawPoints = null)
{
text = text.TrimEnd('\r', '\n');
string identifier = @event.Prefab.Identifier;
if (!string.IsNullOrWhiteSpace(identifier))
{
text = $"Identifier: {identifier.ColorizeObject()}\n{text}";
}
List<RichTextData> richTextData = RichTextData.GetRichTextData(text, out text);
Vector2 size = GUI.SmallFont.MeasureString(text);
Vector2 pos = pinnedPosition;
Rectangle infoRect;
Rectangle? infoBarRect = null;
if (parentRect != null)
{
Rectangle rect = parentRect.Value;
pos = new Vector2(350, GameMain.GraphicsHeight / 2.0f - size.Y / 2);
infoRect = new Rectangle(pos.ToPoint(), size.ToPoint());
infoRect.Inflate(8, 8);
GUI.DrawLine(spriteBatch, new Vector2(rect.Right, rect.Y + rect.Height / 2), new Vector2(infoRect.X, infoRect.Y + infoRect.Height / 2), Color.White);
}
else
{
infoRect = new Rectangle(pos.ToPoint(), size.ToPoint());
infoRect.Inflate(8, 8);
Rectangle barRect = new Rectangle(infoRect.Left, infoRect.Top - 32, infoRect.Width, 32);
const string titleHeader = "Pinned event";
GUI.DrawRectangle(spriteBatch, barRect, Color.DarkGray * 0.8f, isFilled: true);
GUI.DrawString(spriteBatch, barRect.Location.ToVector2() + barRect.Size.ToVector2() / 2 - GUI.SubHeadingFont.MeasureString(titleHeader) / 2, titleHeader, Color.White);
GUI.DrawRectangle(spriteBatch, barRect, Color.White);
infoBarRect = barRect;
}
if (drawPoints != null && drawPoints.Any() && Screen.Selected?.Cam != null)
{
foreach (DebugLine line in drawPoints)
{
if (line.Position != Vector2.Zero)
{
float xPos = infoRect.Right;
if (parentRect == null && pinnedPosition.X + infoRect.Width / 2.0f > GameMain.GraphicsWidth / 2.0f)
{
xPos = infoRect.Left;
}
GUI.DrawLine(spriteBatch, new Vector2(xPos, infoRect.Top + infoRect.Height / 2), Screen.Selected.Cam.WorldToScreen(line.Position), line.Color);
}
}
}
GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, infoRect, Color.White);
GUI.DrawStringWithColors(spriteBatch, pos, text, Color.White, richTextData, null, 0, GUI.SmallFont);
richTextData.Clear();
return infoBarRect ?? infoRect;
}
public void ClientRead(IReadMessage msg)
{
NetworkEventType eventType = (NetworkEventType)msg.ReadByte();
switch (eventType)
{
case NetworkEventType.STATUSEFFECT:
string eventIdentifier = msg.ReadString();
UInt16 actionIndex = msg.ReadUInt16();
UInt16 targetCount = msg.ReadUInt16();
List<Entity> targets = new List<Entity>();
for (int i = 0; i < targetCount; i++)
{
UInt16 targetID = msg.ReadUInt16();
Entity target = Entity.FindEntityByID(targetID);
if (target != null) { targets.Add(target); }
}
var eventPrefab = EventSet.GetEventPrefab(eventIdentifier);
if (eventPrefab == null) { return; }
int j = 0;
foreach (XElement element in eventPrefab.ConfigElement.Descendants())
{
if (j != actionIndex)
{
j++;
continue;
}
foreach (XElement subElement in element.Elements())
{
if (!subElement.Name.ToString().Equals("statuseffect", StringComparison.OrdinalIgnoreCase)) { continue; }
StatusEffect effect = StatusEffect.Load(subElement, $"EventManager.ClientRead ({eventIdentifier})");
foreach (Entity target in targets)
{
effect.Apply(effect.type, 1.0f, target, target as ISerializableEntity);
}
}
break;
}
break;
case NetworkEventType.CONVERSATION:
UInt16 identifier = msg.ReadUInt16();
string eventSprite = msg.ReadString();
byte dialogType = msg.ReadByte();
bool continueConversation = msg.ReadBoolean();
UInt16 speakerId = msg.ReadUInt16();
string text = msg.ReadString();
bool fadeToBlack = msg.ReadBoolean();
byte optionCount = msg.ReadByte();
List<string> options = new List<string>();
for (int i = 0; i < optionCount; i++)
{
options.Add(msg.ReadString());
}
byte endCount = msg.ReadByte();
int[] endings = new int[endCount];
for (int i = 0; i < endCount; i++)
{
endings[i] = msg.ReadByte();
}
if (string.IsNullOrEmpty(text) && optionCount == 0)
{
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{
if (mb.UserData is Pair<string, UInt16> pair && pair.First == "ConversationAction" && pair.Second == identifier)
{
(mb as GUIMessageBox)?.Close();
}
});
}
else
{
ConversationAction.CreateDialog(text, Entity.FindEntityByID(speakerId) as Character, options, endings, eventSprite, identifier, fadeToBlack, (ConversationAction.DialogTypes)dialogType, continueConversation);
}
if (Entity.FindEntityByID(speakerId) is Character speaker)
{
speaker.CampaignInteractionType = CampaignMode.InteractionType.None;
speaker.SetCustomInteract(null, null);
}
break;
case NetworkEventType.MISSION:
string missionIdentifier = msg.ReadString();
MissionPrefab? prefab = MissionPrefab.List.Find(mp => mp.Identifier.Equals(missionIdentifier, StringComparison.OrdinalIgnoreCase));
if (prefab != null)
{
new GUIMessageBox(string.Empty, TextManager.GetWithVariable("missionunlocked", "[missionname]", prefab.Name),
new string[0], type: GUIMessageBox.Type.InGame, icon: prefab.Icon, relativeSize: new Vector2(0.3f, 0.15f), minSize: new Point(512, 128))
{
IconColor = prefab.IconColor
};
}
break;
} }
} }
} }
@@ -6,6 +6,7 @@ namespace Barotrauma
{ {
public override void ClientReadInitial(IReadMessage msg) public override void ClientReadInitial(IReadMessage msg)
{ {
items.Clear();
ushort itemCount = msg.ReadUInt16(); ushort itemCount = msg.ReadUInt16();
for (int i = 0; i < itemCount; i++) for (int i = 0; i < itemCount; i++)
{ {
@@ -17,7 +18,7 @@ namespace Barotrauma
} }
if (items.Count != itemCount) if (items.Count != itemCount)
{ {
throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + "mission: " + Prefab.Identifier + ")"); throw new System.Exception("Error in CargoMission.ClientReadInitial: item count does not match the server count (" + itemCount + " != " + items.Count + ", mission: " + Prefab.Identifier + ")");
} }
if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; } if (requiredDeliveryAmount == 0) { requiredDeliveryAmount = items.Count; }
} }
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
@@ -13,10 +14,20 @@ namespace Barotrauma
string header = messageIndex < Headers.Count ? Headers[messageIndex] : ""; string header = messageIndex < Headers.Count ? Headers[messageIndex] : "";
string message = messageIndex < Messages.Count ? Messages[messageIndex] : ""; string message = messageIndex < Messages.Count ? Messages[messageIndex] : "";
CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message));
}
private IEnumerable<object> ShowMessageBoxAfterRoundSummary(string header, string message)
{
while (GUIMessageBox.VisibleBox?.UserData is RoundSummary)
{
yield return new WaitForSeconds(1.0f);
}
new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon) new GUIMessageBox(header, message, buttons: new string[0], type: GUIMessageBox.Type.InGame, icon: Prefab.Icon)
{ {
IconColor = Prefab.IconColor IconColor = Prefab.IconColor
}; };
yield return CoroutineStatus.Success;
} }
public void ClientRead(IReadMessage msg) public void ClientRead(IReadMessage msg)
@@ -492,7 +492,7 @@ namespace Barotrauma
} }
} }
public Vector2 MeasureString(string text) public Vector2 MeasureString(string text, bool removeExtraSpacing = false)
{ {
if (text == null) if (text == null)
{ {
@@ -501,7 +501,16 @@ namespace Barotrauma
float currentLineX = 0.0f; float currentLineX = 0.0f;
Vector2 retVal = Vector2.Zero; Vector2 retVal = Vector2.Zero;
if (!removeExtraSpacing)
{
retVal.Y = baseHeight * 1.8f; retVal.Y = baseHeight * 1.8f;
}
else
{
retVal.Y = baseHeight;
}
for (int i = 0; i < text.Length; i++) for (int i = 0; i < text.Length; i++)
{ {
if (text[i] == '\n') if (text[i] == '\n')
@@ -1,6 +1,7 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
@@ -144,6 +145,15 @@ namespace Barotrauma
GetSize(element); GetSize(element);
} }
public Sprite GetDefaultSprite()
{
return GetSprite(GUIComponent.ComponentState.None);
}
public Sprite GetSprite(GUIComponent.ComponentState state)
{
return Sprites.ContainsKey(state) ? Sprites[state]?.First()?.Sprite : null;
}
public void GetSize(XElement element) public void GetSize(XElement element)
{ {
Point size = new Point(0, 0); Point size = new Point(0, 0);
@@ -0,0 +1,729 @@
using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
class CrewManagement
{
private CampaignMode campaign => campaignUI.Campaign;
private readonly CampaignUI campaignUI;
private readonly GUIComponent parentComponent;
private GUIListBox hireableList, pendingList, crewList;
private GUIFrame characterPreviewFrame;
private GUIDropDown sortingDropDown;
private GUITextBlock totalBlock;
private GUIButton validateHiresButton;
private GUIButton clearAllButton;
private List<CharacterInfo> PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires;
private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign();
private Point resolutionWhenCreated;
private enum SortingMethod
{
AlphabeticalAsc,
JobAsc,
PriceAsc,
PriceDesc,
SkillAsc,
SkillDesc
}
public CrewManagement(CampaignUI campaignUI, GUIComponent parentComponent)
{
this.campaignUI = campaignUI;
this.parentComponent = parentComponent;
CreateUI();
UpdateLocationView(campaignUI.Campaign.Map.CurrentLocation, true);
campaignUI.Campaign.Map.OnLocationChanged += (prevLocation, newLocation) => UpdateLocationView(newLocation, true, prevLocation);
}
public void RefreshPermissions()
{
RefreshCrewFrames(hireableList);
RefreshCrewFrames(crewList);
RefreshCrewFrames(pendingList);
if (clearAllButton != null) { clearAllButton.Enabled = HasPermission; }
}
private void RefreshCrewFrames(GUIListBox listBox)
{
if (listBox == null) { return; }
listBox.CanBeFocused = HasPermission;
foreach (GUIComponent child in listBox.Content.Children)
{
if (child.FindChild(c => c is GUIButton && c.UserData is CharacterInfo, true) is GUIButton buyButton)
{
buyButton.Enabled = HasPermission;
}
}
}
private void CreateUI()
{
if (parentComponent.FindChild(c => c.UserData as string == "glow") is GUIComponent glowChild)
{
parentComponent.RemoveChild(glowChild);
}
if (parentComponent.FindChild(c => c.UserData as string == "container") is GUIComponent containerChild)
{
parentComponent.RemoveChild(containerChild);
}
new GUIFrame(new RectTransform(new Vector2(1.25f, 1.25f), parentComponent.RectTransform, Anchor.Center),
style: "OuterGlow", color: Color.Black * 0.7f)
{
UserData = "glow"
};
new GUIFrame(new RectTransform(new Vector2(0.95f), parentComponent.RectTransform, anchor: Anchor.Center),
style: null)
{
CanBeFocused = false,
UserData = "container"
};
var availableMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform)
{
MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
})
{
Stretch = true,
RelativeSpacing = 0.02f
};
// Header ------------------------------------------------
var headerGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), availableMainGroup.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.005f
};
var imageWidth = (float)headerGroup.Rect.Height / headerGroup.Rect.Width;
new GUIImage(new RectTransform(new Vector2(imageWidth, 1.0f), headerGroup.RectTransform), "CrewManagementHeaderIcon");
new GUITextBlock(new RectTransform(new Vector2(1.0f - imageWidth, 1.0f), headerGroup.RectTransform), TextManager.Get("campaigncrew.header"), font: GUI.LargeFont)
{
CanBeFocused = false,
ForceUpperCase = true
};
var hireablesGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center,
parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), availableMainGroup.RectTransform)).RectTransform))
{
RelativeSpacing = 0.015f,
Stretch = true
};
var sortGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.04f), hireablesGroup.RectTransform), isHorizontal: true)
{
RelativeSpacing = 0.015f
};
new GUITextBlock(new RectTransform(new Vector2(0.15f, 1.0f), sortGroup.RectTransform), text: TextManager.Get("campaignstore.sortby"));
sortingDropDown = new GUIDropDown(new RectTransform(new Vector2(0.4f, 1.0f), sortGroup.RectTransform), elementCount: 5)
{
OnSelected = (child, userData) =>
{
SortCharacters(hireableList, (SortingMethod)userData);
return true;
}
};
var tag = "sortingmethod.";
sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.JobAsc), userData: SortingMethod.JobAsc);
sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.SkillAsc), userData: SortingMethod.SkillAsc);
sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.SkillDesc), userData: SortingMethod.SkillDesc);
sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.PriceAsc), userData: SortingMethod.PriceAsc);
sortingDropDown.AddItem(TextManager.Get(tag + SortingMethod.PriceDesc), userData: SortingMethod.PriceDesc);
hireableList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.96f),
hireablesGroup.RectTransform,
anchor: Anchor.Center))
{
Spacing = 1
};
var pendingAndCrewMainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).RectTransform, anchor: Anchor.TopRight)
{
MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
})
{
Stretch = true,
RelativeSpacing = 0.02f
};
var playerBalanceContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.75f / 14.0f), pendingAndCrewMainGroup.RectTransform), childAnchor: Anchor.TopRight)
{
RelativeSpacing = 0.005f
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform),
TextManager.Get("campaignstore.balance"), font: GUI.Font, textAlignment: Alignment.BottomRight)
{
AutoScaleVertical = true,
ForceUpperCase = true
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), playerBalanceContainer.RectTransform),
"", font: GUI.SubHeadingFont, textAlignment: Alignment.TopRight)
{
AutoScaleVertical = true,
TextScale = 1.1f,
TextGetter = () => FormatCurrency(campaign.Money)
};
var pendingAndCrewGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), anchor: Anchor.Center,
parent: new GUIFrame(new RectTransform(new Vector2(1.0f, 13.25f / 14.0f), pendingAndCrewMainGroup.RectTransform)
{
MaxSize = new Point(560, campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Rect.Height)
}).RectTransform));
float height = 0.05f;
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaigncrew.pending"), font: GUI.SubHeadingFont);
pendingList = new GUIListBox(new RectTransform(new Vector2(1.0f, 8 * height), pendingAndCrewGroup.RectTransform))
{
Spacing = 1
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), TextManager.Get("campaignmenucrew"), font: GUI.SubHeadingFont);
crewList = new GUIListBox(new RectTransform(new Vector2(1.0f, (8)* height), pendingAndCrewGroup.RectTransform))
{
Spacing = 1
};
var group = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), isHorizontal: true);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), group.RectTransform), TextManager.Get("campaignstore.total"));
totalBlock = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), group.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.Right)
{
TextScale = 1.1f
};
group = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, height), pendingAndCrewGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.TopRight)
{
RelativeSpacing = 0.01f
};
validateHiresButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaigncrew.validate"))
{
ForceUpperCase = true,
OnClicked = (b, o) => ValidatePendingHires(true)
};
clearAllButton = new GUIButton(new RectTransform(new Vector2(1.0f / 3.0f, 1.0f), group.RectTransform), text: TextManager.Get("campaignstore.clearall"))
{
ForceUpperCase = true,
Enabled = HasPermission,
OnClicked = (b, o) => RemoveAllPendingHires()
};
resolutionWhenCreated = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
}
private void UpdateLocationView(Location location, bool removePending, Location prevLocation = null)
{
if (prevLocation != null && prevLocation == location && GameMain.NetworkMember != null) { return; }
if (characterPreviewFrame != null)
{
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null;
}
UpdateHireables(location);
if (pendingList != null)
{
if (removePending)
{
PendingHires?.Clear();
pendingList.Content.ClearChildren();
}
else
{
PendingHires?.ForEach(ci => AddPendingHire(ci));
}
SetTotalHireCost();
}
UpdateCrew();
}
private void UpdateHireables(Location location)
{
if (hireableList != null)
{
hireableList.Content.Children.ToList().ForEach(c => hireableList.RemoveChild(c));
var hireableCharacters = location.GetHireableCharacters();
if (hireableCharacters.None())
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.2f), hireableList.Content.RectTransform), TextManager.Get("HireUnavailable"), textAlignment: Alignment.Center)
{
CanBeFocused = false
};
}
else
{
foreach (CharacterInfo c in hireableCharacters)
{
if (c == null) { continue; }
CreateCharacterFrame(c, hireableList);
}
}
sortingDropDown.SelectItem(SortingMethod.JobAsc);
hireableList.UpdateScrollBarSize();
}
}
public void SetHireables(Location location, List<CharacterInfo> availableHires)
{
HireManager hireManager = location.HireManager;
if (hireManager == null) { return; }
int hireVal = hireManager.AvailableCharacters.Aggregate(0, (curr, hire) => curr + hire.GetIdentifier());
int newVal = availableHires.Aggregate(0, (curr, hire) => curr + hire.GetIdentifier());
if (hireVal != newVal)
{
location.HireManager.AvailableCharacters = availableHires;
UpdateHireables(location);
}
}
public void UpdateCrew()
{
crewList.Content.Children.ToList().ForEach(c => crewList.Content.RemoveChild(c));
foreach (CharacterInfo c in GameMain.GameSession.CrewManager.GetCharacterInfos())
{
if (c == null || !((c.Character?.IsBot ?? true) || campaign is SinglePlayerCampaign)) { continue; }
CreateCharacterFrame(c, crewList);
}
SortCharacters(crewList, SortingMethod.JobAsc);
crewList.UpdateScrollBarSize();
}
private void SortCharacters(GUIListBox list, SortingMethod sortingMethod)
{
if (sortingMethod == SortingMethod.AlphabeticalAsc)
{
list.Content.RectTransform.SortChildren((x, y) =>
(x.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item1.Name.CompareTo((y.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item1.Name));
}
else if (sortingMethod == SortingMethod.JobAsc)
{
SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) =>
String.Compare((x.GUIComponent.UserData as Tuple<CharacterInfo, float>)?.Item1.Job.Name, (y.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item1.Job.Name, StringComparison.Ordinal));
}
else if (sortingMethod == SortingMethod.PriceAsc || sortingMethod == SortingMethod.PriceDesc)
{
SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) =>
(x.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item1.Salary.CompareTo((y.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item1.Salary));
if (sortingMethod == SortingMethod.PriceDesc) { list.Content.RectTransform.ReverseChildren(); }
}
else if (sortingMethod == SortingMethod.SkillAsc || sortingMethod == SortingMethod.SkillDesc)
{
SortCharacters(list, SortingMethod.AlphabeticalAsc);
list.Content.RectTransform.SortChildren((x, y) =>
(x.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item2.CompareTo((y.GUIComponent.UserData as Tuple<CharacterInfo, float>).Item2));
if (sortingMethod == SortingMethod.SkillDesc) { list.Content.RectTransform.ReverseChildren(); }
}
}
private void CreateCharacterFrame(CharacterInfo characterInfo, GUIListBox listBox)
{
Skill skill = null;
Color? jobColor = null;
if (characterInfo.Job != null)
{
skill = characterInfo.Job?.PrimarySkill ?? characterInfo.Job.Skills.OrderByDescending(s => s.Level).FirstOrDefault();
jobColor = characterInfo.Job.Prefab.UIColor;
}
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, 55), parent: listBox.Content.RectTransform), "ListBoxElement")
{
UserData = new Tuple<CharacterInfo, float>(characterInfo, skill != null ? skill.Level : 0.0f)
};
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), frame.RectTransform, anchor: Anchor.Center), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{
Stretch = true
};
float portraitWidth = (0.8f * mainGroup.Rect.Height) / mainGroup.Rect.Width;
new GUICustomComponent(new RectTransform(new Vector2(portraitWidth, 0.8f), mainGroup.RectTransform),
onDraw: (sb, component) => characterInfo.DrawIcon(sb, component.Rect.Center.ToVector2(), targetAreaSize: component.Rect.Size.ToVector2()))
{
CanBeFocused = false
};
GUILayoutGroup nameAndJobGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f - portraitWidth, 0.8f), mainGroup.RectTransform));
GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Name, textColor: jobColor, textAlignment: Alignment.BottomLeft)
{
CanBeFocused = false
};
nameBlock.Text = ToolBox.LimitString(nameBlock.Text, nameBlock.Font, nameBlock.Rect.Width);
GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), nameAndJobGroup.RectTransform),
characterInfo.Job.Name, textColor: Color.White, font: GUI.SmallFont, textAlignment: Alignment.TopLeft)
{
CanBeFocused = false
};
jobBlock.Text = ToolBox.LimitString(jobBlock.Text, jobBlock.Font, jobBlock.Rect.Width);
float width = 0.6f / 3;
if (characterInfo.Job != null)
{
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(width, 0.6f), mainGroup.RectTransform), isHorizontal: true);
float iconWidth = (float)skillGroup.Rect.Height / skillGroup.Rect.Width;
GUIImage skillIcon = new GUIImage(new RectTransform(new Vector2(iconWidth, 1.0f), skillGroup.RectTransform), skill.Icon)
{
CanBeFocused = false
};
if (jobColor.HasValue) { skillIcon.Color = jobColor.Value; }
new GUITextBlock(new RectTransform(new Vector2(1.0f - iconWidth, 1.0f), skillGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.CenterLeft)
{
CanBeFocused = false
};
}
if (listBox != crewList)
{
new GUITextBlock(new RectTransform(new Vector2(width, 1.0f), mainGroup.RectTransform), FormatCurrency(characterInfo.Salary), textAlignment: Alignment.Center)
{
CanBeFocused = false
};
}
if (listBox == hireableList)
{
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementAddButton")
{
UserData = characterInfo,
Enabled = HasPermission,
OnClicked = (b, o) => AddPendingHire(o as CharacterInfo)
};
}
else if (listBox == pendingList)
{
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementRemoveButton")
{
UserData = characterInfo,
Enabled = HasPermission,
OnClicked = (b, o) => RemovePendingHire(o as CharacterInfo)
};
}
else if (listBox == crewList && campaign != null)
{
var currentCrew = GameMain.GameSession.CrewManager.GetCharacterInfos();
new GUIButton(new RectTransform(new Vector2(width, 0.9f), mainGroup.RectTransform), style: "CrewManagementFireButton")
{
UserData = characterInfo,
//can't fire if there's only one character in the crew
Enabled = currentCrew.Contains(characterInfo) && currentCrew.Count() > 1 && HasPermission,
OnClicked = (btn, obj) =>
{
var confirmDialog = new GUIMessageBox(
TextManager.Get("FireWarningHeader"),
TextManager.GetWithVariable("FireWarningText", "[charactername]", ((CharacterInfo)obj).Name),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
confirmDialog.Buttons[0].UserData = (CharacterInfo)obj;
confirmDialog.Buttons[0].OnClicked = FireCharacter;
confirmDialog.Buttons[0].OnClicked += confirmDialog.Close;
confirmDialog.Buttons[1].OnClicked = confirmDialog.Close;
return true;
}
};
}
}
private void CreateCharacterPreviewFrame(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
{
Pivot pivot = listBox == hireableList ? Pivot.TopLeft : Pivot.TopRight;
Point absoluteOffset = new Point(
pivot == Pivot.TopLeft ? listBox.Parent.Parent.Rect.Right + 5 : listBox.Parent.Parent.Rect.Left - 5,
characterFrame.Rect.Top);
int frameSize = (int)(GUI.Scale * 300);
if (GameMain.GraphicsHeight - (absoluteOffset.Y + frameSize) < 0)
{
pivot = listBox == hireableList ? Pivot.BottomLeft : Pivot.BottomRight;
absoluteOffset.Y = characterFrame.Rect.Bottom;
}
characterPreviewFrame = new GUIFrame(new RectTransform(new Point(frameSize), parent: campaignUI.GetTabContainer(CampaignMode.InteractionType.Crew).Parent.RectTransform, pivot: pivot)
{
AbsoluteOffset = absoluteOffset
}, style: "InnerFrame")
{
UserData = characterInfo
};
GUILayoutGroup mainGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), characterPreviewFrame.RectTransform, anchor: Anchor.Center))
{
RelativeSpacing = 0.01f
};
// Character info
GUILayoutGroup infoGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal: true);
GUILayoutGroup infoLabelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.4f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
GUILayoutGroup infoValueGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 1.0f), infoGroup.RectTransform)) { Stretch = true };
float blockHeight = 1.0f / 4;
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("name"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), characterInfo.Name);
if (characterInfo.HasGenders)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("gender"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), TextManager.Get(characterInfo.Gender.ToString()));
}
if (characterInfo.Job is Job job)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoLabelGroup.RectTransform), TextManager.Get("tabmenu.job"));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), infoValueGroup.RectTransform), job.Name);
}
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), infoValueGroup.RectTransform), TextManager.Get("personalitytrait." + trait.Name.Replace(" ", "")));
}
infoLabelGroup.Recalculate();
infoValueGroup.Recalculate();
new GUIImage(new RectTransform(new Vector2(1.0f, 0.05f), mainGroup.RectTransform), "HorizontalLine");
// Skills
GUILayoutGroup skillGroup = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.475f), mainGroup.RectTransform), isHorizontal: true);
GUILayoutGroup skillNameGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 1.0f), skillGroup.RectTransform));
GUILayoutGroup skillLevelGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), skillGroup.RectTransform));
List<Skill> characterSkills = characterInfo.Job.Skills;
blockHeight = 1.0f / characterSkills.Count;
foreach (Skill skill in characterSkills)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillNameGroup.RectTransform), TextManager.Get("SkillName." + skill.Identifier));
new GUITextBlock(new RectTransform(new Vector2(1.0f, blockHeight), skillLevelGroup.RectTransform), ((int)skill.Level).ToString(), textAlignment: Alignment.Right);
}
}
private bool SelectCharacter(GUIListBox listBox, GUIFrame characterFrame, CharacterInfo characterInfo)
{
if (characterPreviewFrame != null && characterPreviewFrame.UserData != characterInfo)
{
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null;
}
if (listBox == null || characterFrame == null || characterInfo == null) { return false; }
if (characterPreviewFrame == null)
{
CreateCharacterPreviewFrame(listBox, characterFrame, characterInfo);
}
return true;
}
private bool AddPendingHire(CharacterInfo characterInfo, bool createNetworkMessage = true)
{
hireableList.Content.RemoveChild(hireableList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo));
hireableList.UpdateScrollBarSize();
if (!PendingHires.Contains(characterInfo)) { PendingHires.Add(characterInfo); }
CreateCharacterFrame(characterInfo, pendingList);
SortCharacters(pendingList, SortingMethod.JobAsc);
pendingList.UpdateScrollBarSize();
SetTotalHireCost();
if (createNetworkMessage) { SendCrewState(true); }
return true;
}
private bool RemovePendingHire(CharacterInfo characterInfo, bool setTotalHireCost = true, bool createNetworkMessage = true)
{
if (PendingHires.Contains(characterInfo)) { PendingHires.Remove(characterInfo); }
pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == characterInfo));
pendingList.UpdateScrollBarSize();
CreateCharacterFrame(characterInfo, hireableList);
SortCharacters(hireableList, (SortingMethod)sortingDropDown.SelectedItemData);
hireableList.UpdateScrollBarSize();
if (setTotalHireCost) { SetTotalHireCost(); }
if (createNetworkMessage) { SendCrewState(true); }
return true;
}
private bool RemoveAllPendingHires(bool createNetworkMessage = true)
{
pendingList.Content.Children.ToList().ForEach(c => RemovePendingHire((c.UserData as Tuple<CharacterInfo, float>).Item1, setTotalHireCost: false, createNetworkMessage));
SetTotalHireCost();
return true;
}
private void SetTotalHireCost()
{
if (pendingList == null || totalBlock == null || validateHiresButton == null) { return; }
int total = 0;
pendingList.Content.Children.ForEach(c => total += (c.UserData as Tuple<CharacterInfo, float>).Item1.Salary);
totalBlock.Text = FormatCurrency(total);
bool enoughMoney = campaign != null ? total <= campaign.Money : true;
totalBlock.TextColor = enoughMoney ? Color.White : Color.Red;
validateHiresButton.Enabled = enoughMoney && pendingList.Content.RectTransform.Children.Any();
}
public bool ValidatePendingHires(bool createNetworkEvent = false)
{
List<CharacterInfo> hires = new List<CharacterInfo>();
int total = 0;
foreach (GUIComponent c in pendingList.Content.Children.ToList())
{
if (c.UserData is Tuple<CharacterInfo, float> info)
{
hires.Add(info.Item1);
total += info.Item1.Salary;
}
}
if (hires.None() || total > campaign.Money) { return false; }
bool atLeastOneHired = false;
foreach (CharacterInfo ci in hires)
{
if (campaign.TryHireCharacter(campaign.Map.CurrentLocation, ci))
{
atLeastOneHired = true;
PendingHires.Remove(ci);
pendingList.Content.RemoveChild(pendingList.Content.FindChild(c => (c.UserData as Tuple<CharacterInfo, float>).Item1 == ci));
}
else
{
break;
}
}
if (atLeastOneHired)
{
UpdateLocationView(campaign.Map.CurrentLocation, true);
SelectCharacter(null, null, null);
var dialog = new GUIMessageBox(
TextManager.Get("newcrewmembers"),
TextManager.GetWithVariable("crewhiredmessage", "[location]", campaignUI?.Campaign?.Map?.CurrentLocation?.Name),
new string[] { TextManager.Get("Ok") });
dialog.Buttons[0].OnClicked += dialog.Close;
}
if (createNetworkEvent)
{
SendCrewState(true, validateHires: true);
}
return false;
}
private bool FireCharacter(GUIButton button, object selection)
{
if (!(selection is CharacterInfo characterInfo)) { return false; }
campaign.CrewManager.FireCharacter(characterInfo);
SelectCharacter(null, null, null);
UpdateCrew();
SendCrewState(false, firedCharacter: characterInfo);
return false;
}
public void Update()
{
if (GameMain.GraphicsWidth != resolutionWhenCreated.X || GameMain.GraphicsHeight != resolutionWhenCreated.Y)
{
CreateUI();
UpdateLocationView(campaign.Map.CurrentLocation, false);
}
if ((GUI.MouseOn?.UserData as Tuple<CharacterInfo, float>)?.Item1 is CharacterInfo characterInfo)
{
if (characterPreviewFrame == null || characterInfo != characterPreviewFrame.UserData)
{
GUIComponent component = GUI.MouseOn;
GUIListBox listBox = null;
do
{
if (component.Parent is GUIListBox)
{
listBox = component.Parent as GUIListBox;
break;
}
else if (component.Parent != null)
{
component = component.Parent;
}
else
{
break;
}
} while (listBox == null);
if (listBox != null)
{
SelectCharacter(listBox, GUI.MouseOn as GUIFrame, characterInfo);
}
}
else
{
// TODO: Reposition the current preview panel if necessary
// Could happen if we scroll a list while hovering?
}
}
else if (characterPreviewFrame != null)
{
characterPreviewFrame.Parent?.RemoveChild(characterPreviewFrame);
characterPreviewFrame = null;
}
}
public void SetPendingHires(List<int> characterInfos, Location location)
{
List<CharacterInfo> oldHires = PendingHires.ToList();
foreach (CharacterInfo pendingHire in oldHires)
{
RemovePendingHire(pendingHire, createNetworkMessage: false);
}
PendingHires.Clear();
foreach (int identifier in characterInfos)
{
CharacterInfo match = location.HireManager.AvailableCharacters.Find(info => info.GetIdentifier() == identifier);
if (match != null)
{
PendingHires.Add(match);
AddPendingHire(match, createNetworkMessage: false);
}
else
{
DebugConsole.ThrowError("Received a hire that doesn't exist.");
}
}
}
/// <summary>
/// Notify the server of crew changes
/// </summary>
/// <param name="updatePending">When set to true will tell the server to update the pending hires</param>
/// <param name="firedCharacter">When not null tell the server to fire this character</param>
/// <param name="validateHires">When set to true will tell the server to validate pending hires</param>
public void SendCrewState(bool updatePending, CharacterInfo firedCharacter = null, bool validateHires = false)
{
if (campaign is MultiPlayerCampaign)
{
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.CREW);
msg.Write(updatePending);
if (updatePending)
{
msg.Write((ushort)PendingHires.Count);
foreach (CharacterInfo pendingHire in PendingHires)
{
msg.Write(pendingHire.GetIdentifier());
}
}
msg.Write(validateHires);
msg.Write(firedCharacter != null);
if (firedCharacter != null)
{
msg.Write(firedCharacter.GetIdentifier());
}
GameMain.Client.ClientPeer?.Send(msg, DeliveryMethod.Reliable);
}
}
private string FormatCurrency(int currency) => TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", currency));
}
}
@@ -79,6 +79,8 @@ namespace Barotrauma
public static readonly string[] rectComponentLabels = { "X", "Y", "W", "H" }; public static readonly string[] rectComponentLabels = { "X", "Y", "W", "H" };
public static readonly string[] colorComponentLabels = { "R", "G", "B", "A" }; public static readonly string[] colorComponentLabels = { "R", "G", "B", "A" };
private static readonly object mutex = new object();
public static Vector2 ReferenceResolution => new Vector2(1920f, 1080f); public static Vector2 ReferenceResolution => new Vector2(1920f, 1080f);
public static float Scale => (UIWidth / ReferenceResolution.X + GameMain.GraphicsHeight / ReferenceResolution.Y) / 2.0f * GameSettings.HUDScale; public static float Scale => (UIWidth / ReferenceResolution.X + GameMain.GraphicsHeight / ReferenceResolution.Y) / 2.0f * GameSettings.HUDScale;
public static float xScale => UIWidth / ReferenceResolution.X * GameSettings.HUDScale; public static float xScale => UIWidth / ReferenceResolution.X * GameSettings.HUDScale;
@@ -126,7 +128,9 @@ namespace Barotrauma
private static Texture2D t; private static Texture2D t;
private static Sprite[] MouseCursorSprites => Style.CursorSprite; private static Sprite[] MouseCursorSprites => Style.CursorSprite;
private static bool debugDrawSounds, debugDrawEvents; private static bool debugDrawSounds, debugDrawEvents, debugDrawMetadata;
private static int debugDrawMetadataOffset;
private static readonly string[] ignoredMetadataInfo = { string.Empty, string.Empty, string.Empty, string.Empty };
public static GraphicsDevice GraphicsDevice { get; private set; } public static GraphicsDevice GraphicsDevice { get; private set; }
@@ -287,6 +291,9 @@ namespace Barotrauma
/// </summary> /// </summary>
public static void Draw(Camera cam, SpriteBatch spriteBatch) public static void Draw(Camera cam, SpriteBatch spriteBatch)
{ {
lock (mutex)
{
if (ScreenChanged) if (ScreenChanged)
{ {
updateList.Clear(); updateList.Clear();
@@ -514,6 +521,29 @@ namespace Barotrauma
"Ctrl+E to show EventManager debug info", Color.White, Color.Black * 0.5f, 0, SmallFont); "Ctrl+E to show EventManager debug info", Color.White, Color.Black * 0.5f, 0, SmallFont);
} }
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
{
if (debugDrawMetadata)
{
string text = "Ctrl+M to hide campaign metadata debug info\n\n" +
$"Ctrl+1 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[0]) ? "hide" : "show")} outpost reputations, \n" +
$"Ctrl+2 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[1]) ? "hide" : "show")} faction reputations, \n" +
$"Ctrl+3 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[2]) ? "hide" : "show")} upgrade levels, \n" +
$"Ctrl+4 to {(string.IsNullOrWhiteSpace(ignoredMetadataInfo[3]) ? "hide" : "show")} upgrade prices";
var (x, y) = SmallFont.MeasureString(text);
Vector2 pos = new Vector2(GameMain.GraphicsWidth - (x + 10), 300);
DrawString(spriteBatch, pos, text, Color.White, Color.Black * 0.5f, 0, SmallFont);
pos.Y += y + 8;
campaignMode.CampaignMetadata?.DebugDraw(spriteBatch, pos, debugDrawMetadataOffset, ignoredMetadataInfo);
}
else
{
const string text = "Ctrl+M to show campaign metadata debug info";
DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - (SmallFont.MeasureString(text).X + 10), 300),
text, Color.White, Color.Black * 0.5f, 0, SmallFont);
}
}
if (MouseOn != null) if (MouseOn != null)
{ {
RectTransform mouseOnRect = MouseOn.RectTransform; RectTransform mouseOnRect = MouseOn.RectTransform;
@@ -571,6 +601,8 @@ namespace Barotrauma
} }
} }
GameMain.GameSession?.EventManager?.DrawPinnedEvent(spriteBatch);
if (HUDLayoutSettings.DebugDraw) HUDLayoutSettings.Draw(spriteBatch); if (HUDLayoutSettings.DebugDraw) HUDLayoutSettings.Draw(spriteBatch);
if (GameMain.Client != null) GameMain.Client.Draw(spriteBatch); if (GameMain.Client != null) GameMain.Client.Draw(spriteBatch);
@@ -631,6 +663,7 @@ namespace Barotrauma
} }
HideCursor = false; HideCursor = false;
} }
}
public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float aberrationStrength = 1.0f) public static void DrawBackgroundSprite(SpriteBatch spriteBatch, Sprite backgroundSprite, float aberrationStrength = 1.0f)
{ {
@@ -681,6 +714,8 @@ namespace Barotrauma
/// Note: does not automatically add children, because we might want to enforce a custom order for them. /// Note: does not automatically add children, because we might want to enforce a custom order for them.
/// </summary> /// </summary>
public static void AddToUpdateList(GUIComponent component) public static void AddToUpdateList(GUIComponent component)
{
lock (mutex)
{ {
if (component == null) if (component == null)
{ {
@@ -701,12 +736,15 @@ namespace Barotrauma
additions.Enqueue(component); additions.Enqueue(component);
} }
} }
}
/// <summary> /// <summary>
/// Adds the component on the removal queue. /// Adds the component on the removal queue.
/// Removal list is evaluated last, and thus any item on both lists are not added to update list. /// Removal list is evaluated last, and thus any item on both lists are not added to update list.
/// </summary> /// </summary>
public static void RemoveFromUpdateList(GUIComponent component, bool alsoChildren = true) public static void RemoveFromUpdateList(GUIComponent component, bool alsoChildren = true)
{
lock (mutex)
{ {
if (updateListSet.Contains(component)) if (updateListSet.Contains(component))
{ {
@@ -724,8 +762,11 @@ namespace Barotrauma
} }
} }
} }
}
public static void ClearUpdateList() public static void ClearUpdateList()
{
lock (mutex)
{ {
if (KeyboardDispatcher.Subscriber is GUIComponent && !updateList.Contains(KeyboardDispatcher.Subscriber as GUIComponent)) if (KeyboardDispatcher.Subscriber is GUIComponent && !updateList.Contains(KeyboardDispatcher.Subscriber as GUIComponent))
{ {
@@ -734,8 +775,11 @@ namespace Barotrauma
updateList.Clear(); updateList.Clear();
updateListSet.Clear(); updateListSet.Clear();
} }
}
private static void RefreshUpdateList() private static void RefreshUpdateList()
{
lock (mutex)
{ {
foreach (var component in updateList) foreach (var component in updateList)
{ {
@@ -749,8 +793,11 @@ namespace Barotrauma
ProcessHelperList(last); ProcessHelperList(last);
ProcessRemovals(); ProcessRemovals();
} }
}
private static void ProcessAdditions() private static void ProcessAdditions()
{
lock (mutex)
{ {
while (additions.Count > 0) while (additions.Count > 0)
{ {
@@ -762,8 +809,11 @@ namespace Barotrauma
} }
} }
} }
}
private static void ProcessRemovals() private static void ProcessRemovals()
{
lock (mutex)
{ {
while (removals.Count > 0) while (removals.Count > 0)
{ {
@@ -776,8 +826,11 @@ namespace Barotrauma
} }
} }
} }
}
private static void ProcessHelperList(List<GUIComponent> list) private static void ProcessHelperList(List<GUIComponent> list)
{
lock (mutex)
{ {
if (list.Count == 0) { return; } if (list.Count == 0) { return; }
foreach (var item in list) foreach (var item in list)
@@ -800,8 +853,11 @@ namespace Barotrauma
} }
list.Clear(); list.Clear();
} }
}
private static void HandlePersistingElements(float deltaTime) private static void HandlePersistingElements(float deltaTime)
{
lock (mutex)
{ {
GUIMessageBox.AddActiveToGUIUpdateList(); GUIMessageBox.AddActiveToGUIUpdateList();
@@ -820,26 +876,35 @@ namespace Barotrauma
GUIMessageBox.VisibleBox.AddToGUIUpdateList(); GUIMessageBox.VisibleBox.AddToGUIUpdateList();
} }
} }
}
#endregion #endregion
public static GUIComponent MouseOn { get; private set; } public static GUIComponent MouseOn { get; private set; }
public static bool IsMouseOn(GUIComponent target) public static bool IsMouseOn(GUIComponent target)
{
lock (mutex)
{ {
if (target == null) { return false; } if (target == null) { return false; }
//if (MouseOn == null) { return true; } //if (MouseOn == null) { return true; }
return target == MouseOn || target.IsParentOf(MouseOn); return target == MouseOn || target.IsParentOf(MouseOn);
} }
}
public static void ForceMouseOn(GUIComponent c) public static void ForceMouseOn(GUIComponent c)
{
lock (mutex)
{ {
MouseOn = c; MouseOn = c;
} }
}
/// <summary> /// <summary>
/// Updated automatically before updating the elements on the update list. /// Updated automatically before updating the elements on the update list.
/// </summary> /// </summary>
public static GUIComponent UpdateMouseOn() public static GUIComponent UpdateMouseOn()
{
lock (mutex)
{ {
GUIComponent prevMouseOn = MouseOn; GUIComponent prevMouseOn = MouseOn;
MouseOn = null; MouseOn = null;
@@ -875,7 +940,11 @@ namespace Barotrauma
return MouseOn; return MouseOn;
} }
}
private static CursorState UpdateMouseCursorState(GUIComponent c) private static CursorState UpdateMouseCursorState(GUIComponent c)
{
lock (mutex)
{ {
// Waiting and drag cursor override everything else // Waiting and drag cursor override everything else
if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; } if (MouseCursor == CursorState.Waiting) { return CursorState.Waiting; }
@@ -937,24 +1006,13 @@ namespace Barotrauma
} }
break; break;
} }
// Campaign map highlighted location
case LobbyScreen lobby:
{
if (lobby.CampaignUI?.Campaign.Map.HighlightedLocation != null) { return CursorState.Hand; }
break;
}
case NetLobbyScreen lobby:
{
if (lobby.CampaignUI?.Campaign.Map.HighlightedLocation != null) { return CursorState.Hand; }
break;
}
} }
} }
if (c != null && c.Visible) if (c != null && c.Visible)
{ {
if (c.AlwaysOverrideCursor) { return c.HoverCursor; }
// When a button opens a submenu, it increases to the size of the entire screen. // When a button opens a submenu, it increases to the size of the entire screen.
// And this is of course picked up as clickable area. // And this is of course picked up as clickable area.
// There has to be a better way of checking this but for now this works. // There has to be a better way of checking this but for now this works.
@@ -1059,6 +1117,7 @@ namespace Barotrauma
component.Rect.Contains(PlayerInput.MousePosition); component.Rect.Contains(PlayerInput.MousePosition);
} }
} }
}
/// <summary> /// <summary>
/// Set the cursor to an hourglass. /// Set the cursor to an hourglass.
@@ -1079,10 +1138,13 @@ namespace Barotrauma
} }
public static void ClearCursorWait() public static void ClearCursorWait()
{
lock (mutex)
{ {
CoroutineManager.StopCoroutines("WaitCursorTimeout"); CoroutineManager.StopCoroutines("WaitCursorTimeout");
MouseCursor = CursorState.Default; MouseCursor = CursorState.Default;
} }
}
public static bool HasSizeChanged(Point referenceResolution, float referenceUIScale, float referenceHUDScale) public static bool HasSizeChanged(Point referenceResolution, float referenceUIScale, float referenceHUDScale)
{ {
@@ -1091,6 +1153,8 @@ namespace Barotrauma
} }
public static void Update(float deltaTime) public static void Update(float deltaTime)
{
lock (mutex)
{ {
if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.S)) if (PlayerInput.KeyDown(Keys.LeftControl) && PlayerInput.KeyHit(Keys.S))
{ {
@@ -1100,6 +1164,51 @@ namespace Barotrauma
{ {
debugDrawEvents = !debugDrawEvents; debugDrawEvents = !debugDrawEvents;
} }
if (PlayerInput.IsCtrlDown() && PlayerInput.KeyHit(Keys.M))
{
debugDrawMetadata = !debugDrawMetadata;
}
if (debugDrawMetadata)
{
if (PlayerInput.KeyHit(Keys.Up))
{
debugDrawMetadataOffset--;
}
if (PlayerInput.KeyHit(Keys.Down))
{
debugDrawMetadataOffset++;
}
if (PlayerInput.IsCtrlDown())
{
if (PlayerInput.KeyHit(Keys.D1))
{
ignoredMetadataInfo[0] = ignoredMetadataInfo[0] == string.Empty ? "reputation.location" : string.Empty;
debugDrawMetadataOffset = 0;
}
if (PlayerInput.KeyHit(Keys.D2))
{
ignoredMetadataInfo[1] = ignoredMetadataInfo[1] == string.Empty ? "reputation.faction" : string.Empty;
debugDrawMetadataOffset = 0;
}
if (PlayerInput.KeyHit(Keys.D3))
{
ignoredMetadataInfo[2] = ignoredMetadataInfo[2] == string.Empty ? "upgrade." : string.Empty;
debugDrawMetadataOffset = 0;
}
if (PlayerInput.KeyHit(Keys.D4))
{
ignoredMetadataInfo[3] = ignoredMetadataInfo[3] == string.Empty ? "upgradeprice." : string.Empty;
debugDrawMetadataOffset = 0;
}
}
}
HandlePersistingElements(deltaTime); HandlePersistingElements(deltaTime);
RefreshUpdateList(); RefreshUpdateList();
@@ -1108,8 +1217,11 @@ namespace Barotrauma
updateList.ForEach(c => c.UpdateAuto(deltaTime)); updateList.ForEach(c => c.UpdateAuto(deltaTime));
UpdateMessages(deltaTime); UpdateMessages(deltaTime);
} }
}
private static void UpdateMessages(float deltaTime) private static void UpdateMessages(float deltaTime)
{
lock (mutex)
{ {
foreach (GUIMessage msg in messages) foreach (GUIMessage msg in messages)
{ {
@@ -1146,6 +1258,8 @@ namespace Barotrauma
messages.RemoveAll(m => m.Timer <= 0.0f); messages.RemoveAll(m => m.Timer <= 0.0f);
} }
}
#region Element drawing #region Element drawing
public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color) public static void DrawIndicator(SpriteBatch spriteBatch, Vector2 worldPosition, Camera cam, float hideDist, Sprite sprite, Color color)
@@ -2012,9 +2126,13 @@ namespace Barotrauma
}; };
msgBox.Buttons[0].OnClicked = (_, userdata) => msgBox.Buttons[0].OnClicked = (_, userdata) =>
{ {
if (GameMain.GameSession.RoundSummary?.Frame != null)
{
GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame);
}
TogglePauseMenu(btn, userData); TogglePauseMenu(btn, userData);
GameMain.GameSession.LoadPrevious(); GameMain.GameSession.LoadPreviousSave();
GameMain.LobbyScreen.Select();
return true; return true;
}; };
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
@@ -2026,15 +2144,20 @@ namespace Barotrauma
}; };
return true; return true;
}; };
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
{
UserData = "save"
};
button.OnClicked += QuitClicked;
button.OnClicked += TogglePauseMenu;
} }
else if (GameMain.GameSession.GameMode is SubTestMode) else if (GameMain.GameSession.GameMode is TestGameMode)
{ {
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor")) button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor"))
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
GameMain.GameSession.GameMode.End(""); GameMain.GameSession.EndRound("");
return true; return true;
} }
}; };
@@ -2042,14 +2165,17 @@ namespace Barotrauma
} }
else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound)) else if (!GameMain.GameSession.GameMode.IsSinglePlayer && GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.ManageRound))
{ {
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("EndRound")) new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform),
text: TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby": "EndRound"))
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; } if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; }
if (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition) if (GameMain.GameSession.GameMode is CampaignMode || (!Submarine.MainSub.AtStartPosition && !Submarine.MainSub.AtEndPosition))
{ {
var msgBox = new GUIMessageBox("", TextManager.Get("EndRoundSubNotAtLevelEnd"), new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) var msgBox = new GUIMessageBox("",
TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
{ {
UserData = "verificationprompt" UserData = "verificationprompt"
}; };
@@ -2073,19 +2199,6 @@ namespace Barotrauma
} }
} }
if (Screen.Selected == GameMain.LobbyScreen)
{
if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
{
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit"))
{
UserData = "save"
};
button.OnClicked += QuitClicked;
button.OnClicked += TogglePauseMenu;
}
}
button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuQuit")); button = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuQuit"));
button.OnClicked += (btn, userData) => button.OnClicked += (btn, userData) =>
{ {
@@ -2118,6 +2231,8 @@ namespace Barotrauma
} }
return true; return true;
}; };
GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock));
} }
} }
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework; using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma namespace Barotrauma
@@ -146,6 +147,11 @@ namespace Barotrauma
} }
} }
public bool Pulse { get; set; }
private float pulseTimer;
private float pulseExpand;
private bool flashed;
public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT) public GUIButton(RectTransform rectT, string text = "", Alignment textAlignment = Alignment.Center, string style = "", Color? color = null) : base(style, rectT)
{ {
CanBeFocused = true; CanBeFocused = true;
@@ -196,7 +202,14 @@ namespace Barotrauma
protected override void Draw(SpriteBatch spriteBatch) protected override void Draw(SpriteBatch spriteBatch)
{ {
//do nothing if (Pulse && pulseTimer > 1.0f)
{
Rectangle expandRect = Rect;
float expand = (pulseExpand * 20.0f) * GUI.Scale;
expandRect.Inflate(expand, expand);
GUI.Style.ButtonPulse.Draw(spriteBatch, expandRect, ToolBox.GradientLerp(pulseExpand, Color.White, Color.White, Color.Transparent));
}
} }
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
@@ -243,14 +256,42 @@ namespace Barotrauma
} }
} }
else else
{
if (!ExternalHighlight)
{ {
State = Selected ? ComponentState.Selected : ComponentState.None; State = Selected ? ComponentState.Selected : ComponentState.None;
} }
else
{
State = ComponentState.Hover;
}
}
foreach (GUIComponent child in Children) foreach (GUIComponent child in Children)
{ {
child.State = State; child.State = State;
} }
if (Pulse)
{
pulseTimer += deltaTime;
if (pulseTimer > 1.0f)
{
if (!flashed)
{
flashed = true;
Frame.Flash(Color.White * 0.2f, 0.8f, true);
}
pulseExpand += deltaTime;
if (pulseExpand > 1.0f)
{
pulseTimer = 0.0f;
pulseExpand = 0.0f;
flashed = false;
}
}
}
} }
} }
} }
@@ -20,7 +20,7 @@ namespace Barotrauma
_instance = new GUICanvas(); _instance = new GUICanvas();
if (GameMain.Instance != null) if (GameMain.Instance != null)
{ {
GameMain.Instance.OnResolutionChanged += RecalculateSize; GameMain.Instance.ResolutionChanged += RecalculateSize;
} }
_instance.ItemComponentHolder = new GUIFrame(new RectTransform(Vector2.One, _instance, Anchor.Center)).RectTransform; _instance.ItemComponentHolder = new GUIFrame(new RectTransform(Vector2.One, _instance, Anchor.Center)).RectTransform;
} }
@@ -1,24 +0,0 @@
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public class GUIColorSettings
{
// Inventory
public static Color EquipmentSlotIconColor = new Color(99, 70, 64);
// Health HUD
public static Color BuffColorLow = Color.LightGreen;
public static Color BuffColorMedium = Color.Green;
public static Color BuffColorHigh = Color.DarkGreen;
public static Color DebuffColorLow = Color.DarkSalmon;
public static Color DebuffColorMedium = Color.Red;
public static Color DebuffColorHigh = Color.DarkRed;
public static Color HealthBarColorLow = Color.Red;
public static Color HealthBarColorMedium = Color.Orange;
public static Color HealthBarColorHigh = new Color(78, 114, 88);
}
}
@@ -11,6 +11,8 @@ using System.Net;
namespace Barotrauma namespace Barotrauma
{ {
public enum SlideDirection { Up, Down, Left, Right }
public abstract class GUIComponent public abstract class GUIComponent
{ {
#region Hierarchy #region Hierarchy
@@ -18,6 +20,8 @@ namespace Barotrauma
public CursorState HoverCursor = CursorState.Default; public CursorState HoverCursor = CursorState.Default;
public bool AlwaysOverrideCursor = false;
public delegate bool SecondaryButtonDownHandler(GUIComponent component, object userData); public delegate bool SecondaryButtonDownHandler(GUIComponent component, object userData);
public SecondaryButtonDownHandler OnSecondaryClicked; public SecondaryButtonDownHandler OnSecondaryClicked;
@@ -75,7 +79,7 @@ namespace Barotrauma
public virtual void RemoveChild(GUIComponent child) public virtual void RemoveChild(GUIComponent child)
{ {
if (child == null) return; if (child == null) { return; }
child.RectTransform.Parent = null; child.RectTransform.Parent = null;
} }
@@ -140,6 +144,11 @@ namespace Barotrauma
public bool AutoDraw { get; set; } = true; public bool AutoDraw { get; set; } = true;
public int UpdateOrder { get; set; } public int UpdateOrder { get; set; }
public bool Bounce { get; set; }
private float bounceTimer;
private float bounceJump;
private bool bounceDown;
public Action<GUIComponent> OnAddedToGUIUpdateList; public Action<GUIComponent> OnAddedToGUIUpdateList;
public enum ComponentState { None, Hover, Pressed, Selected, HoverSelected }; public enum ComponentState { None, Hover, Pressed, Selected, HoverSelected };
@@ -158,6 +167,8 @@ namespace Barotrauma
protected Color disabledColor; protected Color disabledColor;
protected Color pressedColor; protected Color pressedColor;
public bool GlowOnSelect { get; set; }
private CoroutineHandle pulsateCoroutine; private CoroutineHandle pulsateCoroutine;
protected Color flashColor; protected Color flashColor;
@@ -327,6 +338,11 @@ namespace Barotrauma
get { return RectTransform.CountChildren; } get { return RectTransform.CountChildren; }
} }
/// <summary>
/// Currently only used for the fade effect in GUIListBox, should be set to the same value as Color but only assigned once
/// </summary>
public Color DefaultColor { get; set; }
public virtual Color Color public virtual Color Color
{ {
get { return color; } get { return color; }
@@ -463,6 +479,36 @@ namespace Barotrauma
} }
} }
if (Bounce)
{
if (bounceTimer > 3.0f || bounceDown)
{
RectTransform.ScreenSpaceOffset = new Point(RectTransform.ScreenSpaceOffset.X, (int) -(bounceJump * 10f));
if (!bounceDown)
{
bounceJump += deltaTime * 4;
if (bounceJump > 0.5f)
{
bounceDown = true;
}
}
else
{
bounceJump -= deltaTime * 4;
if (bounceJump <= 0.0f)
{
bounceJump = 0.0f;
bounceTimer = 0.0f;
bounceDown = false;
}
}
}
else
{
bounceTimer += deltaTime;
}
}
if (flashTimer > 0.0f) if (flashTimer > 0.0f)
{ {
flashTimer -= deltaTime; flashTimer -= deltaTime;
@@ -526,12 +572,14 @@ namespace Barotrauma
protected virtual Color GetColor(ComponentState state) protected virtual Color GetColor(ComponentState state)
{ {
if (!Enabled) { return DisabledColor; } if (!Enabled) { return DisabledColor; }
if (ExternalHighlight) { return HoverColor; }
return state switch return state switch
{ {
ComponentState.Hover => HoverColor, ComponentState.Hover => HoverColor,
ComponentState.HoverSelected => HoverColor, ComponentState.HoverSelected => HoverColor,
ComponentState.Pressed => PressedColor, ComponentState.Pressed => PressedColor,
ComponentState.Selected => SelectedColor, ComponentState.Selected when !GlowOnSelect => SelectedColor,
_ => Color, _ => Color,
}; };
} }
@@ -619,6 +667,11 @@ namespace Barotrauma
} }
} }
if (GlowOnSelect && State == ComponentState.Selected)
{
GUI.UIGlow.Draw(spriteBatch, Rect, SelectedColor);
}
if (flashTimer > 0.0f) if (flashTimer > 0.0f)
{ {
//the number of flashes depends on the duration, 1 flash per 1 full second //the number of flashes depends on the duration, 1 flash per 1 full second
@@ -690,6 +743,7 @@ namespace Barotrauma
protected virtual void SetAlpha(float a) protected virtual void SetAlpha(float a)
{ {
color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a); color = new Color(color.R / 255.0f, color.G / 255.0f, color.B / 255.0f, a);
hoverColor = new Color(hoverColor.R / 255.0f, hoverColor.G / 255.0f, hoverColor.B / 255.0f, a);;
} }
public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null) public virtual void Flash(Color? color = null, float flashDuration = 1.5f, bool useRectangleFlash = false, bool useCircularFlash = false, Vector2? flashRectInflate = null)
@@ -707,10 +761,77 @@ namespace Barotrauma
CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter)); CoroutineManager.StartCoroutine(LerpAlpha(0.0f, duration, removeAfter));
} }
private IEnumerable<object> LerpAlpha(float to, float duration, bool removeAfter) public void FadeIn(float wait, float duration)
{
SetAlpha(0.0f);
CoroutineManager.StartCoroutine(LerpAlpha(1.0f, duration, false, wait));
}
public void SlideIn(float wait, float duration, int amount, SlideDirection direction)
{
RectTransform.ScreenSpaceOffset = direction switch
{
SlideDirection.Up => new Point(0, amount),
SlideDirection.Down => new Point(0, -amount),
SlideDirection.Left => new Point(amount, 0),
SlideDirection.Right => new Point(-amount, 0),
_ => RectTransform.ScreenSpaceOffset
};
CoroutineManager.StartCoroutine(SlideToPosition(duration, wait, Vector2.Zero));
}
public void SlideOut(float duration, int amount, SlideDirection direction)
{
RectTransform.ScreenSpaceOffset = Point.Zero;
Vector2 targetPos = direction switch
{
SlideDirection.Up => new Vector2(0, amount),
SlideDirection.Down => new Vector2(0, -amount),
SlideDirection.Left => new Vector2(amount, 0),
SlideDirection.Right => new Vector2(-amount, 0),
_ => Vector2.Zero
};
CoroutineManager.StartCoroutine(SlideToPosition(duration, 0.0f, targetPos));
}
private IEnumerable<object> SlideToPosition(float duration, float wait, Vector2 target)
{ {
float t = 0.0f; float t = 0.0f;
float startA = color.A; var (startX, startY) = RectTransform.ScreenSpaceOffset.ToVector2();
var (endX, endY) = target;
while (t < wait)
{
t += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
t = 0.0f;
while (t < duration)
{
t += CoroutineManager.DeltaTime;
RectTransform.ScreenSpaceOffset = new Point((int)MathHelper.Lerp(startX, endX, t / duration), (int)MathHelper.Lerp(startY, endY, t / duration));
yield return CoroutineStatus.Running;
}
RectTransform.ScreenSpaceOffset = new Point(0, 0);
yield return CoroutineStatus.Success;
}
private IEnumerable<object> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f)
{
State = ComponentState.None;
float t = 0.0f;
float startA = color.A / 255.0f;
while (t < wait)
{
t += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
t = 0.0f;
while (t < duration) while (t < duration)
{ {
@@ -6,6 +6,8 @@ namespace Barotrauma
{ {
public class GUIFrame : GUIComponent public class GUIFrame : GUIComponent
{ {
public int OutlineThickness { get; set; }
public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT) public GUIFrame(RectTransform rectT, string style = "", Color? color = null) : base(style, rectT)
{ {
Enabled = true; Enabled = true;
@@ -26,7 +28,7 @@ namespace Barotrauma
if (OutlineColor != Color.Transparent) if (OutlineColor != Color.Transparent)
{ {
GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false); GUI.DrawRectangle(spriteBatch, Rect, OutlineColor * (OutlineColor.A/255.0f), false, thickness: OutlineThickness);
} }
} }
} }
@@ -42,17 +42,32 @@ namespace Barotrauma
{ {
return crop; return crop;
} }
set }
public void SetCrop(bool state, bool center = true)
{ {
crop = value; crop = state;
if (crop) if (crop && sprite != null)
{ {
sourceRect.Width = Math.Min(sprite.SourceRect.Width, Rect.Width); sourceRect.Width = Math.Min(sprite.SourceRect.Width, (int)(Rect.Width / Scale));
sourceRect.Height = Math.Min(sprite.SourceRect.Height, Rect.Height); sourceRect.Height = Math.Min(sprite.SourceRect.Height, (int)(Rect.Height / Scale));
}
if (center)
{
sourceRect.X = (sprite.SourceRect.Width - sourceRect.Width) / 2;
sourceRect.Y = (sprite.SourceRect.Height - sourceRect.Height) / 2;
}
origin = sourceRect.Size.ToVector2() / 2;
}
else
{
origin = sprite == null ? Vector2.Zero : sprite.size / 2;
} }
} }
private Vector2 origin;
public float Scale public float Scale
{ {
get; get;
@@ -72,7 +87,8 @@ namespace Barotrauma
{ {
if (sprite == value) return; if (sprite == value) return;
sprite = value; sprite = value;
sourceRect = sprite.SourceRect; sourceRect = value == null ? Rectangle.Empty : value.SourceRect;
origin = value == null ? Vector2.Zero : value.size / 2;
if (scaleToFit) RecalculateScale(); if (scaleToFit) RecalculateScale();
} }
} }
@@ -134,7 +150,8 @@ namespace Barotrauma
{ {
loadingTextures = true; loadingTextures = true;
loading = true; loading = true;
TaskPool.Add(LoadTextureAsync(), (Task) => TaskPool.Add("LoadTextureAsync",
LoadTextureAsync(), (Task) =>
{ {
loading = false; loading = false;
lazyLoaded = true; lazyLoaded = true;
@@ -178,7 +195,7 @@ namespace Barotrauma
} }
else if (sprite?.Texture != null) else if (sprite?.Texture != null)
{ {
spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, sprite.size / 2, spriteBatch.Draw(sprite.Texture, Rect.Center.ToVector2(), sourceRect, currentColor * (currentColor.A / 255.0f), Rotation, origin,
Scale, SpriteEffects, 0.0f); Scale, SpriteEffects, 0.0f);
} }
@@ -134,7 +134,7 @@ namespace Barotrauma
(RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) * (RectTransform.Children.Count(c => !c.GUIComponent.IgnoreLayoutGroups) - 1) *
(absoluteSpacing + relativeSpacing * thisSize); (absoluteSpacing + relativeSpacing * thisSize);
stretchFactor = totalSize <= 0.0f || minSize >= thisSize ? stretchFactor = totalSize <= 0.0f || minSize >= thisSize || totalSize == minSize ?
1.0f : 1.0f :
(thisSize - minSize) / (totalSize - minSize); (thisSize - minSize) / (totalSize - minSize);
} }
@@ -59,6 +59,37 @@ namespace Barotrauma
private bool useGridLayout; private bool useGridLayout;
private float targetScroll;
private GUIComponent pendingScroll;
public bool AllowMouseWheelScroll { get; set; } = true;
/// <summary>
/// Scrolls the list smoothly
/// </summary>
public bool SmoothScroll { get; set; }
/// <summary>
/// Whether to only allow scrolling from one element to the next when smooth scrolling is enabled
/// </summary>
public bool ClampScrollToElements { get; set; }
/// <summary>
/// When set to true elements at the bottom of the list are gradually faded
/// </summary>
public bool FadeElements { get; set; }
/// <summary>
/// Adds enough extra padding to the bottom so the end of the scroll will only contain the last element
/// </summary>
public bool PadBottom { get; set; }
/// <summary>
/// When set to true always selects the topmost item on the list
/// </summary>
public bool SelectTop { get; set; }
public bool UseGridLayout public bool UseGridLayout
{ {
get { return useGridLayout; } get { return useGridLayout; }
@@ -190,9 +221,12 @@ namespace Barotrauma
public GUIComponent DraggedElement => draggedElement; public GUIComponent DraggedElement => draggedElement;
private bool scheduledScroll = false;
/// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param> /// <param name="isScrollBarOnDefaultSide">For horizontal listbox, default side is on the bottom. For vertical, it's on the right.</param>
public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT) public GUIListBox(RectTransform rectT, bool isHorizontal = false, Color? color = null, string style = "", bool isScrollBarOnDefaultSide = true, bool useMouseDownToSelect = false) : base(style, rectT)
{ {
HoverCursor = CursorState.Hand;
CanBeFocused = true; CanBeFocused = true;
selected = new List<GUIComponent>(); selected = new List<GUIComponent>();
this.useMouseDownToSelect = useMouseDownToSelect; this.useMouseDownToSelect = useMouseDownToSelect;
@@ -364,6 +398,49 @@ namespace Barotrauma
} }
} }
/// <summary>
/// Scrolls the list to the specific element, currently only works when smooth scrolling and PadBottom are enabled.
/// </summary>
/// <param name="component"></param>
public void ScrollToElement(GUIComponent component)
{
GUI.PlayUISound(GUISoundType.Click);
List<GUIComponent> children = Content.Children.ToList();
int index = children.IndexOf(component);
if (index < 0) { return; }
targetScroll = MathHelper.Clamp(MathHelper.Lerp(0, 1, MathUtils.InverseLerp(0, (children.Count - 0.9f), index)), ScrollBar.MinValue, ScrollBar.MaxValue);
}
public void ScrollToEnd(float duration)
{
CoroutineManager.StartCoroutine(ScrollCoroutine());
IEnumerable<object> ScrollCoroutine()
{
if (BarSize >= 1.0f)
{
yield return CoroutineStatus.Success;
}
float t = 0.0f;
float startScroll = BarScroll * BarSize;
float distanceToTravel = ScrollBar.MaxValue - startScroll;
float progress = startScroll;
float speed = distanceToTravel / duration;
while (t < duration && !MathUtils.NearlyEqual(ScrollBar.MaxValue, progress))
{
t += CoroutineManager.DeltaTime;
progress += speed * CoroutineManager.DeltaTime;
BarScroll = progress;
yield return CoroutineStatus.Running;
}
yield return CoroutineStatus.Success;
}
}
private void UpdateChildrenRect() private void UpdateChildrenRect()
{ {
//dragging //dragging
@@ -404,6 +481,32 @@ namespace Barotrauma
} }
} }
if (SelectTop)
{
foreach (GUIComponent child in Content.Children)
{
child.CanBeFocused = !selected.Contains(child);
if (!child.CanBeFocused)
{
child.State = ComponentState.None;
}
}
}
if (SelectTop && Content.Children.Any() && pendingScroll == null)
{
GUIComponent component = Content.Children.FirstOrDefault(c => (c.Rect.Y - Content.Rect.Y) / (float)c.Rect.Height > -0.1f);
if (component != null && !selected.Contains(component))
{
int index = Content.Children.ToList().IndexOf(component);
if (index >= 0)
{
Select(index, false, false, takeKeyBoardFocus: true);
}
}
}
for (int i = 0; i < Content.CountChildren; i++) for (int i = 0; i < Content.CountChildren; i++)
{ {
var child = Content.RectTransform.GetChild(i)?.GUIComponent; var child = Content.RectTransform.GetChild(i)?.GUIComponent;
@@ -418,7 +521,16 @@ namespace Barotrauma
if (mouseDown) if (mouseDown)
{ {
Select(i, autoScroll: false); if (SelectTop)
{
pendingScroll = child;
ScrollToElement(child);
Select(i, autoScroll: false, takeKeyBoardFocus: true);
}
else
{
Select(i, autoScroll: false, takeKeyBoardFocus: true);
}
} }
if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child) if (CanDragElements && PlayerInput.LeftButtonDown() && GUI.MouseOn == child)
@@ -538,10 +650,93 @@ namespace Barotrauma
{ {
UpdateScrollBarSize(); UpdateScrollBarSize();
} }
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && PlayerInput.ScrollWheelSpeed != 0)
if (FadeElements)
{
foreach (var (component, _) in childVisible)
{
float lerp = 0;
float y = component.Rect.Y;
float contentY = Content.Rect.Y;
float height = component.Rect.Height;
if (y < Content.Rect.Y)
{
float distance = (contentY - y) / height;
lerp = distance;
}
float centerY = Content.Rect.Y + Content.Rect.Height / 2.0f;
if (y > centerY)
{
float distance = (y - centerY) / (centerY - height);
lerp = distance;
}
component.Color = component.HoverColor = ToolBox.GradientLerp(lerp, component.DefaultColor, Color.Transparent);
component.DisabledColor = ToolBox.GradientLerp(lerp, component.Style.DisabledColor, Color.Transparent);
component.HoverColor = ToolBox.GradientLerp(lerp, component.Style.HoverColor, Color.Transparent);
foreach (var child in component.GetAllChildren())
{
Color gradient = ToolBox.GradientLerp(lerp, child.DefaultColor, Color.Transparent);
child.Color = child.HoverColor = gradient;
if (child is GUITextBlock block)
{
block.TextColor = block.HoverTextColor = gradient;
}
}
}
}
if (SmoothScroll)
{
if (targetScroll > -1)
{
float distance = Math.Abs(targetScroll - BarScroll);
float speed = Math.Max(distance * BarSize, 0.1f);
BarScroll = (1.0f - speed) * BarScroll + speed * targetScroll;
if (MathUtils.NearlyEqual(BarScroll, targetScroll) || GUIScrollBar.DraggingBar != null)
{
targetScroll = -1;
pendingScroll = null;
}
}
}
if ((GUI.IsMouseOn(this) || GUI.IsMouseOn(ScrollBar)) && AllowMouseWheelScroll && PlayerInput.ScrollWheelSpeed != 0)
{
float speed = PlayerInput.ScrollWheelSpeed / 500.0f * BarSize;
if (SmoothScroll)
{
if (ClampScrollToElements)
{
bool scrollDown = Math.Clamp(PlayerInput.ScrollWheelSpeed, 0, 1) > 0;
if (scrollDown)
{
SelectPrevious(takeKeyBoardFocus: true);
}
else
{
SelectNext(takeKeyBoardFocus: true);
}
}
else
{
pendingScroll = null;
if (targetScroll < 0) { targetScroll = BarScroll; }
targetScroll -= speed;
targetScroll = Math.Clamp(targetScroll, ScrollBar.MinValue, ScrollBar.MaxValue);
}
}
else
{ {
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize;
} }
}
ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f; ScrollBar.Enabled = ScrollBarEnabled && BarSize < 1.0f;
if (AutoHideScrollBar) if (AutoHideScrollBar)
{ {
@@ -553,35 +748,47 @@ namespace Barotrauma
} }
} }
public void SelectNext(bool force = false, bool autoScroll = true) public void SelectNext(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false)
{ {
int index = SelectedIndex + 1; int index = SelectedIndex + 1;
while (index < Content.CountChildren) while (index < Content.CountChildren)
{ {
if (Content.GetChild(index).Visible) GUIComponent child = Content.GetChild(index);
if (child.Visible)
{ {
Select(index, force, autoScroll); Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus);
if (SmoothScroll)
{
pendingScroll = child;
ScrollToElement(child);
}
break; break;
} }
index++; index++;
} }
} }
public void SelectPrevious(bool force = false, bool autoScroll = true) public void SelectPrevious(bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false)
{ {
int index = SelectedIndex - 1; int index = SelectedIndex - 1;
while (index >= 0) while (index >= 0)
{ {
if (Content.GetChild(index).Visible) GUIComponent child = Content.GetChild(index);
if (child.Visible)
{ {
Select(index, force, autoScroll); Select(index, force, !SmoothScroll && autoScroll, takeKeyBoardFocus: takeKeyBoardFocus);
if (SmoothScroll)
{
pendingScroll = child;
ScrollToElement(child);
}
break; break;
} }
index--; index--;
} }
} }
public void Select(int childIndex, bool force = false, bool autoScroll = true) public void Select(int childIndex, bool force = false, bool autoScroll = true, bool takeKeyBoardFocus = false)
{ {
if (childIndex >= Content.CountChildren || childIndex < 0) { return; } if (childIndex >= Content.CountChildren || childIndex < 0) { return; }
@@ -646,7 +853,7 @@ namespace Barotrauma
} }
// If one of the children is the subscriber, we don't want to register, because it will unregister the child. // If one of the children is the subscriber, we don't want to register, because it will unregister the child.
if (RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber)) if (takeKeyBoardFocus && RectTransform.GetAllChildren().None(rt => rt.GUIComponent == GUI.KeyboardDispatcher.Subscriber))
{ {
Selected = true; Selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
@@ -712,6 +919,14 @@ namespace Barotrauma
totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height; totalSize += (ScrollBar.IsHorizontal) ? child.Rect.Width : child.Rect.Height;
} }
totalSize += Content.CountChildren * Spacing; totalSize += Content.CountChildren * Spacing;
if (PadBottom)
{
GUIComponent last = Content.Children.LastOrDefault();
if (last != null)
{
totalSize += Rect.Height - last.Rect.Height;
}
}
} }
float minScrollBarSize = 20.0f; float minScrollBarSize = 20.0f;
@@ -57,7 +57,7 @@ namespace Barotrauma
public GUIMessage(string text, Color color, float lifeTime, ScalableFont font = null) public GUIMessage(string text, Color color, float lifeTime, ScalableFont font = null)
{ {
coloredText = new ColoredText(text, color, false); coloredText = new ColoredText(text, color, false, false);
this.lifeTime = lifeTime; this.lifeTime = lifeTime;
Timer = lifeTime; Timer = lifeTime;
@@ -69,7 +69,7 @@ namespace Barotrauma
public GUIMessage(string text, Color color, Vector2 worldPosition, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, ScalableFont font = null) public GUIMessage(string text, Color color, Vector2 worldPosition, Vector2 velocity, float lifeTime, Alignment textAlignment = Alignment.Center, ScalableFont font = null)
{ {
coloredText = new ColoredText(text, color, false); coloredText = new ColoredText(text, color, false, false);
WorldSpace = true; WorldSpace = true;
pos = worldPosition; pos = worldPosition;
Timer = lifeTime; Timer = lifeTime;
@@ -30,6 +30,7 @@ namespace Barotrauma
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 string Tag { get; private set; }
public bool Closed { get; private set; }
public GUIImage Icon public GUIImage Icon
{ {
@@ -47,9 +48,16 @@ namespace Barotrauma
} }
} }
private bool alwaysVisible; public GUIImage BackgroundIcon { get; private set; }
private GUIImage newBackgroundIcon;
public bool AutoClose;
private readonly bool alwaysVisible;
private float openState; private float openState;
private float iconState;
private bool iconSwitching;
private bool closing; private bool closing;
private Type type; private Type type;
@@ -62,7 +70,7 @@ namespace Barotrauma
this.Buttons[0].OnClicked = Close; this.Buttons[0].OnClicked = Close;
} }
public GUIMessageBox(string headerText, string text, string[] buttons, Vector2? relativeSize = null, Point? minSize = null, Alignment textAlignment = Alignment.TopLeft, Type type = Type.Default, string tag = "", Sprite icon = null) public GUIMessageBox(string headerText, string text, string[] 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)
: base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox") : base(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: GUI.Style.GetComponentStyle("GUIMessageBox." + type) != null ? "GUIMessageBox." + type : "GUIMessageBox")
{ {
int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0; int width = (int)(DefaultWidth * (type == Type.Default ? 1.0f : 1.5f)), height = 0;
@@ -80,6 +88,15 @@ namespace Barotrauma
} }
} }
if (backgroundIcon != null)
{
BackgroundIcon = new GUIImage(new RectTransform(backgroundIcon.size.ToPoint(), RectTransform), backgroundIcon)
{
IgnoreLayoutGroups = true,
Color = Color.Transparent
};
}
InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, type == Type.InGame ? Anchor.TopCenter : Anchor.Center) { IsFixedSize = false }, style: null); InnerFrame = new GUIFrame(new RectTransform(new Point(width, height), RectTransform, type == Type.InGame ? Anchor.TopCenter : Anchor.Center) { IsFixedSize = false }, style: null);
GUI.Style.Apply(InnerFrame, "", this); GUI.Style.Apply(InnerFrame, "", this);
this.type = type; this.type = type;
@@ -145,6 +162,7 @@ namespace Barotrauma
InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight); InnerFrame.RectTransform.AbsoluteOffset = new Point(0, GameMain.GraphicsHeight);
alwaysVisible = true; alwaysVisible = true;
CanBeFocused = false; CanBeFocused = false;
AutoClose = true;
GUI.Style.Apply(InnerFrame, "", this); GUI.Style.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),
@@ -157,6 +175,10 @@ namespace Barotrauma
{ {
Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), icon, scaleToFit: true); Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), icon, scaleToFit: true);
} }
else if (iconStyle != string.Empty)
{
Icon = new GUIImage(new RectTransform(new Vector2(0.2f, 0.95f), horizontalLayoutGroup.RectTransform), iconStyle, scaleToFit: true);
}
Content = new GUILayoutGroup(new RectTransform(new Vector2(icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform)); Content = new GUILayoutGroup(new RectTransform(new Vector2(icon != null ? 0.65f : 0.85f, 1.0f), horizontalLayoutGroup.RectTransform));
@@ -182,6 +204,10 @@ namespace Barotrauma
Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize = Text.RectTransform.NonScaledSize = Text.RectTransform.MinSize = Text.RectTransform.MaxSize =
new Point(Text.Rect.Width, Text.Rect.Height); new Point(Text.Rect.Width, Text.Rect.Height);
Text.RectTransform.IsFixedSize = true; Text.RectTransform.IsFixedSize = true;
if (string.IsNullOrWhiteSpace(headerText))
{
Content.ChildAnchor = Anchor.Center;
}
} }
if (height == 0) if (height == 0)
@@ -226,6 +252,23 @@ namespace Barotrauma
} }
} }
public void SetBackgroundIcon(Sprite icon)
{
if (icon == null) { return; }
GUIImage newIcon = new GUIImage(new RectTransform(icon.size.ToPoint(), RectTransform), icon)
{
IgnoreLayoutGroups = true,
Color = Color.Transparent
};
if (newBackgroundIcon != null)
{
RemoveChild(newBackgroundIcon);
newBackgroundIcon = null;
}
newBackgroundIcon = newIcon;
}
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
if (type == Type.InGame) if (type == Type.InGame)
@@ -246,10 +289,19 @@ namespace Barotrauma
if (!closing) if (!closing)
{ {
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint(); Point step = Vector2.SmoothStep(initialPos, defaultPos, openState).ToPoint();
InnerFrame.RectTransform.AbsoluteOffset = step;
if (BackgroundIcon != null)
{
BackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (BackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - BackgroundIcon.Rect.Size.Y / 2);
if (!MathUtils.NearlyEqual(openState, 1.0f))
{
BackgroundIcon.Color = ToolBox.GradientLerp(openState, Color.Transparent, Color.White);
}
}
openState = Math.Min(openState + deltaTime * 2.0f, 1.0f); openState = Math.Min(openState + deltaTime * 2.0f, 1.0f);
if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn)) if (GUI.MouseOn != InnerFrame && !InnerFrame.IsParentOf(GUI.MouseOn) && AutoClose)
{ {
inGameCloseTimer += deltaTime; inGameCloseTimer += deltaTime;
} }
@@ -262,13 +314,55 @@ namespace Barotrauma
else else
{ {
openState += deltaTime * 2.0f; openState += deltaTime * 2.0f;
InnerFrame.RectTransform.AbsoluteOffset = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint(); Point step = Vector2.SmoothStep(defaultPos, endPos, openState - 1.0f).ToPoint();
InnerFrame.RectTransform.AbsoluteOffset = step;
if (BackgroundIcon != null)
{
BackgroundIcon.Color *= 0.9f;
}
if (openState >= 2.0f) if (openState >= 2.0f)
{ {
if (Parent != null) { Parent.RemoveChild(this); } if (Parent != null) { Parent.RemoveChild(this); }
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); } if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
} }
} }
if (newBackgroundIcon != null)
{
if (!iconSwitching)
{
if (BackgroundIcon != null)
{
BackgroundIcon.Color *= 0.9f;
if (BackgroundIcon.Color.A == 0)
{
BackgroundIcon = null;
iconSwitching = true;
RemoveChild(BackgroundIcon);
}
}
else
{
iconSwitching = true;
}
iconState = 0;
}
else
{
newBackgroundIcon.SetAsFirstChild();
newBackgroundIcon.RectTransform.AbsoluteOffset = new Point(InnerFrame.Rect.Location.X - (int) (newBackgroundIcon.Rect.Size.X / 1.25f), (int)defaultPos.Y - newBackgroundIcon.Rect.Size.Y / 2);
newBackgroundIcon.Color = ToolBox.GradientLerp(iconState, Color.Transparent, Color.White);
if (newBackgroundIcon.Color.A == 255)
{
BackgroundIcon = newBackgroundIcon;
BackgroundIcon.SetAsFirstChild();
newBackgroundIcon = null;
iconSwitching = false;
}
iconState = Math.Min(iconState + deltaTime * 2.0f, 1.0f);
}
}
} }
} }
@@ -284,6 +378,8 @@ namespace Barotrauma
if (Parent != null) { Parent.RemoveChild(this); } if (Parent != null) { Parent.RemoveChild(this); }
if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); } if (MessageBoxes.Contains(this)) { MessageBoxes.Remove(this); }
} }
Closed = true;
} }
public bool Close(GUIButton button, object obj) public bool Close(GUIButton button, object obj)
@@ -179,7 +179,7 @@ namespace Barotrauma
private float pressedDelay = 0.5f; private float pressedDelay = 0.5f;
private bool IsPressedTimerRunning { get { return pressedTimer > 0; } } private bool IsPressedTimerRunning { get { return pressedTimer > 0; } }
public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null) : base(style, rectT) public GUINumberInput(RectTransform rectT, NumberType inputType, string style = "", Alignment textAlignment = Alignment.Center, float? relativeButtonAreaWidth = null, bool hidePlusMinusButtons = false) : base(style, rectT)
{ {
LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true }; LayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, rectT), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
@@ -235,7 +235,7 @@ namespace Barotrauma
return true; return true;
}; };
if (inputType != NumberType.Int) if (inputType != NumberType.Int || hidePlusMinusButtons)
{ {
HidePlusMinusButtons(); HidePlusMinusButtons();
} }
@@ -121,7 +121,7 @@ namespace Barotrauma
(int)(Rect.Width - style.Padding.X + style.Padding.Z), (int)(Rect.Width - style.Padding.X + style.Padding.Z),
(int)(Rect.Height - style.Padding.Y + style.Padding.W)); (int)(Rect.Height - style.Padding.Y + style.Padding.W));
frame.Visible = showFrame; frame.Visible = showFrame;
slider.Visible = true; slider.Visible = BarSize > 0.0f;
if (showFrame) if (showFrame)
{ {
@@ -37,6 +37,8 @@ namespace Barotrauma
public UISprite UIGlow { get; private set; } public UISprite UIGlow { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
public UISprite ButtonPulse { get; private set; }
public SpriteSheet FocusIndicator { get; private set; } public SpriteSheet FocusIndicator { get; private set; }
/// <summary> /// <summary>
@@ -206,6 +208,9 @@ namespace Barotrauma
case "uiglowcircular": case "uiglowcircular":
UIGlowCircular = new UISprite(subElement); UIGlowCircular = new UISprite(subElement);
break; break;
case "endroundbuttonpulse":
ButtonPulse = new UISprite(subElement);
break;
case "focusindicator": case "focusindicator":
FocusIndicator = new SpriteSheet(subElement); FocusIndicator = new SpriteSheet(subElement);
break; break;
@@ -255,7 +260,8 @@ namespace Barotrauma
DebugConsole.NewMessage("Global font not defined in the current UI style file. The global font is used to render western symbols when using Chinese/Japanese/Korean localization. Using default font instead...", Color.Orange); DebugConsole.NewMessage("Global font not defined in the current UI style file. The global font is used to render western symbols when using Chinese/Japanese/Korean localization. Using default font instead...", Color.Orange);
} }
GameMain.Instance.OnResolutionChanged += () => { RescaleElements(); }; // TODO: Needs to unregister if we ever remove GUIStyles.
GameMain.Instance.ResolutionChanged += RescaleElements;
} }
/// <summary> /// <summary>
@@ -272,6 +272,8 @@ namespace Barotrauma
} }
public List<ClickableArea> ClickableAreas { get; private set; } = new List<ClickableArea>(); public List<ClickableArea> ClickableAreas { get; private set; } = new List<ClickableArea>();
public bool Shadow { get; set; }
/// <summary> /// <summary>
/// This is the new constructor. /// This is the new constructor.
/// If the rectT height is set 0, the height is calculated from the text. /// If the rectT height is set 0, the height is calculated from the text.
@@ -320,10 +322,10 @@ namespace Barotrauma
hasColorHighlight = richTextData != null; hasColorHighlight = richTextData != null;
} }
public void CalculateHeightFromText(int padding = 0) public void CalculateHeightFromText(int padding = 0, bool removeExtraSpacing = false)
{ {
if (wrappedText == null) { return; } if (wrappedText == null) { return; }
RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText).Y + padding)); RectTransform.Resize(new Point(RectTransform.Rect.Width, (int)Font.MeasureString(wrappedText, removeExtraSpacing).Y + padding));
} }
public override void ApplyStyle(GUIComponentStyle componentStyle) public override void ApplyStyle(GUIComponentStyle componentStyle)
@@ -443,8 +445,8 @@ namespace Barotrauma
protected override void SetAlpha(float a) protected override void SetAlpha(float a)
{ {
base.SetAlpha(a); // base.SetAlpha(a);
textColor = new Color(textColor.R, textColor.G, textColor.B, a); textColor = new Color(TextColor.R / 255.0f, TextColor.G / 255.0f, TextColor.B / 255.0f, a);
} }
/// <summary> /// <summary>
@@ -626,12 +628,17 @@ namespace Barotrauma
if (!hasColorHighlight) if (!hasColorHighlight)
{ {
Font.DrawString(spriteBatch, string textToShow = Censor ? censoredText : (Wrap ? wrappedText : text);
Censor ? censoredText : (Wrap ? wrappedText : text), Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
pos,
currentTextColor * (currentTextColor.A / 255.0f), if (Shadow)
0.0f, origin, TextScale, {
SpriteEffects.None, textDepth); Vector2 shadowOffset = new Vector2(GUI.IntScale(2));
Font.DrawString(spriteBatch, textToShow, pos + shadowOffset, Color.Black, 0.0f, origin, TextScale, SpriteEffects.None, textDepth);
}
Font.DrawString(spriteBatch, textToShow, pos, colorToShow, 0.0f, origin, TextScale, SpriteEffects.None, textDepth);
} }
else else
{ {
@@ -134,6 +134,10 @@ namespace Barotrauma
{ {
textBlock.OverflowClip = value != null; textBlock.OverflowClip = value != null;
maxTextLength = value; maxTextLength = value;
if (Text.Length > MaxTextLength)
{
SetText(textBlock.Text.Substring(0, (int)maxTextLength));
}
} }
} }
@@ -360,6 +364,7 @@ namespace Barotrauma
} }
else else
{ {
CaretIndex = Math.Min(CaretIndex, textDrawn.Length);
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text; textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex)); Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex));
caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin; caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin;
@@ -75,6 +75,11 @@ namespace Barotrauma
get; private set; get; private set;
} }
public static Rectangle VotingArea
{
get; private set;
}
public static int Padding public static int Padding
{ {
get; private set; get; private set;
@@ -84,7 +89,7 @@ namespace Barotrauma
{ {
if (GameMain.Instance != null) if (GameMain.Instance != null)
{ {
GameMain.Instance.OnResolutionChanged += CreateAreas; GameMain.Instance.ResolutionChanged += CreateAreas;
GameMain.Config.OnHUDScaleChanged += CreateAreas; GameMain.Config.OnHUDScaleChanged += CreateAreas;
CreateAreas(); CreateAreas();
CharacterInfo.Init(); CharacterInfo.Init();
@@ -144,6 +149,13 @@ namespace Barotrauma
int healthWindowY = GameMain.GraphicsHeight / 2 - healthWindowHeight / 2; int healthWindowY = GameMain.GraphicsHeight / 2 - healthWindowHeight / 2;
HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight); HealthWindowAreaLeft = new Rectangle(healthWindowX, healthWindowY, healthWindowWidth, healthWindowHeight);
int votingAreaWidth = (int)(400 * GUI.Scale);
int votingAreaX = GameMain.GraphicsWidth - Padding - votingAreaWidth;
int votingAreaY = Padding + ButtonAreaTop.Height;
// Height is based on text content
VotingArea = new Rectangle(votingAreaX, votingAreaY, votingAreaWidth, 0);
} }
public static void Draw(SpriteBatch spriteBatch) public static void Draw(SpriteBatch spriteBatch)
@@ -165,7 +165,7 @@ namespace Barotrauma
float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0); float noiseStrength = (float)PerlinNoise.CalculatePerlin(noiseT, noiseT, 0);
float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f; float noiseScale = (float)PerlinNoise.CalculatePerlin(noiseT * 5.0f, noiseT * 2.0f, 0) * 4.0f;
noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight), noiseSprite.DrawTiled(spriteBatch, Vector2.Zero, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight),
startOffset: new Point(Rand.Range(0, noiseSprite.SourceRect.Width), Rand.Range(0, noiseSprite.SourceRect.Height)), startOffset: new Vector2(Rand.Range(0.0f, noiseSprite.SourceRect.Width), Rand.Range(0.0f, noiseSprite.SourceRect.Height)),
color: Color.White * noiseStrength * 0.1f, color: Color.White * noiseStrength * 0.1f,
textureScale: Vector2.One * noiseScale); textureScale: Vector2.One * noiseScale);
@@ -185,7 +185,7 @@ namespace Barotrauma
if (LoadState == 100.0f) if (LoadState == 100.0f)
{ {
#if DEBUG #if DEBUG
if (GameMain.Config.AutomaticQuickStartEnabled && GameMain.FirstLoad) if (GameMain.Config.AutomaticQuickStartEnabled || GameMain.Config.AutomaticCampaignLoadEnabled && GameMain.FirstLoad)
{ {
loadText = "QUICKSTARTING ..."; loadText = "QUICKSTARTING ...";
} }
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,683 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using System.Linq;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma
{
class SubmarineSelection
{
private const int submarinesPerPage = 4;
private int currentPage = 1;
private int pageCount;
private bool transferService, purchaseService, initialized;
private int deliveryFee;
private string deliveryLocationName;
public GUIFrame GuiFrame;
private GUIFrame pageIndicatorHolder;
private GUICustomComponent selectedSubmarineIndicator;
private GUILayoutGroup submarineHorizontalGroup, submarineControlsGroup;
private GUIButton browseLeftButton, browseRightButton, confirmButton, confirmButtonAlt;
private GUIListBox specsFrame;
private GUIImage[] pageIndicators;
private GUITextBlock descriptionTextBlock;
private int selectionIndicatorThickness;
private GUIImage listBackground;
private List<SubmarineInfo> subsToShow;
private SubmarineDisplayContent[] submarineDisplays = new SubmarineDisplayContent[submarinesPerPage];
private SubmarineInfo selectedSubmarine = null;
private string purchaseAndSwitchText, purchaseOnlyText, deliveryText, currentSubText, deliveryFeeText, priceText, switchText, missingPreviewText, currencyShorthandText, currencyLongText;
private RectTransform parent;
private Action closeAction;
private Sprite pageIndicator;
public static readonly string[] DeliveryTextVariables = new string[] { "[submarinename1]", "[location1]", "[location2]", "[submarinename2]", "[amount]", "[currencyname]" };
public static readonly string[] SwitchTextVariables = new string[] { "[submarinename1]", "[submarinename2]" };
public static readonly string[] PurchaseAndSwitchTextVariables = new string[] { "[submarinename1]", "[amount]", "[currencyname]", "[submarinename2]" };
public static readonly string[] PurchaseTextVariables = new string[] { "[submarinename]", "[amount]", "[currencyname]" };
private static readonly string[] notEnoughCreditsDeliveryTextVariables = new string[] { "[currencyname]", "[submarinename]", "[location1]", "[location2]" };
private static readonly string[] notEnoughCreditsPurchaseTextVariables = new string[] { "[currencyname]", "[submarinename]" };
private string[] messageBoxOptions;
public const int DeliveryFeePerDistanceTravelled = 1000;
public static bool ContentRefreshRequired = false;
private static readonly Color indicatorColor = new Color(112, 149, 129);
private Point createdForResolution;
private struct SubmarineDisplayContent
{
public GUIFrame background;
public GUIImage submarineImage;
public SubmarineInfo displayedSubmarine;
public GUITextBlock submarineName;
public GUITextBlock submarineClass;
public GUITextBlock submarineFee;
public GUIButton selectSubmarineButton;
public GUITextBlock middleTextBlock;
}
public SubmarineSelection(bool transfer, Action closeAction, RectTransform parent)
{
if (GameMain.GameSession.Campaign == null) return;
transferService = transfer;
purchaseService = !transfer;
this.parent = parent;
this.closeAction = closeAction;
subsToShow = new List<SubmarineInfo>();
if (GameMain.Client == null)
{
messageBoxOptions = new string[2] { TextManager.Get("Yes"), TextManager.Get("Cancel") };
}
else
{
messageBoxOptions = new string[2] { TextManager.Get("Yes") + " " + TextManager.Get("initiatevoting"), TextManager.Get("Cancel") };
}
if (Submarine.MainSub?.Info == null) return;
Initialize();
}
private void Initialize()
{
initialized = true;
if (transferService)
{
deliveryFee = CalculateDeliveryFee();
currentSubText = TextManager.Get("currentsub");
deliveryFeeText = TextManager.Get("deliveryfee");
deliveryText = TextManager.Get("requestdeliverybutton");
switchText = TextManager.Get("switchtosubmarinebutton");
}
else
{
purchaseAndSwitchText = TextManager.Get("purchaseandswitch");
purchaseOnlyText = TextManager.Get("purchase");
priceText = TextManager.Get("price");
}
currencyShorthandText = TextManager.Get("currencyformat");
currencyLongText = TextManager.Get("credit").ToLower();
UpdateSubmarines();
missingPreviewText = TextManager.Get("SubPreviewImageNotFound");
CreateGUI();
}
private int CalculateDeliveryFee()
{
int distanceToOutpost = GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
deliveryLocationName = endLocation.Name;
return DeliveryFeePerDistanceTravelled * distanceToOutpost;
}
private void CreateGUI()
{
createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
GUILayoutGroup content;
GuiFrame = new GUIFrame(new RectTransform(new Vector2(0.75f, 0.7f), parent, Anchor.TopCenter, Pivot.TopCenter) { RelativeOffset = new Vector2(0.0f, 0.02f) });
selectionIndicatorThickness = HUDLayoutSettings.Padding / 2;
GUIFrame background = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center), color: Color.Black * 0.9f)
{
CanBeFocused = false
};
content = new GUILayoutGroup(new RectTransform(new Point(background.Rect.Width - HUDLayoutSettings.Padding * 4, background.Rect.Height - HUDLayoutSettings.Padding * 4), background.RectTransform, Anchor.Center)) { AbsoluteSpacing = (int)(HUDLayoutSettings.Padding * 1.5f) };
GUITextBlock header = new GUITextBlock(new RectTransform(new Vector2(1f, 0.0f), content.RectTransform), transferService ? TextManager.Get("switchsubmarineheader") : TextManager.GetWithVariable("outpostshipyard", "[location]", GameMain.GameSession.Map.CurrentLocation.Name), font: GUI.LargeFont);
header.CalculateHeightFromText(0, true);
GUITextBlock credits = new GUITextBlock(new RectTransform(Vector2.One, header.RectTransform), "", font: GUI.SubHeadingFont, textAlignment: Alignment.CenterRight)
{
TextGetter = CampaignUI.GetMoney
};
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), content.RectTransform), style: "HorizontalLine");
GUILayoutGroup submarineContentGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.4f), content.RectTransform)) { AbsoluteSpacing = HUDLayoutSettings.Padding, Stretch = true };
submarineHorizontalGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.9f), submarineContentGroup.RectTransform)) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding, Stretch = true };
submarineControlsGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), submarineContentGroup.RectTransform), true, Anchor.TopCenter);
GUILayoutGroup infoFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.4f), content.RectTransform)) { IsHorizontal = true, Stretch = true, AbsoluteSpacing = HUDLayoutSettings.Padding };
new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform), style: null, new Color(8, 13, 19)) { IgnoreLayoutGroups = true };
listBackground = new GUIImage(new RectTransform(new Vector2(0.59f, 1f), infoFrame.RectTransform, Anchor.CenterRight), style: null, true)
{
IgnoreLayoutGroups = true
};
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) };
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) };
descriptionTextBlock = new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionFrame.Content.RectTransform), string.Empty, font: GUI.Font, wrap: true) { CanBeFocused = false };
GUILayoutGroup buttonFrame = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.075f), content.RectTransform), childAnchor: Anchor.CenterRight) { IsHorizontal = true, AbsoluteSpacing = HUDLayoutSettings.Padding };
if (closeAction != null)
{
GUIButton closeButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), TextManager.Get("Close"), style: "GUIButtonFreeScale")
{
OnClicked = (button, userData) =>
{
closeAction();
return true;
}
};
}
if (purchaseService) confirmButtonAlt = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), purchaseOnlyText, style: "GUIButtonFreeScale");
confirmButton = new GUIButton(new RectTransform(new Vector2(0.2f, 1f), buttonFrame.RectTransform), purchaseService ? purchaseAndSwitchText : deliveryFee > 0 ? deliveryText : switchText, style: "GUIButtonFreeScale");
SetConfirmButtonState(false);
pageIndicatorHolder = new GUIFrame(new RectTransform(new Vector2(1f, 1.5f), submarineControlsGroup.RectTransform), style: null);
pageIndicator = GUI.Style.GetComponentStyle("GUIPageIndicator").GetDefaultSprite();
UpdatePaging();
for (int i = 0; i < submarineDisplays.Length; i++)
{
SubmarineDisplayContent submarineDisplayElement = new SubmarineDisplayContent();
submarineDisplayElement.background = new GUIFrame(new RectTransform(new Vector2(1f / submarinesPerPage, 1f), submarineHorizontalGroup.RectTransform), style: null, new Color(8, 13, 19));
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.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: GUI.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)GUI.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, Pivot.BottomCenter) { AbsoluteOffset = new Point(0, HUDLayoutSettings.Padding) }, string.Empty, textAlignment: Alignment.Center, font: GUI.SubHeadingFont);
submarineDisplayElement.selectSubmarineButton = new GUIButton(new RectTransform(Vector2.One, submarineDisplayElement.background.RectTransform), style: null);
submarineDisplays[i] = submarineDisplayElement;
}
selectedSubmarineIndicator = new GUICustomComponent(new RectTransform(Point.Zero, submarineHorizontalGroup.RectTransform), onDraw: (sb, component) => DrawSubmarineIndicator(sb, component.Rect)) { IgnoreLayoutGroups = true, CanBeFocused = false };
}
private void UpdatePaging()
{
if (pageIndicatorHolder == null) return;
pageIndicatorHolder.ClearChildren();
if (currentPage > pageCount) currentPage = pageCount;
if (pageCount < 2) return;
browseLeftButton = new GUIButton(new RectTransform(new Vector2(1.15f, 1.15f), pageIndicatorHolder.RectTransform, Anchor.CenterLeft, Pivot.CenterRight) { AbsoluteOffset = new Point(-HUDLayoutSettings.Padding * 3, 0) }, string.Empty, style: "GUIButtonToggleLeft")
{
IgnoreLayoutGroups = true,
OnClicked = (button, userData) =>
{
ChangePage(-1);
return true;
}
};
Point indicatorSize = new Point(GUI.IntScale(pageIndicator.SourceRect.Width * 1.5f), GUI.IntScale(pageIndicator.SourceRect.Height * 1.5f));
pageIndicatorHolder.RectTransform.NonScaledSize = new Point(pageCount * indicatorSize.X + HUDLayoutSettings.Padding * (pageCount - 1), pageIndicatorHolder.RectTransform.NonScaledSize.Y);
int xPos = 0;
int yPos = pageIndicatorHolder.Rect.Height / 2 - indicatorSize.Y / 2;
pageIndicators = new GUIImage[pageCount];
for (int i = 0; i < pageCount; i++)
{
pageIndicators[i] = new GUIImage(new RectTransform(indicatorSize, pageIndicatorHolder.RectTransform) { AbsoluteOffset = new Point(xPos, yPos) }, pageIndicator, null, true);
xPos += indicatorSize.X + HUDLayoutSettings.Padding;
}
for (int i = 0; i < pageIndicators.Length; i++)
{
pageIndicators[i].Color = i == currentPage - 1 ? Color.White : Color.Gray;
}
browseRightButton = new GUIButton(new RectTransform(new Vector2(1.15f, 1.15f), pageIndicatorHolder.RectTransform, Anchor.CenterRight, Pivot.CenterLeft) { AbsoluteOffset = new Point(-HUDLayoutSettings.Padding * 3, 0) }, string.Empty, style: "GUIButtonToggleRight")
{
IgnoreLayoutGroups = true,
OnClicked = (button, userData) =>
{
ChangePage(1);
return true;
}
};
browseLeftButton.Enabled = currentPage > 1;
browseRightButton.Enabled = currentPage < pageCount;
}
private void DrawSubmarineIndicator(SpriteBatch spriteBatch, Rectangle area)
{
if (area == Rectangle.Empty) return;
GUI.DrawRectangle(spriteBatch, area, indicatorColor, thickness: selectionIndicatorThickness);
}
public void Update()
{
if (ContentRefreshRequired)
{
RefreshSubmarineDisplay(true);
}
// Input
if (PlayerInput.KeyHit(Keys.Left))
{
SelectSubmarine(subsToShow.IndexOf(selectedSubmarine), -1);
}
else if (PlayerInput.KeyHit(Keys.Right))
{
SelectSubmarine(subsToShow.IndexOf(selectedSubmarine), 1);
}
}
public void RefreshSubmarineDisplay(bool updateSubs)
{
if (!initialized) Initialize();
if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateGUI();
if (updateSubs) UpdateSubmarines();
if (pageIndicators != null)
{
for (int i = 0; i < pageIndicators.Length; i++)
{
pageIndicators[i].Color = i == currentPage - 1 ? Color.White : Color.Gray;
}
}
int submarineIndex = (currentPage - 1) * submarinesPerPage;
for (int i = 0; i < submarineDisplays.Length; i++)
{
SubmarineInfo subToDisplay = GetSubToDisplay(submarineIndex);
if (subToDisplay == null)
{
submarineDisplays[i].submarineImage.Sprite = null;
submarineDisplays[i].submarineName.Text = string.Empty;
submarineDisplays[i].submarineFee.Text = string.Empty;
submarineDisplays[i].submarineClass.Text = string.Empty;
submarineDisplays[i].selectSubmarineButton.Enabled = false;
submarineDisplays[i].selectSubmarineButton.OnClicked = null;
submarineDisplays[i].displayedSubmarine = null;
submarineDisplays[i].middleTextBlock.AutoDraw = false;
}
else
{
submarineDisplays[i].displayedSubmarine = subToDisplay;
Sprite previewImage = GetPreviewImage(subToDisplay);
if (previewImage != null)
{
submarineDisplays[i].submarineImage.Sprite = previewImage;
submarineDisplays[i].middleTextBlock.AutoDraw = false;
}
else
{
submarineDisplays[i].submarineImage.Sprite = null;
submarineDisplays[i].middleTextBlock.Text = missingPreviewText;
submarineDisplays[i].middleTextBlock.AutoDraw = true;
}
submarineDisplays[i].selectSubmarineButton.Enabled = true;
int index = i;
submarineDisplays[i].selectSubmarineButton.OnClicked = (button, userData) =>
{
SelectSubmarine(subToDisplay, submarineDisplays[index].background.Rect);
return true;
};
submarineDisplays[i].submarineName.Text = subToDisplay.DisplayName;
submarineDisplays[i].submarineClass.Text = $"{TextManager.GetWithVariable("submarineclass.classsuffixformat", "[type]", TextManager.Get($"submarineclass.{subToDisplay.SubmarineClass}"))}";
if (!GameMain.GameSession.IsSubmarineOwned(subToDisplay))
{
string amountString = currencyShorthandText.Replace("[credits]", subToDisplay.Price.ToString());
submarineDisplays[i].submarineFee.Text = priceText.Replace("[amount]", amountString).Replace("[currencyname]", string.Empty).TrimEnd();
}
else
{
if (subToDisplay.Name != CurrentOrPendingSubmarine().Name)
{
if (deliveryFee > 0)
{
string amountString = currencyShorthandText.Replace("[credits]", deliveryFee.ToString());
submarineDisplays[i].submarineFee.Text = deliveryFeeText.Replace("[amount]", amountString).Replace("[currencyname]", string.Empty).TrimEnd();
}
else
{
submarineDisplays[i].submarineFee.Text = string.Empty;
}
}
else
{
submarineDisplays[i].submarineFee.Text = currentSubText;
}
}
if (transferService && subToDisplay.Name == CurrentOrPendingSubmarine().Name && updateSubs)
{
if (selectedSubmarine == null)
{
CoroutineManager.StartCoroutine(SelectOwnSubmarineWithDelay(subToDisplay, submarineDisplays[i]));
}
else
{
SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect);
}
}
else if (!transferService && selectedSubmarine == null || !transferService && GameMain.GameSession.IsSubmarineOwned(selectedSubmarine) || subToDisplay == selectedSubmarine)
{
SelectSubmarine(subToDisplay, submarineDisplays[i].background.Rect);
}
}
submarineIndex++;
}
if (subsToShow.Count == 0)
{
SelectSubmarine(null, Rectangle.Empty);
}
}
private void UpdateSubmarines()
{
subsToShow.Clear();
if (transferService)
{
subsToShow.AddRange(GameMain.GameSession.OwnedSubmarines);
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
string currentSubName = CurrentOrPendingSubmarine().Name;
int currentIndex = subsToShow.FindIndex(s => s.Name == currentSubName);
if (currentIndex != -1)
{
currentPage = (int)Math.Ceiling((currentIndex + 1) / (float)submarinesPerPage);
}
}
else
{
if (GameMain.Client == null)
{
subsToShow.AddRange(SubmarineInfo.SavedSubmarines.Where(s => s.IsCampaignCompatible && !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
}
else
{
subsToShow.AddRange(GameMain.NetLobbyScreen.CampaignSubmarines.Where(s => !GameMain.GameSession.OwnedSubmarines.Any(os => os.Name == s.Name)));
}
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
}
if (transferService) SetConfirmButtonState(selectedSubmarine != null && selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
subsToShow.Sort((x, y) => x.SubmarineClass.CompareTo(y.SubmarineClass));
pageCount = Math.Max(1, (int)Math.Ceiling(subsToShow.Count / (float)submarinesPerPage));
UpdatePaging();
ContentRefreshRequired = false;
}
private SubmarineInfo GetSubToDisplay(int index)
{
if (subsToShow.Count <= index || index < 0) return null;
return subsToShow[index];
}
private Sprite GetPreviewImage(SubmarineInfo info)
{
Sprite preview = info.PreviewImage;
if (preview == null)
{
SubmarineInfo potentialMatch;
if (GameMain.Client == null)
{
potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal);
}
else
{
potentialMatch = GameMain.NetLobbyScreen.CampaignSubmarines.FirstOrDefault(s => s.EqualityCheckVal == info.EqualityCheckVal);
}
preview = potentialMatch?.PreviewImage;
// Try from savedsubmarines with name comparison as a backup
if (preview == null)
{
potentialMatch = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == info.Name);
preview = potentialMatch?.PreviewImage;
}
}
return preview;
}
// Initial submarine selection needs a slight wait to allow the layoutgroups to place content properly
private IEnumerable<object> SelectOwnSubmarineWithDelay(SubmarineInfo info, SubmarineDisplayContent display)
{
yield return new WaitForSeconds(0.05f);
SelectSubmarine(info, display.background.Rect);
}
// Selection based on key input
private void SelectSubmarine(int index, int direction)
{
SubmarineInfo nextSub = GetSubToDisplay(index + direction);
if (nextSub == null) return;
for (int i = 0; i < submarineDisplays.Length; i++)
{
if (submarineDisplays[i].displayedSubmarine == nextSub)
{
SelectSubmarine(nextSub, submarineDisplays[i].background.Rect);
return;
}
}
ChangePage(direction);
for (int i = 0; i < submarineDisplays.Length; i++)
{
if (submarineDisplays[i].displayedSubmarine == nextSub)
{
SelectSubmarine(nextSub, submarineDisplays[i].background.Rect);
return;
}
}
}
private void SelectSubmarine(SubmarineInfo info, Rectangle backgroundRect)
{
#if !DEBUG
if (selectedSubmarine == info) return;
#endif
specsFrame.Content.ClearChildren();
selectedSubmarine = info;
if (info != null)
{
bool owned = GameMain.GameSession.IsSubmarineOwned(info);
if (owned)
{
confirmButton.Text = deliveryFee > 0 ? deliveryText : switchText;
confirmButton.OnClicked = (button, userData) =>
{
ShowTransferPrompt();
return true;
};
}
else
{
confirmButton.Text = purchaseAndSwitchText;
confirmButton.OnClicked = (button, userData) =>
{
ShowBuyPrompt(false);
return true;
};
confirmButtonAlt.Text = purchaseOnlyText;
confirmButtonAlt.OnClicked = (button, userData) =>
{
ShowBuyPrompt(true);
return true;
};
}
SetConfirmButtonState(selectedSubmarine.Name != CurrentOrPendingSubmarine().Name);
selectedSubmarineIndicator.RectTransform.NonScaledSize = backgroundRect.Size;
selectedSubmarineIndicator.RectTransform.AbsoluteOffset = new Point(backgroundRect.Left - submarineHorizontalGroup.Rect.Left, 0);
Sprite previewImage = GetPreviewImage(info);
listBackground.Sprite = previewImage;
listBackground.SetCrop(true);
ScalableFont font = GUI.Font;
info.CreateSpecsWindow(specsFrame, font);
descriptionTextBlock.Text = info.Description;
descriptionTextBlock.CalculateHeightFromText();
}
else
{
listBackground.Sprite = null;
listBackground.SetCrop(false);
descriptionTextBlock.Text = string.Empty;
selectedSubmarineIndicator.RectTransform.NonScaledSize = Point.Zero;
SetConfirmButtonState(false);
}
}
private void SetConfirmButtonState(bool state)
{
if (confirmButtonAlt != null)
{
confirmButtonAlt.Enabled = state;
}
if (confirmButton != null)
{
confirmButton.Enabled = state;
}
}
public static SubmarineInfo CurrentOrPendingSubmarine()
{
if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
{
return Submarine.MainSub.Info;
}
else
{
return GameMain.GameSession.Campaign.PendingSubmarineSwitch;
}
}
private void ChangePage(int pageChangeDirection)
{
SelectSubmarine(null, Rectangle.Empty);
if (pageChangeDirection < 0 && currentPage > 1) currentPage--;
if (pageChangeDirection > 0 && currentPage < pageCount) currentPage++;
browseLeftButton.Enabled = currentPage > 1;
browseRightButton.Enabled = currentPage < pageCount;
RefreshSubmarineDisplay(false);
}
private void ShowTransferPrompt()
{
if (GameMain.GameSession.Campaign.Money < deliveryFee && deliveryFee > 0)
{
new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("notenoughmoneyfordeliverytext", notEnoughCreditsDeliveryTextVariables,
new string[] { currencyLongText, selectedSubmarine.DisplayName, deliveryLocationName, GameMain.GameSession.Map.CurrentLocation.Name }));
return;
}
GUIMessageBox msgBox;
if (deliveryFee > 0)
{
msgBox = new GUIMessageBox(TextManager.Get("deliveryrequestheader"), TextManager.GetWithVariables("deliveryrequesttext", DeliveryTextVariables,
new string[6] { selectedSubmarine.DisplayName, deliveryLocationName, GameMain.GameSession.Map.CurrentLocation.Name, CurrentOrPendingSubmarine().DisplayName, deliveryFee.ToString(), currencyLongText }), messageBoxOptions);
}
else
{
msgBox = new GUIMessageBox(TextManager.Get("switchsubmarineheader"), TextManager.GetWithVariables("switchsubmarinetext", SwitchTextVariables,
new string[2] { CurrentOrPendingSubmarine().DisplayName, selectedSubmarine.DisplayName }), messageBoxOptions);
}
msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{
if (GameMain.Client == null)
{
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, deliveryFee);
GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(selectedSubmarine);
RefreshSubmarineDisplay(true);
}
else
{
GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.SwitchSub);
}
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
private void ShowBuyPrompt(bool purchaseOnly)
{
if (GameMain.GameSession.Campaign.Money < selectedSubmarine.Price)
{
new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("notenoughmoneyforpurchasetext", notEnoughCreditsPurchaseTextVariables,
new string[2] { currencyLongText, selectedSubmarine.DisplayName }));
return;
}
GUIMessageBox msgBox;
if (!purchaseOnly)
{
msgBox = new GUIMessageBox(TextManager.Get("purchaseandswitchsubmarineheader"), TextManager.GetWithVariables("purchaseandswitchsubmarinetext", PurchaseAndSwitchTextVariables,
new string[4] { selectedSubmarine.DisplayName, selectedSubmarine.Price.ToString(), currencyLongText, CurrentOrPendingSubmarine().DisplayName }), messageBoxOptions);
msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{
if (GameMain.Client == null)
{
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
GameMain.GameSession.SwitchSubmarine(selectedSubmarine, 0);
GameMain.GameSession.Campaign.UpgradeManager.RefundResetAndReload(selectedSubmarine);
RefreshSubmarineDisplay(true);
}
else
{
GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.PurchaseAndSwitchSub);
}
return true;
};
}
else
{
msgBox = new GUIMessageBox(TextManager.Get("purchasesubmarineheader"), TextManager.GetWithVariables("purchasesubmarinetext", PurchaseTextVariables,
new string[3] { selectedSubmarine.DisplayName, selectedSubmarine.Price.ToString(), currencyLongText }), messageBoxOptions);
msgBox.Buttons[0].OnClicked = (applyButton, obj) =>
{
if (GameMain.Client == null)
{
GameMain.GameSession.PurchaseSubmarine(selectedSubmarine);
RefreshSubmarineDisplay(true);
}
else
{
GameMain.Client.InitiateSubmarineChange(selectedSubmarine, Networking.VoteType.PurchaseSub);
}
return true;
};
}
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = msgBox.Close;
}
}
}
@@ -4,6 +4,7 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Linq; using System.Linq;
using Barotrauma.Networking; using Barotrauma.Networking;
using System.Globalization;
namespace Barotrauma namespace Barotrauma
{ {
@@ -13,7 +14,7 @@ namespace Barotrauma
private static bool initialized = false; private static bool initialized = false;
private static UISprite spectateIcon, deadIcon, disconnectedIcon; private static UISprite spectateIcon, disconnectedIcon;
private static Sprite ownerIcon, moderatorIcon; private static Sprite ownerIcon, moderatorIcon;
private enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor }; private enum InfoFrameTab { Crew, Mission, MyCharacter, Traitor };
@@ -31,7 +32,7 @@ namespace Barotrauma
private List<Character.TeamType> teamIDs; private List<Character.TeamType> teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022"; private const string inLobbyString = "\u2022 \u2022 \u2022";
private static Color ownCharacterBGColor = Color.Gold * 0.7f; public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private class LinkedGUI private class LinkedGUI
{ {
@@ -115,10 +116,9 @@ namespace Barotrauma
public void Initialize() public void Initialize()
{ {
spectateIcon = GUI.Style.GetComponentStyle("SpectateIcon").Sprites[GUIComponent.ComponentState.None][0]; spectateIcon = GUI.Style.GetComponentStyle("SpectateIcon").Sprites[GUIComponent.ComponentState.None][0];
deadIcon = GUI.Style.GetComponentStyle("DeadIcon").Sprites[GUIComponent.ComponentState.None][0];
disconnectedIcon = GUI.Style.GetComponentStyle("DisconnectedIcon").Sprites[GUIComponent.ComponentState.None][0]; disconnectedIcon = GUI.Style.GetComponentStyle("DisconnectedIcon").Sprites[GUIComponent.ComponentState.None][0];
ownerIcon = GUI.Style.GetComponentStyle("OwnerIcon").Sprites[GUIComponent.ComponentState.None][0].Sprite; ownerIcon = GUI.Style.GetComponentStyle("OwnerIcon").GetDefaultSprite();
moderatorIcon = GUI.Style.GetComponentStyle("ModeratorIcon").Sprites[GUIComponent.ComponentState.None][0].Sprite; moderatorIcon = GUI.Style.GetComponentStyle("ModeratorIcon").GetDefaultSprite();
initialized = true; initialized = true;
} }
@@ -279,7 +279,7 @@ namespace Barotrauma
teamIDs = crew.Select(c => c.TeamID).Distinct().ToList(); teamIDs = crew.Select(c => c.TeamID).Distinct().ToList();
// Show own team first when there's more than one team // Show own team first when there's more than one team
if (teamIDs.Count > 1 && GameMain.Client.Character != null) if (teamIDs.Count > 1 && GameMain.Client?.Character != null)
{ {
Character.TeamType ownTeam = GameMain.Client.Character.TeamID; Character.TeamType ownTeam = GameMain.Client.Character.TeamID;
teamIDs = teamIDs.OrderBy(i => i != ownTeam).ThenBy(i => i).ToList(); teamIDs = teamIDs.OrderBy(i => i != ownTeam).ThenBy(i => i).ToList();
@@ -408,7 +408,7 @@ namespace Barotrauma
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement") GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
{ {
UserData = character, UserData = character,
Color = (Character.Controlled == character) ? ownCharacterBGColor : Color.Transparent Color = (Character.Controlled == character) ? OwnCharacterBGColor : Color.Transparent
}; };
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true) var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
@@ -486,7 +486,7 @@ namespace Barotrauma
GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement") GUIFrame frame = new GUIFrame(new RectTransform(new Point(crewListArray[i].Content.Rect.Width, GUI.IntScale(33f)), crewListArray[i].Content.RectTransform), style: "ListBoxElement")
{ {
UserData = character, UserData = character,
Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? ownCharacterBGColor : Color.Transparent Color = (GameMain.NetworkMember != null && GameMain.Client.Character == character) ? OwnCharacterBGColor : Color.Transparent
}; };
frame.OnSecondaryClicked += (component, data) => frame.OnSecondaryClicked += (component, data) =>
@@ -631,7 +631,7 @@ namespace Barotrauma
{ {
if (GameMain.NetworkMember == null || client == null || !client.HasPermissions) return null; if (GameMain.NetworkMember == null || client == null || !client.HasPermissions) return null;
if (!client.AllowKicking) // Owner cannot be kicked if (client.IsOwner) // Owner cannot be kicked
{ {
return ownerIcon; return ownerIcon;
} }
@@ -648,9 +648,12 @@ namespace Barotrauma
spectateIcon.Draw(spriteBatch, area, Color.White); spectateIcon.Draw(spriteBatch, area, Color.White);
} }
else if (client.Character != null && client.Character.IsDead) else if (client.Character != null && client.Character.IsDead)
{
if (client.Character.Info != null)
{ {
client.Character.Info.DrawJobIcon(spriteBatch, area); client.Character.Info.DrawJobIcon(spriteBatch, area);
} }
}
else else
{ {
Vector2 stringOffset = GUI.GlobalFont.MeasureString(inLobbyString) / 2f; Vector2 stringOffset = GUI.GlobalFont.MeasureString(inLobbyString) / 2f;
@@ -780,7 +783,7 @@ namespace Barotrauma
public static void StorePlayerConnectionChangeMessage(ChatMessage message) public static void StorePlayerConnectionChangeMessage(ChatMessage message)
{ {
if (!GameMain.GameSession?.GameMode?.IsRunning ?? true) { return; } if (!GameMain.GameSession?.IsRunning ?? true) { return; }
string msg = ChatMessage.GetTimeStamp() + message.TextWithSender; string msg = ChatMessage.GetTimeStamp() + message.TextWithSender;
storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType)); storedMessages.Add(new Pair<string, PlayerConnectionChangeType>(msg, message.ChangeType));
@@ -847,15 +850,15 @@ namespace Barotrauma
infoFrame.ClearChildren(); infoFrame.ClearChildren();
GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox"); GUIFrame missionFrame = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
int padding = (int)(0.0245f * missionFrame.Rect.Height); int padding = (int)(0.0245f * missionFrame.Rect.Height);
Location endLocation = GameMain.GameSession.EndLocation; Location location = GameMain.GameSession.EndLocation != null ? GameMain.GameSession.EndLocation : GameMain.GameSession.StartLocation;
Sprite portrait = endLocation.Type.GetPortrait(endLocation.PortraitId); Sprite portrait = location.Type.GetPortrait(location.PortraitId);
bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0; bool hasPortrait = portrait != null && portrait.SourceRect.Width > 0 && portrait.SourceRect.Height > 0;
int contentWidth = hasPortrait ? (int)(missionFrame.Rect.Width * 0.951f) : missionFrame.Rect.Width - padding * 2; int contentWidth = hasPortrait ? (int)(missionFrame.Rect.Width * 0.951f) : missionFrame.Rect.Width - padding * 2;
Vector2 locationNameSize = GUI.LargeFont.MeasureString(endLocation.Name); Vector2 locationNameSize = GUI.LargeFont.MeasureString(location.Name);
Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(endLocation.Name); Vector2 locationTypeSize = GUI.SubHeadingFont.MeasureString(location.Name);
GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, endLocation.Name, font: GUI.LargeFont); GUITextBlock locationNameText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationNameSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, padding) }, location.Name, font: GUI.LargeFont);
GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, endLocation.Type.Name, font: GUI.SubHeadingFont); GUITextBlock locationTypeText = new GUITextBlock(new RectTransform(new Point(contentWidth, (int)locationTypeSize.Y), missionFrame.RectTransform, Anchor.TopCenter) { AbsoluteOffset = new Point(0, locationNameText.Rect.Height + padding) }, location.Type.Name, font: GUI.SubHeadingFont);
int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2; int locationInfoYOffset = locationNameText.Rect.Height + locationTypeText.Rect.Height + padding * 2;
@@ -881,7 +884,8 @@ namespace Barotrauma
string missionNameString = ToolBox.WrapText(mission.Name, missionTextGroup.Rect.Width, GUI.LargeFont); string missionNameString = ToolBox.WrapText(mission.Name, missionTextGroup.Rect.Width, GUI.LargeFont);
string missionDescriptionString = ToolBox.WrapText(mission.Description, missionTextGroup.Rect.Width, GUI.Font); string missionDescriptionString = ToolBox.WrapText(mission.Description, missionTextGroup.Rect.Width, GUI.Font);
string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", mission.Reward.ToString()), missionTextGroup.Rect.Width, GUI.Font); string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", mission.Reward));
string missionRewardString = ToolBox.WrapText(TextManager.GetWithVariable("MissionReward", "[reward]", rewardText), missionTextGroup.Rect.Width, GUI.Font);
Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString); Vector2 missionNameSize = GUI.LargeFont.MeasureString(missionNameString);
Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString); Vector2 missionDescriptionSize = GUI.Font.MeasureString(missionDescriptionString);
@@ -890,13 +894,15 @@ namespace Barotrauma
missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y)); missionDescriptionHolder.RectTransform.NonScaledSize = new Point(missionDescriptionHolder.RectTransform.NonScaledSize.X, (int)(missionNameSize.Y + missionDescriptionSize.Y + missionRewardSize.Y));
missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y); missionTextGroup.RectTransform.NonScaledSize = new Point(missionTextGroup.RectTransform.NonScaledSize.X, missionDescriptionHolder.RectTransform.NonScaledSize.Y);
if (mission.Prefab.Icon != null)
{
float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height; float iconAspectRatio = mission.Prefab.Icon.SourceRect.Width / mission.Prefab.Icon.SourceRect.Height;
int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X); int iconWidth = (int)(0.225f * missionDescriptionHolder.RectTransform.NonScaledSize.X);
int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio)); int iconHeight = Math.Max(missionTextGroup.RectTransform.NonScaledSize.Y, (int)(iconWidth * iconAspectRatio));
Point iconSize = new Point(iconWidth, iconHeight); Point iconSize = new Point(iconWidth, iconHeight);
new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor }; new GUIImage(new RectTransform(iconSize, missionDescriptionHolder.RectTransform), mission.Prefab.Icon, null, true) { Color = mission.Prefab.IconColor };
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionNameString, font: GUI.LargeFont);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionRewardString);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextGroup.RectTransform), missionDescriptionString);
@@ -37,6 +37,12 @@ namespace Barotrauma
private set; private set;
} }
public bool MaintainBorderAspectRatio
{
get;
private set;
}
/// <summary> /// <summary>
/// How much the borders of a sliced sprite are allowed to scale /// How much the borders of a sliced sprite are allowed to scale
/// You may for example want to prevent a 1-pixel border from scaling down (and disappearing) on small resolutions /// You may for example want to prevent a 1-pixel border from scaling down (and disappearing) on small resolutions
@@ -52,6 +58,7 @@ namespace Barotrauma
{ {
Sprite = new Sprite(element); Sprite = new Sprite(element);
MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false); MaintainAspectRatio = element.GetAttributeBool("maintainaspectratio", false);
MaintainBorderAspectRatio = element.GetAttributeBool("maintainborderaspectratio", false);
Tile = element.GetAttributeBool("tile", true); Tile = element.GetAttributeBool("tile", true);
CrossFadeIn = element.GetAttributeBool("crossfadein", CrossFadeIn); CrossFadeIn = element.GetAttributeBool("crossfadein", CrossFadeIn);
CrossFadeOut = element.GetAttributeBool("crossfadeout", CrossFadeOut); CrossFadeOut = element.GetAttributeBool("crossfadeout", CrossFadeOut);
@@ -120,13 +127,16 @@ namespace Barotrauma
{ {
Vector2 pos = new Vector2(rect.X, rect.Y); Vector2 pos = new Vector2(rect.X, rect.Y);
float scale = GetSliceBorderScale(rect.Size); float scale = MaintainBorderAspectRatio ? 1.0f : GetSliceBorderScale(rect.Size);
float aspectScale = MaintainBorderAspectRatio ? Math.Min((float)rect.Width / Sprite.SourceRect.Width, (float)rect.Height / Sprite.SourceRect.Height) : 1.0f;
int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale); int centerHeight = rect.Height - (int)((Slices[0].Height + Slices[6].Height) * scale);
int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale); int centerWidth = rect.Width - (int)((Slices[0].Width + Slices[2].Width) * scale * aspectScale);
for (int x = 0; x < 3; x++) for (int x = 0; x < 3; x++)
{ {
int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale);
int width = (int)(x == 1 ? centerWidth : Slices[x].Width * scale * aspectScale);
if (width <= 0) { continue; } if (width <= 0) { continue; }
for (int y = 0; y < 3; y++) for (int y = 0; y < 3; y++)
{ {
@@ -147,7 +157,7 @@ namespace Barotrauma
else if (Tile) else if (Tile)
{ {
Vector2 startPos = new Vector2(rect.X, rect.Y); Vector2 startPos = new Vector2(rect.X, rect.Y);
Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), null, color); Sprite.DrawTiled(spriteBatch, startPos, new Vector2(rect.Width, rect.Height), color);
} }
else else
{ {
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,235 @@
using System;
using System.Collections.Generic;
using Barotrauma.Networking;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
class VotingInterface
{
public bool VoteRunning = false;
private GUIFrame frame;
private GUITextBlock votingTextBlock, votedTextBlock, voteCounter;
private GUIProgressBar votingTimer;
private GUIButton yesVoteButton, noVoteButton;
private Action onVoteEnd;
private int yesVotes, noVotes, maxVotes;
private Func<int> getYesVotes, getNoVotes, getMaxVotes;
private bool votePassed;
private string votingOnText;
private List<RichTextData> votingOnTextData;
private float votingTime = 100f;
private float timer;
private VoteType currentVoteType;
private Color submarineColor => GUI.Style.Orange;
private Point createdForResolution;
public VotingInterface(Client starter, SubmarineInfo info, VoteType type, float votingTime)
{
if (starter == null || info == null) return;
SetSubmarineVotingText(starter, info, type);
this.votingTime = votingTime;
getYesVotes = SubmarineYesVotes;
getNoVotes = SubmarineNoVotes;
getMaxVotes = SubmarineMaxVotes;
onVoteEnd = () => SendSubmarineVoteEndMessage(info, type);
Initialize(starter, type);
}
private void Initialize(Client starter, VoteType type)
{
currentVoteType = type;
CreateVotingGUI();
if (starter.ID == GameMain.Client.ID) SetGUIToVotedState(2);
VoteRunning = true;
}
private void CreateVotingGUI()
{
createdForResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
if (frame != null) frame.Parent.RemoveChild(frame);
frame = new GUIFrame(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.VotingArea, GameMain.Client.InGameHUD.RectTransform), style: "");
int padding = HUDLayoutSettings.Padding * 2;
int spacing = HUDLayoutSettings.Padding;
int yOffset = padding;
int paddedWidth = frame.Rect.Width - padding * 2;
votingTextBlock = new GUITextBlock(new RectTransform(new Point(paddedWidth, 0), frame.RectTransform), votingOnTextData, votingOnText, wrap: true);
votingTextBlock.RectTransform.NonScaledSize = votingTextBlock.RectTransform.MinSize = votingTextBlock.RectTransform.MaxSize = new Point(votingTextBlock.Rect.Width, votingTextBlock.Rect.Height);
votingTextBlock.RectTransform.IsFixedSize = true;
votingTextBlock.RectTransform.AbsoluteOffset = new Point(padding, yOffset);
yOffset += votingTextBlock.Rect.Height + spacing;
voteCounter = new GUITextBlock(new RectTransform(new Point(paddedWidth, 0), frame.RectTransform), "(0/0)", GUI.Style.Green, textAlignment: Alignment.Center);
voteCounter.RectTransform.NonScaledSize = voteCounter.RectTransform.MinSize = voteCounter.RectTransform.MaxSize = new Point(voteCounter.Rect.Width, voteCounter.Rect.Height);
voteCounter.RectTransform.IsFixedSize = true;
voteCounter.RectTransform.AbsoluteOffset = new Point(padding, yOffset);
yOffset += voteCounter.Rect.Height + spacing;
votingTimer = new GUIProgressBar(new RectTransform(new Point(paddedWidth, Math.Max(spacing, 8)), frame.RectTransform) { AbsoluteOffset = new Point(padding, yOffset) }, HUDLayoutSettings.Padding);
votingTimer.RectTransform.IsFixedSize = true;
yOffset += votingTimer.Rect.Height + spacing;
int buttonWidth = (int)(paddedWidth * 0.3f);
yesVoteButton = new GUIButton(new RectTransform(new Point(buttonWidth, 0), frame.RectTransform) { AbsoluteOffset = new Point((int)(frame.Rect.Width / 2f - buttonWidth - spacing), yOffset) }, TextManager.Get("yes"))
{
OnClicked = (applyButton, obj) =>
{
SetGUIToVotedState(2);
GameMain.Client.Vote(currentVoteType, 2);
return true;
}
};
noVoteButton = new GUIButton(new RectTransform(new Point(buttonWidth, 0), frame.RectTransform) { AbsoluteOffset = new Point(yesVoteButton.RectTransform.AbsoluteOffset.X + yesVoteButton.Rect.Width + padding, yOffset) }, TextManager.Get("no"))
{
OnClicked = (applyButton, obj) =>
{
SetGUIToVotedState(1);
GameMain.Client.Vote(currentVoteType, 1);
return true;
}
};
votedTextBlock = new GUITextBlock(new RectTransform(new Point(paddedWidth, yesVoteButton.Rect.Height), frame.RectTransform), string.Empty, textAlignment: Alignment.Center);
votedTextBlock.RectTransform.IsFixedSize = true;
votedTextBlock.RectTransform.AbsoluteOffset = new Point(padding, yOffset);
votedTextBlock.Visible = false;
yOffset += yesVoteButton.Rect.Height;
frame.RectTransform.NonScaledSize = new Point(frame.Rect.Width, yOffset + padding);
}
private void SetGUIToVotedState(int vote)
{
yesVoteButton.Visible = noVoteButton.Visible = false;
votedTextBlock.Text = TextManager.Get(vote == 2 ? "yesvoted" : "novoted");
votedTextBlock.Visible = true;
}
public void Update(float deltaTime)
{
if (!VoteRunning) return;
if (GameMain.GraphicsWidth != createdForResolution.X || GameMain.GraphicsHeight != createdForResolution.Y) CreateVotingGUI();
yesVotes = getYesVotes();
noVotes = getNoVotes();
maxVotes = getMaxVotes();
voteCounter.Text = $"({yesVotes + noVotes}/{maxVotes})";
timer += deltaTime;
votingTimer.BarSize = timer / votingTime;
}
public void EndVote(bool passed, int yesVoteFinal, int noVoteFinal)
{
VoteRunning = false;
votePassed = passed;
yesVotes = yesVoteFinal;
noVotes = noVoteFinal;
onVoteEnd?.Invoke();
}
#region Submarine Voting
private void SetSubmarineVotingText(Client starter, SubmarineInfo info, VoteType type)
{
string name = starter.Name;
JobPrefab prefab = starter?.Character?.Info?.Job?.Prefab;
Color nameColor = prefab != null ? prefab.UIColor : Color.White;
string characterRichString = $"‖color:{nameColor.R},{nameColor.G},{nameColor.B}‖{name}‖color:end‖";
string submarineRichString = $"‖color:{submarineColor.R},{submarineColor.G},{submarineColor.B}‖{info.DisplayName}‖color:end‖";
switch (type)
{
case VoteType.PurchaseAndSwitchSub:
votingOnText = TextManager.GetWithVariables("submarinepurchaseandswitchvote", new string[] { "[playername]", "[submarinename]", "[amount]", "[currencyname]" }, new string[] { characterRichString, submarineRichString, info.Price.ToString(), TextManager.Get("credit").ToLower() });
break;
case VoteType.PurchaseSub:
votingOnText = TextManager.GetWithVariables("submarinepurchasevote", new string[] { "[playername]", "[submarinename]", "[amount]", "[currencyname]" }, new string[] { characterRichString, submarineRichString, info.Price.ToString(), TextManager.Get("credit").ToLower() });
break;
case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
if (deliveryFee > 0)
{
votingOnText = TextManager.GetWithVariables("submarineswitchfeevote", new string[] { "[playername]", "[submarinename]", "[locationname]", "[amount]", "[currencyname]" }, new string[] { characterRichString, submarineRichString, endLocation.Name, deliveryFee.ToString(), TextManager.Get("credit").ToLower() });
}
else
{
votingOnText = TextManager.GetWithVariables("submarineswitchnofeevote", new string[] { "[playername]", "[submarinename]" }, new string[] { characterRichString, submarineRichString });
}
break;
}
votingOnTextData = RichTextData.GetRichTextData(votingOnText, out votingOnText);
}
private int SubmarineYesVotes()
{
return GameMain.NetworkMember.SubmarineVoteYesCount;
}
private int SubmarineNoVotes()
{
return GameMain.NetworkMember.SubmarineVoteNoCount;
}
private int SubmarineMaxVotes()
{
return GameMain.NetworkMember.SubmarineVoteMax;
}
private void SendSubmarineVoteEndMessage(SubmarineInfo info, VoteType type)
{
GameMain.NetworkMember.AddChatMessage(GetSubmarineVoteResultMessage(info, type, yesVotes.ToString(), noVotes.ToString(), votePassed), ChatMessageType.Server);
}
public static string GetSubmarineVoteResultMessage(SubmarineInfo info, VoteType type, string yesVoteString, string noVoteString, bool votePassed)
{
string result = string.Empty;
switch (type)
{
case VoteType.PurchaseAndSwitchSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchaseandswitchvotepassed" : "submarinepurchaseandswitchvotefailed", new string[] { "[submarinename]", "[amount]", "[currencyname]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, info.Price.ToString(), TextManager.Get("credit").ToLower(), yesVoteString, noVoteString });
break;
case VoteType.PurchaseSub:
result = TextManager.GetWithVariables(votePassed ? "submarinepurchasevotepassed" : "submarinepurchasevotefailed", new string[] { "[submarinename]", "[amount]", "[currencyname]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, info.Price.ToString(), TextManager.Get("credit").ToLower(), yesVoteString, noVoteString });
break;
case VoteType.SwitchSub:
int deliveryFee = SubmarineSelection.DeliveryFeePerDistanceTravelled * GameMain.GameSession.Map.DistanceToClosestLocationWithOutpost(GameMain.GameSession.Map.CurrentLocation, out Location endLocation);
if (deliveryFee > 0)
{
result = TextManager.GetWithVariables(votePassed ? "submarineswitchfeevotepassed" : "submarineswitchfeevotefailed", new string[] { "[submarinename]", "[locationname]", "[amount]", "[currencyname]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, endLocation.Name, deliveryFee.ToString(), TextManager.Get("credit").ToLower(), yesVoteString, noVoteString });
}
else
{
result = TextManager.GetWithVariables(votePassed ? "submarineswitchnofeevotepassed" : "submarineswitchnofeevotefailed", new string[] { "[submarinename]", "[yesvotecount]", "[novotecount]" }, new string[] { info.DisplayName, yesVoteString, noVoteString });
}
break;
default:
break;
}
return result;
}
#endregion
public void Remove()
{
if (frame != null)
{
frame.Parent.RemoveChild(frame);
frame = null;
}
}
}
}
@@ -17,6 +17,7 @@ using System.Threading;
using Barotrauma.Tutorials; using Barotrauma.Tutorials;
using Barotrauma.Media; using Barotrauma.Media;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using System.Threading.Tasks;
namespace Barotrauma namespace Barotrauma
{ {
@@ -35,7 +36,6 @@ namespace Barotrauma
public static GameScreen GameScreen; public static GameScreen GameScreen;
public static MainMenuScreen MainMenuScreen; public static MainMenuScreen MainMenuScreen;
public static LobbyScreen LobbyScreen;
public static NetLobbyScreen NetLobbyScreen; public static NetLobbyScreen NetLobbyScreen;
public static ServerListScreen ServerListScreen; public static ServerListScreen ServerListScreen;
@@ -45,8 +45,11 @@ namespace Barotrauma
public static ParticleEditorScreen ParticleEditorScreen; public static ParticleEditorScreen ParticleEditorScreen;
public static LevelEditorScreen LevelEditorScreen; public static LevelEditorScreen LevelEditorScreen;
public static SpriteEditorScreen SpriteEditorScreen; public static SpriteEditorScreen SpriteEditorScreen;
public static EventEditorScreen EventEditorScreen;
public static CharacterEditor.CharacterEditorScreen CharacterEditorScreen; public static CharacterEditor.CharacterEditorScreen CharacterEditorScreen;
public static CampaignEndScreen CampaignEndScreen;
public static Lights.LightManager LightManager; public static Lights.LightManager LightManager;
public static Sounds.SoundManager SoundManager; public static Sounds.SoundManager SoundManager;
@@ -79,6 +82,10 @@ namespace Barotrauma
set set
{ {
if (gameSession == value) { return; } if (gameSession == value) { return; }
if (value == null && Screen.Selected == GameScreen && gameSession.GameMode is CampaignMode)
{
DebugConsole.AddWarning("GameSession set to null while in the game screen\n" + Environment.StackTrace);
}
if (gameSession?.GameMode != null && gameSession.GameMode != value?.GameMode) if (gameSession?.GameMode != null && gameSession.GameMode != value?.GameMode)
{ {
gameSession.GameMode.Remove(); gameSession.GameMode.Remove();
@@ -100,7 +107,7 @@ namespace Barotrauma
private CoroutineHandle loadingCoroutine; private CoroutineHandle loadingCoroutine;
private bool hasLoaded; private bool hasLoaded;
private GameTime fixedTime; private readonly GameTime fixedTime;
public string ConnectName; public string ConnectName;
public string ConnectEndpoint; public string ConnectEndpoint;
@@ -110,7 +117,7 @@ namespace Barotrauma
private Viewport defaultViewport; private Viewport defaultViewport;
public event Action OnResolutionChanged; public event Action ResolutionChanged;
private bool exiting; private bool exiting;
@@ -190,7 +197,6 @@ namespace Barotrauma
public GameMain(string[] args) public GameMain(string[] args)
{ {
Content.RootDirectory = "Content"; Content.RootDirectory = "Content";
#if DEBUG && WINDOWS #if DEBUG && WINDOWS
GraphicsAdapter.UseDebugLayers = true; GraphicsAdapter.UseDebugLayers = true;
#endif #endif
@@ -273,7 +279,7 @@ namespace Barotrauma
defaultViewport = GraphicsDevice.Viewport; defaultViewport = GraphicsDevice.Viewport;
OnResolutionChanged?.Invoke(); ResolutionChanged?.Invoke();
} }
public void SetWindowMode(WindowMode windowMode) public void SetWindowMode(WindowMode windowMode)
@@ -455,9 +461,10 @@ namespace Barotrauma
{ {
bool waitingForWorkshopUpdates = true; bool waitingForWorkshopUpdates = true;
bool result = false; bool result = false;
TaskPool.Add(SteamManager.AutoUpdateWorkshopItemsAsync(), (task) => TaskPool.Add("AutoUpdateWorkshopItemsAsync",
SteamManager.AutoUpdateWorkshopItemsAsync(), (task) =>
{ {
result = task.Result; result = ((Task<bool>)task).Result;
waitingForWorkshopUpdates = false; waitingForWorkshopUpdates = false;
}); });
@@ -521,6 +528,10 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
TaskPool.Add("InitRelayNetworkAccess", SteamManager.InitRelayNetworkAccess(), (t) => { });
FactionPrefab.LoadFactions();
NPCSet.LoadSets();
CharacterPrefab.LoadAll(); CharacterPrefab.LoadAll();
MissionPrefab.Init(); MissionPrefab.Init();
TraitorMissionPrefab.Init(); TraitorMissionPrefab.Init();
@@ -528,8 +539,9 @@ namespace Barotrauma
Tutorials.Tutorial.Init(); Tutorials.Tutorial.Init();
MapGenerationParams.Init(); MapGenerationParams.Init();
LevelGenerationParams.LoadPresets(); LevelGenerationParams.LoadPresets();
OutpostGenerationParams.LoadPresets();
WreckAIConfig.LoadAll(); WreckAIConfig.LoadAll();
ScriptedEventSet.LoadPrefabs(); EventSet.LoadPrefabs();
AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions)); AfflictionPrefab.LoadAll(GetFilesOfType(ContentType.Afflictions));
SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings)); SkillSettings.Load(GetFilesOfType(ContentType.SkillSettings));
Order.Init(); Order.Init();
@@ -545,6 +557,10 @@ namespace Barotrauma
TitleScreen.LoadState = 55.0f; TitleScreen.LoadState = 55.0f;
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
UpgradePrefab.LoadAll(GetFilesOfType(ContentType.UpgradeModules));
TitleScreen.LoadState = 56.0f;
yield return CoroutineStatus.Running;
JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs)); JobPrefab.LoadAll(GetFilesOfType(ContentType.Jobs));
CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses)); CorpsePrefab.LoadAll(GetFilesOfType(ContentType.Corpses));
@@ -567,7 +583,6 @@ namespace Barotrauma
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
MainMenuScreen = new MainMenuScreen(this); MainMenuScreen = new MainMenuScreen(this);
LobbyScreen = new LobbyScreen();
ServerListScreen = new ServerListScreen(); ServerListScreen = new ServerListScreen();
TitleScreen.LoadState = 70.0f; TitleScreen.LoadState = 70.0f;
@@ -594,7 +609,9 @@ namespace Barotrauma
LevelEditorScreen = new LevelEditorScreen(); LevelEditorScreen = new LevelEditorScreen();
SpriteEditorScreen = new SpriteEditorScreen(); SpriteEditorScreen = new SpriteEditorScreen();
EventEditorScreen = new EventEditorScreen();
CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen(); CharacterEditorScreen = new CharacterEditor.CharacterEditorScreen();
CampaignEndScreen = new CampaignEndScreen();
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -633,7 +650,7 @@ namespace Barotrauma
foreach (ContentPackage contentPackage in Config.SelectedContentPackages) foreach (ContentPackage contentPackage in Config.SelectedContentPackages)
{ {
var exePaths = contentPackage.GetFilesOfType(ContentType.Executable); var exePaths = contentPackage.GetFilesOfType(ContentType.Executable);
if (exePaths.Any() && AppDomain.CurrentDomain.FriendlyName != exePaths.First()) if (exePaths.Any() && AppDomain.CurrentDomain.FriendlyName != Path.GetFileNameWithoutExtension(exePaths.First()))
{ {
var msgBox = new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("IncorrectExe", var msgBox = new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("IncorrectExe",
new string[2] { "[selectedpackage]", "[exename]" }, new string[2] { contentPackage.Name, exePaths.First() }), new string[2] { "[selectedpackage]", "[exename]" }, new string[2] { contentPackage.Name, exePaths.First() }),
@@ -763,7 +780,7 @@ namespace Barotrauma
//reset accumulator if loading //reset accumulator if loading
// -> less choppy loading screens because the screen is rendered after each update // -> less choppy loading screens because the screen is rendered after each update
// -> no pause caused by leftover time in the accumulator when starting a new shift // -> no pause caused by leftover time in the accumulator when starting a new shift
GameMain.ResetFrameTime(); ResetFrameTime();
if (!TitleScreen.PlayingSplashScreen) if (!TitleScreen.PlayingSplashScreen)
{ {
@@ -777,12 +794,34 @@ namespace Barotrauma
} }
#if DEBUG #if DEBUG
if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && Config.AutomaticQuickStartEnabled && FirstLoad) if (TitleScreen.LoadState >= 100.0f && !TitleScreen.PlayingSplashScreen && (Config.AutomaticQuickStartEnabled || Config.AutomaticCampaignLoadEnabled) && FirstLoad && !PlayerInput.KeyDown(Keys.LeftShift))
{ {
loadingScreenOpen = false; loadingScreenOpen = false;
FirstLoad = false; FirstLoad = false;
if (Config.AutomaticQuickStartEnabled)
{
MainMenuScreen.QuickStart(); MainMenuScreen.QuickStart();
} }
else if (Config.AutomaticCampaignLoadEnabled)
{
IEnumerable<string> saveFiles = SaveUtil.GetSaveFiles(SaveUtil.SaveType.Singleplayer);
if (saveFiles.Count() > 0)
{
saveFiles = saveFiles.OrderBy(file => File.GetLastWriteTime(file));
try
{
SaveUtil.LoadGame(saveFiles.Last());
}
catch (Exception e)
{
DebugConsole.ThrowError("Loading save \"" + saveFiles.Last() + "\" failed", e);
return;
}
}
}
}
#endif #endif
if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine)) if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
@@ -845,6 +884,12 @@ namespace Barotrauma
{ {
((GUIMessageBox)GUIMessageBox.VisibleBox).Close(); ((GUIMessageBox)GUIMessageBox.VisibleBox).Close();
} }
else if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary &&
roundSummary.ContinueButton != null &&
roundSummary.ContinueButton.Visible)
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
else if (Tutorial.Initialized && Tutorial.ContentRunning) else if (Tutorial.Initialized && Tutorial.ContentRunning)
{ {
(GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI(); (GameSession.GameMode as TutorialMode).Tutorial.CloseActiveContentGUI();
@@ -868,7 +913,7 @@ namespace Barotrauma
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
} }
bool itemHudActive() static bool itemHudActive()
{ {
if (Character.Controlled?.SelectedConstruction == null) { return false; } if (Character.Controlled?.SelectedConstruction == null) { return false; }
return return
@@ -878,7 +923,7 @@ namespace Barotrauma
} }
#if DEBUG #if DEBUG
if (GameMain.NetworkMember == null) if (NetworkMember == null)
{ {
if (PlayerInput.KeyHit(Keys.P) && !(GUI.KeyboardDispatcher.Subscriber is GUITextBox)) if (PlayerInput.KeyHit(Keys.P) && !(GUI.KeyboardDispatcher.Subscriber is GUITextBox))
{ {
@@ -890,6 +935,10 @@ namespace Barotrauma
GUI.ClearUpdateList(); GUI.ClearUpdateList();
Paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || Tutorial.ContentRunning || DebugConsole.Paused) && Paused = (DebugConsole.IsOpen || GUI.PauseMenuOpen || GUI.SettingsMenuOpen || Tutorial.ContentRunning || DebugConsole.Paused) &&
(NetworkMember == null || !NetworkMember.GameStarted); (NetworkMember == null || !NetworkMember.GameStarted);
if (GameSession?.GameMode != null && GameSession.GameMode.Paused)
{
Paused = true;
}
#if !DEBUG #if !DEBUG
if (NetworkMember == null && !WindowActive && !Paused && true && Screen.Selected != MainMenuScreen && Config.PauseOnFocusLost) if (NetworkMember == null && !WindowActive && !Paused && true && Screen.Selected != MainMenuScreen && Config.PauseOnFocusLost)
@@ -921,7 +970,7 @@ namespace Barotrauma
{ {
(GameSession.GameMode as TutorialMode).Update((float)Timing.Step); (GameSession.GameMode as TutorialMode).Update((float)Timing.Step);
} }
else if (DebugConsole.Paused) else
{ {
if (Screen.Selected.Cam == null) if (Screen.Selected.Cam == null)
{ {
@@ -929,7 +978,7 @@ namespace Barotrauma
} }
else else
{ {
Screen.Selected.Cam.MoveCamera((float)Timing.Step); Screen.Selected.Cam.MoveCamera((float)Timing.Step, allowMove: DebugConsole.Paused, allowZoom: DebugConsole.Paused);
} }
} }
@@ -1027,41 +1076,53 @@ namespace Barotrauma
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close; msgBox.Buttons[1].OnClicked += msgBox.Close;
} }
} }
public static void QuitToMainMenu(bool save) public static void QuitToMainMenu(bool save)
{ {
if (save) if (save)
{ {
SaveUtil.SaveGame(GameMain.GameSession.SavePath); if (GameSession.Submarine != null && !GameSession.Submarine.Removed)
{
GameSession.SubmarineInfo = new SubmarineInfo(GameSession.Submarine);
} }
if (GameMain.Client != null) // Update store stock when saving and quitting in an outpost (normally updated when CampaignMode.End() is called)
if (GameSession?.Campaign is SinglePlayerCampaign campaign && Level.IsLoadedOutpost && campaign.Map?.CurrentLocation != null && campaign.CargoManager != null)
{ {
GameMain.Client.Disconnect(); campaign.Map.CurrentLocation.AddToStock(campaign.CargoManager.SoldItems);
GameMain.Client = null; campaign.CargoManager.ClearSoldItemsProjSpecific();
campaign.Map.CurrentLocation.RemoveFromStock(campaign.CargoManager.PurchasedItems);
}
SaveUtil.SaveGame(GameSession.SavePath);
}
if (Client != null)
{
Client.Disconnect();
Client = null;
} }
CoroutineManager.StopCoroutines("EndCinematic"); CoroutineManager.StopCoroutines("EndCinematic");
if (GameMain.GameSession != null) if (GameSession != null)
{ {
if (Tutorial.Initialized) if (Tutorial.Initialized)
{ {
((TutorialMode)GameMain.GameSession.GameMode).Tutorial?.Stop(); ((TutorialMode)GameSession.GameMode).Tutorial?.Stop();
} }
if (GameSettings.SendUserStatistics) if (GameSettings.SendUserStatistics)
{ {
Mission mission = GameMain.GameSession.Mission; Mission mission = GameSession.Mission;
GameAnalyticsManager.AddDesignEvent("QuitRound:" + (save ? "Save" : "NoSave")); GameAnalyticsManager.AddDesignEvent("QuitRound:" + (save ? "Save" : "NoSave"));
GameAnalyticsManager.AddDesignEvent("EndRound:" + (mission == null ? "NoMission" : (mission.Completed ? "MissionCompleted" : "MissionFailed"))); GameAnalyticsManager.AddDesignEvent("EndRound:" + (mission == null ? "NoMission" : (mission.Completed ? "MissionCompleted" : "MissionFailed")));
} }
GameMain.GameSession = null;
} }
GUIMessageBox.CloseAll(); GUIMessageBox.CloseAll();
GameMain.MainMenuScreen.Select(); MainMenuScreen.Select();
GameSession = null;
} }
public void ShowCampaignDisclaimer(Action onContinue = null) public void ShowCampaignDisclaimer(Action onContinue = null)
@@ -1071,12 +1132,7 @@ namespace Barotrauma
msgBox.Buttons[0].OnClicked = (btn, userdata) => msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{ {
var roadMap = new GUIMessageBox(TextManager.Get("CampaignRoadMapTitle"), TextManager.Get("CampaignRoadMapText"), ShowOpenUrlInWebBrowserPrompt("https://trello.com/b/hBXI8ltN/barotrauma-roadmap-known-issues");
new string[] { TextManager.Get("Back"), TextManager.Get("OK") });
roadMap.Buttons[0].OnClicked += roadMap.Close;
roadMap.Buttons[0].OnClicked += (_, __) => { ShowCampaignDisclaimer(onContinue); return true; };
roadMap.Buttons[1].OnClicked += roadMap.Close;
roadMap.Buttons[1].OnClicked += (_, __) => { onContinue?.Invoke(); return true; };
return true; return true;
}; };
msgBox.Buttons[0].OnClicked += msgBox.Close; msgBox.Buttons[0].OnClicked += msgBox.Close;
@@ -1124,8 +1180,10 @@ namespace Barotrauma
return; return;
} }
var msgBox = new GUIMessageBox(TextManager.Get("bugreportbutton"), ""); var msgBox = new GUIMessageBox(TextManager.Get("bugreportbutton"), "")
msgBox.UserData = "bugreporter"; {
UserData = "bugreporter"
};
var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f }; var linkHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), msgBox.Content.RectTransform)) { Stretch = true, RelativeSpacing = 0.025f };
linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height); linkHolder.RectTransform.MaxSize = new Point(int.MaxValue, linkHolder.Rect.Height);
@@ -0,0 +1,173 @@
using Barotrauma.Extensions;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class CargoManager
{
private class SoldEntity
{
public enum SellStatus
{
Confirmed,
Unconfirmed,
Local
}
public Item Item { get; }
public SellStatus Status { get; set; }
private SoldEntity(Item item, SellStatus status)
{
Item = item;
Status = status;
}
public static SoldEntity CreateInSinglePlayer(Item item) => new SoldEntity(item, SellStatus.Confirmed);
public static SoldEntity CreateInMultiPlayer(Item item) => new SoldEntity(item, SellStatus.Local);
}
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
public List<Item> GetSellableItems(Character character)
{
if (character == null) { return new List<Item>(); }
// Only consider items which have been:
// a) sold in singleplayer or confirmed by server (SellStatus.Confirmed); or
// b) sold locally in multiplier (SellStatus.Local), but the client has not received a campaing state update yet after selling them
var soldEntities = SoldEntities.Where(se => se.Status != SoldEntity.SellStatus.Unconfirmed);
var sellables = Item.ItemList.FindAll(i => i?.Prefab != null && !i.Removed &&
i.GetRootInventoryOwner() == character &&
!i.SpawnedInOutpost &&
(i.ContainedItems == null || i.ContainedItems.None() || i.ContainedItems.All(ci => soldEntities.Any(se => se.Item == ci))) &&
i.IsFullCondition && soldEntities.None(se => se.Item == i));
// Prevent selling things like battery cells from headsets and oxygen tanks from diving masks
var slots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.OuterClothes, InvSlotType.Headset };
foreach (InvSlotType slot in slots)
{
var index = character.Inventory.FindLimbSlot(slot);
if (character.Inventory.Items[index] is Item item && item.ContainedItems != null)
{
foreach (Item containedItem in item.ContainedItems)
{
if (containedItem != null)
{
sellables.Remove(containedItem);
}
}
}
}
return sellables;
}
public void SetItemsInBuyCrate(List<PurchasedItem> items)
{
ItemsInBuyCrate.Clear();
ItemsInBuyCrate.AddRange(items);
OnItemsInBuyCrateChanged?.Invoke();
}
public void SetSoldItems(List<SoldItem> items)
{
SoldItems.Clear();
SoldItems.AddRange(items);
foreach (SoldEntity se in SoldEntities)
{
if (se.Status == SoldEntity.SellStatus.Confirmed) { continue; }
if (SoldItems.Any(si => si.ID == se.Item.ID && si.ItemPrefab == se.Item.Prefab && (GameMain.Client == null || GameMain.Client.ID == si.SellerID)))
{
se.Status = SoldEntity.SellStatus.Confirmed;
}
else
{
se.Status = SoldEntity.SellStatus.Unconfirmed;
}
}
OnSoldItemsChanged?.Invoke();
}
public void ModifyItemQuantityInSellCrate(ItemPrefab itemPrefab, int changeInQuantity)
{
PurchasedItem itemToSell = ItemsInSellCrate.Find(i => i.ItemPrefab == itemPrefab);
if (itemToSell != null)
{
itemToSell.Quantity += changeInQuantity;
if (itemToSell.Quantity < 1)
{
ItemsInSellCrate.Remove(itemToSell);
}
}
else if (changeInQuantity > 0)
{
itemToSell = new PurchasedItem(itemPrefab, changeInQuantity);
ItemsInSellCrate.Add(itemToSell);
}
OnItemsInSellCrateChanged?.Invoke();
}
public void SellItems(List<PurchasedItem> itemsToSell)
{
var itemsInInventory = GetSellableItems(Character.Controlled);
var canAddToRemoveQueue = campaign.IsSinglePlayer && Entity.Spawner != null;
var sellerId = GameMain.Client?.ID ?? 0;
foreach (PurchasedItem item in itemsToSell)
{
var itemValue = GetSellValueAtCurrentLocation(item.ItemPrefab, quantity: item.Quantity);
// check if the store can afford the item
if (location.StoreCurrentBalance < itemValue) { continue; }
var matchingItems = itemsInInventory.FindAll(i => i.Prefab == item.ItemPrefab);
if (matchingItems.Count <= item.Quantity)
{
foreach (Item i in matchingItems)
{
SoldItems.Add(new SoldItem(i.Prefab, i.ID, canAddToRemoveQueue, sellerId));
SoldEntities.Add(campaign.IsSinglePlayer ? SoldEntity.CreateInSinglePlayer(i) : SoldEntity.CreateInMultiPlayer(i));
if (canAddToRemoveQueue) { Entity.Spawner.AddToRemoveQueue(i); }
}
}
else
{
for (int i = 0; i < item.Quantity; i++)
{
var matchingItem = matchingItems[i];
SoldItems.Add(new SoldItem(matchingItem.Prefab, matchingItem.ID, canAddToRemoveQueue, sellerId));
SoldEntities.Add(campaign.IsSinglePlayer ? SoldEntity.CreateInSinglePlayer(matchingItem) : SoldEntity.CreateInMultiPlayer(matchingItem));
if (canAddToRemoveQueue) { Entity.Spawner.AddToRemoveQueue(matchingItem); }
}
}
// Exchange money
campaign.Map.CurrentLocation.StoreCurrentBalance -= itemValue;
campaign.Money += itemValue;
// Remove from the sell crate
if (ItemsInSellCrate.Find(pi => pi.ItemPrefab == item.ItemPrefab) is { } itemToSell)
{
itemToSell.Quantity -= item.Quantity;
if (itemToSell.Quantity < 1)
{
ItemsInSellCrate.Remove(itemToSell);
}
}
}
OnSoldItemsChanged?.Invoke();
}
public void ClearSoldItemsProjSpecific()
{
SoldItems.Clear();
SoldEntities.Clear();
}
}
}
@@ -20,9 +20,6 @@ namespace Barotrauma
/// </summary> /// </summary>
const float CharacterWaitOnSwitch = 10.0f; const float CharacterWaitOnSwitch = 10.0f;
private readonly List<CharacterInfo> characterInfos = new List<CharacterInfo>();
private readonly List<Character> characters = new List<Character>();
private Point screenResolution; private Point screenResolution;
#region UI #region UI
@@ -30,6 +27,7 @@ namespace Barotrauma
public GUIComponent ReportButtonFrame { get; set; } public GUIComponent ReportButtonFrame { get; set; }
private GUIFrame guiFrame; private GUIFrame guiFrame;
private GUIComponent crewAreaWithButtons;
private GUIFrame crewArea; private GUIFrame crewArea;
private GUIListBox crewList; private GUIListBox crewList;
private GUIButton commandButton, toggleCrewButton; private GUIButton commandButton, toggleCrewButton;
@@ -72,19 +70,7 @@ namespace Barotrauma
public CrewManager(XElement element, bool isSinglePlayer) public CrewManager(XElement element, bool isSinglePlayer)
: this(isSinglePlayer) : this(isSinglePlayer)
{ {
foreach (XElement subElement in element.Elements()) AddCharacterElements(element);
{
if (!subElement.Name.ToString().Equals("character", StringComparison.OrdinalIgnoreCase)) { continue; }
var characterInfo = new CharacterInfo(subElement);
characterInfos.Add(characterInfo);
foreach (XElement invElement in subElement.Elements())
{
if (!invElement.Name.ToString().Equals("inventory", StringComparison.OrdinalIgnoreCase)) { continue; }
characterInfo.InventoryData = invElement;
break;
}
}
} }
partial void InitProjectSpecific() partial void InitProjectSpecific()
@@ -96,7 +82,7 @@ namespace Barotrauma
#region Crew Area #region Crew Area
var crewAreaWithButtons = new GUIFrame( crewAreaWithButtons = new GUIFrame(
HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform), HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.CrewArea, guiFrame.RectTransform),
style: null, style: null,
color: Color.Transparent) color: Color.Transparent)
@@ -326,43 +312,6 @@ namespace Barotrauma
return characterInfos; return characterInfos;
} }
public void AddCharacter(Character character)
{
if (character.Removed)
{
DebugConsole.ThrowError("Tried to add a removed character to CrewManager!\n" + Environment.StackTrace);
return;
}
if (character.IsDead)
{
DebugConsole.ThrowError("Tried to add a dead character to CrewManager!\n" + Environment.StackTrace);
return;
}
if (!characters.Contains(character))
{
characters.Add(character);
}
if (!characterInfos.Contains(character.Info))
{
characterInfos.Add(character.Info);
}
AddCharacterToCrewList(character);
DisplayCharacterOrder(character, character.CurrentOrder, character.CurrentOrderOption);
}
public void AddCharacterInfo(CharacterInfo characterInfo)
{
if (characterInfos.Contains(characterInfo))
{
DebugConsole.ThrowError("Tried to add the same character info to CrewManager twice.\n" + Environment.StackTrace);
return;
}
characterInfos.Add(characterInfo);
}
/// <summary> /// <summary>
/// Remove the character from the crew (and crew menus). /// Remove the character from the crew (and crew menus).
/// </summary> /// </summary>
@@ -379,15 +328,6 @@ namespace Barotrauma
if (removeInfo) { characterInfos.Remove(character.Info); } if (removeInfo) { characterInfos.Remove(character.Info); }
} }
/// <summary>
/// Remove info of a selected character. The character will not be visible in any menus or the round summary.
/// </summary>
/// <param name="characterInfo"></param>
public void RemoveCharacterInfo(CharacterInfo characterInfo)
{
characterInfos.Remove(characterInfo);
}
private void AddCharacterToCrewList(Character character) private void AddCharacterToCrewList(Character character)
{ {
if (character == null) { return; } if (character == null) { return; }
@@ -516,7 +456,7 @@ namespace Barotrauma
}; };
new GUIImage( new GUIImage(
new RectTransform(Vector2.One, soundIcons.RectTransform), new RectTransform(Vector2.One, soundIcons.RectTransform),
GUI.Style.GetComponentStyle("GUISoundIcon").Sprites[GUIComponent.ComponentState.None].FirstOrDefault().Sprite, GUI.Style.GetComponentStyle("GUISoundIcon").GetDefaultSprite(),
scaleToFit: true) scaleToFit: true)
{ {
CanBeFocused = false, CanBeFocused = false,
@@ -630,6 +570,22 @@ namespace Barotrauma
ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender)); ChatBox.AddMessage(ChatMessage.Create(senderName, text, messageType, sender));
} }
public void AddSinglePlayerChatMessage(ChatMessage message)
{
if (!IsSinglePlayer)
{
DebugConsole.ThrowError("Cannot add messages to single player chat box in multiplayer mode!\n" + Environment.StackTrace);
return;
}
if (string.IsNullOrEmpty(message.Text)) { return; }
if (message.Sender != null)
{
GameMain.GameSession.CrewManager.SetCharacterSpeaking(message.Sender);
}
ChatBox.AddMessage(message);
}
private WifiComponent GetHeadset(Character character, bool requireEquipped) private WifiComponent GetHeadset(Character character, bool requireEquipped)
{ {
if (character?.Inventory == null) return null; if (character?.Inventory == null) return null;
@@ -861,27 +817,6 @@ namespace Barotrauma
return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder"); return characterComponent?.FindChild(c => c?.UserData is OrderInfo orderInfo && orderInfo.ComponentIdentifier == "previousorder");
} }
private struct OrderInfo
{
public string ComponentIdentifier { get; set; }
public Order Order { get; private set; }
public string OrderOption { get; private set; }
public OrderInfo(Order order, string orderOption)
{
ComponentIdentifier = "currentorder";
Order = order;
OrderOption = orderOption;
}
public OrderInfo(OrderInfo orderInfo)
{
ComponentIdentifier = "previousorder";
Order = orderInfo.Order;
OrderOption = orderInfo.OrderOption;
}
}
#endregion #endregion
#region Updating and drawing the UI #region Updating and drawing the UI
@@ -1123,6 +1058,7 @@ namespace Barotrauma
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || CoroutineManager.IsCoroutineRunning("SubmarineTransition")) { return; }
commandFrame?.AddToGUIUpdateList(); commandFrame?.AddToGUIUpdateList();
@@ -1145,6 +1081,8 @@ namespace Barotrauma
} }
} }
crewAreaWithButtons.Visible = !(GameMain.GameSession?.GameMode is CampaignMode campaign) || (!campaign.ForceMapUI && !campaign.ShowCampaignUI);
guiFrame.AddToGUIUpdateList(); guiFrame.AddToGUIUpdateList();
contextMenu?.AddToGUIUpdateList(false, 1); contextMenu?.AddToGUIUpdateList(false, 1);
subContextMenu?.AddToGUIUpdateList(false, 1); subContextMenu?.AddToGUIUpdateList(false, 1);
@@ -1170,6 +1108,7 @@ namespace Barotrauma
private void SelectCharacter(Character character) private void SelectCharacter(Character character)
{ {
if (ConversationAction.IsDialogOpen) { return; }
if (!AllowCharacterSwitch) { return; } if (!AllowCharacterSwitch) { return; }
//make the previously selected character wait in place for some time //make the previously selected character wait in place for some time
//(so they don't immediately start idling and walking away from their station) //(so they don't immediately start idling and walking away from their station)
@@ -1256,7 +1195,7 @@ namespace Barotrauma
WasCommandInterfaceDisabledThisUpdate = false; WasCommandInterfaceDisabledThisUpdate = false;
if (PlayerInput.KeyDown(InputType.Command) && (GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber == crewList) && if (PlayerInput.KeyDown(InputType.Command) && (GUI.KeyboardDispatcher.Subscriber == null || GUI.KeyboardDispatcher.Subscriber == crewList) &&
commandFrame == null && !clicklessSelectionActive && CanIssueOrders) commandFrame == null && !clicklessSelectionActive && CanIssueOrders && !(GameMain.GameSession?.Campaign?.ShowCampaignUI ?? false))
{ {
if (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) if (PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift))
{ {
@@ -1574,32 +1513,32 @@ namespace Barotrauma
get get
{ {
#if DEBUG #if DEBUG
return Character.Controlled == null || Character.Controlled.Info != null && Character.Controlled.SpeechImpediment < 100.0f; if (Character.Controlled == null) { return true; }
#else
return Character.Controlled?.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
#endif #endif
return Character.Controlled?.Info != null && Character.Controlled.SpeechImpediment < 100.0f;
} }
} }
private bool CanSomeoneHearCharacter() private bool CanSomeoneHearCharacter()
{ {
#if DEBUG #if DEBUG
return true; if (Character.Controlled == null) { return true; }
#else
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
#endif #endif
return Character.Controlled != null && characters.Any(c => c != Character.Controlled && c.CanHearCharacter(Character.Controlled));
} }
private Entity FindEntityContext() private Entity FindEntityContext()
{ {
if (Character.Controlled?.FocusedCharacter != null) if (Character.Controlled?.FocusedCharacter is Character focusedCharacter && !focusedCharacter.IsDead &&
HumanAIController.IsFriendly(Character.Controlled, focusedCharacter) && Character.Controlled.TeamID == focusedCharacter.TeamID)
{ {
if (Character.Controlled?.FocusedItem != null) if (Character.Controlled?.FocusedItem != null)
{ {
Vector2 mousePos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 mousePos = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
if (Vector2.Distance(mousePos, Character.Controlled.FocusedCharacter.WorldPosition) < Vector2.Distance(mousePos, Character.Controlled.FocusedItem.WorldPosition)) if (Vector2.Distance(mousePos, focusedCharacter.WorldPosition) < Vector2.Distance(mousePos, Character.Controlled.FocusedItem.WorldPosition))
{ {
return Character.Controlled.FocusedCharacter; return focusedCharacter;
} }
else else
{ {
@@ -1608,7 +1547,7 @@ namespace Barotrauma
} }
else else
{ {
return Character.Controlled.FocusedCharacter; return focusedCharacter;
} }
} }
@@ -1677,24 +1616,22 @@ namespace Barotrauma
"CommandNodeContainer", "CommandNodeContainer",
scaleToFit: true) scaleToFit: true)
{ {
Color = characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier, Color = characterContext.Info?.Job?.Prefab != null ? characterContext.Info.Job.Prefab.UIColor * nodeColorMultiplier : Color.White,
HoverColor = characterContext.Info.Job.Prefab.UIColor, HoverColor = characterContext.Info?.Job?.Prefab != null ? characterContext.Info.Job.Prefab.UIColor : Color.White,
UserData = "colorsource" UserData = "colorsource"
}; };
// Character icon // Character icon
new GUICustomComponent( var characterIcon = new GUICustomComponent(
new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center), new RectTransform(Vector2.One, startNode.RectTransform, anchor: Anchor.Center),
(spriteBatch, _) => (spriteBatch, _) =>
{ {
if (!(entityContext is Character character)) { return; } if (!(entityContext is Character character) || character?.Info == null) { return; }
var node = startNode; var node = startNode;
character.Info.DrawJobIcon(spriteBatch, character.Info.DrawJobIcon(spriteBatch,
new Rectangle((int)(node.Rect.X + node.Rect.Width * 0.5f), (int)(node.Rect.Y + node.Rect.Height * 0.1f), (int)(node.Rect.Width * 0.6f), (int)(node.Rect.Height * 0.8f))); new Rectangle((int)(node.Rect.X + node.Rect.Width * 0.5f), (int)(node.Rect.Y + node.Rect.Height * 0.1f), (int)(node.Rect.Width * 0.6f), (int)(node.Rect.Height * 0.8f)));
character.Info.DrawIcon(spriteBatch, new Vector2(node.Rect.X + node.Rect.Width * 0.35f, node.Center.Y), node.Rect.Size.ToVector2() * 0.7f); character.Info.DrawIcon(spriteBatch, new Vector2(node.Rect.X + node.Rect.Width * 0.35f, node.Center.Y), node.Rect.Size.ToVector2() * 0.7f);
}) });
{ SetCharacterTooltip(characterIcon, entityContext as Character);
ToolTip = characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")"
};
} }
SetCenterNode(startNode); SetCenterNode(startNode);
@@ -1934,7 +1871,7 @@ namespace Barotrauma
c.HoverColor = c.Color; c.HoverColor = c.Color;
c.PressedColor = c.Color; c.PressedColor = c.Color;
c.SelectedColor = c.Color; c.SelectedColor = c.Color;
c.ToolTip = characterContext != null ? characterContext.Info.DisplayName + " (" + characterContext.Info.Job.Name + ")" : null; SetCharacterTooltip(c, characterContext);
} }
node.OnClicked = null; node.OnClicked = null;
centerNode = node; centerNode = node;
@@ -2040,7 +1977,7 @@ namespace Barotrauma
var reactorOutput = -reactor.CurrPowerConsumption; var reactorOutput = -reactor.CurrPowerConsumption;
// If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor // If player is not an engineer AND the reactor is not powered up AND nobody is using the reactor
// ---> Create shortcut node for "Operate Reactor" order's "Power Up" option // ---> Create shortcut node for "Operate Reactor" order's "Power Up" option
if ((Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) && if ((Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item)) reactorOutput < float.Epsilon && characters.None(c => c.SelectedConstruction == reactor.Item))
{ {
var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled); var order = new Order(Order.GetPrefab("operatereactor"), reactor.Item, reactor, Character.Controlled);
@@ -2053,7 +1990,7 @@ namespace Barotrauma
// TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it // TODO: Reconsider the conditions as bot captain can have the nav term selected without operating it
// If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up // If player is not a captain AND nobody is using the nav terminal AND the nav terminal is powered up
// --> Create shortcut node for Steer order // --> Create shortcut node for Steer order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("captain")) && if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("captain")) &&
sub.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) && sub.GetItems(false).Find(i => i.HasTag("navterminal") && !i.NonInteractable) is Item nav && characters.None(c => c.SelectedConstruction == nav) &&
nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage) nav.GetComponent<Steering>() is Steering steering && steering.Voltage > steering.MinVoltage)
{ {
@@ -2063,7 +2000,7 @@ namespace Barotrauma
// If player is not a security officer AND invaders are reported // If player is not a security officer AND invaders are reported
// --> Create shorcut node for Fight Intruders order // --> Create shorcut node for Fight Intruders order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("securityofficer")) && if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("securityofficer")) &&
(Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders))) (Order.GetPrefab("reportintruders") is Order reportIntruders && ActiveOrders.Any(o => o.First.Prefab == reportIntruders)))
{ {
shortcutNodes.Add( shortcutNodes.Add(
@@ -2072,7 +2009,7 @@ namespace Barotrauma
// If player is not a mechanic AND a breach has been reported // If player is not a mechanic AND a breach has been reported
// --> Create shorcut node for Fix Leaks order // --> Create shorcut node for Fix Leaks order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("mechanic")) && if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("mechanic")) &&
(Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach))) (Order.GetPrefab("reportbreach") is Order reportBreach && ActiveOrders.Any(o => o.First.Prefab == reportBreach)))
{ {
shortcutNodes.Add( shortcutNodes.Add(
@@ -2081,7 +2018,7 @@ namespace Barotrauma
// If player is not an engineer AND broken devices have been reported // If player is not an engineer AND broken devices have been reported
// --> Create shortcut node for Repair Damaged Systems order // --> Create shortcut node for Repair Damaged Systems order
if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info.Job.Prefab != JobPrefab.Get("engineer")) && if (shortcutNodes.Count < maxShorcutNodeCount && (Character.Controlled == null || Character.Controlled.Info?.Job?.Prefab != JobPrefab.Get("engineer")) &&
(Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices))) (Order.GetPrefab("reportbrokendevices") is Order reportBrokenDevices && ActiveOrders.Any(o => o.First.Prefab == reportBrokenDevices)))
{ {
shortcutNodes.Add( shortcutNodes.Add(
@@ -2741,11 +2678,11 @@ namespace Barotrauma
}; };
} }
#if DEBUG
bool canHear = true;
#else
bool canHear = character.CanHearCharacter(Character.Controlled); bool canHear = character.CanHearCharacter(Character.Controlled);
#if DEBUG
if (Character.Controlled == null) { canHear = true; }
#endif #endif
if (!canHear) if (!canHear)
{ {
node.CanBeFocused = orderIcon.CanBeFocused = false; node.CanBeFocused = orderIcon.CanBeFocused = false;
@@ -2904,18 +2841,29 @@ namespace Barotrauma
return sub; return sub;
} }
private void SetCharacterTooltip(GUIComponent component, Character character)
{
if (component == null) { return; }
var tooltip = character?.Info != null ? characterContext.Info.DisplayName : null;
if (string.IsNullOrWhiteSpace(tooltip)) { component.ToolTip = tooltip; return; }
if (character.Info?.Job != null && !string.IsNullOrWhiteSpace(characterContext.Info.Job.Name)) { tooltip += " (" + characterContext.Info.Job.Name + ")"; }
component.ToolTip = tooltip;
}
#region Crew Member Assignment Logic #region Crew Member Assignment Logic
private Character GetCharacterForQuickAssignment(Order order) private Character GetCharacterForQuickAssignment(Order order)
{ {
var controllingCharacter = Character.Controlled != null;
#if !DEBUG #if !DEBUG
if (Character.Controlled == null) { return null; } if (!controllingCharacter) { return null; }
#endif #endif
if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter)) if (order.Category == OrderCategory.Operate && HumanAIController.IsItemOperatedByAnother(null, order.TargetItemComponent, out Character operatingCharacter) &&
(!controllingCharacter || operatingCharacter.CanHearCharacter(Character.Controlled)))
{ {
return operatingCharacter; return operatingCharacter;
} }
return GetCharactersSortedForOrder(order, false).FirstOrDefault() ?? Character.Controlled; return GetCharactersSortedForOrder(order, false).FirstOrDefault(c => !controllingCharacter || c.CanHearCharacter(Character.Controlled)) ?? Character.Controlled;
} }
private List<Character> GetCharactersForManualAssignment(Order order) private List<Character> GetCharactersForManualAssignment(Order order)
@@ -2934,11 +2882,17 @@ namespace Barotrauma
private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf) private IEnumerable<Character> GetCharactersSortedForOrder(Order order, bool includeSelf)
{ {
return characters.FindAll(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID)) return characters.FindAll(c => Character.Controlled == null || ((includeSelf || c != Character.Controlled) && c.TeamID == Character.Controlled.TeamID))
// 1. Prioritize those who are already ordered to operate the item target of the new 'operate' order
.OrderByDescending(c => c.CurrentOrder != null && order.Category == OrderCategory.Operate && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity) .OrderByDescending(c => c.CurrentOrder != null && order.Category == OrderCategory.Operate && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity)
// 2. Prioritize those who are currently dismissed
.ThenByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier) .ThenByDescending(c => c.CurrentOrder == null || c.CurrentOrder.Identifier == dismissedOrderPrefab.Identifier)
// 3. Prioritize those who are not currently assigned with the same type of order (for example, when giving a 'Fix Leak' order, prioritize those who have a different order)
.ThenBy(c => c.CurrentOrder != null && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity) .ThenBy(c => c.CurrentOrder != null && c.CurrentOrder.Identifier == order.Identifier && c.CurrentOrder.TargetEntity == order.TargetEntity)
// 4. Prioritize those with the appropriate job for the order
.ThenByDescending(c => order.HasAppropriateJob(c)) .ThenByDescending(c => order.HasAppropriateJob(c))
// 5. Prioritize those with the lowest "weight" of the current order
.ThenBy(c => c.CurrentOrder?.Weight) .ThenBy(c => c.CurrentOrder?.Weight)
// 6. Prioritize those with the best skill for the order
.ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill)); .ThenByDescending(c => c.GetSkillLevel(order.AppropriateSkill));
} }
@@ -3013,40 +2967,7 @@ namespace Barotrauma
public void InitSinglePlayerRound() public void InitSinglePlayerRound()
{ {
crewList.ClearChildren(); crewList.ClearChildren();
characters.Clear(); InitRound();
WayPoint[] waypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub);
for (int i = 0; i < waypoints.Length; i++)
{
Character character;
character = Character.Create(characterInfos[i], waypoints[i].WorldPosition, characterInfos[i].Name);
if (character.Info != null)
{
if (!character.Info.StartItemsGiven && character.Info.InventoryData != null)
{
DebugConsole.ThrowError($"Error when initializing a single player round: character \"{character.Name}\" has not been given their initial items but has saved inventory data. Using the saved inventory data instead of giving the character new items.");
}
if (character.Info.InventoryData != null)
{
character.Info.SpawnInventoryItems(character.Inventory, character.Info.InventoryData);
}
else if (!character.Info.StartItemsGiven)
{
character.GiveJobItems(waypoints[i]);
}
character.Info.StartItemsGiven = true;
}
AddCharacter(character);
if (i == 0)
{
Character.Controlled = character;
}
}
conversationTimer = Rand.Range(5.0f, 10.0f);
} }
public void EndRound() public void EndRound()
@@ -3072,10 +2993,9 @@ namespace Barotrauma
foreach (CharacterInfo ci in characterInfos) foreach (CharacterInfo ci in characterInfos)
{ {
var infoElement = ci.Save(element); var infoElement = ci.Save(element);
if (ci.InventoryData != null) if (ci.InventoryData != null) { infoElement.Add(ci.InventoryData); }
{ if (ci.HealthData != null) { infoElement.Add(ci.HealthData); }
infoElement.Add(ci.InventoryData); if (ci.LastControlled) { infoElement.Add(new XAttribute("lastcontrolled", true)); }
}
} }
parentElement.Add(element); parentElement.Add(element);
} }
@@ -1,10 +1,65 @@
using Microsoft.Xna.Framework; using Barotrauma.Extensions;
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq;
using System.Threading.Tasks;
namespace Barotrauma namespace Barotrauma
{ {
abstract partial class CampaignMode : GameMode abstract partial class CampaignMode : GameMode
{ {
protected bool crewDead;
protected Color overlayColor;
protected string overlayText, overlayTextBottom;
protected Color overlayTextColor;
protected Sprite overlaySprite;
protected GUIButton endRoundButton;
public GUIButton EndRoundButton => endRoundButton;
protected GUIFrame campaignUIContainer;
public CampaignUI CampaignUI;
public bool ForceMapUI
{
get;
protected set;
}
public override bool Paused
{
get { return ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"); }
}
private bool showCampaignUI;
private bool wasChatBoxOpen;
public bool ShowCampaignUI
{
get { return showCampaignUI; }
set
{
if (value == showCampaignUI) { return; }
var chatBox = CrewManager?.ChatBox ?? GameMain.Client?.ChatBox;
if (value)
{
if (chatBox != null)
{
wasChatBoxOpen = chatBox.ToggleOpen;
chatBox.ToggleOpen = false;
}
}
else if (chatBox != null)
{
chatBox.ToggleOpen = wasChatBoxOpen;
}
showCampaignUI = value;
}
}
public override void ShowStartMessage() public override void ShowStartMessage()
{ {
if (Mission == null) return; if (Mission == null) return;
@@ -15,5 +70,216 @@ namespace Barotrauma
UserData = "missionstartmessage" UserData = "missionstartmessage"
}; };
} }
/// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary>
public bool AllowedToEndRound()
{
//allow ending the round if the client has permissions, is the owner, the only client in the server
//or if no-one has management permissions
if (GameMain.Client == null) { return true; }
return
GameMain.Client.HasPermission(ClientPermissions.ManageRound) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
GameMain.Client.ConnectedClients.None(c =>
c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageRound) || c.HasPermission(ClientPermissions.ManageCampaign)));
}
/// <summary>
/// There is a server-side implementation of the method in <see cref="MultiPlayerCampaign"/>
/// </summary>
public bool AllowedToManageCampaign()
{
//allow ending the round if the client has permissions, is the owner, the only client in the server,
//or if no-one has management permissions
if (GameMain.Client == null) { return true; }
return
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
GameMain.Client.ConnectedClients.None(c =>
c.InGame && (c.IsOwner || c.HasPermission(ClientPermissions.ManageCampaign)));
}
public override void Draw(SpriteBatch spriteBatch)
{
if (overlayColor.A > 0)
{
if (overlaySprite != null)
{
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.Black * (overlayColor.A / 255.0f), isFilled: true);
float scale = Math.Max(GameMain.GraphicsWidth / overlaySprite.size.X, GameMain.GraphicsHeight / overlaySprite.size.Y);
overlaySprite.Draw(spriteBatch, new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2, overlayColor, overlaySprite.size / 2, scale: scale);
}
else
{
GUI.DrawRectangle(spriteBatch, new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), overlayColor, isFilled: true);
}
if (!string.IsNullOrEmpty(overlayText) && overlayTextColor.A > 0)
{
var backgroundSprite = GUI.Style.GetComponentStyle("CommandBackground").GetDefaultSprite();
Vector2 centerPos = new Vector2(GameMain.GraphicsWidth, GameMain.GraphicsHeight) / 2;
backgroundSprite.Draw(spriteBatch,
centerPos,
Color.White * (overlayTextColor.A / 255.0f),
origin: backgroundSprite.size / 2,
rotate: 0.0f,
scale: new Vector2(1.5f, 0.7f) * (GameMain.GraphicsWidth / 3 / backgroundSprite.size.X));
string wrappedText = ToolBox.WrapText(overlayText, GameMain.GraphicsWidth / 3, GUI.Font);
Vector2 textSize = GUI.Font.MeasureString(wrappedText);
Vector2 textPos = centerPos - textSize / 2;
GUI.DrawString(spriteBatch, textPos + Vector2.One, wrappedText, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, textPos, wrappedText, overlayTextColor);
if (!string.IsNullOrEmpty(overlayTextBottom))
{
Vector2 bottomTextPos = centerPos + new Vector2(0.0f, textSize.Y + 30 * GUI.Scale) - GUI.Font.MeasureString(overlayTextBottom) / 2;
GUI.DrawString(spriteBatch, bottomTextPos + Vector2.One, overlayTextBottom, Color.Black * (overlayTextColor.A / 255.0f));
GUI.DrawString(spriteBatch, bottomTextPos, overlayTextBottom, overlayTextColor);
}
}
}
if (GUI.DisableHUD || GUI.DisableUpperHUD || ForceMapUI || CoroutineManager.IsCoroutineRunning("LevelTransition"))
{
endRoundButton.Visible = false;
return;
}
if (Submarine.MainSub == null) { return; }
endRoundButton.Visible = false;
var availableTransition = GetAvailableTransition(out _, out Submarine leavingSub);
string buttonText = "";
switch (availableTransition)
{
case TransitionType.ProgressToNextLocation:
case TransitionType.ProgressToNextEmptyLocation:
if (Level.Loaded.EndOutpost == null || !Level.Loaded.EndOutpost.DockedTo.Contains(leavingSub))
{
buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.EndLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
break;
case TransitionType.LeaveLocation:
// not sure why this can happen at an outpost but it apparently can in multiplayer
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
break;
case TransitionType.ReturnToPreviousLocation:
case TransitionType.ReturnToPreviousEmptyLocation:
if (Level.Loaded.StartOutpost == null || !Level.Loaded.StartOutpost.DockedTo.Contains(leavingSub))
{
buttonText = TextManager.GetWithVariable("EnterLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
break;
case TransitionType.None:
default:
if (Level.Loaded.Type == LevelData.LevelType.Outpost &&
(Character.Controlled?.Submarine?.Info.Type == SubmarineType.Player || (Character.Controlled?.CurrentHull?.OutpostModuleTags?.Contains("airlock") ?? false)))
{
buttonText = TextManager.GetWithVariable("LeaveLocation", "[locationname]", Level.Loaded.StartLocation?.Name ?? "[ERROR]");
endRoundButton.Visible = !ForceMapUI && !ShowCampaignUI;
}
else
{
endRoundButton.Visible = false;
}
break;
}
if (endRoundButton.Visible)
{
endRoundButton.Text = ToolBox.LimitString(buttonText, endRoundButton.Font, endRoundButton.Rect.Width - 5);
if (endRoundButton.Text != buttonText)
{
endRoundButton.ToolTip = buttonText;
}
endRoundButton.Enabled = AllowedToEndRound();
}
endRoundButton.DrawManually(spriteBatch);
}
public Task SelectSummaryScreen(RoundSummary roundSummary, LevelData newLevel, bool mirror, Action action)
{
var roundSummaryScreen = RoundSummaryScreen.Select(overlaySprite, roundSummary);
GUI.ClearCursorWait();
var loadTask = Task.Run(async () =>
{
await Task.Yield();
Rand.ThreadId = System.Threading.Thread.CurrentThread.ManagedThreadId;
GameMain.GameSession.StartRound(newLevel, mirrorLevel: mirror);
Rand.ThreadId = 0;
});
TaskPool.Add("AsyncCampaignStartRound", loadTask, (t) =>
{
overlayColor = Color.Transparent;
action?.Invoke();
});
return loadTask;
}
partial void NPCInteractProjSpecific(Character npc, Character interactor)
{
if (npc == null || interactor == null) { return; }
switch (npc.CampaignInteractionType)
{
case InteractionType.None:
case InteractionType.Talk:
return;
case InteractionType.Upgrade when !UpgradeManager.CanUpgradeSub():
UpgradeManager.CreateUpgradeErrorMessage(TextManager.Get("Dialog.CantUpgrade"), IsSinglePlayer, npc);
return;
case InteractionType.Crew when GameMain.NetworkMember != null:
CampaignUI.CrewManagement.SendCrewState(false);
goto default;
default:
ShowCampaignUI = true;
CampaignUI.SelectTab(npc.CampaignInteractionType);
CampaignUI.UpgradeStore?.RefreshAll();
break;
}
}
public override void AddToGUIUpdateList()
{
if (ShowCampaignUI || ForceMapUI)
{
campaignUIContainer?.AddToGUIUpdateList();
if (CampaignUI?.UpgradeStore?.HoveredItem != null)
{
if (CampaignUI.SelectedTab != InteractionType.Upgrade) { return; }
CampaignUI?.UpgradeStore?.ItemInfoFrame.AddToGUIUpdateList(order: 1);
}
}
base.AddToGUIUpdateList();
CrewManager.AddToGUIUpdateList();
endRoundButton.AddToGUIUpdateList();
}
public override void Update(float deltaTime)
{
base.Update(deltaTime);
if (PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
}
if (ShowCampaignUI || ForceMapUI)
{
CampaignUI?.Update(deltaTime);
}
}
} }
} }
@@ -0,0 +1,114 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace Barotrauma
{
internal partial class CampaignMetadata
{
private const int MaxDrawnElements = 12;
public void DebugDraw(SpriteBatch spriteBatch, Vector2 pos, int debugDrawMetadataOffset, string[] ignoredMetadataInfo)
{
var campaignData = data;
foreach (string ignored in ignoredMetadataInfo)
{
if (!string.IsNullOrWhiteSpace(ignored))
{
campaignData = campaignData.Where(pair => !pair.Key.StartsWith(ignored)).ToDictionary(i => i.Key, i => i.Value);
}
}
int offset = 0;;
if (campaignData.Count > 0)
{
offset = debugDrawMetadataOffset % campaignData.Count;
if (offset < 0) { offset += campaignData.Count; }
}
var text = "Campaign metadata:\n";
int max = 0;
for (int i = offset; i < campaignData.Count + offset; i++)
{
int index = i;
if (index >= campaignData.Count) { index -= campaignData.Count; }
var (key, value) = campaignData.ElementAt(index);
if (max < MaxDrawnElements)
{
text += $"{key.ColorizeObject()}: {value.ColorizeObject()}\n";
max++;
}
else
{
text += "Use arrow keys to scroll";
break;
}
}
text = text.TrimEnd('\n');
List<RichTextData> richTextDatas = RichTextData.GetRichTextData(text, out text) ?? new List<RichTextData>();
Vector2 size = GUI.SmallFont.MeasureString(text);
Vector2 infoPos = new Vector2(GameMain.GraphicsWidth - size.X - 16, pos.Y + 8);
Rectangle infoRect = new Rectangle(infoPos.ToPoint(), size.ToPoint());
infoRect.Inflate(8, 8);
GUI.DrawRectangle(spriteBatch, infoRect, Color.Black * 0.8f, isFilled: true);
GUI.DrawRectangle(spriteBatch, infoRect, Color.White * 0.8f);
if (richTextDatas.Any())
{
GUI.DrawStringWithColors(spriteBatch, infoPos, text, Color.White, richTextDatas, font: GUI.SmallFont);
}
else
{
GUI.DrawString(spriteBatch, infoPos, text, Color.White, font: GUI.SmallFont);
}
float y = infoRect.Bottom + 16;
if (Campaign.Factions != null)
{
const string factionHeader = "Reputations";
Vector2 factionHeaderSize = GUI.SubHeadingFont.MeasureString(factionHeader);
Vector2 factionPos = new Vector2(GameMain.GraphicsWidth - (264 / 2) - factionHeaderSize.X / 2, y);
GUI.DrawString(spriteBatch, factionPos, factionHeader, Color.White, font: GUI.SubHeadingFont);
y += factionHeaderSize.Y + 8;
foreach (Faction faction in Campaign.Factions)
{
string name = faction.Prefab.Name;
Vector2 nameSize = GUI.SmallFont.MeasureString(name);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUI.SmallFont);
y += nameSize.Y + 5;
Color color = ToolBox.GradientLerp(faction.Reputation.NormalizedValue, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(faction.Reputation.NormalizedValue * 255), 10), color, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White);
y += 15;
}
}
Location location = Campaign.Map?.CurrentLocation;
if (location?.Reputation != null)
{
string name = Campaign.Map?.CurrentLocation.Name;
Vector2 nameSize = GUI.SmallFont.MeasureString(name);
GUI.DrawString(spriteBatch, new Vector2(GameMain.GraphicsWidth - 264, y), name, Color.White, font: GUI.SmallFont);
y += nameSize.Y + 5;
float normalizedReputation = MathUtils.InverseLerp(location.Reputation.MinReputation, location.Reputation.MaxReputation, location.Reputation.Value);
Color color = ToolBox.GradientLerp(normalizedReputation, Color.Red, Color.Yellow, Color.LightGreen);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, (int)(normalizedReputation * 255), 10), color, isFilled: true);
GUI.DrawRectangle(spriteBatch, new Rectangle(GameMain.GraphicsWidth - 264, (int) y, 256, 10), Color.White);
}
richTextDatas.Clear();
}
}
}
@@ -1,7 +1,9 @@
using Barotrauma.Networking; using Barotrauma.Extensions;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -11,14 +13,30 @@ namespace Barotrauma
{ {
public bool SuppressStateSending = false; public bool SuppressStateSending = false;
private UInt16 startWatchmanID, endWatchmanID; private UInt16 pendingSaveID = 1;
public UInt16 PendingSaveID
{
get
{
return pendingSaveID;
}
set
{
pendingSaveID = value;
//pending save ID 0 means "no save received yet"
//save IDs are always above 0, so we should never be waiting for 0
if (pendingSaveID == 0) { pendingSaveID++; }
}
}
public static void StartCampaignSetup(IEnumerable<string> saveFiles) public static void StartCampaignSetup(IEnumerable<string> saveFiles)
{ {
var parent = GameMain.NetLobbyScreen.CampaignSetupFrame; var parent = GameMain.NetLobbyScreen.CampaignSetupFrame;
parent.ClearChildren(); parent.ClearChildren();
parent.Visible = true; parent.Visible = true;
GameMain.NetLobbyScreen.HighlightMode(2); GameMain.NetLobbyScreen.HighlightMode(
GameMain.NetLobbyScreen.ModeList.Content.GetChildIndex(GameMain.NetLobbyScreen.ModeList.Content.GetChildByUserData(GameModePreset.MultiPlayerCampaign)));
var layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center)) var layout = new GUILayoutGroup(new RectTransform(Vector2.One, parent.RectTransform, Anchor.Center))
{ {
@@ -38,7 +56,7 @@ namespace Barotrauma
var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var newCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null); var loadCampaignContainer = new GUIFrame(new RectTransform(new Vector2(0.95f, 0.95f), campaignContainer.RectTransform, Anchor.Center), style: null);
var campaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles); GameMain.NetLobbyScreen.CampaignSetupUI = new CampaignSetupUI(true, newCampaignContainer, loadCampaignContainer, null, saveFiles);
var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform), var newCampaignButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), buttonContainer.RectTransform),
TextManager.Get("NewCampaign"), style: "GUITabButton") TextManager.Get("NewCampaign"), style: "GUITabButton")
@@ -69,78 +87,440 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock); GUITextBlock.AutoScaleAndNormalize(newCampaignButton.TextBlock, loadCampaignButton.TextBlock);
campaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign; GameMain.NetLobbyScreen.CampaignSetupUI.StartNewGame = GameMain.Client.SetupNewCampaign;
campaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign; GameMain.NetLobbyScreen.CampaignSetupUI.LoadGame = GameMain.Client.SetupLoadCampaign;
}
partial void InitProjSpecific()
{
var buttonContainer = new GUILayoutGroup(HUDLayoutSettings.ToRectTransform(HUDLayoutSettings.ButtonAreaTop, GUICanvas.Instance),
isHorizontal: true, childAnchor: Anchor.CenterRight)
{
CanBeFocused = false
};
int buttonHeight = (int)(GUI.Scale * 40);
int buttonWidth = GUI.IntScale(200);
endRoundButton = new GUIButton(HUDLayoutSettings.ToRectTransform(new Rectangle((GameMain.GraphicsWidth / 2) - (buttonWidth / 2), HUDLayoutSettings.ButtonAreaTop.Center.Y - (buttonHeight / 2), buttonWidth, buttonHeight), GUICanvas.Instance),
TextManager.Get("EndRound"), textAlignment: Alignment.Center, style: "EndRoundButton")
{
Pulse = true,
TextBlock =
{
Shadow = true,
AutoScaleHorizontal = true
},
OnClicked = (btn, userdata) =>
{
var availableTransition = GetAvailableTransition(out _, out _);
if (Character.Controlled != null &&
availableTransition == TransitionType.ReturnToPreviousLocation &&
Character.Controlled?.Submarine == Level.Loaded?.StartOutpost)
{
GameMain.Client.RequestStartRound();
}
else if (Character.Controlled != null &&
availableTransition == TransitionType.ProgressToNextLocation &&
Character.Controlled?.Submarine == Level.Loaded?.EndOutpost)
{
GameMain.Client.RequestStartRound();
}
else
{
ShowCampaignUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
}
return true;
}
};
buttonContainer.Recalculate();
}
private void InitCampaignUI()
{
campaignUIContainer = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas, Anchor.Center), style: "InnerGlow", color: Color.Black);
CampaignUI = new CampaignUI(this, campaignUIContainer)
{
StartRound = () =>
{
GameMain.Client.RequestStartRound();
}
};
}
public override void Start()
{
base.Start();
CoroutineManager.StartCoroutine(DoInitialCameraTransition(), "MultiplayerCampaign.DoInitialCameraTransition");
}
protected override void LoadInitialLevel()
{
//clients should never call this
throw new InvalidOperationException("");
}
private IEnumerable<object> DoInitialCameraTransition()
{
while (GameMain.Instance.LoadingScreenOpen)
{
yield return CoroutineStatus.Running;
}
if (GameMain.Client.LateCampaignJoin)
{
GameMain.Client.LateCampaignJoin = false;
yield return CoroutineStatus.Success;
}
Character prevControlled = Character.Controlled;
if (prevControlled?.AIController != null)
{
prevControlled.AIController.Enabled = false;
}
GUI.DisableHUD = true;
if (IsFirstRound)
{
Character.Controlled = null;
if (prevControlled != null)
{
prevControlled.ClearInputs();
}
overlayColor = Color.LightGray;
overlaySprite = Map.CurrentLocation.Type.GetPortrait(Map.CurrentLocation.PortraitId);
overlayTextColor = Color.Transparent;
overlayText = TextManager.GetWithVariables("campaignstart",
new string[] { "xxxx", "yyyy" },
new string[] { Map.CurrentLocation.Name, TextManager.Get("submarineclass." + Submarine.MainSub.Info.SubmarineClass) });
float fadeInDuration = 1.0f;
float textDuration = 10.0f;
float timer = 0.0f;
while (timer < textDuration)
{
// Try to grab the controlled here to prevent inputs, assigned late on multiplayer
if (Character.Controlled != null)
{
prevControlled = Character.Controlled;
Character.Controlled = null;
prevControlled.ClearInputs();
}
overlayTextColor = Color.Lerp(Color.Transparent, Color.White, (timer - 1.0f) / fadeInDuration);
timer = Math.Min(timer + CoroutineManager.DeltaTime, textDuration);
yield return CoroutineStatus.Running;
}
var transition = new CameraTransition(prevControlled, GameMain.GameScreen.Cam,
null, null,
fadeOut: false,
duration: 5,
startZoom: 1.5f, endZoom: 1.0f)
{
AllowInterrupt = true,
RemoveControlFromCharacter = false
};
fadeInDuration = 1.0f;
timer = 0.0f;
overlayTextColor = Color.Transparent;
overlayText = "";
while (timer < fadeInDuration)
{
overlayColor = Color.Lerp(Color.LightGray, Color.Transparent, timer / fadeInDuration);
timer += CoroutineManager.DeltaTime;
yield return CoroutineStatus.Running;
}
overlayColor = Color.Transparent;
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
if (prevControlled != null)
{
Character.Controlled = prevControlled;
}
}
else
{
var transition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam,
null, null,
fadeOut: false,
duration: 5,
startZoom: 0.5f, endZoom: 1.0f)
{
AllowInterrupt = true,
RemoveControlFromCharacter = true
};
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
}
if (prevControlled != null)
{
prevControlled.SelectedConstruction = null;
if (prevControlled.AIController != null)
{
prevControlled.AIController.Enabled = true;
}
}
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
}
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null)
{
yield return CoroutineStatus.Success;
}
private IEnumerable<object> DoLevelTransition()
{
SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead";
SoundPlayer.OverrideMusicDuration = 18.0f;
Level prevLevel = Level.Loaded;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
crewDead = false;
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
if (continueButton != null)
{
continueButton.Visible = false;
}
Character.Controlled = null;
yield return new WaitForSeconds(0.1f);
GameMain.Client.EndCinematic?.Stop();
var endTransition = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null,
Alignment.Center,
fadeOut: false,
duration: EndTransitionDuration);
GameMain.Client.EndCinematic = endTransition;
Location portraitLocation = Map?.SelectedLocation ?? Map?.CurrentLocation ?? Level.Loaded?.StartLocation;
if (portraitLocation != null)
{
overlaySprite = portraitLocation.Type.GetPortrait(portraitLocation.PortraitId);
}
float fadeOutDuration = endTransition.Duration;
float t = 0.0f;
while (t < fadeOutDuration || endTransition.Running)
{
t += CoroutineManager.UnscaledDeltaTime;
overlayColor = Color.Lerp(Color.Transparent, Color.White, t / fadeOutDuration);
yield return CoroutineStatus.Running;
}
overlayColor = Color.White;
yield return CoroutineStatus.Running;
//--------------------------------------
//wait for the new level to be loaded
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30);
while (Level.Loaded == prevLevel || Level.Loaded == null)
{
if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; }
yield return CoroutineStatus.Running;
}
endTransition.Stop();
overlayColor = Color.Transparent;
if (DateTime.Now > timeOut) { GameMain.NetLobbyScreen.Select(); }
if (!(Screen.Selected is RoundSummaryScreen))
{
if (continueButton != null)
{
continueButton.Visible = true;
}
}
yield return CoroutineStatus.Success;
} }
public override void Update(float deltaTime) public override void Update(float deltaTime)
{ {
if (CoroutineManager.IsCoroutineRunning("LevelTransition") || Level.Loaded == null) { return; }
if (ShowCampaignUI || ForceMapUI)
{
if (CampaignUI == null) { InitCampaignUI(); }
Character.DisableControls = true;
}
base.Update(deltaTime); base.Update(deltaTime);
if (startWatchmanID > 0 && startWatchman == null) if (PlayerInput.RightButtonClicked() ||
PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape))
{ {
startWatchman = Entity.FindEntityByID(startWatchmanID) as Character; ShowCampaignUI = false;
if (startWatchman != null) { InitializeWatchman(startWatchman); } if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary &&
} roundSummary.ContinueButton != null &&
if (endWatchmanID > 0 && endWatchman == null) roundSummary.ContinueButton.Visible)
{ {
endWatchman = Entity.FindEntityByID(endWatchmanID) as Character; GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
if (endWatchman != null) { InitializeWatchman(endWatchman); }
} }
} }
protected override void WatchmanInteract(Character watchman, Character interactor) if (!GUI.DisableHUD && !GUI.DisableUpperHUD)
{ {
if ((watchman.Submarine == Level.Loaded.StartOutpost && !Submarine.MainSub.AtStartPosition) || endRoundButton.UpdateManually(deltaTime);
(watchman.Submarine == Level.Loaded.EndOutpost && !Submarine.MainSub.AtEndPosition)) if (CoroutineManager.IsCoroutineRunning("LevelTransition") || ForceMapUI) { return; }
{
return;
} }
if (GUIMessageBox.MessageBoxes.Any(mbox => mbox.UserData as string == "watchmanprompt")) if (Level.Loaded.Type == LevelData.LevelType.Outpost)
{ {
return; if (wasDocked)
{
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
bool isDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
if (!isDocked)
{
//undocked from outpost, need to choose a destination
ForceMapUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
}
}
else
{
//wasn't initially docked (sub doesn't have a docking port?)
// -> choose a destination when the sub is far enough from the start outpost
if (!Submarine.MainSub.AtStartPosition)
{
ForceMapUI = true;
if (CampaignUI == null) { InitCampaignUI(); }
CampaignUI.SelectTab(InteractionType.Map);
}
} }
if (GameMain.Client != null && interactor == Character.Controlled) if (CampaignUI == null) { InitCampaignUI(); }
}
}
public override void End(TransitionType transitionType = TransitionType.None)
{ {
var msgBox = new GUIMessageBox("", TextManager.GetWithVariable("CampaignEnterOutpostPrompt", "[locationname]", base.End(transitionType);
Submarine.MainSub.AtStartPosition ? Map.CurrentLocation.Name : Map.SelectedLocation.Name), ForceMapUI = ShowCampaignUI = false;
new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) UpgradeManager.CanUpgrade = true;
// remove all event dialogue boxes
GUIMessageBox.MessageBoxes.ForEachMod(mb =>
{ {
UserData = "watchmanprompt" if (mb is GUIMessageBox msgBox)
{
if (mb.UserData is Pair<string, ushort> pair && pair.First.Equals("conversationaction", StringComparison.OrdinalIgnoreCase))
{
msgBox.Close();
}
}
});
if (transitionType == TransitionType.End)
{
EndCampaign();
}
else
{
IsFirstRound = false;
CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition");
}
}
protected override void EndCampaignProjSpecific()
{
if (GUIMessageBox.VisibleBox?.UserData is RoundSummary roundSummary)
{
GUIMessageBox.MessageBoxes.Remove(GUIMessageBox.VisibleBox);
}
CoroutineManager.StartCoroutine(DoEndCampaignCameraTransition(), "DoEndCampaignCameraTransition");
GameMain.CampaignEndScreen.OnFinished = () =>
{
GameMain.NetLobbyScreen.Select();
if (GameMain.NetLobbyScreen.ContinueCampaignButton != null) { GameMain.NetLobbyScreen.ContinueCampaignButton.Enabled = false; }
if (GameMain.NetLobbyScreen.QuitCampaignButton != null) { GameMain.NetLobbyScreen.QuitCampaignButton.Enabled = false; }
}; };
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
GameMain.Client.RequestRoundEnd();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
} }
private IEnumerable<object> DoEndCampaignCameraTransition()
{
Character controlled = Character.Controlled;
if (controlled != null)
{
controlled.AIController.Enabled = false;
}
GUI.DisableHUD = true;
ISpatialEntity endObject = Level.Loaded.LevelObjectManager.GetAllObjects().FirstOrDefault(obj => obj.Prefab.SpawnPos == LevelObjectPrefab.SpawnPosType.LevelEnd);
var transition = new CameraTransition(endObject ?? Submarine.MainSub, GameMain.GameScreen.Cam,
null, Alignment.Center,
fadeOut: true,
duration: 10,
startZoom: null, endZoom: 0.2f);
while (transition.Running)
{
yield return CoroutineStatus.Running;
}
GameMain.CampaignEndScreen.Select();
GUI.DisableHUD = false;
yield return CoroutineStatus.Success;
} }
public void ClientWrite(IWriteMessage msg) public void ClientWrite(IWriteMessage msg)
{ {
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.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex); msg.Write(map.SelectedLocationIndex == -1 ? UInt16.MaxValue : (UInt16)map.SelectedLocationIndex);
msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex); msg.Write(map.SelectedMissionIndex == -1 ? byte.MaxValue : (byte)map.SelectedMissionIndex);
msg.Write(PurchasedHullRepairs); msg.Write(PurchasedHullRepairs);
msg.Write(PurchasedItemRepairs); msg.Write(PurchasedItemRepairs);
msg.Write(PurchasedLostShuttles); msg.Write(PurchasedLostShuttles);
msg.Write((UInt16)CargoManager.ItemsInBuyCrate.Count);
foreach (PurchasedItem pi in CargoManager.ItemsInBuyCrate)
{
msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, 100);
}
msg.Write((UInt16)CargoManager.PurchasedItems.Count); msg.Write((UInt16)CargoManager.PurchasedItems.Count);
foreach (PurchasedItem pi in CargoManager.PurchasedItems) foreach (PurchasedItem pi in CargoManager.PurchasedItems)
{ {
msg.Write(pi.ItemPrefab.Identifier); msg.Write(pi.ItemPrefab.Identifier);
msg.WriteRangedInteger(pi.Quantity, 0, 100); msg.WriteRangedInteger(pi.Quantity, 0, 100);
} }
msg.Write((UInt16)CargoManager.SoldItems.Count);
foreach (SoldItem si in CargoManager.SoldItems)
{
msg.Write(si.ItemPrefab.Identifier);
msg.Write((UInt16)si.ID);
msg.Write(si.Removed);
msg.Write(si.SellerID);
}
msg.Write((ushort)UpgradeManager.PurchasedUpgrades.Count);
foreach (var (prefab, category, level) in UpgradeManager.PurchasedUpgrades)
{
msg.Write(prefab.Identifier);
msg.Write(category.Identifier);
msg.Write((byte)level);
}
} }
//static because we may need to instantiate the campaign if it hasn't been done yet //static because we may need to instantiate the campaign if it hasn't been done yet
public static void ClientRead(IReadMessage msg) public static void ClientRead(IReadMessage msg)
{ {
bool isFirstRound = msg.ReadBoolean();
byte campaignID = msg.ReadByte(); byte campaignID = msg.ReadByte();
UInt16 updateID = msg.ReadUInt16(); UInt16 updateID = msg.ReadUInt16();
UInt16 saveID = msg.ReadUInt16(); UInt16 saveID = msg.ReadUInt16();
@@ -148,15 +528,47 @@ namespace Barotrauma
UInt16 currentLocIndex = msg.ReadUInt16(); UInt16 currentLocIndex = msg.ReadUInt16();
UInt16 selectedLocIndex = msg.ReadUInt16(); UInt16 selectedLocIndex = msg.ReadUInt16();
byte selectedMissionIndex = msg.ReadByte(); byte selectedMissionIndex = msg.ReadByte();
float? reputation = null;
if (msg.ReadBoolean()) { reputation = msg.ReadSingle(); }
UInt16 startWatchmanID = msg.ReadUInt16(); Dictionary<string, float> factionReps = new Dictionary<string, float>();
UInt16 endWatchmanID = msg.ReadUInt16(); byte factionsCount = msg.ReadByte();
for (int i = 0; i < factionsCount; i++)
{
factionReps.Add(msg.ReadString(), msg.ReadSingle());
}
bool forceMapUI = msg.ReadBoolean();
int money = msg.ReadInt32(); int money = msg.ReadInt32();
bool purchasedHullRepairs = msg.ReadBoolean(); bool purchasedHullRepairs = msg.ReadBoolean();
bool purchasedItemRepairs = msg.ReadBoolean(); bool purchasedItemRepairs = msg.ReadBoolean();
bool purchasedLostShuttles = msg.ReadBoolean(); bool purchasedLostShuttles = msg.ReadBoolean();
byte missionCount = msg.ReadByte();
List<Pair<string, byte>> availableMissions = new List<Pair<string, byte>>();
for (int i = 0; i < missionCount; i++)
{
string missionIdentifier = msg.ReadString();
byte connectionIndex = msg.ReadByte();
availableMissions.Add(new Pair<string, byte>(missionIdentifier, connectionIndex));
}
UInt16? storeBalance = null;
if (msg.ReadBoolean())
{
storeBalance = msg.ReadUInt16();
}
UInt16 buyCrateItemCount = msg.ReadUInt16();
List<PurchasedItem> buyCrateItems = new List<PurchasedItem>();
for (int i = 0; i < buyCrateItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
int itemQuantity = msg.ReadRangedInteger(0, CargoManager.MaxQuantity);
buyCrateItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
}
UInt16 purchasedItemCount = msg.ReadUInt16(); UInt16 purchasedItemCount = msg.ReadUInt16();
List<PurchasedItem> purchasedItems = new List<PurchasedItem>(); List<PurchasedItem> purchasedItems = new List<PurchasedItem>();
for (int i = 0; i < purchasedItemCount; i++) for (int i = 0; i < purchasedItemCount; i++)
@@ -166,6 +578,30 @@ namespace Barotrauma
purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity)); purchasedItems.Add(new PurchasedItem(ItemPrefab.Prefabs[itemPrefabIdentifier], itemQuantity));
} }
UInt16 soldItemCount = msg.ReadUInt16();
List<SoldItem> soldItems = new List<SoldItem>();
for (int i = 0; i < soldItemCount; i++)
{
string itemPrefabIdentifier = msg.ReadString();
UInt16 id = msg.ReadUInt16();
bool removed = msg.ReadBoolean();
byte sellerId = msg.ReadByte();
soldItems.Add(new SoldItem(ItemPrefab.Prefabs[itemPrefabIdentifier], id, removed, sellerId));
}
ushort pendingUpgradeCount = msg.ReadUInt16();
List<PurchasedUpgrade> pendingUpgrades = new List<PurchasedUpgrade>();
for (int i = 0; i < pendingUpgradeCount; i++)
{
string upgradeIdentifier = msg.ReadString();
UpgradePrefab prefab = UpgradePrefab.Find(upgradeIdentifier);
string categoryIdentifier = msg.ReadString();
UpgradeCategory category = UpgradeCategory.Find(categoryIdentifier);
int upgradeLevel = msg.ReadByte();
if (prefab == null || category == null) { continue; }
pendingUpgrades.Add(new PurchasedUpgrade(prefab, category, upgradeLevel));
}
bool hasCharacterData = msg.ReadBoolean(); bool hasCharacterData = msg.ReadBoolean();
CharacterInfo myCharacterInfo = null; CharacterInfo myCharacterInfo = null;
if (hasCharacterData) if (hasCharacterData)
@@ -173,58 +609,98 @@ namespace Barotrauma
myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg); myCharacterInfo = CharacterInfo.ClientRead(CharacterPrefab.HumanSpeciesName, msg);
} }
MultiPlayerCampaign campaign = GameMain.GameSession?.GameMode as MultiPlayerCampaign; if (!(GameMain.GameSession?.GameMode is MultiPlayerCampaign campaign) || campaignID != campaign.CampaignID)
if (campaign == null || campaignID != campaign.CampaignID)
{ {
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer);
GameMain.GameSession = new GameSession(null, savePath, GameMain.GameSession = new GameSession(null, savePath, GameModePreset.MultiPlayerCampaign, mapSeed);
GameModePreset.List.Find(g => g.Identifier == "multiplayercampaign")); campaign = (MultiPlayerCampaign)GameMain.GameSession.GameMode;
campaign = ((MultiPlayerCampaign)GameMain.GameSession.GameMode);
campaign.CampaignID = campaignID; campaign.CampaignID = campaignID;
campaign.GenerateMap(mapSeed);
GameMain.NetLobbyScreen.ToggleCampaignMode(true); GameMain.NetLobbyScreen.ToggleCampaignMode(true);
} }
//server has a newer save file //server has a newer save file
if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID)) if (NetIdUtils.IdMoreRecent(saveID, campaign.PendingSaveID))
{ {
/*//stop any active campaign save transfers, they're outdated now
List<FileReceiver.FileTransferIn> saveTransfers =
GameMain.Client.FileReceiver.ActiveTransfers.FindAll(t => t.FileType == FileTransferType.CampaignSave);
foreach (var transfer in saveTransfers)
{
GameMain.Client.FileReceiver.StopTransfer(transfer);
}
GameMain.Client.RequestFile(FileTransferType.CampaignSave, null, null);*/
campaign.PendingSaveID = saveID; campaign.PendingSaveID = saveID;
} }
if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID)) if (NetIdUtils.IdMoreRecent(updateID, campaign.lastUpdateID))
{ {
campaign.SuppressStateSending = true; campaign.SuppressStateSending = true;
campaign.IsFirstRound = isFirstRound;
//we need to have the latest save file to display location/mission/store //we need to have the latest save file to display location/mission/store
if (campaign.LastSaveID == saveID) if (campaign.LastSaveID == saveID)
{ {
campaign.ForceMapUI = forceMapUI;
UpgradeStore.WaitForServerUpdate = false;
campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex); campaign.Map.SetLocation(currentLocIndex == UInt16.MaxValue ? -1 : currentLocIndex);
campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex); campaign.Map.SelectLocation(selectedLocIndex == UInt16.MaxValue ? -1 : selectedLocIndex);
campaign.Map.SelectMission(selectedMissionIndex); campaign.Map.SelectMission(selectedMissionIndex);
campaign.CargoManager.SetItemsInBuyCrate(buyCrateItems);
campaign.CargoManager.SetPurchasedItems(purchasedItems); campaign.CargoManager.SetPurchasedItems(purchasedItems);
campaign.CargoManager.SetSoldItems(soldItems);
if (storeBalance.HasValue) { campaign.Map.CurrentLocation.StoreCurrentBalance = storeBalance.Value; }
campaign.UpgradeManager.SetPendingUpgrades(pendingUpgrades);
campaign.UpgradeManager.PurchasedUpgrades.Clear();
foreach (var (identifier, rep) in factionReps)
{
Faction faction = campaign.Factions.FirstOrDefault(f => f.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
if (faction?.Reputation != null)
{
faction.Reputation.Value = rep;
}
else
{
DebugConsole.ThrowError($"Received an update for a faction that doesn't exist \"{identifier}\".");
}
} }
campaign.startWatchmanID = startWatchmanID; if (reputation.HasValue)
campaign.endWatchmanID = endWatchmanID; {
campaign.Map.CurrentLocation.Reputation.Value = reputation.Value;
campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
}
foreach (var availableMission in availableMissions)
{
MissionPrefab missionPrefab = MissionPrefab.List.Find(mp => mp.Identifier == availableMission.First);
if (missionPrefab == null)
{
DebugConsole.ThrowError($"Error when receiving campaign data from the server: mission prefab \"{availableMission.First}\" not found.");
continue;
}
if (availableMission.Second < 0 || availableMission.Second >= campaign.Map.CurrentLocation.Connections.Count)
{
DebugConsole.ThrowError($"Error when receiving campaign data from the server: connection index for mission \"{availableMission.First}\" out of range (index: {availableMission.Second}, current location: {campaign.Map.CurrentLocation.Name}, connections: {campaign.Map.CurrentLocation.Connections.Count}).");
continue;
}
LocationConnection connection = campaign.Map.CurrentLocation.Connections[availableMission.Second];
campaign.Map.CurrentLocation.UnlockMission(missionPrefab, connection);
}
GameMain.NetLobbyScreen.ToggleCampaignMode(true);
}
bool shouldRefresh = campaign.Money != money ||
campaign.PurchasedHullRepairs != purchasedHullRepairs ||
campaign.PurchasedItemRepairs != purchasedItemRepairs ||
campaign.PurchasedLostShuttles != purchasedLostShuttles;
campaign.Money = money; campaign.Money = money;
campaign.PurchasedHullRepairs = purchasedHullRepairs; campaign.PurchasedHullRepairs = purchasedHullRepairs;
campaign.PurchasedItemRepairs = purchasedItemRepairs; campaign.PurchasedItemRepairs = purchasedItemRepairs;
campaign.PurchasedLostShuttles = purchasedLostShuttles; campaign.PurchasedLostShuttles = purchasedLostShuttles;
if (shouldRefresh)
{
campaign?.CampaignUI?.UpgradeStore?.RefreshAll();
}
if (myCharacterInfo != null) if (myCharacterInfo != null)
{ {
GameMain.Client.CharacterInfo = myCharacterInfo; GameMain.Client.CharacterInfo = myCharacterInfo;
@@ -240,9 +716,68 @@ namespace Barotrauma
} }
} }
public void ClientReadCrew(IReadMessage msg)
{
ushort availableHireLength = msg.ReadUInt16();
List<CharacterInfo> availableHires = new List<CharacterInfo>();
for (int i = 0; i < availableHireLength; i++)
{
CharacterInfo hire = CharacterInfo.ClientRead("human", msg);
hire.Salary = msg.ReadInt32();
availableHires.Add(hire);
}
ushort pendingHireLength = msg.ReadUInt16();
List<int> pendingHires = new List<int>();
for (int i = 0; i < pendingHireLength; i++)
{
pendingHires.Add(msg.ReadInt32());
}
bool validateHires = msg.ReadBoolean();
bool fireCharacter = msg.ReadBoolean();
int firedIdentifier = -1;
if (fireCharacter) { firedIdentifier = msg.ReadInt32(); }
if (fireCharacter)
{
CharacterInfo firedCharacter = CrewManager.CharacterInfos.FirstOrDefault(info => info.GetIdentifier() == firedIdentifier);
// this one might and is allowed to be null since the character is already fired on the original sender's game
if (firedCharacter != null) { CrewManager.FireCharacter(firedCharacter); }
}
if (map?.CurrentLocation?.HireManager != null && CampaignUI?.CrewManagement != null)
{
CampaignUI?.CrewManagement?.SetHireables(map.CurrentLocation, availableHires);
if (validateHires) { CampaignUI?.CrewManagement.ValidatePendingHires(); }
CampaignUI?.CrewManagement?.SetPendingHires(pendingHires, map?.CurrentLocation);
if (fireCharacter) { CampaignUI?.CrewManagement.UpdateCrew(); }
}
}
public override void Save(XElement element) public override void Save(XElement element)
{ {
//do nothing, the clients get the save files from the server //do nothing, the clients get the save files from the server
} }
public void LoadState(string filePath)
{
DebugConsole.Log($"Loading save file for an existing game session ({filePath})");
SaveUtil.DecompressToDirectory(filePath, SaveUtil.TempPath, null);
string gamesessionDocPath = Path.Combine(SaveUtil.TempPath, "gamesession.xml");
XDocument doc = XMLExtensions.TryLoadXml(gamesessionDocPath);
if (doc == null)
{
DebugConsole.ThrowError($"Failed to load the state of a multiplayer campaign. Could not open the file \"{gamesessionDocPath}\".");
return;
}
Load(doc.Root.Element("MultiPlayerCampaign"));
SubmarineInfo selectedSub;
GameMain.GameSession.OwnedSubmarines = SaveUtil.LoadOwnedSubmarines(doc, out selectedSub);
GameMain.GameSession.SubmarineInfo = selectedSub;
}
} }
} }
File diff suppressed because it is too large Load Diff
@@ -1,80 +0,0 @@
using Barotrauma.Tutorials;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class SubTestMode : GameMode
{
public SubTestMode(GameModePreset preset, object param)
: base(preset, param)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
isRunning = true;
CrewManager.InitSinglePlayerRound();
Submarine.MainSub.SetPosition(Vector2.Zero);
}
public override void Draw(SpriteBatch spriteBatch)
{
if (!isRunning|| GUI.DisableHUD || GUI.DisableUpperHUD) return;
if (Submarine.MainSub == null) return;
}
public override void AddToGUIUpdateList()
{
if (!isRunning) return;
base.AddToGUIUpdateList();
CrewManager.AddToGUIUpdateList();
}
public override void Update(float deltaTime)
{
if (!isRunning) { return; }
base.Update(deltaTime);
}
public override void End(string endMessage = "")
{
isRunning = false;
GameMain.GameSession.EndRound("");
CrewManager.EndRound();
Submarine.Unload();
GameMain.SubEditorScreen.Select();
}
private bool EndRound(Submarine leavingSub)
{
isRunning = false;
End("");
return true;
}
}
}
@@ -0,0 +1,34 @@
using Microsoft.Xna.Framework;
using System;
namespace Barotrauma
{
class TestGameMode : GameMode
{
public Action OnRoundEnd;
public TestGameMode(GameModePreset preset) : base(preset)
{
foreach (JobPrefab jobPrefab in JobPrefab.Prefabs)
{
for (int i = 0; i < jobPrefab.InitialCount; i++)
{
var variant = Rand.Range(0, jobPrefab.Variants);
CrewManager.AddCharacterInfo(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobPrefab: jobPrefab, variant: variant));
}
}
}
public override void Start()
{
base.Start();
CrewManager.InitSinglePlayerRound();
}
public override void End(CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{
OnRoundEnd?.Invoke();
}
}
}
@@ -614,7 +614,7 @@ namespace Barotrauma.Tutorials
GameMain.GameScreen.Cam.TargetPos = Vector2.Zero; GameMain.GameScreen.Cam.TargetPos = Vector2.Zero;
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
var cinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, 5.0f); var cinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, Alignment.CenterLeft, Alignment.CenterRight, duration: 5.0f);
while (cinematic.Running) while (cinematic.Running)
{ {
@@ -101,6 +101,7 @@ namespace Barotrauma.Tutorials
tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>(); tutorial_submarineDoorLight = Item.ItemList.Find(i => i.HasTag("tutorial_submarinedoorlight")).GetComponent<LightComponent>();
var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor")); var medicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("medicaldoctor"));
captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor"); captain_medic = Character.Create(medicInfo, captain_medicSpawnPos, "medicaldoctor");
captain_medic.TeamID = Character.TeamType.Team1;
captain_medic.GiveJobItems(null); captain_medic.GiveJobItems(null);
captain_medic.CanSpeak = captain_medic.AIController.Enabled = false; captain_medic.CanSpeak = captain_medic.AIController.Enabled = false;
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false); SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, false);
@@ -123,14 +124,17 @@ namespace Barotrauma.Tutorials
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic")); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("mechanic"));
captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic"); captain_mechanic = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "mechanic");
captain_mechanic.TeamID = Character.TeamType.Team1;
captain_mechanic.GiveJobItems(); captain_mechanic.GiveJobItems();
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer"); captain_security = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "securityofficer");
captain_security.TeamID = Character.TeamType.Team1;
captain_security.GiveJobItems(); captain_security.GiveJobItems();
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer"); captain_engineer = Character.Create(engineerInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "engineer");
captain_engineer.TeamID = Character.TeamType.Team1;
captain_engineer.GiveJobItems(); captain_engineer.GiveJobItems();
captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false; captain_mechanic.CanSpeak = captain_security.CanSpeak = captain_engineer.CanSpeak = false;
@@ -195,7 +199,7 @@ namespace Barotrauma.Tutorials
// GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5)); // GameMain.GameSession.CrewManager.HighlightOrderButton(captain_security, "operateweapons", highlightColor, new Vector2(5, 5));
HighlightOrderOption("fireatwill"); HighlightOrderOption("fireatwill");
} }
while (!HasOrder(captain_security, "operateweapons", "fireatwill")); while (!HasOrder(captain_security, "operateweapons"));
RemoveCompletedObjective(segments[2]); RemoveCompletedObjective(segments[2]);
yield return new WaitForSeconds(4f, false); yield return new WaitForSeconds(4f, false);
TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command)); TriggerTutorialSegment(3, GameMain.Config.KeyBindText(InputType.Command));
@@ -80,6 +80,7 @@ namespace Barotrauma.Tutorials
var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); var assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1"); patient1 = Character.Create(assistantInfo, patientHull1.WorldPosition, "1");
patient1.TeamID = Character.TeamType.Team1;
patient1.GiveJobItems(null); patient1.GiveJobItems(null);
patient1.CanSpeak = false; patient1.CanSpeak = false;
patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false); patient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 45.0f) }, stun: 0, playSound: false);
@@ -87,22 +88,26 @@ namespace Barotrauma.Tutorials
assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant")); assistantInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("assistant"));
patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2"); patient2 = Character.Create(assistantInfo, patientHull2.WorldPosition, "2");
patient2.TeamID = Character.TeamType.Team1;
patient2.GiveJobItems(null); patient2.GiveJobItems(null);
patient2.CanSpeak = false; patient2.CanSpeak = false;
patient2.AIController.Enabled = false; patient2.AIController.Enabled = false;
var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var mechanicInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient1 = Character.Create(mechanicInfo, WayPoint.GetRandom(SpawnType.Human, mechanicInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient1.TeamID = Character.TeamType.Team1;
subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false); subPatient1.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient1); subPatients.Add(subPatient1);
var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer")); var securityInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("securityofficer"));
var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient2 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, securityInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient2.TeamID = Character.TeamType.Team1;
subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false); subPatient2.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.InternalDamage, 40.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient2); subPatients.Add(subPatient2);
var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")); var engineerInfo = new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer"));
var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3"); var subPatient3 = Character.Create(securityInfo, WayPoint.GetRandom(SpawnType.Human, engineerInfo.Job, Submarine.MainSub).WorldPosition, "3");
subPatient3.TeamID = Character.TeamType.Team1;
subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false); subPatient3.AddDamage(patient1.WorldPosition, new List<Affliction>() { new Affliction(AfflictionPrefab.Burn, 20.0f) }, stun: 0, playSound: false);
subPatients.Add(subPatient3); subPatients.Add(subPatient3);
@@ -376,7 +376,7 @@ namespace Barotrauma.Tutorials
} }
} }
yield return null; yield return null;
} while (!engineer_brokenJunctionBox.IsFullCondition); // Wait until repaired } while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired
SetHighlight(engineer_brokenJunctionBox, false); SetHighlight(engineer_brokenJunctionBox, false);
RemoveCompletedObjective(segments[2]); RemoveCompletedObjective(segments[2]);
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
@@ -408,15 +408,20 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4); // Repair junction box TriggerTutorialSegment(4); // Repair junction box
while (ContentRunning) yield return null; while (ContentRunning) yield return null;
SetHighlight(engineer_submarineJunctionBox_1, true);
SetHighlight(engineer_submarineJunctionBox_2, true);
SetHighlight(engineer_submarineJunctionBox_3, true);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor); engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_1, engineer_repairIcon, engineer_repairIconColor);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor); engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_2, engineer_repairIcon, engineer_repairIconColor);
engineer.AddActiveObjectiveEntity(engineer_submarineJunctionBox_3, 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 // Remove highlights when each individual machine is repaired
do { CheckJunctionBoxHighlights(); yield return null; } while (!engineer_submarineJunctionBox_1.IsFullCondition || !engineer_submarineJunctionBox_2.IsFullCondition || !engineer_submarineJunctionBox_3.IsFullCondition); do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (engineer_submarineJunctionBox_1.Condition < repairableJunctionBoxComponent1.RepairThreshold || engineer_submarineJunctionBox_2.Condition < repairableJunctionBoxComponent2.RepairThreshold || engineer_submarineJunctionBox_3.Condition < repairableJunctionBoxComponent3.RepairThreshold);
CheckJunctionBoxHighlights(); CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3);
RemoveCompletedObjective(segments[4]); RemoveCompletedObjective(segments[4]);
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
@@ -557,19 +562,19 @@ namespace Barotrauma.Tutorials
} }
} }
private void CheckJunctionBoxHighlights() private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{ {
if (engineer_submarineJunctionBox_1.IsFullCondition && engineer_submarineJunctionBox_1.ExternalHighlight) if (engineer_submarineJunctionBox_1.Condition > comp1.RepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight)
{ {
SetHighlight(engineer_submarineJunctionBox_1, false); SetHighlight(engineer_submarineJunctionBox_1, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_1);
} }
if (engineer_submarineJunctionBox_2.IsFullCondition && engineer_submarineJunctionBox_2.ExternalHighlight) if (engineer_submarineJunctionBox_2.Condition > comp2.RepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight)
{ {
SetHighlight(engineer_submarineJunctionBox_2, false); SetHighlight(engineer_submarineJunctionBox_2, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_2);
} }
if (engineer_submarineJunctionBox_3.IsFullCondition && engineer_submarineJunctionBox_3.ExternalHighlight) if (engineer_submarineJunctionBox_3.Condition > comp3.RepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight)
{ {
SetHighlight(engineer_submarineJunctionBox_3, false); SetHighlight(engineer_submarineJunctionBox_3, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3);
@@ -385,7 +385,8 @@ namespace Barotrauma.Tutorials
} }
} }
if (!gotOxygenTank && mechanic.Inventory.FindItemByIdentifier("oxygentank") != null) if (!gotOxygenTank && (mechanic.Inventory.FindItemByIdentifier("oxygentank") != null ||
mechanic_deconstructor.InputContainer.Inventory.FindItemByIdentifier("oxygentank") != null))
{ {
gotOxygenTank = true; gotOxygenTank = true;
} }
@@ -551,7 +552,7 @@ namespace Barotrauma.Tutorials
do do
{ {
yield return null; yield return null;
if (!mechanic_brokenPump.Item.IsFullCondition) if (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold)
{ {
if (!mechanic.HasEquippedItem("wrench")) if (!mechanic.HasEquippedItem("wrench"))
{ {
@@ -575,7 +576,7 @@ namespace Barotrauma.Tutorials
} }
} }
} }
} while (!mechanic_brokenPump.Item.IsFullCondition || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); } while (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive);
RemoveCompletedObjective(segments[9]); RemoveCompletedObjective(segments[9]);
SetHighlight(mechanic_brokenPump.Item, false); SetHighlight(mechanic_brokenPump.Item, false);
do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening); do { yield return null; } while (mechanic_brokenhull_2.WaterPercentage > waterVolumeBeforeOpening);
@@ -592,9 +593,14 @@ namespace Barotrauma.Tutorials
SetHighlight(mechanic_ballastPump_1.Item, true); SetHighlight(mechanic_ballastPump_1.Item, true);
SetHighlight(mechanic_ballastPump_2.Item, true); SetHighlight(mechanic_ballastPump_2.Item, true);
SetHighlight(mechanic_submarineEngine.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 // Remove highlights when each individual machine is repaired
do { CheckHighlights(); yield return null; } while (!mechanic_ballastPump_1.Item.IsFullCondition || !mechanic_ballastPump_2.Item.IsFullCondition || !mechanic_submarineEngine.Item.IsFullCondition); do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (mechanic_ballastPump_1.Item.Condition < repairablePumpComponent1.RepairThreshold || mechanic_ballastPump_2.Item.Condition < repairablePumpComponent2.RepairThreshold || mechanic_submarineEngine.Item.Condition < repairableEngineComponent.RepairThreshold);
CheckHighlights(); CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent);
RemoveCompletedObjective(segments[10]); RemoveCompletedObjective(segments[10]);
GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null); GameMain.GameSession?.CrewManager.AddSinglePlayerChatMessage(radioSpeakerName, TextManager.Get("Mechanic.Radio.Complete"), ChatMessageType.Radio, null);
@@ -617,19 +623,19 @@ namespace Barotrauma.Tutorials
return false; return false;
} }
private void CheckHighlights() private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{ {
if (mechanic_ballastPump_1.Item.IsFullCondition && mechanic_ballastPump_1.Item.ExternalHighlight) if (mechanic_ballastPump_1.Item.Condition > comp1.RepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight)
{ {
SetHighlight(mechanic_ballastPump_1.Item, false); SetHighlight(mechanic_ballastPump_1.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item); mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_1.Item);
} }
if (mechanic_ballastPump_2.Item.IsFullCondition && mechanic_ballastPump_2.Item.ExternalHighlight) if (mechanic_ballastPump_2.Item.Condition > comp2.RepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight)
{ {
SetHighlight(mechanic_ballastPump_2.Item, false); SetHighlight(mechanic_ballastPump_2.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item); mechanic.RemoveActiveObjectiveEntity(mechanic_ballastPump_2.Item);
} }
if (mechanic_submarineEngine.Item.IsFullCondition && mechanic_submarineEngine.Item.ExternalHighlight) if (mechanic_submarineEngine.Item.Condition > comp3.RepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight)
{ {
SetHighlight(mechanic_submarineEngine.Item, false); SetHighlight(mechanic_submarineEngine.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item); mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item);
@@ -315,13 +315,14 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead TriggerTutorialSegment(4, GameMain.Config.KeyBindText(InputType.Select), GameMain.Config.KeyBindText(InputType.Shoot), GameMain.Config.KeyBindText(InputType.Deselect)); // Kill hammerhead
officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos); officer_hammerhead = SpawnMonster("hammerhead", officer_hammerheadSpawnPos);
((EnemyAIController)officer_hammerhead.AIController).StayInsideLevel = false;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget); officer_hammerhead.AIController.SelectTarget(officer.AiTarget);
SetHighlight(officer_coilgunPeriscope, true); SetHighlight(officer_coilgunPeriscope, true);
float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos); float originalDistance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerheadSpawnPos);
do do
{ {
float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition); float distance = Vector2.Distance(officer_coilgunPeriscope.WorldPosition, officer_hammerhead.WorldPosition);
if (distance > originalDistance * 1.5f) if (distance > originalDistance * 1.5f || officer_hammerhead.WorldPosition.Y > officer_coilgunPeriscope.WorldPosition.Y)
{ {
// Don't let the Hammerhead go too far. // Don't let the Hammerhead go too far.
officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000)); officer_hammerhead.TeleportTo(officer_hammerheadSpawnPos + new Vector2(0, -1000));
@@ -329,7 +330,13 @@ namespace Barotrauma.Tutorials
if (distance > originalDistance) if (distance > originalDistance)
{ {
// Ensure that the Hammerhead targets the player // Ensure that the Hammerhead targets the player
officer.AiTarget.SoundRange = float.MaxValue;
officer.AiTarget.SightRange = float.MaxValue;
officer_hammerhead.AIController.SelectTarget(officer.AiTarget); 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; /*var ai = officer_hammerhead.AIController as EnemyAIController;
ai.sight = 2.0f;*/ ai.sight = 2.0f;*/
} }
@@ -58,30 +58,31 @@ namespace Barotrauma.Tutorials
{ {
SubmarineInfo subInfo = new SubmarineInfo(submarinePath); SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Name == levelParams); LevelGenerationParams generationParams = LevelGenerationParams.LevelParams.Find(p => p.Identifier.Equals(levelParams, StringComparison.OrdinalIgnoreCase));
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
GameMain.GameSession = new GameSession(subInfo, "", GameMain.GameSession = new GameSession(subInfo, GameModePreset.Tutorial, missionPrefab: null);
GameModePreset.List.Find(g => g.Identifier == "tutorial"));
(GameMain.GameSession.GameMode as TutorialMode).Tutorial = this; (GameMain.GameSession.GameMode as TutorialMode).Tutorial = this;
if (generationParams != null) if (generationParams != null)
{ {
Biome biome = LevelGenerationParams.GetBiomes().Find(b => generationParams.AllowedBiomes.Contains(b)); Biome biome =
LevelGenerationParams.GetBiomes().FirstOrDefault(b => generationParams.AllowedBiomes.Contains(b)) ??
LevelGenerationParams.GetBiomes().First();
if (startOutpostPath != string.Empty) if (!string.IsNullOrEmpty(startOutpostPath))
{ {
startOutpost = new SubmarineInfo(startOutpostPath); startOutpost = new SubmarineInfo(startOutpostPath);
} }
if (endOutpostPath != string.Empty) if (!string.IsNullOrEmpty(endOutpostPath))
{ {
endOutpost = new SubmarineInfo(endOutpostPath); endOutpost = new SubmarineInfo(endOutpostPath);
} }
Level tutorialLevel = new Level(levelSeed, 0, 0, generationParams, biome, startOutpost, endOutpost); LevelData tutorialLevel = new LevelData(levelSeed, 0, 0, generationParams, biome);
GameMain.GameSession.StartRound(tutorialLevel); GameMain.GameSession.StartRound(tutorialLevel, startOutpost: startOutpost, endOutpost: endOutpost);
} }
else else
{ {
@@ -100,6 +101,13 @@ namespace Barotrauma.Tutorials
base.Start(); base.Start();
Submarine.MainSub.GodMode = true; Submarine.MainSub.GodMode = true;
foreach (Structure wall in Structure.WallList)
{
if (wall.Submarine != null && wall.Submarine.Info.IsOutpost)
{
wall.Indestructible = true;
}
}
CharacterInfo charInfo = configElement.Element("Character") == null ? CharacterInfo charInfo = configElement.Element("Character") == null ?
new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) : new CharacterInfo(CharacterPrefab.HumanSpeciesName, "", JobPrefab.Get("engineer")) :
@@ -114,6 +122,7 @@ namespace Barotrauma.Tutorials
} }
character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false); character = Character.Create(charInfo, wayPoint.WorldPosition, "", false, false);
character.TeamID = Character.TeamType.Team1;
Character.Controlled = character; Character.Controlled = character;
character.GiveJobItems(null); character.GiveJobItems(null);
@@ -126,19 +135,14 @@ namespace Barotrauma.Tutorials
idCard.AddTag("com"); idCard.AddTag("com");
idCard.AddTag("eng"); idCard.AddTag("eng");
List<Entity> entities = Entity.GetEntityList(); foreach (Item item in Item.ItemList)
for (int i = 0; i < entities.Count; i++)
{ {
if (entities[i] is Item) Door door = item.GetComponent<Door>();
{
Door door = (entities[i] as Item).GetComponent<Door>();
if (door != null) if (door != null)
{ {
door.CanBeWelded = false; door.CanBeWelded = false;
} }
} }
}
tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState()); tutorialCoroutine = CoroutineManager.StartCoroutine(UpdateState());
} }
@@ -284,7 +288,7 @@ namespace Barotrauma.Tutorials
yield return new WaitForSeconds(waitBeforeFade); yield return new WaitForSeconds(waitBeforeFade);
var endCinematic = new RoundEndCinematic(Submarine.MainSub, GameMain.GameScreen.Cam, fadeOutTime); var endCinematic = new CameraTransition(Submarine.MainSub, GameMain.GameScreen.Cam, null, Alignment.Center, duration: fadeOutTime);
currentTutorialCompleted = Completed = true; currentTutorialCompleted = Completed = true;
while (endCinematic.Running) yield return null; while (endCinematic.Running) yield return null;
Stop(); Stop();
@@ -26,6 +26,7 @@ namespace Barotrauma.Tutorials
protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 }; protected enum TutorialContentTypes { None = 0, Video = 1, ManualVideo = 2, TextOnly = 3 };
protected string playableContentPath; protected string playableContentPath;
protected Point screenResolution; protected Point screenResolution;
protected WindowMode windowMode;
protected float prevUIScale; protected float prevUIScale;
private GUIFrame holderFrame, objectiveFrame; private GUIFrame holderFrame, objectiveFrame;
@@ -207,7 +208,7 @@ namespace Barotrauma.Tutorials
public virtual void AddToGUIUpdateList() public virtual void AddToGUIUpdateList()
{ {
if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale) if (GameMain.GraphicsWidth != screenResolution.X || GameMain.GraphicsHeight != screenResolution.Y || prevUIScale != GUI.Scale || GameMain.Config.WindowMode != windowMode)
{ {
CreateObjectiveFrame(); CreateObjectiveFrame();
} }
@@ -340,6 +341,7 @@ namespace Barotrauma.Tutorials
} }
screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight); screenResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
windowMode = GameMain.Config.WindowMode;
prevUIScale = GUI.Scale; prevUIScale = GUI.Scale;
} }
@@ -11,8 +11,8 @@ namespace Barotrauma
tutorial.Initialize(); tutorial.Initialize();
} }
public TutorialMode(GameModePreset preset, object param) public TutorialMode(GameModePreset preset)
: base(preset, param) : base(preset)
{ {
} }
@@ -21,6 +21,11 @@ namespace Barotrauma
base.Start(); base.Start();
GameMain.GameSession.CrewManager = new CrewManager(true); GameMain.GameSession.CrewManager = new CrewManager(true);
Tutorial.Start(); Tutorial.Start();
foreach (Item item in Item.ItemList)
{
//don't consider the items to belong in the outpost to prevent the stealing icon from showing
item.SpawnedInOutpost = false;
}
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
@@ -1,14 +1,19 @@
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
namespace Barotrauma namespace Barotrauma
{ {
partial class GameSession partial class GameSession
{ {
public RoundSummary RoundSummary { get; private set; } public RoundSummary RoundSummary
{
get;
private set;
}
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 TabMenu tabMenu; private TabMenu tabMenu;
public bool ToggleTabMenu() public bool ToggleTabMenu()
@@ -46,10 +51,8 @@ namespace Barotrauma
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (GUI.DisableHUD) return; if (GUI.DisableHUD) { return; }
if (GameMode.IsRunning)
{
if (tabMenu == null) if (tabMenu == null)
{ {
if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false) if (PlayerInput.KeyHit(InputType.InfoTab) && GUI.KeyboardDispatcher.Subscriber is GUITextBox == false)
@@ -66,14 +69,6 @@ namespace Barotrauma
ToggleTabMenu(); ToggleTabMenu();
} }
} }
}
else
{
if (tabMenu != null)
{
ToggleTabMenu();
}
}
if (GameMain.NetworkMember != null) if (GameMain.NetworkMember != null)
{ {
@@ -97,7 +92,6 @@ namespace Barotrauma
public void Draw(SpriteBatch spriteBatch) public void Draw(SpriteBatch spriteBatch)
{ {
if (GUI.DisableHUD) return;
GameMode?.Draw(spriteBatch); GameMode?.Draw(spriteBatch);
} }
} }
@@ -1,147 +1,542 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
{ {
class RoundSummary class RoundSummary
{ {
private Location startLocation, endLocation; private const float jobColumnWidthPercentage = 0.11f;
private const float characterColumnWidthPercentage = 0.44f;
private const float statusColumnWidthPercentage = 0.45f;
private GameSession gameSession; private int jobColumnWidth, characterColumnWidth, statusColumnWidth;
private Mission selectedMission; private readonly SubmarineInfo sub;
private readonly Mission selectedMission;
private readonly Location startLocation, endLocation;
public RoundSummary(GameSession gameSession) private readonly GameMode gameMode;
private readonly float initialLocationReputation;
private readonly Dictionary<Faction, float> initialFactionReputations = new Dictionary<Faction, float>();
public GUILayoutGroup ButtonArea { get; private set; }
public GUIButton ContinueButton { get; private set; }
public GUIComponent Frame { get; private set; }
public RoundSummary(SubmarineInfo sub, GameMode gameMode, Mission selectedMission, Location startLocation, Location endLocation)
{ {
this.gameSession = gameSession; this.sub = sub;
this.gameMode = gameMode;
startLocation = gameSession.StartLocation; this.selectedMission = selectedMission;
endLocation = gameSession.EndLocation; this.startLocation = startLocation;
this.endLocation = endLocation;
selectedMission = gameSession.Mission; initialLocationReputation = startLocation?.Reputation?.Value ?? 0.0f;
if (gameMode is CampaignMode campaignMode)
{
foreach (Faction faction in campaignMode.Factions)
{
initialFactionReputations.Add(faction, faction.Reputation.Value);
}
}
} }
public GUIFrame CreateSummaryFrame(string endMessage) public GUIFrame CreateSummaryFrame(GameSession gameSession, string endMessage, List<TraitorMissionResult> traitorResults, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{ {
bool singleplayer = GameMain.NetworkMember == null; bool singleplayer = GameMain.NetworkMember == null;
bool gameOver = gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated); bool gameOver =
bool progress = Submarine.MainSub.AtEndPosition; gameSession.GameMode.IsSinglePlayer ?
gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated) :
gameSession.CrewManager.GetCharacters().All(c => c.IsDead || c.IsIncapacitated || c.IsBot);
if (!singleplayer) if (!singleplayer)
{ {
SoundPlayer.OverrideMusicType = gameOver ? "crewdead" : "endround"; SoundPlayer.OverrideMusicType = gameOver ? "crewdead" : "endround";
SoundPlayer.OverrideMusicDuration = 18.0f; SoundPlayer.OverrideMusicDuration = 18.0f;
} }
GUIFrame background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker"); GUIFrame background = new GUIFrame(new RectTransform(GUI.Canvas.RelativeSize, GUI.Canvas, Anchor.Center), style: "GUIBackgroundBlocker")
GUIFrame frame = new GUIFrame(new RectTransform(Vector2.One, background.RectTransform, Anchor.Center), style: null)
{ {
UserData = "roundsummary" UserData = this
}; };
int width = 760, height = 500; List<GUIComponent> rightPanels = new List<GUIComponent>();
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.5f), frame.RectTransform, Anchor.Center, minSize: new Point(width, height)));
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), innerFrame.RectTransform, Anchor.Center)) int minWidth = 400, minHeight = 350;
int padding = GUI.IntScale(25.0f);
//crew panel -------------------------------------------------------------------------------
GUIFrame crewFrame = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
GUIFrame crewFrameInner = new GUIFrame(new RectTransform(new Point(crewFrame.Rect.Width - padding * 2, crewFrame.Rect.Height - padding * 2), crewFrame.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner.RectTransform, Anchor.Center))
{ {
Stretch = true,
RelativeSpacing = 0.03f
};
GUIListBox infoTextBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), paddedFrame.RectTransform))
{
Spacing = (int)(5 * GUI.Scale)
};
//spacing
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), infoTextBox.Content.RectTransform), style: null);
string summaryText = TextManager.GetWithVariables(gameOver ? "RoundSummaryGameOver" :
(progress ? "RoundSummaryProgress" : "RoundSummaryReturn"), new string[2] { "[sub]", "[location]" },
new string[2] { Submarine.MainSub.Info.Name, progress ? GameMain.GameSession.EndLocation.Name : GameMain.GameSession.StartLocation.Name });
var infoText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
summaryText, wrap: true);
GUIComponent endText = null;
if (!string.IsNullOrWhiteSpace(endMessage))
{
endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.GetServerMessage(endMessage), wrap: true);
}
//don't show the mission info if the mission was not completed and there's no localized "mission failed" text available
if (GameMain.GameSession.Mission != null)
{
string message = GameMain.GameSession.Mission.Completed ? GameMain.GameSession.Mission.SuccessMessage : GameMain.GameSession.Mission.FailureMessage;
if (!string.IsNullOrEmpty(message))
{
//spacing
var spacingTransform = new RectTransform(new Vector2(1.0f, 0.1f), infoTextBox.Content.RectTransform);
new GUIFrame(spacingTransform, style: null);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("Mission"), GameMain.GameSession.Mission.Name),
font: GUI.LargeFont);
var missionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
message, wrap: true);
if (GameMain.GameSession.Mission.Completed && singleplayer)
{
var missionReward = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), infoTextBox.Content.RectTransform),
TextManager.GetWithVariable("MissionReward", "[reward]", GameMain.GameSession.Mission.Reward.ToString()));
}
}
}
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedFrame.RectTransform),
TextManager.Get("RoundSummaryCrewStatus"), font: GUI.LargeFont);
GUIListBox characterListBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.4f), paddedFrame.RectTransform, minSize: new Point(0, 75)), isHorizontal: true);
foreach (CharacterInfo characterInfo in gameSession.CrewManager.GetCharacterInfos())
{
if (GameMain.GameSession.Mission is CombatMission &&
characterInfo.TeamID != GameMain.GameSession.WinningTeam)
{
continue;
}
var characterFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.2f, 1.0f), characterListBox.Content.RectTransform, minSize: new Point(170, 0)))
{
CanBeFocused = false,
Stretch = true Stretch = true
}; };
characterInfo.CreateCharacterFrame(characterFrame, var crewHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent.RectTransform),
characterInfo.Job != null ? (characterInfo.Name + '\n' + "(" + characterInfo.Job.Name + ")") : characterInfo.Name, null); TextManager.Get("crew"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
crewHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader.Rect.Height * 2.0f));
CreateCrewList(crewContent, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID != Character.TeamType.Team2));
//another crew frame for the 2nd team in combat missions
if (gameSession.Mission is CombatMission)
{
crewHeader.Text = CombatMission.GetTeamName(Character.TeamType.Team1);
GUIFrame crewFrame2 = new GUIFrame(new RectTransform(new Vector2(0.35f, 0.55f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight)));
rightPanels.Add(crewFrame2);
GUIFrame crewFrameInner2 = new GUIFrame(new RectTransform(new Point(crewFrame2.Rect.Width - padding * 2, crewFrame2.Rect.Height - padding * 2), crewFrame2.RectTransform, Anchor.Center), style: "InnerFrame");
var crewContent2 = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), crewFrameInner2.RectTransform, Anchor.Center))
{
Stretch = true
};
var crewHeader2 = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), crewContent2.RectTransform),
CombatMission.GetTeamName(Character.TeamType.Team2), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
crewHeader2.RectTransform.MinSize = new Point(0, GUI.IntScale(crewHeader2.Rect.Height * 2.0f));
CreateCrewList(crewContent2, gameSession.CrewManager.GetCharacterInfos().Where(c => c.TeamID == Character.TeamType.Team2));
}
//header -------------------------------------------------------------------------------
string headerText = GetHeaderText(gameOver, transitionType);
GUITextBlock headerTextBlock = null;
if (!string.IsNullOrEmpty(headerText))
{
headerTextBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), crewFrame.RectTransform, Anchor.TopLeft, Pivot.BottomLeft),
headerText, textAlignment: Alignment.BottomLeft, font: GUI.LargeFont, wrap: true);
}
//traitor panel -------------------------------------------------------------------------------
if (traitorResults != null && traitorResults.Any())
{
GUIFrame traitorframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize));
rightPanels.Add(traitorframe);
GUIFrame traitorframeInner = new GUIFrame(new RectTransform(new Point(traitorframe.Rect.Width - padding * 2, traitorframe.Rect.Height - padding * 2), traitorframe.RectTransform, Anchor.Center), style: "InnerFrame");
var traitorContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), traitorframeInner.RectTransform, Anchor.Center))
{
Stretch = true
};
var traitorHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), traitorContent.RectTransform),
TextManager.Get("traitors"), font: GUI.SubHeadingFont);
traitorHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(traitorHeader.Rect.Height * 2.0f));
GUIListBox listBox = CreateCrewList(traitorContent, traitorResults.SelectMany(tr => tr.Characters.Select(c => c.Info)));
foreach (var traitorResult in traitorResults)
{
var traitorMission = TraitorMissionPrefab.List.Find(t => t.Identifier == traitorResult.MissionIdentifier);
if (traitorMission == null) { continue; }
//spacing
new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(25)), listBox.Content.RectTransform), style: null);
var traitorResultHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), listBox.Content.RectTransform), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.05f,
Stretch = true
};
new GUIImage(new RectTransform(new Point(traitorResultHorizontal.Rect.Height), traitorResultHorizontal.RectTransform), traitorMission.Icon, scaleToFit: true)
{
Color = traitorMission.IconColor
};
string traitorMessage = TextManager.GetServerMessage(traitorResult.EndMessage);
if (!string.IsNullOrEmpty(traitorMessage))
{
var textContent = new GUILayoutGroup(new RectTransform(Vector2.One, traitorResultHorizontal.RectTransform))
{
RelativeSpacing = 0.025f
};
var traitorStatusText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
TextManager.Get(traitorResult.Success ? "missioncompleted" : "missionfailed"),
textColor: traitorResult.Success ? GUI.Style.Green : GUI.Style.Red, font: GUI.SubHeadingFont);
var traitorMissionInfo = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), textContent.RectTransform),
traitorMessage, font: GUI.SmallFont, wrap: true);
traitorResultHorizontal.Recalculate();
traitorStatusText.CalculateHeightFromText();
traitorMissionInfo.CalculateHeightFromText();
traitorStatusText.RectTransform.MinSize = new Point(0, traitorStatusText.Rect.Height);
traitorMissionInfo.RectTransform.MinSize = new Point(0, traitorMissionInfo.Rect.Height);
textContent.RectTransform.MaxSize = new Point(int.MaxValue, (int)((traitorStatusText.Rect.Height + traitorMissionInfo.Rect.Height) * 1.2f));
traitorResultHorizontal.RectTransform.MinSize = new Point(0, traitorStatusText.RectTransform.MinSize.Y + traitorMissionInfo.RectTransform.MinSize.Y);
}
}
}
//reputation panel -------------------------------------------------------------------------------
if (gameMode is CampaignMode campaignMode)
{
GUIFrame reputationframe = new GUIFrame(new RectTransform(crewFrame.RectTransform.RelativeSize, background.RectTransform, Anchor.TopCenter, minSize: crewFrame.RectTransform.MinSize));
rightPanels.Add(reputationframe);
GUIFrame reputationframeInner = new GUIFrame(new RectTransform(new Point(reputationframe.Rect.Width - padding * 2, reputationframe.Rect.Height - padding * 2), reputationframe.RectTransform, Anchor.Center), style: "InnerFrame");
var reputationContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.95f), reputationframeInner.RectTransform, Anchor.Center))
{
Stretch = true
};
var reputationHeader = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), reputationContent.RectTransform),
TextManager.Get("reputation"), textAlignment: Alignment.TopLeft, font: GUI.SubHeadingFont);
reputationHeader.RectTransform.MinSize = new Point(0, GUI.IntScale(reputationHeader.Rect.Height * 2.0f));
GUIListBox reputationList = new GUIListBox(new RectTransform(Vector2.One, reputationContent.RectTransform))
{
Padding = new Vector4(2, 5, 0, 0)
};
reputationList.ContentBackground.Color = Color.Transparent;
if (startLocation.Type.HasOutpost && startLocation.Reputation != null)
{
var iconStyle = GUI.Style.GetComponentStyle("LocationReputationIcon");
CreateReputationElement(
reputationList.Content,
startLocation.Name,
startLocation.Reputation.Value, startLocation.Reputation.NormalizedValue, initialLocationReputation,
startLocation.Type.Name, "",
iconStyle?.GetDefaultSprite(), startLocation.Type.GetPortrait(0), iconStyle?.Color ?? Color.White);
}
foreach (Faction faction in campaignMode.Factions)
{
float initialReputation = faction.Reputation.Value;
if (initialFactionReputations.ContainsKey(faction))
{
initialReputation = initialFactionReputations[faction];
}
else
{
DebugConsole.AddWarning($"Could not determine reputation change for faction \"{faction.Prefab.Name}\" (faction was not present at the start of the round).");
}
CreateReputationElement(
reputationList.Content,
faction.Prefab.Name,
faction.Reputation.Value, faction.Reputation.NormalizedValue, initialReputation,
faction.Prefab.ShortDescription, faction.Prefab.Description,
faction.Prefab.Icon, faction.Prefab.BackgroundPortrait, faction.Prefab.IconColor);
}
float otherElementHeight = 0.0f;
float maxDescriptionHeight = 0.0f;
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
maxDescriptionHeight = Math.Max(maxDescriptionHeight, descriptionElement.TextSize.Y * 1.1f);
otherElementHeight = Math.Max(otherElementHeight, descriptionElement.Parent.Rect.Height - descriptionElement.TextSize.Y);
}
foreach (GUIComponent child in reputationList.Content.Children)
{
var descriptionElement = child.FindChild("description", recursive: true) as GUITextBlock;
descriptionElement.RectTransform.MaxSize = new Point(int.MaxValue, (int)(maxDescriptionHeight));
child.RectTransform.MaxSize = new Point(int.MaxValue, (int)((maxDescriptionHeight + otherElementHeight) * 1.2f));
(descriptionElement?.Parent as GUILayoutGroup).Recalculate();
}
}
//mission panel -------------------------------------------------------------------------------
GUIFrame missionframe = new GUIFrame(new RectTransform(new Vector2(0.39f, 0.22f), background.RectTransform, Anchor.TopCenter, minSize: new Point(minWidth, minHeight / 4)));
GUIFrame missionframeInner = new GUIFrame(new RectTransform(new Point(missionframe.Rect.Width - padding * 2, missionframe.Rect.Height - padding * 2), missionframe.RectTransform, Anchor.Center), style: "InnerFrame");
var missionContent = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), missionframeInner.RectTransform, Anchor.Center))
{
RelativeSpacing = 0.05f,
Stretch = true
};
if (!string.IsNullOrWhiteSpace(endMessage))
{
var endText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionContent.RectTransform),
TextManager.GetServerMessage(endMessage), wrap: true);
endText.RectTransform.MinSize = new Point(0, endText.Rect.Height);
var line = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.1f), missionContent.RectTransform), style: "HorizontalLine");
line.RectTransform.NonScaledSize = new Point(line.Rect.Width, GUI.IntScale(5.0f));
}
var missionContentHorizontal = new GUILayoutGroup(new RectTransform(Vector2.One, missionContent.RectTransform), childAnchor: Anchor.TopLeft, isHorizontal: true)
{
RelativeSpacing = 0.025f,
Stretch = true
};
Mission displayedMission = selectedMission ?? startLocation.SelectedMission;
string missionMessage = "";
GUIImage missionIcon;
if (displayedMission != null)
{
missionMessage =
displayedMission == selectedMission ?
displayedMission.Completed ? displayedMission.SuccessMessage : displayedMission.FailureMessage :
displayedMission.Description;
missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), displayedMission.Prefab.Icon, scaleToFit: true)
{
Color = displayedMission.Prefab.IconColor
};
if (displayedMission == selectedMission)
{
new GUIImage(new RectTransform(Vector2.One, missionIcon.RectTransform), displayedMission.Completed ? "MissionCompletedIcon" : "MissionFailedIcon", scaleToFit: true);
}
}
else
{
missionIcon = new GUIImage(new RectTransform(new Point(missionContentHorizontal.Rect.Height), missionContentHorizontal.RectTransform), style: "NoMissionIcon", scaleToFit: true);
}
var missionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, missionContentHorizontal.RectTransform))
{
RelativeSpacing = 0.05f
};
missionContentHorizontal.Recalculate();
missionContent.Recalculate();
missionIcon.RectTransform.MinSize = new Point(0, missionContentHorizontal.Rect.Height);
missionTextContent.RectTransform.MaxSize = new Point(int.MaxValue, missionIcon.Rect.Width);
GUITextBlock missionDescription = null;
if (displayedMission == null)
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.Get("nomission"), font: GUI.LargeFont);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.AddPunctuation(':', TextManager.Get("Mission"), displayedMission.Name), font: GUI.SubHeadingFont);
missionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
missionMessage, wrap: true);
if (displayedMission == selectedMission && displayedMission.Completed)
{
string rewardText = TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", displayedMission.Reward));
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), missionTextContent.RectTransform),
TextManager.GetWithVariable("MissionReward", "[reward]", rewardText));
}
}
ButtonArea = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), missionContent.RectTransform, Anchor.BottomCenter), isHorizontal: true, childAnchor: Anchor.BottomRight)
{
IgnoreLayoutGroups = true,
RelativeSpacing = 0.025f
};
ContinueButton = new GUIButton(new RectTransform(new Vector2(0.25f, 1.0f), ButtonArea.RectTransform), TextManager.Get("Close"));
ButtonArea.RectTransform.NonScaledSize = new Point(ButtonArea.Rect.Width, ContinueButton.Rect.Height);
ButtonArea.RectTransform.IsFixedSize = true;
missionContent.Recalculate();
//description overlapping with the buttons -> switch to small font
if (missionDescription != null && missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y)
{
missionDescription.Font = GUI.Style.SmallFont;
//still overlapping -> shorten the text
if (missionDescription.Rect.Y + missionDescription.TextSize.Y > ButtonArea.Rect.Y && missionDescription.WrappedText.Contains('\n'))
{
missionDescription.ToolTip = missionDescription.Text;
missionDescription.Text = missionDescription.WrappedText.Split('\n').First() + "...";
}
}
// set layout -------------------------------------------------------------------
int panelSpacing = GUI.IntScale(20);
int totalHeight = crewFrame.Rect.Height + panelSpacing + missionframe.Rect.Height;
int totalWidth = crewFrame.Rect.Width;
crewFrame.RectTransform.AbsoluteOffset = new Point(0, (GameMain.GraphicsHeight - totalHeight) / 2);
missionframe.RectTransform.AbsoluteOffset = new Point(0, crewFrame.Rect.Bottom + panelSpacing);
if (rightPanels.Any())
{
totalWidth = crewFrame.Rect.Width * 2 + panelSpacing;
if (headerTextBlock != null)
{
headerTextBlock.RectTransform.MinSize = new Point(totalWidth, 0);
}
crewFrame.RectTransform.AbsoluteOffset = new Point(-(crewFrame.Rect.Width + panelSpacing) / 2, crewFrame.RectTransform.AbsoluteOffset.Y);
foreach (var rightPanel in rightPanels)
{
rightPanel.RectTransform.AbsoluteOffset = new Point((rightPanel.Rect.Width + panelSpacing) / 2, crewFrame.RectTransform.AbsoluteOffset.Y);
}
}
if (!(gameSession.GameMode is CampaignMode))
{
var shadow = new GUIFrame(new RectTransform(new Point((int)(totalWidth * 1.2f), GameMain.GraphicsHeight * 2), background.RectTransform, Anchor.Center), style: "OuterGlow")
{
Color = Color.Black
};
shadow.RectTransform.SetAsFirstChild();
}
Frame = background;
return background;
}
private string GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{
string locationName = Submarine.MainSub.AtEndPosition ? endLocation?.Name : startLocation?.Name;
string textTag;
if (gameOver)
{
textTag = "RoundSummaryGameOver";
}
else
{
switch (transitionType)
{
case CampaignMode.TransitionType.LeaveLocation:
locationName = startLocation?.Name;
textTag = "RoundSummaryLeaving";
break;
case CampaignMode.TransitionType.ProgressToNextLocation:
case CampaignMode.TransitionType.ProgressToNextEmptyLocation:
locationName = endLocation?.Name;
textTag = "RoundSummaryProgress";
break;
case CampaignMode.TransitionType.ReturnToPreviousLocation:
case CampaignMode.TransitionType.ReturnToPreviousEmptyLocation:
locationName = startLocation?.Name;
textTag = "RoundSummaryReturn";
break;
default:
textTag = Submarine.MainSub.AtEndPosition ? "RoundSummaryProgress" : "RoundSummaryReturn";
break;
}
}
if (textTag == null) { return ""; }
if (locationName == null)
{
DebugConsole.ThrowError($"Error while creating round summary: could not determine destination location. Start location: {startLocation?.Name ?? "null"}, end location: {endLocation?.Name ?? "null"}");
locationName = "[UNKNOWN]";
}
string subName = string.Empty;
SubmarineInfo currentOrPending = SubmarineSelection.CurrentOrPendingSubmarine();
if (currentOrPending != null)
{
subName = currentOrPending.DisplayName;
}
return TextManager.GetWithVariables(textTag, new string[2] { "[sub]", "[location]" }, new string[2] { subName, locationName });
}
private GUIListBox CreateCrewList(GUIComponent parent, IEnumerable<CharacterInfo> characterInfos)
{
var headerFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), parent.RectTransform, Anchor.TopCenter, minSize: new Point(0, (int)(30 * GUI.Scale))) { }, isHorizontal: true)
{
AbsoluteSpacing = 2
};
GUIButton jobButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("tabmenu.job"), style: "GUIButtonSmallFreeScale");
GUIButton characterButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("name"), style: "GUIButtonSmallFreeScale");
GUIButton statusButton = new GUIButton(new RectTransform(new Vector2(0f, 1f), headerFrame.RectTransform), TextManager.Get("label.statuslabel"), style: "GUIButtonSmallFreeScale");
float sizeMultiplier = 1.0f;
//sizeMultiplier = (headerFrame.Rect.Width - headerFrame.AbsoluteSpacing * (headerFrame.CountChildren - 1)) / (float)headerFrame.Rect.Width;
jobButton.RectTransform.RelativeSize = new Vector2(jobColumnWidthPercentage * sizeMultiplier, 1f);
characterButton.RectTransform.RelativeSize = new Vector2(characterColumnWidthPercentage * sizeMultiplier, 1f);
statusButton.RectTransform.RelativeSize = new Vector2(statusColumnWidthPercentage * sizeMultiplier, 1f);
jobButton.TextBlock.Font = characterButton.TextBlock.Font = statusButton.TextBlock.Font = GUI.HotkeyFont;
jobButton.CanBeFocused = characterButton.CanBeFocused = statusButton.CanBeFocused = false;
jobButton.TextBlock.ForceUpperCase = characterButton.TextBlock.ForceUpperCase = statusButton.ForceUpperCase = true;
jobColumnWidth = jobButton.Rect.Width;
characterColumnWidth = characterButton.Rect.Width;
statusColumnWidth = statusButton.Rect.Width;
GUIListBox crewList = new GUIListBox(new RectTransform(Vector2.One, parent.RectTransform))
{
Padding = new Vector4(2, 5, 0, 0),
AutoHideScrollBar = false
};
crewList.ContentBackground.Color = Color.Transparent;
headerFrame.RectTransform.RelativeSize -= new Vector2(crewList.ScrollBar.RectTransform.RelativeSize.X, 0.0f);
foreach (CharacterInfo characterInfo in characterInfos)
{
if (characterInfo == null) { continue; }
CreateCharacterElement(characterInfo, crewList);
}
return crewList;
}
private void CreateCharacterElement(CharacterInfo characterInfo, GUIListBox listBox)
{
GUIFrame frame = new GUIFrame(new RectTransform(new Point(listBox.Content.Rect.Width, GUI.IntScale(45)), listBox.Content.RectTransform), style: "ListBoxElement")
{
CanBeFocused = false,
UserData = characterInfo,
Color = (Character.Controlled?.Info == characterInfo) ? TabMenu.OwnCharacterBGColor : Color.Transparent
};
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.9f), frame.RectTransform, Anchor.Center), isHorizontal: true)
{
AbsoluteSpacing = 2,
Stretch = true
};
new GUICustomComponent(new RectTransform(new Point(jobColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform, Anchor.Center), onDraw: (sb, component) => characterInfo.DrawJobIcon(sb, component.Rect))
{
ToolTip = characterInfo.Job.Name ?? "",
HoverColor = Color.White,
SelectedColor = Color.White
};
GUITextBlock characterNameBlock = new GUITextBlock(new RectTransform(new Point(characterColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
ToolBox.LimitString(characterInfo.Name, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: characterInfo.Job.Prefab.UIColor);
string statusText = TextManager.Get("StatusOK"); string statusText = TextManager.Get("StatusOK");
Color statusColor = Color.DarkGreen; Color statusColor = GUI.Style.Green;
Character character = characterInfo.Character; Character character = characterInfo.Character;
if (character == null || character.IsDead) if (character == null || character.IsDead)
{ {
if (characterInfo.CauseOfDeath == null) if (characterInfo.IsNewHire)
{
statusText = TextManager.Get("CampaignCrew.NewHire");
statusColor = GUI.Style.Blue;
}
else if (characterInfo.CauseOfDeath == null)
{ {
statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
statusColor = Color.DarkRed;
} }
else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null) else if (characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction && characterInfo.CauseOfDeath.Affliction == null)
{ {
string errorMsg = "Character \"" + character.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified)."; string errorMsg = "Character \"" + characterInfo.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified).";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
statusColor = GUI.Style.Red;
} }
else else
{ {
statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ? statusText = characterInfo.CauseOfDeath.Type == CauseOfDeathType.Affliction ?
characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription : characterInfo.CauseOfDeath.Affliction.CauseOfDeathDescription :
TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString()); TextManager.Get("CauseOfDeathDescription." + characterInfo.CauseOfDeath.Type.ToString());
}
statusColor = Color.DarkRed; statusColor = Color.DarkRed;
} }
}
else else
{ {
if (character.IsUnconscious) if (character.IsUnconscious)
@@ -156,30 +551,104 @@ namespace Barotrauma
} }
} }
var textHolder = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), characterFrame.RectTransform, Anchor.BottomCenter), style: "InnerGlow", color: statusColor); GUITextBlock statusBlock = new GUITextBlock(new RectTransform(new Point(statusColumnWidth, paddedFrame.Rect.Height), paddedFrame.RectTransform),
new GUITextBlock(new RectTransform(Vector2.One, textHolder.RectTransform, Anchor.Center), ToolBox.LimitString(statusText, GUI.Font, characterColumnWidth), textAlignment: Alignment.Center, textColor: statusColor);
statusText, Color.White,
textAlignment: Alignment.Center,
wrap: true, font: GUI.SmallFont, style: null);
} }
new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), paddedFrame.RectTransform), isHorizontal: true, childAnchor: Anchor.BottomRight) private void CreateReputationElement(GUIComponent parent,
string name, float reputation, float normalizedReputation, float initialReputation,
string shortDescription, string fullDescription, Sprite icon, Sprite backgroundPortrait, Color iconColor)
{ {
RelativeSpacing = 0.05f, var factionFrame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform), style: null);
UserData = "buttonarea"
if (backgroundPortrait != null)
{
new GUICustomComponent(new RectTransform(Vector2.One, factionFrame.RectTransform), onDraw: (sb, customComponent) =>
{
backgroundPortrait.Draw(sb, customComponent.Rect.Center.ToVector2(), customComponent.Color, backgroundPortrait.size / 2, scale: customComponent.Rect.Width / backgroundPortrait.size.X);
})
{
HideElementsOutsideFrame = true,
IgnoreLayoutGroups = true,
Color = iconColor * 0.2f
};
}
var factionInfoHorizontal = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), factionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.02f,
Stretch = true
}; };
paddedFrame.Recalculate(); var factionTextContent = new GUILayoutGroup(new RectTransform(Vector2.One, factionInfoHorizontal.RectTransform))
foreach (GUIComponent child in infoTextBox.Content.Children)
{ {
child.CanBeFocused = false; RelativeSpacing = 0.05f,
if (child is GUITextBlock textBlock) Stretch = true
};
var factionIcon = new GUIImage(new RectTransform(new Point((int)(factionInfoHorizontal.Rect.Height * 0.7f)), factionInfoHorizontal.RectTransform, scaleBasis: ScaleBasis.Smallest), icon, scaleToFit: true)
{ {
textBlock.CalculateHeightFromText(); Color = iconColor
} };
factionInfoHorizontal.Recalculate();
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.15f), factionTextContent.RectTransform),
name, font: GUI.SubHeadingFont)
{
Padding = Vector4.Zero
};
var factionDescription = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.6f), factionTextContent.RectTransform),
shortDescription, font: GUI.SmallFont, wrap: true)
{
UserData = "description",
Padding = Vector4.Zero
};
if (shortDescription != fullDescription && !string.IsNullOrEmpty(fullDescription))
{
factionDescription.ToolTip = fullDescription;
} }
return frame; var sliderHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), factionTextContent.RectTransform),
childAnchor: Anchor.CenterLeft, isHorizontal: true)
{
RelativeSpacing = 0.05f,
Stretch = true
};
sliderHolder.RectTransform.MaxSize = new Point(int.MaxValue, GUI.IntScale(25.0f));
factionTextContent.Recalculate();
new GUICustomComponent(new RectTransform(new Vector2(0.8f, 1.0f), sliderHolder.RectTransform), onDraw: (sb, customComponent) =>
{
GUI.DrawRectangle(sb, customComponent.Rect, GUI.Style.ColorInventoryBackground, isFilled: true);
if (normalizedReputation < 0.5f)
{
int barWidth = (int)((0.5f - normalizedReputation) * customComponent.Rect.Width);
GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X - barWidth, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Red, isFilled: true);
}
else if (normalizedReputation > 0.5f)
{
int barWidth = (int)((normalizedReputation - 0.5f) * customComponent.Rect.Width);
GUI.DrawRectangle(sb, new Rectangle(customComponent.Rect.Center.X, customComponent.Rect.Y, barWidth, customComponent.Rect.Height), GUI.Style.Green, isFilled: true);
}
GUI.DrawLine(sb, new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Y - 2), new Vector2(customComponent.Rect.Center.X, customComponent.Rect.Bottom + 2), factionDescription.TextColor, width: 1);
});
string reputationText = ((int)Math.Round(reputation)).ToString();
int reputationChange = (int)Math.Round( reputation - initialReputation);
if (Math.Abs(reputationChange) > 0)
{
string changeText = $"{(reputationChange > 0 ? "+" : "") + reputationChange}";
string colorStr = XMLExtensions.ColorToString(reputationChange > 0 ? GUI.Style.Green : GUI.Style.Red);
var rtData = RichTextData.GetRichTextData($"{reputationText} (‖color:{colorStr}‖{changeText}‖color:end‖)", out string sanitizedText);
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
rtData, sanitizedText,
textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont);
}
else
{
new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), sliderHolder.RectTransform),
reputationText,
textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont);
}
} }
} }
} }
@@ -0,0 +1,81 @@
#nullable enable
using System;
using System.Linq;
using Barotrauma.Networking;
namespace Barotrauma
{
partial class UpgradeManager
{
partial void UpgradeNPCSpeak(string text, bool isSinglePlayer, Character? character)
{
if (Level.Loaded?.StartOutpost?.Info?.OutpostNPCs == null) { return; }
if (character != null)
{
Speak(character);
return;
}
foreach (Character npc in Level.Loaded.StartOutpost.Info.OutpostNPCs.SelectMany(kpv => kpv.Value))
{
if (npc.CampaignInteractionType == CampaignMode.InteractionType.Upgrade)
{
Speak(npc);
break;
}
}
void Speak(Character npc)
{
ChatMessage message = ChatMessage.Create(npc.Name, text, ChatMessageType.Default, npc);
if (!isSinglePlayer)
{
GameMain.Client?.AddChatMessage(message);
}
else
{
GameMain.GameSession?.CrewManager?.AddSinglePlayerChatMessage(message);
}
}
}
/// <summary>
/// Server has notified us that upgrades were reset.
/// </summary>
/// <param name="inc"></param>
/// <see cref="UpgradeManager.SendUpgradeResetMessage"/>
public void ClientRead(IReadMessage inc)
{
bool shouldReset = inc.ReadBoolean();
int money = inc.ReadInt32();
// uint length = inc.ReadUInt32();
//
// for (int i = 0; i < length; i++)
// {
// string key = inc.ReadString();
// byte value = inc.ReadByte();
// Metadata.SetValue(key, value);
// }
Campaign.Money = money;
if (shouldReset)
{
ResetUpgrades();
}
// spentMoney is local, so this message box should only appear for those who have spent money on upgrades
if (spentMoney > 0)
{
GUIMessageBox msgBox = new GUIMessageBox(TextManager.Get("UpgradeRefundTitle"), TextManager.Get("UpgradeRefundBody"), new [] { TextManager.Get("Ok") });
msgBox.Buttons[0].OnClicked += msgBox.Close;
}
spentMoney = 0;
PendingUpgrades.Clear();
PurchasedUpgrades.Clear();
CanUpgrade = false;
}
}
}
@@ -761,6 +761,44 @@ namespace Barotrauma
RelativeSpacing = 0.01f RelativeSpacing = 0.01f
}; };
#if (!OSX)
AudioDeviceNames = Alc.GetStringList((IntPtr)null, Alc.AllDevicesSpecifier);
if (string.IsNullOrEmpty(AudioOutputDevice))
{
AudioOutputDevice = Alc.GetString((IntPtr)null, Alc.DefaultDeviceSpecifier);
if (AudioDeviceNames.Any() && !AudioDeviceNames.Any(n => n.Equals(AudioOutputDevice, StringComparison.OrdinalIgnoreCase)))
{
AudioOutputDevice = AudioDeviceNames[0];
}
}
var outputDeviceList = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.15f), audioContent.RectTransform), TrimAudioDeviceName(AudioOutputDevice), AudioDeviceNames.Count);
if (AudioDeviceNames?.Count > 0)
{
foreach (string name in AudioDeviceNames)
{
outputDeviceList.AddItem(TrimAudioDeviceName(name), name);
}
outputDeviceList.OnSelected = (GUIComponent selected, object obj) =>
{
string name = obj as string;
if (!GameMain.SoundManager.Disconnected && AudioOutputDevice == name) { return true; }
AudioOutputDevice = name;
GameMain.SoundManager.InitializeAlcDevice(AudioOutputDevice);
return true;
};
}
else
{
outputDeviceList.AddItem(TextManager.Get("AudioNoDevices") ?? "N/A", null);
outputDeviceList.ButtonTextColor = GUI.Style.Red;
outputDeviceList.ButtonEnabled = false;
outputDeviceList.Select(0);
}
#endif
GUITextBlock soundVolumeText = new GUITextBlock(new RectTransform(textBlockScale, audioContent.RectTransform), TextManager.Get("SoundVolume"), font: GUI.SubHeadingFont); GUITextBlock soundVolumeText = new GUITextBlock(new RectTransform(textBlockScale, audioContent.RectTransform), TextManager.Get("SoundVolume"), font: GUI.SubHeadingFont);
GUIScrollBar soundScrollBar = new GUIScrollBar(new RectTransform(textBlockScale, audioContent.RectTransform), GUIScrollBar soundScrollBar = new GUIScrollBar(new RectTransform(textBlockScale, audioContent.RectTransform),
style: "GUISlider", barSize: 0.05f) style: "GUISlider", barSize: 0.05f)
@@ -893,7 +931,7 @@ namespace Barotrauma
deviceList.OnSelected = (GUIComponent selected, object obj) => deviceList.OnSelected = (GUIComponent selected, object obj) =>
{ {
string name = obj as string; string name = obj as string;
if (VoiceCaptureDevice == name) { return true; } if (!(VoipCapture.Instance?.Disconnected ?? true) && VoiceCaptureDevice == name) { return true; }
VoipCapture.ChangeCaptureDevice(name); VoipCapture.ChangeCaptureDevice(name);
return true; return true;
@@ -1179,12 +1217,16 @@ namespace Barotrauma
var inputName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), inputContainer.RectTransform, Anchor.TopLeft) { MinSize = new Point(100, 0) }, var inputName = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1.0f), inputContainer.RectTransform, Anchor.TopLeft) { MinSize = new Point(100, 0) },
TextManager.Get("InputType." + ((InputType)i)), font: GUI.SmallFont) { ForceUpperCase = true }; TextManager.Get("InputType." + ((InputType)i)), font: GUI.SmallFont) { ForceUpperCase = true };
inputNameBlocks.Add(inputName); inputNameBlocks.Add(inputName);
string keyText = KeyBindText((InputType)i);
var keyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), inputContainer.RectTransform), var keyBox = new GUITextBox(new RectTransform(new Vector2(0.4f, 1.0f), inputContainer.RectTransform),
text: KeyBindText((InputType)i), font: GUI.SmallFont, style: "GUITextBoxNoIcon") text: keyText, font: GUI.SmallFont, style: "GUITextBoxNoIcon")
{ {
UserData = i UserData = i
}; };
keyBox.Text = ToolBox.LimitString(keyBox.Text, keyBox.Font, (int)(keyBox.Rect.Width - keyBox.Padding.X - keyBox.Padding.Z)); keyBox.RectTransform.SizeChanged += () =>
{
keyBox.Text = ToolBox.LimitString(keyText, keyBox.Font, (int)(keyBox.Rect.Width - keyBox.Padding.X - keyBox.Padding.Z));
};
keyBox.OnSelected += KeyBoxSelected; keyBox.OnSelected += KeyBoxSelected;
keyBox.SelectedColor = Color.Gold * 0.3f; keyBox.SelectedColor = Color.Gold * 0.3f;
} }
@@ -1337,6 +1379,16 @@ namespace Barotrauma
return true; return true;
}; };
var automaticCampaignLoadTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Automatic campaign load enabled", style: "GUITickBox");
automaticCampaignLoadTickBox.Selected = AutomaticCampaignLoadEnabled;
automaticCampaignLoadTickBox.ToolTip = "Will the game automatically load the latest campaign save when the game is launched";
automaticCampaignLoadTickBox.OnSelected = (tickBox) =>
{
AutomaticCampaignLoadEnabled = tickBox.Selected;
UnsavedSettings = true;
return true;
};
var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Splash screen enabled", style: "GUITickBox"); var showSplashScreenTickBox = new GUITickBox(new RectTransform(tickBoxScale / 0.18f, debugTickBoxes.RectTransform, scaleBasis: ScaleBasis.BothHeight), "Splash screen enabled", style: "GUITickBox");
showSplashScreenTickBox.Selected = EnableSplashScreen; showSplashScreenTickBox.Selected = EnableSplashScreen;
showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched"; showSplashScreenTickBox.ToolTip = "Are the splash screens shown when the game is launched";
@@ -1520,8 +1572,7 @@ namespace Barotrauma
{ {
return GameMain.Client == null && return GameMain.Client == null &&
(ContentPackage.IngameModSwap || (ContentPackage.IngameModSwap ||
(Screen.Selected != GameMain.GameScreen && Screen.Selected != GameMain.GameScreen &&
Screen.Selected != GameMain.LobbyScreen) &&
Screen.Selected != GameMain.SubEditorScreen) && Screen.Selected != GameMain.SubEditorScreen) &&
(!core || (!core ||
(Screen.Selected != GameMain.CharacterEditorScreen && (Screen.Selected != GameMain.CharacterEditorScreen &&
@@ -35,6 +35,26 @@ namespace Barotrauma
} }
private static Dictionary<InvSlotType, Sprite> limbSlotIcons; private static Dictionary<InvSlotType, Sprite> limbSlotIcons;
public static Dictionary<InvSlotType, Sprite> LimbSlotIcons
{
get
{
if (limbSlotIcons == null)
{
limbSlotIcons = new Dictionary<InvSlotType, Sprite>();
int margin = 2;
limbSlotIcons.Add(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128)));
limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128)));
limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
}
return limbSlotIcons;
}
}
public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head; public const InvSlotType PersonalSlots = InvSlotType.Card | InvSlotType.Headset | InvSlotType.InnerClothes | InvSlotType.OuterClothes | InvSlotType.Head;
@@ -88,7 +108,7 @@ namespace Barotrauma
indicatorGroup = new GUILayoutGroup(new RectTransform(Point.Zero, hideButton.RectTransform)) { IsHorizontal = false }; indicatorGroup = new GUILayoutGroup(new RectTransform(Point.Zero, hideButton.RectTransform)) { IsHorizontal = false };
indicatorGroup.ChildAnchor = Anchor.TopCenter; indicatorGroup.ChildAnchor = Anchor.TopCenter;
indicatorSpriteSize = GUI.Style.GetComponentStyle("EquipmentIndicatorDivingSuit").Sprites[GUIComponent.ComponentState.None][0].Sprite.size; indicatorSpriteSize = GUI.Style.GetComponentStyle("EquipmentIndicatorDivingSuit").GetDefaultSprite().size;
indicators[0] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorDivingSuit"); indicators[0] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorDivingSuit");
indicators[1] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorID"); indicators[1] = new GUIImage(new RectTransform(Point.Zero, indicatorGroup.RectTransform), "EquipmentIndicatorID");
@@ -115,20 +135,6 @@ namespace Barotrauma
hidePersonalSlots = false; hidePersonalSlots = false;
if (limbSlotIcons == null)
{
limbSlotIcons = new Dictionary<InvSlotType, Sprite>();
int margin = 2;
limbSlotIcons.Add(InvSlotType.Headset, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(384 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.InnerClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(512 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.Card, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(640 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.Head, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(896 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
limbSlotIcons.Add(InvSlotType.LeftHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(634, 0, 128, 128)));
limbSlotIcons.Add(InvSlotType.RightHand, new Sprite("Content/UI/InventoryUIAtlas.png", new Rectangle(762, 0, 128, 128)));
limbSlotIcons.Add(InvSlotType.OuterClothes, new Sprite("Content/UI/MainIconsAtlas.png", new Rectangle(256 + margin, 128 + margin, 128 - margin * 2, 128 - margin * 2)));
}
SlotPositions = new Vector2[SlotTypes.Length]; SlotPositions = new Vector2[SlotTypes.Length];
CurrentLayout = Layout.Default; CurrentLayout = Layout.Default;
SetSlotPositions(layout); SetSlotPositions(layout);
@@ -522,14 +528,7 @@ namespace Barotrauma
if (hoverOnInventory) { HideTimer = 0.5f; } if (hoverOnInventory) { HideTimer = 0.5f; }
if (HideTimer > 0.0f) { HideTimer -= deltaTime; } if (HideTimer > 0.0f) { HideTimer -= deltaTime; }
for (int i = 0; i < capacity; i++) UpdateSlotInput();
{
if (Items[i] != null && Items[i] != draggingItem && Character.Controlled?.Inventory == this &&
GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(slots[i].InventoryKeyIndex))
{
QuickUseItem(Items[i], true, false, true);
}
}
//force personal slots open if an item is running out of battery/fuel/oxygen/etc //force personal slots open if an item is running out of battery/fuel/oxygen/etc
if (hidePersonalSlots) if (hidePersonalSlots)
@@ -685,6 +684,18 @@ namespace Barotrauma
doubleClickedItem = null; doubleClickedItem = null;
} }
public void UpdateSlotInput()
{
for (int i = 0; i < capacity; i++)
{
if (Items[i] != null && Items[i] != draggingItem && Character.Controlled?.Inventory == this &&
GUI.KeyboardDispatcher.Subscriber == null && !CrewManager.IsCommandInterfaceOpen && PlayerInput.InventoryKeyHit(slots[i].InventoryKeyIndex))
{
QuickUseItem(Items[i], true, false, true);
}
}
}
private void HandleButtonEquipStates(Item item, InventorySlot slot, float deltaTime) private void HandleButtonEquipStates(Item item, InventorySlot slot, float deltaTime)
{ {
slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ? slot.EquipButtonState = slot.EquipButtonRect.Contains(PlayerInput.MousePosition) ?
@@ -1084,9 +1095,9 @@ namespace Barotrauma
!Items[i].AllowedSlots.Any(a => a != InvSlotType.Any)) !Items[i].AllowedSlots.Any(a => a != InvSlotType.Any))
{ {
//draw limb icons on empty slots //draw limb icons on empty slots
if (limbSlotIcons.ContainsKey(SlotTypes[i])) if (LimbSlotIcons.ContainsKey(SlotTypes[i]))
{ {
var icon = limbSlotIcons[SlotTypes[i]]; var icon = LimbSlotIcons[SlotTypes[i]];
icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, GUI.Style.EquipmentSlotIconColor, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X); icon.Draw(spriteBatch, slots[i].Rect.Center.ToVector2() + slots[i].DrawOffset, GUI.Style.EquipmentSlotIconColor, origin: icon.size / 2, scale: slots[i].Rect.Width / icon.size.X);
} }
continue; continue;
@@ -1096,12 +1107,12 @@ namespace Barotrauma
//draw hand icons if the item is equipped in a hand slot //draw hand icons if the item is equipped in a hand slot
if (IsInLimbSlot(Items[i], InvSlotType.LeftHand)) if (IsInLimbSlot(Items[i], InvSlotType.LeftHand))
{ {
var icon = limbSlotIcons[InvSlotType.LeftHand]; var icon = LimbSlotIcons[InvSlotType.LeftHand];
icon.Draw(spriteBatch, new Vector2(slots[i].Rect.X, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); icon.Draw(spriteBatch, new Vector2(slots[i].Rect.X, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.35f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f);
} }
if (IsInLimbSlot(Items[i], InvSlotType.RightHand)) if (IsInLimbSlot(Items[i], InvSlotType.RightHand))
{ {
var icon = limbSlotIcons[InvSlotType.RightHand]; var icon = LimbSlotIcons[InvSlotType.RightHand];
icon.Draw(spriteBatch, new Vector2(slots[i].Rect.Right, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f); icon.Draw(spriteBatch, new Vector2(slots[i].Rect.Right, slots[i].Rect.Bottom) + slots[i].DrawOffset, Color.White * 0.6f, origin: new Vector2(icon.size.X * 0.65f, icon.size.Y * 0.75f), scale: slots[i].Rect.Width / icon.size.X * 0.7f);
} }
@@ -48,6 +48,8 @@ namespace Barotrauma.Items.Components
private void UpdateConvexHulls() private void UpdateConvexHulls()
{ {
if (item.Removed) { return; }
doorRect = new Rectangle( doorRect = new Rectangle(
item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale), item.Rect.Center.X - (int)(doorSprite.size.X / 2 * item.Scale),
item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale), item.Rect.Y - item.Rect.Height / 2 + (int)(doorSprite.size.Y / 2.0f * item.Scale),
@@ -93,7 +95,7 @@ namespace Barotrauma.Items.Components
} }
} }
if (convexHull == null) return; if (convexHull == null) { return; }
if (rect.Height == 0 || rect.Width == 0) if (rect.Height == 0 || rect.Width == 0)
{ {
@@ -128,7 +130,7 @@ namespace Barotrauma.Items.Components
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
color *= (item.Condition / item.Prefab.Health); color *= (item.Condition / item.MaxCondition);
color.A = 255; color.A = 255;
} }
@@ -162,10 +164,10 @@ namespace Barotrauma.Items.Components
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth); color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
} }
if (brokenSprite != null && item.Health < item.Prefab.Health) if (brokenSprite != null && item.Health < item.MaxCondition)
{ {
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.Prefab.Health) : Vector2.One; Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f, 1.0f - item.Health / item.MaxCondition) : Vector2.One;
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f; float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos, spriteBatch.Draw(brokenSprite.Texture, pos,
new Rectangle((int)(brokenSprite.SourceRect.X + brokenSprite.size.X * openState), brokenSprite.SourceRect.Y, new Rectangle((int)(brokenSprite.SourceRect.X + brokenSprite.size.X * openState), brokenSprite.SourceRect.Y,
(int)(brokenSprite.size.X * (1.0f - openState)), (int)brokenSprite.size.Y), (int)(brokenSprite.size.X * (1.0f - openState)), (int)brokenSprite.size.Y),
@@ -188,10 +190,10 @@ namespace Barotrauma.Items.Components
color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth); color, 0.0f, doorSprite.Origin, item.Scale, SpriteEffects.None, doorSprite.Depth);
} }
if (brokenSprite != null && item.Health < item.Prefab.Health) if (brokenSprite != null && item.Health < item.MaxCondition)
{ {
Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.Prefab.Health, 1.0f) : Vector2.One; Vector2 scale = scaleBrokenSprite ? new Vector2(1.0f - item.Health / item.MaxCondition, 1.0f) : Vector2.One;
float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.Prefab.Health : 1.0f; float alpha = fadeBrokenSprite ? 1.0f - item.Health / item.MaxCondition : 1.0f;
spriteBatch.Draw(brokenSprite.Texture, pos, spriteBatch.Draw(brokenSprite.Texture, pos,
new Rectangle(brokenSprite.SourceRect.X, (int)(brokenSprite.SourceRect.Y + brokenSprite.size.Y * openState), new Rectangle(brokenSprite.SourceRect.X, (int)(brokenSprite.SourceRect.Y + brokenSprite.size.Y * openState),
(int)brokenSprite.size.X, (int)(brokenSprite.size.Y * (1.0f - openState))), (int)brokenSprite.size.X, (int)(brokenSprite.size.Y * (1.0f - openState))),
@@ -114,6 +114,7 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific() protected override void RemoveComponentSpecific()
{ {
base.RemoveComponentSpecific();
crosshairSprite?.Remove(); crosshairSprite?.Remove();
crosshairSprite = null; crosshairSprite = null;
crosshairPointerSprite?.Remove(); crosshairPointerSprite?.Remove();
@@ -186,7 +186,6 @@ namespace Barotrauma.Items.Components
} }
} }
private bool shouldMuffleLooping; private bool shouldMuffleLooping;
private float lastMuffleCheckTime; private float lastMuffleCheckTime;
private ItemSound loopingSound; private ItemSound loopingSound;
@@ -295,8 +294,6 @@ namespace Barotrauma.Items.Components
PlaySound(matchingSounds[index], item.WorldPosition); PlaySound(matchingSounds[index], item.WorldPosition);
} }
} }
private void PlaySound(ItemSound itemSound, Vector2 position) private void PlaySound(ItemSound itemSound, Vector2 position)
{ {
if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range) if (Vector2.DistanceSquared(new Vector2(GameMain.SoundManager.ListenerPosition.X, GameMain.SoundManager.ListenerPosition.Y), position) > itemSound.Range * itemSound.Range)
@@ -387,7 +384,6 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
public ItemComponent GetLinkUIToComponent() public ItemComponent GetLinkUIToComponent()
{ {
if (string.IsNullOrEmpty(LinkUIToComponent)) if (string.IsNullOrEmpty(LinkUIToComponent))
@@ -431,13 +427,8 @@ namespace Barotrauma.Items.Components
DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - GUIFrame defined as rect, use RectTransform instead."); DebugConsole.ThrowError("Error in item config \"" + item.ConfigFile + "\" - GUIFrame defined as rect, use RectTransform instead.");
break; break;
} }
GuiFrameSource = subElement;
Color? color = null; ReloadGuiFrame();
if (subElement.Attribute("color") != null) color = subElement.GetAttributeColor("color", Color.White);
string style = subElement.Attribute("style") == null ?
null : subElement.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(subElement, GUI.Canvas.ItemComponentHolder, Anchor.Center), style, color);
DefaultLayout = GUILayoutSettings.Load(subElement);
break; break;
case "alternativelayout": case "alternativelayout":
AlternativeLayout = GUILayoutSettings.Load(subElement); AlternativeLayout = GUILayoutSettings.Load(subElement);
@@ -501,6 +492,42 @@ namespace Barotrauma.Items.Components
return true; //element processed return true; //element processed
} }
private XElement GuiFrameSource;
protected void ReleaseGuiFrame()
{
if (GuiFrame != null)
{
GuiFrame.RectTransform.Parent = null;
}
}
protected void ReloadGuiFrame()
{
if (GuiFrame != null)
{
ReleaseGuiFrame();
}
Color? color = null;
if (GuiFrameSource.Attribute("color") != null)
{
color = GuiFrameSource.GetAttributeColor("color", Color.White);
}
string style = GuiFrameSource.Attribute("style") == null ? null : GuiFrameSource.GetAttributeString("style", "");
GuiFrame = new GUIFrame(RectTransform.Load(GuiFrameSource, GUI.Canvas.ItemComponentHolder, Anchor.Center), style, color);
DefaultLayout = GUILayoutSettings.Load(GuiFrameSource);
if (GuiFrame != null)
{
GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged;
}
GameMain.Instance.ResolutionChanged += OnResolutionChanged;
}
/// <summary>
/// Overload this method and implement. The method is automatically called when the resolution changes.
/// </summary>
protected virtual void CreateGUI() { }
//Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero. //Starts a coroutine that will read the correct state of the component from the NetBuffer when correctionTimer reaches zero.
protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false) protected void StartDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync = false)
{ {
@@ -530,5 +557,29 @@ namespace Barotrauma.Items.Components
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
/// <summary>
/// Launches when the parent of the GuiFrame is changed.
/// </summary>
protected void OnGUIParentChanged(RectTransform newParent)
{
if (newParent == null)
{
// Make sure to unregister. It doesn't matter if we haven't ever registered to the event.
GameMain.Instance.ResolutionChanged -= OnResolutionChangedPrivate;
}
}
protected virtual void OnResolutionChanged() { }
private void OnResolutionChangedPrivate()
{
if (RecreateGUIOnResolutionChange)
{
ReloadGuiFrame();
CreateGUI();
}
OnResolutionChanged();
}
} }
} }
@@ -13,8 +13,6 @@ namespace Barotrauma.Items.Components
private GUICustomComponent guiCustomComponent; private GUICustomComponent guiCustomComponent;
private Point prevResolution;
public Sprite InventoryTopSprite public Sprite InventoryTopSprite
{ {
get { return inventoryTopSprite; } get { return inventoryTopSprite; }
@@ -115,16 +113,16 @@ namespace Barotrauma.Items.Components
{ {
CanBeFocused = false CanBeFocused = false
}; };
GuiFrame.RectTransform.ParentChanged += OnGUIParentChanged;
} }
else else
{ {
//if a GUIFrame has been defined, draw the inventory inside it //if a GUIFrame has been defined, draw the inventory inside it
CreateGUI(); CreateGUI();
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
} }
} }
private void CreateGUI() protected override void CreateGUI()
{ {
var content = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, var content = new GUIFrame(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
style: null) style: null)
@@ -167,16 +165,6 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (hideItems || (item.body != null && !item.body.Enabled)) { return; } if (hideItems || (item.body != null && !item.body.Enabled)) { return; }
if ((prevResolution.X > 0 && prevResolution.Y > 0) &&
(prevResolution.X != GameMain.GraphicsWidth || prevResolution.Y != GameMain.GraphicsHeight))
{
GuiFrame.ClearChildren();
CreateGUI();
prevResolution = new Point(GameMain.GraphicsWidth, GameMain.GraphicsHeight);
}
DrawContainedItems(spriteBatch, itemDepth); DrawContainedItems(spriteBatch, itemDepth);
} }
@@ -238,7 +226,7 @@ namespace Barotrauma.Items.Components
if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; } if (item.FlippedY) { origin.Y = containedItem.Sprite.SourceRect.Height - origin.Y; }
float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth; float containedSpriteDepth = ContainedSpriteDepth < 0.0f ? containedItem.Sprite.Depth : ContainedSpriteDepth;
containedSpriteDepth = itemDepth + (containedSpriteDepth - item.SpriteDepth) / 10000.0f; containedSpriteDepth = itemDepth + (containedSpriteDepth - (item.Sprite?.Depth ?? item.SpriteDepth)) / 10000.0f;
containedItem.Sprite.Draw( containedItem.Sprite.Draw(
spriteBatch, spriteBatch,
@@ -34,7 +34,7 @@ namespace Barotrauma.Items.Components
get { return text; } get { return text; }
set set
{ {
if (value == text || item.Rect.Width < 5) return; if (value == text || item.Rect.Width < 5) { return; }
if (TextBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height) if (TextBlock.Rect.Width != item.Rect.Width || textBlock.Rect.Height != item.Rect.Height)
{ {
@@ -64,7 +64,7 @@ namespace Barotrauma.Items.Components
get { return textColor; } get { return textColor; }
set set
{ {
if (textBlock != null) textBlock.TextColor = value; if (textBlock != null) { textBlock.TextColor = value; }
textColor = value; textColor = value;
} }
} }
@@ -75,7 +75,7 @@ namespace Barotrauma.Items.Components
get { return textBlock == null ? 1.0f : textBlock.TextScale; } get { return textBlock == null ? 1.0f : textBlock.TextScale; }
set set
{ {
if (textBlock != null) textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f); if (textBlock != null) { textBlock.TextScale = MathHelper.Clamp(value, 0.1f, 10.0f); }
} }
} }
@@ -107,7 +107,7 @@ namespace Barotrauma.Items.Components
if (textBlock == null) if (textBlock == null)
{ {
textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "", textBlock = new GUITextBlock(new RectTransform(item.Rect.Size), "",
textColor: textColor, font: GUI.UnscaledSmallFont, textAlignment: Alignment.Center, wrap: true, style: null) textColor: textColor, font: GUI.UnscaledSmallFont, textAlignment: scrollable ? Alignment.CenterLeft : Alignment.Center, wrap: true, style: null)
{ {
TextDepth = item.SpriteDepth - 0.00001f, TextDepth = item.SpriteDepth - 0.00001f,
RoundToNearestPixel = false, RoundToNearestPixel = false,
@@ -27,8 +27,9 @@ namespace Barotrauma.Items.Components
if (backgroundSprite == null) { return; } if (backgroundSprite == null) { return; }
backgroundSprite.DrawTiled(spriteBatch, backgroundSprite.DrawTiled(spriteBatch,
new Vector2(item.DrawPosition.X - item.Rect.Width / 2, -(item.DrawPosition.Y + item.Rect.Height / 2)) - backgroundSprite.Origin, new Vector2(item.DrawPosition.X - item.Rect.Width / 2 * item.Scale, -(item.DrawPosition.Y + item.Rect.Height / 2)) - backgroundSprite.Origin * item.Scale,
new Vector2(backgroundSprite.size.X, item.Rect.Height), color: item.Color, new Vector2(backgroundSprite.size.X * item.Scale, item.Rect.Height), color: item.Color,
textureScale: Vector2.One * item.Scale,
depth: BackgroundSpriteDepth); depth: BackgroundSpriteDepth);
} }
@@ -1,5 +1,6 @@
using Barotrauma.Networking; using Barotrauma.Networking;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.ComponentModel;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -74,6 +75,21 @@ namespace Barotrauma.Items.Components
} }
} }
#if DEBUG
public override void CreateEditingHUD(SerializableEntityEditor editor)
{
base.CreateEditingHUD(editor);
foreach (LimbPos limbPos in limbPositions)
{
PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(limbPos);
PropertyDescriptor limbPosProperty = properties.Find("Position", false);
editor.CreateVector2Field(limbPos, new SerializableProperty(limbPosProperty), limbPos.Position, limbPos.LimbType.ToString(), "");
}
}
#endif
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
State = msg.ReadBoolean(); State = msg.ReadBoolean();
@@ -23,17 +23,15 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
CreateGUI(); CreateGUI();
GameMain.Instance.OnResolutionChanged += RecreateGUI;
} }
private void RecreateGUI() protected override void OnResolutionChanged()
{ {
GuiFrame.ClearChildren(); base.OnResolutionChanged();
CreateGUI();
OnItemLoadedProjSpecific(); OnItemLoadedProjSpecific();
} }
private 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.90f, 0.80f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -170,10 +168,5 @@ namespace Barotrauma.Items.Components
SetActive(msg.ReadBoolean()); SetActive(msg.ReadBoolean());
progressTimer = msg.ReadSingle(); progressTimer = msg.ReadSingle();
} }
protected override void RemoveComponentSpecific()
{
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
}
} }
} }
@@ -42,17 +42,15 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific() partial void InitProjSpecific()
{ {
CreateGUI(); CreateGUI();
GameMain.Instance.OnResolutionChanged += RecreateGUI;
} }
private void RecreateGUI() protected override void OnResolutionChanged()
{ {
GuiFrame.ClearChildren(); base.OnResolutionChanged();
CreateGUI();
OnItemLoadedProjSpecific(); OnItemLoadedProjSpecific();
} }
private void CreateGUI() protected override void CreateGUI()
{ {
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter); var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.95f, 0.9f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter);
@@ -242,8 +240,8 @@ namespace Barotrauma.Items.Components
var item1 = c1.GUIComponent.UserData as FabricationRecipe; var item1 = c1.GUIComponent.UserData as FabricationRecipe;
var item2 = c2.GUIComponent.UserData as FabricationRecipe; var item2 = c2.GUIComponent.UserData as FabricationRecipe;
bool hasSkills1 = DegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f; bool hasSkills1 = FabricationDegreeOfSuccess(character, item1.RequiredSkills) >= 0.5f;
bool hasSkills2 = DegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f; bool hasSkills2 = FabricationDegreeOfSuccess(character, item2.RequiredSkills) >= 0.5f;
if (hasSkills1 != hasSkills2) if (hasSkills1 != hasSkills2)
{ {
@@ -267,7 +265,7 @@ namespace Barotrauma.Items.Components
AutoScaleHorizontal = true, AutoScaleHorizontal = true,
CanBeFocused = false CanBeFocused = false
}; };
var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && DegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f); var firstinSufficient = itemList.Content.Children.FirstOrDefault(c => c.UserData is FabricationRecipe fabricableItem && FabricationDegreeOfSuccess(character, fabricableItem.RequiredSkills) < 0.5f);
if (firstinSufficient != null) if (firstinSufficient != null)
{ {
insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform)); insufficientSkillsText.RectTransform.RepositionChildInHierarchy(itemList.Content.RectTransform.GetChildIndex(firstinSufficient.RectTransform));
@@ -476,7 +474,7 @@ namespace Barotrauma.Items.Components
List<Skill> inadequateSkills = new List<Skill>(); List<Skill> inadequateSkills = new List<Skill>();
if (user != null) if (user != null)
{ {
inadequateSkills = selectedItem.RequiredSkills.FindAll(skill => user.GetSkillLevel(skill.Identifier) < skill.Level); inadequateSkills = selectedItem.RequiredSkills.FindAll(skill => user.GetSkillLevel(skill.Identifier) < Math.Round(skill.Level * SkillRequirementMultiplier));
} }
if (selectedItem.RequiredSkills.Any()) if (selectedItem.RequiredSkills.Any())
@@ -489,13 +487,13 @@ namespace Barotrauma.Items.Components
}; };
foreach (Skill skill in selectedItem.RequiredSkills) foreach (Skill skill in selectedItem.RequiredSkills)
{ {
text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + skill.Level; text += TextManager.Get("SkillName." + skill.Identifier) + " " + TextManager.Get("Lvl").ToLower() + " " + Math.Round(skill.Level * SkillRequirementMultiplier);
if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; } if (skill != selectedItem.RequiredSkills.Last()) { text += "\n"; }
} }
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUI.SmallFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), paddedReqFrame.RectTransform), text, font: GUI.SmallFont);
} }
float degreeOfSuccess = user == null ? 0.0f : DegreeOfSuccess(user, selectedItem.RequiredSkills); float degreeOfSuccess = user == null ? 0.0f : FabricationDegreeOfSuccess(user, selectedItem.RequiredSkills);
if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; } if (degreeOfSuccess > 0.5f) { degreeOfSuccess = 1.0f; }
float requiredTime = user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user); float requiredTime = user == null ? selectedItem.RequiredTime : GetRequiredTime(selectedItem, user);
@@ -621,10 +619,5 @@ namespace Barotrauma.Items.Components
StartFabricating(fabricationRecipes[itemIndex], user); StartFabricating(fabricationRecipes[itemIndex], user);
} }
} }
protected override void RemoveComponentSpecific()
{
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
}
} }
} }
@@ -24,7 +24,17 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
noPowerTip = TextManager.Get("SteeringNoPowerTip"); noPowerTip = TextManager.Get("SteeringNoPowerTip");
CreateGUI();
}
protected override void OnResolutionChanged()
{
base.OnResolutionChanged();
CreateHUD();
}
protected override void CreateGUI()
{
GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f); GuiFrame.RectTransform.RelativeOffset = new Vector2(0.05f, 0.0f);
GuiFrame.CanBeFocused = true; GuiFrame.CanBeFocused = true;
new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset }, new GUICustomComponent(new RectTransform(GuiFrame.Rect.Size - GUIStyle.ItemFrameMargin, GuiFrame.RectTransform, Anchor.Center) { AbsoluteOffset = GUIStyle.ItemFrameOffset },
@@ -53,7 +63,11 @@ namespace Barotrauma.Items.Components
hullAirQualityText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; hullAirQualityText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true };
hullWaterText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true }; hullWaterText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.3f), hullInfoContainer.RectTransform), "") { Wrap = true };
hullInfoFrame.Children.ForEach(c => { c.CanBeFocused = false; c.Children.ForEach(c2 => c2.CanBeFocused = false); }); hullInfoFrame.Children.ForEach(c =>
{
c.CanBeFocused = false;
c.Children.ForEach(c2 => c2.CanBeFocused = false);
});
} }
public override void AddToGUIUpdateList() public override void AddToGUIUpdateList()
@@ -72,7 +86,7 @@ namespace Barotrauma.Items.Components
{ {
submarineContainer.ClearChildren(); submarineContainer.ClearChildren();
if (item.Submarine == null) return; if (item.Submarine == null) { return; }
item.Submarine.CreateMiniMap(submarineContainer); item.Submarine.CreateMiniMap(submarineContainer);
displayedSubs.Clear(); displayedSubs.Clear();
@@ -125,10 +139,8 @@ namespace Barotrauma.Items.Components
GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f, font: GUI.SubHeadingFont); GUI.Style.Orange * (float)Math.Abs(Math.Sin(Timing.TotalTime)), Color.Black * 0.8f, font: GUI.SubHeadingFont);
return; return;
} }
if (!submarineContainer.Children.Any()) { return; } if (!submarineContainer.Children.Any()) { return; }
foreach (GUIComponent child in submarineContainer.Children.FirstOrDefault()?.Children)
foreach (GUIComponent child in submarineContainer.Children.First().Children)
{ {
if (child.UserData is Hull hull) if (child.UserData is Hull hull)
{ {
@@ -177,9 +189,19 @@ namespace Barotrauma.Items.Components
HashSet<Submarine> subs = new HashSet<Submarine>(); HashSet<Submarine> subs = new HashSet<Submarine>();
foreach (Hull hull in Hull.hullList) foreach (Hull hull in Hull.hullList)
{ {
if (hull.Submarine == null) continue; if (hull.Submarine == null) { continue; }
var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull); var hullFrame = submarineContainer.Children.FirstOrDefault()?.FindChild(hull);
if (hullFrame == null) continue; if (hullFrame == null) { continue; }
hullFrame.Visible = true;
if (!submarineContainer.Rect.Contains(hullFrame.Rect))
{
if (hull.Submarine.Info.Type != SubmarineType.Player)
{
hullFrame.Visible = false;
continue;
}
}
hullDatas.TryGetValue(hull, out HullData hullData); hullDatas.TryGetValue(hull, out HullData hullData);
if (hullData == null) if (hullData == null)
@@ -294,7 +316,7 @@ namespace Barotrauma.Items.Components
foreach (Submarine sub in subs) foreach (Submarine sub in subs)
{ {
if (sub.HullVertices == null) { continue; } if (sub.HullVertices == null || sub.Info.IsOutpost) { continue; }
Rectangle worldBorders = sub.GetDockedBorders(); Rectangle worldBorders = sub.GetDockedBorders();
worldBorders.Location += sub.WorldPosition.ToPoint(); worldBorders.Location += sub.WorldPosition.ToPoint();
@@ -0,0 +1,42 @@

namespace Barotrauma.Items.Components
{
partial class OutpostTerminal : ItemComponent
{
private SubmarineSelection selectionUI;
public override bool Select(Character character)
{
if (GameMain.GameSession?.Campaign == null)
{
return false;
}
if (selectionUI == null)
{
selectionUI = new SubmarineSelection(true, null, GUICanvas.Instance.ItemComponentHolder);
}
GuiFrame = selectionUI.GuiFrame;
selectionUI.RefreshSubmarineDisplay(true);
IsActive = true;
return base.Select(character);
}
public override void Update(float deltaTime, Camera cam)
{
if (Character.Controlled?.SelectedConstruction != item)
{
IsActive = false;
return;
}
base.Update(deltaTime, cam);
if (selectionUI != null)
{
selectionUI.Update();
}
}
}
}
@@ -157,16 +157,15 @@ namespace Barotrauma.Items.Components
} }
} }
CreateGUI(); CreateGUI();
GameMain.Instance.OnResolutionChanged += RecreateGUI;
} }
private void RecreateGUI() protected override void OnResolutionChanged()
{ {
GuiFrame.ClearChildren(); base.OnResolutionChanged();
CreateGUI(); UpdateGUIElements();
} }
private void CreateGUI() protected override void CreateGUI()
{ {
bool isConnectedToSteering = item.GetComponent<Steering>() != null; bool isConnectedToSteering = item.GetComponent<Steering>() != null;
Vector2 size = isConnectedToSteering ? controlBoxSize : new Vector2(controlBoxSize.X * 2.0f, controlBoxSize.Y); Vector2 size = isConnectedToSteering ? controlBoxSize : new Vector2(controlBoxSize.X * 2.0f, controlBoxSize.Y);
@@ -667,23 +666,27 @@ namespace Barotrauma.Items.Components
signalWarningText.Visible = false; signalWarningText.Visible = false;
} }
if (GameMain.GameSession == null) { return; } if (GameMain.GameSession == null || Level.Loaded == null) { return; }
if (Level.Loaded == null) { return; }
if (Level.Loaded.StartLocation != null)
{
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
GameMain.GameSession.StartLocation.Name, Level.Loaded.StartLocation.Name,
"outpost", "outpost",
GameMain.GameSession.StartLocation.Name, Level.Loaded.StartLocation.Name,
Level.Loaded.StartPosition, transducerCenter, Level.Loaded.StartPosition, transducerCenter,
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
}
if (Level.Loaded.EndLocation != null && Level.Loaded.Type == LevelData.LevelType.LocationConnection)
{
DrawMarker(spriteBatch, DrawMarker(spriteBatch,
GameMain.GameSession.EndLocation.Name, Level.Loaded.EndLocation.Name,
"outpost", "outpost",
GameMain.GameSession.EndLocation.Name, Level.Loaded.EndLocation.Name,
Level.Loaded.EndPosition, transducerCenter, Level.Loaded.EndPosition, transducerCenter,
displayScale, center, DisplayRadius); displayScale, center, DisplayRadius);
}
foreach (AITarget aiTarget in AITarget.List) foreach (AITarget aiTarget in AITarget.List)
{ {
@@ -1444,8 +1447,6 @@ namespace Barotrauma.Items.Components
sprite.Remove(); sprite.Remove();
} }
targetIcons.Clear(); targetIcons.Clear();
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
} }
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
@@ -53,6 +53,8 @@ namespace Barotrauma.Items.Components
private bool? swapDestinationOrder; private bool? swapDestinationOrder;
private GUIMessageBox enterOutpostPrompt;
private bool levelStartSelected; private bool levelStartSelected;
public bool LevelStartSelected public bool LevelStartSelected
{ {
@@ -105,10 +107,9 @@ namespace Barotrauma.Items.Components
} }
} }
CreateGUI(); CreateGUI();
GameMain.Instance.OnResolutionChanged += RecreateGUI;
} }
private void CreateGUI() protected override void CreateGUI()
{ {
controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterLeft), "ItemUI"); controlContainer = new GUIFrame(new RectTransform(new Vector2(Sonar.controlBoxSize.X, 1 - Sonar.controlBoxSize.Y * 2), GuiFrame.RectTransform, Anchor.CenterLeft), "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)
@@ -220,11 +221,12 @@ namespace Barotrauma.Items.Components
}; };
levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter), levelEndTickBox = new GUITickBox(new RectTransform(new Vector2(1, 0.333f), paddedAutoPilotControls.RectTransform, Anchor.BottomCenter),
GameMain.GameSession?.EndLocation == null ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, textLimit), (GameMain.GameSession?.EndLocation == null || Level.IsLoadedOutpost) ? "" : ToolBox.LimitString(GameMain.GameSession.EndLocation.Name, textLimit),
font: GUI.SmallFont, style: "GUIRadioButton") font: GUI.SmallFont, style: "GUIRadioButton")
{ {
Enabled = autoPilot, Enabled = autoPilot,
Selected = levelEndSelected, Selected = levelEndSelected,
Visible = GameMain.GameSession?.EndLocation != null,
OnSelected = tickBox => OnSelected = tickBox =>
{ {
if (levelEndSelected != tickBox.Selected) if (levelEndSelected != tickBox.Selected)
@@ -321,7 +323,7 @@ namespace Barotrauma.Items.Components
}; };
break; break;
} }
new GUITextBlock(new RectTransform(Vector2.One, left.RectTransform), leftText, font: GUI.SubHeadingFont, wrap: true, textAlignment: Alignment.CenterRight); new GUITextBlock(new RectTransform(Vector2.One, left.RectTransform), leftText, font: GUI.SubHeadingFont, wrap: leftText.Contains(' '), textAlignment: Alignment.CenterRight);
new GUITextBlock(new RectTransform(Vector2.One, center.RectTransform), centerText, font: GUI.Font, textAlignment: Alignment.Center) { Padding = Vector4.Zero }; new GUITextBlock(new RectTransform(Vector2.One, center.RectTransform), centerText, font: GUI.Font, textAlignment: Alignment.Center) { Padding = Vector4.Zero };
var digitalFrame = new GUIFrame(new RectTransform(Vector2.One, right.RectTransform), style: "DigitalFrameDark"); var digitalFrame = new GUIFrame(new RectTransform(Vector2.One, right.RectTransform), style: "DigitalFrameDark");
new GUITextBlock(new RectTransform(Vector2.One * 0.85f, digitalFrame.RectTransform, Anchor.Center), "12345", GUI.Style.TextColorDark, GUI.DigitalFont, Alignment.CenterRight) new GUITextBlock(new RectTransform(Vector2.One * 0.85f, digitalFrame.RectTransform, Anchor.Center), "12345", GUI.Style.TextColorDark, GUI.DigitalFont, Alignment.CenterRight)
@@ -346,6 +348,37 @@ namespace Barotrauma.Items.Components
dockingButton = new GUIButton(new RectTransform(new Vector2(elementScale), dockingContainer.RectTransform, Anchor.Center), dockText, style: "PowerButton") dockingButton = new GUIButton(new RectTransform(new Vector2(elementScale), dockingContainer.RectTransform, Anchor.Center), dockText, style: "PowerButton")
{ {
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{
if (GameMain.GameSession?.Campaign != null)
{
if (Level.IsLoadedOutpost &&
DockingSources.Any(d => d.Docked && (d.DockingTarget?.Item.Submarine?.Info?.IsOutpost ?? false)))
{
GameMain.GameSession.Campaign.CampaignUI.SelectTab(CampaignMode.InteractionType.Map);
GameMain.GameSession.Campaign.ShowCampaignUI = true;
return false;
}
else if (!Level.IsLoadedOutpost && DockingModeEnabled && ActiveDockingSource != null &&
!ActiveDockingSource.Docked && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false))
{
enterOutpostPrompt = new GUIMessageBox("", TextManager.GetWithVariable("campaignenteroutpostprompt", "[locationname]", DockingTarget.Item.Submarine.Info.Name), new string[] { TextManager.Get("yes"), TextManager.Get("no") });
enterOutpostPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
SendDockingSignal();
enterOutpostPrompt.Close();
return true;
};
enterOutpostPrompt.Buttons[1].OnClicked += enterOutpostPrompt.Close;
return false;
}
}
SendDockingSignal();
return true;
}
};
void SendDockingSignal()
{ {
if (GameMain.Client == null) if (GameMain.Client == null)
{ {
@@ -356,9 +389,7 @@ namespace Barotrauma.Items.Components
dockingNetworkMessagePending = true; dockingNetworkMessagePending = true;
item.CreateClientEvent(this); item.CreateClientEvent(this);
} }
return true;
} }
};
dockingButton.Font = GUI.SubHeadingFont; dockingButton.Font = GUI.SubHeadingFont;
dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue); dockingButton.TextBlock.RectTransform.MaxSize = new Point((int)(dockingButton.Rect.Width * 0.7f), int.MaxValue);
dockingButton.TextBlock.AutoScaleHorizontal = true; dockingButton.TextBlock.AutoScaleHorizontal = true;
@@ -413,10 +444,9 @@ namespace Barotrauma.Items.Components
GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.Name); GameMain.GameSession?.EndLocation == null ? "End" : GameMain.GameSession.EndLocation.Name);
} }
private void RecreateGUI() protected override void OnResolutionChanged()
{ {
GuiFrame.ClearChildren(); base.OnResolutionChanged();
CreateGUI();
UpdateGUIElements(); UpdateGUIElements();
} }
@@ -600,6 +630,10 @@ namespace Barotrauma.Items.Components
dockingContainer.Visible = DockingModeEnabled; dockingContainer.Visible = DockingModeEnabled;
statusContainer.Visible = !DockingModeEnabled; statusContainer.Visible = !DockingModeEnabled;
if (!DockingModeEnabled)
{
enterOutpostPrompt?.Close();
}
if (DockingModeEnabled && ActiveDockingSource != null) if (DockingModeEnabled && ActiveDockingSource != null)
{ {
@@ -613,6 +647,10 @@ namespace Barotrauma.Items.Components
dockingButton.Pulsate(Vector2.One, Vector2.One * 1.2f, dockingButton.FlashTimer); dockingButton.Pulsate(Vector2.One, Vector2.One * 1.2f, dockingButton.FlashTimer);
} }
} }
else
{
enterOutpostPrompt?.Close();
}
} }
else if (DockingSources.Any(d => d.Docked)) else if (DockingSources.Any(d => d.Docked))
{ {
@@ -663,7 +701,8 @@ namespace Barotrauma.Items.Components
if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius) if (Vector2.DistanceSquared(PlayerInput.MousePosition, steerArea.Rect.Center.ToVector2()) < steerRadius * steerRadius)
{ {
if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen) if (PlayerInput.PrimaryMouseButtonHeld() && !CrewManager.IsCommandInterfaceOpen && !GameSession.IsTabMenuOpen &&
(!GameMain.GameSession?.Campaign?.ShowCampaignUI ?? true) && !GUIMessageBox.MessageBoxes.Any())
{ {
Vector2 inputPos = PlayerInput.MousePosition - steerArea.Rect.Center.ToVector2(); Vector2 inputPos = PlayerInput.MousePosition - steerArea.Rect.Center.ToVector2();
inputPos.Y = -inputPos.Y; inputPos.Y = -inputPos.Y;
@@ -800,8 +839,7 @@ namespace Barotrauma.Items.Components
maintainPosIndicator?.Remove(); maintainPosIndicator?.Remove();
maintainPosOriginIndicator?.Remove(); maintainPosOriginIndicator?.Remove();
steeringIndicator?.Remove(); steeringIndicator?.Remove();
enterOutpostPrompt?.Close();
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
} }
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
@@ -111,27 +111,28 @@ namespace Barotrauma.Items.Components
if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; } if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; }
if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; } if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; }
if (charge > 0) if (charge > 0 && capacity > 0)
{ {
Color indicatorColor = ToolBox.GradientLerp(charge / capacity, Color.Red, Color.Orange, Color.Green); float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
if (!isHorizontal) if (!isHorizontal)
{ {
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - charge / capacity))) + indicatorPos, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - chargeRatio))) + indicatorPos,
new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * (charge / capacity)), indicatorColor, true, new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * chargeRatio), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f); depth: item.SpriteDepth - 0.00001f);
} }
else else
{ {
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
new Vector2((indicatorSize.X * item.Scale) * (charge / capacity), indicatorSize.Y * item.Scale), indicatorColor, true, new Vector2((indicatorSize.X * item.Scale) * chargeRatio, indicatorSize.Y * item.Scale), indicatorColor, true,
depth: item.SpriteDepth - 0.00001f); depth: item.SpriteDepth - 0.00001f);
} }
} }
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos, new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.00001f); indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.000015f);
} }
public void ClientWrite(IWriteMessage msg, object[] extraData) public void ClientWrite(IWriteMessage msg, object[] extraData)
@@ -83,7 +83,7 @@ namespace Barotrauma.Items.Components
var progressBar = user.UpdateHUDProgressBar( var progressBar = user.UpdateHUDProgressBar(
targetStructure.ID * 1000 + sectionIndex, //unique "identifier" for each wall section targetStructure.ID * 1000 + sectionIndex, //unique "identifier" for each wall section
progressBarPos, progressBarPos,
1.0f - targetStructure.SectionDamage(sectionIndex) / targetStructure.Health, MathUtils.InverseLerp(targetStructure.Prefab.MinHealth, targetStructure.Health, targetStructure.Health - targetStructure.SectionDamage(sectionIndex)),
GUI.Style.Red, GUI.Style.Green); GUI.Style.Red, GUI.Style.Green);
if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f); if (progressBar != null) progressBar.Size = new Vector2(60.0f, 20.0f);
@@ -1,4 +1,5 @@
using Barotrauma.Networking; using System;
using Barotrauma.Networking;
using Barotrauma.Particles; using Barotrauma.Particles;
using Barotrauma.Sounds; using Barotrauma.Sounds;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
@@ -49,6 +50,42 @@ namespace Barotrauma.Items.Components
} }
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{
CreateGUI();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "emitter":
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f);
float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f);
if (maxCondition < minCondition)
{
DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition.");
float temp = maxCondition;
maxCondition = minCondition;
minCondition = temp;
}
particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition));
break;
}
}
}
private void RecreateGUI()
{
if (GuiFrame != null)
{
GuiFrame.ClearChildren();
CreateGUI();
}
}
private void CreateGUI()
{ {
var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) var paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.8f, 0.75f), GuiFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
@@ -68,7 +105,7 @@ namespace Barotrauma.Items.Components
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), paddedFrame.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) requiredSkills[i].Level).ToString()), " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + requiredSkills[i].Identifier), ((int) Math.Round(requiredSkills[i].Level * SkillRequirementMultiplier)).ToString()),
font: GUI.SmallFont) font: GUI.SmallFont)
{ {
UserData = requiredSkills[i] UserData = requiredSkills[i]
@@ -111,33 +148,13 @@ namespace Barotrauma.Items.Components
return true; return true;
} }
}; };
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "emitter":
case "particleemitter":
particleEmitters.Add(new ParticleEmitter(subElement));
float minCondition = subElement.GetAttributeFloat("mincondition", 0.0f);
float maxCondition = subElement.GetAttributeFloat("maxcondition", 100.0f);
if (maxCondition < minCondition)
{
DebugConsole.ThrowError("Invalid damage particle configuration in the Repairable component of " + item.Name + ". MaxCondition needs to be larger than MinCondition.");
float temp = maxCondition;
maxCondition = minCondition;
minCondition = temp;
}
particleEmitterConditionRanges.Add(new Vector2(minCondition, maxCondition));
break;
}
}
} }
partial void UpdateProjSpecific(float deltaTime) partial void UpdateProjSpecific(float deltaTime)
{ {
if (FakeBrokenTimer > 0.0f)
{
item.FakeBroken = true;
if (Character.Controlled == null || (Character.Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f) if (Character.Controlled == null || (Character.Controlled.CharacterHealth.GetAffliction("psychosis")?.Strength ?? 0.0f) <= 0.0f)
{ {
FakeBrokenTimer = 0.0f; FakeBrokenTimer = 0.0f;
@@ -146,8 +163,12 @@ namespace Barotrauma.Items.Components
{ {
FakeBrokenTimer -= deltaTime; FakeBrokenTimer -= deltaTime;
} }
}
else
{
item.FakeBroken = false;
}
item.FakeBroken = FakeBrokenTimer > 0.0f;
if (!GameMain.IsMultiplayer) if (!GameMain.IsMultiplayer)
{ {
@@ -211,7 +232,7 @@ namespace Barotrauma.Items.Components
if (!(c.UserData is Skill skill)) continue; if (!(c.UserData is Skill skill)) continue;
GUITextBlock textBlock = (GUITextBlock)c; GUITextBlock textBlock = (GUITextBlock)c;
if (character.GetSkillLevel(skill.Identifier) < skill.Level) if (character.GetSkillLevel(skill.Identifier) < (skill.Level * SkillRequirementMultiplier))
{ {
textBlock.TextColor = GUI.Style.Red; textBlock.TextColor = GUI.Style.Red;
} }
@@ -247,6 +268,7 @@ namespace Barotrauma.Items.Components
protected override void RemoveComponentSpecific() protected override void RemoveComponentSpecific()
{ {
base.RemoveComponentSpecific();
repairSoundChannel?.FadeOutAndDispose(); repairSoundChannel?.FadeOutAndDispose();
repairSoundChannel = null; repairSoundChannel = null;
} }
@@ -289,7 +289,7 @@ namespace Barotrauma.Items.Components
flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale); flashColor * (float)Math.Sin(FlashTimer % flashCycleDuration / flashCycleDuration * MathHelper.Pi * 0.8f), scale: connectorSpriteScale);
} }
if (Wires.Any(w => w != null && w != DraggingConnected)) if (Wires.Any(w => w != null && w != DraggingConnected && !w.Hidden))
{ {
int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count; int screwIndex = (int)Math.Floor(position.Y / 30.0f) % screwSprites.Count;
screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale); screwSprites[screwIndex].Draw(spriteBatch, position, scale: connectorSpriteScale);
@@ -18,17 +18,9 @@ namespace Barotrauma.Items.Components
partial void InitProjSpecific(XElement element) partial void InitProjSpecific(XElement element)
{ {
CreateGUI(); CreateGUI();
GameMain.Instance.OnResolutionChanged += RecreateGUI;
} }
private void RecreateGUI() protected override void CreateGUI()
{
GuiFrame.ClearChildren();
CreateGUI();
UpdateLabelsProjSpecific();
}
private void CreateGUI()
{ {
uiElements.Clear(); uiElements.Clear();
var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label)); var visibleElements = customInterfaceElementList.Where(ciElement => !string.IsNullOrEmpty(ciElement.Label));
@@ -309,10 +301,5 @@ namespace Barotrauma.Items.Components
} }
} }
} }
protected override void RemoveComponentSpecific()
{
GameMain.Instance.OnResolutionChanged -= RecreateGUI;
}
} }
} }
@@ -187,7 +187,7 @@ namespace Barotrauma.Items.Components
} }
if (IsActive && item.ParentInventory?.Owner is Character user && user == Character.Controlled)// && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance) if (IsActive && item.ParentInventory?.Owner is Character user && user == Character.Controlled)// && Vector2.Distance(newNodePos, nodes[nodes.Count - 1]) > nodeDistance)
{ {
if (user.CanInteract) if (user.CanInteract && currLength < MaxLength)
{ {
Vector2 gridPos = Character.Controlled.Position; Vector2 gridPos = Character.Controlled.Position;
Vector2 roundedGridPos = new Vector2( Vector2 roundedGridPos = new Vector2(
@@ -135,7 +135,7 @@ namespace Barotrauma.Items.Components
Entity targetEntity = Entity.FindEntityByID(dockingTargetID); Entity targetEntity = Entity.FindEntityByID(dockingTargetID);
if (targetEntity == null || !(targetEntity is Item)) if (targetEntity == null || !(targetEntity is Item))
{ {
DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + targetEntity?.ToString() ?? "null" + ")"); DebugConsole.ThrowError("Invalid docking port network event (can't dock to " + (targetEntity?.ToString() ?? "null") + ")");
return; return;
} }
@@ -260,7 +260,11 @@ namespace Barotrauma
item.Name : item.Name :
item.Name + '\n' + description; item.Name + '\n' + description;
} }
if (item.SpawnedInOutpost)
{
string colorStr = XMLExtensions.ColorToString(GUI.Style.Red);
toolTip = $"‖color:{colorStr}‖{toolTip}‖color:end‖";
}
return toolTip; return toolTip;
} }
} }
@@ -1127,6 +1131,8 @@ namespace Barotrauma
public static void DrawFront(SpriteBatch spriteBatch) public static void DrawFront(SpriteBatch spriteBatch)
{ {
if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; } if (GUI.PauseMenuOpen || GUI.SettingsMenuOpen) { return; }
if (GameMain.GameSession?.Campaign != null &&
(GameMain.GameSession.Campaign.ShowCampaignUI || GameMain.GameSession.Campaign.ForceMapUI)) { return; }
subInventorySlotsToDraw.Clear(); subInventorySlotsToDraw.Clear();
subInventorySlotsToDraw.AddRange(highlightedSubInventorySlots); subInventorySlotsToDraw.AddRange(highlightedSubInventorySlots);
@@ -1377,6 +1383,17 @@ namespace Barotrauma
sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black * 0.6f, rotate: rotation, scale: scale); sprite.Draw(spriteBatch, itemPos + Vector2.One * 2, Color.Black * 0.6f, rotate: rotation, scale: scale);
} }
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
if (item.SpawnedInOutpost && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
{
var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand];
Vector2 iconSize = new Vector2(25 * GUI.Scale);
stealIcon.Draw(
spriteBatch,
new Vector2(rect.X + iconSize.X * 0.2f, rect.Bottom - iconSize.Y * 1.2f),
color: GUI.Style.Red,
scale: iconSize.X / stealIcon.size.X);
}
} }
if (inventory != null && if (inventory != null &&
@@ -246,7 +246,7 @@ namespace Barotrauma
float fadeInBrokenSpriteAlpha = 0.0f; float fadeInBrokenSpriteAlpha = 0.0f;
float displayCondition = FakeBroken ? 0.0f : condition; float displayCondition = FakeBroken ? 0.0f : condition;
Vector2 drawOffset = Vector2.Zero; Vector2 drawOffset = Vector2.Zero;
if (displayCondition < Prefab.Health) if (displayCondition < MaxCondition)
{ {
for (int i = 0; i < Prefab.BrokenSprites.Count; i++) for (int i = 0; i < Prefab.BrokenSprites.Count; i++)
{ {
@@ -300,21 +300,22 @@ namespace Barotrauma
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
decorativeSprite.Sprite.DrawTiled(spriteBatch, decorativeSprite.Sprite.DrawTiled(spriteBatch,
new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)), new Vector2(DrawPosition.X + offset.X - rect.Width / 2, -(DrawPosition.Y + offset.Y + rect.Height / 2)),
new Vector2(rect.Width, rect.Height), color: color, size, color: color,
textureScale: Vector2.One * Scale,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
} }
} }
else else
{ {
activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, SpriteRotation, Scale, activeSprite.effects, depth); activeSprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + drawOffset, color, SpriteRotation + rotation, Scale, activeSprite.effects, depth);
fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, SpriteRotation, Scale, activeSprite.effects, depth - 0.000001f); fadeInBrokenSprite?.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X, -DrawPosition.Y) + fadeInBrokenSprite.Offset.ToVector2() * Scale, color * fadeInBrokenSpriteAlpha, SpriteRotation + rotation, Scale, activeSprite.effects, depth - 0.000001f);
foreach (var decorativeSprite in Prefab.DecorativeSprites) foreach (var decorativeSprite in Prefab.DecorativeSprites)
{ {
if (!spriteAnimState[decorativeSprite].IsActive) { continue; } if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState); float rot = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale; Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color, decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + offset.X, -(DrawPosition.Y + offset.Y)), color,
SpriteRotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects, SpriteRotation + rotation + rot, decorativeSprite.Scale * Scale, activeSprite.effects,
depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f)); depth: Math.Min(depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth), 0.999f));
} }
} }
@@ -328,7 +329,7 @@ namespace Barotrauma
if (holdable.Picker.SelectedItems[0] == this) if (holdable.Picker.SelectedItems[0] == this)
{ {
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand); Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.RightHand);
if (holdLimb != null) if (holdLimb?.ActiveSprite != null)
{ {
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() + depthStep * 2; depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() + depthStep * 2;
foreach (WearableSprite wearableSprite in holdLimb.WearingItems) foreach (WearableSprite wearableSprite in holdLimb.WearingItems)
@@ -340,7 +341,7 @@ namespace Barotrauma
else if (holdable.Picker.SelectedItems[1] == this) else if (holdable.Picker.SelectedItems[1] == this)
{ {
Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand); Limb holdLimb = holdable.Picker.AnimController.GetLimb(LimbType.LeftHand);
if (holdLimb != null) if (holdLimb?.ActiveSprite != null)
{ {
depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() - depthStep * 2; depth = holdLimb.ActiveSprite.Depth + holdable.Picker.AnimController.GetDepthOffset() - depthStep * 2;
foreach (WearableSprite wearableSprite in holdLimb.WearingItems) foreach (WearableSprite wearableSprite in holdLimb.WearingItems)
@@ -369,6 +370,23 @@ namespace Barotrauma
} }
} }
foreach (var upgrade in Upgrades)
{
var upgradeSprites = GetUpgradeSprites(upgrade);
foreach (var decorativeSprite in upgradeSprites)
{
if (!spriteAnimState[decorativeSprite].IsActive) { continue; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
var (xOff, yOff) = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + xOff, -(DrawPosition.Y + yOff)), color,
rotation, decorativeSprite.Scale * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
}
activeSprite.effects = oldEffects; activeSprite.effects = oldEffects;
if (fadeInBrokenSprite != null && fadeInBrokenSprite.Sprite != activeSprite) if (fadeInBrokenSprite != null && fadeInBrokenSprite.Sprite != activeSprite)
{ {
@@ -437,6 +455,21 @@ namespace Barotrauma
} }
} }
private void DrawDecorativeSprite(SpriteBatch spriteBatch, DecorativeSprite decorativeSprite, Color color, float depth)
{
if (!spriteAnimState[decorativeSprite].IsActive) { return; }
float rotation = decorativeSprite.GetRotation(ref spriteAnimState[decorativeSprite].RotationState);
Vector2 offset = decorativeSprite.GetOffset(ref spriteAnimState[decorativeSprite].OffsetState) * Scale;
var ca = (float)Math.Cos(-body.Rotation);
var sa = (float)Math.Sin(-body.Rotation);
Vector2 transformedOffset = new Vector2(ca * offset.X + sa * offset.Y, -sa * offset.X + ca * offset.Y);
decorativeSprite.Sprite.Draw(spriteBatch, new Vector2(DrawPosition.X + transformedOffset.X, -(DrawPosition.Y + transformedOffset.Y)), color,
-body.Rotation + rotation, decorativeSprite.Scale * Scale, activeSprite.effects,
depth: depth + (decorativeSprite.Sprite.Depth - activeSprite.Depth));
}
partial void OnCollisionProjSpecific(float impact) partial void OnCollisionProjSpecific(float impact)
{ {
if (impact > 1.0f && if (impact > 1.0f &&
@@ -451,6 +484,22 @@ namespace Barotrauma
public void UpdateSpriteStates(float deltaTime) public void UpdateSpriteStates(float deltaTime)
{ {
DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches); DecorativeSprite.UpdateSpriteStates(Prefab.DecorativeSpriteGroups, spriteAnimState, ID, deltaTime, ConditionalMatches);
foreach (var upgrade in Upgrades)
{
var upgradeSprites = GetUpgradeSprites(upgrade);
foreach (var decorativeSprite in upgradeSprites)
{
var spriteState = spriteAnimState[decorativeSprite];
spriteState.IsActive = true;
foreach (var _ in decorativeSprite.IsActiveConditionals.Where(conditional => !ConditionalMatches(conditional)))
{
spriteState.IsActive = false;
break;
}
}
}
} }
public override void UpdateEditing(Camera cam) public override void UpdateEditing(Camera cam)
@@ -541,12 +590,14 @@ namespace Barotrauma
linkText.TextColor = GUI.Style.Orange; linkText.TextColor = GUI.Style.Orange;
itemsText.TextColor = GUI.Style.Orange; itemsText.TextColor = GUI.Style.Orange;
} }
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.02f, RelativeSpacing = 0.02f,
CanBeFocused = true CanBeFocused = true
}; };
new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall") new GUIButton(new RectTransform(new Vector2(0.23f, 1.0f), buttonContainer.RectTransform), TextManager.Get("MirrorEntityX"), style: "GUIButtonSmall")
{ {
ToolTip = TextManager.Get("MirrorEntityXToolTip"), ToolTip = TextManager.Get("MirrorEntityXToolTip"),
@@ -588,6 +639,22 @@ namespace Barotrauma
buttonContainer.RectTransform.IsFixedSize = true; buttonContainer.RectTransform.IsFixedSize = true;
itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount); itemEditor.AddCustomContent(buttonContainer, itemEditor.ContentCount);
GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Select(b => ((GUIButton)b).TextBlock)); GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Select(b => ((GUIButton)b).TextBlock));
if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule)
{
GUITickBox tickBox = new GUITickBox(new RectTransform(new Point(listBox.Content.Rect.Width, 10)), TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.name"))
{
Font = GUI.SmallFont,
Selected = RemoveIfLinkedOutpostDoorInUse,
ToolTip = TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.description"),
OnSelected = (tickBox) =>
{
RemoveIfLinkedOutpostDoorInUse = tickBox.Selected;
return true;
}
};
itemEditor.AddCustomContent(tickBox, 1);
}
} }
foreach (ItemComponent ic in components) foreach (ItemComponent ic in components)
@@ -667,6 +734,39 @@ namespace Barotrauma
return editingHUD; return editingHUD;
} }
private List<DecorativeSprite> GetUpgradeSprites(Upgrade upgrade)
{
var upgradeSprites = upgrade.Prefab.DecorativeSprites;
if (Prefab.UpgradeOverrideSprites.ContainsKey(upgrade.Prefab.Identifier))
{
upgradeSprites = Prefab.UpgradeOverrideSprites[upgrade.Prefab.Identifier];
}
return upgradeSprites;
}
public override bool AddUpgrade(Upgrade upgrade, bool createNetworkEvent = false)
{
if (upgrade.Prefab.IsWallUpgrade) { return false; }
bool result = base.AddUpgrade(upgrade, createNetworkEvent);
if (result && !upgrade.Disposed)
{
List<DecorativeSprite> upgradeSprites = GetUpgradeSprites(upgrade);
if (upgradeSprites.Any())
{
foreach (DecorativeSprite decorativeSprite in upgradeSprites)
{
decorativeSprite.Sprite.EnsureLazyLoaded();
spriteAnimState.Add(decorativeSprite, new DecorativeSprite.State());
}
UpdateSpriteStates(0.0f);
}
}
return result;
}
private void CreateTagPicker(GUITextBox textBox, IEnumerable<string> availableTags) private void CreateTagPicker(GUITextBox textBox, IEnumerable<string> availableTags)
{ {
var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400)); var msgBox = new GUIMessageBox("", "", new string[] { TextManager.Get("Cancel") }, new Vector2(0.2f, 0.5f), new Point(300, 400));
@@ -756,6 +856,10 @@ namespace Barotrauma
private readonly List<Rectangle> debugInitialHudPositions = new List<Rectangle>(); private readonly List<Rectangle> debugInitialHudPositions = new List<Rectangle>();
private readonly List<ItemComponent> prevActiveHUDs = new List<ItemComponent>();
private readonly List<ItemComponent> activeComponents = new List<ItemComponent>();
private readonly List<ItemComponent> maxPriorityHUDs = new List<ItemComponent>();
public void UpdateHUD(Camera cam, Character character, float deltaTime) public void UpdateHUD(Camera cam, Character character, float deltaTime)
{ {
bool editingHUDCreated = false; bool editingHUDCreated = false;
@@ -774,8 +878,11 @@ namespace Barotrauma
editingHUDRefreshTimer -= deltaTime; editingHUDRefreshTimer -= deltaTime;
} }
List<ItemComponent> prevActiveHUDs = new List<ItemComponent>(activeHUDs); prevActiveHUDs.Clear();
List<ItemComponent> activeComponents = new List<ItemComponent>(components); prevActiveHUDs.AddRange(activeHUDs);
activeComponents.Clear();
activeComponents.AddRange(components);
foreach (MapEntity entity in linkedTo) foreach (MapEntity entity in linkedTo)
{ {
if (prefab.IsLinkAllowed(entity.prefab) && entity is Item i) if (prefab.IsLinkAllowed(entity.prefab) && entity is Item i)
@@ -788,12 +895,11 @@ namespace Barotrauma
activeHUDs.Clear(); activeHUDs.Clear();
//the HUD of the component with the highest priority will be drawn //the HUD of the component with the highest priority will be drawn
//if all components have a priority of 0, all of them are drawn //if all components have a priority of 0, all of them are drawn
List<ItemComponent> maxPriorityHUDs = new List<ItemComponent>(); maxPriorityHUDs.Clear();
bool DrawHud(ItemComponent ic) => ic.ShouldDrawHUD(character) && (ic.CanBeSelected && ic.HasRequiredItems(character, addMessage: false) || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped));
foreach (ItemComponent ic in activeComponents) foreach (ItemComponent ic in activeComponents)
{ {
if (ic.HudPriority > 0 && ic.ShouldDrawHUD(character) && if (ic.HudPriority > 0 && DrawHud(ic) && (maxPriorityHUDs.Count == 0 || ic.HudPriority >= maxPriorityHUDs[0].HudPriority))
(ic.CanBeSelected || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped)) &&
(maxPriorityHUDs.Count == 0 || ic.HudPriority >= maxPriorityHUDs[0].HudPriority))
{ {
if (maxPriorityHUDs.Count > 0 && ic.HudPriority > maxPriorityHUDs[0].HudPriority) { maxPriorityHUDs.Clear(); } if (maxPriorityHUDs.Count > 0 && ic.HudPriority > maxPriorityHUDs[0].HudPriority) { maxPriorityHUDs.Clear(); }
maxPriorityHUDs.Add(ic); maxPriorityHUDs.Add(ic);
@@ -808,8 +914,7 @@ namespace Barotrauma
{ {
foreach (ItemComponent ic in activeComponents) foreach (ItemComponent ic in activeComponents)
{ {
if (ic.ShouldDrawHUD(character) && if (DrawHud(ic))
(ic.CanBeSelected || (character.HasEquippedItem(this) && ic.DrawHudWhenEquipped)))
{ {
activeHUDs.Add(ic); activeHUDs.Add(ic);
} }
@@ -914,11 +1019,11 @@ namespace Barotrauma
color = Color.Cyan; color = Color.Cyan;
} }
} }
texts.Add(new ColoredText(ic.DisplayMsg, color, false)); texts.Add(new ColoredText(ic.DisplayMsg, color, false, false));
} }
if ((PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) && CrewManager.DoesItemHaveContextualOrders(this)) if ((PlayerInput.KeyDown(Keys.LeftShift) || PlayerInput.KeyDown(Keys.RightShift)) && CrewManager.DoesItemHaveContextualOrders(this))
{ {
texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false)); texts.Add(new ColoredText(TextManager.ParseInputTypes(TextManager.Get("itemmsgcontextualorders")), Color.Cyan, false, false));
} }
return texts; return texts;
} }
@@ -1048,6 +1153,29 @@ namespace Barotrauma
ReadPropertyChange(msg, false); ReadPropertyChange(msg, false);
editingHUDRefreshPending = true; editingHUDRefreshPending = true;
break; break;
case NetEntityEvent.Type.Upgrade:
{
string identifier = msg.ReadString();
byte level = msg.ReadByte();
if (UpgradePrefab.Find(identifier) is { } upgradePrefab)
{
Upgrade upgrade = new Upgrade(this, upgradePrefab, level);
byte targetCount = msg.ReadByte();
for (int i = 0; i < targetCount; i++)
{
byte propertyCount = msg.ReadByte();
for (int j = 0; j < propertyCount; j++)
{
float value = msg.ReadSingle();
upgrade.TargetComponents.ElementAt(i).Value[j].SetOriginalValue(value);
}
}
AddUpgrade(upgrade, false);
}
break;
}
case NetEntityEvent.Type.Invalid: case NetEntityEvent.Type.Invalid:
break; break;
} }
@@ -1220,7 +1348,7 @@ namespace Barotrauma
ushort itemId = msg.ReadUInt16(); ushort itemId = msg.ReadUInt16();
ushort inventoryId = msg.ReadUInt16(); ushort inventoryId = msg.ReadUInt16();
DebugConsole.Log("Received entity spawn message for item " + itemName + "."); DebugConsole.Log($"Received entity spawn message for item \"{itemName}\" (identifier: {itemIdentifier}, id: {itemId})");
Vector2 pos = Vector2.Zero; Vector2 pos = Vector2.Zero;
Submarine sub = null; Submarine sub = null;
@@ -1244,7 +1372,7 @@ namespace Barotrauma
} }
byte bodyType = msg.ReadByte(); byte bodyType = msg.ReadByte();
bool spawnedInOutpost = msg.ReadBoolean();
byte teamID = msg.ReadByte(); byte teamID = msg.ReadByte();
bool tagsChanged = msg.ReadBoolean(); bool tagsChanged = msg.ReadBoolean();
string tags = ""; string tags = "";
@@ -1289,6 +1417,7 @@ namespace Barotrauma
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsSDK.Net.EGAErrorSeverity.Error,
errorMsg); errorMsg);
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
inventory = parentItem.GetComponent<ItemContainer>()?.Inventory;
} }
else if (parentItem.components[itemContainerIndex] is ItemContainer container) else if (parentItem.components[itemContainerIndex] is ItemContainer container)
{ {
@@ -1307,7 +1436,8 @@ namespace Barotrauma
var item = new Item(itemPrefab, pos, sub) var item = new Item(itemPrefab, pos, sub)
{ {
ID = itemId ID = itemId,
SpawnedInOutpost = spawnedInOutpost
}; };
if (item.body != null) if (item.body != null)
@@ -105,7 +105,7 @@ namespace Barotrauma
} }
else else
{ {
Vector2 placeSize = size; Vector2 placeSize = size * Scale;
if (placePosition == Vector2.Zero) if (placePosition == Vector2.Zero)
{ {
@@ -161,7 +161,14 @@ namespace Barotrauma
} }
else else
{ {
sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), size, color: SpriteColor); Vector2 placeSize = size * Scale;
if (placePosition != Vector2.Zero)
{
if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); }
if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); }
position = placePosition;
}
sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
} }
} }
@@ -173,7 +180,15 @@ namespace Barotrauma
} }
else else
{ {
if (sprite != null) sprite.DrawTiled(spriteBatch, new Vector2(placeRect.X, -placeRect.Y), placeRect.Size.ToVector2(), null, SpriteColor * 0.8f); Vector2 position = Submarine.MouseToWorldGrid(Screen.Selected.Cam, Submarine.MainSub);
Vector2 placeSize = size * Scale;
if (placePosition != Vector2.Zero)
{
if (ResizeHorizontal) { placeSize.X = Math.Max(position.X - placePosition.X, placeSize.X); }
if (ResizeVertical) { placeSize.Y = Math.Max(placePosition.Y - position.Y, placeSize.Y); }
position = placePosition;
}
sprite?.DrawTiled(spriteBatch, new Vector2(position.X, -position.Y), placeSize, color: SpriteColor);
} }
} }
} }
@@ -83,7 +83,7 @@ namespace Barotrauma
{ {
Vector2 dir = IsHorizontal ? Vector2 dir = IsHorizontal ?
new Vector2(Math.Sign(linkedTo[i].Rect.Center.X - rect.Center.X), 0.0f) new Vector2(Math.Sign(linkedTo[i].Rect.Center.X - rect.Center.X), 0.0f)
: new Vector2(0.0f, Math.Sign((linkedTo[i].Rect.Y - linkedTo[i].Rect.Height / 2.0f) - (rect.Y - rect.Height / 2.0f))); : new Vector2(0.0f, Math.Sign((rect.Y - rect.Height / 2.0f) - (linkedTo[i].Rect.Y - linkedTo[i].Rect.Height / 2.0f)));
Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2)); Vector2 arrowPos = new Vector2(WorldRect.Center.X, -(WorldRect.Y - WorldRect.Height / 2));
arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2)); arrowPos += new Vector2(dir.X * (WorldRect.Width / 2), dir.Y * (WorldRect.Height / 2));
@@ -201,7 +201,7 @@ namespace Barotrauma
for (int i = 1; i < waveY.Length - 1; i++) for (int i = 1; i < waveY.Length - 1; i++)
{ {
float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i])); float maxDelta = Math.Max(Math.Abs(rightDelta[i]), Math.Abs(leftDelta[i]));
if (maxDelta > Rand.Range(1.0f, 10.0f)) if (maxDelta > 1.0f && maxDelta > Rand.Range(1.0f, 10.0f))
{ {
var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]); var particlePos = new Vector2(rect.X + WaveWidth * i, surface + waveY[i]);
if (Submarine != null) particlePos += Submarine.Position; if (Submarine != null) particlePos += Submarine.Position;
@@ -22,7 +22,7 @@ namespace Barotrauma
HashSet<Texture2D> uniqueTextures = new HashSet<Texture2D>(); HashSet<Texture2D> uniqueTextures = new HashSet<Texture2D>();
HashSet<Sprite> uniqueSprites = new HashSet<Sprite>(); HashSet<Sprite> uniqueSprites = new HashSet<Sprite>();
var allLevelObjects = levelObjectManager.GetAllObjects(); var allLevelObjects = LevelObjectManager.GetAllObjects();
foreach (var levelObj in allLevelObjects) foreach (var levelObj in allLevelObjects)
{ {
foreach (Sprite sprite in levelObj.Prefab.Sprites) foreach (Sprite sprite in levelObj.Prefab.Sprites)
@@ -56,7 +56,7 @@ namespace Barotrauma
if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f) if (GameMain.DebugDraw && Screen.Selected.Cam.Zoom > 0.1f)
{ {
foreach (InterestingPosition pos in positionsOfInterest) foreach (InterestingPosition pos in PositionsOfInterest)
{ {
Color color = Color.Yellow; Color color = Color.Yellow;
if (pos.PositionType == PositionType.Cave) if (pos.PositionType == PositionType.Cave)
@@ -71,7 +71,7 @@ namespace Barotrauma
GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true); GUI.DrawRectangle(spriteBatch, new Vector2(pos.Position.X - 15.0f, -pos.Position.Y - 15.0f), new Vector2(30.0f, 30.0f), color, true);
} }
foreach (RuinGeneration.Ruin ruin in ruins) foreach (RuinGeneration.Ruin ruin in Ruins)
{ {
Rectangle ruinArea = ruin.Area; Rectangle ruinArea = ruin.Area;
ruinArea.Y = -ruinArea.Y - ruinArea.Height; ruinArea.Y = -ruinArea.Y - ruinArea.Height;
@@ -113,7 +113,7 @@ namespace Barotrauma
public void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam) public void DrawBack(GraphicsDevice graphics, SpriteBatch spriteBatch, Camera cam)
{ {
float brightness = MathHelper.Clamp(1.1f + (cam.Position.Y - Size.Y) / 100000.0f, 0.1f, 1.0f); float brightness = MathHelper.Clamp(1.1f + (cam.Position.Y - Size.Y) / 100000.0f, 0.1f, 1.0f);
var lightColorHLS = generationParams.AmbientLightColor.RgbToHLS(); var lightColorHLS = GenerationParams.AmbientLightColor.RgbToHLS();
lightColorHLS.Y *= brightness; lightColorHLS.Y *= brightness;
GameMain.LightManager.AmbientLight = ToolBox.HLSToRGB(lightColorHLS); GameMain.LightManager.AmbientLight = ToolBox.HLSToRGB(lightColorHLS);
@@ -121,12 +121,12 @@ namespace Barotrauma
graphics.Clear(BackgroundColor); graphics.Clear(BackgroundColor);
if (renderer == null) return; if (renderer == null) return;
renderer.DrawBackground(spriteBatch, cam, levelObjectManager, backgroundCreatureManager); renderer.DrawBackground(spriteBatch, cam, LevelObjectManager, backgroundCreatureManager);
} }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
foreach (LevelWall levelWall in extraWalls) foreach (LevelWall levelWall in ExtraWalls)
{ {
if (levelWall.Body.BodyType == BodyType.Static) continue; if (levelWall.Body.BodyType == BodyType.Static) continue;
@@ -9,8 +9,12 @@ namespace Barotrauma
{ {
partial class LevelObjectManager partial class LevelObjectManager
{ {
private List<LevelObject> visibleObjectsBack = new List<LevelObject>(); private readonly List<LevelObject> visibleObjectsBack = new List<LevelObject>();
private List<LevelObject> visibleObjectsFront = new List<LevelObject>(); private readonly List<LevelObject> visibleObjectsFront = new List<LevelObject>();
//Maximum number of visible objects drawn at once. Should be large enough to not have an effect during normal gameplay,
//but small enough to prevent wrecking performance when zooming out very far
const int MaxVisibleObjects = 500;
private Rectangle currentGridIndices; private Rectangle currentGridIndices;
@@ -43,7 +47,7 @@ namespace Barotrauma
{ {
for (int y = currentIndices.Y; y <= currentIndices.Height; y++) for (int y = currentIndices.Y; y <= currentIndices.Height; y++)
{ {
if (objectGrid[x, y] == null) continue; if (objectGrid[x, y] == null) { continue; }
foreach (LevelObject obj in objectGrid[x, y]) foreach (LevelObject obj in objectGrid[x, y])
{ {
var objectList = obj.Position.Z >= 0 ? visibleObjectsBack : visibleObjectsFront; var objectList = obj.Position.Z >= 0 ? visibleObjectsBack : visibleObjectsFront;
@@ -69,6 +73,7 @@ namespace Barotrauma
if (drawOrderIndex >= 0) if (drawOrderIndex >= 0)
{ {
objectList.Insert(drawOrderIndex, obj); objectList.Insert(drawOrderIndex, obj);
if (objectList.Count >= MaxVisibleObjects) { break; }
} }
} }
} }
@@ -209,7 +209,7 @@ namespace Barotrauma
level.GenerationParams.WaterParticles.DrawTiled( level.GenerationParams.WaterParticles.DrawTiled(
spriteBatch, origin + offsetS, spriteBatch, origin + offsetS,
new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y), new Vector2(cam.WorldView.Width - offsetS.X, cam.WorldView.Height - offsetS.Y),
rect: srcRect, color: Color.White * alpha, textureScale: new Vector2(texScale)); color: Color.White * alpha, textureScale: new Vector2(texScale));
} }
} }
@@ -170,10 +170,12 @@ namespace Barotrauma.Lights
isHorizontal = BoundingBox.Width > BoundingBox.Height; isHorizontal = BoundingBox.Width > BoundingBox.Height;
if (ParentEntity is Structure structure) if (ParentEntity is Structure structure)
{ {
System.Diagnostics.Debug.Assert(!structure.Removed);
isHorizontal = structure.IsHorizontal; isHorizontal = structure.IsHorizontal;
} }
else if (ParentEntity is Item item) else if (ParentEntity is Item item)
{ {
System.Diagnostics.Debug.Assert(!item.Removed);
var door = item.GetComponent<Door>(); var door = item.GetComponent<Door>();
if (door != null) { isHorizontal = door.IsHorizontal; } if (door != null) { isHorizontal = door.IsHorizontal; }
} }
@@ -444,7 +446,7 @@ namespace Barotrauma.Lights
CalculateDimensions(); CalculateDimensions();
if (ParentEntity == null) return; if (ParentEntity == null) { return; }
var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine); var chList = HullLists.Find(h => h.Submarine == ParentEntity.Submarine);
if (chList != null) if (chList != null)
@@ -56,6 +56,8 @@ namespace Barotrauma.Lights
private readonly Texture2D visionCircle; private readonly Texture2D visionCircle;
private Vector2 losOffset;
public IEnumerable<LightSource> Lights public IEnumerable<LightSource> Lights
{ {
get { return lights; } get { return lights; }
@@ -70,7 +72,7 @@ namespace Barotrauma.Lights
visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png"); visionCircle = Sprite.LoadTexture("Content/Lights/visioncircle.png");
highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png"); highlightRaster = Sprite.LoadTexture("Content/UI/HighlightRaster.png");
GameMain.Instance.OnResolutionChanged += () => GameMain.Instance.ResolutionChanged += () =>
{ {
CreateRenderTargets(graphics); CreateRenderTargets(graphics);
}; };
@@ -279,7 +281,7 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, effect: SolidColorEffect, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList) foreach (Character character in Character.CharacterList)
{ {
if (character.CurrentHull == null || !character.Enabled) { continue; } if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
if (Character.Controlled?.FocusedCharacter == character) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; }
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
Color.Black : Color.Black :
@@ -297,7 +299,7 @@ namespace Barotrauma.Lights
spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform); spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.NonPremultiplied, transformMatrix: spriteBatchTransform);
foreach (Character character in Character.CharacterList) foreach (Character character in Character.CharacterList)
{ {
if (character.CurrentHull == null || !character.Enabled) { continue; } if (character.CurrentHull == null || !character.Enabled || !character.IsVisible) { continue; }
if (Character.Controlled?.FocusedCharacter == character) { continue; } if (Character.Controlled?.FocusedCharacter == character) { continue; }
Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ? Color lightColor = character.CurrentHull.AmbientLight == Color.TransparentBlack ?
Color.Black : Color.Black :
@@ -335,7 +337,20 @@ namespace Barotrauma.Lights
if (Character.Controlled != null) if (Character.Controlled != null)
{ {
Vector2 haloDrawPos = Character.Controlled.DrawPosition; DrawHalo(Character.Controlled);
}
else
{
foreach (Character character in Character.CharacterList)
{
if (character.Submarine == null || character.IsDead || !character.IsHuman) { continue; }
DrawHalo(character);
}
}
void DrawHalo(Character character)
{
Vector2 haloDrawPos = character.DrawPosition;
haloDrawPos.Y = -haloDrawPos.Y; haloDrawPos.Y = -haloDrawPos.Y;
//ambient light decreases the brightness of the halo (no need for a bright halo if the ambient light is bright enough) //ambient light decreases the brightness of the halo (no need for a bright halo if the ambient light is bright enough)
@@ -349,6 +364,7 @@ namespace Barotrauma.Lights
new Vector2(LightSource.LightTexture.Width, LightSource.LightTexture.Height) / 2, scale, SpriteEffects.None, 0.0f); new Vector2(LightSource.LightTexture.Width, LightSource.LightTexture.Height) / 2, scale, SpriteEffects.None, 0.0f);
} }
} }
spriteBatch.End(); spriteBatch.End();
//draw the actual light volumes, additive particles, hull ambient lights and the halo around the player //draw the actual light volumes, additive particles, hull ambient lights and the halo around the player
@@ -477,10 +493,11 @@ namespace Barotrauma.Lights
graphics.Clear(Color.Black); graphics.Clear(Color.Black);
Vector2 diff = lookAtPosition - ViewTarget.WorldPosition; Vector2 diff = lookAtPosition - ViewTarget.WorldPosition;
diff.Y = -diff.Y; diff.Y = -diff.Y;
float rotation = MathUtils.VectorToAngle(diff); if (diff.LengthSquared() > 30.0f) { losOffset = diff; }
float rotation = MathUtils.VectorToAngle(losOffset);
Vector2 scale = new Vector2( Vector2 scale = new Vector2(
MathHelper.Clamp(diff.Length() / 256.0f, 2.0f, 5.0f), 2.0f); MathHelper.Clamp(losOffset.Length() / 256.0f, 2.0f, 5.0f), 2.0f);
spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation, spriteBatch.Draw(visionCircle, new Vector2(ViewTarget.WorldPosition.X, -ViewTarget.WorldPosition.Y), null, Color.White, rotation,
new Vector2(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f); new Vector2(visionCircle.Width * 0.2f, visionCircle.Height / 2), scale, SpriteEffects.None, 0.0f);
@@ -1,50 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Location
{
private HireManager hireManager;
public void RemoveHireableCharacter(CharacterInfo character)
{
if (!Type.HasHireableCharacters)
{
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - the location has no hireable characters.\n" + Environment.StackTrace);
return;
}
if (hireManager == null)
{
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace);
return;
}
hireManager.RemoveCharacter(character);
}
public IEnumerable<CharacterInfo> GetHireableCharacters()
{
if (!Type.HasHireableCharacters)
{
return Enumerable.Empty<CharacterInfo>();
}
if (hireManager == null)
{
hireManager = new HireManager();
}
if (!hireManager.AvailableCharacters.Any())
{
hireManager.GenerateCharacters(location: this, amount: HireManager.MaxAvailableCharacters);
}
return hireManager.AvailableCharacters;
}
partial void RemoveProjSpecific()
{
hireManager?.Remove();
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,14 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic;
namespace Barotrauma namespace Barotrauma
{ {
abstract partial class MapEntityPrefab : IPrefab, IDisposable abstract partial class MapEntityPrefab : IPrefab, IDisposable
{ {
public readonly Dictionary<string, List<DecorativeSprite>> UpgradeOverrideSprites = new Dictionary<string, List<DecorativeSprite>>();
public virtual void UpdatePlacing(Camera cam) public virtual void UpdatePlacing(Camera cam)
{ {
if (PlayerInput.SecondaryMouseButtonClicked()) if (PlayerInput.SecondaryMouseButtonClicked())
@@ -95,6 +95,22 @@ namespace Barotrauma
GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null); GUIListBox listBox = new GUIListBox(new RectTransform(new Vector2(0.95f, 0.8f), editingHUD.RectTransform, Anchor.Center), style: null);
var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont); var editor = new SerializableEntityEditor(listBox.Content.RectTransform, this, inGame, showName: true, titleFont: GUI.LargeFont);
if (Submarine.MainSub?.Info?.Type == SubmarineType.OutpostModule)
{
GUITickBox tickBox = new GUITickBox(new RectTransform(new Point(listBox.Content.Rect.Width, 10)), TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.name"))
{
Font = GUI.SmallFont,
Selected = RemoveIfLinkedOutpostDoorInUse,
ToolTip = TextManager.Get("sp.structure.removeiflinkedoutpostdoorinuse.description"),
OnSelected = (tickBox) =>
{
RemoveIfLinkedOutpostDoorInUse = tickBox.Selected;
return true;
}
};
editor.AddCustomContent(tickBox, 1);
}
var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true) var buttonContainer = new GUILayoutGroup(new RectTransform(new Point(listBox.Content.Rect.Width, heightScaled)), isHorizontal: true)
{ {
Stretch = true, Stretch = true,
@@ -261,7 +277,7 @@ namespace Barotrauma
SpriteEffects oldEffects = Prefab.BackgroundSprite.effects; SpriteEffects oldEffects = Prefab.BackgroundSprite.effects;
Prefab.BackgroundSprite.effects ^= SpriteEffects; Prefab.BackgroundSprite.effects ^= SpriteEffects;
Point backGroundOffset = new Point( Vector2 backGroundOffset = new Vector2(
MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width), MathUtils.PositiveModulo((int)-textureOffset.X, Prefab.BackgroundSprite.SourceRect.Width),
MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height)); MathUtils.PositiveModulo((int)-textureOffset.Y, Prefab.BackgroundSprite.SourceRect.Height));
@@ -299,7 +315,7 @@ namespace Barotrauma
{ {
if (damageEffect != null) if (damageEffect != null)
{ {
float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / Prefab.Health); float newCutoff = MathHelper.Lerp(0.0f, 0.65f, Sections[i].damage / MaxHealth);
if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor) if (Math.Abs(newCutoff - Submarine.DamageEffectCutoff) > 0.01f || color != Submarine.DamageEffectColor)
{ {
@@ -314,7 +330,7 @@ namespace Barotrauma
} }
} }
Point sectionOffset = new Point( Vector2 sectionOffset = new Vector2(
Math.Abs(rect.Location.X - Sections[i].rect.Location.X), Math.Abs(rect.Location.X - Sections[i].rect.Location.X),
Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y)); Math.Abs(rect.Location.Y - Sections[i].rect.Location.Y));
@@ -371,7 +387,7 @@ namespace Barotrauma
{ {
var textPos = SectionPosition(i, true); var textPos = SectionPosition(i, true);
textPos.Y = -textPos.Y; textPos.Y = -textPos.Y;
GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / Health) * 100f) + "%", Color.Yellow); GUI.DrawString(spriteBatch, textPos, "Damage: " + (int)((GetSection(i).damage / MaxHealth) * 100f) + "%", Color.Yellow);
} }
} }
} }
@@ -448,7 +464,7 @@ namespace Barotrauma
for (int i = 0; i < sectionCount; i++) for (int i = 0; i < sectionCount; i++)
{ {
float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * Health; float damage = msg.ReadRangedSingle(0.0f, 1.0f, 8) * MaxHealth;
if (i < Sections.Length) if (i < Sections.Length)
{ {
SetDamage(i, damage); SetDamage(i, damage);
@@ -5,6 +5,7 @@ using FarseerPhysics;
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;
using System.Collections.Generic; using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
@@ -306,8 +307,8 @@ namespace Barotrauma
public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f) public static void DrawGrid(SpriteBatch spriteBatch, int gridCells, Vector2 gridCenter, Vector2 roundedGridCenter, float alpha = 1.0f)
{ {
var horizontalLine = GUI.Style.GetComponentStyle("HorizontalLine").Sprites[GUIComponent.ComponentState.None].First(); var horizontalLine = GUI.Style.GetComponentStyle("HorizontalLine").GetDefaultSprite();
var verticalLine = GUI.Style.GetComponentStyle("VerticalLine").Sprites[GUIComponent.ComponentState.None].First(); var verticalLine = GUI.Style.GetComponentStyle("VerticalLine").GetDefaultSprite();
Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2; Vector2 topLeft = roundedGridCenter - Vector2.One * GridSize * gridCells / 2;
Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2; Vector2 bottomRight = roundedGridCenter + Vector2.One * GridSize * gridCells / 2;
@@ -324,19 +325,19 @@ namespace Barotrauma
float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY); float expandY = MathHelper.Lerp(30.0f, 0.0f, normalizedDistY);
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
horizontalLine.Sprite, horizontalLine,
new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y), new Vector2(topLeft.X - expandX, -bottomRight.Y + i * GridSize.Y),
new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y), new Vector2(bottomRight.X + expandX, -bottomRight.Y + i * GridSize.Y),
Color.White * (1.0f - normalizedDistY) * alpha, depth: 0.6f, width: 3); Color.White * (1.0f - normalizedDistY) * alpha, depth: 0.6f, width: 3);
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
verticalLine.Sprite, verticalLine,
new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY), new Vector2(topLeft.X + i * GridSize.X, -topLeft.Y + expandY),
new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY), new Vector2(topLeft.X + i * GridSize.X, -bottomRight.Y - expandY),
Color.White * (1.0f - normalizedDistX) * alpha, depth: 0.6f, width: 3); Color.White * (1.0f - normalizedDistX) * alpha, depth: 0.6f, width: 3);
} }
} }
public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null) public void CreateMiniMap(GUIComponent parent, IEnumerable<Entity> pointsOfInterest = null, bool ignoreOutpost = false)
{ {
Rectangle worldBorders = GetDockedBorders(); Rectangle worldBorders = GetDockedBorders();
worldBorders.Location += WorldPosition.ToPoint(); worldBorders.Location += WorldPosition.ToPoint();
@@ -354,7 +355,8 @@ namespace Barotrauma
foreach (Hull hull in Hull.hullList) foreach (Hull hull in Hull.hullList)
{ {
if (hull.Submarine != this && !DockedTo.Contains(hull.Submarine)) continue; if (hull.Submarine != this && !(DockedTo.Contains(hull.Submarine))) continue;
if (ignoreOutpost && !IsEntityFoundOnThisSub(hull, true)) { continue; }
Vector2 relativeHullPos = new Vector2( Vector2 relativeHullPos = new Vector2(
(hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width, (hull.WorldRect.X - worldBorders.X) / (float)worldBorders.Width,
@@ -393,10 +395,20 @@ namespace Barotrauma
errorMsgs.Add(TextManager.Get("NoHullsWarning")); errorMsgs.Add(TextManager.Get("NoHullsWarning"));
} }
if (Info.Type != SubmarineType.OutpostModule ||
(Info.OutpostModuleInfo?.ModuleFlags.Any(f => !f.Equals("hallwayvertical", StringComparison.OrdinalIgnoreCase) && !f.Equals("hallwayhorizontal", StringComparison.OrdinalIgnoreCase)) ?? true))
{
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path))
{
errorMsgs.Add(TextManager.Get("NoWaypointsWarning"));
}
}
if (Info.Type == SubmarineType.Player)
{
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
if (item.GetComponent<Items.Components.Vent>() == null) continue; if (item.GetComponent<Items.Components.Vent>() == null) { continue; }
if (!item.linkedTo.Any()) if (!item.linkedTo.Any())
{ {
errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning")); errorMsgs.Add(TextManager.Get("DisconnectedVentsWarning"));
@@ -408,21 +420,37 @@ namespace Barotrauma
{ {
errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning")); errorMsgs.Add(TextManager.Get("NoHumanSpawnpointWarning"));
} }
if (!WayPoint.WayPointList.Any(wp => wp.ShouldBeSaved && wp.SpawnType == SpawnType.Path))
{
errorMsgs.Add(TextManager.Get("NoWaypointsWarning"));
}
if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null) if (WayPoint.WayPointList.Find(wp => wp.SpawnType == SpawnType.Cargo) == null)
{ {
errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning")); errorMsgs.Add(TextManager.Get("NoCargoSpawnpointWarning"));
} }
if (!Item.ItemList.Any(it => it.GetComponent<Items.Components.Pump>() != null && it.HasTag("ballast"))) if (!Item.ItemList.Any(it => it.GetComponent<Items.Components.Pump>() != null && it.HasTag("ballast")))
{ {
errorMsgs.Add(TextManager.Get("NoBallastTagsWarning")); errorMsgs.Add(TextManager.Get("NoBallastTagsWarning"));
} }
}
else if (Info.Type == SubmarineType.OutpostModule)
{
foreach (Item item in Item.ItemList)
{
var junctionBox = item.GetComponent<PowerTransfer>();
if (junctionBox == null) { continue; }
int doorLinks =
item.linkedTo.Count(lt => lt is Gap || (lt is Item it2 && it2.GetComponent<Door>() != null)) +
Item.ItemList.Count(it2 => it2.linkedTo.Contains(item) && !item.linkedTo.Contains(it2));
for (int i = 0; i < item.Connections.Count; i++)
{
int wireCount = item.Connections[i].Wires.Count(w => w != null);
if (doorLinks + wireCount > Connection.MaxLinked)
{
errorMsgs.Add(TextManager.GetWithVariables("InsufficientFreeConnectionsWarning",
new string[] { "[doorcount]", "[freeconnectioncount]" },
new string[] { doorLinks.ToString(), (Connection.MaxLinked - wireCount).ToString() }));
break;
}
}
}
}
if (Gap.GapList.Any(g => g.linkedTo.Count == 0)) if (Gap.GapList.Any(g => g.linkedTo.Count == 0))
{ {
@@ -1,10 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using System; using System;
using System.Collections.Generic;
using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma namespace Barotrauma
{ {
@@ -58,77 +54,9 @@ namespace Barotrauma
Spacing = 5 Spacing = 5
}; };
//space ScalableFont font = parent.Rect.Width < 350 ? GUI.SmallFont : GUI.Font;
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.03f), descriptionBox.Content.RectTransform), style: null);
new GUITextBlock(new RectTransform(new Vector2(1, 0), descriptionBox.Content.RectTransform), TextManager.Get("submarine.name." + Name, true) ?? Name, font: GUI.LargeFont, wrap: true) { ForceUpperCase = true, CanBeFocused = false }; CreateSpecsWindow(descriptionBox, font);
float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth;
ScalableFont font = descriptionBox.Rect.Width < 350 ? GUI.SmallFont : GUI.Font;
Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio;
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
}
if (!string.IsNullOrEmpty(RecommendedCrewExperience))
{
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
}
if (RequiredContentPackages.Any())
{
var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height);
}
// show what game version the submarine was created on
if (!IsVanillaSubmarine() && GameVersion != null)
{
var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), descriptionBox.Content.RectTransform),
TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height);
}
GUITextBlock.AutoScaleAndNormalize(descriptionBox.Content.Children.Where(c => c is GUITextBlock).Cast<GUITextBlock>());
//space //space
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), descriptionBox.Content.RectTransform), style: null);
@@ -145,5 +73,84 @@ namespace Barotrauma
CanBeFocused = false CanBeFocused = false
}; };
} }
public void CreateSpecsWindow(GUIListBox parent, ScalableFont font)
{
float leftPanelWidth = 0.6f;
float rightPanelWidth = 0.4f / leftPanelWidth;
string className = !HasTag(SubmarineTag.Shuttle) ? TextManager.Get($"submarineclass.{SubmarineClass}") : TextManager.Get("shuttle");
int nameHeight = (int)GUI.LargeFont.MeasureString(DisplayName, true).Y;
int classHeight = (int)GUI.SubHeadingFont.MeasureString(className).Y;
int leftPanelWidthInt = (int)(parent.Rect.Width * leftPanelWidth);
var submarineNameText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, nameHeight + HUDLayoutSettings.Padding / 2), parent.Content.RectTransform), DisplayName, textAlignment: Alignment.CenterLeft, font: GUI.LargeFont) { CanBeFocused = false };
submarineNameText.RectTransform.MinSize = new Point(0, (int)submarineNameText.TextSize.Y);
var submarineClassText = new GUITextBlock(new RectTransform(new Point(leftPanelWidthInt, classHeight), parent.Content.RectTransform), className, textAlignment: Alignment.CenterLeft, font: GUI.SubHeadingFont) { CanBeFocused = false };
submarineClassText.RectTransform.MinSize = new Point(0, (int)submarineClassText.TextSize.Y);
Vector2 realWorldDimensions = Dimensions * Physics.DisplayToRealWorldRatio;
if (realWorldDimensions != Vector2.Zero)
{
string dimensionsStr = TextManager.GetWithVariables("DimensionsFormat", new string[2] { "[width]", "[height]" }, new string[2] { ((int)realWorldDimensions.X).ToString(), ((int)realWorldDimensions.Y).ToString() });
var dimensionsText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("Dimensions"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), dimensionsText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
dimensionsStr, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
dimensionsText.RectTransform.MinSize = new Point(0, dimensionsText.Children.First().Rect.Height);
}
if (RecommendedCrewSizeMax > 0)
{
var crewSizeText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("RecommendedCrewSize"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewSizeText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
RecommendedCrewSizeMin + " - " + RecommendedCrewSizeMax, textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewSizeText.RectTransform.MinSize = new Point(0, crewSizeText.Children.First().Rect.Height);
}
if (!string.IsNullOrEmpty(RecommendedCrewExperience))
{
var crewExperienceText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("RecommendedCrewExperience"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), crewExperienceText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
TextManager.Get(RecommendedCrewExperience), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
crewExperienceText.RectTransform.MinSize = new Point(0, crewExperienceText.Children.First().Rect.Height);
}
if (RequiredContentPackages.Any())
{
var contentPackagesText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("RequiredContentPackages"), textAlignment: Alignment.TopLeft, font: font)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), contentPackagesText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
string.Join(", ", RequiredContentPackages), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
contentPackagesText.RectTransform.MinSize = new Point(0, contentPackagesText.Children.First().Rect.Height);
}
// show what game version the submarine was created on
if (!IsVanillaSubmarine() && GameVersion != null)
{
var versionText = new GUITextBlock(new RectTransform(new Vector2(leftPanelWidth, 0), parent.Content.RectTransform),
TextManager.Get("serverlistversion"), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
new GUITextBlock(new RectTransform(new Vector2(rightPanelWidth, 0.0f), versionText.RectTransform, Anchor.TopRight, Pivot.TopLeft),
GameVersion.ToString(), textAlignment: Alignment.TopLeft, font: font, wrap: true)
{ CanBeFocused = false };
versionText.RectTransform.MinSize = new Point(0, versionText.Children.First().Rect.Height);
}
submarineNameText.AutoScaleHorizontal = true;
GUITextBlock.AutoScaleAndNormalize(parent.Content.Children.Where(c => c is GUITextBlock && c != submarineNameText).Cast<GUITextBlock>());
}
} }
} }

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