Construct Quarter – Part 2: Hero Data Framework


Construct Quarter Project Page

Overview

I said in the previous post that my next goal would be creating a spell framework that makes the addition of new spells as easy as possible. Towards that end, I’ve created a framework that automatically sets up units and their abilities from input data.

Abilities

Although this system also initializes unit stats, that functionality is very simple and the primary work went into supporting custom abilities.

Custom Abilities are made up of one or more Ability Actions. An Ability Action has a type, like deal damage or apply buff, and then interprets an array of values based on that type. For deal damage, the first value would be the damage the action should deal. For apply buff, the first value would be interpreted as the ID of the buff that should be applied. Ability Actions can have a delay, which facilitates the easy implementation of things like damage over time effects. Ability Actions also support animations, special effects, and sound. For more dynamic functionality, Ability Actions can have Conditions and Side Effects.

Conditions check the value of a variable or property, and an Ability Action will only execute if all of its conditions are met. One example of how a condition could be used is in the frost mage ability, Shatter. Shatter deals additional damage if the target is already frozen. This ability could be implemented in two actions. The first action would deal damage unconditionally. The second action would deal the extra damage conditionally, only if the target has the frozen debuff.

Side effects set the value of a variable or property. Side effects will only execute if their owning Ability Action’s conditions are met. One example of how a side effect could be used is in the beast master hunter’s abilities Summon Misha and Kill Command. Kill Command should only deal damage if Misha is alive. It can check the content of a unit variable to see if Misha is alive, but something must set that unit variable. Summon Misha can have a side effect that sets that unit variable to the summoned unit.

In the video below, I go through my testing abilities. You’ll probably notice it’s in old Warcraft 3 rather than Refogred. This is because there are still problems with some custom map functionality in the Reforged beta and the old Warcraft 3 therefore works better for testing.

Those six abilities cover a lot of the bases of functionality that I’ll need to start building heroes. They’re created from the following JSON data. You might notice Ability Actions don’t yet have a type. For getting the system up and running, all Ability Actions are debug type, which just concatenates its values as strings and prints out the concatenated string. In the future they’ll also have a target property, but this was unnecessary for the debug type.

[
    {
        "m_Info":{
            "m_Id":"O000",
            "m_Name":"Samuro",
            "m_Tooltip":"",
            "m_Icon":""
        },
        "m_AttackData":{
            "m_Type":0,
            "m_Range":250,
            "m_Speed":2.5,
            "m_Damage":100
        },
        "m_StatData":{
            "m_Health":5000,
            "m_Mana":250,
            "m_HealthRegen":2,
            "m_ManaRegen":1,
            "m_Armor":10
        },
        "m_Abilities":[
            {
                "m_Info":{
                    "m_Id":"AX00",
                    "m_Name":"Setter",
                    "m_Tooltip":"Sets gbFlag to true",
                    "m_Icon":"ReplaceableTextures\\CommandButtons\\BTNAvatarOn.blp"
                },
                "m_Data":{
                    "m_ButtonPosX":0,
                    "m_ButtonPosY":2,
                    "m_Cooldown":1,
                    "m_Range":100,
                    "m_CastTime":0,
                    "m_FollowThroughTime":0,
                    "m_Hotkey":"T"
                },
                "m_Actions":[
                    {
                        "m_Values":[
                            "Setter: gbFlag is ",
                            "gbFlag",
                            ". Setting."
                        ],
                        "m_Conditions":[
                            
                        ],
                        "m_SideEffects":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"1"
                            }
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    }
                ]
            },
            {
                "m_Info":{
                    "m_Id":"AX01",
                    "m_Name":"Checker",
                    "m_Tooltip":"Checks if gbFlag is true and sets to false if it was true.",
                    "m_Icon":"ReplaceableTextures\\CommandButtons\\BTNBerserkForTrolls.blp"
                },
                "m_Data":{
                    "m_ButtonPosX":1,
                    "m_ButtonPosY":2,
                    "m_Cooldown":1.2,
                    "m_Range":100,
                    "m_CastTime":0,
                    "m_FollowThroughTime":1,
                    "m_Hotkey":"C"
                },
                "m_Actions":[
                    {
                        "m_Values":[
                            "Checker: gbFlag shouldn\u0027t be set. It\u0027s currently ",
                            "gbFlag"
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"!1"
                            }
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Attack Walk Stand Spin",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    },
                    {
                        "m_Values":[
                            "Checker: gbFlag should be set. It\u0027s currently ",
                            "gbFlag",
                            ". Unsetting."
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"1"
                            }
                        ],
                        "m_SideEffects":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"0"
                            }
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Attack Walk Stand Spin",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    }
                ]
            },
            {
                "m_Info":{
                    "m_Id":"AX04",
                    "m_Name":"Delayed Checker",
                    "m_Tooltip":"Checks if gbFlag is true initially and again after 5 seconds.",
                    "m_Icon":"ReplaceableTextures\\CommandButtons\\BTNInvisibility.blp"
                },
                "m_Data":{
                    "m_ButtonPosX":1,
                    "m_ButtonPosY":1,
                    "m_Cooldown":2.5,
                    "m_Range":100,
                    "m_CastTime":0,
                    "m_FollowThroughTime":3,
                    "m_Hotkey":"Y"
                },
                "m_Actions":[
                    {
                        "m_Values":[
                            "Delayed Checker: At cast time gbFlag shouldn\u0027t have been be set. It\u0027s currently ",
                            "gbFlag"
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"!1"
                            }
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":2,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Attack Walk Stand Spin",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    },
                    {
                        "m_Values":[
                            "Delayed Checker: At cast time gbFlag should have been set. It\u0027s currently ",
                            "gbFlag"
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"1"
                            }
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":2,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Attack Walk Stand Spin",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    },
                    {
                        "m_Values":[
                            "Delayed Checker: After a delay, gbFlag shouldn\u0027t be set. It\u0027s currently ",
                            "gbFlag"
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"!1"
                            }
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":5,
                            "m_bDelayConditions":true
                        },
                        "m_ArtData":{
                            "m_Animation":"",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    },
                    {
                        "m_Values":[
                            "Delayed Checker: After a delay, gbFlag should be set. It\u0027s currently ",
                            "gbFlag"
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"gbFlag",
                                "m_Rhs":"1"
                            }
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":5,
                            "m_bDelayConditions":true
                        },
                        "m_ArtData":{
                            "m_Animation":"",
                            "m_Sound":"",
                            "m_SpecialEffects":null
                        }
                    }
                ]
            },
            {
                "m_Info":{
                    "m_Id":"AX02",
                    "m_Name":"Builder",
                    "m_Tooltip":"Increments guCounter.",
                    "m_Icon":"ReplaceableTextures\\CommandButtons\\BTNStatUp.blp"
                },
                "m_Data":{
                    "m_ButtonPosX":2,
                    "m_ButtonPosY":2,
                    "m_Cooldown":1,
                    "m_Range":100,
                    "m_CastTime":0,
                    "m_FollowThroughTime":0,
                    "m_Hotkey":"B"
                },
                "m_Actions":[
                    {
                        "m_Values":[
                            "Builder: guCounter is ",
                            "guCounter",
                            ". Incrementing by 1."
                        ],
                        "m_Conditions":[
                            
                        ],
                        "m_SideEffects":[
                            {
                                "m_Refkey":"guCounter",
                                "m_Rhs":"\u002B1"
                            }
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"",
                            "m_Sound":"",
                            "m_SpecialEffects":[
                                {
                                    "m_Effect":"Abilities\\Spells\\NightElf\\TrueshotAura\\TrueshotAura.mdl",
                                    "m_Unit":"cnTarget",
                                    "m_Attachments":[
                                        "overhead",
                                        "origin"
                                    ]
                                }
                            ]
                        }
                    }
                ]
            },
            {
                "m_Info":{
                    "m_Id":"AX03",
                    "m_Name":"Spender",
                    "m_Tooltip":"Resets guCounter.",
                    "m_Icon":"ReplaceableTextures\\CommandButtons\\BTNChestOfGold.blp"
                },
                "m_Data":{
                    "m_ButtonPosX":3,
                    "m_ButtonPosY":2,
                    "m_Cooldown":1,
                    "m_Range":100,
                    "m_CastTime":0,
                    "m_FollowThroughTime":0,
                    "m_Hotkey":"D"
                },
                "m_Actions":[
                    {
                        "m_Values":[
                            "Spender: guCounter should be 0. It is currently ",
                            "guCounter"
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"guCounter",
                                "m_Rhs":"0"
                            }
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"",
                            "m_Sound":"Units\\Undead\\HeroLich\\HeroLichWarcry1.wav",
                            "m_SpecialEffects":null
                        }
                    },
                    {
                        "m_Values":[
                            "Spender: guCounter should be \u003E0. It is currently ",
                            "guCounter",
                            ". Resetting to 0."
                        ],
                        "m_Conditions":[
                            {
                                "m_Refkey":"guCounter",
                                "m_Rhs":"\u003E0"
                            }
                        ],
                        "m_SideEffects":[
                            {
                                "m_Refkey":"guCounter",
                                "m_Rhs":"0"
                            }
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"",
                            "m_Sound":"Units\\Human\\HeroMountainKing\\HeroMountainKingWarcry1.wav",
                            "m_SpecialEffects":null
                        }
                    }
                ]
            },
            {
                "m_Info":{
                    "m_Id":"AX05",
                    "m_Name":"Art Stress",
                    "m_Tooltip":"Do three things over time",
                    "m_Icon":"ReplaceableTextures\\CommandButtons\\BTNChemicalRage.blp"
                },
                "m_Data":{
                    "m_ButtonPosX":2,
                    "m_ButtonPosY":1,
                    "m_Cooldown":12,
                    "m_Range":100,
                    "m_CastTime":0,
                    "m_FollowThroughTime":10,
                    "m_Hotkey":"F"
                },
                "m_Actions":[
                    {
                        "m_Values":[
                            
                        ],
                        "m_Conditions":[
                            
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":0,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Attack Slam",
                            "m_Sound":"Units\\NightElf\\HeroMoonPriestess\\HeroMoonPriestessReady1.wav",
                            "m_SpecialEffects":[
                                {
                                    "m_Effect":"Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl",
                                    "m_Unit":"cnCaster",
                                    "m_Attachments":[
                                        "hand",
                                        "origin"
                                    ]
                                },
                                {
                                    "m_Effect":"Abilities\\Weapons\\IllidanMissile\\IllidanMissile.mdl",
                                    "m_Unit":"cnCaster",
                                    "m_Attachments":[
                                        "overhead"
                                    ]
                                },
                                {
                                    "m_Effect":"Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl",
                                    "m_Unit":"cnTarget",
                                    "m_Attachments":[
                                        "chest"
                                    ]
                                }
                            ]
                        }
                    },
                    {
                        "m_Values":[
                            
                        ],
                        "m_Conditions":[
                            
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":1.15,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Stand Victory",
                            "m_Sound":"Units\\Human\\HeroPaladin\\HeroPaladinYesAttack1.wav",
                            "m_SpecialEffects":[
                                {
                                    "m_Effect":"Abilities\\Spells\\Human\\Avatar\\AvatarCaster.mdl",
                                    "m_Unit":"cnCaster",
                                    "m_Attachments":[
                                        "origin"
                                    ]
                                },
                                {
                                    "m_Effect":"Abilities\\Spells\\Undead\\Web\\Webmissile.mdl",
                                    "m_Unit":"cnCaster",
                                    "m_Attachments":[
                                        "hand"
                                    ]
                                },
                                {
                                    "m_Effect":"Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl",
                                    "m_Unit":"cnTarget",
                                    "m_Attachments":[
                                        "chest"
                                    ]
                                }
                            ]
                        }
                    },
                    {
                        "m_Values":[
                            
                        ],
                        "m_Conditions":[
                            
                        ],
                        "m_SideEffects":[
                            
                        ],
                        "m_Delay":{
                            "m_Duration":4.5,
                            "m_bDelayConditions":false
                        },
                        "m_ArtData":{
                            "m_Animation":"Stand Cinematic    ",
                            "m_Sound":"Units\\Orc\\HeroBladeMaster\\HeroBladeMasterPissed3.wav",
                            "m_SpecialEffects":[
                                {
                                    "m_Effect":"Abilities\\Spells\\Human\\FlameStrike\\FlameStrikeTarget.mdl",
                                    "m_Unit":"cnCaster",
                                    "m_Attachments":[
                                        "origin"
                                    ]
                                },
                                {
                                    "m_Effect":"Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl",
                                    "m_Unit":"cnCaster",
                                    "m_Attachments":[
                                        "origin"
                                    ]
                                },
                                {
                                    "m_Effect":"Abilities\\Spells\\Items\\RitualDagger\\RitualDaggerTarget.mdl",
                                    "m_Unit":"cnTarget",
                                    "m_Attachments":[
                                        "chest"
                                    ]
                                }
                            ]
                        }
                    }
                ]
            }
        ]
    }
]

Next

Next up will be actually implementing the action types I’ll be using like deal damage and apply buff. This should go very quickly and I should be able to have my first hero implemented very soon. I think the more time consuming step is going to be connecting the output of my Hero Balance framework from the Patchwerk Prototype to this Hero Data framework. I’ll also probably want to do a quality pass over that framework to address some of the issues that played out in practice as I used it for the prototype.