• 🏆 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!
  • ✅ Time to vote for the top 3 models! The POLL for Hive's 6th HD Modeling Contest: Mechanical is now open! 📅 Poll close on July 16, 2024! 🔗 Cast your vote now!
  • ✅ The POLL for Hive's Texturing Contest #33 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!

[vJASS] Second Attempt at vJass Spell

Status
Not open for further replies.
Level 9
Joined
Apr 23, 2011
Messages
460
After reading through other spells, and tutorials and what not, I have come up with this, and I'm not exactly sure what could be wrong with it... ideas to fix? Or just trash it.

JASS:
library ArcDrain initializer init uses RegisterPlayerUnitEvent
    globals
        private constant integer SPELL_ID  = 'ArcD'
        private constant integer BUFF_ID   = 'Arcs'
        private constant real MSPEED       = .35
        private constant real TIMERTIME    = 4.
        private constant real TURNOVER     = .25 
    endglobals
    
  struct ArcDrain
        private real TIMEOUT
        private boolean DEALLOCATEME
        private timer TIMER
        private unit CASTER 
        private unit TARGET
        private real DRAIN
        
        private method destroy takes nothing returns nothing
            set this.CASTER = null
            set this.TARGET = null
            set this.TIMER = null
            call this.deallocate()
        endmethod
        
        private method drainer takes nothing returns nothing
            if this.TIMEOUT <= 0 then
                call DestroyTimer(this.TIMER)
                set this.DEALLOCATEME = true
            endif
            call SetUnitState(this.TARGET, UNIT_STATE_MANA, (GetUnitState(this.TARGET, UNIT_STATE_MANA) - this.DRAIN))
            call SetUnitState(this.CASTER, UNIT_STATE_MANA, (GetUnitState(this.CASTER, UNIT_STATE_MANA) + R2I(this.DRAIN * TURNOVER)))
            set this.TIMEOUT = this.TIMEOUT - 1
            if this.DEALLOCATEME then
                call this.deallocate()
            endif
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this 
            call this.drainer()
        endmethod
        
        private static method spell takes nothing returns nothing
            local thistype this = thistype.create()
            set this.CASTER = GetSpellAbilityUnit()
            set this.TARGET = GetSpellTargetUnit()
            set this.DRAIN = ((GetUnitAbilityLevel(this.CASTER, SPELL_ID)*20)+50)
            if not IsUnitType(this.TARGET, UNIT_TYPE_DEAD) and not IsUnitAlly(this.TARGET, Player(GetPlayerId(GetOwningPlayer(this.CASTER)))) then
                call UnitAddAbility(this.TARGET, BUFF_ID)
                set this.TIMER = CreateTimer()
                set this.TIMEOUT = TIMERTIME
                call TimerStart(this.TIMER, 1., true, function thistype.periodic)
            else
                call .destroy()
            endif
        endmethod
        
       static method cast takes nothing returns nothing
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.spell()
                return
            endif
        endmethod
    endstruct
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function ArcDrain.cast)
        set t = null
    endfunction
endlibrary
 
You've got the right idea, but you have no data attachment/data storage, so you have no way of retrieving the data from the struct instance you just created.

Usually the easiest way to fix this is to use a timer system. In this example, I am using TimerUtils. All you need to do is assign the data wherever you start the timer, and then get the data from the expired timer in the "periodic" function. To do that, simply do:
JASS:
library ArcDrain initializer init uses RegisterPlayerUnitEvent, TimerUtils
    globals
        private constant integer SPELL_ID  = 'ArcD'
        private constant integer BUFF_ID   = 'Arcs'
        private constant real MSPEED       = .35
        private constant real TIMERTIME    = 4.
        private constant real TURNOVER     = .25 
    endglobals
    
  struct ArcDrain
        private real TIMEOUT
        private boolean DEALLOCATEME
        private timer TIMER
        private unit CASTER 
        private unit TARGET
        private real DRAIN
        
        private method destroy takes nothing returns nothing
            set this.CASTER = null
            set this.TARGET = null
            set this.TIMER = null
            call this.deallocate()
        endmethod
        
        private method drainer takes nothing returns nothing
            if this.TIMEOUT <= 0 then
                call ReleaseTimer(this.TIMER)
                set this.DEALLOCATEME = true
            endif
            call SetUnitState(this.TARGET, UNIT_STATE_MANA, (GetUnitState(this.TARGET, UNIT_STATE_MANA) - this.DRAIN))
            call SetUnitState(this.CASTER, UNIT_STATE_MANA, (GetUnitState(this.CASTER, UNIT_STATE_MANA) + R2I(this.DRAIN * TURNOVER)))
            set this.TIMEOUT = this.TIMEOUT - 1
            if this.DEALLOCATEME then
                call this.deallocate()
            endif
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this = GetTimerData(GetExpiredTimer())
            call this.drainer()
        endmethod
        
        private static method spell takes nothing returns nothing
            local thistype this = thistype.create()
            set this.CASTER = GetSpellAbilityUnit()
            set this.TARGET = GetSpellTargetUnit()
            set this.DRAIN = ((GetUnitAbilityLevel(this.CASTER, SPELL_ID)*20)+50)
            if not IsUnitType(this.TARGET, UNIT_TYPE_DEAD) and not IsUnitAlly(this.TARGET, Player(GetPlayerId(GetOwningPlayer(this.CASTER)))) then
                call UnitAddAbility(this.TARGET, BUFF_ID)
                set this.TIMER = NewTimer()
                set this.TIMEOUT = TIMERTIME
                call SetTimerData(this.TIMER, this)
                call TimerStart(this.TIMER, 1., true, function thistype.periodic)
            else
                call .destroy()
            endif
        endmethod
        
       static method cast takes nothing returns nothing
            if GetSpellAbilityId() == SPELL_ID then
                call thistype.spell()
                return
            endif
        endmethod
    endstruct
    
    private function init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function ArcDrain.cast)
        set t = null
    endfunction
endlibrary

Of course, now that you actually have a value for this in the periodic function, you don't really have to call a separate function to perform the actions; you can simply perform the actions within that function itself. But I just left everything as it was to demonstrate the procedure, optimizations can be done later.
 
Level 9
Joined
Apr 23, 2011
Messages
460
Well, there isn't really an error. I fixed some of it, will update OP, the buff wasn't being applied, but now it is and the spell isn't looping through removing mana nor giving me any. I could try debugging but I don't feel well right now.

Edit: New errors found. If my mana is at full, or their mana is empty, it returns a double free error o.o I'll have to find a catchall for these errors to check if my mana is max, then don't return any mana, and if their mana is below the range of drain, set the drain value to their total remaining mana, drain it, and then destroy the instance of the struct.
 
Last edited:
Status
Not open for further replies.
Top