Faction Test 100.4.0.0
This commit is contained in:
@@ -61,7 +61,7 @@ namespace Barotrauma
|
||||
|
||||
private LocationType addInitialMissionsForType;
|
||||
|
||||
public bool Discovered { get; private set; }
|
||||
public bool Discovered => GameMain.GameSession?.Map?.IsDiscovered(this) ?? false;
|
||||
|
||||
public readonly Dictionary<LocationTypeChange.Requirement, int> ProximityTimer = new Dictionary<LocationTypeChange.Requirement, int>();
|
||||
public (LocationTypeChange typeChange, int delay, MissionPrefab parentMission)? PendingLocationTypeChange;
|
||||
@@ -83,7 +83,11 @@ namespace Barotrauma
|
||||
|
||||
public int PortraitId { get; private set; }
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
public Faction Faction { get; set; }
|
||||
|
||||
public Faction SecondaryFaction { get; set; }
|
||||
|
||||
public Reputation Reputation => Faction?.Reputation;
|
||||
|
||||
public int TurnsInRadiation { get; set; }
|
||||
|
||||
@@ -135,7 +139,7 @@ namespace Barotrauma
|
||||
foreach (var stockElement in storeElement.GetChildElements("stock"))
|
||||
{
|
||||
var identifier = stockElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
if (identifier.IsEmpty || !(ItemPrefab.FindByIdentifier(identifier) is ItemPrefab prefab)) { continue; }
|
||||
if (identifier.IsEmpty || ItemPrefab.FindByIdentifier(identifier) is not ItemPrefab prefab) { continue; }
|
||||
int qty = stockElement.GetAttributeInt("qty", 0);
|
||||
if (qty < 1) { continue; }
|
||||
Stock.Add(new PurchasedItem(prefab, qty, buyer: null));
|
||||
@@ -157,7 +161,7 @@ namespace Barotrauma
|
||||
foreach (var childElement in element.GetChildElements("item"))
|
||||
{
|
||||
var id = childElement.GetAttributeIdentifier("id", Identifier.Empty);
|
||||
if (id.IsEmpty || !(ItemPrefab.FindByIdentifier(id) is ItemPrefab prefab)) { continue; }
|
||||
if (id.IsEmpty || ItemPrefab.FindByIdentifier(id) is not ItemPrefab prefab) { continue; }
|
||||
specials.Add(prefab);
|
||||
}
|
||||
return specials;
|
||||
@@ -240,7 +244,7 @@ namespace Barotrauma
|
||||
availableStock.Add(stockItem.ItemPrefab, weight);
|
||||
}
|
||||
DailySpecials.Clear();
|
||||
int extraSpecialSalesCount = Location.GetExtraSpecialSalesCount();
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
for (int i = 0; i < Location.DailySpecialsCount + extraSpecialSalesCount; i++)
|
||||
{
|
||||
if (availableStock.None()) { break; }
|
||||
@@ -283,6 +287,18 @@ namespace Barotrauma
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(true);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
if (Location.Faction is { } faction && Faction.GetPlayerAffiliationStatus(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)));
|
||||
}
|
||||
price *= 1f - characters.Max(static c => c.GetStatValue(StatTypes.StoreBuyMultiplier, includeSaved: false));
|
||||
price *= 1f - characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreBuyMultiplier, tag)));
|
||||
}
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -303,6 +319,14 @@ namespace Barotrauma
|
||||
}
|
||||
// Adjust by current location reputation
|
||||
price *= Location.GetStoreReputationModifier(false);
|
||||
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (characters.Any())
|
||||
{
|
||||
price *= 1f + characters.Max(static c => c.GetStatValue(StatTypes.StoreSellMultiplier));
|
||||
price *= 1f + characters.Max(c => item.Tags.Sum(tag => c.Info.GetSavedStatValue(StatTypes.StoreSellMultiplier, tag)));
|
||||
}
|
||||
|
||||
// Price should never go below 1 mk
|
||||
return Math.Max((int)price, 1);
|
||||
}
|
||||
@@ -465,7 +489,7 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Create a location from save data
|
||||
/// </summary>
|
||||
public Location(XElement element)
|
||||
public Location(CampaignMode campaign, XElement element)
|
||||
{
|
||||
Identifier locationTypeId = element.GetAttributeIdentifier("type", "");
|
||||
bool typeNotFound = GetTypeOrFallback(locationTypeId, out LocationType type);
|
||||
@@ -478,13 +502,23 @@ namespace Barotrauma
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
StepsSinceSpecialsUpdated = element.GetAttributeInt("stepssincespecialsupdated", 0);
|
||||
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
IsGateBetweenBiomes = element.GetAttributeBool("isgatebetweenbiomes", false);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
TurnsInRadiation = element.GetAttributeInt(nameof(TurnsInRadiation).ToLower(), 0);
|
||||
StepsSinceSpecialsUpdated = element.GetAttributeInt("stepssincespecialsupdated", 0);
|
||||
|
||||
var factionIdentifier = element.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
if (!factionIdentifier.IsEmpty)
|
||||
{
|
||||
Faction = campaign.Factions.Find(f => f.Prefab.Identifier == factionIdentifier);
|
||||
}
|
||||
var secondaryFactionIdentifier = element.GetAttributeIdentifier("secondaryfaction", Identifier.Empty);
|
||||
if (!secondaryFactionIdentifier.IsEmpty)
|
||||
{
|
||||
SecondaryFaction = campaign.Factions.Find(f => f.Prefab.Identifier == secondaryFactionIdentifier);
|
||||
}
|
||||
Identifier biomeId = element.GetAttributeIdentifier("biome", Identifier.Empty);
|
||||
if (biomeId != Identifier.Empty)
|
||||
{
|
||||
@@ -641,7 +675,7 @@ namespace Barotrauma
|
||||
return new Location(position, zone, rand, requireOutpost, forceLocationType, existingLocations);
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
public void ChangeType(CampaignMode campaign, LocationType newType)
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
@@ -656,34 +690,49 @@ namespace Barotrauma
|
||||
Type = newType;
|
||||
Name = Type.NameFormats == null || !Type.NameFormats.Any() ? baseName : Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
if (Type.HasOutpost)
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandomUnsynced());
|
||||
if (Faction == null)
|
||||
{
|
||||
Faction = campaign.GetRandomFaction(Rand.RandSync.Unsynced);
|
||||
}
|
||||
if (SecondaryFaction == null)
|
||||
{
|
||||
SecondaryFaction = campaign.GetRandomSecondaryFaction(Rand.RandSync.Unsynced);
|
||||
}
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
else
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandomUnsynced());
|
||||
Faction = null;
|
||||
SecondaryFaction = null;
|
||||
}
|
||||
|
||||
UnlockInitialMissions(Rand.RandSync.Unsynced);
|
||||
|
||||
CreateStores(force: true);
|
||||
}
|
||||
|
||||
public void UnlockInitialMissions()
|
||||
public void UnlockInitialMissions(Rand.RandSync randSync = Rand.RandSync.ServerAndClient)
|
||||
{
|
||||
if (Type.MissionIdentifiers.Any())
|
||||
{
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(Rand.RandSync.ServerAndClient));
|
||||
UnlockMissionByIdentifier(Type.MissionIdentifiers.GetRandom(randSync));
|
||||
}
|
||||
if (Type.MissionTags.Any())
|
||||
{
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom(Rand.RandSync.ServerAndClient));
|
||||
UnlockMissionByTag(Type.MissionTags.GetRandom(randSync));
|
||||
}
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
|
||||
{
|
||||
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();
|
||||
@@ -693,7 +742,12 @@ namespace Barotrauma
|
||||
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();
|
||||
@@ -703,6 +757,7 @@ namespace Barotrauma
|
||||
public Mission UnlockMissionByIdentifier(Identifier identifier)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab.Identifier == identifier)) { return null; }
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
|
||||
var missionPrefab = MissionPrefab.Prefabs.Find(mp => mp.Identifier == identifier);
|
||||
if (missionPrefab == null)
|
||||
@@ -717,6 +772,10 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
@@ -728,7 +787,8 @@ namespace Barotrauma
|
||||
|
||||
public Mission UnlockMissionByTag(Identifier tag)
|
||||
{
|
||||
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Any(t => t == tag));
|
||||
if (AvailableMissions.Any(m => !m.Prefab.AllowOtherMissionsInLevel)) { return null; }
|
||||
var matchingMissions = MissionPrefab.Prefabs.Where(mp => mp.Tags.Contains(tag));
|
||||
if (!matchingMissions.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions found.");
|
||||
@@ -750,6 +810,10 @@ namespace Barotrauma
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (!mission.Prefab.AllowOtherMissionsInLevel)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
@@ -960,12 +1024,22 @@ namespace Barotrauma
|
||||
|
||||
private string RandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (!type.ForceLocationName.IsNullOrEmpty())
|
||||
{
|
||||
baseName = type.ForceLocationName.Value;
|
||||
return baseName;
|
||||
}
|
||||
baseName = type.GetRandomName(rand, existingLocations);
|
||||
if (type.NameFormats == null || !type.NameFormats.Any()) { return baseName; }
|
||||
nameFormatIndex = rand.Next() % type.NameFormats.Count;
|
||||
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
|
||||
}
|
||||
|
||||
public void ForceName(string name)
|
||||
{
|
||||
baseName = Name = name;
|
||||
}
|
||||
|
||||
public void LoadStores(XElement locationElement)
|
||||
{
|
||||
UpdateStoreIdentifiers();
|
||||
@@ -1050,13 +1124,21 @@ namespace Barotrauma
|
||||
|
||||
public int GetAdjustedMechanicalCost(int cost)
|
||||
{
|
||||
float discount = Reputation.Value / Reputation.MaxReputation * (MechanicalMaxDiscountPercentage / 100.0f);
|
||||
return (int) Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
|
||||
float discount = 0.0f;
|
||||
if (Reputation != null)
|
||||
{
|
||||
discount = Reputation.Value / Reputation.MaxReputation * (MechanicalMaxDiscountPercentage / 100.0f);
|
||||
}
|
||||
return (int)Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
|
||||
}
|
||||
|
||||
public int GetAdjustedHealCost(int cost)
|
||||
{
|
||||
float discount = Reputation.Value / Reputation.MaxReputation * (HealMaxDiscountPercentage / 100.0f);
|
||||
float discount = 0.0f;
|
||||
if (Reputation != null)
|
||||
{
|
||||
discount = Reputation.Value / Reputation.MaxReputation * (HealMaxDiscountPercentage / 100.0f);
|
||||
}
|
||||
return (int) Math.Ceiling((1.0f - discount) * cost * PriceMultiplier);
|
||||
}
|
||||
|
||||
@@ -1125,7 +1207,7 @@ namespace Barotrauma
|
||||
public void UpdateStores()
|
||||
{
|
||||
// In multiplayer, stores should be updated by the server and loaded from save data by clients
|
||||
if (GameMain.NetworkMember != null && GameMain.NetworkMember.IsClient) { return; }
|
||||
if (GameMain.NetworkMember is { IsClient: true }) { return; }
|
||||
if (Stores == null)
|
||||
{
|
||||
CreateStores();
|
||||
@@ -1167,13 +1249,10 @@ namespace Barotrauma
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
store.Stock.Clear();
|
||||
store.Stock.AddRange(stock);
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
if (++StepsSinceSpecialsUpdated >= SpecialsUpdateInterval || store.DailySpecials.Count() != DailySpecialsCount + extraSpecialSalesCount)
|
||||
{
|
||||
store.GenerateSpecials();
|
||||
}
|
||||
store.GeneratePriceModifier();
|
||||
}
|
||||
|
||||
StepsSinceSpecialsUpdated++;
|
||||
foreach (var identifier in storesToRemove)
|
||||
{
|
||||
Stores.Remove(identifier);
|
||||
@@ -1184,6 +1263,20 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateSpecials()
|
||||
{
|
||||
if (GameMain.NetworkMember is { IsClient: true } || Stores is null) { return; }
|
||||
|
||||
int extraSpecialSalesCount = GetExtraSpecialSalesCount();
|
||||
|
||||
foreach (StoreInfo store in Stores.Values)
|
||||
{
|
||||
if (StepsSinceSpecialsUpdated < SpecialsUpdateInterval && store.DailySpecials.Count == DailySpecialsCount + extraSpecialSalesCount) { continue; }
|
||||
|
||||
store.GenerateSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateStoreIdentifiers()
|
||||
{
|
||||
StoreIdentifiers.Clear();
|
||||
@@ -1231,6 +1324,7 @@ namespace Barotrauma
|
||||
|
||||
public float GetStoreReputationModifier(bool buying)
|
||||
{
|
||||
if (Reputation == null) { return 1.0f; }
|
||||
if (buying)
|
||||
{
|
||||
if (Reputation.Value > 0.0f)
|
||||
@@ -1255,35 +1349,37 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public int GetExtraSpecialSalesCount()
|
||||
public static int GetExtraSpecialSalesCount()
|
||||
{
|
||||
var characters = GameSession.GetSessionCrewCharacters(CharacterType.Both);
|
||||
if (!characters.Any()) { return 0; }
|
||||
return characters.Sum(c => (int)c.GetStatValue(StatTypes.ExtraSpecialSalesCount));
|
||||
}
|
||||
|
||||
public void Discover(bool checkTalents = true)
|
||||
public int HighestSubmarineTierAvailable(SubmarineClass submarineClass)
|
||||
{
|
||||
if (Discovered) { return; }
|
||||
Discovered = true;
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new AbilityLocation(this)));
|
||||
}
|
||||
if (!HasOutpost()) { return 0; }
|
||||
return Biome?.HighestSubmarineTierAvailable(submarineClass, Type.Identifier) ?? SubmarineInfo.HighestTier;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
public int HighestSubmarineTierAvailable() => HighestSubmarineTierAvailable(SubmarineClass.Undefined);
|
||||
|
||||
public bool IsSubmarineAvailable(SubmarineInfo info)
|
||||
{
|
||||
return Biome?.IsSubmarineAvailable(info, Type.Identifier) ?? true;
|
||||
}
|
||||
|
||||
public void Reset(CampaignMode campaign)
|
||||
{
|
||||
if (Type != OriginalType)
|
||||
{
|
||||
ChangeType(OriginalType);
|
||||
ChangeType(campaign, OriginalType);
|
||||
PendingLocationTypeChange = null;
|
||||
}
|
||||
CreateStores(force: true);
|
||||
ClearMissions();
|
||||
LevelData?.EventHistory?.Clear();
|
||||
UnlockInitialMissions();
|
||||
Discovered = false;
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
@@ -1294,7 +1390,6 @@ namespace Barotrauma
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("biome", Biome?.Identifier.Value ?? string.Empty),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("isgatebetweenbiomes", IsGateBetweenBiomes),
|
||||
@@ -1302,6 +1397,16 @@ namespace Barotrauma
|
||||
new XAttribute("timesincelasttypechange", TimeSinceLastTypeChange),
|
||||
new XAttribute(nameof(TurnsInRadiation).ToLower(), TurnsInRadiation),
|
||||
new XAttribute("stepssincespecialsupdated", StepsSinceSpecialsUpdated));
|
||||
|
||||
if (Faction != null)
|
||||
{
|
||||
locationElement.Add(new XAttribute("faction", Faction.Prefab.Identifier));
|
||||
}
|
||||
if (SecondaryFaction != null)
|
||||
{
|
||||
locationElement.Add(new XAttribute("secondaryfaction", SecondaryFaction.Prefab.Identifier));
|
||||
}
|
||||
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
for (int i = 0; i < Type.CanChangeTo.Count; i++)
|
||||
@@ -1423,7 +1528,7 @@ namespace Barotrauma
|
||||
HireManager?.Remove();
|
||||
}
|
||||
|
||||
class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
public class AbilityLocation : AbilityObject, IAbilityLocation
|
||||
{
|
||||
public AbilityLocation(Location location)
|
||||
{
|
||||
|
||||
@@ -24,6 +24,9 @@ namespace Barotrauma
|
||||
public readonly Dictionary<int, int> MinCountPerZone = new Dictionary<int, int>();
|
||||
|
||||
public readonly LocalizedString Name;
|
||||
public readonly LocalizedString Description;
|
||||
|
||||
public readonly LocalizedString ForceLocationName;
|
||||
|
||||
public readonly float BeaconStationChance;
|
||||
|
||||
@@ -38,7 +41,13 @@ namespace Barotrauma
|
||||
|
||||
public bool IsEnterable { get; private set; }
|
||||
|
||||
public bool UseInMainMenu
|
||||
public bool UsePortraitInMainMenu
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public bool UsePortraitInRandomLoadingScreens
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
@@ -70,6 +79,13 @@ namespace Barotrauma
|
||||
public Sprite Sprite { get; private set; }
|
||||
public Sprite RadiationSprite { get; }
|
||||
|
||||
private readonly Identifier forceOutpostGenerationParamsIdentifier;
|
||||
|
||||
/// <summary>
|
||||
/// If set to true, only event sets that explicitly define this location type in <see cref="EventSet.LocationTypeIdentifiers"/> can be selected at this location. Defaults to false.
|
||||
/// </summary>
|
||||
public bool IgnoreGenericEvents { get; }
|
||||
|
||||
public Color SpriteColor
|
||||
{
|
||||
get;
|
||||
@@ -88,6 +104,8 @@ namespace Barotrauma
|
||||
public int DailySpecialsCount { get; } = 1;
|
||||
public int RequestedGoodsCount { get; } = 1;
|
||||
|
||||
public readonly bool ShowSonarMarker = true;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"LocationType (" + Identifier + ")";
|
||||
@@ -96,13 +114,17 @@ namespace Barotrauma
|
||||
public LocationType(ContentXElement element, LocationTypesFile file) : base(file, element.GetAttributeIdentifier("identifier", element.Name.LocalName))
|
||||
{
|
||||
Name = TextManager.Get("LocationName." + Identifier, "unknown");
|
||||
Description = TextManager.Get("LocationDescription." + Identifier, "");
|
||||
|
||||
BeaconStationChance = element.GetAttributeFloat("beaconstationchance", 0.0f);
|
||||
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
UsePortraitInMainMenu = element.GetAttributeBool(nameof(UsePortraitInMainMenu), element.GetAttributeBool("useinmainmenu", false));
|
||||
UsePortraitInRandomLoadingScreens = element.GetAttributeBool(nameof(UsePortraitInRandomLoadingScreens), true);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
IsEnterable = element.GetAttributeBool("isenterable", HasOutpost);
|
||||
|
||||
ShowSonarMarker = element.GetAttributeBool("showsonarmarker", true);
|
||||
|
||||
MissionIdentifiers = element.GetAttributeIdentifierArray("missionidentifiers", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
MissionTags = element.GetAttributeIdentifierArray("missiontags", Array.Empty<Identifier>()).ToImmutableArray();
|
||||
|
||||
@@ -110,26 +132,37 @@ namespace Barotrauma
|
||||
|
||||
ReplaceInRadiation = element.GetAttributeIdentifier(nameof(ReplaceInRadiation), Identifier.Empty);
|
||||
|
||||
forceOutpostGenerationParamsIdentifier = element.GetAttributeIdentifier("forceoutpostgenerationparams", Identifier.Empty);
|
||||
|
||||
IgnoreGenericEvents = element.GetAttributeBool(nameof(IgnoreGenericEvents), false);
|
||||
|
||||
string teamStr = element.GetAttributeString("outpostteam", "FriendlyNPC");
|
||||
Enum.TryParse(teamStr, out OutpostTeam);
|
||||
|
||||
string[] rawNamePaths = element.GetAttributeStringArray("namefile", new string[] { "Content/Map/locationNames.txt" });
|
||||
names = new List<string>();
|
||||
foreach (string rawPath in rawNamePaths)
|
||||
if (element.GetAttribute("name") != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
|
||||
names.AddRange(File.ReadAllLines(path.Value).ToList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
|
||||
}
|
||||
ForceLocationName = TextManager.Get(element.GetAttributeString("name", string.Empty));
|
||||
}
|
||||
if (!names.Any())
|
||||
else
|
||||
{
|
||||
names.Add("ERROR: No names found");
|
||||
string[] rawNamePaths = element.GetAttributeStringArray("namefile", new string[] { "Content/Map/locationNames.txt" });
|
||||
names = new List<string>();
|
||||
foreach (string rawPath in rawNamePaths)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = ContentPath.FromRaw(element.ContentPackage, rawPath.Trim());
|
||||
names.AddRange(File.ReadAllLines(path.Value).ToList());
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to read name file \"rawPath\" for location type \"{Identifier}\"!", e);
|
||||
}
|
||||
}
|
||||
if (!names.Any())
|
||||
{
|
||||
names.Add("ERROR: No names found");
|
||||
}
|
||||
}
|
||||
|
||||
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", Array.Empty<string>());
|
||||
@@ -261,6 +294,15 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public OutpostGenerationParams GetForcedOutpostGenerationParams()
|
||||
{
|
||||
if (OutpostGenerationParams.OutpostParams.TryGet(forceOutpostGenerationParamsIdentifier, out var parameters))
|
||||
{
|
||||
return parameters;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public override void Dispose() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,24 +141,25 @@ namespace Barotrauma
|
||||
|
||||
private readonly bool requireChangeMessages;
|
||||
private readonly string messageTag;
|
||||
private ImmutableArray<string>? messages = null;
|
||||
public IReadOnlyList<string> Messages
|
||||
{
|
||||
get
|
||||
{
|
||||
if (!messages.HasValue)
|
||||
{
|
||||
messages = TextManager.GetAll(messageTag).ToImmutableArray();
|
||||
if (messages.Value.None())
|
||||
{
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError($"No messages defined for the location type change {CurrentType} -> {ChangeToType}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return messages.Value;
|
||||
public IReadOnlyList<string> GetMessages(Faction faction)
|
||||
{
|
||||
if (faction != null && TextManager.ContainsTag(messageTag + "." + faction.Prefab.Identifier))
|
||||
{
|
||||
return TextManager.GetAll(messageTag + "." + faction.Prefab.Identifier).ToImmutableArray();
|
||||
}
|
||||
|
||||
if (TextManager.ContainsTag(messageTag))
|
||||
{
|
||||
return TextManager.GetAll(messageTag).ToImmutableArray();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requireChangeMessages)
|
||||
{
|
||||
DebugConsole.ThrowError($"No messages defined for the location type change {CurrentType} -> {ChangeToType}");
|
||||
}
|
||||
return Enumerable.Empty<string>().ToImmutableArray();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,8 @@ namespace Barotrauma
|
||||
/// </summary>
|
||||
public readonly NamedEvent<LocationChangeInfo> OnLocationChanged = new NamedEvent<LocationChangeInfo>();
|
||||
|
||||
public Location EndLocation { get; private set; }
|
||||
private List<Location> endLocations = new List<Location>();
|
||||
public IReadOnlyList<Location> EndLocations { get { return endLocations; } }
|
||||
|
||||
public Location StartLocation { get; private set; }
|
||||
|
||||
@@ -68,10 +69,15 @@ 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>();
|
||||
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Radiation Radiation;
|
||||
|
||||
private bool wasLocationDiscoveryOrderTracked = true;
|
||||
|
||||
public Map(CampaignSettings settings)
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
@@ -112,7 +118,7 @@ namespace Barotrauma
|
||||
Locations.Add(null);
|
||||
}
|
||||
lairsFound |= subElement.GetAttributeString("type", "").Equals("lair", StringComparison.OrdinalIgnoreCase);
|
||||
Locations[i] = new Location(subElement);
|
||||
Locations[i] = new Location(campaign, subElement);
|
||||
break;
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement)
|
||||
@@ -122,11 +128,6 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}".ToIdentifier(), -100, 100, Rand.Range(-10, 11, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
|
||||
List<XElement> connectionElements = new List<XElement>();
|
||||
foreach (var subElement in element.Elements())
|
||||
@@ -182,23 +183,73 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
}
|
||||
int endLocationindex = element.GetAttributeInt("endlocation", -1);
|
||||
if (endLocationindex > 0 && endLocationindex < Locations.Count)
|
||||
|
||||
if (element.GetAttribute("endlocation") != null)
|
||||
{
|
||||
EndLocation = Locations[endLocationindex];
|
||||
//backwards compatibility
|
||||
int endLocationIndex = element.GetAttributeInt("endlocation", -1);
|
||||
if (endLocationIndex > 0 && endLocationIndex < Locations.Count)
|
||||
{
|
||||
endLocations.Add(Locations[endLocationIndex]);
|
||||
Locations[endLocationIndex].LevelData.ReassignGenerationParams(Seed);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationIndex}, location count: {Locations.Count}).");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationindex}, location count: {Locations.Count}).");
|
||||
foreach (Location location in Locations)
|
||||
int[] endLocationindices = element.GetAttributeIntArray("endlocations", Array.Empty<int>());
|
||||
foreach (int endLocationIndex in endLocationindices)
|
||||
{
|
||||
if (EndLocation == null || location.MapPosition.X > EndLocation.MapPosition.X)
|
||||
if (endLocationIndex > 0 && endLocationIndex < Locations.Count)
|
||||
{
|
||||
EndLocation = location;
|
||||
endLocations.Add(Locations[endLocationIndex]);
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. End location index out of bounds (index: {endLocationIndex}, location count: {Locations.Count}).");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!endLocations.Any())
|
||||
{
|
||||
DebugConsole.AddWarning($"Error while loading the map. No end location(s) found. Choosing the rightmost location as the end location...");
|
||||
Location endLocation = null;
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (endLocation == null || location.MapPosition.X > endLocation.MapPosition.X)
|
||||
{
|
||||
endLocation = location;
|
||||
}
|
||||
}
|
||||
endLocations.Add(endLocation);
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Assert(endLocations.First().Biome != null, "End location biome was null.");
|
||||
System.Diagnostics.Debug.Assert(endLocations.First().Biome.IsEndBiome, "The biome of the end location isn't the end biome.");
|
||||
|
||||
//backwards compatibility (or support for loading maps created with mods that modify the end biome setup):
|
||||
//if there's too few end locations, create more
|
||||
int missingOutpostCount = endLocations.First().Biome.EndBiomeLocationCount - endLocations.Count;
|
||||
|
||||
Location firstEndLocation = EndLocations[0];
|
||||
for (int i = 0; i < missingOutpostCount; i++)
|
||||
{
|
||||
Vector2 mapPos = new Vector2(
|
||||
MathHelper.Lerp(firstEndLocation.MapPosition.X, Width, MathHelper.Lerp(0.2f, 0.8f, i / (float)missingOutpostCount)),
|
||||
Height * MathHelper.Lerp(0.2f, 1.0f, (float)rand.NextDouble()));
|
||||
var newEndLocation = new Location(mapPos, generationParams.DifficultyZones, rand, forceLocationType: firstEndLocation.Type, existingLocations: Locations)
|
||||
{
|
||||
Biome = endLocations.First().Biome
|
||||
};
|
||||
newEndLocation.LevelData = new LevelData(newEndLocation, difficulty: 100.0f);
|
||||
Locations.Add(newEndLocation);
|
||||
endLocations.Add(newEndLocation);
|
||||
}
|
||||
|
||||
//backwards compatibility: if the map contained the now-removed lairs and has no hunting grounds, create some hunting grounds
|
||||
if (lairsFound && !Connections.Any(c => c.LevelData.HasHuntingGrounds))
|
||||
{
|
||||
@@ -209,6 +260,17 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var endLocation in EndLocations)
|
||||
{
|
||||
if (endLocation.Type?.ForceLocationName != null &&
|
||||
!endLocation.Type.ForceLocationName.IsNullOrEmpty())
|
||||
{
|
||||
endLocation.ForceName(endLocation.Type.ForceLocationName.Value);
|
||||
}
|
||||
}
|
||||
|
||||
AssignEndLocationLevelData();
|
||||
|
||||
//backwards compatibility: if locations go out of bounds (map saved with different generation parameters before width/height were included in the xml)
|
||||
float maxX = Locations.Select(l => l.MapPosition.X).Max();
|
||||
if (maxX > Width) { Width = (int)(maxX + 10); }
|
||||
@@ -226,18 +288,13 @@ namespace Barotrauma
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
Generate(campaign.Settings);
|
||||
Generate(campaign);
|
||||
|
||||
if (Locations.Count == 0)
|
||||
{
|
||||
throw new Exception($"Generating a campaign map failed (no locations created). Width: {Width}, height: {Height}");
|
||||
}
|
||||
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, Locations[i], $"location.{i}".ToIdentifier(), -100, 100, Rand.Range(-10, 11, Rand.RandSync.ServerAndClient));
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.Identifier != "outpost") { continue; }
|
||||
@@ -258,6 +315,20 @@ namespace Barotrauma
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
StartLocation.SecondaryFaction = null;
|
||||
var startOutpostFaction = campaign?.Factions.FirstOrDefault(f => f.Prefab.StartOutpost);
|
||||
if (startOutpostFaction != null)
|
||||
{
|
||||
StartLocation.Faction = startOutpostFaction;
|
||||
foreach (var connection in StartLocation.Connections)
|
||||
{
|
||||
var otherLocation = connection.OtherLocation(StartLocation);
|
||||
if (otherLocation.HasOutpost() && otherLocation.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
otherLocation.Faction = startOutpostFaction;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +353,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.Discover(true);
|
||||
if (campaign.IsSinglePlayer && campaign.Settings.TutorialEnabled && LocationType.Prefabs.TryGet("tutorialoutpost", out var tutorialOutpost))
|
||||
{
|
||||
CurrentLocation.ChangeType(campaign, tutorialOutpost);
|
||||
}
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
CurrentLocation.CreateStores();
|
||||
|
||||
foreach (var location in Locations)
|
||||
@@ -297,7 +373,7 @@ namespace Barotrauma
|
||||
|
||||
#region Generation
|
||||
|
||||
private void Generate(CampaignSettings settings)
|
||||
private void Generate(CampaignMode campaign)
|
||||
{
|
||||
Connections.Clear();
|
||||
Locations.Clear();
|
||||
@@ -515,12 +591,14 @@ namespace Barotrauma
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1])
|
||||
else if (connectionsBetweenZones[zone1].Count() < generationParams.GateCount[zone1] &&
|
||||
connectionsBetweenZones[zone1].None(c => c.Locations.Contains(connection.Locations[0]) || c.Locations.Contains(connection.Locations[1])))
|
||||
{
|
||||
connectionsBetweenZones[zone1].Add(connection);
|
||||
}
|
||||
}
|
||||
|
||||
var gateFactions = campaign.Factions.Where(f => f.Prefab.ControlledOutpostPercentage > 0).OrderBy(f => f.Prefab.Identifier).ToList();
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
int zone1 = GetZoneIndex(Connections[i].Locations[0].MapPosition.X);
|
||||
@@ -528,9 +606,9 @@ namespace Barotrauma
|
||||
if (zone1 == zone2) { continue; }
|
||||
if (zone1 == generationParams.DifficultyZones || zone2 == generationParams.DifficultyZones) { continue; }
|
||||
|
||||
if (generationParams.GateCount[Math.Min(zone1, zone2)] == 0) { continue; }
|
||||
|
||||
if (!connectionsBetweenZones[Math.Min(zone1, zone2)].Contains(Connections[i]))
|
||||
int leftZone = Math.Min(zone1, zone2);
|
||||
if (generationParams.GateCount[leftZone] == 0) { continue; }
|
||||
if (!connectionsBetweenZones[leftZone].Contains(Connections[i]))
|
||||
{
|
||||
Connections.RemoveAt(i);
|
||||
}
|
||||
@@ -542,10 +620,17 @@ namespace Barotrauma
|
||||
Connections[i].Locations[1];
|
||||
if (!leftMostLocation.Type.HasOutpost || leftMostLocation.Type.Identifier == "abandoned")
|
||||
{
|
||||
leftMostLocation.ChangeType(LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
|
||||
leftMostLocation.ChangeType(
|
||||
campaign,
|
||||
LocationType.Prefabs.OrderBy(lt => lt.Identifier).First(lt => lt.HasOutpost && lt.Identifier != "abandoned"));
|
||||
}
|
||||
leftMostLocation.IsGateBetweenBiomes = true;
|
||||
Connections[i].Locked = true;
|
||||
|
||||
if (leftMostLocation.Type.HasOutpost && campaign != null && gateFactions.Any())
|
||||
{
|
||||
leftMostLocation.Faction = gateFactions[connectionsBetweenZones[leftZone].IndexOf(Connections[i]) % gateFactions.Count];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -613,20 +698,26 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
CreateEndLocation();
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location, CalculateDifficulty(location.MapPosition.X, location.Biome));
|
||||
if (location.Type.HasOutpost && campaign != null && location.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
location.Faction ??= campaign.GetRandomFaction(Rand.RandSync.ServerAndClient);
|
||||
location.SecondaryFaction ??= campaign.GetRandomSecondaryFaction(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.LevelData = new LevelData(connection);
|
||||
}
|
||||
|
||||
CreateEndLocation(campaign);
|
||||
|
||||
float CalculateDifficulty(float mapPosition, Biome biome)
|
||||
{
|
||||
float settingsFactor = settings.LevelDifficultyMultiplier;
|
||||
float settingsFactor = campaign.Settings.LevelDifficultyMultiplier;
|
||||
float minDifficulty = 0;
|
||||
float maxDifficulty = 100;
|
||||
float difficulty = mapPosition / Width * 100;
|
||||
@@ -695,18 +786,18 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(Connections.All(c => c.Biome != null));
|
||||
}
|
||||
|
||||
private void CreateEndLocation()
|
||||
private void CreateEndLocation(CampaignMode campaign)
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
Vector2 endPos = new Vector2(Width - zoneWidth / 2, Height / 2);
|
||||
Vector2 endPos = new Vector2(Width - zoneWidth * 0.7f, Height / 2);
|
||||
float closestDist = float.MaxValue;
|
||||
EndLocation = Locations.First();
|
||||
var endLocation = Locations.First();
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(endPos, location.MapPosition);
|
||||
if (location.Biome.IsEndBiome && dist < closestDist)
|
||||
{
|
||||
EndLocation = location;
|
||||
endLocation = location;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
@@ -720,17 +811,39 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
if (EndLocation == null || previousToEndLocation == null) { return; }
|
||||
if (endLocation == null || previousToEndLocation == null) { return; }
|
||||
|
||||
endLocations = new List<Location>() { endLocation };
|
||||
if (endLocation.Biome.EndBiomeLocationCount > 1)
|
||||
{
|
||||
FindConnectedEndLocations(endLocation);
|
||||
|
||||
void FindConnectedEndLocations(Location currLocation)
|
||||
{
|
||||
if (endLocations.Count >= endLocation.Biome.EndBiomeLocationCount) { return; }
|
||||
foreach (var connection in currLocation.Connections)
|
||||
{
|
||||
if (connection.Biome != endLocation.Biome) { continue; }
|
||||
var otherLocation = connection.OtherLocation(currLocation);
|
||||
if (otherLocation != null && !endLocations.Contains(otherLocation))
|
||||
{
|
||||
if (endLocations.Count >= endLocation.Biome.EndBiomeLocationCount) { return; }
|
||||
endLocations.Add(otherLocation);
|
||||
FindConnectedEndLocations(otherLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (LocationType.Prefabs.TryGet("none", out LocationType locationType))
|
||||
{
|
||||
previousToEndLocation.ChangeType(locationType);
|
||||
previousToEndLocation.ChangeType(campaign, locationType);
|
||||
}
|
||||
|
||||
//remove all locations from the end biome except the end location
|
||||
for (int i = Locations.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (Locations[i].Biome.IsEndBiome && Locations[i] != EndLocation)
|
||||
if (Locations[i].Biome.IsEndBiome)
|
||||
{
|
||||
for (int j = Locations[i].Connections.Count - 1; j >= 0; j--)
|
||||
{
|
||||
@@ -741,7 +854,10 @@ namespace Barotrauma
|
||||
otherLocation?.Connections.Remove(connection);
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
Locations.RemoveAt(i);
|
||||
if (!endLocations.Contains(Locations[i]))
|
||||
{
|
||||
Locations.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -758,22 +874,38 @@ namespace Barotrauma
|
||||
}
|
||||
var newConnection = new LocationConnection(previousToEndLocation, connectTo)
|
||||
{
|
||||
Biome = EndLocation.Biome,
|
||||
Biome = endLocation.Biome,
|
||||
Difficulty = 100.0f
|
||||
};
|
||||
newConnection.LevelData = new LevelData(newConnection);
|
||||
Connections.Add(newConnection);
|
||||
previousToEndLocation.Connections.Add(newConnection);
|
||||
connectTo.Connections.Add(newConnection);
|
||||
}
|
||||
|
||||
var endConnection = new LocationConnection(previousToEndLocation, EndLocation)
|
||||
var endConnection = new LocationConnection(previousToEndLocation, endLocation)
|
||||
{
|
||||
Biome = EndLocation.Biome,
|
||||
Biome = endLocation.Biome,
|
||||
Difficulty = 100.0f
|
||||
};
|
||||
endConnection.LevelData = new LevelData(endConnection);
|
||||
Connections.Add(endConnection);
|
||||
previousToEndLocation.Connections.Add(endConnection);
|
||||
EndLocation.Connections.Add(endConnection);
|
||||
endLocation.Connections.Add(endConnection);
|
||||
|
||||
AssignEndLocationLevelData();
|
||||
}
|
||||
|
||||
private void AssignEndLocationLevelData()
|
||||
{
|
||||
for (int i = 0; i < endLocations.Count; i++)
|
||||
{
|
||||
var outpostParams = OutpostGenerationParams.OutpostParams.FirstOrDefault(p => p.ForceToEndLocationIndex == i);
|
||||
if (outpostParams != null)
|
||||
{
|
||||
endLocations[i].LevelData.ForceOutpostGenerationParams = outpostParams;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ExpandBiomes(List<LocationConnection> seeds)
|
||||
@@ -807,20 +939,44 @@ namespace Barotrauma
|
||||
{
|
||||
if (SelectedConnection == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n"+Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
if (!endLocations.Contains(CurrentLocation))
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (SelectedLocation == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no location selected).\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
if (endLocations.Contains(CurrentLocation))
|
||||
{
|
||||
int currentEndLocationIndex = endLocations.IndexOf(CurrentLocation);
|
||||
if (currentEndLocationIndex < endLocations.Count - 1)
|
||||
{
|
||||
//more end locations to go, progress to the next one
|
||||
SelectedLocation = endLocations[currentEndLocationIndex + 1];
|
||||
}
|
||||
else
|
||||
{
|
||||
//at the last end location, end of campaign
|
||||
SelectedLocation = StartLocation;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n" + Environment.StackTrace.CleanupStackTrace());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
SelectedConnection.Passed = true;
|
||||
if (SelectedConnection != null)
|
||||
{
|
||||
SelectedConnection.Passed = true;
|
||||
}
|
||||
|
||||
CurrentLocation = SelectedLocation;
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
Visit(CurrentLocation);
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
@@ -851,7 +1007,7 @@ namespace Barotrauma
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discover();
|
||||
Discover(CurrentLocation);
|
||||
|
||||
CurrentLocation.CreateStores();
|
||||
if (prevLocation != CurrentLocation)
|
||||
@@ -966,7 +1122,7 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public void ProgressWorld(CampaignMode.TransitionType transitionType, float roundDuration)
|
||||
public void ProgressWorld(CampaignMode campaign, CampaignMode.TransitionType transitionType, float roundDuration)
|
||||
{
|
||||
//one step per 10 minutes of play time
|
||||
int steps = (int)Math.Floor(roundDuration / (60.0f * 10.0f));
|
||||
@@ -979,13 +1135,23 @@ namespace Barotrauma
|
||||
steps = Math.Min(steps, 5);
|
||||
for (int i = 0; i < steps; i++)
|
||||
{
|
||||
ProgressWorld();
|
||||
ProgressWorld(campaign);
|
||||
}
|
||||
|
||||
// always update specials every step
|
||||
for (int i = 0; i < Math.Max(1, steps); i++)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Discovered) { continue; }
|
||||
location.UpdateSpecials();
|
||||
}
|
||||
}
|
||||
|
||||
Radiation?.OnStep(steps);
|
||||
}
|
||||
|
||||
private void ProgressWorld()
|
||||
private void ProgressWorld(CampaignMode campaign)
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
@@ -1013,14 +1179,14 @@ namespace Barotrauma
|
||||
|
||||
if (location == CurrentLocation || location == SelectedLocation || location.IsGateBetweenBiomes) { continue; }
|
||||
|
||||
if (!ProgressLocationTypeChanges(location) && location.Discovered)
|
||||
if (!ProgressLocationTypeChanges(campaign, location) && location.Discovered)
|
||||
{
|
||||
location.UpdateStores();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ProgressLocationTypeChanges(Location location)
|
||||
private bool ProgressLocationTypeChanges(CampaignMode campaign, Location location)
|
||||
{
|
||||
location.TimeSinceLastTypeChange++;
|
||||
location.LocationTypeChangeCooldown--;
|
||||
@@ -1040,7 +1206,7 @@ namespace Barotrauma
|
||||
location.PendingLocationTypeChange.Value.parentMission);
|
||||
if (location.PendingLocationTypeChange.Value.delay <= 0)
|
||||
{
|
||||
return ChangeLocationType(location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
return ChangeLocationType(campaign, location, location.PendingLocationTypeChange.Value.typeChange);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1073,7 +1239,7 @@ namespace Barotrauma
|
||||
}
|
||||
else
|
||||
{
|
||||
return ChangeLocationType(location, selectedTypeChange);
|
||||
return ChangeLocationType(campaign, location, selectedTypeChange);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1143,7 +1309,7 @@ namespace Barotrauma
|
||||
return distance;
|
||||
}
|
||||
|
||||
private bool ChangeLocationType(Location location, LocationTypeChange change)
|
||||
private bool ChangeLocationType(CampaignMode campaign, Location location, LocationTypeChange change)
|
||||
{
|
||||
string prevName = location.Name;
|
||||
|
||||
@@ -1158,7 +1324,7 @@ namespace Barotrauma
|
||||
{
|
||||
location.ClearMissions();
|
||||
}
|
||||
location.ChangeType(newType);
|
||||
location.ChangeType(campaign, newType);
|
||||
ChangeLocationTypeProjSpecific(location, prevName, change);
|
||||
foreach (var requirement in change.Requirements)
|
||||
{
|
||||
@@ -1174,13 +1340,58 @@ namespace Barotrauma
|
||||
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
public void Discover(Location location, bool checkTalents = true)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (locationsDiscovered.Contains(location)) { return; }
|
||||
locationsDiscovered.Add(location);
|
||||
if (checkTalents)
|
||||
{
|
||||
GameSession.GetSessionCrewCharacters(CharacterType.Both).ForEach(c => c.CheckTalents(AbilityEffectType.OnLocationDiscovered, new Location.AbilityLocation(location)));
|
||||
}
|
||||
}
|
||||
|
||||
public void Visit(Location location)
|
||||
{
|
||||
if (location is null) { return; }
|
||||
if (!location.HasOutpost()) { return; }
|
||||
if (outpostsVisited.Contains(location)) { return; }
|
||||
outpostsVisited.Add(location);
|
||||
}
|
||||
|
||||
public void ClearLocationHistory()
|
||||
{
|
||||
locationsDiscovered.Clear();
|
||||
outpostsVisited.Clear();
|
||||
}
|
||||
|
||||
public int? GetDiscoveryIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return locationsDiscovered.IndexOf(location);
|
||||
}
|
||||
|
||||
public int? GetVisitIndex(Location location)
|
||||
{
|
||||
if (!wasLocationDiscoveryOrderTracked) { return null; }
|
||||
if (location is null) { return -1; }
|
||||
return outpostsVisited.IndexOf(location);
|
||||
}
|
||||
|
||||
public bool IsDiscovered(Location location)
|
||||
{
|
||||
if (location is null) { return false; }
|
||||
return locationsDiscovered.Contains(location);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
{
|
||||
Map map = new Map(campaign, element);
|
||||
map.LoadState(element, false);
|
||||
map.LoadState(campaign, element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
#endif
|
||||
@@ -1190,17 +1401,18 @@ namespace Barotrauma
|
||||
/// <summary>
|
||||
/// Load the state of an existing map from xml (current state of locations, where the crew is now, etc).
|
||||
/// </summary>
|
||||
public void LoadState(XElement element, bool showNotifications)
|
||||
public void LoadState(CampaignMode campaign, XElement element, bool showNotifications)
|
||||
{
|
||||
ClearAnimQueue();
|
||||
SetLocation(element.GetAttributeInt("currentlocation", 0));
|
||||
|
||||
if (!Version.TryParse(element.GetAttributeString("version", ""), out _))
|
||||
if (!Version.TryParse(element.GetAttributeString("version", ""), out Version version))
|
||||
{
|
||||
DebugConsole.ThrowError("Incompatible map save file, loading the game failed.");
|
||||
return;
|
||||
}
|
||||
|
||||
ClearLocationHistory();
|
||||
foreach (var subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
@@ -1216,26 +1428,19 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
location.LoadLocationTypeChange(subElement);
|
||||
|
||||
// Backwards compatibility
|
||||
if (subElement.GetAttributeBool("discovered", false))
|
||||
{
|
||||
location.Discover(checkTalents: false);
|
||||
}
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(location);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
Discover(location);
|
||||
wasLocationDiscoveryOrderTracked = false;
|
||||
}
|
||||
|
||||
Identifier locationType = subElement.GetAttributeIdentifier("type", Identifier.Empty);
|
||||
string prevLocationName = location.Name;
|
||||
LocationType prevLocationType = location.Type;
|
||||
LocationType newLocationType = LocationType.Prefabs.Find(lt => lt.Identifier == locationType) ?? LocationType.Prefabs.First();
|
||||
location.ChangeType(newLocationType);
|
||||
location.ChangeType(campaign, newLocationType);
|
||||
if (showNotifications && prevLocationType != location.Type)
|
||||
{
|
||||
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType == location.Type.Identifier);
|
||||
@@ -1246,6 +1451,12 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
var factionIdentifier = subElement.GetAttributeIdentifier("faction", Identifier.Empty);
|
||||
location.Faction = factionIdentifier.IsEmpty ? null : campaign.Factions.Find(f => f.Prefab.Identifier == factionIdentifier);
|
||||
|
||||
var secondaryFactionIdentifier = subElement.GetAttributeIdentifier("secondaryfaction", Identifier.Empty);
|
||||
location.SecondaryFaction = secondaryFactionIdentifier.IsEmpty ? null : campaign.Factions.Find(f => f.Prefab.Identifier == secondaryFactionIdentifier);
|
||||
|
||||
location.LoadStores(subElement);
|
||||
location.LoadMissions(subElement);
|
||||
|
||||
@@ -1258,6 +1469,36 @@ namespace Barotrauma
|
||||
case "radiation":
|
||||
Radiation = new Radiation(this, generationParams.RadiationParams, subElement);
|
||||
break;
|
||||
case "discovered":
|
||||
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);
|
||||
}
|
||||
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);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1266,6 +1507,27 @@ 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))
|
||||
{
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.Type.HasOutpost && campaign != null && location.Type.OutpostTeam == CharacterTeamType.FriendlyNPC)
|
||||
{
|
||||
location.Faction = campaign.GetRandomFaction(Rand.RandSync.ServerAndClient);
|
||||
if (location != StartLocation)
|
||||
{
|
||||
location.SecondaryFaction = campaign.GetRandomSecondaryFaction(Rand.RandSync.ServerAndClient);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int currentLocationConnection = element.GetAttributeInt("currentlocationconnection", -1);
|
||||
if (currentLocationConnection >= 0)
|
||||
{
|
||||
@@ -1304,7 +1566,7 @@ namespace Barotrauma
|
||||
mapElement.Add(new XAttribute("height", Height));
|
||||
mapElement.Add(new XAttribute("selectedlocation", SelectedLocationIndex));
|
||||
mapElement.Add(new XAttribute("startlocation", Locations.IndexOf(StartLocation)));
|
||||
mapElement.Add(new XAttribute("endlocation", Locations.IndexOf(EndLocation)));
|
||||
mapElement.Add(new XAttribute("endlocations", string.Join(',', EndLocations.Select(e => Locations.IndexOf(e)))));
|
||||
mapElement.Add(new XAttribute("seed", Seed));
|
||||
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
@@ -1333,6 +1595,30 @@ namespace Barotrauma
|
||||
mapElement.Add(Radiation.Save());
|
||||
}
|
||||
|
||||
if (locationsDiscovered.Any())
|
||||
{
|
||||
var discoveryElement = new XElement("discovered");
|
||||
foreach (Location location in locationsDiscovered)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
discoveryElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(discoveryElement);
|
||||
}
|
||||
|
||||
if (outpostsVisited.Any())
|
||||
{
|
||||
var visitElement = new XElement("visited");
|
||||
foreach (Location location in outpostsVisited)
|
||||
{
|
||||
int index = Locations.IndexOf(location);
|
||||
var locationElement = new XElement("location", new XAttribute("i", index));
|
||||
visitElement.Add(locationElement);
|
||||
}
|
||||
mapElement.Add(visitElement);
|
||||
}
|
||||
|
||||
element.Add(mapElement);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user