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

[Solved] Newest Jasshelper/Newgen broke Damage by J4L?

Status
Not open for further replies.
Hey guys, I'm using AIDS and Damage by J4L in my map and noticed some erratic behaviour since I upgraded to Newgen 2.0 release:

It seems that preplaced units do not get the damage event registered properly anymore.

This is the damage detection script (Don't ask why I use such old resources, I like the ease-of-use "block all" functionality).

JASS:
//  
//      ___   _     __  __   _   ___  ____    _______________________________
//     |   \ /_\   /  |/  | /_\ /  _\|  __|   ||      D E A L   I T ,      ||
//     | |) / _ \ / / | / |/ _ \| |/||  __|   ||    D E T E C T   I T ,    ||
//     |___/_/ \_/_/|__/|_|_/ \_\___/|____|   ||     B L O C K   I T .     ||
//                            By Jesus4Lyf    ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//                                                                    v 1.0.5
//      What is Damage?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Damage is a damage dealing, detection and blocking system. It implements
//          all such functionality. It also provides a means to detect what type
//          of damage was dealt, so long as all damage in your map is dealt using
//          this system's deal damage functions (except for basic attacks).
//
//          It is completely recursively defined, meaning if you deal damage on
//          taking damage, the type detection and other features like blocking
//          will not malfunction.
//          
//      How to implement?
//     ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//          Create a new trigger object called Damage, go to 'Edit -> Convert to
//          Custom Text', and replace everything that's there with this script.
//
//          At the top of the script, there is a '//! external ObjectMerger' line.
//          Save your map, close your map, reopen your map, and then comment out this
//          line. Damage is now implemented. This line creates a dummy ability used
//          in the system in some circumstances with damage blocking.
//
//      Functions:
//     ¯¯¯¯¯¯¯¯¯¯¯¯
//          function Damage_RegisterEvent takes trigger whichTrigger returns nothing
//              - This registers a special "any unit takes damage" event.
//              - This event supports dynamic trigger use.
//              - Only triggers registered on this event may block damage.
//              - Only fires when the damage dealt is not 0.
//
//          function Damage_RegisterZeroEvent takes trigger whichTrigger returns nothing
//              - The same as Damage_RegisterEvent, but for only zero damage events
//                (which are excluded from Damage_RegisterEvent).
//              - Note that getting the damage type may be unreliable, since spells
//                like faerie fire trigger 0 damage, but it will count as an attack.
//
//          function Damage_GetType takes nothing returns damagetype
//              - This will get the type of damage dealt, like an event response,
//                for when using a unit takes damage event (or the special event above).
//
//          function Damage_IsPhysical takes nothing returns boolean
//          function Damage_IsSpell takes nothing returns boolean
//          function Damage_IsPure takes nothing returns boolean
//              - Wrappers to simply check if Damage_GetType is certain types.
//
//          function Damage_IsAttack takes nothing returns boolean
//              - Checks if the damage is from a physical attack (so you can deal
//                physical damage without it being registered as an actual attack).
//
//          function Damage_Block takes real amount returns nothing
//          function Damage_BlockAll takes nothing returns nothing
//              - For use only with Damage_RegisterEvent.
//              - Blocks 'amount' of the damage dealt.
//              - Multiple blocks at once work correctly.
//              - Blocking more than 100% of the damage will block 100% instead.
//              - Damage_BlockAll blocks 100% of the damage being dealt.
//
//          function Damage_EnableEvent takes boolean enable returns nothing
//              - For disabling and re-enabling the special event.
//              - Use it to deal damage which you do not want to be detected by
//                the special event.
//
//          function UnitDamageTargetEx takes lots of things returns boolean
//              - Replaces UnitDamageTarget in your map, with the same arguments.
//
//          function Damage_Physical takes unit source, unit target, real amount,
//            attacktype whichType, boolean attack, boolean ranged returns boolean
//              - A clean wrapper for physical damage.
//              - 'attack' determines if this is to be treated as a real physical
//                attack or just physical type damage.
//              - 'ranged' determines if this is to be treated as a ranged or melee
//                attack.
//
//          function Damage_Spell takes unit source, unit target, real amount returns boolean
//              - A clean wrapper for spell damage.
//
//          function Damage_Pure takes unit source, unit target, real amount returns boolean
//              - A clean wrapper for pure type damage (universal type, 100% damage).
//          
//      Thanks:
//     ¯¯¯¯¯¯¯¯¯
//          - Romek, for helping me find a better way to think about damage blocking.
//
library Damage uses AIDS, Event
     //============================================================
     //! external ObjectMerger w3a AIlz dprv anam "Life Bonus" ansf "(Damage System)" Ilif 1 500000 aite 0
     globals
         private constant integer LIFE_BONUS_ABIL='dprv'
     endglobals
     
     //============================================================
     globals
         private Event OnDamageEvent
         private Event OnZeroDamageEvent
         private boolean EventEnabled=true
     endglobals
     
     public function RegisterEvent takes trigger whichTrigger returns nothing
         call OnDamageEvent.register(whichTrigger)
     endfunction
     
     public function RegisterZeroEvent takes trigger whichTrigger returns nothing
         call OnZeroDamageEvent.register(whichTrigger)
     endfunction
     
     public function EnableEvent takes boolean enable returns nothing
         set EventEnabled=enable
     endfunction
     
     //============================================================
     globals
         private integer TypeStackLevel=0
         private damagetype array TypeStackValue
         private boolean array TypeStackAttack
         private real array ToBlock
     endglobals
     
     public function GetType takes nothing returns damagetype
         return TypeStackValue[TypeStackLevel]
     endfunction
     
     public function IsAttack takes nothing returns boolean
         return TypeStackAttack[TypeStackLevel]
     endfunction
     
     public function Block takes real amount returns nothing
         set ToBlock[TypeStackLevel]=ToBlock[TypeStackLevel]+amount
     endfunction
     
     public function BlockAll takes nothing returns nothing
         set ToBlock[TypeStackLevel]=ToBlock[TypeStackLevel]+GetEventDamage()
     endfunction
     
     //============================================================
     globals
         private integer BlockNum=0
         private unit array BlockUnit
         private real array BlockUnitLife
         private real array BlockRedamage
         private unit array BlockDamageSource
         
         private timer BlockTimer=CreateTimer()
     endglobals
     
     //============================================================
     globals
         private unit array RemoveBoosted
         private integer RemoveBoostedMax=0
         
         private timer RemoveBoostedTimer=CreateTimer()
     endglobals
     
     globals//locals
         private real BoostedLifeTemp
         private unit BoostedLifeUnit
     endglobals
     private function RemoveBoostedTimerFunc takes nothing returns nothing
         loop
             exitwhen RemoveBoostedMax==0
             set BoostedLifeUnit=RemoveBoosted[RemoveBoostedMax]
             set BoostedLifeTemp=GetWidgetLife(BoostedLifeUnit)
             call UnitRemoveAbility(BoostedLifeUnit,LIFE_BONUS_ABIL)
             if BoostedLifeTemp>0.405 then
                 call SetWidgetLife(BoostedLifeUnit,BoostedLifeTemp)
             endif
             set RemoveBoostedMax=RemoveBoostedMax-1
         endloop
     endfunction
     
     //============================================================
     private keyword Detector // Darn, I actually had to do this. XD
     globals//locals
         private unit ForUnit
         private real NextHealth
     endglobals
     private function OnDamageActions takes nothing returns boolean
         if EventEnabled then
             if GetEventDamage()==0. then
                 call OnZeroDamageEvent.fire()
             else
                 call OnDamageEvent.fire()
             endif
             
             if ToBlock[TypeStackLevel]!=0. then
                 //====================================================
                 // Blocking
                 set ForUnit=GetTriggerUnit()
                 
                 set NextHealth=GetEventDamage()
                 if ToBlock[TypeStackLevel]>=NextHealth then
                     set NextHealth=GetWidgetLife(ForUnit)+NextHealth
                 else
                     set NextHealth=GetWidgetLife(ForUnit)+ToBlock[TypeStackLevel]
                 endif
                 
                 call SetWidgetLife(ForUnit,NextHealth)
                 if GetWidgetLife(ForUnit)<NextHealth then
                     // NextHealth is over max health.
                     call UnitAddAbility(ForUnit,LIFE_BONUS_ABIL)
                     call SetWidgetLife(ForUnit,NextHealth)
                     
                     set RemoveBoostedMax=RemoveBoostedMax+1
                     set RemoveBoosted[RemoveBoostedMax]=ForUnit
                     call ResumeTimer(RemoveBoostedTimer)
                 endif
                 //====================================================
                 set ToBlock[TypeStackLevel]=0.
             endif
         endif
         return false
     endfunction
     
     //============================================================
     function UnitDamageTargetEx takes unit whichUnit, widget target, real amount, boolean attack, boolean ranged, attacktype attackType, damagetype damageType, weapontype weaponType returns boolean
         local boolean result
         set TypeStackLevel=TypeStackLevel+1
         set TypeStackValue[TypeStackLevel]=damageType
         set TypeStackAttack[TypeStackLevel]=attack
         set result=UnitDamageTarget(whichUnit,target,amount,attack,ranged,attackType,damageType,weaponType)
         set TypeStackLevel=TypeStackLevel-1
         return result
     endfunction
     //! textmacro Damage__DealTypeFunc takes NAME, TYPE
         public function $NAME$ takes unit source, unit target, real amount returns boolean
             return UnitDamageTargetEx(source,target,amount,false,false,ATTACK_TYPE_NORMAL,$TYPE$,WEAPON_TYPE_WHOKNOWS)
         endfunction
         public function Is$NAME$ takes nothing returns boolean
             return GetType()==$TYPE$
         endfunction
     //! endtextmacro
     
     //! runtextmacro Damage__DealTypeFunc("Pure","DAMAGE_TYPE_UNIVERSAL")
     //! runtextmacro Damage__DealTypeFunc("Spell","DAMAGE_TYPE_MAGIC")
     
     // Uses different stuff, but works much the same way.
     public function Physical takes unit source, unit target, real amount, attacktype whichType, boolean attack, boolean ranged returns boolean
         return UnitDamageTargetEx(source,target,amount,attack,ranged,whichType,DAMAGE_TYPE_NORMAL,WEAPON_TYPE_WHOKNOWS)
     endfunction
     public function IsPhysical takes nothing returns boolean
         return GetType()==DAMAGE_TYPE_NORMAL
     endfunction
     
     //============================================================
     private struct Detector extends array // Uses AIDS.
         //! runtextmacro AIDS()
         
         private static conditionfunc ACTIONS_COND
         
         private trigger t
         
         private method AIDS_onCreate takes nothing returns nothing
             //this seems to not run on preplaced units
             set this.t=CreateTrigger()
             call TriggerAddCondition(this.t,thistype.ACTIONS_COND)
             call TriggerRegisterUnitEvent(this.t,this.unit,EVENT_UNIT_DAMAGED)
         endmethod
         
         private method AIDS_onDestroy takes nothing returns nothing
             call DestroyTrigger(this.t)
         endmethod
         
         private static method AIDS_onInit takes nothing returns nothing
             set thistype.ACTIONS_COND=Condition(function OnDamageActions)
         endmethod
     endstruct
     
     //============================================================
     private module InitModule
         private static method onInit takes nothing returns nothing
             local unit abilpreload=CreateUnit(Player(15),'uloc',0,0,0)
             call UnitAddAbility(abilpreload,LIFE_BONUS_ABIL)
             call RemoveUnit(abilpreload)
             set abilpreload=null
             
             set OnDamageEvent=Event.create()
             set OnZeroDamageEvent=Event.create()
             set TypeStackValue[TypeStackLevel]=DAMAGE_TYPE_NORMAL
             set TypeStackAttack[TypeStackLevel]=true
             call TimerStart(RemoveBoostedTimer,0.0,false,function RemoveBoostedTimerFunc)
         endmethod
     endmodule
     private struct InitStruct extends array
         implement InitModule
     endstruct
endlibrary
I've narrowed the problem down to AIDS_onCreate not running on map init. It did before I upgraded to Newgen 2.0 (and thus, Jasshelper).
The units are correctly indexed btw. I've checked GetUnitUser Data already and the units definitely are indexed, but the onCreate function does not run anymore.

Also notice that all units placed after map init work correctly. It's only preplaced units that don't get registered properly.
 
Last edited by a moderator:
It is cohadar's jasshelper. Switch to vexorian's, and it should work just fine (go to Jasshelper -> Vexorian's jasshelper).

It's due to initialization reordering. In Vexorian's jasshelper, there is a static hierarchy for evaluating initializations:

(1) module initializers
(2) struct initializers
(3) library initializers
(4) regular initializers

Let's say you have two libraries, such that library A requires library B. Vex's compiler first checks: "does A and B use the same type of initializer?" If they don't, then it will be ordered based on that hierarchy above. For example, if B uses a library initializer and A uses a struct initializer, A's struct initializer will be ran before B's initializer, even though A requires B.

If they use the same type of initializer, then it will order them properly. For example, if A uses a library init and B uses a library init, then B's init will run before A.

Ultimately, this presented weird issues and this led to us using module initializers in our code (to protect users from themselves). Cohadar sought to fix this issue by making initialization order a bit more intuitive. There is still a hierarchy, but B's initializers will always run before A's. Even if B uses a library init and A uses a module init, B will run before A. This makes sense intuitively, and it is how it should've been from the start.

However, AIDS relied on the counterintuitive implementation that Vexorian made. By running the textmacro, AIDS will set up system triggers to have the onCreate/onEnter methods to evaluate. After all that is completed, it will run a library Init (within the AIDS system) that evaluates all those onCreate/onEnter methods with the appropriate units. However, cohadar's jasshelper will make it so that the library Init is ran before any of the //! textmacro inits are ran, so it just ends up running blank triggers.

There are a lot of ways to fix this:
(1) switch to vexorian's jasshelper (jasshelper -> Vexorian's jasshelper)
(2) you can edit AIDS to make the AIDS init run after the textmacro inits. You can do this in a lot of ways, but one simple method would be to make InitAIDS public and then run it from a scope initializer (I'm pretty sure scope initializers are always executed after library initializers, so it should work out):
JASS:
library AIDS // remove the initializer here
    public function InitAIDS takes nothing returns nothing
        /* code ... */
    endfunction 
endlibrary 

scope AIDSHelper initializer Init
    private function Init takes nothing returns nothing 
        call AIDS_InitAIDS()
    endfunction
endscope

Something along those lines.

TL;DR use vexorian's jasshelper instead of cohadar's jasshelper if you want AIDS to work.
 
I already suspected this to be the issue (since the problem only occured after updating Newgen), but thanks for the detailed explanation of why this happens!
I never bothered with initialization order, struct extends array and all that mystic mumbojumbo before, so that's kind of new for me.

From your possible solutions, I think I'll just switch to Vexorian's Jasshelper. I've never had any problems with the old Jasshelper, so there's no reason for me to use Cohadar's.
 
Status
Not open for further replies.
Top