Merge branch 'master' of https://github.com/Regalis11/Barotrauma.git into Regalis11-master

This commit is contained in:
Evil Factory
2021-12-15 14:45:31 -03:00
388 changed files with 12646 additions and 8136 deletions
@@ -17,7 +17,7 @@ namespace Barotrauma
fadeOutRoutine = CoroutineManager.StartCoroutine(FadeOutColors(Config.DeadEntityColorFadeOutTime)); fadeOutRoutine = CoroutineManager.StartCoroutine(FadeOutColors(Config.DeadEntityColorFadeOutTime));
} }
private IEnumerable<object> FadeOutColors(float time) private IEnumerable<CoroutineStatus> FadeOutColors(float time)
{ {
float timer = 0; float timer = 0;
while (timer < time) while (timer < time)
@@ -36,7 +36,7 @@ namespace Barotrauma
CharacterStateInfo serverPos = character.MemState.Last(); CharacterStateInfo serverPos = character.MemState.Last();
if (!character.isSynced) if (!character.isSynced)
{ {
SetPosition(serverPos.Position, false); SetPosition(serverPos.Position, lerp: false);
Collider.LinearVelocity = Vector2.Zero; Collider.LinearVelocity = Vector2.Zero;
character.MemLocalState.Clear(); character.MemLocalState.Clear();
character.LastNetworkUpdateID = serverPos.ID; character.LastNetworkUpdateID = serverPos.ID;
@@ -63,7 +63,10 @@ namespace Barotrauma
{ {
foreach (var ic in character.MemState[0].SelectedItem.Components) foreach (var ic in character.MemState[0].SelectedItem.Components)
{ {
if (ic.CanBeSelected) ic.Select(character); if (ic.CanBeSelected)
{
ic.Select(character);
}
} }
} }
character.SelectedConstruction = character.MemState[0].SelectedItem; character.SelectedConstruction = character.MemState[0].SelectedItem;
@@ -98,6 +101,16 @@ namespace Barotrauma
if (distSqrd > 10.0f || !character.CanMove) if (distSqrd > 10.0f || !character.CanMove)
{ {
Collider.TargetRotation = newRotation; Collider.TargetRotation = newRotation;
if (distSqrd > 10.0f)
{
//teleported very far - see if we need to move to another sub
Hull serverHull = Hull.FindHull(ConvertUnits.ToDisplayUnits(newPosition), CurrentHull, newPosition.Y < lowestSubPos);
if (currentHull != null && serverHull != null && serverHull.Submarine != currentHull.Submarine)
{
character.Submarine = serverHull.Submarine;
character.CurrentHull = CurrentHull = serverHull;
}
}
SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false); SetPosition(newPosition, lerp: distSqrd < 5.0f, ignorePlatforms: false);
} }
else else
@@ -159,7 +172,7 @@ namespace Barotrauma
if (!character.isSynced) if (!character.isSynced)
{ {
SetPosition(serverPos.Position, false); SetPosition(serverPos.Position, lerp: false);
Collider.LinearVelocity = Vector2.Zero; Collider.LinearVelocity = Vector2.Zero;
character.MemLocalState.Clear(); character.MemLocalState.Clear();
character.LastNetworkUpdateID = serverPos.ID; character.LastNetworkUpdateID = serverPos.ID;
@@ -194,7 +207,7 @@ namespace Barotrauma
{ {
if (character.SelectedConstruction != serverPos.SelectedItem) if (character.SelectedConstruction != serverPos.SelectedItem)
{ {
serverPos.SelectedItem.TryInteract(character, true, true); serverPos.SelectedItem.TryInteract(character, ignoreRequiredItems: true, forceSelectKey: true);
} }
character.SelectedConstruction = serverPos.SelectedItem; character.SelectedConstruction = serverPos.SelectedItem;
} }
@@ -448,8 +461,8 @@ namespace Barotrauma
{ {
DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace()); DebugConsole.ThrowError("Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace());
GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved", GameAnalyticsManager.AddErrorEventOnce("Ragdoll.Draw:LimbsRemoved",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
"Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.Name + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace()); "Failed to draw a ragdoll, limbs have been removed. Character: \"" + character.SpeciesName + "\", removed: " + character.Removed + "\n" + Environment.StackTrace.CleanupStackTrace());
return; return;
} }
@@ -460,12 +473,17 @@ namespace Barotrauma
} }
float depthOffset = GetDepthOffset(); float depthOffset = GetDepthOffset();
if (!MathUtils.NearlyEqual(depthOffset, 0.0f))
{
foreach (Limb limb in limbs) { limb.ActiveSprite.Depth += depthOffset; }
}
for (int i = 0; i < limbs.Length; i++) for (int i = 0; i < limbs.Length; i++)
{ {
var limb = inversedLimbDrawOrder[i]; inversedLimbDrawOrder[i].Draw(spriteBatch, cam, color);
if (depthOffset != 0.0f) { limb.ActiveSprite.Depth += depthOffset; } }
limb.Draw(spriteBatch, cam, color); if (!MathUtils.NearlyEqual(depthOffset, 0.0f))
if (depthOffset != 0.0f) { limb.ActiveSprite.Depth -= depthOffset; } {
foreach (Limb limb in limbs) { limb.ActiveSprite.Depth -= depthOffset; }
} }
LimbJoints.ForEach(j => j.Draw(spriteBatch)); LimbJoints.ForEach(j => j.Draw(spriteBatch));
} }
@@ -486,7 +504,14 @@ namespace Barotrauma
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
depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f); if (maxDepth > ladder.BackgroundSpriteDepth)
{
depthOffset = Math.Max(ladder.BackgroundSpriteDepth - 0.01f - maxDepth, 0.0f);
}
else
{
depthOffset = Math.Max(ladder.Item.GetDrawDepth() + 0.0001f - minDepth, -minDepth);
}
} }
else else
{ {
@@ -581,10 +606,10 @@ namespace Barotrauma
if (this is HumanoidAnimController humanoid) if (this is HumanoidAnimController humanoid)
{ {
Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos); Vector2 pos = ConvertUnits.ToDisplayUnits(humanoid.RightHandIKPos);
if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; } if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.DrawPosition; }
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos); pos = ConvertUnits.ToDisplayUnits(humanoid.LeftHandIKPos);
if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.Position; } if (humanoid.character.Submarine != null) { pos += humanoid.character.Submarine.DrawPosition; }
GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true); GUI.DrawRectangle(spriteBatch, new Rectangle((int)pos.X, (int)-pos.Y, 4, 4), GUI.Style.Green, true);
Vector2 aimPos = humanoid.AimSourceWorldPos; Vector2 aimPos = humanoid.AimSourceWorldPos;
@@ -447,28 +447,7 @@ namespace Barotrauma
if (GameMain.Client != null) { chatMessage += " " + TextManager.Get("DeathChatNotification"); } if (GameMain.Client != null) { chatMessage += " " + TextManager.Get("DeathChatNotification"); }
if (GameMain.NetworkMember.RespawnManager?.UseRespawnPrompt ?? false) GameMain.NetworkMember.RespawnManager?.ShowRespawnPromptIfNeeded();
{
CoroutineManager.Invoke(() =>
{
if (controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
var respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"),
new string[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") });
respawnPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false);
respawnPrompt.Close();
return true;
};
respawnPrompt.Buttons[1].OnClicked += (btn, userdata) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: true);
respawnPrompt.Close();
return true;
};
}, delay: 5.0f);
}
GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead); GameMain.NetworkMember.AddChatMessage(chatMessage, ChatMessageType.Dead);
GameMain.LightManager.LosEnabled = false; GameMain.LightManager.LosEnabled = false;
@@ -235,7 +235,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.RepairIconThreshold)) { continue; } if (!item.Repairables.Any(r => r.IsBelowRepairIconThreshold)) { 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;
@@ -790,10 +790,7 @@ namespace Barotrauma
return false; return false;
} }
}; };
//force update twice because the listbox is insanely janky listBox.ForceLayoutRecalculation();
//TODO: fix all of the UI :)
listBox.ForceUpdate();
listBox.ForceUpdate();
foreach (var childLayoutGroup in listBox.Content.GetAllChildren<GUILayoutGroup>()) foreach (var childLayoutGroup in listBox.Content.GetAllChildren<GUILayoutGroup>())
{ {
childLayoutGroup.Recalculate(); childLayoutGroup.Recalculate();
@@ -305,9 +305,9 @@ namespace Barotrauma
case 0: //NetEntityEvent.Type.InventoryState 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 + ")";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg.Replace("[name]", Name));
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ClientRead:NoInventory" + ID, GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", SpeciesName));
//read anyway to prevent messing up reading the rest of the message //read anyway to prevent messing up reading the rest of the message
_ = msg.ReadUInt16(); _ = msg.ReadUInt16();
@@ -371,21 +371,22 @@ namespace Barotrauma
if (attackLimbIndex == 255 || Removed) { break; } if (attackLimbIndex == 255 || Removed) { break; }
if (attackLimbIndex >= AnimController.Limbs.Length) if (attackLimbIndex >= AnimController.Limbs.Length)
{ {
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Limb index out of bounds (character: {Name}, limb index: {attackLimbIndex}, limb count: {AnimController.Limbs.Length})"); DebugConsole.ThrowError($"Received invalid {(eventType == 4 ? "SetAttackTarget" : "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];
Limb targetLimb = null; Limb targetLimb = null;
if (!(FindEntityByID(targetEntityID) is IDamageable targetEntity)) IDamageable targetEntity = FindEntityByID(targetEntityID) as IDamageable;
if (targetEntity == null && eventType == 4)
{ {
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target entity not found (ID {targetEntityID})"); DebugConsole.ThrowError($"Received invalid SetAttackTarget message. Target entity not found (ID {targetEntityID})");
break; break;
} }
if (targetEntity is Character targetCharacter) if (targetEntity is Character targetCharacter)
{ {
if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length) if (targetLimbIndex >= targetCharacter.AnimController.Limbs.Length)
{ {
DebugConsole.ThrowError($"Received invalid SetAttack/ExecuteAttack message. Target limb index out of bounds (target character: {targetCharacter.Name}, limb index: {targetLimbIndex}, limb count: {targetCharacter.AnimController.Limbs.Length})"); DebugConsole.ThrowError($"Received invalid {(eventType == 4 ? "SetAttackTarget" : "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];
@@ -650,7 +651,7 @@ namespace Barotrauma
{ {
string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})"; string errorMsg = $"Error in CharacterNetworking.ReadStatus: affliction not found ({afflictionName})";
causeOfDeathType = CauseOfDeathType.Unknown; causeOfDeathType = CauseOfDeathType.Unknown;
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:AfflictionIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
} }
else else
{ {
@@ -681,7 +682,7 @@ namespace Barotrauma
if (severedJointIndex < 0 || severedJointIndex >= AnimController.LimbJoints.Length) if (severedJointIndex < 0 || severedJointIndex >= AnimController.LimbJoints.Length)
{ {
string errorMsg = $"Error in CharacterNetworking.ReadStatus: severed joint index out of bounds (index: {severedJointIndex}, joint count: {AnimController.LimbJoints.Length})"; string errorMsg = $"Error in CharacterNetworking.ReadStatus: severed joint index out of bounds (index: {severedJointIndex}, joint count: {AnimController.LimbJoints.Length})";
GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("CharacterNetworking.ReadStatus:JointIndexOutOfBounts", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
} }
else else
{ {
@@ -453,13 +453,14 @@ namespace Barotrauma
{ {
case Alignment.Left: case Alignment.Left:
healthWindow.RectTransform.SetPosition(Anchor.BottomLeft); healthWindow.RectTransform.SetPosition(Anchor.BottomLeft);
healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.InventoryAreaLower.X, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
break; break;
case Alignment.Right: case Alignment.Right:
healthWindow.RectTransform.SetPosition(Anchor.BottomRight); healthWindow.RectTransform.SetPosition(Anchor.BottomRight);
healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
break; break;
} }
healthWindow.RectTransform.AbsoluteOffset = new Point(HUDLayoutSettings.Padding, screenResolution.Y - HUDLayoutSettings.ChatBoxArea.Y + HUDLayoutSettings.Padding);
healthWindow.RectTransform.RecalculateChildren(false); healthWindow.RectTransform.RecalculateChildren(false);
} }
@@ -648,8 +649,9 @@ namespace Barotrauma
grainColor = oxygenLowGrainColor; grainColor = oxygenLowGrainColor;
} }
foreach (Affliction affliction in afflictions) foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{ {
var affliction = kvp.Key;
distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength()); distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength());
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength()); blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength()); radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
@@ -662,16 +664,6 @@ namespace Barotrauma
grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2)); grainColor = Color.Lerp(grainColor, afflictionGrainColor, (float)Math.Pow(1.0f - oxygenLowStrength, 2));
} }
} }
foreach (LimbHealth limbHealth in limbHealths)
{
foreach (Affliction affliction in limbHealth.Afflictions)
{
distortStrength = Math.Max(distortStrength, affliction.GetScreenDistortStrength());
blurStrength = Math.Max(blurStrength, affliction.GetScreenBlurStrength());
radialDistortStrength = Math.Max(radialDistortStrength, affliction.GetRadialDistortStrength());
chromaticAberrationStrength = Math.Max(chromaticAberrationStrength, affliction.GetChromaticAberrationStrength());
}
}
Character.RadialDistortStrength = radialDistortStrength; Character.RadialDistortStrength = radialDistortStrength;
Character.ChromaticAberrationStrength = chromaticAberrationStrength; Character.ChromaticAberrationStrength = chromaticAberrationStrength;
@@ -777,7 +769,7 @@ namespace Barotrauma
{ {
// If no limb is selected or highlighted, select the one with the most critical afflictions. // If no limb is selected or highlighted, select the one with the most critical afflictions.
var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault(); var affliction = SortAfflictionsBySeverity(GetAllAfflictions(a => a.Prefab.IndicatorLimb != LimbType.None)).FirstOrDefault();
if (affliction.DamagePerSecond > 0 || affliction.Strength > 0) if (affliction != null && (affliction.DamagePerSecond > 0 || affliction.Strength > 0))
{ {
var limbHealth = GetMatchingLimbHealth(affliction); var limbHealth = GetMatchingLimbHealth(affliction);
if (limbHealth != null) if (limbHealth != null)
@@ -788,7 +780,7 @@ namespace Barotrauma
else else
{ {
// If no affliction is critical, select the limb which has most damage. // If no affliction is critical, select the limb which has most damage.
var limbHealth = limbHealths.OrderByDescending(l => l.TotalDamage).FirstOrDefault(); var limbHealth = limbHealths.OrderByDescending(l => GetTotalDamage(l)).FirstOrDefault();
selectedLimbIndex = limbHealths.IndexOf(limbHealth); selectedLimbIndex = limbHealths.IndexOf(limbHealth);
} }
} }
@@ -805,7 +797,27 @@ namespace Barotrauma
{ {
var treatmentButton = component.GetChild<GUIButton>(); var treatmentButton = component.GetChild<GUIButton>();
if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; } if (!(treatmentButton?.UserData is ItemPrefab itemPrefab)) { continue; }
treatmentButton.Enabled = Character.Controlled.Inventory.AllItems.Any(it => it.prefab == itemPrefab); var matchingItem = Character.Controlled.Inventory.FindItem(it => it.prefab == itemPrefab, recursive: true);
treatmentButton.Enabled = matchingItem != null;
if (treatmentButton.Enabled && treatmentButton.State == GUIComponent.ComponentState.Hover)
{
//highlight the slot the treatment item is in
var rootContainer = matchingItem.GetRootContainer() ?? matchingItem;
var index = Character.Controlled.Inventory.FindIndex(rootContainer);
if (Character.Controlled.Inventory.visualSlots != null && index > -1 && index < Character.Controlled.Inventory.visualSlots.Length &&
Character.Controlled.Inventory.visualSlots[index].HighlightTimer <= 0.0f)
{
Character.Controlled.Inventory.visualSlots[index].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f);
}
}
if (matchingItem != null && !string.IsNullOrEmpty(treatmentButton.ToolTip)) { continue; }
treatmentButton.ToolTip = $"‖color:255,255,255,255‖{itemPrefab.Name}‖color:end‖" + '\n' + itemPrefab.Description;
if (treatmentButton.Enabled)
{
treatmentButton.ToolTip =
$"‖color:gui.green‖[{TextManager.Get(PlayerInput.MouseButtonsSwapped() ? "input.rightmouse" : "input.leftmouse")}] {TextManager.Get("quickuseaction.usetreatment")}‖color:end‖" + '\n'
+ treatmentButton.RawToolTip;
}
foreach (GUIComponent child in treatmentButton.Children) foreach (GUIComponent child in treatmentButton.Children)
{ {
child.Enabled = treatmentButton.Enabled; child.Enabled = treatmentButton.Enabled;
@@ -951,8 +963,9 @@ namespace Barotrauma
UpdateAlignment(); UpdateAlignment();
} }
foreach (Affliction affliction in afflictions) foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{ {
var affliction = kvp.Key;
if (affliction.Prefab.AfflictionOverlay != null) if (affliction.Prefab.AfflictionOverlay != null)
{ {
Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay; Sprite ScreenAfflictionOverlay = affliction.Prefab.AfflictionOverlay;
@@ -964,7 +977,7 @@ namespace Barotrauma
float damageOverlayAlpha = DamageOverlayTimer; float damageOverlayAlpha = DamageOverlayTimer;
if (Vitality < MaxVitality * 0.1f) if (Vitality < MaxVitality * 0.1f)
{ {
damageOverlayAlpha = Math.Max(1.0f - (Vitality / maxVitality * 10.0f), damageOverlayAlpha); damageOverlayAlpha = Math.Max(1.0f - (Vitality / UnmodifiedMaxVitality * 10.0f), damageOverlayAlpha);
} }
else else
{ {
@@ -1139,18 +1152,34 @@ namespace Barotrauma
afflictionIconContainer.Content.ClearChildren(); afflictionIconContainer.Content.ClearChildren();
return; return;
} }
var currentAfflictions = GetMatchingAfflictions(selectedLimb, a => a.ShouldShowIcon(Character));
if (currentAfflictions.Any(a => !displayedAfflictions.Any(d => d.affliction == a)) || if (afflictionsDirty())
displayedAfflictions.Any(a => !currentAfflictions.Contains(a.affliction)))
{ {
var currentAfflictions = afflictions.Where(a => ShouldDisplayAfflictionOnLimb(a, selectedLimb)).Select(a => a.Key);
CreateAfflictionInfos(currentAfflictions); CreateAfflictionInfos(currentAfflictions);
CreateRecommendedTreatments(); CreateRecommendedTreatments();
} }
//update recommended treatments if the strength of some displayed affliction has changed by > 1 //update recommended treatments if the strength of some displayed affliction has changed by > 1
else if (displayedAfflictions.Any(d => Math.Abs(d.strength - currentAfflictions.First(a => a == d.affliction).Strength) > 1.0f)) else if (displayedAfflictions.Any(d => Math.Abs(d.strength - d.affliction.Strength) > 1.0f))
{ {
CreateRecommendedTreatments(); CreateRecommendedTreatments();
} }
bool afflictionsDirty()
{
//not displaying one of the current afflictions -> dirty
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{
if (!ShouldDisplayAfflictionOnLimb(kvp, selectedLimb)) { continue; }
if (!displayedAfflictions.Any(d => d.affliction == kvp.Key)) { return true; }
}
//displaying an affliction we no longer have -> dirty
foreach ((Affliction affliction, float strength) in displayedAfflictions)
{
if (!afflictions.Any(a => a.Key == affliction)) { return true; }
}
return false;
}
} }
private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions) private void CreateAfflictionInfos(IEnumerable<Affliction> afflictions)
@@ -1158,7 +1187,7 @@ namespace Barotrauma
afflictionIconContainer.ClearChildren(); afflictionIconContainer.ClearChildren();
displayedAfflictions.Clear(); displayedAfflictions.Clear();
Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions).FirstOrDefault(); Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictions, excludeBuffs: false).FirstOrDefault();
GUIButton buttonToSelect = null; GUIButton buttonToSelect = null;
foreach (Affliction affliction in afflictions) foreach (Affliction affliction in afflictions)
@@ -1249,7 +1278,7 @@ namespace Barotrauma
foreach (string treatment in treatmentSuitability.Keys.ToList()) foreach (string treatment in treatmentSuitability.Keys.ToList())
{ {
//prefer suggestions for items the player has //prefer suggestions for items the player has
if (Character.Controlled.Inventory.FindItemByIdentifier(treatment) != null) if (Character.Controlled.Inventory.FindItemByIdentifier(treatment, recursive: true) != null)
{ {
treatmentSuitability[treatment] *= 10.0f; treatmentSuitability[treatment] *= 10.0f;
} }
@@ -1288,12 +1317,11 @@ namespace Barotrauma
var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader") var innerFrame = new GUIButton(new RectTransform(Vector2.One, itemSlot.RectTransform, Anchor.Center, Pivot.Center, scaleBasis: ScaleBasis.Smallest), style: "SubtreeHeader")
{ {
UserData = item, UserData = item,
ToolTip = $"‖color:255,255,255,255‖{item.Name}‖color:end‖" + '\n' + item.Description,
DisabledColor = Color.White * 0.1f, DisabledColor = Color.White * 0.1f,
OnClicked = (btn, userdata) => OnClicked = (btn, userdata) =>
{ {
if (!(userdata is ItemPrefab itemPrefab)) { return false; } if (!(userdata is ItemPrefab itemPrefab)) { return false; }
var item = Character.Controlled.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab); var item = Character.Controlled.Inventory.FindItem(it => it.prefab == itemPrefab, recursive: true);
if (item == null) { return false; } if (item == null) { return false; }
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex); Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == selectedLimbIndex);
item.ApplyTreatment(Character.Controlled, Character, targetLimb); item.ApplyTreatment(Character.Controlled, Character, targetLimb);
@@ -1445,7 +1473,7 @@ namespace Barotrauma
var potentialTreatment = Inventory.DraggingItems.FirstOrDefault(); var potentialTreatment = Inventory.DraggingItems.FirstOrDefault();
if (potentialTreatment == null && GUI.MouseOn?.UserData is ItemPrefab itemPrefab) if (potentialTreatment == null && GUI.MouseOn?.UserData is ItemPrefab itemPrefab)
{ {
potentialTreatment = Character.Controlled.Inventory.AllItems.FirstOrDefault(it => it.prefab == itemPrefab); potentialTreatment = Character.Controlled.Inventory.FindItem(it => it.prefab == itemPrefab, recursive: true);
} }
potentialTreatment ??= Inventory.SelectedSlot?.Item; potentialTreatment ??= Inventory.SelectedSlot?.Item;
@@ -1585,7 +1613,7 @@ namespace Barotrauma
int i = 0; int i = 0;
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
if (limbHealth.IndicatorSprite == null) continue; if (limbHealth.IndicatorSprite == null) { continue; }
float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height); float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
@@ -1604,6 +1632,7 @@ namespace Barotrauma
} }
} }
private static readonly List<Affliction> afflictionsDisplayedOnLimb = new List<Affliction>();
private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight) private void DrawHealthWindow(SpriteBatch spriteBatch, Rectangle drawArea, bool allowHighlight)
{ {
if (Character.Removed) { return; } if (Character.Removed) { return; }
@@ -1614,21 +1643,32 @@ namespace Barotrauma
int i = 0; int i = 0;
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
if (limbHealth.IndicatorSprite == null) continue; if (limbHealth.IndicatorSprite == null) { continue; }
Rectangle limbEffectiveArea = new Rectangle(limbHealth.IndicatorSprite.SourceRect.X + limbHealth.HighlightArea.X, Rectangle limbEffectiveArea = new Rectangle(limbHealth.IndicatorSprite.SourceRect.X + limbHealth.HighlightArea.X,
limbHealth.IndicatorSprite.SourceRect.Y + limbHealth.HighlightArea.Y, limbHealth.IndicatorSprite.SourceRect.Y + limbHealth.HighlightArea.Y,
limbHealth.HighlightArea.Width, limbHealth.HighlightArea.Width,
limbHealth.HighlightArea.Height); limbHealth.HighlightArea.Height);
float damageLerp = limbHealth.TotalDamage > 0.0f ? MathHelper.Lerp(0.2f, 1.0f, limbHealth.TotalDamage / 100.0f) : 0.0f; float totalDamage = GetTotalDamage(limbHealth);
var tempAfflictions = GetMatchingAfflictions(limbHealth, a => true); float damageLerp = totalDamage > 0.0f ? MathHelper.Lerp(0.2f, 1.0f, totalDamage / 100.0f) : 0.0f;
float negativeEffect = tempAfflictions.Where(a => !a.Prefab.IsBuff && a.ShouldShowIcon(Character)).Sum(a => a.Strength); float negativeEffect = 0.0f, positiveEffect = 0.0f;
//float negativeMaxEffect = tempAfflictions.Where(a => !a.Prefab.IsBuff).Sum(a => a.Prefab.MaxStrength); foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
float positiveEffect = tempAfflictions.Where(a => a.Prefab.IsBuff && a.ShouldShowIcon(Character)).Sum(a => a.Strength * 0.2f); {
//float positiveMaxEffect = tempAfflictions.Where(a => a.Prefab.IsBuff).Sum(a => a.Prefab.MaxStrength); if (kvp.Value != limbHealth) { continue; }
var affliction = kvp.Key;
if (!affliction.ShouldShowIcon(Character)) { continue; }
if (!affliction.Prefab.IsBuff)
{
negativeEffect += affliction.Strength;
}
else
{
positiveEffect += affliction.Strength * 0.2f;
}
}
float midPoint = (float)limbEffectiveArea.Center.Y / (float)limbHealth.IndicatorSprite.Texture.Height; float midPoint = (float)limbEffectiveArea.Center.Y / (float)limbHealth.IndicatorSprite.Texture.Height;
float fadeDist = 0.6f * (float)limbEffectiveArea.Height / (float)limbHealth.IndicatorSprite.Texture.Height; float fadeDist = 0.6f * (float)limbEffectiveArea.Height / (float)limbHealth.IndicatorSprite.Texture.Height;
@@ -1695,7 +1735,7 @@ namespace Barotrauma
drawArea.Width / (float)limbIndicatorOverlay.FrameSize.X, drawArea.Width / (float)limbIndicatorOverlay.FrameSize.X,
drawArea.Height / (float)limbIndicatorOverlay.FrameSize.Y); drawArea.Height / (float)limbIndicatorOverlay.FrameSize.Y);
int frame = 0; int frame;
int frameCount = 17; int frameCount = 17;
if (limbIndicatorOverlayAnimState >= frameCount * 2) limbIndicatorOverlayAnimState = 0.0f; if (limbIndicatorOverlayAnimState >= frameCount * 2) limbIndicatorOverlayAnimState = 0.0f;
if (limbIndicatorOverlayAnimState < frameCount) if (limbIndicatorOverlayAnimState < frameCount)
@@ -1739,14 +1779,13 @@ namespace Barotrauma
i = 0; i = 0;
foreach (LimbHealth limbHealth in limbHealths) foreach (LimbHealth limbHealth in limbHealths)
{ {
IEnumerable<Affliction> thisAfflictions = limbHealth.Afflictions.Where(a => a.ShouldShowIcon(Character)); afflictionsDisplayedOnLimb.Clear();
thisAfflictions = thisAfflictions.Concat(afflictions.Where(a => foreach (var affliction in afflictions)
{ {
Limb indicatorLimb = Character.AnimController.GetLimb(a.Prefab.IndicatorLimb); if (ShouldDisplayAfflictionOnLimb(affliction, limbHealth)) { afflictionsDisplayedOnLimb.Add(affliction.Key); }
return indicatorLimb != null && indicatorLimb.HealthIndex == i && a.ShouldShowIcon(Character); }
}));
if (thisAfflictions.Count() <= 0) { i++; continue; } if (!afflictionsDisplayedOnLimb.Any()) { i++; continue; }
if (limbHealth.IndicatorSprite == null) { continue; } if (limbHealth.IndicatorSprite == null) { continue; }
float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height); float scale = Math.Min(drawArea.Width / (float)limbHealth.IndicatorSprite.SourceRect.Width, drawArea.Height / (float)limbHealth.IndicatorSprite.SourceRect.Height);
@@ -1757,12 +1796,12 @@ namespace Barotrauma
Vector2 iconPos = highlightArea.Center.ToVector2(); Vector2 iconPos = highlightArea.Center.ToVector2();
//Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault(); //Affliction mostSevereAffliction = thisAfflictions.FirstOrDefault(a => !a.Prefab.IsBuff && !thisAfflictions.Any(a2 => !a2.Prefab.IsBuff && a2.Strength > a.Strength)) ?? thisAfflictions.FirstOrDefault();
Affliction mostSevereAffliction = SortAfflictionsBySeverity(thisAfflictions, excludeBuffs: false).FirstOrDefault(); Affliction mostSevereAffliction = SortAfflictionsBySeverity(afflictionsDisplayedOnLimb, excludeBuffs: false).FirstOrDefault();
if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); } if (mostSevereAffliction != null) { DrawLimbAfflictionIcon(spriteBatch, mostSevereAffliction, iconScale, ref iconPos); }
if (thisAfflictions.Count() > 1) if (afflictionsDisplayedOnLimb.Count() > 1)
{ {
string additionalAfflictionCount = $"+{thisAfflictions.Count() - 1}"; string additionalAfflictionCount = $"+{afflictionsDisplayedOnLimb.Count() - 1}";
Vector2 displace = GUI.SubHeadingFont.MeasureString(additionalAfflictionCount); Vector2 displace = GUI.SubHeadingFont.MeasureString(additionalAfflictionCount);
GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X * 1.1f, -displace.Y * 0.45f), Color.Black * 0.75f); GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X * 1.1f, -displace.Y * 0.45f), Color.Black * 0.75f);
GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X, -displace.Y * 0.5f), Color.White); GUI.SubHeadingFont.DrawString(spriteBatch, additionalAfflictionCount, iconPos + new Vector2(displace.X, -displace.Y * 0.5f), Color.White);
@@ -1785,6 +1824,22 @@ namespace Barotrauma
} }
} }
private bool ShouldDisplayAfflictionOnLimb(KeyValuePair<Affliction, LimbHealth> kvp, LimbHealth limbHealth)
{
if (!kvp.Key.ShouldShowIcon(Character)) { return false; }
if (kvp.Value == limbHealth)
{
return true;
}
else if (kvp.Value == null)
{
Limb indicatorLimb = Character.AnimController.GetLimb(kvp.Key.Prefab.IndicatorLimb);
return indicatorLimb != null && indicatorLimb.HealthIndex == limbHealths.IndexOf(limbHealth);
}
return false;
}
private void DrawLimbAfflictionIcon(SpriteBatch spriteBatch, Affliction affliction, float iconScale, ref Vector2 iconPos) private void DrawLimbAfflictionIcon(SpriteBatch spriteBatch, Affliction affliction, float iconScale, ref Vector2 iconPos)
{ {
if (!affliction.ShouldShowIcon(Character) || affliction.Prefab.Icon == null) { return; } if (!affliction.ShouldShowIcon(Character) || affliction.Prefab.Icon == null) { return; }
@@ -1815,8 +1870,7 @@ namespace Barotrauma
healthBarHolder.Visible = value; healthBarHolder.Visible = value;
} }
private readonly List<(AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(AfflictionPrefab afflictionPrefab, float strength)>(); private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>();
private readonly List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)> newLimbAfflictions = new List<(LimbHealth limb, AfflictionPrefab afflictionPrefab, float strength)>();
private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>(); private readonly List<(AfflictionPrefab.PeriodicEffect effect, float timer)> newPeriodicEffects = new List<(AfflictionPrefab.PeriodicEffect effect, float timer)>();
public void ClientRead(IReadMessage inc) public void ClientRead(IReadMessage inc)
@@ -1846,47 +1900,9 @@ namespace Barotrauma
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
} }
newAfflictions.Add((afflictionPrefab, afflictionStrength)); newAfflictions.Add((null, afflictionPrefab, afflictionStrength));
} }
foreach (Affliction affliction in afflictions)
{
//deactivate afflictions that weren't included in the network message
if (!newAfflictions.Any(a => a.afflictionPrefab == affliction.Prefab))
{
affliction.Strength = 0.0f;
}
}
foreach (var (afflictionPrefab, strength) in newAfflictions)
{
Affliction existingAffliction = afflictions.Find(a => a.Prefab == afflictionPrefab);
if (existingAffliction == null)
{
existingAffliction = afflictionPrefab.Instantiate(strength);
afflictions.Add(existingAffliction);
}
existingAffliction.SetStrength(strength);
if (existingAffliction == stunAffliction)
{
Character.SetStun(existingAffliction.Strength, true, true);
}
foreach (var periodicEffect in newPeriodicEffects)
{
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
//timer has wrapped around, apply the effect
if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{
existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
{
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: null);
}
}
}
}
newLimbAfflictions.Clear();
byte limbAfflictionCount = inc.ReadByte(); byte limbAfflictionCount = inc.ReadByte();
for (int i = 0; i < limbAfflictionCount; i++) for (int i = 0; i < limbAfflictionCount; i++)
{ {
@@ -1912,43 +1928,50 @@ namespace Barotrauma
float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8); float periodicAfflictionTimer = inc.ReadRangedSingle(afflictionPrefab.PeriodicEffects[j].MinInterval, afflictionPrefab.PeriodicEffects[j].MaxInterval, 8);
newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer)); newPeriodicEffects.Add((afflictionPrefab.PeriodicEffects[j], periodicAfflictionTimer));
} }
newLimbAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength)); newAfflictions.Add((limbHealths[limbIndex], afflictionPrefab, afflictionStrength));
} }
foreach (LimbHealth limbHealth in limbHealths) foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{ {
foreach (Affliction affliction in limbHealth.Afflictions) //deactivate afflictions that weren't included in the network message
if (!newAfflictions.Any(a => kvp.Key.Prefab == a.afflictionPrefab && kvp.Value == a.limb))
{ {
//deactivate afflictions that weren't included in the network message kvp.Key.Strength = 0.0f;
if (!newLimbAfflictions.Any(a => a.limb == limbHealth && a.afflictionPrefab == affliction.Prefab)) }
}
foreach (var (limb, afflictionPrefab, strength) in newAfflictions)
{
Affliction existingAffliction = null;
foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
{
if (kvp.Key.Prefab == afflictionPrefab && kvp.Value == limb)
{ {
affliction.Strength = 0.0f; existingAffliction = kvp.Key;
break;
} }
} }
if (existingAffliction == null)
foreach (var (limb, afflictionPrefab, strength) in newLimbAfflictions)
{ {
if (limb != limbHealth) { continue; } existingAffliction = afflictionPrefab.Instantiate(strength);
Affliction existingAffliction = limbHealth.Afflictions.Find(a => a.Prefab == afflictionPrefab); afflictions.Add(existingAffliction, limb);
if (existingAffliction == null) }
existingAffliction.SetStrength(strength);
if (existingAffliction == stunAffliction)
{
Character.SetStun(existingAffliction.Strength, true, true);
}
foreach (var periodicEffect in newPeriodicEffects)
{
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
//timer has wrapped around, apply the effect
if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {
existingAffliction = afflictionPrefab.Instantiate(strength); existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer;
limbHealth.Afflictions.Add(existingAffliction); foreach (StatusEffect effect in periodicEffect.effect.StatusEffects)
}
existingAffliction.SetStrength(strength);
foreach (var periodicEffect in newPeriodicEffects)
{
if (!existingAffliction.Prefab.PeriodicEffects.Contains(periodicEffect.effect)) { continue; }
//timer has wrapped around, apply the effect
if (periodicEffect.timer - existingAffliction.PeriodicEffectTimers[periodicEffect.effect] > periodicEffect.effect.MinInterval / 2)
{ {
existingAffliction.PeriodicEffectTimers[periodicEffect.effect] = periodicEffect.timer; Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb));
foreach (StatusEffect effect in periodicEffect.effect.StatusEffects) existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
{
Limb targetLimb = Character.AnimController.Limbs.FirstOrDefault(l => l.HealthIndex == limbHealths.IndexOf(limb));
existingAffliction.ApplyStatusEffect(ActionType.OnActive, effect, deltaTime: 1.0f, this, targetLimb: targetLimb);
}
} }
} }
} }
@@ -1966,14 +1989,13 @@ namespace Barotrauma
limb.BurnOverlayStrength = 0.0f; limb.BurnOverlayStrength = 0.0f;
limb.DamageOverlayStrength = 0.0f; limb.DamageOverlayStrength = 0.0f;
if (limbHealths[limb.HealthIndex].Afflictions.Count == 0) continue; foreach (KeyValuePair<Affliction, LimbHealth> kvp in afflictions)
foreach (Affliction a in limbHealths[limb.HealthIndex].Afflictions)
{ {
limb.BurnOverlayStrength += a.Strength / Math.Min(a.Prefab.MaxStrength, 100) * a.Prefab.BurnOverlayAlpha; if (kvp.Value != limbHealths[limb.HealthIndex]) { continue; }
limb.DamageOverlayStrength += a.Strength / Math.Min(a.Prefab.MaxStrength, 100) * a.Prefab.DamageOverlayAlpha; var affliction = kvp.Key;
limb.BurnOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.BurnOverlayAlpha;
limb.DamageOverlayStrength += affliction.Strength / Math.Min(affliction.Prefab.MaxStrength, 100) * affliction.Prefab.DamageOverlayAlpha;
} }
limb.BurnOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count;
limb.DamageOverlayStrength /= limbHealths[limb.HealthIndex].Afflictions.Count;
} }
} }
@@ -30,7 +30,7 @@ namespace Barotrauma
foreach (SkillPrefab skill in Skills) foreach (SkillPrefab skill in Skills)
{ {
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform), new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillContainer.RectTransform),
" - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.X + " - " + (int)skill.LevelRange.Y), " - " + TextManager.AddPunctuation(':', TextManager.Get("SkillName." + skill.Identifier), (int)skill.LevelRange.Start + " - " + (int)skill.LevelRange.End),
font: GUI.SmallFont); font: GUI.SmallFont);
} }
@@ -730,8 +730,6 @@ namespace Barotrauma
} }
} }
body.Dir = Dir;
float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes"); float herpesStrength = character.CharacterHealth.GetAfflictionStrength("spaceherpes");
bool hideLimb = Hide || bool hideLimb = Hide ||
@@ -138,7 +138,7 @@ namespace Barotrauma
var newMsg = queuedMessages.Dequeue(); var newMsg = queuedMessages.Dequeue();
AddMessage(newMsg); AddMessage(newMsg);
if (GameSettings.SaveDebugConsoleLogs) if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging)
{ {
unsavedMessages.Add(newMsg); unsavedMessages.Add(newMsg);
if (unsavedMessages.Count >= messagesPerFile) if (unsavedMessages.Count >= messagesPerFile)
@@ -274,7 +274,10 @@ namespace Barotrauma
AddMessage(newMsg); AddMessage(newMsg);
} }
if (GameSettings.SaveDebugConsoleLogs) unsavedMessages.Add(newMsg); if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging)
{
unsavedMessages.Add(newMsg);
}
} }
} }
} }
@@ -537,7 +540,27 @@ namespace Barotrauma
return; return;
} }
GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName); float difficulty = 40;
if (args.Length > 1)
{
float.TryParse(args[1], out difficulty);
}
LevelGenerationParams levelGenerationParams = null;
if (args.Length > 2)
{
string levelGenerationIdentifier = args[2];
levelGenerationParams = LevelGenerationParams.LevelParams.FirstOrDefault(p => p.Identifier == levelGenerationIdentifier);
}
if (SubmarineInfo.SavedSubmarines.None(s => s.Name.ToLowerInvariant() == subName.ToLowerInvariant()))
{
ThrowError($"Cannot find a sub that matches the name \"{subName}\".");
return;
}
GameMain.MainMenuScreen.QuickStart(fixedSeed: false, subName, difficulty, levelGenerationParams);
}, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().ToArray() })); }, getValidArgs: () => new[] { SubmarineInfo.SavedSubmarines.Select(s => s.Name).Distinct().ToArray() }));
commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) => commands.Add(new Command("steamnetdebug", "steamnetdebug: Toggles Steamworks networking debug logging.", (string[] args) =>
@@ -734,13 +757,10 @@ namespace Barotrauma
AssignOnExecute("teleportcharacter|teleport", (string[] args) => AssignOnExecute("teleportcharacter|teleport", (string[] args) =>
{ {
Character tpCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false); Character tpCharacter = (args.Length == 0) ? Character.Controlled : FindMatchingCharacter(args, false);
if (tpCharacter == null) return; if (tpCharacter != null)
{
var cam = GameMain.GameScreen.Cam; tpCharacter.TeleportTo(GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition));
tpCharacter.AnimController.CurrentHull = null; }
tpCharacter.Submarine = null;
tpCharacter.AnimController.SetPosition(ConvertUnits.ToSimUnits(cam.ScreenToWorld(PlayerInput.MousePosition)));
tpCharacter.AnimController.FindHull(cam.ScreenToWorld(PlayerInput.MousePosition), true);
}); });
AssignOnExecute("spawn|spawncharacter", (string[] args) => AssignOnExecute("spawn|spawncharacter", (string[] args) =>
@@ -1413,7 +1433,7 @@ namespace Barotrauma
commands.Add(new Command("analyzeitem", "analyzeitem: Analyzes one item for exploits.", (string[] args) => commands.Add(new Command("analyzeitem", "analyzeitem: Analyzes one item for exploits.", (string[] args) =>
{ {
if (args.Length < 1) return; if (args.Length < 1) { return; }
List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>(); List<FabricationRecipe> fabricableItems = new List<FabricationRecipe>();
foreach (ItemPrefab iPrefab in ItemPrefab.Prefabs) foreach (ItemPrefab iPrefab in ItemPrefab.Prefabs)
@@ -1447,10 +1467,11 @@ namespace Barotrauma
{ {
foreach (ItemPrefab ingredientItemPrefab in ingredient.ItemPrefabs) foreach (ItemPrefab ingredientItemPrefab in ingredient.ItemPrefabs)
{ {
NewMessage(" Its ingredient " + ingredientItemPrefab.Name + " has base cost " + ingredientItemPrefab.DefaultPrice.Price); int defaultPrice = ingredientItemPrefab.DefaultPrice?.Price ?? 0;
totalPrice += ingredientItemPrefab.DefaultPrice.Price; NewMessage(" Its ingredient " + ingredientItemPrefab.Name + " has base cost " + defaultPrice);
totalPrice += defaultPrice;
totalBestPrice += ingredientItemPrefab.GetMinPrice(); totalBestPrice += ingredientItemPrefab.GetMinPrice();
int basePrice = ingredientItemPrefab.DefaultPrice.Price; int basePrice = defaultPrice;
foreach (KeyValuePair<string, PriceInfo> ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder()) foreach (KeyValuePair<string, PriceInfo> ingredientItemLocationPrice in ingredientItemPrefab.GetBuyPricesUnder())
{ {
if (basePrice > ingredientItemLocationPrice.Value.Price) if (basePrice > ingredientItemLocationPrice.Value.Price)
@@ -1616,7 +1637,7 @@ namespace Barotrauma
var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == parentItem); var fabricationRecipe = fabricableItems.Find(f => f.TargetItem == parentItem);
int totalValue = 0; int totalValue = 0;
NewMessage(parentItem.Name + " has the price " + parentItem.DefaultPrice.Price); NewMessage(parentItem.Name + " has the price " + (parentItem.DefaultPrice?.Price ?? 0));
if (fabricationRecipe != null) if (fabricationRecipe != null)
{ {
NewMessage(" It constructs from:"); NewMessage(" It constructs from:");
@@ -1625,8 +1646,9 @@ namespace Barotrauma
{ {
foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs) foreach (ItemPrefab itemPrefab in requiredItem.ItemPrefabs)
{ {
NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price); int defaultPrice = itemPrefab.DefaultPrice?.Price ?? 0;
totalValue += itemPrefab.DefaultPrice.Price; NewMessage(" " + itemPrefab.Name + " has the price " + defaultPrice);
totalValue += defaultPrice;
} }
} }
NewMessage("Its total value was: " + totalValue); NewMessage("Its total value was: " + totalValue);
@@ -1637,10 +1659,16 @@ namespace Barotrauma
{ {
ItemPrefab itemPrefab = ItemPrefab itemPrefab =
(MapEntityPrefab.Find(deconstructItem.ItemIdentifier, identifier: null, showErrorMessages: false) ?? (MapEntityPrefab.Find(deconstructItem.ItemIdentifier, identifier: null, showErrorMessages: false) ??
MapEntityPrefab.Find(null, identifier: itemNameOrId, showErrorMessages: false)) as ItemPrefab; MapEntityPrefab.Find(null, identifier: deconstructItem.ItemIdentifier, showErrorMessages: false)) as ItemPrefab;
if (itemPrefab == null)
{
ThrowError($" Couldn't find deconstruct product \"{deconstructItem.ItemIdentifier}\"!");
continue;
}
NewMessage(" " + itemPrefab.Name + " has the price " + itemPrefab.DefaultPrice.Price); int defaultPrice = itemPrefab.DefaultPrice?.Price ?? 0;
totalValue += itemPrefab.DefaultPrice.Price; NewMessage(" " + itemPrefab.Name + " has the price " + defaultPrice);
totalValue += defaultPrice;
} }
NewMessage("Its deconstruct value was: " + totalValue); NewMessage("Its deconstruct value was: " + totalValue);
@@ -1792,7 +1820,7 @@ namespace Barotrauma
foreach (var talentTree in TalentTree.JobTalentTrees) foreach (var talentTree in TalentTree.JobTalentTrees)
{ {
foreach (var talentSubTree in talentTree.Value.TalentSubTrees) foreach (var talentSubTree in talentTree.TalentSubTrees)
{ {
string nameIdentifier = "talenttree." + talentSubTree.Identifier; string nameIdentifier = "talenttree." + talentSubTree.Identifier;
if (!tags[language].Contains(nameIdentifier)) if (!tags[language].Contains(nameIdentifier))
@@ -1857,7 +1885,21 @@ namespace Barotrauma
commands.Add(new Command("eventstats", "", (string[] args) => commands.Add(new Command("eventstats", "", (string[] args) =>
{ {
var debugLines = EventSet.GetDebugStatistics(); List<string> debugLines;
if (args.Length > 0)
{
if (!Enum.TryParse(args[0], ignoreCase: true, out Level.PositionType spawnType))
{
var enums = Enum.GetNames(typeof(Level.PositionType));
ThrowError($"\"{args[0]}\" is not a valid Level.PositionType. Available options are: {string.Join(", ", enums)}");
return;
}
debugLines = EventSet.GetDebugStatistics(filter: monsterEvent => monsterEvent.SpawnPosType.HasFlag(spawnType));
}
else
{
debugLines = EventSet.GetDebugStatistics();
}
string filePath = "eventstats.txt"; string filePath = "eventstats.txt";
Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true; Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
File.WriteAllLines(filePath, debugLines); File.WriteAllLines(filePath, debugLines);
@@ -2457,8 +2499,6 @@ namespace Barotrauma
NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green); NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green);
NewMessage("Fullscreen enabled", Color.Green); NewMessage("Fullscreen enabled", Color.Green);
GameSettings.ShowUserStatisticsPrompt = true;
GameSettings.VerboseLogging = false; GameSettings.VerboseLogging = false;
if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster") if (GameMain.Config.MasterServerUrl != "http://www.undertowgames.com/baromaster")
@@ -3134,7 +3174,7 @@ namespace Barotrauma
{ {
string errorMsg = "Failed to spawn a submarine. Arguments: \"" + string.Join(" ", args) + "\"."; string errorMsg = "Failed to spawn a submarine. Arguments: \"" + string.Join(" ", args) + "\".";
ThrowError(errorMsg, e); ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnSubmarine:Error", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("DebugConsole.SpawnSubmarine:Error", GameAnalyticsManager.ErrorSeverity.Error, errorMsg + '\n' + e.Message + '\n' + e.StackTrace.CleanupStackTrace());
} }
}, },
() => () =>
@@ -14,7 +14,8 @@ namespace Barotrauma
{ {
private Graph intensityGraph; private Graph intensityGraph;
private Graph targetIntensityGraph; private Graph targetIntensityGraph;
private float intensityGraphUpdateInterval; private Graph monsterStrengthGraph;
private const float intensityGraphUpdateInterval = 10;
private float lastIntensityUpdate; private float lastIntensityUpdate;
private Vector2 pinnedPosition = new Vector2(256, 128); private Vector2 pinnedPosition = new Vector2(256, 128);
@@ -22,6 +23,8 @@ namespace Barotrauma
public Event? PinnedEvent { get; set; } public Event? PinnedEvent { get; set; }
private bool isGraphSelected;
public void DebugDraw(SpriteBatch spriteBatch) public void DebugDraw(SpriteBatch spriteBatch)
{ {
foreach (Event ev in activeEvents) foreach (Event ev in activeEvents)
@@ -42,17 +45,25 @@ namespace Barotrauma
DrawEventTargetTags(spriteBatch, scriptedEvent); DrawEventTargetTags(spriteBatch, scriptedEvent);
} }
float theoreticalMaxMonsterStrength = 10000;
float relativeMaxMonsterStrength = theoreticalMaxMonsterStrength * GameMain.GameSession.LevelData.Difficulty / 100;
float absoluteMonsterStrength = monsterStrength / theoreticalMaxMonsterStrength;
float relativeMonsterStrength = monsterStrength / relativeMaxMonsterStrength;
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: " + (int)Math.Max(eventCoolDown, 0), Color.White, Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 20), "Event cooldown: " + (int)Math.Max(eventCoolDown, 0), 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);
GUI.DrawString(spriteBatch, new Vector2(15, y + 50), "Target intensity: " + (int)Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, targetIntensity), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 50), "Target intensity: " + (int)Math.Round(targetIntensity * 100), Color.Lerp(Color.White, GUI.Style.Red, targetIntensity), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 65), "AvgHealth: " + (int)Math.Round(avgCrewHealth * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgCrewHealth), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 65), "Crew health: " + (int)Math.Round(avgCrewHealth * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgCrewHealth), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 80), "AvgHullIntegrity: " + (int)Math.Round(avgHullIntegrity * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgHullIntegrity), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 80), "Hull integrity: " + (int)Math.Round(avgHullIntegrity * 100), Color.Lerp(GUI.Style.Red, GUI.Style.Green, avgHullIntegrity), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "FloodingAmount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 95), "Flooding amount: " + (int)Math.Round(floodingAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, floodingAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "FireAmount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 110), "Fire amount: " + (int)Math.Round(fireAmount * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, fireAmount), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "EnemyDanger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 125), "Enemy danger: " + (int)Math.Round(enemyDanger * 100), Color.Lerp(GUI.Style.Green, GUI.Style.Red, enemyDanger), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 140), "MonsterTotalStrength: " + (int)Math.Round(monsterTotalStrength), Color.Lerp(GUI.Style.Green, GUI.Style.Red, monsterTotalStrength / 5000f), Color.Black * 0.6f, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(15, y + 140), "Current monster strength (total): " + (int)Math.Round(monsterStrength), Color.Lerp(GUI.Style.Green, GUI.Style.Red, relativeMonsterStrength), Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 155), "Main events: " + (int)Math.Round(CumulativeMonsterStrengthMain), Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 170), "Ruin events: " + (int)Math.Round(CumulativeMonsterStrengthRuins), Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 185), "Wreck events: " + (int)Math.Round(CumulativeMonsterStrengthWrecks), Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
GUI.DrawString(spriteBatch, new Vector2(15, y + 200), "Cave events: " + (int)Math.Round(CumulativeMonsterStrengthCaves), Color.White, Color.Black * 0.6f, 0, GUI.SmallFont);
#if DEBUG #if DEBUG
if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) && if (PlayerInput.KeyDown(Microsoft.Xna.Framework.Input.Keys.LeftAlt) &&
@@ -64,29 +75,103 @@ namespace Barotrauma
if (intensityGraph == null) if (intensityGraph == null)
{ {
intensityGraph = new Graph(); int graphDensity = 360; // 60 min
targetIntensityGraph = new Graph(); intensityGraph = new Graph(graphDensity);
targetIntensityGraph = new Graph(graphDensity);
monsterStrengthGraph = new Graph(graphDensity);
} }
intensityGraphUpdateInterval = 5.0f;
if (Timing.TotalTime > lastIntensityUpdate + intensityGraphUpdateInterval) if (Timing.TotalTime > lastIntensityUpdate + intensityGraphUpdateInterval)
{ {
intensityGraph.Update(currentIntensity); intensityGraph.Update(currentIntensity);
targetIntensityGraph.Update(targetIntensity); targetIntensityGraph.Update(targetIntensity);
lastIntensityUpdate = (float) Timing.TotalTime; monsterStrengthGraph.Update(relativeMonsterStrength);
lastIntensityUpdate = (float)Timing.TotalTime;
} }
Rectangle graphRect = new Rectangle(15, y + 165, 150, 50); Rectangle graphRect = new Rectangle(15, y + 240, (int)(200 * GUI.xScale), (int)(100 * GUI.yScale));
bool isGraphHovered = graphRect.Contains(PlayerInput.MousePosition);
bool leftMousePressed = PlayerInput.PrimaryMouseButtonDown() || PlayerInput.PrimaryMouseButtonHeld();
bool rightMousePressed = PlayerInput.SecondaryMouseButtonHeld() || PlayerInput.SecondaryMouseButtonDown();
if (!isGraphSelected && isGraphHovered && leftMousePressed)
{
isGraphSelected = true;
}
if (isGraphSelected && rightMousePressed)
{
isGraphSelected = false;
}
Color intensityColor = Color.Lerp(Color.White, GUI.Style.Red, currentIntensity);
if (isGraphHovered || isGraphSelected)
{
graphRect.Size = new Point(GameMain.GraphicsWidth - 30, (int)(GameMain.GraphicsHeight * 0.35f));
intensityColor = Color.Red;
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.95f, isFilled: true);
}
else
{
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.6f, isFilled: true);
}
intensityGraph.Draw(spriteBatch, graphRect, maxValue: 1.0f, xOffset: 0, intensityColor, (sBatch, value, order, pos) =>
{
if (isGraphHovered || isGraphSelected)
{
Vector2 bottomPoint = new Vector2(pos.X, graphRect.Bottom);
float height = 3 * GUI.yScale;
if (order % 6 == 0)
{
height *= 3;
string text = (order / 6).ToString();
var font = GUI.SmallFont;
Vector2 textSize = font.MeasureString(text);
Vector2 textPos = new Vector2(bottomPoint.X - textSize.X / 2, bottomPoint.Y + height * 1.5f);
GUI.DrawString(sBatch, textPos, text, Color.White, font: font);
}
GUI.DrawLine(sBatch, bottomPoint, bottomPoint + Vector2.UnitY * height, Color.White, width: Math.Max(GUI.Scale, 1));
DrawTimeStamps(sBatch, Color.Red, pos, order);
}
});
targetIntensityGraph.Draw(spriteBatch, graphRect, maxValue: 1.0f, xOffset: 0, intensityColor * 0.5f);
if (isGraphHovered || isGraphSelected)
{
float? maxValue = 1;
Color color = Color.White;
if (relativeMonsterStrength > 1)
{
maxValue = null;
color = Color.Yellow;
}
monsterStrengthGraph.Draw(spriteBatch, graphRect, maxValue, color: color, doForEachValue: (sBatch, value, order, pos) => DrawTimeStamps(sBatch, color, pos, order));
}
GUI.DrawRectangle(spriteBatch, graphRect, Color.Black * 0.5f, true); void DrawTimeStamps(SpriteBatch sBatch, Color color, Vector2 pos, int order)
intensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, currentIntensity)); {
targetIntensityGraph.Draw(spriteBatch, graphRect, 1.0f, 0.0f, Color.Lerp(Color.White, GUI.Style.Red, targetIntensity) * 0.5f); if (isGraphHovered || isGraphSelected)
{
foreach (var timeStamp in timeStamps)
{
int t = (int)Math.Abs(Math.Round((timeStamp.Time - lastIntensityUpdate) / intensityGraphUpdateInterval));
if (t == order)
{
float size = 6;
Vector2 p = new Vector2(pos.X - size / 2, pos.Y - size / 2);
ShapeExtensions.DrawPoint(sBatch, p, color, size);
break;
}
}
}
}
GUI.DrawLine(spriteBatch, GUI.DrawLine(spriteBatch,
new Vector2(graphRect.Right, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)), new Vector2(graphRect.Right, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)),
new Vector2(graphRect.Right + 5, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)), Color.Orange, 0, 1); new Vector2(graphRect.Right + 5, graphRect.Y + graphRect.Height * (1.0f - eventThreshold)), Color.Orange, width: 3);
y = graphRect.Bottom + 20; int yStep = (int)(20 * GUI.yScale);
y = graphRect.Bottom + yStep;
if (isGraphHovered || isGraphSelected)
{
y += yStep;
}
int x = graphRect.X; int x = graphRect.X;
if (isCrewAway && crewAwayDuration < settings.FreezeDurationWhenCrewAway) if (isCrewAway && crewAwayDuration < settings.FreezeDurationWhenCrewAway)
{ {
@@ -143,7 +228,7 @@ namespace Barotrauma
if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity) if (CurrentIntensity < eventSet.MinIntensity || CurrentIntensity > eventSet.MaxIntensity)
{ {
GUI.DrawString(spriteBatch, new Vector2(x, y), GUI.DrawString(spriteBatch, new Vector2(x, y),
" intensity between " + ((int) eventSet.MinIntensity) + " and " + ((int) eventSet.MaxIntensity), " intensity between " + eventSet.MinIntensity.FormatDoubleDecimal() + " and " + eventSet.MaxIntensity.FormatDoubleDecimal(),
Color.Orange * 0.8f, null, 0, GUI.SmallFont); Color.Orange * 0.8f, null, 0, GUI.SmallFont);
y += 12; y += 12;
} }
@@ -159,13 +244,13 @@ namespace Barotrauma
if (y > GameMain.GraphicsHeight * 0.9f) if (y > GameMain.GraphicsHeight * 0.9f)
{ {
y = graphRect.Bottom + 35; y = graphRect.Bottom + yStep * 2;
x += 250; x += 300;
} }
} }
GUI.DrawString(spriteBatch, new Vector2(x, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont); GUI.DrawString(spriteBatch, new Vector2(x, y), "Current events: ", Color.White * 0.9f, null, 0, GUI.SmallFont);
y += 15; y += yStep;
foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown())) foreach (Event ev in activeEvents.Where(ev => !ev.IsFinished || PlayerInput.IsShiftDown()))
{ {
@@ -182,17 +267,15 @@ namespace Barotrauma
{ {
GUI.MouseCursor = CursorState.Hand; GUI.MouseCursor = CursorState.Hand;
GUI.DrawRectangle(spriteBatch, outlineRect, Color.White); GUI.DrawRectangle(spriteBatch, outlineRect, Color.White);
if (ev != PinnedEvent) if (ev != PinnedEvent)
{ {
DrawEvent(spriteBatch, ev, rect); DrawEvent(spriteBatch, ev, rect);
} }
else if (PlayerInput.SecondaryMouseButtonHeld() || PlayerInput.SecondaryMouseButtonDown()) else if (rightMousePressed)
{ {
PinnedEvent = null; PinnedEvent = null;
} }
if (leftMousePressed)
if (PlayerInput.PrimaryMouseButtonHeld() || PlayerInput.PrimaryMouseButtonDown())
{ {
PinnedEvent = ev; PinnedEvent = ev;
} }
@@ -201,8 +284,8 @@ namespace Barotrauma
y += 18; y += 18;
if (y > GameMain.GraphicsHeight * 0.9f) if (y > GameMain.GraphicsHeight * 0.9f)
{ {
y = graphRect.Bottom + 35; y = graphRect.Bottom + yStep * 2;
x += 250; x += 300;
} }
} }
} }
@@ -352,9 +435,11 @@ namespace Barotrauma
return DrawInfoRectangle(spriteBatch, scriptedEvent, text, parentRect, positions); return DrawInfoRectangle(spriteBatch, scriptedEvent, text, parentRect, positions);
} }
private readonly List<DebugLine> debugPositions = new List<DebugLine>();
private Rectangle DrawArtifactEvent(SpriteBatch spriteBatch, ArtifactEvent artifactEvent, Rectangle? parentRect = null) private Rectangle DrawArtifactEvent(SpriteBatch spriteBatch, ArtifactEvent artifactEvent, Rectangle? parentRect = null)
{ {
List<DebugLine> positions = new List<DebugLine>(); debugPositions.Clear();
string text = $"Finished: {artifactEvent.IsFinished.ColorizeObject()}\n" + string text = $"Finished: {artifactEvent.IsFinished.ColorizeObject()}\n" +
$"Item: {artifactEvent.Item.ColorizeObject()}\n" + $"Item: {artifactEvent.Item.ColorizeObject()}\n" +
@@ -364,15 +449,15 @@ namespace Barotrauma
if (artifactEvent.Item != null && !artifactEvent.Item.Removed) if (artifactEvent.Item != null && !artifactEvent.Item.Removed)
{ {
Vector2 pos = artifactEvent.Item.WorldPosition; Vector2 pos = artifactEvent.Item.WorldPosition;
positions.Add(new DebugLine(pos, Color.White)); debugPositions.Add(new DebugLine(pos, Color.White));
} }
return DrawInfoRectangle(spriteBatch, artifactEvent, text, parentRect, positions); return DrawInfoRectangle(spriteBatch, artifactEvent, text, parentRect, debugPositions);
} }
private Rectangle DrawMonsterEvent(SpriteBatch spriteBatch, MonsterEvent monsterEvent, Rectangle? parentRect = null) private Rectangle DrawMonsterEvent(SpriteBatch spriteBatch, MonsterEvent monsterEvent, Rectangle? parentRect = null)
{ {
List<DebugLine> positions = new List<DebugLine>(); debugPositions.Clear();
string text = $"Finished: {monsterEvent.IsFinished.ColorizeObject()}\n" + string text = $"Finished: {monsterEvent.IsFinished.ColorizeObject()}\n" +
$"Amount: {monsterEvent.MinAmount.ColorizeObject()} - {monsterEvent.MaxAmount.ColorizeObject()}\n" + $"Amount: {monsterEvent.MinAmount.ColorizeObject()} - {monsterEvent.MaxAmount.ColorizeObject()}\n" +
@@ -383,7 +468,7 @@ namespace Barotrauma
{ {
Vector2 pos = monsterEvent.SpawnPos.Value; Vector2 pos = monsterEvent.SpawnPos.Value;
text += $"Distance from submarine: {Vector2.Distance(pos, Submarine.MainSub.WorldPosition).ColorizeObject()}\n"; text += $"Distance from submarine: {Vector2.Distance(pos, Submarine.MainSub.WorldPosition).ColorizeObject()}\n";
positions.Add(new DebugLine(pos, Color.White)); debugPositions.Add(new DebugLine(pos, Color.White));
} }
if (monsterEvent.Monsters != null) if (monsterEvent.Monsters != null)
@@ -394,11 +479,10 @@ namespace Barotrauma
{ {
text += $" {monster.ColorizeObject()} -> (Dead: {monster.IsDead.ColorizeObject()}, Health: {monster.HealthPercentage.ColorizeObject()}%, AIState: {(monster.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle ).ColorizeObject()})\n"; text += $" {monster.ColorizeObject()} -> (Dead: {monster.IsDead.ColorizeObject()}, Health: {monster.HealthPercentage.ColorizeObject()}%, AIState: {(monster.AIController is EnemyAIController enemyAI ? enemyAI.State : AIState.Idle ).ColorizeObject()})\n";
if (monster.Removed) { continue; } if (monster.Removed) { continue; }
positions.Add(new DebugLine(monster.WorldPosition, Color.Red)); debugPositions.Add(new DebugLine(monster.WorldPosition, Color.Red));
} }
} }
return DrawInfoRectangle(spriteBatch, monsterEvent, text, parentRect, debugPositions);
return DrawInfoRectangle(spriteBatch, monsterEvent, text, parentRect, positions);
} }
private Rectangle DrawInfoRectangle(SpriteBatch spriteBatch, Event @event, string text, Rectangle? parentRect = null, List<DebugLine>? drawPoints = null) private Rectangle DrawInfoRectangle(SpriteBatch spriteBatch, Event @event, string text, Rectangle? parentRect = null, List<DebugLine>? drawPoints = null)
@@ -78,7 +78,7 @@ namespace Barotrauma
CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message)); CoroutineManager.StartCoroutine(ShowMessageBoxAfterRoundSummary(header, message));
} }
private IEnumerable<object> ShowMessageBoxAfterRoundSummary(string header, string message) private IEnumerable<CoroutineStatus> ShowMessageBoxAfterRoundSummary(string header, string message)
{ {
while (GUIMessageBox.VisibleBox?.UserData is RoundSummary) while (GUIMessageBox.VisibleBox?.UserData is RoundSummary)
{ {
@@ -51,16 +51,30 @@ namespace Barotrauma
} }
} }
public float LineHeight => baseHeight * 1.8f;
private uint[] charRanges; private uint[] charRanges;
private int texDims; private int texDims;
private uint baseChar; private uint baseChar;
private struct GlyphData private readonly struct GlyphData
{ {
public int texIndex; public readonly int TexIndex;
public Vector2 drawOffset; public readonly Vector2 DrawOffset;
public float advance; public readonly float Advance;
public Rectangle texCoords; public readonly Rectangle TexCoords;
public GlyphData(
int texIndex = default,
Vector2 drawOffset = default,
float advance = default,
Rectangle texCoords = default)
{
TexIndex = texIndex;
DrawOffset = drawOffset;
Advance = advance;
TexCoords = texCoords;
}
} }
public ScalableFont(XElement element, GraphicsDevice gd = null) public ScalableFont(XElement element, GraphicsDevice gd = null)
@@ -167,9 +181,10 @@ namespace Barotrauma
if (face.Glyph.Metrics.HorizontalAdvance > 0) if (face.Glyph.Metrics.HorizontalAdvance > 0)
{ {
//glyph is empty, but char still applies advance //glyph is empty, but char still applies advance
GlyphData blankData = new GlyphData(); GlyphData blankData = new GlyphData(
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; advance: (float)face.Glyph.Metrics.HorizontalAdvance,
blankData.texIndex = -1; //indicates no texture because the glyph is empty texIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(j, blankData); texCoords.Add(j, blankData);
} }
continue; continue;
@@ -211,13 +226,12 @@ namespace Barotrauma
} }
} }
GlyphData newData = new GlyphData GlyphData newData = new GlyphData(
{ advance: (float)face.Glyph.Metrics.HorizontalAdvance,
advance = (float)face.Glyph.Metrics.HorizontalAdvance, texIndex: texIndex,
texIndex = texIndex, texCoords: new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight),
texCoords = new Rectangle((int)currentCoords.X, (int)currentCoords.Y, glyphWidth, glyphHeight), drawOffset: new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop)
drawOffset = new Vector2(face.Glyph.BitmapLeft, baseHeight * 14 / 10 - face.Glyph.BitmapTop) );
};
texCoords.Add(j, newData); texCoords.Add(j, newData);
for (int y = 0; y < glyphHeight; y++) for (int y = 0; y < glyphHeight; y++)
@@ -278,9 +292,9 @@ namespace Barotrauma
if (face.Glyph.Metrics.HorizontalAdvance > 0) if (face.Glyph.Metrics.HorizontalAdvance > 0)
{ {
//glyph is empty, but char still applies advance //glyph is empty, but char still applies advance
GlyphData blankData = new GlyphData(); GlyphData blankData = new GlyphData(
blankData.advance = (float)face.Glyph.Metrics.HorizontalAdvance; advance: (float)face.Glyph.Metrics.HorizontalAdvance,
blankData.texIndex = -1; //indicates no texture because the glyph is empty texIndex: -1); //indicates no texture because the glyph is empty
texCoords.Add(character, blankData); texCoords.Add(character, blankData);
} }
return; return;
@@ -316,19 +330,18 @@ namespace Barotrauma
currentDynamicPixelBuffer = null; currentDynamicPixelBuffer = null;
} }
GlyphData newData = new GlyphData GlyphData newData = new GlyphData(
{ advance: (float)horizontalAdvance,
advance = (float)horizontalAdvance, texIndex: textures.Count - 1,
texIndex = textures.Count - 1, texCoords: new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight),
texCoords = new Rectangle((int)currentDynamicAtlasCoords.X, (int)currentDynamicAtlasCoords.Y, glyphWidth, glyphHeight), drawOffset: drawOffset
drawOffset = drawOffset );
};
texCoords.Add(character, newData); texCoords.Add(character, newData);
if (currentDynamicPixelBuffer == null) if (currentDynamicPixelBuffer == null)
{ {
currentDynamicPixelBuffer = new uint[texDims * texDims]; currentDynamicPixelBuffer = new uint[texDims * texDims];
textures[newData.texIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims); textures[newData.TexIndex].GetData<uint>(currentDynamicPixelBuffer, 0, texDims * texDims);
} }
for (int y = 0; y < glyphHeight; y++) for (int y = 0; y < glyphHeight; y++)
@@ -339,12 +352,25 @@ namespace Barotrauma
currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff); currentDynamicPixelBuffer[((int)currentDynamicAtlasCoords.X + x) + ((int)currentDynamicAtlasCoords.Y + y) * texDims] = (uint)(byteColor << 24 | 0x00ffffff);
} }
} }
textures[newData.texIndex].SetData<uint>(currentDynamicPixelBuffer); textures[newData.TexIndex].SetData<uint>(currentDynamicPixelBuffer);
currentDynamicAtlasCoords.X += glyphWidth + 2; currentDynamicAtlasCoords.X += glyphWidth + 2;
} }
} }
private GlyphData GetGlyphData(uint charIndex)
{
const uint DEFAULT_INDEX = 0x25A1; //U+25A1 = white square
if (texCoords.TryGetValue(charIndex, out GlyphData gd) ||
texCoords.TryGetValue(DEFAULT_INDEX, out gd))
{
return gd;
}
return new GlyphData(texIndex: -1);
}
public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth) public void DrawString(SpriteBatch sb, string text, Vector2 position, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects se, float layerDepth)
{ {
if (textures.Count == 0 && !DynamicLoading) { return; } if (textures.Count == 0 && !DynamicLoading) { return; }
@@ -358,8 +384,8 @@ namespace Barotrauma
{ {
lineNum++; lineNum++;
currentPos = position; currentPos = position;
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y; currentPos.X -= LineHeight * lineNum * advanceUnit.Y * scale.Y;
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y; currentPos.Y += LineHeight * lineNum * advanceUnit.X * scale.Y;
continue; continue;
} }
@@ -369,19 +395,17 @@ namespace Barotrauma
DynamicRenderAtlas(graphicsDevice, charIndex); DynamicRenderAtlas(graphicsDevice, charIndex);
} }
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0)
{ {
if (gd.texIndex >= 0) Texture2D tex = textures[gd.TexIndex];
{ Vector2 drawOffset;
Texture2D tex = textures[gd.texIndex]; drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
Vector2 drawOffset; drawOffset.Y = gd.DrawOffset.X * advanceUnit.Y * scale.Y + gd.DrawOffset.Y * advanceUnit.X * scale.X;
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
sb.Draw(tex, currentPos + drawOffset, gd.texCoords, color, rotation, origin, scale, se, layerDepth); sb.Draw(tex, currentPos + drawOffset, gd.TexCoords, color, rotation, origin, scale, se, layerDepth);
}
currentPos += gd.advance * advanceUnit * scale.X;
} }
currentPos += gd.Advance * advanceUnit * scale.X;
} }
} }
@@ -400,7 +424,7 @@ namespace Barotrauma
if (text[i] == '\n') if (text[i] == '\n')
{ {
currentPos.X = position.X; currentPos.X = position.X;
currentPos.Y += baseHeight * 1.8f; currentPos.Y += LineHeight;
continue; continue;
} }
@@ -410,15 +434,13 @@ namespace Barotrauma
DynamicRenderAtlas(graphicsDevice, charIndex); DynamicRenderAtlas(graphicsDevice, charIndex);
} }
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0)
{ {
if (gd.texIndex >= 0) Texture2D tex = textures[gd.TexIndex];
{ sb.Draw(tex, currentPos + gd.DrawOffset, gd.TexCoords, color);
Texture2D tex = textures[gd.texIndex];
sb.Draw(tex, currentPos + gd.drawOffset, gd.texCoords, color);
}
currentPos.X += gd.advance;
} }
currentPos.X += gd.Advance;
} }
} }
@@ -444,8 +466,8 @@ namespace Barotrauma
{ {
lineNum++; lineNum++;
currentPos = position; currentPos = position;
currentPos.X -= baseHeight * 1.8f * lineNum * advanceUnit.Y * scale.Y; currentPos.X -= LineHeight * lineNum * advanceUnit.Y * scale.Y;
currentPos.Y += baseHeight * 1.8f * lineNum * advanceUnit.X * scale.Y; currentPos.Y += LineHeight * lineNum * advanceUnit.X * scale.Y;
continue; continue;
} }
@@ -476,22 +498,116 @@ namespace Barotrauma
currentTextColor = color; currentTextColor = color;
} }
if (texCoords.TryGetValue(charIndex, out GlyphData gd) || texCoords.TryGetValue(9633, out gd)) //9633 = white square GlyphData gd = GetGlyphData(charIndex);
if (gd.TexIndex >= 0)
{ {
if (gd.texIndex >= 0) Texture2D tex = textures[gd.TexIndex];
{ Vector2 drawOffset;
Texture2D tex = textures[gd.texIndex]; drawOffset.X = gd.DrawOffset.X * advanceUnit.X * scale.X - gd.DrawOffset.Y * advanceUnit.Y * scale.Y;
Vector2 drawOffset; drawOffset.Y = gd.DrawOffset.X * advanceUnit.Y * scale.Y + gd.DrawOffset.Y * advanceUnit.X * scale.X;
drawOffset.X = gd.drawOffset.X * advanceUnit.X * scale.X - gd.drawOffset.Y * advanceUnit.Y * scale.Y;
drawOffset.Y = gd.drawOffset.X * advanceUnit.Y * scale.Y + gd.drawOffset.Y * advanceUnit.X * scale.X;
sb.Draw(tex, currentPos + drawOffset, gd.texCoords, currentTextColor, rotation, origin, scale, se, layerDepth); sb.Draw(tex, currentPos + drawOffset, gd.TexCoords, currentTextColor, rotation, origin, scale, se, layerDepth);
}
currentPos += gd.advance * advanceUnit * scale.X;
} }
currentPos += gd.Advance * advanceUnit * scale.X;
} }
} }
public string WrapText(string text, float width)
=> WrapText(text, width, requestCharPos: 0, out _, returnAllCharPositions: false, out _);
public string WrapText(string text, float width, int requestCharPos, out Vector2 requestedCharPos)
=> WrapText(text, width, requestCharPos, out requestedCharPos, returnAllCharPositions: false, out _);
public string WrapText(string text, float width, out Vector2[] allCharPositions)
=> WrapText(text, width, requestCharPos: 0, out _, returnAllCharPositions: true, out allCharPositions);
/// <summary>
/// Wraps a string of text to fit within a given width.
/// Optionally returns the caret position of a certain character,
/// or all of them.
/// </summary>
private string WrapText(string text,
float width,
int requestCharPos,
out Vector2 requestedCharPos,
bool returnAllCharPositions,
out Vector2[] allCharPositions)
{
int currLineStart = 0;
Vector2 currentPos = Vector2.Zero;
Vector2 foundCharPos = Vector2.Zero;
int? lastBreakerIndex = null;
string result = "";
var allCharPos = returnAllCharPositions ? new Vector2[text.Length+1] : null;
for (int i = 0; i < text.Length; i++)
{
//Records the caret position of the current character
void recordCurrentPos()
{
if (i == requestCharPos) { foundCharPos = currentPos; }
if (allCharPos != null) { allCharPos[i] = currentPos; }
}
recordCurrentPos();
//Appends a newline to the result and resets the caret position's X value
void nextLine()
{
result += text[currLineStart..i].Remove("\n") + "\n";
lastBreakerIndex = null;
currentPos.X = 0.0f;
currentPos.Y += LineHeight;
currLineStart = i;
}
//If a newline is found in the source, split immediately
if (text[i] == '\n')
{
nextLine();
continue;
}
//Otherwise, advance based on the width of the current character
GlyphData gd = GetGlyphData(text[i]);
float advance = gd.Advance;
if (currentPos.X + advance >= width)
{
//Advancing based on the last character
//would put us past the max width!
if (i > 0 && char.IsWhiteSpace(text[i]) && !char.IsWhiteSpace(text[i - 1]))
{
//Whitespace immediately after a visible
//character can be shrunk down to fit
advance = width - currentPos.X;
}
else
{
if (lastBreakerIndex.HasValue)
{
//A breaker (whitespace or CJK) was found earlier
//in this line, so let's break the line there
i = lastBreakerIndex.Value + 1;
}
nextLine();
recordCurrentPos(); //must re-record current caret position since we are on a new line now
}
}
currentPos.X += advance;
if (char.IsWhiteSpace(text[i]) || TextManager.IsCJK($"{text[i]}"))
{
lastBreakerIndex = i;
}
}
if (requestCharPos >= text.Length) { foundCharPos = currentPos; }
if (allCharPos != null) { allCharPos[text.Length] = currentPos; }
allCharPositions = allCharPos;
result += text[currLineStart..].Remove("\n");
requestedCharPos = foundCharPos;
return result;
}
public Vector2 MeasureString(string text, bool removeExtraSpacing = false) public Vector2 MeasureString(string text, bool removeExtraSpacing = false)
{ {
if (text == null) if (text == null)
@@ -504,7 +620,7 @@ namespace Barotrauma
if (!removeExtraSpacing) if (!removeExtraSpacing)
{ {
retVal.Y = baseHeight * 1.8f; retVal.Y = LineHeight;
} }
else else
{ {
@@ -516,7 +632,7 @@ namespace Barotrauma
if (text[i] == '\n') if (text[i] == '\n')
{ {
currentLineX = 0.0f; currentLineX = 0.0f;
retVal.Y += baseHeight * 1.8f; retVal.Y += LineHeight;
continue; continue;
} }
uint charIndex = text[i]; uint charIndex = text[i];
@@ -524,10 +640,9 @@ namespace Barotrauma
{ {
DynamicRenderAtlas(graphicsDevice, charIndex); DynamicRenderAtlas(graphicsDevice, charIndex);
} }
if (texCoords.TryGetValue(charIndex, out GlyphData gd))
{ GlyphData gd = GetGlyphData(charIndex);
currentLineX += gd.advance; currentLineX += gd.Advance;
}
retVal.X = Math.Max(retVal.X, currentLineX); retVal.X = Math.Max(retVal.X, currentLineX);
} }
return retVal; return retVal;
@@ -536,15 +651,14 @@ namespace Barotrauma
public Vector2 MeasureChar(char c) public Vector2 MeasureChar(char c)
{ {
Vector2 retVal = Vector2.Zero; Vector2 retVal = Vector2.Zero;
retVal.Y = baseHeight * 1.8f; retVal.Y = LineHeight;
if (DynamicLoading && !texCoords.ContainsKey(c)) if (DynamicLoading && !texCoords.ContainsKey(c))
{ {
DynamicRenderAtlas(graphicsDevice, c); DynamicRenderAtlas(graphicsDevice, c);
} }
if (texCoords.TryGetValue(c, out GlyphData gd))
{ GlyphData gd = GetGlyphData(c);
retVal.X = gd.advance; retVal.X = gd.Advance;
}
return retVal; return retVal;
} }
@@ -363,7 +363,7 @@ namespace Barotrauma
OnSecondaryClicked = (_, o) => OnSecondaryClicked = (_, o) =>
{ {
if (!(o is Client client)) { return false; } if (!(o is Client client)) { return false; }
GameMain.GameSession?.CrewManager?.CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); NetLobbyScreen.CreateModerationContextMenu(client);
return true; return true;
}, },
Text = senderName Text = senderName
@@ -397,6 +397,7 @@ namespace Barotrauma
if (GameMain.NetLobbyScreen != null && GameMain.NetworkMember != null) if (GameMain.NetLobbyScreen != null && GameMain.NetworkMember != null)
{ {
clickableArea.OnClick = GameMain.NetLobbyScreen.SelectPlayer; clickableArea.OnClick = GameMain.NetLobbyScreen.SelectPlayer;
clickableArea.OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu;
} }
msgText.ClickableAreas.Add(clickableArea); msgText.ClickableAreas.Add(clickableArea);
} }
@@ -494,7 +495,7 @@ namespace Barotrauma
GUIFrame.Parent.Visible = visible; GUIFrame.Parent.Visible = visible;
} }
private IEnumerable<object> UpdateMessageAnimation(GUIComponent message, float animDuration) private IEnumerable<CoroutineStatus> UpdateMessageAnimation(GUIComponent message, float animDuration)
{ {
float timer = 0.0f; float timer = 0.0f;
while (timer < animDuration) while (timer < animDuration)
@@ -399,7 +399,7 @@ namespace Barotrauma
" Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms", " Max: " + GameMain.PerformanceCounter.DrawTimeGraph.LargestValue().ToString("0.00") + " ms",
GUI.Style.Green, Color.Black * 0.8f, font: SmallFont); GUI.Style.Green, Color.Black * 0.8f, font: SmallFont);
y += 15; y += 15;
GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), null, 0, GUI.Style.Green); GameMain.PerformanceCounter.DrawTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Style.Green);
y += 50; y += 50;
DrawString(spriteBatch, new Vector2(300, y), DrawString(spriteBatch, new Vector2(300, y),
@@ -407,8 +407,8 @@ namespace Barotrauma
" Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms", " Max: " + GameMain.PerformanceCounter.UpdateTimeGraph.LargestValue().ToString("0.00") + " ms",
Color.LightBlue, Color.Black * 0.8f, font: SmallFont); Color.LightBlue, Color.Black * 0.8f, font: SmallFont);
y += 15; y += 15;
GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), null, 0, Color.LightBlue); GameMain.PerformanceCounter.UpdateTimeGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), color: Color.LightBlue);
GameMain.PerformanceCounter.UpdateIterationsGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), 20, 0, GUI.Style.Red); GameMain.PerformanceCounter.UpdateIterationsGraph.Draw(spriteBatch, new Rectangle(300, y, 170, 50), maxValue: 20, color: Style.Red);
y += 50; y += 50;
foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers) foreach (string key in GameMain.PerformanceCounter.GetSavedIdentifiers)
{ {
@@ -438,7 +438,7 @@ namespace Barotrauma
Color.White, Color.Black * 0.5f, 0, SmallFont); Color.White, Color.Black * 0.5f, 0, SmallFont);
DrawString(spriteBatch, new Vector2(10, 40), DrawString(spriteBatch, new Vector2(10, 40),
$"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.FindAll(b => b.Awake && b.Enabled).Count} awake, {GameMain.World.BodyList.FindAll(b => b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled).Count} dynamic)", $"Bodies: {GameMain.World.BodyList.Count} ({GameMain.World.BodyList.Count(b => b != null && b.Awake && b.Enabled)} awake, {GameMain.World.BodyList.Count(b => b != null && b.Awake && b.BodyType == BodyType.Dynamic && b.Enabled)} dynamic)",
Color.White, Color.Black * 0.5f, 0, SmallFont); Color.White, Color.Black * 0.5f, 0, SmallFont);
if (Screen.Selected.Cam != null) if (Screen.Selected.Cam != null)
@@ -941,7 +941,8 @@ namespace Barotrauma
inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame); inventoryIndex = updateList.IndexOf(CharacterHUD.HUDFrame);
} }
if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) || (prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld())) if ((!PlayerInput.PrimaryMouseButtonHeld() && !PlayerInput.PrimaryMouseButtonClicked()) ||
(prevMouseOn == null && !PlayerInput.SecondaryMouseButtonHeld() && !Inventory.DraggingItems.Any()))
{ {
for (var i = updateList.Count - 1; i > inventoryIndex; i--) for (var i = updateList.Count - 1; i > inventoryIndex; i--)
{ {
@@ -1052,10 +1053,10 @@ namespace Barotrauma
// Children in list boxes can be interacted with despite not having // Children in list boxes can be interacted with despite not having
// a GUIButton inside of them so instead of hard coding we check if // a GUIButton inside of them so instead of hard coding we check if
// the children can be interacted with by checking their hover state // the children can be interacted with by checking their hover state
if (parent is GUIListBox listBox) if (parent is GUIListBox listBox && c.Parent == listBox.Content)
{ {
if (listBox.DraggedElement != null) { return CursorState.Dragging; } if (listBox.DraggedElement != null) { return CursorState.Dragging; }
if (listBox.CanDragElements) { return CursorState.Move; } if (listBox.CurrentDragMode != GUIListBox.DragMode.NoDragging) { return CursorState.Move; }
if (listBox.HoverCursor != CursorState.Default) if (listBox.HoverCursor != CursorState.Default)
{ {
@@ -1148,7 +1149,7 @@ namespace Barotrauma
{ {
CoroutineManager.StartCoroutine(WaitCursorCoroutine(), "WaitCursorTimeout"); CoroutineManager.StartCoroutine(WaitCursorCoroutine(), "WaitCursorTimeout");
IEnumerable<object> WaitCursorCoroutine() IEnumerable<CoroutineStatus> WaitCursorCoroutine()
{ {
MouseCursor = CursorState.Waiting; MouseCursor = CursorState.Waiting;
var timeOut = DateTime.Now + new TimeSpan(0, 0, waitSeconds); var timeOut = DateTime.Now + new TimeSpan(0, 0, waitSeconds);
@@ -1360,7 +1361,7 @@ namespace Barotrauma
float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale; float symbolScale = Math.Min(64.0f / sprite.size.X, 1.0f) * scaleMultiplier * Scale;
if (overrideAlpha.HasValue || (dist > visibleRange.Start && dist < visibleRange.End)) if (overrideAlpha.HasValue || visibleRange.Contains(dist))
{ {
float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.Start) / 100.0f, 1.0f - ((dist - visibleRange.End + 100f) / 100.0f), 1.0f); float alpha = overrideAlpha ?? MathUtils.Min((dist - visibleRange.Start) / 100.0f, 1.0f - ((dist - visibleRange.End + 100f) / 100.0f), 1.0f);
Vector2 targetScreenPos = cam.WorldToScreen(worldPosition); Vector2 targetScreenPos = cam.WorldToScreen(worldPosition);
@@ -2254,8 +2255,8 @@ namespace Barotrauma
#region Misc #region Misc
public static void TogglePauseMenu() public static void TogglePauseMenu()
{ {
if (Screen.Selected == GameMain.MainMenuScreen) return; if (Screen.Selected == GameMain.MainMenuScreen) { return; }
if (PreventPauseMenuToggle) return; if (PreventPauseMenuToggle) { return; }
settingsMenuOpen = false; settingsMenuOpen = false;
@@ -2276,162 +2277,121 @@ namespace Barotrauma
Stretch = true, Stretch = true,
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
new GUIButton(new RectTransform(new Vector2(0.1f, 0.1f), pauseMenuInner.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point((int)(15 * GUI.Scale)) }, new GUIButton(new RectTransform(new Vector2(0.1f, 0.1f), pauseMenuInner.RectTransform, Anchor.TopRight) { AbsoluteOffset = new Point((int)(15 * GUI.Scale)) },
"", style: "GUIBugButton") "", style: "GUIBugButton")
{ {
IgnoreLayoutGroups = true, IgnoreLayoutGroups = true,
ToolTip = TextManager.Get("bugreportbutton"), ToolTip = TextManager.Get("bugreportbutton") + $" (v{GameMain.Version})",
OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; } OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
}; };
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuResume")) CreateButton("PauseMenuResume", buttonContainer, null);
{ CreateButton("PauseMenuSettings", buttonContainer, () => { settingsMenuOpen = !settingsMenuOpen; });
OnClicked = TogglePauseMenu
};
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSettings"))
{
OnClicked = (btn, userData) =>
{
TogglePauseMenu();
settingsMenuOpen = !settingsMenuOpen;
return true;
}
};
bool IsOutpostLevel() => GameMain.GameSession != null && Level.IsLoadedOutpost; bool IsOutpostLevel() => GameMain.GameSession != null && Level.IsLoadedOutpost;
if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null) if (Screen.Selected == GameMain.GameScreen && GameMain.GameSession != null)
{ {
if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode) if (GameMain.GameSession.GameMode is SinglePlayerCampaign spMode)
{ {
var retryButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuRetry")); CreateButton("PauseMenuRetry", buttonContainer, verificationTextTag: "PauseMenuRetryVerification", action: () =>
retryButton.OnClicked += (btn, userData) =>
{ {
var msgBox = new GUIMessageBox("", TextManager.Get("PauseMenuRetryVerification"), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") }) if (GameMain.GameSession.RoundSummary?.Frame != null)
{ {
UserData = "verificationprompt" GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame);
}; }
msgBox.Buttons[0].OnClicked = (_, userdata) => GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction");
{ GameMain.GameSession.LoadPreviousSave();
if (GameMain.GameSession.RoundSummary?.Frame != null) });
{
GUIMessageBox.MessageBoxes.Remove(GameMain.GameSession.RoundSummary.Frame);
}
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction");
TogglePauseMenu(btn, userData);
GameMain.GameSession.LoadPreviousSave();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked = (_, userdata) =>
{
TogglePauseMenu(btn, userData);
msgBox.Close();
return true;
};
return true;
};
if (IsOutpostLevel()) if (IsOutpostLevel())
{ {
var saveAndQuitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuSaveQuit")) CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToMainMenuVerification", action: () =>
{ {
UserData = "save", if (IsOutpostLevel()) { GameMain.QuitToMainMenu(save: true); }
OnClicked = (btn, userData) => });
{
pauseMenuOpen = false;
if (IsOutpostLevel())
{
GameMain.QuitToMainMenu(save: true);
}
return true;
}
};
} }
} }
else if (GameMain.GameSession.GameMode is TestGameMode) else if (GameMain.GameSession.GameMode is TestGameMode)
{ {
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), text: TextManager.Get("PauseMenuReturnToEditor")) CreateButton("PauseMenuReturnToEditor", buttonContainer, action: () =>
{ {
OnClicked = (btn, userdata) => GameMain.GameSession?.EndRound("");
{ });
GameMain.GameSession.EndRound("");
pauseMenuOpen = false;
return true;
}
};
} }
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), bool canSave = GameMain.GameSession.GameMode is CampaignMode && IsOutpostLevel();
text: TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby": "EndRound")) if (canSave)
{ {
OnClicked = (btn, userdata) => CreateButton("PauseMenuSaveQuit", buttonContainer, verificationTextTag: "PauseMenuSaveAndReturnToServerLobbyVerification", action: () =>
{ {
if (!GameMain.Client.HasPermission(ClientPermissions.ManageRound)) { return false; } GameMain.Client?.RequestRoundEnd(save: true);
if (GameMain.GameSession.GameMode is CampaignMode && !IsOutpostLevel() || (!Submarine.MainSub.AtStartExit && !Submarine.MainSub.AtEndExit)) });
{ }
var msgBox = new GUIMessageBox("",
TextManager.Get(GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd"), CreateButton(GameMain.GameSession.GameMode is CampaignMode ? "ReturnToServerlobby" : "EndRound", buttonContainer,
new string[] { TextManager.Get("Yes"), TextManager.Get("No") }) verificationTextTag: GameMain.GameSession.GameMode is CampaignMode ? "PauseMenuReturnToServerLobbyVerification" : "EndRoundSubNotAtLevelEnd",
{ action: () =>
UserData = "verificationprompt" {
}; GameMain.Client?.RequestRoundEnd(save: false);
msgBox.Buttons[0].OnClicked = (_, __) => });
{
pauseMenuOpen = false;
GameMain.Client.RequestRoundEnd();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
else
{
pauseMenuOpen = false;
GameMain.Client.RequestRoundEnd();
}
return true;
}
};
} }
} }
var quitButton = new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), buttonContainer.RectTransform), TextManager.Get("PauseMenuQuit")); if (GameMain.GameSession != null || Screen.Selected is CharacterEditorScreen || Screen.Selected is SubEditorScreen)
quitButton.OnClicked += (btn, userData) =>
{ {
if (GameMain.GameSession != null || (Screen.Selected is CharacterEditorScreen || Screen.Selected is SubEditorScreen)) CreateButton("PauseMenuQuit", buttonContainer,
{ verificationTextTag: GameMain.GameSession == null ? "PauseMenuQuitVerificationEditor" : "PauseMenuQuitVerification",
string text = GameMain.GameSession == null ? "PauseMenuQuitVerificationEditor" : "PauseMenuQuitVerification"; action: () =>
var msgBox = new GUIMessageBox("", TextManager.Get(text), new string[] { TextManager.Get("Yes"), TextManager.Get("Cancel") })
{
UserData = "verificationprompt"
};
msgBox.Buttons[0].OnClicked = (yesBtn, userdata) =>
{ {
GameMain.QuitToMainMenu(save: false); GameMain.QuitToMainMenu(save: false);
pauseMenuOpen = false; });
return true; }
}; else
msgBox.Buttons[0].OnClicked += msgBox.Close; {
msgBox.Buttons[1].OnClicked = (_, userdata) => CreateButton("PauseMenuQuit", buttonContainer, action: () => { GameMain.QuitToMainMenu(save: false); });
{ }
pauseMenuOpen = false;
msgBox.Close();
return true;
};
}
else
{
GameMain.QuitToMainMenu(save: false);
pauseMenuOpen = false;
}
return true;
};
GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock)); GUITextBlock.AutoScaleAndNormalize(buttonContainer.Children.Where(c => c is GUIButton).Select(c => ((GUIButton)c).TextBlock));
} }
void CreateButton(string textTag, GUIComponent parent, Action action, string verificationTextTag = null)
{
new GUIButton(new RectTransform(new Vector2(1.0f, 0.1f), parent.RectTransform), TextManager.Get(textTag))
{
OnClicked = (btn, userData) =>
{
if (string.IsNullOrEmpty(verificationTextTag))
{
pauseMenuOpen = false;
action?.Invoke();
}
else
{
CreateVerificationPrompt(verificationTextTag, action);
}
return true;
}
};
}
void CreateVerificationPrompt(string textTag, Action confirmAction)
{
var msgBox = new GUIMessageBox("", TextManager.Get(textTag),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") })
{
UserData = "verificationprompt"
};
msgBox.Buttons[0].OnClicked = (_, __) =>
{
pauseMenuOpen = false;
confirmAction?.Invoke();
return true;
};
msgBox.Buttons[0].OnClicked += msgBox.Close;
msgBox.Buttons[1].OnClicked += msgBox.Close;
}
} }
private static bool TogglePauseMenu(GUIButton button, object obj) private static bool TogglePauseMenu(GUIButton button, object obj)
@@ -531,6 +531,17 @@ namespace Barotrauma
} }
} }
public virtual void ForceLayoutRecalculation()
{
//This is very ugly but it gets the job done, it
//would be real nice to un-jank this some day
ForceUpdate();
ForceUpdate();
foreach (var child in Children) { child.ForceLayoutRecalculation(); }
}
public void ForceUpdate() => Update((float)Timing.Step);
/// <summary> /// <summary>
/// Updates all the children manually. /// Updates all the children manually.
/// </summary> /// </summary>
@@ -831,7 +842,7 @@ namespace Barotrauma
CoroutineManager.StartCoroutine(SlideToPosition(duration, 0.0f, targetPos)); CoroutineManager.StartCoroutine(SlideToPosition(duration, 0.0f, targetPos));
} }
private IEnumerable<object> SlideToPosition(float duration, float wait, Vector2 target) private IEnumerable<CoroutineStatus> SlideToPosition(float duration, float wait, Vector2 target)
{ {
float t = 0.0f; float t = 0.0f;
var (startX, startY) = RectTransform.ScreenSpaceOffset.ToVector2(); var (startX, startY) = RectTransform.ScreenSpaceOffset.ToVector2();
@@ -855,7 +866,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<object> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f) private IEnumerable<CoroutineStatus> LerpAlpha(float to, float duration, bool removeAfter, float wait = 0.0f)
{ {
State = ComponentState.None; State = ComponentState.None;
float t = 0.0f; float t = 0.0f;
@@ -894,7 +905,7 @@ namespace Barotrauma
pulsateCoroutine = CoroutineManager.StartCoroutine(DoPulsate(startScale, endScale, duration), "Pulsate" + ToString()); pulsateCoroutine = CoroutineManager.StartCoroutine(DoPulsate(startScale, endScale, duration), "Pulsate" + ToString());
} }
private IEnumerable<object> DoPulsate(Vector2 startScale, Vector2 endScale, float duration) private IEnumerable<CoroutineStatus> DoPulsate(Vector2 startScale, Vector2 endScale, float duration)
{ {
float t = 0.0f; float t = 0.0f;
while (t < duration) while (t < duration)
@@ -34,7 +34,7 @@ namespace Barotrauma
public GUIScrollBar ScrollBar { get; private set; } public GUIScrollBar ScrollBar { get; private set; }
private readonly Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>(); private readonly Dictionary<GUIComponent, bool> childVisible = new Dictionary<GUIComponent, bool>();
private int totalSize; private int totalSize;
private bool childrenNeedsRecalculation; private bool childrenNeedsRecalculation;
private bool scrollBarNeedsRecalculation; private bool scrollBarNeedsRecalculation;
@@ -53,7 +53,23 @@ namespace Barotrauma
} }
} }
public bool SelectMultiple; public enum SelectMode
{
SelectSingle,
SelectMultiple,
RequireShiftToSelectMultiple
}
public SelectMode CurrentSelectMode = SelectMode.SelectSingle;
public bool SelectMultiple
{
get { return CurrentSelectMode != SelectMode.SelectSingle; }
set
{
CurrentSelectMode = value ? SelectMode.SelectMultiple : SelectMode.SelectSingle;
}
}
public bool HideChildrenOutsideFrame = true; public bool HideChildrenOutsideFrame = true;
@@ -63,6 +79,8 @@ namespace Barotrauma
public bool AllowMouseWheelScroll { get; set; } = true; public bool AllowMouseWheelScroll { get; set; } = true;
public bool AllowArrowKeyScroll { get; set; } = true;
/// <summary> /// <summary>
/// Scrolls the list smoothly /// Scrolls the list smoothly
/// </summary> /// </summary>
@@ -103,7 +121,7 @@ namespace Barotrauma
/// <summary> /// <summary>
/// true if mouse down should select elements instead of mouse up /// true if mouse down should select elements instead of mouse up
/// </summary> /// </summary>
private bool useMouseDownToSelect = false; private readonly bool useMouseDownToSelect = false;
private Vector4? overridePadding; private Vector4? overridePadding;
public Vector4 Padding public Vector4 Padding
@@ -132,10 +150,7 @@ namespace Barotrauma
// TODO: fix implicit hiding // TODO: fix implicit hiding
public bool Selected { get; set; } public bool Selected { get; set; }
public List<GUIComponent> AllSelected public IReadOnlyList<GUIComponent> AllSelected => selected;
{
get { return selected; }
}
public object SelectedData public object SelectedData
{ {
@@ -214,25 +229,34 @@ namespace Barotrauma
public bool AutoHideScrollBar { get; set; } = true; public bool AutoHideScrollBar { get; set; } = true;
private bool IsScrollBarOnDefaultSide { get; set; } private bool IsScrollBarOnDefaultSide { get; set; }
public bool CanDragElements public enum DragMode
{
NoDragging,
DragWithinBox,
DragOutsideBox
}
private DragMode currentDragMode = DragMode.NoDragging;
public DragMode CurrentDragMode
{ {
get get
{ {
return canDragElements; return currentDragMode;
} }
set set
{ {
if (value == false && canDragElements && draggedElement != null) if (value == DragMode.NoDragging && currentDragMode != DragMode.NoDragging && isDraggingElement)
{ {
DraggedElement = null; DraggedElement = null;
} }
canDragElements = value; currentDragMode = value;
} }
} }
private bool canDragElements = false;
private GUIComponent draggedElement; private GUIComponent draggedElement;
private Rectangle draggedReferenceRectangle; private Point dragMousePosRelativeToTopLeftCorner;
private Point draggedReferenceOffset; private bool isDraggingElement => draggedElement != null;
public bool HasDraggedElementIndexChanged { get; private set; } public bool HasDraggedElementIndexChanged { get; private set; }
public GUIComponent DraggedElement public GUIComponent DraggedElement
@@ -246,8 +270,24 @@ namespace Barotrauma
if (value == draggedElement) { return; } if (value == draggedElement) { return; }
draggedElement = value; draggedElement = value;
HasDraggedElementIndexChanged = false; HasDraggedElementIndexChanged = false;
if (value == null) { return; }
dragMousePosRelativeToTopLeftCorner = PlayerInput.MousePosition.ToPoint() - value.Rect.Location;
if (SelectMultiple)
{
if (!AllSelected.Contains(DraggedElement))
{
Select(DraggedElement.ToEnumerable());
}
}
} }
} }
//This exists to work around the fact that rendering child
//elements on top of the listbox's siblings is a clusterfuck.
public bool HideDraggedElement = false;
private readonly bool isHorizontal; private readonly bool isHorizontal;
@@ -354,7 +394,7 @@ namespace Barotrauma
(child.UserData == null && userData == null)) (child.UserData == null && userData == null))
{ {
Select(i, force, autoScroll); Select(i, force, autoScroll);
if (!SelectMultiple) return; if (!SelectMultiple) { return; }
} }
i++; i++;
} }
@@ -363,9 +403,10 @@ namespace Barotrauma
private Point CalculateFrameSize(bool isHorizontal, int scrollBarSize) private Point CalculateFrameSize(bool isHorizontal, int scrollBarSize)
=> isHorizontal ? new Point(Rect.Width, Rect.Height - scrollBarSize) : new Point(Rect.Width - scrollBarSize, Rect.Height); => isHorizontal ? new Point(Rect.Width, Rect.Height - scrollBarSize) : new Point(Rect.Width - scrollBarSize, Rect.Height);
private void RepositionChildren() public Vector2 CalculateTopOffset()
{ {
int x = 0, y = 0; int x = 0;
int y = 0;
if (ScrollBar.BarSize < 1.0f) if (ScrollBar.BarSize < 1.0f)
{ {
if (ScrollBar.IsHorizontal) if (ScrollBar.IsHorizontal)
@@ -378,53 +419,59 @@ namespace Barotrauma
} }
} }
return new Vector2(x, y);
}
private void CalculateChildrenOffsets(Action<int, Point> callback)
{
Vector2 topOffset = CalculateTopOffset();
int x = (int)topOffset.X;
int y = (int)topOffset.Y;
for (int i = 0; i < Content.CountChildren; i++) for (int i = 0; i < Content.CountChildren; i++)
{ {
GUIComponent child = Content.GetChild(i); GUIComponent child = Content.GetChild(i);
if (!child.Visible) { continue; } if (!child.Visible) { continue; }
if (RectTransform != null) if (RectTransform != null)
{ {
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y)) callback(i, new Point(x, y));
{
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
} }
if (useGridLayout) if (useGridLayout)
{ {
void advanceGridLayout(
ref int primaryCoord,
ref int secondaryCoord,
int primaryChildDimension,
int secondaryChildDimension,
int primaryParentDimension)
{
if (primaryCoord + primaryChildDimension + Spacing > primaryParentDimension)
{
primaryCoord = 0;
secondaryCoord += secondaryChildDimension + Spacing;
callback(i, new Point(x, y));
}
primaryCoord += primaryChildDimension + Spacing;
}
if (ScrollBar.IsHorizontal) if (ScrollBar.IsHorizontal)
{ {
if (y + child.Rect.Height + Spacing > Content.Rect.Height) advanceGridLayout(
{ primaryCoord: ref y,
y = 0; secondaryCoord: ref x,
x += child.Rect.Width + Spacing; primaryChildDimension: child.Rect.Height,
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y)) secondaryChildDimension: child.Rect.Width,
{ primaryParentDimension: Content.Rect.Height);
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
y += child.Rect.Height + Spacing;
}
else
{
y += child.Rect.Height + Spacing;
}
} }
else else
{ {
if (x + child.Rect.Width + Spacing > Content.Rect.Width) advanceGridLayout(
{ primaryCoord: ref x,
x = 0; secondaryCoord: ref y,
y += child.Rect.Height + Spacing; primaryChildDimension: child.Rect.Width,
if (child != draggedElement && (child.RectTransform.AbsoluteOffset.X != x || child.RectTransform.AbsoluteOffset.Y != y)) secondaryChildDimension: child.Rect.Height,
{ primaryParentDimension: Content.Rect.Width);
child.RectTransform.AbsoluteOffset = new Point(x, y);
}
x += child.Rect.Width + Spacing;
}
else
{
x += child.Rect.Width + Spacing;
}
} }
} }
else else
@@ -440,6 +487,18 @@ namespace Barotrauma
} }
} }
} }
private void RepositionChildren()
{
CalculateChildrenOffsets((index, offset) =>
{
var child = Content.GetChild(index);
if (child != draggedElement && child.RectTransform.AbsoluteOffset != offset)
{
child.RectTransform.AbsoluteOffset = offset;
}
});
}
/// <summary> /// <summary>
/// Scrolls the list to the specific element, currently only works when smooth scrolling and PadBottom are enabled. /// Scrolls the list to the specific element, currently only works when smooth scrolling and PadBottom are enabled.
@@ -466,7 +525,7 @@ namespace Barotrauma
{ {
CoroutineManager.StartCoroutine(ScrollCoroutine()); CoroutineManager.StartCoroutine(ScrollCoroutine());
IEnumerable<object> ScrollCoroutine() IEnumerable<CoroutineStatus> ScrollCoroutine()
{ {
if (BarSize >= 1.0f) if (BarSize >= 1.0f)
{ {
@@ -490,68 +549,122 @@ namespace Barotrauma
} }
} }
private void StartDraggingElement(GUIComponent child)
private void UpdateChildrenRect()
{ {
//dragging DraggedElement = child;
if (CanDragElements && draggedElement != null) }
private bool UpdateDragging()
{
if (CurrentDragMode == DragMode.NoDragging || !isDraggingElement) { return false; }
if (!PlayerInput.PrimaryMouseButtonHeld())
{ {
if (!PlayerInput.PrimaryMouseButtonHeld()) var draggedElem = draggedElement;
OnRearranged?.Invoke(this, draggedElem.UserData);
DraggedElement = null;
RepositionChildren();
if (AllSelected.Contains(draggedElem)) { return true; }
}
else
{
Vector2 topOffset = CalculateTopOffset();
var mousePos = PlayerInput.MousePosition.ToPoint();
draggedElement.RectTransform.AbsoluteOffset = mousePos - Content.Rect.Location - dragMousePosRelativeToTopLeftCorner;
if (CurrentDragMode != DragMode.DragOutsideBox)
{ {
OnRearranged?.Invoke(this, draggedElement.UserData); var offset = draggedElement.RectTransform.AbsoluteOffset;
DraggedElement = null; draggedElement.RectTransform.AbsoluteOffset =
RepositionChildren(); isHorizontal ? new Point(offset.X, 0) : new Point(0, offset.Y);
}
int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform);
int newIndex = index;
Point draggedOffsetWhenReleased = Point.Zero;
CalculateChildrenOffsets((i, offset) =>
{
if (index != i) { return; }
draggedOffsetWhenReleased = offset;
});
Rectangle draggedRectWhenReleased = new Rectangle(Content.Rect.Location + draggedOffsetWhenReleased, draggedElement.Rect.Size);
void shiftIndices(
float mousePos,
ref int draggedRectWhenReleasedLocation,
int draggedRectWhenReleasedSize)
{
while (mousePos > (draggedRectWhenReleasedLocation + draggedRectWhenReleasedSize) && newIndex < Content.CountChildren-1)
{
newIndex++;
draggedRectWhenReleasedLocation += draggedRectWhenReleasedSize;
}
while (mousePos < draggedRectWhenReleasedLocation && newIndex > 0)
{
newIndex--;
draggedRectWhenReleasedLocation -= draggedRectWhenReleasedSize;
}
if (newIndex != index && AllSelected.Count > 1)
{
this.selected.Sort((a, b) => Content.GetChildIndex(a) - Content.GetChildIndex(b));
int draggedPos = AllSelected.IndexOf(draggedElement);
if (newIndex < draggedPos)
{
newIndex = draggedPos;
}
if (newIndex >= Content.CountChildren - (AllSelected.Count - draggedPos))
{
int max = Content.CountChildren - (AllSelected.Count - draggedPos);
newIndex = max;
}
}
}
if (isHorizontal)
{
shiftIndices(
mousePos.X,
ref draggedRectWhenReleased.X,
draggedRectWhenReleased.Width);
} }
else else
{ {
draggedElement.RectTransform.AbsoluteOffset = isHorizontal ? shiftIndices(
draggedReferenceOffset + new Point((int)PlayerInput.MousePosition.X - draggedReferenceRectangle.Center.X, 0) : mousePos.Y,
draggedReferenceOffset + new Point(0, (int)PlayerInput.MousePosition.Y - draggedReferenceRectangle.Center.Y); ref draggedRectWhenReleased.Y,
draggedRectWhenReleased.Height);
}
int index = Content.RectTransform.GetChildIndex(draggedElement.RectTransform); if (newIndex != index)
int currIndex = index; {
if (AllSelected.Count > 1)
if (isHorizontal)
{ {
while (currIndex > 0 && PlayerInput.MousePosition.X < draggedReferenceRectangle.Left) this.selected.Sort((a, b) => Content.GetChildIndex(a) - Content.GetChildIndex(b));
int indexOfDraggedElem = AllSelected.IndexOf(draggedElement);
IEnumerable<GUIComponent> allSelected = AllSelected;
if (newIndex > index) { allSelected = allSelected.Reverse(); }
foreach (var elem in allSelected)
{ {
currIndex--; elem.RectTransform.RepositionChildInHierarchy(newIndex + AllSelected.IndexOf(elem) - indexOfDraggedElem);
draggedReferenceRectangle.X -= draggedReferenceRectangle.Width;
draggedReferenceOffset.X -= draggedReferenceRectangle.Width;
}
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.X > draggedReferenceRectangle.Right)
{
currIndex++;
draggedReferenceRectangle.X += draggedReferenceRectangle.Width;
draggedReferenceOffset.X += draggedReferenceRectangle.Width;
} }
} }
else else
{ {
while (currIndex > 0 && PlayerInput.MousePosition.Y < draggedReferenceRectangle.Top) draggedElement.RectTransform.RepositionChildInHierarchy(newIndex);
{
currIndex--;
draggedReferenceRectangle.Y -= draggedReferenceRectangle.Height;
draggedReferenceOffset.Y -= draggedReferenceRectangle.Height;
}
while (currIndex < Content.CountChildren - 1 && PlayerInput.MousePosition.Y > draggedReferenceRectangle.Bottom)
{
currIndex++;
draggedReferenceRectangle.Y += draggedReferenceRectangle.Height;
draggedReferenceOffset.Y += draggedReferenceRectangle.Height;
}
} }
HasDraggedElementIndexChanged = true;
if (currIndex != index)
{
draggedElement.RectTransform.RepositionChildInHierarchy(currIndex);
HasDraggedElementIndexChanged = true;
}
return;
} }
return true;
} }
return false;
}
private void UpdateChildrenRect()
{
if (UpdateDragging()) { return; }
if (SelectTop) if (SelectTop)
{ {
foreach (GUIComponent child in Content.Children) foreach (GUIComponent child in Content.Children)
@@ -581,7 +694,7 @@ namespace Barotrauma
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;
if (child == null || !child.Visible) { continue; } if (!(child is { Visible: true })) { continue; }
// selecting // selecting
if (Enabled && (CanBeFocused || CanInteractWhenUnfocusable) && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child)) if (Enabled && (CanBeFocused || CanInteractWhenUnfocusable) && child.CanBeFocused && child.Rect.Contains(PlayerInput.MousePosition) && GUI.IsMouseOn(child))
@@ -595,19 +708,15 @@ namespace Barotrauma
if (SelectTop) if (SelectTop)
{ {
ScrollToElement(child); ScrollToElement(child);
Select(i, autoScroll: false, takeKeyBoardFocus: true);
}
else
{
Select(i, autoScroll: false, takeKeyBoardFocus: true);
} }
Select(i, autoScroll: false, takeKeyBoardFocus: true);
} }
if (CanDragElements && PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child) if (CurrentDragMode != DragMode.NoDragging
&& (CurrentSelectMode != SelectMode.RequireShiftToSelectMultiple || (!PlayerInput.IsShiftDown() && !PlayerInput.IsCtrlDown()))
&& PlayerInput.PrimaryMouseButtonDown() && GUI.MouseOn == child)
{ {
DraggedElement = child; StartDraggingElement(child);
draggedReferenceRectangle = child.Rect;
draggedReferenceOffset = child.RectTransform.AbsoluteOffset;
} }
} }
else if (selected.Contains(child)) else if (selected.Contains(child))
@@ -686,6 +795,13 @@ namespace Barotrauma
OnAddedToGUIUpdateList?.Invoke(this); OnAddedToGUIUpdateList?.Invoke(this);
} }
public override void ForceLayoutRecalculation()
{
base.ForceLayoutRecalculation();
Content.ForceLayoutRecalculation();
ScrollBar.ForceLayoutRecalculation();
}
public void RecalculateChildren() public void RecalculateChildren()
{ {
foreach (GUIComponent child in Content.Children) foreach (GUIComponent child in Content.Children)
@@ -709,8 +825,6 @@ namespace Barotrauma
} }
} }
public void ForceUpdate() => Update((float)Timing.Step);
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
if (!Visible) { return; } if (!Visible) { return; }
@@ -805,7 +919,7 @@ namespace Barotrauma
} }
else else
{ {
ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * BarSize; ScrollBar.BarScroll -= (PlayerInput.ScrollWheelSpeed / 500.0f) * ScrollBar.UnclampedBarSize;
} }
} }
@@ -870,6 +984,7 @@ namespace Barotrauma
if (childIndex >= Content.CountChildren || childIndex < 0) { return; } if (childIndex >= Content.CountChildren || childIndex < 0) { return; }
GUIComponent child = Content.GetChild(childIndex); GUIComponent child = Content.GetChild(childIndex);
if (child is null) { return; }
bool wasSelected = true; bool wasSelected = true;
if (OnSelected != null) if (OnSelected != null)
@@ -880,7 +995,8 @@ namespace Barotrauma
if (!wasSelected) { return; } if (!wasSelected) { return; }
if (SelectMultiple) if (CurrentSelectMode == SelectMode.SelectMultiple ||
(CurrentSelectMode == SelectMode.RequireShiftToSelectMultiple && PlayerInput.IsCtrlDown()))
{ {
if (selected.Contains(child)) if (selected.Contains(child))
{ {
@@ -891,6 +1007,23 @@ namespace Barotrauma
selected.Add(child); selected.Add(child);
} }
} }
else if (CurrentSelectMode == SelectMode.RequireShiftToSelectMultiple && PlayerInput.IsShiftDown())
{
var first = SelectedComponent ?? child;
var last = child;
int firstIndex = Content.GetChildIndex(first);
int lastIndex = Content.GetChildIndex(last);
int sgn = Math.Sign(lastIndex - firstIndex);
selected.Clear(); selected.Add(first);
for (int i = firstIndex + sgn; i != lastIndex; i += sgn)
{
if (Content.GetChild(i) is { Visible: true } interChild)
{
selected.Add(interChild);
}
}
if (first != last) { selected.Add(last); }
}
else else
{ {
selected.Clear(); selected.Clear();
@@ -937,6 +1070,14 @@ namespace Barotrauma
} }
} }
public void Select(IEnumerable<GUIComponent> children)
{
Selected = true;
selected.Clear();
selected.AddRange(children.Where(c => Content.Children.Contains(c)));
foreach (var child in selected) { OnSelected?.Invoke(child, child.UserData); }
}
public void Deselect() public void Deselect()
{ {
Selected = false; Selected = false;
@@ -1007,9 +1148,12 @@ namespace Barotrauma
} }
float minScrollBarSize = 20.0f; float minScrollBarSize = 20.0f;
ScrollBar.UnclampedBarSize = ScrollBar.IsHorizontal ?
Math.Min(Content.Rect.Width / (float)totalSize, 1.0f) :
Math.Min(Content.Rect.Height / (float)totalSize, 1.0f);
ScrollBar.BarSize = ScrollBar.IsHorizontal ? ScrollBar.BarSize = ScrollBar.IsHorizontal ?
Math.Max(Math.Min(Content.Rect.Width / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Width) : Math.Max(ScrollBar.UnclampedBarSize, minScrollBarSize / Content.Rect.Width) :
Math.Max(Math.Min(Content.Rect.Height / (float)totalSize, 1.0f), minScrollBarSize / Content.Rect.Height); Math.Max(ScrollBar.UnclampedBarSize, minScrollBarSize / Content.Rect.Height);
} }
public override void ClearChildren() public override void ClearChildren()
@@ -1052,10 +1196,11 @@ namespace Barotrauma
int i = 0; int i = 0;
foreach (GUIComponent child in Content.Children) foreach (GUIComponent child in Content.Children)
{ {
if (!child.Visible) continue; if (!child.Visible) { continue; }
if (child == draggedElement && CurrentDragMode == DragMode.DragOutsideBox) { continue; }
if (!IsChildInsideFrame(child)) if (!IsChildInsideFrame(child))
{ {
if (lastVisible > 0) break; if (lastVisible > 0) { break; }
continue; continue;
} }
lastVisible = i; lastVisible = i;
@@ -1070,6 +1215,11 @@ namespace Barotrauma
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
} }
if (isDraggingElement && CurrentDragMode == DragMode.DragOutsideBox && !HideDraggedElement)
{
draggedElement.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
}
if (ScrollBarVisible) if (ScrollBarVisible)
{ {
ScrollBar.DrawManually(spriteBatch, alsoChildren: true, recursive: true); ScrollBar.DrawManually(spriteBatch, alsoChildren: true, recursive: true);
@@ -1106,10 +1256,16 @@ namespace Barotrauma
switch (key) switch (key)
{ {
case Keys.Down: case Keys.Down:
SelectNext(); if (!isHorizontal && AllowArrowKeyScroll) { SelectNext(); }
break; break;
case Keys.Up: case Keys.Up:
SelectPrevious(); if (!isHorizontal && AllowArrowKeyScroll) { SelectPrevious(); }
break;
case Keys.Left:
if (isHorizontal && AllowArrowKeyScroll) { SelectPrevious(); }
break;
case Keys.Right:
if (isHorizontal && AllowArrowKeyScroll) { SelectNext(); }
break; break;
case Keys.Enter: case Keys.Enter:
case Keys.Space: case Keys.Space:
@@ -30,7 +30,7 @@ namespace Barotrauma
{ {
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"GUIProgressBar.BarSize_setter", "GUIProgressBar.BarSize_setter",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
"Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace()); "Attempted to set the BarSize of a GUIProgressBar to an invalid value (" + value + ")\n" + Environment.StackTrace.CleanupStackTrace());
return; return;
} }
@@ -105,7 +105,7 @@ namespace Barotrauma
{ {
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"GUIProgressBar.Draw:GetProgress", "GUIProgressBar.Draw:GetProgress",
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
"ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace.CleanupStackTrace()); "ProgressGetter of a GUIProgressBar (" + ProgressGetter.Target.ToString() + " - " + ProgressGetter.Method.ToString() + ") returned an invalid value (" + newSize + ")\n" + Environment.StackTrace.CleanupStackTrace());
} }
else else
@@ -183,6 +183,11 @@ namespace Barotrauma
} }
} }
/// <summary>
/// ListBoxes with lots of content in them clamp the size of the scrollbar above a certain minimum size; this is the relative bar size without the clamping applied.
/// </summary>
public float UnclampedBarSize;
public float BarSize public float BarSize
{ {
get { return barSize; } get { return barSize; }
@@ -299,9 +304,15 @@ namespace Barotrauma
} }
else else
{ {
float barScale = 1.0f;
if (UnclampedBarSize > 0.0f)
{
barScale = (UnclampedBarSize / BarSize);
}
MoveButton(new Vector2( MoveButton(new Vector2(
Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Bar.Rect.Width, Math.Sign(PlayerInput.MousePosition.X - Bar.Rect.Center.X) * Bar.Rect.Width * barScale,
Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Bar.Rect.Height)); Math.Sign(PlayerInput.MousePosition.Y - Bar.Rect.Center.Y) * Bar.Rect.Height * barScale));
} }
} }
} }
@@ -44,6 +44,8 @@ namespace Barotrauma
public UISprite PingCircle { get; private set; } public UISprite PingCircle { get; private set; }
public UISprite YouAreHereCircle { get; private set; }
public UISprite UIGlowCircular { get; private set; } public UISprite UIGlowCircular { get; private set; }
public UISprite UIGlowSolidCircular { get; private set; } public UISprite UIGlowSolidCircular { get; private set; }
@@ -253,6 +255,9 @@ namespace Barotrauma
case "pingcircle": case "pingcircle":
PingCircle = new UISprite(subElement); PingCircle = new UISprite(subElement);
break; break;
case "youareherecircle":
YouAreHereCircle = new UISprite(subElement);
break;
case "radiation": case "radiation":
RadiationSprite = new UISprite(subElement); RadiationSprite = new UISprite(subElement);
break; break;
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -275,6 +276,7 @@ namespace Barotrauma
public delegate void OnClickDelegate(GUITextBlock textBlock, ClickableArea area); public delegate void OnClickDelegate(GUITextBlock textBlock, ClickableArea area);
public OnClickDelegate OnClick; public OnClickDelegate OnClick;
public OnClickDelegate OnSecondaryClick;
} }
public List<ClickableArea> ClickableAreas { get; private set; } = new List<ClickableArea>(); public List<ClickableArea> ClickableAreas { get; private set; } = new List<ClickableArea>();
@@ -376,6 +378,7 @@ namespace Barotrauma
public void SetTextPos() public void SetTextPos()
{ {
cachedCaretPositions = ImmutableArray<Vector2>.Empty;
if (text == null) { return; } if (text == null) { return; }
censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length); censoredText = string.IsNullOrEmpty(text) ? "" : new string('\u2022', text.Length);
@@ -389,7 +392,7 @@ namespace Barotrauma
if (Wrap && rect.Width > 0) if (Wrap && rect.Width > 0)
{ {
wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale, playerInput); wrappedText = ToolBox.WrapText(text, rect.Width - padding.X - padding.Z, Font, textScale);
TextSize = MeasureText(wrappedText); TextSize = MeasureText(wrappedText);
} }
else if (OverflowClip) else if (OverflowClip)
@@ -477,115 +480,56 @@ namespace Barotrauma
disabledTextColor = color; disabledTextColor = color;
} }
protected List<Tuple<Vector2, int>> GetAllPositions() private ImmutableArray<Vector2> cachedCaretPositions = ImmutableArray<Vector2>.Empty;
public ImmutableArray<Vector2> GetAllCaretPositions()
{ {
float halfHeight = Font.MeasureString("T").Y * 0.5f * textScale; if (cachedCaretPositions.Any())
string textDrawn = Censor ? CensoredText : WrappedText;
var positions = new List<Tuple<Vector2, int>>();
if (textDrawn.Contains("\n"))
{ {
string[] lines = textDrawn.Split('\n'); return cachedCaretPositions;
int index = 0;
int totalIndex = 0;
for (int i = 0; i < lines.Length; i++)
{
string line = lines[i];
totalIndex += line.Length;
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y * textScale;
for (int j = 0; j <= line.Length; j++)
{
Vector2 lineTextSize = Font.MeasureString(line.Substring(0, j)) * textScale;
Vector2 indexPos = new Vector2(lineTextSize.X, totalTextHeight - halfHeight) + TextPos - Origin * textScale;
//DebugConsole.NewMessage($"index: {index}, pos: {indexPos}", Color.AliceBlue);
positions.Add(new Tuple<Vector2, int>(indexPos, index + j));
}
index = totalIndex;
}
} }
else string textDrawn = Censor ? CensoredText : Text;
{ float w = Wrap
textDrawn = Censor ? CensoredText : Text; ? (Rect.Width - Padding.X - Padding.Z) / TextScale
for (int i = 0; i <= Text.Length; i++) : float.PositiveInfinity;
{ Font.WrapText(textDrawn, w, out Vector2[] positions);
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, i)) * textScale; cachedCaretPositions = positions.Select(p => p * TextScale + TextPos - Origin * TextScale).ToImmutableArray();
Vector2 indexPos = new Vector2(textSize.X, textSize.Y - halfHeight) + TextPos - Origin * textScale; return cachedCaretPositions;
//DebugConsole.NewMessage($"index: {i}, pos: {indexPos}", Color.WhiteSmoke);
positions.Add(new Tuple<Vector2, int>(indexPos, i));
}
}
return positions;
} }
public int GetCaretIndexFromScreenPos(Vector2 pos) public int GetCaretIndexFromScreenPos(in Vector2 pos)
{ {
return GetCaretIndexFromLocalPos(pos - Rect.Location.ToVector2()); return GetCaretIndexFromLocalPos(pos - Rect.Location.ToVector2());
} }
public int GetCaretIndexFromLocalPos(Vector2 pos) public int GetCaretIndexFromLocalPos(in Vector2 pos)
{ {
var positions = GetAllPositions(); var positions = GetAllCaretPositions();
if (positions.Count == 0) { return 0; } if (positions.Length == 0) { return 0; }
float halfHeight = Font.MeasureString("T").Y * 0.5f * textScale;
var currPosition = positions[0]; float closestXDist = float.PositiveInfinity;
float closestYDist = float.PositiveInfinity;
float topY = positions.Min(p => p.Item1.Y); int closestIndex = -1;
for (int i = 0; i < positions.Length; i++)
for (int i = 1; i < positions.Count; i++)
{ {
var p1 = positions[i]; float xDist = Math.Abs(pos.X - positions[i].X);
var p2 = currPosition; float yDist = Math.Abs(pos.Y - (positions[i].Y + Font.LineHeight * 0.5f));
if (yDist < closestYDist || (MathUtils.NearlyEqual(yDist, closestYDist) && xDist < closestXDist))
float diffY = Math.Abs(p1.Item1.Y - pos.Y) - Math.Abs(p2.Item1.Y - pos.Y);
if (diffY < -3.0f)
{ {
currPosition = p1; closestIndex = i;
continue; closestXDist = xDist;
} closestYDist = yDist;
else if (diffY > 3.0f)
{
continue;
}
else
{
diffY = Math.Abs(p1.Item1.Y - pos.Y);
if (diffY < halfHeight || (p1.Item1.Y == topY && pos.Y < topY))
{
//we are on this line, select the nearest character
float diffX = Math.Abs(p1.Item1.X - pos.X) - Math.Abs(p2.Item1.X - pos.X);
if (diffX < -1.0f)
{
currPosition = p1; continue;
}
else
{
continue;
}
}
else
{
//we are on a different line, preserve order
if (p1.Item2 < p2.Item2)
{
if (p1.Item1.Y > pos.Y) { currPosition = p1; }
}
else if (p1.Item2 > p2.Item2)
{
if (p1.Item1.Y < pos.Y) { currPosition = p1; }
}
continue;
}
} }
} }
//GUI.AddMessage($"index: {posIndex.Item2}, pos: {posIndex.Item1}", Color.WhiteSmoke);
return currPosition != null ? currPosition.Item2 : Text.Length; return closestIndex >= 0 ? closestIndex : Text.Length;
} }
protected override void Update(float deltaTime) protected override void Update(float deltaTime)
{ {
base.Update(deltaTime); base.Update(deltaTime);
if (ClickableAreas.Any() && (GUI.MouseOn?.IsParentOf(this) ?? true)) if (ClickableAreas.Any() && ((GUI.MouseOn?.IsParentOf(this) ?? true) || GUI.MouseOn == this))
{ {
if (!Rect.Contains(PlayerInput.MousePosition)) { return; } if (!Rect.Contains(PlayerInput.MousePosition)) { return; }
int index = GetCaretIndexFromScreenPos(PlayerInput.MousePosition); int index = GetCaretIndexFromScreenPos(PlayerInput.MousePosition);
@@ -598,6 +542,10 @@ namespace Barotrauma
{ {
clickableArea.OnClick?.Invoke(this, clickableArea); clickableArea.OnClick?.Invoke(this, clickableArea);
} }
if (PlayerInput.SecondaryMouseButtonClicked())
{
clickableArea.OnSecondaryClick?.Invoke(this, clickableArea);
}
break; break;
} }
} }
@@ -54,7 +54,7 @@ namespace Barotrauma
private int? maxTextLength; private int? maxTextLength;
private int _caretIndex; private int _caretIndex;
private int CaretIndex public int CaretIndex
{ {
get { return _caretIndex; } get { return _caretIndex; }
set set
@@ -353,34 +353,23 @@ namespace Barotrauma
private void CalculateCaretPos() private void CalculateCaretPos()
{ {
string textDrawn = Censor ? textBlock.CensoredText : textBlock.WrappedText; if (Censor || !Wrap)
if (textDrawn.Contains("\n"))
{ {
string[] lines = textDrawn.Split('\n'); string textDrawn = textBlock.CensoredText;
int totalIndex = 0; CaretIndex = Math.Min(CaretIndex, textDrawn.Length);
for (int i = 0; i < lines.Length; i++) textDrawn = Censor ? textBlock.CensoredText : textBlock.Text;
{ Vector2 textSize = Font.MeasureString(textDrawn[..CaretIndex]) * TextBlock.TextScale;
int currentLineLength = lines[i].Length; caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin * TextBlock.TextScale;
totalIndex += currentLineLength;
// The caret is on this line
if (CaretIndex < totalIndex || totalIndex == textBlock.Text.Length)
{
int diff = totalIndex - CaretIndex;
int index = currentLineLength - diff;
Vector2 lineTextSize = Font.MeasureString(lines[i].Substring(0, index)) * TextBlock.TextScale;
Vector2 lastLineSize = Font.MeasureString(lines[i]) * TextBlock.TextScale;
float totalTextHeight = Font.MeasureString(textDrawn.Substring(0, totalIndex)).Y * TextBlock.TextScale;
caretPos = new Vector2(lineTextSize.X, totalTextHeight - lastLineSize.Y) + textBlock.TextPos - textBlock.Origin * TextBlock.TextScale;
break;
}
}
} }
else else
{ {
CaretIndex = Math.Min(CaretIndex, textDrawn.Length); CaretIndex = Math.Min(CaretIndex, textBlock.Text.Length);
textDrawn = Censor ? textBlock.CensoredText : textBlock.Text; textBlock.Font.WrapText(
Vector2 textSize = Font.MeasureString(textDrawn.Substring(0, CaretIndex)) * TextBlock.TextScale; textBlock.Text,
caretPos = new Vector2(textSize.X, 0) + textBlock.TextPos - textBlock.Origin * TextBlock.TextScale; (textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z) / TextBlock.TextScale,
CaretIndex,
out Vector2 requestedCharPos);
caretPos = requestedCharPos * TextBlock.TextScale + textBlock.TextPos - textBlock.Origin * TextBlock.TextScale;
} }
caretPosDirty = false; caretPosDirty = false;
} }
@@ -393,6 +382,7 @@ namespace Barotrauma
memento.Store(Text); memento.Store(Text);
} }
CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex; CaretIndex = forcedCaretIndex == - 1 ? textBlock.GetCaretIndexFromScreenPos(PlayerInput.MousePosition) : forcedCaretIndex;
CalculateCaretPos();
ClearSelection(); ClearSelection();
selected = true; selected = true;
GUI.KeyboardDispatcher.Subscriber = this; GUI.KeyboardDispatcher.Subscriber = this;
@@ -548,59 +538,37 @@ namespace Barotrauma
if (textBlock.WrappedText.Contains("\n")) if (textBlock.WrappedText.Contains("\n"))
{ {
// Multiline selection // Multiline selection
string[] lines = textBlock.WrappedText.Split('\n'); var characterPositions = textBlock.GetAllCaretPositions();
int totalIndex = 0; (int startIndex, int endIndex) = selectionStartIndex < selectionEndIndex
int previousCharacters = 0; ? (selectionStartIndex, selectionEndIndex)
Vector2 offset = textBlock.TextPos - textBlock.Origin; : (selectionEndIndex, selectionStartIndex);
for (int i = 0; i < lines.Length; i++) endIndex--;
void drawRect(Vector2 topLeft, Vector2 bottomRight)
{ {
string currentLine = lines[i]; int minWidth = GUI.IntScale(5);
int currentLineLength = currentLine.Length; if (bottomRight.X - topLeft.X < minWidth) { bottomRight.X = topLeft.X + minWidth; }
totalIndex += currentLineLength; GUI.DrawRectangle(spriteBatch,
bool containsSelection = IsLeftToRight Rect.Location.ToVector2() + topLeft,
? selectionStartIndex < totalIndex && selectionEndIndex > previousCharacters bottomRight - topLeft,
: selectionEndIndex < totalIndex && selectionStartIndex > previousCharacters; SelectionColor, isFilled: true);
if (containsSelection)
{
Vector2 currentLineSize = Font.MeasureString(currentLine) * TextBlock.TextScale;
if ((IsLeftToRight && selectionStartIndex < previousCharacters && selectionEndIndex > totalIndex)
|| !IsLeftToRight && selectionEndIndex < previousCharacters && selectionStartIndex > totalIndex)
{
// select the whole line
Vector2 topLeft = offset + new Vector2(0, currentLineSize.Y * i);
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, currentLineSize, SelectionColor, isFilled: true);
}
else
{
if (IsLeftToRight)
{
bool selectFromTheBeginning = selectionStartIndex <= previousCharacters;
int startIndex = selectFromTheBeginning ? 0 : Math.Abs(selectionStartIndex - previousCharacters);
int endIndex = Math.Abs(selectionEndIndex - previousCharacters);
int characters = Math.Min(endIndex - startIndex, currentLineLength - startIndex);
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(startIndex, characters)) * TextBlock.TextScale;
Vector2 topLeft = selectFromTheBeginning
? new Vector2(offset.X, offset.Y + currentLineSize.Y * i)
: new Vector2(selectionStartPos.X, offset.Y + currentLineSize.Y * i);
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectedTextSize, SelectionColor, isFilled: true);
}
else
{
bool selectFromTheBeginning = selectionStartIndex >= totalIndex;
bool selectFromTheStart = selectionEndIndex <= previousCharacters;
int startIndex = selectFromTheBeginning ? currentLineLength : Math.Abs(selectionStartIndex - previousCharacters);
int endIndex = selectFromTheStart ? 0 : Math.Abs(selectionEndIndex - previousCharacters);
int characters = Math.Min(Math.Abs(endIndex - startIndex), currentLineLength);
Vector2 selectedTextSize = Font.MeasureString(currentLine.Substring(endIndex, characters)) * TextBlock.TextScale;
Vector2 topLeft = selectFromTheBeginning
? new Vector2(offset.X + currentLineSize.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i)
: new Vector2(selectionStartPos.X - selectedTextSize.X, offset.Y + currentLineSize.Y * i);
GUI.DrawRectangle(spriteBatch, Rect.Location.ToVector2() + topLeft, selectedTextSize, SelectionColor, isFilled: true);
}
}
}
previousCharacters = totalIndex;
} }
Vector2 topLeft = characterPositions[startIndex];
for (int i = startIndex+1; i <= endIndex; i++)
{
Vector2 currPos = characterPositions[i];
if (!MathUtils.NearlyEqual(topLeft.Y, currPos.Y))
{
Vector2 bottomRight = characterPositions[i - 1];
bottomRight += Font.MeasureChar(Text[i - 1]);
drawRect(topLeft, bottomRight);
topLeft = currPos;
}
}
Vector2 finalBottomRight = characterPositions[endIndex];
finalBottomRight += Font.MeasureChar(Text[endIndex]);
drawRect(topLeft, finalBottomRight);
} }
else else
{ {
@@ -738,8 +706,15 @@ namespace Barotrauma
{ {
InitSelectionStart(); InitSelectionStart();
} }
float lineHeight = Font.MeasureString("T").Y * TextBlock.TextScale; float lineHeight = Font.LineHeight * TextBlock.TextScale;
int newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y - lineHeight)); int newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y - lineHeight * 0.5f));
textBlock.Font.WrapText(
textBlock.Text,
(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z) / TextBlock.TextScale,
newIndex,
out Vector2 requestedCharPos);
requestedCharPos *= TextBlock.TextScale;
if (MathUtils.NearlyEqual(requestedCharPos.Y, caretPos.Y)) { newIndex = 0; }
CaretIndex = newIndex; CaretIndex = newIndex;
caretTimer = 0; caretTimer = 0;
HandleSelection(); HandleSelection();
@@ -749,8 +724,15 @@ namespace Barotrauma
{ {
InitSelectionStart(); InitSelectionStart();
} }
lineHeight = Font.MeasureString("T").Y * TextBlock.TextScale; lineHeight = Font.LineHeight * TextBlock.TextScale;
newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y + lineHeight)); newIndex = textBlock.GetCaretIndexFromLocalPos(new Vector2(caretPos.X, caretPos.Y + lineHeight * 1.5f));
textBlock.Font.WrapText(
textBlock.Text,
(textBlock.Rect.Width - textBlock.Padding.X - textBlock.Padding.Z) / TextBlock.TextScale,
newIndex,
out Vector2 requestedCharPos2);
requestedCharPos2 *= TextBlock.TextScale;
if (MathUtils.NearlyEqual(requestedCharPos2.Y, caretPos.Y)) { newIndex = Text.Length; }
CaretIndex = newIndex; CaretIndex = newIndex;
caretTimer = 0; caretTimer = 0;
HandleSelection(); HandleSelection();
@@ -813,6 +795,7 @@ namespace Barotrauma
} }
break; break;
} }
if (caretPosDirty) { CalculateCaretPos(); }
OnKeyHit?.Invoke(this, key); OnKeyHit?.Invoke(this, key);
void HandleSelection() void HandleSelection()
{ {
@@ -1,5 +1,6 @@
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System;
using System.Linq; using System.Linq;
namespace Barotrauma namespace Barotrauma
@@ -37,39 +38,44 @@ namespace Barotrauma
values[0] = newValue; values[0] = newValue;
} }
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float? maxVal, float xOffset, Color color) public delegate void GraphDelegate(SpriteBatch spriteBatch, float value, int order, Vector2 position);
public void Draw(SpriteBatch spriteBatch, Rectangle rect, float? maxValue = null, float xOffset = 0, Color? color = null, GraphDelegate doForEachValue = null)
{ {
color ??= Color.White;
float graphMaxVal = 1.0f; float graphMaxVal = 1.0f;
if (maxVal == null) if (maxValue == null)
{ {
graphMaxVal = LargestValue(); graphMaxVal = LargestValue();
} }
else if (maxVal > 0.0f) else if (maxValue > 0.0f)
{ {
graphMaxVal = (float)maxVal; graphMaxVal = (float)maxValue;
} }
GUI.DrawRectangle(spriteBatch, rect, Color.White); GUI.DrawRectangle(spriteBatch, rect, Color.White);
if (values.Length == 0) return; if (values.Length == 0) { return; }
float lineWidth = (float)rect.Width / (float)(values.Length - 2); float lineWidth = rect.Width / (float)(values.Length - 2);
float yScale = (float)rect.Height / graphMaxVal; float yScale = rect.Height / graphMaxVal;
Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (values[1] + (values[0] - values[1]) * xOffset) * yScale); Vector2 prevPoint = new Vector2(rect.Right, rect.Bottom - (values[1] + (values[0] - values[1]) * xOffset) * yScale);
float currX = rect.Right - ((xOffset - 1.0f) * lineWidth); float currX = rect.Right - ((xOffset - 1.0f) * lineWidth);
for (int i = 1; i < values.Length - 1; i++) for (int i = 1; i < values.Length - 1; i++)
{ {
float value = values[i];
currX -= lineWidth; currX -= lineWidth;
Vector2 newPoint = new Vector2(currX, rect.Bottom - values[i] * yScale); Vector2 newPoint = new Vector2(currX, rect.Bottom - value * yScale);
GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color); GUI.DrawLine(spriteBatch, prevPoint, newPoint - new Vector2(1.0f, 0), color.Value);
prevPoint = newPoint; prevPoint = newPoint;
doForEachValue?.Invoke(spriteBatch, value, i, newPoint);
} }
int lastIndex = values.Length - 1;
Vector2 lastPoint = new Vector2(rect.X, float lastValue = values[lastIndex];
rect.Bottom - (values[values.Length - 1] + (values[values.Length - 2] - values[values.Length - 1]) * xOffset) * yScale); Vector2 lastPoint = new Vector2(rect.X, rect.Bottom - (lastValue + (values[values.Length - 2] - lastValue) * xOffset) * yScale);
GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color.Value);
GUI.DrawLine(spriteBatch, prevPoint, lastPoint, color); doForEachValue?.Invoke(spriteBatch, lastValue, lastIndex, lastPoint);
} }
} }
} }
@@ -379,11 +379,42 @@ namespace Barotrauma
if (currSplashScreen.IsPlaying) if (currSplashScreen.IsPlaying)
{ {
graphics.Clear(Color.Black);
float videoAspectRatio = (float)currSplashScreen.Width / (float)currSplashScreen.Height;
int width; int height;
if (GameMain.GraphicsHeight * videoAspectRatio > GameMain.GraphicsWidth)
{
width = GameMain.GraphicsWidth;
height = (int)(GameMain.GraphicsWidth / videoAspectRatio);
}
else
{
width = (int)(GameMain.GraphicsHeight * videoAspectRatio);
height = GameMain.GraphicsHeight;
}
spriteBatch.Begin(); spriteBatch.Begin();
spriteBatch.Draw(currSplashScreen.GetTexture(), new Rectangle(0, 0, GameMain.GraphicsWidth, GameMain.GraphicsHeight), Color.White); spriteBatch.Draw(
currSplashScreen.GetTexture(),
destinationRectangle: new Rectangle(
GameMain.GraphicsWidth / 2 - width / 2,
GameMain.GraphicsHeight / 2 - height / 2,
width,
height),
sourceRectangle: new Rectangle(0, 0, currSplashScreen.Width, currSplashScreen.Height),
Color.White,
rotation: 0.0f,
origin: Vector2.Zero,
SpriteEffects.None,
layerDepth: 0.0f);
spriteBatch.End(); spriteBatch.End();
if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500) && GameMain.WindowActive && (PlayerInput.KeyHit(Keys.Escape) || PlayerInput.KeyHit(Keys.Space) || PlayerInput.KeyHit(Keys.Enter) || PlayerInput.PrimaryMouseButtonDown())) if (DateTime.Now > videoStartTime + new TimeSpan(0, 0, 0, 0, milliseconds: 500)
&& GameMain.WindowActive
&& (PlayerInput.KeyHit(Keys.Escape)
|| PlayerInput.KeyHit(Keys.Space)
|| PlayerInput.KeyHit(Keys.Enter)
|| PlayerInput.PrimaryMouseButtonDown()))
{ {
currSplashScreen.Dispose(); currSplashScreen = null; currSplashScreen.Dispose(); currSplashScreen = null;
} }
@@ -395,7 +426,7 @@ namespace Barotrauma
} }
bool drawn; bool drawn;
public IEnumerable<object> DoLoading(IEnumerable<object> loader) public IEnumerable<CoroutineStatus> DoLoading(IEnumerable<CoroutineStatus> loader)
{ {
drawn = false; drawn = false;
LoadState = null; LoadState = null;
@@ -732,7 +732,7 @@ namespace Barotrauma
CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration)); CoroutineManager.StartCoroutine(DoScaleAnimation(targetSize, duration));
} }
private IEnumerable<object> DoMoveAnimation(Point targetPos, float duration) private IEnumerable<CoroutineStatus> DoMoveAnimation(Point targetPos, float duration)
{ {
Vector2 startPos = AbsoluteOffset.ToVector2(); Vector2 startPos = AbsoluteOffset.ToVector2();
float t = 0.0f; float t = 0.0f;
@@ -746,7 +746,7 @@ namespace Barotrauma
animTargetPos = null; animTargetPos = null;
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<object> DoScaleAnimation(Point targetSize, float duration) private IEnumerable<CoroutineStatus> DoScaleAnimation(Point targetSize, float duration)
{ {
Vector2 startSize = NonScaledSize.ToVector2(); Vector2 startSize = NonScaledSize.ToVector2();
float t = 0.0f; float t = 0.0f;
@@ -151,9 +151,8 @@ namespace Barotrauma
/// </summary> /// </summary>
public static void DrawPoint(this SpriteBatch spriteBatch, Vector2 position, Color color, float size = 1f) public static void DrawPoint(this SpriteBatch spriteBatch, Vector2 position, Color color, float size = 1f)
{ {
var scale = Vector2.One * size;
var offset = new Vector2(0.5f) - new Vector2(size * 0.5f); var offset = new Vector2(0.5f) - new Vector2(size * 0.5f);
spriteBatch.Draw(GetTexture(spriteBatch), position + offset, null, color, 0.0f, Vector2.Zero, Vector2.One, SpriteEffects.None, 0); spriteBatch.Draw(GetTexture(spriteBatch), position + offset, null, color, 0.0f, Vector2.Zero, new Vector2(size), SpriteEffects.None, 0);
} }
public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 center, float radius, int sides, Color color, public static void DrawCircle(this SpriteBatch spriteBatch, Vector2 center, float radius, int sides, Color color,
@@ -266,7 +266,7 @@ namespace Barotrauma
}; };
if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance) if (balanceAfterTransaction != CurrentLocation.StoreCurrentBalance)
{ {
var newStatus = Location.GetStoreBalanceStatus(balanceAfterTransaction); var newStatus = CurrentLocation.GetStoreBalanceStatus(balanceAfterTransaction);
if (CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier != newStatus.SellPriceModifier) if (CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier != newStatus.SellPriceModifier)
{ {
string tooltipTag = newStatus.SellPriceModifier > CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier ? string tooltipTag = newStatus.SellPriceModifier > CurrentLocation.ActiveStoreBalanceStatus.SellPriceModifier ?
@@ -471,7 +471,7 @@ namespace Barotrauma
} }
// Initial submarine selection needs a slight wait to allow the layoutgroups to place content properly // Initial submarine selection needs a slight wait to allow the layoutgroups to place content properly
private IEnumerable<object> SelectOwnSubmarineWithDelay(SubmarineInfo info, SubmarineDisplayContent display) private IEnumerable<CoroutineStatus> SelectOwnSubmarineWithDelay(SubmarineInfo info, SubmarineDisplayContent display)
{ {
yield return new WaitForSeconds(0.05f); yield return new WaitForSeconds(0.05f);
SelectSubmarine(info, display.background.Rect); SelectSubmarine(info, display.background.Rect);
@@ -562,7 +562,7 @@ namespace Barotrauma
frame.OnSecondaryClicked += (component, data) => frame.OnSecondaryClicked += (component, data) =>
{ {
GameMain.GameSession?.CrewManager?.CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); NetLobbyScreen.CreateModerationContextMenu(client);
return true; return true;
}; };
@@ -917,7 +917,8 @@ namespace Barotrauma
textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea()
{ {
Data = data, Data = data,
OnClick = GameMain.NetLobbyScreen.SelectPlayer OnClick = GameMain.NetLobbyScreen.SelectPlayer,
OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu
}); });
} }
} }
@@ -1486,12 +1487,13 @@ namespace Barotrauma
experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft), experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green) barSize: controlledCharacter.Info.GetProgressTowardsNextLevel(), color: GUI.Style.Green)
{ {
IsHorizontal = true IsHorizontal = true,
}; };
experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight) experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUI.Font, textAlignment: Alignment.CenterRight)
{ {
Shadow = true Shadow = true,
ToolTip = TextManager.Get("experiencetooltip")
}; };
talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true }; talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUI.SubHeadingFont, parseRichText: true, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
@@ -1541,13 +1543,41 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(skillNames); GUITextBlock.AutoScaleAndNormalize(skillNames);
} }
private bool HasUnlockedAllTalents(Character controlledCharacter)
{
if (TalentTree.JobTalentTrees.TryGetValue(controlledCharacter.Info.Job.Prefab.Identifier, out TalentTree talentTree))
{
foreach (TalentSubTree talentSubTree in talentTree.TalentSubTrees)
{
foreach (TalentOption talentOption in talentSubTree.TalentOptionStages)
{
if (talentOption.Talents.None(t => controlledCharacter.HasTalent(t.Identifier)))
{
return false;
}
}
}
}
return true;
}
private void UpdateTalentButtons() private void UpdateTalentButtons()
{ {
Character controlledCharacter = Character.Controlled; Character controlledCharacter = Character.Controlled;
if (controlledCharacter?.Info == null) { return; }
experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; bool unlockedAllTalents = HasUnlockedAllTalents(controlledCharacter);
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
//experienceBar.ToolTip = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}"; if (unlockedAllTalents)
{
experienceText.Text = string.Empty;
experienceBar.BarSize = 1f;
}
else
{
experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
}
selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents); selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
@@ -1555,7 +1585,11 @@ namespace Barotrauma
int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count(); int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
if (talentCount > 0) if (unlockedAllTalents)
{
talentPointText.SetRichText($"‖color:{XMLExtensions.ToStringHex(Color.Gray)}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
}
else if (talentCount > 0)
{ {
string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUI.Style.Red)}‖{-talentCount}‖color:end‖"; string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUI.Style.Red)}‖{-talentCount}‖color:end‖";
string localizedString = TextManager.GetWithVariables("talentmenu.points.spending", new []{ "[amount]", "[used]" }, new []{ pointsLeft, pointsUsed}); string localizedString = TextManager.GetWithVariables("talentmenu.points.spending", new []{ "[amount]", "[used]" }, new []{ pointsLeft, pointsUsed});
@@ -1611,6 +1645,7 @@ namespace Barotrauma
GameMain.Client.CreateEntityEvent(controlledCharacter, new object[] { NetEntityEvent.Type.UpdateTalents }); GameMain.Client.CreateEntityEvent(controlledCharacter, new object[] { NetEntityEvent.Type.UpdateTalents });
} }
} }
selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
UpdateTalentButtons(); UpdateTalentButtons();
} }
@@ -846,23 +846,27 @@ namespace Barotrauma
var currentOrPending = item.PendingItemSwap ?? item.Prefab; var currentOrPending = item.PendingItemSwap ?? item.Prefab;
string name = currentOrPending.Name; string name = currentOrPending.Name;
string quantityText = ""; string nameWithQuantity = "";
if (linkedItems.Count > 1) if (linkedItems.Count > 1)
{ {
foreach (ItemPrefab distinctItem in linkedItems.Select(it => it.Prefab).Distinct()) foreach (ItemPrefab distinctItem in linkedItems.Select(it => it.Prefab).Distinct())
{ {
if (quantityText != string.Empty) if (nameWithQuantity != string.Empty)
{ {
quantityText += ", "; nameWithQuantity += ", ";
} }
int count = linkedItems.Count(it => it.Prefab == distinctItem); int count = linkedItems.Count(it => it.Prefab == distinctItem);
quantityText += distinctItem.Name; nameWithQuantity += distinctItem.Name;
if (count > 1) if (count > 1)
{ {
quantityText += " " + TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString()); nameWithQuantity += " " + TextManager.GetWithVariable("campaignstore.quantity", "[amount]", count.ToString());
} }
} }
} }
else
{
nameWithQuantity = name;
}
bool isOpen = false; bool isOpen = false;
GUIButton toggleButton = new GUIButton(rectT(1f, 0.1f, parent.Content), text: string.Empty, style: "SlideDown") GUIButton toggleButton = new GUIButton(rectT(1f, 0.1f, parent.Content), text: string.Empty, style: "SlideDown")
@@ -884,7 +888,7 @@ namespace Barotrauma
new GUITextBlock(rectT(0.3f, 1f, buttonLayout), text: slotText, font: GUI.SubHeadingFont); new GUITextBlock(rectT(0.3f, 1f, buttonLayout), text: slotText, font: GUI.SubHeadingFont);
GUILayoutGroup group = new GUILayoutGroup(rectT(0.7f, 1f, buttonLayout), isHorizontal: true) { Stretch = true }; GUILayoutGroup group = new GUILayoutGroup(rectT(0.7f, 1f, buttonLayout), isHorizontal: true) { Stretch = true };
string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : quantityText; string title = item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : nameWithQuantity;
GUITextBlock text = new GUITextBlock(rectT(0.7f, 1f, group), text: title, font: GUI.SubHeadingFont, textAlignment: Alignment.Right, parseRichText: true) GUITextBlock text = new GUITextBlock(rectT(0.7f, 1f, group), text: title, font: GUI.SubHeadingFont, textAlignment: Alignment.Right, parseRichText: true)
{ {
TextColor = GUI.Style.Orange TextColor = GUI.Style.Orange
@@ -907,7 +911,7 @@ namespace Barotrauma
if (isUninstallPending) { canUninstall = false; } if (isUninstallPending) { canUninstall = false; }
frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite, frames.Add(CreateUpgradeEntry(rectT(1f, 0.25f, parent.Content), currentOrPending.UpgradePreviewSprite,
item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", quantityText), item.PendingItemSwap != null ? TextManager.GetWithVariable("upgrades.pendingitem", "[itemname]", name) : TextManager.GetWithVariable("upgrades.installeditem", "[itemname]", nameWithQuantity),
currentOrPending.Description, currentOrPending.Description,
0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton")); 0, null, addBuyButton: canUninstall, addProgressBar: false, buttonStyle: "WeaponUninstallButton"));
@@ -0,0 +1,114 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Barotrauma.IO;
using Microsoft.Xna.Framework;
namespace Barotrauma
{
public static partial class GameAnalyticsManager
{
static partial void CreateConsentPrompt()
{
if (consentTextAvailable)
{
var background = new GUIFrame(new RectTransform(Vector2.One, GUI.Canvas), style: "GUIBackgroundBlocker");
var frame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.7f), background.RectTransform, Anchor.Center) { MaxSize = new Point(800, int.MaxValue) });
var content = new GUILayoutGroup(new RectTransform(new Vector2(0.95f), frame.RectTransform, Anchor.Center))
{
Stretch = true,
AbsoluteSpacing = GUI.IntScale(15)
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsentheader"), font: GUI.SubHeadingFont, textColor: Color.White);
var mainText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsenttext"), wrap: true, parseRichText: true);
foreach (var data in mainText.RichTextData)
{
mainText.ClickableAreas.Add(new GUITextBlock.ClickableArea()
{
Data = data,
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) =>
{
GameMain.Instance.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
}
});
}
string privacyPolicyText = File.ReadAllText("daedalic_privacypolicy.txt");
var privacyPolicyBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), content.RectTransform) { MaxSize = new Point(int.MaxValue, GUI.IntScale(200)) });
var privacyPolicy = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), privacyPolicyBox.Content.RectTransform), privacyPolicyText, wrap: true)
{
CanBeFocused = false
};
privacyPolicy.RectTransform.MinSize = new Point(0, (int)privacyPolicy.TextSize.Y);
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), TextManager.Get("statisticsconsentstatement"), wrap: true);
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.0f), content.RectTransform), isHorizontal: true);
void buttonContainerSpacing(float width)
=> new GUIFrame(new RectTransform(new Vector2(width, 1.0f), buttonContainer.RectTransform), style: null);
buttonContainerSpacing(0.1f);
var yesBtn = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("Yes"));
yesBtn.OnClicked += (btn, userdata) =>
{
GUIMessageBox.MessageBoxes.Remove(background);
SetConsentInternal(Consent.Yes);
return true;
};
yesBtn.Enabled = false;
IEnumerable<CoroutineStatus> enableAfterTime(WaitForSeconds time, params GUIComponent[] components)
{
yield return time;
foreach (var c in components)
{
c.Enabled = true;
}
yield return CoroutineStatus.Success;
}
buttonContainerSpacing(0.2f);
var noBtn = new GUIButton(new RectTransform(new Vector2(0.3f, 1.0f), buttonContainer.RectTransform), TextManager.Get("No"));
noBtn.OnClicked += (btn, userdata) =>
{
GUIMessageBox.MessageBoxes.Remove(background);
SetConsent(Consent.No);
return true;
};
noBtn.Enabled = false;
CoroutineManager.StartCoroutine(enableAfterTime(new WaitForSeconds(0.3f), yesBtn, noBtn));
buttonContainerSpacing(0.1f);
buttonContainer.RectTransform.MinSize = new Point(0, yesBtn.RectTransform.MinSize.Y);
buttonContainer.RectTransform.MaxSize = new Point(int.MaxValue, yesBtn.RectTransform.MinSize.Y);
foreach (var child in content.Children)
{
if (child is GUITextBlock textBlock)
{
textBlock.RectTransform.MinSize = new Point(0, (int)textBlock.TextSize.Y);
textBlock.RectTransform.MaxSize = new Point(int.MaxValue, (int)textBlock.TextSize.Y + GUI.IntScale(15));
}
}
frame.RectTransform.MaxSize = new Point(
frame.RectTransform.MaxSize.X,
(int)(content.Children.Sum(c => c.RectTransform.MaxSize.Y + content.AbsoluteSpacing) / content.RectTransform.RelativeSize.Y));
GUIMessageBox.MessageBoxes.Add(background);
}
else
{
//user statistics disabled by default if the prompt cannot be shown in the user's language
SetConsent(Consent.Unknown);
}
}
}
}
@@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using GameAnalyticsSDK.Net;
using Barotrauma.IO; using Barotrauma.IO;
using System.Threading; using System.Threading;
using Barotrauma.Tutorials; using Barotrauma.Tutorials;
@@ -388,51 +387,6 @@ namespace Barotrauma
loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "Load", canLoadInSeparateThread); loadingCoroutine = CoroutineManager.StartCoroutine(Load(canLoadInSeparateThread), "Load", canLoadInSeparateThread);
} }
private void InitUserStats()
{
return;
if (GameSettings.ShowUserStatisticsPrompt)
{
if (TextManager.ContainsTag("statisticspromptheader") && TextManager.ContainsTag("statisticsprompttext"))
{
var userStatsPrompt = new GUIMessageBox(
TextManager.Get("statisticspromptheader"),
TextManager.Get("statisticsprompttext"),
new string[] { TextManager.Get("Yes"), TextManager.Get("No") });
userStatsPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
GameSettings.ShowUserStatisticsPrompt = false;
GameSettings.SendUserStatistics = true;
GameAnalyticsManager.Init();
Config.SaveNewPlayerConfig();
return true;
};
userStatsPrompt.Buttons[0].OnClicked += userStatsPrompt.Close;
userStatsPrompt.Buttons[1].OnClicked += (btn, userdata) =>
{
GameSettings.ShowUserStatisticsPrompt = false;
GameSettings.SendUserStatistics = false;
Config.SaveNewPlayerConfig();
return true;
};
userStatsPrompt.Buttons[1].OnClicked += userStatsPrompt.Close;
}
else
{
//user statistics enabled by default if the prompt cannot be shown in the user's language
GameSettings.ShowUserStatisticsPrompt = false;
GameSettings.SendUserStatistics = true;
GameAnalyticsManager.Init();
Config.SaveNewPlayerConfig();
}
}
else if (GameSettings.SendUserStatistics)
{
GameAnalyticsManager.Init();
}
}
public class LoadingException : Exception public class LoadingException : Exception
{ {
public LoadingException(Exception e) : base("Loading was interrupted due to an error.", innerException: e) public LoadingException(Exception e) : base("Loading was interrupted due to an error.", innerException: e)
@@ -440,7 +394,7 @@ namespace Barotrauma
} }
} }
private IEnumerable<object> Load(bool isSeparateThread) private IEnumerable<CoroutineStatus> Load(bool isSeparateThread)
{ {
if (GameSettings.VerboseLogging) if (GameSettings.VerboseLogging)
{ {
@@ -526,21 +480,17 @@ namespace Barotrauma
DebugConsole.Log("Selected content packages: " + string.Join(", ", Config.AllEnabledPackages.Select(cp => cp.Name))); DebugConsole.Log("Selected content packages: " + string.Join(", ", Config.AllEnabledPackages.Select(cp => cp.Name)));
} }
#if DEBUG #if !DEBUG
GameSettings.ShowUserStatisticsPrompt = false; GameAnalyticsManager.InitIfConsented();
GameSettings.SendUserStatistics = false;
#endif #endif
InitUserStats(); yield return CoroutineStatus.Running;
yield return CoroutineStatus.Running;
Debug.WriteLine("sounds"); Debug.WriteLine("sounds");
int i = 0; int i = 0;
foreach (object crObj in SoundPlayer.Init()) foreach (CoroutineStatus status in SoundPlayer.Init())
{ {
CoroutineStatus status = (CoroutineStatus)crObj;
if (status == CoroutineStatus.Success) break; if (status == CoroutineStatus.Success) break;
i++; i++;
@@ -846,6 +796,8 @@ namespace Barotrauma
} }
#endif #endif
NetworkMember?.Update((float)Timing.Step);
if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine)) if (!hasLoaded && !CoroutineManager.IsCoroutineRunning(loadingCoroutine))
{ {
throw new LoadingException(loadingCoroutine.Exception); throw new LoadingException(loadingCoroutine.Exception);
@@ -920,6 +872,11 @@ namespace Barotrauma
{ {
gameSession.ToggleTabMenu(); gameSession.ToggleTabMenu();
} }
else if (GUIMessageBox.VisibleBox as GUIMessageBox != null &&
GUIMessageBox.VisibleBox.UserData as string == "bugreporter")
{
((GUIMessageBox)GUIMessageBox.VisibleBox).Close();
}
else if (GUI.PauseMenuOpen) else if (GUI.PauseMenuOpen)
{ {
GUI.TogglePauseMenu(); GUI.TogglePauseMenu();
@@ -1131,6 +1088,10 @@ namespace Barotrauma
if (GameSession != null) if (GameSession != null)
{ {
double roundDuration = Timing.TotalTime - GameSession.RoundStartTime;
GameAnalyticsManager.AddProgressionEvent(GameAnalyticsManager.ProgressionStatus.Fail,
GameSession.GameMode?.Name ?? "none",
roundDuration);
if (Tutorial.Initialized) if (Tutorial.Initialized)
{ {
((TutorialMode)GameSession.GameMode).Tutorial?.Stop(); ((TutorialMode)GameSession.GameMode).Tutorial?.Stop();
@@ -1139,6 +1100,7 @@ namespace Barotrauma
GUIMessageBox.CloseAll(); GUIMessageBox.CloseAll();
MainMenuScreen.Select(); MainMenuScreen.Select();
GameSession = null; GameSession = null;
} }
public void ShowCampaignDisclaimer(Action onContinue = null) public void ShowCampaignDisclaimer(Action onContinue = null)
@@ -1238,7 +1200,7 @@ namespace Barotrauma
} }
static bool waitForKeyHit = true; static bool waitForKeyHit = true;
public CoroutineHandle ShowLoading(IEnumerable<object> loader, bool waitKeyHit = true) public CoroutineHandle ShowLoading(IEnumerable<CoroutineStatus> loader, bool waitKeyHit = true)
{ {
waitForKeyHit = waitKeyHit; waitForKeyHit = waitKeyHit;
loadingScreenOpen = true; loadingScreenOpen = true;
@@ -1262,8 +1224,8 @@ namespace Barotrauma
DebugConsole.ThrowError("Error while cleaning unnecessary save files", e); DebugConsole.ThrowError("Error while cleaning unnecessary save files", e);
} }
if (GameSettings.SendUserStatistics) { GameAnalytics.OnQuit(); } if (GameAnalyticsManager.SendUserStatistics) { GameAnalyticsManager.ShutDown(); }
if (GameSettings.SaveDebugConsoleLogs) { DebugConsole.SaveLogs(); } if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); }
base.OnExiting(sender, args); base.OnExiting(sender, args);
} }
@@ -40,12 +40,13 @@ namespace Barotrauma
private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>(); private List<SoldEntity> SoldEntities { get; } = new List<SoldEntity>();
// The bag slot is intentionally left out since we want to be able to sell items from there
private readonly List<InvSlotType> equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
public IEnumerable<Item> GetSellableItems(Character character) public IEnumerable<Item> GetSellableItems(Character character)
{ {
if (character == null) { return new List<Item>(); } if (character == null) { return new List<Item>(); }
var confirmedSoldEntities = GetConfirmedSoldEntities(); var confirmedSoldEntities = GetConfirmedSoldEntities();
// The bag slot is intentionally left out since we want to be able to sell items from there
var equipmentSlots = new List<InvSlotType>() { InvSlotType.Head, InvSlotType.InnerClothes, InvSlotType.OuterClothes, InvSlotType.Headset, InvSlotType.Card };
return character.Inventory.FindAllItems(item => return character.Inventory.FindAllItems(item =>
{ {
if (!IsItemSellable(item, confirmedSoldEntities)) { return false; } if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
@@ -73,6 +74,7 @@ namespace Barotrauma
return Submarine.MainSub.GetItems(true).FindAll(item => return Submarine.MainSub.GetItems(true).FindAll(item =>
{ {
if (!IsItemSellable(item, confirmedSoldEntities)) { return false; } if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
if (item.GetRootInventoryOwner() is Character) { return false; }
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; } if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; } if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
if (!ItemAndAllContainersInteractable(item)) { return false; } if (!ItemAndAllContainersInteractable(item)) { return false; }
@@ -101,7 +103,7 @@ namespace Barotrauma
private bool IsItemSellable(Item item, IEnumerable<SoldEntity> confirmedSoldEntities) private bool IsItemSellable(Item item, IEnumerable<SoldEntity> confirmedSoldEntities)
{ {
if (!item.Prefab.CanBeSold) { return false; } if (!item.Prefab.CanBeSold) { return false; }
if (item.SpawnedInOutpost) { return false; } if (item.SpawnedInCurrentOutpost) { return false; }
if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; } if (!item.Prefab.AllowSellingWhenBroken && item.ConditionPercentage < 90.0f) { return false; }
if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; } if (confirmedSoldEntities.Any(it => it.Item == item)) { return false; }
if (item.OwnInventory?.Container is ItemContainer itemContainer) if (item.OwnInventory?.Container is ItemContainer itemContainer)
@@ -100,7 +100,7 @@ namespace Barotrauma
{ {
AutoHideScrollBar = false, AutoHideScrollBar = false,
CanBeFocused = false, CanBeFocused = false,
CanDragElements = true, CurrentDragMode = GUIListBox.DragMode.DragWithinBox,
CanInteractWhenUnfocusable = true, CanInteractWhenUnfocusable = true,
OnSelected = (component, userData) => false, OnSelected = (component, userData) => false,
SelectMultiple = false, SelectMultiple = false,
@@ -330,7 +330,7 @@ namespace Barotrauma
if (data == null) { return false; } if (data == null) { return false; }
if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client) if (GameMain.NetworkMember?.ConnectedClients?.Find(c => c.Character == data) is Client client)
{ {
CreateModerationContextMenu(PlayerInput.MousePosition.ToPoint(), client); NetLobbyScreen.CreateModerationContextMenu(client);
return true; return true;
} }
return false; return false;
@@ -359,34 +359,41 @@ namespace Barotrauma
CanBeFocused = false CanBeFocused = false
}; };
var jobIconBackground = new GUIImage( // Hide the icon to make more space for the name if the crew list's width is small enough
bool isJobIconVisible = crewListEntrySize.X >= 220;
if (isJobIconVisible)
{
var jobIconBackground = new GUIImage(
new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform), new RectTransform(new Vector2(0.8f * iconRelativeWidth, 0.8f), layoutGroup.RectTransform),
jobIndicatorBackground, jobIndicatorBackground,
scaleToFit: true) scaleToFit: true)
{
CanBeFocused = false,
UserData = "job"
};
if (character?.Info?.Job.Prefab?.Icon != null)
{
new GUIImage(
new RectTransform(Vector2.One, jobIconBackground.RectTransform),
character.Info.Job.Prefab.Icon,
scaleToFit: true)
{ {
CanBeFocused = false, CanBeFocused = false,
Color = character.Info.Job.Prefab.UIColor, UserData = "job"
HoverColor = character.Info.Job.Prefab.UIColor,
PressedColor = character.Info.Job.Prefab.UIColor,
SelectedColor = character.Info.Job.Prefab.UIColor
}; };
if (character?.Info?.Job.Prefab?.Icon != null)
{
new GUIImage(
new RectTransform(Vector2.One, jobIconBackground.RectTransform),
character.Info.Job.Prefab.Icon,
scaleToFit: true)
{
CanBeFocused = false,
Color = character.Info.Job.Prefab.UIColor,
HoverColor = character.Info.Job.Prefab.UIColor,
PressedColor = character.Info.Job.Prefab.UIColor,
SelectedColor = character.Info.Job.Prefab.UIColor
};
}
} }
int iconsVisible = isJobIconVisible ? 5 : 4;
var nameRelativeWidth = 1.0f var nameRelativeWidth = 1.0f
// Start padding // Start padding
- paddingRelativeWidth - paddingRelativeWidth
// 5 icons (job, 3 orders, sound) // icons (job, active orders, current task / voip)
- (5 * 0.8f * iconRelativeWidth) - (iconsVisible * 0.8f * iconRelativeWidth)
// Vertical line // Vertical line
- (0.1f * iconRelativeWidth) - (0.1f * iconRelativeWidth)
// Spacing // Spacing
@@ -425,7 +432,7 @@ namespace Barotrauma
var currentOrderList = new GUIListBox(new RectTransform(new Vector2(0.0f, 1.0f), parent: orderGroup.RectTransform), isHorizontal: true, style: null) var currentOrderList = new GUIListBox(new RectTransform(new Vector2(0.0f, 1.0f), parent: orderGroup.RectTransform), isHorizontal: true, style: null)
{ {
AllowMouseWheelScroll = false, AllowMouseWheelScroll = false,
CanDragElements = true, CurrentDragMode = GUIListBox.DragMode.DragWithinBox,
HideChildrenOutsideFrame = false, HideChildrenOutsideFrame = false,
KeepSpaceForScrollBar = false, KeepSpaceForScrollBar = false,
OnRearranged = OnOrdersRearranged, OnRearranged = OnOrdersRearranged,
@@ -439,7 +446,9 @@ namespace Barotrauma
if (component is GUIListBox list) if (component is GUIListBox list)
{ {
list.CanBeFocused = CanIssueOrders; list.CanBeFocused = CanIssueOrders;
list.CanDragElements = CanIssueOrders && list.Content.CountChildren > 1; list.CurrentDragMode = CanIssueOrders && list.Content.CountChildren > 1
? GUIListBox.DragMode.DragWithinBox
: GUIListBox.DragMode.NoDragging;
} }
}; };
@@ -507,8 +516,11 @@ namespace Barotrauma
{ {
if (!(characterComponent?.UserData is Character character)) { return; } if (!(characterComponent?.UserData is Character character)) { return; }
if (character.Info?.Job?.Prefab == null) { return; } if (character.Info?.Job?.Prefab == null) { return; }
string tooltip = TextManager.GetWithVariables("crewlistelementtooltip",
new string[] { "[name]", "[job]" },
new string[] { character.Name, character.Info.Job.Name });
string color = XMLExtensions.ColorToString(character.Info.Job.Prefab.UIColor); string color = XMLExtensions.ColorToString(character.Info.Job.Prefab.UIColor);
string tooltip = $"‖color:{color}‖{character.Name} ({character.Info.Job.Name})‖color:end‖"; tooltip = $"‖color:{color}‖{tooltip}‖color:end‖";
var richTextData = RichTextData.GetRichTextData(tooltip, out string sanitizedTooltip); var richTextData = RichTextData.GetRichTextData(tooltip, out string sanitizedTooltip);
characterComponent.ToolTip = sanitizedTooltip; characterComponent.ToolTip = sanitizedTooltip;
characterComponent.TooltipRichTextData = richTextData; characterComponent.TooltipRichTextData = richTextData;
@@ -546,7 +558,7 @@ namespace Barotrauma
RemoveCharacter(killedCharacter); RemoveCharacter(killedCharacter);
} }
private IEnumerable<object> KillCharacterAnim(GUIComponent component) private IEnumerable<CoroutineStatus> KillCharacterAnim(GUIComponent component)
{ {
List<GUIComponent> components = component.GetAllChildren().ToList(); List<GUIComponent> components = component.GetAllChildren().ToList();
components.Add(component); components.Add(component);
@@ -777,7 +789,7 @@ namespace Barotrauma
} }
else if (orderGiver != null) else if (orderGiver != null)
{ {
OrderChatMessage msg = new OrderChatMessage(order, option, priority, order?.TargetSpatialEntity ?? order?.TargetItemComponent?.Item as ISpatialEntity, character, orderGiver); OrderChatMessage msg = new OrderChatMessage(order, option, priority, order?.TargetSpatialEntity ?? order?.TargetItemComponent?.Item, character, orderGiver);
GameMain.Client?.SendChatMessage(msg); GameMain.Client?.SendChatMessage(msg);
} }
} }
@@ -956,7 +968,8 @@ namespace Barotrauma
{ {
if (!CanIssueOrders) { return false; } if (!CanIssueOrders) { return false; }
var orderInfo = (OrderInfo)userData; var orderInfo = (OrderInfo)userData;
SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, CharacterInfo.HighestManualOrderPriority, Character.Controlled); int priority = GetManualOrderPriority(character, orderInfo.Order);
SetCharacterOrder(character, orderInfo.Order, orderInfo.OrderOption, priority, Character.Controlled);
return true; return true;
}, },
OnSecondaryClicked = (button, userData) => OnSecondaryClicked = (button, userData) =>
@@ -1141,105 +1154,6 @@ namespace Barotrauma
} }
} }
#region Context Menu
public void CreateModerationContextMenu(Point mousePos, Client client)
{
if (GUIContextMenu.CurrentContextMenu != null) { return; }
if (IsSinglePlayer || client == null || ((!GameMain.Client?.PreviouslyConnectedClients?.Contains(client)) ?? true)) { return; }
bool hasSteam = client.SteamID > 0 && SteamManager.IsInitialized,
canKick = GameMain.Client.HasPermission(ClientPermissions.Kick),
canBan = GameMain.Client.HasPermission(ClientPermissions.Ban) && client.AllowKicking,
canPromo = GameMain.Client.HasPermission(ClientPermissions.ManagePermissions);
// Disable options if we are targeting ourselves
if (client.ID == GameMain.Client?.ID)
{
canKick = canBan = canPromo = false;
}
List<ContextMenuOption> options = new List<ContextMenuOption>();
options.Add(new ContextMenuOption("ViewSteamProfile", isEnabled: hasSteam, onSelected: delegate
{
Steamworks.SteamFriends.OpenWebOverlay($"https://steamcommunity.com/profiles/{client.SteamID}");
}));
options.Add(new ContextMenuOption("ModerationMenu.UserDetails", isEnabled: true, onSelected: delegate
{
GameMain.NetLobbyScreen?.SelectPlayer(client);
}));
// Creates sub context menu options for all the ranks
List<ContextMenuOption> permissionOptions = new List<ContextMenuOption>();
foreach (PermissionPreset rank in PermissionPreset.List)
{
permissionOptions.Add(new ContextMenuOption(rank.Name, isEnabled: true, onSelected: () =>
{
string label = TextManager.GetWithVariables(rank.Permissions == ClientPermissions.None ? "clearrankprompt" : "giverankprompt", new []{ "[user]", "[rank]" }, new []{ client.Name, rank.Name });
GUIMessageBox msgBox = new GUIMessageBox(string.Empty, label, new[] { TextManager.Get("Yes"), TextManager.Get("Cancel") });
msgBox.Buttons[0].OnClicked = delegate
{
client.SetPermissions(rank.Permissions, rank.PermittedCommands);
GameMain.Client.UpdateClientPermissions(client);
msgBox.Close();
return true;
};
msgBox.Buttons[1].OnClicked = delegate
{
msgBox.Close();
return true;
};
}) { Tooltip = rank.Description });
}
options.Add(new ContextMenuOption("Permissions", isEnabled: canPromo, options: permissionOptions.ToArray()));
Color clientColor = client.Character?.Info?.Job.Prefab.UIColor ?? Color.White;
if (GameMain.Client.ConnectedClients.Contains(client))
{
options.Add(new ContextMenuOption(client.MutedLocally ? "Unmute" : "Mute", isEnabled: client.ID != GameMain.Client?.ID, onSelected: delegate
{
client.MutedLocally = !client.MutedLocally;
}));
bool kickEnabled = client.ID != GameMain.Client?.ID && client.AllowKicking;
// if the user can kick create a kick option else create the votekick option
ContextMenuOption kickOption;
if (canKick)
{
kickOption = new ContextMenuOption("Kick", isEnabled: kickEnabled, onSelected: delegate
{
GameMain.Client?.CreateKickReasonPrompt(client.Name, false);
});
}
else
{
kickOption = new ContextMenuOption("VoteToKick", isEnabled: kickEnabled, onSelected: delegate
{
GameMain.Client?.VoteForKick(client);
});
}
options.Add(kickOption);
}
options.Add(new ContextMenuOption("Ban", isEnabled: canBan, onSelected: delegate
{
GameMain.Client?.CreateKickReasonPrompt(client.Name, true);
}));
GUIContextMenu.CreateContextMenu(null, client.Name, headerColor: clientColor, options.ToArray());
}
#endregion
public void AddToGUIUpdateList() public void AddToGUIUpdateList()
{ {
if (GUI.DisableHUD) { return; } if (GUI.DisableHUD) { return; }
@@ -1648,7 +1562,7 @@ namespace Barotrauma
} }
if (characterComponent.Visible) if (characterComponent.Visible)
{ {
if (character == Character.Controlled && characterComponent.State != GUIComponent.ComponentState.Selected) if (character == Character.Controlled && crewList.SelectedComponent != characterComponent)
{ {
crewList.Select(character, force: true); crewList.Select(character, force: true);
} }
@@ -1890,7 +1804,7 @@ namespace Barotrauma
private Hull hullContext; private Hull hullContext;
private WallSection wallContext; private WallSection wallContext;
private bool isContextual; private bool isContextual;
private readonly List<Order> contextualOrders = new List<Order>(); private readonly List<OrderInfo> contextualOrders = new List<OrderInfo>();
private Point shorcutCenterNodeOffset; private Point shorcutCenterNodeOffset;
private const int maxShortcutNodeCount = 4; private const int maxShortcutNodeCount = 4;
@@ -2593,7 +2507,7 @@ namespace Barotrauma
{ {
order = orders[i]; order = orders[i];
disableNode = !CanCharacterBeHeard() || disableNode = !CanCharacterBeHeard() ||
(order.MustSetTarget && (order.ItemComponentType != null || order.TargetItems.Length > 0) && (order.MustSetTarget && (order.ItemComponentType != null || order.GetTargetItems().Any() || order.RequireItems.Any()) &&
order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None()); order.GetMatchingItems(true, interactableFor: characterContext ?? Character.Controlled).None());
optionNodes.Add(new Tuple<GUIComponent, Keys>( optionNodes.Add(new Tuple<GUIComponent, Keys>(
CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, (i + 1) % 10, disableNode: disableNode, checkIfOrderCanBeHeard: false), CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), order, (i + 1) % 10, disableNode: disableNode, checkIfOrderCanBeHeard: false),
@@ -2609,7 +2523,6 @@ namespace Barotrauma
if (contextualOrders.None()) if (contextualOrders.None())
{ {
string orderIdentifier; string orderIdentifier;
// Check if targeting an item or a hull // Check if targeting an item or a hull
if (itemContext != null && itemContext.IsPlayerTeamInteractable) if (itemContext != null && itemContext.IsPlayerTeamInteractable)
{ {
@@ -2618,84 +2531,89 @@ namespace Barotrauma
{ {
targetComponent = null; targetComponent = null;
if (p.UseController && itemContext.Components.None(c => c is Controller)) { continue; } if (p.UseController && itemContext.Components.None(c => c is Controller)) { continue; }
if ((p.TargetItems.Length > 0 && (p.TargetItems.Contains(itemContext.Prefab.Identifier) || itemContext.HasTag(p.TargetItems))) || if (p.HasOptionSpecificTargetItems)
p.TryGetTargetItemComponent(itemContext, out targetComponent))
{ {
contextualOrders.Add(p.HasOptions ? p : new Order(p, itemContext, targetComponent, Character.Controlled)); foreach (string option in p.Options)
{
if (p.TargetItemsMatchItem(itemContext, option))
{
contextualOrders.Add(new OrderInfo(new Order(p, itemContext, targetComponent, Character.Controlled), option));
}
}
}
else if (p.TargetItemsMatchItem(itemContext) || p.TryGetTargetItemComponent(itemContext, out targetComponent))
{
contextualOrders.Add(new OrderInfo(p.HasOptions ? p : new Order(p, itemContext, targetComponent, Character.Controlled), null));
} }
} }
// If targeting a periscope connected to a turret, show the 'operateweapons' order // If targeting a periscope connected to a turret, show the 'operateweapons' order
orderIdentifier = "operateweapons"; orderIdentifier = "operateweapons";
var operateWeaponsPrefab = Order.GetPrefab(orderIdentifier); var operateWeaponsPrefab = Order.GetPrefab(orderIdentifier);
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller)) if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)) && itemContext.Components.Any(c => c is Controller))
{ {
var turret = itemContext.GetConnectedComponents<Turret>().FirstOrDefault(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)) ?? var turret = itemContext.GetConnectedComponents<Turret>().FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) ??
itemContext.GetConnectedComponents<Turret>(recursive: true).FirstOrDefault(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)); itemContext.GetConnectedComponents<Turret>(recursive: true).FirstOrDefault(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item));
if (turret != null) { contextualOrders.Add(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled)); } if (turret != null)
{
contextualOrders.Add(new OrderInfo(new Order(operateWeaponsPrefab, turret.Item, turret, Character.Controlled), null));
}
} }
// If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order // If targeting a repairable item with condition below the repair threshold, show the 'repairsystems' order
orderIdentifier = "repairsystems"; orderIdentifier = "repairsystems";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && itemContext.Repairables.Any(r => itemContext.ConditionPercentage < r.RepairThreshold)) if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)) && itemContext.Repairables.Any(r => r.IsBelowRepairThreshold))
{ {
if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical")))) if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("electrical"))))
{ {
contextualOrders.Add(new Order(Order.GetPrefab("repairelectrical"), itemContext, targetItem: null, Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab("repairelectrical"), itemContext, targetItem: null, Character.Controlled), null));
} }
else if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical")))) else if (itemContext.Repairables.Any(r => r != null && r.requiredSkills.Any(s => s != null && s.Identifier.Equals("mechanical"))))
{ {
contextualOrders.Add(new Order(Order.GetPrefab("repairmechanical"), itemContext, targetItem: null, Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab("repairmechanical"), itemContext, targetItem: null, Character.Controlled), null));
} }
else else
{ {
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled), null));
} }
} }
// Remove the 'pumpwater' order if the target pump is auto-controlled (as it will immediately overwrite the work done by the bot) // Remove the 'pumpwater' order if the target pump is auto-controlled (as it will immediately overwrite the work done by the bot)
orderIdentifier = "pumpwater"; orderIdentifier = "pumpwater";
if (contextualOrders.FirstOrDefault(o => o.Identifier.Equals(orderIdentifier)) is Order o && if (contextualOrders.FirstOrDefault(info => info.Order.Identifier.Equals(orderIdentifier)) is OrderInfo pumpOrderInfo && pumpOrderInfo.Order is Order pumpOrder &&
itemContext.Components.FirstOrDefault(c => c.GetType() == o.ItemComponentType) is Pump pump) itemContext.Components.FirstOrDefault(c => c.GetType() == pumpOrder.ItemComponentType) is Pump pump && pump.IsAutoControlled)
{ {
if (pump.IsAutoControlled) { contextualOrders.Remove(o); } contextualOrders.Remove(pumpOrderInfo);
} }
if (contextualOrders.None()) if (contextualOrders.None())
{ {
orderIdentifier = "cleanupitems"; orderIdentifier = "cleanupitems";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)))
{ {
if (AIObjectiveCleanupItems.IsValidTarget(itemContext, Character.Controlled, checkInventory: false) || AIObjectiveCleanupItems.IsValidContainer(itemContext, Character.Controlled)) if (AIObjectiveCleanupItems.IsValidTarget(itemContext, Character.Controlled, checkInventory: false) || AIObjectiveCleanupItems.IsValidContainer(itemContext, Character.Controlled))
{ {
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), itemContext, targetItem: null, Character.Controlled), null));
} }
} }
} }
AddIgnoreOrder(itemContext); AddIgnoreOrder(itemContext);
} }
else if (hullContext != null) else if (hullContext != null)
{ {
contextualOrders.Add(new Order(Order.GetPrefab("fixleaks"), hullContext, targetItem: null, Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab("fixleaks"), hullContext, targetItem: null, Character.Controlled), null));
if (wallContext != null) if (wallContext != null)
{ {
AddIgnoreOrder(wallContext); AddIgnoreOrder(wallContext);
} }
} }
void AddIgnoreOrder(IIgnorable target) void AddIgnoreOrder(IIgnorable target)
{ {
var orderIdentifier = "ignorethis"; var orderIdentifier = "ignorethis";
if (!target.OrderedToBeIgnored && contextualOrders.None(o => o.Identifier == orderIdentifier)) if (!target.OrderedToBeIgnored && contextualOrders.None(info => info.Order.Identifier == orderIdentifier))
{ {
AddOrder(); AddOrder();
} }
else else
{ {
orderIdentifier = "unignorethis"; orderIdentifier = "unignorethis";
if (target.OrderedToBeIgnored && contextualOrders.None(o => o.Identifier == orderIdentifier)) if (target.OrderedToBeIgnored && contextualOrders.None(info => info.Order.Identifier == orderIdentifier))
{ {
AddOrder(); AddOrder();
} }
@@ -2705,64 +2623,61 @@ namespace Barotrauma
{ {
if (target is WallSection ws) if (target is WallSection ws)
{ {
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), ws.Wall, ws.Wall.Sections.IndexOf(ws), orderGiver: Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), ws.Wall, ws.Wall.Sections.IndexOf(ws), orderGiver: Character.Controlled), null));
} }
else else
{ {
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), target as Entity, null, Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), target as Entity, null, Character.Controlled), null));
} }
} }
} }
orderIdentifier = "wait"; orderIdentifier = "wait";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)))
{ {
Vector2 position = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition); Vector2 position = GameMain.GameScreen.Cam.ScreenToWorld(PlayerInput.MousePosition);
Hull hull = Hull.FindHull(position, guess: Character.Controlled?.CurrentHull); Hull hull = Hull.FindHull(position, guess: Character.Controlled?.CurrentHull);
contextualOrders.Add(new Order(Order.GetPrefab(orderIdentifier), new OrderTarget(position, hull), Character.Controlled)); contextualOrders.Add(new OrderInfo(new Order(Order.GetPrefab(orderIdentifier), new OrderTarget(position, hull), Character.Controlled), null));
} }
if (contextualOrders.None(info => info.Order.Category != OrderCategory.Movement) && characters.Any(c => c != Character.Controlled))
if (contextualOrders.None(o => o.Category != OrderCategory.Movement) && characters.Any(c => c != Character.Controlled))
{ {
orderIdentifier = "follow"; orderIdentifier = "follow";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier))) if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)))
{ {
contextualOrders.Add(Order.GetPrefab(orderIdentifier)); contextualOrders.Add(new OrderInfo(Order.GetPrefab(orderIdentifier), null));
} }
} }
// Show 'dismiss' order only when there are crew members with active orders // Show 'dismiss' order only when there are crew members with active orders
orderIdentifier = "dismissed"; orderIdentifier = "dismissed";
if (contextualOrders.None(o => o.Identifier.Equals(orderIdentifier)) && characters.Any(c => !c.IsDismissed)) if (contextualOrders.None(info => info.Order.Identifier.Equals(orderIdentifier)) && characters.Any(c => !c.IsDismissed))
{ {
contextualOrders.Add(Order.GetPrefab(orderIdentifier)); contextualOrders.Add(new OrderInfo(Order.GetPrefab(orderIdentifier), null));
} }
} }
var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count)); var offsets = MathUtils.GetPointsOnCircumference(Vector2.Zero, nodeDistance, contextualOrders.Count, MathHelper.ToRadians(90f + 180f / contextualOrders.Count));
bool disableNode = !CanCharacterBeHeard(); bool disableNode = !CanCharacterBeHeard();
for (int i = 0; i < contextualOrders.Count; i++) for (int i = 0; i < contextualOrders.Count; i++)
{ {
optionNodes.Add(new Tuple<GUIComponent, Keys>( var info = contextualOrders[i];
CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), contextualOrders[i], (i + 1) % 10, disableNode: disableNode, checkIfOrderCanBeHeard: false), int hotkey = (i + 1) % 10;
!disableNode ? Keys.D0 + (i + 1) % 10 : Keys.None)); var component = string.IsNullOrEmpty(info.OrderOption) ?
CreateOrderNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), info.Order, hotkey, disableNode: disableNode, checkIfOrderCanBeHeard: false) :
CreateOrderOptionNode(nodeSize, commandFrame.RectTransform, offsets[i].ToPoint(), info.Order, info.OrderOption, info.Order.Prefab.GetOptionName(info.OrderOption), hotkey);
optionNodes.Add(new Tuple<GUIComponent, Keys>(component, !disableNode ? Keys.D0 + (i + 1) % 10 : Keys.None));
} }
} }
// TODO: there's duplicate logic here and above -> would be better to refactor so that the conditions are only defined in one place // TODO: there's duplicate logic here and above -> would be better to refactor so that the conditions are only defined in one place
public static bool DoesItemHaveContextualOrders(Item item) public static bool DoesItemHaveContextualOrders(Item item)
{ {
if (Order.PrefabList.Any(o => o.TargetItems.Length > 0 && o.TargetItems.Contains(item.Prefab.Identifier))) { return true; } if (Order.PrefabList.Any(o => o.TargetItemsMatchItem(item))) { return true; }
if (Order.PrefabList.Any(o => item.HasTag(o.TargetItems))) { return true; }
if (Order.PrefabList.Any(o => o.TryGetTargetItemComponent(item, out _))) { return true; } if (Order.PrefabList.Any(o => o.TryGetTargetItemComponent(item, out _))) { return true; }
if (AIObjectiveCleanupItems.IsValidTarget(item, Character.Controlled, checkInventory: false)) { return true; } if (AIObjectiveCleanupItems.IsValidTarget(item, Character.Controlled, checkInventory: false)) { return true; }
if (AIObjectiveCleanupItems.IsValidContainer(item, Character.Controlled)) { return true; } if (AIObjectiveCleanupItems.IsValidContainer(item, Character.Controlled)) { return true; }
if (Order.GetPrefab("loaditems") is Order loadItemsPrefab && AIObjectiveLoadItems.IsValidTarget(item, Character.Controlled, targetContainerTags: loadItemsPrefab.GetTargetItems())) { return true; }
if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold)) { return true; } if (item.Repairables.Any(r => r.IsBelowRepairThreshold)) { return true; }
var operateWeaponsPrefab = Order.GetPrefab("operateweapons"); return Order.GetPrefab("operateweapons") is Order operateWeaponsPrefab && item.Components.Any(c => c is Controller) &&
return item.Components.Any(c => c is Controller) && (item.GetConnectedComponents<Turret>().Any(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)) ||
(item.GetConnectedComponents<Turret>().Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)) || item.GetConnectedComponents<Turret>(recursive: true).Any(c => operateWeaponsPrefab.TargetItemsMatchItem(c.Item)));
item.GetConnectedComponents<Turret>(recursive: true).Any(c => c.Item.HasTag(operateWeaponsPrefab.TargetItems)));
} }
/// <param name="hotkey">Use a negative value (e.g. -1) if there should be no hotkey associated with the node</param> /// <param name="hotkey">Use a negative value (e.g. -1) if there should be no hotkey associated with the node</param>
@@ -2781,7 +2696,7 @@ namespace Barotrauma
disableNode = !CanCharacterBeHeard(); disableNode = !CanCharacterBeHeard();
} }
var mustSetOptionOrTarget = order.HasOptions; bool mustSetOptionOrTarget = order.HasOptions;
Item orderTargetEntity = null; Item orderTargetEntity = null;
// If the order doesn't have options, but must set a target, // If the order doesn't have options, but must set a target,
@@ -2804,14 +2719,14 @@ namespace Barotrauma
{ {
if (disableNode || !CanIssueOrders) { return false; } if (disableNode || !CanIssueOrders) { return false; }
var o = userData as Order; var o = userData as Order;
if (o.MustManuallyAssign && characterContext == null) if (mustSetOptionOrTarget)
{
CreateAssignmentNodes(node);
}
else if (mustSetOptionOrTarget)
{ {
NavigateForward(button, userData); NavigateForward(button, userData);
} }
else if (o.MustManuallyAssign && characterContext == null)
{
CreateAssignmentNodes(node);
}
else else
{ {
if (orderTargetEntity != null) if (orderTargetEntity != null)
@@ -2819,7 +2734,8 @@ namespace Barotrauma
o = new Order(o.Prefab, orderTargetEntity, orderTargetEntity.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: order.OrderGiver); o = new Order(o.Prefab, orderTargetEntity, orderTargetEntity.Components.FirstOrDefault(ic => ic.GetType() == order.ItemComponentType), orderGiver: order.OrderGiver);
} }
var character = !o.TargetAllCharacters ? characterContext ?? GetCharacterForQuickAssignment(o) : null; var character = !o.TargetAllCharacters ? characterContext ?? GetCharacterForQuickAssignment(o) : null;
SetCharacterOrder(character, o, null, CharacterInfo.HighestManualOrderPriority, Character.Controlled); int priority = GetManualOrderPriority(character, o);
SetCharacterOrder(character, o, null, priority, Character.Controlled);
DisableCommandUI(); DisableCommandUI();
} }
return true; return true;
@@ -2925,9 +2841,15 @@ namespace Barotrauma
{ {
NavigateForward(button, userData); NavigateForward(button, userData);
} }
else if (o.Item1.MustManuallyAssign && characterContext == null)
{
CreateAssignmentNodes(button);
}
else else
{ {
SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); var character = characterContext ?? GetCharacterForQuickAssignment(o.Item1);
int priority = GetManualOrderPriority(character, o.Item1);
SetCharacterOrder(character, o.Item1, o.Item2, priority, Character.Controlled);
DisableCommandUI(); DisableCommandUI();
} }
return true; return true;
@@ -2987,12 +2909,21 @@ namespace Barotrauma
var node = new GUIButton(new RectTransform(size, parent: parent, anchor: Anchor.Center), style: null) var node = new GUIButton(new RectTransform(size, parent: parent, anchor: Anchor.Center), style: null)
{ {
UserData = new Tuple<Order, string>(order, option), UserData = new Tuple<Order, string>(order, option),
OnClicked = (_, userData) => OnClicked = (button, userData) =>
{ {
if (!CanIssueOrders) { return false; } if (!CanIssueOrders) { return false; }
var o = userData as Tuple<Order, string>; var o = userData as Tuple<Order, string>;
SetCharacterOrder(characterContext ?? GetCharacterForQuickAssignment(o.Item1), o.Item1, o.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); if (o.Item1.MustManuallyAssign && characterContext == null)
DisableCommandUI(); {
CreateAssignmentNodes(button);
}
else
{
var character = characterContext ?? GetCharacterForQuickAssignment(o.Item1);
int priority = GetManualOrderPriority(character, o.Item1);
SetCharacterOrder(character, o.Item1, o.Item2, priority, Character.Controlled);
DisableCommandUI();
}
return true; return true;
} }
}; };
@@ -3195,7 +3126,9 @@ namespace Barotrauma
OnClicked = (_, userData) => OnClicked = (_, userData) =>
{ {
if (!CanIssueOrders) { return false; } if (!CanIssueOrders) { return false; }
SetCharacterOrder(userData as Character, order.Item1, order.Item2, CharacterInfo.HighestManualOrderPriority, Character.Controlled); var character = userData as Character;
int priority = GetManualOrderPriority(character, order.Item1);
SetCharacterOrder(character, order.Item1, order.Item2, priority, Character.Controlled);
DisableCommandUI(); DisableCommandUI();
return true; return true;
} }
@@ -3473,6 +3406,11 @@ namespace Barotrauma
return order.Name; return order.Name;
} }
private int GetManualOrderPriority(Character character, Order order)
{
return character?.Info?.GetManualOrderPriority(order) ?? CharacterInfo.HighestManualOrderPriority;
}
#region Crew Member Assignment Logic #region Crew Member Assignment Logic
private bool CanOpenManualAssignment(GUIComponent node) private bool CanOpenManualAssignment(GUIComponent node)
{ {
@@ -65,7 +65,7 @@ namespace Barotrauma
public override void ShowStartMessage() public override void ShowStartMessage()
{ {
foreach (Mission mission in Missions) foreach (Mission mission in Missions.ToList())
{ {
new GUIMessageBox( new GUIMessageBox(
mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name, mission.Prefab.IsSideObjective ? TextManager.AddPunctuation(':', TextManager.Get("sideobjective"), mission.Name) : mission.Name,
@@ -60,7 +60,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);
GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, null, saveFiles); GameMain.NetLobbyScreen.CampaignSetupUI = new MultiPlayerCampaignSetupUI(newCampaignContainer, loadCampaignContainer, 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")
@@ -188,7 +188,7 @@ namespace Barotrauma
} }
private IEnumerable<object> DoInitialCameraTransition() private IEnumerable<CoroutineStatus> DoInitialCameraTransition()
{ {
while (GameMain.Instance.LoadingScreenOpen) while (GameMain.Instance.LoadingScreenOpen)
{ {
@@ -310,12 +310,12 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null) protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null)
{ {
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<object> DoLevelTransition() private IEnumerable<CoroutineStatus> DoLevelTransition()
{ {
SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead"; SoundPlayer.OverrideMusicType = CrewManager.GetCharacters().Any(c => !c.IsDead) ? "endround" : "crewdead";
SoundPlayer.OverrideMusicDuration = 18.0f; SoundPlayer.OverrideMusicDuration = 18.0f;
@@ -361,7 +361,7 @@ namespace Barotrauma
//-------------------------------------- //--------------------------------------
//wait for the new level to be loaded //wait for the new level to be loaded
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 30); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, seconds: 60);
while (Level.Loaded == prevLevel || Level.Loaded == null) while (Level.Loaded == prevLevel || Level.Loaded == null)
{ {
if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; } if (DateTime.Now > timeOut || Screen.Selected != GameMain.GameScreen) { break; }
@@ -480,8 +480,6 @@ namespace Barotrauma
{ {
IsFirstRound = false; IsFirstRound = false;
CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition"); CoroutineManager.StartCoroutine(DoLevelTransition(), "LevelTransition");
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
GUI.SetSavingIndicatorState(success && (Level.IsLoadedOutpost || transitionType != TransitionType.None));
} }
} }
@@ -500,7 +498,7 @@ namespace Barotrauma
}; };
} }
private IEnumerable<object> DoEndCampaignCameraTransition() private IEnumerable<CoroutineStatus> DoEndCampaignCameraTransition()
{ {
Character controlled = Character.Controlled; Character controlled = Character.Controlled;
if (controlled != null) if (controlled != null)
@@ -243,7 +243,7 @@ namespace Barotrauma
mirror: map.CurrentLocation != map.SelectedConnection?.Locations[0])); mirror: map.CurrentLocation != map.SelectedConnection?.Locations[0]));
} }
private IEnumerable<object> DoLoadInitialLevel(LevelData level, bool mirror) private IEnumerable<CoroutineStatus> DoLoadInitialLevel(LevelData level, bool mirror)
{ {
GameMain.GameSession.StartRound(level, GameMain.GameSession.StartRound(level,
mirrorLevel: mirror); mirrorLevel: mirror);
@@ -254,7 +254,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
private IEnumerable<object> DoInitialCameraTransition() private IEnumerable<CoroutineStatus> DoInitialCameraTransition()
{ {
while (GameMain.Instance.LoadingScreenOpen) while (GameMain.Instance.LoadingScreenOpen)
{ {
@@ -378,7 +378,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
protected override IEnumerable<object> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null) protected override IEnumerable<CoroutineStatus> DoLevelTransition(TransitionType transitionType, LevelData newLevel, Submarine leavingSub, bool mirror, List<TraitorMissionResult> traitorResults = null)
{ {
NextLevel = newLevel; NextLevel = newLevel;
bool success = CrewManager.GetCharacters().Any(c => !c.IsDead); bool success = CrewManager.GetCharacters().Any(c => !c.IsDead);
@@ -515,7 +515,7 @@ namespace Barotrauma
}; };
} }
private IEnumerable<object> DoEndCampaignCameraTransition() private IEnumerable<CoroutineStatus> DoEndCampaignCameraTransition()
{ {
if (Character.Controlled != null) if (Character.Controlled != null)
{ {
@@ -15,7 +15,7 @@ namespace Barotrauma.Tutorials
{ {
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
Character Controlled = Character.Controlled; Character Controlled = Character.Controlled;
if (Controlled == null) yield return CoroutineStatus.Success; if (Controlled == null) yield return CoroutineStatus.Success;
@@ -634,7 +634,7 @@ namespace Barotrauma.Tutorials
return Character.Controlled.Inventory.FindItemByIdentifier(itemIdentifier) != null; return Character.Controlled.Inventory.FindItemByIdentifier(itemIdentifier) != null;
} }
protected IEnumerable<object> KeepReactorRunning(Reactor reactor) protected IEnumerable<CoroutineStatus> KeepReactorRunning(Reactor reactor)
{ {
do do
{ {
@@ -652,7 +652,7 @@ namespace Barotrauma.Tutorials
/// <summary> /// <summary>
/// keeps the enemy away from the sub until the capacitors are loaded /// keeps the enemy away from the sub until the capacitors are loaded
/// </summary> /// </summary>
private IEnumerable<object> KeepEnemyAway(Character enemy, PowerContainer[] capacitors) private IEnumerable<CoroutineStatus> KeepEnemyAway(Character enemy, PowerContainer[] capacitors)
{ {
do do
{ {
@@ -141,7 +141,7 @@ namespace Barotrauma.Tutorials
captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false; captain_mechanic.AIController.Enabled = captain_security.AIController.Enabled = captain_engineer.AIController.Enabled = false;
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
while (GameMain.Instance.LoadingScreenOpen) yield return null; while (GameMain.Instance.LoadingScreenOpen) yield return null;
@@ -139,7 +139,7 @@ namespace Barotrauma.Tutorials
reactorItem.GetComponent<Reactor>().AutoTemp = true; reactorItem.GetComponent<Reactor>().AutoTemp = true;
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
while (GameMain.Instance.LoadingScreenOpen) yield return null; while (GameMain.Instance.LoadingScreenOpen) yield return null;
@@ -446,7 +446,7 @@ namespace Barotrauma.Tutorials
CoroutineManager.StartCoroutine(TutorialCompleted()); CoroutineManager.StartCoroutine(TutorialCompleted());
} }
public IEnumerable<object> KeepPatientAlive(Character patient) public IEnumerable<CoroutineStatus> KeepPatientAlive(Character patient)
{ {
while (patient != null && !patient.Removed) while (patient != null && !patient.Removed)
{ {
@@ -10,7 +10,7 @@ namespace Barotrauma.Tutorials
{ {
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
/*infoBox = CreateInfoFrame("Use the mouse wheel to zoom in and out, and WASD to move the camera around.", true); /*infoBox = CreateInfoFrame("Use the mouse wheel to zoom in and out, and WASD to move the camera around.", true);
@@ -206,7 +206,7 @@ namespace Barotrauma.Tutorials
engineer_submarineJunctionBox_3.Condition = 0f; engineer_submarineJunctionBox_3.Condition = 0f;
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
while (GameMain.Instance.LoadingScreenOpen) yield return null; while (GameMain.Instance.LoadingScreenOpen) yield return null;
@@ -378,7 +378,7 @@ namespace Barotrauma.Tutorials
} }
} }
yield return null; yield return null;
} while (engineer_brokenJunctionBox.Condition < repairableJunctionBoxComponent.RepairThreshold); // Wait until repaired } while (repairableJunctionBoxComponent.IsBelowRepairThreshold); // Wait until repaired
SetHighlight(engineer_brokenJunctionBox, false); SetHighlight(engineer_brokenJunctionBox, false);
RemoveCompletedObjective(segments[3]); RemoveCompletedObjective(segments[3]);
SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true); SetDoorAccess(engineer_thirdDoor, engineer_thirdDoorLight, true);
@@ -422,7 +422,7 @@ namespace Barotrauma.Tutorials
Repairable repairableJunctionBoxComponent3 = engineer_submarineJunctionBox_3.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(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); do { CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); yield return null; } while (repairableJunctionBoxComponent1.IsBelowRepairThreshold || repairableJunctionBoxComponent2.IsBelowRepairThreshold || repairableJunctionBoxComponent3.IsBelowRepairThreshold);
CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3); CheckJunctionBoxHighlights(repairableJunctionBoxComponent1, repairableJunctionBoxComponent2, repairableJunctionBoxComponent3);
RemoveCompletedObjective(segments[5]); RemoveCompletedObjective(segments[5]);
yield return new WaitForSeconds(2f, false); yield return new WaitForSeconds(2f, false);
@@ -462,7 +462,7 @@ namespace Barotrauma.Tutorials
return engineer?.SelectedConstruction == item; return engineer?.SelectedConstruction == item;
} }
private IEnumerable<object> ReactorOperatedProperly() private IEnumerable<CoroutineStatus> ReactorOperatedProperly()
{ {
float timer; float timer;
@@ -566,17 +566,17 @@ namespace Barotrauma.Tutorials
private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3) private void CheckJunctionBoxHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{ {
if (engineer_submarineJunctionBox_1.Condition > comp1.RepairThreshold && engineer_submarineJunctionBox_1.ExternalHighlight) if (!comp1.IsBelowRepairThreshold && 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.Condition > comp2.RepairThreshold && engineer_submarineJunctionBox_2.ExternalHighlight) if (!comp2.IsBelowRepairThreshold && 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.Condition > comp3.RepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight) if (!comp3.IsBelowRepairThreshold && engineer_submarineJunctionBox_3.ExternalHighlight)
{ {
SetHighlight(engineer_submarineJunctionBox_3, false); SetHighlight(engineer_submarineJunctionBox_3, false);
engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3); engineer.RemoveActiveObjectiveEntity(engineer_submarineJunctionBox_3);
@@ -225,7 +225,7 @@ namespace Barotrauma.Tutorials
base.Update(deltaTime); base.Update(deltaTime);
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
while (GameMain.Instance.LoadingScreenOpen) yield return null; while (GameMain.Instance.LoadingScreenOpen) yield return null;
@@ -550,7 +550,7 @@ namespace Barotrauma.Tutorials
do do
{ {
yield return null; yield return null;
if (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold) if (repairablePumpComponent.IsBelowRepairThreshold)
{ {
if (!mechanic.HasEquippedItem("wrench")) if (!mechanic.HasEquippedItem("wrench"))
{ {
@@ -574,7 +574,7 @@ namespace Barotrauma.Tutorials
} }
} }
} }
} while (mechanic_brokenPump.Item.Condition < repairablePumpComponent.RepairThreshold || mechanic_brokenPump.FlowPercentage >= 0 || !mechanic_brokenPump.IsActive); } while (repairablePumpComponent.IsBelowRepairThreshold || 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);
@@ -597,7 +597,7 @@ namespace Barotrauma.Tutorials
Repairable repairableEngineComponent = mechanic_submarineEngine.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(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); do { CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); yield return null; } while (repairablePumpComponent1.IsBelowRepairThreshold || repairablePumpComponent2.IsBelowRepairThreshold || repairableEngineComponent.IsBelowRepairThreshold);
CheckHighlights(repairablePumpComponent1, repairablePumpComponent2, repairableEngineComponent); 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);
@@ -623,17 +623,17 @@ namespace Barotrauma.Tutorials
private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3) private void CheckHighlights(Repairable comp1, Repairable comp2, Repairable comp3)
{ {
if (mechanic_ballastPump_1.Item.Condition > comp1.RepairThreshold && mechanic_ballastPump_1.Item.ExternalHighlight) if (!comp1.IsBelowRepairThreshold && 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.Condition > comp2.RepairThreshold && mechanic_ballastPump_2.Item.ExternalHighlight) if (!comp2.IsBelowRepairThreshold && 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.Condition > comp3.RepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight) if (!comp3.IsBelowRepairThreshold && mechanic_submarineEngine.Item.ExternalHighlight)
{ {
SetHighlight(mechanic_submarineEngine.Item, false); SetHighlight(mechanic_submarineEngine.Item, false);
mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item); mechanic.RemoveActiveObjectiveEntity(mechanic_submarineEngine.Item);
@@ -201,7 +201,7 @@ namespace Barotrauma.Tutorials
SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true); SetDoorAccess(tutorial_submarineDoor, tutorial_submarineDoorLight, true);
} }
public override IEnumerable<object> UpdateState() public override IEnumerable<CoroutineStatus> UpdateState()
{ {
while (GameMain.Instance.LoadingScreenOpen) yield return null; while (GameMain.Instance.LoadingScreenOpen) yield return null;
@@ -54,7 +54,7 @@ namespace Barotrauma.Tutorials
GameMain.Instance.ShowLoading(Loading()); GameMain.Instance.ShowLoading(Loading());
} }
private IEnumerable<object> Loading() private IEnumerable<CoroutineStatus> Loading()
{ {
SubmarineInfo subInfo = new SubmarineInfo(submarinePath); SubmarineInfo subInfo = new SubmarineInfo(submarinePath);
@@ -259,7 +259,7 @@ namespace Barotrauma.Tutorials
base.Stop(); base.Stop();
} }
private IEnumerable<object> Dead() private IEnumerable<CoroutineStatus> Dead()
{ {
GUI.PreventPauseMenuToggle = true; GUI.PreventPauseMenuToggle = true;
Character.Controlled = character = null; Character.Controlled = character = null;
@@ -279,7 +279,7 @@ namespace Barotrauma.Tutorials
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
protected IEnumerable<object> TutorialCompleted() protected IEnumerable<CoroutineStatus> TutorialCompleted()
{ {
GUI.PreventPauseMenuToggle = true; GUI.PreventPauseMenuToggle = true;
@@ -247,7 +247,7 @@ namespace Barotrauma.Tutorials
} }
} }
public virtual IEnumerable<object> UpdateState() public virtual IEnumerable<CoroutineStatus> UpdateState()
{ {
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
@@ -470,7 +470,7 @@ namespace Barotrauma.Tutorials
CoroutineManager.StartCoroutine(WaitForObjectiveEnd(segment)); CoroutineManager.StartCoroutine(WaitForObjectiveEnd(segment));
} }
private IEnumerable<object> WaitForObjectiveEnd(TutorialSegment objective) private IEnumerable<CoroutineStatus> WaitForObjectiveEnd(TutorialSegment objective)
{ {
yield return new WaitForSeconds(2.0f); yield return new WaitForSeconds(2.0f);
objectiveFrame.RemoveChild(objective.ReplayButton); objectiveFrame.RemoveChild(objective.ReplayButton);
@@ -24,7 +24,7 @@ namespace Barotrauma
foreach (Item item in Item.ItemList) foreach (Item item in Item.ItemList)
{ {
//don't consider the items to belong in the outpost to prevent the stealing icon from showing //don't consider the items to belong in the outpost to prevent the stealing icon from showing
item.SpawnedInOutpost = false; item.SpawnedInCurrentOutpost = false;
} }
} }
@@ -106,7 +106,8 @@ namespace Barotrauma
respawnButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft) respawnButtonContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnInfoFrame.RectTransform, Anchor.CenterRight), isHorizontal: true, childAnchor: Anchor.CenterLeft)
{ {
AbsoluteSpacing = HUDLayoutSettings.Padding, AbsoluteSpacing = HUDLayoutSettings.Padding,
Stretch = true Stretch = true,
Visible = false
}; };
respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn")) respawnTickBox = new GUITickBox(new RectTransform(Vector2.One * 0.9f, respawnButtonContainer.RectTransform, Anchor.Center), TextManager.Get("respawnquestionpromptrespawn"))
{ {
@@ -133,7 +133,7 @@ namespace Barotrauma
string hintIdentifierBase = "onstartedinteracting"; string hintIdentifierBase = "onstartedinteracting";
// onstartedinteracting.brokenitem // onstartedinteracting.brokenitem
if (item.Repairables.Any(r => item.ConditionPercentage < r.RepairThreshold)) if (item.Repairables.Any(r => r.IsBelowRepairThreshold))
{ {
if (DisplayHint($"{hintIdentifierBase}.brokenitem")) { return; } if (DisplayHint($"{hintIdentifierBase}.brokenitem")) { return; }
} }
@@ -192,7 +192,7 @@ namespace Barotrauma
if (!CanDisplayHints(requireGameScreen: false, requireControllingCharacter: false)) { return; } if (!CanDisplayHints(requireGameScreen: false, requireControllingCharacter: false)) { return; }
CoroutineManager.StartCoroutine(DisplayRoundStartedHints(initRoundHandle), "HintManager.DisplayRoundStartedHints"); CoroutineManager.StartCoroutine(DisplayRoundStartedHints(initRoundHandle), "HintManager.DisplayRoundStartedHints");
static IEnumerable<object> InitRound() static IEnumerable<CoroutineStatus> InitRound()
{ {
while (Character.Controlled == null) { yield return CoroutineStatus.Running; } while (Character.Controlled == null) { yield return CoroutineStatus.Running; }
// Get the ballast hulls on round start not to find them again and again later // Get the ballast hulls on round start not to find them again and again later
@@ -211,7 +211,7 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
static IEnumerable<object> DisplayRoundStartedHints(CoroutineHandle initRoundHandle) static IEnumerable<CoroutineStatus> DisplayRoundStartedHints(CoroutineHandle initRoundHandle)
{ {
while (GameMain.Instance.LoadingScreenOpen || Screen.Selected != GameMain.GameScreen || while (GameMain.Instance.LoadingScreenOpen || Screen.Selected != GameMain.GameScreen ||
CoroutineManager.IsCoroutineRunning(initRoundHandle) || CoroutineManager.IsCoroutineRunning(initRoundHandle) ||
@@ -475,12 +475,12 @@ namespace Barotrauma
ItemComponent targetItem = null; ItemComponent targetItem = null;
if (orderPrefab.MustSetTarget) if (orderPrefab.MustSetTarget)
{ {
targetEntity = orderPrefab.GetMatchingItems(true, interactableFor: Character.Controlled).FirstOrDefault(); targetEntity = orderPrefab.GetMatchingItems(true, interactableFor: Character.Controlled, orderOption: orderInfo.option).FirstOrDefault();
if (targetEntity == null) { return; } if (targetEntity == null) { return; }
targetItem = orderPrefab.GetTargetItemComponent(targetEntity); targetItem = orderPrefab.GetTargetItemComponent(targetEntity);
} }
var order = new Order(orderPrefab, targetEntity as Entity, targetItem, orderGiver: Character.Controlled); var order = new Order(orderPrefab, targetEntity as Entity, targetItem, orderGiver: Character.Controlled);
GameMain.GameSession.CrewManager.SetCharacterOrder(Character.Controlled, order, orderInfo.option, CharacterInfo.HighestManualOrderPriority, Character.Controlled); GameMain.GameSession?.CrewManager?.SetCharacterOrder(Character.Controlled, order, orderInfo.option, CharacterInfo.HighestManualOrderPriority, Character.Controlled);
}); });
} }
@@ -635,9 +635,9 @@ namespace Barotrauma
} }
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 \"" + characterInfo.Name + "\" had an invalid cause of death (the type of the cause of death was Affliction, but affliction was not specified)."; string errorMsg = "Character \"[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.Replace("[name]", characterInfo.Name));
GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("RoundSummary:InvalidCauseOfDeath", GameAnalyticsManager.ErrorSeverity.Error, errorMsg.Replace("[name]", characterInfo.SpeciesName));
statusText = TextManager.Get("CauseOfDeathDescription.Unknown"); statusText = TextManager.Get("CauseOfDeathDescription.Unknown");
statusColor = GUI.Style.Red; statusColor = GUI.Style.Red;
} }
@@ -1,12 +1,10 @@
using Barotrauma.Extensions; using Barotrauma.Networking;
using Barotrauma.Networking;
using Microsoft.Xna.Framework; using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input; using Microsoft.Xna.Framework.Input;
using OpenAL; using OpenAL;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
@@ -365,6 +363,12 @@ namespace Barotrauma
TextManager.Get("Settings"), textAlignment: Alignment.TopLeft, font: GUI.LargeFont) TextManager.Get("Settings"), textAlignment: Alignment.TopLeft, font: GUI.LargeFont)
{ ForceUpperCase = true }; { ForceUpperCase = true };
new GUIButton(new RectTransform(new Vector2(1.0f, 1.0f), settingsTitle.RectTransform, Anchor.CenterRight, scaleBasis: ScaleBasis.Smallest), style: "GUIBugButton")
{
ToolTip = TextManager.Get("bugreportbutton") + $" (v{GameMain.Version})",
OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
};
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), TextManager.Get("ContentPackages"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform), TextManager.Get("ContentPackages"), font: GUI.SubHeadingFont);
var corePackageDropdown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform)) var corePackageDropdown = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), leftPanel.RectTransform))
@@ -480,7 +484,7 @@ namespace Barotrauma
"\n" + string.Join("\n", contentPackage.ErrorMessages); "\n" + string.Join("\n", contentPackage.ErrorMessages);
} }
} }
contentPackageList.CanDragElements = CanHotswapPackages(false); contentPackageList.CurrentDragMode = CanHotswapPackages(false) ? GUIListBox.DragMode.DragWithinBox : GUIListBox.DragMode.NoDragging;
contentPackageList.CanBeFocused = CanHotswapPackages(false); contentPackageList.CanBeFocused = CanHotswapPackages(false);
contentPackageList.OnRearranged = OnContentPackagesRearranged; contentPackageList.OnRearranged = OnContentPackagesRearranged;
@@ -509,17 +513,56 @@ namespace Barotrauma
ApplySettings(); ApplySettings();
GameMain.Instance.Exit(); GameMain.Instance.Exit();
return true; return true;
}; msgBox.Buttons[1].OnClicked += (btn, userdata) => };
msgBox.Buttons[1].OnClicked += (btn, userdata) =>
{ {
Language = prevLanguage; Language = prevLanguage;
languageDD.SelectItem(Language); languageDD.SelectItem(Language);
msgBox.Close(); msgBox.Close();
return true; return true;
}; };
return true; return true;
}; };
var statisticsTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.045f), leftPanel.RectTransform), TextManager.Get("statisticsconsenttickbox"))
{
OnSelected = (GUITickBox tickBox) =>
{
GameAnalyticsManager.SetConsent(
tickBox.Selected
? GameAnalyticsManager.Consent.Ask
: GameAnalyticsManager.Consent.No);
return false;
}
};
#if DEBUG
statisticsTickBox.Enabled = false;
#endif
void updateGATickBoxToolTip()
=> statisticsTickBox.ToolTip = TextManager.Get($"GameAnalyticsStatus.{GameAnalyticsManager.UserConsented}");
updateGATickBoxToolTip();
var cachedConsent = GameAnalyticsManager.Consent.Unknown;
var statisticsTickBoxUpdater = new GUICustomComponent(
new RectTransform(Vector2.Zero, statisticsTickBox.RectTransform),
onUpdate: (deltaTime, component) =>
{
bool shouldTickBoxBeSelected = GameAnalyticsManager.UserConsented == GameAnalyticsManager.Consent.Yes;
bool shouldUpdateTickBoxState = cachedConsent != GameAnalyticsManager.UserConsented
|| statisticsTickBox.Selected != shouldTickBoxBeSelected;
if (!shouldUpdateTickBoxState) { return; }
updateGATickBoxToolTip();
cachedConsent = GameAnalyticsManager.UserConsented;
GUITickBox.OnSelectedHandler prevHandler = statisticsTickBox.OnSelected;
statisticsTickBox.OnSelected = null;
statisticsTickBox.Selected = shouldTickBoxBeSelected;
statisticsTickBox.OnSelected = prevHandler;
statisticsTickBox.Enabled = GameAnalyticsManager.UserConsented != GameAnalyticsManager.Consent.Error;
});
// right panel -------------------------------------- // right panel --------------------------------------
var rightPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.99f - leftPanel.RectTransform.RelativeSize.X, leftPanel.RectTransform.RelativeSize.Y), var rightPanel = new GUILayoutGroup(new RectTransform(new Vector2(0.99f - leftPanel.RectTransform.RelativeSize.X, leftPanel.RectTransform.RelativeSize.Y),
@@ -556,13 +599,6 @@ namespace Barotrauma
tabButtons[(int)tab].Text = ToolBox.LimitString(buttonText, tabButtons[(int)tab].Font, (int)(0.75f * tabWidth * tabButtonHolder.Rect.Width)); tabButtons[(int)tab].Text = ToolBox.LimitString(buttonText, tabButtons[(int)tab].Font, (int)(0.75f * tabWidth * tabButtonHolder.Rect.Width));
} }
new GUIButton(new RectTransform(new Vector2(0.05f, 0.75f), tabButtonHolder.RectTransform, Anchor.BottomRight) { RelativeOffset = new Vector2(0.0f, 0.2f) }, style: "GUIBugButton")
{
ToolTip = TextManager.Get("bugreportbutton"),
OnClicked = (btn, userdata) => { GameMain.Instance.ShowBugReporter(); return true; }
};
/// Graphics tab -------------------------------------------------------------- /// Graphics tab --------------------------------------------------------------
var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Graphics].RectTransform, Anchor.TopLeft) var leftColumn = new GUILayoutGroup(new RectTransform(new Vector2(0.46f, 0.95f), tabs[(int)Tab.Graphics].RectTransform, Anchor.TopLeft)
@@ -1168,7 +1204,7 @@ namespace Barotrauma
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError("Failed to set voice capture mode.", e); DebugConsole.ThrowError("Failed to set voice capture mode.", e);
GameAnalyticsManager.AddErrorEventOnce("SetVoiceCaptureMode", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, "Failed to set voice capture mode. " + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("SetVoiceCaptureMode", GameAnalyticsManager.ErrorSeverity.Error, "Failed to set voice capture mode. " + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
VoiceSetting = VoiceMode.Disabled; VoiceSetting = VoiceMode.Disabled;
} }
@@ -1767,7 +1803,7 @@ namespace Barotrauma
return true; return true;
} }
private IEnumerable<object> WaitForKeyPress(GUITextBox keyBox, KeyOrMouse[] keyArray) private IEnumerable<CoroutineStatus> WaitForKeyPress(GUITextBox keyBox, KeyOrMouse[] keyArray)
{ {
yield return CoroutineStatus.Running; yield return CoroutineStatus.Running;
@@ -949,16 +949,17 @@ namespace Barotrauma
//player has selected the inventory of another item -> attempt to move the item there //player has selected the inventory of another item -> attempt to move the item there
return QuickUseAction.PutToContainer; return QuickUseAction.PutToContainer;
} }
else if (character.SelectedCharacter != null && else if (character.SelectedCharacter?.Inventory != null &&
character.SelectedCharacter.Inventory != null &&
!character.SelectedCharacter.Inventory.Locked && !character.SelectedCharacter.Inventory.Locked &&
allowInventorySwap) allowInventorySwap)
{ {
//player has selected the inventory of another character -> attempt to move the item there //player has selected the inventory of another character -> attempt to move the item there
return QuickUseAction.PutToCharacter; return QuickUseAction.PutToCharacter;
} }
else if (character.SelectedBy != null && Character.Controlled == character.SelectedBy && else if (character.SelectedBy?.Inventory != null &&
character.SelectedBy.Inventory != null && !character.SelectedBy.Inventory.Locked && allowInventorySwap) Character.Controlled == character.SelectedBy &&
!character.SelectedBy.Inventory.Locked &&
allowInventorySwap)
{ {
return QuickUseAction.TakeFromCharacter; return QuickUseAction.TakeFromCharacter;
} }
@@ -108,7 +108,7 @@ namespace Barotrauma.Items.Components
float sizeMultiplier = Math.Clamp(chargeRatio, 0.1f, 1f); float sizeMultiplier = Math.Clamp(chargeRatio, 0.1f, 1f);
foreach (ParticleEmitter emitter in particleEmitterCharges) foreach (ParticleEmitter emitter in particleEmitterCharges)
{ {
emitter.Emit(deltaTime, particlePos, hullGuess: null, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier); emitter.Emit(deltaTime, particlePos, hullGuess: item.CurrentHull, sizeMultiplier: sizeMultiplier, colorMultiplier: emitter.Prefab.Properties.ColorMultiplier);
} }
if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying) if (chargeSoundChannel == null || !chargeSoundChannel.IsPlaying)
@@ -157,6 +157,14 @@ namespace Barotrauma.Items.Components
crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale); crosshairSprite?.Draw(spriteBatch, crosshairPos, Color.White, 0, currentCrossHairScale);
crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale); crosshairPointerSprite?.Draw(spriteBatch, crosshairPointerPos, 0, currentCrossHairPointerScale);
} }
if (GameMain.DebugDraw)
{
Vector2 barrelPos = item.DrawPosition + ConvertUnits.ToDisplayUnits(TransformedBarrelPos);
barrelPos = Screen.Selected.Cam.WorldToScreen(barrelPos);
GUI.DrawLine(spriteBatch, barrelPos - Vector2.UnitY * 3, barrelPos + Vector2.UnitY * 3, Color.Red);
GUI.DrawLine(spriteBatch, barrelPos - Vector2.UnitX * 3, barrelPos + Vector2.UnitX * 3, Color.Red);
}
} }
partial void LaunchProjSpecific() partial void LaunchProjSpecific()
@@ -166,7 +174,7 @@ namespace Barotrauma.Items.Components
if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; } if (item.body.Dir < 0.0f) { rotation += MathHelper.Pi; }
foreach (ParticleEmitter emitter in particleEmitters) foreach (ParticleEmitter emitter in particleEmitters)
{ {
emitter.Emit(1.0f, particlePos, hullGuess: null, angle: rotation, particleRotation: rotation); emitter.Emit(1.0f, particlePos, hullGuess: item.CurrentHull, angle: rotation, particleRotation: rotation);
} }
} }
@@ -406,7 +406,7 @@ namespace Barotrauma.Items.Components
DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); DebugConsole.Log("Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"ItemComponent.PlaySound:" + item.Name + GetType().ToString(), "ItemComponent.PlaySound:" + item.Name + GetType().ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
"Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume); "Invalid sound volume (item " + item.Name + ", " + GetType().ToString() + "): " + newVolume);
return 0.0f; return 0.0f;
} }
@@ -576,7 +576,7 @@ namespace Barotrauma.Items.Components
delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync)); delayedCorrectionCoroutine = CoroutineManager.StartCoroutine(DoDelayedCorrection(type, buffer, sendingTime, waitForMidRoundSync));
} }
private IEnumerable<object> DoDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync) private IEnumerable<CoroutineStatus> DoDelayedCorrection(ServerNetObject type, IReadMessage buffer, float sendingTime, bool waitForMidRoundSync)
{ {
while (GameMain.Client != null && while (GameMain.Client != null &&
(correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing))) (correctionTimer > 0.0f || (waitForMidRoundSync && GameMain.Client.MidRoundSyncing)))
@@ -1,9 +1,6 @@
using System; using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Text;
using System.Xml.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
using System.Xml.Linq;
namespace Barotrauma.Items.Components namespace Barotrauma.Items.Components
{ {
@@ -11,7 +8,7 @@ namespace Barotrauma.Items.Components
{ {
public float BackgroundSpriteDepth public float BackgroundSpriteDepth
{ {
get { return item.GetDrawDepth() + 0.1f; } get { return item.GetDrawDepth() + 0.05f; }
} }
public Vector2 DrawSize public Vector2 DrawSize
@@ -14,6 +14,8 @@ namespace Barotrauma.Items.Components
private CoroutineHandle resetPredictionCoroutine; private CoroutineHandle resetPredictionCoroutine;
private float resetPredictionTimer; private float resetPredictionTimer;
private float currentBrightness;
public Vector2 DrawSize public Vector2 DrawSize
{ {
get { return new Vector2(Light.Range * 2, Light.Range * 2); } get { return new Vector2(Light.Range * 2, Light.Range * 2); }
@@ -31,12 +33,40 @@ namespace Barotrauma.Items.Components
{ {
if (Light == null) { return; } if (Light == null) { return; }
Light.Enabled = enabled; Light.Enabled = enabled;
currentBrightness = brightness;
if (enabled) if (enabled)
{ {
Light.Color = LightColor.Multiply(brightness); Light.Color = LightColor.Multiply(brightness);
} }
} }
partial void SetLightSourceTransform()
{
if (ParentBody != null)
{
Light.Position = ParentBody.Position;
}
else if (turret != null)
{
Light.Position = new Vector2(item.Rect.X + turret.TransformedBarrelPos.X, item.Rect.Y - turret.TransformedBarrelPos.Y);
}
else
{
Light.Position = item.Position;
}
PhysicsBody body = ParentBody ?? item.body;
if (body != null)
{
Light.Rotation = body.Dir > 0.0f ? body.DrawRotation : body.DrawRotation - MathHelper.Pi;
Light.LightSpriteEffect = (body.Dir > 0.0f) ? SpriteEffects.None : SpriteEffects.FlipVertically;
}
else
{
Light.Rotation = -Rotation - MathHelper.ToRadians(item.Rotation);
Light.LightSpriteEffect = item.SpriteEffects;
}
}
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1) public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{ {
if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled) if (Light.LightSprite != null && (item.body == null || item.body.Enabled) && lightBrightness > 0.0f && IsOn && Light.Enabled)
@@ -71,7 +101,7 @@ namespace Barotrauma.Items.Components
/// <summary> /// <summary>
/// Reset client-side prediction of the light's state to the last known state sent by the server after resetPredictionTimer runs out /// Reset client-side prediction of the light's state to the last known state sent by the server after resetPredictionTimer runs out
/// </summary> /// </summary>
private IEnumerable<object> ResetPredictionAfterDelay() private IEnumerable<CoroutineStatus> ResetPredictionAfterDelay()
{ {
while (resetPredictionTimer > 0.0f) while (resetPredictionTimer > 0.0f)
{ {
@@ -259,7 +259,9 @@ namespace Barotrauma.Items.Components
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
SetActive(msg.ReadBoolean()); ushort userID = msg.ReadUInt16();
Character user = userID == Entity.NullEntityID ? null : Entity.FindEntityByID(userID) as Character;
SetActive(msg.ReadBoolean(), user);
progressTimer = msg.ReadSingle(); progressTimer = msg.ReadSingle();
} }
} }
@@ -329,8 +329,6 @@ namespace Barotrauma.Items.Components
var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count()); var missingCounts = missingItems.GroupBy(missingItem => missingItem).ToDictionary(x => x.Key, x => x.Count());
missingItems = missingItems.Distinct().ToList(); missingItems = missingItems.Distinct().ToList();
var availableIngredients = GetAvailableIngredients();
foreach (FabricationRecipe.RequiredItem requiredItem in missingItems) foreach (FabricationRecipe.RequiredItem requiredItem in missingItems)
{ {
while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null) while (slotIndex < inputContainer.Capacity && inputContainer.Inventory.GetItemAt(slotIndex) != null)
@@ -341,23 +339,23 @@ namespace Barotrauma.Items.Components
requiredItem.ItemPrefabs requiredItem.ItemPrefabs
.Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier)) .Where(requiredPrefab => availableIngredients.ContainsKey(requiredPrefab.Identifier))
.ForEach(requiredPrefab => { .ForEach(requiredPrefab => {
var availablePrefabs = availableIngredients[requiredPrefab.Identifier]; var availableItems = availableIngredients[requiredPrefab.Identifier];
foreach (Item it in availableItems)
availablePrefabs {
.Where(availablePrefab => availablePrefab.ParentInventory != inputContainer.Inventory) if (it.ParentInventory == inputContainer.Inventory) { continue; }
.Where(availablePrefab => availablePrefab.ParentInventory.visualSlots != null) //slots are null if the inventory has never been displayed var rootContainer = it.GetRootContainer();
.ForEach(availablePrefab => { //(linked item, but the UI is not set to be displayed at the same time) if (rootContainer?.OwnInventory?.visualSlots == null) { continue; }
int availableSlotIndex = availablePrefab.ParentInventory.FindIndex(availablePrefab); int availableSlotIndex = rootContainer.OwnInventory.FindIndex(it.Container == rootContainer ? it : it.Container);
if (availableSlotIndex < 0) { continue; }
if (availablePrefab.ParentInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f) if (rootContainer.OwnInventory.visualSlots[availableSlotIndex].HighlightTimer <= 0.0f)
{
rootContainer.OwnInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
if (slotIndex < inputContainer.Capacity)
{ {
availablePrefab.ParentInventory.visualSlots[availableSlotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f); inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
if (slotIndex < inputContainer.Capacity)
{
inputContainer.Inventory.visualSlots[slotIndex].ShowBorderHighlight(GUI.Style.Green, 0.5f, 0.5f, 0.2f);
}
} }
}); }
}
}); });
if (slotIndex >= inputContainer.Capacity) { break; } if (slotIndex >= inputContainer.Capacity) { break; }
@@ -676,7 +674,17 @@ namespace Barotrauma.Items.Components
activateButton.Enabled = false; activateButton.Enabled = false;
inSufficientPowerWarning.Visible = currPowerConsumption > 0 && !hasPower; inSufficientPowerWarning.Visible = currPowerConsumption > 0 && !hasPower;
var availableIngredients = GetAvailableIngredients(); if (!IsActive)
{
//only check ingredients if the fabricator isn't active (if it is, this is done in Update)
if (refreshIngredientsTimer <= 0.0f)
{
RefreshAvailableIngredients();
refreshIngredientsTimer = RefreshIngredientsInterval;
}
refreshIngredientsTimer -= deltaTime;
}
if (character != null) if (character != null)
{ {
foreach (GUIComponent child in itemList.Content.Children) foreach (GUIComponent child in itemList.Content.Children)
@@ -183,6 +183,7 @@ namespace Barotrauma.Items.Components
private ImmutableDictionary<MapEntity, MiniMapGUIComponent> electricalMapComponents; private ImmutableDictionary<MapEntity, MiniMapGUIComponent> electricalMapComponents;
private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> electricalChildren; private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> electricalChildren;
private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> doorChildren; private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> doorChildren;
private ImmutableDictionary<MiniMapGUIComponent, GUIComponent> weaponChildren;
private ImmutableHashSet<ItemPrefab>? itemsFoundOnSub; private ImmutableHashSet<ItemPrefab>? itemsFoundOnSub;
@@ -366,8 +367,8 @@ namespace Barotrauma.Items.Components
hullInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)), style: "GUIToolTip") hullInfoFrame = new GUIFrame(new RectTransform(new Vector2(0.13f, 0.13f), GUI.Canvas, minSize: new Point(250, 150)), style: "GUIToolTip")
{ {
CanBeFocused = false CanBeFocused = false,
Visible = false
}; };
var hullInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), hullInfoFrame.RectTransform, Anchor.Center)) var hullInfoContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.9f), hullInfoFrame.RectTransform, Anchor.Center))
@@ -431,7 +432,7 @@ namespace Barotrauma.Items.Components
scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center)); scissorComponent = new GUIScissorComponent(new RectTransform(Vector2.One, submarineContainer.RectTransform, Anchor.Center));
miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false }; miniMapContainer = new GUIFrame(new RectTransform(Vector2.One, scissorComponent.Content.RectTransform, Anchor.Center), style: null) { CanBeFocused = false };
ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && it.GetComponent<Door>() != null).ToImmutableHashSet(); ImmutableHashSet<Item> hullPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.Prefab.ShowInStatusMonitor && (it.GetComponent<Door>() != null || it.GetComponent<Turret>() != null)).ToImmutableHashSet();
miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents); miniMapFrame = CreateMiniMap(item.Submarine, submarineContainer, MiniMapSettings.Default, hullPointsOfInterest, out hullStatusComponents);
IEnumerable<Item> electrialPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null); IEnumerable<Item> electrialPointsOfInterest = Item.ItemList.Where(it => it.Submarine == item.Submarine && !it.HiddenInGame && !it.NonInteractable && it.GetComponent<Repairable>() != null);
@@ -460,29 +461,63 @@ namespace Barotrauma.Items.Components
electricalChildren = electricChildren.ToImmutableDictionary(); electricalChildren = electricChildren.ToImmutableDictionary();
Dictionary<MiniMapGUIComponent, GUIComponent> doorChilds = new Dictionary<MiniMapGUIComponent, GUIComponent>(); Dictionary<MiniMapGUIComponent, GUIComponent> doorChilds = new Dictionary<MiniMapGUIComponent, GUIComponent>();
Dictionary<MiniMapGUIComponent, GUIComponent> weaponChilds = new Dictionary<MiniMapGUIComponent, GUIComponent>();
foreach (var (entity, component) in hullStatusComponents) foreach (var (entity, component) in hullStatusComponents)
{ {
if (!hullPointsOfInterest.Contains(entity)) { continue; } if (!hullPointsOfInterest.Contains(entity)) { continue; }
const int minSize = 8; if (!(entity is Item it)) { continue; }
const int borderMaxSize = 2; const int borderMaxSize = 2;
Point size = component.BorderComponent.Rect.Size; if (it.GetComponent<Door>() is { })
size.X = Math.Max(size.X, minSize);
size.Y = Math.Max(size.Y, minSize);
float width = Math.Min(borderMaxSize, Math.Min(size.X, size.Y) / 8f);
GUIFrame frame = new GUIFrame(new RectTransform(size, component.RectComponent.RectTransform, anchor: Anchor.Center), style: "ScanLines", color: DoorIndicatorColor)
{ {
OutlineColor = GUI.Style.Green, const int minSize = 8;
OutlineThickness = width
}; Point size = component.BorderComponent.Rect.Size;
doorChilds.Add(component, frame);
size.X = Math.Max(size.X, minSize);
size.Y = Math.Max(size.Y, minSize);
float width = Math.Min(borderMaxSize, Math.Min(size.X, size.Y) / 8f);
GUIFrame frame = new GUIFrame(new RectTransform(size, component.RectComponent.RectTransform, anchor: Anchor.Center), style: "ScanLines", color: DoorIndicatorColor)
{
OutlineColor = DoorIndicatorColor,
OutlineThickness = width
};
doorChilds.Add(component, frame);
}
else if (it.GetComponent<Turret>() is { } turret)
{
int parentWidth = (int) (submarineContainer.Rect.Width / 16f);
GUICustomComponent frame = new GUICustomComponent(new RectTransform(new Point(parentWidth, parentWidth), component.RectComponent.RectTransform, anchor: Anchor.Center), (batch, customComponent) =>
{
Vector2 center = customComponent.Center;
float rotation = turret.Rotation;
if (!hasPower)
{
float minRotation = MathHelper.ToRadians(Math.Min(turret.RotationLimits.X, turret.RotationLimits.Y)),
maxRotation = MathHelper.ToRadians(Math.Max(turret.RotationLimits.X, turret.RotationLimits.Y));
rotation = (minRotation + maxRotation) / 2;
}
if (turret.WeaponIndicatorSprite is { } weaponSprite)
{
Vector2 origin = weaponSprite.Origin;
float scale = parentWidth / Math.Max(weaponSprite.size.X, weaponSprite.size.Y);
Color color = !hasPower ? NoPowerColor : turret.ActiveUser is null ? GUI.Style.Red : GUI.Style.Green;
weaponSprite.Draw(batch, center, color, origin, rotation, scale, it.SpriteEffects);
}
});
weaponChilds.Add(component, frame);
}
} }
doorChildren = doorChilds.ToImmutableDictionary(); doorChildren = doorChilds.ToImmutableDictionary();
weaponChildren = weaponChilds.ToImmutableDictionary();
Rectangle parentRect = miniMapFrame.Rect; Rectangle parentRect = miniMapFrame.Rect;
@@ -637,44 +672,61 @@ namespace Barotrauma.Items.Components
return; return;
} }
if (currentMode == MiniMapMode.HullStatus) if (currentMode == MiniMapMode.HullStatus && item.Submarine != null)
{ {
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.End(); spriteBatch.End();
spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, samplerState: GUI.SamplerState, rasterizerState: GameMain.ScissorTestEnable);
spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect; spriteBatch.GraphicsDevice.ScissorRectangle = submarineContainer.Rect;
if (item.Submarine != null) var sprite = GUI.Style.UIGlowSolidCircular?.Sprite;
float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f;
if (sprite != null)
{ {
var sprite = GUI.Style.UIGlowSolidCircular?.Sprite; Vector2 spriteSize = sprite.size;
float alpha = (MathF.Sin(blipState / maxBlipState * MathHelper.TwoPi) + 1.5f) * 0.5f; Rectangle worldBorders = item.Submarine.GetDockedBorders();
if (sprite != null) worldBorders.Location += item.Submarine.WorldPosition.ToPoint();
foreach (Gap gap in Gap.GapList)
{ {
Vector2 spriteSize = sprite.size; if (gap.IsRoomToRoom || gap.linkedTo.Count == 0 || gap.Submarine != item.Submarine || gap.ConnectedDoor != null || gap.HiddenInGame) { continue; }
Rectangle worldBorders = item.Submarine.GetDockedBorders(); RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders);
worldBorders.Location += item.Submarine.WorldPosition.ToPoint();
foreach (Gap gap in Gap.GapList)
{
if (gap.IsRoomToRoom || gap.Submarine != item.Submarine || gap.ConnectedDoor != null) { continue; }
RectangleF entityRect = ScaleRectToUI(gap, miniMapFrame.Rect, worldBorders);
Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f; Vector2 scale = new Vector2(entityRect.Size.X / spriteSize.X, entityRect.Size.Y / spriteSize.Y) * 2.0f;
Color color = ToolBox.GradientLerp(gap.Open, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorLow) * alpha; Color color = ToolBox.GradientLerp(gap.Open, GUI.Style.HealthBarColorMedium, GUI.Style.HealthBarColorLow) * alpha;
sprite.Draw(spriteBatch, sprite.Draw(spriteBatch,
miniMapFrame.Rect.Location.ToVector2() + entityRect.Center, miniMapFrame.Rect.Location.ToVector2() + entityRect.Center,
color, origin: sprite.Origin, rotate: 0.0f, scale: scale); color, origin: sprite.Origin, rotate: 0.0f, scale: scale);
}
} }
} }
if (currentMode == MiniMapMode.HullStatus) if (currentMode == MiniMapMode.HullStatus && hullStatusComponents != null)
{ {
foreach (var (entity, component) in hullStatusComponents) foreach (var (entity, component) in hullStatusComponents)
{ {
if (!(entity is Hull hull)) { continue; } if (!(entity is Hull hull)) { continue; }
if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; } if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; }
DrawHullCards(spriteBatch, hull, hullData, component.RectComponent); DrawHullCards(spriteBatch, hull, hullData, component.RectComponent);
if (item.CurrentHull is { } currentHull && currentHull == hull)
{
Sprite pingCircle = GUI.Style.YouAreHereCircle.Sprite;
if (pingCircle is null) { continue; }
Vector2 charPos = item.WorldPosition;
Vector2 hullPos = hull.WorldRect.Location.ToVector2(),
hullSize = hull.WorldRect.Size.ToVector2();
Vector2 relativePos = (charPos - hullPos) / hullSize * component.RectComponent.Rect.Size.ToVector2();
relativePos.Y = -relativePos.Y;
float parentWidth = submarineContainer.Rect.Width / 64f;
float spriteSize = pingCircle.size.X * (parentWidth / pingCircle.size.X);
Vector2 drawPos = component.RectComponent.Rect.Location.ToVector2() + relativePos;
drawPos -= new Vector2(spriteSize, spriteSize) / 2f;
pingCircle.Draw(spriteBatch, drawPos, GUI.Style.Red * 0.8f, Vector2.Zero, 0f, parentWidth / pingCircle.size.X);
}
} }
} }
@@ -936,15 +988,47 @@ namespace Barotrauma.Items.Components
continue; continue;
} }
hullData.HullOxygenAmount = RequireOxygenDetectors ? hullData.ReceivedOxygenAmount : hull.OxygenPercentage; if (RequireOxygenDetectors)
hullData.HullWaterAmount = RequireWaterDetectors ? hullData.ReceivedWaterAmount : Math.Min(hull.WaterVolume / hull.Volume, 1.0f); {
hullData.HullOxygenAmount = hullData.ReceivedOxygenAmount;
}
else if (hullData.LinkedHulls.Any())
{
hullData.HullOxygenAmount = 0.0f;
foreach (Hull linkedHull in hullData.LinkedHulls)
{
hullData.HullOxygenAmount += linkedHull.OxygenPercentage;
}
hullData.HullOxygenAmount /= hullData.LinkedHulls.Count;
}
else
{
hullData.HullOxygenAmount = hull.OxygenPercentage;
}
if (RequireWaterDetectors)
{
hullData.HullWaterAmount = hullData.ReceivedWaterAmount;
}
else if (hullData.LinkedHulls.Any())
{
hullData.HullWaterAmount = 0.0f;
foreach (Hull linkedHull in hullData.LinkedHulls)
{
hullData.HullWaterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f);
}
hullData.HullWaterAmount /= hullData.LinkedHulls.Count;
}
else
{
hullData.HullWaterAmount = Math.Min(hull.WaterVolume / hull.Volume, 1.0f);
}
float gapOpenSum = 0.0f; float gapOpenSum = 0.0f;
if (ShowHullIntegrity) if (ShowHullIntegrity)
{ {
float amount = 1f + hullData.LinkedHulls.Count; float amount = 1f + hullData.LinkedHulls.Count;
gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => !g.IsRoomToRoom).Sum(g => g.Open) / amount; gapOpenSum = hull.ConnectedGaps.Concat(hullData.LinkedHulls.SelectMany(h => h.ConnectedGaps)).Where(g => !g.IsRoomToRoom && !g.HiddenInGame).Sum(g => g.Open) / amount;
borderColor = Color.Lerp(neutralColor, GUI.Style.Red, Math.Min(gapOpenSum, 1.0f)); borderColor = Color.Lerp(neutralColor, GUI.Style.Red, Math.Min(gapOpenSum, 1.0f));
} }
@@ -958,15 +1042,6 @@ namespace Barotrauma.Items.Components
float? oxygenAmount = hullData.HullOxygenAmount, float? oxygenAmount = hullData.HullOxygenAmount,
waterAmount = hullData.HullWaterAmount; waterAmount = hullData.HullWaterAmount;
foreach (Hull linkedHull in hullData.LinkedHulls)
{
oxygenAmount += linkedHull.OxygenPercentage;
waterAmount += Math.Min(linkedHull.WaterVolume / linkedHull.Volume, 1.0f);
}
oxygenAmount /= (hullData.LinkedHulls.Count + 1);
waterAmount /= (hullData.LinkedHulls.Count + 1);
string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty; string line1 = gapOpenSum > 0.1f ? TextManager.Get("MiniMapHullBreach") : string.Empty;
Color line1Color = GUI.Style.Red; Color line1Color = GUI.Style.Red;
@@ -1039,10 +1114,9 @@ namespace Barotrauma.Items.Components
} }
else if (it.GetComponent<PowerTransfer>() is { } powerTransfer) else if (it.GetComponent<PowerTransfer>() is { } powerTransfer)
{ {
int current = (int) -powerTransfer.CurrPowerConsumption, int current = (int)-powerTransfer.CurrPowerConsumption, load = (int)powerTransfer.PowerLoad;
load = (int) powerTransfer.PowerLoad;
line1 = TextManager.GetWithVariable("statusmonitor.junctioncurrent.tooltip", "[amount]", current.ToString()); line1 = TextManager.GetWithVariable("statusmonitor.junctionpower.tooltip", "[amount]", current.ToString(), fallBackTag: "statusmonitor.junctioncurrent.tooltip");
line2 = TextManager.GetWithVariable("statusmonitor.junctionload.tooltip", "[amount]", load.ToString()); line2 = TextManager.GetWithVariable("statusmonitor.junctionload.tooltip", "[amount]", load.ToString());
} }
@@ -1057,10 +1131,9 @@ namespace Barotrauma.Items.Components
private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container) private void DrawHUDBack(SpriteBatch spriteBatch, GUICustomComponent container)
{ {
if (item.Submarine != null) if (item.Submarine == null) { return; }
{
DrawSubmarine(spriteBatch); DrawSubmarine(spriteBatch);
}
if (Voltage < MinVoltage) { return; } if (Voltage < MinVoltage) { return; }
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
@@ -1086,38 +1159,42 @@ namespace Barotrauma.Items.Components
} }
else else
{ {
bool hullsVisible = currentMode == MiniMapMode.HullStatus; bool hullsVisible = currentMode == MiniMapMode.HullStatus && item.Submarine != null;
foreach (var (entity, component) in hullStatusComponents) if (hullStatusComponents != null)
{ {
if (!(entity is Hull hull)) { continue; } foreach (var (entity, component) in hullStatusComponents)
if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; }
if (hullData.Distort) { continue; }
GUIComponent hullFrame = component.RectComponent;
if (hullsVisible && hullData.HullWaterAmount is { } waterAmount)
{ {
if (hullFrame.Rect.Height * waterAmount > 3.0f) if (!(entity is Hull hull)) { continue; }
if (!hullDatas.TryGetValue(hull, out HullData? hullData) || hullData is null) { continue; }
if (hullData.Distort) { continue; }
GUIComponent hullFrame = component.RectComponent;
if (hullsVisible && hullData.HullWaterAmount is { } waterAmount)
{ {
RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount); if (!RequireWaterDetectors) { waterAmount = hull.WaterPercentage / 100.0f; }
if (hullFrame.Rect.Height * waterAmount > 1.0f)
const float width = 1f;
GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor);
if (!MathUtils.NearlyEqual(waterAmount, 1.0f))
{ {
Vector2 offset = new Vector2(0, width); RectangleF waterRect = new RectangleF(hullFrame.Rect.X, hullFrame.Rect.Y + hullFrame.Rect.Height * (1.0f - waterAmount), hullFrame.Rect.Width, hullFrame.Rect.Height * waterAmount);
GUI.DrawLine(spriteBatch, waterRect.Location + offset, new Vector2(waterRect.Right, waterRect.Y) + offset, HullWaterLineColor, width: width);
const float width = 1f;
GUI.DrawFilledRectangle(spriteBatch, waterRect, HullWaterColor);
if (!MathUtils.NearlyEqual(waterAmount, 1.0f))
{
Vector2 offset = new Vector2(0, width);
GUI.DrawLine(spriteBatch, waterRect.Location + offset, new Vector2(waterRect.Right, waterRect.Y) + offset, HullWaterLineColor, width: width);
}
} }
} }
}
if (hullsVisible && hullData.HullOxygenAmount is { } oxygenAmount) if (hullsVisible && hullData.HullOxygenAmount is { } oxygenAmount)
{ {
GUI.DrawRectangle(spriteBatch, hullFrame.Rect, Color.Lerp(GUI.Style.Red * 0.5f, GUI.Style.Green * 0.3f, oxygenAmount / 100.0f), true); GUI.DrawRectangle(spriteBatch, hullFrame.Rect, Color.Lerp(GUI.Style.Red * 0.5f, GUI.Style.Green * 0.3f, oxygenAmount / 100.0f), true);
}
} }
} }
} }
@@ -1221,7 +1298,7 @@ namespace Barotrauma.Items.Components
Vector2 spriteScale = new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y); Vector2 spriteScale = new Vector2(entityRect.Size.X / sprite.size.X, entityRect.Size.Y / sprite.size.Y);
Vector2 origin = new Vector2(sprite.Origin.X * spriteScale.X, sprite.Origin.Y * spriteScale.Y); Vector2 origin = new Vector2(sprite.Origin.X * spriteScale.X, sprite.Origin.Y * spriteScale.Y);
if (item.GetComponent<Turret>() is { } turret) if (!item.Prefab.ShowInStatusMonitor && item.GetComponent<Turret>() is { } turret)
{ {
Vector2 drawPos = turret.GetDrawPos(); Vector2 drawPos = turret.GetDrawPos();
drawPos.Y = -drawPos.Y; drawPos.Y = -drawPos.Y;
@@ -1361,7 +1438,7 @@ namespace Barotrauma.Items.Components
{ {
if (linkedEntity is Hull linkedHull) if (linkedEntity is Hull linkedHull)
{ {
if (linkedHulls.Contains(linkedHull)) { continue; } if (linkedHulls.Contains(linkedHull) || linkedHull.HiddenInGame) { continue; }
linkedHulls.Add(linkedHull); linkedHulls.Add(linkedHull);
GetLinkedHulls(linkedHull, linkedHulls); GetLinkedHulls(linkedHull, linkedHulls);
} }
@@ -1541,7 +1618,7 @@ namespace Barotrauma.Items.Components
bool IsPartofSub(MapEntity entity) bool IsPartofSub(MapEntity entity)
{ {
if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine)) { return false; } if (entity.Submarine != sub && !connectedSubs.Contains(entity.Submarine) || entity.HiddenInGame) { return false; }
return !settings.IgnoreOutposts || sub.IsEntityFoundOnThisSub(entity, true); return !settings.IgnoreOutposts || sub.IsEntityFoundOnThisSub(entity, true);
} }
@@ -158,7 +158,7 @@ namespace Barotrauma.Items.Components
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), columnLeft.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.01f), columnLeft.RectTransform), style: "HorizontalLine");
float relativeYMargin = 0.02f; float relativeYMargin = 0.02f;
Vector2 relativeTextSize = new Vector2(0.9f, 0.2f); Vector2 relativeTextSize = new Vector2(0.9f, 0.15f);
Vector2 sliderSize = new Vector2(1.0f, 0.125f); Vector2 sliderSize = new Vector2(1.0f, 0.125f);
Vector2 meterSize = new Vector2(1, 1 - relativeTextSize.Y - relativeYMargin - sliderSize.Y - 0.1f); Vector2 meterSize = new Vector2(1, 1 - relativeTextSize.Y - relativeYMargin - sliderSize.Y - 0.1f);
@@ -198,7 +198,7 @@ namespace Barotrauma.Items.Components
FissionRateScrollBar = new GUIScrollBar(new RectTransform(sliderSize, leftArea.RectTransform, Anchor.TopCenter) FissionRateScrollBar = new GUIScrollBar(new RectTransform(sliderSize, leftArea.RectTransform, Anchor.TopCenter)
{ {
RelativeOffset = new Vector2(0, fissionMeter.RectTransform.RelativeOffset.Y + meterSize.Y) RelativeOffset = new Vector2(0, fissionMeter.RectTransform.RelativeOffset.Y + meterSize.Y + relativeYMargin)
}, },
style: "DeviceSlider", barSize: 0.15f) style: "DeviceSlider", barSize: 0.15f)
{ {
@@ -208,7 +208,7 @@ namespace Barotrauma.Items.Components
{ {
LastUser = Character.Controlled; LastUser = Character.Controlled;
unsentChanges = true; unsentChanges = true;
targetFissionRate = scrollAmount * 100.0f; TargetFissionRate = scrollAmount * 100.0f;
return false; return false;
} }
@@ -216,7 +216,7 @@ namespace Barotrauma.Items.Components
TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter) TurbineOutputScrollBar = new GUIScrollBar(new RectTransform(sliderSize, rightArea.RectTransform, Anchor.TopCenter)
{ {
RelativeOffset = new Vector2(0, turbineMeter.RectTransform.RelativeOffset.Y + meterSize.Y) RelativeOffset = new Vector2(0, turbineMeter.RectTransform.RelativeOffset.Y + meterSize.Y + relativeYMargin)
}, },
style: "DeviceSlider", barSize: 0.15f, isHorizontal: true) style: "DeviceSlider", barSize: 0.15f, isHorizontal: true)
{ {
@@ -226,7 +226,7 @@ namespace Barotrauma.Items.Components
{ {
LastUser = Character.Controlled; LastUser = Character.Controlled;
unsentChanges = true; unsentChanges = true;
targetTurbineOutput = scrollAmount * 100.0f; TargetTurbineOutput = scrollAmount * 100.0f;
return false; return false;
} }
@@ -370,7 +370,7 @@ namespace Barotrauma.Items.Components
}; };
string loadStr = TextManager.Get("ReactorLoad"); string loadStr = TextManager.Get("ReactorLoad");
string kW = TextManager.Get("kilowatt"); string kW = TextManager.Get("kilowatt");
loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)load).ToString())} {kW}"; loadText.TextGetter += () => $"{loadStr.Replace("[kw]", ((int)Load).ToString())} {kW}";
var graph = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed"); var graph = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.9f), graphArea.RectTransform), style: "InnerFrameRed");
new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graph.RectTransform, Anchor.Center), DrawGraph, null); new GUICustomComponent(new RectTransform(new Vector2(0.9f, 0.98f), graph.RectTransform, Anchor.Center), DrawGraph, null);
@@ -387,8 +387,8 @@ namespace Barotrauma.Items.Components
public override void OnItemLoaded() public override void OnItemLoaded()
{ {
base.OnItemLoaded(); base.OnItemLoaded();
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f; TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f; FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
var itemContainer = item.GetComponent<ItemContainer>(); var itemContainer = item.GetComponent<ItemContainer>();
if (itemContainer != null) if (itemContainer != null)
{ {
@@ -462,7 +462,7 @@ namespace Barotrauma.Items.Components
if (graphTimer > updateGraphInterval) if (graphTimer > updateGraphInterval)
{ {
UpdateGraph(outputGraph, -currPowerConsumption); UpdateGraph(outputGraph, -currPowerConsumption);
UpdateGraph(loadGraph, load); UpdateGraph(loadGraph, Load);
graphTimer = 0.0f; graphTimer = 0.0f;
} }
@@ -487,7 +487,7 @@ namespace Barotrauma.Items.Components
float jitter = 0.0f; float jitter = 0.0f;
if (FissionRate > allowedFissionRate.Y - 5.0f) if (FissionRate > allowedFissionRate.Y - 5.0f)
{ {
float jitterAmount = Math.Min(targetFissionRate - allowedFissionRate.Y, 10.0f); float jitterAmount = Math.Min(TargetFissionRate - allowedFissionRate.Y, 10.0f);
float t = graphTimer / updateGraphInterval; float t = graphTimer / updateGraphInterval;
jitter = (PerlinNoise.GetPerlin(t * 0.5f, t * 0.1f) - 0.5f) * jitterAmount; jitter = (PerlinNoise.GetPerlin(t * 0.5f, t * 0.1f) - 0.5f) * jitterAmount;
@@ -525,12 +525,12 @@ namespace Barotrauma.Items.Components
criticalHeatWarning.Selected = temperature > allowedTemperature.Y && lightOn; criticalHeatWarning.Selected = temperature > allowedTemperature.Y && lightOn;
lowTemperatureWarning.Selected = temperature < allowedTemperature.X && lightOn; lowTemperatureWarning.Selected = temperature < allowedTemperature.X && lightOn;
criticalOutputWarning.Selected = -currPowerConsumption > load * 1.5f && lightOn; criticalOutputWarning.Selected = -currPowerConsumption > Load * 1.5f && lightOn;
warningButtons["ReactorWarningOverheating"].Selected = temperature > optimalTemperature.Y && lightOn; warningButtons["ReactorWarningOverheating"].Selected = temperature > optimalTemperature.Y && lightOn;
warningButtons["ReactorWarningHighOutput"].Selected = -currPowerConsumption > load * 1.1f && lightOn; warningButtons["ReactorWarningHighOutput"].Selected = -currPowerConsumption > Load * 1.1f && lightOn;
warningButtons["ReactorWarningLowTemp"].Selected = temperature < optimalTemperature.X && lightOn; warningButtons["ReactorWarningLowTemp"].Selected = temperature < optimalTemperature.X && lightOn;
warningButtons["ReactorWarningLowOutput"].Selected = -currPowerConsumption < load * 0.9f && lightOn; warningButtons["ReactorWarningLowOutput"].Selected = -currPowerConsumption < Load * 0.9f && lightOn;
warningButtons["ReactorWarningFuelOut"].Selected = prevAvailableFuel < fissionRate * 0.01f && lightOn; warningButtons["ReactorWarningFuelOut"].Selected = prevAvailableFuel < fissionRate * 0.01f && lightOn;
warningButtons["ReactorWarningLowFuel"].Selected = prevAvailableFuel < fissionRate && lightOn; warningButtons["ReactorWarningLowFuel"].Selected = prevAvailableFuel < fissionRate && lightOn;
warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn; warningButtons["ReactorWarningMeltdown"].Selected = meltDownTimer > MeltdownDelay * 0.5f || item.Condition == 0.0f && lightOn;
@@ -571,12 +571,12 @@ namespace Barotrauma.Items.Components
unsentChanges = true; unsentChanges = true;
if (input.X != 0.0f && GUIScrollBar.DraggingBar != FissionRateScrollBar) if (input.X != 0.0f && GUIScrollBar.DraggingBar != FissionRateScrollBar)
{ {
targetFissionRate = MathHelper.Clamp(targetFissionRate + input.X, 0.0f, 100.0f); TargetFissionRate = MathHelper.Clamp(TargetFissionRate + input.X, 0.0f, 100.0f);
FissionRateScrollBar.BarScroll += input.X / 100.0f; FissionRateScrollBar.BarScroll += input.X / 100.0f;
} }
if (input.Y != 0.0f && GUIScrollBar.DraggingBar != TurbineOutputScrollBar) if (input.Y != 0.0f && GUIScrollBar.DraggingBar != TurbineOutputScrollBar)
{ {
targetTurbineOutput = MathHelper.Clamp(targetTurbineOutput + input.Y, 0.0f, 100.0f); TargetTurbineOutput = MathHelper.Clamp(TargetTurbineOutput + input.Y, 0.0f, 100.0f);
TurbineOutputScrollBar.BarScroll += input.Y / 100.0f; TurbineOutputScrollBar.BarScroll += input.Y / 100.0f;
} }
} }
@@ -596,7 +596,7 @@ namespace Barotrauma.Items.Components
MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f), MathHelper.Clamp((allowedRange.X - range.X) / (range.Y - range.X), 0.0f, 0.95f),
MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f)); MathHelper.Clamp((allowedRange.Y - range.X) / (range.Y - range.X), 0.0f, 1.0f));
Vector2 sectorRad = new Vector2(-1.57f, 1.57f); Vector2 sectorRad = new Vector2(-1.35f, 1.35f);
Vector2 optimalSectorRad = new Vector2( Vector2 optimalSectorRad = new Vector2(
MathHelper.Lerp(sectorRad.X, sectorRad.Y, optimalRangeNormalized.X), MathHelper.Lerp(sectorRad.X, sectorRad.Y, optimalRangeNormalized.X),
@@ -606,23 +606,25 @@ namespace Barotrauma.Items.Components
MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.X), MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.X),
MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.Y)); MathHelper.Lerp(sectorRad.X, sectorRad.Y, allowedRangeNormalized.Y));
Vector2 pointerPos = pos - new Vector2(0, 30) * scale;
if (optimalRangeNormalized.X == optimalRangeNormalized.Y) if (optimalRangeNormalized.X == optimalRangeNormalized.Y)
{ {
sectorSprite.Draw(spriteBatch, pos, GUI.Style.Red, MathHelper.PiOver2, scale); sectorSprite.Draw(spriteBatch, pointerPos, GUI.Style.Red, MathHelper.PiOver2, scale);
} }
else else
{ {
spriteBatch.End(); spriteBatch.End();
Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle; Rectangle prevScissorRect = spriteBatch.GraphicsDevice.ScissorRectangle;
spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3); spriteBatch.GraphicsDevice.ScissorRectangle = new Rectangle(0, 0, GameMain.GraphicsWidth, (int)(pointerPos.Y + (meterSprite.size.Y - meterSprite.Origin.Y) * scale) - 3);
spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable); spriteBatch.Begin(SpriteSortMode.Deferred, rasterizerState: GameMain.ScissorTestEnable);
float scaleMultiplier = 0.95f; float scaleMultiplier = 0.95f;
sectorSprite.Draw(spriteBatch, pos, optimalRangeColor, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, optimalRangeColor, MathHelper.PiOver2 + (allowedSectorRad.X + allowedSectorRad.Y) / 2.0f, scale * scaleMultiplier);
sectorSprite.Draw(spriteBatch, pos, offRangeColor, optimalSectorRad.X, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, offRangeColor, optimalSectorRad.X, scale * scaleMultiplier);
sectorSprite.Draw(spriteBatch, pos, warningColor, allowedSectorRad.X, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, warningColor, allowedSectorRad.X, scale * scaleMultiplier);
sectorSprite.Draw(spriteBatch, pos, offRangeColor, MathHelper.Pi + optimalSectorRad.Y, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, offRangeColor, MathHelper.Pi + optimalSectorRad.Y, scale * scaleMultiplier);
sectorSprite.Draw(spriteBatch, pos, warningColor, MathHelper.Pi + allowedSectorRad.Y, scale * scaleMultiplier); sectorSprite.Draw(spriteBatch, pointerPos, warningColor, MathHelper.Pi + allowedSectorRad.Y, scale * scaleMultiplier);
spriteBatch.End(); spriteBatch.End();
spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect; spriteBatch.GraphicsDevice.ScissorRectangle = prevScissorRect;
@@ -634,7 +636,7 @@ namespace Barotrauma.Items.Components
float normalizedValue = (value - range.X) / (range.Y - range.X); float normalizedValue = (value - range.X) / (range.Y - range.X);
float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue); float valueRad = MathHelper.Lerp(sectorRad.X, sectorRad.Y, normalizedValue);
Vector2 offset = new Vector2(0, 40) * scale; Vector2 offset = new Vector2(0, 40) * scale;
meterPointer.Draw(spriteBatch, pos - offset, valueRad, scale); meterPointer.Draw(spriteBatch, pointerPos, valueRad, scale);
} }
static void UpdateGraph<T>(IList<T> graph, T newValue) static void UpdateGraph<T>(IList<T> graph, T newValue)
@@ -713,8 +715,8 @@ namespace Barotrauma.Items.Components
{ {
msg.Write(autoTemp); msg.Write(autoTemp);
msg.Write(PowerOn); msg.Write(PowerOn);
msg.WriteRangedSingle(targetFissionRate, 0.0f, 100.0f, 8); msg.WriteRangedSingle(TargetFissionRate, 0.0f, 100.0f, 8);
msg.WriteRangedSingle(targetTurbineOutput, 0.0f, 100.0f, 8); msg.WriteRangedSingle(TargetTurbineOutput, 0.0f, 100.0f, 8);
correctionTimer = CorrectionDelay; correctionTimer = CorrectionDelay;
} }
@@ -730,17 +732,17 @@ namespace Barotrauma.Items.Components
AutoTemp = msg.ReadBoolean(); AutoTemp = msg.ReadBoolean();
PowerOn = msg.ReadBoolean(); PowerOn = msg.ReadBoolean();
Temperature = msg.ReadRangedSingle(0.0f, 100.0f, 8); Temperature = msg.ReadRangedSingle(0.0f, 100.0f, 8);
targetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8); TargetFissionRate = msg.ReadRangedSingle(0.0f, 100.0f, 8);
targetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8); TargetTurbineOutput = msg.ReadRangedSingle(0.0f, 100.0f, 8);
degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8); degreeOfSuccess = msg.ReadRangedSingle(0.0f, 1.0f, 8);
if (Math.Abs(FissionRateScrollBar.BarScroll - targetFissionRate / 100.0f) > 0.01f) if (Math.Abs(FissionRateScrollBar.BarScroll - TargetFissionRate / 100.0f) > 0.01f)
{ {
FissionRateScrollBar.BarScroll = targetFissionRate / 100.0f; FissionRateScrollBar.BarScroll = TargetFissionRate / 100.0f;
} }
if (Math.Abs(TurbineOutputScrollBar.BarScroll - targetTurbineOutput / 100.0f) > 0.01f) if (Math.Abs(TurbineOutputScrollBar.BarScroll - TargetTurbineOutput / 100.0f) > 0.01f)
{ {
TurbineOutputScrollBar.BarScroll = targetTurbineOutput / 100.0f; TurbineOutputScrollBar.BarScroll = TargetTurbineOutput / 100.0f;
} }
IsActive = true; IsActive = true;
@@ -456,7 +456,7 @@ namespace Barotrauma.Items.Components
} }
} }
float distort = 1.0f - item.Condition / item.MaxCondition; float distort = MathHelper.Clamp(1.0f - item.Condition / item.MaxCondition, 0.0f, 1.0f);
for (int i = sonarBlips.Count - 1; i >= 0; i--) for (int i = sonarBlips.Count - 1; i >= 0; i--)
{ {
sonarBlips[i].FadeTimer -= deltaTime * MathHelper.Lerp(0.5f, 2.0f, distort); sonarBlips[i].FadeTimer -= deltaTime * MathHelper.Lerp(0.5f, 2.0f, distort);
@@ -1623,6 +1623,11 @@ namespace Barotrauma.Items.Components
markerDistances.Add(targetIdentifier, cachedDistance); markerDistances.Add(targetIdentifier, cachedDistance);
dist = path.TotalLength; dist = path.TotalLength;
} }
else
{
var cachedDistance = new CachedDistance(transducerPosition, worldPosition, linearDist, Timing.TotalTime + Rand.Range(4.0f, 7.0f));
markerDistances.Add(targetIdentifier, cachedDistance);
}
} }
Vector2 position = worldPosition - transducerPosition; Vector2 position = worldPosition - transducerPosition;
@@ -35,6 +35,13 @@ namespace Barotrauma.Items.Components
private FixActions requestStartFixAction; private FixActions requestStartFixAction;
private bool qteSuccess;
private float qteTimer;
private const float QteDuration = 0.5f;
private float qteCooldown;
private const float QteCooldownDuration = 0.5f;
public float FakeBrokenTimer; public float FakeBrokenTimer;
[Serialize("", false, description: "An optional description of the needed repairs displayed in the repair interface.")] [Serialize("", false, description: "An optional description of the needed repairs displayed in the repair interface.")]
@@ -55,14 +62,13 @@ namespace Barotrauma.Items.Components
if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; } if (!HasRequiredItems(character, false) || character.SelectedConstruction != item) { return false; }
if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; } if (character.IsTraitor && item.ConditionPercentage > MinSabotageCondition) { return true; }
float maxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(character); float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
if (item.Condition / maxRepairConditionMultiplier < RepairThreshold) { return true; }
if (MathUtils.Percentage(item.Condition, defaultMaxCondition) < RepairThreshold) { return true; }
if (CurrentFixer == character) if (CurrentFixer == character)
{ {
float condition = item.Condition / item.MaxRepairConditionMultiplier; if (item.Condition < item.MaxCondition)
float maxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
if (condition < maxCondition * maxRepairConditionMultiplier)
{ {
return true; return true;
} }
@@ -143,11 +149,14 @@ namespace Barotrauma.Items.Components
progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform), progressBar = new GUIProgressBar(new RectTransform(new Vector2(0.6f, 1.0f), progressBarHolder.RectTransform),
color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar"); color: GUI.Style.Green, barSize: 0.0f, style: "DeviceProgressBar");
progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.Center) progressBarOverlayText = new GUITextBlock(new RectTransform(Vector2.One, progressBar.RectTransform), string.Empty, font: GUI.SubHeadingFont, textAlignment: Alignment.Center)
{ {
IgnoreLayoutGroups = true IgnoreLayoutGroups = true
}; };
qteTimer = QteDuration;
repairButtonText = TextManager.Get("RepairButton"); repairButtonText = TextManager.Get("RepairButton");
repairingText = TextManager.Get("Repairing"); repairingText = TextManager.Get("Repairing");
RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText) RepairButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1.0f), progressBarHolder.RectTransform, Anchor.TopCenter), repairButtonText)
@@ -157,6 +166,11 @@ namespace Barotrauma.Items.Components
requestStartFixAction = FixActions.Repair; requestStartFixAction = FixActions.Repair;
item.CreateClientEvent(this); item.CreateClientEvent(this);
return true; return true;
},
OnButtonDown = () =>
{
QTEAction();
return true;
} }
}; };
RepairButton.TextBlock.AutoScaleHorizontal = true; RepairButton.TextBlock.AutoScaleHorizontal = true;
@@ -181,6 +195,11 @@ namespace Barotrauma.Items.Components
requestStartFixAction = FixActions.Sabotage; requestStartFixAction = FixActions.Sabotage;
item.CreateClientEvent(this); item.CreateClientEvent(this);
return true; return true;
},
OnButtonDown = () =>
{
QTEAction();
return true;
} }
}; };
@@ -251,6 +270,20 @@ namespace Barotrauma.Items.Components
{ {
repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull); repairSoundChannel = SoundPlayer.PlaySound("repair", item.WorldPosition, hullGuess: item.CurrentHull);
} }
if (qteCooldown > 0.0f)
{
qteCooldown -= deltaTime;
if (qteCooldown <= 0.0f)
{
qteTimer = QteDuration;
}
}
else
{
qteTimer -= deltaTime * (qteTimer / QteDuration);
if (qteTimer < 0.0f) { qteTimer = QteDuration; }
}
} }
else else
{ {
@@ -268,6 +301,26 @@ namespace Barotrauma.Items.Components
progressBar.BarSize = item.Condition / defaultMaxCondition; progressBar.BarSize = item.Condition / defaultMaxCondition;
progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green); progressBar.Color = ToolBox.GradientLerp(progressBar.BarSize, GUI.Style.Red, GUI.Style.Orange, GUI.Style.Green);
Rectangle sliderRect = progressBar.GetSliderRect(1.0f);
Color qteSliderColor = Color.White;
if (qteCooldown > 0.0f)
{
qteSliderColor = qteSuccess ? GUI.Style.Green : GUI.Style.Red * 0.5f;
progressBar.Color = ToolBox.GradientLerp(qteCooldown / QteCooldownDuration, progressBar.Color, qteSliderColor, Color.White);
}
else
{
if (qteTimer / QteDuration <= item.Condition / item.MaxCondition)
{
qteSliderColor = Color.Lerp(qteSliderColor, GUI.Style.Green, 0.5f);
}
}
progressBar.Parent.Parent.Parent.DrawManually(spriteBatch, true);
GUI.DrawRectangle(spriteBatch,
new Rectangle(sliderRect.X + (int)((qteTimer / QteDuration) * sliderRect.Width), sliderRect.Y - 5, 2, sliderRect.Height + 10),
qteSliderColor, true);
if (item.Condition > defaultMaxCondition) if (item.Condition > defaultMaxCondition)
{ {
float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f); float extraCondition = item.MaxCondition * (item.MaxRepairConditionMultiplier - 1.0f);
@@ -280,9 +333,9 @@ namespace Barotrauma.Items.Components
progressBarOverlayText.Visible = false; progressBarOverlayText.Visible = false;
} }
RepairButton.Enabled = (currentFixerAction == FixActions.None || (CurrentFixer == character && currentFixerAction != FixActions.Repair)) && !item.IsFullCondition && item.ConditionPercentage < RepairThreshold; RepairButton.Enabled = (currentFixerAction == FixActions.None || CurrentFixer == character) && !item.IsFullCondition;
RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ? RepairButton.Text = (currentFixerAction == FixActions.None || CurrentFixer != character || currentFixerAction != FixActions.Repair) ?
repairButtonText : repairButtonText :
repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1); repairingText + new string('.', ((int)(Timing.TotalTime * 2.0f) % 3) + 1);
SabotageButton.Visible = character.IsTraitor; SabotageButton.Visible = character.IsTraitor;
@@ -350,6 +403,30 @@ namespace Barotrauma.Items.Components
repairSoundChannel = null; repairSoundChannel = null;
} }
private void QTEAction()
{
if (currentFixerAction == FixActions.Repair)
{
float defaultMaxCondition = item.MaxCondition / item.MaxRepairConditionMultiplier;
qteSuccess = qteCooldown <= 0.0f && qteTimer / QteDuration <= item.Condition / defaultMaxCondition;
}
else
{
return;
}
if (!GameMain.IsMultiplayer) { RepairBoost(qteSuccess); }
SoundPlayer.PlayUISound(qteSuccess ? GUISoundType.IncreaseQuantity : GUISoundType.DecreaseQuantity);
//on failure during cooldown reset cursor to beginning
if (!qteSuccess && qteCooldown > 0.0f) { qteTimer = QteDuration; }
qteCooldown = QteCooldownDuration;
//this will be set on button down so we can reset it here
requestStartFixAction = FixActions.None;
item.CreateClientEvent(this);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
deteriorationTimer = msg.ReadSingle(); deteriorationTimer = msg.ReadSingle();
@@ -361,11 +438,17 @@ namespace Barotrauma.Items.Components
currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2); currentFixerAction = (FixActions)msg.ReadRangedInteger(0, 2);
CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null; CurrentFixer = currentFixerID != 0 ? Entity.FindEntityByID(currentFixerID) as Character : null;
item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer); item.MaxRepairConditionMultiplier = GetMaxRepairConditionMultiplier(CurrentFixer);
if (CurrentFixer == null)
{
qteTimer = QteDuration;
qteCooldown = 0.0f;
}
} }
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.WriteRangedInteger((int)requestStartFixAction, 0, 2); msg.WriteRangedInteger((int)requestStartFixAction, 0, 2);
msg.Write(qteSuccess);
} }
} }
} }
@@ -26,16 +26,11 @@ namespace Barotrauma.Items.Components
AutoHideScrollBar = false AutoHideScrollBar = false
}; };
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox CreateFillerBlock();
// This could be removed if GUIListBox supported aligning its children
fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty)
{
CanBeFocused = false
};
new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(0.9f, 0.01f), layoutGroup.RectTransform), style: "HorizontalLine");
inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: Color.LimeGreen) inputBox = new GUITextBox(new RectTransform(new Vector2(1, .1f), layoutGroup.RectTransform), textColor: TextColor)
{ {
MaxTextLength = MaxMessageLength, MaxTextLength = MaxMessageLength,
OverflowClip = true, OverflowClip = true,
@@ -55,6 +50,16 @@ namespace Barotrauma.Items.Components
}; };
} }
// Create fillerBlock to cover historyBox so new values appear at the bottom of historyBox
// This could be removed if GUIListBox supported aligning its children
public void CreateFillerBlock()
{
fillerBlock = new GUITextBlock(new RectTransform(new Vector2(1, 1), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), string.Empty)
{
CanBeFocused = false
};
}
private void SendOutput(string input) private void SendOutput(string input)
{ {
if (input.Length > MaxMessageLength) if (input.Length > MaxMessageLength)
@@ -63,15 +68,15 @@ namespace Barotrauma.Items.Components
} }
OutputValue = input; OutputValue = input;
ShowOnDisplay(input, addToHistory: true); ShowOnDisplay(input, addToHistory: true, TextColor);
item.SendSignal(input, "signal_out"); item.SendSignal(input, "signal_out");
} }
partial void ShowOnDisplay(string input, bool addToHistory) partial void ShowOnDisplay(string input, bool addToHistory, Color color)
{ {
if (addToHistory) if (addToHistory)
{ {
messageHistory.Add(input); messageHistory.Add(new TerminalMessage(input, color));
while (messageHistory.Count > MaxMessages) while (messageHistory.Count > MaxMessages)
{ {
messageHistory.RemoveAt(0); messageHistory.RemoveAt(0);
@@ -85,7 +90,7 @@ namespace Barotrauma.Items.Components
GUITextBlock newBlock = new GUITextBlock( GUITextBlock newBlock = new GUITextBlock(
new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter), new RectTransform(new Vector2(1, 0), historyBox.Content.RectTransform, anchor: Anchor.TopCenter),
"> " + input, "> " + input,
textColor: Color.LimeGreen, wrap: true, font: UseMonospaceFont ? GUI.MonospacedFont : GUI.GlobalFont) textColor: color, wrap: true, font: UseMonospaceFont ? GUI.MonospacedFont : GUI.GlobalFont)
{ {
CanBeFocused = false CanBeFocused = false
}; };
@@ -130,7 +135,12 @@ namespace Barotrauma.Items.Components
public void ClientWrite(IWriteMessage msg, object[] extraData = null) public void ClientWrite(IWriteMessage msg, object[] extraData = null)
{ {
msg.Write((string)extraData[2]); if (extraData is null) { return; }
if (extraData[2] is string str)
{
msg.Write(str);
}
} }
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
@@ -0,0 +1,12 @@
using Barotrauma.Networking;
namespace Barotrauma.Items.Components
{
partial class TriggerComponent : ItemComponent, IServerSerializable
{
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{
CurrentForceFluctuation = msg.ReadRangedSingle(0.0f, 1.0f, 8);
}
}
}
@@ -14,6 +14,7 @@ namespace Barotrauma.Items.Components
partial class Turret : Powered, IDrawableComponent, IServerSerializable partial class Turret : Powered, IDrawableComponent, IServerSerializable
{ {
private Sprite crosshairSprite, crosshairPointerSprite; private Sprite crosshairSprite, crosshairPointerSprite;
public Sprite WeaponIndicatorSprite;
private GUIProgressBar powerIndicator; private GUIProgressBar powerIndicator;
@@ -134,6 +135,9 @@ namespace Barotrauma.Items.Components
case "crosshair": case "crosshair":
crosshairSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath)); crosshairSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
break; break;
case "weaponindicator":
WeaponIndicatorSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
break;
case "crosshairpointer": case "crosshairpointer":
crosshairPointerSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath)); crosshairPointerSprite = new Sprite(subElement, texturePath.Contains("/") ? "" : Path.GetDirectoryName(item.Prefab.FilePath));
break; break;
@@ -160,7 +160,7 @@ namespace Barotrauma.Items.Components
{ {
errorMsg += "\nTrying to dock the submarine to itself."; errorMsg += "\nTrying to dock the submarine to itself.";
} }
GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("DockingPort.ClientRead:JointNotCreated", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
} }
if (isLocked) if (isLocked)
@@ -101,7 +101,7 @@ namespace Barotrauma
private float currentHighlightState, fadeInDuration, fadeOutDuration; private float currentHighlightState, fadeInDuration, fadeOutDuration;
private Color currentHighlightColor; private Color currentHighlightColor;
private IEnumerable<object> UpdateBorderHighlight() private IEnumerable<CoroutineStatus> UpdateBorderHighlight()
{ {
HighlightTimer = 1.0f; HighlightTimer = 1.0f;
while (currentHighlightState < fadeInDuration + fadeOutDuration) while (currentHighlightState < fadeInDuration + fadeOutDuration)
@@ -314,7 +314,7 @@ namespace Barotrauma
} }
} }
string colorStr = XMLExtensions.ColorToString(!item.AllowStealing ? GUI.Style.Red : Color.White); string colorStr = XMLExtensions.ColorToString(item.SpawnedInCurrentOutpost && !item.AllowStealing ? GUI.Style.Red : Color.White);
toolTip = $"‖color:{colorStr}‖{name}‖color:end‖"; toolTip = $"‖color:{colorStr}‖{name}‖color:end‖";
if (item.GetComponent<Quality>() != null) if (item.GetComponent<Quality>() != null)
@@ -1255,9 +1255,18 @@ namespace Barotrauma
else else
{ {
bool anySuccess = false; bool anySuccess = false;
bool allowCombine = true;
//if we're dragging a stack of partial items or trying to drag to a stack of partial items
//(which should not normally exist, but can happen when e.g. fire damages a stack of items)
//don't allow combining because it leads to weird behavior (stack of items of mixed quality)
if (DraggingItems.Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1 ||
selectedInventory.GetItemsAt(slotIndex).Count(it => !it.IsFullCondition && it.Condition > 0.0f) > 1)
{
allowCombine = false;
}
foreach (Item item in DraggingItems) foreach (Item item in DraggingItems)
{ {
bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, true, Character.Controlled); bool success = selectedInventory.TryPutItem(item, slotIndex, allowSwapping: !anySuccess, allowCombine, Character.Controlled);
anySuccess |= success; anySuccess |= success;
if (!success) { break; } if (!success) { break; }
} }
@@ -1673,7 +1682,7 @@ namespace Barotrauma
} }
sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale); sprite.Draw(spriteBatch, itemPos, spriteColor, rotation, scale);
if ((!item.AllowStealing || (inventory != null && inventory.slots[slotIndex].Items.Any(it => !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand)) if (((item.SpawnedInCurrentOutpost && !item.AllowStealing) || (inventory != null && inventory.slots[slotIndex].Items.Any(it => it.SpawnedInCurrentOutpost && !it.AllowStealing))) && CharacterInventory.LimbSlotIcons.ContainsKey(InvSlotType.LeftHand))
{ {
var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand]; var stealIcon = CharacterInventory.LimbSlotIcons[InvSlotType.LeftHand];
Vector2 iconSize = new Vector2(25 * GUI.Scale); Vector2 iconSize = new Vector2(25 * GUI.Scale);
@@ -1818,7 +1827,7 @@ namespace Barotrauma
} }
} }
private IEnumerable<object> SyncItemsAfterDelay(UInt16 lastEventID) private IEnumerable<CoroutineStatus> SyncItemsAfterDelay(UInt16 lastEventID)
{ {
while (syncItemsDelay > 0.0f || while (syncItemsDelay > 0.0f ||
//don't apply inventory updates until //don't apply inventory updates until
@@ -175,12 +175,12 @@ namespace Barotrauma
} }
} }
float displayCondition = FakeBroken ? 0.0f : condition; float displayCondition = FakeBroken ? 0.0f : ConditionPercentage;
for (int i = 0; i < Prefab.BrokenSprites.Count;i++) for (int i = 0; i < Prefab.BrokenSprites.Count;i++)
{ {
if (Prefab.BrokenSprites[i].FadeIn) { continue; } if (Prefab.BrokenSprites[i].FadeIn) { continue; }
float minCondition = i > 0 ? Prefab.BrokenSprites[i - i].MaxCondition : 0.0f; float minCondition = i > 0 ? Prefab.BrokenSprites[i - i].MaxConditionPercentage : 0.0f;
if (displayCondition <= minCondition || displayCondition <= Prefab.BrokenSprites[i].MaxCondition) if (displayCondition <= minCondition || displayCondition <= Prefab.BrokenSprites[i].MaxConditionPercentage)
{ {
activeSprite = Prefab.BrokenSprites[i].Sprite; activeSprite = Prefab.BrokenSprites[i].Sprite;
break; break;
@@ -284,8 +284,8 @@ namespace Barotrauma
{ {
if (Prefab.BrokenSprites[i].FadeIn) if (Prefab.BrokenSprites[i].FadeIn)
{ {
float min = i > 0 ? Prefab.BrokenSprites[i - i].MaxCondition : 0.0f; float min = i > 0 ? Prefab.BrokenSprites[i - i].MaxConditionPercentage : 0.0f;
float max = Prefab.BrokenSprites[i].MaxCondition; float max = Prefab.BrokenSprites[i].MaxConditionPercentage;
fadeInBrokenSpriteAlpha = 1.0f - ((displayCondition - min) / (max - min)); fadeInBrokenSpriteAlpha = 1.0f - ((displayCondition - min) / (max - min));
if (fadeInBrokenSpriteAlpha > 0.0f && fadeInBrokenSpriteAlpha <= 1.0f) if (fadeInBrokenSpriteAlpha > 0.0f && fadeInBrokenSpriteAlpha <= 1.0f)
{ {
@@ -293,7 +293,7 @@ namespace Barotrauma
} }
continue; continue;
} }
if (displayCondition <= Prefab.BrokenSprites[i].MaxCondition) if (displayCondition <= Prefab.BrokenSprites[i].MaxConditionPercentage)
{ {
activeSprite = Prefab.BrokenSprites[i].Sprite; activeSprite = Prefab.BrokenSprites[i].Sprite;
drawOffset = Prefab.BrokenSprites[i].Offset.ToVector2() * Scale; drawOffset = Prefab.BrokenSprites[i].Offset.ToVector2() * Scale;
@@ -648,12 +648,18 @@ namespace Barotrauma
if (linkedTo.Contains(otherEntity)) if (linkedTo.Contains(otherEntity))
{ {
linkedTo.Remove(otherEntity); linkedTo.Remove(otherEntity);
if (otherEntity.linkedTo != null && otherEntity.linkedTo.Contains(this)) otherEntity.linkedTo.Remove(this); if (otherEntity.linkedTo != null && otherEntity.linkedTo.Contains(this))
{
otherEntity.linkedTo.Remove(this);
}
} }
else else
{ {
linkedTo.Add(otherEntity); linkedTo.Add(otherEntity);
if (otherEntity.Linkable && otherEntity.linkedTo != null) otherEntity.linkedTo.Add(this); if (otherEntity.Linkable && otherEntity.linkedTo != null)
{
otherEntity.linkedTo.Add(this);
}
} }
} }
} }
@@ -1445,7 +1451,7 @@ namespace Barotrauma
#else #else
if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); } if (GameSettings.VerboseLogging) { DebugConsole.ThrowError(errorMsg); }
#endif #endif
GameAnalyticsManager.AddErrorEventOnce("Item.ClientReadPosition:nophysicsbody", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("Item.ClientReadPosition:nophysicsbody", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
return; return;
} }
@@ -1586,7 +1592,7 @@ namespace Barotrauma
string errorMsg = "Failed to spawn item, prefab not found (name: " + (itemName ?? "null") + ", identifier: " + (itemIdentifier ?? "null") + ")"; string errorMsg = "Failed to spawn item, prefab not found (name: " + (itemName ?? "null") + ", identifier: " + (itemIdentifier ?? "null") + ")";
errorMsg += "\n" + string.Join(", ", GameMain.Config.AllEnabledPackages.Select(cp => cp.Name)); errorMsg += "\n" + string.Join(", ", GameMain.Config.AllEnabledPackages.Select(cp => cp.Name));
GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:PrefabNotFound" + (itemName ?? "null") + (itemIdentifier ?? "null"), GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:PrefabNotFound" + (itemName ?? "null") + (itemIdentifier ?? "null"),
GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, GameAnalyticsManager.ErrorSeverity.Critical,
errorMsg); errorMsg);
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
return null; return null;
@@ -1607,7 +1613,7 @@ namespace Barotrauma
string errorMsg = string errorMsg =
$"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of \"{parentItem.prefab.Identifier} ({parentItem.ID})\" (component index out of range). Index: {itemContainerIndex}, components: {parentItem.components.Count}."; $"Failed to spawn item \"{(itemIdentifier ?? "null")}\" in the inventory of \"{parentItem.prefab.Identifier} ({parentItem.ID})\" (component index out of range). Index: {itemContainerIndex}, components: {parentItem.components.Count}.";
GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ?? "null") + (itemIdentifier ?? "null"), GameAnalyticsManager.AddErrorEventOnce("Item.ReadSpawnData:ContainerIndexOutOfRange" + (itemName ?? "null") + (itemIdentifier ?? "null"),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
errorMsg); errorMsg);
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
inventory = parentItem.GetComponent<ItemContainer>()?.Inventory; inventory = parentItem.GetComponent<ItemContainer>()?.Inventory;
@@ -1632,7 +1638,7 @@ namespace Barotrauma
{ {
item = new Item(itemPrefab, pos, sub, id: itemId) item = new Item(itemPrefab, pos, sub, id: itemId)
{ {
SpawnedInOutpost = spawnedInOutpost, SpawnedInCurrentOutpost = spawnedInOutpost,
AllowStealing = allowStealing, AllowStealing = allowStealing,
Quality = quality Quality = quality
}; };
@@ -11,7 +11,7 @@ namespace Barotrauma
class BrokenItemSprite class BrokenItemSprite
{ {
//sprite will be rendered if the condition of the item is below this //sprite will be rendered if the condition of the item is below this
public readonly float MaxCondition; public readonly float MaxConditionPercentage;
public readonly Sprite Sprite; public readonly Sprite Sprite;
public readonly bool FadeIn; public readonly bool FadeIn;
public readonly Point Offset; public readonly Point Offset;
@@ -19,7 +19,7 @@ namespace Barotrauma
public BrokenItemSprite(Sprite sprite, float maxCondition, bool fadeIn, Point offset) public BrokenItemSprite(Sprite sprite, float maxCondition, bool fadeIn, Point offset)
{ {
Sprite = sprite; Sprite = sprite;
MaxCondition = MathHelper.Clamp(maxCondition, 0.0f, 100.0f); MaxConditionPercentage = MathHelper.Clamp(maxCondition, 0.0f, 100.0f);
FadeIn = fadeIn; FadeIn = fadeIn;
Offset = offset; Offset = offset;
} }
@@ -95,10 +95,10 @@ namespace Barotrauma
MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y)); MathHelper.Clamp(particlePos.Y, hull.WorldRect.Y - hull.WorldRect.Height, hull.WorldRect.Y));
} }
private IEnumerable<object> DimLight(LightSource light) private IEnumerable<CoroutineStatus> DimLight(LightSource light)
{ {
float currBrightness = 1.0f; float currBrightness = 1.0f;
while (light.Color.A > 0.0f && flashDuration > 0.0f) while (light.Color.A > 0.0f && flashDuration > 0.0f && currBrightness > 0.0f)
{ {
light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, (byte)(currBrightness * 255)); light.Color = new Color(light.Color.R, light.Color.G, light.Color.B, (byte)(currBrightness * 255));
currBrightness -= 1.0f / flashDuration * CoroutineManager.DeltaTime; currBrightness -= 1.0f / flashDuration * CoroutineManager.DeltaTime;
@@ -92,7 +92,7 @@ namespace Barotrauma
DebugConsole.ThrowError("Invalid left normal"); DebugConsole.ThrowError("Invalid left normal");
#endif #endif
GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed, GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidLeftNormal:" + level.Seed,
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, GameAnalyticsManager.ErrorSeverity.Warning,
"Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")"); "Invalid left normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + leftNormal + ", seed: " + level.Seed + ")");
if (cell.Body != null) if (cell.Body != null)
@@ -127,7 +127,7 @@ namespace Barotrauma
DebugConsole.ThrowError("Invalid right normal"); DebugConsole.ThrowError("Invalid right normal");
#endif #endif
GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed, GameAnalyticsManager.AddErrorEventOnce("CaveGenerator.GenerateWallShapes:InvalidRightNormal:" + level.Seed,
GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, GameAnalyticsManager.ErrorSeverity.Warning,
"Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")"); "Invalid right normal (leftedge: " + leftEdge + ", rightedge: " + rightEdge + ", normal: " + rightNormal + ", seed: " + level.Seed + ")");
if (cell.Body != null) if (cell.Body != null)
@@ -497,11 +497,13 @@ namespace Barotrauma.Lights
return true; return true;
} }
private readonly Dictionary<Hull, Rectangle> visibleHulls = new Dictionary<Hull, Rectangle>();
private Dictionary<Hull, Rectangle> GetVisibleHulls(Camera cam) private Dictionary<Hull, Rectangle> GetVisibleHulls(Camera cam)
{ {
Dictionary<Hull, Rectangle> visibleHulls = new Dictionary<Hull, Rectangle>(); visibleHulls.Clear();
foreach (Hull hull in Hull.hullList) foreach (Hull hull in Hull.hullList)
{ {
if (hull.HiddenInGame) { continue; }
var drawRect = var drawRect =
hull.Submarine == null ? hull.Submarine == null ?
hull.Rect : hull.Rect :
@@ -438,7 +438,7 @@ namespace Barotrauma
for (int i = 0; i < Bodies.Count; i++) for (int i = 0; i < Bodies.Count; i++)
{ {
Vector2 pos = FarseerPhysics.ConvertUnits.ToDisplayUnits(Bodies[i].Position); Vector2 pos = FarseerPhysics.ConvertUnits.ToDisplayUnits(Bodies[i].Position);
if (Submarine != null) pos += Submarine.Position; if (Submarine != null) { pos += Submarine.DrawPosition; }
pos.Y = -pos.Y; pos.Y = -pos.Y;
GUI.DrawRectangle(spriteBatch, GUI.DrawRectangle(spriteBatch,
pos, pos,
@@ -536,7 +536,7 @@ namespace Barotrauma
invalidMessage = true; invalidMessage = true;
string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". Section count does not match (server: {sectionCount} client: {Sections.Length})"; string errorMsg = $"Error while reading a network event for the structure \"{Name} ({ID})\". Section count does not match (server: {sectionCount} client: {Sections.Length})";
DebugConsole.NewMessage(errorMsg, Color.Red); DebugConsole.NewMessage(errorMsg, Color.Red);
GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("Structure.ClientRead:SectionCountMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
} }
for (int i = 0; i < sectionCount; i++) for (int i = 0; i < sectionCount; i++)
@@ -98,7 +98,7 @@ namespace Barotrauma
{ {
string errorMsg = "Error when loading round sound (" + element + ") - file path not set"; string errorMsg = "Error when loading round sound (" + element + ") - file path not set";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FilePathEmpty" + element.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null; return null;
} }
@@ -124,7 +124,7 @@ namespace Barotrauma
{ {
string errorMsg = "Failed to load sound file \"" + filename + "\"."; string errorMsg = "Failed to load sound file \"" + filename + "\".";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + filename, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return null; return null;
} }
} }
@@ -148,7 +148,7 @@ namespace Barotrauma
{ {
string errorMsg = "Failed to load sound file \"" + roundSound.Filename + "\"."; string errorMsg = "Failed to load sound file \"" + roundSound.Filename + "\".";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace()); GameAnalyticsManager.AddErrorEventOnce("Submarine.LoadRoundSound:FileNotFound" + roundSound.Filename, GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + Environment.StackTrace.CleanupStackTrace());
return; return;
} }
} }
@@ -183,13 +183,14 @@ namespace Barotrauma
visibleSubs.Clear(); visibleSubs.Clear();
foreach (Submarine sub in Loaded) foreach (Submarine sub in Loaded)
{ {
if (sub.WorldPosition.Y < Level.MaxEntityDepth) continue; if (sub.WorldPosition.Y < Level.MaxEntityDepth) { continue; }
int margin = 500;
Rectangle worldBorders = new Rectangle( Rectangle worldBorders = new Rectangle(
sub.Borders.X + (int)sub.WorldPosition.X - 500, sub.VisibleBorders.X + (int)sub.WorldPosition.X - margin,
sub.Borders.Y + (int)sub.WorldPosition.Y + 500, sub.VisibleBorders.Y + (int)sub.WorldPosition.Y + margin,
sub.Borders.Width + 1000, sub.VisibleBorders.Width + margin * 2,
sub.Borders.Height + 1000); sub.VisibleBorders.Height + margin * 2);
if (RectsOverlap(worldBorders, cam.WorldView)) if (RectsOverlap(worldBorders, cam.WorldView))
{ {
@@ -26,7 +26,7 @@ namespace Barotrauma
catch (Exception e) catch (Exception e)
{ {
DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e); DebugConsole.ThrowError("Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.", e);
GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.AddErrorEventOnce("Submarine..ctor:PreviewImageLoadingFailed", GameAnalyticsManager.ErrorSeverity.Error,
"Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted."); "Loading the preview image of the submarine \"" + Name + "\" failed. The file may be corrupted.");
PreviewImage = null; PreviewImage = null;
} }
@@ -202,6 +202,7 @@ namespace Barotrauma
} }
} }
GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock)); GUITextBlock.AutoScaleAndNormalize(parent.Content.GetAllChildren<GUITextBlock>().Where(c => c != submarineNameText && c != descBlock));
parent.ForceLayoutRecalculation();
} }
} }
} }
@@ -160,7 +160,7 @@ namespace Barotrauma.Networking
public TransferInDelegate OnTransferFailed; public TransferInDelegate OnTransferFailed;
private readonly List<FileTransferIn> activeTransfers; private readonly List<FileTransferIn> activeTransfers;
private readonly List<Pair<int, double>> finishedTransfers; private readonly List<(int transferId, double finishedTime)> finishedTransfers;
private readonly Dictionary<FileTransferType, string> downloadFolders = new Dictionary<FileTransferType, string>() private readonly Dictionary<FileTransferType, string> downloadFolders = new Dictionary<FileTransferType, string>()
{ {
@@ -176,7 +176,7 @@ namespace Barotrauma.Networking
public FileReceiver() public FileReceiver()
{ {
activeTransfers = new List<FileTransferIn>(); activeTransfers = new List<FileTransferIn>();
finishedTransfers = new List<Pair<int, double>>(); finishedTransfers = new List<(int transferId, double finishedTime)>();
} }
public void ReadMessage(IReadMessage inc) public void ReadMessage(IReadMessage inc)
@@ -193,8 +193,8 @@ namespace Barotrauma.Networking
case (byte)FileTransferMessageType.Initiate: case (byte)FileTransferMessageType.Initiate:
{ {
byte transferId = inc.ReadByte(); byte transferId = inc.ReadByte();
var existingTransfer = activeTransfers.Find(t => t.ID == transferId); var existingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.EndPointString) && t.ID == transferId);
finishedTransfers.RemoveAll(t => t.First == transferId); finishedTransfers.RemoveAll(t => t.transferId == transferId);
byte fileType = inc.ReadByte(); byte fileType = inc.ReadByte();
//ushort chunkLen = inc.ReadUInt16(); //ushort chunkLen = inc.ReadUInt16();
int fileSize = inc.ReadInt32(); int fileSize = inc.ReadInt32();
@@ -211,7 +211,7 @@ namespace Barotrauma.Networking
} }
else //resend acknowledgement packet else //resend acknowledgement packet
{ {
GameMain.Client.UpdateFileTransfer(transferId, 0); GameMain.Client.UpdateFileTransfer(transferId, existingTransfer.Received);
} }
return; return;
} }
@@ -316,14 +316,14 @@ namespace Barotrauma.Networking
{ {
byte transferId = inc.ReadByte(); byte transferId = inc.ReadByte();
var activeTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId); var activeTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.EndPointString) && t.ID == transferId);
if (activeTransfer == null) if (activeTransfer == null)
{ {
//it's possible for the server to send some extra data //it's possible for the server to send some extra data
//before it acknowledges that the download is finished, //before it acknowledges that the download is finished,
//so let's suppress the error message in that case //so let's suppress the error message in that case
finishedTransfers.RemoveAll(t => t.Second + 5.0 < Timing.TotalTime); finishedTransfers.RemoveAll(t => t.finishedTime + 5.0 < Timing.TotalTime);
if (!finishedTransfers.Any(t => t.First == transferId)) if (!finishedTransfers.Any(t => t.transferId == transferId))
{ {
GameMain.Client.CancelFileTransfer(transferId); GameMain.Client.CancelFileTransfer(transferId);
DebugConsole.ThrowError("File transfer error: received data without a transfer initiation message"); DebugConsole.ThrowError("File transfer error: received data without a transfer initiation message");
@@ -373,7 +373,7 @@ namespace Barotrauma.Networking
if (ValidateReceivedData(activeTransfer, out string errorMessage)) if (ValidateReceivedData(activeTransfer, out string errorMessage))
{ {
finishedTransfers.Add(new Pair<int, double>(transferId, Timing.TotalTime)); finishedTransfers.Add((transferId, Timing.TotalTime));
StopTransfer(activeTransfer); StopTransfer(activeTransfer);
Md5Hash.RemoveFromCache(activeTransfer.FilePath); Md5Hash.RemoveFromCache(activeTransfer.FilePath);
OnFinished(activeTransfer); OnFinished(activeTransfer);
@@ -391,7 +391,7 @@ namespace Barotrauma.Networking
case (byte)FileTransferMessageType.Cancel: case (byte)FileTransferMessageType.Cancel:
{ {
byte transferId = inc.ReadByte(); byte transferId = inc.ReadByte();
var matchingTransfer = activeTransfers.Find(t => t.Connection == inc.Sender && t.ID == transferId); var matchingTransfer = activeTransfers.Find(t => t.Connection.EndpointMatches(t.Connection.EndPointString) && t.ID == transferId);
if (matchingTransfer != null) if (matchingTransfer != null)
{ {
new GUIMessageBox("File transfer cancelled", "The server has cancelled the transfer of the file \"" + matchingTransfer.FileName + "\"."); new GUIMessageBox("File transfer cancelled", "The server has cancelled the transfer of the file \"" + matchingTransfer.FileName + "\".");
@@ -528,7 +528,7 @@ namespace Barotrauma.Networking
transfer.Status = FileTransferStatus.Canceled; transfer.Status = FileTransferStatus.Canceled;
} }
if (activeTransfers.Contains(transfer)) activeTransfers.Remove(transfer); if (activeTransfers.Contains(transfer)) { activeTransfers.Remove(transfer); }
transfer.Dispose(); transfer.Dispose();
if (deleteFile && File.Exists(transfer.FilePath)) if (deleteFile && File.Exists(transfer.FilePath))
@@ -462,7 +462,7 @@ namespace Barotrauma.Networking
private bool wrongPassword; private bool wrongPassword;
// Before main looping starts, we loop here and wait for approval message // Before main looping starts, we loop here and wait for approval message
private IEnumerable<object> WaitForStartingInfo() private IEnumerable<CoroutineStatus> WaitForStartingInfo()
{ {
GUI.SetCursorWaiting(); GUI.SetCursorWaiting();
requiresPw = false; requiresPw = false;
@@ -651,7 +651,7 @@ namespace Barotrauma.Networking
{ {
errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace(); errorMsg += "\nInner exception: " + e.InnerException.Message + "\n" + e.InnerException.StackTrace.CleanupStackTrace();
} }
GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.Update:CheckServerMessagesException" + e.TargetSite.ToString(), GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError("Error while reading a message from server.", e); DebugConsole.ThrowError("Error while reading a message from server.", e);
new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", new string[2] { "[message]", "[targetsite]" }, new string[2] { e.Message, e.TargetSite.ToString() })); new GUIMessageBox(TextManager.Get("Error"), TextManager.GetWithVariables("MessageReadError", new string[2] { "[message]", "[targetsite]" }, new string[2] { e.Message, e.TargetSite.ToString() }));
Disconnect(); Disconnect();
@@ -781,7 +781,7 @@ namespace Barotrauma.Networking
#if DEBUG #if DEBUG
DebugConsole.ThrowError("Error while reading an ingame update message from server.", e); DebugConsole.ThrowError("Error while reading an ingame update message from server.", e);
#endif #endif
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadDataMessage:ReadIngameUpdate", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw; throw;
} }
break; break;
@@ -793,7 +793,7 @@ namespace Barotrauma.Networking
errorMsg += "\n" + Environment.StackTrace.CleanupStackTrace(); errorMsg += "\n" + Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"GameClient.ReadDataMessage:VoipClientNull", "GameClient.ReadDataMessage:VoipClientNull",
GameMain.Client == null ? GameAnalyticsSDK.Net.EGAErrorSeverity.Error : GameAnalyticsSDK.Net.EGAErrorSeverity.Warning, GameMain.Client == null ? GameAnalyticsManager.ErrorSeverity.Error : GameAnalyticsManager.ErrorSeverity.Warning,
errorMsg); errorMsg);
return; return;
} }
@@ -863,8 +863,8 @@ namespace Barotrauma.Networking
if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize) if (roundInitStatus == RoundInitStatus.WaitingForStartGameFinalize)
{ {
//waiting for a save file //waiting for a save file
if (campaign != null && if (campaign != null &&
campaign.PendingSaveID > campaign.LastSaveID && NetIdUtils.IdMoreRecent(campaign.PendingSaveID, campaign.LastSaveID) &&
fileReceiver.ActiveTransfers.Any(t => t.FileType == FileTransferType.CampaignSave)) fileReceiver.ActiveTransfers.Any(t => t.FileType == FileTransferType.CampaignSave))
{ {
return; return;
@@ -874,6 +874,7 @@ namespace Barotrauma.Networking
break; break;
case ServerPacketHeader.ENDGAME: case ServerPacketHeader.ENDGAME:
CampaignMode.TransitionType transitionType = (CampaignMode.TransitionType)inc.ReadByte(); CampaignMode.TransitionType transitionType = (CampaignMode.TransitionType)inc.ReadByte();
bool save = inc.ReadBoolean();
string endMessage = string.Empty; string endMessage = string.Empty;
endMessage = inc.ReadString(); endMessage = inc.ReadString();
@@ -907,6 +908,7 @@ namespace Barotrauma.Networking
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
CoroutineManager.StartCoroutine(EndGame(endMessage, traitorResults, transitionType), "EndGame"); CoroutineManager.StartCoroutine(EndGame(endMessage, traitorResults, transitionType), "EndGame");
GUI.SetSavingIndicatorState(save);
break; break;
case ServerPacketHeader.CAMPAIGN_SETUP_INFO: case ServerPacketHeader.CAMPAIGN_SETUP_INFO:
UInt16 saveCount = inc.ReadUInt16(); UInt16 saveCount = inc.ReadUInt16();
@@ -992,7 +994,7 @@ namespace Barotrauma.Networking
{ {
string errorMsg = "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server." + string errorMsg = "Submarine equality check failed. The submarine loaded at your end doesn't match the one loaded by the server." +
" There may have been an error in receiving the up-to-date submarine file from the server."; " There may have been an error in receiving the up-to-date submarine file from the server.";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:SubsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
@@ -1013,7 +1015,7 @@ namespace Barotrauma.Networking
if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id))) if (!GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier).OrderBy(id => id).SequenceEqual(serverMissionIdentifiers.OrderBy(id => id)))
{ {
string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})"; string errorMsg = $"Mission equality check failed. The mission selected at your end doesn't match the one loaded by the server (server: {string.Join(", ", serverMissionIdentifiers)}, client: {string.Join(", ", GameMain.GameSession.Missions.Select(m => m.Prefab.Identifier))})";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:MissionsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers); GameMain.GameSession.EnforceMissionOrder(serverMissionIdentifiers);
@@ -1034,7 +1036,7 @@ namespace Barotrauma.Networking
", seed: " + Level.Loaded.Seed + ", seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" +
", mirrored: " + Level.Loaded.Mirrored + ")."; ", mirrored: " + Level.Loaded.Mirrored + ").";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
else else
@@ -1050,7 +1052,7 @@ namespace Barotrauma.Networking
", seed: " + Level.Loaded.Seed + ", seed: " + Level.Loaded.Seed +
", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" + ", sub: " + Submarine.MainSub.Info.Name + " (" + Submarine.MainSub.Info.MD5Hash.ShortHash + ")" +
", mirrored: " + Level.Loaded.Mirrored + ")."; ", mirrored: " + Level.Loaded.Mirrored + ").";
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:LevelsDontMatch" + Level.Loaded.Seed, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
throw new Exception(errorMsg); throw new Exception(errorMsg);
} }
} }
@@ -1120,8 +1122,8 @@ namespace Barotrauma.Networking
{ {
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"GameClient.HandleDisconnectMessage", "GameClient.HandleDisconnectMessage",
GameAnalyticsSDK.Net.EGAErrorSeverity.Debug, GameAnalyticsManager.ErrorSeverity.Debug,
"Client received a disconnect message. Reason: " + disconnectReason.ToString() + ", message: " + disconnectMsg); "Client received a disconnect message. Reason: " + disconnectReason.ToString());
} }
if (disconnectReason == DisconnectReason.ServerFull) if (disconnectReason == DisconnectReason.ServerFull)
@@ -1241,7 +1243,7 @@ namespace Barotrauma.Networking
} }
} }
private IEnumerable<object> WaitInServerQueue() private IEnumerable<CoroutineStatus> WaitInServerQueue()
{ {
waitInServerQueueBox = new GUIMessageBox( waitInServerQueueBox = new GUIMessageBox(
TextManager.Get("ServerQueuePleaseWait"), TextManager.Get("ServerQueuePleaseWait"),
@@ -1429,7 +1431,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.RefreshEnabledElements(); GameMain.NetLobbyScreen.RefreshEnabledElements();
} }
private IEnumerable<object> StartGame(IReadMessage inc) private IEnumerable<CoroutineStatus> StartGame(IReadMessage inc)
{ {
Character?.Remove(); Character?.Remove();
Character = null; Character = null;
@@ -1473,6 +1475,7 @@ namespace Barotrauma.Networking
serverSettings.AllowFriendlyFire = inc.ReadBoolean(); serverSettings.AllowFriendlyFire = inc.ReadBoolean();
serverSettings.LockAllDefaultWires = inc.ReadBoolean(); serverSettings.LockAllDefaultWires = inc.ReadBoolean();
serverSettings.AllowRagdollButton = inc.ReadBoolean(); serverSettings.AllowRagdollButton = inc.ReadBoolean();
serverSettings.AllowLinkingWifiToChat = inc.ReadBoolean();
GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean(); GameMain.NetLobbyScreen.UsingShuttle = inc.ReadBoolean();
GameMain.LightManager.LosMode = (LosMode)inc.ReadByte(); GameMain.LightManager.LosMode = (LosMode)inc.ReadByte();
bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits(); bool includesFinalize = inc.ReadBoolean(); inc.ReadPadBits();
@@ -1535,7 +1538,7 @@ namespace Barotrauma.Networking
gameStarted = true; gameStarted = true;
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectSub" + subName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -1547,7 +1550,7 @@ namespace Barotrauma.Networking
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
string errorMsg = "Failed to select shuttle \"" + shuttleName + "\" (hash: " + shuttleHash + ")."; string errorMsg = "Failed to select shuttle \"" + shuttleName + "\" (hash: " + shuttleHash + ").";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("GameClient.StartGame:FailedToSelectShuttle" + shuttleName, GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
@@ -1567,31 +1570,47 @@ namespace Barotrauma.Networking
if (GameMain.GameSession?.CrewManager != null) { GameMain.GameSession.CrewManager.Reset(); } if (GameMain.GameSession?.CrewManager != null) { GameMain.GameSession.CrewManager.Reset(); }
byte campaignID = inc.ReadByte(); byte campaignID = inc.ReadByte();
UInt16 campaignSaveID = inc.ReadUInt16();
int nextLocationIndex = inc.ReadInt32(); int nextLocationIndex = inc.ReadInt32();
int nextConnectionIndex = inc.ReadInt32(); int nextConnectionIndex = inc.ReadInt32();
int selectedLocationIndex = inc.ReadInt32(); int selectedLocationIndex = inc.ReadInt32();
bool mirrorLevel = inc.ReadBoolean(); bool mirrorLevel = inc.ReadBoolean();
if (campaign.CampaignID != campaignID) if (campaign.CampaignID != campaignID)
{ {
string errorMsg = "Failed to start campaign round (campaign ID does not match).";
gameStarted = true; gameStarted = true;
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError("Failed to start campaign round (campaign ID does not match).");
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
else if (campaign.Map == null) else if (campaign.Map == null)
{ {
string errorMsg = "Failed to start campaign round (campaign map not loaded yet).";
gameStarted = true; gameStarted = true;
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError("Failed to start campaign round (campaign map not loaded yet).");
GameMain.NetLobbyScreen.Select(); GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted; roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure; yield return CoroutineStatus.Failure;
} }
if (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.PendingSaveID))
{
campaign.PendingSaveID = campaignSaveID;
DateTime saveFileTimeOut = DateTime.Now + new TimeSpan(0,0,60);
while (NetIdUtils.IdMoreRecent(campaignSaveID, campaign.LastSaveID))
{
if (DateTime.Now > saveFileTimeOut)
{
gameStarted = true;
DebugConsole.ThrowError("Failed to start campaign round (timed out while waiting for the up-to-date save file).");
GameMain.NetLobbyScreen.Select();
roundInitStatus = RoundInitStatus.Interrupted;
yield return CoroutineStatus.Failure;
}
yield return new WaitForSeconds(0.1f);
}
}
campaign.Map.SelectLocation(selectedLocationIndex); campaign.Map.SelectLocation(selectedLocationIndex);
LevelData levelData = nextLocationIndex > -1 ? LevelData levelData = nextLocationIndex > -1 ?
@@ -1670,10 +1689,6 @@ namespace Barotrauma.Networking
} }
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; } if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
clientPeer.Update((float)Timing.Step);
if (roundInitStatus != RoundInitStatus.WaitingForStartGameFinalize) { break; }
} }
catch (Exception e) catch (Exception e)
{ {
@@ -1749,7 +1764,7 @@ namespace Barotrauma.Networking
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
public IEnumerable<object> EndGame(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None) public IEnumerable<CoroutineStatus> EndGame(string endMessage, List<TraitorMissionResult> traitorResults = null, CampaignMode.TransitionType transitionType = CampaignMode.TransitionType.None)
{ {
//round starting up, wait for it to finish //round starting up, wait for it to finish
DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 60); DateTime timeOut = DateTime.Now + new TimeSpan(0, 0, 60);
@@ -1859,8 +1874,7 @@ namespace Barotrauma.Networking
GameMain.GameSession.OwnedSubmarines = new List<SubmarineInfo>(); GameMain.GameSession.OwnedSubmarines = new List<SubmarineInfo>();
for (int i = 0; i < ownedIndexes.Length; i++) for (int i = 0; i < ownedIndexes.Length; i++)
{ {
int index; if (int.TryParse(ownedIndexes[i], out int index))
if (int.TryParse(ownedIndexes[i], out index))
{ {
SubmarineInfo sub = GameMain.Client.ServerSubmarines[index]; SubmarineInfo sub = GameMain.Client.ServerSubmarines[index];
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "owned")) if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "owned"))
@@ -2108,9 +2122,6 @@ namespace Barotrauma.Networking
bool autoRestartEnabled = inc.ReadBoolean(); bool autoRestartEnabled = inc.ReadBoolean();
float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f; float autoRestartTimer = autoRestartEnabled ? inc.ReadSingle() : 0.0f;
bool radiationEnabled = inc.ReadBoolean();
byte maxMissionCount = inc.ReadByte();
//ignore the message if we already a more up-to-date one //ignore the message if we already a more up-to-date one
//or if we're still waiting for the initial update //or if we're still waiting for the initial update
if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) && if (NetIdUtils.IdMoreRecent(updateID, GameMain.NetLobbyScreen.LastUpdateID) &&
@@ -2160,22 +2171,15 @@ namespace Barotrauma.Networking
if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "campaign")) if (GameMain.NetLobbyScreen.CheckIfCampaignSubMatches(sub, "campaign"))
{ {
GameMain.NetLobbyScreen.CampaignSubmarines.Add(sub); GameMain.NetLobbyScreen.CampaignSubmarines.Add(sub);
} }
}
if (HasPermission(ClientPermissions.ManageCampaign) && !gameStarted && GameMain.NetLobbyScreen?.CampaignSetupUI != null)
{
GameMain.NetLobbyScreen.CampaignSetupUI.RefreshMultiplayerCampaignSubUI(GameMain.NetLobbyScreen.CampaignSubmarines);
} }
} }
GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating); GameMain.NetLobbyScreen.SetAllowSpectating(allowSpectating);
GameMain.NetLobbyScreen.LevelSeed = levelSeed; GameMain.NetLobbyScreen.LevelSeed = levelSeed;
GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty); GameMain.NetLobbyScreen.SetLevelDifficulty(levelDifficulty);
GameMain.NetLobbyScreen.SetRadiationEnabled(radiationEnabled);
GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode); GameMain.NetLobbyScreen.SetBotSpawnMode(botSpawnMode);
GameMain.NetLobbyScreen.SetBotCount(botCount); GameMain.NetLobbyScreen.SetBotCount(botCount);
GameMain.NetLobbyScreen.SetMaxMissionCount(maxMissionCount);
GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer); GameMain.NetLobbyScreen.SetAutoRestart(autoRestartEnabled, autoRestartTimer);
serverSettings.VoiceChatEnabled = voiceChatEnabled; serverSettings.VoiceChatEnabled = voiceChatEnabled;
@@ -2347,7 +2351,7 @@ namespace Barotrauma.Networking
{ {
errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text); errorLines.Add("[" + DebugConsole.Messages[i].Time + "] " + DebugConsole.Messages[i].Text);
} }
GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, string.Join("\n", errorLines)); GameAnalyticsManager.AddErrorEventOnce("GameClient.ReadInGameUpdate", GameAnalyticsManager.ErrorSeverity.Critical, string.Join("\n", errorLines));
DebugConsole.ThrowError("Writing object data to \"networkerror_data.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues"); DebugConsole.ThrowError("Writing object data to \"networkerror_data.log\", please send this file to us at http://github.com/Regalis11/Barotrauma/issues");
@@ -2583,29 +2587,29 @@ namespace Barotrauma.Networking
subElement.ToolTip = newSub.Description; subElement.ToolTip = newSub.Description;
} }
if (GameMain.NetLobbyScreen.FailedSelectedSub != null && if (GameMain.NetLobbyScreen.FailedSelectedSub.HasValue &&
GameMain.NetLobbyScreen.FailedSelectedSub.First == newSub.Name && GameMain.NetLobbyScreen.FailedSelectedSub.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedSub.Second == newSub.MD5Hash.Hash) GameMain.NetLobbyScreen.FailedSelectedSub.Value.Hash == newSub.MD5Hash.Hash)
{ {
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.SubList); GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.SubList);
} }
if (GameMain.NetLobbyScreen.FailedSelectedShuttle != null && if (GameMain.NetLobbyScreen.FailedSelectedShuttle.HasValue &&
GameMain.NetLobbyScreen.FailedSelectedShuttle.First == newSub.Name && GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Name == newSub.Name &&
GameMain.NetLobbyScreen.FailedSelectedShuttle.Second == newSub.MD5Hash.Hash) GameMain.NetLobbyScreen.FailedSelectedShuttle.Value.Hash == newSub.MD5Hash.Hash)
{ {
GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.ShuttleList.ListBox); GameMain.NetLobbyScreen.TrySelectSub(newSub.Name, newSub.MD5Hash.Hash, GameMain.NetLobbyScreen.ShuttleList.ListBox);
} }
Pair<string, string> failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.First == newSub.Name && s.Second == newSub.MD5Hash.Hash); NetLobbyScreen.FailedSubInfo failedCampaignSub = GameMain.NetLobbyScreen.FailedCampaignSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.Hash);
if (failedCampaignSub != null) if (failedCampaignSub != default)
{ {
GameMain.NetLobbyScreen.CampaignSubmarines.Add(newSub); GameMain.NetLobbyScreen.CampaignSubmarines.Add(newSub);
GameMain.NetLobbyScreen.FailedCampaignSubs.Remove(failedCampaignSub); GameMain.NetLobbyScreen.FailedCampaignSubs.Remove(failedCampaignSub);
} }
Pair<string, string> failedOwnedSub = GameMain.NetLobbyScreen.FailedOwnedSubs.Find(s => s.First == newSub.Name && s.Second == newSub.MD5Hash.Hash); NetLobbyScreen.FailedSubInfo failedOwnedSub = GameMain.NetLobbyScreen.FailedOwnedSubs.Find(s => s.Name == newSub.Name && s.Hash == newSub.MD5Hash.Hash);
if (failedOwnedSub != null) if (failedOwnedSub != default)
{ {
GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(newSub); GameMain.NetLobbyScreen.ServerOwnedSubmarines.Add(newSub);
GameMain.NetLobbyScreen.FailedOwnedSubs.Remove(failedOwnedSub); GameMain.NetLobbyScreen.FailedOwnedSubs.Remove(failedOwnedSub);
@@ -2675,8 +2679,11 @@ namespace Barotrauma.Networking
public override void CreateEntityEvent(INetSerializable entity, object[] extraData) public override void CreateEntityEvent(INetSerializable entity, object[] extraData)
{ {
if (!(entity is IClientSerializable)) throw new InvalidCastException("Entity is not IClientSerializable"); if (!(entity is IClientSerializable clientSerializable))
entityEventManager.CreateEvent(entity as IClientSerializable, extraData); {
throw new InvalidCastException($"Entity is not {nameof(IClientSerializable)}");
}
entityEventManager.CreateEvent(clientSerializable, extraData);
} }
public bool HasPermission(ClientPermissions permission) public bool HasPermission(ClientPermissions permission)
@@ -2941,7 +2948,6 @@ namespace Barotrauma.Networking
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.SelectSub); msg.Write((UInt16)ClientPermissions.SelectSub);
msg.Write(false);
msg.Write(isShuttle); msg.WritePadBits(); msg.Write(isShuttle); msg.WritePadBits();
msg.Write((UInt16)subIndex); msg.Write((UInt16)subIndex);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE); msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
@@ -2949,23 +2955,6 @@ namespace Barotrauma.Networking
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
/// <summary>
/// Tell the server to add / remove a purchasable submarine (permission required)
/// </summary>
public void RequestCampaignSub(SubmarineInfo sub, bool add)
{
if (!HasPermission(ClientPermissions.SelectSub) || sub == null) return;
IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.SelectSub);
msg.Write(true);
msg.Write(sub.EqualityCheckVal);
msg.Write(add);
msg.Write((byte)ServerNetObject.END_OF_MESSAGE);
clientPeer.Send(msg, DeliveryMethod.Reliable);
}
/// <summary> /// <summary>
/// Tell the server to select a mode (permission required) /// Tell the server to select a mode (permission required)
/// </summary> /// </summary>
@@ -3024,12 +3013,13 @@ namespace Barotrauma.Networking
/// <summary> /// <summary>
/// Tell the server to end the round (permission required) /// Tell the server to end the round (permission required)
/// </summary> /// </summary>
public void RequestRoundEnd() public void RequestRoundEnd(bool save)
{ {
IWriteMessage msg = new WriteOnlyMessage(); IWriteMessage msg = new WriteOnlyMessage();
msg.Write((byte)ClientPacketHeader.SERVER_COMMAND); msg.Write((byte)ClientPacketHeader.SERVER_COMMAND);
msg.Write((UInt16)ClientPermissions.ManageRound); msg.Write((UInt16)ClientPermissions.ManageRound);
msg.Write(true); //indicates round end msg.Write(true); //indicates round end
msg.Write(save);
clientPeer.Send(msg, DeliveryMethod.Reliable); clientPeer.Send(msg, DeliveryMethod.Reliable);
} }
@@ -3332,7 +3322,9 @@ namespace Barotrauma.Networking
if (respawnManager.RespawnCountdownStarted) if (respawnManager.RespawnCountdownStarted)
{ {
float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds; float timeLeft = (float)(respawnManager.RespawnTime - DateTime.Now).TotalSeconds;
respawnText = TextManager.GetWithVariable(respawnManager.UsingShuttle ? "RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft)); respawnText = TextManager.GetWithVariable(
respawnManager.UsingShuttle && !respawnManager.ForceSpawnInMainSub ?
"RespawnShuttleDispatching" : "RespawningIn", "[time]", ToolBox.SecondsToReadableTime(timeLeft));
} }
else if (respawnManager.PendingRespawnCount > 0) else if (respawnManager.PendingRespawnCount > 0)
{ {
@@ -3448,7 +3440,7 @@ namespace Barotrauma.Networking
} }
// Need a delayed selection due to the inputbox being deselected when a left click occurs outside of it // Need a delayed selection due to the inputbox being deselected when a left click occurs outside of it
IEnumerable<object> selectCoroutine() IEnumerable<CoroutineStatus> selectCoroutine()
{ {
yield return new WaitForSeconds(0.01f, true); yield return new WaitForSeconds(0.01f, true);
chatBox.InputBox.Select(chatBox.InputBox.Text.Length); chatBox.InputBox.Select(chatBox.InputBox.Text.Length);
@@ -72,6 +72,9 @@ namespace Barotrauma
{ {
CreateLabeledTickBox(parent, nameof(DangerousItemStealBots)); CreateLabeledTickBox(parent, nameof(DangerousItemStealBots));
} }
CreateLabeledSlider(parent, 0.0f, 30.0f, 0.5f, nameof(DangerousItemContainKarmaDecrease));
CreateLabeledTickBox(parent, nameof(IsDangerousItemContainKarmaDecreaseIncremental));
CreateLabeledSlider(parent, 0.0f, 100.0f, 1.0f, nameof(MaxDangerousItemContainKarmaDecrease));
} }
private void CreateLabeledSlider(GUIComponent parent, float min, float max, float step, string propertyName) private void CreateLabeledSlider(GUIComponent parent, float min, float max, float step, string propertyName)
@@ -43,24 +43,9 @@ namespace Barotrauma.Networking
public void CreateEvent(IClientSerializable entity, object[] extraData = null) public void CreateEvent(IClientSerializable entity, object[] extraData = null)
{ {
if (GameMain.Client == null || GameMain.Client.Character == null) return; if (GameMain.Client?.Character == null) { return; }
if (!(entity is Entity)) if (!ValidateEntity(entity)) { return; }
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + "!");
return;
}
if (((Entity)entity).Removed)
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the entity has been removed.\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
if (((Entity)entity).IdFreed)
{
DebugConsole.ThrowError("Can't create an entity event for " + entity + " - the ID of the entity has been freed.\n" + Environment.StackTrace.CleanupStackTrace());
return;
}
var newEvent = new ClientEntityEvent(entity, (UInt16)(ID + 1)) var newEvent = new ClientEntityEvent(entity, (UInt16)(ID + 1))
{ {
@@ -161,7 +146,7 @@ namespace Barotrauma.Networking
UInt16 firstEventID = msg.ReadUInt16(); UInt16 firstEventID = msg.ReadUInt16();
int eventCount = msg.ReadByte(); int eventCount = msg.ReadByte();
for (int i = 0; i < eventCount; i++) for (int i = 0; i < eventCount; i++)
{ {
//16 = entity ID, 8 = msg length //16 = entity ID, 8 = msg length
@@ -179,7 +164,7 @@ namespace Barotrauma.Networking
UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i); UInt16 thisEventID = (UInt16)(firstEventID + (UInt16)i);
UInt16 entityID = msg.ReadUInt16(); UInt16 entityID = msg.ReadUInt16();
if (entityID == Entity.NullEntityID) if (entityID == Entity.NullEntityID)
{ {
if (GameSettings.VerboseLogging) if (GameSettings.VerboseLogging)
@@ -240,12 +225,15 @@ namespace Barotrauma.Networking
if (msg.BitPosition != msgPosition + msgLength * 8) if (msg.BitPosition != msgPosition + msgLength * 8)
{ {
string errorMsg = "Message byte position incorrect after reading an event for the entity \"" + entity.ToString() var prevEntity = entities.Count >= 2 ? entities[entities.Count - 2] : null;
+ "\". Read " + (msg.BitPosition - msgPosition) + " bits, expected message length was " + (msgLength * 8) + " bits."; ushort prevId = prevEntity is Entity p ? p.ID : (ushort)0;
string errorMsg = $"Message byte position incorrect after reading an event for the entity \"{entity}\" (ID {(entity is Entity e ? e.ID : 0)}). "
+$"The previous entity was \"{prevEntity}\" (ID {prevId}) "
+$"Read {msg.BitPosition - msgPosition} bits, expected message length was {msgLength * 8} bits.";
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:BitPosMismatch", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
//TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick //TODO: force the BitPosition to correct place? Having some entity in a potentially incorrect state is not as bad as a desync kick
//msg.BitPosition = (int)(msgPosition + msgLength * 8); //msg.BitPosition = (int)(msgPosition + msgLength * 8);
@@ -264,7 +252,7 @@ namespace Barotrauma.Networking
DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e); DebugConsole.ThrowError("Failed to read event for entity \"" + entity.ToString() + "\"!", e);
GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(), GameAnalyticsManager.AddErrorEventOnce("ClientEntityEventManager.Read:ReadFailed" + entity.ToString(),
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg); GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
msg.BitPosition = (int)(msgPosition + msgLength * 8); msg.BitPosition = (int)(msgPosition + msgLength * 8);
msg.ReadPadBits(); msg.ReadPadBits();
} }
@@ -60,11 +60,11 @@ namespace Barotrauma.Networking
{ {
GUI.DrawRectangle(spriteBatch, rect, Color.Black * 0.4f, true); GUI.DrawRectangle(spriteBatch, rect, Color.Black * 0.4f, true);
graphs[(int)NetStatType.ReceivedBytes].Draw(spriteBatch, rect, null, 0.0f, Color.Cyan); graphs[(int)NetStatType.ReceivedBytes].Draw(spriteBatch, rect, color: Color.Cyan);
graphs[(int)NetStatType.SentBytes].Draw(spriteBatch, rect, null, 0.0f, GUI.Style.Orange); graphs[(int)NetStatType.SentBytes].Draw(spriteBatch, rect, null, color: GUI.Style.Orange);
if (graphs[(int)NetStatType.ResentMessages].Average() > 0) if (graphs[(int)NetStatType.ResentMessages].Average() > 0)
{ {
graphs[(int)NetStatType.ResentMessages].Draw(spriteBatch, rect, null, 0.0f, GUI.Style.Red); graphs[(int)NetStatType.ResentMessages].Draw(spriteBatch, rect, color: GUI.Style.Red);
GUI.SmallFont.DrawString(spriteBatch, "Peak resent: " + graphs[(int)NetStatType.ResentMessages].LargestValue() + " messages/s", GUI.SmallFont.DrawString(spriteBatch, "Peak resent: " + graphs[(int)NetStatType.ResentMessages].LargestValue() + " messages/s",
new Vector2(rect.Right + 10, rect.Y + 50), GUI.Style.Red); new Vector2(rect.Right + 10, rect.Y + 50), GUI.Style.Red);
} }
@@ -115,13 +115,11 @@ namespace Barotrauma.Networking
{ {
if (!isActive) { return; } if (!isActive) { return; }
byte incByte = inc.ReadByte(); PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
//Console.WriteLine(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte); //Console.WriteLine(isCompressed + " " + isConnectionInitializationStep + " " + (int)incByte);
if (isConnectionInitializationStep && initializationStep != ConnectionInitialization.Success) if (packetHeader.IsConnectionInitializationStep() && initializationStep != ConnectionInitialization.Success)
{ {
ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false)); ReadConnectionInitializationStep(new ReadWriteMessage(inc.Data, (int)inc.Position, inc.LengthBits, false));
} }
@@ -133,7 +131,7 @@ namespace Barotrauma.Networking
initializationStep = ConnectionInitialization.Success; initializationStep = ConnectionInitialization.Success;
} }
UInt16 length = inc.ReadUInt16(); UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Data, isCompressed, inc.PositionInBytes, length, ServerConnection); IReadMessage msg = new ReadOnlyMessage(inc.Data, packetHeader.IsCompressed(), inc.PositionInBytes, length, ServerConnection);
OnMessageReceived?.Invoke(msg); OnMessageReceived?.Invoke(msg);
} }
} }
@@ -112,33 +112,28 @@ namespace Barotrauma.Networking
NetworkConnection.TimeoutThresholdInGame : NetworkConnection.TimeoutThresholdInGame :
NetworkConnection.TimeoutThreshold; NetworkConnection.TimeoutThreshold;
byte incByte = data[0]; PacketHeader packetHeader = (PacketHeader)data[0];
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!isServerMessage) { return; } if (!packetHeader.IsServerMessage()) { return; }
if (isConnectionInitializationStep) if (packetHeader.IsConnectionInitializationStep())
{ {
ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8); ulong low = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8);
ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32); ulong high = Lidgren.Network.NetBitWriter.ReadUInt32(data, 32, 8 + 32);
ulong lobbyId = low + (high << 32); ulong lobbyId = low + (high << 32);
Steam.SteamManager.JoinLobby(lobbyId, false); Steam.SteamManager.JoinLobby(lobbyId, false);
IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - 9, ServerConnection); IReadMessage inc = new ReadOnlyMessage(data, false, 1 + 8, dataLength - (1 + 8), ServerConnection);
if (initializationStep != ConnectionInitialization.Success) if (initializationStep != ConnectionInitialization.Success)
{ {
incomingInitializationMessages.Add(inc); incomingInitializationMessages.Add(inc);
} }
} }
else if (isHeartbeatMessage) else if (packetHeader.IsHeartbeatMessage())
{ {
return; //TODO: implement heartbeats return; //TODO: implement heartbeats
} }
else if (isDisconnectMessage) else if (packetHeader.IsDisconnectMessage())
{ {
IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection); IReadMessage inc = new ReadOnlyMessage(data, false, 1, dataLength - 1, ServerConnection);
string msg = inc.ReadString(); string msg = inc.ReadString();
@@ -147,10 +142,9 @@ namespace Barotrauma.Networking
} }
else else
{ {
UInt16 length = data[1]; UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(data, 16, 8);
length |= (UInt16)(((UInt32)data[2]) << 8);
IReadMessage inc = new ReadOnlyMessage(data, isCompressed, 3, length, ServerConnection); IReadMessage inc = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 3, length, ServerConnection);
incomingDataMessages.Add(inc); incomingDataMessages.Add(inc);
} }
} }
@@ -147,18 +147,13 @@ namespace Barotrauma.Networking
DeliveryMethod deliveryMethod = (DeliveryMethod)data[0]; DeliveryMethod deliveryMethod = (DeliveryMethod)data[0];
byte incByte = data[1]; PacketHeader packetHeader = (PacketHeader)data[1];
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (!remotePeer.Authenticated & !remotePeer.Authenticating && isConnectionInitializationStep) if (!remotePeer.Authenticated & !remotePeer.Authenticating && packetHeader.IsConnectionInitializationStep())
{ {
remotePeer.DisconnectTime = null; remotePeer.DisconnectTime = null;
IReadMessage authMsg = new ReadOnlyMessage(data, isCompressed, 2, dataLength - 2, null); IReadMessage authMsg = new ReadOnlyMessage(data, packetHeader.IsCompressed(), 2, dataLength - 2, null);
ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte(); ConnectionInitialization initializationStep = (ConnectionInitialization)authMsg.ReadByte();
if (initializationStep == ConnectionInitialization.SteamTicketAndVersion) if (initializationStep == ConnectionInitialization.SteamTicketAndVersion)
{ {
@@ -242,17 +237,11 @@ namespace Barotrauma.Networking
int p2pDataStart = inc.BytePosition; int p2pDataStart = inc.BytePosition;
byte incByte = inc.ReadByte(); PacketHeader packetHeader = (PacketHeader)inc.ReadByte();
bool isCompressed = (incByte & (byte)PacketHeader.IsCompressed) != 0;
bool isConnectionInitializationStep = (incByte & (byte)PacketHeader.IsConnectionInitializationStep) != 0;
bool isDisconnectMessage = (incByte & (byte)PacketHeader.IsDisconnectMessage) != 0;
bool isServerMessage = (incByte & (byte)PacketHeader.IsServerMessage) != 0;
bool isHeartbeatMessage = (incByte & (byte)PacketHeader.IsHeartbeatMessage) != 0;
if (recipientSteamId != selfSteamID) if (recipientSteamId != selfSteamID)
{ {
if (!isServerMessage) if (!packetHeader.IsServerMessage())
{ {
DebugConsole.ThrowError("Received non-server message meant for remote peer"); DebugConsole.ThrowError("Received non-server message meant for remote peer");
return; return;
@@ -262,7 +251,7 @@ namespace Barotrauma.Networking
if (peer == null) { return; } if (peer == null) { return; }
if (isDisconnectMessage) if (packetHeader.IsDisconnectMessage())
{ {
DisconnectPeer(peer, inc.ReadString()); DisconnectPeer(peer, inc.ReadString());
return; return;
@@ -273,8 +262,8 @@ namespace Barotrauma.Networking
{ {
case DeliveryMethod.Reliable: case DeliveryMethod.Reliable:
case DeliveryMethod.ReliableOrdered: case DeliveryMethod.ReliableOrdered:
//the documentation seems to suggest that the Reliable send type //the documentation seems to suggest that the
//enforces packet order (TODO: verify) //Reliable send type enforces packet order
sendType = Steamworks.P2PSend.Reliable; sendType = Steamworks.P2PSend.Reliable;
break; break;
default: default:
@@ -284,17 +273,31 @@ namespace Barotrauma.Networking
byte[] p2pData; byte[] p2pData;
if (isConnectionInitializationStep) if (packetHeader.IsConnectionInitializationStep())
{ {
p2pData = new byte[inc.LengthBytes - p2pDataStart + 8]; p2pData = new byte[inc.LengthBytes - p2pDataStart + 8];
p2pData[0] = inc.Buffer[p2pDataStart]; p2pData[0] = inc.Buffer[p2pDataStart];
Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 64, p2pData, 8); Lidgren.Network.NetBitWriter.WriteUInt64(SteamManager.CurrentLobbyID, 8 * 8, p2pData, 1 * 8);
Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 9, inc.LengthBytes - p2pDataStart - 1); Array.Copy(inc.Buffer, p2pDataStart+1, p2pData, 1 + 8, inc.LengthBytes - p2pDataStart - 1);
} }
else else
{ {
p2pData = new byte[inc.LengthBytes - p2pDataStart]; p2pData = new byte[inc.LengthBytes - p2pDataStart];
Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length); Array.Copy(inc.Buffer, p2pDataStart, p2pData, 0, p2pData.Length);
if (!packetHeader.IsHeartbeatMessage() && !packetHeader.IsDisconnectMessage())
{
UInt16 length = Lidgren.Network.NetBitWriter.ReadUInt16(p2pData, 16, 8);
if (length > p2pData.Length - 2)
{
string errorMsg = $"Length written in message to send to client is larger than buffer size ({length} > {p2pData.Length - 2})";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce(
"SteamP2POwnerPeerLengthValidationFail",
GameAnalyticsManager.ErrorSeverity.Error,
errorMsg);
}
}
} }
if (p2pData.Length + 4 >= MsgConstants.MTU) if (p2pData.Length + 4 >= MsgConstants.MTU)
@@ -323,21 +326,21 @@ namespace Barotrauma.Networking
} }
else else
{ {
if (isDisconnectMessage) if (packetHeader.IsDisconnectMessage())
{ {
DebugConsole.ThrowError("Received disconnect message from owned server"); DebugConsole.ThrowError("Received disconnect message from owned server");
return; return;
} }
if (!isServerMessage) if (!packetHeader.IsServerMessage())
{ {
DebugConsole.ThrowError("Received non-server message from owned server"); DebugConsole.ThrowError("Received non-server message from owned server");
return; return;
} }
if (isHeartbeatMessage) if (packetHeader.IsHeartbeatMessage())
{ {
return; //timeout is handled by Lidgren, ignore this message return; //no timeout since we're using pipes, ignore this message
} }
if (isConnectionInitializationStep) if (packetHeader.IsConnectionInitializationStep())
{ {
IWriteMessage outMsg = new WriteOnlyMessage(); IWriteMessage outMsg = new WriteOnlyMessage();
outMsg.Write(selfSteamID); outMsg.Write(selfSteamID);
@@ -358,7 +361,7 @@ namespace Barotrauma.Networking
initializationStep = ConnectionInitialization.Success; initializationStep = ConnectionInitialization.Success;
} }
UInt16 length = inc.ReadUInt16(); UInt16 length = inc.ReadUInt16();
IReadMessage msg = new ReadOnlyMessage(inc.Buffer, isCompressed, inc.BytePosition, length, ServerConnection); IReadMessage msg = new ReadOnlyMessage(inc.Buffer, packetHeader.IsCompressed(), inc.BytePosition, length, ServerConnection);
OnMessageReceived?.Invoke(msg); OnMessageReceived?.Invoke(msg);
return; return;
@@ -1,4 +1,5 @@
using System; using System;
using System.Linq;
namespace Barotrauma.Networking namespace Barotrauma.Networking
{ {
@@ -17,6 +18,11 @@ namespace Barotrauma.Networking
get; private set; get; private set;
} }
public bool ForceSpawnInMainSub
{
get; private set;
}
partial void UpdateTransportingProjSpecific(float deltaTime) partial void UpdateTransportingProjSpecific(float deltaTime)
{ {
if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; } if (GameMain.Client?.Character == null || GameMain.Client.Character.Submarine != RespawnShuttle) { return; }
@@ -30,10 +36,46 @@ namespace Barotrauma.Networking
GameMain.Client.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server); GameMain.Client.AddChatMessage("ServerMessage.ShuttleLeaving", ChatMessageType.Server);
} }
} }
private CoroutineHandle respawnPromptCoroutine;
public void ShowRespawnPromptIfNeeded(float delay = 5.0f)
{
if (!UseRespawnPrompt) { return; }
if (CoroutineManager.IsCoroutineRunning(respawnPromptCoroutine) || GUIMessageBox.MessageBoxes.Any(mb => mb.UserData as string == "respawnquestionprompt"))
{
return;
}
respawnPromptCoroutine = CoroutineManager.Invoke(() =>
{
if (Character.Controlled != null || (!(GameMain.GameSession?.IsRunning ?? false))) { return; }
var respawnPrompt = new GUIMessageBox(
TextManager.Get("tutorial.tryagainheader"), TextManager.Get("respawnquestionprompt"),
new string[] { TextManager.Get("respawnquestionpromptrespawn"), TextManager.Get("respawnquestionpromptwait") })
{
UserData = "respawnquestionprompt"
};
respawnPrompt.Buttons[0].OnClicked += (btn, userdata) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: false);
respawnPrompt.Close();
return true;
};
respawnPrompt.Buttons[1].OnClicked += (btn, userdata) =>
{
GameMain.Client?.SendRespawnPromptResponse(waitForNextRoundRespawn: true);
respawnPrompt.Close();
return true;
};
}, delay: delay);
}
public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime) public void ClientRead(ServerNetObject type, IReadMessage msg, float sendingTime)
{ {
bool respawnPromptPending = false;
var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length); var newState = (State)msg.ReadRangedInteger(0, Enum.GetNames(typeof(State)).Length);
ForceSpawnInMainSub = false;
switch (newState) switch (newState)
{ {
case State.Transporting: case State.Transporting:
@@ -46,13 +88,14 @@ namespace Barotrauma.Networking
if (CurrentState != newState) if (CurrentState != newState)
{ {
CoroutineManager.StopCoroutines("forcepos"); CoroutineManager.StopCoroutines("forcepos");
//CoroutineManager.StartCoroutine(ForceShuttleToPos(Level.Loaded.StartPosition - Vector2.UnitY * Level.ShaftHeight, 100.0f), "forcepos");
} }
break; break;
case State.Waiting: case State.Waiting:
PendingRespawnCount = msg.ReadUInt16(); PendingRespawnCount = msg.ReadUInt16();
RequiredRespawnCount = msg.ReadUInt16(); RequiredRespawnCount = msg.ReadUInt16();
respawnPromptPending = msg.ReadBoolean();
RespawnCountdownStarted = msg.ReadBoolean(); RespawnCountdownStarted = msg.ReadBoolean();
ForceSpawnInMainSub = msg.ReadBoolean();
ResetShuttle(); ResetShuttle();
float newRespawnTime = msg.ReadSingle(); float newRespawnTime = msg.ReadSingle();
RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f)); RespawnTime = DateTime.Now + new TimeSpan(0, 0, 0, 0, milliseconds: (int)(newRespawnTime * 1000.0f));
@@ -63,6 +106,12 @@ namespace Barotrauma.Networking
} }
CurrentState = newState; CurrentState = newState;
if (respawnPromptPending)
{
GameMain.Client.HasSpawned = true;
ShowRespawnPromptIfNeeded(delay: 1.0f);
}
msg.ReadPadBits(); msg.ReadPadBits();
} }
} }
@@ -106,10 +106,10 @@ namespace Barotrauma.Networking
public void CreatePreviewWindow(GUIFrame frame) public void CreatePreviewWindow(GUIFrame frame)
{ {
frame.ClearChildren();
if (frame == null) { return; } if (frame == null) { return; }
frame.ClearChildren();
var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUI.LargeFont) var title = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), frame.RectTransform), ServerName, font: GUI.LargeFont)
{ {
ToolTip = ServerName ToolTip = ServerName
@@ -242,7 +242,8 @@ namespace Barotrauma.Networking
textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea() textBlock.ClickableAreas.Add(new GUITextBlock.ClickableArea()
{ {
Data = data, Data = data,
OnClick = GameMain.NetLobbyScreen.SelectPlayer OnClick = GameMain.NetLobbyScreen.SelectPlayer,
OnSecondaryClick = GameMain.NetLobbyScreen.ShowPlayerContextMenu
}); });
} }
} }
@@ -128,8 +128,18 @@ namespace Barotrauma.Networking
{ {
cachedServerListInfo = null; cachedServerListInfo = null;
ServerName = incMsg.ReadString(); NetFlags requiredFlags = (NetFlags)incMsg.ReadByte();
ServerMessageText = incMsg.ReadString();
if (requiredFlags.HasFlag(NetFlags.Name))
{
ServerName = incMsg.ReadString();
}
if (requiredFlags.HasFlag(NetFlags.Message))
{
ServerMessageText = incMsg.ReadString();
}
PlayStyle = (PlayStyle)incMsg.ReadByte();
MaxPlayers = incMsg.ReadByte(); MaxPlayers = incMsg.ReadByte();
HasPassword = incMsg.ReadBoolean(); HasPassword = incMsg.ReadBoolean();
IsPublic = incMsg.ReadBoolean(); IsPublic = incMsg.ReadBoolean();
@@ -139,9 +149,13 @@ namespace Barotrauma.Networking
TickRate = incMsg.ReadRangedInteger(1, 60); TickRate = incMsg.ReadRangedInteger(1, 60);
GameMain.NetworkMember.TickRate = TickRate; GameMain.NetworkMember.TickRate = TickRate;
ReadExtraCargo(incMsg); if (requiredFlags.HasFlag(NetFlags.Properties))
{
Voting.ClientRead(incMsg); ReadExtraCargo(incMsg);
}
ReadHiddenSubs(incMsg);
GameMain.NetLobbyScreen.UpdateSubVisibility();
bool isAdmin = incMsg.ReadBoolean(); bool isAdmin = incMsg.ReadBoolean();
incMsg.ReadPadBits(); incMsg.ReadPadBits();
@@ -151,7 +165,7 @@ namespace Barotrauma.Networking
} }
} }
public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? radiationEnabled = null, bool? useRespawnShuttle = null, int maxMissionCount = 0) public void ClientAdminWrite(NetFlags dataToSend, int? missionTypeOr = null, int? missionTypeAnd = null, float? levelDifficulty = null, bool? autoRestart = null, int traitorSetting = 0, int botCount = 0, int botSpawnMode = 0, bool? useRespawnShuttle = null)
{ {
if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return; if (!GameMain.Client.HasPermission(Networking.ClientPermissions.ManageSettings)) return;
@@ -202,6 +216,11 @@ namespace Barotrauma.Networking
Whitelist.ClientAdminWrite(outMsg); Whitelist.ClientAdminWrite(outMsg);
} }
if (dataToSend.HasFlag(NetFlags.HiddenSubs))
{
WriteHiddenSubs(outMsg);
}
if (dataToSend.HasFlag(NetFlags.Misc)) if (dataToSend.HasFlag(NetFlags.Misc))
{ {
outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All); outMsg.WriteRangedInteger(missionTypeOr ?? (int)Barotrauma.MissionType.None, 0, (int)Barotrauma.MissionType.All);
@@ -216,8 +235,6 @@ namespace Barotrauma.Networking
outMsg.Write(autoRestart != null); outMsg.Write(autoRestart != null);
outMsg.Write(autoRestart ?? false); outMsg.Write(autoRestart ?? false);
outMsg.Write(radiationEnabled ?? RadiationEnabled);
outMsg.Write((byte)maxMissionCount + 1);
outMsg.WritePadBits(); outMsg.WritePadBits();
} }
@@ -288,7 +305,7 @@ namespace Barotrauma.Networking
}; };
//center frames //center frames
GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.4f, 0.8f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) }); GUIFrame innerFrame = new GUIFrame(new RectTransform(new Vector2(0.5f, 0.85f), settingsFrame.RectTransform, Anchor.Center) { MinSize = new Point(400, 430) });
GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), innerFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter) GUILayoutGroup paddedFrame = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.95f), innerFrame.RectTransform, Anchor.Center), childAnchor: Anchor.TopCenter)
{ {
Stretch = true, Stretch = true,
@@ -363,7 +380,7 @@ namespace Barotrauma.Networking
selectionFrame.RectTransform.NonScaledSize = new Point(selectionFrame.Rect.Width, selectionFrame.Children.First().Rect.Height); selectionFrame.RectTransform.NonScaledSize = new Point(selectionFrame.Rect.Width, selectionFrame.Children.First().Rect.Height);
selectionFrame.RectTransform.IsFixedSize = true; selectionFrame.RectTransform.IsFixedSize = true;
GetPropertyData("SubSelectionMode").AssignGUIComponent(selectionMode); GetPropertyData(nameof(SubSelectionMode)).AssignGUIComponent(selectionMode);
// Mode Selection // Mode Selection
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsModeSelection"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), serverTab.RectTransform), TextManager.Get("ServerSettingsModeSelection"), font: GUI.SubHeadingFont);
@@ -381,7 +398,7 @@ namespace Barotrauma.Networking
} }
selectionFrame.RectTransform.NonScaledSize = new Point(selectionFrame.Rect.Width, selectionFrame.Children.First().Rect.Height); selectionFrame.RectTransform.NonScaledSize = new Point(selectionFrame.Rect.Width, selectionFrame.Children.First().Rect.Height);
selectionFrame.RectTransform.IsFixedSize = true; selectionFrame.RectTransform.IsFixedSize = true;
GetPropertyData("ModeSelectionMode").AssignGUIComponent(selectionMode); GetPropertyData(nameof(ModeSelectionMode)).AssignGUIComponent(selectionMode);
new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), style: "HorizontalLine"); new GUIFrame(new RectTransform(new Vector2(1.0f, 0.02f), serverTab.RectTransform), style: "HorizontalLine");
@@ -389,7 +406,7 @@ namespace Barotrauma.Networking
var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), var voiceChatEnabled = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
TextManager.Get("ServerSettingsVoiceChatEnabled")); TextManager.Get("ServerSettingsVoiceChatEnabled"));
GetPropertyData("VoiceChatEnabled").AssignGUIComponent(voiceChatEnabled); GetPropertyData(nameof(VoiceChatEnabled)).AssignGUIComponent(voiceChatEnabled);
//*********************************************** //***********************************************
@@ -407,14 +424,14 @@ namespace Barotrauma.Networking
} }
}; };
startIntervalSlider.Range = new Vector2(10.0f, 300.0f); startIntervalSlider.Range = new Vector2(10.0f, 300.0f);
GetPropertyData("AutoRestartInterval").AssignGUIComponent(startIntervalSlider); GetPropertyData(nameof(AutoRestartInterval)).AssignGUIComponent(startIntervalSlider);
startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll); startIntervalSlider.OnMoved(startIntervalSlider, startIntervalSlider.BarScroll);
//*********************************************** //***********************************************
var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), var startWhenClientsReady = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform),
TextManager.Get("ServerSettingsStartWhenClientsReady")); TextManager.Get("ServerSettingsStartWhenClientsReady"));
GetPropertyData("StartWhenClientsReady").AssignGUIComponent(startWhenClientsReady); GetPropertyData(nameof(StartWhenClientsReady)).AssignGUIComponent(startWhenClientsReady);
CreateLabeledSlider(serverTab, "ServerSettingsStartWhenClientsReadyRatio", out GUIScrollBar slider, out GUITextBlock sliderLabel); CreateLabeledSlider(serverTab, "ServerSettingsStartWhenClientsReadyRatio", out GUIScrollBar slider, out GUITextBlock sliderLabel);
string clientsReadyRequiredLabel = sliderLabel.Text; string clientsReadyRequiredLabel = sliderLabel.Text;
@@ -425,19 +442,19 @@ namespace Barotrauma.Networking
((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString()); ((GUITextBlock)scrollBar.UserData).Text = clientsReadyRequiredLabel.Replace("[percentage]", ((int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f)).ToString());
return true; return true;
}; };
GetPropertyData("StartWhenClientsReadyRatio").AssignGUIComponent(slider); GetPropertyData(nameof(StartWhenClientsReadyRatio)).AssignGUIComponent(slider);
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
//*********************************************** //***********************************************
var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowSpectating")); var allowSpecBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsAllowSpectating"));
GetPropertyData("AllowSpectating").AssignGUIComponent(allowSpecBox); GetPropertyData(nameof(AllowSpectating)).AssignGUIComponent(allowSpecBox);
var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsShareSubFiles")); var shareSubsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsShareSubFiles"));
GetPropertyData("AllowFileTransfers").AssignGUIComponent(shareSubsBox); GetPropertyData(nameof(AllowFileTransfers)).AssignGUIComponent(shareSubsBox);
var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed")); var randomizeLevelBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsRandomizeSeed"));
GetPropertyData("RandomizeSeed").AssignGUIComponent(randomizeLevelBox); GetPropertyData(nameof(RandomizeSeed)).AssignGUIComponent(randomizeLevelBox);
var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSaveLogs")) var saveLogsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), serverTab.RectTransform), TextManager.Get("ServerSettingsSaveLogs"))
{ {
@@ -448,7 +465,7 @@ namespace Barotrauma.Networking
return true; return true;
} }
}; };
GetPropertyData("SaveServerLogs").AssignGUIComponent(saveLogsBox); GetPropertyData(nameof(SaveServerLogs)).AssignGUIComponent(saveLogsBox);
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// game settings // game settings
@@ -480,20 +497,20 @@ namespace Barotrauma.Networking
selectionPlayStyle.AddRadioButton((int)playStyle, selectionTick); selectionPlayStyle.AddRadioButton((int)playStyle, selectionTick);
playStyleTickBoxes.Add(selectionTick); playStyleTickBoxes.Add(selectionTick);
} }
GetPropertyData("PlayStyle").AssignGUIComponent(selectionPlayStyle); GetPropertyData(nameof(PlayStyle)).AssignGUIComponent(selectionPlayStyle);
GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock)); GUITextBlock.AutoScaleAndNormalize(playStyleTickBoxes.Select(t => t.TextBlock));
playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W)); playstyleList.RectTransform.MinSize = new Point(0, (int)(playstyleList.Content.Children.First().Rect.Height * 2.0f + playstyleList.Padding.Y + playstyleList.Padding.W));
var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), var endVoteBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsEndRoundVoting")); TextManager.Get("ServerSettingsEndRoundVoting"));
GetPropertyData("AllowEndVoting").AssignGUIComponent(endVoteBox); GetPropertyData(nameof(AllowEndVoting)).AssignGUIComponent(endVoteBox);
CreateLabeledSlider(roundsTab, "ServerSettingsEndRoundVotesRequired", out slider, out sliderLabel); CreateLabeledSlider(roundsTab, "ServerSettingsEndRoundVotesRequired", out slider, out sliderLabel);
string endRoundLabel = sliderLabel.Text; string endRoundLabel = sliderLabel.Text;
slider.Step = 0.2f; slider.Step = 0.2f;
slider.Range = new Vector2(0.5f, 1.0f); slider.Range = new Vector2(0.5f, 1.0f);
GetPropertyData("EndVoteRequiredRatio").AssignGUIComponent(slider); GetPropertyData(nameof(EndVoteRequiredRatio)).AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{ {
((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + " " + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; ((GUITextBlock)scrollBar.UserData).Text = endRoundLabel + " " + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
@@ -503,13 +520,13 @@ namespace Barotrauma.Networking
var respawnBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), var respawnBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("ServerSettingsAllowRespawning")); TextManager.Get("ServerSettingsAllowRespawning"));
GetPropertyData("AllowRespawn").AssignGUIComponent(respawnBox); GetPropertyData(nameof(AllowRespawn)).AssignGUIComponent(respawnBox);
CreateLabeledSlider(roundsTab, "ServerSettingsRespawnInterval", out slider, out sliderLabel); CreateLabeledSlider(roundsTab, "ServerSettingsRespawnInterval", out slider, out sliderLabel);
string intervalLabel = sliderLabel.Text; string intervalLabel = sliderLabel.Text;
slider.Range = new Vector2(10.0f, 600.0f); slider.Range = new Vector2(10.0f, 600.0f);
slider.StepValue = 10.0f; slider.StepValue = 10.0f;
GetPropertyData("RespawnInterval").AssignGUIComponent(slider); GetPropertyData(nameof(RespawnInterval)).AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{ {
GUITextBlock text = scrollBar.UserData as GUITextBlock; GUITextBlock text = scrollBar.UserData as GUITextBlock;
@@ -518,18 +535,26 @@ namespace Barotrauma.Networking
}; };
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
var minRespawnText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), "") var respawnLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.15f), roundsTab.RectTransform),
isHorizontal: true);
var minRespawnLayout
= new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnLayout.RectTransform));
var minRespawnText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), minRespawnLayout.RectTransform), "")
{ {
ToolTip = TextManager.Get("ServerSettingsMinRespawnToolTip") ToolTip = TextManager.Get("ServerSettingsMinRespawnToolTip")
}; };
string minRespawnLabel = TextManager.Get("ServerSettingsMinRespawn") + " "; string minRespawnLabel = TextManager.Get("ServerSettingsMinRespawn") + " ";
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); CreateLabeledSlider(minRespawnLayout, "", out slider, out sliderLabel);
sliderLabel.RectTransform.RelativeSize = Vector2.Zero;
slider.RectTransform.RelativeSize = new Vector2(1.0f, 0.5f);
slider.ToolTip = minRespawnText.RawToolTip; slider.ToolTip = minRespawnText.RawToolTip;
slider.UserData = minRespawnText; slider.UserData = minRespawnText;
slider.Step = 0.1f; slider.Step = 0.1f;
slider.Range = new Vector2(0.0f, 1.0f); slider.Range = new Vector2(0.0f, 1.0f);
GetPropertyData("MinRespawnRatio").AssignGUIComponent(slider); GetPropertyData(nameof(MinRespawnRatio)).AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{ {
((GUITextBlock)scrollBar.UserData).Text = minRespawnLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; ((GUITextBlock)scrollBar.UserData).Text = minRespawnLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
@@ -537,13 +562,18 @@ namespace Barotrauma.Networking
}; };
slider.OnMoved(slider, MinRespawnRatio); slider.OnMoved(slider, MinRespawnRatio);
var respawnDurationText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), "") var respawnDurationLayout
= new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), respawnLayout.RectTransform));
var respawnDurationText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), respawnDurationLayout.RectTransform), "")
{ {
ToolTip = TextManager.Get("ServerSettingsRespawnDurationToolTip") ToolTip = TextManager.Get("ServerSettingsRespawnDurationToolTip")
}; };
string respawnDurationLabel = TextManager.Get("ServerSettingsRespawnDuration") + " "; string respawnDurationLabel = TextManager.Get("ServerSettingsRespawnDuration") + " ";
CreateLabeledSlider(roundsTab, "", out slider, out sliderLabel); CreateLabeledSlider(respawnDurationLayout, "", out slider, out sliderLabel);
sliderLabel.RectTransform.RelativeSize = Vector2.Zero;
slider.RectTransform.RelativeSize = new Vector2(1.0f, 0.5f);
slider.ToolTip = respawnDurationText.RawToolTip; slider.ToolTip = respawnDurationText.RawToolTip;
slider.UserData = respawnDurationText; slider.UserData = respawnDurationText;
slider.Step = 0.1f; slider.Step = 0.1f;
@@ -556,7 +586,7 @@ namespace Barotrauma.Networking
{ {
return value <= 0.0f ? 1.0f : (value - scrollBar.Range.X) / (scrollBar.Range.Y - scrollBar.Range.X); return value <= 0.0f ? 1.0f : (value - scrollBar.Range.X) / (scrollBar.Range.Y - scrollBar.Range.X);
}; };
GetPropertyData("MaxTransportTime").AssignGUIComponent(slider); GetPropertyData(nameof(MaxTransportTime)).AssignGUIComponent(slider);
slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) => slider.OnMoved = (GUIScrollBar scrollBar, float barScroll) =>
{ {
if (barScroll == 1.0f) if (barScroll == 1.0f)
@@ -572,14 +602,34 @@ namespace Barotrauma.Networking
}; };
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
var losModeLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform),
TextManager.Get("LosEffect"));
var losModeRadioButtonLayout
= new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.1f), roundsTab.RectTransform),
isHorizontal: true)
{
Stretch = true
};
var losModeRadioButtonGroup = new GUIRadioButtonGroup();
LosMode[] losModes = (LosMode[])Enum.GetValues(typeof(LosMode));
for (int i = 0; i < losModes.Length; i++)
{
var losTick = new GUITickBox(new RectTransform(new Vector2(0.3f, 1.0f), losModeRadioButtonLayout.RectTransform), TextManager.Get($"LosMode{losModes[i]}"), font: GUI.SmallFont, style: "GUIRadioButton");
losModeRadioButtonGroup.AddRadioButton(i, losTick);
}
GetPropertyData(nameof(LosMode)).AssignGUIComponent(losModeRadioButtonGroup);
var traitorsMinPlayerCount = CreateLabeledNumberInput(roundsTab, "ServerSettingsTraitorsMinPlayerCount", 1, 16, "ServerSettingsTraitorsMinPlayerCountToolTip"); var traitorsMinPlayerCount = CreateLabeledNumberInput(roundsTab, "ServerSettingsTraitorsMinPlayerCount", 1, 16, "ServerSettingsTraitorsMinPlayerCountToolTip");
GetPropertyData("TraitorsMinPlayerCount").AssignGUIComponent(traitorsMinPlayerCount); GetPropertyData(nameof(TraitorsMinPlayerCount)).AssignGUIComponent(traitorsMinPlayerCount);
var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton")); var ragdollButtonBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsAllowRagdollButton"));
GetPropertyData("AllowRagdollButton").AssignGUIComponent(ragdollButtonBox); GetPropertyData(nameof(AllowRagdollButton)).AssignGUIComponent(ragdollButtonBox);
var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations")); var disableBotConversationsBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), roundsTab.RectTransform), TextManager.Get("ServerSettingsDisableBotConversations"));
GetPropertyData("DisableBotConversations").AssignGUIComponent(disableBotConversationsBox); GetPropertyData(nameof(DisableBotConversations)).AssignGUIComponent(disableBotConversationsBox);
var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true) var buttonHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.07f), roundsTab.RectTransform), isHorizontal: true)
{ {
@@ -669,7 +719,6 @@ namespace Barotrauma.Networking
RelativeSpacing = 0.05f RelativeSpacing = 0.05f
}; };
if (ip.InventoryIcon != null || ip.sprite != null) if (ip.InventoryIcon != null || ip.sprite != null)
{ {
GUIImage img = new GUIImage(new RectTransform(new Point(itemFrame.Rect.Height), itemFrame.RectTransform), GUIImage img = new GUIImage(new RectTransform(new Point(itemFrame.Rect.Height), itemFrame.RectTransform),
@@ -692,7 +741,7 @@ namespace Barotrauma.Networking
GUINumberInput.NumberType.Int, textAlignment: Alignment.CenterLeft) GUINumberInput.NumberType.Int, textAlignment: Alignment.CenterLeft)
{ {
MinValueInt = 0, MinValueInt = 0,
MaxValueInt = 100, MaxValueInt = MaxExtraCargoItemsOfType,
IntValue = cargoVal IntValue = cargoVal
}; };
amountInput.OnValueChanged += (numberInput) => amountInput.OnValueChanged += (numberInput) =>
@@ -700,16 +749,26 @@ namespace Barotrauma.Networking
if (ExtraCargo.ContainsKey(ip)) if (ExtraCargo.ContainsKey(ip))
{ {
ExtraCargo[ip] = numberInput.IntValue; ExtraCargo[ip] = numberInput.IntValue;
if (numberInput.IntValue <= 0) ExtraCargo.Remove(ip); if (numberInput.IntValue <= 0) { ExtraCargo.Remove(ip); }
} }
else else if (ExtraCargo.Keys.Count() < MaxExtraCargoItemTypes)
{ {
ExtraCargo.Add(ip, numberInput.IntValue); ExtraCargo.Add(ip, numberInput.IntValue);
} }
numberInput.IntValue = ExtraCargo.ContainsKey(ip) ? ExtraCargo[ip] : 0;
CoroutineManager.Invoke(() =>
{
foreach (var child in cargoFrame.Content.GetAllChildren())
{
if (child.GetChild<GUINumberInput>() is GUINumberInput otherNumberInput)
{
otherNumberInput.PlusButton.Enabled = ExtraCargo.Keys.Count() < MaxExtraCargoItemTypes && otherNumberInput.IntValue < otherNumberInput.MaxValueInt;
}
}
}, 0.0f);
}; };
} }
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
// antigriefing // antigriefing
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
@@ -729,35 +788,35 @@ namespace Barotrauma.Networking
var allowFriendlyFire = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var allowFriendlyFire = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowFriendlyFire")); TextManager.Get("ServerSettingsAllowFriendlyFire"));
GetPropertyData("AllowFriendlyFire").AssignGUIComponent(allowFriendlyFire); GetPropertyData(nameof(AllowFriendlyFire)).AssignGUIComponent(allowFriendlyFire);
var killableNPCs = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var killableNPCs = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsKillableNPCs")); TextManager.Get("ServerSettingsKillableNPCs"));
GetPropertyData("KillableNPCs").AssignGUIComponent(killableNPCs); GetPropertyData(nameof(KillableNPCs)).AssignGUIComponent(killableNPCs);
var destructibleOutposts = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var destructibleOutposts = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsDestructibleOutposts")); TextManager.Get("ServerSettingsDestructibleOutposts"));
GetPropertyData("DestructibleOutposts").AssignGUIComponent(destructibleOutposts); GetPropertyData(nameof(DestructibleOutposts)).AssignGUIComponent(destructibleOutposts);
var lockAllDefaultWires = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var lockAllDefaultWires = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsLockAllDefaultWires")); TextManager.Get("ServerSettingsLockAllDefaultWires"));
GetPropertyData("LockAllDefaultWires").AssignGUIComponent(lockAllDefaultWires); GetPropertyData(nameof(LockAllDefaultWires)).AssignGUIComponent(lockAllDefaultWires);
var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var allowRewiring = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowRewiring")); TextManager.Get("ServerSettingsAllowRewiring"));
GetPropertyData("AllowRewiring").AssignGUIComponent(allowRewiring); GetPropertyData(nameof(AllowRewiring)).AssignGUIComponent(allowRewiring);
var allowWifiChatter = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var allowWifiChatter = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowWifiChat")); TextManager.Get("ServerSettingsAllowWifiChat"));
GetPropertyData("AllowLinkingWifiToChat").AssignGUIComponent(allowWifiChatter); GetPropertyData(nameof(AllowLinkingWifiToChat)).AssignGUIComponent(allowWifiChatter);
var allowDisguises = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var allowDisguises = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowDisguises")); TextManager.Get("ServerSettingsAllowDisguises"));
GetPropertyData("AllowDisguises").AssignGUIComponent(allowDisguises); GetPropertyData(nameof(AllowDisguises)).AssignGUIComponent(allowDisguises);
var voteKickBox = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform), var voteKickBox = new GUITickBox(new RectTransform(new Vector2(0.48f, 0.05f), tickBoxContainer.Content.RectTransform),
TextManager.Get("ServerSettingsAllowVoteKick")); TextManager.Get("ServerSettingsAllowVoteKick"));
GetPropertyData("AllowVoteKick").AssignGUIComponent(voteKickBox); GetPropertyData(nameof(AllowVoteKick)).AssignGUIComponent(voteKickBox);
GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock)); GUITextBlock.AutoScaleAndNormalize(tickBoxContainer.Content.Children.Select(c => ((GUITickBox)c).TextBlock));
@@ -772,7 +831,7 @@ namespace Barotrauma.Networking
((GUITextBlock)scrollBar.UserData).Text = votesRequiredLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %"; ((GUITextBlock)scrollBar.UserData).Text = votesRequiredLabel + (int)MathUtils.Round(scrollBar.BarScrollValue * 100.0f, 10.0f) + " %";
return true; return true;
}; };
GetPropertyData("KickVoteRequiredRatio").AssignGUIComponent(slider); GetPropertyData(nameof(KickVoteRequiredRatio)).AssignGUIComponent(slider);
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
CreateLabeledSlider(antigriefingTab, "ServerSettingsAutobanTime", out slider, out sliderLabel); CreateLabeledSlider(antigriefingTab, "ServerSettingsAutobanTime", out slider, out sliderLabel);
@@ -784,13 +843,13 @@ namespace Barotrauma.Networking
((GUITextBlock)scrollBar.UserData).Text = autobanLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue); ((GUITextBlock)scrollBar.UserData).Text = autobanLabel + ToolBox.SecondsToReadableTime(scrollBar.BarScrollValue);
return true; return true;
}; };
GetPropertyData("AutoBanTime").AssignGUIComponent(slider); GetPropertyData(nameof(AutoBanTime)).AssignGUIComponent(slider);
slider.OnMoved(slider, slider.BarScroll); slider.OnMoved(slider, slider.BarScroll);
var wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword")); var wrongPasswordBanBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsBanAfterWrongPassword"));
GetPropertyData("BanAfterWrongPassword").AssignGUIComponent(wrongPasswordBanBox); GetPropertyData(nameof(BanAfterWrongPassword)).AssignGUIComponent(wrongPasswordBanBox);
var allowedPasswordRetries = CreateLabeledNumberInput(antigriefingTab, "ServerSettingsPasswordRetriesBeforeBan", 0, 10); var allowedPasswordRetries = CreateLabeledNumberInput(antigriefingTab, "ServerSettingsPasswordRetriesBeforeBan", 0, 10);
GetPropertyData("MaxPasswordRetriesBeforeBan").AssignGUIComponent(allowedPasswordRetries); GetPropertyData(nameof(MaxPasswordRetriesBeforeBan)).AssignGUIComponent(allowedPasswordRetries);
wrongPasswordBanBox.OnSelected += (tb) => wrongPasswordBanBox.OnSelected += (tb) =>
{ {
allowedPasswordRetries.Enabled = tb.Selected; allowedPasswordRetries.Enabled = tb.Selected;
@@ -800,7 +859,7 @@ namespace Barotrauma.Networking
// karma -------------------------------------------------------------------------- // karma --------------------------------------------------------------------------
var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsUseKarma")); var karmaBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform), TextManager.Get("ServerSettingsUseKarma"));
GetPropertyData("KarmaEnabled").AssignGUIComponent(karmaBox); GetPropertyData(nameof(KarmaEnabled)).AssignGUIComponent(karmaBox);
karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform)); karmaPresetDD = new GUIDropDown(new RectTransform(new Vector2(1.0f, 0.05f), antigriefingTab.RectTransform));
foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys) foreach (string karmaPreset in GameMain.NetworkMember.KarmaManager.Presets.Keys)
@@ -871,6 +930,7 @@ namespace Barotrauma.Networking
//-------------------------------------------------------------------------------- //--------------------------------------------------------------------------------
Whitelist.CreateWhiteListFrame(settingsTabs[(int)SettingsTab.Whitelist]); Whitelist.CreateWhiteListFrame(settingsTabs[(int)SettingsTab.Whitelist]);
Whitelist.localEnabled = Whitelist.Enabled;
} }
private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label) private void CreateLabeledSlider(GUIComponent parent, string labelTag, out GUIScrollBar slider, out GUITextBlock label)
@@ -147,7 +147,14 @@ namespace Barotrauma.Steam
if (currentLobby == null) if (currentLobby == null)
{ {
DebugConsole.ThrowError("Failed to create Steam lobby"); DebugConsole.ThrowError("Failed to create Steam lobby: returned lobby was null");
lobbyState = LobbyState.NotConnected;
return;
}
if (currentLobby.Value.Result != Steamworks.Result.OK)
{
DebugConsole.ThrowError($"Failed to create Steam lobby: result was {currentLobby.Value.Result}");
lobbyState = LobbyState.NotConnected; lobbyState = LobbyState.NotConnected;
return; return;
} }
@@ -525,18 +532,6 @@ namespace Barotrauma.Steam
} }
#region Connecting to servers #region Connecting to servers
private static Steamworks.AuthTicket currentTicket = null;
public static Steamworks.AuthTicket GetAuthSessionTicket()
{
if (!isInitialized)
{
return null;
}
currentTicket?.Cancel();
currentTicket = Steamworks.SteamUser.GetAuthSessionTicket();
return currentTicket;
}
public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID) public static Steamworks.BeginAuthResult StartAuthSession(byte[] authTicketData, ulong clientSteamID)
{ {
@@ -884,9 +879,9 @@ namespace Barotrauma.Steam
catch (Exception e) catch (Exception e)
{ {
string errorMsg = "Failed to save workshop item preview image to \"" + previewImagePath + "\" when creating workshop item staging folder."; string errorMsg = "Failed to save workshop item preview image when creating workshop item staging folder.";
GameAnalyticsManager.AddErrorEventOnce("SteamManager.CreateWorkshopItemStaging:WriteAllBytesFailed" + previewImagePath, GameAnalyticsManager.AddErrorEventOnce("SteamManager.CreateWorkshopItemStaging:WriteAllBytesFailed" + previewImagePath,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg + "\n" + e.Message); GameAnalyticsManager.ErrorSeverity.Error, errorMsg + "\n" + e.Message);
} }
return true; return true;
@@ -935,7 +930,7 @@ namespace Barotrauma.Steam
return workshopPublishStatus; return workshopPublishStatus;
} }
private static IEnumerable<object> PublishItem(WorkshopPublishStatus workshopPublishStatus) private static IEnumerable<CoroutineStatus> PublishItem(WorkshopPublishStatus workshopPublishStatus)
{ {
if (!isInitialized) if (!isInitialized)
{ {
@@ -1434,8 +1429,8 @@ namespace Barotrauma.Steam
"\" not found. Could not combine path (" + (item.Directory ?? "directory name empty") + ")."; "\" not found. Could not combine path (" + (item.Directory ?? "directory name empty") + ").";
DebugConsole.ThrowError(errorMessage); DebugConsole.ThrowError(errorMessage);
GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemInstalled:PathCombineException" + item.Title, GameAnalyticsManager.AddErrorEventOnce("SteamManager.CheckWorkshopItemInstalled:PathCombineException" + item.Title,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
errorMessage); "Metadata file for a Workshop item not found. Could not combine path.");
return false; return false;
} }
@@ -1567,8 +1562,8 @@ namespace Barotrauma.Steam
} }
GameAnalyticsManager.AddErrorEventOnce( GameAnalyticsManager.AddErrorEventOnce(
"SteamManager.AutoUpdateWorkshopItems:" + e.Message, "SteamManager.AutoUpdateWorkshopItems:" + e.Message,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
"Failed to autoupdate workshop item \"" + item.Title + "\". " + e.Message + "\n" + e.StackTrace.CleanupStackTrace()); "Failed to autoupdate workshop item. " + e.Message + "\n" + e.StackTrace.CleanupStackTrace());
}); });
} }
} }
@@ -113,7 +113,7 @@ namespace Barotrauma.Networking
UserData = "capturedevicenotfound" UserData = "capturedevicenotfound"
}; };
} }
GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.AddErrorEventOnce("Alc.CaptureDeviceOpenFailed", GameAnalyticsManager.ErrorSeverity.Error,
"Alc.CaptureDeviceOpen(" + deviceName + ") failed. Error code: " + errorCode); "Alc.CaptureDeviceOpen(" + deviceName + ") failed. Error code: " + errorCode);
GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled; GameMain.Config.VoiceSetting = GameSettings.VoiceMode.Disabled;
Instance?.Dispose(); Instance?.Dispose();
@@ -17,7 +17,11 @@ namespace Barotrauma
allowSubVoting = value; allowSubVoting = value;
GameMain.NetLobbyScreen.SubList.Enabled = value || GameMain.NetLobbyScreen.SubList.Enabled = value ||
(GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub)); (GameMain.Client != null && GameMain.Client.HasPermission(ClientPermissions.SelectSub));
GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true).Visible = value; var subVotesLabel = GameMain.NetLobbyScreen.Frame.FindChild("subvotes", true) as GUITextBlock;
subVotesLabel.Visible = value;
var subVisButton = GameMain.NetLobbyScreen.SubVisibilityButton;
subVisButton.RectTransform.AbsoluteOffset
= new Point(value ? (int)(subVotesLabel.TextSize.X + subVisButton.Rect.Width) : 0, 0);
UpdateVoteTexts(null, VoteType.Sub); UpdateVoteTexts(null, VoteType.Sub);
GameMain.NetLobbyScreen.SubList.Deselect(); GameMain.NetLobbyScreen.SubList.Deselect();
@@ -66,10 +70,10 @@ namespace Barotrauma
if (clients == null) { return; } if (clients == null) { return; }
List<Pair<object, int>> voteList = GetVoteList(voteType, clients); IReadOnlyDictionary<object, int> voteList = GetVoteCounts<object>(voteType, clients);
foreach (Pair<object, int> votable in voteList) foreach (KeyValuePair<object, int> votable in voteList)
{ {
SetVoteText(listBox, votable.First, votable.Second); SetVoteText(listBox, votable.Key, votable.Value);
} }
break; break;
case VoteType.StartRound: case VoteType.StartRound:
@@ -56,17 +56,12 @@ namespace Barotrauma.Networking
{ {
nameBox.Enabled = box.Selected; nameBox.Enabled = box.Selected;
ipBox.Enabled = box.Selected; ipBox.Enabled = box.Selected;
addNewButton.Enabled = box.Selected; addNewButton.Enabled = box.Selected && !string.IsNullOrEmpty(ipBox.Text) && !string.IsNullOrEmpty(nameBox.Text);
localEnabled = box.Selected; localEnabled = box.Selected;
return true; return true;
} }
}; };
localEnabled = Enabled;
var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform)); var listBox = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.7f), whitelistFrame.RectTransform));
foreach (WhiteListedPlayer wlp in whitelistedPlayers) foreach (WhiteListedPlayer wlp in whitelistedPlayers)
{ {
@@ -106,8 +106,12 @@ namespace Barotrauma.Particles
public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null) public void Init(ParticlePrefab prefab, Vector2 position, Vector2 speed, float rotation, Hull hullGuess = null, bool drawOnTop = false, float collisionIgnoreTimer = 0f, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
this.prefab = prefab; this.prefab = prefab;
#if DEBUG
debugName = $"Particle ({prefab.Name})"; debugName = $"Particle ({prefab.Name})";
#else
//don't instantiate new string objects in release builds
debugName = prefab.Name;
#endif
spriteIndex = Rand.Int(prefab.Sprites.Count); spriteIndex = Rand.Int(prefab.Sprites.Count);
animState = 0; animState = 0;
@@ -136,7 +136,7 @@ namespace Barotrauma.Particles
Prefab = prefab; Prefab = prefab;
} }
public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null) public void Emit(float deltaTime, Vector2 position, Hull hullGuess = null, float angle = 0.0f, float particleRotation = 0.0f, float velocityMultiplier = 1.0f, float sizeMultiplier = 1.0f, float amountMultiplier = 1.0f, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
if (GameMain.Client?.MidRoundSyncing ?? false) { return; } if (GameMain.Client?.MidRoundSyncing ?? false) { return; }
@@ -159,7 +159,7 @@ namespace Barotrauma.Particles
for (float z = 0.0f; z < dist; z += Prefab.Properties.EmitAcrossRayInterval) for (float z = 0.0f; z < dist; z += Prefab.Properties.EmitAcrossRayInterval)
{ {
Vector2 pos = tracerPoints.Item1 + dir * z; Vector2 pos = tracerPoints.Item1 + dir * z;
Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: null); Emit(pos, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: null);
} }
} }
} }
@@ -169,7 +169,7 @@ namespace Barotrauma.Particles
float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond; float emitInterval = 1.0f / Prefab.Properties.ParticlesPerSecond;
while (emitTimer > emitInterval) while (emitTimer > emitInterval)
{ {
Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, tracerPoints: tracerPoints); Emit(position, hullGuess, angle, particleRotation, velocityMultiplier, sizeMultiplier, colorMultiplier, overrideParticle, mirrorAngle, tracerPoints: tracerPoints);
emitTimer -= emitInterval; emitTimer -= emitInterval;
} }
} }
@@ -183,16 +183,19 @@ namespace Barotrauma.Particles
} }
} }
private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, Tuple<Vector2, Vector2> tracerPoints = null) private void Emit(Vector2 position, Hull hullGuess, float angle, float particleRotation, float velocityMultiplier, float sizeMultiplier, Color? colorMultiplier = null, ParticlePrefab overrideParticle = null, bool mirrorAngle = false, Tuple<Vector2, Vector2> tracerPoints = null)
{ {
var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab; var particlePrefab = overrideParticle ?? Prefab.ParticlePrefab;
if (particlePrefab == null) { return; } if (particlePrefab == null) { return; }
angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad); Vector2 velocity = Vector2.Zero;
if (!MathUtils.NearlyEqual(Prefab.Properties.VelocityMax * velocityMultiplier, 0.0f) || !MathUtils.NearlyEqual(Prefab.Properties.DistanceMax, 0.0f))
Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle)); {
Vector2 velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier; angle += Rand.Range(Prefab.Properties.AngleMinRad, Prefab.Properties.AngleMaxRad) * (mirrorAngle ? -1 : 1);
position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax); Vector2 dir = new Vector2((float)Math.Cos(angle), (float)Math.Sin(angle));
velocity = dir * Rand.Range(Prefab.Properties.VelocityMin, Prefab.Properties.VelocityMax) * velocityMultiplier;
position += dir * Rand.Range(Prefab.Properties.DistanceMin, Prefab.Properties.DistanceMax);
}
var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints); var particle = GameMain.ParticleManager.CreateParticle(particlePrefab, position, velocity, particleRotation, hullGuess, Prefab.DrawOnTop, tracerPoints: tracerPoints);
@@ -167,8 +167,8 @@ namespace Barotrauma.Particles
if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; } if (minPos.X > expandedViewRect.Right || maxPos.X < expandedViewRect.X) { return null; }
if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; } if (minPos.Y > expandedViewRect.Y || maxPos.Y < expandedViewRect.Y - expandedViewRect.Height) { return null; }
if (particles[particleCount] == null) particles[particleCount] = new Particle(); if (particles[particleCount] == null) { particles[particleCount] = new Particle(); }
particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints); particles[particleCount].Init(prefab, position, velocity, rotation, hullGuess, drawOnTop, collisionIgnoreTimer, tracerPoints: tracerPoints);
@@ -201,7 +201,7 @@ namespace Barotrauma
DebugConsole.ThrowError(errorMsg); DebugConsole.ThrowError(errorMsg);
#endif #endif
GameAnalyticsManager.AddErrorEventOnce("PhysicsBody.ClientRead:InvalidData" + parentDebugName, GameAnalyticsManager.AddErrorEventOnce("PhysicsBody.ClientRead:InvalidData" + parentDebugName,
GameAnalyticsSDK.Net.EGAErrorSeverity.Error, GameAnalyticsManager.ErrorSeverity.Error,
errorMsg); errorMsg);
return null; return null;
} }
@@ -1,11 +1,9 @@
#region Using Statements #region Using Statements
using System; using System;
using System.Collections.Generic;
using Barotrauma.IO; using Barotrauma.IO;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
using GameAnalyticsSDK.Net;
using Barotrauma.Steam; using Barotrauma.Steam;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
@@ -245,6 +243,13 @@ namespace Barotrauma
} }
} }
if (GameAnalyticsManager.SendUserStatistics)
{
//send crash report before appending debug console messages (which may contain non-anonymous information)
GameAnalyticsManager.AddErrorEvent(GameAnalyticsManager.ErrorSeverity.Critical, sb.ToString());
GameAnalyticsManager.ShutDown();
}
sb.AppendLine("Last debug messages:"); sb.AppendLine("Last debug messages:");
for (int i = DebugConsole.Messages.Count - 1; i >= 0; i--) for (int i = DebugConsole.Messages.Count - 1; i >= 0; i--)
{ {
@@ -255,13 +260,11 @@ namespace Barotrauma
File.WriteAllText(filePath, crashReport); File.WriteAllText(filePath, crashReport);
if (GameSettings.SaveDebugConsoleLogs) DebugConsole.SaveLogs(); if (GameSettings.SaveDebugConsoleLogs || GameSettings.VerboseLogging) { DebugConsole.SaveLogs(); }
if (GameSettings.SendUserStatistics) if (GameAnalyticsManager.SendUserStatistics)
{ {
CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.", filePath); CrashMessageBox("A crash report (\"" + filePath + "\") was saved in the root folder of the game and sent to the developers.", filePath);
GameAnalytics.AddErrorEvent(EGAErrorSeverity.Critical, crashReport);
GameAnalytics.OnQuit();
} }
else else
{ {
@@ -7,14 +7,10 @@ namespace Barotrauma
{ {
protected readonly GUIComponent newGameContainer, loadGameContainer; protected readonly GUIComponent newGameContainer, loadGameContainer;
protected GUIListBox subList;
protected GUIListBox saveList; protected GUIListBox saveList;
protected List<GUITickBox> subTickBoxes;
protected GUITextBox saveNameBox, seedBox; protected GUITextBox saveNameBox, seedBox;
protected GUILayoutGroup subPreviewContainer;
protected GUIButton loadGameButton; protected GUIButton loadGameButton;
public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame; public Action<SubmarineInfo, string, string, CampaignSettings> StartNewGame;
@@ -7,6 +7,7 @@ using System.Linq;
using System.Xml.Linq; using System.Xml.Linq;
using System.Globalization; using System.Globalization;
using Barotrauma.Extensions; using Barotrauma.Extensions;
using Barotrauma.Networking;
namespace Barotrauma namespace Barotrauma
{ {
@@ -14,55 +15,79 @@ namespace Barotrauma
{ {
private GUIButton deleteMpSaveButton; private GUIButton deleteMpSaveButton;
public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<SubmarineInfo> submarines, IEnumerable<string> saveFiles = null) public MultiPlayerCampaignSetupUI(GUIComponent newGameContainer, GUIComponent loadGameContainer, IEnumerable<string> saveFiles = null)
: base(newGameContainer, loadGameContainer) : base(newGameContainer, loadGameContainer)
{ {
var columnContainer = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: true) var verticalLayout = new GUILayoutGroup(new RectTransform(Vector2.One, newGameContainer.RectTransform), isHorizontal: false)
{ {
Stretch = true, Stretch = true,
RelativeSpacing = 0.0f RelativeSpacing = 0.0f
}; };
var leftColumn = new GUILayoutGroup(new RectTransform(Vector2.One, columnContainer.RectTransform)) // New game
{ new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft);
Stretch = true, saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
RelativeSpacing = 0.015f
};
var rightColumn = new GUILayoutGroup(new RectTransform(Vector2.Zero, columnContainer.RectTransform))
{
Stretch = true,
RelativeSpacing = 0.015f
};
columnContainer.Recalculate();
// New game left side
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("SaveName"), font: GUI.SubHeadingFont);
saveNameBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, string.Empty)
{ {
textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); } textFilterFunction = (string str) => { return ToolBox.RemoveInvalidFileNameChars(str); }
}; };
new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.02f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont); new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, TextManager.Get("MapSeed"), font: GUI.SubHeadingFont, textAlignment: Alignment.BottomLeft);
seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.05f), leftColumn.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8)); seedBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 0.03f), verticalLayout.RectTransform) { MinSize = new Point(0, 20) }, ToolBox.RandomSeed(8));
// Spacing to fix the multiplayer campaign setup layout GUIFrame radiationBoxContainer
CreateMultiplayerCampaignSubList(leftColumn.RectTransform); = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), verticalLayout.RectTransform), style: null);
GUITickBox radiationEnabledTickBox = null;
//spacing if (MapGenerationParams.Instance.RadiationParams != null)
//new GUIFrame(new RectTransform(new Vector2(1.0f, 0.25f), leftColumn.RectTransform), style: null);
// New game right side
subPreviewContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), rightColumn.RectTransform))
{ {
Stretch = true radiationEnabledTickBox = new GUITickBox(new RectTransform(new Vector2(1.0f, 0.5f), radiationBoxContainer.RectTransform, Anchor.Center), TextManager.Get("CampaignOption.EnableRadiation"), font: GUI.Style.Font)
{
Selected = true,
OnSelected = box => true
};
}
var maxMissionCountSettingHolder = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), verticalLayout.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
var maxMissionCountDescription = new GUITextBlock(new RectTransform(new Vector2(0.7f, 0.0f), maxMissionCountSettingHolder.RectTransform), TextManager.Get("maxmissioncount", fallBackTag: "missions"), wrap: true)
{
ToolTip = TextManager.Get("maxmissioncounttooltip")
}; };
int maxMissionCount = GameMain.NetworkMember.ServerSettings.MaxMissionCount;
var maxMissionCountContainer = new GUILayoutGroup(new RectTransform(new Vector2(0.5f, 1.0f), maxMissionCountSettingHolder.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { RelativeSpacing = 0.05f, Stretch = true };
var maxMissionCountButtons = new GUIButton[2];
maxMissionCountButtons[0]
= new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform),
style: "GUIButtonToggleLeft");
var maxMissionCountText = new GUITextBlock(new RectTransform(new Vector2(0.7f, 1.0f), maxMissionCountContainer.RectTransform), "0", textAlignment: Alignment.Center, style: "GUITextBox");
var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.12f), void updateMissionCountText()
leftColumn.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true); {
maxMissionCount = MathHelper.Clamp(maxMissionCount,
CampaignSettings.MinMissionCountLimit,
CampaignSettings.MaxMissionCountLimit);
maxMissionCountText.Text = maxMissionCount.ToString(CultureInfo.InvariantCulture);
}
maxMissionCountButtons[1]
= new GUIButton(new RectTransform(new Vector2(0.15f, 1.0f), maxMissionCountContainer.RectTransform),
style: "GUIButtonToggleRight");
maxMissionCountButtons[0].OnClicked = (button, o) =>
{
maxMissionCount--;
updateMissionCountText();
return false;
};
maxMissionCountButtons[1].OnClicked = (button, o) =>
{
maxMissionCount++;
updateMissionCountText();
return false;
};
updateMissionCountText();
maxMissionCountSettingHolder.Children.ForEach(c => c.ToolTip = maxMissionCountSettingHolder.ToolTip);
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight) { MaxSize = new Point(350, 60) }, TextManager.Get("StartCampaignButton")) var buttonContainer = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.04f),
verticalLayout.RectTransform) { MaxSize = new Point(int.MaxValue, 60) }, childAnchor: Anchor.BottomRight, isHorizontal: true);
StartButton = new GUIButton(new RectTransform(new Vector2(0.4f, 1f), buttonContainer.RectTransform, Anchor.BottomRight), TextManager.Get("StartCampaignButton"))
{ {
OnClicked = (GUIButton btn, object userData) => OnClicked = (GUIButton btn, object userData) =>
{ {
@@ -85,20 +110,19 @@ namespace Barotrauma
if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash)) if (string.IsNullOrEmpty(selectedSub.MD5Hash.Hash))
{ {
((GUITextBlock)subList.SelectedComponent).TextColor = Color.DarkRed * 0.8f; new GUIMessageBox(TextManager.Get("error"), TextManager.Get("nohashsubmarineselected"));
subList.SelectedComponent.CanBeFocused = false;
subList.Deselect();
return false; return false;
} }
string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text); string savePath = SaveUtil.CreateSavePath(SaveUtil.SaveType.Multiplayer, saveNameBox.Text);
bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled; bool hasRequiredContentPackages = selectedSub.RequiredContentPackagesInstalled;
CampaignSettings settings = new CampaignSettings(); CampaignSettings settings = new CampaignSettings
{
RadiationEnabled = radiationEnabledTickBox?.Selected ?? GameMain.NetworkMember.ServerSettings.RadiationEnabled,
MaxMissionCount = maxMissionCount
};
settings.RadiationEnabled = GameMain.NetLobbyScreen.IsRadiationEnabled();
settings.MaxMissionCount = GameMain.NetLobbyScreen.GetMaxMissionCount();
if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages) if (selectedSub.HasTag(SubmarineTag.Shuttle) || !hasRequiredContentPackages)
{ {
if (!hasRequiredContentPackages) if (!hasRequiredContentPackages)
@@ -148,7 +172,9 @@ namespace Barotrauma
return true; return true;
} }
}; };
StartButton.RectTransform.MaxSize = RectTransform.MaxPoint;
StartButton.Children.ForEach(c => c.RectTransform.MaxSize = RectTransform.MaxPoint);
InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUI.Style.SmallFont, textColor: GUI.Style.Green) InitialMoneyText = new GUITextBlock(new RectTransform(new Vector2(0.6f, 1f), buttonContainer.RectTransform), "", font: GUI.Style.SmallFont, textColor: GUI.Style.Green)
{ {
TextGetter = () => TextGetter = () =>
@@ -163,115 +189,12 @@ namespace Barotrauma
} }
}; };
columnContainer.Recalculate(); verticalLayout.Recalculate();
leftColumn.Recalculate();
rightColumn.Recalculate();
if (submarines != null) { UpdateSubList(submarines); }
UpdateLoadMenu(saveFiles); UpdateLoadMenu(saveFiles);
} }
private void CreateMultiplayerCampaignSubList(RectTransform parent) private IEnumerable<CoroutineStatus> WaitForCampaignSetup()
{
GUILayoutGroup subHolder = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.725f), parent))
{
RelativeSpacing = 0.005f,
Stretch = true
};
var subLabel = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.055f), subHolder.RectTransform) { MinSize = new Point(0, 25) }, TextManager.Get("purchasablesubmarines", fallBackTag: "workshoplabelsubmarines"), font: GUI.SubHeadingFont);
var filterContainer = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.05f), subHolder.RectTransform), isHorizontal: true)
{
Stretch = true
};
var searchTitle = new GUITextBlock(new RectTransform(new Vector2(0.001f, 1.0f), filterContainer.RectTransform), TextManager.Get("serverlog.filter"), textAlignment: Alignment.CenterLeft, font: GUI.Font);
var searchBox = new GUITextBox(new RectTransform(new Vector2(1.0f, 1.0f), filterContainer.RectTransform, Anchor.CenterRight), font: GUI.Font, createClearButton: true);
filterContainer.RectTransform.MinSize = searchBox.RectTransform.MinSize;
searchBox.OnSelected += (sender, userdata) => { searchTitle.Visible = false; };
searchBox.OnDeselected += (sender, userdata) => { searchTitle.Visible = true; };
searchBox.OnTextChanged += (textBox, text) =>
{
foreach (GUIComponent child in subList.Content.Children)
{
if (!(child.UserData is SubmarineInfo sub)) { continue; }
child.Visible = string.IsNullOrEmpty(text) ? true : sub.DisplayName.ToLower().Contains(text.ToLower());
}
return true;
};
subList = new GUIListBox(new RectTransform(Vector2.One, subHolder.RectTransform));
subTickBoxes = new List<GUITickBox>();
for (int i = 0; i < GameMain.Client.ServerSubmarines.Count; i++)
{
SubmarineInfo sub = GameMain.Client.ServerSubmarines[i];
if (!sub.IsCampaignCompatible) continue;
var frame = new GUIFrame(new RectTransform(new Vector2(1.0f, 0.2f), subList.Content.RectTransform) { MinSize = new Point(0, 20) },
style: "ListBoxElement")
{
ToolTip = sub.Description,
UserData = sub
};
int buttonSize = (int)(frame.Rect.Height * 0.8f);
GUITickBox tickBox = new GUITickBox(new RectTransform(new Vector2(0.8f, 1.0f), frame.RectTransform, Anchor.CenterLeft), ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Content.Rect.Width - 65))
{
UserData = sub,
OnSelected = (GUITickBox box) =>
{
GameMain.Client.RequestCampaignSub(box.UserData as SubmarineInfo, box.Selected);
return true;
}
};
subTickBoxes.Add(tickBox);
tickBox.Selected = GameMain.NetLobbyScreen.CampaignSubmarines.Contains(sub);
frame.RectTransform.MinSize = new Point(0, tickBox.RectTransform.MinSize.Y);
var subTextBlock = tickBox.TextBlock;
var matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name && s.MD5Hash?.Hash == sub.MD5Hash?.Hash);
if (matchingSub == null) matchingSub = SubmarineInfo.SavedSubmarines.FirstOrDefault(s => s.Name == sub.Name);
if (matchingSub == null)
{
subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f);
frame.ToolTip = TextManager.Get("SubNotFound");
}
else if (matchingSub?.MD5Hash == null || matchingSub.MD5Hash?.Hash != sub.MD5Hash?.Hash)
{
subTextBlock.TextColor = new Color(subTextBlock.TextColor, 0.5f);
frame.ToolTip = TextManager.Get("SubDoesntMatch");
}
if (!sub.RequiredContentPackagesInstalled)
{
subTextBlock.TextColor = Color.Lerp(subTextBlock.TextColor, Color.DarkRed, 0.5f);
frame.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + frame.RawToolTip;
}
var classText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), frame.RectTransform, Anchor.CenterRight),
TextManager.Get($"submarineclass.{sub.SubmarineClass}"), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{
TextColor = subTextBlock.TextColor * 0.8f,
ToolTip = subTextBlock.RawToolTip
};
}
}
public void RefreshMultiplayerCampaignSubUI(List<SubmarineInfo> campaignSubs)
{
for (int i = 0; i < subTickBoxes.Count; i++)
{
subTickBoxes[i].Selected = campaignSubs.Contains(subTickBoxes[i].UserData as SubmarineInfo);
}
}
private IEnumerable<object> WaitForCampaignSetup()
{ {
GUI.SetCursorWaiting(); GUI.SetCursorWaiting();
string headerText = TextManager.Get("CampaignStartingPleaseWait"); string headerText = TextManager.Get("CampaignStartingPleaseWait");
@@ -298,64 +221,6 @@ namespace Barotrauma
yield return CoroutineStatus.Success; yield return CoroutineStatus.Success;
} }
public void UpdateSubList(IEnumerable<SubmarineInfo> submarines)
{
List<SubmarineInfo> subsToShow;
string downloadFolder = Path.GetFullPath(SaveUtil.SubmarineDownloadFolder);
subsToShow = submarines.Where(s => s.IsCampaignCompatibleIgnoreClass && Path.GetDirectoryName(Path.GetFullPath(s.FilePath)) != downloadFolder).ToList();
subsToShow.Sort((s1, s2) =>
{
int p1 = s1.Price > CampaignMode.InitialMoney ? 10 : 0;
int p2 = s2.Price > CampaignMode.InitialMoney ? 10 : 0;
return p1.CompareTo(p2) * 100 + s1.Name.CompareTo(s2.Name);
});
subList.ClearChildren();
foreach (SubmarineInfo sub in subsToShow)
{
var textBlock = new GUITextBlock(
new RectTransform(new Vector2(1, 0.1f), subList.Content.RectTransform) { MinSize = new Point(0, 30) },
ToolBox.LimitString(sub.DisplayName, GUI.Font, subList.Rect.Width - 65), style: "ListBoxElement")
{
ToolTip = sub.Description,
UserData = sub
};
if (!sub.RequiredContentPackagesInstalled)
{
textBlock.TextColor = Color.Lerp(textBlock.TextColor, Color.DarkRed, .5f);
textBlock.ToolTip = TextManager.Get("ContentPackageMismatch") + "\n\n" + textBlock.RawToolTip;
}
var priceText = new GUITextBlock(new RectTransform(new Vector2(0.5f, 1.0f), textBlock.RectTransform, Anchor.CenterRight),
TextManager.GetWithVariable("currencyformat", "[credits]", string.Format(CultureInfo.InvariantCulture, "{0:N0}", sub.Price)), textAlignment: Alignment.CenterRight, font: GUI.SmallFont)
{
TextColor = sub.Price > CampaignMode.InitialMoney ? GUI.Style.Red : textBlock.TextColor * 0.8f,
ToolTip = textBlock.ToolTip
};
#if !DEBUG
if (!GameMain.DebugDraw)
{
if (sub.Price > CampaignMode.InitialMoney || !sub.IsCampaignCompatible)
{
textBlock.CanBeFocused = false;
textBlock.TextColor *= 0.5f;
}
}
#endif
}
if (SubmarineInfo.SavedSubmarines.Any())
{
var validSubs = subsToShow.Where(s => s.IsCampaignCompatible && s.Price <= CampaignMode.InitialMoney).ToList();
if (validSubs.Count > 0)
{
subList.Select(validSubs[Rand.Int(validSubs.Count)]);
}
}
}
private List<string> prevSaveFiles; private List<string> prevSaveFiles;
public void UpdateLoadMenu(IEnumerable<string> saveFiles = null) public void UpdateLoadMenu(IEnumerable<string> saveFiles = null)
{ {
@@ -12,6 +12,10 @@ namespace Barotrauma
{ {
class SinglePlayerCampaignSetupUI : CampaignSetupUI class SinglePlayerCampaignSetupUI : CampaignSetupUI
{ {
private GUIListBox subList;
protected GUILayoutGroup subPreviewContainer;
public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; } public CharacterInfo.AppearanceCustomizationMenu[] CharacterMenus { get; private set; }
private GUIButton nextButton; private GUIButton nextButton;
@@ -38,7 +42,7 @@ namespace Barotrauma
pageContainer.BarScroll = targetScroll; pageContainer.BarScroll = targetScroll;
} }
for (int i=0; i<CharacterMenus.Length; i++) for (int i = 0; i < CharacterMenus.Length; i++)
{ {
CharacterMenus[i]?.Update(); CharacterMenus[i]?.Update();
} }
@@ -77,6 +81,7 @@ namespace Barotrauma
{ {
ScrollBarEnabled = false, ScrollBarEnabled = false,
ScrollBarVisible = false, ScrollBarVisible = false,
AllowArrowKeyScroll = false,
HoverCursor = CursorState.Default HoverCursor = CursorState.Default
}; };
@@ -705,7 +705,7 @@ namespace Barotrauma.CharacterEditor
{ {
string errorMsg = "Attempted to modify the state of the physics simulation while a time step was running."; string errorMsg = "Attempted to modify the state of the physics simulation while a time step was running.";
DebugConsole.ThrowError(errorMsg, e); DebugConsole.ThrowError(errorMsg, e);
GameAnalyticsManager.AddErrorEventOnce("CharacterEditorScreen.Update:WorldLockedException" + e.Message, GameAnalyticsSDK.Net.EGAErrorSeverity.Critical, errorMsg); GameAnalyticsManager.AddErrorEventOnce("CharacterEditorScreen.Update:WorldLockedException" + e.Message, GameAnalyticsManager.ErrorSeverity.Critical, errorMsg);
} }
} }
// Camera // Camera
@@ -1802,9 +1802,11 @@ namespace Barotrauma.CharacterEditor
{ {
case AnimationType.Walk: case AnimationType.Walk:
case AnimationType.Run: case AnimationType.Run:
case AnimationType.Crouch:
if (!ragdollParams.CanWalk) { continue; } if (!ragdollParams.CanWalk) { continue; }
break; break;
case AnimationType.Crouch:
if (!ragdollParams.CanWalk || !isHumanoid) { continue; }
break;
case AnimationType.SwimSlow: case AnimationType.SwimSlow:
case AnimationType.SwimFast: case AnimationType.SwimFast:
break; break;
@@ -2690,7 +2692,15 @@ namespace Barotrauma.CharacterEditor
characterDropDown.SelectItem(currentCharacterConfig); characterDropDown.SelectItem(currentCharacterConfig);
characterDropDown.OnSelected = (component, data) => characterDropDown.OnSelected = (component, data) =>
{ {
SpawnCharacter((string)data); string configFile = (string)data;
try
{
SpawnCharacter(configFile);
}
catch (Exception e)
{
HandleSpawnException(configFile, e);
}
return true; return true;
}; };
if (currentCharacterConfig == CharacterPrefab.HumanConfigFile) if (currentCharacterConfig == CharacterPrefab.HumanConfigFile)
@@ -2719,19 +2729,48 @@ namespace Barotrauma.CharacterEditor
prevCharacterButton.TextBlock.AutoScaleHorizontal = true; prevCharacterButton.TextBlock.AutoScaleHorizontal = true;
prevCharacterButton.OnClicked += (b, obj) => prevCharacterButton.OnClicked += (b, obj) =>
{ {
SpawnCharacter(GetPreviousConfigFile()); string configFile = GetPreviousConfigFile();
try
{
SpawnCharacter(configFile);
}
catch (Exception e)
{
HandleSpawnException(configFile, e);
}
return true; return true;
}; };
var nextCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), charButtons.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("NextCharacter")); var nextCharacterButton = new GUIButton(new RectTransform(new Vector2(0.5f, 1.0f), charButtons.RectTransform, Anchor.TopRight), GetCharacterEditorTranslation("NextCharacter"));
prevCharacterButton.TextBlock.AutoScaleHorizontal = true; prevCharacterButton.TextBlock.AutoScaleHorizontal = true;
nextCharacterButton.OnClicked += (b, obj) => nextCharacterButton.OnClicked += (b, obj) =>
{ {
SpawnCharacter(GetNextConfigFile()); string configFile = GetNextConfigFile();
try
{
SpawnCharacter(configFile);
}
catch (Exception e)
{
HandleSpawnException(configFile, e);
}
return true; return true;
}; };
charButtons.RectTransform.MinSize = new Point(0, prevCharacterButton.RectTransform.MinSize.Y); charButtons.RectTransform.MinSize = new Point(0, prevCharacterButton.RectTransform.MinSize.Y);
characterPanelToggle = new ToggleButton(new RectTransform(new Vector2(0.08f, 1), characterSelectionPanel.RectTransform, Anchor.CenterLeft, Pivot.CenterRight), Direction.Right); characterPanelToggle = new ToggleButton(new RectTransform(new Vector2(0.08f, 1), characterSelectionPanel.RectTransform, Anchor.CenterLeft, Pivot.CenterRight), Direction.Right);
characterSelectionPanel.RectTransform.MinSize = new Point(0, (int)(content.RectTransform.Children.Sum(c => c.MinSize.Y) * 1.2f)); characterSelectionPanel.RectTransform.MinSize = new Point(0, (int)(content.RectTransform.Children.Sum(c => c.MinSize.Y) * 1.2f));
void HandleSpawnException(string configFile, Exception e)
{
if (configFile != CharacterPrefab.HumanConfigFile)
{
DebugConsole.ThrowError($"Failed to spawn the character \"{configFile}\".", e);
SpawnCharacter(CharacterPrefab.HumanConfigFile);
}
else
{
throw new Exception($"Failed to spawn the character \"{configFile}\".", innerException: e);
}
}
} }
private void CreateFileEditPanel() private void CreateFileEditPanel()

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