Construct Quarter – Part 3: First Heroes


Construct Quarter Project Page

New Heroes

After creating a hero framework, the next step was actually creating some heroes. I finished the Blademaster, Beastmaster, and Holy Priest. I set up the framework to support different types of actions that spells could execute when cast but had not yet actually implemented those actions. I therefore had to implement actions like damage and healing as they became needed for spells being added. The benefit of this system is that once these actions are implemented once, they can easily be re-used for future spells.

Hero Balancer

Up to this point, the ability values for things like damage and healing were just placeholder. In my previous project, I already figured out the logic I wanted to use to balance ability values. I thought connecting this system to my hero framework would be difficult but it wasn’t bad at all. Although my initial plan was for this to output json that could be consumed by the main project, there were problems with deseralization at runtime so I instead set up the balancer to output a header of input data for the main project. The header backing the hero abilities in the above video is included below. It’s pretty hard to read, but that’s because its being automatically generated.

public class GeneratedData{
    static public readonly InputData.Unit[] m_GeneratedUnits = {
        new InputData.Unit(
            @"O000",
            new HeroData.AttackData(300, 2.5f, 15),
            new HeroData.StatData(10000, 2000, 2, 2, 1.4929658f),
            new InputData.Ability[]{
                new InputData.Ability(
                    @"HBL0",
                    new HeroData.AbilityData(7.0f, 100.0f, 0.0f, 0.0f, 0.75f, 1, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)2,
                            new string[]{@"58.333332", @"1", @"0"},
                            null,
                            @"cnTarget",
                            new HeroData.ArtData(@"", @"", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Abilities\Spells\NightElf\Cyclone\CycloneTarget.mdl", @"cnTarget", new string[]{@"origin"}, 0.75f, 1.25f)}),
                            null,
                            null
                         )
                    },
                    new HeroData.RealOverride[]{new HeroData.RealOverride(@"Cri2", 0.20927057f), new HeroData.RealOverride(@"Cri3", 0.0f)}
                ),
                new InputData.Ability(
                    @"HBL1",
                    new HeroData.AbilityData(0.0f, 100.0f, 0.0f, 0.0f, 0.0f, 0, 2),
                    null,
                    new HeroData.RealOverride[]{new HeroData.RealOverride(@"Eev1", 0.04110672f)}
                ),
                new InputData.Ability(
                    @"HBL2",
                    new HeroData.AbilityData(10.0f, 500.0f, 0.0f, 0.5f, 0.0f, 1, 1),
                    null,
                    null
                ),
                new InputData.Ability(
                    @"HBL3",
                    new HeroData.AbilityData(14.0f, 128.0f, 0.0f, 0.0f, 0.0f, 2, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)0,
                            null,
                            null,
                            null,
                            new HeroData.ArtData(@"", @"Abilities\Spells\Orc\MirrorImage\MirrorImage.flac", null),
                            null,
                            null
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)3,
                            new string[]{@"gnMirrorImage", @"1985.4545", @"0", @"0.4"},
                            new HeroData.ActionDelay(0.5f, false, 0),
                            null,
                            null,
                            null,
                            null
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)4,
                            new string[]{@"HBL4", @"taunt"},
                            new HeroData.ActionDelay(0.51f, false, 0),
                            @"gnMirrorImage",
                            null,
                            null,
                            null
                         )
                    },
                    null
                ),
                new InputData.Ability(
                    @"HBL5",
                    new HeroData.AbilityData(28.0f, 100.0f, 0.0f, 0.0f, 3.5f, 3, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)5,
                            new string[]{@"1323.6365"},
                            null,
                            @"cnCaster",
                            new HeroData.ArtData(@"Attack Walk Stand Spin 1", @"Abilities\Spells\Other\Tornado\TornadoLoop.flac", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Abilities\Spells\Other\Tornado\TornadoElementalSmall.mdl", @"cnCaster", new string[]{@"origin"}, 0.6f, 2.4f)}),
                            null,
                            null
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)2,
                            new string[]{@"48.125", @"1", @"0"},
                            new HeroData.ActionDelay(1.1f, false, 3),
                            @"cnTarget",
                            new HeroData.ArtData(@"", @"", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Abilities\Spells\Other\Monsoon\MonsoonBoltTarget.mdl", @"cnTarget", new string[]{@"origin"}, 1.0f, 0.0f)}),
                            null,
                            null
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)5,
                            new string[]{@"873.6001"},
                            new HeroData.ActionDelay(1.1f, false, 3),
                            @"cnCaster",
                            new HeroData.ArtData(@"", @"", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Abilities\Spells\Other\Drain\DrainCaster.mdl", @"cnCaster", new string[]{@"chest"}, 0.75f, 0.9f)}),
                            null,
                            null
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)2,
                            new string[]{@"48.125", @"1", @"0"},
                            new HeroData.ActionDelay(1.1f, true, 3),
                            @"cnTarget",
                            new HeroData.ArtData(@"", @"", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Abilities\Spells\Human\Thunderclap\ThunderClapCaster.mdl", @"cnTarget", new string[]{@"origin"}, 1.0f, 0.0f)}),
                            new InputData.Expression[]{new InputData.Expression(@"fnHasBuff", @"cnTarget.BBL0")},
                            null
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)5,
                            new string[]{@"873.6001"},
                            new HeroData.ActionDelay(1.1f, true, 3),
                            @"cnCaster",
                            new HeroData.ArtData(@"", @"", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Abilities\Spells\Items\AIhe\AIheTarget.mdl", @"cnCaster", new string[]{@"chest"}, 1.0f, 0.9f)}),
                            new InputData.Expression[]{new InputData.Expression(@"fnHasBuff", @"cnTarget.BBL0")},
                            null
                         )
                    },
                    null
                )
            }
        ),
        new InputData.Unit(
            @"N000",
            new HeroData.AttackData(1000, 2.5f, 8),
            new HeroData.StatData(6000, 2000, 2, 2, 1.0f),
            new InputData.Ability[]{
                new InputData.Ability(
                    @"HBM0",
                    new HeroData.AbilityData(6.3f, 1000.0f, 0.0f, 0.0f, 0.0f, 2, 2),
                    null,
                    new HeroData.RealOverride[]{new HeroData.RealOverride(@"Hfa1", 14.189189f)}
                ),
                new InputData.Ability(
                    @"HBM1",
                    new HeroData.AbilityData(9.0f, 1000.0f, 0.0f, 0.5f, 0.0f, 2, -1),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)3,
                            new string[]{@"gnMisha", @"500", @"19.707205", @"2.5"},
                            new HeroData.ActionDelay(0.5f, false, 0),
                            null,
                            null,
                            null,
                            null
                         )
                    },
                    null
                ),
                new InputData.Ability(
                    @"HBM2",
                    new HeroData.AbilityData(21.0f, 1000.0f, 0.0f, 0.0f, 0.2f, 1, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)2,
                            new string[]{@"236.48648", @"1", @"1"},
                            null,
                            @"cnTarget",
                            new HeroData.ArtData(@"Spell Slam 1", @"Units\Creeps\\OgreYesAttack2.flac", new HeroData.SpecialEffect[]{new HeroData.SpecialEffect(@"Objects\Spawnmodels\Human\HumanLargeDeathExplode\HumanLargeDeathExplode.mdl", @"cnTarget", new string[]{@"chest"}, 1.0f, 0.0f)}),
                            new InputData.Expression[]{new InputData.Expression(@"fnUnitExists", @"gnMisha")},
                            null
                         )
                    },
                    null
                ),
                new InputData.Ability(
                    @"HBM3",
                    new HeroData.AbilityData(63.0f, 1000.0f, 0.0f, 0.0f, 0.0f, 3, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)4,
                            new string[]{@"HBM4", @"berserk", @"bsk2", @"0.9000001"},
                            null,
                            @"gnMisha",
                            null,
                            new InputData.Expression[]{new InputData.Expression(@"fnUnitExists", @"gnMisha")},
                            null
                         )
                    },
                    new HeroData.RealOverride[]{new HeroData.RealOverride(@"bsk2", 2.2170608f)}
                )
            }
        ),
        new InputData.Unit(
            @"N001",
            new HeroData.AttackData(1000, 2.5f, 100),
            new HeroData.StatData(6000, 5000, 2, 5, 1.0f),
            new InputData.Ability[]{
                new InputData.Ability(
                    @"HHP0",
                    new HeroData.AbilityData(5.64f, 500.0f, 299.66614f, 0.0f, 0.0f, 2, 2),
                    null,
                    new HeroData.RealOverride[]{new HeroData.RealOverride(@"Hea1", 1845.8182f)}
                ),
                new InputData.Ability(
                    @"HHP1",
                    new HeroData.AbilityData(7.52f, 500.0f, 725.4959f, 0.0f, 0.0f, 1, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)5,
                            new string[]{@"2695.4807"},
                            null,
                            @"cnTarget",
                            null,
                            null,
                            null
                         )
                    },
                    null
                ),
                new InputData.Ability(
                    @"HHP2",
                    new HeroData.AbilityData(10.254545f, 500.0f, 1118.3533f, 0.0f, 0.0f, 0, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)5,
                            new string[]{@"4155.089"},
                            null,
                            @"cnTarget",
                            null,
                            null,
                            null
                         )
                    },
                    null
                ),
                new InputData.Ability(
                    @"HHP3",
                    new HeroData.AbilityData(112.8f, 500.0f, 0.0f, 0.0f, 0.0f, 3, 2),
                    new InputData.AbilityAction[]{
                        new InputData.AbilityAction(
                            (HeroData.ActionType)0,
                            null,
                            null,
                            null,
                            null,
                            null,
                            new InputData.Expression[]{new InputData.Expression(@"gnGuardianSpiritTarget", @"cnTarget")}
                         ),
                        new InputData.AbilityAction(
                            (HeroData.ActionType)0,
                            null,
                            new HeroData.ActionDelay(8.1f, false, 0),
                            null,
                            null,
                            null,
                            new InputData.Expression[]{new InputData.Expression(@"gnGuardianSpiritTarget", @"null")}
                         )
                    },
                    new HeroData.RealOverride[]{new HeroData.RealOverride(@"ahdu", 8.0f), new HeroData.RealOverride(@"adur", 8.0f)}
                )
            }
        )
    };
}

Next

I need to add more heroes up to whatever a full team is going to be. I’m currently thinking 8 heroes for a team. That would be 2 tanks, 2 healers, and 4 dps. When I have a full team, I’ll implement the Patchwerk fight. Once I have the full team fighting Patchwerk, I’ll implement the environment and the rest of the bosses. After that, I might add more heroes to let you choose a team, but I also might just call it there.