• 0

    posted a message on Get trigger name?

    In WC3 there was a function, name of current trigger. It's really useful for debugging purposes, put it in all my debug messages to know what trigger caused the problem. It's much faster than typing out the names, especially when changing trigger names around.

    Posted in: Miscellaneous Development
  • 0

    posted a message on Trigger module: Bitwise and, or missing

    The bit operations are missing from the trigger editor (& | << >> and their assignment counterparts). These tools are necessary to perform operations on bit flag presets.


    Workarounds

    Update:

    I produced a function library to solve this issue: Bit Flag Operators

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Trigger module bug: Bitwise preset assignment broken.

    If you attempt to set a bitwise preset variable, the editor bugs out.

    1. Create a trigger
    2. Create a local variable somePreset, of the type Preset: Animation Flag.
    3. Create a trigger action "Set Variable - Set somePreset"
    4. Change the Value to "Full Match" and click OK.

    Nothing happens. The Value field doesn't update, it remains blank. This bug occurs with all bitwise flag presets.


    Workarounds

    1. Instead of the Value option, you can use the Custom Script option and set it directly to the integer representation, though this makes the preset itself pointless - the purpose of enumerations (presets) is to use non-numeric representations of data.
    2. Set the preset indirectly, by passing the value to a function which returns the value:
    Set Animation Flags
        Options: Function
        Return Type: Animation Flag
        Parameters
            value <Animation Flag>
        Grammar Text: Value
        Custom Script Code:
            #PARAM(value)
    

    The disadvantage to this method is you need a different function for every preset, but when your map runs, it's identical to setting the preset directly because the function call never happens (the custom script instruction replaces the function with the actual value you send it). It also looks the same in the editor, since the grammar text is just the parameter.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Trigger module bug: Deleting line bugs icon

    If you delete something in the Triggers viewpane directly above a folder with an icon (label), the folder's icon resets to the default icon, though the label remains.

    1. Create a trigger
    2. Create a folder
    3. Give the folder a label
    4. Delete the trigger

    Icon resets.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on [Trigger] Multithreading and AI

    Contents:

    1. Multithreading
    2. Action Definitions
    3. Using Multithreading
    4. Example: artificial intelligence for units in a tower defense


      Multithreading

    Computers have a capability called multithreading. It allows your program to do multiple tasks simultaneously, rather than one at a time in sequence. Say you have the following situation:

    • A map initialization trigger that does a massive amount of tasks (set up players, set global variables, create leaderboards, and so on).
    • The order the tasks are done in doesn't matter.
    • After all that stuff is done, you want to do more stuff that uses the results of the previous tasks. Your new tasks might rely on player groups AND variables you set up earlier in two different tasks, for example.

    By taking advantage of multithreading, your system can do tasks in whatever order is fastest for the CPU, rather than the order you originally specified. There's other useful applications of threads too. One example is if you have a bunch of units and there's a separate function controlling the activity of each unit with waits or loops. Situations like this are common in many computer systems, such as the SC2 game itself. One thread can control the UI, another control units, another monitoring network traffic, etc.

    (At a technical level, the reason multithreading can speed things up is as follows. One trigger action might require a lengthy delay for your computer to read from memory, so the CPU can switch to another thread you're doing arithmetic in, and CPU's can often do 40 math operations in the time it takes 1 memory read to complete. So by the time it gets the data back from memory it needed, you've already done all the math operations, halving the total execution time of your tasks. Situations aren't always as perfect as this, but threading in general keeps your CPU utilization closer to its maximum potential. It's also very helpful at simplifying complex tasks like AI, discussed later.)


    Action Definitions

    To use this multithreading capability, you use action definitions. Action definitions are basically just triggers that only have an "Action" section. Instead of having events, you tell this action definition to run from other triggers just like you would with actions "Create Unit" or "Set Variable." If you select the "options" section of a new action definition, you'll see the "Create Thread" option at the bottom. This is what allows your computer to run the action in its own thread, independent of whatever the rest of your map is doing.

    However, sometimes you can't simply set an action's "Create Thread" property to true and reap the benefits. If this action accesses variables that are changed by other actions with "Create Thread" enabled, and you don't synchronize use of the variables, your map can break.

    Say you have two actions 1 and 2, an integer variable i which is set to 0, and the actions look like this:

    Action 1
        Set variable i = i + 1
    
    Action 2
        Set variable i = i + 1
    

    Here's a possible order these actions might run, since they're in separate, parallel threads:

    1. Action 1 reads i and thinks it's 0.
    2. Action 2 reads i and thinks it's 0.
    3. Action 2 calculates 0+1 = 1, and stores this new value to i: 1.
    4. Action 2 finishes.
    5. Action 1 already read i, so still thinks it's 0. It calculates 0+1 = 1, and stores this new value to i: 1.
    6. Action 1 finishes.
    7. i is 1.

    In multithreading, 1 + 1 can equal 1. How to solve this problem? The variable i is obviously something critical to your program - multiple actions are using it. So you need to tell your computer it's critical, and allow one action to use it at a time, locking out other actions from the variable until done. To do this, create a new global boolean variable "Lock i". Then use the "Critical Section" trigger action, like this:

    Action 1
        General - Enter critical section using Lock i
            Actions
                Set variable i = i + 1
    
    Action 2
        General - Enter critical section using Lock i
            Actions
                Set variable i = i + 1
    




    Using Multithreading

    Now, how to use this to optimize your map initialization?

    1. Create three global variables:
      • Initializations Complete <integer>
      • Initializations Total <integer> = 5 (however many Init actions you have below)
      • Init Lock <boolean>.
    2. Create two triggers, but don't do anything with them yet:
      • Init Part 1
      • Init Part 2
    3. Create a function definition for various parts of your initialization, organized however you want. For example, I have these actions in my map:
      • Init Players
      • Init Units
      • Init Leaderboards
      • Init Bases
      • Init Environment
    4. In each action definition, click its Options section, and enable "Create Thread."
    5. At the bottom of each of these action definitions, create a critical section that increases your "Initializations Complete" count by 1, like this:
      General - Enter critical section using Init Lock
         Actions
            Variable - Modify Initializers Complete: + 1
            General - If
               If
                  Initializers Complete == Initializations Total
               Then
                  Trigger - Run Init Part 2 (Check Conditions, Don't Wait until it finishes)
      
    6. Now in the "Init Part 1" trigger:
      • Give it the event "Map Initialization"
      • Add an action for each of your Init actions created above.
    7. In the "Init Part 2" trigger, put anything that relies on your other initializations being completed.

      Now at map initialization, the player's computer will start a thread for each of the Init tasks you wish to complete. Depending on what you need to get done, this can speed up the process significantly.


      Example: Artificial Intelligence

    Multithreading can be used for anything with parallel tasks. This might be useful for units in a tower defense to see if they should attack a nearby tower that's blocking them, or could even be used for coordinating complicated group actions in any map by having multiple layers of threads... one layer for individual units, another for groups, and another to control it on a global scale.

    Here's a great example. Say you want units in a tower defense to figure out if they're being blocked. Basically, the AI for each unit waits until it's given the go-ahead every few seconds, performs some simple calculations, then uses available data to determine what the unit should do. The AI will automatically stop running when the unit dies (you could have them stop under other conditions too). I'm writing the method below off the top of my head from what I remember doing for a wireless network, so hopefully I don't miss any details.

    Create a global variable:

    1. GlobalUnitStep counter stores what step you're on

    Create a new action definition for your AI:

    Unit AI
       Options: Action, Create Thread
       Return Type: (None)
       Parameters
          Unit = No Unit <Unit>
          Destination = No Point <Point>
       Grammar Text: Unit AI(Unit, Destination)
       Hint Text: (None)
       Custom Script Code
       Local Variables
          Local Time Step = 0 <Integer>
          Process Actions = false <Boolean>
          Position = No Point <Point>
          Velocity = 0.0 <Real>
       Actions
          General - Enter critical section using Global Time Step Lock
             Actions
                Variable - Set Local Time Step = Global Time Step
          General - While
             Conditions
                (Unit is alive) == true
                (Distance between Position and Destination) > 3.0
             Actions
                Variable - Set Process Actions = false
                General - While
                   Conditions
                      Process Actions == false
                   Actions
                      General - Enter critical section using Global Time Step Lock
                         Actions
                            General - If
                               If
                                  Global Time Step > Local Time Step
                               Then
                                  Variable - Set Process Actions = true
                                  Variable - Modify Local Time Step: + 1
                      General - If
                         If
                            Process Actions == false
                         Then
                            General - Wait (Random real between 2.0 and 5.0) Game Time seconds
                -------
                ------- Put core AI actions here
                -------
          General - If
             If
                (Unit is alive) == true
             Then
                ------- Run your action that handles units reaching their destination
             Else
                General - Wait 10.0 Game Time seconds
                Unit - Remove Unit from the game
    

    Now create a clock trigger telling your AI's when to proceed.

    1. Create a global countdown timer, with a time set to something like 10 seconds, repeating.
    2. Give your clock trigger an event "timer expires" for your timer
    3. Create a critical section action in the clock trigger that increments GlobalUnitStep + 1

    Now you can do the core part of your AI. You might add things like:

    • Update what the unit is doing (attacking a tower, moving to point, healing nearby units, etc)
    • Update velocity: on average, how much has this unit been moving per second? You can store this as a running average.
    • Actions to check if the unit's velocity is low, and if it's trying to move, tell it to now attack a nearby tower instead.

    Whenever you create your waves of the tower defense, call your AI action for each created unit, pass it the parameter of the unit to control and destination. The threads will keep track of their own units, handling everything automatically.

    Integers like GlobalUnitStep do have an upper bound, which is at least in the 32,000 range, more on some computer systems. Incrementing the integer once every 5 seconds won't get you into problems unless your map runs more than 8 hours though, so you probably won't need to worry about the complexity of wrapping the counter back to 0.

    Hopefully I didn't forget to mention anything. Multithreading can be a somewhat tricky concept, and requires careful thinking to make sure different threads aren't overwriting each other's actions. Basically, if all your threads do mostly unrelated tasks and you use critical sections for the tasks that do overlap (such as accessing a counter), you'll be fine.

    Posted in: Tutorials
  • 0

    posted a message on Trigger module bug: records with arrays

    The editor breaks whenever you move a record containing arrays indexed by a variable.

    Steps to reproduce:

    1. Create a new map
    2. Create a new record, someRecordType
    3. Create an integer array variable for the record, someIntArray. Size of the array doesn't matter.
    4. In the default melee initialization trigger (though this bug happens regardless of what trigger it's in), create two local variables:
      1. i <Integer>
      2. record <someRecordType>
    5. Create a Set Variable action in the trigger. Set record.someIntArray[i] = 0.
    6. Copy-paste the newly created action to the same location. (Or move the position of the action relative to other actions in the trigger.)

    The following error occurs in the editor message window:

    Warning: No matching parameter found for 'index' within Record 'someRecordType'

    The line you moved changes to:

    Set record.someIntArray[Unknown Index] = 0.


    Workaround

    When selecting the array index of the second-level record, instead of using a parameter or variable option, use the custom script option and refer to the variable manually. The disadvantage of this method, like any other use of custom script in the gui, is the potential overlooked bugs and extra time expenditure if you wish to change variable names.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Cannot pass records to functions ?!

    @vjeux: Go

    Well the & before the variable name is a given... it's very strange this functionality is not available. They did say it would be like C after all.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Critical Strike

    Small notes: "math - cycle" is actually modulo addition, and "set life" won't give the attacking unit credit for the kill. The "damage unit X from unit Y" actions from the Warcraft 3 editor appear to have been removed in the SC2 editor, so it's probably difficult at best through triggers... might be something in the data editor though.

    Posted in: Miscellaneous Development
  • 0

    posted a message on How to refer to this editor variable in custom script?

    What I'm trying to do is this:

    Pick each unit in (base[b].type[t].unittype[Spawners][v] units owned by player p)
    	(Do stuff with the picked units)
    

    My enum (preset):

    Player Unit Type
        Values
            ... (other enum entries)
            Spawners
            Power
            ...
    

    However, since it's not possible to use nested records (structs) in most native gui functions, I use script for the "base[b].type[t].unittype[Spawners][v]" section:

    gv_base[lv_b].lv_type[lv_t].lv_unittype[ ??? ][lv_v]
    

    I don't know what to put in the ???. Function parameters are "lp_", global variables "gv_", and so on and so on, but what about enumerations? I've been stumped on this for a while now.

    Posted in: Miscellaneous Development
  • 0

    posted a message on Cannot pass records to functions ?!

    So it's not even possible to pass structs as arguments to functions in galaxy? This is bizzare...

    I wonder if it all ties in to the fact triggers aren't stored in galaxy at all... I've never seen something so strange as a language that "compiles" into XML.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Records basically unusuable in the trigger editor?

    At first I thought it was just several independent issues and bugs, but I've gradually come to realize it might be something much simpler and broader.

    Records (structs) are essentially unusable in the trigger editor in the current state. They can't be used for function parameters, their member data are close to impossible to use in comparison operations (or de facto impossible if the record contains sub-records), and the majority of built-in trigger editor functions appear to be constructed in such a way they cannot accept record member data as arguments (it's impossible to do "Create 1 record.unittype at point", for example).

    I really, truly hope this problem is only a beta issue, and records will be usable in the editor by the release date 9 weeks from now. It looks like until then we'll be forced to use script if we want to use structs.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Trigger module bug: comparison operator & records

    @Thalassicus: Go

    An even bigger problem is I've discovered it's impossible to check the non-boolean values of records that have sub-records, even using the switch-sides workaround discussed above.

    struct1.struct2.someIntegerMember == someIntegerVariable
    
    (or)
    
    someIntegerVariable == struct1.struct2.someIntegerMember
    

    Either way this very simple operation can't be done, because struct2 doesn't match the type of someIntegerVariable, thus cannot be selected and it's impossible to do any comparison operations with struct2's member values.

    The solution to this is simply: do no type checking for records with the comparison operator until compile time. Allow us to pick any member of a records and check it against anything else... if we mess it up, it'll be our fault. Currently, structs are basically unusuable in the editor.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Cannot pass records to functions ?!

    I got all my objects set up, then went to add them to functions and sat there, dumbfound, when I didn't see "- Record" under the type list for parameters. This is the most bizzare missing functionality I've come across. I hope it's something that the team just hasn't gotten around to adding yet...

    I can't create a scripted function argument either, so it's going to be frustrating - to say the least - to pass objects between functions. Either code the whole thing manually, which can result in all sorts of wasted time debugging, or pass structures as their indexes in arrays. And since we have no memory allocation function... this makes the second solution impossible in situations where instantiations of struct objects are created dynamically.

    Posted in: Galaxy Editor Bugs and Feedback
  • 0

    posted a message on Storm the Base!

    Storm the Base !


    Overview

    In Storm the Base, captains and swarms of zerg squads clash, as part of a fast-paced multiplayer zone control. Each team attempts to capture control points held by the other team.

    The viewpoint in the video above is me on the zerg force, and the attacker (Zerahan) is controlling a "Marshal"-class tank captain who tanks with summons. There can be a total of 6 players on each side, and will 6 possible captains. The number of Zerg units are limited only by supply, giving you endless waves of carnage! Zerg gameplay is inspired by the real time strategy game World in Conflict.

    You can see more and play the map by searching for "Storm the Base" on battlenet! The project's page is below.



    Our long-term goal is to make this into a functioning mod system anyone can easily create maps for with just basic terrain and triggering skills.

    Storm the Base is created by Zerahan and myself, Thalassicus. Zerahan's done most of the terrain, while I've been focusing on abilities and triggers. If anyone has an art interest (user interface, skins, models, that sort of thing) it'd be great to have you on the team, just contact me.

    Update: Sun May 30th

    The first public, open beta version has been released to battlenet! You can play by searching for "Storm the Base" at the create or join custom game screens.

    Major changes since the last version include:

    1. Transitioned the primary game mode to Zone Control
    2. The internal structure of the base system has been redesigned from the bottom-up. It now uses a flexible object-oriented approach that can easily be modified to meet the needs of our future maps.
    3. New upgrades can be purchased by both factions. The attackers buy upgrades at their flying warp prism, and defenders buy upgrades at their hatcheries.
    4. An additional captain has been added, the Preceptor:
      • Single-target heal and shield buff
      • Area-effect confuse ability
      • Area-effect damage over time
      • Single-target buff converting damage taken to energy
      • Summonable Psi Orb to scout and electocute enemies

    Update: Friday June 18th

    For anyone interested in how to do day/night cycles, we've uploaded a new map on the project page. This is a new map we'll be using for the first release after beta comes back online. It features closer-spaced control points and desert terrain with a complex day/night cycle lighting set, using careful transitions to simulate realistic times. Most important, the time where units have no shadows is minimized to about 10 seconds out of each 7-minute cycle... something very difficult to accomplish realistically, as anyone who's experimented with day/night lighting has probably discovered.

    You can download and test the Storm the Base - Badlands file to see the lighting cycle in action. To speed up the cycle, just alter the day length in the Initialization trigger.

    Posted in: Project Workplace
  • 0

    posted a message on Critical bug with core SC2 player array in patch

    @xenrathe: Go

    Quote from xenrathe: Go

    @Thalassicus: Go

    My map also has this error (or at least a similar error), but I wasn't using 16 players. I was using 4.

    Strangely enough I traced my error to the fact that my loading screen text had a Y offset of -100. Switch to 0 or greater, and I could load it up again.

    I believe that the error's related to some change in how the game editor load map info.

    Oddly enough, I thought the problem with my map was first the loading screen too. I think you're right, it's an issue with map info in general. I believe I had the loading screen problem like you did... I changed the screen to another one, then back to the original, and the map worked until I encountered the # of players issue.

    Quote from BrotherLaz: Go

    Hmm... does it matter which slots are empty or does it have to be slot 15? I have 6v6 where player 0 is neutral (mostly just used during hero select), 13 14 are shared team assets (buildings etc) and 15 is hostile (unclaimed buildings, zerg).

    The slots don't appear to matter, just must have one slot (any slot) set to None for the map to work.

    Posted in: Galaxy Editor Bugs and Feedback
  • To post a comment, please or register a new account.