(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,151 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Barotrauma
{
partial class Location
{
public List<LocationConnection> Connections;
private string baseName;
private int nameFormatIndex;
public bool Discovered;
public int TypeChangeTimer;
public string BaseName { get => baseName; }
public string Name { get; private set; }
public Vector2 MapPosition { get; private set; }
public LocationType Type { get; private set; }
public int PortraitId { get; private set; }
public int MissionsCompleted;
private List<Mission> availableMissions = new List<Mission>();
public IEnumerable<Mission> AvailableMissions
{
get
{
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;
}
}
public Mission SelectedMission
{
get;
set;
}
public int SelectedMissionIndex
{
get
{
if (SelectedMission == null) { return -1; }
return availableMissions.IndexOf(SelectedMission);
}
set
{
if (value < 0 || value >= AvailableMissions.Count())
{
SelectedMission = null;
return;
}
SelectedMission = availableMissions[value];
}
}
public Location(Vector2 mapPosition, int? zone, Random rand)
{
this.Type = LocationType.Random(rand, zone);
this.Name = RandomName(Type, rand);
this.MapPosition = mapPosition;
PortraitId = ToolBox.StringToInt(Name);
Connections = new List<LocationConnection>();
}
public static Location CreateRandom(Vector2 position, int? zone , Random rand)
{
return new Location(position, zone, rand);
}
public IEnumerable<Mission> GetMissionsInConnection(LocationConnection connection)
{
System.Diagnostics.Debug.Assert(Connections.Contains(connection));
return AvailableMissions.Where(m => m.Locations[1] == connection.OtherLocation(this));
}
public void ChangeType(LocationType newType)
{
if (newType == Type) { return; }
//clear missions from this and adjacent locations (they may be invalid now)
availableMissions.Clear();
foreach (LocationConnection connection in Connections)
{
connection.OtherLocation(this)?.availableMissions.Clear();
}
DebugConsole.Log("Location " + baseName + " changed it's type from " + Type + " to " + newType);
Type = newType;
Name = Type.NameFormats[nameFormatIndex % Type.NameFormats.Count].Replace("[name]", baseName);
}
public void CheckMissionCompleted()
{
foreach (Mission mission in availableMissions)
{
if (mission.Completed)
{
DebugConsole.Log("Mission \"" + mission.Name + "\" completed in \"" + Name + "\".");
MissionsCompleted++;
}
}
availableMissions.RemoveAll(m => m.Completed);
}
private string RandomName(LocationType type, Random rand)
{
baseName = type.GetRandomName(rand);
nameFormatIndex = rand.Next() % type.NameFormats.Count;
return type.NameFormats[nameFormatIndex].Replace("[name]", baseName);
}
public void Remove()
{
RemoveProjSpecific();
}
partial void RemoveProjSpecific();
}
}
@@ -0,0 +1,57 @@
using Microsoft.Xna.Framework;
using System.Collections.Generic;
namespace Barotrauma
{
class LocationConnection
{
public Biome Biome;
public float Difficulty;
public List<Vector2[]> CrackSegments;
public bool Passed;
public Level Level { get; set; }
public Vector2 CenterPos
{
get
{
return (Locations[0].MapPosition + Locations[1].MapPosition) / 2.0f;
}
}
public Location[] Locations { get; private set; }
public float Length
{
get;
private set;
}
public LocationConnection(Location location1, Location location2)
{
Locations = new Location[] { location1, location2 };
Length = Vector2.Distance(location1.MapPosition, location2.MapPosition);
}
public Location OtherLocation(Location location)
{
if (Locations[0] == location)
{
return Locations[1];
}
else if (Locations[1] == location)
{
return Locations[0];
}
else
{
return null;
}
}
}
}
@@ -0,0 +1,244 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class LocationType
{
public static readonly List<LocationType> List = new List<LocationType>();
private List<string> nameFormats;
private List<string> names;
private Sprite symbolSprite;
private readonly List<Sprite> portraits = new List<Sprite>();
//<name, commonness>
private List<Tuple<JobPrefab, float>> hireableJobs;
private float totalHireableWeight;
public Dictionary<int, float> CommonnessPerZone = new Dictionary<int, float>();
public readonly string Identifier;
public readonly string Name;
public readonly List<LocationTypeChange> CanChangeTo = new List<LocationTypeChange>();
public bool UseInMainMenu
{
get;
private set;
}
public List<string> NameFormats
{
get { return nameFormats; }
}
public bool HasHireableCharacters
{
get { return hireableJobs.Any(); }
}
public Sprite Sprite
{
get { return symbolSprite; }
}
public Color SpriteColor
{
get;
private set;
}
public override string ToString()
{
return $"LocationType (" + Identifier + ")";
}
private LocationType(XElement element)
{
Identifier = element.GetAttributeString("identifier", element.Name.ToString());
Name = TextManager.Get("LocationName." + Identifier);
nameFormats = TextManager.GetAll("LocationNameFormat." + Identifier);
UseInMainMenu = element.GetAttributeBool("useinmainmenu", false);
string nameFile = element.GetAttributeString("namefile", "Content/Map/locationNames.txt");
try
{
names = File.ReadAllLines(nameFile).ToList();
}
catch (Exception e)
{
DebugConsole.ThrowError("Failed to read name file for location type \"" + Identifier + "\"!", e);
names = new List<string>() { "Name file not found" };
}
string[] commonnessPerZoneStrs = element.GetAttributeStringArray("commonnessperzone", new string[] { "" });
foreach (string commonnessPerZoneStr in commonnessPerZoneStrs)
{
string[] splitCommonnessPerZone = commonnessPerZoneStr.Split(':');
if (splitCommonnessPerZone.Length != 2 ||
!int.TryParse(splitCommonnessPerZone[0].Trim(), out int zoneIndex) ||
!float.TryParse(splitCommonnessPerZone[1].Trim(), NumberStyles.Float, CultureInfo.InvariantCulture, out float zoneCommonness))
{
DebugConsole.ThrowError("Failed to read commonness values for location type \"" + Identifier + "\" - commonness should be given in the format \"zone0index: zone0commonness, zone1index: zone1commonness\"");
break;
}
CommonnessPerZone[zoneIndex] = zoneCommonness;
}
hireableJobs = new List<Tuple<JobPrefab, float>>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "hireable":
string jobIdentifier = subElement.GetAttributeString("identifier", "");
JobPrefab jobPrefab = null;
if (jobIdentifier == "")
{
DebugConsole.ThrowError("Error in location type \""+ Identifier + "\" - hireable jobs should be configured using identifiers instead of names.");
}
else
{
jobPrefab = JobPrefab.Get(jobIdentifier.ToLowerInvariant());
}
if (jobPrefab == null)
{
DebugConsole.ThrowError("Error in in location type " + Identifier + " - could not find a job with the identifier \"" + jobIdentifier + "\".");
continue;
}
float jobCommonness = subElement.GetAttributeFloat("commonness", 1.0f);
totalHireableWeight += jobCommonness;
Tuple<JobPrefab, float> hireableJob = new Tuple<JobPrefab, float>(jobPrefab, jobCommonness);
hireableJobs.Add(hireableJob);
break;
case "symbol":
symbolSprite = new Sprite(subElement, lazyLoad: true);
SpriteColor = subElement.GetAttributeColor("color", Color.White);
break;
case "changeto":
CanChangeTo.Add(new LocationTypeChange(Identifier, subElement));
break;
case "portrait":
var portrait = new Sprite(subElement, lazyLoad: true);
if (portrait != null)
{
portraits.Add(portrait);
}
break;
}
}
}
public JobPrefab GetRandomHireable()
{
float randFloat = Rand.Range(0.0f, totalHireableWeight, Rand.RandSync.Server);
foreach (Tuple<JobPrefab, float> hireable in hireableJobs)
{
if (randFloat < hireable.Item2) return hireable.Item1;
randFloat -= hireable.Item2;
}
return null;
}
public Sprite GetPortrait(int portraitId)
{
if (portraits.Count == 0) { return null; }
return portraits[Math.Abs(portraitId) % portraits.Count];
}
public string GetRandomName(Random rand)
{
return names[rand.Next() % names.Count];
}
public static LocationType Random(Random rand, int? zone = null)
{
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;
if (allowedLocationTypes.Count == 0)
{
DebugConsole.ThrowError("Could not generate a random location type - no location types for the zone " + zone + " found!");
}
if (zone.HasValue)
{
return ToolBox.SelectWeightedRandom(
allowedLocationTypes,
allowedLocationTypes.Select(a => a.CommonnessPerZone[zone.Value]).ToList(),
rand);
}
else
{
return allowedLocationTypes[rand.Next() % allowedLocationTypes.Count];
}
}
public static void Init()
{
List.Clear();
var locationTypeFiles = GameMain.Instance.GetFilesOfType(ContentType.LocationTypes);
foreach (ContentFile file in locationTypeFiles)
{
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
if (doc == null) { continue; }
var mainElement = doc.Root;
if (doc.Root.IsOverride())
{
mainElement = doc.Root.FirstElement();
DebugConsole.NewMessage($"Overriding all location types with '{file.Path}'", Color.Yellow);
List.Clear();
}
else if (List.Any())
{
DebugConsole.NewMessage($"Loading additional location types from file '{file.Path}'");
}
foreach (XElement sourceElement in mainElement.Elements())
{
var element = sourceElement;
bool allowOverriding = false;
if (sourceElement.IsOverride())
{
element = sourceElement.FirstElement();
allowOverriding = true;
}
string identifier = element.GetAttributeString("identifier", null);
if (string.IsNullOrWhiteSpace(identifier))
{
DebugConsole.ThrowError($"Error in '{file.Path}': No identifier defined for {element.Name.ToString()}");
continue;
}
var duplicate = List.FirstOrDefault(l => l.Identifier == identifier);
if (duplicate != null)
{
if (allowOverriding)
{
List.Remove(duplicate);
DebugConsole.NewMessage($"Overriding the location type with the identifier '{identifier}' with '{file.Path}'", Color.Yellow);
}
else
{
DebugConsole.ThrowError($"Error in '{file.Path}': Duplicate identifier defined with the identifier '{identifier}'");
continue;
}
}
LocationType locationType = new LocationType(element);
List.Add(locationType);
}
}
}
}
}
@@ -0,0 +1,39 @@
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
namespace Barotrauma
{
class LocationTypeChange
{
public readonly string ChangeToType;
public readonly float Probability;
public readonly int RequiredDuration;
public List<string> Messages = new List<string>();
//the change can't happen if there's a location of the given type next to this one
public readonly List<string> DisallowedAdjacentLocations;
//the change can only happen if there's at least one of the given types of locations next to this one
public readonly List<string> RequiredAdjacentLocations;
public LocationTypeChange(string currentType, XElement element)
{
ChangeToType = element.GetAttributeString("type", "");
Probability = element.GetAttributeFloat("probability", 1.0f);
RequiredDuration = element.GetAttributeInt("requiredduration", 0);
DisallowedAdjacentLocations = element.GetAttributeStringArray("disallowedadjacentlocations", new string[0]).ToList();
RequiredAdjacentLocations = element.GetAttributeStringArray("requiredadjacentlocations", new string[0]).ToList();
string messageTag = element.GetAttributeString("messagetag", "LocationChange." + currentType + ".ChangeTo." + ChangeToType);
Messages = TextManager.GetAll(messageTag);
if (Messages == null)
{
DebugConsole.ThrowError("No messages defined for the location type change " + currentType + " -> " + ChangeToType);
}
}
}
}
@@ -0,0 +1,631 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using Voronoi2;
namespace Barotrauma
{
partial class Map
{
private MapGenerationParams generationParams;
private readonly int size;
private List<LocationConnection> connections;
public Action<Location, LocationConnection> OnLocationSelected;
//from -> to
public Action<Location, Location> OnLocationChanged;
public Action<LocationConnection, Mission> OnMissionSelected;
public Location CurrentLocation { get; private set; }
public int CurrentLocationIndex
{
get { return Locations.IndexOf(CurrentLocation); }
}
public Location SelectedLocation { get; private set; }
public int SelectedLocationIndex
{
get { return Locations.IndexOf(SelectedLocation); }
}
public int SelectedMissionIndex
{
get { return SelectedConnection == null ? -1 : CurrentLocation.SelectedMissionIndex; }
}
public LocationConnection SelectedConnection { get; private set; }
public string Seed { get; private set; }
public List<Location> Locations { get; private set; }
public Map(string seed)
{
generationParams = MapGenerationParams.Instance;
this.Seed = seed;
this.size = generationParams.Size;
Locations = new List<Location>();
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)
{
if (location.Type.Identifier != "City") continue;
float dist = Vector2.DistanceSquared(center, location.MapPosition);
if (dist > largestDist)
{
largestDist = dist;
CurrentLocation = location;
}
}
CurrentLocation.Discovered = true;
foreach (LocationConnection connection in connections)
{
connection.Level = Level.CreateRandom(connection);
}
InitProjectSpecific();
}
partial void InitProjectSpecific();
public float[,] Noise;
private void GenerateNoiseMap(int octaves, float persistence)
{
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++)
{
for (int y = 0; y < generationParams.NoiseResolution; y++)
{
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);
}
}
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;
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);
}
}
}
}
partial void GenerateNoiseMapProjSpecific();
private void Generate()
{
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)
{
for (float y = mapCenter.Y - locationRadius; y < mapCenter.Y + locationRadius; y += generationParams.VoronoiSiteInterval)
{
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));
}
}
}
Voronoi voronoi = new Voronoi(0.5f);
List<GraphEdge> edges = voronoi.MakeVoronoiGraph(sites, size, size);
float zoneRadius = size / 2 / generationParams.DifficultyZones;
sites.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;
Location[] newLocations = new Location[2];
newLocations[0] = Locations.Find(l => l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2);
newLocations[1] = Locations.Find(l => l != newLocations[0] && (l.MapPosition == edge.Point1 || l.MapPosition == edge.Point2));
for (int i = 0; i < 2; i++)
{
if (newLocations[i] != null) continue;
Vector2[] points = new Vector2[] { edge.Point1, edge.Point2 };
int positionIndex = Rand.Int(1, Rand.RandSync.Server);
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));
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);
}
//remove connections that are too short
float minConnectionDistanceSqr = generationParams.MinConnectionDistance * generationParams.MinConnectionDistance;
for (int i = connections.Count - 1; i >= 0; i--)
{
LocationConnection connection = connections[i];
if (Vector2.DistanceSquared(connection.Locations[0].MapPosition, connection.Locations[1].MapPosition) > minConnectionDistanceSqr)
{
continue;
}
//locations.Remove(connection.Locations[0]);
connections.Remove(connection);
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];
}
}
HashSet<Location> connectedLocations = new HashSet<Location>();
foreach (LocationConnection connection in connections)
{
connection.Locations[0].Connections.Add(connection);
connection.Locations[1].Connections.Add(connection);
connectedLocations.Add(connection.Locations[0]);
connectedLocations.Add(connection.Locations[1]);
}
//remove orphans
Locations.RemoveAll(c => !connectedLocations.Contains(c));
//remove locations that are too close to each other
float minLocationDistanceSqr = generationParams.MinLocationDistance * generationParams.MinLocationDistance;
for (int i = Locations.Count - 1; i >= 0; i--)
{
for (int j = Locations.Count - 1; j > i; j--)
{
float dist = Vector2.DistanceSquared(Locations[i].MapPosition, Locations[j].MapPosition);
if (dist > minLocationDistanceSqr)
{
continue;
}
//move connections from Locations[j] to Locations[i]
foreach (LocationConnection connection in Locations[j].Connections)
{
if (connection.Locations[0] == Locations[j])
{
connection.Locations[0] = Locations[i];
}
else
{
connection.Locations[1] = Locations[i];
}
Locations[i].Connections.Add(connection);
}
Locations.RemoveAt(j);
}
}
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--)
{
if (connection.Locations.Contains(connections[n].Locations[0])
&& connection.Locations.Contains(connections[n].Locations[1]))
{
connections.RemoveAt(n);
}
}
}
foreach (LocationConnection connection in connections)
{
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);
}
AssignBiomes();
GenerateNoiseMapProjSpecific();
}
private void AssignBiomes()
{
float locationRadius = size * 0.5f * generationParams.LocationRadius;
var biomes = LevelGenerationParams.GetBiomes();
Vector2 centerPos = new Vector2(size, size) / 2;
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;
if (i == generationParams.DifficultyZones - 1 ||
Vector2.Distance(connection.Locations[0].MapPosition, centerPos) < zoneRadius ||
Vector2.Distance(connection.Locations[1].MapPosition, centerPos) < zoneRadius)
{
connection.Biome = allowedBiomes[Rand.Range(0, allowedBiomes.Count, Rand.RandSync.Server)];
}
}
}
}
private void ExpandBiomes(List<LocationConnection> seeds)
{
List<LocationConnection> nextSeeds = new List<LocationConnection>();
foreach (LocationConnection connection in seeds)
{
foreach (Location location in connection.Locations)
{
foreach (LocationConnection otherConnection in location.Connections)
{
if (otherConnection == connection) continue;
if (otherConnection.Biome != null) continue; //already assigned
otherConnection.Biome = connection.Biome;
nextSeeds.Add(otherConnection);
}
}
}
if (nextSeeds.Count > 0)
{
ExpandBiomes(nextSeeds);
}
}
public void MoveToNextLocation()
{
Location prevLocation = CurrentLocation;
SelectedConnection.Passed = true;
CurrentLocation = SelectedLocation;
CurrentLocation.Discovered = true;
SelectedLocation = null;
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
}
public void SetLocation(int index)
{
if (index == -1)
{
CurrentLocation = null;
return;
}
if (index < 0 || index >= Locations.Count)
{
DebugConsole.ThrowError("Location index out of bounds");
return;
}
Location prevLocation = CurrentLocation;
CurrentLocation = Locations[index];
CurrentLocation.Discovered = true;
OnLocationChanged?.Invoke(prevLocation, CurrentLocation);
}
public void SelectLocation(int index)
{
if (index == -1)
{
SelectedLocation = null;
SelectedConnection = null;
OnLocationSelected?.Invoke(null, null);
return;
}
if (index < 0 || index >= Locations.Count)
{
DebugConsole.ThrowError("Location index out of bounds");
return;
}
SelectedLocation = Locations[index];
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
public void SelectLocation(Location location)
{
if (!Locations.Contains(location))
{
string errorMsg = "Failed to select a location. " + (location?.Name ?? "null") + " not found in the map.";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectLocation:LocationNotFound", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
SelectedLocation = location;
SelectedConnection = connections.Find(c => c.Locations.Contains(CurrentLocation) && c.Locations.Contains(SelectedLocation));
OnLocationSelected?.Invoke(SelectedLocation, SelectedConnection);
}
public void SelectMission(int missionIndex)
{
if (SelectedConnection == null) { return; }
if (CurrentLocation == null)
{
string errorMsg = "Failed to select a mission (current location not set).";
DebugConsole.ThrowError(errorMsg);
GameAnalyticsManager.AddErrorEventOnce("Map.SelectMission:CurrentLocationNotSet", GameAnalyticsSDK.Net.EGAErrorSeverity.Error, errorMsg);
return;
}
CurrentLocation.SelectedMissionIndex = missionIndex;
//the destination must be the same as the destination of the mission
if (CurrentLocation.SelectedMission != null &&
CurrentLocation.SelectedMission.Locations[1] != SelectedLocation)
{
SelectLocation(CurrentLocation.SelectedMission.Locations[1]);
}
OnMissionSelected?.Invoke(SelectedConnection, CurrentLocation.SelectedMission);
}
public void SelectRandomLocation(bool preferUndiscovered)
{
List<Location> nextLocations = CurrentLocation.Connections.Select(c => c.OtherLocation(CurrentLocation)).ToList();
List<Location> undiscoveredLocations = nextLocations.FindAll(l => !l.Discovered);
if (undiscoveredLocations.Count > 0 && preferUndiscovered)
{
SelectLocation(undiscoveredLocations[Rand.Int(undiscoveredLocations.Count, Rand.RandSync.Unsynced)]);
}
else
{
SelectLocation(nextLocations[Rand.Int(nextLocations.Count, Rand.RandSync.Unsynced)]);
}
}
public void ProgressWorld()
{
foreach (Location location in Locations)
{
if (!location.Discovered) continue;
//find which types of locations this one can change to
List<LocationTypeChange> allowedTypeChanges = new List<LocationTypeChange>();
List<LocationTypeChange> readyTypeChanges = new List<LocationTypeChange>();
foreach (LocationTypeChange typeChange in location.Type.CanChangeTo)
{
//check if there are any adjacent locations that would prevent the change
bool disallowedFound = false;
foreach (string disallowedLocationName in typeChange.DisallowedAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(disallowedLocationName, StringComparison.OrdinalIgnoreCase)))
{
disallowedFound = true;
break;
}
}
if (disallowedFound) continue;
//check that there's a required adjacent location present
bool requiredFound = false;
foreach (string requiredLocationName in typeChange.RequiredAdjacentLocations)
{
if (location.Connections.Any(c => c.OtherLocation(location).Type.Identifier.Equals(requiredLocationName, StringComparison.OrdinalIgnoreCase)))
{
requiredFound = true;
break;
}
}
if (!requiredFound && typeChange.RequiredAdjacentLocations.Count > 0) continue;
allowedTypeChanges.Add(typeChange);
if (location.TypeChangeTimer >= typeChange.RequiredDuration)
{
readyTypeChanges.Add(typeChange);
}
}
//select a random type change
if (Rand.Range(0.0f, 1.0f) < readyTypeChanges.Sum(t => t.Probability))
{
var selectedTypeChange =
ToolBox.SelectWeightedRandom(readyTypeChanges, readyTypeChanges.Select(t => t.Probability).ToList(), Rand.RandSync.Unsynced);
if (selectedTypeChange != null)
{
string prevName = location.Name;
location.ChangeType(LocationType.List.Find(lt => lt.Identifier.Equals(selectedTypeChange.ChangeToType, StringComparison.OrdinalIgnoreCase)));
ChangeLocationType(location, prevName, selectedTypeChange);
location.TypeChangeTimer = -1;
break;
}
}
if (allowedTypeChanges.Count > 0)
{
location.TypeChangeTimer++;
}
else
{
location.TypeChangeTimer = 0;
}
}
}
partial void ChangeLocationType(Location location, string prevName, LocationTypeChange change);
partial void ClearAnimQueue();
public static Map LoadNew(XElement element)
{
string mapSeed = element.GetAttributeString("seed", "a");
Map map = new Map(mapSeed);
map.Load(element, false);
return map;
}
public void Load(XElement element, bool showNotifications)
{
ClearAnimQueue();
SetLocation(element.GetAttributeInt("currentlocation", 0));
if (!Version.TryParse(element.GetAttributeString("version", ""), out _))
{
DebugConsole.ThrowError("Incompatible map save file, loading the game failed.");
return;
}
foreach (XElement subElement in element.Elements())
{
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);
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;
if (showNotifications && prevLocationType != location.Type)
{
var change = prevLocationType.CanChangeTo.Find(c => c.ChangeToType.Equals(location.Type.Identifier, StringComparison.OrdinalIgnoreCase));
if (change != null)
{
ChangeLocationType(location, prevLocationName, change);
}
}
break;
case "connection":
int connectionIndex = subElement.GetAttributeInt("i", 0);
connections[connectionIndex].Passed = true;
break;
}
}
}
public void Save(XElement element)
{
XElement mapElement = new XElement("map");
mapElement.Add(new XAttribute("version", GameMain.Version.ToString()));
mapElement.Add(new XAttribute("currentlocation", CurrentLocationIndex));
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);
}
for (int i = 0; i < connections.Count; i++)
{
var connection = connections[i];
if (!connection.Passed) continue;
var connectionElement = new XElement("connection",
new XAttribute("i", i),
new XAttribute("passed", connection.Passed));
mapElement.Add(connectionElement);
}
element.Add(mapElement);
}
public void Remove()
{
foreach (Location location in Locations)
{
location.Remove();
}
RemoveProjSpecific();
}
partial void RemoveProjSpecific();
}
}
@@ -0,0 +1,276 @@
using Microsoft.Xna.Framework;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;
namespace Barotrauma
{
class MapGenerationParams : ISerializableEntity
{
private static MapGenerationParams instance;
private static string loadedFile;
public static MapGenerationParams Instance
{
get
{
return instance;
}
}
#if DEBUG
[Serialize(false, true), Editable]
public bool ShowNoiseMap { get; set; }
[Serialize(true, true), Editable]
public bool ShowLocations { get; set; }
[Serialize(true, true), Editable]
public bool ShowLevelTypeNames { get; set; }
[Serialize(true, true), Editable]
public bool ShowOverlay { get; set; }
#else
public readonly bool ShowLocations = true;
public readonly bool ShowLevelTypeNames = false;
public readonly bool ShowOverlay = true;
#endif
[Serialize(6, true)]
public int DifficultyZones { get; set; } //Number of difficulty zones
[Serialize(2000, true)]
public int Size { 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; }
[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. " +
"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; }
[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(10.0f, true, description: "Connections smaller than this are removed."), Editable(0.0f, 500.0f)]
public float MinConnectionDistance { get; set; }
[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)]
public float ConnectionIndicatorIterationMultiplier { get; set; }
[Serialize(0.1f, true, description: "ConnectionDisplacementMultiplier for the UI indicator lines between locations."), Editable(0.0f, 10.0f)]
public float ConnectionIndicatorDisplacementMultiplier { get; set; }
public Sprite ConnectionSprite { get; private set; }
#if CLIENT
[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; }
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 MapCircle { get; private set; }
public Sprite LocationIndicator { get; private set; }
#endif
public List<Sprite> BackgroundTileSprites { get; private set; }
public string Name
{
get { return GetType().ToString(); }
}
public Dictionary<string, SerializableProperty> SerializableProperties
{
get; private set;
}
public static void Init()
{
var files = ContentPackage.GetFilesOfType(GameMain.Config.SelectedContentPackages, ContentType.MapGenerationParameters);
if (!files.Any())
{
DebugConsole.ThrowError("No map generation parameters found in the selected content packages!");
return;
}
// Let's not actually load the parameters until we have solved which file is the last, because loading the parameters takes some resources that would also need to be released.
XElement selectedElement = null;
string selectedFile = null;
foreach (ContentFile file in files)
{
XDocument doc = XMLExtensions.TryLoadXml(file.Path);
if (doc == null) { continue; }
var mainElement = doc.Root;
if (doc.Root.IsOverride())
{
mainElement = doc.Root.FirstElement();
if (selectedElement != null)
{
DebugConsole.NewMessage($"Overriding the map generation parameters with '{file.Path}'", Color.Yellow);
}
}
else if (selectedElement != null)
{
DebugConsole.ThrowError($"Error in {file.Path}: Another map generation parameter file already loaded! Use <override></override> tags to override it.");
break;
}
selectedElement = mainElement;
selectedFile = file.Path;
}
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();
#endif
instance = null;
if (selectedElement == null)
{
DebugConsole.ThrowError("Could not find a valid element in the map generation parameter files!");
}
else
{
instance = new MapGenerationParams(selectedElement);
loadedFile = selectedFile;
}
}
private MapGenerationParams(XElement element)
{
SerializableProperties = SerializableProperty.DeserializeProperties(this, element);
BackgroundTileSprites = new List<Sprite>();
foreach (XElement subElement in element.Elements())
{
switch (subElement.Name.ToString().ToLowerInvariant())
{
case "connectionsprite":
ConnectionSprite = new Sprite(subElement);
break;
case "backgroundtile":
BackgroundTileSprites.Add(new Sprite(subElement));
break;
#if CLIENT
case "mapcircle":
MapCircle = new Sprite(subElement);
break;
case "locationindicator":
LocationIndicator = new Sprite(subElement);
break;
case "decorativemapsprite":
DecorativeMapSprite = new SpriteSheet(subElement);
break;
case "decorativegraphsprite":
DecorativeGraphSprite = new SpriteSheet(subElement);
break;
case "decorativelinetop":
DecorativeLineTop = new SpriteSheet(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);
break;
#endif
}
}
}
}
}