Faction Test 100.4.0.0

This commit is contained in:
Markus Isberg
2022-11-14 18:28:28 +02:00
parent 87426b68b2
commit c772b61fc1
412 changed files with 16984 additions and 5530 deletions
@@ -163,6 +163,7 @@ namespace Barotrauma
{
if (!subs.Contains(item.Submarine)) { continue; }
if (item.GetRootInventoryOwner() is Character) { continue; }
if (item.NonInteractable) { continue; }
containers.AddRange(item.GetComponents<ItemContainer>());
}
containers.Shuffle(Rand.RandSync.ServerAndClient);
@@ -158,6 +158,16 @@ namespace Barotrauma
this.campaign = campaign;
}
public static bool HasUnlockedStoreItem(ItemPrefab prefab)
{
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
if (character.HasStoreAccessForItem(prefab)) { return true; }
}
return false;
}
private List<T> GetItems<T>(Identifier identifier, Dictionary<Identifier, List<T>> items, bool create = false)
{
if (items.TryGetValue(identifier, out var storeSpecificItems) && storeSpecificItems != null)
@@ -8,19 +8,14 @@ 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 CampaignMetadata(XElement element)
{
Campaign = campaign;
foreach (var subElement in element.Elements())
{
if (string.Equals(subElement.Name.ToString(), "data", StringComparison.InvariantCultureIgnoreCase))
@@ -1,9 +1,17 @@
#nullable enable
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
namespace Barotrauma
{
public enum FactionAffiliation
{
Positive,
Neutral,
Negative
}
class Faction
{
public Reputation Reputation { get; }
@@ -14,6 +22,42 @@ namespace Barotrauma
Prefab = prefab;
Reputation = new Reputation(metadata, this, prefab.MinReputation, prefab.MaxReputation, prefab.InitialReputation);
}
/// <summary>
/// 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(Identifier identifier, ImmutableHashSet<Character>? characterList = null)
{
if (GameMain.GameSession?.Campaign?.Factions is not { } factions) { return FactionAffiliation.Neutral; }
characterList ??= GameSession.GetSessionCrewCharacters(CharacterType.Both);
foreach (Character character in characterList)
{
if (character.Info is not { } info) { continue; }
foreach (Faction faction in factions)
{
Identifier factionIdentifier = faction.Prefab.Identifier;
if (info.GetSavedStatValue(StatTypes.Affiliation, factionIdentifier) > 0f)
{
return factionIdentifier == identifier
? FactionAffiliation.Positive
: FactionAffiliation.Negative;
}
}
}
return FactionAffiliation.Neutral;
}
public static FactionAffiliation GetPlayerAffiliationStatus(Faction faction, ImmutableHashSet<Character>? characterList = null) => GetPlayerAffiliationStatus(faction.Prefab.Identifier, characterList);
public override string ToString()
{
return $"{base.ToString()} ({Prefab?.Identifier.ToString() ?? "null"})";
}
}
internal class FactionPrefab : Prefab
@@ -25,6 +69,50 @@ 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 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);
}
}
public ImmutableArray<AutomaticMission> AutomaticMissions;
public bool StartOutpost { get; }
public int MenuOrder { get; }
/// <summary>
@@ -42,38 +130,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,16 @@
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.05f;
public const float ReputationLossPerWallDamage = 0.05f;
public const float ReputationLossPerStolenItemPrice = 0.005f;
public const float MinReputationLossPerStolenItem = 0.05f;
public const float MaxReputationLossPerStolenItem = 1.0f;
public Identifier Identifier { get; }
public int MinReputation { get; }
@@ -66,10 +65,21 @@ namespace Barotrauma
float reputationGainMultiplier = 1f;
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
reputationGainMultiplier += character.GetStatValue(StatTypes.ReputationGainMultiplier);
reputationGainMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationGainMultiplier);
reputationGainMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationGainMultiplier, Identifier) ?? 0;
}
reputationChange *= reputationGainMultiplier;
}
else if (reputationChange < 0f)
{
float reputationLossMultiplier = 1f;
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
reputationLossMultiplier *= 1f + character.GetStatValue(StatTypes.ReputationLossMultiplier);
reputationLossMultiplier *= 1f + character.Info?.GetSavedStatValue(StatTypes.ReputationLossMultiplier, Identifier) ?? 0;
}
reputationChange *= reputationLossMultiplier;
}
Value += reputationChange;
}
@@ -24,8 +24,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
@@ -45,7 +43,8 @@ namespace Barotrauma
public UpgradeManager UpgradeManager;
public MedicalClinic MedicalClinic;
public List<Faction> Factions;
private List<Faction> factions;
public IReadOnlyList<Faction> Factions => factions;
public CampaignMetadata CampaignMetadata;
@@ -139,6 +138,15 @@ namespace Barotrauma
public virtual bool PurchasedLostShuttles { get; set; }
public virtual bool PurchasedItemRepairs { get; set; }
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)));
}
protected CampaignMode(GameModePreset preset, CampaignSettings settings)
: base(preset)
{
@@ -211,7 +219,7 @@ namespace Barotrauma
return Level.Loaded?.StartLocation ?? Map.CurrentLocation;
}
public List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
public static List<Submarine> GetSubsToLeaveBehind(Submarine leavingSub)
{
//leave subs behind if they're not docked to the leaving sub and not at the same exit
return Submarine.Loaded.FindAll(sub =>
@@ -266,7 +274,7 @@ namespace Barotrauma
wasDocked = Level.Loaded.StartOutpost != null && connectedSubs.Contains(Level.Loaded.StartOutpost);
}
public int GetHullRepairCost()
public static int GetHullRepairCost()
{
float totalDamage = 0;
foreach (Structure wall in Structure.WallList)
@@ -283,7 +291,7 @@ namespace Barotrauma
return (int)Math.Min(totalDamage * HullRepairCostPerDamage, MaxHullRepairCost);
}
public int GetItemRepairCost()
public static int GetItemRepairCost()
{
float totalRepairDuration = 0.0f;
foreach (Item item in Item.ItemList)
@@ -299,12 +307,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));
}
}
@@ -341,10 +349,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));
@@ -357,7 +364,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.");
@@ -383,15 +390,95 @@ 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))
{
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; }
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)
@@ -486,13 +573,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;
@@ -537,8 +617,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
{
@@ -551,7 +655,7 @@ namespace Barotrauma
/// <summary>
/// Which submarine is at a position where it can leave the level and enter another one (if any).
/// </summary>
private Submarine GetLeavingSub()
private static Submarine GetLeavingSub()
{
if (Level.IsLoadedOutpost)
{
@@ -606,8 +710,17 @@ namespace Barotrauma
static Submarine GetLeavingSubAtEnd(IEnumerable<Character> leavingPlayers)
{
if (Level.Loaded.EndOutpost != null && Level.Loaded.EndOutpost.ExitPoints.Any())
{
Submarine closestSub = Submarine.FindClosest(Level.Loaded.EndOutpost.WorldPosition, ignoreOutposts: true, ignoreRespawnShuttle: true, teamType: leavingPlayers.FirstOrDefault()?.TeamID);
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)
{
@@ -738,8 +851,9 @@ namespace Barotrauma
foreach (Location location in Map.Locations)
{
location.LevelData = new LevelData(location, location.Biome.AdjustedMaxDifficulty);
location.Reset();
location.Reset(this);
}
Map.ClearLocationHistory();
Map.SetLocation(Map.Locations.IndexOf(Map.StartLocation));
Map.SelectLocation(-1);
if (Map.Radiation != null)
@@ -771,11 +885,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");
@@ -946,11 +1105,28 @@ 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.HumanPrefab?.Faction != null && Factions.FirstOrDefault(f => f.Prefab.Identifier == npc.HumanPrefab.Faction) is Faction faction)
{
location.Reputation.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
faction.Reputation?.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
}
else
{
Location location = Map?.CurrentLocation;
if (location != null)
{
location.Reputation?.AddReputation(-attackResult.Damage * Reputation.ReputationLossPerNPCDamage);
}
}
}
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 abstract void Save(XElement element);
@@ -1025,7 +1201,7 @@ namespace Barotrauma
}
}
protected void LeaveUnconnectedSubs(Submarine leavingSub)
protected static void LeaveUnconnectedSubs(Submarine leavingSub)
{
if (leavingSub != Submarine.MainSub && !leavingSub.DockedTo.Contains(Submarine.MainSub))
{
@@ -2,6 +2,7 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
@@ -17,6 +18,9 @@ namespace Barotrauma
[Serialize("", IsPropertySaveable.Yes)]
public string PresetName { get; set; } = string.Empty;
[Serialize(true, IsPropertySaveable.Yes)]
public bool TutorialEnabled { get; set; }
[Serialize(false, IsPropertySaveable.Yes), NetworkSerialize]
public bool RadiationEnabled { get; set; }
@@ -103,12 +107,9 @@ namespace Barotrauma
private static int GetAddedMissionCount()
{
int count = 0;
foreach (Character character in GameSession.GetSessionCrewCharacters(CharacterType.Both))
{
count += (int)character.GetStatValue(StatTypes.ExtraMissionCount);
}
return count;
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (!characters.Any()) { return 0; }
return characters.Max(static character => (int)character.GetStatValue(StatTypes.ExtraMissionCount));
}
}
}
@@ -105,9 +105,9 @@ namespace Barotrauma
#endif
}
CampaignID = currentCampaignID;
CampaignMetadata = new CampaignMetadata(this);
CampaignMetadata = new CampaignMetadata();
UpgradeManager = new UpgradeManager(this);
InitCampaignData();
InitFactions();
}
public static MultiPlayerCampaign StartNew(string mapSeed, CampaignSettings settings)
@@ -133,13 +133,13 @@ namespace Barotrauma
}
partial void InitProjSpecific();
public static string GetCharacterDataSavePath(string savePath)
{
return Path.Combine(SaveUtil.MultiplayerSaveFolder, Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
return Path.Combine(Path.GetDirectoryName(savePath), Path.GetFileNameWithoutExtension(savePath) + "_CharacterData.xml");
}
public string GetCharacterDataSavePath()
public static string GetCharacterDataSavePath()
{
return GetCharacterDataSavePath(GameMain.GameSession.SavePath);
}
@@ -190,11 +190,11 @@ 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);
CampaignMetadata = new CampaignMetadata(subElement);
break;
case "upgrademanager":
case "pendingupgrades":
@@ -237,10 +237,10 @@ namespace Barotrauma
};
}
CampaignMetadata ??= new CampaignMetadata(this);
CampaignMetadata ??= new CampaignMetadata();
UpgradeManager ??= new UpgradeManager(this);
InitCampaignData();
InitFactions();
#if SERVER
characterData.Clear();
string characterDataPath = GetCharacterDataSavePath();
@@ -29,6 +29,14 @@ namespace Barotrauma
public readonly Sprite Banner;
public readonly EndMessageInfo EndMessage;
public enum EndType { None, Continue, Restart }
public readonly record struct EndMessageInfo(
EndType EndType,
Identifier NextTutorialIdentifier);
public TutorialPrefab(ContentFile file, ContentXElement element) : base(file, element.GetAttributeIdentifier("identifier", ""))
{
Order = element.GetAttributeInt("order", int.MaxValue);
@@ -59,6 +67,13 @@ namespace Barotrauma
}
EventIdentifier = element.GetChildElement("scriptedevent")?.GetAttributeIdentifier("identifier", "") ?? Identifier.Empty;
if (element.GetChildElement("endmessage") is ContentXElement endMessageElement)
{
EndMessage = new EndMessageInfo(
EndType: endMessageElement.GetAttributeEnum("type", EndType.None),
NextTutorialIdentifier: endMessageElement.GetAttributeIdentifier("nexttutorial", Identifier.Empty));
}
}
public CharacterInfo GetTutorialCharacterInfo()
@@ -9,6 +9,7 @@ using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Networking;
using Barotrauma.Extensions;
namespace Barotrauma
{
@@ -72,7 +73,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];
}
@@ -83,7 +87,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];
}
@@ -245,13 +252,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;
}
@@ -295,10 +333,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);
@@ -391,6 +430,7 @@ namespace Barotrauma
}
}
GameMode!.AddExtraMissions(LevelData);
foreach (Mission mission in GameMode!.Missions)
{
// setting level for missions that may involve difficulty-related submarine creation
@@ -566,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)
@@ -582,6 +621,9 @@ namespace Barotrauma
}
}
#if CLIENT
ObjectiveManager.ResetObjectives();
#endif
EventManager?.StartRound(Level.Loaded);
SteamAchievementManager.OnStartRound();
@@ -622,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();
@@ -677,7 +728,7 @@ namespace Barotrauma
else
{
Submarine.SetPosition(spawnPos - Vector2.UnitY * 100.0f);
Submarine.NeutralizeBallast();
Submarine.NeutralizeBallast();
Submarine.EnableMaintainPosition();
}
}
@@ -686,6 +737,7 @@ namespace Barotrauma
Submarine.NeutralizeBallast();
Submarine.EnableMaintainPosition();
}
}
else
{
@@ -756,7 +808,7 @@ namespace Barotrauma
/// </remarks>
public static ImmutableHashSet<Character> GetSessionCrewCharacters(CharacterType type)
{
if (!(GameMain.GameSession.CrewManager is { } crewManager)) { return ImmutableHashSet<Character>.Empty; }
if (GameMain.GameSession.CrewManager is not { } crewManager) { return ImmutableHashSet<Character>.Empty; }
IEnumerable<Character> players;
IEnumerable<Character> bots;
@@ -766,8 +818,8 @@ namespace Barotrauma
players = GameMain.Server.ConnectedClients.Select(c => c.Character).Where(c => c?.Info != null && !c.IsDead);
bots = crewManager.GetCharacters().Where(c => !c.IsRemotePlayer);
#elif CLIENT
players = crewManager.GetCharacters().Where(c => c.IsPlayer);
bots = crewManager.GetCharacters().Where(c => c.IsBot);
players = crewManager.GetCharacters().Where(static c => c.IsPlayer);
bots = crewManager.GetCharacters().Where(static c => c.IsBot);
#endif
if (type.HasFlag(CharacterType.Bot))
{
@@ -835,7 +887,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);
@@ -847,6 +899,7 @@ namespace Barotrauma
if (GameMain.NetLobbyScreen != null) { GameMain.NetLobbyScreen.OnRoundEnded(); }
TabMenu.OnRoundEnded();
GUIMessageBox.MessageBoxes.RemoveAll(mb => mb.UserData as string == "ConversationAction" || ReadyCheck.IsReadyCheck(mb));
ObjectiveManager.ResetUI();
#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()
@@ -9,7 +9,7 @@ using Barotrauma.Networking;
namespace Barotrauma
{
internal partial class MedicalClinic
internal sealed partial class MedicalClinic
{
public enum NetworkHeader
{
@@ -18,7 +18,8 @@ namespace Barotrauma
ADD_PENDING,
REMOVE_PENDING,
CLEAR_PENDING,
HEAL_PENDING
HEAL_PENDING,
ADD_EVERYTHING_TO_PENDING
}
public enum AfflictionSeverity
@@ -43,23 +44,10 @@ namespace Barotrauma
}
[NetworkSerialize]
public struct NetHealRequest : INetSerializableStruct
{
public HealRequestResult Result;
}
public readonly record struct NetHealRequest(HealRequestResult Result) : INetSerializableStruct;
[NetworkSerialize]
public struct NetRemovedAffliction : INetSerializableStruct
{
public NetCrewMember CrewMember;
public NetAffliction Affliction;
}
public struct NetPendingCrew : INetSerializableStruct
{
[NetworkSerialize(ArrayMaxSize = CrewManager.MaxCrewSize)]
public NetCrewMember[] CrewMembers;
}
public readonly record struct NetRemovedAffliction(NetCrewMember CrewMember, NetAffliction Affliction) : INetSerializableStruct;
public struct NetAffliction : INetSerializableStruct
{
@@ -69,42 +57,18 @@ namespace Barotrauma
[NetworkSerialize]
public ushort Strength;
[NetworkSerialize]
public int VitalityDecrease;
[NetworkSerialize]
public ushort Price;
public AfflictionSeverity AfflictionSeverity
public void SetAffliction(Affliction affliction, CharacterHealth characterHealth)
{
get
{
if (Prefab is null) { return AfflictionSeverity.Low; }
float normalizedStrength = Strength / Prefab.MaxStrength;
// lesser than 0.1
if (normalizedStrength <= 0.1)
{
return AfflictionSeverity.Low;
}
// between 0.1 and 0.5
if (normalizedStrength > 0.1f && normalizedStrength < 0.5f)
{
return AfflictionSeverity.Medium;
}
// greater than 0.5
return AfflictionSeverity.High;
}
}
public Affliction Affliction
{
set
{
Identifier = value.Identifier;
Strength = (ushort)Math.Ceiling(value.Strength);
Price = (ushort)(value.Prefab.BaseHealCost + Strength * value.Prefab.HealCostMultiplier);
}
Identifier = affliction.Identifier;
Strength = (ushort)Math.Ceiling(affliction.Strength);
Price = (ushort)(affliction.Prefab.BaseHealCost + Strength * affliction.Prefab.HealCostMultiplier);
VitalityDecrease = (int)affliction.GetVitalityDecrease(characterHealth);
}
private AfflictionPrefab? cachedPrefab;
@@ -146,17 +110,23 @@ namespace Barotrauma
}
}
public struct NetCrewMember : INetSerializableStruct
public record struct NetCrewMember : INetSerializableStruct
{
[NetworkSerialize]
public int CharacterInfoID;
[NetworkSerialize]
public NetAffliction[] Afflictions;
public ImmutableArray<NetAffliction> Afflictions;
public CharacterInfo CharacterInfo
public NetCrewMember(CharacterInfo info)
{
set => CharacterInfoID = value.GetIdentifierUsingOriginalName();
CharacterInfoID = info.GetIdentifierUsingOriginalName();
Afflictions = ImmutableArray<NetAffliction>.Empty;
}
public NetCrewMember(CharacterInfo info, ImmutableArray<NetAffliction> afflictions): this(info)
{
Afflictions = afflictions;
}
public readonly CharacterInfo? FindCharacterInfo(ImmutableArray<CharacterInfo> crew)
@@ -194,11 +164,11 @@ namespace Barotrauma
private static bool IsOutpostInCombat()
{
if (!(Level.Loaded is { Type: LevelData.LevelType.Outpost })) { return false; }
if (Level.Loaded is not { Type: LevelData.LevelType.Outpost }) { return false; }
IEnumerable<Character> crew = GetCrewCharacters().Where(c => c.Character != null).Select(c => c.Character).ToImmutableHashSet();
IEnumerable<Character> crew = GetCrewCharacters().Where(static c => c.Character != null).Select(static c => c.Character).ToImmutableHashSet();
foreach (Character npc in Character.CharacterList.Where(c => c.TeamID == CharacterTeamType.FriendlyNPC))
foreach (Character npc in Character.CharacterList.Where(static c => c.TeamID == CharacterTeamType.FriendlyNPC))
{
bool isInCombatWithCrew = !npc.IsInstigator && npc.AIController is HumanAIController { ObjectiveManager: { CurrentObjective: AIObjectiveCombat combatObjective } } && crew.Contains(combatObjective.Enemy);
if (isInCombatWithCrew) { return true; }
@@ -238,6 +208,20 @@ namespace Barotrauma
PendingHeals.Clear();
}
private void AddEverythingToPending()
{
foreach (CharacterInfo info in GetCrewCharacters())
{
if (info.Character?.CharacterHealth is not { } health) { continue; }
var afflictions = GetAllAfflictions(health);
if (afflictions.Length is 0) { continue; }
InsertPendingCrewMember(new NetCrewMember(info, afflictions));
}
}
private void RemovePendingAffliction(NetCrewMember crewMember, NetAffliction affliction)
{
foreach (NetCrewMember listMember in PendingHeals.ToList())
@@ -255,7 +239,7 @@ namespace Barotrauma
newAfflictions.Add(pendingAffliction);
}
pendingMember.Afflictions = newAfflictions.ToArray();
pendingMember.Afflictions = newAfflictions.ToImmutableArray();
}
if (!pendingMember.Afflictions.Any()) { continue; }
@@ -280,9 +264,9 @@ namespace Barotrauma
static float GetShowTreshold(Affliction affliction) => Math.Max(0, Math.Min(affliction.Prefab.ShowIconToOthersThreshold, affliction.Prefab.ShowInHealthScannerThreshold));
}
private NetAffliction[] GetAllAfflictions(CharacterHealth health)
private ImmutableArray<NetAffliction> GetAllAfflictions(CharacterHealth health)
{
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(a => IsHealable(a));
IEnumerable<Affliction> rawAfflictions = health.GetAllAfflictions().Where(IsHealable);
List<NetAffliction> afflictions = new List<NetAffliction>();
@@ -298,19 +282,20 @@ namespace Barotrauma
}
else
{
newAffliction = new NetAffliction { Affliction = affliction };
newAffliction = new NetAffliction();
newAffliction.SetAffliction(affliction, health);
newAffliction.Price = (ushort)GetAdjustedPrice(newAffliction.Price);
}
afflictions.Add(newAffliction);
}
return afflictions.ToArray();
return afflictions.ToImmutableArray();
static int GetHealPrice(Affliction affliction) => (int)(affliction.Prefab.BaseHealCost + (affliction.Prefab.HealCostMultiplier * affliction.Strength));
}
public int GetTotalCost() => PendingHeals.SelectMany(h => h.Afflictions).Aggregate(0, (current, affliction) => current + affliction.Price);
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;
@@ -325,7 +310,7 @@ namespace Barotrauma
}
#endif
return Character.CharacterList.Where(c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(c => c.Info).ToImmutableArray();
return Character.CharacterList.Where(static c => c.Info != null && c.TeamID == CharacterTeamType.Team1).Select(static c => c.Info).ToImmutableArray();
}
#if DEBUG && CLIENT
@@ -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() { }
}
}
@@ -207,7 +207,7 @@ namespace Barotrauma
price = 0;
}
if (Campaign.TryPurchase(client, price))
if (force || Campaign.TryPurchase(client, price))
{
if (GameMain.NetworkMember == null || GameMain.NetworkMember.IsServer)
{