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

[vJASS] [System] Illusion

Level 22
Joined
Feb 6, 2014
Messages
2,466
JASS:
library Illusion /*

                         Illusion v1.33
                            by Flux
          
            Allows easy creation of Illusion with any damage factor.
  
    */ requires DamageEvent, DamageModify/*
    http://www.hiveworkshop.com/threads/damagepackage.287101/
    Required to manipulate damage given and damage taken by illusions.
  
  
    */ optional Table /*
    If not found, the system will create a hashtable. You cannot create more than 256 hashtable
    per map.

  
    ********************************************************************************
    *************************************** API ************************************
    ********************************************************************************
  
    Illusion.create(player, unitSource, x, y)
        - Create an Illusion based on <unitSource>, owned by <player>, positioned at (<x>, <y>)
      
    this.duration = <timedLife>
        - Add a timer to an illusion.
        - Cannot be overwritten once set.
      
    Illusion.get(unit)
        - Return the 'Illusion instance' based on <unit> parameter.
        
    this.unit
        - Refers to the actual illusion unit
      
    this.damageGiven
        - Determines damage dealt factor.
      
    this.damageTaken
        - Determines damage received factor. 
  
  
    CREDITS:
        Bribe         - Table
        Flux          - DamageEvent and DamageModify
      
*/
    //===================================================================
    //========================= CONFIGURATION ===========================
    //===================================================================
    globals
        //Rawcode of Illusion Ability based on "Item Illusions"
        private constant integer ILLUSION_SPELL = 'AILS'
      
        private constant integer DUMMY_ID = 'dumi'
      
        private constant integer REFRESH_COUNT = 30
        //Dummy unit owner
        private constant player DUMMY_OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
    endglobals
    //===================================================================
    //======================= END CONFIGURATION =========================
    //===================================================================
  
    native UnitAlive takes unit u returns boolean
  
    struct Illusion
      
        readonly unit unit
        public real damageTaken
        public real damageGiven
      
        static if LIBRARY_Table then
            private static Table tb
        else
            private static hashtable hash = InitHashtable()
        endif
      
        private static trigger deathTrg = CreateTrigger()
        private static group g = CreateGroup()
        private static timer t = CreateTimer()
        private static integer count = 0
        private static unit dummy
        private static unit illu
      
        static method get takes unit u returns thistype
            static if LIBRARY_Table then
                return thistype.tb[GetHandleId(u)]
            else
                return LoadInteger(thistype.hash, GetHandleId(u), 0)
            endif
        endmethod
      
        private static method reAdd takes nothing returns nothing
            call TriggerRegisterUnitEvent(thistype.deathTrg, GetEnumUnit(), EVENT_UNIT_DEATH)
        endmethod
      
        private static method onDeath takes nothing returns boolean
            call thistype(thistype.get(GetTriggerUnit())).destroy()
            return false
        endmethod
      
        method destroy takes nothing returns nothing
            if UnitAlive(this.unit) then
                call KillUnit(this.unit)
            endif
            static if LIBRARY_Table then
                call thistype.tb.remove(GetHandleId(this.unit))
            else
                call RemoveSavedInteger(thistype.hash, GetHandleId(this.unit), 0)
            endif
            call GroupRemoveUnit(thistype.g, this.unit)
            //Death trigger refresh
            set thistype.count = thistype.count + 1
            if thistype.count >= REFRESH_COUNT then
                call DestroyTrigger(thistype.deathTrg)
                set thistype.deathTrg = CreateTrigger()
                call TriggerAddCondition(thistype.deathTrg, Condition(function thistype.onDeath))
                call ForGroup(thistype.g, function thistype.reAdd)
                set thistype.count = 0
            endif
            set this.unit = null
            call this.deallocate()
        endmethod
      
      
        private static method onDamage takes nothing returns nothing
            //If source is illusion
            if IsUnitInGroup(Damage.source, thistype.g) then
                set Damage.amount = Damage.amount*thistype.get(Damage.source).damageGiven
            endif
            //If target is illusion
            if IsUnitInGroup(Damage.target, thistype.g) then
                set Damage.amount = Damage.amount*thistype.get(Damage.target).damageTaken
            endif
        endmethod
      
        private static method entered takes nothing returns boolean
            set thistype.illu = GetSummonedUnit()
            return false
        endmethod
      
        method operator duration= takes real time returns nothing
            call UnitApplyTimedLife(this.unit, 'BTLF', time)
        endmethod
      
        static method create takes player owner, unit source, real x, real y returns thistype
            local thistype this
            set thistype.illu = null
            //Create the Illusion Unit
            if source != null and UnitAlive(source) then
                call SetUnitX(thistype.dummy, GetUnitX(source))
                call SetUnitY(thistype.dummy, GetUnitY(source))
                call SetUnitOwner(thistype.dummy, GetOwningPlayer(source), false)
                if IssueTargetOrderById(thistype.dummy, 852274, source) then
                    if thistype.illu != null then
                        call SetUnitOwner(thistype.illu, owner, true)
                        if IsUnitType(source, UNIT_TYPE_STRUCTURE) then
                            call SetUnitPosition(thistype.illu, x, y)
                        else
                            call SetUnitX(thistype.illu, x)
                            call SetUnitY(thistype.illu, y)
                        endif
                    else
                        debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, "[Illusion] No illusion created")
                        call SetUnitOwner(thistype.dummy, DUMMY_OWNER, false)
                        return 0
                    endif
                else
                    debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, "[Illusion] Issued illusion create order failed")
                    call SetUnitOwner(thistype.dummy, DUMMY_OWNER, false)
                    return 0
                endif
                call SetUnitOwner(thistype.dummy, DUMMY_OWNER, false)
            else
                debug call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 3600, "[Illusion] Source unit dead or non-existing")
                return 0
            endif
            //Initialize struct
            set this = thistype.allocate()
            set this.unit = thistype.illu
            set this.damageTaken = 1.0
            set this.damageGiven = 1.0
            call SetUnitAnimation(thistype.illu, "stand")
            call GroupAddUnit(thistype.g, this.unit)
            call TriggerRegisterUnitEvent(thistype.deathTrg, this.unit, EVENT_UNIT_DEATH)
            static if LIBRARY_Table then
                set thistype.tb[GetHandleId(this.unit)] = this 
            else
                call SaveInteger(thistype.hash, GetHandleId(this.unit), 0, this)
            endif
            set thistype.illu = null
            return this
        endmethod
      
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            set thistype.dummy = CreateUnit(DUMMY_OWNER, DUMMY_ID, 0, 0, 0)
            call TriggerRegisterUnitEvent(t, thistype.dummy, EVENT_UNIT_SUMMON)
            call TriggerAddCondition(t, Condition(function thistype.entered))
            call TriggerAddCondition(thistype.deathTrg, Condition(function thistype.onDeath))
            call UnitAddAbility(thistype.dummy, ILLUSION_SPELL)
            static if LIBRARY_Table then
                set thistype.tb = Table.create()
            endif
            call Damage.registerModifier(function thistype.onDamage)
        endmethod
      
    endstruct
  
endlibrary

Example of using the system:

JASS:
scope Test1 initializer Init
  
  
    private function BeginTest takes nothing returns nothing
        local Illusion illu1 = Illusion.create(Player(0), tinker, 0, 0)
        local Illusion illu2 = Illusion.create(Player(0), firelord, 0, 0)
        local Illusion illu3 = Illusion.create(Player(0), tank, 0, 0)
        local Illusion illu4 = Illusion.create(Player(0), flying, 0, 0)
      
        //Set Duration
        set illu1.duration = 30
        set illu2.duration = 30
        set illu3.duration = 30
        set illu4.duration = 30
      
        //Set Damage Manipulation of each illusion
        set illu1.damageTaken = 1   //Receives the same damage
        set illu1.damageGiven = 10  //Deals 10 times the original damage
      
        set illu2.damageTaken = 3   //Receives thrice damage
        set illu2.damageGiven = 1.5 //Deals 1.5 times the original damage
      
        set illu3.damageTaken = 0   //Will not receive any damage
        set illu3.damageGiven = 0   //Will not deal any damage
      
        set illu4.damageTaken = 0   //Will not receive any damage
        set illu4.damageGiven = 2.0 //Deals twice the original damage
      
        //Order the illusions to move to the corners of the map
        call IssuePointOrder(illu1.unit, "move", 3000, 3000)
        call IssuePointOrder(illu2.unit, "move", -3000, -3000)
        call IssuePointOrder(illu3.unit, "move", -3000, 3000)
        call IssuePointOrder(illu4.unit, "move", 3000, -3000)
    endfunction
  
  

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local string s = "1"
        call TriggerRegisterPlayerChatEvent(t, Player(0), s, true)
        call TriggerAddAction(t, function BeginTest)
        call BJDebugMsg("Type '" + s + "' to create 4 Illusions owned by Player(0) at (0,0)")
    endfunction

endscope


Changelog:
v1.00 - [2 March 2016]
- Initial Release

v1.10 - [15 March 2016]
- Optimized the code.
- Made the Debug Messages more specific.
- Added RegisterPlayerUnitEvent as an optional requirement.
- Improved documentation.

v1.20 - [10 June 2016]
- Revised most of the code.
- Only one object editor ability based on Item Illusion is needed.
- Damage dealt, damage received and duration can now be manipulated dynamically.

v1.30 - [2 November 2016]
- Fixed bug involving damage source and damage target are both illusion.
- Now requires DamageEvent and DamageModify.
- Renamed "static method instance" to "static method get".
- Optimized refreshing system.
- Removed duration parameter in "static method create"
- Added "method operator duration="

v1.31 - [2 November 2016]
- Fixed potential leak with refresh system.
- Improved configuration.

v1.32 - [4 February 2017]
- Replaced periodic trigger cleanup with unused event counter cleanup.
- Added debug messages.
- Illusions created now automatically plays "stand" animation.
- Shortened onDamage function.

v1.33 - [11 February 2017]
- Reorder a function placement to avoid trigger evaluation.
- Removed unnecessary function call.
- Dummy changes ownership back to NEUTRAL upon failing creation of Illusion.
- Improved debug messages.
 

Attachments

  • Illusion v1.33.w3x
    42.5 KB · Views: 263
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
Division of doc into multiline comment & novjass block kills its readability.
6 lines of text seem too much for just one function which is rather self-explanatory.

I'm against having NEUTRAL & ILLUSION_ORDER globals. You won't be configuring those, no matter what.

JASS:
if source != null and UnitAlive(source) then
->>
if UnitAlive(source) then
Snippet - Illusion.

Add some newlines withing that function, it's a block of text right now.
IsUnitIllusion() should be replaced with dummy == GetSummoningUnit() check for more safety. The nulling of 'illu' prior the process is uneccessary anyway, it should be added after the action instead (if ever).

The debugging messages seem to be wrong e.g.: debug call BJDebugMsg("|cffffcc00[Illusion]|r: Invalid integer abilityId!") should never happen if IssueTargetOrderById(dummy, ILLUSION_ORDER, source) evaluates to true. Even if it does, the message itself is not valid.

RegisterPlayerUnitEvent anyone?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Division of doc into multiline comment & novjass block kills its readability.
6 lines of text seem too much for just one function which is rather self-explanatory.
Readability seems fine in my opinion. Plus I need to explain the abilityId so I went and explain the rest of the input arguments as well. What's wrong with providing sufficient documentation?

I'm against having NEUTRAL & ILLUSION_ORDER globals. You won't be configuring those, no matter what.
Yeah, I have a feeling that ILLUSION_ORDER does not need a constant variable. I just did it for readability, Ill remove it then. I'll put NEUTRAL in the non-configuration globals block.

JASS:
if source != null and UnitAlive(source) then
->>
if UnitAlive(source) then
Right, I don't know why I add the source != null in the first place.

I admit, I don't know such a similar resource exist. But I find some flaws in that resource.
1. You can't create illusions based on enemies, even if Targets Allowed have 'Enemies' ticked. Mine does without needing to Modify Targets Allowed to include 'Enemies'
2. It lacks debug messages. If something is not working, you won't know what part is not working.
3. It lacks safety features, Example, if you wanted to create an illusion based on a Structure but you forgot to include it in Targets Allowed, then an illusion won't be created and LastCreatedIllusion will still point to the previous illusion.
So if I do something like
JASS:
local unit illu1 = UnitCreateIllusion(tinker, Player(0), 'A000', 1)
local unit illu2 = UnitCreateIllusion(tower, Player(0), 'A000', 1)
call IssuePointOrder(illu1, "move", 3000, 3000)
call IssuePointOrder(illu2, "move", -3000, -3000)
Tinker illusion will actually move to (-3000, -3000) if the user forgot to include 'Structures' in Targets Allowed.

Add some newlines withing that function, it's a block of text right now.
But there are white spaces between functions. I don't know where are you referring to.

IsUnitIllusion() should be replaced with dummy == GetSummoningUnit() check for more safety.
Good suggestion, that means I don't have to Enable-Disable the trigger.

The nulling of 'illu' prior the process is uneccessary anyway, it should be added after the action instead (if ever).
That set illu=null is actually critical because it is a global variable so if an incorrect abilityId (e.g. ability not based on Item Illusions) is used, an illusion won't be created and unit illu will still point to the last created illusion thus it will return the previous illusion created.


The debugging messages seem to be wrong e.g.: debug call BJDebugMsg("|cffffcc00[Illusion]|r: Invalid integer abilityId!") should never happen if IssueTargetOrderById(dummy, ILLUSION_ORDER, source) evaluates to true. Even if it does, the message itself is not valid.
Yes it can, example, your abilityId is based on Storm bolt, IssueTargetOrderById will return true but no illusion will be created so it will result to error message "Invalid integer abilityId!".

I try to avoid requirements as much as possible. But maybe I'll add them as optional requirements.
 
Last edited:

Bannar

Code Reviewer
Level 26
Joined
Mar 19, 2008
Messages
3,140
That set illu=null is actually critical because it is a global variable so if an incorrect abilityId (e.g. ability not based on Item Illusions) is used, an illusion won't be created and unit illu will still point to the last created illusion thus it will return the previous illusion created.
Sure, but if you read carefully - you would see that "illu" reset should be after the action, not prior. If nothing is found, dont null.
Tho the global declaration should change in such case: private unit illu = null.

Just remind yourself about custom event calling:
JASS:
// granted we have trigger handle already prepared and iniliatized

// private real caller = 0
// body of invoker:
(...)
set caller = MY_EVENT_CONSTANT
set caller = 0
(...)
Additional set caller = 0 at the top would be completely unnecessary.

It's a very minor thing, dont really need to change that. Just stating some facts.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Sure, but if you read carefully - you would see that "illu" reset should be after the action, not prior. If nothing is found, dont null.
Tho the global declaration should change in such case: private unit illu = null.

Just remind yourself about custom event calling:
JASS:
// granted we have trigger handle already prepared and iniliatized

// private real caller = 0
// body of invoker:
(...)
set caller = MY_EVENT_CONSTANT
set caller = 0
(...)
Additional set caller = 0 at the top would be completely unnecessary.

It's a very minor thing, dont really need to change that. Just stating some facts.

I can't null it after because I need to return it.
JASS:
...
return illu
set illu = null   //won't happen
 
Level 33
Joined
Apr 24, 2012
Messages
5,113
Wait what, Dirac made this resource long time ago? I should be visiting TheHelper more often.

Tho in my opinion, I think this is a bit better than Dirac's, but it lacks the RegisterPlayerUnitEvent lib (which is a go) and the fact that Dirac's resource forces the users to use the Wand Ability rather than the system itself must do it.

Also, I think GetSummonedUnit() is much better or you could not just check if it is an illusion because as long as the function executes, the event is executed as well. I don't think that another summon ability will affect the method once it is cast at the same time the function is executed(that is a very rare case)
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
Your CreateIllusion function can be simplified:

JASS:
    function CreateIllusion takes player owner, unit source, real x, real y returns unit    
        set illu = null //makes it so that the illusion will always be null or be the required unit.
        if source != null and UnitAlive(source) then
            call SetUnitX(dummy, GetUnitX(source))
            call SetUnitY(dummy, GetUnitY(source))
            call SetUnitOwner(dummy, GetOwningPlayer(source), false)
            call UnitAddAbility(dummy, ABIL_ID) //ABIL_ID should be a constant, set by the library, instead of per-call.
            call EnableTrigger(findIllusion)
            if not IssueTargetOrderById(dummy, ILLUSION_ORDER, source) then
                debug call BJDebugMsg("|cffffcc00[Illusion]|r: Unable to target unit source!")
            endif
            call DisableTrigger(findIllusion)
            call UnitRemoveAbility(dummy, ABIL_ID)
            call SetUnitOwner(dummy, NEUTRAL, false)
            if illu != null then
                call SetUnitOwner(illu, owner, true) //Moved to within this if/else block.
                if IsUnitType(source, UNIT_TYPE_STRUCTURE) then
                    call SetUnitPosition(illu, x, y)
                else
                    call SetUnitX(illu, x)
                    call SetUnitY(illu, y)
                endif
            debug else
                debug call BJDebugMsg("|cffffcc00[Illusion]|r: Invalid integer ABIL_ID!") 
            endif
        debug else
            debug call BJDebugMsg("|cffffcc00[Illusion]|r: Invalid unit source!")
        endif
        return illu //only one return statement is needed.
    endfunction

Also, if you will not use RegisterPlayerUnitEvent, at least use TriggerRegisterVariableEventBJ. That is the most useless function in the world to inline as saving map file size is more relevant than saving a function call during map init.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Your CreateIllusion function can be simplified
^Noted, thanks.

ABIL_ID should be a constant, set by the library, instead of per-call.
No, it is not a universal illusion creator that handles the damage dealt and damage taken part via DDS. I think it's much simpler to create abilities based on Item Illusion and the input argument rawcode in CreateIllusion will determine the damage dealt, damage taken and duration of the illusion.


Also, if you will not use RegisterPlayerUnitEvent, at least use TriggerRegisterVariableEventBJ. That is the most useless function in the world to inline as saving map file size is more relevant than saving a function call during map init.
Actually I was using TriggerRegisterAnyUnitEventBJ at first but I decided to register another event (To make it only 1 Item Illusion ability is needed) so I end up using 2 TriggerRegisterAnyUnitEventBJ that's why I inlined it. Then after some time, I removed the other event (since it's mostly a dead end path anyway) and decided to not change back what I inlined. Anyway, yeah I will use TriggerRegisterAnyUnitEventBJ in something like this form in the next update.
JASS:
static if LIBRARY_RegisterPlayerUnitEvent
    call RegisterPlayerUnitEvent(...)
else
    call TriggerRegisterAnyUnitEventBJ(...)
endif
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,456
OK, I've ran some tests and, in the environment you provided, everything seems to work correctly.

To improve the demo map, I suggest making the on-screen text either last longer or only get displayed after a 0 second wait/timer so it appears in the message log.

The description doesn't cover the installation steps. The user has to create the ability or copy it from your map, and that's not documented anywhere.

With those changes, this resource looks acceptable.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Updated!

To improve the demo map, I suggest making the on-screen text either last longer or only get displayed after a 0 second wait/timer so it appears in the message log.
I would have did this, but TriggerSleepAction doesn't work with it so I leave it as is. Using a 0 second timer is just too much work.

The description doesn't cover the installation steps. The user has to create the ability or copy it from your map, and that's not documented anywhere.
Done
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Though I already made one but didn't bother to upload it because I did not know what DDS to use. But in this example I used PDDS.
JASS:
library IllusionSystem /*
    
                    IllusionSystem v1.00
                        by Flux
        
        This system allows you to create an Illusion with dynamic damage dealt and damage
        taken.
        
    -----------------------------------
    API:
    -----------------------------------
      struct Illusion
            
        - real damageTaken
        - real damageDealt
            
        - static method create takes player owner, unit source, real x, real y, real duration returns thistype
    -----------------------------------
    
    */ requires /*
    */ Illusion /*       [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-illusion-276304/[/url]
    */ DamageEvent /*    [url]http://www.hiveworkshop.com/forums/jass-resources-412/system-physical-damage-detection-228456/[/url]
    
    */ optional Table /* [url]http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/[/url]
    
    Credits:
        looking_for_help - DamageEvent
        Bribe            - Table
    
    */
    
    //====================== CONFIGURATION ====================
    globals
        //Ability based on Item Illusion that has Damage Dealt = 1, Damage Received = 1, Duration = 0
        private constant integer ILLUSION_ABILITY = 'Ailu'
    endglobals
    //=================== END CONFIGURATION ===================
    
    
    struct Illusion
        
        readonly unit unit
        real damageDealt
        real damageTaken
        
        private static group g = CreateGroup()
        
        static if LIBRARY_Table then
            private static Table tb
        else
            private hashtable hash = InitHashtable()
        endif
        
        method destroy takes nothing returns nothing
            static if LIBRARY_Table then
                call tb.remove(GetHandleId(.unit))
            else
                call FlushChildHashtable(hash, GetHandleId(.unit))
            endif
            call GroupRemoveUnit(g, .unit)
            call RemoveUnit(.unit)
            set .unit = null
            call .deallocate()
        endmethod
        
        private static method onDamage takes nothing returns nothing
            local thistype this
            if IsUnitInGroup(PDDS.source, g) then
                static if LIBRARY_Table then
                    set this = tb[GetHandleId(PDDS.source)]
                else
                    set this = LoadInteger(hash, GetHandleId(PDDS.source), 0)
                endif
                set PDDS.amount = .damageDealt*PDDS.amount
            elseif IsUnitInGroup(PDDS.target, g) then
                static if LIBRARY_Table then
                    set this = tb[GetHandleId(PDDS.target)]
                else
                    set this = LoadInteger(hash, GetHandleId(PDDS.target), 0)
                endif
                set PDDS.amount = .damageTaken*PDDS.amount
            endif
        endmethod
        
        private static method onDeath takes nothing returns nothing
            static if LIBRARY_Table then
                local thistype this = tb[GetHandleId(GetTriggerUnit())]
            else
                local thistype this = LoadInteger(hash, GetHandleId(GetTriggerUnit()), 0)
            endif
            if this > 0 then
                call .destroy()
            endif
        endmethod
        
        static method create takes player owner, unit source, real x, real y, real duration returns thistype
            local thistype this = .allocate()
            set .unit = CreateIllusion(owner, source, ILLUSION_ABILITY, x, y) 
            //Initialized both damage modifiers to 1
            set .damageDealt = 1
            set .damageTaken = 1
            call GroupAddUnit(g, .unit)
            if duration > 0 then
                call UnitApplyTimedLife(.unit, 'BTLF', duration)
            endif
            static if LIBRARY_Table then
                set tb[GetHandleId(.unit)] = this
            else
                call SaveInteger(hash, GetHandleId(.unit), 0, this)
            endif
            return this
        endmethod
        
        private static method onInit takes nothing returns nothing
            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
            else
                local trigger t = CreateTrigger()
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
                call TriggerAddCondition(t, Condition(function thistype.onDeath))
            endif
            static if LIBRARY_Table then
                set tb = Table.create()
            endif
            call AddDamageHandler(function thistype.onDamage)
        endmethod
        
    endstruct
    
endlibrary

Example of using it:
JASS:
scope Test5 initializer Init
    
    
    private function BeginTest takes nothing returns nothing
        local Illusion illu = Illusion.create(Player(0), tinker, 0, 0, 15)
        set illu.damageDealt = 0.5
        set illu.damageTaken = 2.0
    endfunction
    
    

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        local string s = "5"
        call TriggerRegisterPlayerChatEvent(t, Player(0), s, true)
        call TriggerAddAction(t, function BeginTest)
        call BJDebugMsg("Type '" + s + "' to create a Tinker Illusion with that deals 50% damage and takes 200% damage")
    endfunction

endscope
 

Attachments

  • Illusionv1.10.w3x
    53.4 KB · Views: 144

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I hope you can implement a way to check the number of illusions count per unit (the ones conjured by a unit) and a way to limit illusions per unit by destroying the one with the least expire time. By the way, is it even possible to detect expire time?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I hope you can implement a way to check the number of illusions count per unit (the ones conjured by a unit)
The user can do that if he would build a wrapper function out of this one. Example (using Bribe's Table):
JASS:
function CustomFuncIllusion takes unit ....
    local unit id = GetHandleId(unit)
    if counter[id] < limit then
        call Illusion.create(....)
        set counter[id] = counter[id] + 1
    endif
endfunction

and a way to limit illusions per unit by destroying the one with the least expire time
Add a queued linked list to the wrapper function so that the first added is easily accessible upon reaching the limit. However, that only works for same timers for all Illusions.

By the way, is it even possible to detect expire time?
It's possible, though I haven't created an API for it. The method would be is to add a timer member to the struct and detecting the time left by using TimerGetRemaining.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I'm not versed in Jass but I fiddled with it anyway and could create two variables: the first to check whether the created image is an illusion, but the originator won't have this variable; the second is for the number of illusions which has the shortcoming of refering to the number of illusions of this image-source rather than the very originator, so ,for instance, if an illusion creates an illusion, the first will have 2 illusion count, the second will also have 2 illusion count (assigned from its duplicator,) whereas the originator will have 1 illusion count (could't assign the second illusion's illusion count to it.)

I also tried to count units in group g but its private and I'm not sure whether each originator has its own unique g group or the group is used by all originators in the system.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
I'm not versed in Jass but I fiddled with it anyway and could create two variables: the first to check whether the created image is an illusion, but the originator won't have this variable; the second is for the number of illusions which has the shortcoming of refering to the number of illusions of this image-source rather than the very originator, so ,for instance, if an illusion creates an illusion, the first will have 2 illusion count, the second will also have 2 illusion count (assigned from its duplicator,) whereas the originator will have 1 illusion count (could't assign the second illusion's illusion count to it.)

I also tried to count units in group g but its private and I'm not sure whether each originator has its own unique g group or the group is used by all originators in the system.

I'm not quite getting what you're trying to say. What are you trying to achieve? Did you tried my suggestion?
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I tried something similar to your idea of counting illusions, and it's confounding for me. My gripe with your idea is that it can't count the total number of illusions conjured by the caster, because it's not the caster that generated them, it is its images. I'm basing my example on illusions generating illusions, as in Phantom Edge ("Additionally, it enables his images to generate their own images.")

Quite confusing, quite the idea.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
Yes that did cross my mind. I hope you could figure something out to make this the definite Illusion System.
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Yes that did cross my mind. I hope you could figure something out to make this the definite Illusion System.
I'll probably update this in the future, however the "feature" you want (Count how many illusions currently conjured by a certain unit) is too specific to be implemented into this system. It will fit more into a separate spell submission.
I don't want to clutter this system with a lot of "features" which are too specific and can be implemented by the user itself.
More features != Greater Quality.

Another workaround for you (this time MUI) is use another Table/hashtable as a pointer which always points to the handle id of the original non-Illusion source. Example (using Bribe's Table for 1 dimension and syntax):
set source[handle id of some illusion] = handle id of (orig) Phantom Lancer


(orig) Phantom Lancer created an illusion,
let handle id of (orig) Phantom Lancer = 12345
let handle id of new illusion = 54321

Note:
set source[handle id of orig] = handle id of orig //done at map init

JASS:
set source[54321] = source[12345] //source[12345] = 12345 which was done at map init
//then increment the counter using source[] as index
set counter[source[54321]] = counter[source[54321]] + 1
//but source[54321] = 12345, therefore counter[12345] is now 1



An illusion (the one with handle id of 54321) created another illusion
therefore, handle id of the source = 54321
let handle id of the new illusion = 67890

JASS:
set source[67890] = source[54321]
//then increment the counter using source[] as index
set counter[source[67890]] = counter[source[67890]] + 1
//but source[67890] = source[54321] = source[12345] = 12345, therefore counter[12345] is now 2
In this example, notice that the counter referenced to Phantom Lancer still incremented even though the one who created the illusion is another illusion which was originally created by Phantom Lancer.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I think this matters.

JASS:
           static if LIBRARY_RegisterPlayerUnitEvent then
               call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SUMMON, function thistype.entered)
           else
               call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SUMMON)
               call TriggerAddCondition(t, Condition(function thistype.entered))
           endif

===>
JASS:
           static if LIBRARY_RegisterPlayerUnitEvent then
               call RegisterPlayerUnitEventForPlayer(EVENT_PLAYER_UNIT_SUMMON, function thistype.entered, DUMMY_OWNER)
           else
               call TriggerRegisterPlayerUnitEvent(t, DUMMY_OWNER, EVENT_PLAYER_UNIT_SUMMON, null)
               call TriggerAddCondition(t, Condition(function thistype.entered))
           endif

EDIT:
and also I coudn't find this in your code
JASS:
/*
        static method instance takes unit u returns thistype
        - Return the 'Illusion instance' based on unit parameter.
 
Last edited:

Deleted member 219079

D

Deleted member 219079

I really love the work put into Grimex. Makes you able to CnP a script from internet without downloading any files, very convenient.
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I'll probably update this in the future, however the "feature" you want (Count how many illusions currently conjured by a certain unit) is too specific to be implemented into this system. It will fit more into a separate spell submission.
I don't want to clutter this system with a lot of "features" which are too specific and can be implemented by the user itself.
More features != Greater Quality.
It just seems like an essential feature for me.


Another workaround for you (this time MUI) is use another Table/hashtable as a pointer which always points to the handle id of the original non-Illusion source. Example (using Bribe's Table for 1 dimension and syntax):
set source[handle id of some illusion] = handle id of (orig) Phantom Lancer


(orig) Phantom Lancer created an illusion,
let handle id of (orig) Phantom Lancer = 12345
let handle id of new illusion = 54321

Note:
set source[handle id of orig] = handle id of orig //done at map init

JASS:
set source[54321] = source[12345] //source[12345] = 12345 which was done at map init
//then increment the counter using source[] as index
set counter[source[54321]] = counter[source[54321]] + 1
//but source[54321] = 12345, therefore counter[12345] is now 1



An illusion (the one with handle id of 54321) created another illusion
therefore, handle id of the source = 54321
let handle id of the new illusion = 67890

JASS:
set source[67890] = source[54321]
//then increment the counter using source[] as index
set counter[source[67890]] = counter[source[67890]] + 1
//but source[67890] = source[54321] = source[12345] = 12345, therefore counter[12345] is now 2
In this example, notice that the counter referenced to Phantom Lancer still incremented even though the one who created the illusion is another illusion which was originally created by Phantom Lancer.
Doesn't Scenario 2 still have the shortcoming of not overwriting the original non-illusion unit's counter?
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I missed the "pointer" part. I gave it a go but it seems that if I use two heroes for the same player both will use the same counter; here are the triggers:

  • Juxtaposition Register
    • Events
    • Unit - A unit Learns a skill
    • Conditions
    • (Learned Hero Skill) Equal to (==) Juxtaposition
    • Actions
    • Set Jux_Source[(GetHandleId(GetTriggerUnit()))] = (GetHandleId(GetTriggerUnit()))
in Illusion.create:
JASS:
set udg_Jux_Source[GetHandleId(.unit)] = udg_Jux_Source[GetHandleId(source)]
set udg_Jux_Counter[udg_Jux_Source[GetHandleId(.unit)]] = udg_Jux_Counter[udg_Jux_Source[GetHandleId(.unit)]] + 1
in Illusion.onDeath:
JASS:
set udg_Jux_Counter[udg_Jux_Source[GetHandleId(.unit)]] = udg_Jux_Counter[udg_Jux_Source[GetHandleId(.unit)]] - 1
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
Tried it and it worked fine. I don't see a reason why it would not work.

Also, I just noticed that the test map's dummy unit has a model and has no 'Aloc', a careless mistake. Anyway, this will soon be updated to integrate with my DamagePackage.
 

Attachments

  • Juxtapose.w3x
    56.4 KB · Views: 137

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
JASS:
           if IsUnitInGroup(source, g) then
               static if LIBRARY_Table then
                   set this = tb[GetHandleId(source)]
               else
                   set this = LoadInteger(hash, GetHandleId(source), 0)
               endif
               //Manipulate the damage
               static if LIBRARY_DamageEvent then
                   set PDDS.amount = .damageGiven*PDDS.amount
               else
                   set newAmount = .damageGiven*amount
                   //! runtextmacro ILLUSION_ALTER_DAMAGE("newAmount")
               endif
           elseif IsUnitInGroup(target, g) then
               static if LIBRARY_Table then
                   set this = tb[GetHandleId(target)]
               else
                   set this = LoadInteger(hash, GetHandleId(target), 0)
               endif
               //Manipulate the damage
               static if LIBRARY_DamageEvent then
                   set PDDS.amount = .damageTaken*PDDS.amount
               else
                   set newAmount = .damageTaken*amount
                   //! runtextmacro ILLUSION_ALTER_DAMAGE("newAmount")
               endif
           endif
Hey Flux, I'm not sure but will this correctly calculate the newAmount if source and target are both illusions from this system? The newAmount in the case I specified should be .damageGiven*.damageTaken*amount right?
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I modified the code so it limits the maximum number of illusions, it worked fine; but then I modified it so it registers originators when they learn Mirror Image and it started to crash when the originator deals damage. Here is the Spell trigger:
JASS:
scope Juxtapose
 
  globals
  private Table juxSource
  private Table counter
  endglobals
 
  private struct Spell
 
  private static method onDeath takes nothing returns boolean
  local integer handleId = GetHandleId(GetTriggerUnit())
  local integer id
  if juxSource.has(handleId) then
  set id = juxSource[handleId]
  set counter[id] = counter[id] - 1
  call juxSource.remove(handleId)
  endif
  return false
  endmethod
 
  private static method onDamage takes nothing returns nothing
  local integer id
  local Illusion il
  local integer count
  if PDDS.damageType == PHYSICAL and GetUnitTypeId(PDDS.source) == 'Ogrh' then

  //il.unit is the illusion unit
  set id = juxSource[GetHandleId(PDDS.source)]
  set count = counter[id]
  if  (count < 3)  then
  set il = Illusion.create(GetOwningPlayer(PDDS.source), PDDS.source, GetUnitX(PDDS.source) + GetRandomReal(-100, 100), GetUnitY(PDDS.source) + GetRandomReal(-100, 100), 10)
  endif
  set juxSource[GetHandleId(il.unit)] = id
  set counter[id] = counter[id] + 1
  call BJDebugMsg("Illusion Counter of id = " + I2S(id) + " = " + I2S(counter[id]))
  endif
  endmethod
 
 
  private static method Register_MirrorImage_Conditions takes nothing returns boolean
 
  local integer id = GetHandleId(GetLearningUnit())
  local trigger t = CreateTrigger()
  if ( GetLearnedSkillBJ() == 'AOmi' ) then
  set juxSource = Table.create()
  set juxSource[id] = id
  set counter = Table.create()
  call AddDamageHandler(function thistype.onDamage)
  call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
  call TriggerAddCondition(t, function thistype.onDeath)
  endif
  return false
  endmethod
 
  private static method onInit takes nothing returns nothing
  local trigger Register_MirrorImage = CreateTrigger(  )
  call TriggerRegisterAnyUnitEventBJ( Register_MirrorImage, EVENT_PLAYER_HERO_SKILL )
  call TriggerAddCondition( Register_MirrorImage, Condition( function thistype.Register_MirrorImage_Conditions ) )
  endmethod
 
  endstruct
 
endscope
 

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
I'm modifying the code in the map you attached so there were only two heroes with the skill and only one of them had learned the skill, then when the latter has dealt damage, the game halted.
Spell was the only trigger modified.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
Here is the Spell trigger:
There is a flaw in the conditions. With the current setup, every summoned illusion will have their own summoned illusion count. You don't actually have to register the HERO_SKILL_LEARNED event. You just have to do something like:
JASS:
            local unit source = PDDS.source
            local integer id = GetHandleId(source)
            if GetUnitAbilityLevel(source, 'ABIL') > 0 then
                if IsUnitIllusion(source) then
                    set id = GetHandleId(tb.unit[GetHandleId(source)])
                endif
                if count.integer[id] < 5 then
                     set illu = Illusion.create...
                     loop
                        if IsUnitIllusion(source) then
                            set source = tb.unit[GetHandleId(source)]
                        else
                            break
                        endif
                    endloop
                    set id = GetHandleId(source)
                    set count.integer[id] = count.integer[id] + 1
                    set tb.unit[GetHandleId(illu)] = source
                endif
            endif
 
Last edited:

Iph

Iph

Level 2
Joined
Jul 31, 2016
Messages
39
There is a flaw in the conditions. With the current setup, every summoned illusion will have their own summoned illusion count. You don't actually have to register the HERO_SKILL_LEARNED event. You just have to do something like:
JASS:
            local unit source = PDDS.source
            local integer id = GetHandleId(source)
            if GetUnitAbilityLevel(source, 'ABIL') > 0 then
                if IsUnitIllusion(source) then
                    set id = GetHandleId(tb.unit[GetHandleId(source)])
                endif
                if count.integer[id] < 5 then
                     set illu = Illusion.create...
                     loop
                        if IsUnitIllusion(source) then
                            set source = tb.unit[GetHandleId(source)]
                        else
                            break
                        endif
                    endloop
                    set id = GetHandleId(source)
                    set count.integer[id] = count.integer[id] + 1
                    set tb.unit[GetHandleId(illu)] = source
                endif
            endif
I tried it but it produced compilation errors, I'm not quite sure how to implement it. Also, what's the loop for?
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
I tried it but it produced compilation errors
If you just CnP this, then it will because its just for example.
I'm not quite sure how to implement it. Also, what's the loop for?
Everytime an unit creates an illusion, the summoned illusion will be saved with the handleid of its summoning unit as its index. So what the loop does is that it traces the source of the current illusion and if its summoner is still an illusion, it will trace its source again. It will only stop when it traces back to the non-illusion origin. So the Illusion count will be saved only for the original non illusion unit.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
JASS:
            static if LIBRARY_RegisterPlayerUnitEvent then
                call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SUMMON, function thistype.entered)
            else
The new RegisterPlayerUnitEvent included in Bannar's RegisterEvent pack allows users to choose either the old API by Mag's or the new one. In which case, you might need to add another static if above ^ - static if RPUE_VERSION_NEW then.


This can be simplified.
JASS:
                if IssueTargetOrderById(thistype.dummy, 852274, source) then
                    if thistype.illu != null then
                        call SetUnitOwner(thistype.illu, owner, true)
                        if IsUnitType(source, UNIT_TYPE_STRUCTURE) then
                            call SetUnitPosition(thistype.illu, x, y)
                        else
                            call SetUnitX(thistype.illu, x)
                            call SetUnitY(thistype.illu, y)
                        endif
                    endif
                endif
//    ===>
                if IssueTargetOrderById(thistype.dummy, 852274, source) and thistype.illu != null then
                    call SetUnitOwner(thistype.illu, owner, true)
                    if IsUnitType(source, UNIT_TYPE_STRUCTURE) then
                        call SetUnitPosition(thistype.illu, x, y)
                    else
                        call SetUnitX(thistype.illu, x)
                        call SetUnitY(thistype.illu, y)
                    endif
                endif
Also static method create should return 0 if source is null and if illusion creation failed.

I'm not sure if method operator duration= is needed since you can't update the duration to last longer and in case the user wants to update the duration to a lower value, a call UnitApplyTimedLife() would suffice.
So I think it's more reasonable to just make users define duration upon calling Illusion.create().
JASS:
local Illusion u = Illusion.create(Player(0), source, 0, 0)
set u.duration = 5
set u.duration = 10
//Illusion still dies after 5 seconds because of UnitApplyTimedLife() does not override other timedlifes

Btw, how about you change method get() to a static operator (preferably [])? It would also prevent users from doing call Illusion.get(u). What do you think?
 
Level 22
Joined
Feb 6, 2014
Messages
2,466
The new RegisterPlayerUnitEvent included in Bannar's RegisterEvent pack allows users to choose either the old API by Mag's or the new one. In which case, you might need to add another static if above ^ -
static if RPUE_VERSION_NEW then
.
Ok sure I can make it compatible with Bannar's.

This can be simplified.
Before there were debug messages but now I removed them but forgot to simplify the if-conditions. Sorry about that.

Also static method create should
return 0
if source is null and if illusion creation failed.
Before, it used to return 0 together with debug messages, don't know why i removed them. Will re-add that in the next update.

I'm not sure if method operator duration= is needed since you can't update the duration to last longer and in case the user wants to update the duration to a lower value, a
call UnitApplyTimedLife()
would suffice.
That's just syntax sugar. I mentioned in the documentation that you can't overwrite it.

Btw, how about you change method get() to a static operator (preferably [])? It would also prevent users from doing
call Illusion.[COLOR=color: #666666]get[/COLOR](u)
. What do you think?
I think users would not be that stupid to do that. Also, noobs could mistake it for an array if they didn't know method operators.
 

AGD

AGD

Level 16
Joined
Mar 29, 2016
Messages
688
That's just syntax sugar. I mentioned in the documentation that you can't overwrite it.
Well okay, I didn't read the API description, sorry.

I think users would not be that stupid to do that. Also, noobs could mistake it for an array if they didn't know method operators.
Right

Before there were debug messages but now I removed them
Debug messages would be good imo, though not really necessary. So your choice.
 
Top