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

[Spell] Need help with this spell

Status
Not open for further replies.
Level 3
Joined
Sep 9, 2009
Messages
658
So...I've got another question. I'm trying to convert some of my old GUI spells to JASS and I've come across this problem.

In a periodic trigger, I have shadow balls created at random locations around the target point of the ability. After 25 of them have been created, a big Shadow Ball would be created at the center of the target point. It should stay for around 1 second before being killed and damaging enemies in a large area. And herein lies the problem, how do I get it to stay for 1 second without using any kind of waits as they mess up the periodic trigger?

If it helps, I've attached the test map I'm using. It's pretty small in size so it's easy to download.


JASS:
function VW_periodic takes nothing returns nothing
//setting up the variables
    local timer t = GetExpiredTimer()
    local integer parentKey = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_Hash, parentKey, 0)
    local rect r = LoadRectHandle(udg_Hash, parentKey, 1)//Loading the rect.
    local integer count = LoadInteger(udg_Hash, parentKey, 2)
    local real damage = LoadReal(udg_Hash, parentKey, 4)
    local real x = GetRandomReal(GetRectMinX(r),GetRectMaxX(r))
    local real y = GetRandomReal(GetRectMinY(r),GetRectMaxY(r))
    local group g = CreateGroup()
    local integer i = 0
    local real angle = 0.174
    local real px = LoadReal(udg_Hash, parentKey, 5)
    local real py = LoadReal(udg_Hash, parentKey, 6)
    local unit shadowball
    local unit u1
    local real dx
    local real dy
    local real px1
    local real py1
//end of set up
    set count = count + 1
    call SaveInteger(udg_Hash, parentKey, 2, count)
    set shadowball = CreateUnit(GetOwningPlayer(u), 'e001', x, y, 270)
    //Can I do set shadowball = KillUnit(CreateUnit((GetOwningPlayer(u), 'e001', x, y, 270))
    //and still be able to extract its X and Y coordinates?
    call KillUnit(shadowball)
    set dx = GetUnitX(shadowball)
    set dy = GetUnitY(shadowball)
    call DestroyEffect(AddSpecialEffect("DarkNova.mdx", dx, dy))
    call GroupEnumUnitsInRange(g, dx, dy, 350, null)
    loop
        set u1 = FirstOfGroup(g)
        exitwhen u1 == null
        if IsUnitEnemy(u1, GetOwningPlayer(u)) then
            call UnitDamageTarget(u, u1, damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        endif
        call GroupRemoveUnit(g, u1)
    endloop
    if count >= 25 then
        call PauseTimer(t)
        set shadowball = CreateUnit(GetOwningPlayer(u), 'e002', px, py, 270)
        call KillUnit(shadowball)
        //This is where the problem is. I want the unit to stay alive for 1 second 
        //before being killing it. I can't use polledwait or triggersleep action because they
        //mess up this trigger. I could use a countdown timer but is that the best option?
        loop
            exitwhen i == 36
                set i = i + 1
                set px1 = px + 300 * Cos(angle)
                set py1 = py + 300 * Sin(angle)
                call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px1, py1))
                set px1 = px + 600 * Cos(angle)
                set py1 = py + 600 * Sin(angle)
                call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px1, py1))
                set px1 = px + 900 * Cos(angle)
                set py1 = py + 900 * Sin(angle)
                call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px1, py1))
                set angle = angle+0.174 
        endloop
        //cleaning up the leaks
        call DestroyTimer(t)
        set u = null
        set u1 = null
        set shadowball = null
        set g = null
        call FlushChildHashtable(udg_Hash, parentKey)
    endif
    //cleaning up
    set g = null
    set u = null
    set u1 = null
    set t = null
    set r = null //Can't do RemoveRect(r) because it messes up the spawn locations. This is fine, right?
    call DestroyGroup(g)
endfunction

function Trig_Vanishing_World_Actions takes nothing returns nothing
//Declaring variables
    local timer t = CreateTimer()
    local unit u = GetTriggerUnit()
    local location l = GetSpellTargetLoc()
    local real x = GetLocationX(l)//I need this to get the coordinates so I can make my rect.
    local real y = GetLocationY(l)//I need this to get the coordinates so I can make my rect.
    local real px = GetLocationX(l)//I'm storing it again so I can get the target loc later on.
    local real py = GetLocationY(l)//I'm storing it again so I can get the target loc later on.
    local integer parentKey = GetHandleId(t)
    local rect r = Rect(x-600,y-600,x+600,y+600)//the rect I want 
    local real final_damage = 1000 * GetHeroInt(u,true)
    local real periodic_damage = (GetHeroInt(u,true) * 0.10) * 100
//End of declaration
//Setting up the hastable
    call SaveUnitHandle(udg_Hash, parentKey, 0, u)//The triggering unit
    call SaveRectHandle(udg_Hash, parentKey, 1, r)//I need to save the rect, right?
    call SaveInteger(udg_Hash, parentKey, 2, 0)//Saving the count, so that it'll stop after 5 seconds.
    call SaveReal(udg_Hash, parentKey, 3, final_damage)//I'm storing the damage so the spell will still deal damage even if the Hero dies.
    call SaveReal(udg_Hash, parentKey, 4, periodic_damage)
    call SaveReal(udg_Hash, parentKey, 5, px)//The coordinates of the target point
    call SaveReal(udg_Hash, parentKey, 6, py)
//End of set up
    call TimerStart(t,.20,true,function VW_periodic)
    call RemoveLocation(l)//Removing the location
endfunction

function Trig_Vanishing_World_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A000' then
        call Trig_Vanishing_World_Actions()
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Vanishing_World takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Trig_Vanishing_World_Conditions ) )
    set t = null
    set udg_Hash = InitHashtable()
endfunction
 

Attachments

  • Kil'jaeden and Archimonde Spells.w3x
    247.4 KB · Views: 71
Level 15
Joined
Oct 29, 2012
Messages
1,474
Use a timer , for example :
  • Countdown Timer - Create 'your timer'[Player Number of (Owner of(Triggering Unit))] for 1.00 as a One-shout timer .
  • Events
    • Time - 'Your Timer'[1] Expires
    • Time - 'Your Timer'[2] Expires
    • Time - 'Your Timer'[3] Expires
    • Time - 'Your Timer'[4] Expires
  • Actions
    • Your spell's trigger
This is better than waits . Timers really help
 
Level 11
Joined
Aug 6, 2009
Messages
697
Use an if/then/else function in your periodic trigger using an integer/real variable.
EX:
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • RG_Duration2[TempInt] Greater than 0.00
    • Then - Actions
      • Set RG_Duration2[TempInt] = (RG_Duration2[TempInt] - 0.05)
The periodic event is .05 seconds.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
Use local timers.

Every X interval create the balls. After all of them are created, stop creating balls, create the big one, and start a local 1second timer. When that timer expires blow everything.

There's no need for a rect nor locations.

Basically you have
1- Function to set all the base stuff and start the timer that every X interval will create the small balls (every 0.05 would require 1,25 seconds to create all the 25 balls)
2- The function that creates the balls and counts them and adds them to a local group (stored in the timer id)
3- The function that moves all the small balls to the center in one second (in 33 intervals of 0.03) while increases the size of the big ball at the center (in 33 intervals of 0.03. Increasing 3% of size every interval)
4- The function that blows everything at interval #34, deals the damage and cleans all stuff.

You can do all this with local timers, 1 hashtable, 1 trigger.

The place where you placed the comment about the problem of waiting 1 second is where you have to start the timer that will expire in 1 second and do the rest of the actions you want.

EDIT: Your map doesn't even compile in my PC. It just doesn't start.

EDIT: I'm working on this abil. I'll do some small changes. I hope you like them.
 
Last edited:
Level 29
Joined
Oct 24, 2012
Messages
6,543
Timers is the only solution instead of waits in a MUI spell

this is not true.

to make a multi step MUI ability without timers is rather easy. u use a counter and increase it every iteration ( every .03 seconds)
u need an ITE to do this.

when the counter reaches a certain integer thats when u do ur actions.

since ur using jass check out my spell implosion bomb. its in jass. i use a multistep ability with counters because i pull the units in and shrink them then i shoot them out to there original spots
 
Level 3
Joined
Sep 9, 2009
Messages
658
Timers is the only solution instead of waits in a MUI spell

Actually, I think you can use waits in an MUI spell it's just that short waits are inaccurate they're hardly ever used. Plus, they can cause desyncs.

@deathismyfriend around which part of the trigger should I look at?

@Spartipilo The map doesn't work? I don't have any scripts in the map headers though, and that's usually what makes maps unusable for me. So what's the problem? Or did you manage to get it working?
 
Level 29
Joined
Oct 24, 2012
Messages
6,543
this is the main part that uses the counter. in the setup function i set what the values should be depending on the ability lvl. look at the config and setup trigger to understand the counter completely.

u can use waits and u can counter that inaccuracy to a point. look at my GUI tutorial for a more in depth explanation on waits. i also have a link to a small snippet of wait inaccuracy.

JASS:
function ImplosionBombTimerActions takes nothing returns nothing
    local integer L = 1
    local real x
    local real y
    local real d
    local real v = udg_velocityImpBomb/2
    loop
        exitwhen L > udg_maxIndexImpBomb
        
        set udg_durCounterImpBomb[ L] = udg_durCounterImpBomb[ L] + 1
        if udg_targetImpBomb[ L] == null then
            if udg_durCounterImpBomb[ L] == udg_durInt1ImpBomb[ L] - 32 then // sets the variables for second stage on. damage units here
                call DestroyEffect( AddSpecialEffectTarget( udg_damageEffectImpBomb, udg_dummyImpBomb[ L], udg_attachPointDummyImpBomb))
            elseif udg_durCounterImpBomb[ L] >= udg_durInt2ImpBomb[ L] + 1 then // de-index the caster here.
                call SetUnitPropWindow( udg_casterImpBomb[ L], GetUnitDefaultPropWindow( udg_casterImpBomb[ L]))
                call RemoveUnit( udg_dummyImpBomb[ L])
                call ImplosionBombDeIndex( L)
                set L = L - 1
            endif
        elseif udg_durCounterImpBomb[ L] < udg_durInt1ImpBomb[ L] then // first faze. pull units into implosion
            if DistanceBetweenXYImpBomb( udg_casterXImpBomb[ L], udg_casterYImpBomb[ L], udg_targetMXImpBomb[ L], udg_targetMYImpBomb[ L]) >= v then
                call ImplosionBombMovingUnits( L)
            endif
            call ImplosionBombChangeScale( L)
        elseif udg_durCounterImpBomb[ L] == udg_durInt1ImpBomb[ L] then // sets the variables for second stage on. damage units here
            set d = DistanceBetweenXYImpBomb( udg_casterXImpBomb[ L], udg_casterYImpBomb[ L], udg_targetMXImpBomb[ L], udg_targetMYImpBomb[ L])
            if d >= v then
                call ImplosionBombMovingUnits( L)
            endif
            call ImplosionBombChangeScale( L)
            set d = udg_aoeTotalImpBomb[ L] - d
            call UnitDamageTarget( udg_casterImpBomb[ L], udg_targetImpBomb[ L], ( d/udg_aoeTotalImpBomb[ L])*udg_damToDealImpBomb[ L], true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
            set udg_effectImpBomb[ L] = AddSpecialEffectTarget( udg_unitEffectImpBomb, udg_targetImpBomb[ L], udg_attachPointUnitImpBomb)
            //set udg_angleImpBomb[ L] = udg_angleImpBomb[ L] + 180.00
            set udg_distanceMXImpBomb[ L] = udg_distanceMXImpBomb[ L] * -2
            set udg_distanceMYImpBomb[ L] = udg_distanceMYImpBomb[ L] * -2
            set udg_decScaleImpBomb[ L] = -1.00/I2R( udg_durInt2ImpBomb[ L] - udg_durInt1ImpBomb[ L])
            if GetWidgetLife( udg_targetImpBomb[ L]) < 0.405 then
                call ImplosionBombDeIndex( L)
                set L = L - 1
            endif
            
        elseif udg_durCounterImpBomb[ L] < udg_durInt2ImpBomb[ L] then // final faze. moving units to there original position
            if DistanceBetweenXYImpBomb( udg_targetMXImpBomb[ L], udg_targetMYImpBomb[ L], udg_targetSXImpBomb[ L], udg_targetSYImpBomb[ L]) >= udg_velocityImpBomb then
                call ImplosionBombMovingUnits( L)
            elseif udg_targetMXImpBomb[ L] != udg_targetSXImpBomb[ L] and udg_targetMYImpBomb[ L] != udg_targetSYImpBomb[ L] then
                call SetUnitX( udg_targetImpBomb[ L], udg_targetSXImpBomb[ L])
                call SetUnitY( udg_targetImpBomb[ L], udg_targetSYImpBomb[ L])
            endif
            call ImplosionBombChangeScale( L)
            
        elseif udg_durCounterImpBomb[ L] >= udg_durInt2ImpBomb[ L] then // de-index here
            call ImplosionBombChangeScale( L)
            call ImplosionBombDeIndex( L)
            set L = L - 1
        endif
        set L = L + 1
    endloop
    if udg_maxIndexImpBomb <= 0 then
        call PauseTimer( udg_timerImpBomb)
    endif
endfunction
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
I started a new scrip based on yours in a new map.

This does something similar to what you had. It just lacks some better eyecandy effects.

Just add the skill and cast it. Oh... i also forgot to flush the Hash and destroy the timer and group. Anyway, this is just a preview for you to look and let me know if it works. If you know how to finish it, good, if you don't, let me know.
 

Attachments

  • VanishWorld.w3x
    20.7 KB · Views: 74
Level 3
Joined
Sep 9, 2009
Messages
658
@deathismyfriends I can't be sure but I think I get it. I'll see if I can do what you did.

@Spartipilo I forgot to ask this but how do you plan on creating the dummies at random locations around the caster without using regions?

EIDT: Oh, you posted already. I'll just look at the map then.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
Here's the script. It does everything in the same trigger.

JASS:
globals
    hashtable VanishHash = InitHashtable()
    integer VanishChargesCount = 25 // Number of balls to be created
    integer VanishChargeId = 'hfoo' // ID of the charge
    real VanishAoE = 500 // AoE of balls to be created
    real VanishChargeTime = 3 // Balls will be created within this period
    real VanishBlowTime = 1 // Balls will go to the center within this period *FALSE. I never used this*
    real VanishBallSize = 3.0/VanishChargesCount // Charges increase size of Main Ball by this value
    group VanishGroup = CreateGroup()
endglobals

    // Charge Stack Up and explode
function VW_GroupActions takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local real size
    local unit u = GetEnumUnit()
    local unit ball = LoadUnitHandle(VanishHash, id, 7)
    local real x1 = GetUnitX(u)
    local real y1 = GetUnitY(u)
    local real x2 = GetUnitX(ball)
    local real y2 = GetUnitY(ball)
    local real dx = x2-x1
    local real dy = y2-y1
    local real distance = SquareRoot(dx*dx + dy*dy)
    local real offset = 4
    local real angle = Atan2(dy, dx)
    local real nx = x1 + offset * Cos(angle)
    local real ny = y1 + offset * Sin(angle)
    local unit u1
    local player p = GetOwningPlayer(ball)
    local real FinalDmg
    local unit caster
    
    if distance > 92 then
        call SetUnitX(u, nx)
        call SetUnitY(u, ny)
    else
        call RemoveUnit(u)
        set size = LoadReal(VanishHash, id, 8) + VanishBallSize
        call SaveReal(VanishHash, id, 8, size)
        call SetUnitScale(ball, size, size, size)
        
        if size >= 3 then
            call KillUnit(ball)
            call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", x2, y2))
            // Deal Damage Around Ball
            call GroupEnumUnitsInRange(VanishGroup, x2, y2, VanishAoE, null)
            set FinalDmg = LoadReal(VanishHash, id, 2)
            set caster = LoadUnitHandle(VanishHash, id, 0)
            loop
                set u1 = FirstOfGroup(VanishGroup)
                exitwhen u1 == null
                if IsUnitEnemy(u1, p) then
                    call UnitDamageTarget(caster, u1, FinalDmg, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                endif
                call GroupRemoveUnit(VanishGroup, u1)
            endloop
            set caster = null // This is not in the map script. Just in this script
            call PauseTimer(t)
            call DestroyTimer(t)
            call FlushChildHashtable(VanishHash, id)
            set bj_wantDestroyGroup = true 
        endif
    endif

    set t = null
    set u = null
    set ball = null
    set p = null
endfunction

    // Charges Stack Up
function VW_Moving takes nothing returns nothing
    local group g = LoadGroupHandle(VanishHash, GetHandleId(GetExpiredTimer()), 6)
    call ForGroup(g, function VW_GroupActions)
    set g = null
endfunction

    // Charge Creation and Periodic Damage
function VW_periodic takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit caster = LoadUnitHandle(VanishHash, id, 0)
    local integer count = LoadInteger(VanishHash, id, 1)
    local real PeriDmg = LoadReal(VanishHash, id, 3)
    local real x = LoadReal(VanishHash, id, 4) 
    local real y = LoadReal(VanishHash, id, 5)
    local group g = LoadGroupHandle(VanishHash, id, 6)
    local real random = GetRandomReal(0, 359)
    local real random2 = GetRandomReal(VanishAoE/2, VanishAoE)
    local real x2 = x + random2 * Cos(random * bj_DEGTORAD)
    local real y2 = y + random2 * Sin(random * bj_DEGTORAD)
    local player p = GetOwningPlayer(caster)
    local unit shadowball
    local unit u1
    
    if count <= VanishChargesCount then
        set count = count + 1
        call SaveInteger(VanishHash, id, 1, count)
        set shadowball = CreateUnit(p, VanishChargeId, x2, y2, 270) // Create Charge
        call GroupAddUnit(g, shadowball) // Add Charge to Group
        call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\Bolt\\BoltImpact.mdl", x2, y2)) // Special Effect on the Charge
        // Deal Damage Around Created Charge
        call GroupEnumUnitsInRange(VanishGroup, x2, y2, 350, null)
        loop
            set u1 = FirstOfGroup(VanishGroup)
            exitwhen u1 == null
            if IsUnitEnemy(u1, p) then
                call UnitDamageTarget(caster, u1, PeriDmg, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            endif
            call GroupRemoveUnit(VanishGroup, u1)
        endloop
    else
        call PauseTimer(t)
        set shadowball = CreateUnit(p, VanishChargeId, x, y, 270) // The Center Ball
        call SetUnitScale(shadowball, 0, 0, 0) // Making it Small
        call SaveUnitHandle(VanishHash, id, 7, shadowball) // The Ball Id
        call SaveReal(VanishHash, id, 8, 0) // The Size (0)
        call TimerStart(t, 0.03, true, function VW_Moving) // Start 0.03 timer 
    endif
    
    // Clearing Leaks
    set t = null
    set caster = null
    set g = null
    set shadowball = null
    set p = null
endfunction

function Trig_Vanishing_World_Actions takes nothing returns nothing
    // Setting Variables
    local unit caster = GetTriggerUnit()
    local real x = GetSpellTargetX()
    local real y = GetSpellTargetY()
    local timer t = CreateTimer()
    local integer id = GetHandleId(t)
    local integer CasterInt = GetHeroInt(caster, true)
    local real FinalDmg = CasterInt * 1000
    local real PeriDmg = CasterInt * 10
    local group g = CreateGroup()
    
    // Setting Hashtable
    call SaveUnitHandle(VanishHash, id, 0, caster)
    call SaveInteger(VanishHash, id, 1, 0) // Count
    call SaveReal(VanishHash, id, 2, FinalDmg)
    call SaveReal(VanishHash, id, 3, PeriDmg)
    call SaveReal(VanishHash, id, 4, x) 
    call SaveReal(VanishHash, id, 5, y)
    call SaveGroupHandle(VanishHash, id, 6, g) // Group
    
    // Start Timer
    call TimerStart(t, VanishChargeTime/VanishChargesCount, true, function VW_periodic)
    
    // Clearing Leaks
    set g = null
    set caster = null
    set t = null
endfunction

function Trig_Vanishing_World_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

//===========================================================================
function InitTrig_Vanishing_World takes nothing returns nothing
    set gg_trg_Vanishing_World = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Vanishing_World, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(gg_trg_Vanishing_World, Condition(function Trig_Vanishing_World_Conditions))
    call TriggerAddAction( gg_trg_Vanishing_World, function Trig_Vanishing_World_Actions )
endfunction

I also updated the MapFile. I could add a lot of configurations in the "globals" block

Creates the balls dealing damage, moves the ball to the center, charges a big ball, and deals big damage when it explodes.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
Well... I know Action function and TriggerAddAction functions can be avoided by just mixing actions in the conditions block... But that wouldn't be a "leak", just a "not-the-best coding practice"
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
yeah.. u're right. I'll wait for the TO to check the map. He'll surelly tell me to change or do something to it, then i'll merge the actions and conditions.
 
Level 3
Joined
Sep 9, 2009
Messages
658
@Spartipilo if you've made a new map, then I don't think you've seen the shadowballs. They're pretty big even if the scaling is 1 so I'll be cutting the movement to the center part. I like your trigger though, it showed me a lot of things. Thanks.

Oh by the way, I thought bj_wantDestroyGroup = true only works if you haven't set the last created group to a variable? Since you used a variable shouldn't you be destroying the variable instead?

@dimf Well, initially I had planned on extending the counter to 26, if the counter is less than 26, I'll spawn the dummy units, once it reaches 26 I'll spawn the big one, pause the timer and start it again as a oneshot 1 second timer which will kill the big unit and deal the final damage.

I'll try to make the spell again, just for personal improvement.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
@sniper_zero: No, i haven't seen the shadowballs. I just used Wisp as a ball reference. Thanks for the feedback. bj_wantDestroyGroup = true actually is missplaced there. You should replace it with - load the group from the timer and destroy it. bj_wantDestroyGroup = true works for ForGroupBJ function, which i'm not using.

You can use count > 25 just as you can use count = 26.

I didn't used the models of balls and shadows because I don't have them :/

Good you want to improve it. Post it here once it's finished.
 
Level 3
Joined
Sep 9, 2009
Messages
658
attachment.php


@Spartipilo Here's what the shadow ball looks like. I think they'd look weird if they move lol.
 

Attachments

  • Shadowball.png
    Shadowball.png
    265.6 KB · Views: 114
Level 20
Joined
Jul 14, 2011
Messages
3,213
Well, It would be awesome if they move :D

Why don't you test? Just make the model small as a wisp and check how it grows :D
 
Level 3
Joined
Sep 9, 2009
Messages
658
Well, I redid the whole thing from scratch and it works fine for the most part. Thanks to Spartpilo's trigger, I don't even have to use rects anymore. But the ITE conditions don't work right.

I have set it so that once the counter reaches 26, a big shadow ball will be created. That works but the other conditions like if count !=25, if count == 31 don't work so the shadow balls just keep being created one after the other.

Here's the new trigger.

JASS:
function Vanishing_World_Periodic takes nothing returns nothing
    //Variable setup
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_Hash, id, 0)
    local real tx = LoadReal(udg_Hash, id, 1)
    local real ty = LoadReal(udg_Hash, id, 2)
    local real final_damage = LoadReal(udg_Hash, id, 3)
    local real periodic_damage = LoadReal(udg_Hash, id, 4)
    local integer count = LoadInteger(udg_Hash, id, 5)
    local real rand_distance = GetRandomReal(200, 900)
    local real rand_angle = GetRandomReal(0, 6.263)
    local real rx = tx + rand_distance * Cos(rand_angle)
    local real ry = ty + rand_distance * Sin(rand_angle)
    local unit u1 //will be used for FirstofGroup
    local unit shadowball
    local real sx
    local real sy
    local group g = CreateGroup()
    local integer i = 0
    local real angle = 0.174
    local real px //x coordinate of the polar projection
    local real py //y coordinate of the polar projection
    //End of variable setup
    
    set count = count + 1// 1 Shadow Ball has spawned.
    call SaveInteger(udg_Hash, id, 5, count)
    call DisplayTextToPlayer(GetOwningPlayer(u), GetUnitX(u), GetUnitY(u), I2S(count))
    if count != 26 then //It'll keep creating shadow balls until the 25th tick
        set shadowball = CreateUnit(GetOwningPlayer(u), 'e001', rx, ry, 270)
        set sx = GetUnitX(shadowball)
        set sy = GetUnitY(shadowball)
        call KillUnit(shadowball)
        call DestroyEffect(AddSpecialEffect("DarkNova.mdx", sx, sy))
        call GroupEnumUnitsInRange(g, sx, sy, 350, null)
        loop
            set u1 = FirstOfGroup(g)
            exitwhen u1 == null
            if IsUnitEnemy(u1, GetOwningPlayer(u)) then
                call UnitDamageTarget(u, u1, periodic_damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            endif
            call GroupRemoveUnit(g, u1)
        endloop
    //clean up
    set u = null
    set u1 = null
    set shadowball = null
    set g = null
    set t = null
    else
        if count == 26 then//works fine
            set shadowball = CreateUnit(GetOwningPlayer(u), 'e002', tx, ty, 270)
            set sx = GetUnitX(shadowball)
            set sy = GetUnitY(shadowball)
        else//does not work
            if count == 31 then
                call KillUnit(shadowball)
                loop
                    exitwhen i == 36
                        set i = i + 1
                        set px = tx + 300 * Cos(angle)
                        set py = ty + 300 * Sin(angle)
                        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
                        set px = tx + 600 * Cos(angle)
                        set py = ty + 600 * Sin(angle)
                        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
                        set px = tx + 900 * Cos(angle)
                        set py = ty + 900 * Sin(angle)
                        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
                        set angle = angle + 0.174
                endloop
                call PauseTimer(t)
                call DestroyTimer(t)
                set u = null
                set u1 = null
                set t = null
                set g = null
                set shadowball = null
                call DestroyGroup(g)
                call FlushChildHashtable(udg_Hash, id)
            endif
        endif
    endif
endfunction

function Vanishing_World_Actions takes nothing returns nothing
    //Variable Setup
    local unit u = GetTriggerUnit()
    local timer t = CreateTimer()
    local real tx = GetSpellTargetX()// X coordinate of the spell target location
    local real ty = GetSpellTargetY()// Y coordinate of the spell target location
    local real final_damage = 1000 * GetHeroInt(u,true)//Final damage at the end
    local real periodic_damage = (GetHeroInt(u,true) * 0.10) * 100//damage during the periodic actions
    local integer id = GetHandleId(t)//Id of the timer, will be used as the parentKey
    //End of variable set up
    
    //Setting up the hashtable
    call SaveUnitHandle(udg_Hash, id, 0, u)
    call SaveReal(udg_Hash, id, 1, tx)
    call SaveReal(udg_Hash, id, 2, ty)
    call SaveReal(udg_Hash, id, 3, final_damage)//I'm storing the damage so the spell will still deal damage even if the Hero dies.
    call SaveReal(udg_Hash, id, 4, periodic_damage)
    call SaveInteger(udg_Hash, id, 5, 0)//Storing the count so, the trigger will know when to stop.
    //End of hashtable setup
    
    // Start of the periodic loop
    call TimerStart(t, 0.2, true, function Vanishing_World_Periodic)
endfunction

function Trig_Vanishing_World_v2_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A000' then
        call Vanishing_World_Actions()
    endif
    return false
endfunction
 
Level 3
Joined
Sep 9, 2009
Messages
658
Well, I got it working. But I had to make another function. Is that bad?
Plus, it's a good thing if the timer's handle id doesn't change per instance of the spell cast right?

JASS:
function Vanishing_World_Final takes nothing returns nothing
    //Variable Setup
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_Hash, id, 0)
    local unit shadowball = LoadUnitHandle(udg_Hash, id, 6)
    local real tx = LoadReal(udg_Hash, id, 1)
    local real ty = LoadReal(udg_Hash, id, 2)
    local real x = LoadReal(udg_Hash, id, 7)
    local real y = LoadReal(udg_Hash, id, 8)
    local real final_damage = LoadReal(udg_Hash, id, 3)
    local integer i = 0
    local real angle = 0.174
    local real px //x coordinate of the polar projection
    local real py //y coordinate of the polar projection
    local unit u1
    local unit u2
    local group g = CreateGroup()
    //End variable setup
    
    call KillUnit(shadowball)
    set u2 = CreateUnit(GetOwningPlayer(u), 'e003', tx, ty, 270)
    call KillUnit(u2)
    loop
        exitwhen i == 36
        set i = i + 1
        set px = tx + 300 * Cos(angle)
        set py = ty + 300 * Sin(angle)
        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
        call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py))
        set px = tx + 600 * Cos(angle)
        set py = ty + 600 * Sin(angle)
        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
        call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py))        
        set px = tx + 900 * Cos(angle)
        set py = ty + 900 * Sin(angle)
        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
        call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py))        
        set angle = angle + 0.174
    endloop
    set i = 0
    set angle = 0.523
    loop
        exitwhen i == 12
        set i = i + 1
        set px = tx + 300 * Cos(angle)
        set py = ty + 300 * Sin(angle)
        call DestroyEffect(AddSpecialEffect("StarExplosion.mdx", px, py))
        set px = tx + 600 * Cos(angle)
        set py = ty + 600 * Sin(angle)
        call DestroyEffect(AddSpecialEffect("StarExplosion.mdx", px, py))     
        set px = tx + 900 * Cos(angle)
        set py = ty + 900 * Sin(angle)
        call DestroyEffect(AddSpecialEffect("StarExplosion.mdx", px, py))       
        set angle = angle + 0.523
    endloop
    call GroupEnumUnitsInRange(g, tx, ty, 950, null)
    loop
        set u1 = FirstOfGroup(g)
        exitwhen u1 == null
        if IsUnitEnemy(u1, GetOwningPlayer(u)) then
            call UnitDamageTarget(u, u1, final_damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        endif
        call GroupRemoveUnit(g, u1)
    endloop
    
    //Clean up
    call PauseTimer(t)
    call DestroyTimer(t)
    set t = null
    set shadowball = null
    set u = null
    set u1 = null
    call DestroyGroup(g)
    set g = null
    call FlushChildHashtable(udg_Hash, id)
    call BJDebugMsg(I2S(id))
endfunction

function Vanishing_World_Periodic takes nothing returns nothing
    //Variable setup
    local timer t = GetExpiredTimer()
    local integer id = GetHandleId(t)
    local unit u = LoadUnitHandle(udg_Hash, id, 0)
    local real tx = LoadReal(udg_Hash, id, 1)
    local real ty = LoadReal(udg_Hash, id, 2)
    local real periodic_damage = LoadReal(udg_Hash, id, 4)
    local integer count = LoadInteger(udg_Hash, id, 5)
    local real rand_distance = GetRandomReal(200, 900)
    local real rand_angle = GetRandomReal(0, 6.263)
    local real rx = tx + rand_distance * Cos(rand_angle)
    local real ry = ty + rand_distance * Sin(rand_angle)
    local unit u1 //will be used for FirstofGroup
    local unit shadowball
    local real sx
    local real sy
    local group g = CreateGroup()
    local integer i = 0
    //End of variable setup
    
    set count = count + 1// 1 Shadow Ball has spawned.
    call SaveInteger(udg_Hash, id, 5, count)
    call DisplayTextToPlayer(GetOwningPlayer(u), GetUnitX(u), GetUnitY(u), I2S(count))
    if count < 26 then //It'll keep creating shadow balls until the 25th tick
        set shadowball = CreateUnit(GetOwningPlayer(u), 'e001', rx, ry, 270)
        set sx = GetUnitX(shadowball)
        set sy = GetUnitY(shadowball)
        call KillUnit(shadowball)
        call DestroyEffect(AddSpecialEffect("DarkNova.mdx", sx, sy))
        call GroupEnumUnitsInRange(g, sx, sy, 350, null)
        loop
            set u1 = FirstOfGroup(g)
            exitwhen u1 == null
            if IsUnitEnemy(u1, GetOwningPlayer(u)) then
                call UnitDamageTarget(u, u1, periodic_damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            endif
            call GroupRemoveUnit(g, u1)
        endloop
    //clean up
        set u = null
        set u1 = null
        set shadowball = null
        call DestroyGroup(g)
        set g = null
        set t = null
    elseif count == 26 then
        call PauseTimer(t)
        set shadowball = CreateUnit(GetOwningPlayer(u), 'e002', tx, ty, 270)
        set sx = GetUnitX(shadowball)
        set sy = GetUnitY(shadowball)
        call SaveUnitHandle(udg_Hash, id, 6, shadowball)
        call SaveReal(udg_Hash, id, 7, sx)
        call SaveReal(udg_Hash, id, 8, sy)
        call TimerStart(t, 1, false, function Vanishing_World_Final)
        call DisplayTextToPlayer(GetOwningPlayer(u), GetUnitX(u), GetUnitY(u), R2S(TimerGetRemaining(t)))
        //clean up
        set shadowball = null
    endif
endfunction

function Vanishing_World_Actions takes nothing returns nothing
    //Variable Setup
    local unit u = GetTriggerUnit()
    local timer t = CreateTimer()
    local real tx = GetSpellTargetX()// X coordinate of the spell target location
    local real ty = GetSpellTargetY()// Y coordinate of the spell target location
    local real final_damage = 1000 * GetHeroInt(u,true)//Final damage at the end
    local real periodic_damage = (GetHeroInt(u,true) * 0.10) * 100//damage during the periodic actions
    local integer id = GetHandleId(t)//Id of the timer, will be used as the parentKey
    //End of variable set up

    //Setting up the hashtable
    call SaveUnitHandle(udg_Hash, id, 0, u)
    call SaveReal(udg_Hash, id, 1, tx)
    call SaveReal(udg_Hash, id, 2, ty)
    call SaveReal(udg_Hash, id, 3, final_damage)//I'm storing the damage so the spell will still deal damage even if the Hero dies.
    call SaveReal(udg_Hash, id, 4, periodic_damage)
    call SaveInteger(udg_Hash, id, 5, 0)//Storing the count so, the trigger will know when to stop.
    //End of hashtable setup
    
    // Start of the periodic loop
    call TimerStart(t, 0.2, true, function Vanishing_World_Periodic)
endfunction

function Trig_Vanishing_World_v2_Conditions takes nothing returns boolean
    if GetSpellAbilityId() == 'A000' then
        call Vanishing_World_Actions()
    endif
    return false
endfunction

//===========================================================================
function InitTrig_Vanishing_World_v2_Working takes nothing returns nothing
    local trigger t = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( t, Condition( function Trig_Vanishing_World_v2_Conditions ) )
    set t = null
    set udg_Hash = InitHashtable()    
endfunction
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
Set GetHeroInt(u, true) to another variable.
JASS:
local real final_damage = 1000 * GetHeroInt(u,true)//Final damage at the end
    local real periodic_damage = (GetHeroInt(u,true) * 0.10) * 100//damage during the periodic actions

Random Real should be between 0 and 359. After that it just repeats.

Here you're increasing the count without any condition. So, after count = 26, it keeps going up. You have to check first if the count is less than 26, if it's increase the count, else, do the rest of the actions.
JASS:
set count = count + 1// 1 Shadow Ball has spawned.

You have to set this to a variable since you use it tons of times (in the damage loop and unit creation)
JASS:
GetOwningPlayer(u))

Creating/destroying groups is a really heavy task for the game. That's why I used a single global group. You should too.
JASS:
call DestroyGroup(g)
set g = null

JASS:
        set shadowball = CreateUnit(GetOwningPlayer(u), 'e002', tx, ty, 270)
        set sx = GetUnitX(shadowball) // You already know this. It's tx
        set sy = GetUnitY(shadowball) // You already know this, it's ty

You use this like 46 times in the loop. Do the sum in a local variable
JASS:
        set px = tx + 300 * Cos(angle)
        set py = ty + 300 * Sin(angle)

You need local timers to make this MUI. Good thing you can use the same timer for both functions so you don't have to re-save data nor anything else.

Your scripts are somehow hard to read. You should go with the colors. The first thing is setting the locals. Then comes the actions. Then destroying stuff. Then nullying variables.

Put comment headers to every part of the script and try to use caps. FinalDmg is easier to read than final_damage. Id, RandAng, ShadowBall, Caster/Picked/Looper instead of u, u1, u2)

The bad thing about having more functions is that you have to re-load the timer data in that one. Specially in this case you dont need it, since you can use the count as a timer and use an ITE.
 
Level 3
Joined
Sep 9, 2009
Messages
658
Random Real should be between 0 and 359. After that it just repeats.

I did use 0 and 359. It's just in radians. 359*(3.14/180) = 6.2625. If you're talking about (200, 900) then that's the distance. Or should they also be between 0 and 359?

Here you're increasing the count without any condition. So, after count = 26, it keeps going up. You have to check first if the count is less than 26, if it's increase the count, else, do the rest of the actions.
JASS:
set count = count + 1// 1 Shadow Ball has spawned.

JASS:
        set shadowball = CreateUnit(GetOwningPlayer(u), 'e002', tx, ty, 270)
        set sx = GetUnitX(shadowball) // You already know this. It's tx
        set sy = GetUnitY(shadowball) // You already know this, it's ty

You use this like 46 times in the loop. Do the sum in a local variable
JASS:
        set px = tx + 300 * Cos(angle)
        set py = ty + 300 * Sin(angle)

You need local timers to make this MUI. Good thing you can use the same timer for both functions so you don't have to re-save data nor anything else.

Your scripts are somehow hard to read. You should go with the colors. The first thing is setting the locals. Then comes the actions. Then destroying stuff. Then nullying variables.

The bad thing about having more functions is that you have to re-load the timer data in that one. Specially in this case you dont need it, since you can use the count as a timer and use an ITE.

Okay, I'll place a condition.

Thanks for pointing that out.

Can you show me how to do that? I don't get what you mean.

Isn't the script already using a local timer though? I tried casting it multiple times and it didn't bug or anything.

Yeah, my script format sucks. I'm trying to work on it.

I have another copy of the trigger. I'll see again if I can get that working with only two functions.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
I did use 0 and 359. It's just in radians. 359*(3.14/180) = 6.2625. If you're talking about (200, 900) then that's the distance. Or should they also be between 0 and 359?
uh... ok :) My bad aboud the radians thing.

I said the thing about local timers because you said: "it's a good thing if the timer's handle id doesn't change per instance of the spell cast right?" The only way to achieve that is having a single timer. THat's why I said you can't do this with a single global timer, you need local ones.

If you make a custom function and use a count timer you can improve things a lot here. Once you finish polishing the script, post it again so we can keep improving it.
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
JASS:
local real rx1 = tx + 300
local real ry1 = ty + 300
local real rx2 = tx +600
local real ry2 = ty + 600
local real rx3 = tx + 900
local real ry3 = tx + 900
local real ca
local real sa

        exitwhen i == 36
        set i = i + 1
        set ca = Cos(angle)
        set sa = Sin(angle)
        set px = rx1 * ca
        set py = ry1 * sa
        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
        call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py))
        set px = rx2 * ca
        set py = ry2 * sa
        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
        call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py))        
        set px = rx3 * ca
        set py = ry3 * sa
        call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
        call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py))        
        set angle = angle + 0.174
 
Level 3
Joined
Sep 9, 2009
Messages
658
Thanks! That's a big help. :grin:

Never thought it'd take me a whole day just to get this one spell right.

EDIT: Actually, what you posted isn't right. 300, 600 and 900 should be multiplied first by cos and sin.

You did the addition first before the multiplication which screws up the offsets. But I got the idea, thanks again.
 
Last edited:
Level 3
Joined
Sep 9, 2009
Messages
658
@Spartipilo Well, I tried but I can't really do what you did. I don't think I can do local real r = 600 * Cos(angle) either because that would make it a static value, I think.

So I just made my own polar projection functions. I just hope that makes it more efficient now.

JASS:
function PolarProjectionX takes real x, real dist, real angle returns real
    return x + dist * Cos(angle)
endfunction

function PolarProjectionY takes real y, real dist, real angle returns real
    return y + dist * Sin(angle)
endfunction

JASS:
        loop
            exitwhen i == 36
                set i = i + 1
                set px = PolarProjectionX(tx, 300, angle)
                set py = PolarProjectionY(ty, 300, angle)
                call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
                call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py)) 
                set px = PolarProjectionX(tx, 600, angle)
                set py = PolarProjectionY(ty, 600, angle)
                call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
                call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py)) 
                set px = PolarProjectionX(tx, 900, angle)
                set py = PolarProjectionY(ty, 900, angle)
                call DestroyEffect(AddSpecialEffect("DarkPillar.mdx", px, py))
                call DestroyEffect(AddSpecialEffect("Nebula.mdx", px, py)) 
                set angle = angle + 0.174
        endloop
 
Status
Not open for further replies.
Top