Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
War Support
#1
I am saddened to see that you have stopped modding Humankind. I am currently using most of your mods and have been since you first posted them. They are spot on changes that make the game so much more enjoyable for me. I do hope that they stay relevant for a very long time. 

I think that while there are things in Humankind that need work, the base game provides a good foundation to build on. I hope to make the game better and better over time with both my own mods and some from others, like yourselves. 

I am using Amplitude's mod tools to mess around with units, cultures, districts, buildings, etc. I am 62, and while I have a computer sciences degree and learned 4 programming languages, that was a long time ago. But I always found the logic in programming languages very comforting. I like to have control over the problems I am working on, and I really like knowing that any errors in the code are a result of my own doing. I always enjoyed the inevitable debugging sessions. It has probably been 15 years or more since I last wrote any code, but am looking forward to giving it a try soon. Once I have tweaked the units to my liking I would love to make a .dll mod for it. The same with districts, etc. I saw your other post with the link to some of the .dll code, thanks for that.

In the mean time, I was hoping to maybe get some help or advice from you on War Support. I am interested in the War Support once I am at war with another culture, not so much before that. From what I have read and gleamed through some of the database values, forums, devtalk, and localized text, it looks like that once war has begun, there are events that drive down my opponents War Support, and once their support reaches 0 then a surrender is triggered. I think there are essentially 4 events that effect both mine and their war support: winning/losing a battle, retreating/causing a retreat, destroying an administrative center and destroying/capturing a city. I looked for "Effect_Retreat_ blah blah blah" in the database hoping to be able to change the values. But no such luck. I would like my opponents war support to decrease a bit slower so that I have the opportunity to occupy more of their territory and gain more "spoils" at the point of surrender. Obviously setting those values to 1 would make war last way too long, but I would like to be able to experiment with them to find a better balance. Most of my games are Endless with 7 opponents on huge maps that last several hundred turns. I really enjoy long drawn out games.

I would also like to find the formula they use to calculate the War Score Value of each city once a surrender event is triggered. I think I would like to see those values increased a bit as well. Maybe not, but would love to be able to play around with both sets of numbers. 

Thoughts? Advice? I know they recently announced they are going to give the option to continue war after a surrender event is triggered, but they also added the caveat that there would be increased risk for doing so; for continuing the war. Not sure what that means, but doesn't sound like much fun either. Like I said, I always play against 7 AI and they always have many more units, many of them more advanced that mine, and are always able to replenish them at a pretty fast pace. So, will have to wait and see what Amplitude changes, but not too eager for it. I would prefer to come up with my own solution; something I will have control over and be able to tweak as necessary.

Thanks again for the work you put into these mods, like I said I really enjoy them and use many of them every game. 

Sincerely, 

Pops.
Reply
#2
Hi PopsiclePrime,

I'm glad that you are enjoying the mods that we made Smile

The war support penalties seem to be something that may be possible to affect through a database descriptor, but the default values are hard coded in the DLL.

Amplitude.Mercury.Simulation.DiplomaticAmbassy in Amplitude.Mercury.Firstpass.dll has some of these values hard coded as constants passed to attributes.
Code:
[PropertyDefinition(BaseValue = 8f)]
public Property InstantBattleWonMoralGain;

[PropertyDefinition(BaseValue = -8f)]
public Property InstantBattleLostMoralGain;

[PropertyDefinition(BaseValue = -5f)]
public Property InstantMyEmpireRetreatedMoralGain;

[PropertyDefinition(BaseValue = 5f)]
public Property InstantOtherEmpireRetreatedMoralGain;
Full decompiled source HERE

It looks like there is at least one database level change that affects values in this class. The religious tenet "Hunt the Infidels" (ReligionTenet_Tier1_Tenet04) increases war support for the player that has that tenet when an enemy retreats from a battle with them. It uses the property path Empires.MyAmbassyToOthers.InstantBattleWonMoralGain (the path goes right to the part quoted above and could be altered to affect the retreat gains/losses). There may be some way to work it into the database with the mod tools to have it apply to everyone, but I'm not sure what the best way to do that would be since most of the global defaults seem to be hardcoded in the DLL with alterations in the DB for specific things like tenets, cultures, civics, etc. Maybe it could be added as a legacy trait for the neolithic culture (I guess that would work unless a player used a mod to start in a later era). It could make sense to put it as a modifier in the AI difficulty definitions so that higher difficulty levels only affect the AI's war support, but I don't know if the official mod tool gives access to that yet.

A DLL mod could work as well. You could replace the attribute values quoted above with a BepInEx preload mod. I used this method to unlock developer mode by loading a different attribute value into the assembly info attributes of the main game dll. That approach is a little more invasive than a normal function patch and requires using Mono.Cecil to manually make changes to the DLL in memory before the game is loaded.

What I would probably do if I wanted to make this change in a way that was easy to alter and share, would be to patch the functions that are using that value and add logic to alter the value before it is used (the dev mode unlock would not have worked as well this way because there were just way too many places that checked for the dev mode flag). For the retreat (and many other war support value changes), it looks like you would want to change functions in the Amplitude.Mercury.Simulation.DiplomaticMoralHelper class.

Code:
private static FixedPoint GetInstantMoralDeltaForInfluenceType(DiplomaticAmbassy myEmpireAmbassy, DiplomaticMoralInfluenceType action)
{
return action switch
{
//...Trimmed irrelevant cases
DiplomaticMoralInfluenceType.MyEmpireRetreated => myEmpireAmbassy.InstantMyEmpireRetreatedMoralGain.Value,
//...
};
}
This could be altered with a Harmony DLL patch either at the beginning or end to handle that case differently and either return a lower static value, adjust the value based on some attributes of the arguments (the 2 empires and their diplomatic info), or limit it so that other modifiers could still affect the value but never let it get to a certain level. For example, you could run some code in a post fix that checks if it less than -2, then set it to -2.
Code:
[HarmonyPatch(typeof(DiplomaticMoralHelper))]
public class DiplomaticMoralHelper_Patch
{
    [HarmonyPatch(nameof(GetInstantMoralDeltaForInfluenceType))]
    [HarmonyPostfix]
    public static void GetInstantMoralDeltaForInfluenceType(
        ref FixedPoint __result,
        DiplomaticAmbassy myEmpireAmbassy, DiplomaticMoralInfluenceType action)
    {
        if(action == DiplomaticMoralInfluenceType.MyEmpireRetreated && __result < -2)
        {
            __result = -2;

        }
    }
}

As far as the war score costs of territory in a surrender, I had never really looked at that before. After scanning the decompiled source, it looks like Amplitude.Mercury.AI.Brain.Analysis.Diplomacy.ComputeSurrenderTermScores.Process() is where that is calculated.
Code:
public override void Process()
{
Brain.GetAnalysisData<MajorEmpireData, MajorEmpire>(Brain.ControlledEmpire, out var analysisData);
for (int i = 0; i < Brain.ControlledEmpire.AliveEmbassies.Length; i++)
{
DiplomaticEmbassy diplomaticEmbassy = Brain.ControlledEmpire.AliveEmbassies[i];
int surrenderPropositionIndexToFill = -1;
bool propositionToFillAgainstUs = false;
diplomaticEmbassy.GetSurrenderPropositionToFill(out surrenderPropositionIndexToFill, out propositionToFillAgainstUs);
if (surrenderPropositionIndexToFill < 0)
{
continue;
}
ref SurrenderProposition reference = ref Amplitude.Mercury.Interop.AI.Snapshots.Surrender.SurrenderPropositions.Data[surrenderPropositionIndexToFill];
Brain.GetAnalysisData<RelationData, DiplomaticRelation>(diplomaticEmbassy.Relation, out var analysisData2);
int num = 1 + reference.IncludedDemands.Length;
bool flag = reference.SubmissionDemand.FailureFlags == SurrenderSubmissionFailureFlags.None;
if (flag)
{
num++;
}
for (int j = 0; j < reference.SurrenderTerritories.Length; j++)
{
ref SurrenderTerritoryInfo reference2 = ref reference.SurrenderTerritories[j];
if (reference2.FailureFlags == SurrenderTerritoryFailureFlags.None && reference2.Connectivity != 0 && reference2.Connectivity != SurrenderTerritoryConnectivity.NotValid)
{
num++;
}
}
Array.Resize(ref analysisData2.SurrenderTermScores, num);
SurrenderTermScoreSortMethod sortMethod = (propositionToFillAgainstUs ? SurrenderTermScoreSortMethod.DesireOnImpactDescending : SurrenderTermScoreSortMethod.OnlyDesireDescending);
int num2 = 0;
SurrenderTermScore surrenderTermScore = new SurrenderTermScore(SurrenderTermCategory.Money, sortMethod);
DiplomaticTermPass.ComputeMoneyTermScores(ref surrenderTermScore.Motive, ref surrenderTermScore.Impact, analysisData, propositionToFillAgainstUs);
analysisData2.SurrenderTermScores[num2++] = surrenderTermScore;
if (flag)
{
SurrenderTermScore surrenderTermScore2 = new SurrenderTermScore(SurrenderTermCategory.Submission, sortMethod);
DiplomaticTermPass.ComputeSubmissionTermScore(ref surrenderTermScore2.Motive, ref surrenderTermScore2.Impact, analysisData2, propositionToFillAgainstUs);
surrenderTermScore2.WarMoraleCost = (int)reference.SubmissionDemand.WarCost;
analysisData2.SurrenderTermScores[num2++] = surrenderTermScore2;
}
for (int k = 0; k < reference.IncludedDemands.Length; k++)
{
ref SurrenderDemandInfo reference3 = ref reference.IncludedDemands[k];
SurrenderTermScore surrenderTermScore3 = new SurrenderTermScore(SurrenderTermCategory.Demand, sortMethod);
surrenderTermScore3.TargetIndex = k;
if ((propositionToFillAgainstUs && diplomaticEmbassy.OtherEmpire.DemandsValidity[reference3.DemandIndex]) || (!propositionToFillAgainstUs && Brain.ControlledEmpire.DemandsValidity[reference3.DemandIndex]))
{
switch (reference3.DemandGainType)
{
case DemandGainType.Money:
DiplomaticTermPass.ComputeMoneyTermScores(ref surrenderTermScore3.Motive, ref surrenderTermScore3.Impact, analysisData, propositionToFillAgainstUs);
break;
case DemandGainType.Territory:
{
AIEntityRepository.Instance.TryGetEntity<Territory>(reference3.DemandGain.GainGuid, out var entity);
DiplomaticTermPass.ComputeTerritoryTermScores(Brain, ref surrenderTermScore3.Motive, ref surrenderTermScore3.Impact, entity, propositionToFillAgainstUs);
break;
}
case DemandGainType.ForceCivic:
DiplomaticTermPass.ComputeCivicTermScore(Brain, ref surrenderTermScore3.Motive, ref surrenderTermScore3.Impact, analysisData2, propositionToFillAgainstUs);
break;
case DemandGainType.ForceReligion:
DiplomaticTermPass.ComputeReligionTermScore(Brain, ref surrenderTermScore3.Motive, ref surrenderTermScore3.Impact, analysisData2, propositionToFillAgainstUs);
break;
case DemandGainType.DiplomaticAction:
DiplomaticTermPass.ComputeInterventionTermScore(Brain, ref surrenderTermScore3.Motive, ref surrenderTermScore3.Impact, ref reference3.DemandGain, analysisData2, propositionToFillAgainstUs);
break;
default:
surrenderTermScore3.Motive.Add(0.1f);
surrenderTermScore3.Impact.Add(1f);
Diagnostics.LogError($"AI does not handle DemandGainType {reference3.DemandGainType}");
break;
}
}
else
{
DiplomaticTermPass.ComputeMoneyTermScores(ref surrenderTermScore3.Motive, ref surrenderTermScore3.Impact, analysisData, propositionToFillAgainstUs);
}
surrenderTermScore3.WarMoraleCost = (int)reference3.WarCostPerGrievance * reference3.NumberOfGrievances;
analysisData2.SurrenderTermScores[num2++] = surrenderTermScore3;
}
for (int l = 0; l < reference.SurrenderTerritories.Length; l++)
{
ref SurrenderTerritoryInfo reference4 = ref reference.SurrenderTerritories[l];
if (reference4.FailureFlags == SurrenderTerritoryFailureFlags.None && reference4.Connectivity != 0 && reference4.Connectivity != SurrenderTerritoryConnectivity.NotValid)
{
SurrenderTermScore surrenderTermScore4 = new SurrenderTermScore(SurrenderTermCategory.Territory, sortMethod);
surrenderTermScore4.TargetIndex = l;
DiplomaticTermPass.ComputeTerritoryTermScores(Brain, ref surrenderTermScore4.Motive, ref surrenderTermScore4.Impact, Amplitude.Mercury.Interop.AI.Snapshots.World.Territories[l], propositionToFillAgainstUs);
surrenderTermScore4.WarMoraleCost = (int)reference4.WarCost;
analysisData2.SurrenderTermScores[num2++] = surrenderTermScore4;
}
}
AllocFreeSorter.Sort(analysisData2.SurrenderTermScores, surrenderTermSorter);
if (!propositionToFillAgainstUs)
{
continue;
}
int num3 = 0;
float num4 = (float)Brain.ControlledEmpire.MoneyStock + CommonValues.Economy.AcceptableDebtDuration * (float)Brain.ControlledEmpire.MoneyNet;
int num5 = (int)reference.WinnerWarScore - (int)reference.IncludedDemandCost - (int)reference.IncludedTerritoriesCost;
if (reference.SubmissionDemand.Included)
{
num5 -= (int)reference.SubmissionDemand.WarCost;
}
if (reference.MoneyRetribution.IsIncluded)
{
num5 -= reference.MoneyRetribution.NumberOfRetribution * (int)reference.MoneyRetribution.WarMoralCostPerRetribution;
}
int num6 = 0;
while (num5 > 0)
{
ref SurrenderTermScore reference5 = ref analysisData2.SurrenderTermScores[num6];
bool flag2 = true;
int num7 = 0;
switch (reference5.Category)
{
case SurrenderTermCategory.Demand:
{
ref DiplomaticDemandInfo reference6 = ref diplomaticEmbassy.OtherEmpire.DiplomaticDemands.Data[reference5.TargetIndex];
if (reference6.DemandGainType == DemandGainType.Money)
{
num7 = reference6.DemandGain.GainParam;
if ((float)(num7 + num3) > num4)
{
flag2 = false;
}
}
else
{
flag2 = true;
}
break;
}
case SurrenderTermCategory.Money:
num7 = (int)((float)num5 * (float)reference.MoneyRetribution.MoneyGainPerRetribution / (float)reference.MoneyRetribution.WarMoralCostPerRetribution);
reference5.WarMoraleCost = num5;
if ((float)(num7 + num3) > num4)
{
flag2 = false;
}
break;
case SurrenderTermCategory.Submission:
case SurrenderTermCategory.Territory:
flag2 = true;
break;
default:
Diagnostics.LogError($"Surrender term category {reference5.Category} is not handled");
break;
}
if (flag2)
{
num5 -= reference5.WarMoraleCost;
num3 += num7;
num6++;
continue;
}
if ((float)num7 > num4 * 2f)
{
reference5.Impact.Set(1f);
}
else
{
reference5.Impact.Multiply(1.4f);
reference5.Impact.Clamp01();
}
int num8 = num6;
for (int m = num6 + 1; m < analysisData2.SurrenderTermScores.Length; m++)
{
ref SurrenderTermScore reference7 = ref analysisData2.SurrenderTermScores[m];
if (!(reference7.Impact.Value < 0.9f) || !(reference7.Impact.Value <= reference5.Impact.Value))
{
break;
}
num8 = m;
}
if (num8 != num6)
{
SurrenderTermScore surrenderTermScore5 = analysisData2.SurrenderTermScores[num6];
Array.Copy(analysisData2.SurrenderTermScores, num6 + 1, analysisData2.SurrenderTermScores, num6, num8 - num6);
analysisData2.SurrenderTermScores[num8] = surrenderTermScore5;
}
else
{
num5 -= reference5.WarMoraleCost;
num3 += num7;
num6++;
}
}
}
}
Reply
#3
Wow! Okay cool. Thanks for this. Stuff I can actually understand, make sense of.
Reply


Forum Jump:


Users browsing this thread: 1 Guest(s)