• 🏆 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!

HeroicUnit - Engineering Upgrade on non-hero units!

Status
Not open for further replies.
Level 9
Joined
Jul 30, 2012
Messages
156

Heroic Unit - Units with hero capabilities


My main motivation to find this is because I'm developing a system, that makes heavy use of Engineering Upgrade. And as you all know, you cannot add Engineering Upgrade to non-hero units. If you do, it crashes the game.

I tracked down the cause of this crash: It's because Enginnering Upgrade tries to access the 'AHer' ability of a unit. Since non-heroes don't have this ability, CUnit::GetAbility returns null, and the game crashes with a null-pointer exception.

So I started to look for a way to add 'AHer' to non-hero units, and I finally got it! To do that, I morph the unit into a hero temporarily, using a powerup. As we know, this kind of morphing requires some cautions:
Comments on the hero unit type: Blizzard did not account for morphing units into heroes/backwards. Heroes benefit from hero attributes, which influence stats such as hitpoints or armor. When the unit reverts into non-hero form, those bonuses are not dissolved, so the unit ends up with false values. To counter this, just set hero attributes to zero. Also set the base armor value of heroes to 0 (gameplay constant Hero Attribute - Defense Base (before Agility Bonus)). You can equalize this by manually adding the default base armor to each hero in your map. In addition, check Values - Hero - Hide Hero Interface Icon, else the hero portrait will persist.

My system takes care of almost all of them, the only you thing you need to do is to change the Gameplay Constant above to 0.

How it works


Basically, when you morph a non-hero to a hero, 3 things happen, in this sequence:

1 - Hero attributes are applied. The unit gains the 'AHer' ability.
2 - The unit loses all non-permanent abilities and gains the abilities of the new unit type.
3 - The stats of the new unit type (such as life and movement speed) are applied.

The trick is, by using a life change event, I can intercept the morphing function in the first step, and make the unit morph back immediately. After that, the previous morphing function will be resumed, and the 'AHer' ability will be added to our unit in the non-hero form.

Adding 'AHer' to a non-hero unit causes many interesting things:

- The unit can have Hero Skills!!! After calling the function, the unit will have the Hero Skills of the hero type used to morph temporarily.
- The unit will benefit from Hero Attributes, you can and or remove abilities that modify hero stats, and they will work as expected.
- The unit can make full use of Engineering Upgrade and Tome items, without crashing
- The unit can acquire experience and even level up! Leveling up will add hero stats as they were specified in the temporary hero type.
- The unit still behaves as non-hero for spell targeting rules, and it will normally decay and be removed of the game after death
- Unfortunately some (if not all) of the JASS natives for heroes (SetHeroLevel, SetHeroStr/Agi/Int) will NOT work for this "Heroic Unit". But you can still manipulate the unit's stats and level by using Attribute Bonus and Tome of Power.

How to use it


Jass NewGen Pack is required ofc. You just need to create a trigger, convert to custom text, and add this library as the code. Pay attention to the IDs that I'm using on the textmacro. You may need to modify them to avoid conflicts with other things on your map.

JASS:
//! zinc
library HeroicUnit
{

//! textmacro CreateObjects takes HERO, SPELL, ITEM
//! external ObjectMerger w3u nech $HERO$ uabi "AInv" uhhb 1 ustr 1
//! external ObjectMerger w3a Amrf $SPELL$ Emeu 1 "$HERO$"
//! external ObjectMerger w3t tkno $ITEM$ iabi "$SPELL$"

constant integer HERO_ID = '$HERO$'; //Hero type into which the unit morphs.
constant integer SPELL_ID = '$SPELL$'; //Spell that morphs the unit into a hero temporarily.
constant integer ITEM_ID = '$ITEM$'; //Powerup that holds the morphing spell
constant integer BONUS_ID = 'AIs1'; //Stat bonus ability. Must provide exactly the same stats of the hero.
constant integer DETECTOR = 'Adef'; //Used to detect morphing. Immolation could be used too
constant integer ORDER = 852056; //Order Id for "undefend". Should be 852178 for "unimmolation"

//! endtextmacro
//! runtextmacro CreateObjects("N000", "A000", "I000")

function OnMorph() -> boolean
{
	unit u = GetTriggerUnit();
	trigger t = GetTriggeringTrigger();
	if (GetTriggerEventId() == EVENT_UNIT_STATE_LIMIT)
	{
		DisableTrigger(t);
		UnitRemoveAbility(u, SPELL_ID);
	}
	else if (GetUnitTypeId(u) != HERO_ID)
	{
		UnitAddAbility(u, SPELL_ID);
		UnitAddAbility(u, BONUS_ID);
		UnitMakeAbilityPermanent(u, true, BONUS_ID);
		TriggerRegisterUnitStateEvent(t, u, UNIT_STATE_LIFE, GREATER_THAN, GetWidgetLife(u)+1.);
		RemoveItem(UnitAddItemById(u, ITEM_ID));
	}
	else
	{
		UnitAddAbility(u, DETECTOR);
	}
	t = null;
	u = null;
	return false;
}

public function UnitMakeHeroic (unit u) -> boolean
{
	trigger t = CreateTrigger();
	real hp = GetWidgetLife(u);
	real mp = GetUnitState(u, UNIT_STATE_MANA);
	SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE));
	TriggerRegisterUnitEvent(t, u, EVENT_UNIT_ISSUED_ORDER);
	TriggerAddCondition(t, Condition(function OnMorph));
	UnitAddAbility(u, 'AInv');
	UnitAddAbility(u, DETECTOR);
	UnitAddAbility(u, BONUS_ID);
	RemoveItem(UnitAddItemById(u, ITEM_ID));
	UnitRemoveAbility(u, BONUS_ID);
	SetWidgetLife(u, hp);
	SetUnitState(u, UNIT_STATE_MANA, mp);
	SetUnitAnimation(u, "stand");
	DestroyTrigger(t);
	t = null;
	return GetUnitAbilityLevel(u, 'AHer') > 0;
}

}
//! endzinc

Then, when you want to make a unit "heroic", just call UnitMakeHeroic, and the 'AHer' ability will be magically added to your unit, giving all the effects stated above. Note that this function will not interrupt the unit's orders in any way, but it will reset the unit's current animation.

I just finished this snippet yesterday, there are still many things that must be done, such as a good tutorial and proper documentation, as well as improvements to the code. So I can say this is still a "beta" version, but it's already stable enough to be used.

I appreciate any feedback, as well as suggestions for improvement, this is my first "Library", if there are any programming conventions that I failed to follow, I'd like to know.

EDIT: Now I have attached a TestMap. In this map, the Goblin Tinker is changed to a normal unit, and he can still learn all of his hero skills, even Engineering Upgrade. Also you will note that his stats increase when leveling up.
 

Attachments

  • TestMap.w3x
    17.6 KB · Views: 233
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
That is one nasty hack you have here...
What will the UI show for these unit/hero hybrids? Will it actually show the stats? Will the unit show up in the quick slots?

I'm currently trying to get some ideas about what this could be used for.

As I stated the main motivation for me was the ability to use Engineering Upgrade with non-hero units. The other things are just "side effects", that can be nullified if you want, but may also be useful for someone else. And no, the hero stats UI (the icon with hero stats in the right) will not appear for this unit. And the F1 icon will appear if you don't set "Hide Hero Interface Icon" for the temporary hero type.
 
And no, the hero stats UI (the icon with hero stats in the right) will not appear for this unit.
This is actually pretty neat, as it allows having "hidden" hero stats for non-hero units for internal calculations (for example, in RPGs where you want creeps to be non-heroes, but still be able to have a variable level and have str/agi/int scores).
Also, you can use it to change the base armor and attack (by just applying str/agi to the unit via powerups), which is not possible without upgrades otherwise.
 
Level 9
Joined
Jul 30, 2012
Messages
156
Also, you can use it to change the base armor and attack (by just applying str/agi to the unit via powerups), which is not possible without upgrades otherwise.

Well that is already possible for heroes as you just have natives to modify attributes. The only advantage, as you said, is to have these stats hidden on these hybrid heroes.

So IsUnitType/UNIT_TYPE_HERO will not give false positives with this? Can you please attach an example map for those of us who prefer copying from object editor?

Here is the implementation of IsUnitType from game.dll
attachment.php

Basically the game determines if a unit is a hero simply by checking the first letter of the rawcode. If it's an uppercase letter, the unit is considered as a hero. Also the line "TEST DWORD PTR [ECX+5C],40000000" is an explicit check for the "Illusion" flag, that's why Illusions are not considered as heroes, because Blizzard explicitly did it this way.

So it's completely impossible to have a false positive, as the rawcode of the unit is that of a non-hero. If you want a condition that checks for both Heroes and Unit-Heroes, just check for the presence of 'AHer'.

And I've attached a testmap in the first post. :)
 

Attachments

  • IsHero.png
    IsHero.png
    129.2 KB · Views: 1,008
Well that is already possible for heroes as you just have natives to modify attributes. The only advantage, as you said, is to have these stats hidden on these hybrid heroes.
Yes, but the major difference is: we can now do this on non-heroes (or at least units that look like they aren't heroes).
 
I was tinkering with this idea a little and have a neat idea on how to use this in a fashion that allows randomly generated creeps in an RPG.

For this, I have some questions about this method:

  1. What will the UI display for the level of the unit if it is owned by Player(PLAYER_NEUTRAL_AGGRESSIVE)? The unit level or the hero level?
  2. Is there a way to get the hero level of such a heroic unit by script? I could workaround this by simply storing the level I assign in a lookup table, but it certainly doesn't hurt asking. Maybe you already found something for that.
  3. In case I can not use Agi to set the armor of the heroic unit (because I disabled that in gameplay constants), do you have any ideas how I could use this to set the base armor of a unit?
  4. Related to 3) and a possible solution: What happens if the unit has an inventory before doing the heroic unit morph? Will any temporary armor bonus (the green +X, for example from items) at this time be written into the permanent armor count (the white number) like it happens when Chaos-Morphing heroes? Any idea how to combine this bug with heroic units to allow adjusting the base armor of heroic units without adding AGI?


Basicly, my idea for randomized creeps was the following procedure:
  • Create an array of base units in object data at level 1
  • Create a table that sorts them by type (melee, ranged, caster), size (swarmer, medium, giant, boss) and domain (fire, water, ice, shadow, magical, undead, nature, etc.)
  • Create a table of abilities and sort them in the same way as the units
  • Use the heroic morph to turn the units into heroic units
  • Adjust their base damage, base armor and health/mana by a formula
  • Adjust their level via tomes
  • Add a random selection of abilities based on the type/size/property table

Basicly, almost all of this above is possible right now, except for the base armor stuff ... and the displayed unit level in the UI is still a question mark for me. It obviously would look dumb if all these units are displayed as level 1s despite obviously being much higher level.


This could be amazing for rogue-like games. I am currently thinking about combining such a random monster generator with destructable dungeon wall models and the Donjon dungeon generator algorithm.
 
Last edited:
Sorry for double posting, but I wanted this to be visible:

I just did some tests and it seems that when neutral aggressive owns the unit, it actually displays the hero level below the unit and not the unit level. This is awesome! It basicly allows dynamically setting the displayed unit level beneath the unit name.
The UI/command card of the unit will still show the unit level, though, but this only happens if the race property of the unit in the object editor is set to one of the neutral races. If you for example set it to "human", it hides the unit level as it does with all non-neutral races.

I haven't found a solution for the non-temporary armor change yet, though. Maybe I'll try using the chaos ability first.


EDIT:
Okay, it seems that this DOES indeed work. Basicly:
- you create a random hero unit (doesn't matter what it is, can be a dummy)
- set its armor and stats to 0 in OE
- apply whatever armor/attackpower you want via an ability in a trigger
- morph the unit towards the base unit for the HeroicUnit
- make the unit heroic with your function
--> unit has modified armor and damage values and can have it's level set via Tomes
--> we can now create creeps with 100% dynamically adjustable level, attack and armor values by trigger


Here's what I did in the testing map:
JASS:
function Trig_Melee_Initialization_Actions takes nothing returns nothing
    local unit u
    local item it
    //create a paladin as a hero dummy
    call CreateNUnitsAtLoc( 1, 'Hpal', Player(0), GetRectCenter(GetPlayableMapRect()), bj_UNIT_FACING )
    set u = GetLastCreatedUnit()
    //add a ring of protection to apply a "green" armor bonus to the paladin
    set it = UnitAddItemById(u, 'rde3')
    //morph the paladin into the tinker base unit via chaos ability
    call UnitAddAbility(u, 'S000')
    //unfortunately, morphing via chaos is not instant, so we need a small delay here
    call TriggerSleepAction(0)
    //remove the ring of protection again
    call UnitRemoveItem(u, it)

    //start the magic
    call UnitMakeHeroic(u)
    //add the levelup tome
    call UnitAddItemById(u, 'tkno')
endfunction


EDIT2:
However, the objectmerger lines you've used give me a LUA error:
 

Attachments

  • ObjectmergerError.jpg
    ObjectmergerError.jpg
    54.7 KB · Views: 147
  • TestMap.w3x
    18 KB · Views: 125
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
However, the objectmerger lines you've used give me a LUA error:

FINALLY! I'm glad to see that I'm not the only one who gets this error.
I'd really like to know WHY I CAN'T USE LUA INSIDE TEXTMACROS???

I've seen many code snippets in TheHelper that use Lua scripts inside a textmacro. But when I try to do that, it always gives me this error.

And do you know why?

I had a REALLY hard job finding out why. I simply couldn't understand those error messages. Were I doing something wrong?

No, the problem is Jasshelper! The problem is that Jasshelper parses textmacros before Lua scripts. And for all textmacros, Jasshelper will silently add a line comment to EVERY LINE of the textmacro.

After all textmacros are evaluated, Lua scripts will run. And those comments will break Lua scripts! (Lua does not use // as comment operator)

The worst part about it is that apparently I was the only person in the world getting this error. I found plenty of libraries and snippets that were using Lua inside textmacros, and no one was complaining about errors.

It seemed there was absolutely nothing I could do. Well, at least not without going insane. So I simply decided to do what I do best: cracking executables.

I just loaded jasshelper.exe in debugger, analyzed it, and manually patched it to prevent the insertion of those comments. After that, my Lua scripts worked perfectly.

The bad thing is, I shouldn't really need to do that. Somebody has obviously done something very wrong, either with jasshelper, or with ObjectMerger. And how those snippets on TheHelper could work? Were they using a different version of JNGP?

All I can do for now, is uploading my modified version of jasshelper.exe. I will do that tomorrow morning, as soon as I get on my computer. It will certainly work for you.
 
FINALLY! I'm glad to see that I'm not the only one who gets this error.
I'd really like to know WHY I CAN'T USE LUA INSIDE TEXTMACROS???

I've seen many code snippets in TheHelper that use Lua scripts inside a textmacro. But when I try to do that, it always gives me this error.

And do you know why?

I had a REALLY hard job finding out why. I simply couldn't understand those error messages. Were I doing something wrong?

No, the problem is Jasshelper! The problem is that Jasshelper parses textmacros before Lua scripts. And for all textmacros, Jasshelper will silently add a line comment to EVERY LINE of the textmacro.

After all textmacros are evaluated, Lua scripts will run. And those comments will break Lua scripts! (Lua does not use // as comment operator)

The worst part about it is that apparently I was the only person in the world getting this error. I found plenty of libraries and snippets that were using Lua inside textmacros, and no one was complaining about errors.

It seemed there was absolutely nothing I could do. Well, at least not without going insane. So I simply decided to do what I do best: cracking executables.

I just loaded jasshelper.exe in debugger, analyzed it, and manually patched it to prevent the insertion of those comments. After that, my Lua scripts worked perfectly.

The bad thing is, I shouldn't really need to do that. Somebody has obviously done something very wrong, either with jasshelper, or with ObjectMerger. And how those snippets on TheHelper could work? Were they using a different version of JNGP?

All I can do for now, is uploading my modified version of jasshelper.exe. I will do that tomorrow morning, as soon as I get on my computer. It will certainly work for you.
I haven't tried both versions of jasshelper in Newgen 2.0.7 yet... maybe Cohadars Jasshelper compiles it right? I've always been using Vexorians until now.

But it doesn't matter really. You can always just write a LUA function instead of a textmacro. Those work fine and do mostly the same things. You can use them to generate all the object data at least. For creating the global variables, well, that's a bit more complicated and requires some imported LUA scripts.
 
Level 9
Joined
Jul 30, 2012
Messages
156
As I told you, here is the jasshelper I'm using. I got the same error both with Cohadar's and Vexorian's Jasshelper. They both put comments on the textmacros, and ObjectMerger.exe fails when those comments are passed in the command line. This one solves the problem, I just removed 2 function calls, so that it does not place any comments in text macros.
 

Attachments

  • jasshelper.zip
    393.9 KB · Views: 114
As I told you, here is the jasshelper I'm using. I got the same error both with Cohadar's and Vexorian's Jasshelper. They both put comments on the textmacros, and ObjectMerger.exe fails when those comments are passed in the command line. This one solves the problem, I just removed 2 function calls, so that it does not place any comments in text macros.
You should definitely try to find a way that doesn't require a modified jasshelper, you know... ;)
Just so that people can actually use that...


In the end, the whole thing can be combined (with the chaos trick I posted and the SetUnitMaxState snippet) into a system that allows for this beauty:
call CreateUnitWithProperties(raw, x, y, facing, level, maxlife, maxmana, basearmor, baseattack)

... in fact, I have already begun coding that. It's the wet dream of anyone interested in procedural generated content, since base armor/attack and displayed level were the only things completely impossible to modify via script until now.

Here's another cool thing:
Your "heroic unit" approach basicly allows "repairing" a unit that got "damaged" from a hero->nonhero morph. Normally, when you morph a hero to a nonhero, anything that affects it's stats in any way (or using an item in the inventory) will crash the game. This seems not to be the case for a hero that got morphed into a "heroic unit", since all stats are still 100% functional.

So basicly, we can do kinky chaos morph tricks to adjust base armor and attack stats at any point of the game if we repair the unit afterwards by making it a heroic unit again:

- morph the heroic unit via chaos into a dummy hero
- adjust all armor/attack bonuses via dummy abilities
- morph the heroic unit into the base heroic unit
- use MakeUnitHeroic to turn it back into a (visually) normal unit
 
Last edited:
Level 9
Joined
Jul 30, 2012
Messages
156
You should definitely try to find a way that doesn't require a modified jasshelper, you know... ;)
Just so that people can actually use that...


In the end, the whole thing can be combined (with the chaos trick I posted and the SetUnitMaxState snippet) into a system that allows for this beauty:
call CreateUnitWithProperties(raw, x, y, facing, level, maxlife, maxmana, basearmor, baseattack)

... in fact, I have already begun coding that. It's the wet dream of anyone interested in procedural generated content, since base armor/attack and displayed level were the only things completely impossible to modify via script until now.

Here's another cool thing:
Your "heroic unit" approach basicly allows "repairing" a unit that got "damaged" from a hero->nonhero morph. Normally, when you morph a hero to a nonhero, anything that affects it's stats in any way (or using an item in the inventory) will crash the game. This seems not to be the case for a hero that got morphed into a "heroic unit", since all stats are still 100% functional.

So basicly, we can do kinky chaos morph tricks to adjust base armor and attack stats at any point of the game if we repair the unit afterwards by making it a heroic unit again:

- morph the heroic unit via chaos into a dummy hero
- adjust all armor/attack bonuses via dummy abilities
- morph the heroic unit into the base heroic unit
- use MakeUnitHeroic to turn it back into a (visually) normal unit

That's a very interesting idea indeed. I never paid much attention to this project, in the beginning I saw it only as a means to use Engineering Upgrade with units, but now I see there are many possibilities that this brings.

I'd like to know how exactly do you plan to use this library. Will you be calling MakeUnitHeroic in the middle of game, or only when creating the unit? How are you going to add abilities to your creeps? With triggers, or directly in the Object Editor?

The current implementation is using powerups to morph the unit, but this is only because powerups don't interrupt the unit's orders. Since you're using Chaos before calling it, and Chaos already stuns the unit for a brief moment, we don't need to care about that.

Also as you can see I'm morphing the unit 4 times, that's because the unit loses all abilities if I don't do that. But if you're going to add abilities with triggers, that's not necessary.

And I just found a very interesting trick for you: make a Chaos ability and set the alternate unit type to a blank string! (The World Editor won't allow you to type a blank string, so the solution is going to the Raise Dead ability, select the "Level 1 - Unit Type Two" field, do a Ctrl-C, and paste it on chaos)

Now when you add this blank chaos to a hero, or an heroic unit, it will copy the green stats to the base stats, without morphing! You can use that trick to adjust your creeps stats at any time, without the need to call MakeUnitHeroic again.

Regarding your previous question, there's no direct way to retrieve the HeroicUnit's level, or any of the hidden Hero attributes. None of the JASS natives for heroes work with heroic units. However, if you morph the heroic unit into a real hero, that hero will keep all the current attributes of the heroic unit, and you'll be able to read them. (Of course you'll need to unmorph and make the unit heroic again)

So, I'll be doing some more tests with this thing. If you have any more questions or suggestions, let me know. And btw, did you find that stun snippet you talked about? It could be really useful for me.
 
I'd like to know how exactly do you plan to use this library. Will you be calling MakeUnitHeroic in the middle of game, or only when creating the unit? How are you going to add abilities to your creeps? With triggers, or directly in the Object Editor?
Well, the idea is to generate units completely by script, including abilities.
So, yes, I basicly only want to use UnitMakeHeroic upon creation to set the level of the creep and allow chaos morphs to change armor/attack.

The current implementation is using powerups to morph the unit, but this is only because powerups don't interrupt the unit's orders. Since you're using Chaos before calling it, and Chaos already stuns the unit for a brief moment, we don't need to care about that.
Yeah I figured that already. Could you make a "light" version that uses the least amount of object data possible?
Since I only want to adjust the values directly upon creation, I don't really care about any orders interrupted or stuns applied for a brief second.

Also as you can see I'm morphing the unit 4 times, that's because the unit loses all abilities if I don't do that. But if you're going to add abilities with triggers, that's not necessary.
Yes, the idea was that I add abilities via script, so that isn't needed either!

And I just found a very interesting trick for you: make a Chaos ability and set the alternate unit type to a blank string! (The World Editor won't allow you to type a blank string, so the solution is going to the Raise Dead ability, select the "Level 1 - Unit Type Two" field, do a Ctrl-C, and paste it on chaos)
This is nice! Does that only work for "true" heroes or even for heroic units? Because I've found the chaos ability trick to adjust armor only working on real heroes when I tried an alternate unit defined.
This would get rid of A LOT of object data.

Regarding your previous question, there's no direct way to retrieve the HeroicUnit's level, or any of the hidden Hero attributes. None of the JASS natives for heroes work with heroic units. However, if you morph the heroic unit into a real hero, that hero will keep all the current attributes of the heroic unit, and you'll be able to read them. (Of course you'll need to unmorph and make the unit heroic again)
Not really needed anyway. Since you need script to set the level anyways, you always know what it is and can just store it in a table to retrieve it.
It's just a minor inconvenience, not really a showstopper.

So, I'll be doing some more tests with this thing. If you have any more questions or suggestions, let me know. And btw, did you find that stun snippet you talked about? It could be really useful for me.
If you could do a "HeroicUnit Light" with the minimum amount of object data (best case scenario: one unit/ability for all morphs total instead of one for each unit type), that would be awesome!

About the stun system: I was wrong; the system I'm using uses an instant firebolt with it's required level set to 6 (to circumvent spell immunity) and a projectile speed of 0 (to make the missile hit the target instantly), combined with a dummy with 0 cast point and backswing point to make the missile fire instantly.
--> The missile will hit before the next line of code
 
Level 19
Joined
Dec 12, 2010
Messages
2,069
About the stun system: I was wrong; the system I'm using uses an instant firebolt with it's required level set to 6 (to circumvent spell immunity) and a projectile speed of 0 (to make the missile hit the target instantly), combined with a dummy with 0 cast point and backswing point to make the missile fire instantly.
--> The missile will hit before the next line of code
exactly why I wonder about it. this stuff had never kicked me down. in case if you need non-penetrating stun you could as well check magic immune type before firing
 
Status
Not open for further replies.
Top