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

[JASS] Spell with cast delay only triggers once

Status
Not open for further replies.
Level 2
Joined
May 24, 2019
Messages
21
Quick summary about my experience: I've used GUI for years, but wanted to improve myself so I've learnt about the details of hashtables yesterday and then started with JASS just this morning, so sorry for any noobish mistakes.

About the spell: When beginning casting nothing happens for 3 seconds (channeling phase), then a lightning bolt strikes, damaging the area for a damage amount depending on the intelligence of the caster.
Since I want to make it MUI I've used hashtables now for the first time but have no clue if they work as intended.

However, there are two strange occurences when casting:
  1. When casting the spell for the first time, everything works as intended besides no bounty is received if a hostile unit is killed by the spell (even though bounty usually works).
  2. When casting the spell another time, nothing seems to work after the first part (the timer initialization) - I'm not sure what I've damaged, maybe when trying to clean up memory leaks?
It would be awesome if you could help me making it work and also any other feedback on my work is highly appreciated (is it actually MUI, can I make it more efficient etc.).

Thanks in advance!


JASS:
constant function Lightstrike_ID takes nothing returns integer
     return 'A03J'
 endfunction

 function Trig_LightningStrikeJASS_Conditions takes nothing returns boolean
     return GetSpellAbilityId() == Lightstrike_ID()
 endfunction

function actual_spell takes nothing returns nothing   

    local location targetloc  
    local unit caster   
    local unit dummy
    local location casterloc
    local player casterowner
    local integer playerid
    local real defface = 0.
    local integer spelllevel
    local integer inthero
    local real spelldamage

    set targetloc = LoadLocationHandleBJ(0, GetHandleIdBJ(GetEnumUnit()), udg_hasht1)
    set caster = LoadUnitHandleBJ(1, GetHandleIdBJ(GetEnumUnit()), udg_hasht1)
    set casterloc = GetUnitLoc(caster)
    set casterowner = GetOwningPlayer(caster)
    set playerid = GetPlayerId(casterowner)
    set defface = 0.
    set spelllevel = GetUnitAbilityLevel(caster,Lightstrike_ID)
    set inthero = GetHeroInt(caster,true)
    set spelldamage = inthero * (5 + spelllevel)
   
    call CreateUnitAtLoc(casterowner, 'h00I', casterloc, defface)
        set dummy = GetLastCreatedUnit()
        call UnitApplyTimedLifeBJ( 1.00, 'BTLF', dummy)
        call UnitDamagePointLoc( dummy, 0.3, 30.00, targetloc, spelldamage, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL )
        call AddSpecialEffectLocBJ( targetloc, "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl" )
        call DestroyEffectBJ(bj_lastCreatedEffect)
        call RemoveLocation (targetloc)
        call RemoveLocation (casterloc)
        call FlushChildHashtableBJ(GetHandleIdBJ(GetEnumUnit()), udg_hasht1)
        call GroupRemoveUnit(udg_lightningstrikegroup,GetEnumUnit())
        set caster = null
        set dummy = null
        set casterowner = null
       

endfunction


function spell_init takes nothing returns nothing
     local timer t=GetExpiredTimer()
     call PauseTimer(t)
     call DestroyTimer(t)
     set t=null
    call ForGroup(udg_lightningstrikegroup, function actual_spell)
endfunction

function Trig_LightningStrikeJASS_Actions takes nothing returns nothing

    local timer t
    local location targetloc = GetSpellTargetLoc()
    local unit caster = GetSpellAbilityUnit()
   
    call GroupAddUnit(udg_lightningstrikegroup,caster)
    call SaveLocationHandleBJ(targetloc,0,GetHandleId(caster),udg_hasht1)
    call SaveUnitHandleBJ(caster,1,GetHandleId(caster),udg_hasht1)
   
    call CreateTimer()
    set t = GetLastCreatedTimerBJ()
    call TimerStart(t, 3.00, false, function spell_init)
    set t = null  
 endfunction


//===========================================================================
function InitTrig_LightningStrikeJASS takes nothing returns nothing
    set gg_trg_LightningStrikeJASS = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_LightningStrikeJASS, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_LightningStrikeJASS, Condition( function Trig_LightningStrikeJASS_Conditions ) )
    call TriggerAddAction( gg_trg_LightningStrikeJASS, function Trig_LightningStrikeJASS_Actions )
endfunction
 
Is the hashtable ever created? set udg_hash1 = InitHashtable()

The lastCreatedTimer function should not be used, it's for set, yet. It's only set if the BJ functions are called to create timers -- but you use JASS and call the native directly. So you can do:
local timer watch = CreateTimer()

The concept with group is not needed. What you want with timer and hashtable is basically this concept:
  • Attach all data to timer, start the timer with some timeout
  • When timer expired, load all attached data again
... so we don't want the caster unit as key to load and to save data, but only the timer. The caster will be one data, attached to the timer.

Example with your save and loading:
JASS:
function spellInit takes nothing returns nothing
    local timer watch = GetExpiredTimer()
    local integer hashKey = GetHandleId(watch)
    local unit caster = LoadUnitHandle(hash, hashKey, 0)
    local location castLoc = LoadLocationHandle(hash, hashKey, 1)
 
    // .. some actions using the loaded data

    call FlushChildHashtable(hash, hashKey) // clear entries from hashtable
    call DestroyTimer(watch)
    set watch = null
    set caster = null
    set castLoc = null
endfunction

function onCast takes nothing returns nothing
    local timer watch = CreateTimer()
    local unit caster = GetSpellAbilityUnit()
    local location castLoc = GetSpellTargetLoc()
    local integer hashKey = GetHandleId(watch)
 
    // attach data to key
    call SaveUnitHandle(hash, hashKey, 0, caster)
    call SaveLocationHandle(hash, hashKey, 1, castLoc)
 
    call TimerStart(watch, 3.00, false, function spell_init)
 
    set castLoc = null
    set caster = null
    set watch = null
endfunction
You see you can diretly use the natives from common.j, too, to save/load, and don't have to use the BJs from blizzard.j.

You might want have a look at JASS Class.
 
Last edited:
Level 2
Joined
May 24, 2019
Messages
21
[...]

You might want have a look at JASS Class.

Thank you so much, that's very helpful already, will look into that!

I've tried to start with the JASS Class, but it looks like it would be good to work with vJASS instead (some solutions posted by other members are made with vJASS).
However I can't find out how to install it, since JNGP won't work anymore.
Could please tell me how I currently can work with vJASS, since I want to learn everything correct from scratch and don't wanna miss out on functionalities like libraries and so on.

Thanks in advance!
 
Last edited:
Level 8
Joined
May 21, 2019
Messages
435
If the bounty isn't working, then the problem is probably that the dummy unit (which is doing the damage) is not correctly getting the right player. You could try having the trigger spit out the variables in in-game messages to check if they are actually what you'd expect them to be.
 
Level 2
Joined
May 24, 2019
Messages
21
Thanks, very helpful! I've solved the issue now (by completely removing the dummy unit), so happy if the thread would get closed.
 
Status
Not open for further replies.
Top