Build 1.1.4.0
This commit is contained in:
@@ -260,10 +260,12 @@ namespace Barotrauma
|
||||
}
|
||||
bool success = false;
|
||||
bool isCampaign = GameMain.GameSession?.GameMode is CampaignMode;
|
||||
float levelDifficulty = Level.Loaded?.Difficulty ?? 0.0f;
|
||||
foreach (PreferredContainer preferredContainer in itemPrefab.PreferredContainers)
|
||||
{
|
||||
if (preferredContainer.CampaignOnly && !isCampaign) { continue; }
|
||||
if (preferredContainer.NotCampaign && isCampaign) { continue; }
|
||||
if (levelDifficulty < preferredContainer.MinLevelDifficulty || levelDifficulty > preferredContainer.MaxLevelDifficulty) { continue; }
|
||||
if (preferredContainer.SpawnProbability <= 0.0f || preferredContainer.MaxAmount <= 0 && preferredContainer.Amount <= 0) { continue; }
|
||||
validContainers = GetValidContainers(preferredContainer, containers, validContainers, primary: true);
|
||||
if (validContainers.None())
|
||||
|
||||
@@ -8,6 +8,8 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using System.Collections;
|
||||
using System.Collections.Immutable;
|
||||
#if SERVER
|
||||
using Barotrauma.Networking;
|
||||
#endif
|
||||
@@ -414,14 +416,31 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
#endif
|
||||
return Submarine.MainSub.GetItems(true).FindAll(item =>
|
||||
return FindAllSellableItems().Where(it => IsItemSellable(it, confirmedSoldEntities));
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<Item> FindAllItemsOnPlayerAndSub(Character character)
|
||||
{
|
||||
List<Item> allItems = new();
|
||||
if (character?.Inventory is { } inv)
|
||||
{
|
||||
allItems.AddRange(inv.FindAllItems(recursive: true));
|
||||
}
|
||||
allItems.AddRange(FindAllSellableItems());
|
||||
return allItems;
|
||||
}
|
||||
|
||||
public static IEnumerable<Item> FindAllSellableItems()
|
||||
{
|
||||
if (Submarine.MainSub is null) { return Enumerable.Empty<Item>(); }
|
||||
|
||||
return Submarine.MainSub.GetItems(true).FindAll(static item =>
|
||||
{
|
||||
if (!IsItemSellable(item, confirmedSoldEntities)) { return false; }
|
||||
if (item.GetRootInventoryOwner() is Character) { return false; }
|
||||
if (!item.Components.All(c => !(c is Holdable h) || !h.Attachable || !h.Attached)) { return false; }
|
||||
if (!item.Components.All(c => !(c is Wire w) || w.Connections.All(c => c == null))) { return false; }
|
||||
if (!item.Components.All(static c => c is not Holdable { Attachable: true, Attached: true })) { return false; }
|
||||
if (!item.Components.All(static c => c is not Wire w || w.Connections.All(static c => c is null))) { return false; }
|
||||
if (!ItemAndAllContainersInteractable(item)) { return false; }
|
||||
if (item.GetRootContainer() is Item rootContainer && rootContainer.HasTag("dontsellitems")) { return false; }
|
||||
if (item.GetRootContainer() is { } rootContainer && rootContainer.HasTag("dontsellitems")) { return false; }
|
||||
return true;
|
||||
}).Distinct();
|
||||
|
||||
@@ -471,6 +490,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 +587,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,11 @@ 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)))
|
||||
if (Level.Loaded != null && Level.Loaded.ShouldSpawnCrewInsideOutpost())
|
||||
{
|
||||
spawnWaypoints = WayPoint.WayPointList.FindAll(wp =>
|
||||
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)
|
||||
@@ -262,9 +262,8 @@ namespace Barotrauma
|
||||
while (spawnWaypoints.Any() && spawnWaypoints.Count < characterInfos.Count)
|
||||
{
|
||||
spawnWaypoints.Add(spawnWaypoints[Rand.Int(spawnWaypoints.Count)]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (spawnWaypoints == null || !spawnWaypoints.Any())
|
||||
{
|
||||
spawnWaypoints = mainSubWaypoints;
|
||||
@@ -290,6 +289,16 @@ namespace Barotrauma
|
||||
else if (!character.Info.StartItemsGiven)
|
||||
{
|
||||
character.GiveJobItems(mainSubWaypoints[i]);
|
||||
foreach (Item item in character.Inventory.AllItems)
|
||||
{
|
||||
//if the character is loaded from a human prefab with preconfigured items, its ID card gets assigned to the sub it spawns in
|
||||
//we don't want that in this case, the crew's cards shouldn't be submarine-specific
|
||||
var idCard = item.GetComponent<Items.Components.IdCard>();
|
||||
if (idCard != null)
|
||||
{
|
||||
idCard.SubmarineSpecificID = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (character.Info.HealthData != null)
|
||||
{
|
||||
@@ -298,6 +307,7 @@ namespace Barotrauma
|
||||
|
||||
character.LoadTalents();
|
||||
|
||||
character.GiveIdCardTags(mainSubWaypoints[i]);
|
||||
character.GiveIdCardTags(spawnWaypoints[i]);
|
||||
character.Info.StartItemsGiven = true;
|
||||
if (character.Info.OrderData != null)
|
||||
@@ -410,20 +420,18 @@ namespace Barotrauma
|
||||
{
|
||||
List<Character> availableSpeakers = new List<Character>() { npc, player };
|
||||
List<Identifier> dialogFlags = new List<Identifier>() { "OutpostNPC".ToIdentifier(), "EnterOutpost".ToIdentifier() };
|
||||
if (npc.HumanPrefab != null)
|
||||
{
|
||||
foreach (var tag in npc.HumanPrefab.GetTags())
|
||||
{
|
||||
dialogFlags.Add(tag);
|
||||
}
|
||||
}
|
||||
if (GameMain.GameSession?.GameMode is CampaignMode campaignMode)
|
||||
{
|
||||
if (campaignMode.Map?.CurrentLocation?.Type?.Identifier == "abandoned")
|
||||
{
|
||||
if (npc.TeamID == CharacterTeamType.None)
|
||||
{
|
||||
dialogFlags.Remove("OutpostNPC".ToIdentifier());
|
||||
dialogFlags.Add("Bandit".ToIdentifier());
|
||||
}
|
||||
else if (npc.TeamID == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
dialogFlags.Remove("OutpostNPC".ToIdentifier());
|
||||
dialogFlags.Add("Hostage".ToIdentifier());
|
||||
}
|
||||
dialogFlags.Remove("OutpostNPC".ToIdentifier());
|
||||
}
|
||||
else if (campaignMode.Map?.CurrentLocation?.Reputation != null)
|
||||
{
|
||||
|
||||
@@ -8,19 +8,15 @@ namespace Barotrauma
|
||||
{
|
||||
internal partial class CampaignMetadata
|
||||
{
|
||||
public CampaignMode Campaign { get; }
|
||||
|
||||
private readonly Dictionary<Identifier, object> data = new Dictionary<Identifier, object>();
|
||||
|
||||
public CampaignMetadata(CampaignMode campaign)
|
||||
public CampaignMetadata()
|
||||
{
|
||||
Campaign = campaign;
|
||||
}
|
||||
|
||||
public CampaignMetadata(CampaignMode campaign, XElement element)
|
||||
public void Load(XElement element)
|
||||
{
|
||||
Campaign = campaign;
|
||||
|
||||
data.Clear();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
if (string.Equals(subElement.Name.ToString(), "data", StringComparison.InvariantCultureIgnoreCase))
|
||||
@@ -59,10 +55,11 @@ namespace Barotrauma
|
||||
{
|
||||
DebugConsole.Log($"Set the value \"{identifier}\" to {value}");
|
||||
|
||||
SteamAchievementManager.OnCampaignMetadataSet(identifier, value, unlockClients: true);
|
||||
|
||||
if (!data.ContainsKey(identifier))
|
||||
{
|
||||
data.Add(identifier, value);
|
||||
SteamAchievementManager.OnCampaignMetadataSet(identifier, value);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
#nullable enable
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
public enum FactionAffiliation
|
||||
{
|
||||
Affiliated,
|
||||
Neutral
|
||||
Positive,
|
||||
Neutral,
|
||||
Negative
|
||||
}
|
||||
|
||||
class Faction
|
||||
@@ -25,21 +28,25 @@ 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 FactionAffiliation GetPlayerAffiliationStatus()
|
||||
public static FactionAffiliation GetPlayerAffiliationStatus(Faction faction)
|
||||
{
|
||||
float affiliation = 1f;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
if (character.Info is not { } info) { continue; }
|
||||
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return FactionAffiliation.Neutral; }
|
||||
|
||||
affiliation *= 1f + info.GetSavedStatValue(StatTypes.Affiliation, Prefab.Identifier);
|
||||
bool isHighest = true;
|
||||
foreach (Faction otherFaction in factions)
|
||||
{
|
||||
if (otherFaction == faction || otherFaction.Reputation.Value < faction.Reputation.Value) { continue; }
|
||||
|
||||
isHighest = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return affiliation switch
|
||||
{
|
||||
>= 1f => FactionAffiliation.Affiliated,
|
||||
_ => FactionAffiliation.Neutral
|
||||
};
|
||||
return isHighest ? FactionAffiliation.Positive : FactionAffiliation.Negative;
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Prefab?.Identifier.ToString() ?? "null"})";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +59,54 @@ namespace Barotrauma
|
||||
public LocalizedString Description { get; }
|
||||
public LocalizedString ShortDescription { get; }
|
||||
|
||||
public class HireableCharacter
|
||||
{
|
||||
public readonly Identifier NPCSetIdentifier;
|
||||
public readonly Identifier NPCIdentifier;
|
||||
public readonly float MinReputation;
|
||||
|
||||
public HireableCharacter(ContentXElement element)
|
||||
{
|
||||
NPCSetIdentifier = element.GetAttributeIdentifier("from", element.GetAttributeIdentifier("npcsetidentifier", Identifier.Empty));
|
||||
NPCIdentifier = element.GetAttributeIdentifier("identifier", element.GetAttributeIdentifier("npcidentifier", Identifier.Empty));
|
||||
MinReputation = element.GetAttributeFloat("minreputation", 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<HireableCharacter> HireableCharacters;
|
||||
|
||||
public class AutomaticMission
|
||||
{
|
||||
public readonly Identifier MissionTag;
|
||||
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)
|
||||
{
|
||||
MissionTag = element.GetAttributeIdentifier("missiontag", Identifier.Empty);
|
||||
LevelType = element.GetAttributeEnum("leveltype", LevelData.LevelType.LocationConnection);
|
||||
MinReputation = element.GetAttributeFloat("minreputation", 0.0f);
|
||||
MaxReputation = element.GetAttributeFloat("maxreputation", 0.0f);
|
||||
if (MinReputation > MaxReputation)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in faction prefab \"{parentDebugName}\": MinReputation cannot be larger than MaxReputation.");
|
||||
}
|
||||
float probability = element.GetAttributeFloat("probability", 0.0f);
|
||||
MinProbability = element.GetAttributeFloat("minprobability", probability);
|
||||
MaxProbability = element.GetAttributeFloat("maxprobability", probability);
|
||||
MaxDistanceFromFactionOutpost = element.GetAttributeInt(nameof(MaxDistanceFromFactionOutpost), int.MaxValue);
|
||||
DisallowBetweenOtherFactionOutposts = element.GetAttributeBool(nameof(DisallowBetweenOtherFactionOutposts), false);
|
||||
}
|
||||
}
|
||||
|
||||
public ImmutableArray<AutomaticMission> AutomaticMissions;
|
||||
|
||||
public bool StartOutpost { get; }
|
||||
|
||||
|
||||
public int MenuOrder { get; }
|
||||
|
||||
/// <summary>
|
||||
@@ -69,38 +124,73 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public int InitialReputation { get; }
|
||||
|
||||
public float ControlledOutpostPercentage { get; }
|
||||
|
||||
public float SecondaryControlledOutpostPercentage { get; }
|
||||
|
||||
#if CLIENT
|
||||
public Sprite? Icon { get; private set; }
|
||||
|
||||
public Sprite? IconSmall { get; private set; }
|
||||
|
||||
public Sprite? BackgroundPortrait { get; private set; }
|
||||
#endif
|
||||
|
||||
public Color IconColor { get; }
|
||||
#endif
|
||||
|
||||
public FactionPrefab(ContentXElement element, FactionsFile file) : base(file, element.GetAttributeIdentifier("identifier", string.Empty))
|
||||
{
|
||||
MenuOrder = element.GetAttributeInt("menuorder", 0);
|
||||
StartOutpost = element.GetAttributeBool("startoutpost", false);
|
||||
MinReputation = element.GetAttributeInt("minreputation", -100);
|
||||
MaxReputation = element.GetAttributeInt("maxreputation", 100);
|
||||
InitialReputation = element.GetAttributeInt("initialreputation", 0);
|
||||
ControlledOutpostPercentage = element.GetAttributeFloat("controlledoutpostpercentage", 0);
|
||||
SecondaryControlledOutpostPercentage = element.GetAttributeFloat("secondarycontrolledoutpostpercentage", 0);
|
||||
Name = element.GetAttributeString("name", null) ?? TextManager.Get($"faction.{Identifier}").Fallback("Unnamed");
|
||||
Description = element.GetAttributeString("description", null) ?? TextManager.Get($"faction.{Identifier}.description").Fallback("");
|
||||
ShortDescription = element.GetAttributeString("shortdescription", null) ?? TextManager.Get($"faction.{Identifier}.shortdescription").Fallback("");
|
||||
#if CLIENT
|
||||
|
||||
List<HireableCharacter> hireableCharacters = new List<HireableCharacter>();
|
||||
List<AutomaticMission> automaticMissions = new List<AutomaticMission>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
|
||||
if (subElement.Name.ToString().Equals("icon", StringComparison.OrdinalIgnoreCase))
|
||||
var subElementId = subElement.NameAsIdentifier();
|
||||
if (subElementId == "icon")
|
||||
{
|
||||
IconColor = subElement.GetAttributeColor("color", Color.White);
|
||||
#if CLIENT
|
||||
Icon = new Sprite(subElement);
|
||||
#endif
|
||||
}
|
||||
else if (subElement.Name.ToString().Equals("portrait", StringComparison.OrdinalIgnoreCase))
|
||||
else if (subElementId == "iconsmall")
|
||||
{
|
||||
#if CLIENT
|
||||
IconSmall = new Sprite(subElement);
|
||||
#endif
|
||||
}
|
||||
else if (subElementId == "portrait")
|
||||
{
|
||||
#if CLIENT
|
||||
BackgroundPortrait = new Sprite(subElement);
|
||||
#endif
|
||||
}
|
||||
else if (subElementId == "hireable")
|
||||
{
|
||||
hireableCharacters.Add(new HireableCharacter(subElement));
|
||||
}
|
||||
else if (subElementId == "mission" || subElementId == "automaticmission")
|
||||
{
|
||||
automaticMissions.Add(new AutomaticMission(subElement, Identifier.ToString()));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
HireableCharacters = hireableCharacters.ToImmutableArray();
|
||||
AutomaticMissions = automaticMissions.ToImmutableArray();
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"{base.ToString()} ({Identifier})";
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class Reputation
|
||||
{
|
||||
public const float HostileThreshold = 0.2f;
|
||||
public const float ReputationLossPerNPCDamage = 0.1f;
|
||||
public const float ReputationLossPerStolenItemPrice = 0.01f;
|
||||
public const float ReputationLossPerWallDamage = 0.1f;
|
||||
public const float MinReputationLossPerStolenItem = 0.5f;
|
||||
public const float MaxReputationLossPerStolenItem = 10.0f;
|
||||
public const float ReputationLossPerNPCDamage = 0.025f;
|
||||
public const float ReputationLossPerWallDamage = 0.025f;
|
||||
public const float ReputationLossPerStolenItemPrice = 0.0025f;
|
||||
public const float MinReputationLossPerStolenItem = 0.025f;
|
||||
public const float MaxReputationLossPerStolenItem = 0.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Maximum amount of reputation loss you can get from damaging outpost NPCs per round
|
||||
/// </summary>
|
||||
public const float MaxReputationLossFromNPCDamage = 10.0f;
|
||||
/// <summary>
|
||||
/// Maximum amount of reputation loss you can get from damaging outpost walls per round
|
||||
/// </summary>
|
||||
public const float MaxReputationLossFromWallDamage = 10.0f;
|
||||
|
||||
public Identifier Identifier { get; }
|
||||
public int MinReputation { get; }
|
||||
@@ -19,6 +27,8 @@ namespace Barotrauma
|
||||
public int InitialReputation { get; }
|
||||
public CampaignMetadata Metadata { get; }
|
||||
|
||||
public float ReputationAtRoundStart { get; set; }
|
||||
|
||||
private readonly Identifier metaDataIdentifier;
|
||||
|
||||
/// <summary>
|
||||
@@ -59,27 +69,47 @@ namespace Barotrauma
|
||||
Value = newReputation;
|
||||
}
|
||||
|
||||
public void AddReputation(float reputationChange)
|
||||
public float GetReputationChangeMultiplier(float reputationChange)
|
||||
{
|
||||
if (reputationChange > 0f)
|
||||
{
|
||||
float reputationGainMultiplier = 1f;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier);
|
||||
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)
|
||||
{
|
||||
float reputationLossMultiplier = 1f;
|
||||
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
|
||||
{
|
||||
reputationLossMultiplier += character.GetStatValue(StatTypes.ReputationLossMultiplier);
|
||||
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, float maxReputationChangePerRound = float.MaxValue)
|
||||
{
|
||||
float currentValue = Value;
|
||||
float currentReputationChange = currentValue - ReputationAtRoundStart;
|
||||
if (Math.Abs(currentReputationChange) >= maxReputationChangePerRound &&
|
||||
Math.Sign(currentReputationChange) == Math.Sign(reputationChange))
|
||||
{
|
||||
return;
|
||||
}
|
||||
float newValue = Value + reputationChange * GetReputationChangeMultiplier(reputationChange);
|
||||
if (Math.Abs(newValue - ReputationAtRoundStart) > maxReputationChangePerRound &&
|
||||
Math.Sign(newValue - currentValue) == Math.Sign(newValue - ReputationAtRoundStart))
|
||||
{
|
||||
newValue = ReputationAtRoundStart + maxReputationChangePerRound * Math.Sign(reputationChange);
|
||||
}
|
||||
Value = newValue;
|
||||
}
|
||||
|
||||
public readonly NamedEvent<Reputation> OnReputationValueChanged = new NamedEvent<Reputation>();
|
||||
@@ -108,6 +138,7 @@ namespace Barotrauma
|
||||
metaDataIdentifier = $"reputation.{Identifier}".ToIdentifier();
|
||||
MinReputation = minReputation;
|
||||
MaxReputation = maxReputation;
|
||||
ReputationAtRoundStart = initialReputation;
|
||||
InitialReputation = initialReputation;
|
||||
Faction = faction;
|
||||
Location = location;
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -23,8 +23,6 @@ namespace Barotrauma
|
||||
public const int MaxMoney = int.MaxValue / 2; //about 1 billion
|
||||
public const int InitialMoney = 8500;
|
||||
|
||||
//duration of the cinematic + credits at the end of the campaign
|
||||
protected const float EndCinematicDuration = 240.0f;
|
||||
//duration of the camera transition at the end of a round
|
||||
protected const float EndTransitionDuration = 5.0f;
|
||||
//there can be no events before this time has passed during the 1st campaign round
|
||||
@@ -44,9 +42,10 @@ namespace Barotrauma
|
||||
public UpgradeManager UpgradeManager;
|
||||
public MedicalClinic MedicalClinic;
|
||||
|
||||
public List<Faction> Factions;
|
||||
private List<Faction> factions;
|
||||
public IReadOnlyList<Faction> Factions => factions;
|
||||
|
||||
public CampaignMetadata CampaignMetadata;
|
||||
public readonly CampaignMetadata CampaignMetadata;
|
||||
|
||||
protected XElement petsElement;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -96,6 +95,8 @@ namespace Barotrauma
|
||||
public SubmarineInfo PendingSubmarineSwitch;
|
||||
public bool TransferItemsOnSubSwitch { get; set; }
|
||||
|
||||
public bool SwitchedSubsThisRound { get; private set; }
|
||||
|
||||
protected Map map;
|
||||
public Map Map
|
||||
{
|
||||
@@ -106,20 +107,24 @@ namespace Barotrauma
|
||||
{
|
||||
get
|
||||
{
|
||||
if (Map.CurrentLocation != null)
|
||||
//map can be null if we're in the process of loading the save
|
||||
if (Map != null)
|
||||
{
|
||||
foreach (Mission mission in map.CurrentLocation.SelectedMissions)
|
||||
if (Map.CurrentLocation != null)
|
||||
{
|
||||
if (mission.Locations[0] == mission.Locations[1] ||
|
||||
mission.Locations.Contains(Map.SelectedLocation))
|
||||
foreach (Mission mission in map.CurrentLocation.SelectedMissions)
|
||||
{
|
||||
yield return mission;
|
||||
if (mission.Locations[0] == mission.Locations[1] ||
|
||||
mission.Locations.Contains(Map.SelectedLocation))
|
||||
{
|
||||
yield return mission;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Mission mission in extraMissions)
|
||||
{
|
||||
yield return mission;
|
||||
foreach (Mission mission in extraMissions)
|
||||
{
|
||||
yield return mission;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,10 +146,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)
|
||||
@@ -157,26 +171,26 @@ namespace Barotrauma
|
||||
|
||||
CargoManager = new CargoManager(this);
|
||||
MedicalClinic = new MedicalClinic(this);
|
||||
CampaignMetadata = new CampaignMetadata();
|
||||
Identifier messageIdentifier = new Identifier("money");
|
||||
|
||||
#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();
|
||||
@@ -239,6 +253,11 @@ namespace Barotrauma
|
||||
prevCampaignUIAutoOpenType = TransitionType.None;
|
||||
#endif
|
||||
|
||||
foreach (var faction in factions)
|
||||
{
|
||||
faction.Reputation.ReputationAtRoundStart = faction.Reputation.Value;
|
||||
}
|
||||
|
||||
if (PurchasedHullRepairsInLatestSave)
|
||||
{
|
||||
foreach (Structure wall in Structure.WallList)
|
||||
@@ -272,6 +291,7 @@ namespace Barotrauma
|
||||
PurchasedLostShuttlesInLatestSave = PurchasedLostShuttles = false;
|
||||
var connectedSubs = Submarine.MainSub.GetConnectedSubs();
|
||||
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
|
||||
SwitchedSubsThisRound = false;
|
||||
}
|
||||
|
||||
public static int GetHullRepairCost()
|
||||
@@ -307,12 +327,12 @@ namespace Barotrauma
|
||||
return (int)Math.Min(totalRepairDuration * ItemRepairCostPerRepairDuration, MaxItemRepairCost);
|
||||
}
|
||||
|
||||
public void InitCampaignData()
|
||||
public void InitFactions()
|
||||
{
|
||||
Factions = new List<Faction>();
|
||||
factions = new List<Faction>();
|
||||
foreach (FactionPrefab factionPrefab in FactionPrefab.Prefabs)
|
||||
{
|
||||
Factions.Add(new Faction(CampaignMetadata, factionPrefab));
|
||||
factions.Add(new Faction(CampaignMetadata, factionPrefab));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -358,10 +378,9 @@ namespace Barotrauma
|
||||
currentLocation.DeselectMission(mission);
|
||||
}
|
||||
}
|
||||
|
||||
if (levelData.HasBeaconStation && !levelData.IsBeaconActive)
|
||||
{
|
||||
var beaconMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Any(t => t.Equals("beaconnoreward", StringComparison.OrdinalIgnoreCase))).OrderBy(m => m.UintIdentifier);
|
||||
var beaconMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Contains("beaconnoreward")).OrderBy(m => m.UintIdentifier);
|
||||
if (beaconMissionPrefabs.Any())
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||
@@ -374,7 +393,7 @@ namespace Barotrauma
|
||||
}
|
||||
if (levelData.HasHuntingGrounds)
|
||||
{
|
||||
var huntingGroundsMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase))).OrderBy(m => m.UintIdentifier);
|
||||
var huntingGroundsMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Contains("huntinggrounds")).OrderBy(m => m.UintIdentifier);
|
||||
if (!huntingGroundsMissionPrefabs.Any())
|
||||
{
|
||||
DebugConsole.AddWarning("Could not find a hunting grounds mission for the level. No mission with the tag \"huntinggrounds\" found.");
|
||||
@@ -400,15 +419,108 @@ namespace Barotrauma
|
||||
weights[i] = weight;
|
||||
}
|
||||
var huntingGroundsMissionPrefab = ToolBox.SelectWeightedRandom(prefabs, weights, rand);
|
||||
if (!Missions.Any(m => m.Prefab.Tags.Any(t => t.Equals("huntinggrounds", StringComparison.OrdinalIgnoreCase))))
|
||||
if (!Missions.Any(m => m.Prefab.Tags.Contains("huntinggrounds")))
|
||||
{
|
||||
extraMissions.Add(huntingGroundsMissionPrefab.Instantiate(Map.SelectedConnection.Locations, Submarine.MainSub));
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (Faction faction in factions.OrderBy(f => f.Prefab.MenuOrder))
|
||||
{
|
||||
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 =
|
||||
MathHelper.Lerp(
|
||||
automaticMission.MinProbability,
|
||||
automaticMission.MaxProbability,
|
||||
MathUtils.InverseLerp(automaticMission.MinReputation, automaticMission.MaxReputation, faction.Reputation.Value));
|
||||
if (rand.NextDouble() < probability)
|
||||
{
|
||||
var missionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Any(t => t == automaticMission.MissionTag)).OrderBy(m => m.UintIdentifier);
|
||||
if (missionPrefabs.Any())
|
||||
{
|
||||
var missionPrefab = ToolBox.SelectWeightedRandom(missionPrefabs, p => (float)p.Commonness, rand);
|
||||
if (missionPrefab.Type == MissionType.Pirate && Missions.Any(m => m.Prefab.Type == MissionType.Pirate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (automaticMission.LevelType == LevelData.LevelType.Outpost)
|
||||
{
|
||||
extraMissions.Add(missionPrefab.Instantiate(new Location[] { currentLocation, currentLocation }, Submarine.MainSub));
|
||||
}
|
||||
else
|
||||
{
|
||||
extraMissions.Add(missionPrefab.Instantiate(Map.SelectedConnection.Locations, Submarine.MainSub));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (levelData.Biome.IsEndBiome)
|
||||
{
|
||||
Identifier endMissionTag = Identifier.Empty;
|
||||
if (levelData.Type == LevelData.LevelType.LocationConnection)
|
||||
{
|
||||
int locationIndex = map.EndLocations.IndexOf(map.SelectedLocation);
|
||||
if (locationIndex > -1)
|
||||
{
|
||||
endMissionTag = ("endlevel_locationconnection_" + locationIndex).ToIdentifier();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
int locationIndex = map.EndLocations.IndexOf(map.CurrentLocation);
|
||||
if (locationIndex > -1)
|
||||
{
|
||||
endMissionTag = ("endlevel_location_" + locationIndex).ToIdentifier();
|
||||
}
|
||||
}
|
||||
if (!endMissionTag.IsEmpty)
|
||||
{
|
||||
var endLevelMissionPrefabs = MissionPrefab.Prefabs.Where(m => m.Tags.Contains(endMissionTag)).OrderBy(m => m.UintIdentifier);
|
||||
if (endLevelMissionPrefabs.Any())
|
||||
{
|
||||
Random rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||
var endLevelMissionPrefab = ToolBox.SelectWeightedRandom(endLevelMissionPrefabs, p => (float)p.Commonness, rand);
|
||||
if (!Missions.Any(m => m.Prefab.Type == endLevelMissionPrefab.Type))
|
||||
{
|
||||
if (levelData.Type == LevelData.LevelType.LocationConnection)
|
||||
{
|
||||
extraMissions.Add(endLevelMissionPrefab.Instantiate(map.SelectedConnection.Locations, Submarine.MainSub));
|
||||
}
|
||||
else
|
||||
{
|
||||
extraMissions.Add(endLevelMissionPrefab.Instantiate(new Location[] { map.CurrentLocation, map.CurrentLocation }, Submarine.MainSub));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public void LoadNewLevel()
|
||||
{
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient)
|
||||
@@ -503,13 +615,6 @@ namespace Barotrauma
|
||||
{
|
||||
if (leavingSub.AtEndExit)
|
||||
{
|
||||
if (Map.EndLocation != null &&
|
||||
map.SelectedLocation == Map.EndLocation &&
|
||||
Map.EndLocation.Connections.Any(c => c.LevelData == Level.Loaded.LevelData))
|
||||
{
|
||||
nextLevel = map.StartLocation.LevelData;
|
||||
return TransitionType.End;
|
||||
}
|
||||
if (Level.Loaded.EndLocation != null && Level.Loaded.EndLocation.Type.HasOutpost && Level.Loaded.EndOutpost != null)
|
||||
{
|
||||
nextLevel = Level.Loaded.EndLocation.LevelData;
|
||||
@@ -554,8 +659,32 @@ namespace Barotrauma
|
||||
}
|
||||
else if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
nextLevel = map.SelectedLocation == null ? null : map.SelectedConnection?.LevelData;
|
||||
return nextLevel == null ? TransitionType.None : TransitionType.LeaveLocation;
|
||||
int currentEndLocationIndex = map.EndLocations.IndexOf(map.CurrentLocation);
|
||||
if (currentEndLocationIndex > -1)
|
||||
{
|
||||
if (currentEndLocationIndex == map.EndLocations.Count - 1)
|
||||
{
|
||||
//at the last end location, end of campaign
|
||||
nextLevel = map.StartLocation?.LevelData;
|
||||
return TransitionType.End;
|
||||
}
|
||||
else if (leavingSub.AtEndExit && currentEndLocationIndex < map.EndLocations.Count - 1)
|
||||
{
|
||||
//more end locations to go, progress to the next one
|
||||
nextLevel = map.EndLocations[currentEndLocationIndex + 1]?.LevelData;
|
||||
return TransitionType.ProgressToNextLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
nextLevel = null;
|
||||
return TransitionType.None;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
nextLevel = map.SelectedLocation == null ? null : map.SelectedConnection?.LevelData;
|
||||
return nextLevel == null ? TransitionType.None : TransitionType.LeaveLocation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -579,9 +708,11 @@ namespace Barotrauma
|
||||
//TODO: ignore players who don't have the permission to trigger a transition between levels?
|
||||
var leavingPlayers = Character.CharacterList.Where(c => !c.IsDead && (c == Character.Controlled || c.IsRemotePlayer));
|
||||
|
||||
CharacterTeamType submarineTeam = leavingPlayers.FirstOrDefault()?.TeamID ?? CharacterTeamType.Team1;
|
||||
|
||||
//allow leaving if inside an outpost, and the submarine is either docked to it or close enough
|
||||
Submarine leavingSubAtStart = GetLeavingSubAtStart(leavingPlayers);
|
||||
Submarine leavingSubAtEnd = GetLeavingSubAtEnd(leavingPlayers);
|
||||
Submarine leavingSubAtStart = GetLeavingSubAtStart(leavingPlayers, submarineTeam);
|
||||
Submarine leavingSubAtEnd = GetLeavingSubAtEnd(leavingPlayers, submarineTeam);
|
||||
|
||||
int playersInSubAtStart = leavingSubAtStart == null || !leavingSubAtStart.AtStartExit ? 0 :
|
||||
leavingPlayers.Count(c => c.Submarine == leavingSubAtStart || leavingSubAtStart.DockedTo.Contains(c.Submarine) || (Level.Loaded.StartOutpost != null && c.Submarine == Level.Loaded.StartOutpost));
|
||||
@@ -595,11 +726,11 @@ namespace Barotrauma
|
||||
|
||||
return playersInSubAtStart > playersInSubAtEnd ? leavingSubAtStart : leavingSubAtEnd;
|
||||
|
||||
static Submarine GetLeavingSubAtStart(IEnumerable<Character> leavingPlayers)
|
||||
static Submarine GetLeavingSubAtStart(IEnumerable<Character> leavingPlayers, CharacterTeamType submarineTeam)
|
||||
{
|
||||
if (Level.Loaded.StartOutpost == null)
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: submarineTeam);
|
||||
if (closestSub == null) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
@@ -609,26 +740,35 @@ namespace Barotrauma
|
||||
if (Level.Loaded.StartOutpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = Level.Loaded.StartOutpost.DockedTo.FirstOrDefault();
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != leavingPlayers.FirstOrDefault()?.TeamID) { return null; }
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { return null; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection && !leavingPlayers.Any(s => s.Submarine == Level.Loaded.StartOutpost)) { return null; }
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartOutpost.WorldPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.StartOutpost.WorldPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: submarineTeam);
|
||||
if (closestSub == null || !closestSub.AtStartExit) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
}
|
||||
|
||||
static Submarine GetLeavingSubAtEnd(IEnumerable<Character> leavingPlayers)
|
||||
static Submarine GetLeavingSubAtEnd(IEnumerable<Character> leavingPlayers, CharacterTeamType submarineTeam)
|
||||
{
|
||||
if (Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.ExitPoints.Any())
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndOutpost.WorldPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: submarineTeam);
|
||||
if (closestSub == null || !closestSub.AtEndExit) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
//no "end" in outpost levels
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost) { return null; }
|
||||
if (Level.Loaded.Type == LevelData.LevelType.Outpost)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Level.Loaded.EndOutpost == null)
|
||||
{
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndExitPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: submarineTeam);
|
||||
if (closestSub == null) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
@@ -638,13 +778,13 @@ namespace Barotrauma
|
||||
if (Level.Loaded.EndOutpost.DockedTo.Any())
|
||||
{
|
||||
var dockedSub = Level.Loaded.EndOutpost.DockedTo.FirstOrDefault();
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != leavingPlayers.FirstOrDefault()?.TeamID) { return null; }
|
||||
if (dockedSub == GameMain.NetworkMember?.RespawnManager?.RespawnShuttle || dockedSub.TeamID != submarineTeam) { return null; }
|
||||
return dockedSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : dockedSub;
|
||||
}
|
||||
|
||||
//nothing docked, check if there's a sub close enough to the outpost and someone inside the outpost
|
||||
if (Level.Loaded.Type == LevelData.LevelType.LocationConnection && !leavingPlayers.Any(s => s.Submarine == Level.Loaded.EndOutpost)) { return null; }
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndOutpost.WorldPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
|
||||
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndOutpost.WorldPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: submarineTeam);
|
||||
if (closestSub == null || !closestSub.AtEndExit) { return null; }
|
||||
return closestSub.DockedTo.Contains(Submarine.MainSub) ? Submarine.MainSub : closestSub;
|
||||
}
|
||||
@@ -765,8 +905,8 @@ namespace Barotrauma
|
||||
}
|
||||
foreach (Location location in Map.Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, location.Biome.AdjustedMaxDifficulty);
|
||||
location.Reset();
|
||||
location.LevelData = new LevelData(location, Map, location.Biome.AdjustedMaxDifficulty);
|
||||
location.Reset(this);
|
||||
}
|
||||
Map.ClearLocationHistory();
|
||||
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
|
||||
@@ -779,6 +919,10 @@ namespace Barotrauma
|
||||
{
|
||||
location.TurnsInRadiation = 0;
|
||||
}
|
||||
foreach (var faction in Factions)
|
||||
{
|
||||
faction.Reputation.SetReputation(faction.Prefab.InitialReputation);
|
||||
}
|
||||
EndCampaignProjSpecific();
|
||||
|
||||
if (CampaignMetadata != null)
|
||||
@@ -800,11 +944,56 @@ namespace Barotrauma
|
||||
|
||||
protected virtual void EndCampaignProjSpecific() { }
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random faction based on their ControlledOutpostPercentage
|
||||
/// </summary>
|
||||
/// <param name="allowEmpty">If true, the method can return null if the sum of the factions ControlledOutpostPercentage is less than 100%</param>
|
||||
public Faction GetRandomFaction(Rand.RandSync randSync, bool allowEmpty = true)
|
||||
{
|
||||
return GetRandomFaction(Factions, randSync, secondary: false, allowEmpty);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a random faction based on their SecondaryControlledOutpostPercentage
|
||||
/// </summary>
|
||||
/// <param name="allowEmpty">If true, the method can return null if the sum of the factions SecondaryControlledOutpostPercentage is less than 100%</param>
|
||||
public Faction GetRandomSecondaryFaction(Rand.RandSync randSync, bool allowEmpty = true)
|
||||
{
|
||||
return GetRandomFaction(Factions, randSync, secondary: true, allowEmpty);
|
||||
}
|
||||
|
||||
public static Faction GetRandomFaction(IEnumerable<Faction> factions, Rand.RandSync randSync, bool secondary = false, bool allowEmpty = true)
|
||||
{
|
||||
return GetRandomFaction(factions, Rand.GetRNG(randSync), secondary, allowEmpty);
|
||||
}
|
||||
|
||||
public static Faction GetRandomFaction(IEnumerable<Faction> factions, Random random, bool secondary = false, bool allowEmpty = true)
|
||||
{
|
||||
List<Faction> factionsList = factions.OrderBy(f => f.Prefab.Identifier).ToList();
|
||||
List<float> weights = factionsList.Select(f => secondary ? f.Prefab.SecondaryControlledOutpostPercentage : f.Prefab.ControlledOutpostPercentage).ToList();
|
||||
float percentageSum = weights.Sum();
|
||||
if (percentageSum < 100.0f && allowEmpty)
|
||||
{
|
||||
//chance of non-faction-specific outposts if percentage of controlled outposts is <100
|
||||
factionsList.Add(null);
|
||||
weights.Add(100.0f - percentageSum);
|
||||
}
|
||||
return ToolBox.SelectWeightedRandom(factionsList, weights, random);
|
||||
}
|
||||
|
||||
public bool TryHireCharacter(Location location, CharacterInfo characterInfo, Client client = null)
|
||||
{
|
||||
if (characterInfo == null) { return false; }
|
||||
if (characterInfo.MinReputationToHire.factionId != Identifier.Empty)
|
||||
{
|
||||
if (GetReputation(characterInfo.MinReputationToHire.factionId) < characterInfo.MinReputationToHire.reputation)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (!TryPurchase(client, characterInfo.Salary)) { return false; }
|
||||
characterInfo.IsNewHire = true;
|
||||
characterInfo.Title = null;
|
||||
location.RemoveHireableCharacter(characterInfo);
|
||||
CrewManager.AddCharacterInfo(characterInfo);
|
||||
GameAnalyticsManager.AddMoneySpentEvent(characterInfo.Salary, GameAnalyticsManager.MoneySink.Crew, characterInfo.Job?.Prefab.Identifier.Value ?? "unknown");
|
||||
@@ -859,11 +1048,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 &&
|
||||
@@ -975,11 +1167,36 @@ namespace Barotrauma
|
||||
if (npc == null || attacker == null || npc.IsDead || npc.IsInstigator) { return; }
|
||||
if (npc.TeamID != CharacterTeamType.FriendlyNPC) { return; }
|
||||
if (!attacker.IsRemotePlayer && attacker != Character.Controlled) { return; }
|
||||
Location location = Map?.CurrentLocation;
|
||||
if (location != null)
|
||||
|
||||
if (npc.Faction != null && Factions.FirstOrDefault(f => f.Prefab.Identifier == npc.Faction) is Faction faction)
|
||||
{
|
||||
location.Reputation.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
|
||||
faction.Reputation?.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage, Reputation.MaxReputationLossFromNPCDamage);
|
||||
}
|
||||
else
|
||||
{
|
||||
Location location = Map?.CurrentLocation;
|
||||
location?.Reputation?.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage, Reputation.MaxReputationLossFromNPCDamage);
|
||||
}
|
||||
}
|
||||
|
||||
public Faction GetFaction(Identifier identifier)
|
||||
{
|
||||
return factions.Find(f => f.Prefab.Identifier == identifier);
|
||||
}
|
||||
|
||||
public float GetReputation(Identifier factionIdentifier)
|
||||
{
|
||||
var faction =
|
||||
factionIdentifier == "location".ToIdentifier() ?
|
||||
factions.Find(f => f == Map?.CurrentLocation?.Faction) :
|
||||
factions.Find(f => f.Prefab.Identifier == factionIdentifier);
|
||||
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);
|
||||
@@ -996,7 +1213,20 @@ namespace Barotrauma
|
||||
new XAttribute(nameof(TotalPlayTime).ToLowerInvariant(), TotalPlayTime),
|
||||
new XAttribute(nameof(TotalPassedLevels).ToLowerInvariant(), TotalPassedLevels));
|
||||
}
|
||||
|
||||
|
||||
protected void LoadEvents(XElement element)
|
||||
{
|
||||
TotalPlayTime = element.GetAttributeDouble(nameof(TotalPlayTime).ToLowerInvariant(), 0);
|
||||
TotalPassedLevels = element.GetAttributeInt(nameof(TotalPassedLevels).ToLowerInvariant(), 0);
|
||||
}
|
||||
|
||||
protected XElement SaveEvents()
|
||||
{
|
||||
return new XElement("events",
|
||||
new XAttribute(nameof(EventManager.QueuedEventsForNextRound).ToLowerInvariant(),
|
||||
string.Join(',', GameMain.GameSession.EventManager.QueuedEventsForNextRound)));
|
||||
}
|
||||
|
||||
public void LogState()
|
||||
{
|
||||
DebugConsole.NewMessage("********* CAMPAIGN STATUS *********", Color.White);
|
||||
@@ -1080,6 +1310,7 @@ namespace Barotrauma
|
||||
TransferItemsBetweenSubs();
|
||||
}
|
||||
RefreshOwnedSubmarines();
|
||||
SwitchedSubsThisRound = true;
|
||||
PendingSubmarineSwitch = null;
|
||||
}
|
||||
|
||||
@@ -1097,7 +1328,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)
|
||||
{
|
||||
@@ -1132,15 +1363,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)
|
||||
@@ -1149,25 +1394,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; }
|
||||
|
||||
+18
-8
@@ -1,4 +1,5 @@
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Extensions;
|
||||
using Barotrauma.IO;
|
||||
using Barotrauma.Networking;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
@@ -53,7 +54,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
private bool ValidateFlag(NetFlags flag)
|
||||
private static bool ValidateFlag(NetFlags flag)
|
||||
{
|
||||
if (MathHelper.IsPowerOfTwo((int)flag)) { return true; }
|
||||
#if DEBUG
|
||||
@@ -105,9 +106,8 @@ namespace Barotrauma
|
||||
#endif
|
||||
}
|
||||
CampaignID = currentCampaignID;
|
||||
CampaignMetadata = new CampaignMetadata(this);
|
||||
UpgradeManager = new UpgradeManager(this);
|
||||
InitCampaignData();
|
||||
InitFactions();
|
||||
}
|
||||
|
||||
public static MultiPlayerCampaign StartNew(string mapSeed, CampaignSettings settings)
|
||||
@@ -190,11 +190,20 @@ namespace Barotrauma
|
||||
//map already created, update it
|
||||
//if we're not downloading the initial save file (LastSaveID > 0),
|
||||
//show notifications about location type changes
|
||||
map.LoadState(subElement, LastSaveID > 0);
|
||||
map.LoadState(this, subElement, LastSaveID > 0);
|
||||
}
|
||||
break;
|
||||
case "metadata":
|
||||
CampaignMetadata = new CampaignMetadata(this, subElement);
|
||||
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":
|
||||
@@ -214,6 +223,9 @@ namespace Barotrauma
|
||||
case "stats":
|
||||
LoadStats(subElement);
|
||||
break;
|
||||
case "eventmanager":
|
||||
GameMain.GameSession.EventManager.Load(subElement);
|
||||
break;
|
||||
case Wallet.LowerCaseSaveElementName:
|
||||
Bank = new Wallet(Option<Character>.None(), subElement);
|
||||
break;
|
||||
@@ -237,10 +249,8 @@ namespace Barotrauma
|
||||
};
|
||||
}
|
||||
|
||||
CampaignMetadata ??= new CampaignMetadata(this);
|
||||
UpgradeManager ??= new UpgradeManager(this);
|
||||
|
||||
InitCampaignData();
|
||||
#if SERVER
|
||||
characterData.Clear();
|
||||
string characterDataPath = GetCharacterDataSavePath();
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Collections.Immutable;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
using Barotrauma.Networking;
|
||||
using Barotrauma.Extensions;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
@@ -75,7 +76,10 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Map != null) { return Map.CurrentLocation; }
|
||||
if (dummyLocations == null) { dummyLocations = CreateDummyLocations(LevelData?.Seed ?? string.Empty); }
|
||||
if (dummyLocations == null)
|
||||
{
|
||||
dummyLocations = LevelData == null ? CreateDummyLocations(seed: string.Empty) : CreateDummyLocations(LevelData);
|
||||
}
|
||||
if (dummyLocations == null) { throw new NullReferenceException("dummyLocations is null somehow!"); }
|
||||
return dummyLocations[0];
|
||||
}
|
||||
@@ -86,7 +90,10 @@ namespace Barotrauma
|
||||
get
|
||||
{
|
||||
if (Map != null) { return Map.SelectedLocation; }
|
||||
if (dummyLocations == null) { dummyLocations = CreateDummyLocations(LevelData?.Seed ?? string.Empty); }
|
||||
if (dummyLocations == null)
|
||||
{
|
||||
dummyLocations = LevelData == null ? CreateDummyLocations(seed: string.Empty) : CreateDummyLocations(LevelData);
|
||||
}
|
||||
if (dummyLocations == null) { throw new NullReferenceException("dummyLocations is null somehow!"); }
|
||||
return dummyLocations[1];
|
||||
}
|
||||
@@ -248,13 +255,44 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public static Location[] CreateDummyLocations(LevelData levelData, LocationType? forceLocationType = null)
|
||||
{
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(levelData.Seed));
|
||||
var forceParams = levelData?.ForceOutpostGenerationParams;
|
||||
if (forceLocationType == null &&
|
||||
forceParams != null && forceParams.AllowedLocationTypes.Any() && !forceParams.AllowedLocationTypes.Contains("Any".ToIdentifier()))
|
||||
{
|
||||
forceLocationType =
|
||||
LocationType.Prefabs.Where(lt => forceParams.AllowedLocationTypes.Contains(lt.Identifier)).GetRandom(rand);
|
||||
}
|
||||
var dummyLocations = CreateDummyLocations(rand, forceLocationType);
|
||||
List<Faction> factions = new List<Faction>();
|
||||
foreach (var factionPrefab in FactionPrefab.Prefabs)
|
||||
{
|
||||
factions.Add(new Faction(new CampaignMetadata(), factionPrefab));
|
||||
}
|
||||
foreach (var location in dummyLocations)
|
||||
{
|
||||
if (location.Type.HasOutpost)
|
||||
{
|
||||
location.Faction = CampaignMode.GetRandomFaction(factions, rand, secondary: false);
|
||||
location.SecondaryFaction = CampaignMode.GetRandomFaction(factions, rand, secondary: true);
|
||||
}
|
||||
}
|
||||
return dummyLocations;
|
||||
}
|
||||
|
||||
public static Location[] CreateDummyLocations(string seed, LocationType? forceLocationType = null)
|
||||
{
|
||||
return CreateDummyLocations(new MTRandom(ToolBox.StringToInt(seed)), forceLocationType);
|
||||
}
|
||||
|
||||
private static Location[] CreateDummyLocations(Random rand, LocationType? forceLocationType = null)
|
||||
{
|
||||
var dummyLocations = new Location[2];
|
||||
MTRandom rand = new MTRandom(ToolBox.StringToInt(seed));
|
||||
for (int i = 0; i < 2; i++)
|
||||
{
|
||||
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true, forceLocationType: forceLocationType);
|
||||
dummyLocations[i] = Location.CreateRandom(new Vector2((float)rand.NextDouble() * 10000.0f, (float)rand.NextDouble() * 10000.0f), null, rand, requireOutpost: true, forceLocationType);
|
||||
}
|
||||
return dummyLocations;
|
||||
}
|
||||
@@ -268,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))
|
||||
{
|
||||
@@ -286,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;
|
||||
}
|
||||
@@ -298,10 +331,11 @@ namespace Barotrauma
|
||||
public void PurchaseSubmarine(SubmarineInfo newSubmarine, Client? client = null)
|
||||
{
|
||||
if (Campaign is null) { return; }
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.TryPurchase(client, newSubmarine.Price)) { return; }
|
||||
int price = newSubmarine.GetPrice();
|
||||
if ((GameMain.NetworkMember is null || GameMain.NetworkMember is { IsServer: true }) && !Campaign.TryPurchase(client, price)) { return; }
|
||||
if (!OwnedSubmarines.Any(s => s.Name == newSubmarine.Name))
|
||||
{
|
||||
GameAnalyticsManager.AddMoneySpentEvent(newSubmarine.Price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
|
||||
GameAnalyticsManager.AddMoneySpentEvent(price, GameAnalyticsManager.MoneySink.SubmarinePurchase, newSubmarine.Name);
|
||||
OwnedSubmarines.Add(newSubmarine);
|
||||
#if SERVER
|
||||
(Campaign as MultiPlayerCampaign)?.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.SubList);
|
||||
@@ -395,6 +429,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
GameMode!.AddExtraMissions(LevelData);
|
||||
foreach (Mission mission in GameMode!.Missions)
|
||||
{
|
||||
// setting level for missions that may involve difficulty-related submarine creation
|
||||
@@ -505,6 +540,8 @@ namespace Barotrauma
|
||||
existingRoundSummary.ContinueButton.Visible = true;
|
||||
}
|
||||
|
||||
CharacterHUD.ClearBossProgressBars();
|
||||
|
||||
RoundSummary = new RoundSummary(GameMode, Missions, StartLocation, EndLocation);
|
||||
|
||||
if (GameMode is not TutorialMode && GameMode is not TestGameMode)
|
||||
@@ -514,14 +551,15 @@ namespace Barotrauma
|
||||
{
|
||||
GUI.AddMessage(levelData.Biome.DisplayName, Color.Lerp(Color.CadetBlue, Color.DarkRed, levelData.Difficulty / 100.0f), 5.0f, playSound: false);
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Destination"), EndLocation.Name), Color.CadetBlue, playSound: false);
|
||||
if (missions.Count > 1)
|
||||
var missionsToShow = missions.Where(m => m.Prefab.ShowStartMessage);
|
||||
if (missionsToShow.Count() > 1)
|
||||
{
|
||||
string joinedMissionNames = string.Join(", ", missions.Select(m => m.Name));
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), joinedMissionNames), Color.CadetBlue, playSound: false);
|
||||
}
|
||||
else
|
||||
{
|
||||
var mission = missions.FirstOrDefault();
|
||||
var mission = missionsToShow.FirstOrDefault();
|
||||
GUI.AddMessage(TextManager.AddPunctuation(':', TextManager.Get("Mission"), mission?.Name ?? TextManager.Get("None")), Color.CadetBlue, playSound: false);
|
||||
}
|
||||
}
|
||||
@@ -568,7 +606,6 @@ namespace Barotrauma
|
||||
if (GameMode != null && Submarine != null)
|
||||
{
|
||||
missions.Clear();
|
||||
GameMode.AddExtraMissions(LevelData);
|
||||
missions.AddRange(GameMode.Missions);
|
||||
GameMode.Start();
|
||||
foreach (Mission mission in missions)
|
||||
@@ -611,7 +648,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CreatureMetrics.Instance.RecentlyEncountered.Clear();
|
||||
CreatureMetrics.RecentlyEncountered.Clear();
|
||||
|
||||
GameMain.GameScreen.Cam.Position = Character.Controlled?.WorldPosition ?? Submarine.MainSub.WorldPosition;
|
||||
RoundDuration = 0.0f;
|
||||
@@ -627,7 +664,16 @@ namespace Barotrauma
|
||||
return;
|
||||
}
|
||||
|
||||
if (level.StartOutpost != null)
|
||||
var originalSubPos = Submarine.WorldPosition;
|
||||
var spawnPoint = WayPoint.WayPointList.Find(wp => wp.SpawnType.HasFlag(SpawnType.Submarine) && wp.Submarine == level.StartOutpost);
|
||||
if (spawnPoint != null)
|
||||
{
|
||||
//pre-determine spawnpoint, just use it directly
|
||||
Submarine.SetPosition(spawnPoint.WorldPosition);
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
}
|
||||
else if (level.StartOutpost != null)
|
||||
{
|
||||
//start by placing the sub below the outpost
|
||||
Rectangle outpostBorders = Level.Loaded.StartOutpost.GetDockedBorders();
|
||||
@@ -682,7 +728,7 @@ namespace Barotrauma
|
||||
else
|
||||
{
|
||||
Submarine.SetPosition(spawnPos - Vector2.UnitY * 100.0f);
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
}
|
||||
}
|
||||
@@ -691,6 +737,7 @@ namespace Barotrauma
|
||||
Submarine.NeutralizeBallast();
|
||||
Submarine.EnableMaintainPosition();
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -841,7 +888,7 @@ namespace Barotrauma
|
||||
|
||||
GUI.PreventPauseMenuToggle = true;
|
||||
|
||||
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null)
|
||||
if (!(GameMode is TestGameMode) && Screen.Selected == GameMain.GameScreen && RoundSummary != null && transitionType != CampaignMode.TransitionType.End)
|
||||
{
|
||||
GUI.ClearMessages();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData is RoundSummary);
|
||||
@@ -854,6 +901,7 @@ namespace Barotrauma
|
||||
TabMenu.OnRoundEnded();
|
||||
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
|
||||
ObjectiveManager.ResetUI();
|
||||
CharacterHUD.ClearBossProgressBars();
|
||||
#endif
|
||||
SteamAchievementManager.OnRoundEnded(this);
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ namespace Barotrauma
|
||||
public List<CharacterInfo> AvailableCharacters { get; set; }
|
||||
public List<CharacterInfo> PendingHires = new List<CharacterInfo>();
|
||||
|
||||
public const int MaxAvailableCharacters = 10;
|
||||
public const int MaxAvailableCharacters = 6;
|
||||
|
||||
public HireManager()
|
||||
{
|
||||
@@ -32,6 +32,24 @@ namespace Barotrauma
|
||||
var variant = Rand.Range(0, job.Variants, Rand.RandSync.ServerAndClient);
|
||||
AvailableCharacters.Add(new CharacterInfo(CharacterPrefab.HumanSpeciesName, jobOrJobPrefab: job, variant: variant));
|
||||
}
|
||||
if (location.Faction != null) { GenerateFactionCharacters(location.Faction.Prefab); }
|
||||
if (location.SecondaryFaction != null) { GenerateFactionCharacters(location.SecondaryFaction.Prefab); }
|
||||
}
|
||||
|
||||
private void GenerateFactionCharacters(FactionPrefab faction)
|
||||
{
|
||||
foreach (var character in faction.HireableCharacters)
|
||||
{
|
||||
HumanPrefab humanPrefab = NPCSet.Get(character.NPCSetIdentifier, character.NPCIdentifier);
|
||||
if (humanPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Couldn't create a hireable for the location: character prefab \"{character.NPCIdentifier}\" not found in the NPC set \"{character.NPCSetIdentifier}\".");
|
||||
continue;
|
||||
}
|
||||
var characterInfo = humanPrefab.CreateCharacterInfo(Rand.RandSync.ServerAndClient);
|
||||
characterInfo.MinReputationToHire = (faction.Identifier, character.MinReputation);
|
||||
AvailableCharacters.Add(characterInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
class SlideshowPrefab : Prefab
|
||||
{
|
||||
public static readonly PrefabCollection<SlideshowPrefab> Prefabs = new PrefabCollection<SlideshowPrefab>();
|
||||
|
||||
public class Slide
|
||||
{
|
||||
public readonly LocalizedString Text;
|
||||
public readonly Sprite Portrait;
|
||||
|
||||
public readonly float FadeInDelay, FadeInDuration, FadeOutDuration;
|
||||
public readonly float TextFadeInDelay, TextFadeInDuration;
|
||||
|
||||
public Slide(ContentXElement element)
|
||||
{
|
||||
string text = element.GetAttributeString(nameof(Text), string.Empty);
|
||||
Text = TextManager.Get(text).Fallback(text);
|
||||
|
||||
FadeInDelay = element.GetAttributeFloat(nameof(FadeInDelay), 0.0f);
|
||||
FadeInDuration = element.GetAttributeFloat(nameof(FadeInDuration), 2.0f);
|
||||
FadeOutDuration = element.GetAttributeFloat(nameof(FadeOutDuration), 2.0f);
|
||||
TextFadeInDelay = element.GetAttributeFloat(nameof(TextFadeInDelay), 2.0f);
|
||||
TextFadeInDuration = element.GetAttributeFloat(nameof(TextFadeInDuration), 3.0f);
|
||||
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "portrait":
|
||||
Portrait = new Sprite(subElement, lazyLoad: true);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly ImmutableArray<Slide> Slides;
|
||||
|
||||
public SlideshowPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
|
||||
{
|
||||
List<Slide> slides = new List<Slide>();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "slide":
|
||||
slides.Add(new Slide(subElement));
|
||||
break;
|
||||
}
|
||||
}
|
||||
Slides = slides.ToImmutableArray();
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user