A regular trigger is saved in the TRIG section within the scenario file in a map. A
virtual trigger is not saved in the TRIG section/in the scenario file, but rather is written in at some place in StarCrafts memory using EUDs and added to the trigger list using EUDs.
Because a virtual trigger is created and can be modified with EUDs, the contents of a virtual trigger are dynamic - conditions and actions can be added, removed, and modified at will as can previous and next trigger pointers - as opposed to regular triggers, which by virtue of being allocated dynamically cannot be altered with EUDs (since you would not know what address they will be located at and cannot derive the player you would use in your EUD actions).
Virtual triggers, in theory, enables operations such as traversing the unit list (or any pointer-based list), jumping from place to place in the trigger list (emulating loops and function calls), ending trigger execution before the list is finished, and making adjustments to regular triggers (e.g. changing the player you're setting deaths for or changing the display text string).
The structure of a trigger as it appears in StarCraft's memory is as follows:
Trigger List Element (2408 bytes)
struct { // Trigger List Element (2408 bytes)
+0x0 u32 prevTrigPtr;
+0x4 u32 nextTrigPtr; // List will terminate if 0
+0x8 conditions[16]; // 20 bytes each
+0x148 actions[64]; // 32 bytes each
+0x948 u32 executionFlags; // 0x1 - Ignore Conditions Once, 0x4 - Preserve Trigger, 0x8 - Disabled
+0x94C u8[28] executesForPlayer;
}
Creating a virtual trigger is just a matter of writing the trigger to some place in memory, then adding it to the trigger list, to do so you first need to scope out where you're going to write it to memory, as triggers are 2408 bytes in memory your options for Blizzard write-supported memory locations are actually very limited.
Candidate Memory Locations
As per
Supported EUD AddressesStart End Attributes
0x005187E8 0x005193A0 backed by code Unit buttonpage map (3000 bytes, up to 1 full trigger)
0x005193A0 0x00519E50 backed by code Unit status card/HUD info (2736 bytes, up to 1 full trigger)
0x0057F0F0 0x005967F0 simple data Game Data Structure (96000 bytes, up to 39 full triggers)
0x005973A0 0x005993A0 backed by code dark_pcx_imageBuffer (8192 bytes, up to 3 full triggers, may change graphics)
0x0059CCA8 0x006283E8 backed by code unitArray (571200 bytes, up to 237 full triggers, not every address in the unit table is editable, units may overwrite trigger data)
0x00640B60 0x00641672 backed by code gameText (2834 bytes, up to 1 full trigger)
0x00657AA0 0x00658AA0 backed by code fog related? does nothing? (4096, up to 1 full trigger)
0x0066EC48 0x0066FBE4 backed by code imagesIscriptID (3996 bytes, up to 1 full trigger)
0x006CAFA0 0x006CD9A0 simple data path something (10752 bytes, up to 4 full triggers, may interfere with pathing)
Of all of these the
Game data structure appears to be the most promising, particularly the final member:std::array<u8, 29604> __unk_1035a; // literally unused?
This appears to be the ExtRegion 0xeb (our resident blizzard dev) mentioned
here. I've copied the address out below:
0x0058F44C 0x005967F0 simple data empty (29604 bytes, up to 12 full triggers)
Once you've selected a memory location to serve as "VirtualTriggerStart" you need to write in the trigger using EUDs, which is a simple, albeit tedious operation (until tools are developed/enhanced).
Values to Write In
0x000 + VirtualTriggerStart = virtPrevTrigPtr
0x004 + VirtualTriggerStart = virtNextTrigPtr
0x008 + VirtualTriggerStart = firstConditionStart (20 bytes each)
0x148 + VirtualTriggerStart = firstActionStart (32 bytes each)
0x948 + VirtualTriggerStart = executionFlags // 0x1 - Ignore Conditions Once, 0x4 - Preserve Trigger, 0x8 - Disabled
0x94C + VirtualTriggerStart = executesForPlayer1 (0 - doesn't execute, 1 - does execute)
0x94D + VirtualTriggerStart = executesForPlayer2 (0 - doesn't execute, 1 - does execute... and so on for the rest of the players)
0x968 + VirtualTriggerStart = SecondVirtualTriggerStart (or whatever you want just after the virtual trigger)
Condition Values
+0x000 - locationNum (1 based)
+0x004 - players
+0x008 - amount
+0x00C - unitIdComparisonCondition // Is 16777216*ConditionId + 65536*Comparison + UnitId
+0x010 - typeIndexFlagsInternalData // Is 65536*InternalData + 256*Flags + TypeIndex (type index being resource type/score type/switch number)
Action Values
+0x000 - locationNum (1 based)
+0x004 - stringNum
+0x008 - wavId
+0x00C - time
+0x010 - group (e.g. player 1)
+0x014 - number (number being the amount/secondGroup/locationDest/unitPropNum/scriptNum)
+0x018 - typeActionType2 // Is 16777216*Type2 + 65536*ActionId + Type (type being unitId/score/resourceType/allianceStatus, type2 being numUnits/switchAction/unitOrder/modifyType)
+0x01C - flagsInternalData // Is 16777216*flags + internalData
Once your trigger is written you need to add the trigger to the trigger list. While it's theoretically possible to add a virtual trigger to the middle or end of the trigger list or to add it to multiple players, it is simplest to add the virtual trigger to the start of a single players trigger list. This is done in three easy steps:
1.) Set the value of the virtual triggers PrevTriggerPointer to that players TriggerListHeader
2.) Move the value currently in a players FirstTriggerPointer into the virtual triggers NextTriggerPointer (use binary countoffs to do a destructive copy from one address to the other, that is, if source is greater than a binary countoff value, subtract from the source and add to the destination)
3.) Set the value of the players FirstTriggerPointer to the VirtualTriggerStart
Now that the theory is all established I'm going to walk through an example of creating a virtual trigger, specifically this trigger:
Player 1
(no conditions)
Modify death counts for Player 1: Set To 1 for Terran Marine.
Preserve Trigger.
First I select the memory location at which my virtual trigger will reside: VirtualTriggerStart = 0x0058F44C
Next I figure out what is needed for set deaths: The group field for Player 1 (+0x010), the unitId field for Terran Marine (+0x018), the numericModifier field for "Set To" (+0x018), and the number field for 1.
Set Deaths Fields
struct { // Action Struct for Set Deaths
+0x10 u32 group;
+0x14 u32 amount;
+0x18 u32 unitActionIdModType; // Is 16777216*ModifyType + 65536*ActionId + UnitId... If UnitId is 0 (terran marine), Set Deaths = 120389632, Add Deaths = 137166848, Subtract Deaths = 153944064
}
Next I calculate the value for each action field
group = 0; // (player 1, but it's 0-based so 0, because this is 0 I don't need to set anything)
amount = 1; // (value of 1)
unitActionIdModType = 120389632; // (terran marine (0), set deaths actionId (45), set to (7))Then the offset for each action field I'll be setting within the virtual trigger, the first action starts at VirtualTriggerStart + 0x148 = 0x0058F44C + 0x148 = 0x0058F594.
0x0058F5A8 is the virtual trigger action amount field that I'll set to 1
0x0058F5AC is the virtual trigger action unitActionIdModType field that I'll set to 120389632To preserve the trigger I don't really need another action, all I need is to set the virtual trigger's execution flags at VirtualTriggerStart + 0x948 to 4
0x0058FD94 is the virtual trigger's execution flags that I'll set to 4Resulting in the following actions to setup the virtual triggers contents:
Set Memory(0x58F5A8, Set To, 1); // Set the virtual trigger set deaths amount ot 1
Set Memory(0x58F5AC, Set To, 120389632); // Set the unitId to Terran Marine, modType to Set To, and the actionId to Set Deaths
Set Memory(0x58FD94, Set To, 4); // Set the execution flags to preserve triggerNext I need to add this trigger to the trigger list, first by setting the virtual triggers virtPrevTrigPtr to the trigger list header 0x000 + VirtualTriggerStart = 0x0058F44C
Set Memory(0x58F44C, Set To, 5350016);Then by moving the value of player 1's FirstTriggerPointer (p1FirstTriggerPointer) (0x0051A288) to virtNextTrigPtr (0x58F450) field with binary countoffs, I also don't want these countoffs to continue firing if some of them were not used, so I guard against that using switch 1.
Text Triggers to copy p1FirstTriggerPointer to virtNextTrigPtr
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 2147483648);
Actions:
Set Memory(0x51A288, Subtract, 2147483648);
Set Memory(0x58F450, Add, 2147483648);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 1073741824);
Actions:
Set Memory(0x51A288, Subtract, 1073741824);
Set Memory(0x58F450, Add, 1073741824);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 536870912);
Actions:
Set Memory(0x51A288, Subtract, 536870912);
Set Memory(0x58F450, Add, 536870912);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 268435456);
Actions:
Set Memory(0x51A288, Subtract, 268435456);
Set Memory(0x58F450, Add, 268435456);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 134217728);
Actions:
Set Memory(0x51A288, Subtract, 134217728);
Set Memory(0x58F450, Add, 134217728);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 67108864);
Actions:
Set Memory(0x51A288, Subtract, 67108864);
Set Memory(0x58F450, Add, 67108864);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 33554432);
Actions:
Set Memory(0x51A288, Subtract, 33554432);
Set Memory(0x58F450, Add, 33554432);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 16777216);
Actions:
Set Memory(0x51A288, Subtract, 16777216);
Set Memory(0x58F450, Add, 16777216);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 8388608);
Actions:
Set Memory(0x51A288, Subtract, 8388608);
Set Memory(0x58F450, Add, 8388608);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 4194304);
Actions:
Set Memory(0x51A288, Subtract, 4194304);
Set Memory(0x58F450, Add, 4194304);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 2097152);
Actions:
Set Memory(0x51A288, Subtract, 2097152);
Set Memory(0x58F450, Add, 2097152);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 1048576);
Actions:
Set Memory(0x51A288, Subtract, 1048576);
Set Memory(0x58F450, Add, 1048576);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 524288);
Actions:
Set Memory(0x51A288, Subtract, 524288);
Set Memory(0x58F450, Add, 524288);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 262144);
Actions:
Set Memory(0x51A288, Subtract, 262144);
Set Memory(0x58F450, Add, 262144);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 131072);
Actions:
Set Memory(0x51A288, Subtract, 131072);
Set Memory(0x58F450, Add, 131072);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 65536);
Actions:
Set Memory(0x51A288, Subtract, 65536);
Set Memory(0x58F450, Add, 65536);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 32768);
Actions:
Set Memory(0x51A288, Subtract, 32768);
Set Memory(0x58F450, Add, 32768);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 16384);
Actions:
Set Memory(0x51A288, Subtract, 16384);
Set Memory(0x58F450, Add, 16384);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 8192);
Actions:
Set Memory(0x51A288, Subtract, 8192);
Set Memory(0x58F450, Add, 8192);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 4096);
Actions:
Set Memory(0x51A288, Subtract, 4096);
Set Memory(0x58F450, Add, 4096);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 2048);
Actions:
Set Memory(0x51A288, Subtract, 2048);
Set Memory(0x58F450, Add, 2048);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 1024);
Actions:
Set Memory(0x51A288, Subtract, 1024);
Set Memory(0x58F450, Add, 1024);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 512);
Actions:
Set Memory(0x51A288, Subtract, 512);
Set Memory(0x58F450, Add, 512);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 256);
Actions:
Set Memory(0x51A288, Subtract, 256);
Set Memory(0x58F450, Add, 256);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 128);
Actions:
Set Memory(0x51A288, Subtract, 128);
Set Memory(0x58F450, Add, 128);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 64);
Actions:
Set Memory(0x51A288, Subtract, 64);
Set Memory(0x58F450, Add, 64);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 32);
Actions:
Set Memory(0x51A288, Subtract, 32);
Set Memory(0x58F450, Add, 32);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 16);
Actions:
Set Memory(0x51A288, Subtract, 16);
Set Memory(0x58F450, Add, 16);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 8);
Actions:
Set Memory(0x51A288, Subtract, 8);
Set Memory(0x58F450, Add, 8);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 4);
Actions:
Set Memory(0x51A288, Subtract, 4);
Set Memory(0x58F450, Add, 4);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 2);
Actions:
Set Memory(0x51A288, Subtract, 2);
Set Memory(0x58F450, Add, 2);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Memory(0x51A288, At least, 1);
Actions:
Set Memory(0x51A288, Subtract, 1);
Set Memory(0x58F450, Add, 1);
}
Trigger("Player 1"){
Conditions:
Switch("Switch1", not set);
Actions:
Set Switch("Switch1", set);
}
And finally I set p1FirstTriggerPointer (0x0051A288) to VirtualTriggerStart (0x0058F44C = 5829708)
Set Memory(0x51A288, Set To, 5829708);Attached is the example map, the virtual trigger setup occurs after elapsed time: 3s as earlier seems to crash (I'm probably missing a pointer or a trigger count or something somewhere that would make it work on the first trigger cycle).
Attachments:
Post has been edited 7 time(s), last time on Jan 28 2018, 9:10 pm by jjf28.
TheNitesWhoSay - Clan Aura -
githubReached the top of StarCraft theory crafting 2:12 AM CST, August 2nd, 2014.