Faction Test v1.0.1.0

This commit is contained in:
Regalis11
2023-02-16 15:01:28 +02:00
parent caa5a2f762
commit 2c5a7923b0
309 changed files with 7502 additions and 4335 deletions
@@ -3,7 +3,6 @@ using Barotrauma.Extensions;
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Xml.Linq;
@@ -63,10 +62,17 @@ namespace Barotrauma
public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
public bool Visited => GameMain.GameSession?.Map?.IsVisited(this) ?? false;
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
public int LocationTypeChangeCooldown;
/// <summary>
/// Is some mission blocking this location from changing its type?
/// </summary>
public bool LocationTypeChangesBlocked => availableMissions.Any(m => m.Prefab.BlockLocationTypeChanges);
public string BaseName { get => baseName; }
public string Name { get; private set; }
@@ -96,6 +102,7 @@ namespace Barotrauma
public class StoreInfo
{
public Identifier Identifier { get; }
public Identifier MerchantFaction { get; private set; }
public int Balance { get; set; }
public List<PurchasedItem> Stock { get; } = new List<PurchasedItem>();
public List<ItemPrefab> DailySpecials { get; } = new List<ItemPrefab>();
@@ -105,6 +112,7 @@ namespace Barotrauma
/// </summary>
public int PriceModifier { get; set; }
public Location Location { get; }
private float MaxReputationModifier => Location.StoreMaxReputationModifier;
private StoreInfo(Location location)
{
@@ -129,6 +137,7 @@ namespace Barotrauma
public StoreInfo(Location location, XElement storeElement) : this(location)
{
Identifier = storeElement.GetAttributeIdentifier("identifier", "");
MerchantFaction = storeElement.GetAttributeIdentifier(nameof(MerchantFaction), "");
Balance = storeElement.GetAttributeInt("balance", location.StoreInitialBalance);
PriceModifier = storeElement.GetAttributeInt("pricemodifier", 0);
// Backwards compatibility: before introducing support for multiple stores, this value was saved as a store element attribute
@@ -285,13 +294,14 @@ namespace Barotrauma
{
price = Location.DailySpecialPriceModifier * price;
}
// Adjust by current location reputation
price *= Location.GetStoreReputationModifier(true);
// Adjust by current reputation
price *= GetReputationModifier(true);
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (characters.Any())
{
if (Location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(faction) is FactionAffiliation.Positive)
var faction = GetMerchantOrLocationFactionIdentifier();
if (!faction.IsEmpty && GameMain.GameSession.Campaign.GetFactionAffiliation(faction) is FactionAffiliation.Positive)
{
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplierAffiliated, includeSaved: false));
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplierAffiliated, tag)));
@@ -317,8 +327,8 @@ namespace Barotrauma
{
price = Location.RequestGoodPriceModifier * price;
}
// Adjust by current location reputation
price *= Location.GetStoreReputationModifier(false);
// Adjust by location reputation
price *= GetReputationModifier(false);
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
if (characters.Any())
@@ -331,6 +341,45 @@ namespace Barotrauma
return Math.Max((int)price, 1);
}
public void SetMerchantFaction(Identifier factionIdentifier)
{
MerchantFaction = factionIdentifier;
}
public Identifier GetMerchantOrLocationFactionIdentifier()
{
return MerchantFaction.IfEmpty(Location.Faction?.Prefab.Identifier ?? Identifier.Empty);
}
public float GetReputationModifier(bool buying)
{
var factionIdentifier = GetMerchantOrLocationFactionIdentifier();
var reputation = GameMain.GameSession.Campaign.GetFaction(factionIdentifier)?.Reputation;
if (reputation == null) { return 1.0f; }
if (buying)
{
if (reputation.Value > 0.0f)
{
return MathHelper.Lerp(1.0f, 1.0f - MaxReputationModifier, reputation.Value / reputation.MaxReputation);
}
else
{
return MathHelper.Lerp(1.0f, 1.0f + MaxReputationModifier, reputation.Value / reputation.MinReputation);
}
}
else
{
if (reputation.Value > 0.0f)
{
return MathHelper.Lerp(1.0f, 1.0f + MaxReputationModifier, reputation.Value / reputation.MaxReputation);
}
else
{
return MathHelper.Lerp(1.0f, 1.0f - MaxReputationModifier, reputation.Value / reputation.MinReputation);
}
}
}
public override string ToString()
{
return Identifier.Value;
@@ -392,6 +441,8 @@ namespace Barotrauma
}
}
public void SelectMission(Mission mission)
{
if (!SelectedMissions.Contains(mission) && mission != null)
@@ -454,19 +505,22 @@ namespace Barotrauma
public bool IsGateBetweenBiomes;
private struct LoadedMission
private readonly struct LoadedMission
{
public MissionPrefab MissionPrefab { get; }
public int OriginLocationIndex { get; }
public int DestinationIndex { get; }
public bool SelectedMission { get; }
public readonly MissionPrefab MissionPrefab;
public readonly int TimesAttempted;
public readonly int OriginLocationIndex;
public readonly int DestinationIndex;
public readonly bool SelectedMission;
public LoadedMission(MissionPrefab prefab, int originLocationIndex, int destinationIndex, bool selectedMission)
public LoadedMission(XElement element)
{
MissionPrefab = prefab;
OriginLocationIndex = originLocationIndex;
DestinationIndex = destinationIndex;
SelectedMission = selectedMission;
var id = element.GetAttributeIdentifier("prefabid", Identifier.Empty);
MissionPrefab = MissionPrefab.Prefabs.TryGet(id, out var prefab) ? prefab : null;
TimesAttempted = element.GetAttributeInt("timesattempted", 0);
OriginLocationIndex = element.GetAttributeInt("origin", -1);
DestinationIndex = element.GetAttributeInt("destinationindex", -1);
SelectedMission = element.GetAttributeBool("selected", false);
}
}
@@ -577,12 +631,9 @@ namespace Barotrauma
killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", Array.Empty<int>()).ToHashSet();
System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationTypeId}\"!");
if (Type == null)
{
Type = LocationType.Prefabs.First();
}
Type ??= LocationType.Prefabs.First();
LevelData = new LevelData(element.Element("Level"));
LevelData = new LevelData(element.Element("Level"), clampDifficultyToBiome: true);
PortraitId = ToolBox.StringToInt(Name);
@@ -661,14 +712,11 @@ namespace Barotrauma
loadedMissions = new List<LoadedMission>();
foreach (XElement childElement in missionsElement.GetChildElements("mission"))
{
var id = childElement.GetAttributeString("prefabid", null);
if (string.IsNullOrWhiteSpace(id)) { continue; }
var prefab = MissionPrefab.Prefabs.Find(p => p.Identifier == id);
if (prefab == null) { continue; }
var origin = childElement.GetAttributeInt("origin", -1);
var destination = childElement.GetAttributeInt("destinationindex", -1);
var selected = childElement.GetAttributeBool("selected", false);
loadedMissions.Add(new LoadedMission(prefab, origin, destination, selected));
var loadedMission = new LoadedMission(childElement);
if (loadedMission.MissionPrefab != null)
{
loadedMissions.Add(loadedMission);
}
}
}
}
@@ -693,7 +741,7 @@ namespace Barotrauma
Type = newType;
Name = Type.NameFormats == null || !Type.NameFormats.Any() ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
if (Type.HasOutpost)
if (Type.HasOutpost && Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
{
if (Faction == null)
{
@@ -734,30 +782,14 @@ namespace Barotrauma
{
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return; }
var mission = InstantiateMission(missionPrefab, connection);
if (!mission.Prefab.AllowOtherMissionsInLevel)
{
availableMissions.Clear();
}
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
AddMission(InstantiateMission(missionPrefab, connection));
}
public void UnlockMission(MissionPrefab missionPrefab)
{
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return; }
var mission = InstantiateMission(missionPrefab);
if (!mission.Prefab.AllowOtherMissionsInLevel)
{
availableMissions.Clear();
}
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
AddMission(InstantiateMission(missionPrefab));
}
public Mission UnlockMissionByIdentifier(Identifier identifier)
@@ -778,14 +810,7 @@ namespace Barotrauma
{
return null;
}
if (!mission.Prefab.AllowOtherMissionsInLevel)
{
availableMissions.Clear();
}
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
AddMission(mission);
return mission;
}
return null;
@@ -816,14 +841,7 @@ namespace Barotrauma
{
return null;
}
if (!mission.Prefab.AllowOtherMissionsInLevel)
{
availableMissions.Clear();
}
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#endif
AddMission(mission);
return mission;
}
else
@@ -835,6 +853,20 @@ namespace Barotrauma
return null;
}
private void AddMission(Mission mission)
{
if (!mission.Prefab.AllowOtherMissionsInLevel)
{
availableMissions.Clear();
}
availableMissions.Add(mission);
#if CLIENT
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
#else
(GameMain.GameSession?.Campaign as MultiPlayerCampaign)?.IncrementLastUpdateIdForFlag(MultiPlayerCampaign.NetFlags.MapAndMissions);
#endif
}
private Mission InstantiateMission(MissionPrefab prefab, out LocationConnection connection)
{
if (prefab.IsAllowed(this, this))
@@ -933,6 +965,7 @@ namespace Barotrauma
{
mission.OriginLocation = map.Locations[loadedMission.OriginLocationIndex];
}
mission.TimesAttempted = loadedMission.TimesAttempted;
availableMissions.Add(mission);
if (loadedMission.SelectedMission) { selectedMissions.Add(mission); }
}
@@ -1332,33 +1365,6 @@ namespace Barotrauma
}
}
public float GetStoreReputationModifier(bool buying)
{
if (Reputation == null) { return 1.0f; }
if (buying)
{
if (Reputation.Value > 0.0f)
{
return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation);
}
else
{
return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation);
}
}
else
{
if (Reputation.Value > 0.0f)
{
return MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation);
}
else
{
return MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation);
}
}
}
public static int GetExtraSpecialSalesCount()
{
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
@@ -1488,6 +1494,7 @@ namespace Barotrauma
{
var storeElement = new XElement("store",
new XAttribute("identifier", store.Identifier.Value),
new XAttribute(nameof(store.MerchantFaction), store.MerchantFaction),
new XAttribute("balance", store.Balance),
new XAttribute("pricemodifier", store.PriceModifier));
foreach (PurchasedItem item in store.Stock)
@@ -1532,6 +1539,7 @@ namespace Barotrauma
missionsElement.Add(new XElement("mission",
new XAttribute("prefabid", mission.Prefab.Identifier),
new XAttribute("destinationindex", destinationIndex),
new XAttribute(nameof(Mission.TimesAttempted), mission.TimesAttempted),
new XAttribute("origin", originIndex),
new XAttribute("selected", selectedMissions.Contains(mission))));
}
@@ -41,6 +41,11 @@ namespace Barotrauma
public bool IsEnterable { get; private set; }
/// <summary>
/// Can this location type be used in the random, non-campaign levels that don't take place in any specific zone
/// </summary>
public bool AllowInRandomLevels { get; private set; }
public bool UsePortraitInRandomLoadingScreens
{
get;
@@ -115,6 +120,7 @@ namespace Barotrauma
UsePortraitInRandomLoadingScreens = element.GetAttributeBool(nameof(UsePortraitInRandomLoadingScreens), true);
HasOutpost = element.GetAttributeBool("hasoutpost", true);
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
AllowInRandomLevels = element.GetAttributeBool(nameof(AllowInRandomLevels), true);
ShowSonarMarker = element.GetAttributeBool("showsonarmarker", true);
@@ -263,14 +269,31 @@ namespace Barotrauma
return names[rand.Next() % names.Length];
}
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false)
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false, Func<LocationType, bool> predicate = null)
{
Debug.Assert(Prefabs.Any(), "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
LocationType[] allowedLocationTypes =
Prefabs.Where(lt => (!zone.HasValue || lt.CommonnessPerZone.ContainsKey(zone.Value)) && (!requireOutpost || lt.HasOutpost))
Prefabs.Where(lt =>
(predicate == null || predicate(lt)) && IsValid(lt))
.OrderBy(p => p.UintIdentifier).ToArray();
bool IsValid(LocationType lt)
{
if (requireOutpost && !lt.HasOutpost) { return false; }
if (zone.HasValue)
{
if (!lt.CommonnessPerZone.ContainsKey(zone.Value)) { return false; }
}
//if zone is not defined, this is a "random" (non-campaign) level
//-> don't choose location types that aren't allowed in those
else if (!lt.AllowInRandomLevels)
{
return false;
}
return true;
}
if (allowedLocationTypes.Length == 0)
{
DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
@@ -4,7 +4,7 @@ using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Xml.Linq;
using Barotrauma.Extensions;
using static Barotrauma.LocationTypeChange;
namespace Barotrauma
{
@@ -58,7 +58,7 @@ namespace Barotrauma
public Requirement(XElement element, LocationTypeChange change)
{
RequiredLocations = element.GetAttributeIdentifierArray("requiredlocations", element.GetAttributeIdentifierArray("requiredadjacentlocations", Array.Empty<Identifier>())).ToImmutableArray();
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 1);
RequiredProximity = Math.Max(element.GetAttributeInt("requiredproximity", 1), 0);
ProximityProbabilityIncrease = element.GetAttributeFloat("proximityprobabilityincrease", 0.0f);
RequiredProximityForProbabilityIncrease = element.GetAttributeInt("requiredproximityforprobabilityincrease", -1);
RequireBeaconStation = element.GetAttributeBool("requirebeaconstation", false);
@@ -91,37 +91,30 @@ namespace Barotrauma
}
}
public bool AnyWithinDistance(Location startLocation, int distance)
{
return Map.LocationOrConnectionWithinDistance(
startLocation,
maxDistance: distance,
criteria: MatchesLocation,
connectionCriteria: MatchesConnection);
}
public bool MatchesLocation(Location location)
{
return RequiredLocations.Contains(location.Type.Identifier) && !location.IsCriticallyRadiated();
}
public bool AnyWithinDistance(Location location, int maxDistance, int currentDistance = 0, HashSet<Location> checkedLocations = null)
public bool MatchesConnection(LocationConnection connection)
{
if (currentDistance > maxDistance) { return false; }
if (currentDistance > 0 && MatchesLocation(location)) { return true; }
checkedLocations ??= new HashSet<Location>();
checkedLocations.Add(location);
foreach (var connection in location.Connections)
if (RequireBeaconStation && connection.LevelData.HasBeaconStation && connection.LevelData.IsBeaconActive)
{
if (RequireBeaconStation && connection.LevelData.HasBeaconStation && connection.LevelData.IsBeaconActive)
{
return true;
}
if (RequireHuntingGrounds && connection.LevelData.HasHuntingGrounds)
{
return true;
}
var otherLocation = connection.OtherLocation(location);
if (!checkedLocations.Contains(otherLocation))
{
if (AnyWithinDistance(otherLocation, maxDistance, currentDistance + 1, checkedLocations)) { return true; }
}
return true;
}
if (RequireHuntingGrounds && connection.LevelData.HasHuntingGrounds)
{
return true;
}
return false;
}
}
@@ -227,8 +220,9 @@ namespace Barotrauma
if (location.LocationTypeChangeCooldown > 0) { return 0.0f; }
if (location.IsGateBetweenBiomes) { return 0.0f; }
if (DisallowedAdjacentLocations.Any() &&
AnyWithinDistance(location, DisallowedProximity, (otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
if (DisallowedAdjacentLocations.Any() &&
Map.LocationOrConnectionWithinDistance(location, DisallowedProximity,
(otherLocation) => { return DisallowedAdjacentLocations.Contains(otherLocation.Type.Identifier); }))
{
return 0.0f;
}
@@ -247,7 +241,6 @@ namespace Barotrauma
probability *= requirement.Probability;
}
}
if (location.ProximityTimer.ContainsKey(requirement))
{
if (requirement.AnyWithinDistance(location, requirement.RequiredProximityForProbabilityIncrease))
@@ -266,25 +259,5 @@ namespace Barotrauma
return probability;
}
private bool AnyWithinDistance(Location location, int maxDistance, Func<Location, bool> predicate, int currentDistance = 0, HashSet<Location> checkedLocations = null)
{
if (currentDistance > maxDistance) { return false; }
if (currentDistance > 0 && predicate(location)) { return true; }
checkedLocations ??= new HashSet<Location>();
checkedLocations.Add(location);
foreach (var connection in location.Connections)
{
var otherLocation = connection.OtherLocation(location);
if (!checkedLocations.Contains(otherLocation))
{
if (AnyWithinDistance(otherLocation, maxDistance, predicate, currentDistance + 1, checkedLocations)) { return true; }
}
}
return false;
}
}
}
@@ -70,13 +70,13 @@ namespace Barotrauma
public List<Location> Locations { get; private set; }
private readonly List<Location> locationsDiscovered = new List<Location>();
private readonly List<Location> outpostsVisited = new List<Location>();
private readonly List<Location> locationsVisited = new List<Location>();
public List<LocationConnection> Connections { get; private set; }
public Radiation Radiation;
private bool wasLocationDiscoveryOrderTracked = true;
private bool trackedLocationDiscoveryAndVisitOrder = true;
public Map(CampaignSettings settings)
{
@@ -939,6 +939,12 @@ namespace Barotrauma
public void MoveToNextLocation()
{
if (SelectedLocation == null && Level.Loaded?.EndLocation != null)
{
//force the location at the end of the level to be selected, even if it's been deselect on the map
//(e.g. due to returning to an empty location the beginning of the level during the round)
SelectLocation(Level.Loaded.EndLocation);
}
if (SelectedConnection == null)
{
if (!endLocations.Contains(CurrentLocation))
@@ -1043,12 +1049,24 @@ namespace Barotrauma
Location prevSelected = SelectedLocation;
SelectedLocation = Locations[index];
var currentDisplayLocation = GameMain.GameSession?.Campaign?.GetCurrentDisplayLocation();
SelectedConnection =
Connections.Find(c => c.Locations.Contains(currentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
if (currentDisplayLocation == SelectedLocation)
{
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
}
else
{
SelectedConnection =
Connections.Find(c => c.Locations.Contains(currentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
}
if (SelectedConnection?.Locked ?? false)
{
DebugConsole.ThrowError("A locked connection was selected - this should not be possible.\n" + Environment.StackTrace.CleanupStackTrace());
string errorMsg =
$"A locked connection was selected ({SelectedConnection.Locations[0].Name} -> {SelectedConnection.Locations[1].Name}." +
$" Current location: {CurrentLocation}, current display location: {currentDisplayLocation}).\n"
+ Environment.StackTrace.CleanupStackTrace();
GameAnalyticsManager.AddErrorEventOnce("MapSelectLocation:LockedConnectionSelected", GameAnalyticsManager.ErrorSeverity.Error, errorMsg);
DebugConsole.ThrowError(errorMsg);
}
if (prevSelected != SelectedLocation)
{
@@ -1266,51 +1284,6 @@ namespace Barotrauma
return false;
}
public int DistanceToClosestLocationWithOutpost(Location startingLocation, out Location endingLocation)
{
if (startingLocation.Type.HasOutpost)
{
endingLocation = startingLocation;
return 0;
}
int iterations = 0;
int distance = 0;
endingLocation = null;
List<Location> testedLocations = new List<Location>();
List<Location> locationsToTest = new List<Location> { startingLocation };
while (endingLocation == null && iterations < 100)
{
List<Location> nextTestingBatch = new List<Location>();
for (int i = 0; i < locationsToTest.Count; i++)
{
Location testLocation = locationsToTest[i];
for (int j = 0; j < testLocation.Connections.Count; j++)
{
Location potentialOutpost = testLocation.Connections[j].OtherLocation(testLocation);
if (potentialOutpost.Type.HasOutpost)
{
distance = iterations + 1;
endingLocation = potentialOutpost;
}
else if (!testedLocations.Contains(potentialOutpost))
{
nextTestingBatch.Add(potentialOutpost);
}
}
testedLocations.Add(testLocation);
}
locationsToTest = nextTestingBatch;
iterations++;
}
return distance;
}
private bool ChangeLocationType(CampaignMode campaign, Location location, LocationTypeChange change)
{
string prevName = location.Name;
@@ -1321,6 +1294,8 @@ namespace Barotrauma
return false;
}
if (location.LocationTypeChangesBlocked) { return false; }
if (newType.OutpostTeam != location.Type.OutpostTeam ||
newType.HasOutpost != location.Type.HasOutpost)
{
@@ -1338,6 +1313,50 @@ namespace Barotrauma
return true;
}
public static bool LocationOrConnectionWithinDistance(Location startLocation, int maxDistance, Func<Location, bool> criteria, Func<LocationConnection, bool> connectionCriteria = null)
{
return GetDistanceToClosestLocationOrConnection(startLocation, maxDistance, criteria, connectionCriteria) <= maxDistance;
}
/// <summary>
/// Get the shortest distance from the start location to another location that satisfies the specified criteria.
/// </summary>
/// <returns>The distance to a matching location, or int.MaxValue if none are found.</returns>
public static int GetDistanceToClosestLocationOrConnection(Location startLocation, int maxDistance, Func<Location, bool> criteria, Func<LocationConnection, bool> connectionCriteria = null)
{
int distance = 0;
var locationsToTest = new List<Location>() { startLocation };
var nextBatchToTest = new HashSet<Location>();
var checkedLocations = new HashSet<Location>();
while (locationsToTest.Any())
{
foreach (var location in locationsToTest)
{
checkedLocations.Add(location);
if (criteria(location)) { return distance; }
foreach (var connection in location.Connections)
{
if (connectionCriteria != null && connectionCriteria(connection))
{
return distance;
}
var otherLocation = connection.OtherLocation(location);
if (!checkedLocations.Contains(otherLocation))
{
nextBatchToTest.Add(otherLocation);
}
}
if (distance > maxDistance) { return int.MaxValue; }
}
distance++;
locationsToTest.Clear();
locationsToTest.AddRange(nextBatchToTest);
nextBatchToTest.Clear();
}
return int.MaxValue;
}
partial void ChangeLocationTypeProjSpecific(Location location, string prevName, LocationTypeChange change);
partial void ClearAnimQueue();
@@ -1356,29 +1375,38 @@ namespace Barotrauma
public void Visit(Location location)
{
if (location is null) { return; }
if (!location.HasOutpost()) { return; }
if (outpostsVisited.Contains(location)) { return; }
outpostsVisited.Add(location);
if (locationsVisited.Contains(location)) { return; }
locationsVisited.Add(location);
RemoveFogOfWarProjSpecific(location);
}
public void ClearLocationHistory()
{
locationsDiscovered.Clear();
outpostsVisited.Clear();
locationsVisited.Clear();
}
public int? GetDiscoveryIndex(Location location)
{
if (!wasLocationDiscoveryOrderTracked) { return null; }
if (!trackedLocationDiscoveryAndVisitOrder) { return null; }
if (location is null) { return -1; }
return locationsDiscovered.IndexOf(location);
}
public int? GetVisitIndex(Location location)
public int? GetVisitIndex(Location location, bool includeLocationsWithoutOutpost = false)
{
if (!wasLocationDiscoveryOrderTracked) { return null; }
if (!trackedLocationDiscoveryAndVisitOrder) { return null; }
if (location is null) { return -1; }
return outpostsVisited.IndexOf(location);
int index = locationsVisited.IndexOf(location);
if (includeLocationsWithoutOutpost) { return index; }
int noOutpostLocations = 0;
for (int i = 0; i < index; i++)
{
if (locationsVisited[i] is not Location l) { continue; }
if (l.HasOutpost()) { continue; }
noOutpostLocations++;
}
return index - noOutpostLocations;
}
public bool IsDiscovered(Location location)
@@ -1387,6 +1415,14 @@ namespace Barotrauma
return locationsDiscovered.Contains(location);
}
public bool IsVisited(Location location)
{
if (location is null) { return false; }
return locationsVisited.Contains(location);
}
partial void RemoveFogOfWarProjSpecific(Location location);
/// <summary>
/// Load a previously saved map from an xml element
/// </summary>
@@ -1431,11 +1467,13 @@ namespace Barotrauma
}
location.LoadLocationTypeChange(subElement);
// Backwards compatibility
// Backwards compatibility: if the discovery status is defined in the location element,
// the game was saved using when the discovery order still wasn't being tracked
if (subElement.GetAttributeBool("discovered", false))
{
Discover(location);
wasLocationDiscoveryOrderTracked = false;
Visit(location);
trackedLocationDiscoveryAndVisitOrder = false;
}
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
@@ -1472,32 +1510,45 @@ namespace Barotrauma
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
break;
case "discovered":
bool trackedVisitedEmptyLocations = subElement.GetAttributeBool("trackedvisitedemptylocations", false);
foreach (var childElement in subElement.GetChildElements("location"))
{
int index = childElement.GetAttributeInt("i", -1);
if (index < 0) { continue; }
if (Locations[index] is not Location l) { continue; }
Discover(l);
if (GetLocation(childElement) is Location l)
{
Discover(l);
if (!trackedVisitedEmptyLocations)
{
if (!l.HasOutpost())
{
Visit(l);
}
trackedLocationDiscoveryAndVisitOrder = false;
}
}
}
break;
case "visited":
foreach (var childElement in subElement.GetChildElements("location"))
{
int index = childElement.GetAttributeInt("i", -1);
if (index < 0) { continue; }
if (Locations[index] is not Location l) { continue; }
Visit(l);
if (GetLocation(childElement) is Location l)
{
Visit(l);
}
}
break;
}
Location GetLocation(XElement element)
{
int index = element.GetAttributeInt("i", -1);
if (index < 0) { return null; }
return Locations[index];
}
}
void Discover(Location location)
{
this.Discover(location, checkTalents: false);
#if CLIENT
RemoveFogOfWar(location);
#endif
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
{
furthestDiscoveredLocation = location;
@@ -1509,9 +1560,6 @@ namespace Barotrauma
location?.InstantiateLoadedMissions(this);
}
#if RELEASE
TODO: MAKE SURE THE VERSION NUMBER BELOW IS CORRECT FOR THE FULL RELEASE (OR WHICHEVER UPDATE WE ADD THE FACTIONS IN)
#endif
//backwards compatibility:
//if the save is from a version prior to the addition of faction-specific outposts, assign factions
if (version < new Version(1, 0) && Locations.None(l => l.Faction != null || l.SecondaryFaction != null))
@@ -1599,7 +1647,8 @@ namespace Barotrauma
if (locationsDiscovered.Any())
{
var discoveryElement = new XElement("discovered");
var discoveryElement = new XElement("discovered",
new XAttribute("trackedvisitedemptylocations", true));
foreach (Location location in locationsDiscovered)
{
int index = Locations.IndexOf(location);
@@ -1609,10 +1658,10 @@ namespace Barotrauma
mapElement.Add(discoveryElement);
}
if (outpostsVisited.Any())
if (locationsVisited.Any())
{
var visitElement = new XElement("visited");
foreach (Location location in outpostsVisited)
foreach (Location location in locationsVisited)
{
int index = Locations.IndexOf(location);
var locationElement = new XElement("location", new XAttribute("i", index));