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

HeroImage

  • Like
Reactions: Wolf_Wing
This is an old idea that I had a long time ago. The code itself explains what this monster do. Enjoy it :3.

Comments, suggestions, etc. are welcome.

JASS:
library HeroImage /* v2.0.0.0
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
*   HeroImage it's a simple library (very useful for AoS maps) which can
*   be used to show the stats (life and mana) of our allies heroes.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*   
*   struct UnitIndex
*   
*       static method create takes unit which returns thistype
*           -   Creates the copy for "which"
*
*       method destroy takes nothing returns nothing
*           -   Destroys the copy
*
*       static method isOriginal takes unit which returns boolean
*           -   If "which" is an "original" unit, returns true
*
*       static method isCopy takes unit which returns boolean
*           -   If "which" is a copy from a unit, returns true
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
*
*   Thanks to
*   
*       -   Jesus4Lyf from thehelper.net
*       -   muZk from who knows where <.<
*       -   Magtheridon96 from hiveworkshop.com
*       -   rulerofiron99 from hiveworkshop.com
*       -   -Kobas- from hiveworkshop.com
*       -   ck5524209 from hiveworkshop.com
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
    globals
        // Lower value = higher quality but more lag
        private constant real TIMEOUT = 1.
    endglobals
    
    globals
        private real WORLD_BOUNDS_MAX_X = 0.
        private real WORLD_BOUNDS_MAX_Y = 0.
        
        private constant hashtable HASHTABLE = InitHashtable()
    endglobals

    struct HeroImage
        private thistype prev
        private thistype next
    
        readonly unit original
        readonly unit array copies[12]
        
        static method isOriginal takes unit which returns boolean
            return HaveSavedInteger(HASHTABLE, GetHandleId(which), 0)
        endmethod
        
        static method isCopy takes unit which returns boolean
            return HaveSavedInteger(HASHTABLE, GetHandleId(which), 1)
        endmethod
        
        method destroy takes nothing returns nothing
            local integer i = 0
            
            call RemoveSavedHandle(HASHTABLE, GetHandleId(this.original), 0)
            set this.original = null
            
            loop
                set i = i + 1
                exitwhen this.copies[i] == null
                
                call RemoveSavedHandle(HASHTABLE, GetHandleId(this.copies[i]), 1)
                call RemoveUnit(this.copies[i])
                
                set this.copies[i] = null
            endloop
            
            set this.prev.next = this.next
            set this.next.prev = this.prev
            
            call this.deallocate()
        endmethod

        private static method getInstanceFromOriginal takes unit original returns thistype
            return LoadInteger(HASHTABLE, GetHandleId(original), 0)
        endmethod
        
        private static method getInstanceFromCopy takes unit copy returns thistype
            return LoadInteger(HASHTABLE, GetHandleId(copy), 1)
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local real originalHp
            local real originalMp
            local integer originalXp
            local unit copy
            local integer n = 0
            
            loop
                exitwhen this == 0
                
                if (this.original == null) then
                    call this.destroy()
                else
                    set originalHp = GetWidgetLife(this.original)
                    set originalMp = GetUnitState(this.original, UNIT_STATE_MANA)
                    set originalXp = GetHeroXP(this.original)
                    
                    set n = 0
                    loop
                        set n = n + 1
                        exitwhen this.copies[n] == null
                        
                        set copy = this.copies[n]
                        
                        // HP
                        if (originalHp <= 0.405) then
                            call SetWidgetLife(copy, 1)
                        else                        
                            call SetWidgetLife(copy, originalHp)
                        endif
                        
                        // MP
                        call SetUnitState(copy, UNIT_STATE_MANA, originalMp)
                    
                        // Level
                        call SetHeroXP(copy, originalXp, false)
                    endloop
                endif
                
                set this = this.next
            endloop
            
            set copy = null
        endmethod
        
        private static method onOrder takes nothing returns boolean
            local thistype this = thistype.getInstanceFromCopy(GetOrderTargetUnit())
            
            if ( this == 0 ) then
                return false
            endif
            
            call IssueTargetOrderById(GetTriggerUnit(), GetIssuedOrderId(), this.original)
            
            return false
        endmethod
        
        private static method onSelection takes nothing returns boolean
            local thistype this = thistype.getInstanceFromCopy(GetTriggerUnit())
            
            if ( this == 0 ) then
                return false
            endif
            
            if ( GetLocalPlayer() == GetTriggerPlayer() ) then
                call ClearSelection()
                call SelectUnit(this.original, true)
                
                call SetCameraPosition(GetUnitX(this.original), GetUnitY(this.original))
            endif
            
            return false
        endmethod
        
        private static method onLearnSpell takes nothing returns boolean
            local thistype this = thistype.getInstanceFromOriginal(GetLearningUnit())
            
            local integer spellId = 0
            local integer spellLevel = 0
            
            local unit copy = null
            local integer n = 0
            
            if ( this == 0 ) then
                return false
            endif
            
            set spellId = GetLearnedSkill()
            set spellLevel = GetLearnedSkillLevel()
            
            loop
                set n = n + 1
                exitwhen this.copies[n] == null
                
                set copy = this.copies[n]
                
                if (GetUnitAbilityLevel(copy, spellId) != 0) then
                    call SetUnitAbilityLevel(copy, spellId, spellLevel)
                else
                    call UnitAddAbility(copy, spellId)
                endif
                
                call UnitModifySkillPoints(copy, -1)
            endloop
            
            set copy = null
            
            return false
        endmethod
        
        private static method onDie takes nothing returns boolean            
            if ( thistype.isCopy(GetTriggerUnit()) == false ) then
                return false
            endif
            
            call ReviveHero(GetTriggerUnit(), WORLD_BOUNDS_MAX_X, WORLD_BOUNDS_MAX_Y, false)
            
            return false
        endmethod
        
        private static method onAcquiresItem takes nothing returns boolean
            local thistype this = thistype.getInstanceFromOriginal(GetTriggerUnit())
            
            local item manipulatedItem = null
            local integer manipulatedItemTypeId = 0
            local integer manipulatedItemCharges = 0
            
            local integer i = 0
            
            if ( this == 0 ) then
                return false
            endif
            
            set manipulatedItem = GetManipulatedItem()
            set manipulatedItemTypeId = GetItemTypeId(manipulatedItem)
            set manipulatedItemCharges = GetItemCharges(manipulatedItem)
            
            loop
                set i = i + 1
                exitwhen this.copies[i] == null
                
                call SetItemCharges(UnitAddItemById(this.copies[i], manipulatedItemTypeId), manipulatedItemCharges)
            endloop
            
            set manipulatedItem = null
            
            return false
        endmethod
        
        private static method onLosesItem takes nothing returns boolean
            local thistype this = thistype.getInstanceFromOriginal(GetTriggerUnit())
            
            local item manipulatedItem = null
            local integer manipulatedItemTypeId = 0
            local integer manipulatedItemCharges = 0
            
            local item tempItem = null
            
            local integer i = 0
            local integer n = 0
            
            if ( this == 0 ) then
                return false
            endif
            
            set manipulatedItem = GetManipulatedItem()
            set manipulatedItemTypeId = GetItemTypeId(manipulatedItem)
            set manipulatedItemCharges = GetItemCharges(manipulatedItem)
            
            loop
                set i = i + 1
                exitwhen this.copies[i] == null
                
                set n = 0
                loop
                    set tempItem = UnitItemInSlot(this.copies[i], n)
                    
                    if ( GetItemTypeId(tempItem) == manipulatedItemTypeId and GetItemCharges(tempItem) == manipulatedItemCharges ) then
                        call RemoveItem(tempItem)
                        exitwhen true
                    endif
                    
                    set n = n + 1
                    exitwhen n >= bj_MAX_INVENTORY
                endloop
            endloop
            
            set manipulatedItem = null
            set tempItem = null
            
            return false
        endmethod
        
        static method create takes unit which returns thistype
            local thistype this = 0
            local integer whichId = 0
            local player playerOwner = null
            local player enumPlayer = null
            local unit copy = null
            local integer unitTypeId = 0
            local integer index = 0
            local integer n = 0
            
            if ( IsUnitType(which, UNIT_TYPE_HERO) == false ) then
                debug call BJDebugMsg("HeroImage_HeroImage.create() -> Only heroes unit can be copied")
                return 0
            endif
            
            if ( thistype.isOriginal(which) ) then
                debug call BJDebugMsg("HeroImage_HeroImage.create() -> " + GetUnitName(which) + " has already a copy")
                return 0
            endif
            
            if ( thistype.isCopy(which) ) then
                debug call BJDebugMsg("HeroImage_HeroImage.create() -> Copy units can't have copies")
                return 0
            endif
            
            set this = thistype.allocate()
            
            set whichId = GetHandleId(which)
            
            // thanks Jesus4Lyf =3
            set thistype(0).next.prev = this
            set this.next = thistype(0).next
            set thistype(0).next = this
            set this.prev = thistype(0)
            
            set playerOwner = GetOwningPlayer(which)
            set unitTypeId = GetUnitTypeId(which)
            
            set this.original = which
            
            call SaveInteger(HASHTABLE, whichId, 0, this)
        
            loop                
                set enumPlayer = Player(index)
                
                if ( IsPlayerAlly(enumPlayer, playerOwner) and enumPlayer != playerOwner ) then
                    set copy = CreateUnit(enumPlayer, unitTypeId, WORLD_BOUNDS_MAX_X, WORLD_BOUNDS_MAX_Y, 0)
                    
                    call SaveInteger(HASHTABLE, GetHandleId(copy), 1, this)
                    
                    set n = n + 1
                    set this.copies[n] = copy
                endif
                
                set index = index + 1
                exitwhen index == bj_MAX_PLAYERS
            endloop
            
            set enumPlayer = null
            set playerOwner = null
            set copy = null
            
            return this
        endmethod
        
        private static method onInit takes nothing returns nothing
            local trigger orderTrigger = CreateTrigger()
            local trigger selectionTrigger = CreateTrigger()
            local trigger learnSpellTrigger = CreateTrigger()
            local trigger dieTrigger = CreateTrigger()
            local trigger acquiresItemTrigger = CreateTrigger()
            local trigger losesItemTrigger = CreateTrigger()
            
            local boolexpr onOrderCondition = Condition(function thistype.onOrder)
            local boolexpr onSelectionCondition = Condition(function thistype.onSelection)
            local boolexpr onLearnSpellCondition = Condition(function thistype.onLearnSpell)
            local boolexpr onDieCondition = Condition(function thistype.onDie)
            local boolexpr onAcquiresItemCondition = Condition(function thistype.onAcquiresItem)
            local boolexpr onLosesItemCondition = Condition(function thistype.onLosesItem)
            
            local rect worldBounds = GetWorldBounds()
            
            local player enumPlayer = null
            local integer index = 0
            
            set WORLD_BOUNDS_MAX_X = GetRectMaxX(worldBounds)
            set WORLD_BOUNDS_MAX_Y = GetRectMaxY(worldBounds)
            
            call RemoveRect(worldBounds)
            set worldBounds = null
            
            loop
                set enumPlayer = Player(index)
            
                call TriggerRegisterPlayerUnitEvent(orderTrigger, enumPlayer, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, null)
                call TriggerAddCondition(orderTrigger, onOrderCondition)
                
                call TriggerRegisterPlayerUnitEvent(selectionTrigger, enumPlayer, EVENT_PLAYER_UNIT_SELECTED, null)
                call TriggerAddCondition(selectionTrigger, onSelectionCondition)
                
                call TriggerRegisterPlayerUnitEvent(learnSpellTrigger, enumPlayer, EVENT_PLAYER_HERO_SKILL, null)
                call TriggerAddCondition(learnSpellTrigger, onLearnSpellCondition)
                
                call TriggerRegisterPlayerUnitEvent(dieTrigger, enumPlayer, EVENT_PLAYER_UNIT_DEATH, null)
                call TriggerAddCondition(dieTrigger, onDieCondition)
                
                call TriggerRegisterPlayerUnitEvent(acquiresItemTrigger, enumPlayer, EVENT_PLAYER_UNIT_PICKUP_ITEM, null)
                call TriggerAddCondition(acquiresItemTrigger, onAcquiresItemCondition)
                
                call TriggerRegisterPlayerUnitEvent(losesItemTrigger, enumPlayer, EVENT_PLAYER_UNIT_DROP_ITEM, null)
                call TriggerAddCondition(losesItemTrigger, onLosesItemCondition)

                set index = index + 1
                exitwhen index == bj_MAX_PLAYER_SLOTS
            endloop
            
            call TimerStart(CreateTimer(), TIMEOUT, true, function thistype.periodic)
            
            set orderTrigger = null
            set selectionTrigger = null
            set learnSpellTrigger = null
            set dieTrigger = null
            set acquiresItemTrigger = null
            set losesItemTrigger = null
            
            set onOrderCondition = null
            set onSelectionCondition = null
            set onLearnSpellCondition = null
            set onDieCondition = null
            set onAcquiresItemCondition = null
            set onLosesItemCondition = null
            
            set enumPlayer = null
        endmethod
    endstruct
endlibrary

Changelog

v2.0.0
  • Removed un-used variables
  • Modified/improved debug messages
  • Now, does not leak in events
  • Now, the periodic method is not so hard
  • Almost rewrited.

v1.0.0
  • Now does not desync
  • Re-writed

Keywords:
aos, stats, hero, heroes
Contents

Library: HeroImage (Map)

Reviews
HeroImage | Reviewed by Maker | 29th Sep 2013 APPROVED Nice and well coded system. I'm sure it can be useful in many maps [TR] 04:29, 8th Aug 2012 Magtheridon96: GetWorldBounds() leaks. You should store it into a...

Moderator

M

Moderator


HeroImage | Reviewed by Maker | 29th Sep 2013
APPROVED


126248-albums6177-picture66521.png


  • Nice and well coded system. I'm sure it can be useful in many maps
[tr]


04:29, 8th Aug 2012
Magtheridon96: GetWorldBounds() leaks.
You should store it into a rect variable and use RemoveRect to delete it.

Other than that, wonderful spell! ^_^

edit
Just realized that this also leaks events.
It would be better to have the events registered for all units instead of specific ones, and you would figure out when to run your spell's code based on the triggering unit. (You have a hashtable in there, so you are capable of attaching data to units. Store a boolean into the hashtable using the handle id and any key so that you keep track of units that should have code run for them upon the firing of the event.)
 
Use a doubly linked list.
Your current list-remove method is not totally functional since you're using dynamic indexing like that.

JASS:
// insert to list:
set next[this] = 0
set prev[this] = prev[0]
set next[prev[0]] = this
set prev[0] = this

// remove from list:
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]

// iterate over list, pretty much the same thing you're doing here
local thistype this = next[0]
loop
    exitwhen this == 0

    set this = next[this]
endloop

My method is using arrays now, but it can be easily changed to use the '.' operator, and it would still be the same thing.

edit
By the way, you can use 0.03125 without the trailing 0's. I debunked that myth about inaccuracy a very long time ago.
You need to run the map for a year to be a few seconds off. <.<
 
Level 22
Joined
Feb 3, 2009
Messages
3,292
Is it just me or it doesn't work at all?
The bellow trigger, it created a paladin but no clone.

  • GUI
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set point = (Center of (Playable map area))
      • -------- New hero for player 1 --------
      • Unit - Create 1 Paladin for Player 1 (Red) at point facing 0.00 degrees
      • Set unit = (Last created unit)
      • -------- Creating his copy... --------
      • Custom script: call HeroImage_Add(udg_unit)
      • -------- Don't forget the leaks >:] --------
      • Custom script: call RemoveLocation(udg_point)
      • Set unit = No unit
 
Level 10
Joined
Sep 19, 2011
Messages
527
My method is using arrays now, but it can be easily changed to use the '.' operator, and it would still be the same thing.

edit
By the way, you can use 0.03125 without the trailing 0's. I debunked that myth about inaccuracy a very long time ago.
You need to run the map for a year to be a few seconds off. <.<

Ok, I will use the '.' method to make it more readable.

Is it just me or it doesn't work at all?
The bellow trigger, it created a paladin but no clone.

  • GUI
    • Events
      • Map initialization
    • Conditions
    • Actions
      • Set point = (Center of (Playable map area))
      • -------- New hero for player 1 --------
      • Unit - Create 1 Paladin for Player 1 (Red) at point facing 0.00 degrees
      • Set unit = (Last created unit)
      • -------- Creating his copy... --------
      • Custom script: call HeroImage_Add(udg_unit)
      • -------- Don't forget the leaks >:] --------
      • Custom script: call RemoveLocation(udg_point)
      • Set unit = No unit

It doesn't create another copy of your hero (why you want two icons on the top?).
 
Level 10
Joined
Sep 19, 2011
Messages
527
Updated, thanks Magtheridon96.

I was thinking on add an alert when our allies heroes are being attacked.

But if I try with

call UnitDamageTarget(this.copyUnit, this.copyUnit, 1, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)

It doesn't alert you (red icon)... any ideas?.

Greetings
 
Level 10
Joined
Sep 19, 2011
Messages
527
Quite a nice system. I believe this was done in tKoK, and it is a fantastic idea.
Only problem is, what if they target the ally with some healing spell and it does either of two things.
Error: Out of Range
Or
It heals the image and not the real unit

I'm wondering if you covered this or not, but if you didn't keep it in mind :p

Orders are redirected to its original.

Check 'order' method :ogre_hurrhurr:
 
Not big deal, more like a hint. When you use debug messages it's always better to state name of functions.
In your example
JASS:
call BJDebugMsg("HeroImage ERROR: That unit has already a copy or it is one")
//Can be
call BJDebugMsg("HeroImage_HeroImage.create() -> That unit has already a copy or it is one")

Someday you will code 500-600 even 1000 lines of code long script, and you will get lost without good "documentation".
 
Top