(965c31410) v0.10.4.0
This commit is contained in:
@@ -1,13 +1,61 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using Barotrauma.Extensions;
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
{
|
||||
partial class Location
|
||||
{
|
||||
public List<LocationConnection> Connections;
|
||||
public class TakenItem
|
||||
{
|
||||
public readonly ushort OriginalID;
|
||||
public readonly ushort OriginalContainerID;
|
||||
public readonly ushort ModuleIndex;
|
||||
public readonly string Identifier;
|
||||
|
||||
public TakenItem(string identifier, UInt16 originalID, UInt16 originalContainerID, ushort moduleIndex)
|
||||
{
|
||||
OriginalID = originalID;
|
||||
OriginalContainerID = originalContainerID;
|
||||
ModuleIndex = moduleIndex;
|
||||
Identifier = identifier;
|
||||
}
|
||||
|
||||
public TakenItem(Item item)
|
||||
{
|
||||
System.Diagnostics.Debug.Assert(item.OriginalModuleIndex >= 0, "Trying to add a non-outpost item to a location's taken items");
|
||||
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
{
|
||||
OriginalContainerID = item.OriginalContainerID;
|
||||
}
|
||||
OriginalID = item.OriginalID;
|
||||
ModuleIndex = (ushort)item.OriginalModuleIndex;
|
||||
Identifier = item.prefab.Identifier;
|
||||
}
|
||||
|
||||
public bool IsEqual(TakenItem obj)
|
||||
{
|
||||
return obj.OriginalID == OriginalID && obj.OriginalContainerID == OriginalContainerID && obj.ModuleIndex == ModuleIndex && obj.Identifier == Identifier;
|
||||
}
|
||||
public bool Matches(Item item)
|
||||
{
|
||||
if (item.OriginalContainerID != Entity.NullEntityID)
|
||||
{
|
||||
return item.OriginalContainerID == OriginalContainerID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
}
|
||||
else
|
||||
{
|
||||
return item.OriginalID == OriginalID && item.OriginalModuleIndex == ModuleIndex && item.prefab.Identifier == Identifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public readonly List<LocationConnection> Connections = new List<LocationConnection>();
|
||||
|
||||
private string baseName;
|
||||
private int nameFormatIndex;
|
||||
@@ -20,43 +68,59 @@ namespace Barotrauma
|
||||
|
||||
public string Name { get; private set; }
|
||||
|
||||
public Biome Biome { get; set; }
|
||||
|
||||
public Vector2 MapPosition { get; private set; }
|
||||
|
||||
public LocationType Type { get; private set; }
|
||||
|
||||
public int PortraitId { get; private set; }
|
||||
public LevelData LevelData { get; set; }
|
||||
|
||||
public int MissionsCompleted;
|
||||
|
||||
private List<Mission> availableMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> AvailableMissions
|
||||
private float normalizedDepth;
|
||||
public float NormalizedDepth
|
||||
{
|
||||
get
|
||||
get { return normalizedDepth; }
|
||||
set
|
||||
{
|
||||
CheckMissionCompleted();
|
||||
|
||||
for (int i = availableMissions.Count; i < Connections.Count * 2; i++)
|
||||
{
|
||||
int seed = (ToolBox.StringToInt(BaseName) + MissionsCompleted * 10 + i) % int.MaxValue;
|
||||
MTRandom rand = new MTRandom(seed);
|
||||
|
||||
LocationConnection connection = Connections[(MissionsCompleted + i) % Connections.Count];
|
||||
Location destination = connection.OtherLocation(this);
|
||||
|
||||
var mission = Mission.LoadRandom(new Location[] { this, destination }, rand, true, MissionType.All, true);
|
||||
if (mission == null) { continue; }
|
||||
if (availableMissions.Any(m => m.Prefab == mission.Prefab)) { continue; }
|
||||
if (GameSettings.VerboseLogging && mission != null)
|
||||
{
|
||||
DebugConsole.NewMessage("Generated a new mission for a location (location: " + Name + ", seed: " + seed.ToString("X") + ", missions completed: " + MissionsCompleted + ", type: " + mission.Name + ")", Color.White);
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
}
|
||||
|
||||
return availableMissions;
|
||||
if (!MathUtils.IsValid(value)) { return; }
|
||||
normalizedDepth = MathHelper.Clamp(value, 0.0f, 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
public int PortraitId { get; private set; }
|
||||
|
||||
public Reputation Reputation { get; set; }
|
||||
|
||||
private const float StoreMaxReputationModifier = 0.1f;
|
||||
private const float StoreSellPriceModifier = 0.8f;
|
||||
private const float MechanicalMaxDiscountPercentage = 50.0f;
|
||||
public const int StoreInitialBalance = 5000;
|
||||
public int StoreCurrentBalance { get; set; }
|
||||
public List<PurchasedItem> StoreStock { get; set; }
|
||||
|
||||
private readonly List<TakenItem> takenItems = new List<TakenItem>();
|
||||
public IEnumerable<TakenItem> TakenItems
|
||||
{
|
||||
get { return takenItems; }
|
||||
}
|
||||
|
||||
private readonly HashSet<int> killedCharacterIdentifiers = new HashSet<int>();
|
||||
public IEnumerable<int> KilledCharacterIdentifiers
|
||||
{
|
||||
get { return killedCharacterIdentifiers; }
|
||||
}
|
||||
|
||||
private readonly List<Mission> availableMissions = new List<Mission>();
|
||||
public IEnumerable<Mission> AvailableMissions
|
||||
{
|
||||
get
|
||||
{
|
||||
availableMissions.RemoveAll(m => m.Completed);
|
||||
return availableMissions;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Mission SelectedMission
|
||||
{
|
||||
get;
|
||||
@@ -81,20 +145,273 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand)
|
||||
private float priceMultiplier = 1.0f;
|
||||
public float PriceMultiplier
|
||||
{
|
||||
this.Type = LocationType.Random(rand, zone);
|
||||
this.Name = RandomName(Type, rand);
|
||||
this.MapPosition = mapPosition;
|
||||
get { return priceMultiplier; }
|
||||
set { priceMultiplier = MathHelper.Clamp(value, 0.1f, 10.0f); }
|
||||
}
|
||||
|
||||
private float mechanicalpriceMultiplier = 1.0f;
|
||||
public float MechanicalPriceMultiplier
|
||||
{
|
||||
get => mechanicalpriceMultiplier;
|
||||
set => mechanicalpriceMultiplier = MathHelper.Clamp(value, 0.1f, 10.0f);
|
||||
}
|
||||
|
||||
public string LastTypeChangeMessage;
|
||||
|
||||
private struct LoadedMission
|
||||
{
|
||||
public MissionPrefab MissionPrefab { get; }
|
||||
public int DestinationIndex { get; }
|
||||
public bool SelectedMission { get; }
|
||||
|
||||
public LoadedMission(MissionPrefab prefab, int destinationIndex, bool selectedMission)
|
||||
{
|
||||
MissionPrefab = prefab;
|
||||
DestinationIndex = destinationIndex;
|
||||
SelectedMission = selectedMission;
|
||||
}
|
||||
}
|
||||
|
||||
private List<LoadedMission> loadedMissions;
|
||||
|
||||
public HireManager HireManager;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return $"Location ({Name ?? "null"})";
|
||||
}
|
||||
|
||||
public Location(Vector2 mapPosition, int? zone, Random rand, bool requireOutpost = false, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
Type = LocationType.Random(rand, zone, requireOutpost);
|
||||
Name = RandomName(Type, rand, existingLocations);
|
||||
MapPosition = mapPosition;
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
public static Location CreateRandom(Vector2 position, int? zone , Random rand)
|
||||
public Location(XElement element)
|
||||
{
|
||||
return new Location(position, zone, rand);
|
||||
string locationType = element.GetAttributeString("type", "");
|
||||
Type = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase));
|
||||
baseName = element.GetAttributeString("basename", "");
|
||||
Name = element.GetAttributeString("name", "");
|
||||
MapPosition = element.GetAttributeVector2("position", Vector2.Zero);
|
||||
NormalizedDepth = element.GetAttributeFloat("normalizeddepth", 0.0f);
|
||||
TypeChangeTimer = element.GetAttributeInt("changetimer", 0);
|
||||
Discovered = element.GetAttributeBool("discovered", false);
|
||||
PriceMultiplier = element.GetAttributeFloat("pricemultiplier", 1.0f);
|
||||
MechanicalPriceMultiplier = element.GetAttributeFloat("mechanicalpricemultipler", 1.0f);
|
||||
|
||||
string[] takenItemStr = element.GetAttributeStringArray("takenitems", new string[0]);
|
||||
foreach (string takenItem in takenItemStr)
|
||||
{
|
||||
string[] takenItemSplit = takenItem.Split(';');
|
||||
if (takenItemSplit.Length != 4)
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item data \"{takenItem}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[1], out ushort id))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item id \"{takenItemSplit[1]}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[2], out ushort containerId))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken container id \"{takenItemSplit[2]}\"");
|
||||
continue;
|
||||
}
|
||||
if (!ushort.TryParse(takenItemSplit[3], out ushort moduleIndex))
|
||||
{
|
||||
DebugConsole.ThrowError($"Error in saved location: could not parse taken item module index \"{takenItemSplit[3]}\"");
|
||||
continue;
|
||||
}
|
||||
takenItems.Add(new TakenItem(takenItemSplit[0], id, containerId, moduleIndex));
|
||||
}
|
||||
|
||||
killedCharacterIdentifiers = element.GetAttributeIntArray("killedcharacters", new int[0]).ToHashSet();
|
||||
|
||||
System.Diagnostics.Debug.Assert(Type != null, $"Could not find the location type \"{locationType}\"!");
|
||||
if (Type == null)
|
||||
{
|
||||
Type = LocationType.List.First();
|
||||
}
|
||||
|
||||
LevelData = new LevelData(element.Element("Level"));
|
||||
|
||||
PortraitId = ToolBox.StringToInt(Name);
|
||||
|
||||
if (element.GetChildElement("store") is XElement storeElement)
|
||||
{
|
||||
StoreCurrentBalance = storeElement.GetAttributeInt("balance", StoreInitialBalance);
|
||||
StoreStock = LoadStoreStock(storeElement);
|
||||
}
|
||||
|
||||
LoadMissions(element);
|
||||
}
|
||||
|
||||
public void LoadMissions(XElement locationElement)
|
||||
{
|
||||
if (locationElement.GetChildElement("missions") is XElement missionsElement)
|
||||
{
|
||||
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.List.Find(p => p.Identifier.Equals(id, StringComparison.OrdinalIgnoreCase));
|
||||
if (prefab == null) { continue; }
|
||||
var destination = childElement.GetAttributeInt("destinationindex", -1);
|
||||
var selected = childElement.GetAttributeBool("selected", false);
|
||||
loadedMissions.Add(new LoadedMission(prefab, destination, selected));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static Location CreateRandom(Vector2 position, int? zone, Random rand, bool requireOutpost, IEnumerable<Location> existingLocations = null)
|
||||
{
|
||||
return new Location(position, zone, rand, requireOutpost, existingLocations);
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
CreateStore(force: true);
|
||||
}
|
||||
|
||||
public void UnlockMission(MissionPrefab missionPrefab, LocationConnection connection)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab)) { return; }
|
||||
availableMissions.Add(InstantiateMission(missionPrefab, connection));
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
}
|
||||
|
||||
public MissionPrefab UnlockMissionByIdentifier(string identifier)
|
||||
{
|
||||
if (AvailableMissions.Any(m => m.Prefab.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase))) { return null; }
|
||||
|
||||
var missionPrefab = MissionPrefab.List.Find(mp => mp.Identifier.Equals(identifier, StringComparison.OrdinalIgnoreCase));
|
||||
if (missionPrefab == null)
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the identifier \"{identifier}\": matching mission not found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
return missionPrefab;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public MissionPrefab UnlockMissionByTag(string tag)
|
||||
{
|
||||
var matchingMissions = MissionPrefab.List.FindAll(mp => mp.Tags.Any(t => t.Equals(tag, StringComparison.OrdinalIgnoreCase)));
|
||||
if (!matchingMissions.Any())
|
||||
{
|
||||
DebugConsole.ThrowError($"Failed to unlock a mission with the tag \"{tag}\": no matching missions not found.");
|
||||
}
|
||||
else
|
||||
{
|
||||
var unusedMissions = matchingMissions.Where(m => !availableMissions.Any(mission => mission.Prefab == m));
|
||||
if (unusedMissions.Any())
|
||||
{
|
||||
var suitableMissions = unusedMissions.Where(m => Connections.Any(c => m.IsAllowed(this, c.OtherLocation(this))));
|
||||
if (!suitableMissions.Any())
|
||||
{
|
||||
suitableMissions = unusedMissions;
|
||||
}
|
||||
MissionPrefab missionPrefab = suitableMissions.GetRandom();
|
||||
var mission = InstantiateMission(missionPrefab);
|
||||
//don't allow duplicate missions in the same connection
|
||||
if (AvailableMissions.Any(m => m.Prefab == missionPrefab && m.Locations.Contains(mission.Locations[0]) && m.Locations.Contains(mission.Locations[1])))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
availableMissions.Add(mission);
|
||||
#if CLIENT
|
||||
GameMain.GameSession?.Campaign?.CampaignUI?.RefreshLocationInfo();
|
||||
#endif
|
||||
return missionPrefab;
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.AddWarning($"Failed to unlock a mission with the tag \"{tag}\": all available missions have already been unlocked.");
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private Mission InstantiateMission(MissionPrefab prefab, LocationConnection connection = null)
|
||||
{
|
||||
if (connection == null)
|
||||
{
|
||||
var suitableConnections = Connections.Where(c => prefab.IsAllowed(this, c.OtherLocation(this)));
|
||||
if (!suitableConnections.Any())
|
||||
{
|
||||
suitableConnections = Connections;
|
||||
}
|
||||
//prefer connections that haven't been passed through, and connections with fewer available missions
|
||||
connection = ToolBox.SelectWeightedRandom(
|
||||
suitableConnections.ToList(),
|
||||
suitableConnections.Select(c => (c.Passed ? 1.0f : 5.0f) / Math.Max(availableMissions.Count(m => m.Locations.Contains(c.OtherLocation(this))), 1.0f)).ToList(),
|
||||
Rand.RandSync.Unsynced);
|
||||
}
|
||||
|
||||
Location destination = connection.OtherLocation(this);
|
||||
return prefab.Instantiate(new Location[] { this, destination });
|
||||
}
|
||||
|
||||
public void InstantiateLoadedMissions(Map map)
|
||||
{
|
||||
availableMissions.Clear();
|
||||
if (loadedMissions == null || loadedMissions.None()) { return; }
|
||||
foreach (LoadedMission loadedMission in loadedMissions)
|
||||
{
|
||||
Location destination = null;
|
||||
if (loadedMission.DestinationIndex >= 0 && loadedMission.DestinationIndex < map.Locations.Count)
|
||||
{
|
||||
destination = map.Locations[loadedMission.DestinationIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
destination = Connections.First().OtherLocation(this);
|
||||
}
|
||||
var mission = loadedMission.MissionPrefab.Instantiate(new Location[] { this, destination });
|
||||
availableMissions.Add(mission);
|
||||
if (loadedMission.SelectedMission) { SelectedMission = mission; }
|
||||
}
|
||||
loadedMissions = null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes all unlocked missions from the location
|
||||
/// </summary>
|
||||
public void ClearMissions()
|
||||
{
|
||||
availableMissions.Clear();
|
||||
SelectedMissionIndex = -1;
|
||||
}
|
||||
|
||||
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
|
||||
@@ -102,50 +419,346 @@ namespace Barotrauma
|
||||
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
|
||||
return AvailableMissions.Where(m => m.Locations[1] == connection.OtherLocation(this));
|
||||
}
|
||||
|
||||
public void ChangeType(LocationType newType)
|
||||
|
||||
public void RemoveHireableCharacter(CharacterInfo character)
|
||||
{
|
||||
if (newType == Type) { return; }
|
||||
|
||||
//clear missions from this and adjacent locations (they may be invalid now)
|
||||
availableMissions.Clear();
|
||||
foreach (LocationConnection connection in Connections)
|
||||
if (!Type.HasHireableCharacters)
|
||||
{
|
||||
connection.OtherLocation(this)?.availableMissions.Clear();
|
||||
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - the location has no hireable characters.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
if (HireManager == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Cannot hire a character from location \"" + Name + "\" - hire manager has not been instantiated.\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
|
||||
|
||||
Type = newType;
|
||||
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
|
||||
HireManager.RemoveCharacter(character);
|
||||
}
|
||||
|
||||
public void CheckMissionCompleted()
|
||||
public IEnumerable<CharacterInfo> GetHireableCharacters()
|
||||
{
|
||||
foreach (Mission mission in availableMissions)
|
||||
if (!Type.HasHireableCharacters)
|
||||
{
|
||||
if (mission.Completed)
|
||||
{
|
||||
DebugConsole.Log("Mission \"" + mission.Name + "\" completed in \"" + Name + "\".");
|
||||
MissionsCompleted++;
|
||||
}
|
||||
return Enumerable.Empty<CharacterInfo>();
|
||||
}
|
||||
|
||||
availableMissions.RemoveAll(m => m.Completed);
|
||||
HireManager ??= new HireManager();
|
||||
|
||||
if (!HireManager.AvailableCharacters.Any())
|
||||
{
|
||||
HireManager.GenerateCharacters(location: this, amount: HireManager.MaxAvailableCharacters);
|
||||
}
|
||||
return HireManager.AvailableCharacters;
|
||||
}
|
||||
|
||||
private string RandomName(LocationType type, Random rand)
|
||||
private string RandomName(LocationType type, Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
baseName = type.GetRandomName(rand);
|
||||
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);
|
||||
}
|
||||
|
||||
private List<PurchasedItem> CreateStoreStock()
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
foreach (ItemPrefab prefab in ItemPrefab.Prefabs)
|
||||
{
|
||||
if (prefab.CanBeBoughtAtLocation(this, out PriceInfo priceInfo))
|
||||
{
|
||||
var quantity = priceInfo.MinAvailableAmount > 0 ? priceInfo.MinAvailableAmount :
|
||||
(priceInfo.MaxAvailableAmount > 0 ? Math.Min(priceInfo.MaxAvailableAmount, 5) : 5);
|
||||
stock.Add(new PurchasedItem(prefab, quantity));
|
||||
}
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
public static List<PurchasedItem> LoadStoreStock(XElement storeElement)
|
||||
{
|
||||
var stock = new List<PurchasedItem>();
|
||||
if (storeElement == null) { return stock; }
|
||||
foreach (XElement stockElement in storeElement.GetChildElements("stock"))
|
||||
{
|
||||
var id = stockElement.GetAttributeString("id", null);
|
||||
if (string.IsNullOrWhiteSpace(id)) { continue; }
|
||||
var prefab = ItemPrefab.Prefabs.Find(p => p.Identifier == id);
|
||||
if (prefab == null) { continue; }
|
||||
var qty = stockElement.GetAttributeInt("qty", 0);
|
||||
if (qty < 1) { continue; }
|
||||
stock.Add(new PurchasedItem(prefab, qty));
|
||||
}
|
||||
return stock;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark the items that have been taken from the outpost to prevent them from spawning when re-entering the outpost
|
||||
/// </summary>
|
||||
public void RegisterTakenItems(IEnumerable<Item> items)
|
||||
{
|
||||
foreach (Item item in items)
|
||||
{
|
||||
if (takenItems.Any(it => it.Matches(item) && it.OriginalID == item.OriginalID)) { continue; }
|
||||
if (item.OriginalModuleIndex < 0)
|
||||
{
|
||||
DebugConsole.ThrowError("Tried to register a non-outpost item as being taken from the outpost.");
|
||||
continue;
|
||||
}
|
||||
takenItems.Add(new TakenItem(item));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Mark the characters who have been killed to prevent them from spawning when re-entering the outpost
|
||||
/// </summary>
|
||||
public void RegisterKilledCharacters(IEnumerable<Character> characters)
|
||||
{
|
||||
foreach (Character character in characters)
|
||||
{
|
||||
if (character?.Info == null) { continue; }
|
||||
killedCharacterIdentifiers.Add(character.Info.GetIdentifier());
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveTakenItems()
|
||||
{
|
||||
foreach (TakenItem takenItem in takenItems)
|
||||
{
|
||||
Item item = Item.ItemList.Find(it => takenItem.Matches(it));
|
||||
item?.Remove();
|
||||
}
|
||||
}
|
||||
|
||||
public int GetAdjustedItemBuyPrice(PriceInfo priceInfo)
|
||||
{
|
||||
// TODO: Check priceInfo.CanBeBought
|
||||
if (priceInfo == null) { return 0; }
|
||||
var price = priceInfo.Price;
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price);
|
||||
}
|
||||
else
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price);
|
||||
}
|
||||
// Item price should never go below 1 mk
|
||||
return Math.Max(price, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If item.GetPriceInfo() returns null, this will return 0
|
||||
/// </summary>
|
||||
public int GetAdjustedItemBuyPrice(ItemPrefab item) => GetAdjustedItemBuyPrice(item?.GetPriceInfo(this));
|
||||
|
||||
public int GetAdjustedItemSellPrice(PriceInfo priceInfo)
|
||||
{
|
||||
if (priceInfo == null) { return 0; }
|
||||
var price = (int)(StoreSellPriceModifier * priceInfo.Price);
|
||||
if (Reputation.Value > 0.0f)
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f + StoreMaxReputationModifier, Reputation.Value / Reputation.MaxReputation) * price);
|
||||
}
|
||||
else
|
||||
{
|
||||
price = (int)(MathHelper.Lerp(1.0f, 1.0f - StoreMaxReputationModifier, Reputation.Value / Reputation.MinReputation) * price);
|
||||
}
|
||||
// Item price should never go below 1 mk
|
||||
return Math.Max(price, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If item.GetPriceInfo() returns null, this will return 0
|
||||
/// </summary>
|
||||
public int GetAdjustedItemSellPrice(ItemPrefab item) => GetAdjustedItemSellPrice(item?.GetPriceInfo(this));
|
||||
|
||||
public int GetAdjustedMechanicalCost(int cost)
|
||||
{
|
||||
float discount = Reputation.Value / Reputation.MaxReputation * (MechanicalMaxDiscountPercentage / 100.0f);
|
||||
return (int) Math.Ceiling((1.0f - discount) * cost * MechanicalPriceMultiplier);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// If 'force' is true, the stock will be recreated even if it has been created previously already.
|
||||
/// This is used when (at least) when the type of the location changes.
|
||||
/// </summary>
|
||||
public void CreateStore(bool force = false)
|
||||
{
|
||||
if (!force && StoreStock != null) { return; }
|
||||
|
||||
if (StoreStock != null)
|
||||
{
|
||||
StoreCurrentBalance = Math.Max(StoreCurrentBalance, StoreInitialBalance);
|
||||
var newStock = CreateStoreStock();
|
||||
foreach (PurchasedItem oldStockItem in StoreStock)
|
||||
{
|
||||
if (newStock.Find(i => i.ItemPrefab == oldStockItem.ItemPrefab) is PurchasedItem newStockItem)
|
||||
{
|
||||
if (oldStockItem.Quantity > newStockItem.Quantity)
|
||||
{
|
||||
newStockItem.Quantity = oldStockItem.Quantity;
|
||||
}
|
||||
}
|
||||
}
|
||||
StoreStock = newStock;
|
||||
}
|
||||
else
|
||||
{
|
||||
StoreCurrentBalance = StoreInitialBalance;
|
||||
StoreStock = CreateStoreStock();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateStore()
|
||||
{
|
||||
if (StoreStock == null)
|
||||
{
|
||||
CreateStore();
|
||||
return;
|
||||
}
|
||||
|
||||
if (StoreCurrentBalance < StoreInitialBalance)
|
||||
{
|
||||
StoreCurrentBalance = Math.Min(StoreCurrentBalance + (int)(StoreInitialBalance / 10.0f), StoreInitialBalance);
|
||||
}
|
||||
|
||||
var stock = StoreStock;
|
||||
var stockToRemove = new List<PurchasedItem>();
|
||||
foreach (PurchasedItem item in stock)
|
||||
{
|
||||
if (item.ItemPrefab.CanBeBoughtAtLocation(this, out PriceInfo priceInfo))
|
||||
{
|
||||
item.Quantity += 1;
|
||||
if (priceInfo.MaxAvailableAmount > 0)
|
||||
{
|
||||
item.Quantity = Math.Min(item.Quantity, priceInfo.MaxAvailableAmount);
|
||||
}
|
||||
else
|
||||
{
|
||||
item.Quantity = Math.Min(item.Quantity, CargoManager.MaxQuantity);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
stockToRemove.Add(item);
|
||||
}
|
||||
}
|
||||
stockToRemove.ForEach(i => stock.Remove(i));
|
||||
StoreStock = stock;
|
||||
}
|
||||
|
||||
public void AddToStock(List<SoldItem> items)
|
||||
{
|
||||
if (StoreStock == null || items == null) { return; }
|
||||
#if DEBUG
|
||||
if (items.Any()) { DebugConsole.NewMessage("Adding items to stock at " + Name, Color.Purple); }
|
||||
#endif
|
||||
foreach (SoldItem item in items)
|
||||
{
|
||||
if (StoreStock.FirstOrDefault(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem stockItem)
|
||||
{
|
||||
stockItem.Quantity += 1;
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Added 1x " + item.ItemPrefab.Name + ", new total: " + stockItem.Quantity, Color.Cyan);
|
||||
#endif
|
||||
}
|
||||
#if DEBUG
|
||||
else
|
||||
{
|
||||
DebugConsole.NewMessage(item.ItemPrefab.Name + " not sold at location, can't add", Color.Cyan);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
public void RemoveFromStock(List<PurchasedItem> items)
|
||||
{
|
||||
if (StoreStock == null || items == null) { return; }
|
||||
#if DEBUG
|
||||
if (items.Any()) { DebugConsole.NewMessage("Removing items from stock at " + Name, Color.Purple); }
|
||||
#endif
|
||||
foreach (PurchasedItem item in items)
|
||||
{
|
||||
if (StoreStock.FirstOrDefault(i => i.ItemPrefab == item.ItemPrefab) is PurchasedItem stockItem)
|
||||
{
|
||||
stockItem.Quantity = Math.Max(stockItem.Quantity - item.Quantity, 0);
|
||||
#if DEBUG
|
||||
DebugConsole.NewMessage("Removed " + item.Quantity + "x " + item.ItemPrefab.Name + ", new total: " + stockItem.Quantity, Color.Cyan);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public XElement Save(Map map, XElement parentElement)
|
||||
{
|
||||
var locationElement = new XElement("location",
|
||||
new XAttribute("type", Type.Identifier),
|
||||
new XAttribute("basename", BaseName),
|
||||
new XAttribute("name", Name),
|
||||
new XAttribute("discovered", Discovered),
|
||||
new XAttribute("position", XMLExtensions.Vector2ToString(MapPosition)),
|
||||
new XAttribute("normalizeddepth", NormalizedDepth.ToString("G", CultureInfo.InvariantCulture)),
|
||||
new XAttribute("pricemultiplier", PriceMultiplier),
|
||||
new XAttribute("mechanicalpricemultipler", MechanicalPriceMultiplier));
|
||||
LevelData.Save(locationElement);
|
||||
|
||||
if (TypeChangeTimer > 0)
|
||||
{
|
||||
locationElement.Add(new XAttribute("changetimer", TypeChangeTimer));
|
||||
}
|
||||
if (takenItems.Any())
|
||||
{
|
||||
locationElement.Add(new XAttribute(
|
||||
"takenitems",
|
||||
string.Join(',', takenItems.Select(it => it.Identifier + ";" + it.OriginalID + ";" + it.OriginalContainerID + ";" + it.ModuleIndex))));
|
||||
}
|
||||
if (killedCharacterIdentifiers.Any())
|
||||
{
|
||||
locationElement.Add(new XAttribute("killedcharacters", string.Join(',', killedCharacterIdentifiers)));
|
||||
}
|
||||
|
||||
if (StoreStock != null)
|
||||
{
|
||||
var storeElement = new XElement("store", new XAttribute("balance", StoreCurrentBalance));
|
||||
foreach (PurchasedItem item in StoreStock)
|
||||
{
|
||||
if (item?.ItemPrefab == null) { continue; }
|
||||
storeElement.Add(new XElement("stock",
|
||||
new XAttribute("id", item.ItemPrefab.Identifier),
|
||||
new XAttribute("qty", item.Quantity)));
|
||||
}
|
||||
locationElement.Add(storeElement);
|
||||
}
|
||||
|
||||
if (AvailableMissions is List<Mission> missions && missions.Any())
|
||||
{
|
||||
var missionsElement = new XElement("missions");
|
||||
foreach (Mission mission in missions)
|
||||
{
|
||||
var location = mission.Locations.FirstOrDefault(l => l != this);
|
||||
var i = map.Locations.IndexOf(location);
|
||||
missionsElement.Add(new XElement("mission",
|
||||
new XAttribute("prefabid", mission.Prefab.Identifier),
|
||||
new XAttribute("destinationindex", i),
|
||||
new XAttribute("selected", mission == SelectedMission)));
|
||||
}
|
||||
locationElement.Add(missionsElement);
|
||||
}
|
||||
|
||||
parentElement.Add(locationElement);
|
||||
|
||||
return locationElement;
|
||||
}
|
||||
|
||||
public void Remove()
|
||||
{
|
||||
RemoveProjSpecific();
|
||||
}
|
||||
|
||||
partial void RemoveProjSpecific();
|
||||
public void RemoveProjSpecific()
|
||||
{
|
||||
HireManager?.Remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,11 +9,11 @@ namespace Barotrauma
|
||||
|
||||
public float Difficulty;
|
||||
|
||||
public List<Vector2[]> CrackSegments;
|
||||
public readonly List<Vector2[]> CrackSegments = new List<Vector2[]>();
|
||||
|
||||
public bool Passed;
|
||||
|
||||
public Level Level { get; set; }
|
||||
public LevelData LevelData { get; set; }
|
||||
|
||||
public Vector2 CenterPos
|
||||
{
|
||||
@@ -33,8 +33,7 @@ namespace Barotrauma
|
||||
|
||||
public LocationConnection(Location location1, Location location2)
|
||||
{
|
||||
Locations = new Location[] { location1, location2 };
|
||||
|
||||
Locations = new Location[] { location1, location2 };
|
||||
Length = Vector2.Distance(location1.MapPosition, location2.MapPosition);
|
||||
}
|
||||
|
||||
|
||||
@@ -13,10 +13,10 @@ namespace Barotrauma
|
||||
{
|
||||
public static readonly List<LocationType> List = new List<LocationType>();
|
||||
|
||||
private List<string> nameFormats;
|
||||
private List<string> names;
|
||||
private readonly List<string> nameFormats;
|
||||
private readonly List<string> names;
|
||||
|
||||
private Sprite symbolSprite;
|
||||
private readonly Sprite symbolSprite;
|
||||
|
||||
private readonly List<Sprite> portraits = new List<Sprite>();
|
||||
|
||||
@@ -47,6 +47,12 @@ namespace Barotrauma
|
||||
get { return hireableJobs.Any(); }
|
||||
}
|
||||
|
||||
public bool HasOutpost
|
||||
{
|
||||
get;
|
||||
private set;
|
||||
}
|
||||
|
||||
public Sprite Sprite
|
||||
{
|
||||
get { return symbolSprite; }
|
||||
@@ -66,9 +72,10 @@ namespace Barotrauma
|
||||
private LocationType(XElement element)
|
||||
{
|
||||
Identifier = element.GetAttributeString("identifier", element.Name.ToString());
|
||||
Name = TextManager.Get("LocationName." + Identifier);
|
||||
Name = TextManager.Get("LocationName." + Identifier, fallBackTag: "unknown");
|
||||
nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
|
||||
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
|
||||
HasOutpost = element.GetAttributeBool("hasoutpost", true);
|
||||
|
||||
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
|
||||
try
|
||||
@@ -158,16 +165,25 @@ namespace Barotrauma
|
||||
return portraits[Math.Abs(portraitId) % portraits.Count];
|
||||
}
|
||||
|
||||
public string GetRandomName(Random rand)
|
||||
public string GetRandomName(Random rand, IEnumerable<Location> existingLocations)
|
||||
{
|
||||
if (existingLocations != null)
|
||||
{
|
||||
var unusedNames = names.Where(name => !existingLocations.Any(l => l.BaseName == name)).ToList();
|
||||
if (unusedNames.Count > 0)
|
||||
{
|
||||
return unusedNames[rand.Next() % unusedNames.Count];
|
||||
}
|
||||
}
|
||||
return names[rand.Next() % names.Count];
|
||||
}
|
||||
|
||||
public static LocationType Random(Random rand, int? zone = null)
|
||||
public static LocationType Random(Random rand, int? zone = null, bool requireOutpost = false)
|
||||
{
|
||||
Debug.Assert(List.Count > 0, "LocationType.list.Count == 0, you probably need to initialize LocationTypes");
|
||||
|
||||
List<LocationType> allowedLocationTypes = zone.HasValue ? List.FindAll(lt => lt.CommonnessPerZone.ContainsKey(zone.Value)) : List;
|
||||
List<LocationType> allowedLocationTypes =
|
||||
List.FindAll(lt => (!zone.HasValue || lt.CommonnessPerZone.ContainsKey(zone.Value)) && (!requireOutpost || lt.HasOutpost));
|
||||
|
||||
if (allowedLocationTypes.Count == 0)
|
||||
{
|
||||
@@ -239,6 +255,11 @@ namespace Barotrauma
|
||||
List.Add(locationType);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (EventSet eventSet in EventSet.List)
|
||||
{
|
||||
eventSet.CheckLocationTypeErrors();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,16 +9,24 @@ namespace Barotrauma
|
||||
{
|
||||
partial class Map
|
||||
{
|
||||
private MapGenerationParams generationParams;
|
||||
|
||||
private readonly int size;
|
||||
private readonly MapGenerationParams generationParams;
|
||||
|
||||
private Location furthestDiscoveredLocation;
|
||||
|
||||
private int Width => generationParams.Width;
|
||||
private int Height => generationParams.Height;
|
||||
|
||||
private List<LocationConnection> connections;
|
||||
public Action<Location, LocationConnection> OnLocationSelected;
|
||||
//from -> to
|
||||
/// <summary>
|
||||
/// From -> To
|
||||
/// </summary>
|
||||
public Action<Location, Location> OnLocationChanged;
|
||||
public Action<LocationConnection, Mission> OnMissionSelected;
|
||||
|
||||
public Location EndLocation { get; private set; }
|
||||
|
||||
public Location StartLocation { get; private set; }
|
||||
|
||||
public Location CurrentLocation { get; private set; }
|
||||
|
||||
public int CurrentLocationIndex
|
||||
@@ -44,140 +52,176 @@ namespace Barotrauma
|
||||
|
||||
public List<Location> Locations { get; private set; }
|
||||
|
||||
public Map(string seed)
|
||||
public List<LocationConnection> Connections { get; private set; }
|
||||
|
||||
public Map()
|
||||
{
|
||||
generationParams = MapGenerationParams.Instance;
|
||||
this.Seed = seed;
|
||||
this.size = generationParams.Size;
|
||||
|
||||
Locations = new List<Location>();
|
||||
Connections = new List<LocationConnection>();
|
||||
}
|
||||
|
||||
connections = new List<LocationConnection>();
|
||||
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(this.Seed));
|
||||
|
||||
Generate();
|
||||
|
||||
//start from the colony furthest away from the center
|
||||
float largestDist = 0.0f;
|
||||
Vector2 center = new Vector2(size, size) / 2;
|
||||
foreach (Location location in Locations)
|
||||
/// <summary>
|
||||
/// Load a previously saved campaign map from XML
|
||||
/// </summary>
|
||||
private Map(CampaignMode campaign, XElement element) : this()
|
||||
{
|
||||
Seed = element.GetAttributeString("seed", "a");
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
if (location.Type.Identifier != "City") continue;
|
||||
float dist = Vector2.DistanceSquared(center, location.MapPosition);
|
||||
if (dist > largestDist)
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
largestDist = dist;
|
||||
CurrentLocation = location;
|
||||
case "location":
|
||||
int i = subElement.GetAttributeInt("i", 0);
|
||||
while (Locations.Count <= i)
|
||||
{
|
||||
Locations.Add(null);
|
||||
}
|
||||
Locations[i] = new Location(subElement);
|
||||
Locations[i].Reputation ??= new Reputation(campaign.CampaignMetadata, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.Discovered = true;
|
||||
System.Diagnostics.Debug.Assert(!Locations.Contains(null));
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
connection.Level = Level.CreateRandom(connection);
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "connection":
|
||||
Point locationIndices = subElement.GetAttributePoint("locations", new Point(0, 1));
|
||||
var connection = new LocationConnection(Locations[locationIndices.X], Locations[locationIndices.Y])
|
||||
{
|
||||
Passed = subElement.GetAttributeBool("passed", false),
|
||||
Difficulty = subElement.GetAttributeFloat("difficulty", 0.0f)
|
||||
};
|
||||
Locations[locationIndices.X].Connections.Add(connection);
|
||||
Locations[locationIndices.Y].Connections.Add(connection);
|
||||
connection.LevelData = new LevelData(subElement.Element("Level"));
|
||||
string biomeId = subElement.GetAttributeString("biome", "");
|
||||
connection.Biome = LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.Identifier == biomeId) ?? LevelGenerationParams.GetBiomes().First();
|
||||
Connections.Add(connection);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int startLocationindex = element.GetAttributeInt("startlocation", -1);
|
||||
if (startLocationindex > 0 && startLocationindex < Locations.Count)
|
||||
{
|
||||
StartLocation = Locations[startLocationindex];
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error while loading the map (start location index out of bounds).");
|
||||
}
|
||||
int endLocationindex = element.GetAttributeInt("endlocation", -1);
|
||||
if (endLocationindex > 0 && endLocationindex < Locations.Count)
|
||||
{
|
||||
EndLocation = Locations[endLocationindex];
|
||||
}
|
||||
else
|
||||
{
|
||||
DebugConsole.ThrowError("Error while loading the map (end location index out of bounds).");
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (EndLocation == null || location.MapPosition.X > EndLocation.MapPosition.X)
|
||||
{
|
||||
EndLocation = location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
partial void InitProjectSpecific();
|
||||
|
||||
public float[,] Noise;
|
||||
|
||||
private void GenerateNoiseMap(int octaves, float persistence)
|
||||
/// <summary>
|
||||
/// Generate a new campaign map from the seed
|
||||
/// </summary>
|
||||
public Map(CampaignMode campaign, string seed) : this()
|
||||
{
|
||||
float z = Rand.Range(0.0f, 1.0f, Rand.RandSync.Server);
|
||||
Noise = new float[generationParams.NoiseResolution, generationParams.NoiseResolution];
|
||||
|
||||
float min = float.MaxValue, max = 0.0f;
|
||||
for (int x = 0; x < generationParams.NoiseResolution; x++)
|
||||
Seed = seed;
|
||||
Rand.SetSyncedSeed(ToolBox.StringToInt(Seed));
|
||||
|
||||
Generate();
|
||||
|
||||
if (Locations.Count == 0)
|
||||
{
|
||||
for (int y = 0; y < generationParams.NoiseResolution; y++)
|
||||
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, $"location.{i}", -100, 100, Rand.Range(-10, 10, Rand.RandSync.Server));
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Type.Identifier.Equals("city", StringComparison.OrdinalIgnoreCase) &&
|
||||
!location.Type.Identifier.Equals("outpost", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (CurrentLocation == null || location.MapPosition.X < CurrentLocation.MapPosition.X)
|
||||
{
|
||||
Noise[x, y] = (float)PerlinNoise.OctavePerlin(
|
||||
(double)x / generationParams.NoiseResolution,
|
||||
(double)y / generationParams.NoiseResolution,
|
||||
z, generationParams.NoiseFrequency, octaves, persistence);
|
||||
min = Math.Min(Noise[x, y], min);
|
||||
max = Math.Max(Noise[x, y], max);
|
||||
CurrentLocation = StartLocation = furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
float radius = generationParams.NoiseResolution / 2;
|
||||
Vector2 center = Vector2.One * radius;
|
||||
float range = max - min;
|
||||
|
||||
float centerDarkenRadius = radius * generationParams.CenterDarkenRadius;
|
||||
float edgeDarkenRadius = radius * generationParams.EdgeDarkenRadius;
|
||||
for (int x = 0; x < generationParams.NoiseResolution; x++)
|
||||
{
|
||||
for (int y = 0; y < generationParams.NoiseResolution; y++)
|
||||
{
|
||||
//normalize the noise to 0-1 range
|
||||
Noise[x, y] = (Noise[x, y] - min) / range;
|
||||
CurrentLocation.CreateStore();
|
||||
CurrentLocation.Discovered = true;
|
||||
|
||||
float dist = Vector2.Distance(center, new Vector2(x, y));
|
||||
if (dist < centerDarkenRadius)
|
||||
{
|
||||
float angle = (float)Math.Atan2(y - center.Y, x - center.X);
|
||||
float phase = angle * generationParams.CenterDarkenWaveFrequency + Noise[x, y] * generationParams.CenterDarkenWavePhaseNoise;
|
||||
float currDarkenRadius = centerDarkenRadius * (0.6f + (float)Math.Sin(phase) * 0.4f);
|
||||
if (dist < currDarkenRadius)
|
||||
{
|
||||
float darkenAmount = 1.0f - (dist / currDarkenRadius);
|
||||
Noise[x, y] = MathHelper.Lerp(Noise[x, y], Noise[x, y] * (1.0f - generationParams.CenterDarkenStrength), darkenAmount);
|
||||
}
|
||||
}
|
||||
if (dist > edgeDarkenRadius)
|
||||
{
|
||||
float darkenAmount = Math.Min((dist - edgeDarkenRadius) / (radius - edgeDarkenRadius), 1.0f);
|
||||
Noise[x, y] = MathHelper.Lerp(Noise[x, y], 1.0f - generationParams.EdgeDarkenStrength, darkenAmount);
|
||||
}
|
||||
}
|
||||
}
|
||||
InitProjectSpecific();
|
||||
}
|
||||
|
||||
partial void GenerateNoiseMapProjSpecific();
|
||||
partial void InitProjectSpecific();
|
||||
|
||||
#region Generation
|
||||
|
||||
private void Generate()
|
||||
{
|
||||
connections.Clear();
|
||||
Connections.Clear();
|
||||
Locations.Clear();
|
||||
|
||||
GenerateNoiseMap(generationParams.NoiseOctaves, generationParams.NoisePersistence);
|
||||
|
||||
List<Vector2> sites = new List<Vector2>();
|
||||
float mapRadius = size / 2;
|
||||
Vector2 mapCenter = new Vector2(mapRadius, mapRadius);
|
||||
|
||||
float locationRadius = mapRadius * generationParams.LocationRadius;
|
||||
|
||||
for (float x = mapCenter.X - locationRadius; x < mapCenter.X + locationRadius; x += generationParams.VoronoiSiteInterval)
|
||||
List<Vector2> voronoiSites = new List<Vector2>();
|
||||
for (float x = 10.0f; x < Width - 10.0f; x += generationParams.VoronoiSiteInterval.X)
|
||||
{
|
||||
for (float y = mapCenter.Y - locationRadius; y < mapCenter.Y + locationRadius; y += generationParams.VoronoiSiteInterval)
|
||||
for (float y = 10.0f; y < Height - 10.0f; y += generationParams.VoronoiSiteInterval.Y)
|
||||
{
|
||||
float noiseVal = Noise[(int)(x / size * generationParams.NoiseResolution), (int)(y / size * generationParams.NoiseResolution)];
|
||||
if (Rand.Range(generationParams.VoronoiSitePlacementMinVal, 1.0f, Rand.RandSync.Server) <
|
||||
noiseVal * generationParams.VoronoiSitePlacementProbability)
|
||||
{
|
||||
sites.Add(new Vector2(x, y));
|
||||
}
|
||||
voronoiSites.Add(new Vector2(
|
||||
x + generationParams.VoronoiSiteVariance.X * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server),
|
||||
y + generationParams.VoronoiSiteVariance.Y * Rand.Range(-0.5f, 0.5f, Rand.RandSync.Server)));
|
||||
}
|
||||
}
|
||||
|
||||
Voronoi voronoi = new Voronoi(0.5f);
|
||||
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
|
||||
float zoneRadius = size / 2 / generationParams.DifficultyZones;
|
||||
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(voronoiSites, Width, Height);
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
|
||||
sites.Clear();
|
||||
Vector2 margin = new Vector2(
|
||||
Math.Min(10, Width * 0.1f),
|
||||
Math.Min(10, Height * 0.2f));
|
||||
|
||||
float startX = margin.X, endX = Width - margin.X;
|
||||
float startY = margin.Y, endY = Height - margin.Y;
|
||||
|
||||
if (!edges.Any())
|
||||
{
|
||||
throw new Exception($"Generating a campaign map failed (no edges in the voronoi graph). Width: {Width}, height: {Height}, margin: {margin}");
|
||||
}
|
||||
|
||||
voronoiSites.Clear();
|
||||
foreach (GraphEdge edge in edges)
|
||||
{
|
||||
if (edge.Point1 == edge.Point2) continue;
|
||||
|
||||
if (Vector2.DistanceSquared(edge.Point1, mapCenter) >= locationRadius * locationRadius ||
|
||||
Vector2.DistanceSquared(edge.Point2, mapCenter) >= locationRadius * locationRadius) continue;
|
||||
if (edge.Point1 == edge.Point2) { continue; }
|
||||
|
||||
if (edge.Point1.X < margin.X || edge.Point1.X > Width - margin.X || edge.Point1.Y < startY || edge.Point1.Y > endY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (edge.Point2.X < margin.X || edge.Point2.X > Width - margin.X || edge.Point2.Y < startY || edge.Point2.Y > endY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Location[] newLocations = new Location[2];
|
||||
newLocations[0] = Locations.Find(l => l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2);
|
||||
@@ -193,23 +237,20 @@ namespace Barotrauma
|
||||
|
||||
Vector2 position = points[positionIndex];
|
||||
if (newLocations[1 - i] != null && newLocations[1 - i].MapPosition == position) position = points[1 - positionIndex];
|
||||
int zone = MathHelper.Clamp(generationParams.DifficultyZones - (int)Math.Floor(Vector2.Distance(position, mapCenter) / zoneRadius), 1, generationParams.DifficultyZones);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server));
|
||||
int zone = MathHelper.Clamp((int)Math.Floor(position.X / zoneWidth) + 1, 1, generationParams.DifficultyZones);
|
||||
newLocations[i] = Location.CreateRandom(position, zone, Rand.GetRNG(Rand.RandSync.Server), requireOutpost: false, Locations);
|
||||
Locations.Add(newLocations[i]);
|
||||
}
|
||||
|
||||
var newConnection = new LocationConnection(newLocations[0], newLocations[1]);
|
||||
float centerDist = Vector2.Distance(newConnection.CenterPos, mapCenter);
|
||||
newConnection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 10.0f, Rand.RandSync.Server), 0, 100);
|
||||
|
||||
connections.Add(newConnection);
|
||||
Connections.Add(newConnection);
|
||||
}
|
||||
|
||||
//remove connections that are too short
|
||||
float minConnectionDistanceSqr = generationParams.MinConnectionDistance * generationParams.MinConnectionDistance;
|
||||
for (int i = connections.Count - 1; i >= 0; i--)
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
LocationConnection connection = connections[i];
|
||||
LocationConnection connection = Connections[i];
|
||||
|
||||
if (Vector2.DistanceSquared(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minConnectionDistanceSqr)
|
||||
{
|
||||
@@ -217,17 +258,17 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
//locations.Remove(connection.Locations[0]);
|
||||
connections.Remove(connection);
|
||||
Connections.Remove(connection);
|
||||
|
||||
foreach (LocationConnection connection2 in connections)
|
||||
foreach (LocationConnection connection2 in Connections)
|
||||
{
|
||||
if (connection2.Locations[0] == connection.Locations[0]) connection2.Locations[0] = connection.Locations[1];
|
||||
if (connection2.Locations[1] == connection.Locations[0]) connection2.Locations[1] = connection.Locations[1];
|
||||
if (connection2.Locations[0] == connection.Locations[0]) { connection2.Locations[0] = connection.Locations[1]; }
|
||||
if (connection2.Locations[1] == connection.Locations[0]) { connection2.Locations[1] = connection.Locations[1]; }
|
||||
}
|
||||
}
|
||||
|
||||
HashSet<Location> connectedLocations = new HashSet<Location>();
|
||||
foreach (LocationConnection connection in connections)
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.Locations[0].Connections.Add(connection);
|
||||
connection.Locations[1].Connections.Add(connection);
|
||||
@@ -263,57 +304,182 @@ namespace Barotrauma
|
||||
}
|
||||
Locations[i].Connections.Add(connection);
|
||||
}
|
||||
Locations[i].Connections.RemoveAll(c => c.OtherLocation(Locations[i]) == Locations[j]);
|
||||
Locations.RemoveAt(j);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = connections.Count - 1; i >= 0; i--)
|
||||
for (int i = Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
i = Math.Min(i, connections.Count - 1);
|
||||
LocationConnection connection = connections[i];
|
||||
for (int n = Math.Min(i - 1, connections.Count - 1); n >= 0; n--)
|
||||
i = Math.Min(i, Connections.Count - 1);
|
||||
LocationConnection connection = Connections[i];
|
||||
for (int n = Math.Min(i - 1, Connections.Count - 1); n >= 0; n--)
|
||||
{
|
||||
if (connection.Locations.Contains(connections[n].Locations[0])
|
||||
&& connection.Locations.Contains(connections[n].Locations[1]))
|
||||
if (connection.Locations.Contains(Connections[n].Locations[0])
|
||||
&& connection.Locations.Contains(Connections[n].Locations[1]))
|
||||
{
|
||||
connections.RemoveAt(n);
|
||||
Connections.RemoveAt(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in connections)
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
float centerDist = Vector2.Distance(connection.CenterPos, mapCenter);
|
||||
connection.Difficulty = MathHelper.Clamp(((1.0f - centerDist / mapRadius) * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 0, 100);
|
||||
for (int i = location.Connections.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (!Connections.Contains(location.Connections[i]))
|
||||
{
|
||||
location.Connections.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.Difficulty = MathHelper.Clamp((connection.CenterPos.X / Width * 100) + Rand.Range(-10.0f, 0.0f, Rand.RandSync.Server), 1.2f, 100.0f);
|
||||
}
|
||||
|
||||
AssignBiomes();
|
||||
CreateEndLocation();
|
||||
|
||||
GenerateNoiseMapProjSpecific();
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location.LevelData = new LevelData(location);
|
||||
location.NormalizedDepth = location.MapPosition.X / Width;
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
connection.LevelData = new LevelData(connection);
|
||||
}
|
||||
}
|
||||
|
||||
partial void GenerateLocationConnectionVisuals();
|
||||
|
||||
public Biome GetBiome(Vector2 mapPos)
|
||||
{
|
||||
return GetBiome(mapPos.X);
|
||||
}
|
||||
|
||||
public Biome GetBiome(float xPos)
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
int zoneIndex = (int)Math.Floor(xPos / zoneWidth) + 1;
|
||||
if (zoneIndex < 1)
|
||||
{
|
||||
return LevelGenerationParams.GetBiomes().First();
|
||||
|
||||
}
|
||||
else if (zoneIndex >= generationParams.DifficultyZones)
|
||||
{
|
||||
return LevelGenerationParams.GetBiomes().Last();
|
||||
}
|
||||
return LevelGenerationParams.GetBiomes().FirstOrDefault(b => b.AllowedZones.Contains(zoneIndex));
|
||||
}
|
||||
|
||||
private void AssignBiomes()
|
||||
{
|
||||
float locationRadius = size * 0.5f * generationParams.LocationRadius;
|
||||
|
||||
var biomes = LevelGenerationParams.GetBiomes();
|
||||
Vector2 centerPos = new Vector2(size, size) / 2;
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
|
||||
List<Biome> allowedBiomes = new List<Biome>(10);
|
||||
for (int i = 0; i < generationParams.DifficultyZones; i++)
|
||||
{
|
||||
List<Biome> allowedBiomes = biomes.FindAll(b => b.AllowedZones.Contains(generationParams.DifficultyZones - i));
|
||||
float zoneRadius = locationRadius * ((i + 1.0f) / generationParams.DifficultyZones);
|
||||
foreach (LocationConnection connection in connections)
|
||||
{
|
||||
if (connection.Biome != null) continue;
|
||||
allowedBiomes.Clear();
|
||||
allowedBiomes.AddRange(biomes.Where(b => b.AllowedZones.Contains(generationParams.DifficultyZones - i)));
|
||||
float zoneX = Width - zoneWidth * i;
|
||||
|
||||
if (i == generationParams.DifficultyZones - 1 ||
|
||||
Vector2.Distance(connection.Locations[0].MapPosition, centerPos) < zoneRadius ||
|
||||
Vector2.Distance(connection.Locations[1].MapPosition, centerPos) < zoneRadius)
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (location.MapPosition.X < zoneX)
|
||||
{
|
||||
connection.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)];
|
||||
location.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)];
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (LocationConnection connection in Connections)
|
||||
{
|
||||
if (connection.Biome != null) { continue; }
|
||||
connection.Biome = connection.Locations[0].Biome;
|
||||
}
|
||||
|
||||
System.Diagnostics.Debug.Assert(Locations.All(l => l.Biome != null));
|
||||
System.Diagnostics.Debug.Assert(Connections.All(c => c.Biome != null));
|
||||
}
|
||||
|
||||
private void CreateEndLocation()
|
||||
{
|
||||
float zoneWidth = Width / generationParams.DifficultyZones;
|
||||
Vector2 endPos = new Vector2(Width - zoneWidth / 2, Height / 2);
|
||||
float closestDist = float.MaxValue;
|
||||
EndLocation = Locations.First();
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
float dist = Vector2.DistanceSquared(endPos, location.MapPosition);
|
||||
if (location.Biome.IsEndBiome && dist < closestDist)
|
||||
{
|
||||
EndLocation = location;
|
||||
closestDist = dist;
|
||||
}
|
||||
}
|
||||
|
||||
Location previousToEndLocation = null;
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Biome.IsEndBiome && (previousToEndLocation == null || location.MapPosition.X > previousToEndLocation.MapPosition.X))
|
||||
{
|
||||
previousToEndLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
if (EndLocation == null || previousToEndLocation == null) { return; }
|
||||
|
||||
//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)
|
||||
{
|
||||
for (int j = Locations[i].Connections.Count - 1; j >= 0; j--)
|
||||
{
|
||||
if (j >= Locations[i].Connections.Count) { continue; }
|
||||
var connection = Locations[i].Connections[j];
|
||||
var otherLocation = connection.OtherLocation(Locations[i]);
|
||||
Locations[i].Connections.RemoveAt(j);
|
||||
otherLocation?.Connections.Remove(connection);
|
||||
Connections.Remove(connection);
|
||||
}
|
||||
Locations.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
//removed all connections from the second-to-last location, need to reconnect it
|
||||
if (!previousToEndLocation.Connections.Any())
|
||||
{
|
||||
Location connectTo = Locations.First();
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Biome.IsEndBiome && location != previousToEndLocation && location.MapPosition.X > connectTo.MapPosition.X)
|
||||
{
|
||||
connectTo = location;
|
||||
}
|
||||
}
|
||||
var newConnection = new LocationConnection(previousToEndLocation, connectTo)
|
||||
{
|
||||
Biome = EndLocation.Biome,
|
||||
Difficulty = 100.0f
|
||||
};
|
||||
Connections.Add(newConnection);
|
||||
previousToEndLocation.Connections.Add(newConnection);
|
||||
connectTo.Connections.Add(newConnection);
|
||||
}
|
||||
|
||||
var endConnection = new LocationConnection(previousToEndLocation, EndLocation)
|
||||
{
|
||||
Biome = EndLocation.Biome,
|
||||
Difficulty = 100.0f
|
||||
};
|
||||
Connections.Add(endConnection);
|
||||
previousToEndLocation.Connections.Add(endConnection);
|
||||
EndLocation.Connections.Add(endConnection);
|
||||
}
|
||||
|
||||
private void ExpandBiomes(List<LocationConnection> seeds)
|
||||
@@ -340,9 +506,22 @@ namespace Barotrauma
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#endregion Generation
|
||||
|
||||
public void MoveToNextLocation()
|
||||
{
|
||||
if (SelectedConnection == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no connection selected).\n"+Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
if (SelectedLocation == null)
|
||||
{
|
||||
DebugConsole.ThrowError("Could not move to the next location (no location selected).\n" + Environment.StackTrace);
|
||||
return;
|
||||
}
|
||||
|
||||
Location prevLocation = CurrentLocation;
|
||||
SelectedConnection.Passed = true;
|
||||
|
||||
@@ -350,6 +529,7 @@ namespace Barotrauma
|
||||
CurrentLocation.Discovered = true;
|
||||
SelectedLocation = null;
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
@@ -371,6 +551,16 @@ namespace Barotrauma
|
||||
CurrentLocation = Locations[index];
|
||||
CurrentLocation.Discovered = true;
|
||||
|
||||
if (prevLocation != CurrentLocation)
|
||||
{
|
||||
var connection = CurrentLocation.Connections.Find(c => c.Locations.Contains(prevLocation));
|
||||
if (connection != null)
|
||||
{
|
||||
connection.Passed = true;
|
||||
}
|
||||
}
|
||||
|
||||
CurrentLocation.CreateStore();
|
||||
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
|
||||
}
|
||||
|
||||
@@ -392,7 +582,9 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SelectedLocation = Locations[index];
|
||||
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
SelectedConnection =
|
||||
Connections.Find(c => c.Locations.Contains(GameMain.GameSession?.Campaign?.CurrentDisplayLocation) && c.Locations.Contains(SelectedLocation)) ??
|
||||
Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
@@ -407,7 +599,7 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
SelectedLocation = location;
|
||||
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
SelectedConnection = Connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
|
||||
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
|
||||
}
|
||||
|
||||
@@ -427,7 +619,7 @@ namespace Barotrauma
|
||||
if (CurrentLocation.SelectedMission != null &&
|
||||
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
|
||||
{
|
||||
SelectLocation(CurrentLocation.SelectedMission.Locations[1]);
|
||||
CurrentLocation.SelectedMissionIndex = -1;
|
||||
}
|
||||
|
||||
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
|
||||
@@ -452,7 +644,12 @@ namespace Barotrauma
|
||||
{
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
if (!location.Discovered) continue;
|
||||
if (!location.Discovered) { continue; }
|
||||
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
|
||||
//find which types of locations this one can change to
|
||||
List<LocationTypeChange> allowedTypeChanges = new List<LocationTypeChange>();
|
||||
@@ -469,7 +666,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (disallowedFound) continue;
|
||||
if (disallowedFound) { continue; }
|
||||
|
||||
//check that there's a required adjacent location present
|
||||
bool requiredFound = false;
|
||||
@@ -481,7 +678,7 @@ namespace Barotrauma
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) continue;
|
||||
if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) { continue; }
|
||||
|
||||
allowedTypeChanges.Add(typeChange);
|
||||
|
||||
@@ -514,22 +711,76 @@ namespace Barotrauma
|
||||
{
|
||||
location.TypeChangeTimer = 0;
|
||||
}
|
||||
|
||||
location.UpdateStore();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change);
|
||||
partial void ClearAnimQueue();
|
||||
|
||||
public static Map LoadNew(XElement element)
|
||||
/// <summary>
|
||||
/// Load a previously saved map from an xml element
|
||||
/// </summary>
|
||||
public static Map Load(CampaignMode campaign, XElement element)
|
||||
{
|
||||
string mapSeed = element.GetAttributeString("seed", "a");
|
||||
Map map = new Map(mapSeed);
|
||||
map.Load(element, false);
|
||||
|
||||
Map map = new Map(campaign, element);
|
||||
map.LoadState(element, false);
|
||||
#if CLIENT
|
||||
map.DrawOffset = -map.CurrentLocation.MapPosition;
|
||||
#endif
|
||||
return map;
|
||||
}
|
||||
|
||||
public void Load(XElement element, bool showNotifications)
|
||||
/// <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)
|
||||
{
|
||||
ClearAnimQueue();
|
||||
SetLocation(element.GetAttributeInt("currentlocation", 0));
|
||||
@@ -545,17 +796,27 @@ namespace Barotrauma
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
case "location":
|
||||
string locationType = subElement.GetAttributeString("type", "");
|
||||
Location location = Locations[subElement.GetAttributeInt("i", 0)];
|
||||
int typeChangeTimer = subElement.GetAttributeInt("changetimer", 0);
|
||||
int missionsCompleted = subElement.GetAttributeInt("missionscompleted", 0);
|
||||
|
||||
location.TypeChangeTimer = subElement.GetAttributeInt("changetimer", 0);
|
||||
location.Discovered = subElement.GetAttributeBool("discovered", false);
|
||||
if (location.Discovered)
|
||||
{
|
||||
#if CLIENT
|
||||
RemoveFogOfWar(StartLocation);
|
||||
#endif
|
||||
if (furthestDiscoveredLocation == null || location.MapPosition.X > furthestDiscoveredLocation.MapPosition.X)
|
||||
{
|
||||
furthestDiscoveredLocation = location;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
string locationType = subElement.GetAttributeString("type", "");
|
||||
string prevLocationName = location.Name;
|
||||
LocationType prevLocationType = location.Type;
|
||||
location.Discovered = true;
|
||||
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)));
|
||||
location.TypeChangeTimer = typeChangeTimer;
|
||||
location.MissionsCompleted = missionsCompleted;
|
||||
LocationType newLocationType = LocationType.List.Find(lt => lt.Identifier.Equals(locationType, StringComparison.OrdinalIgnoreCase)) ?? LocationType.List.First();
|
||||
location.ChangeType(newLocationType);
|
||||
if (showNotifications && prevLocationType != location.Type)
|
||||
{
|
||||
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase));
|
||||
@@ -564,13 +825,25 @@ namespace Barotrauma
|
||||
ChangeLocationType(location, prevLocationName, change);
|
||||
}
|
||||
}
|
||||
location.LoadMissions(subElement);
|
||||
break;
|
||||
case "connection":
|
||||
int connectionIndex = subElement.GetAttributeInt("i", 0);
|
||||
connections[connectionIndex].Passed = true;
|
||||
Connections[connectionIndex].Passed = subElement.GetAttributeBool("passed", false);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
foreach (Location location in Locations)
|
||||
{
|
||||
location?.InstantiateLoadedMissions(this);
|
||||
}
|
||||
|
||||
int currentLocationConnection = element.GetAttributeInt("currentlocationconnection", -1);
|
||||
if (currentLocationConnection >= 0)
|
||||
{
|
||||
SelectLocation(Connections[currentLocationConnection].OtherLocation(CurrentLocation));
|
||||
}
|
||||
}
|
||||
|
||||
public void Save(XElement element)
|
||||
@@ -579,38 +852,39 @@ namespace Barotrauma
|
||||
|
||||
mapElement.Add(new XAttribute("version", GameMain.Version.ToString()));
|
||||
mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex));
|
||||
if (GameMain.GameSession.GameMode is CampaignMode campaign)
|
||||
{
|
||||
if (campaign.NextLevel != null && campaign.NextLevel.Type == LevelData.LevelType.LocationConnection)
|
||||
{
|
||||
mapElement.Add(new XAttribute("currentlocationconnection", Connections.IndexOf(CurrentLocation.Connections.Find(c => c.LevelData == campaign.NextLevel))));
|
||||
}
|
||||
else if (Level.Loaded != null && Level.Loaded.Type == LevelData.LevelType.LocationConnection && !CurrentLocation.Type.HasOutpost)
|
||||
{
|
||||
mapElement.Add(new XAttribute("currentlocationconnection", Connections.IndexOf(Connections.Find(c => c.LevelData == Level.Loaded.LevelData))));
|
||||
}
|
||||
}
|
||||
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("seed", Seed));
|
||||
|
||||
for (int i = 0; i < Locations.Count; i++)
|
||||
{
|
||||
var location = Locations[i];
|
||||
if (!location.Discovered) continue;
|
||||
|
||||
var locationElement = new XElement("location", new XAttribute("i", i));
|
||||
locationElement.Add(new XAttribute("type", location.Type.Identifier));
|
||||
if (location.TypeChangeTimer > 0)
|
||||
{
|
||||
locationElement.Add(new XAttribute("changetimer", location.TypeChangeTimer));
|
||||
}
|
||||
|
||||
location.CheckMissionCompleted();
|
||||
if (location.MissionsCompleted > 0)
|
||||
{
|
||||
locationElement.Add(new XAttribute("missionscompleted", location.MissionsCompleted));
|
||||
}
|
||||
|
||||
mapElement.Add(locationElement);
|
||||
var locationElement = location.Save(this, mapElement);
|
||||
locationElement.Add(new XAttribute("i", i));
|
||||
}
|
||||
|
||||
for (int i = 0; i < connections.Count; i++)
|
||||
for (int i = 0; i < Connections.Count; i++)
|
||||
{
|
||||
var connection = connections[i];
|
||||
if (!connection.Passed) continue;
|
||||
var connection = Connections[i];
|
||||
|
||||
var connectionElement = new XElement("connection",
|
||||
new XAttribute("i", i),
|
||||
new XAttribute("passed", connection.Passed));
|
||||
|
||||
new XAttribute("passed", connection.Passed),
|
||||
new XAttribute("difficulty", connection.Difficulty),
|
||||
new XAttribute("biome", connection.Biome.Identifier),
|
||||
new XAttribute("locations", Locations.IndexOf(connection.Locations[0]) + "," + Locations.IndexOf(connection.Locations[1])));
|
||||
connection.LevelData.Save(connectionElement);
|
||||
mapElement.Add(connectionElement);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
using Microsoft.Xna.Framework;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace Barotrauma
|
||||
@@ -20,9 +18,6 @@ namespace Barotrauma
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
[Serialize(false, true), Editable]
|
||||
public bool ShowNoiseMap { get; set; }
|
||||
|
||||
[Serialize(true, true), Editable]
|
||||
public bool ShowLocations { get; set; }
|
||||
|
||||
@@ -40,8 +35,11 @@ namespace Barotrauma
|
||||
[Serialize(6, true)]
|
||||
public int DifficultyZones { get; set; } //Number of difficulty zones
|
||||
|
||||
[Serialize(2000, true)]
|
||||
public int Size { get; set; }
|
||||
[Serialize(8000, true), Editable]
|
||||
public int Width { get; set; }
|
||||
|
||||
[Serialize(500, true), Editable]
|
||||
public int Height { get; set; }
|
||||
|
||||
[Serialize(20.0f, true, description: "Connections with a length smaller or equal to this generate the smallest possible levels (using the MinWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)]
|
||||
public float SmallLevelConnectionLength { get; set; }
|
||||
@@ -49,57 +47,13 @@ namespace Barotrauma
|
||||
[Serialize(200.0f, true, description: "Connections with a length larger or equal to this generate the largest possible levels (using the MaxWidth parameter in the level generation paramaters)."), Editable(0.0f, 5000.0f)]
|
||||
public float LargeLevelConnectionLength { get; set; }
|
||||
|
||||
[Serialize(1024, true)]
|
||||
public int NoiseResolution { get; set; } //Resolution of the noisemap overlay
|
||||
|
||||
[Serialize(10.0f, true), Editable(0.0f, 1000.0f)]
|
||||
public float NoiseFrequency { get; set; }
|
||||
|
||||
[Serialize(8, true), Editable(1, 100)]
|
||||
public int NoiseOctaves { get; set; }
|
||||
|
||||
[Serialize(0.5f, true), Editable(0.0f, 1.0f)]
|
||||
public float NoisePersistence { get; set; }
|
||||
|
||||
[Serialize("200,200", true), Editable]
|
||||
public Vector2 TileSpriteSize { get; set; }
|
||||
[Serialize("280,80", true), Editable]
|
||||
public Vector2 TileSpriteSpacing { get; set; }
|
||||
|
||||
[Serialize(1.0f, true, description: "How dark the center of the map is (1.0f = black)."), Editable(0.0f, 1.0f)]
|
||||
public float CenterDarkenStrength { get; set; }
|
||||
|
||||
[Serialize(0.9f, true, description: "How close to the center the darkening starts (0.8f = 20% from the edge)."), Editable(0.0f, 1.0f)]
|
||||
public float CenterDarkenRadius { get; set; }
|
||||
|
||||
[Serialize(5, true, description: "The edge of the dark center area is wave-shaped, and the frequency is determined by this value." +
|
||||
" I.e. how many points does the star-shaped dark area in the center have."), Editable(0, 1000)]
|
||||
public int CenterDarkenWaveFrequency { get; set; }
|
||||
|
||||
[Serialize(15.0f, true, description: "How heavily the noise map affects the phase of the edge wave (higher value = more irregular shape)."), Editable(0, 1000.0f)]
|
||||
public float CenterDarkenWavePhaseNoise { get; set; }
|
||||
|
||||
[Serialize(0.8f, true, description: "How dark the edges of the map are (1.0f = black)."), Editable(0.0f, 1.0f)]
|
||||
public float EdgeDarkenStrength { get; set; }
|
||||
|
||||
[Serialize(0.9f, true, description: "How far from the center the darkening starts (0.95f = 5% from the edge)."), Editable(0.0f, 1.0f)]
|
||||
public float EdgeDarkenRadius { get; set; }
|
||||
|
||||
[Serialize(0.9f, true, description: "How far from the center locations can be placed."), Editable(0.0f, 1.0f)]
|
||||
public float LocationRadius { get; set; }
|
||||
|
||||
[Serialize(20.0f, true, description: "How far from each other voronoi sites are placed. " +
|
||||
[Serialize("20,20", true, description: "How far from each other voronoi sites are placed. " +
|
||||
"Sites determine shape of the voronoi graph. Locations are placed at the vertices of the voronoi cells. " +
|
||||
"(Decreasing this value causes the number of sites, and the complexity of the map, to increase exponentially - be careful when adjusting)"), Editable(1.0f, 100.0f)]
|
||||
public float VoronoiSiteInterval { get; set; }
|
||||
"(Decreasing this value causes the number of sites, and the complexity of the map, to increase exponentially - be careful when adjusting)"), Editable]
|
||||
public Point VoronoiSiteInterval { get; set; }
|
||||
|
||||
[Serialize(0.3f, true, description: "How likely it is for a site to be placed at a given spot (e.g. 20% probability for a site to be placed every 5 units of the map). " +
|
||||
"Multiplied with the noise value in the spot, meaning that sites are less likely to appear in dark spots."), Editable(0.01f, 1.0f)]
|
||||
public float VoronoiSitePlacementProbability { get; set; }
|
||||
|
||||
[Serialize(0.1f, true, description: "Probability * noise ^ 2 must be higher than this for a site to be placed. " +
|
||||
"= How bright the noise map must be at a given spot for a location to be placed there"), Editable(0.01f, 1.0f)]
|
||||
public float VoronoiSitePlacementMinVal { get; set; }
|
||||
[Serialize("5,5", true), Editable]
|
||||
public Point VoronoiSiteVariance { get; set; }
|
||||
|
||||
[Serialize(10.0f, true, description: "Connections smaller than this are removed."), Editable(0.0f, 500.0f)]
|
||||
public float MinConnectionDistance { get; set; }
|
||||
@@ -107,48 +61,60 @@ namespace Barotrauma
|
||||
[Serialize(5.0f, true, description: "Locations that are closer than this to another location are removed."), Editable(0.0f, 100.0f)]
|
||||
public float MinLocationDistance { get; set; }
|
||||
|
||||
[Serialize(0.2f, true, description: "Affects how many iterations are done when generating the jagged shape of the connections (iterations = Sqrt(connectionLength * multiplier))."), Editable(0.0f, 10.0f)]
|
||||
public float ConnectionIterationMultiplier { get; set; }
|
||||
|
||||
[Serialize(0.5f, true, description: "How large the \"bends\" in the connections are (displacement = connectionLength * multiplier)."), Editable(0.0f, 10.0f)]
|
||||
public float ConnectionDisplacementMultiplier { get; set; }
|
||||
|
||||
[Serialize(0.1f, true, description: "ConnectionIterationMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f)]
|
||||
[Serialize(0.1f, true, description: "ConnectionIterationMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
public float ConnectionIndicatorIterationMultiplier { get; set; }
|
||||
|
||||
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f)]
|
||||
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f, DecimalCount = 2)]
|
||||
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
|
||||
|
||||
public Sprite ConnectionSprite { get; private set; }
|
||||
|
||||
#if CLIENT
|
||||
|
||||
[Serialize(0.75f, true), Editable(DecimalCount = 2)]
|
||||
public float MinZoom { get; set; }
|
||||
|
||||
[Serialize(1.5f, true), Editable(DecimalCount = 2)]
|
||||
public float MaxZoom { get; set; }
|
||||
|
||||
[Serialize(1.0f, true), Editable(DecimalCount = 2)]
|
||||
public float MapTileScale { get; set; }
|
||||
|
||||
[Serialize(15.0f, true, description: "Size of the location icons in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)]
|
||||
public float LocationIconSize { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the low-difficulty connections on the map."), Editable()]
|
||||
public Color LowDifficultyColor { get; set; }
|
||||
[Serialize("210,143,83,255", true, description: "The color used to display the medium-difficulty connections on the map."), Editable()]
|
||||
public Color MediumDifficultyColor { get; set; }
|
||||
[Serialize("216,154,138", true, description: "The color used to display the high-difficulty connections on the map."), Editable()]
|
||||
public Color HighDifficultyColor { get; set; }
|
||||
[Serialize(5.0f, true, description: "Width of the connections between locations, in pixels when at 100% zoom."), Editable(1.0f, 1000.0f)]
|
||||
public float LocationConnectionWidth { get; set; }
|
||||
|
||||
[Serialize("220,220,100,255", true, description: "The color used to display the indicators (current location, selected location, etc)."), Editable()]
|
||||
public Color IndicatorColor { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the connections between locations."), Editable()]
|
||||
public Color ConnectionColor { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the connections between locations when they're highlighted."), Editable()]
|
||||
public Color HighlightedConnectionColor { get; set; }
|
||||
|
||||
[Serialize("150,150,150,255", true, description: "The color used to display the connections the player hasn't travelled through."), Editable()]
|
||||
public Color UnvisitedConnectionColor { get; set; }
|
||||
|
||||
public Sprite ConnectionSprite { get; private set; }
|
||||
public Sprite PassedConnectionSprite { get; private set; }
|
||||
|
||||
public SpriteSheet DecorativeMapSprite { get; private set; }
|
||||
public SpriteSheet DecorativeGraphSprite { get; private set; }
|
||||
public SpriteSheet DecorativeLineTop { get; private set; }
|
||||
public SpriteSheet DecorativeLineBottom { get; private set; }
|
||||
public SpriteSheet DecorativeLineCorner { get; private set; }
|
||||
|
||||
public SpriteSheet ReticleLarge { get; private set; }
|
||||
public SpriteSheet ReticleMedium { get; private set; }
|
||||
public SpriteSheet ReticleSmall { get; private set; }
|
||||
public Sprite MissionIcon { get; private set; }
|
||||
public Sprite TypeChangeIcon { get; private set; }
|
||||
|
||||
public Sprite MapCircle { get; private set; }
|
||||
public Sprite LocationIndicator { get; private set; }
|
||||
public Sprite FogOfWarSprite { get; private set; }
|
||||
public Sprite CurrentLocationIndicator { get; private set; }
|
||||
public Sprite SelectedLocationIndicator { get; private set; }
|
||||
|
||||
private readonly Dictionary<string, List<Sprite>> mapTiles = new Dictionary<string, List<Sprite>>();
|
||||
public Dictionary<string, List<Sprite>> MapTiles
|
||||
{
|
||||
get { return mapTiles; }
|
||||
}
|
||||
#endif
|
||||
|
||||
public List<Sprite> BackgroundTileSprites { get; private set; }
|
||||
|
||||
public string Name
|
||||
{
|
||||
get { return GetType().ToString(); }
|
||||
@@ -195,19 +161,26 @@ namespace Barotrauma
|
||||
|
||||
if (selectedFile == loadedFile) { return; }
|
||||
|
||||
instance?.ConnectionSprite?.Remove();
|
||||
instance?.BackgroundTileSprites.ForEach(s => s.Remove());
|
||||
#if CLIENT
|
||||
instance?.MapCircle?.Remove();
|
||||
instance?.LocationIndicator?.Remove();
|
||||
instance?.DecorativeMapSprite?.Remove();
|
||||
instance?.DecorativeGraphSprite?.Remove();
|
||||
instance?.DecorativeLineTop?.Remove();
|
||||
instance?.DecorativeLineBottom?.Remove();
|
||||
instance?.DecorativeLineCorner?.Remove();
|
||||
instance?.ReticleLarge?.Remove();
|
||||
instance?.ReticleMedium?.Remove();
|
||||
instance?.ReticleSmall?.Remove();
|
||||
if (instance != null)
|
||||
{
|
||||
instance?.ConnectionSprite?.Remove();
|
||||
instance?.PassedConnectionSprite?.Remove();
|
||||
instance?.SelectedLocationIndicator?.Remove();
|
||||
instance?.CurrentLocationIndicator?.Remove();
|
||||
instance?.DecorativeGraphSprite?.Remove();
|
||||
instance?.MissionIcon?.Remove();
|
||||
instance?.TypeChangeIcon?.Remove();
|
||||
instance?.FogOfWarSprite?.Remove();
|
||||
foreach (List<Sprite> spriteList in instance.mapTiles.Values)
|
||||
{
|
||||
foreach (Sprite sprite in spriteList)
|
||||
{
|
||||
sprite.Remove();
|
||||
}
|
||||
}
|
||||
instance.mapTiles.Clear();
|
||||
}
|
||||
#endif
|
||||
instance = null;
|
||||
|
||||
@@ -225,48 +198,44 @@ namespace Barotrauma
|
||||
private MapGenerationParams(XElement element)
|
||||
{
|
||||
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
|
||||
BackgroundTileSprites = new List<Sprite>();
|
||||
|
||||
foreach (XElement subElement in element.Elements())
|
||||
{
|
||||
switch (subElement.Name.ToString().ToLowerInvariant())
|
||||
{
|
||||
#if CLIENT
|
||||
case "connectionsprite":
|
||||
ConnectionSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "backgroundtile":
|
||||
BackgroundTileSprites.Add(new Sprite(subElement));
|
||||
case "passedconnectionsprite":
|
||||
PassedConnectionSprite = new Sprite(subElement);
|
||||
break;
|
||||
#if CLIENT
|
||||
case "mapcircle":
|
||||
MapCircle = new Sprite(subElement);
|
||||
case "maptile":
|
||||
string biome = subElement.GetAttributeString("biome", "");
|
||||
if (!mapTiles.ContainsKey(biome))
|
||||
{
|
||||
mapTiles[biome] = new List<Sprite>();
|
||||
}
|
||||
mapTiles[biome].Add(new Sprite(subElement));
|
||||
break;
|
||||
case "fogofwarsprite":
|
||||
FogOfWarSprite = new Sprite(subElement);
|
||||
break;
|
||||
case "locationindicator":
|
||||
LocationIndicator = new Sprite(subElement);
|
||||
case "currentlocationindicator":
|
||||
CurrentLocationIndicator = new Sprite(subElement);
|
||||
break;
|
||||
case "decorativemapsprite":
|
||||
DecorativeMapSprite = new SpriteSheet(subElement);
|
||||
case "selectedlocationindicator":
|
||||
SelectedLocationIndicator = new Sprite(subElement);
|
||||
break;
|
||||
case "decorativegraphsprite":
|
||||
DecorativeGraphSprite = new SpriteSheet(subElement);
|
||||
break;
|
||||
case "decorativelinetop":
|
||||
DecorativeLineTop = new SpriteSheet(subElement);
|
||||
case "missionicon":
|
||||
MissionIcon = new Sprite(subElement);
|
||||
break;
|
||||
case "decorativelinebottom":
|
||||
DecorativeLineBottom = new SpriteSheet(subElement);
|
||||
break;
|
||||
case "decorativelinecorner":
|
||||
DecorativeLineCorner = new SpriteSheet(subElement);
|
||||
break;
|
||||
case "reticlelarge":
|
||||
ReticleLarge = new SpriteSheet(subElement);
|
||||
break;
|
||||
case "reticlemedium":
|
||||
ReticleMedium = new SpriteSheet(subElement);
|
||||
break;
|
||||
case "reticlesmall":
|
||||
ReticleSmall = new SpriteSheet(subElement);
|
||||
case "typechangeicon":
|
||||
TypeChangeIcon = new Sprite(subElement);
|
||||
break;
|
||||
#endif
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user