Multi-Location Quest Injection

This commit is contained in:
Reinier Vens 2021-05-16 05:24:51 +01:00
commit b0673c82fc
23 changed files with 279 additions and 105 deletions

View file

@ -42,8 +42,8 @@ public abstract class Entity
public string Name;
public int Population;
public Dictionary<Guid, float> Personality = new Dictionary<Guid, float>();
// public int Population;
// public Dictionary<Guid, float> Personality = new Dictionary<Guid, float>();
public readonly Dictionary<string, float> Messages = new Dictionary<string, float>();
public readonly Dictionary<object, float> VisibilitySources = new Dictionary<object, float>();
@ -288,7 +288,7 @@ public abstract class Entity
// TODO: Inter-faction hostility
// When the entity faction owns the zone, they are hostile to trespassers or those hostile to them
if (Faction.ID == Zone.GalaxyZone.Owner.ID)
if (Faction.ID == Zone.GalaxyZone.Owner?.ID)
return recursive ? !(other.PresencePermitted?.Value ?? true) : !(other.PresencePermitted?.Value ?? true)|| other.IsHostileTo(this, true);
return !recursive && other.IsHostileTo(this, true);

View file

@ -85,6 +85,7 @@ public static class EntitySerializer
Restore(itemManager, zone, pack, entity, instantiate);
entity.SecurityLevel = pack.SecurityLevel;
entity.SecurityRadius = pack.SecurityRadius;
if (pack.Story >= 0) entity.Story = zone.GalaxyZone.Locations[pack.Story];
return entity;
}
@ -166,7 +167,7 @@ public class ShipPack : EntityPack
public class OrbitalEntityPack : EntityPack
{
[Key(17)] public Guid Orbit;
[Key(18)] public SavedStory Story;
[Key(18)] public int Story = -1;
[Key(19)] public SecurityLevel SecurityLevel;
[Key(20)] public float SecurityRadius;
}

View file

@ -168,6 +168,7 @@ public enum CompoundCommodityCategory
public enum LocationType
{
Station,
Asteroid,
Planet
}

View file

@ -26,6 +26,7 @@ public class Galaxy
public GalaxyZone Entrance { get; }
public GalaxyZone Exit { get; }
public Dictionary<Faction, FactionRelationship> FactionRelationships { get; } = new Dictionary<Faction, FactionRelationship>();
private Action<string> Log { get; }
private HashSet<Guid> _containedFactions;
private GalaxyZone[] _exitPath;
@ -41,8 +42,9 @@ public class Galaxy
}
}
public Galaxy(CultCache cultCache, SavedGame savedGame)
public Galaxy(CultCache cultCache, SavedGame savedGame, Action<string> log)
{
Log = log;
Background = savedGame.Background;
Factions = savedGame.Factions.Select(cultCache.Get<Faction>).ToArray();
@ -87,11 +89,13 @@ public class Galaxy
SectorGenerationSettings settings,
SectorBackgroundSettings background,
NameGeneratorSettings nameGeneratorSettings,
CultCache cache,
CultCache cache,
Action<string> log,
Action<string> progressCallback = null,
uint seed = 0)
{
Background = background;
Log = log;
var factions = cache.GetAll<Faction>();
var random = new Random(seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed);
Factions = factions.OrderBy(x => random.NextFloat()).Take(settings.MegaCount).ToArray();
@ -129,12 +133,14 @@ public class Galaxy
CultCache cache,
PlayerSettings playerSettings,
DirectoryInfo narrativeDirectory,
Action<string> log,
Action<string> progressCallback = null,
uint seed = 0)
{
Faction ResolveFaction(string name) => cache.GetAll<Faction>().FirstOrDefault(f => f.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
Background = background;
Log = log;
var random = new Random(seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed);
var factions = new List<Faction>();
@ -219,12 +225,8 @@ public class Galaxy
GenerateNames(cache, nameGeneratorSettings, ref random, progressCallback);
progressCallback?.Invoke("Weaving Narrative");
// var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random);
// var storyPlacement = processor.PlaceStories();
// foreach (var story in storyPlacement.Keys)
// {
// storyPlacement[story]
// }
var processor = new StoryProcessor(playerSettings, narrativeDirectory, this, ref random, Log);
processor.ProcessStories();
progressCallback?.Invoke("Done!");
if(progressCallback!=null) Thread.Sleep(500); // Inserting Delay to make it seem like it's doing more work lmao
@ -554,15 +556,18 @@ public class GalaxyZone
public class GalaxyQuest
{
public Story Story;
public Dictionary<string, LocationStory> Locations = new Dictionary<string, LocationStory>();
public Dictionary<string, LocationStory> KnotLocations = new Dictionary<string, LocationStory>();
}
public class LocationStory
{
public GalaxyZone Zone;
public string FileName;
public string Name;
public Story Story;
public Faction Faction;
public SecurityLevel Security;
public LocationType Type;
public int Turrets;
public Dictionary<string, List<GalaxyQuest>> KnotQuests = new Dictionary<string, List<GalaxyQuest>>();
}

View file

@ -26,22 +26,24 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
}
private Dictionary<Story, string[]> _storyKnotPaths = new Dictionary<Story, string[]>();
private Dictionary<Story, GalaxyZone> _storyLocations = new Dictionary<Story, GalaxyZone>();
private Dictionary<Story, LocationStory> _placedStories = new Dictionary<Story, LocationStory>();
private Dictionary<string, Story> _processedStories = new Dictionary<string, Story>();
private DirectoryInfo _locationsPath;
private DirectoryInfo _questsPath;
private IFileHandler _inkFileHandler;
public DirectoryInfo NarrativeDirectory { get; }
private Random _random;
public Galaxy Galaxy { get; }
public PlayerSettings Settings { get; }
private Action<string> Log { get; }
public StoryProcessor(PlayerSettings settings, DirectoryInfo narrativeDirectory, Galaxy galaxy, ref Random random)
public StoryProcessor(PlayerSettings settings, DirectoryInfo narrativeDirectory, Galaxy galaxy, ref Random random, Action<string> log)
{
NarrativeDirectory = narrativeDirectory;
Galaxy = galaxy;
_random = random;
Log = log;
Settings = settings;
_inkFileHandler = new AetheriaInkFileHandler(narrativeDirectory);
@ -56,7 +58,7 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
foreach(var inkFile in locationFiles) ProcessLocation(GetStory(inkFile), inkFile);
var questFiles = _questsPath.EnumerateFiles("*ink");
foreach(var inkFile in questFiles) ProcessQuest(GetStory(inkFile));
foreach(var inkFile in questFiles) ProcessQuest(GetStory(inkFile), inkFile);
}
public GalaxyZone ResolveZone(string path)
@ -74,13 +76,18 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
var faction = ResolveFaction(factionString);
return !Galaxy.HomeZones.ContainsKey(faction) ? null : Galaxy.HomeZones[faction];
}
return ResolveLocation(path)?.Zone;
}
public LocationStory ResolveLocation(string path)
{
var locationInkFile = new FileInfo(Path.Combine(_locationsPath.FullName, $"{path}.ink"));
if (!locationInkFile.Exists) return null;
var story = GetStory(locationInkFile);
ProcessLocation(story, locationInkFile);
return _storyLocations.ContainsKey(story) ? _storyLocations[story] : null;
return _placedStories.ContainsKey(story) ? _placedStories[story] : null;
}
public Faction ResolveFaction(string name)
@ -95,20 +102,20 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
// If the story has already been read / compiled, return it directly
if (_processedStories.ContainsKey(fileName)) return _processedStories[fileName];
var compiledFileName = Path.Combine(inkFile.Directory.FullName, $"{fileName}.json");
// Calculate a hash of the ink file
var hash = File.ReadAllBytes(inkFile.FullName).GetHashSHA1();
if (File.Exists(compiledFileName) && // If a compiled version of the ink file exists
Settings.HashedStoryFiles.ContainsKey(fileName) && // And its hash has been saved
hash == Settings.HashedStoryFiles[fileName]) // And the saved hash matches the current hash
{
// Load the story from the existing compiled JSON representation
return new Story(File.ReadAllText(compiledFileName));
}
Settings.HashedStoryFiles[fileName] = hash;
// var compiledFileName = Path.Combine(inkFile.Directory.FullName, $"{fileName}.json");
//
// // Calculate a hash of the ink file
// var hash = File.ReadAllBytes(inkFile.FullName).GetHashSHA1();
//
// if (File.Exists(compiledFileName) && // If a compiled version of the ink file exists
// Settings.HashedStoryFiles.ContainsKey(fileName) && // And its hash has been saved
// hash == Settings.HashedStoryFiles[fileName]) // And the saved hash matches the current hash
// {
// // Load the story from the existing compiled JSON representation
// return new Story(File.ReadAllText(compiledFileName));
// }
//
// Settings.HashedStoryFiles[fileName] = hash;
var compiler = new Compiler(File.ReadAllText(inkFile.FullName), new Compiler.Options
{
countAllVisits = true,
@ -117,25 +124,45 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
var story = compiler.Compile();
var knots = compiler.parsedStory.FindAll<Knot>().Select(k=>k.runtimePath.ToString()).ToArray();
_storyKnotPaths[story] = knots;
File.WriteAllText(compiledFileName, story.ToJson());
//File.WriteAllText(compiledFileName, story.ToJson());
_processedStories[fileName] = story;
return story;
}
public void ProcessQuest(Story story)
public void ProcessQuest(Story story, FileInfo inkFile)
{
var fileName = Path.GetFileNameWithoutExtension(inkFile.FullName);
var quest = new GalaxyQuest();
quest.Story = story;
foreach (var knot in _storyKnotPaths[story])
{
var tags = GetContentTags(story.TagsForContentAtPath(knot));
var tags = story.TagsForContentAtPath(knot);
if(tags==null) continue;
var contentTags = GetContentTags(tags);
if (contentTags.ContainsKey("location"))
{
var locationName = contentTags["location"].First();
var locationStory = ResolveLocation(locationName);
if (tags.Contains("required") && locationStory == null)
{
Log($"Quest \"{fileName}\" unable to find required location \"{locationName}\" for knot \"{knot}\"");
return;
}
quest.KnotLocations[knot] = locationStory;
}
}
foreach (var x in quest.KnotLocations)
{
if (!x.Value.KnotQuests.ContainsKey(x.Key)) x.Value.KnotQuests[x.Key] = new List<GalaxyQuest>();
x.Value.KnotQuests[x.Key].Add(quest);
}
}
public void ProcessLocation(Story story, FileInfo inkFile)
{
if (_storyLocations.ContainsKey(story)) return; // Don't place already placed stories, idiot!
_storyLocations[story] = null; // Avoids potential for infinite loops when evaluating constraints
if (_placedStories.ContainsKey(story)) return; // Don't place already placed stories, idiot!
_placedStories[story] = null; // Avoids potential for infinite loops when evaluating constraints
var fileName = Path.GetFileNameWithoutExtension(inkFile.FullName);
var contentTags = GetContentTags(story.globalTags);
@ -184,6 +211,7 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
var location = new LocationStory
{
Zone = zone,
FileName = fileName,
Name = contentTags.ContainsKey("name") ? contentTags["name"].First() : fileName,
Faction = contentTags.ContainsKey("faction") ? ResolveFaction(contentTags["faction"].First()) : zone.Owner,
@ -193,11 +221,17 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
Story = story,
Type = contentTags.ContainsKey("type")
? (LocationType) Enum.Parse(typeof(LocationType), contentTags["type"].First(), true)
: LocationType.Station
: LocationType.Station,
Turrets = contentTags.ContainsKey("turrets") ? int.Parse(contentTags["turrets"].First()) : 0
};
zone.Locations.Add(location);
_storyLocations[story] = zone;
if (contentTags.ContainsKey("namezone"))
{
zone.Name = contentTags["namezone"].First();
}
_placedStories[story] = location;
}
// Parse all tags of the format X:Y into a dictionary with a list of every Y for each X
@ -209,7 +243,7 @@ public class StoryProcessor : IZoneResolver, IFactionResolver
if (tag.IndexOf(':') == -1) continue;
var tokens = tag.Split(':');
var leftSide = tokens[0].Trim();
var leftSide = tokens[0].Trim().ToLowerInvariant();
var rightSide = tokens[1].Trim();
if (!contentTags.ContainsKey(leftSide)) contentTags[leftSide] = new List<string>();
contentTags[leftSide].Add(rightSide);

View file

@ -14,6 +14,7 @@ public class OrbitalEntity : Entity
public Guid OrbitData;
public SecurityLevel SecurityLevel;
public float SecurityRadius;
public LocationStory Story;
public OrbitalEntity(ItemManager itemManager, Zone zone, EquippableItem hull, Guid orbit, EntitySettings settings) : base(itemManager, zone, hull, settings)
{

View file

@ -226,7 +226,8 @@ public static class ZoneGenerator
var nearestFactionHomeZone = galaxy.HomeZones[nearestFaction];
var factionPresence = nearestFaction.InfluenceDistance - nearestFactionHomeZone.Distance[galaxyZone] + 1;
var stationCount = (int)(random.NextFloat() * (factionPresence + 1));
var storyStations = galaxyZone.Locations.Where(story => story.Type == LocationType.Station).ToArray();
var stationCount = (int)(random.NextFloat() * (factionPresence + 1)) + storyStations.Length;
var potentialLagrangePoints = planets
.Where(p => p.Parent != null && p.Parent.Children
.TrueForAll(c => !(c != p && abs(c.Distance - p.Distance) < .1f))) // Filter Rosettes
@ -238,59 +239,92 @@ public static class ZoneGenerator
.Take(stationCount)
.Select(p=>orbitMap[p])
.ToArray();
var loadoutGenerator = isTutorial ? new LoadoutGenerator(ref random, itemManager, nearestFaction, .5f) :
new LoadoutGenerator(ref random, itemManager, galaxy, galaxyZone, nearestFaction, .5f);
foreach (var orbit in selectedStationOrbits)
var loadoutGenerators = new Dictionary<Faction, LoadoutGenerator>();
LoadoutGenerator GetLoadoutGenerator(Faction faction)
{
if (!loadoutGenerators.ContainsKey(faction))
loadoutGenerators[faction] = isTutorial
? new LoadoutGenerator(ref random, itemManager, faction, .5f)
: new LoadoutGenerator(ref random, itemManager, galaxy, galaxyZone, faction, .5f);
return loadoutGenerators[faction];
}
OrbitData CreateLagrangeOrbit(OrbitData baseOrbit)
{
var security = (SecurityLevel) ((int) ((1f - pow(random.NextFloat(), factionPresence / 2f)) * 3f));
var lagrangeOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = orbit.Parent,
Distance = new ReactiveProperty<float>(orbit.Distance.Value),
Phase = orbit.Phase + PI / 3 * sign(random.NextFloat() - .5f)
Parent = baseOrbit.Parent,
Distance = new ReactiveProperty<float>(baseOrbit.Distance.Value),
Phase = baseOrbit.Phase + PI / 3 * sign(random.NextFloat() - .5f)
};
pack.Orbits.Add(lagrangeOrbit);
return lagrangeOrbit;
}
void PlaceTurret(OrbitData orbit, LoadoutGenerator loadoutGenerator, int distanceMultiplier)
{
var phase = 20f * distanceMultiplier / orbit.Distance.Value;
var station = loadoutGenerator.GenerateStationLoadout();
var turretOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = orbit.Parent,
Distance = new ReactiveProperty<float>(orbit.Distance.Value),
Phase = orbit.Phase + phase
};
pack.Orbits.Add(turretOrbit);
var turret = loadoutGenerator.GenerateTurretLoadout();
turret.Orbit = turretOrbit.ID;
pack.Entities.Add(turret);
}
void PlaceTurrets(OrbitData orbit, LoadoutGenerator loadoutGenerator, int count)
{
for(int t=0; t<count; t++)
{
var dist = t / 2;
if (t % 2 == 0) dist = -dist;
PlaceTurret(orbit, loadoutGenerator, dist);
}
}
for (var i = 0; i < storyStations.Length; i++)
{
var story = storyStations[i];
var orbit = selectedStationOrbits[i];
var lagrangeOrbit = CreateLagrangeOrbit(orbit);
var station = GetLoadoutGenerator(story.Faction).GenerateStationLoadout();
station.Orbit = lagrangeOrbit.ID;
station.SecurityLevel = story.Security;
station.SecurityRadius = pack.Radius;
station.Story = i;
pack.Entities.Add(station);
PlaceTurrets(lagrangeOrbit, GetLoadoutGenerator(story.Faction), story.Turrets);
}
for (var i = storyStations.Length; i < selectedStationOrbits.Length; i++)
{
var orbit = selectedStationOrbits[i];
var security = (SecurityLevel) ((int) ((1f - pow(random.NextFloat(), factionPresence / 2f)) * 3f));
var lagrangeOrbit = CreateLagrangeOrbit(orbit);
var station = GetLoadoutGenerator(nearestFaction).GenerateStationLoadout();
station.Orbit = lagrangeOrbit.ID;
station.SecurityLevel = security;
station.SecurityRadius = pack.Radius * .75f;
station.SecurityRadius = pack.Radius;
pack.Entities.Add(station);
var turretPhase = 20f / lagrangeOrbit.Distance.Value;
var leadingTurretOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = orbit.Parent,
Distance = new ReactiveProperty<float>(orbit.Distance.Value),
Phase = lagrangeOrbit.Phase + turretPhase
};
pack.Orbits.Add(leadingTurretOrbit);
var leadingTurret = loadoutGenerator.GenerateTurretLoadout();
leadingTurret.Orbit = leadingTurretOrbit.ID;
pack.Entities.Add(leadingTurret);
var trailingTurretOrbit = new OrbitData
{
ID = Guid.NewGuid(),
Parent = orbit.Parent,
Distance = new ReactiveProperty<float>(orbit.Distance.Value),
Phase = lagrangeOrbit.Phase - turretPhase
};
pack.Orbits.Add(trailingTurretOrbit);
var trailingTurret = loadoutGenerator.GenerateTurretLoadout();
trailingTurret.Orbit = trailingTurretOrbit.ID;
pack.Entities.Add(trailingTurret);
PlaceTurrets(lagrangeOrbit, GetLoadoutGenerator(nearestFaction), 2);
}
var enemyCount = (int)(random.NextFloat() * factionPresence * 2) + stationCount;
for (int i = 0; i < enemyCount; i++)
{
pack.Entities.Add(loadoutGenerator.GenerateShipLoadout());
pack.Entities.Add(GetLoadoutGenerator(nearestFaction).GenerateShipLoadout());
}
return pack;