Imperator Development Diary - 15th of April

  • We have updated our Community Code of Conduct. Please read through the new rules for the forum that are an integral part of Paradox Interactive’s User Agreement.
Showing developer posts only. Show all posts in this thread.

Trin Tragula

Design Lead - Crusader Kings 3
Paradox Staff
28 Badges
Aug 1, 2003
6.561
14.599
  • Victoria: Revolutions
  • IPO Investor
  • Paradox Order
  • Mount & Blade: Warband
  • Europa Universalis IV: Pre-order
  • Crusader Kings II: Holy Knight (pre-order)
  • 500k Club
  • 200k Club
  • Victoria 2: Heart of Darkness
  • Victoria 2: A House Divided
  • Victoria 2
  • Sengoku
  • Semper Fi
  • Rome Gold
  • Arsenal of Democracy
  • Europa Universalis III Complete
  • March of the Eagles
  • Magicka
  • Europa Universalis III Complete
  • Heir to the Throne
  • Hearts of Iron III
  • For the Motherland
  • For The Glory
  • Europa Universalis IV
  • Divine Wind
  • Europa Universalis III Complete
  • Deus Vult
  • Crusader Kings II
Hello and welcome to another Development Diary for Imperator: Rome!
Today will be a different type of Diary compared to some previous ones as we will be focusing entirely on some of the new technical things that Imperator brings to our scripting capabilities. In other words we will be looking at what new things we are adding that you will be able to use to mod the game.
As in any Paradox Game there will be a wide list of triggers, effects, scopes, etc, but this diary will focus on more high level new things and systems, such as the new script value system or the scriptable gui system.
These things are likely best described by those who created them so with that I will leave the word to @blackninja9939 who will talk about scopes, variables and the scriptable GUI and then @Meneth who will be introducing the wonderful world of Scriptvalues.

SCOPES:

Hello all! I'm Matthew Clohessy and I work as a programmer at PDS. Up until six months ago I was a Content Designer before moving roles, some of you might've seen me around in various modding sections of the forums before as I used to mod Crusader Kings 2 a lot. Over the past year whilst working on <insert cool projects here> I've been doing a variety of improvements to the new Jomini script system to make it a lot more usable, versatile and consistent than the old versions.

Jomini is our Grand Strategy Library which is a midlayer between the game projects and the Clausewitz engine, it contains things that a GSG game can share such as the idea of the game state update, multiplayer, provinces and of course our script system.

Here I'll give you a brief overview of some of the brand new things in the Jomini script system as well as updated and improved versions of systems we had in older games.

With that preamble out of the way lets dive in! The idea of scope types and switching between them is in Jomini, the current Jomini scope types are: no scope, bool, value, color and flag. Yes numbers and bools etc. are a scope type, it has its pros and cons. We refer to these scope types as primitive scopes due to their basic nature and generally not having an object attached to it just the raw ID.
Every event or interaction has a “top scope” which stores root, saved scopes and local variables.

Event Targets are how we 1-1 switch between scope objects, they are comprised of one or more “links” separated by dots. Eg: root.mother.father
As they are separated by dots they can be used in one line so you can do
set_character_religion = root.father.mother.religion
A link can have multiple input types to lead to one output type, allowing polymorphic links that can do more than one thing! So “culture” can move from a province, character, country, pop etc. to their culture.

A scope object can be saved with an arbitrary name to reference later on in the top scope, in our older games these were called event targets. The name was changed as internally both were called event targets before and one is shorter to type for script. Eg:
Code:
father = { save_scope_as = cool_person }
scope:cool_person = { kill_painfully = yes }

Gone are the days of needing things like father = { character = root.mother.father } as a condition to see if two characters are the same.
Now we can just do father = root.mother.father, this works for any event target so you can compare things very simply.

This also allows the comparison of numeric values using >, <, >=, <=, = and !=

Numeric links can be used as the value in an effect such as mother = { add_loyalty = root.prominence }

SCRIPT LISTS:

Script lists are how we move from one scope to one or more from a list of similar objects. Eg: any_sibling

With the new system we internally only register the list builder such as sibling, the code then automatically generates the various versions for the script.
Currently we have four versions created: any_, every_, random_ and ordered_
The first three should be recognizable, but they’ve all been extended with new functionality
  • Any: Is a trigger that returns true if any of the list meet certain conditions, can have an optional count or percent parameter to indicate X many or Y percent of the list must meet the conditions
  • Every: Runs effects on all members of the list if they meet certain conditions. Can have multiple alternative_limits for backup conditions if the previous set was not met.
  • Random: Runs effects one one member of the list if they meet certain conditions. Can also have alternative_limits as well as a weight to influence which random object to run effects on.
  • Ordered: Runs effect on the entry in a list based on position or range of positions. The list can be ordered by any script value such as loyalty or gold. Can have a limit and alternative_limits on it to filter members of the list.
VARIABLES:

Any non-primitive scope type can be made to store variables in it, which scope types to have them is a game level decision so if you find a scope that does not make a request for it to be added, variables themselves can be any scope type. You can store a value, bool, flag or character etc. inside of them.
This allows for recording a saved scope on a specific object instead of just in a top scope.

Variables can be stored in three places: a scope object (character, country etc.), locally in a top scope (like a normal saved scope) or globally in the game state.

Variables themselves are treated as a scope object referring to whatever is stored in them allow you to scope to them.
One could have a best friend variable on a character which they save someone as the value then scope to that best friend variable to give the character a gift later on.

The event target link to scope to a variable depends on the storage type:
Code:
var:name
local_var:name
global_var:name

There are effects and triggers to manipulate and check numeric variables to change their value etc.

LISTS:

You can create a custom list of event scopes or variables which can be iterated over as a script list.
Code:
every_character = {
    limit = {
        has_variable = olympic_attendee
    }
    add_to_list = olympic_competitor_list
}
random_in_list = {
    list = olympic_competitor_list
    die_very_painfully = yes
}

You can remove items from lists and check for their presence etc.

SWEET SWEET DOCUMENTATION:

We have previously made attempts at automatic documentation with varied success, some bits could be outdated or incorrect, some core information was not printed at all etc.
Now the script documentation console command has been moved to Jomini and it outputs to separate files in your games log folder:
  • All effects, the scopes they can be used in and a brief description, if they are a script list the scope they lead to.
  • All triggers, the scopes they can be used in and a brief description, if they are a script list the scope they lead to.
  • All scope types, character, country, value etc.
  • All event target links, the scopes they can be used from, the scope they output to and a brief description.
  • All saved scopes created by the code.
  • All modifiers, the scope they can be applied to eg: levy_reinforcement_rate
  • All on actions, if they are from code or script and the expected scope they are called in

GUI & LOCALIZATION SYSTEM:

We have a new GUI system for the games which works with its own specif setup of scripting, it is also the same system used for the localization system. Collectively this is called the Data System
Everything you can run must be either registered in by the code or made as a scripted gui.

All things you can use in the data are split into four categories:
  1. Types, the type of an object which corresponds to its class/struct in the code
  2. Promotes, moving from an object of one type to an object of another
  3. Functions, calling a function on an object which returns something
  4. Callbacks, calling a function on an object which does not return anything

Another thing to keep in mind is that the data system obeys (for the most part) how const works in C++. Without getting too technical functions, promote and callbacks can be marked as const only, which means that object which are const cannot call non-const. This is unlikely to affect you if you use the scripted guis though.

Scripted GUI

The scripted gui lets you evaluate and execute arbitrary script via the UI in a manner that will keep the game synchronized in multiplayer. You define the script in common/scripted_guis and can then reference that in data entries.

For example a cheat button to give you gold and take it from another character:
Code:
# common/scripted_guis
cheat_gold_button = {
    scope = character
    saved_scopes = {
        second
    }
    is_shown = { # Can be omitted as always true
        always = yes
    }
    is_valid = {
        gold < 5000
    }
    effect = {
        add_gold = 500
        scope:second = {
            add_gold = -500
        }
    }
}

# in a gui entry
button = {
    name = "my_cheat_button"
    datacontext = "[GetScriptedGui('cheat_gold_button')"
    texture = "gfx/interface/icons/shared_icons/bankruptcy.dds"
    visible = "[ScriptedGui.IsShown( GuiScope.SetRoot( SomeCharacter.MakeScope ).AddScope( SomeOtherCharacter.MakeScope ).End )]"
    enabled = "[ScriptedGui.IsValid( GuiScope.SetRoot( SomeCharacter.MakeScope ).AddScope( SomeOtherCharacter.MakeScope ).End )]"
    onclick = "[ScriptedGui.Execute( GuiScope.SetRoot( SomeCharacter.MakeScope ).AddScope( SomeOtherCharacter.MakeScope ).End )]"
    tooltip = "[ScriptedGui.BuildTooltip( GuiScope.SetRoot( SomeCharacter.MakeScope ).AddScope( SomeOtherCharacter.MakeScope ).End )]"
}

The AI will currently not use these buttons, you could however make hidden pulse events to have them evaluate the same actions.

And with that it is time for @Meneth to talk about what Script values are and why we love them.

Script Values

Good afternoon. I'm Magne "Meneth" Skjæran, and I'm a programmer at PDS. I used to work on Crusader Kings II, where I among other things made a variety of improvements to the script system.
For a while now, I've been working on <redacted>, and I've also recently had a 1 month stint on Imperator.
As part of my work, I added a script math system to Jomini. Jomini is the layer between Clausewitz and the game that handles things like script system basics that don't relate to any specific game.
This is not something we've had in any previous game; the closest one could get was heavy use of variables, which was highly limiting.
I'm here today to talk in some detail about this system, outlining what it can do, so let's start from the beginning.

Script values

The script math system builds on the script value system. Most of our games have some version of this: the ability to define named values in one file for use in multiple places:
some_value_name = 1000
Which can then be used wherever:
Code:
add_gold = some_value_name
In our older games, the support for this could at times be spotty. In games based on Jomini, this is supported almost anywhere numbers can be used.
In the Jomini games, these values can also be things that aren't just simple numbers. You can for instance do things like:
Code:
add_gold = scope:some_country.gold # Adds as much gold as "some_country" has

Mathematical operations

With the script math system, you can now do simple math in script. Instead of "some_value_name = 1000", you can insert math:

Code:
some_value_name = {
    value = scope:some_country.gold
    add = 50
    multiply = 100
}

Which would result in ( "some country"'s gold + 50 ) * 100.
We support the following operations:

  • value = ... # Sets the value to the right-hand-side (RHS)
  • add = ... # Adds the RHS
  • subtract = ... # Subtracts the RHS
  • multiply = ... # Multiplies with the RHS
  • divide = ... # Divides with the RHS
  • modulo = ... # Takes the remainder from dividing with the RHS
  • min = ... # Increases the value to the RHS if it is lower
  • max = ... # Decreases the value to the RHS if it is higher
  • floor = yes # Rounds down. 1.2 -> 1, -1.8 -> -2
  • ceiling = yes # Rounds up
  • round = yes # Rounds to the nearest integer
As you can see, this allows you do to complex math, letting you implement things like costs that depend on a lot of factors in a simple manner.

Inlining

Taking the system further, anything that supports taking a script value by name (E.G., add_gold = some_value_name) also supports doing that math inline.
So instead of "add_gold = some_value_name", you can do this:

Code:
add_gold = {
    value = scope:some_country.gold
    add = 50
    multiply = 100
}

Which will give the exact same result. This is very handy when a value is only used in a single place, since you can then easily see and tweak it where it is being used.
You can even do this inside the math itself. Imagine you want to do the math "gold * ( prestige + 50 )". While you could do this by reordering the math, that'd be pretty tedious. With inlining, you don't have to:

Code:
add_gold = {
    value = gold
    multiply = {
        value = prestige
        add = 50
    }
}

There's no limit on how far you can nest the math.

Conditional logic
Beyond just simple math, you can also have conditional logic. For instance, perhaps you want a reward to be higher if a country has a specific innovation:
Code:
add_gold = {
    value = 100
    if = {
        limit = { has_innovation = some_innovation }
        multiply = 3
    }
    else_if = {
        limit = { has_innovation = some_other_innovation }
        multiply = 2
    }
}
This will result in 300 if the country has some_innovation, 200 if it only has some_other_innovation, and 100 if it has neither.

Ranges

For effects, you can also randomize numbers.
You can do this in two ways.
First there's a very simple syntax:
Code:
add_gold = { 10 100 }
Which would result in a random amount between 10 and 100.

This would also work:
Code:
add_gold = { some_value some_other_value }

However, this syntax does not work with inlining of math. So for that, we have two statements; integer_range and fixed_range.
integer_range will give an integer number in the designated range (E.G., 1, 2, 3). fixed_range will give a fixed-point number (E.G., 0.1, 0.2, 0.345).
An example of this:
Code:
add_gold = {
    integer_range = {
        min = { value = gold multiply = 2 }
        max = { value = gold multiply = 10 }
    }
}

This would give between 2 and 10 times the country's gold.

Lists

We also support list operations, allowing you to work with collections of items and base the math on each individual item in the collection.
Any list that works in normal script (E.G., every_country, every_subject, every_character) will also work in script math.
The script below for example would add the gold of all your subjects:
Code:
add_gold = {
    every_subject= {
        add = gold
    }
}
You can also change scope. Perhaps you want to add all of your overlord's subjects' gold instead:
Code:
add_gold = {
    overlord = {
        every_subject = {
            add = gold
        }
    }
}
As you can see, this system makes it simple to do a lot of things that in our past games was either difficult or even impossible to do in script.
We've used the system a lot in Imperator, and we look forward to seeing what modders will do with it as well.

endofdays.png

And with that this developer diary is at an end. Since script does not lend itself to pretty screenshots here is one from the various screens you can get when the game ends. It's appearance and the text will differ depending on how well you did, this was gotten by making the game end in our devclash save, where I'm in control of the proud nation of Bactria. The game seems to think we've achieved little of note sadly :)
 
Interesting, so how would you scope to a random POP? By going province.city.? or by something like any_pop and limit the search?
Yeah pretty much, there is a script list to move from a city to the pops in it and then you use the limit to restrict the search.
 
Another question, I reckon scripted GUIs have the same capabilities as they are implemented in HoI4, or can we expect new things like pie charts since Imperator uses them relatively often, or even sliders?

I also want to say how awesome you guys are for ever expanding the modding possibilities!
Right now, it mostly has parity with HoI4. More powerful in some areas due to being hooked into the data system. Though lacks a few bells and whistles like support for custom lists.

Can you use more advanced maths like finding the square root of a number or multiplying a number to the power of another number?
Wouldn't be particularly difficult to add, but I'm not sure how likely it is that we'd add it. Those operations (except purely integer powers) are very expensive, so it's something to be avoided. Not supporting it at all ensures it is avoided entirely.
Though if there's interesting use cases, we might reconsider that.
 
Another question, I reckon scripted GUIs have the same capabilities as they are implemented in HoI4, or can we expect new things like pie charts since Imperator uses them relatively often, or even sliders?

I also want to say how awesome you guys are for ever expanding the modding possibilities!
As Meneth said it currently lacks a few of the bells and whistles like custom lists although I've got a working prototype of that I just need to find some free time to sit down and finish it off.
The new data system is generally a lot more powerful in being able to display whatever you want but the scripted gui stuff has evolved bit by bit in my evenings and I've yet too pull it up to the level of HoIs in every area, will try to at some point though! :D

I'm picking up some bad vibrations regarding father figures and athletes. o_O
Hahaha, that was mostly just cause it was very quick to type those ones ;)
 
Oh yeah, that would be interesting, is the max number of a value still around 2 million?
For a lot of things, yes.
Though the script math is internally done 64-bit rather than 32-bit, and has a max value a bit short of 300 trillion. So briefly going above 2 million in the middle of some math shouldn't mess things up, but if the final result is out of range it'll log an error and clamp it to the allowed range (+/- 2 million).
Values that are 64-bit support the full range of that, of course.

Does that mean that we can expect you to keep supporting moddable GUI adding new stuff to it that allows for newer and more things, (like custom lists, since you mention an example)? ;)
Depending on how much we use it internally, and modders externally, that's a possibility.
But no promises.
 
How easy/difficult will it be to mod in nations, compared to CK2 or EU4 or HoI4?

About the same :)
A new country requires a country definition file with its color, it will need a flag (though these can be scripted from existing emblems in Imperator).
Adding the country at start means adding it to a big file called setup.txt which is a bit like a save file, but more readable, something we use instead of the history files that many older games use.

An example for a country entry in the setup file could look like:

Code:
JUD    = { #Judean Subject State, at the start loyal to the Antigonids. Would later be a Ptolemaic subject.
            government = theocratic_monarchy
            primary_culture = hebrew
            religion = judaism

            capital = 687
           
            own_control_core =     {
                683 684 685 686 687 688 689 691 692 711 713 715 #judea_region
                681 710 712 714 716 717 720 731
            }
        }
 
Say that I want to give myself 100 gold for every owned province with horse, so the script will look like this right?
Code:
add_gold = {
every_owned_province = {
limit = { goods = horse }
add = 100
}
}
That looks right.

Will it be possible to mod in more omens, more than the currently default 8 omens? Say I wanted to add 2 more omens to each of the religions, and bring up the number of omens to 10?
I'm relatively sure that's possible.
 
HOI4 has something called "targeted variables". It's something that allows you to create a variable that's targeted at each country. For example, if someone wanted to add "country opinion" variable. They could and it would work as root.country_opinion@target (target can be any country id or prev/this etc..) and the value of country opinion would change dynamically based on the target country. Is such thing possible in Imperator Rome or can it be done differently?

Also what is the difference between Data System and Scripted GUI? Does the Data System control what's already available in the GUI? Or can you create new GUI/buttons via the Data System?

I hope it's possible to change GUI mechanics or create new buttons via the Data System. Because if the AI can't use Scripted GUI I don't see much use for it at least not yet (I assume AI support may be added later). Some actions would be scope based (like selected state) and it would be hard to trigger them by events for the AI.
You got an example of targeted variables? In HoI a variable is a lot more limited in what it can contain, in Jomini a variable can be anything at all so not sure if that would cover your use case already.

The data system is the overarching grander system of everything in the gui and localization. The scripted gui is a specific subset of functions in the gui which link into the script system.
For modding needs the terms are relatively unimportant but for us there is a distinction.

I hope you guys are in touch with Shultays from the HOI4 development team. He's followed closely the use of Scripted GUIs, Variables in the HOI4 community and made several changes/updates that made them way more handy! I hope he'll have some advice for the new Imperator: Rome :)

But here's a few suggestions, at least based on my experimentation with HOI4 variables and scripted GUIs:
  • Please add targeted variables or something of the sort. They are the most useful aspect of variables at least in my experience, especially when you want to work on diplomacy, espionage or interactions between different countries. The ability to use a variable that has a dynamic value that changes based on the target country is really great.
  • I see that you already added the ability to store countries, characters etc.. inside variables which is great! But please also give us the ability to .GetName or something that can extract the name of what you saved in the variable to show it on the GUI or for localisation purposes.
  • Arrays could be something that's useful for the future too. But I think you already said you're working on it!
Yep I've been talking a bit with him, the ability to do GetName etc is gonna be in but not for the first release, I had to work around some stuff to make it happen nicely same for displaying lists.
Arrays already exist in the script albeit called lists to keep them more in line with the script list terminology, I've got a prototype for them being shown in the gui which again will not be in for release but should be in "soon"
 
That's great to know! For targeted variable, it may be already supported in your system. I don't know how different it is, but i'll explain:

An example of the use of targeted variables. For example in my mod, I had to create something that's called "Spy Network Strength" that controls the ability to conduct espionage country in the target country (the variable must be equal to 100). it's kinda like EU4 Spy Network...

The goal is to make sure the variable is unique to each target country. For example France's Spy Network Strength in Germany may be 80, but France's Spy Network Strength in Italy may be 90.

To do that I had to use targeted variables in the scripted GUI that looks sort of like: "ROOT.network_strength@THIS". Root obviously refers to the originating/player country, and THIS refers to the selected country.

Such variable would be dynamic based on the selected/target country.

It may be already possible in your system. But if it's not, perhaps it's something worth considering in the future :)
Ahhh hmmm I am not sure if that would be possible. I'd rather not go down the @THIS whatever route because its rather obtuse in what it actually is and working with in CK2 was not all that fun either lol, I'll look at trying a solution, I am sure there is some work around already with combining lists and variables and other magic but a cleaner one would be good.
 
Not sure who to ask about this, so my apologies if I should've tagged someone else. Anyway, my question is: Is it possible to use existing in-game proportions as triggers? Specifically, I'm considering making a mod that provides a benefit/incentive for not going full monoculture, and for that I need some way to "see" how multicultural a city, province, or country actually is. There's those really nice looking pie charts in game for that info, so I'm wondering if it is possible to pull the info that feeds into those charts, and if an area is multicultural enough (maybe requiring non-slave minority culture pops to make up a third of the total pops in the area?), give the citizen pops in that area a boost to their research rate output or something.
You can iterate over pops somewhere and count how many meet a set condition to count them.

Is it possible to use GetName on saved scopes though? I think saved scopes are what used to be called event targets? Also are saved scopes local or global? If a character is saved as cool_person, is it saved within the scope of that country or is it global (as in there can be only one cool_person in the world or each country can have its own cool_person?)

Lastly, if someone wanted to mod the economy screen, would they use scripted GUIs or Data System?
Yes you can, you would something like [SCOPE.sC( 'cool_person' ).GetName].

They are locally saved in the top scope and are removed once the top scope ends. If you want to save something on a country or globally use variables.

The scripted guis is just a subset of the data system, the data system is the overarching system for the gui and the localization. The scripted gui is just a specific subset that handles linking the gui to script effects and triggers.
 
Great!

I think I got it, if I understood correctly, as far as modding is concerned, the Data System that uses C++ stuff is used for modding only the look/design of the UI, whereas Scripted GUIs are used if new buttons need to be added with their own effects, correct?
Sort of, the data system has things that can run specific code effects or evaluate code conditions. The scripted gui is only if you want to run something via the script system.
 
Side question: how moddable are mapmodes? Is it possible to add new mapmodes or modify existing ones?

The basic rules for coloring the map are in code due to performance reasons. But which colors are used in the different coloring modes can be set through defines, and new map modes can be created (or existing ones changed) with new combinations of coloring mode, map icons, names on map, and a bunch of zoom level and shading parameters.
 
Is there a way to limit that check to an area when a pop is added or removed from that area, and at game start? I can't help but worry that if I let it try to check constantly, there would be really bad lag. (As an aside, how does culture converting pops work, exactly? Does the game remove the pop being converted then add a pop with the change made back to the location the original pop was, or does the game actually alter the pop?)

Anyway, I believe what I'll want to do is do two iterations, one to add up the number of non-slave pops in a city, and the other to add up the number of pops that are not slaves and are not the country's primary culture, assigning both of these values their own variables. Then I'll need to divide the latter variable by the former, check if that value is greater than or equal to 0.34, then apply the modifier if it is.

Does that sound about right? Or is there some easier way of doing this that I'm missing?
No there is no hook in like an on action for when pops are removed, would be way too frequent. Checking pop percentages every day will give you bad performance yes.
Pretty sure it just changes the culture of the pop, does not make a new one and kill the old one.

That check would not need two iterations, you can just iterate over all pops once and do an if check inside, would likely be better performance as you are not building the list twice just once.
I am not sure if there is some smart game level trigger to check this stuff already, there might be.
 
@blackninja9939

I have seen bits of the new GUI editor in the stream today, which looks very promising.

But I'm wondering, can Scripted GUI be referenced in that in-game GUI editor? Also what are the possible scopes for Scripted GUI? In HOI4 it's selected country and selected state.

I see in your example that there is a character scope. Are there other scopes for Scripted GUIs?
Don't think they are referenceable in the gui editor, good thing for me to look into.

Any scope can be used in the gui editor, as long as you can get it out of wherever you are in the data system when you add the button or there is a global promote to get what you need.