Faction Test v1.0.1.0
This commit is contained in:
@@ -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));
|
||||
|
||||
Reference in New Issue
Block a user