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

CreateUnitWithProperties (and improved HeroicUnit)

Status
Not open for further replies.

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
This snippet works just like CreateUnit(), but also allows setting the unit level, base (= "white") armor and base (= "white") attack.

CreateUnitWithProperties(player, rawcode, x, y, facing, attack, armor, level)

It does so by using the HeroicUnit exploit found by Lean and applying a neat chaos morph trick on top of it to set the base armor of a unit.
It will then apply leveling tomes to set the level of the unit (the HeroicUnit exploit allows that) and use attack tomes to set the damage.

There are three requirements for the units created, though:
- The base attack damage of the unit in the object editor must be 0 (attack dice and dice sides doesn't matter)
- The base armor of the unit in the object editor must be 0
- The unit must have the hero inventory ability (AInv)

One important thing to know, though:
GetUnitLevel and GetHeroLevel will not return the correct value for units created with this snippet. The former will return the level defined in the object editor and the latter will always return 0.
But you can always keep track of the unit levels via a simple table.

JASS:
library UnitWithProperties uses HeroicUnit

//Please enter 3 unused rawcodes abilities and two unused item rawcodes below:
//! runtextmacro CreatePropertyObjects("A001", "A002", "A003", "I001", "I002")

//Now save & restart the editor, then search for the "PropertyMorph" ability and replace the unit field with an empty unit
//(the editor won't allow entering no unit, but you can copy and paste "empty" data from the raise dead ability level 2 field)

//! textmacro CreatePropertyObjects takes ABIL, ARMOR, ATTACK, ITEM, LEVELS
//! external ObjectMerger w3a Sca1 $ABIL$ anam "PropertyMorph" achd 0 areq ""
//! external ObjectMerger w3a AId1 $ARMOR$ aite 0 alev 99 Idef 1 1 anam "PropertyArmorBonus"
//! external ObjectMerger w3a AIaa $ATTACK$ Iaa1 "1" anam "PropertyAttackBonus"
//! external ObjectMerger w3t tkno $ITEM$ iabi $ATTACK$ ipow 0 iuse 0 iper 0 unam "ProperyAttackTome"
//! external ObjectMerger w3t tkno $LEVELS$ ipow 0 iuse 0 iper 0 unam "ProperyLevelTome"

globals
  private hashtable TimerHash = InitHashtable()
  private constant real DELAY = 0.5 //Adjusts the delay needed for the morph ability to finish

  private constant integer ABIL_ID = '$ABIL$' //Morph ability with empty unit field to convert green armor stat
  private constant integer ARMOR_ID = '$ARMOR$' //Armor bonus ability
  private constant integer ATTACK_ID = '$ATTACK$' //Attack bonus ability
  private constant integer ITEM_ID = '$ITEM$' //Attack bonus tome
  private constant integer LEVELS_ID = '$LEVELS$' //Level bonus tome
endglobals
//! endtextmacro

private function callback takes nothing returns nothing
  local timer t = GetExpiredTimer()
  local unit u = LoadUnitHandle(TimerHash, GetHandleId(t), 0)
  local item it
  local integer attack = LoadInteger(TimerHash, GetHandleId(t), 1)
  call UnitRemoveAbility(u, ARMOR_ID)

  set it = CreateItem(ITEM_ID,0,0)
  call UnitAddItem(u, it)
  loop
  exitwhen attack <= 0
  call UnitUseItem(u, it)
  set attack = attack - 1
  endloop
  call UnitRemoveItem(u, it)
  call RemoveItem(it)
  call FlushChildHashtable(TimerHash, GetHandleId(t))
  call DestroyTimer(t)
  set t = null
  set u = null
  set it = null
endfunction

function CreateUnitWithProperties takes player id, integer unitid, real x, real y, real face, integer attack, integer armor, integer level returns unit
  local integer i = 0
  local unit u = CreateUnit(id, unitid, x, y, face)
  local item it = CreateItem(LEVELS_ID,0,0)
  local timer t = CreateTimer()
  call UnitMakeHeroic(u)
  call UnitAddItem(u, it)
  loop
  exitwhen i >= level-1
  call UnitUseItem(u, it)
  set i = i + 1
  endloop
  call UnitRemoveItem(u, it)
  call RemoveItem(it)
  if armor > 0 then
  call UnitAddAbility(u, ARMOR_ID)
  call SetUnitAbilityLevel(u, ARMOR_ID, armor)
  endif
  call UnitAddAbility(u, ABIL_ID)
  call TimerStart(t, DELAY, false, function callback)
  call SaveUnitHandle(TimerHash, GetHandleId(t), 0, u)
  call SaveInteger(TimerHash, GetHandleId(t), 1, attack+1)
  set t = null
  return u
endfunction

endlibrary


The main purpose of this snippet is to create creeps of any level without requiring a bulk of object data. You can use SetUnitMaxState snippet to adjust the life and mana values of created units.
 
Last edited:
Level 5
Joined
Sep 6, 2010
Messages
91
Good contribution, this could be complemented well with Memory by leandrotp , perhaps with that method can alter the levels of the Non Hero unit, and could handle armor and attack values.
The main purpose of this snippet is to create creeps of any level without requiring a bulk of object data. You can use SetUnitMaxState snippet to adjust the life and mana values of created units.

For my part, I did not know you could alter the maximum mana and life with SetUnitMaxState, thanks for the info and the system.
Greetings...
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
These are the first few lines on my Unit.create() method:
JASS:
        local thistype this = .allocate()
       
        set .shadow = CreateImage(.DUMMY_SHADOW_IMAGE, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0)
        call DestroyImage(.shadow)
        set .ref = CreateUnit(owner, unitType.rawcode, x, y, face)
See how I dont null .shadow?
It allows me to have the shadow of a unit as image pointer.
So I can hide it, remove it, replace it, etc.

Also, I dont know how you morph the unit.
But it might be an idea to use reverse morphing.
It will morph instantly.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Good contribution, this could be complemented well with Memory by leandrotp , perhaps with that method can alter the levels of the Non Hero unit, and could handle armor and attack values.


For my part, I did not know you could alter the maximum mana and life with SetUnitMaxState, thanks for the info and the system.
Greetings...
There is no synergy with the memory method on leandrotp, simply because memory can do all that much easier and quicker.
The purpose of making this snippet was avoiding potentially unsafe memory editing that might get fixed eventually.


These are the first few lines on my Unit.create() method:
JASS:
        local thistype this = .allocate()
   
        set .shadow = CreateImage(.DUMMY_SHADOW_IMAGE, 1, 1, 1, 0, 0, 0, 1, 1, 1, 0)
        call DestroyImage(.shadow)
        set .ref = CreateUnit(owner, unitType.rawcode, x, y, face)
See how I dont null .shadow?
It allows me to have the shadow of a unit as image pointer.
So I can hide it, remove it, replace it, etc.

Also, I dont know how you morph the unit.
But it might be an idea to use reverse morphing.
It will morph instantly.
If someone needs control over shadows, then he can just replace the CreateUnit call with yours. I see no reason to hardcode that into this snippet. It's just a snippet, after all.

And I don't morph the unit beyond what HeroicUnit already does.

There is no reverse morphing with the method I use, simply because there is no alternate unit in the first place. I'm just abusing a bug with chaos without actually morphing the unit.


Btw, here's a demo map.

I took the liberty to make some adjustments to the textmacro of HeroicUnit, simply because it was missing some object data fields.
 

Attachments

  • CreateUnitWithProperty.w3x
    21.4 KB · Views: 94
Last edited:

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
Here is one extra requirement I didn't notice before: It requires the STR stat to actually grant at least 1 life in gameplay constants. It crashes otherwise, since, the HeroicUnit exploit no longer works.

Not a big bonerkill, since you can always subtract the extra life gained from STR afterwards, but depending on what you do in your map, that might be more or less complicated.


I also highly recommend to replace the armor ability with BonusMod if you use this in your map.
 

Zwiebelchen

Hosted Project GR
Level 35
Joined
Sep 17, 2009
Messages
7,236
It's fire and ice, to be honest. You have to decide between spamming object data or spamming tomes (btw, "spamming tomes" is misleading lingo here, because there is only 1 tome created; it's a permanent tome that is just used multiple times on the unit). I honestly can't tell which is worse. I prefer having clean object data over not having to spam tomes, tbh, that's why I designed it like that.

Of course, you can just edit the script to allow for base-2 tomes if you prefer that over my approach.

After all, this is a working demo code, not a full-fledged system. I use it in it's current form in Gaias though and know that it works well and don't have any issues with it. And if it aint broken, why fix it?
 
Last edited:
I'm wondering why this code doesn't seem to work, would appreciate any help on this.


[jass=]
library HeroicUnit
//Please enter unused rawcodes for a human hero (starts with capital "H"), an ability and an item below:
///! runtextmacro CreateHeroicObjects("H0Z0", "A0Z0", "I0Z0")

///! textmacro CreateHeroicObjects takes HERO, SPELL, ITEM
///! external ObjectMerger w3u Hpal $HERO$ uabi "AInv" uhhb 1 udef 0 uint 0 uagi 0 ustr 1 uagp 0 uinp 0 ustp 0
///! external ObjectMerger w3a Amrf $SPELL$ Emeu 1 $HERO$ anam "HeroicMorph"
///! external ObjectMerger w3t tkno $ITEM$ iabi $SPELL$ ipow 1 iuse 0 iper 1 unam "HeroicMorphTome"
globals
private constant integer HERO_ID = 'H0Z0' //Dummy Hero into which the unit morphs.
private constant integer SPELL_ID = 'A0Z0' //Spell that morphs the unit into a hero temporarily.
private constant integer ITEM_ID = 'I0Z0' //Powerup that holds the morphing spell
private constant integer BONUS_ID = 'AIs1' //Stat bonus ability. Must provide exactly the same stats of the hero.
private constant integer DETECTOR = 'Adef' //Used to detect morphing. Immolation could be used too
endglobals
///! endtextmacro

function OnMorph takes nothing returns boolean
local unit u = GetTriggerUnit()
local trigger t = GetTriggeringTrigger()
if GetTriggerEventId() == EVENT_UNIT_STATE_LIMIT then
call DisableTrigger(t)
call UnitRemoveAbility(u, SPELL_ID)
elseif GetUnitTypeId(u) != HERO_ID then
call UnitAddAbility(u, SPELL_ID)
call UnitAddAbility(u, BONUS_ID)
call UnitMakeAbilityPermanent(u, true, BONUS_ID)
call TriggerRegisterUnitStateEvent(t, u, UNIT_STATE_LIFE, GREATER_THAN, GetWidgetLife(u)+1.)
call RemoveItem(UnitAddItemById(u, ITEM_ID))
else
call UnitAddAbility(u, DETECTOR)
endif
set t = null
set u = null
return false
endfunction
function UnitMakeHeroic takes unit u returns boolean
local trigger t = CreateTrigger()
local real hp = GetWidgetLife(u)
local real mp = GetUnitState(u, UNIT_STATE_MANA)
call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
call TriggerRegisterUnitEvent(t, u, EVENT_UNIT_ISSUED_ORDER)
call TriggerAddCondition(t, Condition(function OnMorph))
call UnitAddAbility(u, 'AInv')
call UnitAddAbility(u, DETECTOR)
call UnitAddAbility(u, BONUS_ID)
call RemoveItem(UnitAddItemById(u, ITEM_ID))
call UnitRemoveAbility(u, BONUS_ID)
call SetUnitAnimation(u, "stand")
call DestroyTrigger(t)
set t = null
return GetUnitAbilityLevel(u, 'AHer') > 0
endfunction
endlibrary
[/code]
[jass=]
library UnitWithProperties uses HeroicUnit
//Please enter 3 unused rawcodes abilities and two unused item rawcodes below:
///! runtextmacro CreatePropertyObjects("A0Z1", "A0Z2", "A0Z3", "I0Z1", "I0Z2")
//Then search for the "PropertyMorph" ability and replace the unit field with an empty unit
//(the editor won't allow entering no unit, but you can copy and paste "empty" data from the raise dead ability level 2 field)
///! textmacro CreatePropertyObjects takes ABIL, ARMOR, ATTACK, ITEM, LEVELS
///! external ObjectMerger w3a Sca1 $ABIL$ anam "PropertyMorph" achd 0 areq ""
///! external ObjectMerger w3a AId1 $ARMOR$ aite 0 alev 99 Idef 1 1 anam "PropertyArmorBonus"
///! external ObjectMerger w3a AIaa $ATTACK$ Iaa1 "1" anam "PropertyAttackBonus"
///! external ObjectMerger w3t tkno $ITEM$ iabi $ATTACK$ ipow 0 iuse 0 iper 0 unam "ProperyAttackTome"
///! external ObjectMerger w3t tkno $LEVELS$ ipow 0 iuse 0 iper 0 unam "ProperyLevelTome"
globals
hashtable TimerHash = InitHashtable()
private constant real DELAY = 0.5 //Adjusts the delay needed for the morph ability to finish

private constant integer ABIL_ID = 'A0Z1' //Morph ability with empty unit field to convert green armor stat
private constant integer ARMOR_ID = 'A0Z2' //Armor bonus ability
private constant integer ATTACK_ID = 'A0Z3' //Attack bonus ability
private constant integer ITEM_ID = 'I0Z1' //Attack bonus tome
private constant integer LEVELS_ID = 'I0Z2' //Level bonus tome
endglobals
///! endtextmacro
private function callback takes nothing returns nothing
local timer t = GetExpiredTimer()
local unit u = LoadUnitHandle(TimerHash, GetHandleId(t), 0)
local item it
local integer attack = LoadInteger(TimerHash, GetHandleId(t), 1)
call UnitRemoveAbility(u, ARMOR_ID)

set it = CreateItem(ITEM_ID,0,0)
call UnitAddItem(u, it)
loop
exitwhen attack <= 0
call UnitUseItem(u, it)
set attack = attack - 1
endloop
call UnitRemoveItem(u, it)
call RemoveItem(it)
call FlushChildHashtable(TimerHash, GetHandleId(t))
call DestroyTimer(t)
set t = null
set u = null
set it = null
endfunction
function CreateUnitWithProperties takes player id, integer unitid, real x, real y, real face, integer attack, integer armor, real life, real mana, integer level returns unit
local integer i = 0
local unit u = CreateUnit(id, unitid, x, y, face)
local item it = CreateItem(LEVELS_ID,0,0)
local timer t = CreateTimer()
call UnitMakeHeroic(u)
call UnitAddItem(u, it)
loop
exitwhen i >= level-1
call UnitUseItem(u, it)
set i = i + 1
endloop
call UnitRemoveItem(u, it)
call RemoveItem(it)
if armor > 0 then
call UnitAddAbility(u, ARMOR_ID)
call SetUnitAbilityLevel(u, ARMOR_ID, armor)
endif
call UnitAddAbility(u, ABIL_ID)
call TimerStart(t, DELAY, false, function callback)
call SaveUnitHandle(TimerHash, GetHandleId(t), 0, u)
call SaveInteger(TimerHash, GetHandleId(t), 1, attack+1)
set t = null
return u
endfunction
endlibrary
[/code]
 
Level 9
Joined
Jul 30, 2012
Messages
156
It's been a long time since when I last messed with HeroicUnit. I even made an updated version, but never published it. Here it is:

JASS:
//! zinc
library HeroicUnit
{
// ============================== CONSTANTS ================================

/* This is used for the life change event. It must
be 1 unit greater than the base HP of the hero.*/
constant real THRESHOLD = 300.1;

/*After making the unit heroic, it will have the attributes of the hero type used to
morph. This ability is provided as a helper, if you want the heroic unit to have all
attributes set to 0. Just set it to provide a stat bonus equal to the hero's starting
stats. In this example the hero's stats are (1,0,0) and I'm using "Strength Bonus +1" as
helper. If you don't need this feature just set this to 0*/
constant integer BONUS = 'AIs1';

// ================================ OBJECTS ================================

//! textmacro CreateObjects takes HERO, DC, BUR
constant integer DARK_CONVERSION = '$DC$'; //Dark Conversion morphs the unit into a hero
constant integer BURROW = '$BUR$'; //Burrow makes the unit morph back


//* After creating the objects, delete the 1st "/" of this line.

//! JASSHELPER BUG! - This line must be here or the map doesn't compile
//! externalblock extension=lua ObjectMerger $FILENAME$
    //! i races = {"human", "orc", "undead", "nightelf", "demon", "other", "creeps", "commoner", "critters", "naga"}
    //! i setobjecttype("abilities")
    //! i createobject("SNdc","$DC$")
    //! i makechange(current,"alev","10")
    //! i for i=1, 10 do
        //! i makechange(current,"atar",i,"vuln,invu,alive,dead")
        //! i makechange(current,"aran",i,99999)
        //! i makechange(current,"Ndc1",i,races[i])
        //! i makechange(current,"Ndc2",i,"$HERO$")
    //! i end
    //! i createobject("Abu2","$BUR$")
    //! i makechange(current,"Emeu","1","$HERO$")
    //! i setobjecttype("units")
    //! i createobject("ngh1","$HERO$")
    //! i makechange(current,"uhhb",1)
    //! i makechange(current,"ustr",1)
    //! i makechange(current,"upra","INT")
//! endexternalblock
// */
//! endtextmacro

/*The first argument is the type id of the hero you want to use,
the others must be fresh ids for the abilities to be generated*/
//! runtextmacro CreateObjects("N000", "A000", "A001")

// ================================ GLOBALS ================================

/*If this is set to true, every unit in the map will be made "heroic" as
soon as it is created. This is public, so you can set it from your code.

This boolean can be used to create illusions of heroic units! Just set it
to true before creating the illusion, and it will work as expected. All
stats of the heroic unit will be correctly copied to the illusion!*/
public boolean Hooking = false;

/*If you already have a dummy caster in your map
you may modify this function to make use of it.*/
public unit Dummy;

function GetDummy() -> unit
{
    Dummy = CreateUnit(Player(15), 'uloc', .0, .0, .0);
    ShowUnit(Dummy, false);
    UnitRemoveAbility(Dummy, 'Amov');
    UnitRemoveAbility(Dummy, 'Aatk');
    return Dummy;
}

// ================================== CODE ==================================

public function UnitMakeHeroic (unit u) -> boolean
{
    trigger t = CreateTrigger();
    integer i = GetHandleId(GetUnitRace(u));
    if (i > 6) i-=1;
    SetUnitAbilityLevel(Dummy, DARK_CONVERSION, i);
    SetUnitOwner(Dummy, GetOwningPlayer(u), false);
    UnitAddAbility(u, BURROW);
    UnitAddAbility(u, BONUS);
    UnitMakeAbilityPermanent(u, true, BONUS);
    TriggerRegisterUnitStateEvent(t, u, UNIT_STATE_LIFE, GREATER_THAN, THRESHOLD);
    TriggerAddCondition(t, Condition(function () -> boolean
    {
        UnitRemoveAbility(GetTriggerUnit(), BURROW);
        return false;
    }));
    IssueTargetOrderById(Dummy, 852228, u)
    UnitRemoveAbility(u, 'BNdc');
    UnitRemoveAbility(u, BONUS);
    DestroyTrigger(t);
    t = null;
    return GetUnitAbilityLevel(u, 'AHer') > 0;
}

function onInit()
{
    trigger t = CreateTrigger();
    region r = CreateRegion();
    rect w = GetWorldBounds();
    Dummy = GetDummy();
    UnitAddAbility(Dummy, DARK_CONVERSION);
    UnitMakeAbilityPermanent(Dummy, true, DARK_CONVERSION);
    RegionAddRect(r, w);
    TriggerRegisterEnterRegion(t, r, Condition(function() -> boolean
    {
        unit u = GetFilterUnit();
        if (Hooking && GetUnitAbilityLevel(u, 'AHer') == 0)
        {
            Hooking = false;
            UnitMakeHeroic(u);
            Hooking = true;
        }
        u = null;
        return false;
    }));
    RemoveRect(w);
    w = null;
    r = null;
    t = null;
}
}
//! endzinc
 
Last edited:
Level 10
Joined
Apr 9, 2004
Messages
502
Question on this because I was planning on using morphed units to allow for some alteration of targeting and other stuff and was planning to accomplish this using alternate units to simulate units that are either on the ground, in midair, or on top of the trees and destructables. Does morphing a unit to a different unit reset all of this or do they keep their stats, especially the heroic ability that allows them to use the engineering upgrade?
 
Level 9
Joined
Jul 30, 2012
Messages
156
Question on this because I was planning on using morphed units to allow for some alteration of targeting and other stuff and was planning to accomplish this using alternate units to simulate units that are either on the ground, in midair, or on top of the trees and destructables. Does morphing a unit to a different unit reset all of this or do they keep their stats, especially the heroic ability that allows them to use the engineering upgrade?

If you morph the heroic unit into a true hero, it keeps the 'AHer' ability and all the hero stats. But if you morph into a non-hero unit, everything gets messed up. The unit will actually keep any bonuses granted by hero stats, but it will lose the 'AHer' ability, and consequently you won't be able to use Engineering Upgrade unless you call UnitMakeHeroic again.

Notice that if the unit already has an Engineering Upgrade ability, it will not crash immediately. The game only crashes if you add, remove, or change the level of the Engineering Upgrade ability while the unit doesn't have 'AHer'.
 
Status
Not open for further replies.
Top