alpaca
04-06-2007, 22:55
Note that this is only about editing campaign_script.txt (and show_me scripts)
I had a novel idea:
We seriously need a scripting tutorial. I think it's pretty hard to learn it by yourself, but at the same time that's what you desperately have to do if you want to become good.
So, I will start working on a basic tutorial, which I will edit into this very post, but in addition to that, the script-o-rama will contain a few other sections (in following posts):
- Scripting examples (how-tos mainly, and tutorials about special script features)
- Scripting tasks (these are supposed to challenge would-be scripters to think a bit first, and will have a proposed solution attached)
- Scripting concepts (here, I'll talk about some tricks and concepts I often use that are not exactly trivial, but can be very useful - these will be more like articles really)
For all of that, I'd like to ask for the help of the community.
Anybody who feels like he'd like to contribute a bit can write an example, task or concept article - and over time, I hope that the script-o-rama will become a knowledge-base in which all different experience levels of scripters are well-served.
You can also ask questions here or pose a scripting challenge which people can try to solve - I'll do that from time to time, as I think it's a good way of finding a good solution to have thoughts from different people on the topic.
At long last, this will go to both the .org and the TWC which will give it hopefully maximum exposure.
Please comment on what you think about this idea :whip:
--------------------------------------------------------------------
Contents
I The Tutorial
II How-Tos
II.1 Spawning characters and armies
II.2 Scripting for difficulty settings and player/ai only
III Articles
III.1 Logging for Scripters
III.2 Available UI Elements
III.3 Things to look out for
IV Examples
III.1 A good four turns per year script
III.2 A take on a re-emergent factions script
--------------------------------------------------------------------
Section I: The Tutorial
Prerequisites
To follow this tutorial, you'll need:
- Either the files released by CA(here (https://forums.totalwar.org/vb/showthread.php?t=73971)) or the files export_descr_character_traits.txt, export_descr_ancillaries.txt, text/export_VnVs.txt and text/export_ancillaries.txt unpacked from the packs - for the latter two you need to convert them from the unpacked .strings.bin files using my .strings.bin converter (https://forums.totalwar.org/vb/showthread.php?t=75229)
- A working mod folder, or a config using file_first. See this tutorial (https://forums.totalwar.org/vb/showthread.php?t=76289) for instructions
- CA's docudemon files (https://forums.totalwar.org/vb/showthread.php?t=83119)
It will also help to read these tutorials and articles:
- My traits tutorial (https://forums.totalwar.org/vb/showthread.php?t=77176)
- My trigger tutorial (https://forums.totalwar.org/vb/showthread.php?t=77177)
- The "Things to look out for" article
Please note that I will use -tags to signify additional information that you won't necessarily need, but you should refer to if you want to know something specific or didn't understand something. It will look like this:
-
In syntax definitions, I'll use the following symbols:
<x> - required argument
[x] - optional argument
{x,y,z} - set of required arguments (i.e. choose one, the special set {logic_token} means you can use one of the following: {<,<=,==,>=,>,!=} so smaller, smaller or equal, equal, greater or equal, greater, not equal)
Scripting
Writing a tutorial is always a hard and arduous process. It entails a lot of frustration and demands a large time commitment, but on the other hand it's often a very good way to become better at what you're writing about.
So, why am I writing this tutorial? To get better at scripting? Probably not. I'm already somewhat good at it, and most of what will go into this tutorial will probably be daily routine for me.
I'm writing this tutorial to allow new modders an easier access to one of the most difficult, but at the same time most interesting, areas of modding.
The problem I have with writing a scripting tutorial is that it's already pretty difficult to explain the basic concept behind it, but once you have grasped that, thinking out and developing your own scripts won't become easier.
A last word before I get to the actual tutorial: Before attempting to script, you should make yourself comfortable with editing traits or ancillaries, and especially with triggers. These are a bit easier to handle and provide a good start before you continue with scripting.
In this tutorial I want to walk you through the steps that you have to follow to create your first own script, which will give you some money and fire an event once you conquer Constantinople playing the Turks (note that I'm not one to start with a "hello world" equivalent for scripting but will take you right into the action).
1. The campaign script
The campaign script (data/world/maps/campaign/imperial_campaign/campaign_script.txt) is executed once at the start of the campaign and will then continue to run until its end. The state of the campaign script (including counter values and such) is stored in the save-game, in contrast to RTW.
So, what can you do with this file? If you open it in a text editor and have a look at it, you will notice that it's already pretty long in its unmodded state.
It contains a part that handles the Mongol and Timurid invasions (which is linked to descr_events.txt) and a few tutorials for the player.
However, for this tutorial, please copy the file and in the original, delete everything until it looks like this:
script
wait_monitors
end_script
What you see above is an empty script, except for the wait_monitors command which tells the game to wait until all monitors are finished:
2. Monitors
Ah yes, the basic and most important scripting construct. If you want to become a scripter, it's paramount to understand these correctly.
Ok, but what is a monitor? First of all, there are two different types of monitors: monitor_event and monitor_conditions. Of these, monitor_event is far more important and more frequently used, because most conditions have a trigger requirement and therefore can't be used in a monitor_conditions
For our tutorial, we will only use a single monitor_event that will observe our capturing Constantinople:
monitor_event GeneralCaptureSettlement SettlementName Constantinople
and FactionType Turks
add_events
event historic turks_capture_constantinople
date 0 0
end_add_events
console_command add_money turks, 20000
end_monitor
This monitor is fairly straight-forward. It checks on a general capturing a settlement, and if that settlement is Constantinople and the general is Turkish, it will execute the code.
Nothing much fancy happens there, either: We throw a historic event for the player (we'll need to describe that later, mind) and give the Turks 20000 florins in cash.
2.1 monitor_event
The struct for monitor_event looks like this:
monitor_event <Event> [not] <Condition>
[and [not] <Condition2>]
...
end_monitor
<Event> tells the game at which time it should execute the monitor and check whether all <Conditions> are true. Only if all of them return true at the time the monitor is run, the code will actually be executed.
Examples for events are CharacterTurnEnd, FactionTurnStart and ButtonPressed - examples for conditions are I_TurnNumber, CharacterIsLocal and I_InBattle.
Basically, an event is what it says on the box. The game has an event engine that will execute any triggers and monitors watching a specified event when that event occurs. I'm not sure about the order in which they are executed though.
For more info on events and conditions, please refer to my triggers and monitors tutorial (https://forums.totalwar.org/vb/showthread.php?t=77177)
2.2 monitor_conditions
The monitor_conditions command is in a lot of ways similar to monitor_event, with the important difference that instead of having an event that executes the monitor, it uses a meta-event that executes the monitor_conditions every now and then to check if all specified conditions are true.
The struct for monitor_conditions looks like this:
monitor_conditions [not] <Condition>
[and [not] <Condition2>]
...
end_monitor
The syntax is basically the same as above, without the triggering event.
This command has the advantage that it gives you a way to "instantly" react on things, with the drawback that the game will slow down considerably if you use a lot of condition monitors.
The other important downside for monitor_conditions is that since it doesn't have an event that can export certain information, you can only use so-called independant conditions (usually signified by an I_ prefixed to the condition, but it works with all conditions that don't have a trigger requirement).
3. Counters
Another important scripting aspect are the so-called counters. If you did a bit of programming in the past, these are somewhat similar to variables, but not quite.
The main use of counters is to store values, however it's often difficult retrieve these values again. To do that sometimes requires a lot of creativity and unorthodox thinking (I'll write a few how-tos later to show some of these principles and get you started) so a lot of counters are only used for boolean (i.e. yes/no) operations.
To use counters, you will first have to declare them. The syntax for this is:
declare_counter <name>
This will initialize your counter and set it to 0
You can then interact with it via the set_counter and inc_counter commands and the I_CompareCounter condition.
set_counter:
This sets your counter to a fixed value. The syntax is set_counter <name> <value>
inc_counter:
This command is sometimes more useful. It doesn't set your counter but changes it's value by adding a number to it. The syntax is inc_counter <name> <value> where value can be negative to decrease the counter
I_CompareCounter:
This condition takes the counter name, a logic token and a comparison value as arguments (I_CompareCounter <name> {<, <=, ==, >=, >, !=} <value>). Note that <value> can only be a constant value (i.e. a number you enter through your keyboard) and not another counter or condition
In our example, I want to modify the above monitor to give the Turks less money when they capture the city for the second time and no money at all when they lost and recaptured it twice (but still fire the message).
Consider this code:
declare_counter turks_capture_constantinople_counter
monitor_event GeneralCaptureSettlement SettlementName Constantinople
and FactionType Turks
add_events
event historic turks_capture_constantinople
date 0 0
end_add_events
if I_CompareCounter turks_capture_constantinople_counter == 0
console_command add_money turks, 20000
end_if
if I_CompareCounter turks_capture_constantinople_counter == 1
console_command add_money turks, 10000
end_if
inc_counter turks_capture_constantinople_counter 1
end_monitor
4. Describing the Event
Well this doesn't actually have anything to do with scripting, but we need to do it to finish our tutorial. To add the turks_capture_constantinople event to the game, we need to give it a title and body in data/text/historic_events.txt:
{TURKS_CAPTURE_CONSTANTINOPLE_TITLE}The Turks capture Constantinople
{TURKS_CAPTURE_CONSTANTINOPLE_BODY}An incredible event has occured! The Turks captured the unconquerable, glorious city of Constantinople and now have a foothold in Europe. The whole occident trembles in fear before the mighty new conquerors who were able to take one of the greatest cities of Christendom apparently effortlessly.
If you also want to have a custom picture for the event, you'll have to create data/ui/southern_european/eventpics/turks_capture_constantinople.tga and respectively for all cultures (I used the Jihad successful picture).
Now, enjoy your new historic event :2thumbsup:
https://img211.imageshack.us/img211/8200/turksconstantinopleng5.th.jpg (https://img211.imageshack.us/my.php?image=turksconstantinopleng5.jpg)
--------------------------------------------------------------------
Section II: How-Tos
Spawning characters and armies
This is one of the "daily bread" tasks for a lot of scripters and it's actually fairly easy to do.
spawn_character
To create a character by script (that is, an agent), the command you're looking for is the spawn_character command. It is described in the docudemon as:
Identifier: spawn_character
Parameters: faction, character as in character description in historical battle
Description: create an army at a particular location
So the syntax is spawn_character <faction> <character description>
The character description is in fact the same as the one used in descr_strat, so you should be comfortable with it. Just select the right tile, name and character type and you're set up fine.
A working example is
spawn_character england William, diplomat, age 18, x 2 y 15
Actually pretty much any tile is valid to spawn a character. You can spawn diplomats at sea or on woods for example, which will render them immobile and prone to die in storms :laugh4:
The only caveat to this command is that you can't give your spawned characters traits directly but you can use the console_command give_trait afterwards because, well, you know the name you entered in the character description.
spawn_army
This command is somewhat similar to the above but instead of creating agents it will create captains or named characters with whole armies attached to them.
In the docudemon it reads like this:
Identifier: spawn_army ... end
Parameters: faction, character and units as in army description in historical battle (character description for general and unit descriptions for remainder of the army)
Description: create an army at a particular location
Be careful with the syntax there, as the example is very outdated. The character description is almost the same as in descr_strat (except for the omitted army keyword), so this example would work:
spawn_army
faction slave
character Primus Branka, named character, age 18, x 73, y 28
traits GoodCommander 1
unit NE Catapult exp 1 armour 0 weapon_lvl 0
end
So the syntax is:
spawn_army
faction <faction>
character <character description>
[traits]
unit <unit description>
...
end
This command is commonly used to spawn rebel armies and such, but it can serve as a way to create generals for a faction (they don't go into the family tree).
I hope you like this little how-to and have fun spawning characters and armies.
Scripting for different difficulty settings and player/ai only scripting
by tornnight
Difficulty Script
declare_counter difficulty
declare_counter difficulty_easy
declare_counter difficulty_medium
declare_counter difficulty_hard
declare_counter difficulty_very_hard
declare_counter difficulty_set
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = easy
set_event_counter difficulty_easy 1
set_event_counter difficulty_medium 0
set_event_counter difficulty_hard 0
set_event_counter difficulty_very_hard 0
set_counter difficulty 1
set_counter difficulty_set 1
log always Campaign Difficulty: easy
terminate_monitor
end_monitor
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = medium
set_event_counter difficulty_easy 0
set_event_counter difficulty_medium 1
set_event_counter difficulty_hard 0
set_event_counter difficulty_very_hard 0
set_counter difficulty 2
set_counter difficulty_set 1
log always Campaign Difficulty: medium
terminate_monitor
end_monitor
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = hard
set_event_counter difficulty_easy 0
set_event_counter difficulty_medium 0
set_event_counter difficulty_hard 1
set_event_counter difficulty_very_hard 0
set_counter difficulty 3
set_counter difficulty_set 1
log always Campaign Difficulty: hard
terminate_monitor
end_monitor
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = very_hard
set_event_counter difficulty_easy 0
set_event_counter difficulty_medium 0
set_event_counter difficulty_hard 0
set_event_counter difficulty_very_hard 1
set_counter difficulty 4
set_counter difficulty_set 1
log always Campaign Difficulty: very_hard
terminate_monitor
end_monitor
AI/Player Script
declare_counter local_england
declare_counter ai_england
monitor_event FactionTurnStart FactionIsLocal
and FactionType england
set_event_counter local_england 1
set_event_counter ai_england 0
if not I_HotseatEnabled
terminate_monitor
end_if
end_monitor
monitor_event FactionTurnStart not FactionIsLocal
and FactionType england
set_event_counter local_england 0
set_event_counter ai_england 1
if not I_HotseatEnabled
terminate_monitor
end_if
end_monitor
You can also add building capabilities specific to a campaign difficulty level and depending on if it's for the AI or player.
income_bonus bonus 200 requires factions { england, } and event_counter local_england 1 and event_counter difficulty_easy 1
income_bonus bonus 400 requires factions { england, } and event_counter ai_england 1 and event_counter difficulty_easy 1
You can now use the difficulty counter for events that occur during the Local Faction and AI Factions. The CampaignDifficulty condition only works correctly for the Local Faction. It returns medium for AI faction events.
I_CompareCounter difficulty >= 3
Section III: Articles
Logging for Scripters
This is quite a short article, but nonetheless very important.
I hope you are familiar with the game log file, as it can give you much valuable information about what happens in the game.
For our needs however, the whole trace information is a bit too much, we don't want to know which files are missing when we're scripting, so the right setting for us is:
[log]
to=logs/system.log.txt
level=*script* trace
This will give us information such as when a certain trigger fired (with the trigger name logged) and logs the execution of script commands, but won't discard any crucial error message from other parts of the game, either. So it's a very useful thing for scripting and I'd suggest to use it to everyone who wants to improve their productivity instead of wondering whether a certain trigger works.
Available UI Elements
This is a small thing few people besides R:TW veterans know about, but which is very useful. Consider for example the ButtonPressed event/condition combination. It needs the name of a UI element as a parameter, but except for looking through the .exe or sussing it out the advice file there's only one way to get these:
https://forums.totalwar.org/vb/showpost.php?p=929325&postcount=5
The above are for R:TW, but most of the stuff hasn't changed. Just try out good candidates and if you're lucky you'll find the one you're looking for.
An example for the usage of these files: You want to trigger a monitor on the player's pressing the construction button. The right header for that is:
monitor_event ButtonPressed ButtonPressed construction_button
Please note that you have to use ButtonPressed twice, once for the event and once for the condition.
Things to look out for
Scripting in general
When you change something in your script, it won't work with saved games. You have to start a new campaign.
Commands
terminate_battle appears to crash the script engine. After using it, no monitors worked for me.
Events
The loading order for the TurnStart and TurnEnd events is: CharacterTurn[Start/End], SettlementTurn[Start/End], FactionTurn[Start/End]
Take care when using BuildingCompleted - after triggering, it disables the create_building console command for some time. Introducing a wait within the monitor works.
Conditions
MissionSucceeded doesn't seem to work properly. That is, it appears only to trigger on slightly_successful. A possible workaround is to use
MissionSuccessLevel > not_successful
Section IV: Examples
A good four turns per year script
I made a very neat version for a 4tpy script that utilizes the power behind setting timescale to 0.25 in descr_strat (so years will take 4 turns). Here's the code:
declare_counter season_counter
monitor_event FactionTurnStart FactionIsLocal
inc_counter season_counter 1
; 0 equals winter
if I_CompareCounter season_counter == 0
console_command season winter
end_if
if I_CompareCounter season_counter > 0
console_command season summer
end_if
; Reset the counter on the autumn turn so that it'll be 0 next turn
if I_CompareCounter season_counter == 3
set_counter season_counter -1
end_if
end_monitor
What this does is basically having a counter that goes in a cycle of four different states (0,1,2,3) where actually the states for 1,2 and 3 are the same. So it'll tell the game it has winter only on every fourth turn and summer on all other turns. Characters will only age on the transition from the winter to the next summer turn, so they'll age at the same rate as the years go by.
This script is also very easily adapted to an even smaller timescale (1/n where n is a natural number, e.g. 1/12=0.083333) by simply increasing the requirement of the last if statement to n-1:
if I_CompareCounter season_counter == 11
set_counter season_counter -1
end_if
If you use smaller timescales be careful with events because add_events with a date of 0 won't work anymore (discovered by wilddog, I can confirm it)
A take on a re-emergent factions script
by Red Spot
first of all it might be interesting to know there is only 1 skill-level at wich I play this game; Very Hard
side-effect is that some factions go bankrupt in a matter of turns on this setting, so I've included a sizeable cheat for the AI factions wich makes having a good econ. more or less a player-feature (AI does not know how nor is able to compete with the player in the area of having a decent econ.)
it is also needed for the respawned factions as I am not willing to add new (dummy) units with no upkeep.
secondly, as just about everyone knows, when you have like 12+ -ish settlements its basicly cat in the bag, you win ...
with respawning factions it means that whoever has slain a faction or at least owns their original settlements has some hard fighting to do every once in while when factions re-appear
I also find it somewhat dissapointing if I'm not at some point encountering certain factions as they have been slain before I actually really come into contact with them, now there will always be the option to face them in battle
the script; the events are dorment, I mean the script by itself does not start anything, it relies on an event in descr_events.
when this event is triggered it will trigger an event in the script that than does a check on destroyed factions the next turn and per destroyed faction calls an event to respawn the faction with a small(-ish) random interval.
at this point though the script keeps itself active, I mean it keeps calling the respawn event every [random x] turns and keeps doing so untill the game is finished, in effect keeping all factions alive though with possible "dead-intervals" :P
on a side note; no use in spawning agents with re-appearing factions as it doesnt really work, if say you have 1 town hall and 1 diplomat and than spawn a diplomat it wont spawn, when you're not at your agent-limit you can spawn one, but as when you dont own any settlements you dont have a town hall you also cant have a diplomat ...
do note; factions need the ability to horde for this to work, wich can be read about in the second post of this topic
;SET FOLLOWING EVENT IN DESCR_EVENTS TO START THE RESPAWNING!!!
;;;event counter faction_respawn_trigger
;;;date 1
;Interval set at 40-80 years -> 80-160 turns.
;faction_respawn_event ==> global spawn starter event
;faction_respawn_england ==> per faction spawn event
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
script
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;the trigger on a seperate event so it can be used in like EDB, kill it once it has done its job!
;;;
monitor_event EventCounter EventCounterType faction_respawn_trigger
and EventCounter >= 1
add_events
event counter faction_respawn_event
date 1
end_add_events
terminate_monitor
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;COPY FOR OTHER FACTIONS!!!
;;;
monitor_event FactionTurnStart FactionType england
and Treasury <= 10000
if I_LocalFaction england
terminate_monitor
end_if
console_command add_money england, 12000
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;check after the player did his/her turn, else we could miss out on yet an other victory.
;;;
monitor_event FactionTurnEnd FactionIsLocal
and I_EventCounter faction_respawn_event >= 1
;;;
;;;COPY FOR OTHER FACTIONS!!!
;;;
if I_FactionLeaderTrait england Factionleader == 0
add_events
event counter faction_respawn_england
date 3 25
end_add_events
end_if
;;;
;;;
;;;reset the counter and call the event again in order to have better control over the spawn-intervals.
;;;
set_event_counter faction_respawn_event 0
add_events
event counter faction_respawn_event
date 38 78
end_add_events
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;COPY FOR OTHER FACTIONS!!!
;;;
monitor_event EventCounter EventCounterType faction_respawn_england
and EventCounter >= 1
spawn_army
faction england
character William, named character, age 30, x 100, y 150, family
traits GoodCommander 4 , Loyal 4 , ContentGeneral 3 , BattleChivalry 3 , GoodRiskyAttacker 2 , Fertile 2
unit NE Bodyguard exp 2 armour 1 weapon_lvl 1
end
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wait_monitors
end_script
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
I had a novel idea:
We seriously need a scripting tutorial. I think it's pretty hard to learn it by yourself, but at the same time that's what you desperately have to do if you want to become good.
So, I will start working on a basic tutorial, which I will edit into this very post, but in addition to that, the script-o-rama will contain a few other sections (in following posts):
- Scripting examples (how-tos mainly, and tutorials about special script features)
- Scripting tasks (these are supposed to challenge would-be scripters to think a bit first, and will have a proposed solution attached)
- Scripting concepts (here, I'll talk about some tricks and concepts I often use that are not exactly trivial, but can be very useful - these will be more like articles really)
For all of that, I'd like to ask for the help of the community.
Anybody who feels like he'd like to contribute a bit can write an example, task or concept article - and over time, I hope that the script-o-rama will become a knowledge-base in which all different experience levels of scripters are well-served.
You can also ask questions here or pose a scripting challenge which people can try to solve - I'll do that from time to time, as I think it's a good way of finding a good solution to have thoughts from different people on the topic.
At long last, this will go to both the .org and the TWC which will give it hopefully maximum exposure.
Please comment on what you think about this idea :whip:
--------------------------------------------------------------------
Contents
I The Tutorial
II How-Tos
II.1 Spawning characters and armies
II.2 Scripting for difficulty settings and player/ai only
III Articles
III.1 Logging for Scripters
III.2 Available UI Elements
III.3 Things to look out for
IV Examples
III.1 A good four turns per year script
III.2 A take on a re-emergent factions script
--------------------------------------------------------------------
Section I: The Tutorial
Prerequisites
To follow this tutorial, you'll need:
- Either the files released by CA(here (https://forums.totalwar.org/vb/showthread.php?t=73971)) or the files export_descr_character_traits.txt, export_descr_ancillaries.txt, text/export_VnVs.txt and text/export_ancillaries.txt unpacked from the packs - for the latter two you need to convert them from the unpacked .strings.bin files using my .strings.bin converter (https://forums.totalwar.org/vb/showthread.php?t=75229)
- A working mod folder, or a config using file_first. See this tutorial (https://forums.totalwar.org/vb/showthread.php?t=76289) for instructions
- CA's docudemon files (https://forums.totalwar.org/vb/showthread.php?t=83119)
It will also help to read these tutorials and articles:
- My traits tutorial (https://forums.totalwar.org/vb/showthread.php?t=77176)
- My trigger tutorial (https://forums.totalwar.org/vb/showthread.php?t=77177)
- The "Things to look out for" article
Please note that I will use -tags to signify additional information that you won't necessarily need, but you should refer to if you want to know something specific or didn't understand something. It will look like this:
-
In syntax definitions, I'll use the following symbols:
<x> - required argument
[x] - optional argument
{x,y,z} - set of required arguments (i.e. choose one, the special set {logic_token} means you can use one of the following: {<,<=,==,>=,>,!=} so smaller, smaller or equal, equal, greater or equal, greater, not equal)
Scripting
Writing a tutorial is always a hard and arduous process. It entails a lot of frustration and demands a large time commitment, but on the other hand it's often a very good way to become better at what you're writing about.
So, why am I writing this tutorial? To get better at scripting? Probably not. I'm already somewhat good at it, and most of what will go into this tutorial will probably be daily routine for me.
I'm writing this tutorial to allow new modders an easier access to one of the most difficult, but at the same time most interesting, areas of modding.
The problem I have with writing a scripting tutorial is that it's already pretty difficult to explain the basic concept behind it, but once you have grasped that, thinking out and developing your own scripts won't become easier.
A last word before I get to the actual tutorial: Before attempting to script, you should make yourself comfortable with editing traits or ancillaries, and especially with triggers. These are a bit easier to handle and provide a good start before you continue with scripting.
In this tutorial I want to walk you through the steps that you have to follow to create your first own script, which will give you some money and fire an event once you conquer Constantinople playing the Turks (note that I'm not one to start with a "hello world" equivalent for scripting but will take you right into the action).
1. The campaign script
The campaign script (data/world/maps/campaign/imperial_campaign/campaign_script.txt) is executed once at the start of the campaign and will then continue to run until its end. The state of the campaign script (including counter values and such) is stored in the save-game, in contrast to RTW.
So, what can you do with this file? If you open it in a text editor and have a look at it, you will notice that it's already pretty long in its unmodded state.
It contains a part that handles the Mongol and Timurid invasions (which is linked to descr_events.txt) and a few tutorials for the player.
However, for this tutorial, please copy the file and in the original, delete everything until it looks like this:
script
wait_monitors
end_script
What you see above is an empty script, except for the wait_monitors command which tells the game to wait until all monitors are finished:
2. Monitors
Ah yes, the basic and most important scripting construct. If you want to become a scripter, it's paramount to understand these correctly.
Ok, but what is a monitor? First of all, there are two different types of monitors: monitor_event and monitor_conditions. Of these, monitor_event is far more important and more frequently used, because most conditions have a trigger requirement and therefore can't be used in a monitor_conditions
For our tutorial, we will only use a single monitor_event that will observe our capturing Constantinople:
monitor_event GeneralCaptureSettlement SettlementName Constantinople
and FactionType Turks
add_events
event historic turks_capture_constantinople
date 0 0
end_add_events
console_command add_money turks, 20000
end_monitor
This monitor is fairly straight-forward. It checks on a general capturing a settlement, and if that settlement is Constantinople and the general is Turkish, it will execute the code.
Nothing much fancy happens there, either: We throw a historic event for the player (we'll need to describe that later, mind) and give the Turks 20000 florins in cash.
2.1 monitor_event
The struct for monitor_event looks like this:
monitor_event <Event> [not] <Condition>
[and [not] <Condition2>]
...
end_monitor
<Event> tells the game at which time it should execute the monitor and check whether all <Conditions> are true. Only if all of them return true at the time the monitor is run, the code will actually be executed.
Examples for events are CharacterTurnEnd, FactionTurnStart and ButtonPressed - examples for conditions are I_TurnNumber, CharacterIsLocal and I_InBattle.
Basically, an event is what it says on the box. The game has an event engine that will execute any triggers and monitors watching a specified event when that event occurs. I'm not sure about the order in which they are executed though.
For more info on events and conditions, please refer to my triggers and monitors tutorial (https://forums.totalwar.org/vb/showthread.php?t=77177)
2.2 monitor_conditions
The monitor_conditions command is in a lot of ways similar to monitor_event, with the important difference that instead of having an event that executes the monitor, it uses a meta-event that executes the monitor_conditions every now and then to check if all specified conditions are true.
The struct for monitor_conditions looks like this:
monitor_conditions [not] <Condition>
[and [not] <Condition2>]
...
end_monitor
The syntax is basically the same as above, without the triggering event.
This command has the advantage that it gives you a way to "instantly" react on things, with the drawback that the game will slow down considerably if you use a lot of condition monitors.
The other important downside for monitor_conditions is that since it doesn't have an event that can export certain information, you can only use so-called independant conditions (usually signified by an I_ prefixed to the condition, but it works with all conditions that don't have a trigger requirement).
3. Counters
Another important scripting aspect are the so-called counters. If you did a bit of programming in the past, these are somewhat similar to variables, but not quite.
The main use of counters is to store values, however it's often difficult retrieve these values again. To do that sometimes requires a lot of creativity and unorthodox thinking (I'll write a few how-tos later to show some of these principles and get you started) so a lot of counters are only used for boolean (i.e. yes/no) operations.
To use counters, you will first have to declare them. The syntax for this is:
declare_counter <name>
This will initialize your counter and set it to 0
You can then interact with it via the set_counter and inc_counter commands and the I_CompareCounter condition.
set_counter:
This sets your counter to a fixed value. The syntax is set_counter <name> <value>
inc_counter:
This command is sometimes more useful. It doesn't set your counter but changes it's value by adding a number to it. The syntax is inc_counter <name> <value> where value can be negative to decrease the counter
I_CompareCounter:
This condition takes the counter name, a logic token and a comparison value as arguments (I_CompareCounter <name> {<, <=, ==, >=, >, !=} <value>). Note that <value> can only be a constant value (i.e. a number you enter through your keyboard) and not another counter or condition
In our example, I want to modify the above monitor to give the Turks less money when they capture the city for the second time and no money at all when they lost and recaptured it twice (but still fire the message).
Consider this code:
declare_counter turks_capture_constantinople_counter
monitor_event GeneralCaptureSettlement SettlementName Constantinople
and FactionType Turks
add_events
event historic turks_capture_constantinople
date 0 0
end_add_events
if I_CompareCounter turks_capture_constantinople_counter == 0
console_command add_money turks, 20000
end_if
if I_CompareCounter turks_capture_constantinople_counter == 1
console_command add_money turks, 10000
end_if
inc_counter turks_capture_constantinople_counter 1
end_monitor
4. Describing the Event
Well this doesn't actually have anything to do with scripting, but we need to do it to finish our tutorial. To add the turks_capture_constantinople event to the game, we need to give it a title and body in data/text/historic_events.txt:
{TURKS_CAPTURE_CONSTANTINOPLE_TITLE}The Turks capture Constantinople
{TURKS_CAPTURE_CONSTANTINOPLE_BODY}An incredible event has occured! The Turks captured the unconquerable, glorious city of Constantinople and now have a foothold in Europe. The whole occident trembles in fear before the mighty new conquerors who were able to take one of the greatest cities of Christendom apparently effortlessly.
If you also want to have a custom picture for the event, you'll have to create data/ui/southern_european/eventpics/turks_capture_constantinople.tga and respectively for all cultures (I used the Jihad successful picture).
Now, enjoy your new historic event :2thumbsup:
https://img211.imageshack.us/img211/8200/turksconstantinopleng5.th.jpg (https://img211.imageshack.us/my.php?image=turksconstantinopleng5.jpg)
--------------------------------------------------------------------
Section II: How-Tos
Spawning characters and armies
This is one of the "daily bread" tasks for a lot of scripters and it's actually fairly easy to do.
spawn_character
To create a character by script (that is, an agent), the command you're looking for is the spawn_character command. It is described in the docudemon as:
Identifier: spawn_character
Parameters: faction, character as in character description in historical battle
Description: create an army at a particular location
So the syntax is spawn_character <faction> <character description>
The character description is in fact the same as the one used in descr_strat, so you should be comfortable with it. Just select the right tile, name and character type and you're set up fine.
A working example is
spawn_character england William, diplomat, age 18, x 2 y 15
Actually pretty much any tile is valid to spawn a character. You can spawn diplomats at sea or on woods for example, which will render them immobile and prone to die in storms :laugh4:
The only caveat to this command is that you can't give your spawned characters traits directly but you can use the console_command give_trait afterwards because, well, you know the name you entered in the character description.
spawn_army
This command is somewhat similar to the above but instead of creating agents it will create captains or named characters with whole armies attached to them.
In the docudemon it reads like this:
Identifier: spawn_army ... end
Parameters: faction, character and units as in army description in historical battle (character description for general and unit descriptions for remainder of the army)
Description: create an army at a particular location
Be careful with the syntax there, as the example is very outdated. The character description is almost the same as in descr_strat (except for the omitted army keyword), so this example would work:
spawn_army
faction slave
character Primus Branka, named character, age 18, x 73, y 28
traits GoodCommander 1
unit NE Catapult exp 1 armour 0 weapon_lvl 0
end
So the syntax is:
spawn_army
faction <faction>
character <character description>
[traits]
unit <unit description>
...
end
This command is commonly used to spawn rebel armies and such, but it can serve as a way to create generals for a faction (they don't go into the family tree).
I hope you like this little how-to and have fun spawning characters and armies.
Scripting for different difficulty settings and player/ai only scripting
by tornnight
Difficulty Script
declare_counter difficulty
declare_counter difficulty_easy
declare_counter difficulty_medium
declare_counter difficulty_hard
declare_counter difficulty_very_hard
declare_counter difficulty_set
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = easy
set_event_counter difficulty_easy 1
set_event_counter difficulty_medium 0
set_event_counter difficulty_hard 0
set_event_counter difficulty_very_hard 0
set_counter difficulty 1
set_counter difficulty_set 1
log always Campaign Difficulty: easy
terminate_monitor
end_monitor
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = medium
set_event_counter difficulty_easy 0
set_event_counter difficulty_medium 1
set_event_counter difficulty_hard 0
set_event_counter difficulty_very_hard 0
set_counter difficulty 2
set_counter difficulty_set 1
log always Campaign Difficulty: medium
terminate_monitor
end_monitor
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = hard
set_event_counter difficulty_easy 0
set_event_counter difficulty_medium 0
set_event_counter difficulty_hard 1
set_event_counter difficulty_very_hard 0
set_counter difficulty 3
set_counter difficulty_set 1
log always Campaign Difficulty: hard
terminate_monitor
end_monitor
monitor_event FactionTurnStart FactionIsLocal
and I_CompareCounter difficulty_set = 0
and CampaignDifficulty = very_hard
set_event_counter difficulty_easy 0
set_event_counter difficulty_medium 0
set_event_counter difficulty_hard 0
set_event_counter difficulty_very_hard 1
set_counter difficulty 4
set_counter difficulty_set 1
log always Campaign Difficulty: very_hard
terminate_monitor
end_monitor
AI/Player Script
declare_counter local_england
declare_counter ai_england
monitor_event FactionTurnStart FactionIsLocal
and FactionType england
set_event_counter local_england 1
set_event_counter ai_england 0
if not I_HotseatEnabled
terminate_monitor
end_if
end_monitor
monitor_event FactionTurnStart not FactionIsLocal
and FactionType england
set_event_counter local_england 0
set_event_counter ai_england 1
if not I_HotseatEnabled
terminate_monitor
end_if
end_monitor
You can also add building capabilities specific to a campaign difficulty level and depending on if it's for the AI or player.
income_bonus bonus 200 requires factions { england, } and event_counter local_england 1 and event_counter difficulty_easy 1
income_bonus bonus 400 requires factions { england, } and event_counter ai_england 1 and event_counter difficulty_easy 1
You can now use the difficulty counter for events that occur during the Local Faction and AI Factions. The CampaignDifficulty condition only works correctly for the Local Faction. It returns medium for AI faction events.
I_CompareCounter difficulty >= 3
Section III: Articles
Logging for Scripters
This is quite a short article, but nonetheless very important.
I hope you are familiar with the game log file, as it can give you much valuable information about what happens in the game.
For our needs however, the whole trace information is a bit too much, we don't want to know which files are missing when we're scripting, so the right setting for us is:
[log]
to=logs/system.log.txt
level=*script* trace
This will give us information such as when a certain trigger fired (with the trigger name logged) and logs the execution of script commands, but won't discard any crucial error message from other parts of the game, either. So it's a very useful thing for scripting and I'd suggest to use it to everyone who wants to improve their productivity instead of wondering whether a certain trigger works.
Available UI Elements
This is a small thing few people besides R:TW veterans know about, but which is very useful. Consider for example the ButtonPressed event/condition combination. It needs the name of a UI element as a parameter, but except for looking through the .exe or sussing it out the advice file there's only one way to get these:
https://forums.totalwar.org/vb/showpost.php?p=929325&postcount=5
The above are for R:TW, but most of the stuff hasn't changed. Just try out good candidates and if you're lucky you'll find the one you're looking for.
An example for the usage of these files: You want to trigger a monitor on the player's pressing the construction button. The right header for that is:
monitor_event ButtonPressed ButtonPressed construction_button
Please note that you have to use ButtonPressed twice, once for the event and once for the condition.
Things to look out for
Scripting in general
When you change something in your script, it won't work with saved games. You have to start a new campaign.
Commands
terminate_battle appears to crash the script engine. After using it, no monitors worked for me.
Events
The loading order for the TurnStart and TurnEnd events is: CharacterTurn[Start/End], SettlementTurn[Start/End], FactionTurn[Start/End]
Take care when using BuildingCompleted - after triggering, it disables the create_building console command for some time. Introducing a wait within the monitor works.
Conditions
MissionSucceeded doesn't seem to work properly. That is, it appears only to trigger on slightly_successful. A possible workaround is to use
MissionSuccessLevel > not_successful
Section IV: Examples
A good four turns per year script
I made a very neat version for a 4tpy script that utilizes the power behind setting timescale to 0.25 in descr_strat (so years will take 4 turns). Here's the code:
declare_counter season_counter
monitor_event FactionTurnStart FactionIsLocal
inc_counter season_counter 1
; 0 equals winter
if I_CompareCounter season_counter == 0
console_command season winter
end_if
if I_CompareCounter season_counter > 0
console_command season summer
end_if
; Reset the counter on the autumn turn so that it'll be 0 next turn
if I_CompareCounter season_counter == 3
set_counter season_counter -1
end_if
end_monitor
What this does is basically having a counter that goes in a cycle of four different states (0,1,2,3) where actually the states for 1,2 and 3 are the same. So it'll tell the game it has winter only on every fourth turn and summer on all other turns. Characters will only age on the transition from the winter to the next summer turn, so they'll age at the same rate as the years go by.
This script is also very easily adapted to an even smaller timescale (1/n where n is a natural number, e.g. 1/12=0.083333) by simply increasing the requirement of the last if statement to n-1:
if I_CompareCounter season_counter == 11
set_counter season_counter -1
end_if
If you use smaller timescales be careful with events because add_events with a date of 0 won't work anymore (discovered by wilddog, I can confirm it)
A take on a re-emergent factions script
by Red Spot
first of all it might be interesting to know there is only 1 skill-level at wich I play this game; Very Hard
side-effect is that some factions go bankrupt in a matter of turns on this setting, so I've included a sizeable cheat for the AI factions wich makes having a good econ. more or less a player-feature (AI does not know how nor is able to compete with the player in the area of having a decent econ.)
it is also needed for the respawned factions as I am not willing to add new (dummy) units with no upkeep.
secondly, as just about everyone knows, when you have like 12+ -ish settlements its basicly cat in the bag, you win ...
with respawning factions it means that whoever has slain a faction or at least owns their original settlements has some hard fighting to do every once in while when factions re-appear
I also find it somewhat dissapointing if I'm not at some point encountering certain factions as they have been slain before I actually really come into contact with them, now there will always be the option to face them in battle
the script; the events are dorment, I mean the script by itself does not start anything, it relies on an event in descr_events.
when this event is triggered it will trigger an event in the script that than does a check on destroyed factions the next turn and per destroyed faction calls an event to respawn the faction with a small(-ish) random interval.
at this point though the script keeps itself active, I mean it keeps calling the respawn event every [random x] turns and keeps doing so untill the game is finished, in effect keeping all factions alive though with possible "dead-intervals" :P
on a side note; no use in spawning agents with re-appearing factions as it doesnt really work, if say you have 1 town hall and 1 diplomat and than spawn a diplomat it wont spawn, when you're not at your agent-limit you can spawn one, but as when you dont own any settlements you dont have a town hall you also cant have a diplomat ...
do note; factions need the ability to horde for this to work, wich can be read about in the second post of this topic
;SET FOLLOWING EVENT IN DESCR_EVENTS TO START THE RESPAWNING!!!
;;;event counter faction_respawn_trigger
;;;date 1
;Interval set at 40-80 years -> 80-160 turns.
;faction_respawn_event ==> global spawn starter event
;faction_respawn_england ==> per faction spawn event
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
script
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;the trigger on a seperate event so it can be used in like EDB, kill it once it has done its job!
;;;
monitor_event EventCounter EventCounterType faction_respawn_trigger
and EventCounter >= 1
add_events
event counter faction_respawn_event
date 1
end_add_events
terminate_monitor
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;COPY FOR OTHER FACTIONS!!!
;;;
monitor_event FactionTurnStart FactionType england
and Treasury <= 10000
if I_LocalFaction england
terminate_monitor
end_if
console_command add_money england, 12000
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;check after the player did his/her turn, else we could miss out on yet an other victory.
;;;
monitor_event FactionTurnEnd FactionIsLocal
and I_EventCounter faction_respawn_event >= 1
;;;
;;;COPY FOR OTHER FACTIONS!!!
;;;
if I_FactionLeaderTrait england Factionleader == 0
add_events
event counter faction_respawn_england
date 3 25
end_add_events
end_if
;;;
;;;
;;;reset the counter and call the event again in order to have better control over the spawn-intervals.
;;;
set_event_counter faction_respawn_event 0
add_events
event counter faction_respawn_event
date 38 78
end_add_events
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;COPY FOR OTHER FACTIONS!!!
;;;
monitor_event EventCounter EventCounterType faction_respawn_england
and EventCounter >= 1
spawn_army
faction england
character William, named character, age 30, x 100, y 150, family
traits GoodCommander 4 , Loyal 4 , ContentGeneral 3 , BattleChivalry 3 , GoodRiskyAttacker 2 , Fertile 2
unit NE Bodyguard exp 2 armour 1 weapon_lvl 1
end
end_monitor
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
wait_monitors
end_script
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;