Skip to content

Commit

Permalink
feat: faction relationship randomisation option (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
sargeantPig authored Sep 8, 2024
1 parent 07cb35b commit 7d9e909
Show file tree
Hide file tree
Showing 9 changed files with 261 additions and 2 deletions.
67 changes: 66 additions & 1 deletion RTWLibPlus/dataWrappers/baseWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ public void DeleteChunks(string stopAt, string ident)
{
IBaseObj item = this.Data[i];

if (ident == item.Ident)
if (ident == item.Ident && !identFound)
{
identFound = true;
startI = i;
Expand Down Expand Up @@ -311,6 +311,71 @@ public List<IBaseObj> GetItemsByCriteriaDepth(List<IBaseObj> list, string stopAt
}
return found;
}

public List<IBaseObj> GetItemsByCriteriaSimpleDepth(List<IBaseObj> list, string stopAt, string lookFor, Dictionary<string, bool> criteria, params string[] criteriaTags)
{
if (criteria.Count == 0)
{
criteria = criteriaTags.ToDictionary(item => item, item => false);
}

List<IBaseObj> found = [];
foreach (BaseObj item in list)
{
if (criteriaTags.Any(tag => tag == item.Tag))
{
criteria[item.Tag] = true;
}

bool criteriaMet = criteria.Values.All((value) => value);
if (item.Ident == lookFor && criteriaMet)
{
found.Add(item);
}

if (item.Items.Count > 0)
{
List<IBaseObj> nest = this.GetItemsByCriteriaSimpleDepth(item.Items, stopAt, lookFor, criteria, criteriaTags);
found.AddRange(nest);
}

if (item.Ident == stopAt && criteriaMet)
{
return found;
}
}
return found;
}

public List<IBaseObj> GetItemsByKeyDict(List<IBaseObj> data, string stopAt, string lookFor, KeyValuePair<string, string> criteria)
{
List<IBaseObj> result = [];
bool found = false;
foreach (BaseObj item in data)
{
if (item.Ident == criteria.Key && item.Value == criteria.Value)
{
found = true;
}

if (found && item.Ident == lookFor)
{
result.Add(item);
}

if (item.Items.Count > 0)
{
result.AddRange(this.GetItemsByKeyDict(item.Items, stopAt, lookFor, criteria));
}

if (item.Ident == stopAt)
{
return result;
}
}
return result;
}

public List<IBaseObj> GetAllItemsButStopAt(Func<string, bool> stopAt, params string[] criteriaTags)
{
bool[] criteria = new bool[criteriaTags.Length];
Expand Down
16 changes: 16 additions & 0 deletions RTWLibPlus/dataWrappers/ds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using RTWLibPlus.parsers.objects;
using System;
using System.Collections.Generic;
using System.Linq;

public class DS : BaseWrapper, IWrapper
{
Expand Down Expand Up @@ -63,6 +64,21 @@ private void SetLastOfGroup()
}
}

public Dictionary<string, string[]> GetFactionRegionDict()
{
Dictionary<string, string[]> factionRegions = [];
List<IBaseObj> factions = this.GetItemsByIdent("faction");
foreach (IBaseObj faction in factions)
{
string name = faction.Tag.Split('\t')[1].Trim(',');
string tag = faction.Tag;
List<IBaseObj> faction_cities = this.GetItemsByCriteriaSimpleDepth(this.Data, "character", "region", [], tag);
factionRegions.Add(name, faction_cities.ToArray(x => x.Value));
}

return factionRegions;
}

public Dictionary<string, List<IBaseObj>> GetSettlementsByFaction(SMF smf)
{
Dictionary<string, List<IBaseObj>> settlementsByFaction = [];
Expand Down
12 changes: 12 additions & 0 deletions RTWLibPlus/helpers/base.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace RTWLibPlus.helpers;

using System;
using System.Collections.Generic;
using System.Linq;
using RTWLibPlus.interfaces;

public static class BaseHelper
{
public static string[] ToArray(this List<IBaseObj> objects, Func<IBaseObj, string> sequence) => objects.Select(sequence).ToArray();

}
37 changes: 37 additions & 0 deletions RTWLibPlus/map/city.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
namespace RTWLibPlus.map;
using RTWLibPlus.dataWrappers;
using RTWLibPlus.dataWrappers.TGA;
using RTWLibPlus.helpers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

public class CityMap
Expand Down Expand Up @@ -54,6 +56,41 @@ public Vector2 GetClosestWater(Vector2 from)
return water;
}

public Dictionary<string, string[]> GetClosestRegions(Dictionary<string, string[]> factionRegions, int maxDistance)
{
Dictionary<string, string[]> closeFactions = [];

foreach (KeyValuePair<string, string[]> a in factionRegions)
{
List<string> closeFactionList = [];
closeFactions.Add(a.Key, []);
foreach (string areg in a.Value)
{
Vector2 acoords = this.CityCoordinates[areg];
foreach (KeyValuePair<string, string[]> b in factionRegions)
{
if (a.Key == b.Key)
{
continue;
}
foreach (string breg in b.Value)
{
Vector2 bcoords = this.CityCoordinates[breg];

double distance = acoords.DistanceTo(bcoords);

if (distance < maxDistance)
{
closeFactionList.Add(b.Key);
}
}
}
}
closeFactions[a.Key] = new HashSet<string>([.. closeFactionList]).ToArray();
}
return closeFactions;
}

private void GetCityCoords(TGA image, DR dr)
{
for (int i = 0; i < image.Pixels.Length; i++)
Expand Down
2 changes: 2 additions & 0 deletions RTWLibPlus/modifiers/stratModifier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ public static string ChangeCharacterCoordinates(string character, Vector2 coords
return split.ToString(',', ' ');
}

public static string CreateFactionCoreAttitude(string factionA, string factionB, int relation) => string.Format("core_attitudes\t{0},\t{1}\t\t{2}", factionA, relation, factionB);

public static string CreateFactionRelation(string factionA, string factionB, int relation) => string.Format("faction_relationships\t{0},\t{1}\t\t{2}", factionA, relation, factionB);
}

47 changes: 47 additions & 0 deletions RTWLibPlus/randomiser/randDS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,53 @@ public static string SwitchUnitsToRecruitable(EDU edu, DS ds, RandWrap rnd)
return "Units switched to units in a given factions recruitment pool";
}

public static string RandRelations(DS ds, SMF smf, CityMap cm, int range)
{
ds.DeleteChunks("script", "core_attitudes");

List<string> factions = smf.GetFactions();
List<string> factionsCopy = smf.GetFactions();
Dictionary<string, string[]> factionRegions = ds.GetFactionRegionDict();
Dictionary<string, string[]> closeFactions = cm.GetClosestRegions(factionRegions, range);
Dictionary<string, string[]> closeFactionsCopy = closeFactions;
RandRelationshipHandle(ds, factions, closeFactions, true);
RandRelationshipHandle(ds, factionsCopy, closeFactionsCopy, false);
return "Relationships randomise";
}

private static void RandRelationshipHandle(DS ds, List<string> factions, Dictionary<string, string[]> closeFactions, bool core)
{
int[] relation_values = [0, 100, 200, 400, 600];
for (int i = 0; i < factions.Count; i++)
{
List<IBaseObj> faction_cities = ds.GetItemsByCriteriaSimpleDepth(ds.Data, "character", "region", [], string.Format("faction\t{0},", factions[i]));
foreach (string closeFaction in closeFactions[factions[i]])
{
DSObj newRelation;
if (core)
{
newRelation = new(
StratModifier.CreateFactionCoreAttitude(factions[i], closeFaction, relation_values.GetRandom(out int tag, RandWrap.RND)),
StratModifier.CreateFactionCoreAttitude(factions[i], closeFaction, relation_values[tag]),
0);
}
else
{
newRelation = new(
StratModifier.CreateFactionRelation(factions[i], closeFaction, relation_values.GetRandom(out int tag, RandWrap.RND)),
StratModifier.CreateFactionRelation(factions[i], closeFaction, relation_values[tag]),
0);
}

ds.InsertAt(ds.Data.Count - 2, newRelation);
closeFactions[closeFaction] = closeFactions[closeFaction].FindAndRemove(factions[i]);
}

factions.RemoveAt(0);
i = 0;
}
}

private static void MatchCharacterCoordsToCities(string[] factionList, RandWrap rnd, DS ds, CityMap cm)
{
rnd.RefreshRndSeed();
Expand Down
20 changes: 20 additions & 0 deletions RTWLib_CLI/cmd/modules/randomiser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,26 @@ public string StratArmiesUseOwnedUnits()
return RandDS.SwitchUnitsToRecruitable(this.edu, this.ds, this.rnd);
}

public string StratRandomRelations(int range)
{
if (this.ds == null)
{
return "DS not loaded - run 'rand initialsetup'";
}

if (this.smf == null)
{
return "SMF not loaded - run 'rand initialsetup'";
}

if (this.cm == null)
{
return "CM not loaded - run 'rand initialsetup'";
}

return RandDS.RandRelations(this.ds, this.smf, this.cm, range);
}

public string PaintFactionMap()
{
FactionMap factionMap = new();
Expand Down
23 changes: 22 additions & 1 deletion RTWLib_Tests/randomised/Tests_RandDS.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ namespace RTWLib_Tests.randomised;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RTWLibPlus.data;
using RTWLibPlus.dataWrappers;
using RTWLibPlus.dataWrappers.TGA;
using RTWLibPlus.helpers;
using RTWLibPlus.interfaces;
using RTWLibPlus.map;
using RTWLibPlus.parsers.objects;
using RTWLibPlus.randomiser;

Expand All @@ -26,7 +28,7 @@ public void UnitsAreRecruitable()
SMF smf = new(smfParse, this.config);
List<string> beforeUnits = edu.GetUnitsFromFaction("romans_julii", []);
RandEDU.RandomiseOwnership(edu, this.rand, smf);
RandDS.SwitchUnitsToRecruitable(edu, ds, rand);
RandDS.SwitchUnitsToRecruitable(edu, ds, this.rand);
List<IBaseObj> units = ds.GetItemsByCriteria("character", "unit", "faction\tromans_julii,", "character", "army");
List<string> eduUnits = edu.GetUnitsFromFaction("romans_julii", []);
//RFH.Write("eddu-test.txt", edu.Output());
Expand All @@ -38,4 +40,23 @@ public void UnitsAreRecruitable()
Assert.AreEqual(true, has);
}
}

[TestMethod]
public void RelationsAreRandom()
{
List<IBaseObj> dsParse = RFH.ParseFile(Creator.DScreator, ' ', false, "resources", "descr_strat.txt");
DS ds = new(dsParse, this.config);
List<IBaseObj> smfParse = RFH.ParseFile(Creator.SMFcreator, ':', false, "resources", "descr_sm_factions.txt");
SMF smf = new(smfParse, this.config);
TGA image = new("tgafile", RFH.CurrDirPath("resources", "map_regions.tga"), "");
List<IBaseObj> drparse = RFH.ParseFile(Creator.DRcreator, '\t', false, "resources", "descr_regions.txt");
DR dr = new(drparse, this.config);

CityMap cm = new(image, dr);
List<IBaseObj> before = ds.GetItemsByIdent("core_attitudes").DeepCopy();
RandDS.RandRelations(ds, smf, cm, 30);
List<IBaseObj> after = ds.GetItemsByIdent("core_attitudes").DeepCopy();

Assert.IsTrue(before.Count < after.Count);
}
}
39 changes: 39 additions & 0 deletions RTWLib_Tests/wrappers/Tests_ds.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,15 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using RTWLibPlus.data;
using RTWLibPlus.dataWrappers;
using RTWLibPlus.dataWrappers.TGA;
using RTWLibPlus.helpers;
using RTWLibPlus.interfaces;
using RTWLibPlus.map;
using RTWLibPlus.Modifiers;
using RTWLibPlus.parsers;
using RTWLibPlus.parsers.objects;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;

[TestClass]
Expand Down Expand Up @@ -204,5 +207,41 @@ public void GetFactionByRegion()

Assert.AreEqual(expected, result); //check number of returned ca
}
[TestMethod]
public void GetSettlementsForFaction()
{
string[] ds = this.dp.ReadFile(RFH.CurrDirPath("resources", "descr_strat.txt"), false);
List<IBaseObj> dsParse = this.dp.Parse(ds, Creator.DScreator);
DS parsedds = new(dsParse, this.config);
List<IBaseObj> faction_cities = parsedds.GetItemsByCriteriaSimpleDepth(parsedds.Data, "character", "region", [], "faction\tromans_julii,");
int expected = 2;
int result = faction_cities.Count;
Assert.AreEqual(expected, result); //check number of returned ca
}
[TestMethod]
public void GetFactionRegionsDict()
{
string[] ds = this.dp.ReadFile(RFH.CurrDirPath("resources", "descr_strat.txt"), false);
List<IBaseObj> dsParse = this.dp.Parse(ds, Creator.DScreator);
DS parsedds = new(dsParse, this.config);
TGA image = new("tgafile", RFH.CurrDirPath("resources", "map_regions.tga"), "");
List<IBaseObj> drparse = RFH.ParseFile(Creator.DRcreator, '\t', false, "resources", "descr_regions.txt");
DR dr = new(drparse, this.config);

CityMap cm = new(image, dr);
Dictionary<string, string[]> fr = parsedds.GetFactionRegionDict();

string[] expectedFirst = ["Etruria", "Umbria"];
string[] expectedSecond = ["Gallaecia", "Lusitania", "Hispania", "Taraconenis"];
Assert.IsTrue(expectedFirst.SequenceEqual(fr["romans_julii"]));
Assert.IsTrue(expectedSecond.SequenceEqual(fr["spain"]));

Dictionary<string, string[]> closeFactions = cm.GetClosestRegions(fr, 30);

string[] closestMockOne = ["romans_julii", "romans_brutii", "romans_scipii", "carthage", "gauls", "greek_cities", "slave"];
string[] closestMockTwo = ["gauls", "germans", "slave"];

Assert.IsTrue(closestMockOne.SequenceEqual(closeFactions["romans_senate"]));
Assert.IsTrue(closestMockTwo.SequenceEqual(closeFactions["britons"]));
}
}

0 comments on commit 7d9e909

Please sign in to comment.