I wrote some wrappers to make generating trigedit (for smcdraft2) compatible triggers from ruby easy.
Trigedit is very handy because you can copy and paste into it, unfortunately it is very tedious to to actually write triggers in it.
These wrappers make it easy for three reasons:
1) There is a consistent syntax using duck typing and smart default arguments for unit, location, and player.
2) You can name any abstraction and reuse it
3) You can use loops to generate triggers that are very repetitive (binary cutoffs, shared vision ai scripts, etc).
I realize Wormer has already created a macro system for generating triggers. It might be really good, but I didn't look into it very much because I was not keen on needing to learn a whole new language just to generate triggers (well if you don't know ruby this will still be a problem for you, but at least it will be a useful language that you learn). It also seemed to still suffer from problem of having a bunch of functions and needing to remember their argument orders.
With these wrappers the arguments for nearly all conditions/actions is straightforward.
To specify the player for a cond/action use array indexing []
To specify a location, use .at
To specify a unit, use a symbol :
The default location if omitted is Anywhere
The default player if omitted is current player
The default unit if omitted is any unit
For example the condition to check if player 3 has at least 5 marines is
:marine[3]>=5
The action to create 1 zergling at "start" for allies is
:zergling["allies"].at("start")+1
The action to remove all units for the current player is
clear
This last example might seem like a special case but it is not. Clear is simply an alias to (set 0) or (self|0). any unit, anywhere, and current player are all just default. Note that when you set unit to 0 it uses remove all, if a location is present it automatically uses remove units at location. You could remove just x units by using self-x. If you wanted to kill the units instead of remove them, just specify kill (for example kill.clear or :zergling.kill-1)
To actually build triggers use trig, or ptrig as a shortcut for including the action preserve trigger. trig accepts all arguments as conditions or actions, as well as a block which is ran. Any argument that is not a condition or action is assumed to be the player to run the trigger for. You can also use "section" to specify default players trigger is for if non are given in trig. "Always" is assumed if no conditions present.
Some examples:
section All
trig(Ore.set 50)
Code
#this will create hyper triggers for player 1
#or you just write hypertriggers which is defined as this already
3.times{
ptrig(1) {
63.times { wait 0 }
}
}
#or you just write hypertriggers which is defined as this already
3.times{
ptrig(1) {
63.times { wait 0 }
}
}
The best thing about this is being able to create your own abstractions and reuse them. For example in my phantom map here is the trigger that handles victory for slayers. (The abstractions are reused for victory for phantoms).
Code
trig(Phantom[All].off?, PhantomChosen.on?) {
playwav 'death3'
fi {
BeenPhantom.on?
display'<04>Have a fun time in <17>hell'
kill.clear
}
sync {
BeenPhantom.off?
display '<04>Good has prevailed, the <07>phantom <04>is no more!'
swait(20)
playwav 'yippie2'
victory
}
}
playwav 'death3'
fi {
BeenPhantom.on?
display'<04>Have a fun time in <17>hell'
kill.clear
}
sync {
BeenPhantom.off?
display '<04>Good has prevailed, the <07>phantom <04>is no more!'
swait(20)
playwav 'yippie2'
victory
}
}
This might not seem that short to handle victory. But alot is actually going on here. Some explaitions:
Phantom and BeenPhantom are death counter variables used to represent if a player is/was the phantom. They were created by simply Phantom = var. var is a function which creates a death counter variable automatically using unused units.
PhantomChosen is a switch which was created using PhantomChosen = switch.
swait is a simulated wait, which must be used to avoid wait blocks if hyper triggers are present (which they are, since in a phantom game any player could be absent so the hyper triggers just run for all players).
fi means if, but that is a reserved word in ruby.
sync, means to start running this trigger next trigger cycle starting with the first player. This is used because say there are 3 players. Player 1 is a slayer, player 2 is the phantom, player 3 is also a slayer. Player 2 is eliminated, he will have a trigger running for him that clears his Phantom switch. This means player 3 will detect the victory first. But victory actually ends the scenario in defeat for all players that do not also run the victory trigger this very trigger cycle, thus if sync was not used, player 1 would see defeat even though its victory would run just 1/12th of a second later (this was actually a bug in my first phantom map).
The triggers that actually get generated look like this:
Code
Trigger("all players"){
Conditions:
Deaths("all players", "Khaydarin Crystal Formation\r", at most, 0);
Actions:
Set Deaths("current player", "Khaydarin Crystal Formation\r", set to, 1);
}
Trigger("all players"){
Conditions:
Always();
Actions:
Preserve Trigger();
Set Deaths("current player", "Cantina\r", subtract, 1);
}
Trigger("all players"){
Conditions:
Deaths("all players", "Unused Terran Bldg type 2\r", at most, 0);
Switch("switch7", set);
Actions:
Play WAV("staredit\\wav\\death3.wav", 0);
Set Deaths("current player", "Mineral Field (Type 2)\r", set to, 1);
Set Switch("switch15", set);
}
Trigger("all players"){
Conditions:
Deaths("current player", "Mineral Field (Type 2)\r", at least, 1);
Deaths("current player", "Unused type 2\r", at least, 1);
Actions:
Display Text Message(always display, "<04>Have a fun time in <17>hell");
Kill Unit("current player", "any unit");
}
Trigger("all players"){
Conditions:
Deaths("current player", "Khaydarin Crystal Formation\r", at least, 1);
Switch("switch15", set);
Actions:
Set Switch("switch16", set);
}
Trigger("all players"){
Conditions:
Switch("switch16", set);
Deaths("current player", "Unused type 2\r", at most, 0);
Deaths("current player", "Cantina\r", at most, 0);
Actions:
Display Text Message(always display, "<04>Good has prevailed, the <07>phantom <04>is no more!");
Set Deaths("current player", "Cantina\r", set to, 21);
}
Trigger("all players"){
Conditions:
Deaths("current player", "Cantina\r", exactly, 1);
Actions:
Play WAV("staredit\\wav\\yippie2.wav", 0);
Victory();
}
Conditions:
Deaths("all players", "Khaydarin Crystal Formation\r", at most, 0);
Actions:
Set Deaths("current player", "Khaydarin Crystal Formation\r", set to, 1);
}
Trigger("all players"){
Conditions:
Always();
Actions:
Preserve Trigger();
Set Deaths("current player", "Cantina\r", subtract, 1);
}
Trigger("all players"){
Conditions:
Deaths("all players", "Unused Terran Bldg type 2\r", at most, 0);
Switch("switch7", set);
Actions:
Play WAV("staredit\\wav\\death3.wav", 0);
Set Deaths("current player", "Mineral Field (Type 2)\r", set to, 1);
Set Switch("switch15", set);
}
Trigger("all players"){
Conditions:
Deaths("current player", "Mineral Field (Type 2)\r", at least, 1);
Deaths("current player", "Unused type 2\r", at least, 1);
Actions:
Display Text Message(always display, "<04>Have a fun time in <17>hell");
Kill Unit("current player", "any unit");
}
Trigger("all players"){
Conditions:
Deaths("current player", "Khaydarin Crystal Formation\r", at least, 1);
Switch("switch15", set);
Actions:
Set Switch("switch16", set);
}
Trigger("all players"){
Conditions:
Switch("switch16", set);
Deaths("current player", "Unused type 2\r", at most, 0);
Deaths("current player", "Cantina\r", at most, 0);
Actions:
Display Text Message(always display, "<04>Good has prevailed, the <07>phantom <04>is no more!");
Set Deaths("current player", "Cantina\r", set to, 21);
}
Trigger("all players"){
Conditions:
Deaths("current player", "Cantina\r", exactly, 1);
Actions:
Play WAV("staredit\\wav\\yippie2.wav", 0);
Victory();
}
Note if you reuse this abstraction the first two triggers would not also be duplicated, they would be reused automatically. This may now seem like alot of bloat in the actual triggers. But it is pretty much the bare minimum if you actually want to do it right. (When I did it by hand, I had less because I used toggleable hyper triggers instead of swait, but I switched to swaits because I didn't want replays to desynch).
This trigger generation system is something that I just made, there may still be bugs and quirks, but the could thing is it is fairly simple, if you want to change the way something behaves you can just do it your self, since it is just a couple of ruby files. I realize this quick tutorial I have given is not really enough to learn how to use them to their fullest, I may try to explain it in more detail later. If you use them, it probably will be confusing at first. But if you map alot, this really does make it easy once you understand it. I would prefer to use them to using menu based triggers for anything, even non repetitive stuff. There are tons of things that could be added or improved, but I make no promises to update. (I wrote these primarily to help with my own mapping)
If you want to try to modify what I've done or add your own abstractions, I'll give a brief description of the files:
units.rb: loads the list of units
bindings.rb: creates a bunch of functions that do the trig edit equivalent of all actions and conditions, for example
displaytextmessage(AlwaysDisplay, "asdf") generates:
Display Text Message(Always Display, "asdf")
wrapper.rb: creates the class like consistent behavior wrappers
abstractions.rb: a bunch of abstractions like swait
phantom.rb: the code to generate my phantom map triggers
autohotkey.ini: you can use with a program called autohotkey to automatically copy out.txt to clipboard, switch to trigedit, paste, compile, check if error and save map.
None.