Hearts of Iron IV

Hearts of Iron IV

EU4 Style Monuments
 This topic has been pinned, so it's probably important
LimonenZitrone  [developer] 21 Sep, 2021 @ 11:22pm
TUTORIAL: Adding Monuments
TUTORIAL: Adding Monuments
This is a tutorial for making a sub mod and adding additional, new monuments that way.
For that you need to create a new mod and set this mod, EU4 Style Monuments, as a dependency in the .mod files. You will also have to use both, this mod and your sub mod together and should you publish your sub mod, mark this mod as required on the workshop page.

How are monuments set up?
While it might sound relatively easy to add to the monuments database, it does require to edit quite a few of things because of how HOI4 works.
There are a few files you need to edit or create.
For a quick overview:
  • scripted localisation
  • scripted triggers
  • dynamic modifiers
  • ideas
  • an image
  • a .gfx file to define the image(s)
  • localisation
  • scripted effects/on actions

Some of these do involve multiple files and/or multiple parts in the file(s).
And some of them can be done in a new file, while others require to replace certain files. However the file structure is set up to be as least as possible dependent on constant updates after the main mod updates.
This means that, theoretically, an update of the main mod - as long as only new monuments are added - should not require an update of the sub mod - as long as it only adds monuments the way described in this tutorial.

However, this also means there are two general rules for sub mods that do this:
These sub mods will and cannot be combatible with each other, unless the creators make their mods combatible manually (by including the changes of the other, in which case they could just make a mod together).
Each monument has an ID, which is used internally for assigned effects, name, etc. You should start your monuments with a rather high number to prevent the main mod and your sub mod to colide in using the same IDs. For example, you could start at an ID of 1000.


Making a monument
As written above, there are a lot of files involved in making a monument.
However before you start, you should think of a few things, unrelated of how the actual implementation progress works:
  • What bonuses should each level of the monument give?
  • What conditions should it have for building/gaining the bonuses?
  • Where should it be?
  • What should its starting level be?

Ideally you should also have a name, description and image at hand to use.
Furthermore you need an ID. This sounds more complicated than it is. It is just an integer that is not yet used as an ID. This number is used everywhere to identify your monument (e.g. it is used as a variable value, array value and in the name of the ideas and dynamic modifiers). You will add this value to an array in a state, to spawn your monument. And you will check a variable for this ID in multiple instances to set specific stuff for your monument (e.g. image, name, triggers, etc.)

Scripted Localisation
There is one scripted localisation entry that you need to edit.
It is located in the file named zzz_monuments_sub_mod_scripted_localisation.txt in common/scripted_localisation.
Copy this file to your mod, it must replace the one of the main mod.

While there are comments in that file that should be enough to explain it, I'll give a small run down on what the entry is used for.

GetMonumentImageBackUp is used to grab the correct image for your monument. Make sure it points to the correct sprite type for each specific monument. While the name of the sprite type doesn't have to follow any specific requirements itself, I advise to stick to the format I used in the main mod, which is:
GFX_monument_<name>
<name> being a placeholder here for the name, in a codified form (aka no umlauts, no spaces), for example: GFX_monument_brandenburg_gate
Example:
text = { trigger = { check_variable = { v = 1000 } } localization_key = "GFX_monument_cool_palace" }
This would assign the monument with the ID 1000 the image that is defined under the sprite type GFX_monument_cool_palace.

Scripted Triggers
There are multiple scripted trigger entries that you need to edit.
They are all located in the file named zzz_monuments_sub_mod_scripted_triggers.txt in common/scripted_triggers.
Copy this file to your mod, it must replace the one of the main mod.

While there are comments in that file that should be enough to explain it, I'll give a small run down on what entries it has and what they are used for.

monument_build_trigger_back_up defines the triggers that the state and owner/controller must fulfill to build the monument (building means upgrading from level 0 to 1).
In order to connect triggers to specific monuments, you need to use an if else_if chain checking for the specific monument ID. Since there is a game rule that can switch the requirements from owner to controller, you do not scope to either of those scopes, but instead use "var:bonus_target", which is set up to be the correct scope (owner/controller) for the chosen game rule.
For example:
if = { limit = { check_variable = { monument_id = 1000 } } var:bonus_target = { original_tag = USA } } else_if = { limit = { check_variable = { monument_id = 1001 } } var:bonus_target = { original_tag = GER } }
In this example the monument with the ID 1000 could only be built by the USA, while the monument with the ID 1001 could only be built by Germany.
Note that you only need to add triggers to this section, if you want additional triggers that are required for building the monument than for upgrading it/getting its bonuses.
For example if you only want the USA to be able to build a specific monument, but it should still be possible for other countries to get the bonuses on conquest or being able to upgrade it further. Tags are usually not used here, though they make a good, simple example.

can_use_monument_bonuses_back_up defines the triggers that the state and owner/controller must fulfill for being able to upgrade the monument from level 1 up to 3 as well as to get the bonuses the monument gives. The method is the same as with the trigger above, so I will not post another code example here.

calculate_monument_construction_cost_back_up is used to alter the political power cost of a monument (building/upgrading). You only need to put something into this trigger, if you want to increase or decrease the price of a specific monument. The base cost is 50 PP, however it is further altered by level after this calculation is made.
If you decide to do that, you would follow the above method of if else_if chains that check for the monument ID, however instead of then checking for further requirements, you just add to a specific temp variable:
if = { limit = { check_variable = { monument_id = 1000 } } add_to_temp_variable = { monument_construction_cost_temp = 25 } }
This example would add 25 to the base cost of the monument with the ID 1000. Note that this only affects the base price, the end price might still be altered by modifiers and game rules.

calculate_monument_construction_time_back_up is used to alter the time a monument building/upgrade takes. It works exactly like the cost trigger above, just that it uses a different temp variable name: monument_construction_time_temp
The base time is 50 days, however it is further altered by level after this calculation is made.

monument_cannot_downgrade_back_up defines whether a monument is considered a Natural Monument, which will never downgrade on conquest or nuclear bomb drop.
You simply add a variable check with the specific ID to make a monument a Natural Monument in-game.
OR = { check_variable = { monument_id = 1000 } check_variable = { monument_id = 1001 } }
In this example, the monuments with the IDs 1000 and 1001 will be considered Natural Monuments. Note that there is a line with hidden_trigger = { always = no } in the OR section by default, you can remove this when you have added variable checks. It is simply there to prevent the OR by mistake returning true because of missing conditions.

The 3 other sections in the file AI_wants_to_upgrade_monument_back_up_special, AI_wants_to_upgrade_monument_back_up_strong and AI_wants_to_upgrade_monument_back_up_weak follow the exact same method. You only need to use them if you want to tweak AI decision in what monuments to prefer when spending PP for them.

Dynamic Modifiers
Dynamic modifiers are used for the state bonuses a monument might give.
It is important to follow the right naming format as well as also define them, even if your monument does not have any state bonuses.

Since you don't need to replace any main mod file for these, make a new, uniquely named file in common/dynamic_modifiers, in which you put yours.

Each monument has 4 dynamic modifiers, following this format:

monument_<ID>_level_0_modifier = { enable = { always = yes } remove_trigger = { always = no } } monument_<ID>_level_1_modifier = { enable = { always = yes } remove_trigger = { always = no } # bonuses } monument_<ID>_level_2_modifier = { enable = { always = yes } remove_trigger = { always = no } # bonuses } monument_<ID>_level_3_modifier = { enable = { always = yes } remove_trigger = { always = no } # bonuses }

Just like before, <ID> is again a placeholder here for the actual ID, so example: monument_1000_level_0_modifier, etc.

Ideas
Ideas are used for the country bonuses a monument might give.
It is important to follow the right naming format as well as also define them, even if your monument does not have any country bonuses.

Since you don't need to replace any main mod file for these, make a new, uniquely named file in common/ideas, in which you put yours.
Ideas have different categories in which they can be put, I suggest using the hidden_ideas category in order to not clutter the national spirits overview.

Each monument has 4 ideas, following this format:
ideas = { hidden_ideas = { monument_<ID>_level_0_idea = { name = "MONUMENT_<ID>_NAME" allowed = { always = no } allowed_civil_war = { always = yes } cancel_if_invalid = no modifier = { } } monument_<ID>_level_1_idea = { name = "MONUMENT_<ID>_NAME" allowed = { always = no } allowed_civil_war = { always = yes } cancel_if_invalid = no modifier = { # bonuses } } monument_<ID>_level_2_idea = { name = "MONUMENT_<ID>_NAME" allowed = { always = no } allowed_civil_war = { always = yes } cancel_if_invalid = no modifier = { # bonuses } } monument_<ID>_level_3_idea = { name = "MONUMENT_<ID>_NAME" allowed = { always = no } allowed_civil_war = { always = yes } cancel_if_invalid = no traits = { } modifier = { # bonuses } } } }

Just like before, <ID> is again a placeholder here for the actual ID, so example: monument_1000_level_0_idea, etc.
Note that you should only have one ideas = { } and one hidden_ideas = { } section in the file that include all your monument ideas.

Image
The size for the image must be 300x150 px. In order to still have a relatively small file size, I suggest to save it as a dds with DXT 3 or 5.
Where exactly you save that image inside your mod's gfx folder doesn't really matter, as long as you point to the right path in the sprite type.

When you have saved the image, you need to define that sprite type.
Since you don't need to replace any main mod file for this, make a new, uniquely named file with the extension .gfx in the interface folder (note, interface, not gfx/interface).
This file will include all your sprite type definitions for monument images.
spriteTypes = { spriteType = { name = "GFX_monument_cool_palace" textureFile = "gfx/interface/monuments/cool_palace.dds" } }

All your sprite types should be defined in that one spriteTypes section.
While it makes sense to use the same name in the icon and sprite type, those don't have to match to work. You just need to refer to the correct icon in the correct folder and make sure your sprite type name matches with what you put into the very first scripted localisation.

Now that you defined the image, you are not quite done yet. You need to define one more sprite type for the monument. The sprite type for the image used when the monument is disabled.
You can do that in the same file or make a new one following the same method as above.

One thing to note for this is that it must use the same name as the sprite type for the normal image, just with an additional _disabled at the end.
Furthermore it must include a specific shader as effect file. This effect file is the same for all of the disabled sprite types and makes the saturation of the image be reduced in-game.
spriteType = { name = "GFX_monument_cool_palace_disabled" textureFile = "gfx/interface/monuments/cool_palace.dds" effectFile = "gfx/FX/monument_disabled_shader.lua" }

Localisation
Localisation is used to change code names, strings, into readable, normal text. There are quite a few strings you need to localize for the monument.

Since you don't need to replace any main mod file for this, make a new, uniquely named file with the extension .yml in the localisation folder. Make sure the name ends on the specific language (e.g. custom_name_l_english.yml) and that the file is encoded correctly. For further help for that, take a look at the HOI4 wiki.

At a minimum you need to localize 10 strings:
  • the name
  • the description
  • the bonuses for the level 0
  • the bonuses for the level 1
  • the bonuses for the level 2
  • the bonuses for the level 3
  • the name of the dynamic modifier for level 0
  • the name of the dynamic modifier for level 1
  • the name of the dynamic modifier for level 2
  • the name of the dynamic modifier for level 3

The first and the last 4 should be the same tho, so it's just a matter of copy pasting a string.
Here is an example:
MONUMENT_1000_NAME: "Cool Palace" MONUMENT_1000_DESC: "The §Y$MONUMENT_1000_NAME$§! is a very cool palace." MONUMENT_1000_LEVEL_0_BONUS: "$MONUMENT_BONUS_NONE$" MONUMENT_1000_LEVEL_1_BONUS: "$MONUMENT_STATE_BONUS$ \n $state_production_speed_buildings_factor$: §G+10.00%§!" MONUMENT_1000_LEVEL_2_BONUS: "$MONUMENT_STATE_BONUS$ \n $state_production_speed_buildings_factor$: §G+15.00%§! \n\n$MONUMENT_COUNTRY_BONUS$ \n $drift_defence_factor$: §G+10.00%§!" MONUMENT_1000_LEVEL_3_BONUS: "$MONUMENT_STATE_BONUS$ \n $state_production_speed_buildings_factor$: §G+20.00%§! \n\n$MONUMENT_COUNTRY_BONUS$ \n $drift_defence_factor$: §G+15.00%§! \n $political_power_factor$: §G+10.00%§!" monument_1000_level_0_modifier: "$MONUMENT_1000_NAME$" monument_1000_level_1_modifier: "$MONUMENT_1000_NAME$" monument_1000_level_2_modifier: "$MONUMENT_1000_NAME$" monument_1000_level_3_modifier: "$MONUMENT_1000_NAME$"

Note that you need to manually put in the bonuses. So make sure they are the same as they are in the ideas and dynamic modifiers.

As you can see there are some parts of this localisation between dollar signs. These are other localisation strings that the game will paste into the specific text.
§ is used to color the text, so e.g. §Ytext§! makes "text" yellow, §Gtext§! makes "text" green and §! just returns to the previous color, which is usually white.

So in this example, the description would in-game be shown as "The Cool Palace is a very cool palace."
While this is just a good way to not repeat the name manually in the description and dynamic modifier names, it is very helpful in the bonus strings.
$MONUMENT_BONUS_NONE$ automatically returns "None", and you usually should just keep this as it is, since monuments should never really give a bonus at level 0 (though it is technically possible).
$MONUMENT_STATE_BONUS$ automatically returns "State Modifiers:".
$MONUMENT_COUNTRY_BONUS$ automatically returns "Country Modifiers:".

While this doesn't sound like much work to just write manually all the time, it is very helpful to prevent mistakes - or a lot of work should I change it in the main mod, since then it would automatically change in yours.
However for the modifier names it is definitely most useful, as it is much better to automatically paste the vanilla modifier localisation into the tooltip rather than having to manually type it in. Especially since this makes sure that the modifier names will always be translated into the correct language the game is running in.
For this reason I also suggest to make localisation files for all languages, even if the text you localise it in is English only. People will still be able to use the mod no matter the game language, and while the flavour texts and names would be in English only, the effects would still be translated.

I generally use the modifier name used in the idea or dynamic modifier as the localisation string here, as you can see in the example above. Should you encounter a modifier that I have not yet used in the main mod, this string might not be the same as the vanilla localisation string. In that case you can, just like I did, make another string pass to the correct string.
drift_defence_factor: "$MODIFIER_DRIFT_DEFENCE_FACTOR$"

Scripted Effects/On Actions
Now you did nearly everything a monument needs. Just one more thing is needed. To actually spawn it.
You can either make a scripted effect and call this in a specific on action or directly in the specific on action.
This just depends on your preferences and whether you want to be able to test it in-game directly via the console.

Either way, the on action you should use for your effect(s) is the on_startup on action. Should you use a scripted effect inside it, make sure to first scope to a random country or a state before using it, since the on action itself has no scope, but you need a scope to execute a scripted effect.
If you use the spawn effects directly in the on action, you automatically scope to the states anyway.

To spawn the monument, you need to use a scripted effect of the main mod inside the specific state scope.
1 = { set_temp_variable = { monument_id = 1000 } set_temp_variable = { level = 1 } add_monument_to_state = yes }

You first scope to that state, in this example it's the state with the ID 1.
Then you need to set two temp variables, monument_id to the ID of the monument and level to the starting level it should have, possible values are 0, 1, 2 and 3.
Then you just use the scripted effect add_monument_to_state = yes.

If you followed all these steps correctly (and you restarted the game, which might be needed for everything to correctly reload), your monument should be set up.
To test if all effects work properly, you can select the state and use the console command set_monument_level <ID> <level>, in which <ID> and <level> are placeholders, so for example: set_monument_level 1000 3
Note that for that to work, you game might have to run in -debug mode.
Last edited by LimonenZitrone; 27 Nov, 2024 @ 3:21pm