From edaf4b09fe882b06abfe0f784134cafa46862114 Mon Sep 17 00:00:00 2001
From: Markus Isberg <3e849f2e5c@pm.me>
Date: Thu, 27 Oct 2022 17:54:57 +0300
Subject: [PATCH] Build 0.20.0.0
---
.../Characters/Animation/Ragdoll.cs | 8 +-
.../ClientSource/Characters/Character.cs | 16 +
.../ClientSource/Characters/CharacterHUD.cs | 2 +-
.../Characters/CharacterNetworking.cs | 2 +-
.../ClientSource/DebugConsole.cs | 230 +++---
.../ClientSource/GUI/ChatBox.cs | 2 +-
.../ClientSource/GUI/CrewManagement.cs | 2 +-
.../ClientSource/GUI/FileSelection.cs | 11 +
.../BarotraumaClient/ClientSource/GUI/GUI.cs | 57 +-
.../ClientSource/GUI/GUITextBlock.cs | 7 +
.../ClientSource/GUI/Store.cs | 22 +-
.../ClientSource/GUI/SubmarineSelection.cs | 2 +-
.../ClientSource/GUI/TabMenu.cs | 493 +------------
.../ClientSource/GUI/TalentMenu.cs | 680 ++++++++++++++++++
.../ClientSource/GUI/UpgradeStore.cs | 34 +-
.../GameAnalytics/GameAnalyticsManager.cs | 2 +-
.../BarotraumaClient/ClientSource/GameMain.cs | 15 +-
.../GameSession/GameModes/CampaignMode.cs | 7 +-
.../GameModes/SinglePlayerCampaign.cs | 6 +-
.../ClientSource/GameSession/RoundSummary.cs | 2 +-
.../ClientSource/Items/CharacterInventory.cs | 4 +-
.../Items/Components/ElectricalDischarger.cs | 31 +-
.../Components/Machines/Deconstructor.cs | 2 +
.../Items/Components/Machines/MiniMap.cs | 2 +-
.../Items/Components/Machines/Sonar.cs | 2 +-
.../Items/Components/Machines/Steering.cs | 2 +-
.../Items/Components/Power/PowerContainer.cs | 40 +-
.../ClientSource/Items/Components/Turret.cs | 2 +-
.../ClientSource/Items/Item.cs | 9 +
.../ClientSource/Items/ItemPrefab.cs | 7 +
.../ClientSource/Map/Map/Map.cs | 26 +-
.../ClientEntityEventManager.cs | 12 +-
.../Networking/ServerList/PingUtils.cs | 32 +-
.../ClientSource/Networking/ServerSettings.cs | 30 +-
.../ClientSource/Particles/ParticleEmitter.cs | 3 +
.../ClientSource/Screens/CampaignUI.cs | 19 +-
.../Screens/CharacterEditor/Wizard.cs | 13 +-
.../ClientSource/Screens/EditorScreen.cs | 2 +-
.../ClientSource/Screens/MainMenuScreen.cs | 4 +-
.../ClientSource/Screens/NetLobbyScreen.cs | 6 +-
.../ServerListScreen/ServerListScreen.cs | 17 +-
.../Screens/SpriteEditorScreen.cs | 3 +
.../ClientSource/Screens/SubEditorScreen.cs | 15 +-
.../ClientSource/Screens/TestScreen.cs | 53 +-
.../Serialization/SerializableEntityEditor.cs | 18 +-
.../ClientSource/Settings/SettingsMenu.cs | 19 +-
.../StatusEffects/StatusEffect.cs | 22 +-
.../WorkshopMenu/Mutable/InstalledTab.cs | 23 +-
.../Steam/WorkshopMenu/Mutable/ItemList.cs | 3 +-
.../Mutable/MutableWorkshopMenu.cs | 27 +-
.../Steam/WorkshopMenu/Mutable/PublishTab.cs | 3 +-
.../BarotraumaClient/LinuxClient.csproj | 2 +-
Barotrauma/BarotraumaClient/MacClient.csproj | 2 +-
.../BarotraumaClient/WindowsClient.csproj | 2 +-
.../BarotraumaServer/LinuxServer.csproj | 2 +-
Barotrauma/BarotraumaServer/MacServer.csproj | 2 +-
.../ServerSource/Characters/CharacterInfo.cs | 2 +-
.../Characters/CharacterNetworking.cs | 6 +-
.../ServerSource/DebugConsole.cs | 15 +
.../BarotraumaServer/ServerSource/GameMain.cs | 2 +-
.../GameSession/GameModes/CampaignMode.cs | 5 +-
.../GameModes/MultiPlayerCampaign.cs | 5 +-
.../Items/Components/DockingPort.cs | 2 +-
.../ServerSource/Items/Item.cs | 8 +
.../ServerSource/Networking/BanList.cs | 4 +-
.../ServerSource/Networking/Client.cs | 5 +
.../ServerSource/Networking/GameServer.cs | 86 ++-
.../ServerEntityEventManager.cs | 3 +-
.../Primitives/Peers/Server/ServerPeer.cs | 4 +-
.../BarotraumaServer/WindowsServer.csproj | 2 +-
.../Characters/AI/EnemyAIController.cs | 237 +++---
.../AI/Objectives/AIObjectiveCombat.cs | 5 +-
.../AI/Objectives/AIObjectiveRepairItem.cs | 4 +-
.../SharedSource/Characters/AI/PetBehavior.cs | 52 +-
.../AI/ShipCommand/ShipIssueWorker.cs | 4 +-
.../Characters/AI/ShipCommandManager.cs | 2 +-
.../Characters/Animation/AnimController.cs | 2 +-
.../Animation/HumanoidAnimController.cs | 25 +-
.../Characters/Animation/Ragdoll.cs | 10 +-
.../SharedSource/Characters/Attack.cs | 21 +
.../SharedSource/Characters/Character.cs | 148 +++-
.../SharedSource/Characters/CharacterInfo.cs | 96 ++-
.../Health/Afflictions/AfflictionPrefab.cs | 4 +
.../Characters/Health/CharacterHealth.cs | 21 +-
.../SharedSource/Characters/Limb.cs | 27 +-
.../Characters/Params/CharacterParams.cs | 2 +-
.../AbilityConditionals/AbilityCondition.cs | 12 +-
.../AbilityConditionCharacter.cs | 14 +-
.../AbilityConditionCharacterNotLooted.cs | 19 +
.../AbilityConditionCharacterUnconcious.cs | 16 +
.../AbilityConditionItem.cs | 6 +-
.../AbilityConditionLocation.cs | 8 +
.../AbilityConditionMission.cs | 48 +-
.../AbilityConditionReduceAffliction.cs | 1 -
.../AbilityConditionAllyNearby.cs | 47 ++
.../AbilityConditionCrewMemberUnconscious.cs | 22 +
.../AbilityConditionHasAffliction.cs | 2 +-
.../AbilityConditionHasItem.cs | 2 +-
.../AbilityConditionHasLevel.cs | 43 ++
.../AbilityConditionHasPermanentStat.cs | 11 +-
.../AbilityConditionHasTalent.cs | 19 +
.../AbilityConditionHoldingItem.cs | 34 +
.../AbilityConditionLowestLevel.cs | 23 +
.../AbilityConditionNearbyCharacterCount.cs | 39 +
.../Talents/Abilities/AbilityObjects.cs | 1 -
.../Talents/Abilities/CharacterAbility.cs | 2 +-
...cterAbilityApplyStatusEffectToNonHumans.cs | 35 +
.../CharacterAbilityApplyStatusEffects.cs | 71 +-
...racterAbilityApplyStatusEffectsToAllies.cs | 28 +-
...ilityApplyStatusEffectsToApprenticeship.cs | 63 ++
.../CharacterAbilityGainSimultaneousSkill.cs | 20 +-
.../CharacterAbilityGiveExperience.cs | 35 +
.../Abilities/CharacterAbilityGiveItemStat.cs | 31 +
.../CharacterAbilityGiveItemStatToTags.cs | 39 +
.../CharacterAbilityGivePermanentStat.cs | 39 +-
.../CharacterAbilityGiveReputation.cs | 31 +
.../Abilities/CharacterAbilityMarkAsLooted.cs | 18 +
.../CharacterAbilityModifyAffliction.cs | 28 +-
.../CharacterAbilityReduceAffliction.cs | 27 +
.../CharacterAbilityRemoveRandomIngredient.cs | 19 +
.../CharacterAbilityResetPermanentStat.cs | 7 +-
.../CharacterAbilitySetMetadataInt.cs | 29 +
.../Abilities/CharacterAbilityUnlockTree.cs | 29 -
.../CharacterAbilityTandemFire.cs | 10 +-
...erAbilityUnlockApprenticeshipTalentTree.cs | 48 ++
.../AbilityGroups/CharacterAbilityGroup.cs | 46 +-
.../CharacterAbilityGroupEffect.cs | 30 +-
.../CharacterAbilityGroupInterval.cs | 80 ++-
.../Characters/Talents/CharacterTalent.cs | 13 +-
.../Characters/Talents/TalentPrefab.cs | 28 +-
.../Characters/Talents/TalentTree.cs | 138 +++-
.../ContentManagement/ContentPath.cs | 2 +-
.../ContentManagement/ContentXElement.cs | 4 +
.../ContentManagement/Identifier.cs | 4 +
.../SharedSource/DebugConsole.cs | 14 +-
.../BarotraumaShared/SharedSource/Enums.cs | 47 +-
.../EventActions/NPCChangeTeamAction.cs | 10 +-
.../Events/EventActions/SpawnAction.cs | 44 +-
.../SharedSource/Events/EventSet.cs | 4 +-
.../GameSession/AutoItemPlacer.cs | 1 +
.../SharedSource/GameSession/CargoManager.cs | 10 +
.../SharedSource/GameSession/Data/Factions.cs | 8 +
.../GameSession/Data/Reputation.cs | 9 +
.../GameSession/GameModes/CampaignMode.cs | 21 +-
.../GameSession/GameModes/CampaignSettings.cs | 8 +-
.../SharedSource/GameSession/GameSession.cs | 6 +-
.../GameSession/UpgradeManager.cs | 26 +-
.../Items/Components/DockingPort.cs | 7 +-
.../Items/Components/ElectricalDischarger.cs | 102 ++-
.../Items/Components/Holdable/Holdable.cs | 55 +-
.../Items/Components/Holdable/MeleeWeapon.cs | 4 +-
.../Items/Components/Holdable/RepairTool.cs | 15 +-
.../Items/Components/ItemComponent.cs | 26 +-
.../Components/Machines/Deconstructor.cs | 11 +-
.../Items/Components/Machines/Engine.cs | 15 +-
.../Items/Components/Machines/Fabricator.cs | 71 +-
.../Items/Components/Machines/Pump.cs | 22 +-
.../Items/Components/Machines/Reactor.cs | 62 +-
.../Items/Components/Machines/Sonar.cs | 28 +-
.../Items/Components/Power/PowerContainer.cs | 19 +-
.../Items/Components/Projectile.cs | 3 +-
.../Items/Components/Repairable.cs | 24 +-
.../SharedSource/Items/Components/Wearable.cs | 3 +
.../SharedSource/Items/Inventory.cs | 8 +
.../SharedSource/Items/Item.cs | 146 +++-
.../SharedSource/Items/ItemEventData.cs | 20 +-
.../SharedSource/Items/ItemPrefab.cs | 38 +-
.../SharedSource/Items/ItemStatManager.cs | 64 ++
.../SharedSource/Items/RelatedItem.cs | 45 +-
.../Map/Creatures/BallastFloraBehavior.cs | 11 +-
.../SharedSource/Map/Explosion.cs | 25 +-
.../SharedSource/Map/Levels/Level.cs | 7 +-
.../Levels/LevelObjects/LevelObjectManager.cs | 53 +-
.../SharedSource/Map/Map/Location.cs | 62 +-
.../SharedSource/Map/Map/LocationType.cs | 4 +-
.../SharedSource/Map/Map/Map.cs | 50 +-
.../SharedSource/Map/PriceInfo.cs | 3 +
.../SharedSource/NetStructBitField.cs | 31 +-
.../Networking/ChildServerRelay.cs | 5 +
.../Networking/INetSerializableStruct.cs | 102 +--
.../Primitives/Address/LidgrenAddress.cs | 19 +
.../Primitives/Endpoint/LidgrenEndpoint.cs | 8 +-
.../Networking/Primitives/Message/Message.cs | 153 ++--
.../Primitives/NetworkPeerStructs.cs | 9 +-
.../SharedSource/Networking/RespawnManager.cs | 2 +-
.../SharedSource/Physics/PhysicsBody.cs | 31 +-
.../SharedSource/Prefabs/PrefabCollection.cs | 3 +-
.../SharedSource/Screens/NetLobbyScreen.cs | 12 +-
.../Serialization/XMLExtensions.cs | 7 +
.../StatusEffects/StatusEffect.cs | 92 ++-
.../Text/LocalizedString/LocalizedString.cs | 6 +-
.../Text/LocalizedString/TagLString.cs | 4 +-
.../SharedSource/Text/TextManager.cs | 2 +
.../SharedSource/Upgrades/UpgradePrefab.cs | 50 +-
Barotrauma/BarotraumaShared/changelog.txt | 108 +++
.../BarotraumaShared/serversettings.xml | 57 --
.../INetSerializableStructTests.cs | 14 +-
197 files changed, 4344 insertions(+), 1773 deletions(-)
create mode 100644 Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacterNotLooted.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionData/AbilityConditionCharacterUnconcious.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionAllyNearby.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionCrewMemberUnconscious.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasLevel.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHasTalent.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionHoldingItem.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionLowestLevel.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/AbilityConditionals/AbilityConditionDataless/AbilityConditionNearbyCharacterCount.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectToNonHumans.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityApplyStatusEffectsToApprenticeship.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveExperience.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveItemStat.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveItemStatToTags.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityGiveReputation.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityMarkAsLooted.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityReduceAffliction.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityRemoveRandomIngredient.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilitySetMetadataInt.cs
delete mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CharacterAbilityUnlockTree.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Characters/Talents/Abilities/CustomAbilities/CharacterAbilityUnlockApprenticeshipTalentTree.cs
create mode 100644 Barotrauma/BarotraumaShared/SharedSource/Items/ItemStatManager.cs
delete mode 100644 Barotrauma/BarotraumaShared/serversettings.xml
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
index dc50608c8..007c6d8a3 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Animation/Ragdoll.cs
@@ -442,8 +442,7 @@ namespace Barotrauma
{
foreach (Limb limb in Limbs)
{
- if (limb == null || limb.IsSevered || limb.ActiveSprite == null) { continue; }
-
+ if (limb == null || limb.IsSevered || limb.ActiveSprite == null || !limb.DoesFlip) { continue; }
Vector2 spriteOrigin = limb.ActiveSprite.Origin;
spriteOrigin.X = limb.ActiveSprite.SourceRect.Width - spriteOrigin.X;
limb.ActiveSprite.Origin = spriteOrigin;
@@ -468,7 +467,10 @@ namespace Barotrauma
{
var damageSound = character.GetSound(s => s.Type == CharacterSound.SoundType.Damage);
float range = damageSound != null ? damageSound.Range * 2 : ConvertUnits.ToDisplayUnits(character.AnimController.Collider.GetSize().Length() * 10);
- SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range);
+ if (!limbJoint.Params.BreakSound.IsNullOrEmpty() && !limbJoint.Params.BreakSound.Equals("none", StringComparison.OrdinalIgnoreCase))
+ {
+ SoundPlayer.PlayDamageSound(limbJoint.Params.BreakSound, 1.0f, limbJoint.LimbA.body.DrawPosition, range: range);
+ }
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
index 99557594c..99cd9bd2d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/Character.cs
@@ -109,6 +109,22 @@ namespace Barotrauma
set => grainStrength = Math.Max(0, value);
}
+ ///
+ /// Can be used to set camera shake from status effects
+ ///
+ public float CameraShake
+ {
+ get { return Screen.Selected?.Cam?.Shake ?? 0.0f; }
+ set
+ {
+ if (!MathUtils.IsValid(value)) { return; }
+ if (Screen.Selected?.Cam != null)
+ {
+ Screen.Selected.Cam.Shake = value;
+ }
+ }
+ }
+
private readonly List bloodEmitters = new List();
public IEnumerable BloodEmitters
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
index f4742ac64..df6381f18 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterHUD.cs
@@ -307,7 +307,7 @@ namespace Barotrauma
{
if (!brokenItem.IsInteractable(character)) { continue; }
float alpha = GetDistanceBasedIconAlpha(brokenItem);
- if (alpha <= 0.0f) continue;
+ if (alpha <= 0.0f) { continue; }
GUI.DrawIndicator(spriteBatch, brokenItem.DrawPosition, cam, 100.0f, GUIStyle.BrokenIcon.Value.Sprite,
Color.Lerp(GUIStyle.Red, GUIStyle.Orange * 0.5f, brokenItem.Condition / brokenItem.MaxCondition) * alpha);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs
index 4a9a99547..8d5e63f26 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Characters/CharacterNetworking.cs
@@ -501,7 +501,7 @@ namespace Barotrauma
info?.ClearSavedStatValues(statType);
for (int i = 0; i < savedStatValueCount; i++)
{
- string statIdentifier = msg.ReadString();
+ Identifier statIdentifier = msg.ReadIdentifier();
float statValue = msg.ReadSingle();
bool removeOnDeath = msg.ReadBoolean();
info?.ChangeSavedStatValue(statType, statValue, statIdentifier, removeOnDeath, setValue: true);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
index bfa8a5194..9d6be3cbd 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/DebugConsole.cs
@@ -1136,6 +1136,17 @@ namespace Barotrauma
});
AssignRelayToServer("debugdraw", false);
+ AssignOnExecute("debugdrawlocalization", (string[] args) =>
+ {
+ if (args.None() || !bool.TryParse(args[0], out bool state))
+ {
+ state = !TextManager.DebugDraw;
+ }
+ TextManager.DebugDraw = state;
+ NewMessage("Localization debug draw mode " + (TextManager.DebugDraw ? "enabled" : "disabled"), Color.White);
+ });
+ AssignRelayToServer("debugdraw", false);
+
AssignOnExecute("togglevoicechatfilters", (string[] args) =>
{
if (args.None() || !bool.TryParse(args[0], out bool state))
@@ -1695,6 +1706,8 @@ namespace Barotrauma
config.Language = language;
GameSettings.SetCurrentConfig(config);
}
+
+ HashSet missingTexts = new HashSet();
//key = text tag, value = list of languages the tag is missing from
Dictionary> missingTags = new Dictionary>();
@@ -1755,20 +1768,38 @@ namespace Barotrauma
foreach (Type itemComponentType in typeof(ItemComponent).Assembly.GetTypes().Where(type => type.IsSubclassOf(typeof(ItemComponent))))
{
- foreach (var property in itemComponentType.GetProperties())
+ checkSerializableEntityType(itemComponentType);
+ }
+ checkSerializableEntityType(typeof(Item));
+ checkSerializableEntityType(typeof(Hull));
+ checkSerializableEntityType(typeof(Structure));
+
+ void checkSerializableEntityType(Type t)
+ {
+ foreach (var property in t.GetProperties())
{
- if (!property.IsDefined(typeof(InGameEditable), false)) { continue; }
+ if (!property.IsDefined(typeof(Editable), false)) { continue; }
string propertyTag = $"{property.DeclaringType.Name}.{property.Name}";
- addIfMissingAll(language,
+ if (addIfMissingAll(language,
propertyTag.ToIdentifier(),
property.Name.ToIdentifier(),
- $"sp.{propertyTag}.name".ToIdentifier());
+ $"sp.{property.Name}.name".ToIdentifier(),
+ $"sp.{propertyTag}.name".ToIdentifier()) && language == "English".ToLanguageIdentifier())
+ {
+ missingTexts.Add($"{property.Name.FormatCamelCaseWithSpaces()}");
+ }
- addIfMissingAll(language,
+ var description = (property.GetCustomAttributes(true).First(a => a is Serialize) as Serialize).Description;
+
+ if (addIfMissingAll(language,
$"sp.{propertyTag}.description".ToIdentifier(),
- $"{property.Name.ToIdentifier()}.description".ToIdentifier());
+ $"sp.{property.Name}.description".ToIdentifier(),
+ $"{property.Name.ToIdentifier()}.description".ToIdentifier()) && language == "English".ToLanguageIdentifier())
+ {
+ missingTexts.Add($"{description}");
+ }
}
}
@@ -1889,6 +1920,23 @@ namespace Barotrauma
ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
SwapLanguage(TextManager.DefaultLanguage);
+ if (missingTexts.Any())
+ {
+ ShowQuestionPrompt("Dump the property names and descriptions missing from English to a new xml file? Y/N",
+ (option) =>
+ {
+ if (option.ToLowerInvariant() == "y")
+ {
+ string path = "newtexts.txt";
+ Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
+ File.WriteAllLines(path, missingTexts);
+ Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
+ ToolBox.OpenFileWithShell(Path.GetFullPath(path));
+ SwapLanguage(TextManager.DefaultLanguage);
+ }
+ });
+ }
+
void addIfMissing(Identifier tag, LanguageIdentifier language)
{
if (!tags[language].Contains(tag))
@@ -1897,15 +1945,90 @@ namespace Barotrauma
missingTags[tag].Add(language);
}
}
- void addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
+ bool addIfMissingAll(LanguageIdentifier language, params Identifier[] potentialTags)
{
if (!potentialTags.Any(t => tags[language].Contains(t)))
{
var tag = potentialTags.First();
if (!missingTags.ContainsKey(tag)) { missingTags[tag] = new HashSet(); }
missingTags[tag].Add(language);
+ return true;
+ }
+ return false;
+ }
+ }));
+
+
+ commands.Add(new Command("checkduplicateloca", "", (string[] args) =>
+ {
+ if (args.Length < 1)
+ {
+ ThrowError("Please specify a file path.");
+ return;
+ }
+ XDocument doc1 = XMLExtensions.TryLoadXml(args[0]);
+ if (doc1?.Root == null)
+ {
+ ThrowError($"Could not load the file \"{args[0]}\"");
+ return;
+ }
+ List<(string tag, string text)> texts = new List<(string tag, string text)>();
+
+ bool duplicatesFound = false;
+ foreach (XElement element in doc1.Root.Elements())
+ {
+ string tag = element.Name.ToString();
+ string text = element.ElementInnerText();
+ if (texts.Any(t => t.tag == tag))
+ {
+ ThrowError($"Duplicate tag \"{tag}\".");
+ duplicatesFound = true;
}
}
+ if (duplicatesFound)
+ {
+ ThrowError($"Aborting, please fix duplicate tags in the file and try again.");
+ return;
+ }
+
+ foreach (XElement element in doc1.Root.Elements())
+ {
+ string tag = element.Name.ToString();
+ string text = element.ElementInnerText();
+ if (texts.Any(t => t.text == text))
+ {
+ if (tag.StartsWith("sp."))
+ {
+ string[] split = tag.Split('.');
+ if (split.Length > 3)
+ {
+ texts.RemoveAll(t => t.text == text);
+ string newTag = $"sp.{split[2]}.{split[3]}";
+ texts.Add((newTag, text));
+ NewMessage($"Duplicate text \"{tag}\", merging to \"{newTag}\".");
+ }
+ else
+ {
+ NewMessage($"Duplicate text \"{tag}\", using existing one \"{texts.Find(t => t.text == text).tag}\".");
+ }
+ }
+ else
+ {
+ texts.Add((tag, text));
+ ThrowError($"Duplicate text \"{tag}\". Could not determine if the text can be merged with an existing one, please check it manually.");
+ }
+ }
+ else
+ {
+ texts.Add((tag, text));
+ }
+ }
+
+ string filePath = "uniquetexts.xml";
+ Barotrauma.IO.Validation.SkipValidationInDebugBuilds = true;
+ File.WriteAllLines(filePath, texts.Select(t => $"<{t.tag}>{t.text}{t.tag}>"));
+ Barotrauma.IO.Validation.SkipValidationInDebugBuilds = false;
+ ToolBox.OpenFileWithShell(Path.GetFullPath(filePath));
}));
commands.Add(new Command("comparelocafiles", "comparelocafiles [file1] [file2]", (string[] args) =>
@@ -2585,99 +2708,6 @@ namespace Barotrauma
}));
#endif
- commands.Add(new Command("cleanbuild", "", (string[] args) =>
- {
- /*GameSettings.CurrentConfig.MusicVolume = 0.5f;
- GameSettings.CurrentConfig.SoundVolume = 0.5f;
- GameSettings.CurrentConfig.DynamicRangeCompressionEnabled = true;
- GameSettings.CurrentConfig.VoipAttenuationEnabled = true;
- NewMessage("Music and sound volume set to 0.5", Color.Green);
-
- GameSettings.CurrentConfig.GraphicsWidth = 0;
- GameSettings.CurrentConfig.GraphicsHeight = 0;
- GameSettings.CurrentConfig.WindowMode = WindowMode.BorderlessWindowed;
- NewMessage("Resolution set to 0 x 0 (screen resolution will be used)", Color.Green);
- NewMessage("Fullscreen enabled", Color.Green);
-
- GameSettings.CurrentConfig.VerboseLogging = false;
-
- if (GameSettings.CurrentConfig.MasterServerUrl != "http://www.undertowgames.com/baromaster")
- {
- ThrowError("MasterServerUrl \"" + GameSettings.CurrentConfig.MasterServerUrl + "\"!");
- }
-
- GameSettings.SaveCurrentConfig();*/
- throw new NotImplementedException();
- #warning TODO: reimplement
-
- var saveFiles = Barotrauma.IO.Directory.GetFiles(SaveUtil.SaveFolder);
-
- foreach (string saveFile in saveFiles)
- {
- Barotrauma.IO.File.Delete(saveFile);
- NewMessage("Deleted " + saveFile, Color.Green);
- }
-
- if (Barotrauma.IO.Directory.Exists(Barotrauma.IO.Path.Combine(SaveUtil.SaveFolder, "temp")))
- {
- Barotrauma.IO.Directory.Delete(Barotrauma.IO.Path.Combine(SaveUtil.SaveFolder, "temp"), true);
- NewMessage("Deleted temp save folder", Color.Green);
- }
-
- if (Barotrauma.IO.Directory.Exists(ServerLog.SavePath))
- {
- var logFiles = Barotrauma.IO.Directory.GetFiles(ServerLog.SavePath);
-
- foreach (string logFile in logFiles)
- {
- Barotrauma.IO.File.Delete(logFile);
- NewMessage("Deleted " + logFile, Color.Green);
- }
- }
-
- if (Barotrauma.IO.File.Exists("filelist.xml"))
- {
- Barotrauma.IO.File.Delete("filelist.xml");
- NewMessage("Deleted filelist", Color.Green);
- }
-
- if (Barotrauma.IO.File.Exists("Data/bannedplayers.txt"))
- {
- Barotrauma.IO.File.Delete("Data/bannedplayers.txt");
- NewMessage("Deleted bannedplayers.txt", Color.Green);
- }
-
- if (Barotrauma.IO.File.Exists("Submarines/TutorialSub.sub"))
- {
- Barotrauma.IO.File.Delete("Submarines/TutorialSub.sub");
-
- NewMessage("Deleted TutorialSub from the submarine folder", Color.Green);
- }
-
- /*if (Barotrauma.IO.File.Exists(GameServer.SettingsFile))
- {
- Barotrauma.IO.File.Delete(GameServer.SettingsFile);
- NewMessage("Deleted server settings", Color.Green);
- }
-
- if (Barotrauma.IO.File.Exists(GameServer.ClientPermissionsFile))
- {
- Barotrauma.IO.File.Delete(GameServer.ClientPermissionsFile);
- NewMessage("Deleted client permission file", Color.Green);
- }*/
-
- if (Barotrauma.IO.File.Exists("crashreport.log"))
- {
- Barotrauma.IO.File.Delete("crashreport.log");
- NewMessage("Deleted crashreport.log", Color.Green);
- }
-
- if (!Barotrauma.IO.File.Exists("Content/Map/TutorialSub.sub"))
- {
- ThrowError("TutorialSub.sub not found!");
- }
- }));
-
commands.Add(new Command("reloadcorepackage", "", (string[] args) =>
{
if (args.Length < 1)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
index 094a4b81c..2456e2ba8 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/ChatBox.cs
@@ -769,7 +769,7 @@ namespace Barotrauma
if (Character.Controlled != null && ChatMessage.CanUseRadio(Character.Controlled, out WifiComponent radio))
{
radio.Channel = channel;
- GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()]));
+ GameMain.Client?.CreateEntityEvent(radio.Item, new Item.ChangePropertyEventData(radio.SerializableProperties["channel".ToIdentifier()], radio));
if (setText)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs
index b25b07ca4..85afac3c9 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/CrewManagement.cs
@@ -25,7 +25,7 @@ namespace Barotrauma
private PlayerBalanceElement? playerBalanceElement;
private List PendingHires => campaign.Map?.CurrentLocation?.HireManager?.PendingHires;
- private bool HasPermission => campaignUI.Campaign.AllowedToManageCampaign(ClientPermissions.ManageHires);
+ private bool HasPermission => CampaignMode.AllowedToManageCampaign(ClientPermissions.ManageHires);
private Point resolutionWhenCreated;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs
index 4fcd29809..373fecbbf 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/FileSelection.cs
@@ -357,6 +357,17 @@ namespace Barotrauma
string txt = directory;
if (txt.StartsWith(currentDirectory)) { txt = txt.Substring(currentDirectory.Length); }
if (!txt.EndsWith("/")) { txt += "/"; }
+ //get directory info
+ DirectoryInfo dirInfo = new DirectoryInfo(directory);
+ try
+ {
+ //this will throw an exception if the directory can't be opened
+ Directory.GetDirectories(directory);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ continue;
+ }
var itemFrame = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.05f), fileList.Content.RectTransform), txt)
{
UserData = ItemIsDirectory.Yes
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
index c158fc21d..fb4fd861b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUI.cs
@@ -3,7 +3,6 @@ using System.Collections.Generic;
using System.Diagnostics;
using Barotrauma.IO;
using System.Linq;
-using System.Xml.Linq;
using Barotrauma.CharacterEditor;
using Barotrauma.Extensions;
using Barotrauma.Items.Components;
@@ -50,6 +49,14 @@ namespace Barotrauma
static class GUI
{
+ // Controls where a line is drawn for given coords.
+ public enum OutlinePosition
+ {
+ Default = 0, // Thickness is inside of top left and outside of bottom right coord
+ Inside = 1, // Thickness is subtracted from the inside
+ Centered = 2, // Thickness is centered on given coords
+ Outside = 3, // Tickness is added to the outside
+ }
public static GUICanvas Canvas => GUICanvas.Instance;
public static CursorState MouseCursor = CursorState.Default;
@@ -1605,6 +1612,54 @@ namespace Barotrauma
}
}
+ public static void DrawRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 origin, float rotation, Color clr, float depth = 0.0f, float thickness = 1, OutlinePosition outlinePos = OutlinePosition.Centered)
+ {
+ Vector2 topLeft = new Vector2(-origin.X, -origin.Y);
+ Vector2 topRight = new Vector2(-origin.X + size.X, -origin.Y);
+ Vector2 bottomLeft = new Vector2(-origin.X, -origin.Y + size.Y);
+ Vector2 actualSize = size;
+
+ switch(outlinePos)
+ {
+ case OutlinePosition.Default:
+ actualSize += new Vector2(thickness);
+ break;
+ case OutlinePosition.Centered:
+ topLeft -= new Vector2(thickness * 0.5f);
+ topRight -= new Vector2(thickness * 0.5f);
+ bottomLeft -= new Vector2(thickness * 0.5f);
+ actualSize += new Vector2(thickness);
+ break;
+ case OutlinePosition.Inside:
+ topRight -= new Vector2(thickness, 0.0f);
+ bottomLeft -= new Vector2(0.0f, thickness);
+ break;
+ case OutlinePosition.Outside:
+ topLeft -= new Vector2(thickness);
+ topRight -= new Vector2(0.0f, thickness);
+ bottomLeft -= new Vector2(thickness, 0.0f);
+ actualSize += new Vector2(thickness * 2.0f);
+ break;
+ }
+
+ Matrix rotate = Matrix.CreateRotationZ(rotation);
+ topLeft = Vector2.Transform(topLeft, rotate) + position;
+ topRight = Vector2.Transform(topRight, rotate) + position;
+ bottomLeft = Vector2.Transform(bottomLeft, rotate) + position;
+
+ Rectangle srcRect = new Rectangle(0, 0, 1, 1);
+ sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
+ sb.Draw(solidWhiteTexture, topLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
+ sb.Draw(solidWhiteTexture, topRight, srcRect, clr, rotation, Vector2.Zero, new Vector2(thickness, actualSize.Y), SpriteEffects.None, depth);
+ sb.Draw(solidWhiteTexture, bottomLeft, srcRect, clr, rotation, Vector2.Zero, new Vector2(actualSize.X, thickness), SpriteEffects.None, depth);
+ }
+
+ public static void DrawFilledRectangle(SpriteBatch sb, Vector2 position, Vector2 size, Vector2 pivot, float rotation, Color clr, float depth = 0.0f)
+ {
+ Rectangle srcRect = new Rectangle(0, 0, 1, 1);
+ sb.Draw(solidWhiteTexture, position, srcRect, clr, rotation, (pivot/size), size, SpriteEffects.None, depth);
+ }
+
public static void DrawFilledRectangle(SpriteBatch sb, RectangleF rect, Color clr, float depth = 0.0f)
{
DrawFilledRectangle(sb, rect.Location, rect.Size, clr, depth);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs
index 99461d746..e4a94bc56 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/GUITextBlock.cs
@@ -584,6 +584,13 @@ namespace Barotrauma
{
string textToShow = Censor ? censoredText : (Wrap ? wrappedText.Value : text.SanitizedValue);
Color colorToShow = currentTextColor * (currentTextColor.A / 255.0f);
+ if (TextManager.DebugDraw)
+ {
+ if (!text.NestedStr.Loaded || text.NestedStr.Language == LanguageIdentifier.None)
+ {
+ colorToShow = Color.Magenta;
+ }
+ }
if (Shadow)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
index eca7c4334..b48a3867f 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/Store.cs
@@ -139,10 +139,10 @@ namespace Barotrauma
return tab switch
{
StoreTab.Buy => true,
- StoreTab.Sell => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
- StoreTab.SellSub => campaignUI.Campaign.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
+ StoreTab.Sell => CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.SellInventoryItems),
+ StoreTab.SellSub => CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.SellSubItems),
_ => false,
- };
+ };
}
private void UpdatePermissions()
@@ -892,7 +892,7 @@ namespace Barotrauma
void CreateOrUpdateItemFrame(ItemPrefab itemPrefab, int quantity)
{
- if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo))
+ if (itemPrefab.CanBeBoughtFrom(ActiveStore, out PriceInfo priceInfo) && itemPrefab.CanCharacterBuy())
{
bool isDailySpecial = ActiveStore.DailySpecials.Contains(itemPrefab);
var itemFrame = isDailySpecial ?
@@ -1995,11 +1995,23 @@ namespace Barotrauma
int totalPrice = 0;
foreach (var item in itemsToPurchase)
{
- if (item?.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
+ if (item is null) { continue; }
+
+ if (item.ItemPrefab == null || !item.ItemPrefab.CanBeBoughtFrom(ActiveStore, out var priceInfo))
{
itemsToRemove.Add(item);
continue;
}
+
+ if (item.ItemPrefab.DefaultPrice.RequiresUnlock)
+ {
+ if (!CargoManager.HasUnlockedStoreItem(item.ItemPrefab))
+ {
+ itemsToRemove.Add(item);
+ continue;
+ }
+ }
+
totalPrice += item.Quantity * ActiveStore.GetAdjustedItemBuyPrice(item.ItemPrefab, priceInfo: priceInfo);
}
itemsToRemove.ForEach(i => itemsToPurchase.Remove(i));
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
index 7a9a97fc5..0bdb26de7 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/SubmarineSelection.cs
@@ -673,7 +673,7 @@ namespace Barotrauma
{
if (GameMain.GameSession?.Campaign?.PendingSubmarineSwitch == null)
{
- return Submarine.MainSub.Info;
+ return Submarine.MainSub?.Info;
}
else
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs
index 8a9d4e8c1..7518da497 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TabMenu.cs
@@ -34,7 +34,7 @@ namespace Barotrauma
private List teamIDs;
private const string inLobbyString = "\u2022 \u2022 \u2022";
- private GUIFrame pendingChangesFrame = null;
+ public static GUIFrame PendingChangesFrame = null;
public static Color OwnCharacterBGColor = Color.Gold * 0.7f;
private bool isTransferMenuOpen;
@@ -44,6 +44,7 @@ namespace Barotrauma
private float transferMenuOpenState;
private bool transferMenuStateCompleted;
private readonly HashSet registeredEvents = new HashSet();
+ private readonly TalentMenu talentMenu = new TalentMenu();
private class LinkedGUI
{
@@ -206,14 +207,10 @@ namespace Barotrauma
transferMenuButton.RectTransform.AbsoluteOffset = new Point(0, -pos - transferMenu.Rect.Height);
}
GameSession.UpdateTalentNotificationIndicator(talentPointNotification);
- if (Character.Controlled?.Info is { } characterInfo && talentResetButton != null && talentApplyButton != null)
+
+ if (SelectedTab is InfoFrameTab.Talents)
{
- int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
- talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
- if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
- {
- talentApplyButton.Flash(GUIStyle.Orange);
- }
+ talentMenu?.Update();
}
if (SelectedTab != InfoFrameTab.Crew) { return; }
@@ -251,6 +248,10 @@ namespace Barotrauma
{
infoFrame?.AddToGUIUpdateList();
NetLobbyScreen.JobInfoFrame?.AddToGUIUpdateList();
+ if (SelectedTab is InfoFrameTab.Talents)
+ {
+ talentMenu?.AddToGUIUpdateList();
+ }
}
public static void OnRoundEnded()
@@ -325,11 +326,11 @@ namespace Barotrauma
AbsoluteOffset = new Point(contentFrame.Rect.X, contentFrame.Rect.Bottom + GUI.IntScale(8))
}, style: null);
- pendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
+ PendingChangesFrame = new GUIFrame(new RectTransform(Vector2.One, bottomDisclaimerFrame.RectTransform, Anchor.Center), style: null);
if (GameMain.NetLobbyScreen?.CampaignCharacterDiscarded ?? false)
{
- NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
+ NetLobbyScreen.CreateChangesPendingFrame(PendingChangesFrame);
}
SetBalanceText(balanceText, campaignMode.Bank.Balance);
@@ -403,7 +404,7 @@ namespace Barotrauma
CreateSubmarineInfo(infoFrameHolder, Submarine.MainSub);
break;
case InfoFrameTab.Talents:
- CreateCharacterInfo(infoFrameHolder);
+ talentMenu.CreateGUI(infoFrameHolder);
break;
}
}
@@ -1774,370 +1775,10 @@ namespace Barotrauma
sub.Info.CreateSpecsWindow(specsListBox, GUIStyle.Font, includeTitle: false, includeClass: false, includeDescription: true);
}
}
- private Color unselectedColor = new Color(240, 255, 255, 225);
- private Color unselectableColor = new Color(100, 100, 100, 225);
- private Color pressedColor = new Color(60, 60, 60, 225);
-
- private readonly List<(GUIButton button, GUIComponent icon)> talentButtons = new List<(GUIButton button, GUIComponent icon)>();
- private readonly List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)> talentCornerIcons = new List<(Identifier talentTree, int index, GUIImage icon, GUIFrame background, GUIFrame backgroundGlow)>();
- private List selectedTalents = new List();
-
- private GUITextBlock experienceText;
- private GUIProgressBar experienceBar;
- private GUITextBlock talentPointText;
- private GUIListBox skillListBox;
-
- private GUIButton talentApplyButton,
- talentResetButton;
private GUIImage talentPointNotification;
- private readonly ImmutableDictionary talentStageStyles = new Dictionary
- {
- { TalentTree.TalentTreeStageState.Invalid, GUIStyle.GetComponentStyle("TalentTreeLocked") },
- { TalentTree.TalentTreeStageState.Locked, GUIStyle.GetComponentStyle("TalentTreeLocked") },
- { TalentTree.TalentTreeStageState.Unlocked, GUIStyle.GetComponentStyle("TalentTreePurchased") },
- { TalentTree.TalentTreeStageState.Available, GUIStyle.GetComponentStyle("TalentTreeUnlocked") },
- { TalentTree.TalentTreeStageState.Highlighted, GUIStyle.GetComponentStyle("TalentTreeAvailable") },
- }.ToImmutableDictionary();
-
- private readonly ImmutableDictionary talentStageBackgroundColors = new Dictionary
- {
- { TalentTree.TalentTreeStageState.Invalid, new Color(48,48,48,255) },
- { TalentTree.TalentTreeStageState.Locked, new Color(48,48,48,255) },
- { TalentTree.TalentTreeStageState.Unlocked, new Color(24,37,31,255) },
- { TalentTree.TalentTreeStageState.Available, new Color(50,47,33,255) },
- { TalentTree.TalentTreeStageState.Highlighted, new Color(50,47,33,255) },
- }.ToImmutableDictionary();
-
- private void CreateCharacterInfo(GUIFrame infoFrame)
- {
- infoFrame.ClearChildren();
- talentButtons.Clear();
- talentCornerIcons.Clear();
-
- GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, infoFrame.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
- int padding = GUI.IntScale(15);
- GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), infoFrame.RectTransform, Anchor.Center), style: null);
-
- GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
-
- GUIFrame characterSettingsFrame = null;
- GUILayoutGroup characterLayout = null;
- if (!(GameMain.NetworkMember is null))
- {
- characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, frame.RectTransform), style: null) { Visible = false };
- characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
- GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
- GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
- GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
- }
-
- Character controlledCharacter = Character.Controlled;
- CharacterInfo info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
- if (info == null) { return; }
-
- Job job = info.Job;
-
- GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 1.0f), content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
- {
- AbsoluteSpacing = GUI.IntScale(10),
- Stretch = true
- };
-
- GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), contentLayout.RectTransform, Anchor.Center), isHorizontal: true);
-
- new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
- {
- float posY = component.Rect.Center.Y - component.Rect.Width / 2;
- info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
- });
-
- GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
- {
- AbsoluteSpacing = GUI.IntScale(5),
- CanBeFocused = true
- };
-
- GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
-
- if (!info.OmitJobInMenus)
- {
- nameBlock.TextColor = job.Prefab.UIColor;
- GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
- }
-
- LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
- Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
- GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
- traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
-
- IEnumerable talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e));
- if (talentsOutsideTree.Count() > 0)
- {
- //spacing
- new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
-
- GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
-
- talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont);
- talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
-
- var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
- {
- AutoHideScrollBar = false,
- ResizeContentToMakeSpaceForScrollBar = false
- };
- extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
- extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
- extraTalentLayout.Recalculate();
- extraTalentList.ForceLayoutRecalculation();
-
- foreach (var extraTalent in talentsOutsideTree)
- {
- var img = new GUIImage(new RectTransform(new Point(extraTalentList.Content.Rect.Height), extraTalentList.Content.RectTransform), sprite: extraTalent.Icon, scaleToFit: true)
- {
- ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
- Color = GUIStyle.Green
- };
- img.RectTransform.SizeChanged += () =>
- {
- img.RectTransform.MaxSize = new Point(img.Rect.Height);
- };
- }
- }
-
- GUILayoutGroup skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
- {
- AbsoluteSpacing = GUI.IntScale(5),
- Stretch = true
- };
-
- GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
-
- skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
- CreateSkillList(controlledCharacter, info, skillListBox);
-
- new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
-
- GUIListBox talentTreeListBox = new GUIListBox(new RectTransform(new Vector2(1f, 0.6f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
-
- if (controlledCharacter == null)
- {
- talentTreeListBox.Enabled = false;
- }
- else
- {
- if (!TalentTree.JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree talentTree)) { return; }
-
- selectedTalents = info.GetUnlockedTalentsInTree().ToList();
-
- List subTreeNames = new List();
- foreach (var subTree in talentTree.TalentSubTrees)
- {
- GUIFrame subTreeFrame = new GUIFrame(new RectTransform(new Vector2(0.333f, 1f), talentTreeListBox.Content.RectTransform, anchor: Anchor.TopLeft), style: null);
- GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(new Vector2(1f, 1f), subTreeFrame.RectTransform, Anchor.Center), false, childAnchor: Anchor.TopCenter);
-
- GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.111f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
- int elementPadding = GUI.IntScale(8);
- Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
- GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
- subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
-
- for (int i = 0; i < 4; i++)
- {
- GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.222f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter), style: null);
-
- Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
-
- GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center), style: "TalentBackground")
- {
- Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
- };
- GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
-
- GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
- {
- CanBeFocused = false,
- Color = talentStageBackgroundColors[TalentTree.TalentTreeStageState.Locked]
- };
-
- Point iconSize = cornerIcon.RectTransform.NonScaledSize;
- cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
-
- if (subTree.TalentOptionStages.Length <= i) { continue; }
-
- TalentOption talentOption = subTree.TalentOptionStages[i];
- GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.75f, 0.7f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
- GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
-
- foreach (Identifier talentId in talentOption.TalentIdentifiers.OrderBy(t => t))
- {
- if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab talent)) { continue; }
- GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentOptionLayoutGroup.RectTransform), style: null)
- {
- CanBeFocused = false
- };
-
- GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
- GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
- {
- ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
- UserData = talent.Identifier,
- PressedColor = pressedColor,
- Enabled = controlledCharacter != null,
- OnClicked = (button, userData) =>
- {
- // deselect other buttons in tier by removing their selected talents from pool
- foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren())
- {
- if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
- {
- if (!controlledCharacter.HasTalent(otherTalentIdentifier))
- {
- selectedTalents.Remove(otherTalentIdentifier);
- }
- }
- }
- Identifier talentIdentifier = (Identifier)userData;
-
- if (TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents))
- {
- if (!selectedTalents.Contains(talentIdentifier))
- {
- selectedTalents.Add(talentIdentifier);
- }
- }
- else if (!controlledCharacter.HasTalent(talentIdentifier))
- {
- selectedTalents.Remove(talentIdentifier);
- }
-
- UpdateTalentInfo();
- return true;
- },
- };
-
- talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
-
- GUIComponent iconImage;
- if (talent.Icon is null)
- {
- iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
- {
- OutlineColor = GUIStyle.Red,
- TextColor = GUIStyle.Red,
- PressedColor = unselectableColor,
- DisabledColor = unselectableColor,
- CanBeFocused = false,
- };
- }
- else
- {
- iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
- {
- PressedColor = unselectableColor,
- DisabledColor = unselectableColor * 0.5f,
- CanBeFocused = false,
- };
- }
- iconImage.Enabled = talentButton.Enabled;
- talentButtons.Add((talentButton, iconImage));
- }
- talentCornerIcons.Add((subTree.Identifier, i, cornerIcon, talentBackground, talentBackgroundHighlight));
- }
- }
- GUITextBlock.AutoScaleAndNormalize(subTreeNames);
-
- GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), contentLayout.RectTransform, Anchor.TopCenter), isHorizontal: true)
- {
- RelativeSpacing = 0.01f,
- Stretch = true
- };
-
- GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
- GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
-
- experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
- barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
- {
- IsHorizontal = true,
- };
-
- experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
- {
- Shadow = true,
- ToolTip = TextManager.Get("experiencetooltip")
- };
-
- talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight) { AutoScaleVertical = true };
-
- talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
- {
- OnClicked = ResetTalentSelection
- };
- talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
- {
- OnClicked = ApplyTalentSelection,
- };
- GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
- }
-
- if (!(GameMain.NetworkMember is null))
- {
- GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
- text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
- {
- IgnoreLayoutGroups = false
- };
- newCharacterBox.TextBlock.AutoScaleHorizontal = true;
-
- newCharacterBox.OnClicked = (button, o) =>
- {
- if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
- {
- GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
- {
- newCharacterBox.Text = TextManager.Get("settings");
- if (pendingChangesFrame != null)
- {
- NetLobbyScreen.CreateChangesPendingFrame(pendingChangesFrame);
- }
- OpenMenu();
- });
- return true;
- }
-
- OpenMenu();
- return true;
-
- void OpenMenu()
- {
- characterSettingsFrame!.Visible = true;
- content.Visible = false;
- }
- };
-
- if (!(characterLayout is null))
- {
- GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
- new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
- {
- OnClicked = (button, o) =>
- {
- GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
- characterSettingsFrame!.Visible = false;
- content.Visible = true;
- return true;
- }
- };
- }
- }
-
- UpdateTalentInfo();
- }
-
- private void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
+ public static void CreateSkillList(Character character, CharacterInfo info, GUIListBox parent)
{
parent.Content.ClearChildren();
List skillNames = new List();
@@ -2172,116 +1813,10 @@ namespace Barotrauma
GUITextBlock.AutoScaleAndNormalize(skillNames);
}
- private void UpdateTalentInfo()
- {
- Character controlledCharacter = Character.Controlled;
- if (controlledCharacter?.Info == null) { return; }
-
- if (SelectedTab != InfoFrameTab.Talents) { return; }
-
- bool unlockedAllTalents = controlledCharacter.HasUnlockedAllTalents();
-
- if (unlockedAllTalents)
- {
- experienceText.Text = string.Empty;
- experienceBar.BarSize = 1f;
- }
- else
- {
- experienceText.Text = $"{controlledCharacter.Info.ExperiencePoints - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()} / {controlledCharacter.Info.GetExperienceRequiredToLevelUp() - controlledCharacter.Info.GetExperienceRequiredForCurrentLevel()}";
- experienceBar.BarSize = controlledCharacter.Info.GetProgressTowardsNextLevel();
- }
-
- selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
-
- string pointsLeft = controlledCharacter.Info.GetAvailableTalentPoints().ToString();
-
- int talentCount = selectedTalents.Count - controlledCharacter.Info.GetUnlockedTalentsInTree().Count();
-
- if (unlockedAllTalents)
- {
- talentPointText.SetRichText($"‖color:{XMLExtensions.ToStringHex(Color.Gray)}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
- }
- else if (talentCount > 0)
- {
- string pointsUsed = $"‖color:{XMLExtensions.ColorToString(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
- LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
- talentPointText.SetRichText(localizedString);
- }
- else
- {
- talentPointText.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
- }
-
- foreach (var (talentTree, index, icon, frame, glow) in talentCornerIcons)
- {
- TalentTree.TalentTreeStageState state = TalentTree.GetTalentOptionStageState(controlledCharacter, talentTree, index, selectedTalents);
- GUIComponentStyle newStyle = talentStageStyles[state];
- icon.ApplyStyle(newStyle);
- icon.Color = newStyle.Color;
- frame.Color = talentStageBackgroundColors[state];
- glow.Visible = state == TalentTree.TalentTreeStageState.Highlighted;
- }
-
- foreach (var talentButton in talentButtons)
- {
- Identifier talentIdentifier = (Identifier)talentButton.button.UserData;
- bool unselectable = !TalentTree.IsViableTalentForCharacter(controlledCharacter, talentIdentifier, selectedTalents) || controlledCharacter.HasTalent(talentIdentifier);
- Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
- Color hoverColor = Color.White;
-
- if (controlledCharacter.HasTalent(talentIdentifier))
- {
- newTalentColor = GUIStyle.Green;
- }
- else if (selectedTalents.Contains(talentIdentifier))
- {
- newTalentColor = GUIStyle.Orange;
- hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
- }
-
- talentButton.icon.Color = newTalentColor;
- talentButton.icon.HoverColor = hoverColor;
- }
-
- CreateSkillList(controlledCharacter, controlledCharacter.Info, skillListBox);
- }
-
- private void ApplyTalents(Character controlledCharacter)
- {
- selectedTalents = TalentTree.CheckTalentSelection(controlledCharacter, selectedTalents);
- foreach (Identifier talent in selectedTalents)
- {
- controlledCharacter.GiveTalent(talent);
- if (GameMain.Client != null)
- {
- GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
- }
- }
- selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
- UpdateTalentInfo();
- }
-
- private bool ApplyTalentSelection(GUIButton guiButton, object userData)
- {
- Character controlledCharacter = Character.Controlled;
- ApplyTalents(controlledCharacter);
- return true;
- }
-
- private bool ResetTalentSelection(GUIButton guiButton, object userData)
- {
- Character controlledCharacter = Character.Controlled;
- if (controlledCharacter?.Info == null) { return false; }
- selectedTalents = controlledCharacter.Info.GetUnlockedTalentsInTree().ToList();
- UpdateTalentInfo();
- return true;
- }
-
public void OnExperienceChanged(Character character)
{
if (character != Character.Controlled) { return; }
- UpdateTalentInfo();
+ talentMenu.UpdateTalentInfo();
}
public void OnClose()
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs
new file mode 100644
index 000000000..a3151bf46
--- /dev/null
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/TalentMenu.cs
@@ -0,0 +1,680 @@
+#nullable enable
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.Linq;
+using Barotrauma.Extensions;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+using static Barotrauma.TalentTree;
+using static Barotrauma.TalentTree.TalentTreeStageState;
+
+namespace Barotrauma
+{
+ internal readonly record struct TalentButton(GUIButton Button,
+ GUIComponent IconComponent,
+ TalentPrefab Prefab)
+ {
+ public Identifier Identifier => Prefab.Identifier;
+ }
+
+ internal readonly record struct TalentCornerIcon(Identifier TalentTree,
+ int Index,
+ GUIImage IconComponent,
+ GUIFrame BackgroundComponent,
+ GUIFrame GlowComponent);
+
+ internal readonly struct TalentTreeStyle
+ {
+ public readonly GUIComponentStyle ComponentStyle;
+ public readonly Color Color;
+
+ public TalentTreeStyle(string componentStyle, Color color)
+ {
+ ComponentStyle = GUIStyle.GetComponentStyle(componentStyle);
+ Color = color;
+ }
+ }
+
+ internal sealed class TalentMenu
+ {
+ private static readonly Color unselectedColor = new Color(240, 255, 255, 225),
+ unselectableColor = new Color(100, 100, 100, 225),
+ pressedColor = new Color(60, 60, 60, 225),
+ lockedColor = new Color(48, 48, 48, 255),
+ unlockedColor = new Color(24, 37, 31, 255),
+ availableColor = new Color(50, 47, 33, 255);
+
+ private static readonly ImmutableDictionary talentStageStyles =
+ new Dictionary
+ {
+ [Invalid] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
+ [Locked] = new TalentTreeStyle("TalentTreeLocked", lockedColor),
+ [Unlocked] = new TalentTreeStyle("TalentTreePurchased", unlockedColor),
+ [Available] = new TalentTreeStyle("TalentTreeUnlocked", availableColor),
+ [Highlighted] = new TalentTreeStyle("TalentTreeAvailable", availableColor)
+ }.ToImmutableDictionary();
+
+ private readonly HashSet talentButtons = new HashSet();
+ private readonly HashSet showCaseTalentFrames = new HashSet();
+ private readonly HashSet talentCornerIcons = new HashSet();
+ private HashSet selectedTalents = new HashSet();
+
+ private GUIListBox? skillListBox;
+ private GUITextBlock? talentPointText;
+ private GUIProgressBar? experienceBar;
+ private GUITextBlock? experienceText;
+ private GUILayoutGroup? skillLayout;
+
+ private GUIButton? talentApplyButton,
+ talentResetButton;
+
+ public void CreateGUI(GUIFrame parent)
+ {
+ parent.ClearChildren();
+ talentButtons.Clear();
+ talentCornerIcons.Clear();
+ showCaseTalentFrames.Clear();
+
+ GUIFrame background = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform, Anchor.TopCenter), style: "GUIFrameListBox");
+ int padding = GUI.IntScale(15);
+ GUIFrame frame = new GUIFrame(new RectTransform(new Point(background.Rect.Width - padding, background.Rect.Height - padding), parent.RectTransform, Anchor.Center), style: null);
+
+ GUIFrame content = new GUIFrame(new RectTransform(new Vector2(0.98f), frame.RectTransform, Anchor.Center), style: null);
+
+ GUILayoutGroup contentLayout = new GUILayoutGroup(new RectTransform(Vector2.One, content.RectTransform, anchor: Anchor.Center), childAnchor: Anchor.TopCenter)
+ {
+ AbsoluteSpacing = GUI.IntScale(10),
+ Stretch = true
+ };
+
+ Character? controlledCharacter = Character.Controlled;
+ CharacterInfo? info = controlledCharacter?.Info ?? GameMain.Client?.CharacterInfo;
+ if (info is null) { return; }
+
+ CreateStatPanel(contentLayout, info);
+
+ new GUIFrame(new RectTransform(new Vector2(1f, 1f), contentLayout.RectTransform), style: "HorizontalLine");
+
+ if (JobTalentTrees.TryGet(info.Job.Prefab.Identifier, out TalentTree? talentTree))
+ {
+ CreateTalentMenu(contentLayout, info, talentTree!);
+ }
+
+ CreateFooter(contentLayout, info);
+ UpdateTalentInfo();
+
+ if (GameMain.NetworkMember != null)
+ {
+ CreateMultiplayerCharacterSettings(frame, content);
+ }
+ }
+
+ private void CreateMultiplayerCharacterSettings(GUIComponent parent, GUIComponent content)
+ {
+ if (skillLayout is null) { return; }
+
+ GUIFrame characterSettingsFrame = new GUIFrame(new RectTransform(Vector2.One, parent.RectTransform), style: null) { Visible = false };
+ GUILayoutGroup characterLayout = new GUILayoutGroup(new RectTransform(Vector2.One, characterSettingsFrame.RectTransform));
+ GUIFrame containerFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.9f), characterLayout.RectTransform), style: null);
+ GUIFrame playerFrame = new GUIFrame(new RectTransform(new Vector2(0.9f, 0.7f), containerFrame.RectTransform, Anchor.Center), style: null);
+ GameMain.NetLobbyScreen.CreatePlayerFrame(playerFrame, alwaysAllowEditing: true, createPendingText: false);
+
+ GUIButton newCharacterBox = new GUIButton(new RectTransform(new Vector2(0.5f, 0.2f), skillLayout.RectTransform, Anchor.BottomRight),
+ text: GameMain.NetLobbyScreen.CampaignCharacterDiscarded ? TextManager.Get("settings") : TextManager.Get("createnew"), style: "GUIButtonSmall")
+ {
+ IgnoreLayoutGroups = false,
+ TextBlock =
+ {
+ AutoScaleHorizontal = true
+ }
+ };
+
+ newCharacterBox.OnClicked = (button, o) =>
+ {
+ if (!GameMain.NetLobbyScreen.CampaignCharacterDiscarded)
+ {
+ GameMain.NetLobbyScreen.TryDiscardCampaignCharacter(() =>
+ {
+ newCharacterBox.Text = TextManager.Get("settings");
+ if (TabMenu.PendingChangesFrame != null)
+ {
+ NetLobbyScreen.CreateChangesPendingFrame(TabMenu.PendingChangesFrame);
+ }
+
+ OpenMenu();
+ });
+ return true;
+ }
+
+ OpenMenu();
+ return true;
+
+ void OpenMenu()
+ {
+ characterSettingsFrame!.Visible = true;
+ content.Visible = false;
+ }
+ };
+
+ GUILayoutGroup characterCloseButtonLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.1f), characterLayout.RectTransform), childAnchor: Anchor.BottomCenter);
+ new GUIButton(new RectTransform(new Vector2(0.4f, 1f), characterCloseButtonLayout.RectTransform), TextManager.Get("ApplySettingsButton")) //TODO: Is this text appropriate for this circumstance for all languages?
+ {
+ OnClicked = (button, o) =>
+ {
+ GameMain.Client?.SendCharacterInfo(GameMain.Client.PendingName);
+ characterSettingsFrame!.Visible = false;
+ content.Visible = true;
+ return true;
+ }
+ };
+ }
+
+ private void CreateStatPanel(GUIComponent parent, CharacterInfo info)
+ {
+ Job job = info.Job;
+
+ GUILayoutGroup topLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), parent.RectTransform, Anchor.Center), isHorizontal: true);
+
+ new GUICustomComponent(new RectTransform(new Vector2(0.25f, 1f), topLayout.RectTransform), onDraw: (batch, component) =>
+ {
+ float posY = component.Rect.Center.Y - component.Rect.Width / 2;
+ info.DrawPortrait(batch, new Vector2(component.Rect.X, posY), Vector2.Zero, component.Rect.Width, false, false);
+ });
+
+ GUILayoutGroup nameLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.3f, 1f), topLayout.RectTransform))
+ {
+ AbsoluteSpacing = GUI.IntScale(5),
+ CanBeFocused = true
+ };
+
+ GUITextBlock nameBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), info.Name, font: GUIStyle.SubHeadingFont);
+
+ if (!info.OmitJobInMenus)
+ {
+ nameBlock.TextColor = job.Prefab.UIColor;
+ GUITextBlock jobBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), nameLayout.RectTransform), job.Name, font: GUIStyle.SmallFont) { TextColor = job.Prefab.UIColor };
+ }
+
+ if (info.PersonalityTrait != null)
+ {
+ LocalizedString traitString = TextManager.AddPunctuation(':', TextManager.Get("PersonalityTrait"), info.PersonalityTrait.DisplayName);
+ Vector2 traitSize = GUIStyle.SmallFont.MeasureString(traitString);
+ GUITextBlock traitBlock = new GUITextBlock(new RectTransform(Vector2.One, nameLayout.RectTransform), traitString, font: GUIStyle.SmallFont);
+ traitBlock.RectTransform.NonScaledSize = traitSize.Pad(traitBlock.Padding).ToPoint();
+ }
+
+ ImmutableHashSet talentsOutsideTree = info.GetUnlockedTalentsOutsideTree().Select(static e => TalentPrefab.TalentPrefabs.Find(c => c.Identifier == e)).ToImmutableHashSet();
+ if (talentsOutsideTree.Any())
+ {
+ //spacing
+ new GUIFrame(new RectTransform(new Vector2(1.0f, 0.05f), nameLayout.RectTransform), style: null);
+
+ GUILayoutGroup extraTalentLayout = new GUILayoutGroup(new RectTransform(new Vector2(1.0f, 0.3f), nameLayout.RectTransform), childAnchor: Anchor.TopCenter);
+
+ talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), extraTalentLayout.RectTransform, anchor: Anchor.Center), TextManager.Get("talentmenu.extratalents"), font: GUIStyle.SubHeadingFont);
+ talentPointText.RectTransform.MaxSize = new Point(int.MaxValue, (int)talentPointText.TextSize.Y);
+
+ var extraTalentList = new GUIListBox(new RectTransform(new Vector2(0.9f, 0.8f), extraTalentLayout.RectTransform, anchor: Anchor.Center), isHorizontal: true)
+ {
+ AutoHideScrollBar = false,
+ ResizeContentToMakeSpaceForScrollBar = false
+ };
+ extraTalentList.ScrollBar.RectTransform.SetPosition(Anchor.BottomCenter, Pivot.TopCenter);
+ extraTalentList.RectTransform.MinSize = new Point(0, GUI.IntScale(65));
+ extraTalentLayout.Recalculate();
+ extraTalentList.ForceLayoutRecalculation();
+
+ foreach (var extraTalent in talentsOutsideTree)
+ {
+ if (extraTalent is null) { continue; }
+ GUIImage talentImg = new GUIImage(new RectTransform(Vector2.One, extraTalentList.Content.RectTransform, scaleBasis: ScaleBasis.BothHeight), sprite: extraTalent.Icon, scaleToFit: true)
+ {
+ ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{extraTalent.DisplayName}‖color:end‖" + "\n\n" + extraTalent.Description),
+ Color = GUIStyle.Green
+ };
+ }
+ }
+
+ skillLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.45f, 1f), topLayout.RectTransform), childAnchor: Anchor.TopRight)
+ {
+ AbsoluteSpacing = GUI.IntScale(5),
+ Stretch = true
+ };
+
+ GUITextBlock skillBlock = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.0f), skillLayout.RectTransform), TextManager.Get("skills"), font: GUIStyle.SubHeadingFont);
+
+ skillListBox = new GUIListBox(new RectTransform(new Vector2(1f, 1f - skillBlock.RectTransform.RelativeSize.Y), skillLayout.RectTransform), style: null);
+ TabMenu.CreateSkillList(info.Character, info, skillListBox);
+ }
+
+ private void CreateTalentMenu(GUIComponent parent, CharacterInfo info, TalentTree tree)
+ {
+ GUIListBox mainList = new GUIListBox(new RectTransform(new Vector2(1f, 0.9f), parent.RectTransform, anchor: Anchor.TopCenter));
+
+ selectedTalents = info.GetUnlockedTalentsInTree().ToHashSet();
+
+ List subTreeNames = new List();
+ foreach (var subTree in tree.TalentSubTrees)
+ {
+ GUIListBox talentList;
+ GUIComponent talentParent;
+ Vector2 treeSize;
+ switch (subTree.Type)
+ {
+ case TalentTreeType.Primary:
+ talentList = mainList;
+ treeSize = new Vector2(1f, 0.5f);
+ break;
+ case TalentTreeType.Specialization:
+ talentList = GetSpecializationList();
+ treeSize = new Vector2(0.333f, 1f);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException($"Invalid TalentTreeType \"{subTree.Type}\"");
+ }
+ talentParent = talentList.Content;
+
+ GUILayoutGroup subTreeLayoutGroup = new GUILayoutGroup(new RectTransform(treeSize, talentParent.RectTransform), isHorizontal: false, childAnchor: Anchor.TopCenter)
+ {
+ Stretch = true
+ };
+
+ if (subTree.Type != TalentTreeType.Primary)
+ {
+ GUIFrame subtreeTitleFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.05f), subTreeLayoutGroup.RectTransform, anchor: Anchor.TopCenter)
+ { MinSize = new Point(0, GUI.IntScale(30)) }, style: null);
+ subtreeTitleFrame.RectTransform.IsFixedSize = true;
+ int elementPadding = GUI.IntScale(8);
+ Point headerSize = subtreeTitleFrame.RectTransform.NonScaledSize;
+ GUIFrame subTreeTitleBackground = new GUIFrame(new RectTransform(new Point(headerSize.X - elementPadding, headerSize.Y), subtreeTitleFrame.RectTransform, anchor: Anchor.Center), style: "SubtreeHeader");
+ subTreeNames.Add(new GUITextBlock(new RectTransform(Vector2.One, subTreeTitleBackground.RectTransform, anchor: Anchor.TopCenter), subTree.DisplayName, font: GUIStyle.SubHeadingFont, textAlignment: Alignment.Center));
+ }
+
+ int optionAmount = subTree.TalentOptionStages.Length;
+ for (int i = 0; i < optionAmount; i++)
+ {
+ TalentOption option = subTree.TalentOptionStages[i];
+ CreateTalentOption(subTreeLayoutGroup, subTree, i, option, info);
+ }
+ subTreeLayoutGroup.RectTransform.Resize(new Point(subTreeLayoutGroup.Rect.Width,
+ subTreeLayoutGroup.Children.Sum(c => c.Rect.Height + subTreeLayoutGroup.AbsoluteSpacing)));
+ subTreeLayoutGroup.RectTransform.MinSize = new Point(subTreeLayoutGroup.Rect.Width, subTreeLayoutGroup.Rect.Height);
+ subTreeLayoutGroup.Recalculate();
+ if (subTree.Type == TalentTreeType.Specialization)
+ {
+ talentList.RectTransform.Resize(new Point(talentList.Rect.Width, Math.Max(subTreeLayoutGroup.Rect.Height, talentList.Rect.Height)));
+ talentList.RectTransform.MinSize = new Point(0, talentList.Rect.Height);
+ }
+ }
+
+ var specializationList = GetSpecializationList();
+ GetSpecializationList().RectTransform.Resize(new Point(specializationList.Content.Children.Sum(c => c.Rect.Width), specializationList.Rect.Height));
+
+ GUITextBlock.AutoScaleAndNormalize(subTreeNames);
+
+ GUIListBox GetSpecializationList()
+ {
+ if (mainList.Content.Children.LastOrDefault() is GUIListBox specializationList)
+ {
+ return specializationList;
+ }
+
+ GUIListBox newSpecializationList = new GUIListBox(new RectTransform(new Vector2(1.0f, 0.5f), mainList.Content.RectTransform, Anchor.TopCenter), isHorizontal: true, style: null);
+ return newSpecializationList;
+ }
+ }
+
+ private void CreateTalentOption(GUIComponent parent, TalentSubTree subTree, int index, TalentOption talentOption, CharacterInfo info)
+ {
+ int elementPadding = GUI.IntScale(8);
+ GUIFrame talentOptionFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.01f), parent.RectTransform, anchor: Anchor.TopCenter)
+ { MinSize = new Point(0, GUI.IntScale(65)) }, style: null);
+
+ Point talentFrameSize = talentOptionFrame.RectTransform.NonScaledSize;
+
+ GUIFrame talentBackground = new GUIFrame(new RectTransform(new Point(talentFrameSize.X - elementPadding, talentFrameSize.Y - elementPadding), talentOptionFrame.RectTransform, anchor: Anchor.Center),
+ style: "TalentBackground")
+ {
+ Color = talentStageStyles[Locked].Color
+ };
+ GUIFrame talentBackgroundHighlight = new GUIFrame(new RectTransform(Vector2.One, talentBackground.RectTransform, anchor: Anchor.Center), style: "TalentBackgroundGlow") { Visible = false };
+
+ GUIImage cornerIcon = new GUIImage(new RectTransform(new Vector2(0.2f), talentOptionFrame.RectTransform, anchor: Anchor.BottomRight, scaleBasis: ScaleBasis.BothHeight) { MaxSize = new Point(16) }, style: null)
+ {
+ CanBeFocused = false,
+ Color = talentStageStyles[Locked].Color
+ };
+
+ Point iconSize = cornerIcon.RectTransform.NonScaledSize;
+ cornerIcon.RectTransform.AbsoluteOffset = new Point(iconSize.X / 2, iconSize.Y / 2);
+
+ GUILayoutGroup talentOptionCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.6f, 0.9f), talentOptionFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
+ GUILayoutGroup talentOptionLayoutGroup = new GUILayoutGroup(new RectTransform(Vector2.One, talentOptionCenterGroup.RectTransform), isHorizontal: true, childAnchor: Anchor.CenterLeft) { Stretch = true };
+
+ HashSet talentOptionIdentifiers = talentOption.TalentIdentifiers.OrderBy(static t => t).ToHashSet();
+ bool hasShowcase = talentOption.ShowcaseTalent.TryUnwrap(out Identifier showcaseTalentIdentifier);
+ GUILayoutGroup showcaseLayout = talentOptionLayoutGroup;
+
+ if (hasShowcase)
+ {
+ talentOptionIdentifiers.Add(showcaseTalentIdentifier);
+ Point parentSize = talentBackground.RectTransform.NonScaledSize;
+ GUIFrame showCaseFrame = new GUIFrame(new RectTransform(new Point((int)(parentSize.X / 3f * (talentOptionIdentifiers.Count - 1)), parentSize.Y)), style: "GUITooltip")
+ {
+ UserData = showcaseTalentIdentifier,
+ IgnoreLayoutGroups = true,
+ Visible = false
+ };
+ GUILayoutGroup showcaseCenterGroup = new GUILayoutGroup(new RectTransform(new Vector2(0.9f, 0.7f), showCaseFrame.RectTransform, Anchor.Center), childAnchor: Anchor.CenterLeft);
+ showcaseLayout = new GUILayoutGroup(new RectTransform(Vector2.One, showcaseCenterGroup.RectTransform), isHorizontal: true) { Stretch = true };
+ showCaseTalentFrames.Add(showCaseFrame);
+ }
+
+ foreach (Identifier talentId in talentOptionIdentifiers)
+ {
+ if (!TalentPrefab.TalentPrefabs.TryGet(talentId, out TalentPrefab? talent)) { continue; }
+
+ bool isShowCaseTalent = hasShowcase && talentId == showcaseTalentIdentifier;
+ GUIComponent talentParent;
+
+ if (hasShowcase && talentId != showcaseTalentIdentifier)
+ {
+ talentParent = showcaseLayout;
+ }
+ else
+ {
+ talentParent = talentOptionLayoutGroup;
+ }
+
+ GUIFrame talentFrame = new GUIFrame(new RectTransform(Vector2.One, talentParent.RectTransform), style: null)
+ {
+ CanBeFocused = false
+ };
+
+ GUIFrame croppedTalentFrame = new GUIFrame(new RectTransform(Vector2.One, talentFrame.RectTransform, anchor: Anchor.Center, scaleBasis: ScaleBasis.BothHeight), style: null);
+ GUIButton talentButton = new GUIButton(new RectTransform(Vector2.One, croppedTalentFrame.RectTransform, anchor: Anchor.Center), style: null)
+ {
+ ToolTip = RichString.Rich($"‖color:{Color.White.ToStringHex()}‖{talent.DisplayName}‖color:end‖" + "\n\n" + talent.Description),
+ UserData = talent.Identifier,
+ PressedColor = pressedColor,
+ Enabled = info.Character != null,
+ OnClicked = (button, userData) =>
+ {
+ if (isShowCaseTalent)
+ {
+ foreach (GUIComponent component in showCaseTalentFrames)
+ {
+ if (component.UserData is Identifier showcaseIdentifier && showcaseIdentifier == talentId)
+ {
+ component.RectTransform.ScreenSpaceOffset = new Point((int)(button.Rect.Location.X - component.Rect.Width / 2f + button.Rect.Width / 2f), button.Rect.Location.Y - component.Rect.Height);
+ component.Visible = true;
+ }
+ else
+ {
+ component.Visible = false;
+ }
+ }
+
+ return true;
+ }
+
+ Character? controlledCharacter = info.Character;
+ if (controlledCharacter is null) { return false; }
+
+ if (talentOption.MaxChosenTalents is 1)
+ {
+ // deselect other buttons in tier by removing their selected talents from pool
+ foreach (GUIButton guiButton in talentOptionLayoutGroup.GetAllChildren())
+ {
+ if (guiButton.UserData is Identifier otherTalentIdentifier && guiButton != button)
+ {
+ if (!controlledCharacter.HasTalent(otherTalentIdentifier))
+ {
+ selectedTalents.Remove(otherTalentIdentifier);
+ }
+ }
+ }
+ }
+
+ Identifier talentIdentifier = (Identifier)userData;
+
+ if (IsViableTalentForCharacter(info.Character, talentIdentifier, selectedTalents))
+ {
+ if (!selectedTalents.Contains(talentIdentifier))
+ {
+ selectedTalents.Add(talentIdentifier);
+ }
+ else
+ {
+ selectedTalents.Remove(talentIdentifier);
+ }
+ }
+ else if (!controlledCharacter.HasTalent(talentIdentifier))
+ {
+ selectedTalents.Remove(talentIdentifier);
+ }
+
+ UpdateTalentInfo();
+ return true;
+ },
+ };
+
+ talentButton.Color = talentButton.HoverColor = talentButton.PressedColor = talentButton.SelectedColor = talentButton.DisabledColor = Color.Transparent;
+
+ GUIComponent iconImage;
+ if (talent.Icon is null)
+ {
+ iconImage = new GUITextBlock(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), text: "???", font: GUIStyle.LargeFont, textAlignment: Alignment.Center, style: null)
+ {
+ OutlineColor = GUIStyle.Red,
+ TextColor = GUIStyle.Red,
+ PressedColor = unselectableColor,
+ DisabledColor = unselectableColor,
+ CanBeFocused = false,
+ };
+ }
+ else
+ {
+ iconImage = new GUIImage(new RectTransform(Vector2.One, talentButton.RectTransform, anchor: Anchor.Center), sprite: talent.Icon, scaleToFit: true)
+ {
+ Color = talent.ColorOverride.TryUnwrap(out Color color) ? color : Color.White,
+ PressedColor = unselectableColor,
+ DisabledColor = unselectableColor * 0.5f,
+ CanBeFocused = false,
+ };
+ }
+
+ iconImage.Enabled = talentButton.Enabled;
+ if (isShowCaseTalent) { continue; }
+
+ talentButtons.Add(new TalentButton(talentButton, iconImage, talent));
+ }
+
+ talentCornerIcons.Add(new TalentCornerIcon(subTree.Identifier, index, cornerIcon, talentBackground, talentBackgroundHighlight));
+ }
+
+ private void CreateFooter(GUIComponent parent, CharacterInfo info)
+ {
+ GUILayoutGroup bottomLayout = new GUILayoutGroup(new RectTransform(new Vector2(1f, 0.07f), parent.RectTransform, Anchor.TopCenter), isHorizontal: true)
+ {
+ RelativeSpacing = 0.01f,
+ Stretch = true
+ };
+
+ GUILayoutGroup experienceLayout = new GUILayoutGroup(new RectTransform(new Vector2(0.59f, 1f), bottomLayout.RectTransform));
+ GUIFrame experienceBarFrame = new GUIFrame(new RectTransform(new Vector2(1f, 0.5f), experienceLayout.RectTransform), style: null);
+
+ experienceBar = new GUIProgressBar(new RectTransform(new Vector2(1f, 1f), experienceBarFrame.RectTransform, Anchor.CenterLeft),
+ barSize: info.GetProgressTowardsNextLevel(), color: GUIStyle.Green)
+ {
+ IsHorizontal = true,
+ };
+
+ experienceText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 1.0f), experienceBarFrame.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.Font, textAlignment: Alignment.CenterRight)
+ {
+ Shadow = true,
+ ToolTip = TextManager.Get("experiencetooltip")
+ };
+
+ talentPointText = new GUITextBlock(new RectTransform(new Vector2(1.0f, 0.5f), experienceLayout.RectTransform, anchor: Anchor.Center), "", font: GUIStyle.SubHeadingFont, textAlignment: Alignment.CenterRight)
+ { AutoScaleVertical = true };
+
+ talentResetButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("reset"), style: "GUIButtonFreeScale")
+ {
+ OnClicked = ResetTalentSelection
+ };
+ talentApplyButton = new GUIButton(new RectTransform(new Vector2(0.19f, 1f), bottomLayout.RectTransform), text: TextManager.Get("applysettingsbutton"), style: "GUIButtonFreeScale")
+ {
+ OnClicked = ApplyTalentSelection,
+ };
+ GUITextBlock.AutoScaleAndNormalize(talentResetButton.TextBlock, talentApplyButton.TextBlock);
+ }
+
+ private bool ResetTalentSelection(GUIButton guiButton, object userData)
+ {
+ UpdateTalentInfo();
+ return true;
+ }
+
+ private void ApplyTalents(Character controlledCharacter)
+ {
+ foreach (Identifier talent in CheckTalentSelection(controlledCharacter, selectedTalents))
+ {
+ controlledCharacter.GiveTalent(talent);
+ if (GameMain.Client != null)
+ {
+ GameMain.Client.CreateEntityEvent(controlledCharacter, new Character.UpdateTalentsEventData());
+ }
+ }
+
+ UpdateTalentInfo();
+ }
+
+ private bool ApplyTalentSelection(GUIButton guiButton, object userData)
+ {
+ Character controlledCharacter = Character.Controlled;
+ if (controlledCharacter is null) { return false; }
+
+ ApplyTalents(controlledCharacter);
+ return true;
+ }
+
+ public void UpdateTalentInfo()
+ {
+ if (!(Character.Controlled is { Info: var info } character)) { return; }
+
+ bool unlockedAllTalents = character.HasUnlockedAllTalents();
+
+ if (experienceBar is null || experienceText is null) { return; }
+
+ if (unlockedAllTalents)
+ {
+ experienceText.Text = string.Empty;
+ experienceBar.BarSize = 1f;
+ }
+ else
+ {
+ experienceText.Text = $"{info.ExperiencePoints - info.GetExperienceRequiredForCurrentLevel()} / {info.GetExperienceRequiredToLevelUp() - info.GetExperienceRequiredForCurrentLevel()}";
+ experienceBar.BarSize = info.GetProgressTowardsNextLevel();
+ }
+
+ selectedTalents = CheckTalentSelection(character, selectedTalents).ToHashSet();
+
+ string pointsLeft = info.GetAvailableTalentPoints().ToString();
+
+ int talentCount = selectedTalents.Count - info.GetUnlockedTalentsInTree().Count();
+
+ if (unlockedAllTalents)
+ {
+ talentPointText?.SetRichText($"‖color:{Color.Gray.ToStringHex()}‖{TextManager.Get("talentmenu.alltalentsunlocked")}‖color:end‖");
+ }
+ else if (talentCount > 0)
+ {
+ string pointsUsed = $"‖color:{XMLExtensions.ToStringHex(GUIStyle.Red)}‖{-talentCount}‖color:end‖";
+ LocalizedString localizedString = TextManager.GetWithVariables("talentmenu.points.spending", ("[amount]", pointsLeft), ("[used]", pointsUsed));
+ talentPointText?.SetRichText(localizedString);
+ }
+ else
+ {
+ talentPointText?.SetRichText(TextManager.GetWithVariable("talentmenu.points", "[amount]", pointsLeft));
+ }
+
+ foreach (TalentCornerIcon cornerIcon in talentCornerIcons)
+ {
+ TalentTreeStageState state = GetTalentOptionStageState(character, cornerIcon.TalentTree, cornerIcon.Index, selectedTalents);
+ TalentTreeStyle style = talentStageStyles[state];
+ GUIComponentStyle newStyle = style.ComponentStyle;
+ cornerIcon.IconComponent.ApplyStyle(newStyle);
+ cornerIcon.IconComponent.Color = newStyle.Color;
+ cornerIcon.BackgroundComponent.Color = style.Color;
+ cornerIcon.GlowComponent.Visible = state == Highlighted;
+ }
+
+ foreach (TalentButton talentButton in talentButtons)
+ {
+ Identifier talentIdentifier = talentButton.Identifier;
+ bool unselectable = !IsViableTalentForCharacter(character, talentIdentifier, selectedTalents) || character.HasTalent(talentIdentifier);
+ Color newTalentColor = unselectable ? unselectableColor : unselectedColor;
+ Color hoverColor = Color.White;
+ bool selected = false;
+
+ if (character.HasTalent(talentIdentifier))
+ {
+ selected = true;
+ newTalentColor = GUIStyle.Green;
+ }
+ else if (selectedTalents.Contains(talentIdentifier))
+ {
+ selected = true;
+ newTalentColor = GUIStyle.Orange;
+ hoverColor = Color.Lerp(GUIStyle.Orange, Color.White, 0.7f);
+ }
+
+ bool shouldOverride = !unselectable || selected;
+
+ if (shouldOverride && talentButton.Prefab.ColorOverride.TryUnwrap(out Color overrideColor))
+ {
+ newTalentColor = overrideColor;
+ }
+
+ talentButton.IconComponent.Color = newTalentColor;
+ talentButton.IconComponent.HoverColor = hoverColor;
+ }
+
+ if (skillListBox is null) { return; }
+
+ TabMenu.CreateSkillList(character, info, skillListBox);
+ }
+
+ public void AddToGUIUpdateList()
+ {
+ bool mouseInteracted = PlayerInput.PrimaryMouseButtonClicked() || PlayerInput.SecondaryMouseButtonClicked() || PlayerInput.ScrollWheelSpeed != 0;
+ bool keyboardInteracted = PlayerInput.KeyHit(Keys.Escape) || GameSettings.CurrentConfig.KeyMap.Bindings[InputType.InfoTab].IsHit();
+
+ foreach (GUIComponent component in showCaseTalentFrames)
+ {
+ component.AddToGUIUpdateList(order: 1);
+ if (!component.Visible) { continue; }
+
+ if (keyboardInteracted || (mouseInteracted && !component.Rect.Contains(PlayerInput.MousePosition)))
+ {
+ component.Visible = false;
+ }
+ }
+ }
+
+ public void Update()
+ {
+ if (Character.Controlled?.Info is not { } characterInfo || talentResetButton is null || talentApplyButton is null) { return; }
+
+ int talentCount = selectedTalents.Count - characterInfo.GetUnlockedTalentsInTree().Count();
+ talentResetButton.Enabled = talentApplyButton.Enabled = talentCount > 0;
+ if (talentApplyButton.Enabled && talentApplyButton.FlashTimer <= 0.0f)
+ {
+ talentApplyButton.Flash(GUIStyle.Orange);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs
index 08a186081..b58bcc3fc 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GUI/UpgradeStore.cs
@@ -433,8 +433,8 @@ namespace Barotrauma
Location location = Campaign.Map.CurrentLocation;
- int hullRepairCost = Campaign.GetHullRepairCost();
- int itemRepairCost = Campaign.GetItemRepairCost();
+ int hullRepairCost = CampaignMode.GetHullRepairCost();
+ int itemRepairCost = CampaignMode.GetItemRepairCost();
int shuttleRetrieveCost = CampaignMode.ShuttleReplaceCost;
if (location != null)
{
@@ -847,7 +847,7 @@ namespace Barotrauma
foreach (UpgradePrefab prefab in prefabs)
{
- if (prefab.MaxLevel is 0) { continue; }
+ if (prefab.GetMaxLevelForCurrentSub() == 0) { continue; }
CreateUpgradeEntry(prefab, category, parent.Content, submarine, entitiesOnSub);
}
}
@@ -1080,7 +1080,7 @@ namespace Barotrauma
public static GUIFrame CreateUpgradeFrame(UpgradePrefab prefab, UpgradeCategory category, CampaignMode campaign, RectTransform rectTransform, bool addBuyButton = true)
{
- int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
+ int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
return CreateUpgradeEntry(rectTransform, prefab.Sprite, prefab.Name, prefab.Description, price, new CategoryData(category, prefab), addBuyButton, upgradePrefab: prefab, currentLevel: campaign.UpgradeManager.GetUpgradeLevel(prefab, category));
}
@@ -1177,11 +1177,12 @@ namespace Barotrauma
private static void UpdateUpgradePercentageText(GUITextBlock text, UpgradePrefab upgradePrefab, int currentLevel)
{
- float nextIncrease = upgradePrefab.IncreaseOnTooltip * (Math.Min(currentLevel + 1, upgradePrefab.MaxLevel));
+ int maxLevel = upgradePrefab.GetMaxLevelForCurrentSub();
+ float nextIncrease = upgradePrefab.IncreaseOnTooltip * Math.Min(currentLevel + 1, maxLevel);
if (nextIncrease != 0f)
{
text.Text = $"{Math.Round(nextIncrease, 1)} %";
- if (currentLevel == upgradePrefab.MaxLevel)
+ if (currentLevel == maxLevel)
{
text.TextColor = Color.Gray;
}
@@ -1221,7 +1222,7 @@ namespace Barotrauma
{
LocalizedString promptBody = TextManager.GetWithVariables("Upgrades.PurchasePromptBody",
("[upgradename]", prefab.Name),
- ("[amount]", prefab.Price.GetBuyprice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString()));
+ ("[amount]", prefab.Price.GetBuyPrice(Campaign.UpgradeManager.GetUpgradeLevel(prefab, category), Campaign.Map?.CurrentLocation).ToString()));
currectConfirmation = EventEditorScreen.AskForConfirmation(TextManager.Get("Upgrades.PurchasePromptTitle"), promptBody, () =>
{
if (GameMain.NetworkMember != null)
@@ -1617,14 +1618,15 @@ namespace Barotrauma
{
int currentLevel = campaign.UpgradeManager.GetUpgradeLevel(prefab, category);
- LocalizedString progressText = TextManager.GetWithVariables("upgrades.progressformat", ("[level]", currentLevel.ToString()), ("[maxlevel]", prefab.MaxLevel.ToString()));
+ int maxLevel = prefab.GetMaxLevelForCurrentSub();
+ LocalizedString progressText = TextManager.GetWithVariables("upgrades.progressformat", ("[level]", currentLevel.ToString()), ("[maxlevel]", maxLevel.ToString()));
if (prefabFrame.FindChild("progressbar", true) is { } progressParent)
{
GUIProgressBar bar = progressParent.GetChild();
if (bar != null)
{
- bar.BarSize = currentLevel / (float) prefab.MaxLevel;
- bar.Color = currentLevel >= prefab.MaxLevel ? GUIStyle.Green : GUIStyle.Orange;
+ bar.BarSize = currentLevel / (float)maxLevel;
+ bar.Color = currentLevel >= maxLevel ? GUIStyle.Green : GUIStyle.Orange;
}
GUITextBlock block = progressParent.GetChild();
@@ -1637,12 +1639,12 @@ namespace Barotrauma
GUITextBlock priceLabel = textBlocks[0];
priceLabel.Visible = true;
- int price = prefab.Price.GetBuyprice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
+ int price = prefab.Price.GetBuyPrice(campaign.UpgradeManager.GetUpgradeLevel(prefab, category), campaign.Map?.CurrentLocation);
if (priceLabel != null && !WaitForServerUpdate)
{
priceLabel.Text = TextManager.FormatCurrency(price);
- if (currentLevel >= prefab.MaxLevel)
+ if (currentLevel >= maxLevel)
{
priceLabel.Text = TextManager.Get("Upgrade.MaxedUpgrade");
}
@@ -1651,7 +1653,7 @@ namespace Barotrauma
GUIButton button = buttonParent.GetChild();
if (button != null)
{
- button.Enabled = currentLevel < prefab.MaxLevel;
+ button.Enabled = currentLevel < maxLevel;
if (WaitForServerUpdate || campaign.GetBalance() < price)
{
button.Enabled = false;
@@ -1697,13 +1699,14 @@ namespace Barotrauma
foreach (GUIComponent component in indicators.Children)
{
- if (!(component is GUIImage image)) { continue; }
+ if (component is not GUIImage image) { continue; }
foreach (UpgradePrefab prefab in prefabs)
{
if (component.UserData != prefab) { continue; }
- if (prefab.MaxLevel is 0)
+ int maxLevel = prefab.GetMaxLevelForCurrentSub();
+ if (maxLevel == 0)
{
component.Visible = false;
continue;
@@ -1715,7 +1718,6 @@ namespace Barotrauma
GUIComponentStyle onStyle = styles["upgradeindicatoron".ToIdentifier()];
GUIComponentStyle dimStyle = styles["upgradeindicatordim".ToIdentifier()];
GUIComponentStyle offStyle = styles["upgradeindicatoroff".ToIdentifier()];
- int maxLevel = prefab.MaxLevel;
if (maxLevel == 0)
{
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs b/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs
index e8069b34b..a0a55d717 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameAnalytics/GameAnalyticsManager.cs
@@ -30,7 +30,7 @@ namespace Barotrauma
Data = data,
OnClick = (GUITextBlock component, GUITextBlock.ClickableArea area) =>
{
- GameMain.Instance.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
+ GameMain.ShowOpenUrlInWebBrowserPrompt("https://gameanalytics.com/privacy/");
}
});
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
index 226487ec1..5965d350e 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameMain.cs
@@ -791,6 +791,10 @@ namespace Barotrauma
{
GUI.TogglePauseMenu();
}
+ else if (GameSession?.Campaign is { ShowCampaignUI: true, ForceMapUI: false })
+ {
+ GameSession.Campaign.ShowCampaignUI = false;
+ }
//open the pause menu if not controlling a character OR if the character has no UIs active that can be closed with ESC
else if ((Character.Controlled == null || !itemHudActive())
&& CharacterHealth.OpenHealthWindow == null
@@ -1200,7 +1204,7 @@ namespace Barotrauma
base.OnExiting(sender, args);
}
- public void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
+ public static void ShowOpenUrlInWebBrowserPrompt(string url, string promptExtensionTag = null)
{
if (string.IsNullOrEmpty(url)) { return; }
if (GUIMessageBox.VisibleBox?.UserData as string == "verificationprompt") { return; }
@@ -1218,7 +1222,14 @@ namespace Barotrauma
};
msgBox.Buttons[0].OnClicked = (btn, userdata) =>
{
- ToolBox.OpenFileWithShell(url);
+ try
+ {
+ ToolBox.OpenFileWithShell(url);
+ }
+ catch (Exception e)
+ {
+ DebugConsole.ThrowError($"Failed to open the url {url}", e);
+ }
msgBox.Close();
return true;
};
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
index 54d942d3b..3311d1650 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/CampaignMode.cs
@@ -86,10 +86,12 @@ namespace Barotrauma
}
}
+ private static bool IsOwner(Client client) => client != null && client.IsOwner;
+
///
/// There is a server-side implementation of the method in
///
- public bool AllowedToManageCampaign(ClientPermissions permissions)
+ public static bool AllowedToManageCampaign(ClientPermissions permissions)
{
//allow managing the round if the client has permissions, is the owner, the only client in the server,
//or if no-one has management permissions
@@ -97,9 +99,8 @@ namespace Barotrauma
return
GameMain.Client.HasPermission(permissions) ||
GameMain.Client.HasPermission(ClientPermissions.ManageCampaign) ||
- GameMain.Client.ConnectedClients.Count == 1 ||
GameMain.Client.IsServerOwner ||
- GameMain.Client.ConnectedClients.None(c => c.InGame && (c.IsOwner || c.HasPermission(permissions)));
+ AnyOneAllowedToManageCampaign(permissions);
}
public static bool AllowedToManageWallets()
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
index dc7bc12b1..965b3a274 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/GameModes/SinglePlayerCampaign.cs
@@ -407,6 +407,11 @@ namespace Barotrauma
GUI.SetSavingIndicatorState(success);
crewDead = false;
+ if (success)
+ {
+ // Event history must be registered before ending the round or it will be cleared
+ GameMain.GameSession.EventManager.RegisterEventHistory();
+ }
GameMain.GameSession.EndRound("", traitorResults, transitionType);
var continueButton = GameMain.GameSession.RoundSummary?.ContinueButton;
RoundSummary roundSummary = null;
@@ -466,7 +471,6 @@ namespace Barotrauma
if (success)
{
GameMain.GameSession.SubmarineInfo = new SubmarineInfo(GameMain.GameSession.Submarine);
- GameMain.GameSession.EventManager.RegisterEventHistory();
SaveUtil.SaveGame(GameMain.GameSession.SavePath);
}
else
diff --git a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
index a4ec49919..da2c6b35b 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/GameSession/RoundSummary.cs
@@ -506,7 +506,7 @@ namespace Barotrauma
private LocalizedString GetHeaderText(bool gameOver, CampaignMode.TransitionType transitionType)
{
- string locationName = Submarine.MainSub.AtEndExit ? endLocation?.Name : startLocation?.Name;
+ string locationName = Submarine.MainSub is { AtEndExit: true } ? endLocation?.Name : startLocation?.Name;
string textTag;
if (gameOver)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs
index 1645e1be1..ef7418390 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/CharacterInventory.cs
@@ -774,7 +774,6 @@ namespace Barotrauma
}
else
{
- bool isEquippable = item.AllowedSlots.Any(s => s != InvSlotType.Any);
var selectedContainer = character.SelectedItem?.GetComponent();
if (selectedContainer != null &&
@@ -802,8 +801,7 @@ namespace Barotrauma
}
else if (character.HeldItems.Any(i =>
i.OwnInventory != null &&
- /*disallow putting into equipped item if the item is equippable (equip as the quick action instead)*/
- ((i.OwnInventory.CanBePut(item) && (allowInventorySwap || !isEquippable)) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
+ (i.OwnInventory.CanBePut(item) || (i.OwnInventory.Capacity == 1 && i.OwnInventory.AllowSwappingContainedItems && i.OwnInventory.Container.CanBeContained(item)))))
{
return QuickUseAction.PutToEquippedItem;
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs
index 1684642d0..f649e423c 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/ElectricalDischarger.cs
@@ -25,14 +25,31 @@ namespace Barotrauma.Items.Components
foreach (Node node in nodes)
{
GameMain.ParticleManager.CreateParticle("swirlysmoke", node.WorldPosition, Vector2.Zero);
+
+ if (node.ParentIndex > -1)
+ {
+ Vector2 diff = nodes[node.ParentIndex].WorldPosition - node.WorldPosition;
+ float dist = diff.Length();
+ Vector2 normalizedDiff = diff / dist;
+ for (float x = 0.0f; x < dist; x += 50.0f)
+ {
+ var spark = GameMain.ParticleManager.CreateParticle("ElectricShock", node.WorldPosition + normalizedDiff * x, Vector2.Zero);
+ if (spark != null)
+ {
+ spark.Size *= 0.3f;
+ }
+ }
+
+ }
}
}
public void DrawElectricity(SpriteBatch spriteBatch)
{
+ if (timer <= 0.0f) { return; }
for (int i = 0; i < nodes.Count; i++)
{
- if (nodes[i].Length <= 1.0f) continue;
+ if (nodes[i].Length <= 1.0f) { continue; }
var node = nodes[i];
electricitySprite.Draw(spriteBatch,
(i + frameOffset) % electricitySprite.FrameCount,
@@ -46,10 +63,16 @@ namespace Barotrauma.Items.Components
if (GameMain.DebugDraw)
{
- for (int i = 0; i < nodes.Count; i++)
+ for (int i = 1; i < nodes.Count; i++)
{
- if (nodes[i].Length <= 1.0f) continue;
- GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 5, Color.LightCyan, isFilled: true);
+ GUI.DrawLine(spriteBatch,
+ new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y),
+ new Vector2(nodes[nodes[i].ParentIndex].WorldPosition.X, -nodes[nodes[i].ParentIndex].WorldPosition.Y),
+ Color.LightCyan,
+ width: 3);
+
+ if (nodes[i].Length <= 1.0f) { continue; }
+ GUI.DrawRectangle(spriteBatch, new Vector2(nodes[i].WorldPosition.X, -nodes[i].WorldPosition.Y), Vector2.One * 10, Color.LightCyan, isFilled: true);
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
index dd3b30b66..71cfdb1b4 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Deconstructor.cs
@@ -265,6 +265,8 @@ namespace Barotrauma.Items.Components
foreach (DeconstructItem deconstructItem in it.Prefab.DeconstructItems)
{
if (!deconstructItem.IsValidDeconstructor(item)) { continue; }
+ float percentageHealth = it.Condition / it.MaxCondition;
+ if (percentageHealth < deconstructItem.MinCondition || percentageHealth > deconstructItem.MaxCondition) { continue; }
RegisterItem(deconstructItem.ItemIdentifier, deconstructItem.Amount);
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs
index 3b25c87b0..c0b717496 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/MiniMap.cs
@@ -1119,7 +1119,7 @@ namespace Barotrauma.Items.Components
if (it.GetComponent() is { } battery)
{
- int batteryCapacity = (int)(battery.Charge / battery.Capacity * 100f);
+ int batteryCapacity = (int)(battery.Charge / battery.GetCapacity() * 100f);
line2 = TextManager.GetWithVariable("statusmonitor.battery.tooltip", "[amount]", batteryCapacity.ToString());
}
else if (it.GetComponent() is { } powerTransfer)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
index 188c75cf9..b6ff975de 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Sonar.cs
@@ -1028,7 +1028,7 @@ namespace Barotrauma.Items.Components
{
foreach (var c in MineralClusters)
{
- var unobtainedMinerals = c.resources.Where(i => i != null && i.GetRootInventoryOwner() == i);
+ var unobtainedMinerals = c.resources.Where(i => i != null && i.GetComponent() is { Attached: true });
if (unobtainedMinerals.None()) { continue; }
if (!CheckResourceMarkerVisibility(c.center, transducerCenter)) { continue; }
var i = unobtainedMinerals.FirstOrDefault();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
index 7f06e5dbf..1eff3054d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Machines/Steering.cs
@@ -390,7 +390,7 @@ namespace Barotrauma.Items.Components
!ActiveDockingSource.Docked && DockingTarget?.Item?.Submarine == Level.Loaded.StartOutpost && (DockingTarget?.Item?.Submarine?.Info.IsOutpost ?? false))
{
// Docking to an outpost
- var subsToLeaveBehind = campaign.GetSubsToLeaveBehind(Item.Submarine);
+ var subsToLeaveBehind = CampaignMode.GetSubsToLeaveBehind(Item.Submarine);
if (subsToLeaveBehind.Any())
{
enterOutpostPrompt = new GUIMessageBox(
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs
index 72070c051..c3f44c01d 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Power/PowerContainer.cs
@@ -133,35 +133,43 @@ namespace Barotrauma.Items.Components
public void Draw(SpriteBatch spriteBatch, bool editing = false, float itemDepth = -1)
{
- if (indicatorSize.X <= 1.0f || indicatorSize.Y <= 1.0f) { return; }
+ Vector2 scaledIndicatorSize = indicatorSize * item.Scale;
+ if (scaledIndicatorSize.X <= 2.0f || scaledIndicatorSize.Y <= 2.0f) { return; }
+ const float outlineThickness = 1.0f;
Vector2 itemSize = new Vector2(item.Sprite.SourceRect.Width, item.Sprite.SourceRect.Height) * item.Scale;
- Vector2 indicatorPos = -itemSize / 2 + indicatorPosition * item.Scale;
- if (item.FlippedX && item.Prefab.CanSpriteFlipX) { indicatorPos.X = -indicatorPos.X - indicatorSize.X * item.Scale; }
- if (item.FlippedY && item.Prefab.CanSpriteFlipY) { indicatorPos.Y = -indicatorPos.Y - indicatorSize.Y * item.Scale; }
+ Vector2 indicatorPos = -itemSize / 2.0f + indicatorPosition * item.Scale;
+ Vector2 itemPosition = new Vector2(item.DrawPosition.X, -item.DrawPosition.Y);
+ Vector2 flip = new Vector2(item.FlippedX && item.Prefab.CanSpriteFlipX ? -1.0f : 1.0f, item.FlippedY && item.Prefab.CanSpriteFlipY ? -1.0f : 1.0f);
+ Matrix rotate = Matrix.CreateRotationZ(item.RotationRad);
+ Vector2 center = Vector2.Transform((indicatorPos + (scaledIndicatorSize * 0.5f)) * flip, rotate) + itemPosition;
if (charge > 0 && capacity > 0)
{
float chargeRatio = MathHelper.Clamp(charge / capacity, 0.0f, 1.0f);
Color indicatorColor = ToolBox.GradientLerp(chargeRatio, Color.Red, Color.Orange, Color.Green);
- if (!isHorizontal)
+ Vector2 indicatorCenter = (indicatorPos + (scaledIndicatorSize * 0.5f)) * flip;
+ Vector2 indicatorSize;
+
+ if (isHorizontal)
{
- GUI.DrawRectangle(spriteBatch,
- new Vector2(item.DrawPosition.X, -item.DrawPosition.Y + ((indicatorSize.Y * item.Scale) * (1.0f - chargeRatio))) + indicatorPos,
- new Vector2(indicatorSize.X * item.Scale, (indicatorSize.Y * item.Scale) * chargeRatio), indicatorColor, true,
- depth: item.SpriteDepth - 0.00001f);
+ float indicatorLength = (scaledIndicatorSize.X - outlineThickness * 2.0f) * chargeRatio;
+ indicatorCenter.X += -scaledIndicatorSize.X * 0.5f + (flipIndicator ? scaledIndicatorSize.X - outlineThickness - indicatorLength * 0.5f : outlineThickness + indicatorLength * 0.5f);
+ indicatorSize = new Vector2(indicatorLength, scaledIndicatorSize.Y);
}
else
{
- GUI.DrawRectangle(spriteBatch,
- new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
- new Vector2((indicatorSize.X * item.Scale) * chargeRatio, indicatorSize.Y * item.Scale), indicatorColor, true,
- depth: item.SpriteDepth - 0.00001f);
+ float indicatorLength = (scaledIndicatorSize.Y - outlineThickness * 2.0f) * chargeRatio;
+ indicatorCenter.Y += -scaledIndicatorSize.Y * 0.5f + (flipIndicator ? outlineThickness + indicatorLength * 0.5f : scaledIndicatorSize.Y - outlineThickness - indicatorLength * 0.5f);
+ indicatorSize = new Vector2(scaledIndicatorSize.X, indicatorLength);
}
+
+ indicatorCenter = Vector2.Transform(indicatorCenter, rotate) + itemPosition;
+
+ GUI.DrawFilledRectangle(spriteBatch, indicatorCenter, indicatorSize, indicatorSize * 0.5f, item.RotationRad, indicatorColor, item.SpriteDepth - 0.00001f);
}
- GUI.DrawRectangle(spriteBatch,
- new Vector2(item.DrawPosition.X, -item.DrawPosition.Y) + indicatorPos,
- indicatorSize * item.Scale, Color.Black, depth: item.SpriteDepth - 0.000015f);
+
+ GUI.DrawRectangle(spriteBatch, center, scaledIndicatorSize, scaledIndicatorSize * 0.5f, item.RotationRad, Color.Black, item.SpriteDepth - 0.000015f, outlineThickness, GUI.OutlinePosition.Inside);
}
public void ClientEventWrite(IWriteMessage msg, NetEntityEvent.IData extraData)
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs
index 03e4e2eff..a5395e8e0 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Components/Turret.cs
@@ -581,7 +581,7 @@ namespace Barotrauma.Items.Components
var battery = recipient.Item?.GetComponent();
if (battery == null || battery.Item.Condition <= 0.0f) { continue; }
availableCharge += battery.Charge;
- availableCapacity += battery.Capacity;
+ availableCapacity += battery.GetCapacity();
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
index 0c35c29eb..05513e00a 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/Item.cs
@@ -1415,6 +1415,15 @@ namespace Barotrauma
case EventType.ChangeProperty:
ReadPropertyChange(msg, false);
break;
+ case EventType.ItemStat:
+ byte length = msg.ReadByte();
+ for (int i = 0; i < length; i++)
+ {
+ var statIdentifier = INetSerializableStruct.Read(msg);
+ var statValue = msg.ReadSingle();
+ StatManager.ApplyStat(statIdentifier, statValue);
+ }
+ break;
case EventType.Upgrade:
Identifier identifier = msg.ReadIdentifier();
byte level = msg.ReadByte();
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs
index 8c6aed81b..bc69b2e51 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Items/ItemPrefab.cs
@@ -208,6 +208,13 @@ namespace Barotrauma
DecorativeSpriteGroups = decorativeSpriteGroups.Select(kvp => (kvp.Key, kvp.Value.ToImmutableArray())).ToImmutableDictionary();
}
+ public bool CanCharacterBuy()
+ {
+ if (!DefaultPrice.RequiresUnlock) { return true; }
+
+ return Character.Controlled is not null && Character.Controlled.HasStoreAccessForItem(this);
+ }
+
public override void UpdatePlacing(Camera cam)
{
Vector2 position = Submarine.MouseToWorldGrid(cam, Submarine.MainSub);
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs
index 278e3a36c..c32f82187 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Map/Map/Map.cs
@@ -162,23 +162,27 @@ namespace Barotrauma
RemoveFogOfWar(StartLocation);
- GenerateLocationConnectionVisuals();
+ GenerateAllLocationConnectionVisuals();
}
- partial void GenerateLocationConnectionVisuals()
+ partial void GenerateAllLocationConnectionVisuals()
{
foreach (LocationConnection connection in Connections)
{
- Vector2 connectionStart = connection.Locations[0].MapPosition;
- Vector2 connectionEnd = connection.Locations[1].MapPosition;
- float connectionLength = Vector2.Distance(connectionStart, connectionEnd);
- int iterations = Math.Min((int)Math.Sqrt(connectionLength * generationParams.ConnectionIndicatorIterationMultiplier), 5);
- connection.CrackSegments.Clear();
- connection.CrackSegments.AddRange(MathUtils.GenerateJaggedLine(
- connectionStart, connectionEnd,
- iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier));
+ GenerateLocationConnectionVisuals(connection);
}
}
+ partial void GenerateLocationConnectionVisuals(LocationConnection connection)
+ {
+ Vector2 connectionStart = connection.Locations[0].MapPosition;
+ Vector2 connectionEnd = connection.Locations[1].MapPosition;
+ float connectionLength = Vector2.Distance(connectionStart, connectionEnd);
+ int iterations = Math.Min((int)Math.Sqrt(connectionLength * generationParams.ConnectionIndicatorIterationMultiplier), 5);
+ connection.CrackSegments.Clear();
+ connection.CrackSegments.AddRange(MathUtils.GenerateJaggedLine(
+ connectionStart, connectionEnd,
+ iterations, connectionLength * generationParams.ConnectionIndicatorDisplacementMultiplier));
+ }
private void LocationChanged(Location prevLocation, Location newLocation)
{
@@ -414,7 +418,7 @@ namespace Barotrauma
new GUIMessageBox(string.Empty, TextManager.Get("LockedPathTooltip"));
}
//clients aren't allowed to select the location without a permission
- else if ((GameMain.GameSession?.GameMode as CampaignMode)?.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap) ?? false)
+ else if (CampaignMode.AllowedToManageCampaign(Networking.ClientPermissions.ManageMap))
{
connectionHighlightState = 0.0f;
SelectedConnection = connection;
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs
index 4a46f3c9e..c6cce06c2 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/NetEntityEvent/ClientEntityEventManager.cs
@@ -277,16 +277,12 @@ namespace Barotrauma.Networking
public void Clear()
{
- ID = 0;
-
lastReceivedID = 0;
-
firstNewID = null;
-
- events.Clear();
eventLastSent.Clear();
-
MidRoundSyncingDone = false;
+
+ ClearSelf();
}
///
@@ -297,6 +293,10 @@ namespace Barotrauma.Networking
{
ID = 0;
events.Clear();
+ if (thisClient != null)
+ {
+ thisClient.LastSentEntityEventID = 0;
+ }
}
}
}
diff --git a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs
index fc23c4df9..56138ec83 100644
--- a/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs
+++ b/Barotrauma/BarotraumaClient/ClientSource/Networking/ServerList/PingUtils.cs
@@ -14,7 +14,7 @@ namespace Barotrauma.Networking
{
static class PingUtils
{
- private static readonly Dictionary activePings = new Dictionary();
+ private static readonly Dictionary activePings = new Dictionary();
private static bool steamPingInfoReady;
@@ -36,9 +36,9 @@ namespace Barotrauma.Networking
switch (serverInfo.Endpoint)
{
- case LidgrenEndpoint { NetEndpoint: { Address: var address } }:
+ case LidgrenEndpoint { NetEndpoint: var endPoint }:
- GetIPAddressPing(serverInfo, address, onPingDiscovered);
+ GetIPAddressPing(serverInfo, endPoint, onPingDiscovered);
break;
case SteamP2PEndpoint steamP2PEndpoint:
TaskPool.Add($"EstimateSteamLobbyPing ({steamP2PEndpoint.StringRepresentation})",
@@ -131,9 +131,9 @@ namespace Barotrauma.Networking
}
}
- private static void GetIPAddressPing(ServerInfo serverInfo, IPAddress address, Action onPingDiscovered)
+ private static void GetIPAddressPing(ServerInfo serverInfo, IPEndPoint endPoint, Action onPingDiscovered)
{
- if (IPAddress.IsLoopback(address))
+ if (IPAddress.IsLoopback(endPoint.Address))
{
serverInfo.Ping = Option.Some(0);
onPingDiscovered(serverInfo);
@@ -142,24 +142,24 @@ namespace Barotrauma.Networking
{
lock (activePings)
{
- if (activePings.ContainsKey(address)) { return; }
- activePings.Add(address, activePings.Any() ? activePings.Values.Max() + 1 : 0);
+ if (activePings.ContainsKey(endPoint)) { return; }
+ activePings.Add(endPoint, activePings.Any() ? activePings.Values.Max() + 1 : 0);
}
serverInfo.Ping = Option.None();
- TaskPool.Add($"PingServerAsync ({address})", PingServerAsync(address, 1000),
+ TaskPool.Add($"PingServerAsync ({endPoint})", PingServerAsync(endPoint, 1000),
rtt =>
{
if (!rtt.TryGetResult(out serverInfo.Ping)) { serverInfo.Ping = Option.None(); }
onPingDiscovered(serverInfo);
lock (activePings)
{
- activePings.Remove(address);
+ activePings.Remove(endPoint);
}
});
}
}
- private static async Task