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

Void Portal spell effects linger

Status
Not open for further replies.
Hi guys. Another dev on a map of mine re-created a spell from hive to make it more lightweight. The spell is an AoE ground target spell that basically creates a black hole that damages units and pulls them towards the middle. The spell works great, but it's bugged to where if a unit gets hit by it and survives, that unit is likely to just start spinning randomly later on in the game. This seems to mainly happen if the unit is in a tight space and is trying to leave said tight space. Here's the script:
JASS:
scope SpellSucc

    globals
        private constant integer voidPortalMaxIndex = 10
        private constant integer ID = 'Avpt'
        private constant string MODEL = "war3mapImported\\Void Rift II Orange.mdx"
        private constant real DURATION = 5.00
        private constant real TICKRATE = 0.03
        private constant real DAMAGE = 100
        private constant real AOE = 350
        private constant real VELOCITY_CENTER = 40
        private constant real VELOCITY_ROTATION = 275
        private constant boolean CHECK_PATHING = false
        private constant boolean ALWAYS_PULL_CENTER = true
        private constant boolean PAUSE_UNITS = false
        private constant boolean DISABLE_MOVEMENT = true
        private constant boolean MOVE_DEAD_UNITS = false
        private constant boolean DELETE_CORPSES = false
        private constant real CORPSE_DELETE_RADIUS = 100.00
        
    endglobals

    private struct VoidPortal
        static integer currentIndex = 0
        static VoidPortal array all
        
        integer index
        unit caster
        group affectedUnits = CreateGroup()
        location targetLoc
        effect sfx
        real timerDuration
            
        static method GetIndex takes nothing returns integer
            local integer i
            if VoidPortal.currentIndex >= voidPortalMaxIndex then
                set VoidPortal.currentIndex = 1
                set i = 0
            else
                set i = VoidPortal.currentIndex
                set VoidPortal.currentIndex = VoidPortal.currentIndex + 1
            endif
            return i
        endmethod
        
        static method create takes nothing returns VoidPortal
            local VoidPortal t = VoidPortal.allocate()
            set t.index = GetIndex()
            set VoidPortal.all[t.index] = t
            
            
            return t
        endmethod
        
        method destroy takes nothing returns nothing
            local unit u
            loop
                set u = FirstOfGroup(this.affectedUnits)
                exitwhen u == null
                if DISABLE_MOVEMENT then
                    call SetUnitPropWindow(u,GetUnitDefaultPropWindow(u))
                    call SetUnitMoveSpeed(u,GetUnitDefaultMoveSpeed(u))
                endif
                if PAUSE_UNITS then
                    call PauseUnit(u,false)
                endif
                call GroupRemoveUnit(this.affectedUnits,u)
            endloop
            call DestroyGroup(this.affectedUnits)
            set this.affectedUnits = null            
            call RemoveLocation(targetLoc)
            set targetLoc = null
            set caster = null
            call DestroyEffect(sfx)
            set sfx = null
            set VoidPortal.all[index] = 0
            set u = null
            call this.deallocate()
        endmethod
    
    endstruct


    private function CheckUnitConditions takes unit filter, VoidPortal t returns boolean
        
        
        local location locFilter = GetUnitLoc(filter)

        local boolean isEnemy = IsUnitEnemy(filter, GetOwningPlayer(t.caster))
        
        local boolean isNotMagicImmune = not IsUnitType(filter,UNIT_TYPE_MAGIC_IMMUNE)

    local boolean isNotAsh = not IsUnitType(filter,UNIT_TYPE_SAPPER)

        if GetUnitTypeId(filter) == 'e008' then
            set filter = null
            return false
        endif
        
        call RemoveLocation(locFilter)
        set locFilter = null
        
        
        if isEnemy and isNotMagicImmune and isNotAsh and (GetUnitState(filter, UNIT_STATE_LIFE) > 0.405 or MOVE_DEAD_UNITS) and  not BlzIsUnitInvulnerable(filter) then
            set filter = null
            return true
        else
            set filter = null
            return false
        endif

    return filter != t.caster
    endfunction
    
    
    private function MovementLoop takes VoidPortal t returns nothing
    local group unitsInRange = CreateGroup()
    local group unitsInRangeAll = CreateGroup()
    local unit f
    local location locEnum
    local real angle //angle to center
    local real angle2 //angle to center -90, clockwise rotation
    local real destx
    local real desty
    
    if t.timerDuration <= 0 then
        call t.destroy()    
    else
        
        set unitsInRangeAll = GetUnitsInRangeOfLocAll(AOE,t.targetLoc)
        loop
            set f = FirstOfGroup(unitsInRangeAll)
            exitwhen (f == null)
            
            if CheckUnitConditions(f,t) then
                call GroupAddUnit(unitsInRange,f)
            endif
            
            call GroupRemoveUnit(unitsInRangeAll,f)
        endloop
        set f = null

        
        loop
            set f = FirstOfGroup(unitsInRange)
            exitwhen (f == null)
            call GroupRemoveUnit(unitsInRange,f)
            //move units
            set locEnum = GetUnitLoc(f)
            
            
            call UnitDamageTarget(t.caster, f, (DAMAGE * TICKRATE * GetUnitAbilityLevel(t.caster,ID))/DURATION,true,true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_DEATH,WEAPON_TYPE_WHOKNOWS)
            
            if DELETE_CORPSES and DistanceBetweenPoints(t.targetLoc, locEnum) < CORPSE_DELETE_RADIUS and IsUnitDeadBJ(f) then
                call RemoveUnit(f)
            endif
            
            
            
            set angle = AngleBetweenPoints(locEnum, t.targetLoc)
            set angle2 = angle+90
            if angle2 > 360 then
                set angle2 = angle2 - 360
            endif
            

            
            
            
            set destx = (GetUnitX(f) + (TICKRATE * VELOCITY_CENTER) * Cos(angle * bj_DEGTORAD))
            set desty = (GetUnitY(f) + (TICKRATE * VELOCITY_CENTER) * Sin(angle * bj_DEGTORAD))
            set destx = (destx + (TICKRATE * VELOCITY_ROTATION) * Cos(angle2 * bj_DEGTORAD))
            set desty = (desty + (TICKRATE * VELOCITY_ROTATION) * Sin(angle2 * bj_DEGTORAD))
            
            //dont move these units
            if (not (IsUnitType(f, UNIT_TYPE_STRUCTURE)) or not (IsUnitType(f, UNIT_TYPE_ANCIENT))) then
            
                if DISABLE_MOVEMENT then
                    call SetUnitFacing(f,angle)
                    call SetUnitPropWindow(f, 0)
                endif
                if PAUSE_UNITS then
                    call PauseUnit(f, true)
                endif
                call GroupAddUnit(t.affectedUnits,f)
            
            
                if CHECK_PATHING then
                    if IsTerrainWalkable(destx,desty) or IsUnitDeadBJ(f) then
                        call SetUnitX(f,destx)
                        call SetUnitY(f,desty)
                    elseif ALWAYS_PULL_CENTER then
                        call SetUnitX(f,GetUnitX(f) + (TICKRATE * VELOCITY_CENTER) * Cos(angle * bj_DEGTORAD))
                        call SetUnitY(f,GetUnitY(f) + (TICKRATE * VELOCITY_CENTER) * Sin(angle * bj_DEGTORAD))
                    endif
                else
                    call SetUnitX(f,destx)
                    call SetUnitY(f,desty)
                endif
            endif

            call RemoveLocation(locEnum)

            set locEnum = null
            
        endloop   
        call DestroyGroup(unitsInRange)
        set unitsInRange = null
        call DestroyGroup(unitsInRangeAll)
        set unitsInRangeAll = null
        set t.timerDuration = t.timerDuration - TICKRATE
    endif

    
        
    endfunction


    private function isSpellID takes nothing returns boolean
        return GetSpellAbilityId() == ID
    endfunction



    private function timerExpired takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i == voidPortalMaxIndex
            if (VoidPortal.all[i] != 0) then
                call MovementLoop(VoidPortal.all[i])
            endif
            set i = i+1
        endloop
    endfunction


function KillTrees takes nothing returns nothing
    if ( GetDestructableTypeId(GetEnumDestructable()) == 'ATtr' ) then
        call KillDestructable( GetEnumDestructable())
    endif
endfunction

    private function SuccActions takes nothing returns nothing
        local VoidPortal t = VoidPortal.create()
        set t.caster = GetTriggerUnit()
        set t.targetLoc = GetSpellTargetLoc()
        set t.timerDuration = DURATION
        set t.sfx = AddSpecialEffectLocBJ(t.targetLoc, MODEL)
    call BlzSetSpecialEffectScale( t.sfx, 1.45 )
        call BlzSetSpecialEffectHeight(t.sfx,GetLocationZ(t.targetLoc))
        set udg_TmpPoint6 = GetSpellTargetLoc()
    if ( GetUnitAbilityLevelSwapped(GetSpellAbilityId(), GetTriggerUnit()) >= 3 )then
        call EnumDestructablesInCircleBJ( 350.00, udg_TmpPoint6, function KillTrees )
        call RemoveLocation(udg_TmpPoint6)
    endif
    
    endfunction

    //===========================================================================
    function InitTrig_VoidPortal takes nothing returns nothing
        local trigger t = CreateTrigger()
        set gg_trg_VoidPortal = CreateTrigger(  )
        call TriggerRegisterAnyUnitEventBJ(gg_trg_VoidPortal,EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_VoidPortal, Condition(function isSpellID))
        call TriggerAddAction( gg_trg_VoidPortal, function SuccActions )
        call TriggerRegisterTimerEvent(t,TICKRATE,true)
        call TriggerAddAction(t,function timerExpired)
    endfunction

endscope

what do I do here? thanks in advance
 
Level 39
Joined
Feb 27, 2007
Messages
5,011
I realize you didn't write this but still:
  1. Don't use triggers for timers. Just use timers directly, there's no need to involve a trigger.
  2. BJs and other useless wrapper functions like GetUnitAbilityLevelSwapped() and AddSpecialEffectLocBJ() should never be used.
  3. Switch to using XY coordinates directly and never ever touch locations again unless you need GetLocationZ().
  4. The GetIndex() method is just... why? Totally pointless and can actually fuck up the whole thing in the right situation (
I stopped looking at things I could nitpick at this point, but IMO there are a lot of general methodology things that could be fixed/cleaned up. Direct your dev to me and I'll give them feedback if they care. I have a guess as to what the issue is: something in the instance indexing is getting borked and the .destroy() method is never called on that instance of the spell. Thus the units' prop windows are never reset. Put some debug messages in there to make sure everything runs as expected.

But also I would fix the way instance id allocation works in this spell because it really is a "just why?" situation.
 
I realize you didn't write this but still:
  1. Don't use triggers for timers. Just use timers directly, there's no need to involve a trigger.
  2. BJs and other useless wrapper functions like GetUnitAbilityLevelSwapped() and AddSpecialEffectLocBJ() should never be used.
  3. Switch to using XY coordinates directly and never ever touch locations again unless you need GetLocationZ().
  4. The GetIndex() method is just... why? Totally pointless and can actually fuck up the whole thing in the right situation (
I stopped looking at things I could nitpick at this point, but IMO there are a lot of general methodology things that could be fixed/cleaned up. Direct your dev to me and I'll give them feedback if they care. I have a guess as to what the issue is: something in the instance indexing is getting borked and the .destroy() method is never called on that instance of the spell. Thus the units' prop windows are never reset. Put some debug messages in there to make sure everything runs as expected.

But also I would fix the way instance id allocation works in this spell because it really is a "just why?" situation.
I did put a debug message in the destroy method just now and it is getting to the part of the code where it resets the prop windows 🤔🤔🤔
 
Status
Not open for further replies.
Top