• 🏆 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 randomly causes fatal error

Status
Not open for further replies.
Level 14
Joined
Jul 19, 2007
Messages
772
This is a JASS-based spell copied from a spellpack so I haven't made it myself. I've just made some simple editings like amount of life drained, healing, number of orbs summoned, duration and more... Well it sometimes randomly causes the game to crash in to fatal error while playing it online and especially if there is a lot of actions and spells casted while this spell also is casted. Any ideas what causes it and how to solve it?
JASS:
library ElvenOrbs initializer InitTrig_Elven_Orbs
// Spell Touch of Light by The_Witcher
//
// Creates orbs that patrol in a area, draining life from enemies.
// When their time has come they return and heal the caster and his allies!
//
// THE SETUP PART:

    globals
    // the rawcode of the spell
        private constant integer SPELL_ID = 'A0D3'
   
    // the rawcode of the dummy used as orb
        private constant integer ORB_ID = 'h01Z'
   
    // the range in which the orbs can drain
        private constant integer ORB_DRAIN_RANGE = 150
   
    // the timer interval (increase if laggy)
        private constant real INTERVAL = 0.02
   
    // the flying height of the orbs
        private constant real ORB_HEIGHT = 80
   
    // the time it takes a orb to drain life
        private constant real DRAIN_TIME = 0.5
   
    // false = only caster is healed
    // true = caster and near allies are healed
        private constant boolean AOE_HEAL = true
   
    // the sfx created on units while healing
        private constant string HEAL_SFX = "Abilities\\Spells\\Other\\HealingSpray\\HealBottleMissile.mdl"
   
    // the attachement point for HEAL_SFX
        private constant string HEAL_SFX_TARGET = "origin"
   
    // the sfx created on units while draining
        private constant string DRAIN_SFX = "Abilities\\Spells\\Human\\Slow\\SlowCaster.mdl"
   
    // the sfx created on a orb when it returns to the caster
        private constant string EXPLODE_SFX = "Abilities\\Spells\\Other\\HealingSpray\\HealBottleMissile.mdl"
   
    // the lightning created while draining
        private constant string LIGHTNING = "HWPB"
   
    // false = lightning is normal
    // true = lightnings endings are switched
        private constant boolean LIGHTNING_TURNED = true
    endglobals

    private function GetOrbAmount takes integer lvl returns integer
        return 5 // 2 orbs more each level !!CAN NEVER BE HIGHER THAN 50!!
    endfunction

    private function GetOrbDrainCooldown takes integer lvl returns real
        return 1.1 - 0.1 * lvl // 1sec / 0.9sec / 0.8sec / 0.7sec
    endfunction

    private function GetOrbAreaSize takes integer lvl returns real
        return 200. + 50 * lvl // 250 / 300 / 350 / 400 radius for the area where the orbs do their work
    endfunction

    private function GetDrainPercentage takes integer lvl returns real
        return 0.01 + 0.02 * lvl //draines 2% more each level
    endfunction

    private function GetDuration takes integer lvl returns real
        return 4. + 1 * lvl //lasts for 5 / 6 / 7 / 8... seconds
    endfunction

    private function GetHealPercentage takes integer lvl returns real
        return 0.15 //heals 2% more each level
    endfunction

    private function GetHealRange takes integer lvl returns real
        return 100. + 50 * lvl //heals in a AOE of 150 / 200 / 250 / 300...
    endfunction

    private function GetOrbSpeed takes integer lvl returns real
        return 5. + 1. * lvl // 6 / 7 / 8 / 9 per interval...
    endfunction

//---------------------------------------------------------
//-----------Don't modify anything below this line---------
//---------------------------------------------------------

    private struct data
        unit u
        real x
        real y
        integer lvl
        unit array orb [50]
        unit array target [50]
        integer array phase [50]
        real array drained [50]
        real array delay [50]
        lightning array light [50]
        timer tim = CreateTimer()
        integer orbs = 0
        real temp = 19
        real t = 0
    endstruct

    globals
        private hashtable h = InitHashtable()
        private data DATA
        private group g = CreateGroup()
        private location loc = Location(0, 0)
    endglobals

    private function AngleBetweenCoords takes real x, real y, real xx, real yy returns real
        return Atan2(yy - y, xx - x)
    endfunction

    private function DistanceBetweenCoords takes real x , real y , real xx , real yy returns real
        return SquareRoot((x - xx) * (x - xx) + (y - yy) * (y - yy))
    endfunction

    private function DistanceBetweenUnits takes unit a, unit b returns real
        local real dx = GetUnitX(b) - GetUnitX(a)
        local real dy = GetUnitY(b) - GetUnitY(a)
        return SquareRoot(dx * dx + dy * dy)
    endfunction

    private function AngleBetweenUnits takes unit a, unit b returns real
        return Atan2(GetUnitY(b) - GetUnitY(a), GetUnitX(b) - GetUnitX(a))
    endfunction

    private function EnemiesOnly takes nothing returns boolean
        local unit u = GetFilterUnit()
        if IsUnitEnemy(u, GetOwningPlayer(DATA.u)) and not(IsUnitType(u, UNIT_TYPE_STRUCTURE)) and not(IsUnitType(u, UNIT_TYPE_MECHANICAL)) and not(IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and GetUnitState(u, UNIT_STATE_LIFE) > 0.405 and u != null then
            set u = null
            return true
        endif
        set u = null
        return false
    endfunction

    private function AlliesOnly takes nothing returns boolean
        local unit u = GetFilterUnit()
        if IsUnitAlly(u, GetOwningPlayer(DATA.u)) and not(IsUnitType(u, UNIT_TYPE_STRUCTURE)) and not(IsUnitType(u, UNIT_TYPE_MECHANICAL)) and not(IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and GetUnitState(u, UNIT_STATE_LIFE) > 0.405 and u != null then
            set u = null
            return true
        endif
        set u = null
        return false
    endfunction

    private function AOEheal takes nothing returns nothing
        call SetWidgetLife(GetEnumUnit(), GetWidgetLife(GetEnumUnit()) + DATA.temp * GetHealPercentage(DATA.lvl))
        call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX, GetEnumUnit(), HEAL_SFX_TARGET))
    endfunction

    function RandomEnemyUnit takes real x, real y, real range returns unit
        local unit a = null
        local unit i
        local integer b = 0
        call GroupClear(g)
        call GroupEnumUnitsInRange(g, x, y, range, Condition(function EnemiesOnly))
        loop
            set i = FirstOfGroup(g)
            exitwhen i == null
            set b = b + 1
            if (GetRandomInt(1, b) == 1) then
                set a = i
            endif
            call GroupRemoveUnit(g, i)
        endloop
        set bj_groupRandomCurrentPick = a
        set a = null
        return bj_groupRandomCurrentPick
    endfunction

    private function Execute takes nothing returns nothing
        local data dat = LoadInteger(h, GetHandleId(GetExpiredTimer()), 0)
        local real X
        local real Y
        local real a
        local integer i = 0
    //****check whether caster is alive****
        if (IsUnitType(dat.u, UNIT_TYPE_DEAD) or GetUnitTypeId(dat.u) == 0 ) then
            loop
                exitwhen i >= dat.orbs
                call RemoveUnit(dat.orb[i])
                call DestroyLightning(dat.light[i])
                set i = i + 1
            endloop
            set dat.orbs = 0
        else
        //****increase time****
            set dat.t = dat.t + INTERVAL
        //****new orbs****
            if dat.orbs < IMinBJ(GetOrbAmount(dat.lvl), 50) and dat.t <= GetDuration(dat.lvl) then
                set dat.temp = dat.temp + 1
                if dat.temp == 20 then
                    set dat.orb[dat.orbs] = CreateUnit(GetOwningPlayer(dat.u), ORB_ID, GetUnitX(dat.u), GetUnitY(dat.u), 0)
                    call UnitAddAbility(dat.orb[dat.orbs], 'Aloc')
                    call UnitAddAbility(dat.orb[dat.orbs], 'Amrf')
                    call SetUnitFlyHeight(dat.orb[dat.orbs], ORB_HEIGHT, 0)
                    set dat.temp = 0
                    set DATA = dat
                    set dat.target[dat.orbs] = RandomEnemyUnit(dat.x, dat.y, GetOrbAreaSize(dat.lvl))
                    set dat.orbs = dat.orbs + 1
                endif
            endif
        //****process all active orbs****
            loop
                exitwhen i >= dat.orbs
                if dat.phase[i] == 0 then
            //****search a target and drain****
                //****reduce cooldown****
                    if dat.delay[i] > 0 then
                        set dat.delay[i] = dat.delay[i] - INTERVAL
                        if dat.delay[i] < 0 then
                            set dat.delay[i] = 0
                        endif
                        if dat.delay[i] <= GetOrbDrainCooldown(dat.lvl) then
                            if IsUnitPaused(dat.orb[i]) then
                                call DestroyLightning(dat.light[i])
                                call PauseUnit(dat.orb[i], false)
                            //****new target****
                                set DATA = dat
                                set dat.target[i] = RandomEnemyUnit(dat.x, dat.y, GetOrbAreaSize(dat.lvl))
                            endif
                        else
                            call MoveLocation(loc, GetUnitX(dat.orb[i]), GetUnitY(dat.orb[i]))
                            set a = GetLocationZ(loc)
                            call MoveLocation(loc, GetUnitX(dat.target[i]), GetUnitY(dat.target[i]))
                            if LIGHTNING_TURNED then
                                call MoveLightningEx(dat.light[i], true, GetUnitX(dat.target[i]), GetUnitY(dat.target[i]), GetLocationZ(loc) + 50, GetUnitX(dat.orb[i]), GetUnitY(dat.orb[i]), a + ORB_HEIGHT)
                            else
                                call MoveLightningEx(dat.light[i], true, GetUnitX(dat.orb[i]), GetUnitY(dat.orb[i]), a + ORB_HEIGHT, GetUnitX(dat.target[i]), GetUnitY(dat.target[i]), GetLocationZ(loc) + 50)
                            endif
                        endif
                    endif
                //****if time is up and the orb isn't draining, start returning to the caster****
                    if dat.delay[i] <= GetOrbDrainCooldown(dat.lvl) and dat.t > GetDuration(dat.lvl) then
                        set dat.phase[i] = 1
                    elseif dat.target[i] != null then
                //****distance to target check*****
                        if DistanceBetweenUnits(dat.target[i], dat.orb[i]) < ORB_DRAIN_RANGE then
                        //****no cooldown left?****        
                            if dat.delay[i] == 0 then
                            //****drain****
                                set X = GetUnitX(dat.target[i])
                                set Y = GetUnitY(dat.target[i])
                                set a = GetWidgetLife(dat.target[i]) * GetDrainPercentage(dat.lvl)
                                set dat.drained[i] = dat.drained[i] + a
                                call UnitDamageTarget(dat.orb[i], dat.target[i], a, false, false, null, DAMAGE_TYPE_UNIVERSAL, null)
                            //****effects****
                                call DestroyEffect(AddSpecialEffect(DRAIN_SFX, X, Y))
                                if LIGHTNING_TURNED then
                                    set dat.light[i] = AddLightningEx(LIGHTNING, true, X, Y, GetUnitFlyHeight(dat.target[i]) + 50, GetUnitX(dat.orb[i]), GetUnitY(dat.orb[i]), ORB_HEIGHT)
                                else
                                    set dat.light[i] = AddLightningEx(LIGHTNING, true, GetUnitX(dat.orb[i]), GetUnitY(dat.orb[i]), ORB_HEIGHT, X, Y, GetUnitFlyHeight(dat.target[i]) + 50)
                                endif
                            //****cooldown****
                                call PauseUnit(dat.orb[i], true)
                                set dat.delay[i] = GetOrbDrainCooldown(dat.lvl) + DRAIN_TIME
                            endif
                    //****distance too huge so go nearer****
                        elseif not IsUnitPaused(dat.orb[i]) then
                            set a = AngleBetweenUnits(dat.orb[i], dat.target[i])
                            set X = GetUnitX(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Cos(a)
                            set Y = GetUnitY(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Sin(a)
                            call SetUnitX(dat.orb[i], X)
                            call SetUnitY(dat.orb[i], Y)
                        endif
                    else
                    //****new target****
                        set DATA = dat
                        set dat.target[i] = RandomEnemyUnit(dat.x, dat.y, GetOrbAreaSize(dat.lvl))
                    endif
                else
            //****Time is up so return to caster****
                    set a = AngleBetweenCoords(GetUnitX(dat.orb[i]), GetUnitY(dat.orb[i]), GetUnitX(dat.u), GetUnitY(dat.u))
                    set X = GetUnitX(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Cos(a)
                    set Y = GetUnitY(dat.orb[i]) + GetOrbSpeed(dat.lvl) * Sin(a)
                    call SetUnitX(dat.orb[i], X)
                    call SetUnitY(dat.orb[i], Y)
                //****caster reached so heal****
                    if DistanceBetweenCoords(X, Y, GetUnitX(dat.u), GetUnitY(dat.u)) < 10 then
                        call RemoveUnit(dat.orb[i])
                        call DestroyEffect(AddSpecialEffect(EXPLODE_SFX, X, Y))
                        if AOE_HEAL then
                            set DATA = dat
                            set dat.temp = dat.drained[i]
                            call GroupEnumUnitsInRange(g, X, Y, GetHealRange(dat.lvl), Condition(function AlliesOnly))
                            call ForGroup(g, function AOEheal)
                        else
                            call SetWidgetLife(dat.u, GetWidgetLife(dat.u) + dat.drained[i] * GetHealPercentage(dat.lvl))
                            call DestroyEffect(AddSpecialEffectTarget(HEAL_SFX, dat.u, HEAL_SFX_TARGET))
                        endif
                        set dat.orbs = dat.orbs - 1
                        set dat.orb[i] = dat.orb[dat.orbs]
                        set i = i - 1
                    endif
                endif
                set i = i + 1
            endloop
        endif
    //****no orbs left ==> end spell****
        if dat.orbs == 0 then
            call FlushChildHashtable(h, GetHandleId(dat.tim))
            call DestroyTimer(dat.tim)
            call dat.destroy()
        endif
    endfunction

    private function Cast takes nothing returns boolean
        local data dat
        local integer i = 0
        if GetSpellAbilityId() == SPELL_ID then
            set dat = data.create()
            loop
                exitwhen i == 50
                set dat.phase[i] = 0
                set dat.drained[i] = 0
                set dat.delay[i] = 0
                set i = i + 1
            endloop
            set dat.x = GetSpellTargetX()
            set dat.y = GetSpellTargetY()
            set dat.u = GetTriggerUnit()
            set dat.lvl = GetUnitAbilityLevel(dat.u, SPELL_ID)
            call SaveInteger(h, GetHandleId(dat.tim), 0, dat)
            call TimerStart(dat.tim, INTERVAL, true, function Execute)
        endif
        return false
    endfunction

    private function InitTrig_Elven_Orbs takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerAddCondition(t, Condition(function Cast))
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    endfunction

endlibrary


//Code indented using The_Witcher's Script Language Aligner
//Download the newest version and report bugs at [url]www.hiveworkshop.com[/url]
 
Level 14
Joined
Jul 19, 2007
Messages
772
Under the lines where you have "call DestroyLightning(dat.light)", do this:

set dat.light = null
Hmm I did it now but are you sure that's gonna solve the problem? When I'm looking through the "Syntax Errors" I'm getting this report
errors.jpg
so what does it mean??
 
Level 14
Joined
Jul 19, 2007
Messages
772
At the top, change the initializer name to Init and do the same with the function at the bottom with the InitTrig_ name.

Oh so that was the problem! :eek: Thank you so much! It seems to be working correctly now so far :)
Well the spell doesn't seems to cause fatal error anymore and that's very nice but there is a problem with the "destroy lightning" thing because sometimes if a lot of the orbs drains life from the same target at the sametime the lightning effects wont be gone sometimes :-/ so why does it happening?
 
Status
Not open for further replies.
Top