• 🏆 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] Actions do not load

Status
Not open for further replies.
Level 2
Joined
Sep 21, 2013
Messages
22
I've ran into this big issue with my spell, Magic Javelin. I have it set up using vJass and have evaded using a timer (due to lack of knowledge and internet to look it up) and finally have the chance to look up timers. However, it appears that although the conditions are met, the actions are not ran at all.

I have added debug messages in each of the functions that I find might interfere, and even commented out the temporary trigger to see if the actions would run. I haven't ran into this issue before with my other triggers and have no idea why it doesn't work.

Here is the spell

JASS:
scope MagicJavelin initializer Init
//*********************************************************************************************************************\\
//***********************************************config****************************************************************\\
//*********************************************************************************************************************\\

    globals
        private constant real       INTERVAL            = 0.03625   //Time between ticks
        
        private constant integer    SPELL_ID            = 'A002'    //Object data
        private constant integer    DUMMY_ID            = 'h001'
        
        private constant real       INITIAL_DISTANCE    = 150        //Range data/hit sensitivity
        private constant real       HIT_RADIUS          = 15        // !Do not exceed INITIAL_DISTANCE!
        
        private constant real       DAMAGE              = 50        //Initial damage
        private constant real       DAMAGE_PER_LEVEL    = 75        //Additional damage per level
        private constant real       DAMAGE_PER_SECOND   = 100       //Initial damage per second travelled
        private constant real       DAMAGE_PER_LVLSEC   = 150       //Additional damage per second travelled per level
        
        private constant real       INITIAL_SPEED       = 700       //Initial travel speed
        private constant real       SPEED_CLIMB         = 250       //Amount of travel between ticks
        private constant real       SPEED_DIVISOR       = 100       //How much speed is divided by. Set to 100 for higher precision.
    endglobals
    
//Switches/Toggleables
    globals
        private constant boolean    FRIENDLY_FIRE       = true      //Determines if it hits allies
    endglobals
    
//Formulae    
    private function SpeedCalc takes nothing returns real
        return INITIAL_SPEED / SPEED_DIVISOR
    endfunction
    
    private function ClimbCalc takes integer nTicks returns real
        return (SPEED_CLIMB / SPEED_DIVISOR) * nTicks
    endfunction
    
    private function DamageCalc takes integer level, integer nTicks returns real
    
    //Step 1: Find initial damage.
        local real Damage = DAMAGE + (DAMAGE_PER_LEVEL * (level - 1))
        
    //Step 2: Calculate Damage Per Second
        local real DPS = DAMAGE_PER_SECOND + (DAMAGE_PER_LVLSEC * (level - 1))
        
    //Step 3: Convert to Damage Per Tick
        set DPS = DPS / INTERVAL
        
    //Step 4: Calculate final damage based on time travelled.
        return Damage + (DPS * nTicks)
    endfunction
    
//*********************************************************************************************************************\\
//*******************************************End of config*************************************************************\\
//*********************************************************************************************************************\\

    globals
        private unit Caster
        private unit Dummy
        private player PlayerID
        
        private real Angle
        
        private integer Ticks = 0
        
        private group UnitHit
        
        private trigger ticker
    endglobals
    
    private function SetGlobals takes nothing returns nothing
        local location ls
        local location lc
        
        call BJDebugMsg("Globals set")
        
        set Caster = GetSpellAbilityUnit()
        set PlayerID = GetOwningPlayer(Caster)
        
        set lc = GetUnitLoc(Caster)
        set ls = GetSpellTargetLoc()
        set Angle = AngleBetweenPoints(lc, ls)
        
        call RemoveLocation(ls)
        call RemoveLocation(lc)
    endfunction
    
//*********************************************************************************************************************\\
//***********************************conditional data******************************************************************\\
//*********************************************************************************************************************\\


    
//*********************************************************************************************************************\\
//*********************************************************************************************************************\\
//*********************************************************************************************************************\\

    private function GroupConds takes nothing returns boolean
        if(FRIENDLY_FIRE) then
            return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)) == false and (IsUnitAliveBJ(GetFilterUnit()) == true)
        endif
        return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)) == false and (IsUnitAliveBJ(GetFilterUnit()) == true and not(IsUnitAlly( GetFilterUnit(), PlayerID )))
    endfunction

    private function GroupActions takes nothing returns nothing
        call UnitDamageTargetBJ( Caster, GetEnumUnit(), DamageCalc(GetHeroLevel(Caster), Ticks), ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL )
        call RemoveUnit( Dummy )
        call BJDebugMsg("UNIT HIT")
        
        set UnitHit = null
        call DestroyGroup( UnitHit )
        call DestroyTrigger( ticker )
    endfunction
    
    private function Tick takes nothing returns nothing
        local location dummyLoc = GetUnitLoc(Dummy)
        set Ticks = Ticks + 1
        
        call BJDebugMsg("Tick Tock")
        call SetUnitPositionLoc( Dummy, PolarProjectionBJ(dummyLoc, ClimbCalc(Ticks), Angle) )
        set UnitHit = GetUnitsInRangeOfLocMatching(HIT_RADIUS, dummyLoc, Condition(function GroupConds))
        call RemoveLocation(dummyLoc)
        
        call ForGroupBJ( UnitHit, function GroupActions )
        set UnitHit = null
        call DestroyGroup(UnitHit)
    endfunction
    
//*********************************************************************************************************************\\
//**************************************************core***************************************************************\\
//*********************************************************************************************************************\\

    private function StartTicking takes nothing returns nothing
        call BJDebugMsg("Ticker started")
        set ticker = CreateTrigger()
        
        call TriggerRegisterTimerEventPeriodic( ticker, INTERVAL )
        call TriggerAddAction( ticker, function Tick )
        
    endfunction

    private function Actions takes nothing returns nothing
        local location casterLoc = GetUnitLoc(Caster)
        local location castLoc = GetSpellTargetLoc()
        
        call BJDebugMsg("Actions initialized")
        call SetGlobals()
        
        call CreateUnitAtLoc( PlayerID, DUMMY_ID, PolarProjectionBJ(casterLoc, INITIAL_DISTANCE, Angle), Angle )
        set Dummy = GetLastCreatedUnit()
        
        call BJDebugMsg(R2S(GetLocationX(casterLoc)) + "__" + R2S(GetLocationY(casterLoc)))
        call BJDebugMsg(R2S(GetLocationX(castLoc)) + "__" + R2S(GetLocationY(castLoc)))
        
        call StartTicking()
    endfunction

    private function Conditions takes nothing returns boolean
        local boolean b = GetSpellAbilityId() == SPELL_ID
        if(b) then
            call BJDebugMsg("true")
        else
            call BJDebugMsg("False")
        endif
        return b
    endfunction    
    
    //===========================================================================
    
    private function Init takes nothing returns nothing
    
        local trigger t = CreateTrigger(  )
        
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( t, Condition( function Conditions ) )
        call TriggerAddAction( t, function Actions )
        
        call BJDebugMsg("Trigger Created")
        
    endfunction

endscope

If you're wondering about the condition being after the actions, I have set it there to see if I could run the actions function through the conditions. Still, no luck. It's strange because not even the debug message is displayed for the actions, even when the conditions run true and I run actions through the conditions functions itself to bypass the trigger's call to the actions.

What could be wrong? I've tried to write it as cleanly as possible so it's easier to read. I feel there is an issue with the actions function itself, but it's still a mystery why it wouldn't run at all.

PS: Please bear with the lack of decomposition and structs. This spell is still in its primitive stages.
 
Level 14
Joined
Jun 27, 2008
Messages
1,325
Hm my first guess would be that the threat executing your action stops for some reason before the debug message is reached.
Try this:

JASS:
private function Actions takes nothing returns nothing
        local location casterLoc
        local location castLoc
        
        call BJDebugMsg("Actions initialized")
        set casterLoc = GetUnitLoc(Caster)
        set castLoc = GetSpellTargetLoc()
        call SetGlobals()
        [...]

If this works then you know that GetUnitLoc(Caster) or GetSpellTargetLoc() kills your threat. The most likely reason would be that "Caster" is not initialized (you read "Caster" before you call SetGlobals()). Reading uninitialized globals causes wc3 to crash the current threat.
 
Level 2
Joined
Sep 21, 2013
Messages
22
Thank you for the quick reply!

It appears that was the case. I had forgotten that I had initially attempted to set the globals inside the initialization trigger and hadn't changed the function to reflect the change back. It was attempting to get the location of "Caster" while "Caster" was not set... whoops.

Anyway, it's very appreciated. You helped me out quite a bit, haha.
 
Level 2
Joined
Sep 21, 2013
Messages
22
I am aware, I've been working on this for quite some time. I'm just running into the issue of "dD is not of a type that allows . syntax" in function Finish above the 2 structs. I believe I have to import a timer system in order to get what I need, just because I want to send my unit data across functions without using globals (unless globals are instanced and specific to each trigger queued).

Anyway, this is what I got so far. This is my first spell utilizing structs in over 4 years, so it's just mostly memory at this point. There's still a lot of cleanup, but 1 problem at a time, you know.

JASS:
scope MagicJavelin initializer Init
//*********************************************************************************************************************\\
//***********************************************config****************************************************************\\
//*********************************************************************************************************************\\

    globals
        private constant real       INTERVAL            = 0.03625   //Time between ticks
        
        private constant integer    SPELL_ID            = 'A002'    //Object data
        private constant integer    DUMMY_ID            = 'h001'
        
        private constant real       INITIAL_DISTANCE    = 200        //Range data/hit sensitivity
        private constant real       HIT_RADIUS          = 15        // !Do not exceed INITIAL_DISTANCE!
        
        private constant real       DAMAGE              = 50        //Initial damage
        private constant real       DAMAGE_PER_LEVEL    = 75        //Additional damage per level
        private constant real       DAMAGE_PER_SECOND   = 100       //Initial damage per second travelled
        private constant real       DAMAGE_PER_LVLSEC   = 150       //Additional damage per second travelled per level
        
        private constant real       INITIAL_SPEED       = 1500       //Initial travel speed
        private constant real       SPEED_CLIMB         = 20        //Amount of travel between ticks
        private constant real       SPEED_DIVISOR       = 100       //How much speed is divided by. Set to 100 for higher precision.
    endglobals
    
//Switches/Toggleables
    globals
        private constant boolean    FRIENDLY_FIRE       = true      //Determines if it hits allies
    endglobals
    
//Formulae    
    private function SpeedCalc takes nothing returns real
        return INITIAL_SPEED / SPEED_DIVISOR
    endfunction
    
    private function ClimbCalc takes integer nTicks returns real
        return SpeedCalc() + ((SPEED_CLIMB / SPEED_DIVISOR) * nTicks)
    endfunction
    
    private function DamageCalc takes integer level, integer nTicks returns real
    
    //Step 1: Find initial damage.
        local real Damage = DAMAGE + (DAMAGE_PER_LEVEL * (level - 1))
        
    //Step 2: Calculate Damage Per Second
        local real DPS = DAMAGE_PER_SECOND + (DAMAGE_PER_LVLSEC * (level - 1))
        
    //Step 3: Convert to Damage Per Tick
        set DPS = DPS / INTERVAL
        
    //Step 4: Calculate final damage based on time travelled.
        return Damage + (DPS * nTicks)
    endfunction
    
//*********************************************************************************************************************\\
//*******************************************End of config*************************************************************\\
//*********************************************************************************************************************\\

    globals
    
        private constant real pi = 3.1415926535
        
        private unit Caster
        
        private unit Dummy
        
        private group UnitHit
        
        private trigger ticker
        
    endglobals
    
//*********************************************************************************************************************\\
//********************************************struct data**************************************************************\\
//*********************************************************************************************************************\\
    private function GroupConds takes nothing returns boolean
        if(FRIENDLY_FIRE) then
            return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)) == false and (IsUnitAliveBJ(GetFilterUnit()) == true) and (GetFilterUnit() != Dummy)
        endif
        return (IsUnitType(GetFilterUnit(), UNIT_TYPE_STRUCTURE)) == false and (IsUnitAliveBJ(GetFilterUnit()) == true and not(IsUnitAlly( GetFilterUnit(), PlayerID )) and (GetFilterUnit() != Dummy))
    endfunction
    
    private function Finish takes nothing returns nothing
        local DummyData dD = GetUnitUserData(Dummy)
        call UnitDamageTargetBJ( Caster, GetEnumUnit(), DamageCalc(GetHeroLevel(Caster), dD.nTicks), ATTACK_TYPE_PIERCE, DAMAGE_TYPE_NORMAL )
        call RemoveUnit( Dummy )
        
        call dD.destroy()
    endmethod
    
    private struct CasterData
        unit u
        real x
        real y
        real angle
        
        real castX = GetSpellTargetX()
        real castY = GetSpellTargetY()
        
        player owner
        
        method init takes CasterData cD returns nothing
            set this.u = GetSpellAbilityUnit()
            
            set this.x = GetUnitX(this.u)
            set this.y = GetUnitY(this.u)
            
            set this.owner = GetOwningPlayer(this.u)
            set this.angle = angleBetweenCoords(this.x, this.y, this.castX, this.castY)
            
            set Caster = this.u
            call SetUnitUserData(this.u, cD)
        endmethod
        
        method angleBetweenCoords takes real x1, real y1, real x2, real y2 returns real
            local real deltaX = x2 - x1
            local real deltaY = y2 - y1
            
            return Atan2(deltaY, deltaX) * 180 / pi
        endmethod
    endstruct
    
    private struct DummyData
        unit dummy
        real x
        real y
        real angle
        integer nTicks = 0
        
        method init takes unit d, real a, DummyData dD returns nothing
            set this.dummy = d
            set this.angle = a
            
            set Dummy = d
            call SetUnitUserData(this.dummy, dD)
        endmethod
        
        method offsetX takes real x, real d returns real
            return x + d * Cos(Deg2Rad(this.angle))
        endmethod
        
        method offsetY takes real y, real d returns real
            return y + d * Sin(Deg2Rad(this.angle))
        endmethod
        
        method createDummy takes real x, real y, player p returns unit
            local real x2 = this.offsetX(x, INITIAL_DISTANCE)
            local real y2 = this.offsetY(y, INITIAL_DISTANCE)
            
            return CreateUnit(p, DUMMY_ID, x2, y2, a)
        endmethod
        
        method checkCollision takes nothing returns nothing
            local group g = CreateGroup()
            call GroupEnumUnitsInRange(g, this.x, this.y, HIT_RADIUS, function GroupConds)
            call DestroyBoolExpr(function GroupConds)
            
            call ForGroup(g, function this.finish)
        endmethod
        
        method move takes nothing returns nothing
            local x2 = this.offsetX(this.x, ClimbCalc(this.nTick))
            local y2 = this.offsetY(this.y, ClimbCalc(this.nTick))
            
            call SetUnitX(this.dummy, x2)
            call SetUnitY(this.dummy, y2)
            
            set this.x = x2
            set this.y = y2
            
            set this.nTicks = this.nTicks + 1
            call this.checkCollision()
        endmethod
    endstruct
    
//*********************************************************************************************************************\\
//*********************************************************************************************************************\\
//*********************************************************************************************************************\\
    
    private function Tick takes nothing returns nothing
        local DummyData dD = GetUnitUserData(Dummy)
        
        call dD.move()
    endfunction
    
//*********************************************************************************************************************\\
//**************************************************core***************************************************************\\
//*********************************************************************************************************************\\
    
    private function StartTicking takes nothing returns nothing
    
        set ticker = CreateTrigger()
        
        call TriggerRegisterTimerEventPeriodic( ticker, INTERVAL )
        call TriggerAddAction( ticker, function Tick )
        
    endfunction

    private function Actions takes nothing returns boolean
    
        local CasterData cD = CasterData.create()
        local DummyData dD = DummyData.create()
        
        if(GetSpellAbilityId() == SPELL_ID) then 
            call cD.init(cD)
            call dD.init(dD.createDummy(cD.x, cD.y, cD.owner), cD.a, dD)
        
            call StartTicking()
            
            return true
        endif
        
        cD.destroy()
        dD.destroy()
        return false
        
    endfunction
    
    //===========================================================================
    
    private function Init takes nothing returns nothing
    
        local trigger t = CreateTrigger(  )
        
        call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition( t, Condition( function Conditions ) )
        call TriggerAddAction( t, function Actions )
        
    endfunction

endscope

I'm just a little concerned if my structs contain too many methods and if I should remove some of the methods and place them elsewhere. I see a few that I can do that with, but not too many that would be convenient (at least without a timer system).

I still love this though. 5 years later and spells are still really fun to make.

UPDATE: Immediately I notice that I destroy dD twice, so.. just a small thing.
UPDATE 2: Also forgot to set the condition to actions... double whoops. It will cause an error later, but it's an easy fix.
 
Level 2
Joined
Sep 21, 2013
Messages
22
not really... even a single timer + a struct or lots of arrays utilizing dynamic indexing can do the job

mmm yes, but I come from java, where arrays are heaven, and I'm extremely biased and don't like arrays in other languages. This is no exception, but I probably don't need to go to the extent to have a full on timer system. Thanks.

muzzel said:
vJass doesnt allow forward declaration so you need to move the struct definition above the "Finish" function.

Ah, I see. Thanks. This language seems really backwards imo. Seems that it doesn't like to fit with just a standard ruleset in compiling.


Again, my thanks to the both of you. Seems a bit hard to learn Jass past a certain point. Luckily I have a lot of programming practice from other languages, and they apply quite well.
 
Level 2
Joined
Sep 21, 2013
Messages
22
Well, one thing down :) Now if only I could write a program that made me better at terraining. That would be a miracle, haha.
 
Status
Not open for further replies.
Top