I love the population system from Total War Rome Remastered because in order to be able to recruit armies, you have to have enough people in your cities. However, the only way to lose population is to recruit units, to have a plague or to conquer and destroy the settlement.
Thankfully, the scripting capabilities give us a great opportunity to introduce new mechanics into the game which we will implement in this tutorial:
+ Make settlements lose a random amount of population if taxes are high with a low probability
+ Make settlements lose a random amount of population if taxes are very high with a higher probability
+ Make settlements lose a random amount of population if they have a plague with a higher probability (additionally to the many people we lose to the plague)
+ Make settlements lose a random amount of population if there are riots with a low probability
But not everything in life is bad, so we also want to do:
+ Make settlements gain a random amount of population if taxes are very low with high probability
+ Make settlements gain a random amount of population if it is the capital of the faction with a low probability
What I like about this mechanics very much is that it forces the player to think whether he should do higher tax rates to get more money but at the same time risking not only riots from the taxed but also the fact that they could simply emigrate to another country. It also gives capitals another advantage other than being the only settlement with no capital distance malus for public order - it simply grows faster than the other cities simply because it is the center of your faction.
A cool idea would be if sieges also would make the settlements population go down, but unfortunately I havent found a way how to implement that yet. The game doesnt seem to offer us a condition to check if a settlement is under siege.
To implement this script, we are going to need a programming language like Python and a templating engine like Pythons quik in this example. Also, you have to have a list of all your RegionIds (Rome, Sparta, ...) of all the regions on your map. I use the modding build tool Atlas when modding Rome Remastered so I dont have to worry about that as I always have my data available during execution.
Now, we only need two parts of code for this example. Part One triggers whether a settlement is going to gain or lose population in a round. Part two will react to the trigger and do two things: First it will randomize the amount of population that the settlement will gain or lose and secondly it will execute the population gain/loss and also display a message to the player.
And here is the code (Python/quik templating) - of course you can use any other programming language and templating engine you wish, just adapt the logic to your particular language syntax and keywords.
Part One (you can play around with the percentage probabilities in RandomPercent to see what makes more sense for you):
Code:
#for @Region in @Get.AllRegions():
monitor_event SettlementTurnStart SettlementName @{Region.IdCity}
and SettlementIsLocal
and I_CompareCounter popPlus@{Region.UID} = 0
and I_CompareCounter popMinus@{Region.UID} = 0
if SettlementTaxLevel = tax_low
and RandomPercent < 10
console_command reseed_random
set_counter popPlus@{Region.UID} 1
end_if
if IsCapital
and RandomPercent < 5
console_command reseed_random
set_counter popPlus@{Region.UID} 1
end_if
if SettlementTaxLevel = tax_high
and RandomPercent < 10
console_command reseed_random
set_counter popMinus@{Region.UID} 1
end_if
if SettlementTaxLevel = tax_extortionate
and RandomPercent < 15
console_command reseed_random
set_counter popMinus@{Region.UID} 1
end_if
if SettlementHasPlague
and RandomPercent < 5
console_command reseed_random
set_counter popMinus@{Region.UID} 1
end_if
if IsSettlementRioting
and RandomPercent < 5
console_command reseed_random
set_counter popMinus@{Region.UID} 1
end_if
end_monitor
#end
And also finally Part Two:
Code:
monitor_event FactionTurnStart TrueCondition
and FactionIsLocal
#for @Region in @Get.AllRegions():
monitor_conditions I_CompareCounter popPlus@{Region.UID} = 1
#for @a in [2, 3, 4, 5, 6, 7, 8, 9, 10]:
if RandomPercent < 10
inc_counter popPlus@{Region.UID} 1
console_command reseed_random
end_if
#end
#for @a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
if I_CompareCounter popPlus@{Region.UID} = @{a}
console_command add_population @Region.IdCity @{a}00
@Scripter.MessageInfo("Immigration to @{Region.NameCity}", "The region of @{Region.NameRegion} and especially the settlement of @{Region.NameCity} are currently experiencing a wave of immigration. @{a}00 new citizen have arrived. Migration is caused by low taxes and war.", "wonder_captured")
end_if
#end
set_counter popPlus@{Region.UID} 0
end_monitor
monitor_conditions I_CompareCounter popMinus@{Region.UID} = 1
#for @a in [2, 3, 4, 5, 6, 7, 8, 9, 10]:
if RandomPercent < 10
inc_counter popMinus@{Region.UID} 1
console_command reseed_random
end_if
#end
#for @a in [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]:
if I_CompareCounter popMinus@{Region.UID} = @{a}
console_command add_population @Region.IdCity -@{a}00
@Scripter.MessageInfo("Emigration from @{Region.NameCity}", "The region of @{Region.NameRegion} and especially the settlement of @{Region.NameCity} are currently experiencing a wave of emigration. @{a}00 citizen have left into foreign lands. Migration is caused by low taxes and war.", "wonder_captured")
end_if
#end
set_counter popMinus@{Region.UID} 0
end_monitor
#end
end_monitor
Note that I use a shorter Region UID for the counter names to make the size of the script smaller. However in my mod with 106 regions only, this script is just about 1 MB heavy so it is no big deal. Also, as I am working with Atlas, I dont have to worry about two things you have to worry about when you dont:
-> Declaring counters at the top of the file
-> Replacing @Scripter.MessageInfo() with the following snippet to generate a message to the player:
Code:
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title x_TITLE
body x_BODY
image wonder_captured
}
Dont forget about that.
The population migrations here range between 100 and 1000 people in case of a migration being triggered, however the most probable outcome is between 200 and 500 people.
This is how it looks like in practice:
Poor Cornith got taxed heavily so people decided to move out of it to the capital of Thessalonica and also to Larissa, where the taxes were set to very low. For demonstration purposes I set the probabilities of migration to 100% however, so in practice, there would not be migration for each turn immediately of course.
This here is how the generated code looks like with the example of one region (Thermon):
Part 1 Triggering the migration
Code:
monitor_event SettlementTurnStart SettlementName Thermon
and SettlementIsLocal
and I_CompareCounter popPlusRE0 = 0
and I_CompareCounter popMinusRE0 = 0
if SettlementTaxLevel = tax_low
and RandomPercent < 100
console_command reseed_random
set_counter popPlusRE0 1
end_if
if IsCapital
and RandomPercent < 100
console_command reseed_random
set_counter popPlusRE0 1
end_if
if SettlementTaxLevel = tax_high
and RandomPercent < 100
console_command reseed_random
set_counter popMinusRE0 1
end_if
if SettlementTaxLevel = tax_extortionate
and RandomPercent < 100
console_command reseed_random
set_counter popMinusRE0 1
end_if
if SettlementHasPlague
and RandomPercent < 100
console_command reseed_random
set_counter popMinusRE0 1
end_if
if IsSettlementRioting
and RandomPercent < 100
console_command reseed_random
set_counter popMinusRE0 1
end_if
end_monitor
...
Part Two:
Code:
monitor_event FactionTurnStart TrueCondition
and FactionIsLocal
monitor_conditions I_CompareCounter popPlusRE0 = 1
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popPlusRE0 1
console_command reseed_random
end_if
if I_CompareCounter popPlusRE0 = 1
console_command add_population Thermon 100
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 54AE7777557C2DEF703A8510171E54A0FC0674CB_TITLE
body 54AE7777557C2DEF703A8510171E54A0FC0674CB_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 2
console_command add_population Thermon 200
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 67732C47F976551AD31E61988EB4CFCEFE4D0629_TITLE
body 67732C47F976551AD31E61988EB4CFCEFE4D0629_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 3
console_command add_population Thermon 300
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 43251ADBE4E76E43519BA740E98D6E68B0F602EB_TITLE
body 43251ADBE4E76E43519BA740E98D6E68B0F602EB_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 4
console_command add_population Thermon 400
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 8787C3D5950D248417D4AABE1237AEDA5077C92E_TITLE
body 8787C3D5950D248417D4AABE1237AEDA5077C92E_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 5
console_command add_population Thermon 500
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 196E6B06A9E276AECC2E2753F9D285188B89AB2F_TITLE
body 196E6B06A9E276AECC2E2753F9D285188B89AB2F_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 6
console_command add_population Thermon 600
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title C449F16ABCAA3CC8606B13689F2A967293B0F1F2_TITLE
body C449F16ABCAA3CC8606B13689F2A967293B0F1F2_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 7
console_command add_population Thermon 700
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title C10A3379D7E2D3287E6D3EDC796662E485F930CB_TITLE
body C10A3379D7E2D3287E6D3EDC796662E485F930CB_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 8
console_command add_population Thermon 800
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 95A5D08624D17698CBBCD08C0C01538A0FEAF312_TITLE
body 95A5D08624D17698CBBCD08C0C01538A0FEAF312_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 9
console_command add_population Thermon 900
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 5066AF962940D18D43C6AA7CABDF114D1FD77BDC_TITLE
body 5066AF962940D18D43C6AA7CABDF114D1FD77BDC_BODY
image wonder_captured
}
end_if
if I_CompareCounter popPlusRE0 = 10
console_command add_population Thermon 1000
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title D9EE8F3CEE9C2A2F304ABB84610AB0253D270287_TITLE
body D9EE8F3CEE9C2A2F304ABB84610AB0253D270287_BODY
image wonder_captured
}
end_if
set_counter popPlusRE0 0
end_monitor
monitor_conditions I_CompareCounter popMinusRE0 = 1
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if RandomPercent < 10
inc_counter popMinusRE0 1
console_command reseed_random
end_if
if I_CompareCounter popMinusRE0 = 1
console_command add_population Thermon -100
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 170EB3073E6DB83C01147A7832F7C313262969D5_TITLE
body 170EB3073E6DB83C01147A7832F7C313262969D5_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 2
console_command add_population Thermon -200
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title A9CF0BD078B44AE64ECE8DE9CD7966C8736B6AFB_TITLE
body A9CF0BD078B44AE64ECE8DE9CD7966C8736B6AFB_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 3
console_command add_population Thermon -300
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 415C099937B4A58F6C9D07642ADB10A2C0F471B6_TITLE
body 415C099937B4A58F6C9D07642ADB10A2C0F471B6_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 4
console_command add_population Thermon -400
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 1FDD6CA9611A81B44BBD7C79BAE28B04BDBB10F7_TITLE
body 1FDD6CA9611A81B44BBD7C79BAE28B04BDBB10F7_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 5
console_command add_population Thermon -500
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 426E81EC88F349E6E55D98A1B263A274480076B9_TITLE
body 426E81EC88F349E6E55D98A1B263A274480076B9_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 6
console_command add_population Thermon -600
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title A47BABFB1BDFEA76E78AC0D65DE5552E4B162153_TITLE
body A47BABFB1BDFEA76E78AC0D65DE5552E4B162153_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 7
console_command add_population Thermon -700
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title C5D207EE0F51829987842FEBED12B6934155F936_TITLE
body C5D207EE0F51829987842FEBED12B6934155F936_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 8
console_command add_population Thermon -800
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 91A6A34048338FAC32565060070385471033751B_TITLE
body 91A6A34048338FAC32565060070385471033751B_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 9
console_command add_population Thermon -900
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title 180DE7E8747C5A4158C1AEBEA4EE52908A945309_TITLE
body 180DE7E8747C5A4158C1AEBEA4EE52908A945309_BODY
image wonder_captured
}
end_if
if I_CompareCounter popMinusRE0 = 10
console_command add_population Thermon -1000
message_prompt
{
flag_counter dummy_f
result_counter dummy_r
title ACA88CAF47A1748EB7A83D65182E8052456063B0_TITLE
body ACA88CAF47A1748EB7A83D65182E8052456063B0_BODY
image wonder_captured
}
end_if
set_counter popMinusRE0 0
end_monitor
...
Notice how much shorter the python templating version is. Just the campaign script code for one single city is already 10x as long as the entire python templating code.