Faction Test 100.4.0.0
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
-9
@@ -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();
|
||||
|
||||
+15
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user