Greetings mortals, Ultra was a mammoth 8 months of my life during which I learned a great deal about the advanced working of AoM. I'd like to share a couple of these with you.
This guide assumes an intermediate working knowledge of triggers and builds on my previousfloating building guide.
So this may be a bit of a basic premise but I didn't know about it and the possibilities and huge - so it should change your map making if you don't know it! This allowed me to make a rather complex system in Ultra - the reward system. I'm going to use this as a case study.
So you get rewards for kills, nothing new there - but dig a bit deeper. There are 158 potential rewards. Some rewards are even unique to the hero that you choose. How in Yeeb's name was this done in relatively few triggers?
The answer is strings, and QuestVarception (QVception for short). We use the well known"+(1*trQuestVarGet("QV"))+" code which is normally used to put a QV in a chat without the trailing decimal points.
I'm sure you can all figure out the basic shop setup. Type your reward dialogue (say +47 attack), name your column and then have another trigger when the player chooses it.
This set up does work, but you will need to trigger every single possible reward outcome, for each player.
I have done this in far less than 158 triggers - make it less than 10. My setup itself uses some good forward thinking QVs, I built Ultra to my highest ever standards to make sure I could easily edit things and streamline the whole process. So here's my setup:
STEP 1 - Decide the choices
I sneakily coded all the choices in my own trigger as a string. Let's say the player has reached a skillpoint and now had the choice of two rewards.
Reward 1 - +500 hitpoints
Reward 2 - A bonus to their first god power
I use the following code to write my trigger, and place it at the start of the map:
So I can very easily send the player their reward choices with aSend chat with QV string to player effect I rocked up.
This is where it gets clever. I've got to use the shop multiple times, and each time the choices will be different.
I decided for this example to have two new QVs, P1RewardL and P1RewardR. These are the L and R columns that will appear to represent the two choices. When the player gets a new reward I set these two vars to the value of the string I want to use. So:
P1RewardL = 1
P1RewardR = 21
This means I can now change my static cinematic blocks into columns and change their names to my desired strings. I'd put this code in the change name trigger:"+trStringQuestVarGet("R1")+"
I hope you've been following this so far as now we have a problem and I can show you QVception as the solution. Changing the name to the string R1 is all very well, but again I don't want to do this each time for all my rewards. I stored the reward number as a QV earlier (P1RewardL). Now I present to you QVCEPTION! I put the following code in the change name box:
"+trStringQuestVarGet("R"+(1*trQuestVarGet("P1RewardL"))+"")+"
Yes I used the code for removing the decimal points of a QV. Yes you can do this. This, my friends is QVception. I just saved myself putting 158 different change names in there.
We now fire an event to let the game know a shop has been made.
STEP 2 - Create the shop
We deploy an army (old man) to the shop area after changing any old men in area that were there before so they don't pile up.
Our player now has a choice and can click each column to see what the reward does. From here we fire our shop triggers.
Because of the setup there are 2 of these rather than one. One for the L column and one for the R column.
STEP 3 - The player chooses the reward
Let's say we want +500hp. We move the old man to that left column. Our L shop trigger detects an army in that area and turns the columns back to cinematic blocks.
A new QV called 'Reward' is set to equal P1RewardL, and another QV called 'RewardPlayer' is set to 1. I now fire the reward trigger.
STEP 4 - Giving the reward
The reward trigger. One trigger, all 158 rewards for all players. It's full of conditionals checking what the reward var is - and it only gets fired immediately when we want to grant a reward of the player's choosing.
For any reward we can just send the string back and add the word 'chosen' on the end with a sound effect.
To actually grant it I do a bit more quest var magic.
If this were a simple regeneration I could use QVception again. I store each player's regeneration value as a QV and have it looping to damage them every second. We could alter this var by -5 to give an extra 5 regen/second to our hero.
All it is will be QV modifyP"+(1*trQuestVarGet("RewardPlayer"))+"Regen to -5.
Yep, QVception works on regular QVs as well as strings.
So it is a large but simple trigger. I echo the string to the player at the very start and then have conditionals for each reward that can just be a QV modify as above.
For the real HP example I set a couple more vars of delta and value to 0 and 500 respectively. I then fire a modify protounit trigger that will modify all heroes of the reward player QV to the value of 'value'. All stats that can be changed in modify protounit are represented by a number. The delta var being 0 represents hitpoints.
EXTRA - Applications and more advanced uses
So why R21? Well I have 20 heroes in Ultra and when you choose one there's a quest var which stores the ID 1-20. If I want to grant a player a unique reward for their hero I will have this stored in strings R21-R40. I simply set the reward string var to 20 (our starting position) and then add the QV which corresponds to the hero ID a player chose, That way they always get the reward that goes with their character, neat! You can use this for random event text and it makes things a lot easier to change. Just remember if you change anything in your typetest, you will need to reload that trigger in your scenario before the changes take effect.
Nottud will be very keen to point out the"+ means you are 'breaking out' of the text string and then you have your special code followed by +" to re-enter the string. I don't pretend to understand this, but I guess it's a bit like me leaving my phone in a patient after taking out their appendix. (For legal reasons, this did not happen.)
Before going any further here please make sure you have read myfloating building guide, as the next sections all build on this concept! Pun intended.
Previously I've done this with lots of different triggers to make the steps simple. So a build like this one should take a fair few triggers, right? Nope, just one. We also don't have any cinematic blocks for this - so the building can be created whenever needed.
To do this in one trigger we will need to use XS code. It's not as scary as it sounds.
But Yeeb, XS code IS Scary!
Fine, brief tutorial. Seasoned XSers can skip this bit. Make sure you have the XS Code, XS code unit name, and XS code QV unit name triggers (check resources if not).
Let's just do something really simple.
Make a new map, place a single unit and a single trigger with a single effect. We will give that unit a heading of 47. Run the map, this creates a trigtemp file with all the code for that map inside it. Now we look at this file to extract the raw XS code. Findtrigtemp.xs at:
SteamLibrary\steamapps\common\Age of Mythology\trigger2
You should be able to find the heading code with ease.
trUnitSetHeading(47);
This is our XS code. Now we can just copy this in to XS user code unit (because the code is applied to a unit).
There - we just did some XS. It's not that scary! If you look at nottud's command viewer there is a long list of all possible codes.
Back on track with our building order
So we use XS and also QVs. This reduced the chance of error and also lets us QVcept it later.
So here are the effect setup, order is important here:
Set QV next unit name (QVRelic)Army deploy - prisonerArmy change type - relicSet QV next unit name (QVHero)Army deploy - Amanra (or any hero)Immediate garrison relic in heroTransform hero into wadjet spitTransform relic into desired objectTeleport hero to vector
How do we garrison a unit that doesn't exist in another unit that doesn't exist? We use the QV set next unit name before we deploy the unit. This is why we need XS code.
We need the unit immediate garrison code, and tweak this so our two units will successfully garrison and complete the building setup. If you had this on a loop and modified the vector each time, you would get a perpetually building tower of columns. You will need XS user code for QV unit name, give it a go and try to figure it out using what you've learned.
Now something more advanced than just a column stack, let's make something sexy.
We've got a couple of options here.
If making a more simple structure - such as a circular arena of stacked columns such as the one found in the final boss of Project Ultra - we can use the all in one trigger and loop it. In order to get the correct units to garrison we will need to add to certain QVs in that trigger, such as the position of our vectors.
Alternatively for a more complex build like one of the buildings in Yeebaaopolis (Ultra), we can use nottud's ydelegate function to call a build order.
Option one - looping building trigger:
Let's build a circular column arena. First of all we want to get our units in a perfect circle. I did this by having a teleport all units in circle effect fire once, and testing this in the editor. This allowed me to copy the units that will form the 'base' of our arena. Be careful, you need to make sure the unit IDs increment by one per step rather than being spread around the circle randomly. Select units from lowest ID to highest ID in order to do this before setting up your circle.
You will need the following XS code to set a vector to a unit position by QVID.
We need a setup trigger before we go into our loop. I've got 40 units in this example as units, they are selectedin ID order and teleported to a circle. We also set up our QVVector var to the lowest ID number unit. If you started this from scratch and the lowest unit is ID 0, there's no need to do this. Then we fire event and go into our looping trigger!
For conditions, we want it to stop after 40 iterations. So for condition we want QVBuilds to be less than 40. Our first effect will be +1 to QVBuilds.
From here, we work through our checklist as outlined above, with a couple of extra steps to account for changing the position of our vector.
Set QV next unit name (QVRelic)Army deploy - prisonerArmy change type - relicSet QV next unit name (QVHero)Army deploy - Amanra (or any hero)Immediate garrison relic in heroTransform hero into wadjet spitTransform relic into desired objectModify vector +Y Teleport hero to vector Add one to QVBuilds Set vector for next unit
This will work well if we want a ring of columns atop each other. But for fallen columns we need to include heading. We can add 9 to a QVheading and have our armies deploy at this heading or set them to the heading after. If we add 189 to heading (and then -360 if >360) we get the symmetrical alternating column design in the first picture.
Option 2: Calling a build order with delegate
This option is better if you want more complex buildings. Our setup for the actual build is the same as before however we add ydelegate to the top (see nottud's delegate trigger pack).
This means with the ydelegate function we can 'call' upon a blank build order during our trigger and mutate QVID QVRelic to our desired object. We can use this multiple times in the same trigger as long as we use and apply QVHero/QVRelic effects immediately after and before calling another.
With this option you can construct complex buildings in just a single trigger!
Now let's level up and use QVception to call a build order. What extra help does this give us? It means we can call multiple build orders at once and then identify them if we are clever with how we name our new units.
So if we name a new unit as QVRelic"+(1*trQuestVarGet("BuildID"))+" it means we have a database of all our builds saved. We can then destroy and move about later, giving so much ability to customise!
Resources:
nottud's Command Viewernottud's delegate trigger pack
Trigger code for
Send chat with QV string to player (trigger loader required)
______________________________________ Yeebaagooon ______________________________________
____________________ AoMH Seraph ____________________
"You can't trust yeebaagooon to lead a rebelion, He would send everyone to steal mirrors so he could bask in his own brilliance." - Out Reach
"Yeebaagooon had never seen a more handsome man in all his life. He couldn't control himself, He needed to act. Gripping the mirror in his strong arms he kissed the figure before him..." - Out Reach
AoMH: Unfinished Scenarios|Singleplayer: Codename Ripto|Multiplayer: Minigames Z|CSC 7
Ex Seraphs Dictator, Spore Heaven Seraph
This guide assumes an intermediate working knowledge of triggers and builds on my previous
So this may be a bit of a basic premise but I didn't know about it and the possibilities and huge - so it should change your map making if you don't know it! This allowed me to make a rather complex system in Ultra - the reward system. I'm going to use this as a case study.
So you get rewards for kills, nothing new there - but dig a bit deeper. There are 158 potential rewards. Some rewards are even unique to the hero that you choose. How in Yeeb's name was this done in relatively few triggers?
The answer is strings, and QuestVarception (QVception for short). We use the well known
I'm sure you can all figure out the basic shop setup. Type your reward dialogue (say +47 attack), name your column and then have another trigger when the player chooses it.
This set up does work, but you will need to trigger every single possible reward outcome, for each player.
I have done this in far less than 158 triggers - make it less than 10. My setup itself uses some good forward thinking QVs, I built Ultra to my highest ever standards to make sure I could easily edit things and streamline the whole process. So here's my setup:
I sneakily coded all the choices in my own trigger as a string. Let's say the player has reached a skillpoint and now had the choice of two rewards.
I use the following code to write my trigger, and place it at the start of the map:
Why reward 21 not 2? I'll explain this later - let's assume there are 19 other stat rewards between R1 and R21. The R prefix just stands for reward.
<Effect name="Ultra string set">
<Command> trStringQuestVarSet("R1", "+500 hitpoints");< /Command>
<Command> trStringQuestVarSet("R21", "+5 percent critical strike chance");< /Command>
</Effect>
So I can very easily send the player their reward choices with a
This is where it gets clever. I've got to use the shop multiple times, and each time the choices will be different.
I decided for this example to have two new QVs, P1RewardL and P1RewardR. These are the L and R columns that will appear to represent the two choices. When the player gets a new reward I set these two vars to the value of the string I want to use. So:
P1RewardL = 1
P1RewardR = 21
This means I can now change my static cinematic blocks into columns and change their names to my desired strings. I'd put this code in the change name trigger:
I hope you've been following this so far as now we have a problem and I can show you QVception as the solution. Changing the name to the string R1 is all very well, but again I don't want to do this each time for all my rewards. I stored the reward number as a QV earlier (P1RewardL). Now I present to you QVCEPTION! I put the following code in the change name box:
Yes I used the code for removing the decimal points of a QV. Yes you can do this. This, my friends is QVception. I just saved myself putting 158 different change names in there.
We now fire an event to let the game know a shop has been made.
We deploy an army (old man) to the shop area after changing any old men in area that were there before so they don't pile up.
Our player now has a choice and can click each column to see what the reward does. From here we fire our shop triggers.
Because of the setup there are 2 of these rather than one. One for the L column and one for the R column.
Let's say we want +500hp. We move the old man to that left column. Our L shop trigger detects an army in that area and turns the columns back to cinematic blocks.
A new QV called 'Reward' is set to equal P1RewardL, and another QV called 'RewardPlayer' is set to 1. I now fire the reward trigger.
The reward trigger. One trigger, all 158 rewards for all players. It's full of conditionals checking what the reward var is - and it only gets fired immediately when we want to grant a reward of the player's choosing.
For any reward we can just send the string back and add the word 'chosen' on the end with a sound effect.
To actually grant it I do a bit more quest var magic.
If this were a simple regeneration I could use QVception again. I store each player's regeneration value as a QV and have it looping to damage them every second. We could alter this var by -5 to give an extra 5 regen/second to our hero.
All it is will be QV modify
Yep, QVception works on regular QVs as well as strings.
So it is a large but simple trigger. I echo the string to the player at the very start and then have conditionals for each reward that can just be a QV modify as above.
For the real HP example I set a couple more vars of delta and value to 0 and 500 respectively. I then fire a modify protounit trigger that will modify all heroes of the reward player QV to the value of 'value'. All stats that can be changed in modify protounit are represented by a number. The delta var being 0 represents hitpoints.
So why R21? Well I have 20 heroes in Ultra and when you choose one there's a quest var which stores the ID 1-20. If I want to grant a player a unique reward for their hero I will have this stored in strings R21-R40. I simply set the reward string var to 20 (our starting position) and then add the QV which corresponds to the hero ID a player chose, That way they always get the reward that goes with their character, neat! You can use this for random event text and it makes things a lot easier to change. Just remember if you change anything in your typetest, you will need to reload that trigger in your scenario before the changes take effect.
Nottud will be very keen to point out the
Before going any further here please make sure you have read my
Previously I've done this with lots of different triggers to make the steps simple. So a build like this one should take a fair few triggers, right? Nope, just one. We also don't have any cinematic blocks for this - so the building can be created whenever needed.
To do this in one trigger we will need to use XS code. It's not as scary as it sounds.
Fine, brief tutorial. Seasoned XSers can skip this bit. Make sure you have the XS Code, XS code unit name, and XS code QV unit name triggers (check resources if not).
Let's just do something really simple.
Make a new map, place a single unit and a single trigger with a single effect. We will give that unit a heading of 47. Run the map, this creates a trigtemp file with all the code for that map inside it. Now we look at this file to extract the raw XS code. Find
You should be able to find the heading code with ease.
This is our XS code. Now we can just copy this in to XS user code unit (because the code is applied to a unit).
There - we just did some XS. It's not that scary! If you look at nottud's command viewer there is a long list of all possible codes.
So we use XS and also QVs. This reduced the chance of error and also lets us QVcept it later.
So here are the effect setup, order is important here:
How do we garrison a unit that doesn't exist in another unit that doesn't exist? We use the QV set next unit name before we deploy the unit. This is why we need XS code.
We need the unit immediate garrison code, and tweak this so our two units will successfully garrison and complete the building setup. If you had this on a loop and modified the vector each time, you would get a perpetually building tower of columns. You will need XS user code for QV unit name, give it a go and try to figure it out using what you've learned.
Answer
trImmediateUnitGarrison(""+(1*trQuestVarGet("QVHero"))+"");
trUnitTeleport(trVectorQuestVarGetX("V1"),trVectorQuestVarGetY("V1"),trVectorQuestVarGetZ("V1"));
Now something more advanced than just a column stack, let's make something sexy.
We've got a couple of options here.
If making a more simple structure - such as a circular arena of stacked columns such as the one found in the final boss of Project Ultra - we can use the all in one trigger and loop it. In order to get the correct units to garrison we will need to add to certain QVs in that trigger, such as the position of our vectors.
Alternatively for a more complex build like one of the buildings in Yeebaaopolis (Ultra), we can use nottud's ydelegate function to call a build order.
Let's build a circular column arena. First of all we want to get our units in a perfect circle. I did this by having a teleport all units in circle effect fire once, and testing this in the editor. This allowed me to copy the units that will form the 'base' of our arena. Be careful, you need to make sure the unit IDs increment by one per step rather than being spread around the circle randomly. Select units from lowest ID to highest ID in order to do this before setting up your circle.
- Finished build
You will need the following XS code to set a vector to a unit position by QVID.
The first unit you place has an ID of 0, so as all QVs default to 0 if they are not pre-defined, this will set QVVector to the position of the first unit you place on the map.
trVectorQuestVarSet("V1", kbGetBlockPosition(""+(1*trQuestVarGet("QVVector"))+""));
We need a setup trigger before we go into our loop. I've got 40 units in this example as units, they are selected
For conditions, we want it to stop after 40 iterations. So for condition we want QVBuilds to be less than 40. Our first effect will be +1 to QVBuilds.
From here, we work through our checklist as outlined above, with a couple of extra steps to account for changing the position of our vector.
This will work well if we want a ring of columns atop each other. But for fallen columns we need to include heading. We can add 9 to a QVheading and have our armies deploy at this heading or set them to the heading after. If we add 189 to heading (and then -360 if >
This option is better if you want more complex buildings. Our setup for the actual build is the same as before however we add ydelegate to the top (see nottud's delegate trigger pack).
This means with the ydelegate function we can 'call' upon a blank build order during our trigger and mutate QVID QVRelic to our desired object. We can use this multiple times in the same trigger as long as we use and apply QVHero/QVRelic effects immediately after and before calling another.
With this option you can construct complex buildings in just a single trigger!
Now let's level up and use QVception to call a build order. What extra help does this give us? It means we can call multiple build orders at once and then identify them if we are clever with how we name our new units.
So if we name a new unit as QVRelic"+(1*trQuestVarGet("BuildID"))+" it means we have a database of all our builds saved. We can then destroy and move about later, giving so much ability to customise!
Resources:
Trigger code for
Reveal code for all triggers by clicking.
<Effect name="Send Chat With QV String player"> <Param name="PlayerID" dispName="$$22444$$From Player" VarType="long">0</Param> <Param name="Message" dispName="$$20056$$Message" VarType="stringid">default</Param> <Param name="QSName" dispName="String Name" VarType="string">S1</Param> <Param name="Message2" dispName="$$20056$$Message" VarType="stringid">default</Param> <Command>trChatSendToPlayer(0,%PlayerID%,"%Message%"+trStringQuestVarGet("%QSName%")+"%Message2%");</Command> </Effect>
"You can't trust yeebaagooon to lead a rebelion, He would send everyone to steal mirrors so he could bask in his own brilliance." - Out Reach
"Yeebaagooon had never seen a more handsome man in all his life. He couldn't control himself, He needed to act. Gripping the mirror in his strong arms he kissed the figure before him..." - Out Reach
AoMH: Unfinished Scenarios|Singleplayer: Codename Ripto|Multiplayer: Minigames Z|CSC 7
Ex Seraphs Dictator, Spore Heaven Seraph