Skip to content

Tweak county development calculation, distribute excess development #2002

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

Merged
merged 1 commit into from
Jun 22, 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
18 changes: 11 additions & 7 deletions ImperatorToCK3.UnitTests/CK3/Titles/LandedTitlesTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -374,11 +374,11 @@ public void DevelopmentIsCorrectlyCalculatedFor1ProvinceTo1BaronyCountyMapping()
var date = config.CK3BookmarkDate;
titles.ImportDevelopmentFromImperator(ck3Provinces, date, defaultConfig.ImperatorCivilizationWorth);

Assert.Equal(6, titles["c_county1"].GetDevelopmentLevel(date)); // 0.4*25=10; 10-sqrt(10)≈6
Assert.Equal(8, titles["c_county1"].GetDevelopmentLevel(date)); // 0.4*(25-sqrt(25)) ≈ 8
}

[Fact]
public void DevelopmentFromImperatorProvinceCanBeSplitForTargetProvinces() {
public void DevelopmentFromImperatorProvinceCanBeUsedForMultipleCK3Provinces() {
var conversionDate = new Date(476, 1, 1);
var config = new Configuration { CK3BookmarkDate = conversionDate };
var titles = new Title.LandedTitles();
Expand Down Expand Up @@ -406,9 +406,9 @@ public void DevelopmentFromImperatorProvinceCanBeSplitForTargetProvinces() {
var date = config.CK3BookmarkDate;
titles.ImportDevelopmentFromImperator(ck3Provinces, date, defaultConfig.ImperatorCivilizationWorth);

Assert.Equal(1, titles["c_county1"].GetDevelopmentLevel(date)); // 0.4*7=2.8; 2.8-sqrt(2.8)≈1
Assert.Equal(1, titles["c_county2"].GetDevelopmentLevel(date)); // same as above
Assert.Equal(1, titles["c_county3"].GetDevelopmentLevel(date)); // same as above
Assert.Equal(6, titles["c_county1"].GetDevelopmentLevel(date)); // 0.4 * (21-sqrt(21) ≈ 6
Assert.Equal(6, titles["c_county2"].GetDevelopmentLevel(date)); // same as above
Assert.Equal(6, titles["c_county3"].GetDevelopmentLevel(date)); // same as above
}

[Fact]
Expand Down Expand Up @@ -439,8 +439,12 @@ public void DevelopmentOfCountyIsCalculatedFromAllCountyProvinces() {

var date = config.CK3BookmarkDate;
titles.ImportDevelopmentFromImperator(ck3Provinces, date, defaultConfig.ImperatorCivilizationWorth);

Assert.Equal(6, titles["c_county1"].GetDevelopmentLevel(date)); // 0.4*(10+40)/2=10; 10-sqrt(10)≈6

// Dev from province 1: 10-sqrt(10) ≈ 6
// Dev from province 2: 40-sqrt(40) ≈ 33
// Average: (6+33)/2 ≈ 19
// Average multiplied by civilization worth: 0.4*19 ≈ 8
Assert.Equal(8, titles["c_county1"].GetDevelopmentLevel(date));
}

[Fact]
Expand Down
91 changes: 74 additions & 17 deletions ImperatorToCK3/CK3/Titles/LandedTitles.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,11 +1124,11 @@ private HashSet<string> GetCountyHolderIds(Date date) {
}

public void ImportDevelopmentFromImperator(ProvinceCollection ck3Provinces, Date date, double irCivilizationWorth) {
static bool IsCountyOutsideImperatorMap(Title county, IReadOnlyDictionary<string, int> impProvsPerCounty) {
return impProvsPerCounty[county.Id] == 0;
static bool IsCountyOutsideImperatorMap(Title county, IReadOnlyDictionary<string, int> irProvsPerCounty) {
return irProvsPerCounty[county.Id] == 0;
}

double CalculateCountyDevelopment(Title county, IReadOnlyDictionary<ulong, int> ck3ProvsPerIRProv) {
double CalculateCountyDevelopment(Title county) {
double dev = 0;
IEnumerable<ulong> countyProvinceIds = county.CountyProvinceIds;
int provsCount = 0;
Expand All @@ -1137,44 +1137,48 @@ double CalculateCountyDevelopment(Title county, IReadOnlyDictionary<ulong, int>
Logger.Warn($"CK3 province {ck3ProvId} not found!");
continue;
}
++provsCount;
var sourceProvinces = ck3Province.ImperatorProvinces;
if (sourceProvinces.Count == 0) {
continue;
}

dev += sourceProvinces.Sum(srcProv => srcProv.CivilizationValue / ck3ProvsPerIRProv[srcProv.Id]);
++provsCount;

var devFromProvince = sourceProvinces.Average(srcProv => srcProv.CivilizationValue);
dev += devFromProvince;
}

dev = Math.Max(0, dev - Math.Sqrt(dev));
if (provsCount > 0) {
dev /= provsCount;
}
dev *= irCivilizationWorth;
dev /= provsCount;
dev -= Math.Sqrt(dev);
return dev;
}

Logger.Info("Importing development from Imperator...");

var counties = this.Where(t => t.Rank == TitleRank.county).ToList();
var (irProvsPerCounty, ck3ProvsPerImperatorProv) = GetIRProvsPerCounty(ck3Provinces, counties);
var irProvsPerCounty = GetIRProvsPerCounty(ck3Provinces, counties);

foreach (var county in counties) {
if (IsCountyOutsideImperatorMap(county, irProvsPerCounty)) {
// Don't change development for counties outside of Imperator map.
continue;
}

double dev = CalculateCountyDevelopment(county, ck3ProvsPerImperatorProv);
double dev = CalculateCountyDevelopment(county);

county.History.Fields.Remove("development_level");
county.History.AddFieldValue(date, "development_level", "change_development_level", (int)dev);
}

DistributeExcessDevelopment(date);

Logger.IncrementProgress();
return;

static (Dictionary<string, int>, Dictionary<ulong, int>) GetIRProvsPerCounty(ProvinceCollection ck3Provinces, IEnumerable<Title> counties) {
Dictionary<string, int> impProvsPerCounty = [];
Dictionary<ulong, int> ck3ProvsPerImperatorProv = [];
static Dictionary<string, int> GetIRProvsPerCounty(ProvinceCollection ck3Provinces, IEnumerable<Title> counties) {
Dictionary<string, int> irProvsPerCounty = [];
foreach (var county in counties) {
HashSet<ulong> imperatorProvs = [];
foreach (ulong ck3ProvId in county.CountyProvinceIds) {
Expand All @@ -1186,15 +1190,68 @@ double CalculateCountyDevelopment(Title county, IReadOnlyDictionary<ulong, int>
var sourceProvinces = ck3Province.ImperatorProvinces;
foreach (var irProvince in sourceProvinces) {
imperatorProvs.Add(irProvince.Id);
ck3ProvsPerImperatorProv.TryGetValue(irProvince.Id, out var currentValue);
ck3ProvsPerImperatorProv[irProvince.Id] = currentValue + 1;
}
}

impProvsPerCounty[county.Id] = imperatorProvs.Count;
irProvsPerCounty[county.Id] = imperatorProvs.Count;
}

return (impProvsPerCounty, ck3ProvsPerImperatorProv);
return irProvsPerCounty;
}
}

private void DistributeExcessDevelopment(Date date) {
var topRealms = this
.Where(t => t.Rank > TitleRank.county && t.GetHolderId(date) != "0" && t.GetDeFactoLiege(date) is null)
.ToList();

// For every realm, get list of counties with over 100 development.
// Distribute the excess development to the realm's least developed counties.
foreach (var realm in topRealms) {
var realmCounties = realm.GetDeFactoVassalsAndBelow(date, "c").Values
.Select(c => new {County = c, Development = c.GetOwnOrInheritedDevelopmentLevel(date)})
.Where(c => c.Development.HasValue)
.Select(c => new {c.County, Development = c.Development!.Value})
.ToList();
var excessDevCounties = realmCounties
.Where(c => c.Development > 100)
.OrderByDescending(c => c.Development)
.ToList();
if (excessDevCounties.Count == 0) {
continue;
}

var leastDevCounties = realmCounties
.Where(c => c.Development < 100)
.OrderBy(c => c.Development)
.Select(c => c.County)
.ToList();
if (leastDevCounties.Count == 0) {
continue;
}

var excessDevSum = excessDevCounties.Sum(c => c.Development - 100);
Logger.Debug($"Top realm {realm.Id} has {excessDevSum} excess development to distribute among {leastDevCounties.Count} counties.");

// Now that we've calculated the excess dev, we can cap the county dev at 100.
foreach (var excessDevCounty in excessDevCounties) {
excessDevCounty.County.SetDevelopmentLevel(100, date);
}

while (excessDevSum > 0 && leastDevCounties.Count > 0) {
var devPerCounty = excessDevSum / leastDevCounties.Count;
foreach (var county in leastDevCounties.ToList()) {
var currentDev = county.GetOwnOrInheritedDevelopmentLevel(date) ?? 0;
var devToAdd = Math.Max(devPerCounty, 100 - currentDev);
var newDevValue = currentDev + devToAdd;

county.SetDevelopmentLevel(newDevValue, date);
excessDevSum -= devToAdd;
if (newDevValue >= 100) {
leastDevCounties.Remove(county);
}
}
}
}
}

Expand Down
Loading