• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Streamlined object generation

Status
Not open for further replies.
Level 21
Joined
Mar 27, 2012
Messages
3,232
Perhaps by this point everyone knows about ObjectMerger and how to use lua to make it easier. Well, it doesn't fully solve the problem in some cases. Having a scripted way to generate objects is better than typing monstrous lines of objectmerger commands, but it still sucks to have to rewrite it for every map. It would also suck to have to keep a folder per map like Nestharus does, because that's quite a lot of stuff to keep track of. It's fine to have one folder per map, but it shouldn't be mandatory.

This is why I created ObjectDefinition. Essentially what ObjectDefinition does is that it creates objects based on specifically written files. You can make them in notepad if you want. That alone wouldn't be all that impressive, but what really makes ObjectDefinition shine is the shortcuts you can take.
Without further ado, the features.

In your map you put a few lines of lua script that doesn't have to change after this. Example:
JASS:
//! externalblock extension=Lua ObjectMerger $FILENAME$
  //! i require "lua\\ObjectDefinition"
  //! i require "lua\\Heroic Functions"
  //! i local t = ProcessFolder("Heroic")
  //! i t = addEngineers(t)
  //! i ImplementTable(t)
//! endexternalblock
Note that this example has a lot of map-specific stuff. A more clean example would be:
JASS:
//! externalblock extension=Lua ObjectMerger $FILENAME$
  //! i require "lua\\ObjectDefinition"
  //! i local t = ProcessFolder("Heroic")
  //! i ImplementTable(t)
//! endexternalblock
The system assumes that you're passing it a folder in the JNGP folder(this whole thing requires JNGP, if you haven't realized by now). It will take every file of a specific kind from this folder and implements the objects from those files. You can also give it specific files instead of folders with ProcessFile.

The files

ObjectDefinition ignores every file that doesn't have the right extension. By default this extension is .obj, but you can change it in the configuration if needed.
Within every file with that extension it expects a basic format:
field=value

There are 2 fields that must exist for any objects to be created at all: baseid and raw4. By excluding those you can make objects that are not implemented, which can be used as templates.
baseid is the rawcode of the ability that you want to base the new object on.
raw4 is the rawcode of the new ability that you're creating. If you want multiple abilities, you can just add them with a comma. For example:
raw4=A000,A001,AGHK

For fields with levels you just add the level after the field. Example: Acdn1=5
If you insert 0 as the level it will apply for every level, based on how many levels you specified in the object. The amount of levels must be defined in the object files.

You can add stuff to the previous line by starting a line with a plus. E.g, +field=value
You can add stuff to a different object by adding the objectid and a colon to the beginning of the line. E.g, object:field=value. If the object doesn't exist, it will be created, so you can create dummy objects/templates like this.
It's also possible to copy all field-value pairs from another object by using inherit. It's a special field that never makes it to the game.
inherit=rawcode
inherit=rawcode,rawcode,rawcode...

There's also the possibility to set a field dynamically. That is, without having to write the specific value. You can do this by adding -f to the end of the line. The way it works is that any value ending with -f is treated as lua code to be executed. -f is shorthand for "function".
Example: aubx=return 1+3-f
This alone wouldn't be very useful, but it lets you use various variables, such as the current level being defined(when you've set the level of the field to 0). It also lets you use various functions:
getField(object,field) - returns the value of the specified field on specified object. If you don't specify an object and instead write getField(field), then it assumes that the object is the same as the field in which this function is called.


Finishing notes

I originally started writing ObjectDefinition as a map-specific script to externalize object creation. I didn't really think of it as a general-purpose tool, but over time I made it more and more into one, as that was what I needed. However, despite the numerous rewrites it's still not built from the ground up to be a general purpose tool. Code is organized badly, functions that I consider core features are spread around, etc. As such, I will not release it publicly right now, but instead rewrite it first in a more structured manner. Once I finish rewriting the whole thing I'll make numerous examples on how to use the various features.

Current intended structure would be that I have these files:

ObjectDefinition - The main script that you should run everything else through.
ODFunctions - Gives access to some extra convenience functions. Loaded by default, so you don't have to do anything special. I'm keeping this separate because although useful, those functions can be added and removed without breaking the system. They are not the core of the system, external object definition is.
ODConfig - Text file for configuring various things, such as the markers used for specific features.

I will start rewriting ObjectDefinition on saturday, 3/09/2016. It might take a couple days. Meanwhile I would appreciate feedback on anything mentioned here, especially features that would make things easier.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
If I've understood correctly you want to turn this:

dummy.lua-object-merger:
JASS:
//! externalblock extension=lua ObjectMerger $FILENAME$
    //! i setobjecttype("units")

    //! i createobject("ewsp", "e600")
        //! i makechange(current, "unam", "xe unit")
        //! i makechange(current, "upgr", "")
        //! i makechange(current, "ubui", "")
        //! i makechange(current, "uabi", "Aeth")
        //! i makechange(current, "umpr", "0.1")
        //! i makechange(current, "umpm", "1000")
        //! i makechange(current, "umpi", "1000")
        //! i makechange(current, "ucol", "0")
        //! i makechange(current, "urac", "commoner")
        //! i makechange(current, "ufoo", "0")
        //! i makechange(current, "udty", "divine")
        //! i makechange(current, "uico", "replaceableTextures\\CommandButtons\\BTNtemp.blp")
        //! i makechange(current, "umvs", "522")
        //! i makechange(current, "umdl", "war3mapImported\\dummy.mdl")
        //! i makechange(current, "uble", "0")
        //! i makechange(current, "ucbs", "0")
        //! i makechange(current, "umxp", "0")
        //! i makechange(current, "umxr", "0")
        //! i makechange(current, "uspa", "")
        //! i makechange(current, "uimz", "0")
        //! i makechange(current, "ulpz", "0")
        //! i makechange(current, "unsf", "(Caster System?)")
        //! i makechange(current, "uhom", "1")
        //! i makechange(current, "usin", "1")
        //! i makechange(current, "utyp", "_")
        //! i makechange(current, "uprw", "1")
        //! i makechange(current, "utip", "")
        //! i makechange(current, "utub", "http://wc3campaigns.net/vexorian")
        //! i makechange(current, "usid", "1")

//! endexternalblock

into this:

dummy.obj:
JASS:
baseid="ewsp"
raw4="e600"
unam="xe unit"
upgr=""
ubui=""
uabi="Aeth"
umpr="0.1"
umpm="1000"
umpi="1000"
ucol="0"
urac="commoner"
ufoo="0"
udty="divine"
uico="replaceableTextures\\CommandButtons\\BTNtemp.blp"
umvs="522"
umdl="war3mapImported\\dummy.mdl"
uble="0"
ucbs="0"
umxp="0"
umxr="0"
uspa=""
uimz="0"
ulpz="0"
unsf="(Caster System?)"
uhom="1"
usin="1"
utyp="_"
uprw="1"
utip=""
utub="http://wc3campaigns.net/vexorian"
usid="1"

If that's the case then, I guess it's less typing but it's still tedious.
The easiest way to create/modify objects in my opinion is by using WE's Object Editor becase that's what it was made to do well.

In my opinion object generation is useful:
when you want to create many similar objects (e.g: BonusMode).
when you want to distribute objects that make some scripted system work and have them embeded into the system's text (saves people's time by not having to manually copy the objects one by one from the system's demo map into theirs)​

A compelling reason of using ObjectDefinition instead of WE's Object Editor in my opinion would be to have a way to mark some/all the fields of an object as "exported to Jass", i.e being able to reference them in-game via scripting:

JASS:
    local integer footman_default_armor = GetObjectFieldInteger('hfoo', 'udef') // 2
    local string footman_default_armor_type = GetObjectFiledString('hfoo', 'udty') // "Large"

    // having the above aliasd to more meaningful names would be better of course:
    set footman_default_armor = GetUnitDefaultArmor('hfoo')
    set footman_default_armor_type = GetUnitDefaultArmorType('hfoo')

PS: WE's Object Editor should've been able to export object fields to Jass, or better yet, Jass should have the natives to access any object's field (is this in the patch wish list?) =)
 
Level 21
Joined
Mar 27, 2012
Messages
3,232
Creating tons of objects with a specific formula is not only limited to things like BonusMod. You could also run the balance of your entire map through this system by making the stats of units be defined with a formula. You could put those formulas into dummy objects that won't get implemented(no raw4) and inherit them into other objects(did I forget to mention inherit? Probably. I'll add it to the first post). Thus you could essentially define templates for units of a specific kind(melee, ranged, magic) and scale their stats by level.
Also, if you ever want to make a system whereby you can freely choose abilities and they'll have hotkeys based on the slot you'll need to create one ability per slot. This system makes that much more straightforward than normal object editor. Similarly, if you want to change ability cooldown dynamically through abusing engineering upgrade, then you'll need one ability per each. The bigger your map, the easier it is to find a use for something like this.
Injecting data from object files to map script is something that this system can't inherently do, but it's an intriguing idea. If I could get the editor to tell me the path of the map currently being saved, then I could technically make it happen. It would be hard, but it would be doable. It would also be more modular than the all-or-nothing approach that WaterKnight's postproc has to object data injection.
EDIT: If I'm going to do injection I might as well also allow the ability to set a list of scripts that an object needs to work. Then it would work as an all-in-one spell importer too. But it all depends on whether the editor runs lua scripts before or after vJASS processing. If it runs them after, then none of this injection stuff will work well. Although I could make it auto-generate some vJASS script that you could import normally. There's always a way, I guess.
 
Last edited:
Status
Not open for further replies.
Top