• 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.

AndrisN

Private
1 Badges
Feb 16, 2010
23
26
  • March of the Eagles
I will be writing a series of posts on some of the behaviors of the Europa Engine that lies behind HOI1, HOI2 (and its various sequels), Arsenal of Democracy, and Darkest Hour. These posts will cover several topics, including:

1. scripting language that the Europa Engine understands
2. naval composition and fleet management
3. amphibious invasions
4. concepts of land warfare (both offensive and defensive)
5. air fleets and sizes
6. convoying and supplies
7. technology/research
8. and ultimately (my) general philosophy on how a competent AI should work.

Other topics to be covered as needed.

My goal is to share a little bit the understanding that I acquired from modding my own HOI1 mod (CORE v0.88: The End of the Beginning), just recently released. I am sure there are more experienced modders out there, and hopefully this series will lead to them sharing their insights in turn. I have tried to read many of the well-known modders’ posts on the forum, and I am sure some of what I'm saying will be repeat. That said, hopefully there is new content here for everyone, to help improve everyone’s mods.

With that, let's get started.

Fundamental Assumption:

I'm going to start with an assumption that the reader will need to accept, for this series to provide value. I believe it is a reasonable assumption supported by historical research into Paradox's games, which an open-minded reader can accept. However, it is just that: an assumption.

Assumption: Paradox reused a significant portion of their existing Europa Engine created for EU1 and EU2, in creating the Hearts of Iron series. There was certainly some stuff added to account for a different type of game, but HOI developed out of the EU series. In a similar manner, the Hearts of Iron 1 (HOI1) game was significantly reused in sequels running the Europa Engine, namely HOI2, DD, Armageddon, Arsenal of Democracy (AoD), and Darkest Hour (DH). Again, there was stuff added in the later games (I consider these additional 'modules'), but for whatever reason (development time, budget, etc), most people today are playing an HOI 1 game far more than they realize when playing the sequels. It is the graphic interface and the map that has changed/improved most significantly, while the underlying game mechanics and AI are much closer to HOI1 than most people (especially those who never played or modded the original) realize.


Conclusion: To understand some of the AI and modding limitations that these sequels face, requires a look at the original game itself: HOI1 1.06c. Most of these so-called 'hard-coded' limitations were there back then as well. If we can find a work-around in HOI1, we can most likely use that (or a similar) work-around in the later games. I propose that some of today’s so-called 'hard-coded' problems DO HAVE workarounds, through a mix of scripting bulk events (hence my preference for VBA or similar) and an understanding of how the Europa Engine arranges for the AI, existing units and manages new ones. These will be explained more in follow-up posts.

Goal: My goals with this series are to show that:

a) There are valuable insights to be gained from looking at HOI 1 code, in regards to better modding HOI sequels
b) There are a surprising number of commonalities in AI behavior between HOI 1 and the sequels
c) Some features of HOI 1 merit a 2nd look, in reincorporating into the sequels
d) HOI 1, when further developed and “bug-fixed”, can provide a surprisingly challenging game for HP, while using limited computer resources (i.e. fast)


Part 1. Scripting language for the Europa Engine

To start, I will look at some features of the Europa scripting language, that are perhaps unfamiliar to many modders. I will draw on some common coding parallels, using the book that I used to learn VB6, which is "Visual Basic 6: Core Language, Little Black Book" by Steven Holzner. However, the concepts are basic to any modern programming language, even if you aren't familiar with VB6.

At first glance, I would consider the Europa scripting language as limited, despite an enormous increase in the number of ported out variables from the engine. HOI1 had/has approximately 40-50 triggers and 50-60 commands, as well as the logical operators NOT/AND/OR, and crucially the trigger flag/command setflag/command clrflag. The sequels have many more, for example, AoD 1.08 has 80+ triggers and 200+ commands based on the event commands.txt file. And I am sure, newer versions have even more.

However, other than the 3 logical operators (NOT/AND/OR) as well as the flag/setflag/clrflag, everything else is pre-defined in what it can do. Therefore, the other triggers and commands behave IMO like pre-defined variables rather than user-defined variables, and so provide a limited opportunity to develop more advanced scripting. Additionally, it requires the game developers to port out a new variable, if it hasn't been defined already, which can take months or years. I want to emphasize, however, that all this is not meant as criticism, but rather as an observation.

All that said, this is not as difficult as could be, because of the "flag" type. This is another term for Boolean and I think, the only true, user-defined variable in the Europa language. With some tinkering, you can build some surprisingly complex code (with enough lines) with just Booleans and the (3) logic operators. Attached is 2 simple lines of VBA code for a basic counter, increasing from 0 to 1:

intCounter = 0 ' Initialize Variable
intCounter = intCounter + 1 'Arithmetic Operator and Assignment Operator



Equivalent scripting in HOI is the following:

event = {
id = 100001 #Event initialize all values to FALSE
random = no
country = GER

trigger = {
flag = intCounter0
}

name = AI_EVENT
desc = ""
style = 0

date = { day = 1 month = january year = 1936 }
offset = 3
deathdate = { day = 30 month = december year = 1936 }

action_a = {
command = { type = setflag which = intCounter0 }
command = { type = clrflag which = intCounter1 }
command = { type = clrflag which = intCounter2 }
command = { type = clrflag which = intCounter3 }
.
.
.
command = { type = clrflag which = intCounterN-2 }
command = { type = clrflag which = intCounterN-1 }
command = { type = clrflag which = intCounterN }
}
}

event = {
id = 100002 #Event assigning the correct value to TRUE
random = no
country = GER

trigger = {
flag = intCounter0
}

name = AI_EVENT
desc = ""
style = 0

date = { day = 1 month = january year = 1936 }
offset = 3
deathdate = { day = 30 month = december year = 1936 }

action_a = {
command = { type = clrflag which = intCounter0 }
command = { type = setflag which = intCounter1 }
}
}

So 2 lines of code vs. ~50 lines of code, but these are (approximately) the same.

Increasing/decreasing a counter by 1 can be done, but not very exciting. How about by 2, 3, 4, etc? This can be accomplished by understanding that increasing by 2 is really increasing by 1, and then increasing a 2nd time by 1. You break higher numbers into units of 1, have flags for each, and use the logical operators in the trigger portion of the event to determine the order that the counter event chain (because it quickly becomes a chain) gets triggered. Keeping track of the flags in an organized way is the hard part, and it is HIGHLY recommended to develop a global naming structure for flags.

Even here, this is not very exciting on its own. Who wants to build a separate counter event chain EACH time there is a reason to modify the counter's value? The persistent = yes (not shown in the above) is the key here. This is the closest equivalent of a Loop and is a unique command/data structure in the Europa language.

Persistent Command
IMO, the full capabilities of the persistent command haven't been discussed in the documentation that I have been able to find. From what I have seen, it is often used with repetitive resource exchange events, where 1 country has an event every year for the resource exchange and triggers the single persistent event that covers the other side of the exchange for the 2nd country (Also, for example the Japanese-Soviet Non-aggression Treaty uses this event form)

How does the persistent command work?

1. There is an important distinction, whether the persistent event (i.e. persistent = yes) has a start date/offset/deathdate. I believe these events will trigger multiple times (provided other triggers are met) within the date range, but only on reloading the game. I don't have much experience and may be wrong, but using the persistent command this way doesn't really give you good control of its loop characteristic. It will trigger each time on reload, and that’s pretty much it.

2. However, when the persistent event has NO date range (and has to be triggered externally from another event), some interesting possibilities emerge. Used this way, the persistent command works similar to a VBA Do Loop with the condition for executing the loop at the top (i.e. before the Loop body). It is important to understand that if the persistent event has no separate trigger section, it will automatically fire each time the event is externally triggered (just like any externally-triggered event without a persistent line). Our example from above will always execute action_a when any outside event calls 100002, and the persistent = yes line is added.

event = {
id = 100002
random = no
country = GER
persistent = yes

#Triggered by outside event

name = AI_EVENT
desc = ""
style = 0

action_a = {
command = { type = clrflag which = intCounter0 }
command = { type = setflag which = intCounter1 }
}
}

By comparison, having a separate trigger section within a persistent event will stop it from automatically firing, unless the internal trigger is met. In this case, event 100002 will not fire when externally triggered , unless flag = intCounter0 is TRUE.


event = {
id = 100002 #Event assigning the correct value to TRUE
random = no
country = GER
persistent = yes

#Triggered by outside event and additional internal trigger

trigger = {
flag = intCounter0
}

name = AI_EVENT
desc = ""
style = 0

action_a = {
command = { type = clrflag which = intCounter0 }
command = { type = setflag which = intCounter1 }
}
}

So why is this important? You can create an outside event, which TRIGGERS many (possibly 100s) of persistent events. However, only those persistent events FIRE whose internal trigger = TRUE. See below:

event = {
id = 99000
random = no
country = GER

trigger = {
exists = GER
}

name = "Outside Event"
desc = ""
style = 0

date = { day = 30 month = january year = 1936 }

action_a = {
name = "OK"

#This event triggers BOTH the "Counter 1" and "Counter 2" events
#but only 1 will fire (or neither), depending on the internal trigger of the
#persistent event

command = { type = trigger which = 100002 } #Counter 1
command = { type = trigger which = 100003 } #Counter 2
}
}

event = {
id = 100002 #Event assigning the correct value to TRUE
random = no
country = GER
persistent = yes

#Triggered by outside event

trigger = {
flag = intCounter0
}

name = "Counter 1 Event"
desc = ""
style = 0

action_a = {
command = { type = clrflag which = intCounter0 }
command = { type = setflag which = intCounter1 }
}
}

event = {
id = 100003 #Event assigning the correct value to TRUE
random = no
country = GER
persistent = yes

#Triggered by outside event

trigger = {
flag = intCounter1
}

name = "Counter 2 Event"
desc = ""
style = 0

action_a = {
command = { type = clrflag which = intCounter1 }
command = { type = setflag which = intCounter2 }
}
}

You have just created a dynamic counter that can move up or down, similar to the much-more concise intCounter = intCounter + 1 statement from VBA. For example, if you want to create a badboy value for countries (like EU2), the Outside Event is set to persistent and is in turn triggered by different historical events, such as the Reoccupation of the Rhineland, Anschluss, Treaty of Munich, etc.



Some Applications of Dynamic Counters:

intCounter = intCounter + 1 and
intCounter = intCounter – 1

Being able to execute these statements in the Europa Engine, when triggered repeatedly or due to many different conditions, opens up an entirely new dimension to scripting. Dynamic counters are equivalent to sliders, which in-game are currently limited to pre-defined Policy sliders (Hawk vs. Dove, Political Left vs. Political Right).

However, this same concept of sliders/dynamic counters can be applied to AI behavior, and on a much finer scale than the “switch” events currently used. For example, the “recklessness” AI parameter can be redefined as a slider with values 0,1,2, and 3, and is evaluated/"tuned" monthly (or even twice/month) to start to approach a real-time variation of AI aggressiveness, based on changing political/military conditions. Similarly, many of the lines in the ai file are really slider values, and can be tuned based on the value of dynamic counters

Other applications:

Separate badboy counter
Additional Economic/Military/Diplomacy Sliders
Complete Rework of the Intelligence System using Soft-coding only
Comparison of 2 user-defined values (by creating a "flags matrix")
etc.


Case Statements/Select…Case Structure

There is one other important point to consider, when looking at the above relationship between 99000 (Outside Event) and 100002 and 100003 (Persistent Events). Combined this way, 99000 serves as a VBA Select…Case statement. This code structure is much more efficient and user-friendly than having to create many If/Then statements for the equivalent result. Because that is what a standard HOI event really is -> a simple If /Then. (Note: and not an If/Then/Else or much less a Case statement, where you can choose between many options). The trigger section is the If and the command section is the Then.

So in summary, the HOI scripting language is capable of imitating basic arithmetic operations (+1/-1) through turning ON/OFF Boolean operators, it can be used to create Do Loops (using the persistent command without a date range and with an internal trigger), and it can create Case statements (Outside event with multiple trigger commands connected to multiple persistent events each with an internal trigger). These provide the beginnings of a much-more robust, user-defined programming language than would first appear, IMO. Combined with the increasing number of ported-out triggers and commands from the developers, you can create sophisticated event chains to provide a much greater level of human control over AI behavior.

I read somewhere that the max. event id is 2.1 billion (type Long), which means that the Europa Engine can take a LOT of events. I would be surprised, if even the most complex mods have over 500,000 events (~0.03% of the engine's limit). IMO, using VBA (or similar) to bulk create events can address some of the perceived hard-coded issues with the HOI franchise.
 
  • 1Like
Reactions: