Faction Test v1.0.1.0

This commit is contained in:
Regalis11
2023-02-16 15:01:28 +02:00
parent caa5a2f762
commit 2c5a7923b0
309 changed files with 7502 additions and 4335 deletions
@@ -21,7 +21,7 @@ namespace Barotrauma
for (int i = 0; i < Submarine.MainSubs.Length; i++)
{
var sub = Submarine.MainSubs[i];
if (sub == null || sub.Info.InitialSuppliesSpawned || !sub.Info.IsPlayer) { continue; }
if (sub == null || sub.Info.InitialSuppliesSpawned || sub.Info.IsManuallyOutfitted || !sub.Info.IsPlayer) { continue; }
//1st pass: items defined in the start item set, only spawned in the main sub (not drones/shuttles or other linked subs)
SpawnStartItems(sub, startItemSet);
//2nd pass: items defined using preferred containers, spawned in the main sub and all the linked subs (drones, shuttles etc)
@@ -8,6 +8,7 @@ using System.Linq;
using System.Text;
using System.Xml.Linq;
using Barotrauma.Networking;
using System.Collections;
#if SERVER
using Barotrauma.Networking;
#endif
@@ -471,6 +472,21 @@ namespace Barotrauma
return true;
}
public static IEnumerable<Hull> FindCargoRooms(IEnumerable<Submarine> subs) => subs.SelectMany(s => FindCargoRooms(s));
public static IEnumerable<Hull> FindCargoRooms(Submarine sub) => WayPoint.WayPointList
.Where(wp => wp.Submarine == sub && wp.SpawnType == SpawnType.Cargo)
.Select(wp => wp.CurrentHull)
.Distinct();
public static IEnumerable<Item> FilterCargoCrates(IEnumerable<Item> items, Func<Item, bool> conditional = null)
=> items.Where(it => it.HasTag("crate") && !it.NonInteractable && !it.NonPlayerTeamInteractable && !it.HiddenInGame && !it.Removed && (conditional == null || conditional(it)));
public static IEnumerable<ItemContainer> FindReusableCargoContainers(IEnumerable<Submarine> subs, IEnumerable<Hull> cargoRooms = null) =>
FilterCargoCrates(Item.ItemList, it => subs.Contains(it.Submarine) && (cargoRooms == null || cargoRooms.Contains(it.CurrentHull)))
.Select(it => it.GetComponent<ItemContainer>())
.Where(c => c != null);
public static ItemContainer GetOrCreateCargoContainerFor(ItemPrefab item, ISpatialEntity cargoRoomOrSpawnPoint, ref List<ItemContainer> availableContainers)
{
ItemContainer itemContainer = null;
@@ -553,8 +569,8 @@ namespace Barotrauma
}
#endif
}
List<ItemContainer> availableContainers = new List<ItemContainer>();
var connectedSubs = sub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player);
List<ItemContainer> availableContainers = FindReusableCargoContainers(connectedSubs, FindCargoRooms(connectedSubs)).ToList();
foreach (PurchasedItem pi in itemsToSpawn)
{
Vector2 position = GetCargoPos(cargoRoom, pi.ItemPrefab);
@@ -248,11 +248,27 @@ namespace Barotrauma
List<WayPoint> spawnWaypoints = null;
List<WayPoint> mainSubWaypoints = WayPoint.SelectCrewSpawnPoints(characterInfos, Submarine.MainSub).ToList();
if (Level.IsLoadedOutpost && Submarine.Loaded.Any(s => s.Info.Type == SubmarineType.Outpost && (s.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false)))
bool hostileOutpost = false;
if (Level.IsLoadedOutpost)
{
spawnWaypoints = WayPoint.WayPointList.FindAll(wp =>
if (Submarine.Loaded.Any(s => s.Info.Type == SubmarineType.Outpost && (s.Info.OutpostGenerationParams?.SpawnCrewInsideOutpost ?? false)))
{
hostileOutpost = true;
}
else if (GameMain.GameSession?.GameMode is CampaignMode campaign)
{
var reputation = campaign.Map?.CurrentLocation?.Reputation;
if (reputation != null && reputation.NormalizedValue < Reputation.HostileThreshold)
{
hostileOutpost = true;
}
}
}
if (hostileOutpost)
{
spawnWaypoints = WayPoint.WayPointList.FindAll(wp =>
wp.SpawnType == SpawnType.Human &&
wp.Submarine == Level.Loaded.StartOutpost &&
wp.Submarine == Level.Loaded.StartOutpost &&
wp.CurrentHull != null &&
wp.CurrentHull.OutpostModuleTags.Contains("airlock".ToIdentifier()));
while (spawnWaypoints.Count > characterInfos.Count)
@@ -264,7 +280,6 @@ namespace Barotrauma
spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]);
}
}
if (spawnWaypoints == null || !spawnWaypoints.Any())
{
spawnWaypoints = mainSubWaypoints;
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
namespace Barotrauma
{
@@ -27,28 +28,20 @@ namespace Barotrauma
/// Get what kind of affiliation this faction has towards the player depending on who they chose to side with via talents
/// </summary>
/// <returns></returns>
public static FactionAffiliation GetPlayerAffiliationStatus(Faction faction, ImmutableHashSet<Character>? characterList = null)
public static FactionAffiliation GetPlayerAffiliationStatus(Faction faction)
{
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return FactionAffiliation.Neutral; }
characterList ??= GameSession.GetSessionCrewCharacters(CharacterType.Both);
foreach (Character character in characterList)
bool isHighest = true;
foreach (Faction otherFaction in factions)
{
if (character.Info is not { } info) { continue; }
if (otherFaction == faction || otherFaction.Reputation.Value < faction.Reputation.Value) { continue; }
foreach (Faction otherFaction in factions)
{
Identifier factionIdentifier = otherFaction.Prefab.Identifier;
if (info.GetSavedStatValue(StatTypes.Affiliation, factionIdentifier) > 0f)
{
return factionIdentifier == faction.Prefab.Identifier
? FactionAffiliation.Positive
: FactionAffiliation.Negative;
}
}
isHighest = false;
break;
}
return FactionAffiliation.Neutral;
return isHighest ? FactionAffiliation.Positive : FactionAffiliation.Negative;
}
public override string ToString()
@@ -88,6 +81,8 @@ namespace Barotrauma
public readonly LevelData.LevelType LevelType;
public readonly float MinReputation, MaxReputation;
public readonly float MinProbability, MaxProbability;
public readonly int MaxDistanceFromFactionOutpost;
public readonly bool DisallowBetweenOtherFactionOutposts;
public AutomaticMission(ContentXElement element, string parentDebugName)
{
@@ -102,6 +97,8 @@ namespace Barotrauma
float probability = element.GetAttributeFloat("probability", 0.0f);
MinProbability = element.GetAttributeFloat("minprobability", probability);
MaxProbability = element.GetAttributeFloat("maxprobability", probability);
MaxDistanceFromFactionOutpost = element.GetAttributeInt("maxdistance", int.MaxValue);
DisallowBetweenOtherFactionOutposts = element.GetAttributeBool(nameof(DisallowBetweenOtherFactionOutposts), false);
}
}
@@ -58,7 +58,7 @@ namespace Barotrauma
Value = newReputation;
}
public void AddReputation(float reputationChange)
public float GetReputationChangeMultiplier(float reputationChange)
{
if (reputationChange > 0f)
{
@@ -68,7 +68,7 @@ namespace Barotrauma
reputationGainMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationGainMultiplier, includeSaved: false);
reputationGainMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationGainMultiplier, Identifier) ?? 0;
}
reputationChange *= reputationGainMultiplier;
return reputationGainMultiplier;
}
else if (reputationChange < 0f)
{
@@ -78,9 +78,14 @@ namespace Barotrauma
reputationLossMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationLossMultiplier, includeSaved: false);
reputationLossMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationLossMultiplier, Identifier) ?? 0;
}
reputationChange *= reputationLossMultiplier;
return reputationLossMultiplier;
}
Value += reputationChange;
return 1.0f;
}
public void AddReputation(float reputationChange)
{
Value += reputationChange * GetReputationChangeMultiplier(reputationChange);
}
public readonly NamedEvent<Reputation> OnReputationValueChanged = new NamedEvent<Reputation>();
@@ -69,7 +69,7 @@ namespace Barotrauma
public Option<int> RewardDistributionChanged;
public Option<int> BalanceChanged;
public WalletChangedData MergeInto(WalletChangedData other)
public readonly WalletChangedData MergeInto(WalletChangedData other)
{
other.BalanceChanged = AddOptionalInt(other.BalanceChanged, BalanceChanged);
other.RewardDistributionChanged = AddOptionalInt(other.RewardDistributionChanged, RewardDistributionChanged);
@@ -80,32 +80,20 @@ namespace Barotrauma
static Option<int> AddOptionalInt(Option<int> a, Option<int> b)
{
return a switch
{
Some<int> some1 => b switch
{
Some<int> some2 => Option<int>.Some(some1.Value + some2.Value),
None<int> _ => Option<int>.Some(some1.Value),
_ => throw new ArgumentOutOfRangeException(nameof(b))
},
None<int> _ => b switch
{
Some<int> some1 => Option<int>.Some(some1.Value),
None<int> _ => Option<int>.None(),
_ => throw new ArgumentOutOfRangeException(nameof(b))
},
_ => throw new ArgumentOutOfRangeException(nameof(a))
};
bool hasValue1 = a.TryUnwrap(out var value1);
bool hasValue2 = b.TryUnwrap(out var value2);
return hasValue1
? hasValue2
? Option.Some(value1 + value2)
: Option.Some(value1)
: hasValue2
? Option.Some(value2)
: Option.None;
}
static Option<int> TurnToNoneIfZero(Option<int> option)
{
return option switch
{
Some<int> s => s.Value == 0 ? Option<int>.None() : option,
None<int> _ => option,
_ => throw new ArgumentOutOfRangeException(nameof(option))
};
return option.Bind(i => i == 0 ? Option.None : Option.Some(i));
}
}
}
@@ -223,12 +211,8 @@ namespace Barotrauma
};
}
public string GetOwnerLogName() => Owner switch
{
Some<Character> { Value: var character } => character.Name,
None<Character> _ => "the bank",
_ => throw new ArgumentOutOfRangeException(nameof(Owner))
};
public string GetOwnerLogName()
=> Owner.TryUnwrap(out var character) ? character.Name : "the bank";
partial void SettingsChanged(Option<int> balanceChanged, Option<int> rewardChanged);
@@ -5,6 +5,7 @@ using FarseerPhysics;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
@@ -13,13 +14,11 @@ namespace Barotrauma
abstract partial class CampaignMode : GameMode
{
[NetworkSerialize]
public struct SaveInfo : INetSerializableStruct
{
public string FilePath;
public int SaveTime;
public string SubmarineName;
public string[] EnabledContentPackageNames;
}
public readonly record struct SaveInfo(
string FilePath,
Option<SerializableDateTime> SaveTime,
string SubmarineName,
ImmutableArray<string> EnabledContentPackageNames) : INetSerializableStruct;
public const int MaxMoney = int.MaxValue / 2; //about 1 billion
public const int InitialMoney = 8500;
@@ -84,9 +83,9 @@ namespace Barotrauma
public bool CheatsEnabled;
public const float HullRepairCostPerDamage = 0.5f, ItemRepairCostPerRepairDuration = 1.0f;
public const float HullRepairCostPerDamage = 0.1f, ItemRepairCostPerRepairDuration = 1.0f;
public const int ShuttleReplaceCost = 1000;
public const int MaxHullRepairCost = 2000, MaxItemRepairCost = 2000;
public const int MaxHullRepairCost = 600, MaxItemRepairCost = 2000;
protected bool wasDocked;
@@ -141,10 +140,19 @@ namespace Barotrauma
private static bool AnyOneAllowedToManageCampaign(ClientPermissions permissions)
{
if (GameMain.NetworkMember == null) { return true; }
//allow managing if no-one with permissions is alive
return
GameMain.NetworkMember.ConnectedClients.Count == 1 ||
GameMain.NetworkMember.ConnectedClients.None(c => c.InGame && c.Character is { IsIncapacitated: false, IsDead: false } && (IsOwner(c) || c.HasPermission(permissions)));
if (GameMain.NetworkMember.ConnectedClients.Count == 1) { return true; }
if (GameMain.NetworkMember.GameStarted)
{
//allow managing if no-one with permissions is alive and in-game
return GameMain.NetworkMember.ConnectedClients.None(c =>
c.InGame && c.Character is { IsIncapacitated: false, IsDead: false } &&
(IsOwner(c) || c.HasPermission(permissions)));
}
else
{
return GameMain.NetworkMember.ConnectedClients.None(c => IsOwner(c) || c.HasPermission(permissions));
}
}
protected CampaignMode(GameModePreset preset, CampaignSettings settings)
@@ -163,21 +171,20 @@ namespace Barotrauma
#if CLIENT
OnMoneyChanged.RegisterOverwriteExisting(new Identifier("CampaignMoneyChangeNotification"), e =>
{
if (!(e.ChangedData.BalanceChanged is Some<int> { Value: var changed })) { return; }
if (!e.ChangedData.BalanceChanged.TryUnwrap(out var changed)) { return; }
if (changed == 0) { return; }
bool isGain = changed > 0;
Color clr = isGain ? GUIStyle.Yellow : GUIStyle.Red;
switch (e.Owner)
if (e.Owner.TryUnwrap(out var owner))
{
case Some<Character> { Value: var owner}:
owner.AddMessage(FormatMessage(), clr, playSound: Character.Controlled == owner, messageIdentifier, changed);
break;
case None<Character> _ when IsSinglePlayer:
Character.Controlled?.AddMessage(FormatMessage(), clr, playSound: true, messageIdentifier, changed);
break;
owner.AddMessage(FormatMessage(), clr, playSound: Character.Controlled == owner, messageIdentifier, changed);
}
else if (IsSinglePlayer)
{
Character.Controlled?.AddMessage(FormatMessage(), clr, playSound: true, messageIdentifier, changed);
}
string FormatMessage() => TextManager.GetWithVariable(isGain ? "moneygainformat" : "moneyloseformat", "[money]", TextManager.FormatCurrency(Math.Abs(changed))).ToString();
@@ -408,14 +415,27 @@ namespace Barotrauma
}
foreach (Faction faction in factions.OrderBy(f => f.Prefab.MenuOrder))
{
if (currentLocation.Faction != faction && currentLocation.SecondaryFaction != faction &&
map.SelectedLocation?.Faction != faction && map.SelectedLocation?.SecondaryFaction != faction)
{
continue;
}
foreach (var automaticMission in faction.Prefab.AutomaticMissions)
{
if (faction.Reputation.Value < automaticMission.MinReputation || faction.Reputation.Value > automaticMission.MaxReputation) { continue; }
if (automaticMission.DisallowBetweenOtherFactionOutposts && levelData.Type == LevelData.LevelType.LocationConnection)
{
if (Map.SelectedConnection.Locations.All(l => l.Faction != null && l.Faction != faction))
{
continue;
}
}
if (automaticMission.MaxDistanceFromFactionOutpost < int.MaxValue)
{
if (!Map.LocationOrConnectionWithinDistance(
currentLocation,
automaticMission.MaxDistanceFromFactionOutpost,
loc => loc.Faction == faction))
{
continue;
}
}
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed + TotalPassedLevels));
if (levelData.Type != automaticMission.LevelType) { continue; }
float probability =
@@ -1012,11 +1032,14 @@ namespace Barotrauma
public void AssignNPCMenuInteraction(Character character, InteractionType interactionType)
{
character.CampaignInteractionType = interactionType;
if (character.CampaignInteractionType == InteractionType.Store &&
character.HumanPrefab is { Identifier: var merchantId })
{
character.MerchantIdentifier = merchantId;
map.CurrentLocation?.GetStore(merchantId)?.SetMerchantFaction(character.Faction);
}
character.DisableHealthWindow =
interactionType != InteractionType.None &&
interactionType != InteractionType.Examine &&
@@ -1129,7 +1152,7 @@ namespace Barotrauma
if (npc.TeamID != CharacterTeamType.FriendlyNPC) { return; }
if (!attacker.IsRemotePlayer && attacker != Character.Controlled) { return; }
if (npc.HumanPrefab?.Faction != null && Factions.FirstOrDefault(f => f.Prefab.Identifier == npc.HumanPrefab.Faction) is Faction faction)
if (npc.Faction != null && Factions.FirstOrDefault(f => f.Prefab.Identifier == npc.Faction) is Faction faction)
{
faction.Reputation?.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
}
@@ -1143,6 +1166,11 @@ namespace Barotrauma
}
}
public Faction GetFaction(Identifier identifier)
{
return factions.Find(f => f.Prefab.Identifier == identifier);
}
public float GetReputation(Identifier factionIdentifier)
{
var faction =
@@ -1152,6 +1180,12 @@ namespace Barotrauma
return faction?.Reputation?.Value ?? 0.0f;
}
public FactionAffiliation GetFactionAffiliation(Identifier factionIdentifier)
{
var faction = GetFaction(factionIdentifier);
return Faction.GetPlayerAffiliationStatus(faction);
}
public abstract void Save(XElement element);
protected void LoadStats(XElement element)
@@ -1267,7 +1301,7 @@ namespace Barotrauma
var itemsToTransfer = new List<(Item item, Item container)>();
if (PendingSubmarineSwitch != null)
{
var connectedSubs = currentSub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player).ToHashSet();
var connectedSubs = currentSub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player);
// Remove items from the old sub
foreach (Item item in Item.ItemList)
{
@@ -1283,7 +1317,6 @@ namespace Barotrauma
if (item.Components.None(c => c is Pickable)) { continue; }
if (item.Components.Any(c => c is Pickable p && p.IsAttached)) { continue; }
if (item.Components.Any(c => c is Wire w && w.Connections.Any(c => c != null))) { continue; }
if (item.Container?.GetComponent<ItemContainer>() is { DrawInventory: false }) { continue; }
itemsToTransfer.Add((item, item.Container));
item.Submarine = null;
}
@@ -1303,15 +1336,29 @@ namespace Barotrauma
{
// Load the new sub
var newSub = new Submarine(PendingSubmarineSwitch);
var connectedSubs = newSub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player).ToHashSet();
// Move the transferred items
List<ItemContainer> availableContainers = Item.ItemList
.Where(it => connectedSubs.Contains(it.Submarine) && it.HasTag("crate") && !it.NonInteractable && !it.NonPlayerTeamInteractable && !it.HiddenInGame && !it.Removed)
.Select(it => it.GetComponent<ItemContainer>())
.Where(c => c != null)
.ToList();
var connectedSubs = newSub.GetConnectedSubs().Where(s => s.Info.Type == SubmarineType.Player);
WayPoint wp = WayPoint.WayPointList.FirstOrDefault(wp => wp.SpawnType == SpawnType.Cargo && connectedSubs.Contains(wp.Submarine));
Hull spawnHull = wp?.CurrentHull ?? Hull.HullList.FirstOrDefault(h => connectedSubs.Contains(h.Submarine) && !h.IsWetRoom);
if (spawnHull == null)
{
DebugConsole.AddWarning($"Failed to transfer items between subs. No cargo waypoint or dry hulls found in the new sub.");
return;
}
// First move the cargo containers, so that we can reuse them
var cargoContainers = itemsToTransfer.Where(it => it.item.HasTag("crate"));
foreach (var (item, oldContainer) in cargoContainers)
{
Vector2 simPos = ConvertUnits.ToSimUnits(CargoManager.GetCargoPos(spawnHull, item.Prefab));
item.SetTransform(simPos, 0.0f, findNewHull: false, setPrevTransform: false);
item.CurrentHull = spawnHull;
item.Submarine = spawnHull.Submarine;
}
// Then move the other items
var cargoRooms = CargoManager.FindCargoRooms(newSub);
List<ItemContainer> availableContainers = CargoManager.FindReusableCargoContainers(connectedSubs).ToList();
foreach (var (item, oldContainer) in itemsToTransfer)
{
if (cargoContainers.Contains((item, oldContainer))) { continue; }
Item newContainer = null;
item.Submarine = newSub;
if (item.Container == null)
@@ -1320,25 +1367,16 @@ namespace Barotrauma
}
if (item.Container == null && (newContainer == null || !newContainer.OwnInventory.TryPutItem(item, user: null, createNetworkEvent: false)))
{
WayPoint wp = WayPoint.GetRandom(SpawnType.Cargo, null, newSub);
Hull spawnHull = wp?.CurrentHull ?? Hull.HullList.Where(h => h.Submarine == newSub && !h.IsWetRoom).GetRandomUnsynced();
if (spawnHull == null)
var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers);
if (cargoContainer == null || !cargoContainer.Inventory.TryPutItem(item, user: null, createNetworkEvent: false))
{
DebugConsole.AddWarning($"Failed to transfer items between subs. No cargo waypoint or dry hulls found in the new sub.");
return;
Vector2 simPos = ConvertUnits.ToSimUnits(CargoManager.GetCargoPos(spawnHull, item.Prefab));
item.SetTransform(simPos, 0.0f, findNewHull: false, setPrevTransform: false);
}
if (spawnHull != null)
else if (cargoContainer.Item.Submarine is Submarine containerSub)
{
var cargoContainer = CargoManager.GetOrCreateCargoContainerFor(item.Prefab, spawnHull, ref availableContainers);
if (cargoContainer == null || !cargoContainer.Inventory.TryPutItem(item, user: null, createNetworkEvent: false))
{
Vector2 simPos = ConvertUnits.ToSimUnits(CargoManager.GetCargoPos(spawnHull, item.Prefab));
item.SetTransform(simPos, 0.0f, findNewHull: false, setPrevTransform: false);
}
}
else
{
DebugConsole.AddWarning($"Failed to transfer item {item.Prefab.Identifier} ({item.ID}), because no cargo spawn point could be found!");
// Use the item's sub in case the sub consists of multiple linked subs.
item.Submarine = containerSub;
}
}
string newContainerName = newContainer == null ? "(null)" : $"{newContainer.Prefab.Identifier} ({newContainer.Tags})";
@@ -11,11 +11,14 @@ namespace Barotrauma
{
public static CampaignSettings Empty => new CampaignSettings(element: null);
#if CLIENT
public static CampaignSettings CurrentSettings = new CampaignSettings(GameSettings.CurrentConfig.SavedCampaignSettings);
#endif
public string Name => "CampaignSettings";
public const string LowerCaseSaveElementName = "campaignsettings";
[Serialize("", IsPropertySaveable.Yes)]
[Serialize("Normal", IsPropertySaveable.Yes)]
public string PresetName { get; set; } = string.Empty;
[Serialize(true, IsPropertySaveable.Yes)]
@@ -53,7 +56,6 @@ namespace Barotrauma
return definition.GetInt(StartingBalanceAmount.ToIdentifier());
}
return 8000;
}
}
@@ -65,7 +67,7 @@ namespace Barotrauma
{
return definition.GetFloat(Difficulty.ToIdentifier());
}
return 0;
return 0;
}
}
@@ -82,7 +84,7 @@ namespace Barotrauma
}
public const int DefaultMaxMissionCount = 2;
public const int MaxMissionCountLimit = 3;
public const int MaxMissionCountLimit = 10;
public const int MinMissionCountLimit = 1;
public Dictionary<Identifier, SerializableProperty> SerializableProperties { get; private set; }
@@ -46,15 +46,15 @@ namespace Barotrauma
public static void Init()
{
#if CLIENT
Tutorial = new GameModePreset("tutorial".ToIdentifier(), typeof(TutorialMode), true);
DevSandbox = new GameModePreset("devsandbox".ToIdentifier(), typeof(GameMode), true);
SinglePlayerCampaign = new GameModePreset("singleplayercampaign".ToIdentifier(), typeof(SinglePlayerCampaign), true);
TestMode = new GameModePreset("testmode".ToIdentifier(), typeof(TestGameMode), true);
Tutorial = new GameModePreset("tutorial".ToIdentifier(), typeof(TutorialMode), isSinglePlayer: true);
DevSandbox = new GameModePreset("devsandbox".ToIdentifier(), typeof(GameMode), isSinglePlayer: true);
SinglePlayerCampaign = new GameModePreset("singleplayercampaign".ToIdentifier(), typeof(SinglePlayerCampaign), isSinglePlayer: true);
TestMode = new GameModePreset("testmode".ToIdentifier(), typeof(TestGameMode), isSinglePlayer: true);
#endif
Sandbox = new GameModePreset("sandbox".ToIdentifier(), typeof(GameMode), false);
Mission = new GameModePreset("mission".ToIdentifier(), typeof(CoOpMode), false);
PvP = new GameModePreset("pvp".ToIdentifier(), typeof(PvPMode), false);
MultiPlayerCampaign = new GameModePreset("multiplayercampaign".ToIdentifier(), typeof(MultiPlayerCampaign), false, false);
Sandbox = new GameModePreset("sandbox".ToIdentifier(), typeof(GameMode), isSinglePlayer: false);
Mission = new GameModePreset("mission".ToIdentifier(), typeof(CoOpMode), isSinglePlayer: false);
PvP = new GameModePreset("pvp".ToIdentifier(), typeof(PvPMode), isSinglePlayer: false);
MultiPlayerCampaign = new GameModePreset("multiplayercampaign".ToIdentifier(), typeof(MultiPlayerCampaign), isSinglePlayer: false);
}
}
}
@@ -194,7 +194,16 @@ namespace Barotrauma
}
break;
case "metadata":
var prevReputations = Factions.ToDictionary(k => k, v => v.Reputation.Value);
CampaignMetadata.Load(subElement);
foreach (var faction in Factions)
{
if (!MathUtils.NearlyEqual(prevReputations[faction], faction.Reputation.Value))
{
faction.Reputation.OnReputationValueChanged?.Invoke(faction.Reputation);
Reputation.OnAnyReputationValueChanged.Invoke(faction.Reputation);
}
}
break;
case "upgrademanager":
case "pendingupgrades":
@@ -306,7 +306,7 @@ namespace Barotrauma
/// <summary>
/// Switch to another submarine. The sub is loaded when the next round starts.
/// </summary>
public void SwitchSubmarine(SubmarineInfo newSubmarine, bool transferItems, int cost, Client? client = null)
public void SwitchSubmarine(SubmarineInfo newSubmarine, bool transferItems, Client? client = null)
{
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
{
@@ -324,11 +324,6 @@ namespace Barotrauma
}
}
}
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && cost > 0)
{
Campaign!.TryPurchase(client, cost);
}
GameAnalyticsManager.AddMoneySpentEvent(cost, GameAnalyticsManager.MoneySink.SubmarineSwitch, newSubmarine.Name);
Campaign!.PendingSubmarineSwitch = newSubmarine;
Campaign!.TransferItemsOnSubSwitch = transferItems;
}
@@ -586,9 +581,7 @@ namespace Barotrauma
StatusEffect.StopAll();
#if CLIENT
#if !DEBUG
GameMain.LightManager.LosEnabled = GameMain.Client == null || GameMain.Client.CharacterInfo != null;
#endif
GameMain.LightManager.LosEnabled = (GameMain.Client == null || GameMain.Client.CharacterInfo != null) && !GameMain.DevMode;
if (GameMain.LightManager.LosEnabled) { GameMain.LightManager.LosAlpha = 1f; }
if (GameMain.Client == null) { GameMain.LightManager.LosMode = GameSettings.CurrentConfig.Graphics.LosMode; }
#endif
@@ -652,7 +645,7 @@ namespace Barotrauma
}
}
CreatureMetrics.Instance.RecentlyEncountered.Clear();
CreatureMetrics.RecentlyEncountered.Clear();
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition;
RoundDuration = 0.0f;
@@ -905,6 +898,7 @@ namespace Barotrauma
TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
ObjectiveManager.ResetUI();
CharacterHUD.ClearBossHealthBars();
#endif
SteamAchievementManager.OnRoundEnded(this);
@@ -1123,7 +1117,10 @@ namespace Barotrauma
XDocument doc = new XDocument(new XElement("Gamesession"));
XElement rootElement = doc.Root ?? throw new NullReferenceException("Game session XML element is invalid: document is null.");
rootElement.Add(new XAttribute("savetime", ToolBox.Epoch.NowLocal));
rootElement.Add(new XAttribute("savetime", SerializableDateTime.UtcNow.ToUnixTime()));
#warning TODO: after this gets on main, replace savetime with the commented line
//rootElement.Add(new XAttribute("savetime", SerializableDateTime.LocalNow));
rootElement.Add(new XAttribute("version", GameMain.Version));
if (Submarine?.Info != null && !Submarine.Removed && Campaign != null)
{
@@ -14,6 +14,8 @@ namespace Barotrauma
public enum NetworkHeader
{
REQUEST_AFFLICTIONS,
AFFLICTION_UPDATE,
UNSUBSCRIBE_ME,
REQUEST_PENDING,
ADD_PENDING,
REMOVE_PENDING,
@@ -295,6 +297,42 @@ namespace Barotrauma
static int GetHealPrice(Affliction affliction) => (int)(affliction.Prefab.BaseHealCost + (affliction.Prefab.HealCostMultiplier * affliction.Strength));
}
public static void OnAfflictionCountChanged(Character character) =>
GameMain.GameSession?.Campaign?.MedicalClinic?.OnAfflictionCountChangedPrivate(character);
private void OnAfflictionCountChangedPrivate(Character character)
{
if (character is not { CharacterHealth: { } health, Info: { } info }) { return; }
ImmutableArray<NetAffliction> afflictions = GetAllAfflictions(health);
#if CLIENT
if (GameMain.NetworkMember is null)
{
ui?.UpdateAfflictions(new NetCrewMember(info, afflictions));
}
ui?.UpdateCrewPanel();
#elif SERVER
foreach (AfflictionSubscriber sub in afflictionSubscribers.ToList())
{
if (sub.Expiry < DateTimeOffset.Now)
{
afflictionSubscribers.Remove(sub);
continue;
}
if (sub.Target == info)
{
ServerSend(new NetCrewMember(info, afflictions),
header: NetworkHeader.AFFLICTION_UPDATE,
deliveryMethod: DeliveryMethod.Unreliable,
targetClient: sub.Subscriber);
}
}
#endif
}
public int GetTotalCost() => PendingHeals.SelectMany(static h => h.Afflictions).Aggregate(0, static (current, affliction) => current + affliction.Price);
private int GetAdjustedPrice(int price) => campaign?.Map?.CurrentLocation is { Type: { HasOutpost: true } } currentLocation ? currentLocation.GetAdjustedHealCost(price) : int.MaxValue;
@@ -330,7 +368,7 @@ namespace Barotrauma
new NetAffliction { Identifier = "internaldamage".ToIdentifier(), Strength = 80, Price = 10 },
new NetAffliction { Identifier = "blunttrauma".ToIdentifier(), Strength = 50, Price = 10 },
new NetAffliction { Identifier = "lacerations".ToIdentifier(), Strength = 20, Price = 10 },
new NetAffliction { Identifier = "burn".ToIdentifier(), Strength = 10, Price = 10 }
new NetAffliction { Identifier = AfflictionPrefab.DamageType, Strength = 10, Price = 10 }
};
#endif
}
@@ -692,11 +692,13 @@ namespace Barotrauma
/// Gets the progress that is shown on the store interface.
/// Includes values stored in the metadata and <see cref="PendingUpgrades"/>, and takes submarine tier and class restrictions into account
/// </summary>
public int GetUpgradeLevel(UpgradePrefab prefab, UpgradeCategory category)
/// <param name="info">Submarine used to determine the upgrade limit. If not defined, will default to the current sub.</param>
public int GetUpgradeLevel(UpgradePrefab prefab, UpgradeCategory category, SubmarineInfo? info = null)
{
if (!Metadata.HasKey(FormatIdentifier(prefab, category))) { return GetPendingLevel(); }
return Math.Min(GetRealUpgradeLevel(prefab, category) + GetPendingLevel(), prefab.GetMaxLevelForCurrentSub());
int maxLevel = info is null ? prefab.GetMaxLevelForCurrentSub() : prefab.GetMaxLevel(info);
return Math.Min(GetRealUpgradeLevel(prefab, category) + GetPendingLevel(), maxLevel);
int GetPendingLevel()
{
@@ -713,6 +715,14 @@ namespace Barotrauma
return !Metadata.HasKey(FormatIdentifier(prefab, category)) ? 0 : Metadata.GetInt(FormatIdentifier(prefab, category), 0);
}
/// <summary>
/// Gets the level of the upgrade that is stored in the metadata. Takes into account the limits of the provided submarine.
/// </summary>
public int GetRealUpgradeLevelForSub(UpgradePrefab prefab, UpgradeCategory category, SubmarineInfo info)
{
return Math.Min(GetRealUpgradeLevel(prefab, category), prefab.GetMaxLevel(info));
}
/// <summary>
/// Stores the target upgrade level in the campaign metadata.
/// </summary>