UI Improvements

Target box now has bars for visibility and HP
Hit indicator appears when target is damaged by player's weapons
WIP narrative integration
This commit is contained in:
Reinier Vens 2021-04-27 16:44:44 +01:00
commit ecd52b20c8
30 changed files with 2397 additions and 131 deletions

View file

@ -5,7 +5,8 @@
"MessagePack",
"Unity.Mathematics",
"UniRx",
"RethinkDB"
"RethinkDB",
"Ink"
],
"includePlatforms": [],
"excludePlatforms": [],

View file

@ -113,7 +113,8 @@ public abstract class Entity
public int AssignedPopulation => PopulationAssignments.Sum(pa => pa.AssignedPopulation);
public float Mass { get; private set; }
public float Visibility => VisibilitySources.Values.Sum();
public Subject<Entity> IncomingHit = new Subject<Entity>();
public Subject<(int2 pos, float damage)> ArmorDamage = new Subject<(int2, float)>();
public Subject<(EquippedItem item, float damage)> ItemDamage = new Subject<(EquippedItem, float)>();
// public Subject<EquippedItem> ItemOffline = new Subject<EquippedItem>();

View file

@ -136,6 +136,16 @@ public static class EntitySerializer
}
}
[MessagePackObject]
public class SavedStory
{
[Key(0)]
public string StoryJson;
[Key(1)]
public string StateJson;
}
[MessagePackObject]
public class ShipPack : EntityPack
{
@ -149,6 +159,9 @@ public class OrbitalEntityPack : EntityPack
{
[Key(16)]
public Guid Orbit;
[Key(17)]
public SavedStory Story;
}
[MessagePackObject,

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 5ed144b07bdd45089df886ea901ea366
timeCreated: 1619469268

View file

@ -0,0 +1,176 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Ink;
using Ink.Runtime;
using MessagePack;
using Path = System.IO.Path;
using Random = Unity.Mathematics.Random;
public class StoryProcessor : IZoneResolver, IFactionResolver
{
private class AetheriaInkFileHandler : IFileHandler {
private DirectoryInfo NarrativeRoot { get; }
public AetheriaInkFileHandler(DirectoryInfo narrativeRoot)
{
NarrativeRoot = narrativeRoot;
}
public string ResolveInkFilename (string includeName) => Path.Combine (NarrativeRoot.FullName, includeName);
public string LoadInkFileContents (string fullFilename) => File.ReadAllText (fullFilename);
}
private Dictionary<Story, SectorZone> _placedStories = new Dictionary<Story, SectorZone>();
private Dictionary<string, Story> _processedStories = new Dictionary<string, Story>();
private DirectoryInfo _locationsPath;
private IFileHandler _inkFileHandler;
public DirectoryInfo NarrativeDirectory { get; }
private Random _random;
public Sector Sector { get; }
public PlayerSettings Settings { get; }
public StoryProcessor(PlayerSettings settings, DirectoryInfo narrativeDirectory, Sector sector, ref Random random)
{
NarrativeDirectory = narrativeDirectory;
Sector = sector;
_random = random;
Settings = settings;
_inkFileHandler = new AetheriaInkFileHandler(narrativeDirectory);
_locationsPath = narrativeDirectory.CreateSubdirectory("Locations");
var locationFiles = _locationsPath.EnumerateFiles("*.ink");
foreach(var inkFile in locationFiles) PlaceStory(GetStory(inkFile));
}
public SectorZone ResolveZone(string path)
{
// Handle special cases: "start" and "end"
if (path.Equals("start", StringComparison.InvariantCultureIgnoreCase))
return Sector.Entrance;
if (path.Equals("end", StringComparison.InvariantCultureIgnoreCase))
return Sector.Exit;
// Handle faction home reference: format = "home.<faction>"
if(path.StartsWith("home"))
{
var factionString = path.Substring(5);
var faction = ResolveFaction(factionString);
return !Sector.HomeZones.ContainsKey(faction) ? null : Sector.HomeZones[faction];
}
var locationInkFile = new FileInfo(Path.Combine(_locationsPath.FullName, $"{path}.ink"));
if (!locationInkFile.Exists) return null;
var story = GetStory(locationInkFile);
PlaceStory(story);
return _placedStories.ContainsKey(story) ? _placedStories[story] : null;
}
public Faction ResolveFaction(string name)
{
return Sector.Factions.FirstOrDefault(f => f.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
}
Story GetStory(FileInfo inkFile)
{
var fileName = Path.GetFileNameWithoutExtension(inkFile.FullName);
// 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 compiler = new Compiler(File.ReadAllText(inkFile.FullName), new Compiler.Options
{
countAllVisits = true,
fileHandler = _inkFileHandler
});
var story = compiler.Compile();
File.WriteAllText(compiledFileName, story.ToJson());
return story;
}
public void PlaceStory(Story story)
{
if (_placedStories.ContainsKey(story)) return; // Don't place already placed stories, idiot!
_placedStories[story] = null; // Avoids potential for infinite loops when evaluating constraints
var contentTags = GetContentTags(story);
var constraints = new List<ZoneConstraint>();
if(contentTags.ContainsKey("constraint"))
foreach (var constraintString in contentTags["constraint"])
{
var args = constraintString.Split(' ');
var flip = args[0] == "not";
if (flip) args = args.Skip(1).ToArray();
var constraintName = args[0];
args = args.Skip(1).ToArray();
ZoneConstraint constraint = constraintName switch
{
"DistanceFrom" => new DistanceConstraint(args, this) { Flip = flip },
"FactionPresent" => new FactionPresenceConstraint(args, this) { Flip = flip },
"FactionOwner" => new FactionOwnerConstraint(args, this) { Flip = flip },
_ => null
};
if(constraint!=null) constraints.Add(constraint);
}
ZoneSelector selector;
if (contentTags.ContainsKey("select"))
{
var selectorString = contentTags["select"].First();
var args = selectorString.Split(' ');
var flip = args[0] == "not";
if (flip) args = args.Skip(1).ToArray();
var selectorName = args[0];
args = args.Skip(1).ToArray();
selector = selectorName switch
{
"DistanceFrom" => new DistanceSelector(args, this) { Flip = flip },
_ => new RandomSelector(ref _random)
};
}
else
selector = new RandomSelector(ref _random);
var zoneCandidates = new List<SectorZone>();
zoneCandidates.AddRange(Sector.Zones.Where(z=>constraints.All(c=>c.Test(z))));
_placedStories[story] = selector.SelectZone(zoneCandidates);
}
// Parse all tags of the format X:Y into a dictionary with a list of every Y for each X
public static Dictionary<string, List<string>> GetContentTags(Story story)
{
var contentTags = new Dictionary<string, List<string>>();
foreach (var tag in story.globalTags)
{
if (tag.IndexOf(':') == -1) continue;
var tokens = tag.Split(':');
var leftSide = tokens[0].Trim();
var rightSide = tokens[1].Trim();
if (!contentTags.ContainsKey(leftSide)) contentTags[leftSide] = new List<string>();
contentTags[leftSide].Add(rightSide);
}
return contentTags;
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 40906e10d0c24af688d9f300548a3066
timeCreated: 1619469326

View file

@ -0,0 +1,89 @@
using System;
using System.Collections.Generic;
using System.Linq;
public interface IZoneResolver
{
public SectorZone ResolveZone(string path);
}
public interface IFactionResolver
{
public Faction ResolveFaction(string name);
}
public abstract class ZoneConstraint
{
public bool Flip = false;
public bool Test(SectorZone zone)
{
return Flip ^ TestZone(zone);
}
protected abstract bool TestZone(SectorZone zone);
}
public class FactionPresenceConstraint : ZoneConstraint
{
private Faction TargetFaction { get; }
public FactionPresenceConstraint(string[] args, IFactionResolver resolver)
{
TargetFaction = resolver.ResolveFaction(args[0]);
}
protected override bool TestZone(SectorZone zone)
{
return zone.Factions.Any(f=>f.ID == TargetFaction?.ID);
}
}
public class FactionOwnerConstraint : ZoneConstraint
{
private Faction TargetFaction { get; }
public FactionOwnerConstraint(string[] args, IFactionResolver resolver)
{
TargetFaction = resolver.ResolveFaction(args[0]);
}
protected override bool TestZone(SectorZone zone)
{
return zone.Owner.ID == TargetFaction?.ID;
}
}
public class DistanceConstraint : ZoneConstraint
{
private SectorZone _targetZone;
private Predicate<int> _test;
public DistanceConstraint(string[] args, IZoneResolver zoneResolver)
{
if (args.Length == 3)
{
_targetZone = zoneResolver.ResolveZone(args[0].Trim());
if (!int.TryParse(args[2], out var v)) _test = _ => false;
else
{
var trimmed = args[1].Trim();
_test = trimmed switch
{
"<" => i => i < v,
">" => i => i > v,
"=" => i => i == v,
_ => _ => false
};
}
}
else
{
_test = _ => false;
}
}
protected override bool TestZone(SectorZone zone)
{
return _targetZone != null && _test(zone.Distance[_targetZone]);
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: e5dcf761c4ab4b0ca8a477daec28b35f
timeCreated: 1619128088

View file

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MessagePack;
using Random = Unity.Mathematics.Random;
public abstract class ZoneSelector
{
public abstract SectorZone SelectZone(List<SectorZone> candidates);
}
public abstract class OrderedZoneSelector : ZoneSelector
{
public bool Flip = false;
protected abstract Func<SectorZone, IComparable> Comparison { get; }
public override SectorZone SelectZone(List<SectorZone> candidates)
{
var sorted = candidates.OrderBy(Comparison);
return Flip ? sorted.Last() : sorted.First();
}
}
public class DistanceSelector : OrderedZoneSelector
{
private SectorZone Target { get; }
public DistanceSelector(string[] args, IZoneResolver zoneResolver)
{
Target = zoneResolver.ResolveZone(args[0].Trim());
}
protected override Func<SectorZone, IComparable> Comparison => zone => Target?.Distance[zone] ?? 0;
}
public class RandomSelector : ZoneSelector
{
private Random Random { get; }
public RandomSelector(ref Random random)
{
Random = random;
}
public override SectorZone SelectZone(List<SectorZone> candidates)
{
return candidates[Random.NextInt(candidates.Count)];
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 917c5a50cc724463bfce61429b4b661e
timeCreated: 1619133153

View file

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using MessagePack;
@ -10,6 +11,7 @@ public class PlayerSettings
[Key(2)] public TemperatureUnit TemperatureUnit = TemperatureUnit.Celsius;
[Key(3)] public int SignificantDigits = 3;
[Key(4)] public bool TutorialPassed;
[Key(5)] public Dictionary<string, string> HashedStoryFiles = new Dictionary<string, string>();
public string FormatTemperature(float t)
{

View file

@ -68,7 +68,8 @@ public class Sector
x => Zones[x.Value]);
Entrance = Zones[savedGame.Entrance];
Exit = Zones[savedGame.Exit];
if(savedGame.Exit != -1)
Exit = Zones[savedGame.Exit];
CalculateDistanceMatrix();
}
@ -119,26 +120,27 @@ public class Sector
Action<string> progressCallback = null,
uint seed = 0)
{
Faction ResolveFaction(string name) => cache.GetAll<Faction>().FirstOrDefault(f => f.Name.StartsWith(name, StringComparison.InvariantCultureIgnoreCase));
Background = background;
var allFactions = cache.GetAll<Faction>();
var random = new Random(seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed);
var factions = new List<Faction>();
var protagonistFaction = allFactions.First(f => f.Name.StartsWith(settings.ProtagonistFaction, StringComparison.InvariantCultureIgnoreCase));
var protagonistFaction = ResolveFaction(settings.ProtagonistFaction);
factions.Add(protagonistFaction);
var antagonistFaction = allFactions.First(f => f.Name.StartsWith(settings.AntagonistFaction, StringComparison.InvariantCultureIgnoreCase));
var antagonistFaction = ResolveFaction(settings.AntagonistFaction);
factions.Add(antagonistFaction);
var bufferFaction = allFactions.First(f => f.Name.StartsWith(settings.BufferFaction, StringComparison.InvariantCultureIgnoreCase));
var bufferFaction = ResolveFaction(settings.BufferFaction);
factions.Add(bufferFaction);
var questFaction = allFactions.First(f => f.Name.StartsWith(settings.QuestFaction, StringComparison.InvariantCultureIgnoreCase));
var questFaction = ResolveFaction(settings.QuestFaction);
factions.Add(questFaction);
var neutralFactions = settings.NeutralFactions
.Select(s => allFactions.First(f => f.Name.StartsWith(s, StringComparison.InvariantCultureIgnoreCase)))
.Select(ResolveFaction)
.ToArray();
factions.AddRange(neutralFactions);

View file

@ -33,6 +33,8 @@ public class ZonePack
[JsonProperty("time"), Key(5)]
public double Time;
}
// [RethinkTable("Galaxy"), MessagePackObject, JsonObject(MemberSerialization.OptIn)]