I'm a programmer (not a pro-gamer) working on a new tool which will drastically expand the capabilities of UMS maps as well as make authoring way easier (no more trigger bullshit). It doesn't use EUD, doesn't require client mods and can't be patched out. The tool is quickly becoming usable and I need someone to help me with testing and feedback. Let me know in this thread if you're interested to help. Experience with SCMDraft 2 and/ or the Blizzard editor as well as (really) basic programming skills are required.
I'm curious but skeptical, especially of claims that you can "drastically expand the capabilities of UMS maps". We at staredit.net have pretty well pushed UMS mapping to its limits already. That said, if you can back up your claims, I'll help with both testing and feedback.
On April 25 2017 13:24 integral wrote: I'm curious but skeptical, especially of claims that you can "drastically expand the capabilities of UMS maps". We at staredit.net have pretty well pushed UMS mapping to its limits already. That said, if you can back up your claims, I'll help with both testing and feedback.
I've created a programming language, IR and a compiler. It takes the source code + a .scx and produces a new .scx. In its essence it's a virtual machine running on triggers. It doesn't offer any new features per se (like EUDs) but you can create much more complicated behaviors and logic than with hand-made triggers as it's a real programming language with variables, expressions, loops, functions, etc.
Here is a simple example I can compile and run right now (image because the forum software messes up the formatting):
And by the way the resources on staredit.net (especially the wiki) have been absolutely essential for creating this, I am so grateful to your community!
On April 25 2017 10:57 biryusky wrote: if you have source code on on a github repository (either private or public) maybe i can take a look. What language did you write this tool with?
Give me your GitHub username and I'll add you to the repo. The compiler is written in modern C++.
As a sidenote, I will open source everything in the coming weeks (days), as soon as it's in a semi-finished state.
Interesting. A lot of that code looks very similar to current triggers. I'm also concerned about how flexible those functions are, I change conditions and actions constantly when I'm making a map. Adding a single condition and action to a particular function seems like it's going to be the same as writing triggers, correct me if I am wrong. A programmatic approach is helpful still, maybe even required for modern maps, so anyone who isn't using something like that will benefit tremendously. I'm currently using a simple string generator, but I suspect with similar results:
One thing I haven't done yet though is write macros to generate the triggers necessary for complex math between two death counts. Multiplication, division, comparison, etc. Currently I'm generating each calculation separately. If you do something like this in your program, I'm sold. This alone is useful enough to make it all worth it.
Another thing I need to do but haven't done yet is figure out how to use death counts as memory for multiple values/variables. Not having any form of memory without death counts sucks and I'm already low on death counts for a lot of maps. I'm not excited trying to think about how to store multiple values in a death count and then extract those values with addition and subtraction. Maybe you're a genius and can do this easily, here's hoping.
The code similarity to current triggers is intentional (to ease the transition for map creators). Don't be mistaken though, this is a full-blown programming language.
I'm very sleepy now (5 am here) so forgive me if I miss some important detail but here's a short explanation of how it works:
- You write your code in a high level language not unlike C++ or JavaScript. It supports local & global variables, arithmetic, complex expressions, function calls with arguments and return value, event callbacks, you name it. This all goes into a handwritten parser which outputs an AST. - From the AST linearized IR code (basically a list of low level instructions) is generated. - The IR language has all the instructions you'd expect to be able to implement the higher level one. Stuff like push, pop, call, ret, set, add, sub, etc + integration with the game like spawning/ killing units, checking locations, setting resources. So a piece of code gets translated to something like:
- This list of instructions is fed into the compiler backend which produces a bunch of triggers and creates the new map file. The actual codegen was the hard part of all this but I have it working properly (and pretty efficiently) now.
- For map makers this means calling the .exe with the map and code file which "combines" them into the final map which you can run in the game.
I feel like I'll be able to release something in a few days or a week tops with the progress I'm making. For the future there is a lot of work that can be done on optimizing the AST and IR once I'm done with all the major features.
To answer your questions specifically:
I'm also concerned about how flexible those functions are, I change conditions and actions constantly when I'm making a map
Everything can be changed instantly, recompilation takes milliseconds. You can go from code to testing in the game in 10 seconds. Also there are preprocessor macros (#define for C/ C++ people) and global variables so you can change stuff in a lot of places at once.
Adding a single condition and action to a particular function seems like it's going to be the same as writing triggers, correct me if I am wrong
Well, kind of. With the exception that you can write most stuff once and then reuse it. You can even have multiple code files so you can make a library of common functions for all your maps. Also I'm considering some kind of meta-programming (template) support in the language to make managing a lot of event handlers even less painful.
Multiplication, division, comparison, etc. Currently I'm generating each calculation separately. If you do something like this in your program, I'm sold
Yeah, you can do stuff like ((x + 15) * foo()). You can go crazy with the expressions until you blow the stack.
Another thing I need to do but haven't done yet is figure out how to use death counts as memory for multiple values/variables.
I use the death counts for player 8 as general purpose memory. It would be possible to pack more than one value into a single "register" to save on memory, though I'm not sure if you'd need that many registers (there are ~220 available) if you're not doing the triggers by hand. The compiler is pretty good at figuring out what to throw out when it's not necessary anymore. We'll have to test the tool with a non-trivial map and see what happens and figure it out from there.
I'll be back tomorrow to talk more. Anyone PM me GitHub username if you want access to the work in progress stuff.
Yeah, you can do stuff like ((x + 15) * foo()). You can go crazy with the expressions until you blow the stack.
Just to be 100% clear, because this is sounding slightly too good to be true, I need the triggers themselves to do the calculating. As in, I need a function that generates the entire subtract/add trees necessary to multiply numbers using triggers. Using complex expressions to generate triggers is nice, but using your program to generate complex expressions *as calculated by triggers* is where the money is.
etc. until 0, then take result (right wall missile trap) and subtract it back into left wall flame trap while multiplying or dividing by another value somewhere else, usually hardcoded since you can't exactly have a condition for every possibility without exceeding trigger limit. This is the sort of thing I haven't coded a way to cleanly generate yet, and until I see evidence (as in trigger output) of this function existing in your program I won't believe you've done it either.
I use the death counts for player 8 as general purpose memory. It would be possible to pack more than one value into a single "register" to save on memory, though I'm not sure if you'd need that many registers (there are ~220 available) if you're not doing the triggers by hand. The compiler is pretty good at figuring out what to throw out when it's not necessary anymore. We'll have to test the tool with a non-trivial map and see what happens and figure it out from there.
I'm using almost all already in most of my maps, to the point where I need to start recycling them sometimes where possible. If I had the ability to use more per register I would be able to do some pretty neat stuff with triggers that no one has done before. As an example, I have plans for a primitive AI that would be able to make attack/retreat decisions on the basis of counting non-arbitrary units per non-arbitrary location. I would use ~40 death counts per location, with calculations to extract those values and compare them, if this weren't prohibitively difficult to code.
Just to be 100% clear, because this is sounding slightly too good to be true, I need the triggers themselves to do the calculating. As in, I need a function that generates the entire subtract/add trees necessary to multiply numbers using triggers.
Exactly what I've done. The expressions are emitted as trigger chains and evaluated at runtime inside the game (as in your example). And you can do much more than just multiply two numbers. Here is the result of compiling one of my examples from the previous posts opened in SCMDraft (you don't actually need a map editor for writing the triggers, the compiler reads/ writes to the map file directly).
I would use ~40 death counts per location, with calculations to extract those values and compare them, if this weren't prohibitively difficult to code.
This would be quite possible to code, especially after I implement arrays which is more or less next on my list.
I'm using almost all already in most of my maps, to the point where I need to start recycling them sometimes where possible.
That's possibly because you use a register (death count) only for one thing. The language I'm making is block scoped and reuses "dead" values, so you can do more with less registers. Also temporaries from expression calculations are reused.
The expressions are emitted as trigger chains and evaluated at runtime inside the game (as in your example).
k you're a genius, I'm sold
And you can do much more than just multiply two numbers.
keep going I'm close
That's possibly because you use a register (death count) only for one thing. The language I'm making is block scoped and reuses "dead" values, so you can do more with less registers. Also temporaries from expression calculations are reused.
Ok, I was skeptical of you, so you can be skeptical of me! Totally fair. Most are in continual use, actually, and I'm using a lot of the heroes and buildings in the map so I just can't use those. I'm more or less at the limit on everything - ccmu, strings, death counts, locations, switches, death counts, even # of triggers that can run without lag. So yeah I'm definitely at the point where I'd be needing to reuse death counts if I want to add things. What I want to do is probably an edge case though, so probably not where you'll want to focus when you're still in development.
This would be quite possible to code, especially after I implement arrays which is more or less next on my list.
One value per unit type per location and I get ~5 locations without having to reuse stuff, so I haven't even tried to mess with this. Plus I run out of triggers doing a count for every unit type. Until recently I've been pretty sure what I wanted to do is just outside the range of what's possible with the current engine, but if I (we?) can figure out a clever way to store multiple values, I can do some really neat things.
I'm Alex, I ... mod games I guess. I've participated in many modding communities and started some myself.
Where did you come from?
I used to make UMS maps with the blizzard editor when I was like 10. I think it may have helped me become a programmer. Also I played a lot of competitive SC2 a few years ago, now I'm back to Brood War.
Who the fuck writes a programming language for triggers?
People who should be working for their job instead. Also it just happens I had to write a bunch of code parsers recently so the knowledge is still fresh in my mind. The IR + codegen part is new to me so don't expect LLVM or GCC levels of sophistication, at least to start.
Why are you doing this?
I love modding and I love writing tools so it was a natural consequence. The actual decision to make this came a few days ago after I got annoyed at the limitations of a UMS map I play a lot.
What help do you need?
Testing, feedback and writing documentation. I'll setup avenues for all of this as soon as I decide to release the initial version in a few days.
I've decided to release my current version so I can work together with all of you to make it better.
The source code is available at https://github.com/AlexanderDzhoganov/langums under the MIT license. Compileable with any modern C++ compiler, doesn't have any external dependencies.
As you'll see there is plenty missing at the moment, but I'm adding stuff all the time. The readme will also get updated with more info and examples as we go. I'm here to answer any questions.
On April 27 2017 07:35 KhaosKreator wrote: This sounds pretty neat. Maybe I'll use this when it's done and start making maps again.
Given how much stuff I was able to add in the latest updates you can pretty much start using it. The language syntax will be kept stable so you just have to update langums.exe to get new features when they're available.
On April 27 2017 08:29 TheFish7 wrote: Wow, really great to see something like this popping up.
I used to know the editor pretty much in and out but with 0 programming knowledge outside of that I probably won't be much help.
If you have experience with StarEdit or SCMDraft triggers then this should be pretty straightforward for you. Also it can be your foray into real programming ^_^
I took a little time and threw together a very very barebones vscode extension for langums, the only feature is syntax highlighting on language constants and keywords, but hopefully it makes testing out the language a little nicer for you early adopters.
On April 27 2017 12:12 integral wrote: Forced player 8 computer and no preplaced units for p8 makes this unusable for a large majority of maps. Just a heads up.
There needs to be at least one player whose death counts aren't messed with by having his units die. I picked player 8 pretty arbitrarily. Which one it is I'll make configurable (I guess using player 11 would be better?). Do you recommend using another player id by default? I'm pretty sure I can get rid of the requirement for having a Computer player at all which I'll do today.
On April 27 2017 14:56 Glenstorm wrote: I took a little time and threw together a very very barebones vscode extension for langums
There needs to be at least one player whose death counts aren't messed with by having his units die. I picked player 8 pretty arbitrarily. Which one it is I'll make configurable (I guess using player 11 would be better?). Do you recommend using another player id by default? I'm pretty sure I can get rid of the requirement for having a Computer player at all which I'll do today.
It's probably your highest priority to allow choices here, because your project is 100% dead in the water unless you give mapmakers options. Default player 8 and even default p8 computer is fine, but forced is not -- and if that player has preplaced units and those units can die, then you either need to do conditional checks with triggers (e.g. if this unit is owned by p8, don't use it) or allow mapmakers to identify which units they want to use for death counts. The first thing I do when I'm making a map is make a list of the death counts I'm using and how they're being used, so this would not be unfamiliar.
A simple input system to let users tell the program: 1. which player/force needs to own the death counts 2. which units not to use for death counts
will solve the problem. This will allow users to introduce errors, yes, but it's worth it. One suggestion I have is to use the wiki list of units that can't be killed at all as default, and then allow users to expand these as necessary. Simpler maps won't need much beyond this, and advanced maps usually come with advanced mapmakers who need to be able to choose which units they're using.
Goliath Turret Tank Turret(Tank Mode) Nuclear Missile Alan Schezar Turret Edmund Duke Turret Edmund Duke Turret Tank Turret (Siege Mode) Scanner Sweep Unused - Cargo Ship Unused - Mercenary Gunship Map Revealer Disruption Web Unused1 Unused2 Uraj Crystal Khalis Crystal Unused Zerg Building1 Unused Zerg Building2 Unused Protoss Building1 Unused Protoss Building2 Khaydarin Crystal Formation Mineral Field (Type 1) Mineral Field (Type 2) Mineral Field (Type 3) Cave Cave-in Cantina Mining Platform Independant Command Center Independant Starport Independant Jump Gate Ruins Kyadarin Crystal Formation Vespene Geyser Zerg Marker Terran Marker Protoss Marker Zerg Beacon Terran Beacon Protoss Beacon Zerg Flag Beacon Terran Flag Beacon Protoss Flag Beacon Dark Swarm Floor Hatch Left Upper Level Door Right Upper Level Door Left Pit Door Right Pit Door Start Location Flag Psi Emitter Data Disc Khaydarin Crystal
I dunno how you coded this, but hopefully it isn't too hard to change.
Also consider that for a number of things I want to run death count calcs for every player on the map, not just player 8. I really need to be able to customize which player (and how many) owns any given trigger, and have death count variables stored for every individual player where necessary. This is pretty much the baseline for functionality. p8 only is pretty specific.
I've pushed a new version with the following changes:
- A Computer player is no longer necessary - The main triggers are emitted for Player 1 by default (can be changed with the --triggers-owner option) - The death counts of Player 8 are still used by default for storage, but can be changed now with the --registers-owner option.
The first thing I do when I'm making a map is make a list of the death counts I'm using and how they're being used
You kind of don't need to do this with LangUMS. The compiler will assign your variables to death counts automatically and more likely than not more efficiently than you can do by hand (at least in theory).
Also consider that for a number of things I want to run death count calcs for every player on the map
You can do this with the deaths() event handler, it allows you to check deaths for whatever player you want. Or you want to check for several players at once with some exclusion mask? I have something for this planned.
A simple input system to let users tell the program: 1. which player/force needs to own the death counts 2. which units not to use for death counts
1 is done in the latest update. I'll consider doing 2 soon.
On April 27 2017 12:12 integral wrote: Forced player 8 computer and no preplaced units for p8 makes this unusable for a large majority of maps. Just a heads up.
There needs to be at least one player whose death counts aren't messed with by having his units die. I picked player 8 pretty arbitrarily. Which one it is I'll make configurable (I guess using player 11 would be better?). Do you recommend using another player id by default? I'm pretty sure I can get rid of the requirement for having a Computer player at all which I'll do today.
On April 27 2017 14:56 Glenstorm wrote: I took a little time and threw together a very very barebones vscode extension for langums
Really cool, already using it! Thanks.
Unused players 9...11 would be obvious choices. Neutral P12 for the most part as well, probably (not quite sure how deaths of units from a player who left the game are counted). You could also add some flexibility by detecting players set to unused owner and/or inactive race and use all of their DCs as memory. Furthermore, there are a ton of unused units, which cannot even be placed in a map without crashing the game. These still have DCs, though, which can be used for any player as nothing can mess with them. DCs for invincible-by-default units, such as resources, powerups or Khaydarin Crystal Formations are also usually safe for grabs (unless there are destroy triggers for them). Only a limited selection of units will be used per player, to begin with, so ideally, I guess, you should add an advanced option to manually have map makers specify memory space themselves. Does your program parse units pre-placed for each player in a map when compiling the triggers in the map file? You could then just automatically assign every unit to memory which is neither pre-placed for a certain player in the map file nor has any create or give triggers for that player ; or, for maximum flexibility, but less safe, just add a debugger warning whenever a unit assigned as memory clashes with a pre-placed unit or create/give trigger.
Sadly I believe players 9 to 11's death counts can't be manipulated and they can't run triggers, otherwise that'd have been the obvious choice, yes.
Neutral P12 for the most part as well, probably (not quite sure how deaths of units from a player who left the game are counted).
I have no idea about this one, someone more experienced than me should pitch in and say if it would be ok to use Player 12's death counts as storage without messing up.
You could also add some flexibility by detecting players set to unused owner and/or inactive race and use all of their DCs as memory.
Great idea, definitely doing this.
Only a limited selection of units will be used per player, to begin with, so ideally, I guess, you should add an advanced option to manually have map makers specify memory space themselves.
I can do this pretty easily. Look forward to it soon.
Does your program parse units pre-placed for each player in a map when compiling the triggers in the map file?
Yes, it can.
You could then just automatically assign every unit to memory which is neither pre-placed for a certain player in the map file, nor has any create or give triggers for that player, or, for maximum flexibility, but less safe, just add a debugger warning whenever a unit assigned as memory clashes with a pre-placed unit or create/give trigger.
You kind of don't need to do this with LangUMS. The compiler will assign your variables to death counts automatically and most likely more efficiently than you can do by hand at least in theory.
People have been making maps for a long time. Even if the compiler you wrote is really fucking cool, and it is, it's actually straight up insulting to think that it knows what I need better than I do. Efficiency is cool, but unless it's able to interface with the decade of work I've already done, it's really not gonna matter if it saves a few triggers here or there. Coming in with a necessarily disruptive approach is gonna put a lot of people off.
I've already got a lot of completed maps. I constantly copy and paste code snippets and modules from other maps I've made. I'm not rewriting my already assigned death counts just so I can use your program to generate some triggers for me. I'm also not gonna rewrite all my already-written modules in your generator just because the compiler wants to assign stuff automatically. Your compiler doesn't know which death counts I'm already using, but I'll tell it if you let me.
Maybe your tool is marketed more toward people who don't really make maps much, or have never really made a map before, or who are making a completely new map. If so, I'll back off a bit. But if you want established mapmakers to use this, it needs to be able to work with the things that people have already done.
People have been making maps for a long time. Even if the compiler you wrote is really fucking cool, and it is, it's actually straight up insulting to think that it knows what I need better than I do.
I understand. I come from a programming background where we're drilled that "the compiler knows better" from day 1, it's like a mantra (and it's mostly true given a decent enough compiler, which mine most likely ain't yet).
Efficiency is cool, but unless it's able to interface with the decade of work I've already done, it's really not gonna matter if it saves a few triggers here or there.
This is an unusual perspective for me. I did start with the assumption that langums maps will be made from scratch. Interfacing with already existing ones is definitely something I had not considered until now but I do realize it's important and I'm open to making it work. Could you give me a detailed explanation on how you imagine the interfacing between langums code and your existing triggers to work? Feel free to take some time and test out some stuff in langums to get a feel for it.
Coming in with a necessarily disruptive approach is gonna put a lot of people off.
I believe it is a small pain for major gains down the road. Do understand though that I care equally for people entering the community as well as experienced members. The tool must be suitable for both and decisions will be made which may not fully fit one side's point of view. Of course the opinion of experienced people weights a whole lot more in my decision process than someone who has never released a map.
I'm not rewriting my already assigned death counts just so I can use your program to generate some triggers for me. I'm also not gonna rewrite all my already-written modules in your generator just because the compiler wants to assign stuff automatically.
This is understandable. Let's see how to make it work.
Your compiler doesn't know which death counts I'm already using, but I'll tell it if you let me.
This will be a part of the "integration" facilities I can put in. I'm imagining something like
global foo = <Player4, TerranMarine>;
can map an already existing death count to a langums variable. Let me finish adding the basic features over the next few days and I'll see how to make this work.
On a sidenote, can anyone point me to a list of AI scripts available. Their names should be 4 chars long.
This is an unusual perspective for me. I did start with the assumption that langums maps will be made from scratch.
That's a pretty huge assumption, glad you acknowledged it.
Interfacing with already existing ones is definitely something I had not considered until now but I do realize it's important and I'm open to making it work. Could you give me a detailed explanation on how you imagine the interfacing between langums code and your existing triggers to work? Feel free to take some time and test out some stuff in langums to get a feel for it.
Basically just let me tell the program what death counts it's allowed to use and what it isn't, and I'll use it like I currently use my own string generator -- copy and paste the output into my master trigger file. I also don't need (or want) it to auto-generate an .scx file, or even be linked to one. I write a lot of my triggers in modules, separately. An option to output as .txt directly from the script would be great for this.
Basically just let me tell the program what death counts it's allowed to use and what it isn't
This is next on my list after I'm done implementing the rest of the conditions & actions that are missing at the moment. I can do you one better though and have the compiler automatically figure out which death counts are unused by analyzing the .scx file. Let me know if that's a thing you'd want.
I'll use it like I currently use my own string generator -- copy and paste the output into my master trigger file
Would that be in scmdraft 2's trigger format? I'm not aware of StarEdit having a text format for triggers but I may be wrong.
Sadly I believe players 9 to 11's death counts can't be manipulated and they can't run triggers, otherwise that'd have been the obvious choice, yes.
Can confirm.
I have no idea about this one, someone more experienced than me should pitch in and say if it would be ok to use Player 12's death counts as storage without messing up.
Not really viable either, for several reasons. Lots of death count uses won't work properly with it. You could try to make it work but it'd be messy. From the quirks and nuances page:
When a player leaves the game, their units are given to Player 12 (Neutral). Any alliances that remaining players had with the departed player will remain intact toward those Player 12 units. Changing a player's alliance status with the departed player will likewise affect that player's alliance with those units.
Deaths for extended players' units (and for Neutral units) can be read in conditions but cannot be altered with the Set Deaths action. They will accurately represent the deaths their units have amassed.
Units cannot be created for Players 9-12. You must instead create the units for a normal player, and give the units to P9-12 as a separate action.
Would that be in scmdraft 2's trigger format? I'm not aware of StarEdit having a text format for triggers but I may be wrong.
Yeah. People who want to use StarEdit (I guess they still exist???) can use the auto-generate feature.
I can do you one better though and have the compiler automatically figure out which death counts are unused by analyzing the .scx file. Let me know if that's a thing you'd want.
lol. It actually isn't, automation be damned, because I'll tell you what I'm gonna want to do: I'm gonna use your program to do some math stuff I've been needing to do for a while, and I'm gonna copy and paste it into my pre-existing maps. Even if it could read ALL the maps and figure out which ones weren't used, it's still better if I can just tell it to use the ones I want it to.
On edit: This isn't a huge dealbreaker or anything. As it is I'd just use notepad to find/replace what your compiler came up with, which is more or less fine. I can also extract the .txt file myself by opening scmdraft2 afterwards.
Analyzing the .scx file for unused death counts will be useful in other scenarios for sure.
The compiler will use these death counters as internal registers and won't touch anything else. Keep in mind depending on how complex your calculations are you may need plenty of those. I'd say about 24 is the bare minimum required to operate give or take depending on how many functions/ events you have. You'll probably want to use the --preserve-triggers option also. Soon there will be an easy way to map langums variables to existing (external) death counters to allow for easy interaction both ways.
On April 28 2017 01:40 frogmelter wrote: Does this have RNG support? [ie. var bar = rand(0, 10) // returns a number from 0 to 10 inclusive at the bottom, exclusive at the top]
It does now. The latest update includes the rnd256() built-in which returns a random value between 0 and 255. Use like:
var foo = rnd256();
For now you can scale it yourself using division if you need a smaller range though it will give you a non-uniform distribution but that's maybe not an issue for your use case.
You could then just automatically assign every unit to memory which is neither pre-placed for a certain player in the map file, nor has any create or give triggers for that player, or, for maximum flexibility, but less safe, just add a debugger warning whenever a unit assigned as memory clashes with a pre-placed unit or create/give trigger.
Another really good idea. I'll do this also.
Thinking about this one step further, for units/buildings that can actually be built, it also need to be checked whether the player is actually 1. allowed (via unit settings) and able (has a production structure or worker via pre-placement or create/give trigger) to. at least in theory, acquire that unit. There are of course lots of ways around either allowing the player to have the unit (players has workers, but only unbuildable ground, blocked building exits, units come at prohibitive cost, unit building is just use as control buttons and the units are removed immediately upon spawn etc.) or having unit deaths actually affect death counters (if a known amount of units die in a controlled enough fashion, death counters can be adjusted for that). So ideally you should also allow players to force assign-death a specific death counter to a specific purpose. Means a lot more handiwork and potential errors by the map maker, but can also squeeze out that bit of extra memory and control...
Maybe your tool is marketed more toward people who don't really make maps much, or have never really made a map before, or who are making a completely new map. If so, I'll back off a bit. But if you want established mapmakers to use this, it needs to be able to work with the things that people have already done.
If the triggers are already written for a map, you certainly don't need any tools for that any more. However, when any one, including you, starts any new map from scratch...
I understand. I come from a programming background where we're drilled that "the compiler knows better" from day 1, it's like a mantra (and it's mostly true given a decent enough compiler, which mine most likely ain't yet).
A good compiler for a sufficiently complex program, certainly. The issue is more that UMS map makers are used to writing all their stuff in assembler, and they don't want to rewrite any code that ain't broken ;D Also, there is the issue that when memory is very limited (not just data memory but also the memory the program itself takes up, in this case meaning there is a trigger limit to a map), assembler might just be the best choice... So to run with the analogy, you should probably include the option for the super-geeks to use some inline assembler...
On April 27 2017 21:57 integral wrote: Maybe your tool is marketed more toward people who don't really make maps much, or have never really made a map before, or who are making a completely new map. If so, I'll back off a bit. But if you want established mapmakers to use this, it needs to be able to work with the things that people have already done.
But established mapmakers make completely new maps all the time?
Having the compiler use available unused memory by default is obviously the way to go. You just have to make sure there are advanced options for people that want to manage that themselves.
I've pushed a new version with a much cleaned-up internal code representation with hopefully way less codegen bugs. If something didn't work as it should before, hopefully it works now.
Just wanna chime in and say it's really really cool what you're doing here. I've never made any detailed maps before, but as a programmer knowing I can make cool advanced maps using code instead of having to learn about the trigger system and manipulating death counters is really enticing. I'm going to take a look at this soon!
Maybe your tool is marketed more toward people who don't really make maps much, or have never really made a map before, or who are making a completely new map. If so, I'll back off a bit. But if you want established mapmakers to use this, it needs to be able to work with the things that people have already done.
If the triggers are already written for a map, you certainly don't need any tools for that any more. However, when any one, including you, starts any new map from scratch...
On April 28 2017 03:34 KhaosKreator wrote: But established mapmakers make completely new maps all the time?
What? Even for maps I've already completed, I still use tools to update and edit it... Even if I didn't, this isn't actually relevant to what I was saying, because even in new maps I still copy and paste from what I've done in the past. The whole point of a program like this is to save time.
The last big limitation is now lifted. You no longer need a reserved player at all. The "unused" unit death counters for all players are mapped by default. This gives us 416 memory locations to work with which should be more than enough for the default configuration. You can still pass a registers file with --reg to override it.
I still need a list of the names of all AI scripts if anyone has such a thing. Otherwise I'll just export a map from scmdraft 2 and extract them manually but it'd be nice to not have to.
On April 29 2017 01:07 integral wrote: Good stuff. I think asking on TL for specific things like AI scripts won't be as likely to get a response as asking on staredit.net.
I'd love to join that community but the website is really really slow for me for some reason (10-20 secs to load a page). I had to clone the wiki locally so I can work without waiting forever.
On April 29 2017 01:07 integral wrote: Good stuff. I think asking on TL for specific things like AI scripts won't be as likely to get a response as asking on staredit.net.
I'd love to join that community but the website is really really slow for me for some reason (10-20 secs to load a page). I had to clone the wiki locally so I can work without waiting forever.
There is now a way to spawn units with properties. I think it is more flexible (?) than scmdraft or staredit as you can reuse CUWP slots for different unit types and they're quite scarce at 64 max. More info in the README here - https://github.com/AlexanderDzhoganov/langums#spawning-units-with-properties
Just pushed an update to my vscode extension which adds in code completion on intrinsics, and gets up to date with the mammoth pile of new functionality in langums.
Some future features I might be looking at are filling in intellisense when working with intrinsic functions, and scanning the .scx file for locations and exposing those to autocomplete (if I figure out a friendly cross-platform way of distributing libmpq, might poke around asm.js).
Btw, you're a monster nlight, it's taking a pretty big chunk of my post-work free time just to keep up with your language updates
On April 29 2017 14:04 Glenstorm wrote: Just pushed an update to my vscode extension which adds in code completion on intrinsics, and gets up to date with the mammoth pile of new functionality in langums.
Some future features I might be looking at are filling in intellisense when working with intrinsic functions, and scanning the .scx file for locations and exposing those to autocomplete (if I figure out a friendly cross-platform way of distributing libmpq).
Btw, you're a monster nlight, it's taking a pretty big chunk of my post-work free time just to keep up with your language updates
Probably doesn't get you all the way there (it's missing a lot of the stuff that'd probably be useful for you to auto-complete), but gets you much closer without using any native code, and is open for contributions if you'd like to add things
I made a list of all AI script names if anyone is interested in it for whatever reason.
You are still missing a lot of them. Those are just the standard StarEdit accessible ones. There are also all the Campaign scripts some spell scripts (recall/Dweb/nuke here, load nuclear silo), scripts that handle some unit spawns in some of the campaign missions, the all-so-important junkyard dog script (makes any unit move around like a critter), the vision scripts, and you even seem to have missed the suicide mission scripts. Basically, have a look in SCMDraft, it can access all of them, to get the full list.
On April 29 2017 01:07 integral wrote: Good stuff. I think asking on TL for specific things like AI scripts won't be as likely to get a response as asking on staredit.net.
I'd love to join that community but the website is really really slow for me for some reason (10-20 secs to load a page). I had to clone the wiki locally so I can work without waiting forever.
Yeah. It's a long-running joke how slow it is.
Besides not really being a UMS mapmaker, this is one of the reasons I try to stay away from Staredit.net lol. But for me it's like 1-2 mins to load a page. My god it is horrible. So frustrating lol.
However I find it funny that some of the smartest guys in BW use such a slow website.
You are still missing a lot of them. Those are just the standard StarEdit accessible ones. There are also all the Campaign scripts some spell scripts (recall/Dweb/nuke here, load nuclear silo), scripts that handle some unit spawns in some of the campaign missions, the all-so-important junkyard dog script (makes any unit move around like a critter), the vision scripts, and you even seem to have missed the suicide mission scripts. Basically, have a look in SCMDraft, it can access all of them, to get the full list.
I dumped these from scmdraft by using the Classic Triggers editor. Maybe they're not all listed there? Give me some hints where to look.
code completion on intrinsics, and gets up to date with the mammoth pile of new functionality in langums.
Awesome!
Btw, you're a monster nlight, it's taking a pretty big chunk of my post-work free time just to keep up with your language updates
Don't worry this won't be for too long, I'm almost done with the core features
What's the strategy for handling multi-condition events? I was working on some demo code where I found myself wanting to do the trigger equivalent of "Player 1 brings 1 civ to location and has >=100 minerals" and I haven't seen anything setup like that in the tests. Some syntaxes I imagined where nested lambdas, using one condition as an if inside the other, or some weird bring(...) && accumulate(...) => { } setup.
I've been poking around with snippets and in all likelihood I have to make the event snippets a bit less aggressive at setting up syntax for you, if they're viable in stuff like if statements.
On April 29 2017 22:29 Glenstorm wrote: What's the strategy for handling multi-condition events? I was working on some demo code where I found myself wanting to do the trigger equivalent of "Player 1 brings 1 civ to location and has >=100 minerals" and I haven't seen anything setup like that in the tests.
You can specify more than one condition per event handler separated by commas (up to 63 total), it's actually in the examples in the README. I will add some to the tests as well. For your case it would look like:
That code completion gif is really cool, nice job.
On a sidenote, LangUMS now supports 100% of the built-in trigger conditions & actions in the game. Now I'm starting to expand into new territory. The first addition is the is_present(Player) function which tells you if a player (or players) is currently present in the game.
if (is_present(Player3)) { print("Player 3 is in the game."); }
You can also use it to get the number of players in-game at the moment like this:
var playerCount = is_present();
Everyone who is actively trying out, testing, using, doing whatever with the tool please join the discord here - https://discord.gg/TNehfve so we can talk in real time.
You are still missing a lot of them. Those are just the standard StarEdit accessible ones. There are also all the Campaign scripts some spell scripts (recall/Dweb/nuke here, load nuclear silo), scripts that handle some unit spawns in some of the campaign missions, the all-so-important junkyard dog script (makes any unit move around like a critter), the vision scripts, and you even seem to have missed the suicide mission scripts. Basically, have a look in SCMDraft, it can access all of them, to get the full list.
I dumped these from scmdraft by using the Classic Triggers editor. Maybe they're not all listed there? Give me some hints where to look.
Not sure what you did, but you should have access to all of them to either the "execute AI script" or the "execute AI script at location" trigger actions. Maybe you do not have the latest official SCMDraft version (2.0.8.)?
Not sure what you did, but you should have access to all of them to either the "execute AI script" or the "execute AI script at location" trigger actions. Maybe you do not have the latest official SCMDraft version (2.0.8.)?
I see now. My bad. It appears I did use StarEdit after all because I do see a lot of other AI scripts in scmdraft. I'll make sure to dump these also and include them in the list. Thanks!
Right now can only be indexed with a constant but there will be a way to iterate them at some point. Indexing with variables is unlikely to be supported.
Also comparison operators are now 5000% (not a real number) faster. Well, much faster and generate much less triggers.
Not sure what you did, but you should have access to all of them to either the "execute AI script" or the "execute AI script at location" trigger actions. Maybe you do not have the latest official SCMDraft version (2.0.8.)?
I see now. My bad. It appears I did use StarEdit after all because I do see a lot of other AI scripts in scmdraft. I'll make sure to dump these also and include them in the list. Thanks!
Will your editor be able to do advanced functions that programs in the past like firegraft/memgraft have been able to do such as assigning abilities to different units, or replacing unit/portrait graphics with new content, or terrain blending between different titlesets? Doing this without a custom exe is the kind of flexibility I want in SC1.
BTW I'd check the http://www.campaigncreations.org/forum/ forums, there are a lot of old school modders/campaign creators there, potentially a good place to share your work.
nlight I think I might be the only mapmaker in the world who has a current need to use arrays, and I don't know enough computer science to intuitively grok your implementation here. I don't really have a specific question because I'm not even sure what you've done.
So my understanding is that you're defining an array with 5 elements, 0-4, and then you're able to do stuff with that. What does that look like in triggers though? A unique death count for each element, I assume?
It's four elements - foo[N] declares an array with size N so foo[4] will declare foo with 4 elements.
What does that look like in triggers though? A unique death count for each element, I assume?
Yeah. Each array element takes a register (i.e. death count). It's fair to say that "regular" variables are just arrays with size of one as far as the compiler treats them. Arrays don't survive past the IR phase, they are more or less what you'd call "syntax sugar". Indexing with anything that's not a constant literal is not possible at the moment e.g. being able to say "myArray[someVariable]". Though there are (inefficient) ways to implement it so I'll look into that in the future.
It's four elements - foo[N] declares an array with size N so foo[4] will declare foo with 4 elements.
Ah, ok.
Yeah. Each array element takes a register (i.e. death count). It's fair to say that "regular" variables are just arrays with size of one as far as the compiler treats them. Arrays don't survive past the IR phase, they are more or less what you'd call "syntax sugar". Indexing with anything that's not a constant literal is not possible at the moment e.g. being able to say "myArray[someVariable]". Though there are (inefficient) ways to implement it so I'll look into that in the future.
Everything in starcraft coding is super inefficient, I dunno how the developers could stand making maps and the campaign without actual variables and operators. Kinda boggles my mind a bit.
That sound is the Terran rescue unit sound (sound\misc\TRescue.wav). Either you have some trigger using that sound directly, or you are creating rescuable units (units for a player set to rescuable) somewhere near your own.
Hi! I by no means mean to deter you from continuing this project (I think it's awesome), but as is the code seems to be a bit difficult to read (i.e. just by looking at your code, it's hard to tell what exactly everything does).
On May 02 2017 02:08 Powder_Keg wrote: Hi! I by no means mean to deter you from continuing this project (I think it's awesome), but as is the code seems to be a bit difficult to read (i.e. just by looking at your code, it's hard to tell what exactly everything does).
Is there a way you could make it more readable?
You mean the code of the compiler or the LangUMS language itself? Also you'll have to be more specific, which parts do you find unreadable? Maybe give examples.
On May 01 2017 23:53 Freakling wrote: That sound is the Terran rescue unit sound (sound\misc\TRescue.wav). Either you have some trigger using that sound directly, or you are creating rescuable units (units for a player set to rescuable) somewhere near your own.
It turned out to be a glitched map issue, not triggers related. I'm still investigating what's up.
Maybe it's just me, and maybe it's just that your language is meant to be understood differently from what people are used to, but here's what I had trouble understanding while reading through your code:
When triggers start and when they end
Which players own the triggers
What variables are deathcounters and what are just integers (similarly boolians and switches)
Though, these are relatively minor complaints. Looking through it again the code is quite understandable, it just takes some getting used to.
On May 02 2017 02:59 Powder_Keg wrote: Maybe it's just me, and maybe it's just that your language is meant to be understood differently from what people are used to, but here's what I had trouble understanding while reading through your code:
I think you have a misunderstanding of what the langums language is. It's an imperative language (meaning statements are executed sequentially, never out of order) not unlike C or JavaScript. It is not meant to be read like scmdraft trigger lists. Control starts at the top of the main() function and executes statements one by one. Typically you'll see something like
while(true) { poll_events(); }
at the end of the main() function. This makes it so all event handlers get executed one after another indefinitely. Again, there is no out of order execution, everything is sequential and linearizable.
[*]What variables are deathcounters and what are just integers (similarly boolians and switches)
All variables are unsigned integers but the underlying storage is death counters. This is abstracted away from the user more or less.
[*]Which players own the triggers
You can set that with the --triggers-owner command-line argument.
[*]When triggers start and when they end
The emitted triggers are vastly different than the langums code and really hard to follow for a human in the same way machine code is hard to follow for a human. You don't really care about that when using this tool.
I think you have a misunderstanding of what the langums language is. It's an imperative language (meaning statements are executed sequentially, never out of order) not unlike C or JavaScript. It is not meant to be read like scmdraft trigger lists.
Your code generates triggers which the map will run through sequentially, that I understand (or am I wrong here? I don't see how I could be.)
What I don't understand from your code is when it generates those triggers.
What I don't understand from your code is when it generates those triggers.
You really shouldn't care about what actual triggers are emitted as long as you can observe the behavior described by the LangUMS code you've written. Well, unless you're working on the compiler itself.
(e.g. the triggers outputted would have "Set Switch1" as an action, and then it would be 32 or so triggers to do a binary addition or w/e it's called adding deathcounter2 to deathcounter1 with each trigger having "Switch 1 is Set" as one of their condition)
The if condition is implemented using two triggers - one will have the false condition, the other the true condition. Depending on which one executes the "instruction counter" will be set to a different location effectively jumping to another point of execution. Basically every emitted trigger begins with a check if the current value of the instruction counter is equal to its address. This way we turn the inherently parallel triggers into a sequential machine. No switches are used for this. Switches are used as mutexes for some operations internally but they are not exposed to the user in any way I think.
Also, for example, if I just write
var integer = 5
does that take up a deathcounter slot?
Yes. Every variable takes up a death count slot and every array takes up as many slots as its size. Local variables exist only for the duration of their block (code surrounded by {}) and their storage gets reused.
So each trigger outputted has a "Deathcounter == X" where X is the line of the code, basically?
Also, triggers are by default sequential - StarCraft runs through them from the top to the bottom, but you probably already knew that.
Edit: Would that sometimes cause some triggers to not be run until the next trigger cycle though? for example, lets say you are at line 500 and then now you want to next execute line 200 - to do that you have to wait until the next trigger cycle, which takes up like 1/11 of a second O_O
On May 02 2017 04:41 Powder_Keg wrote: So each trigger outputted has a "Deathcounter == X" where X is the line of the code, basically?
This is a relatively simplistic way to put it but yeah.
Also, triggers are by default sequential - StarCraft runs through them from the top to the bottom, but you probably already knew that.
They are executed in sequence by the game code by virtue of the game code being sequential but they are inherently a parallel machine. This "design" makes it easy to introduce race conditions (the bane of sane programming) in your logic. LangUMS actively prevents you from introducing race conditions unless you try really hard to twist it. That's the biggest gain from all this. In the end it's all just automation for something absolutely doable by hand but very tedious and error-prone.
Thanks for answering all my questions btw, I appreciate it :D
What about this question:
Edit: Would that sometimes cause some triggers to not be run until the next trigger cycle though? for example, lets say you are at line 500 and then now you want to next execute line 200 - to do that you have to wait until the next trigger cycle, which takes up like 1/11 of a second O_O
Would that sometimes cause some triggers to not be run until the next trigger cycle though? for example, lets say you are at line 500 and then now you want to next execute line 200 - to do that you have to wait until the next trigger cycle, which takes up like 1/11 of a second O_O
Yeah, jumps take one full cycle to execute in the worst case, though that is amortized by some compiler optimizations and it will only get better at it. This isn't any better if you sequence the triggers by hand.
EDIT: On second thought, maybe you're asking doesn't that mean you will miss in-game events if your code happens to be doing something else at that time. The answer is yes and no. All events are currently buffered but only once. Say you have a "bring marines to x" condition in your code. If the player brings the marines but 20 cycles pass between the calls to poll_events() it won't fire off the event 20 times but just 1. This can be an advantage or a drawback depending on where you're coming from. It's totally possible to buffer all 20 events and fire them off but I think it's more useful the other way around. I can turn this into an option eventually.
I am confused on why you say SC is inherently parallel.
SC runs triggers in the following way:
1. It first unpacks all triggers belonging to "Force 1," Force 2," etc., and changes them to belong to just the players in those forces - e.g. if Player 1 and Player 2 are in Force 1, then a trigger owned by Force 1 is the same thing as a trigger owned by Player 1 and Player 2. Similarly, a trigger owned by Current Player or All Players is the same thing as a trigger owned by Player 1, Player 2, etc. all the way to Player 8.
2. It then runs through all triggers owned by Player 1, from the top of the list to the bottom of the list. After this has finished it runs through all triggers owned Player 2, and then Player 3, etc. all the way to Player 8.
3. It then repeats this process.
^ How is that not sequential?
EDIT: On second thought, maybe you're asking doesn't that mean you will miss in-game events if your code happens to be doing something else at that time. The answer is yes and no. All events are currently buffered but only once. Say you have a "bring marines to x" condition in your code. If the player brings the marines but 20 cycles pass between the calls to poll_events() it won't fire off the event 20 times but just 1. This can be an advantage or a drawback depending on where you're coming from. It's totally possible to buffer all 20 events and fire them off but I think it's more useful the other way around. I can turn this into an option eventually.
I gotta be honest, I don't understand a lot of what you just said. What does it mean "All events are currently buffered but only once?" What does poll_events() do? If the player brings 20 marines to the location, and this poll_events() doesn't fire, and then the player removes 20 marines from the location, and then poll_events() does fire, will it not realize that in the meantime the player has brought 20 marines to the location?
Yeah, jumps take one full cycle to execute in the worst case, though that is amortized by some compiler optimizations and it will only get better at it. This isn't any better if you sequence the triggers by hand.
So does this mean that sometimes the triggers wont run immediately? e.g. if I have a "Player brings at least 1 Terran marine to location X" condition, and in game player brings a marine to location X, that there is a chance that the trigger still wont execute because there's some inner workings that have to be sorted out first? This sounds like something that could actually totally break a map, and is something that using normal triggers no one would ever have to worry about - i.e. it sounds like it would just be more difficult to make stuff using this than it is otherwise
Triggers may be executed sequentially in reality but they are inherently a parallel machine i.e. two unrelated statements in different parts of the program can, in effect, act at once and modify the same parts of memory. This is essentially parallelism and suffers from the drawbacks of any parallel machine (you need locking to avoid data races).
If the player brings 20 marines to the location, and this poll_events() doesn't fire, and then the player removes 20 marines from the location, and then poll_events() does fire, will it not realize that in the meantime the player has brought 20 marines to the location?
Yes it will that's the point of buffering the events so you don't lose any.
So does this mean that sometimes the triggers wont run immediately?
Nothing ever runs "immediately". Your CPU needs time to execute instructions after all.
To answer your question, if the main langums loop is busy with something else and you're not calling poll_events() quickly enough it may delay stuff (but not break it). This can be anywhere from one trigger cycle to many depending on what you're doing in the code.
if I have a "Player brings at least 1 Terran marine to location X" condition, and in game player brings a marine to location X, that there is a chance that the trigger still wont execute because there's some inner workings that have to be sorted out first?
I don't know where you got that from. LangUMS doesn't roll dice to figure out if your stuff will run properly or not. It has pretty well defined semantics and the code does what it says. The trigger will be executed for sure on the next poll_events() call.
Triggers may be executed sequentially in reality but they are inherently a parallel machine i.e. two unrelated statements in different parts of the program can, in effect, act at once and modify the same parts of memory. This is essentially parallelism and suffers from the drawbacks of any parallel machine (you need locking to avoid data races).
. . . But two triggers are never run at the same time. They are always checked in the same cycle, and they can both run in the same cycle, but even then SC will just run the first one first in the cycle and the second one second in the cycle. I still don't understand why you say it is parallel. Can you give a concrete example of SC executing triggers in parallel please?
Yes it will that's the point of buffering the events so you don't lose any.
Then why not poll_events() every trigger cycle? I don't understand why this was introduced - what is it's purpose?
Nothing ever runs "immediately". Your CPU needs time to execute instructions after all.
I meant in the same trigger cycle that you meet those conditions. It sounds like with langUMS all the conditions of a trigger can be met for a trigger cycle or two and then become unmet and the triggers would never know.
I don't know where you got that from. LangUMS doesn't roll dice to figure out if your stuff will run properly or not. It has pretty well defined semantics and the code does what it says. The trigger will be executed for sure on the next poll_events() call.
I get that the code determines what happens, but I guess the problem here is I don't understand the code, and from what it sounds like the expected behavior is difficult to determine. I mean, a lot of trigger systems require things to be 'trigger-cycle' perfect (i.e. for triggers to execute as soon as the conditions are met), but with this it seems like sometimes this won't happen?
On May 06 2017 03:02 Powder_Keg wrote: Triggers may be executed sequentially in reality but they are inherently a parallel machine i.e. two unrelated statements in different parts of the program can, in effect, act at once and modify the same parts of memory. This is essentially parallelism and suffers from the drawbacks of any parallel machine (you need locking to avoid data races).
That's what I mean by parallel. Two triggers can execute during the same cycle and without some form of locking if they access the same "memory" (death counts or whatever else) it creates a data race. This is an issue that experienced map makers are just used to dealing with but it's not simple and is prone to mistakes.
Then why not poll_events() every trigger cycle? I don't understand why this was introduced - what is it's purpose?
The purpose of poll_events() is to run the associated code for any event handlers whose conditions have been fulfilled since the last time poll_events() was called. This is necessary since LangUMS code is sequential and control can't just jump to random places whenever something in-game happens.
You can't poll_events() until the previous one has finished. So the minimum time between poll_events() is the time it takes to execute all your event handlers. This will never be zero unless you have zero event handlers in which case poll_events() emits no instructions.
I meant in the same trigger cycle that you meet those conditions. It sounds like with langUMS all the conditions of a trigger can be met for a trigger cycle or two and then become unmet and the triggers would never know.
The events are buffered so if the conditions are met the event handler will fire the next poll_events() cycle.
I get that the code determines what happens, but I guess the problem here is I don't understand the code, and from what it sounds like the expected behavior is difficult to determine.
The expected behavior is trivial to determine as the code is sequential. You have to read it like any other programming language, each statement follows the preceding one starting from the top of your main() function. Control never just jumps around the place for no reason.
On May 06 2017 03:57 KhaosKreator wrote: I'm going to try to get involved in this and make some test maps this weekend, I think.