Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: faction relationship randomisation option #63

Merged
merged 1 commit into from
Sep 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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"]));
}
}
Loading