HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Need Help(if help is possible)

06-27-2008, 08:46 AM#1
goldendercon
I need help with this new spell that I have been working on, although it never seems to work correctly no matter how hard I tried.
The following are characteristics of the said spell
  • A channeling spell
  • Has a range of 300 x current level
  • It is a circle spell, actually only a dynamic circle effect(or whatever it's called)
  • damages enemy units w/in range for 25x current level
  • heals friendly units in range for 5hp per second x current level and gives rejuvenation

here's what I have gotten to so far
4 separate triggers(which is bad( i think?))

Collapse JASS:
//TESH.scrollpos=11
//TESH.alwaysfold=0
function Trig_TidePoolInit_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A005' 
endfunction

function Trig_TidePoolInit_Action1 takes nothing returns nothing
    local integer i = 1
    local unit F= GetTriggerUnit()
    local unit array E
    set udg_Vrtxcaster= GetTriggerUnit()
    set udg_rotation2= 0
    loop
        exitwhen i > 45
        call CreateNUnitsAtLoc( 1, 'h004', GetOwningPlayer(F), PolarProjectionBJ(GetUnitLoc(F), 800, ( 45 * I2R(i) )), bj_UNIT_FACING )
        set udg_Vrtx_Unit[i] = GetLastCreatedUnit()
        set i = i + 1
    endloop
    call EnableTrigger( gg_trg_Tide_PoolMovement )
    call EnableTrigger( gg_trg_Tide_Pool_damage )
endfunction

//===========================================================================
function InitTrig_TidePoolInit takes nothing returns nothing
    set gg_trg_TidePoolInit = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_TidePoolInit, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_TidePoolInit, Condition( function Trig_TidePoolInit_Conditions ) )
    call TriggerAddAction( gg_trg_TidePoolInit, function Trig_TidePoolInit_Action1 )
endfunction

//2nd trigger
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Tide_PoolMovement_Actions takes nothing returns nothing
local integer i=1
    local location loc=GetUnitLoc(udg_Vrtxcaster)
    local location loc2

    set udg_rotation2=udg_rotation2+5   

    loop
        exitwhen i>45
        set loc2=GetUnitLoc(udg_Vrtx_Unit[i])
// Replace DistanceBetweenPoints with 400 ?
        call SetUnitPositionLoc( udg_Vrtx_Unit[i], PolarProjectionBJ(loc, 800,  udg_rotation + i*15  ))
        call RemoveLocation(loc2)
        set i=i+1
    endloop
endfunction

//===========================================================================
function InitTrig_Tide_PoolMovement takes nothing returns nothing
    set gg_trg_Tide_PoolMovement = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Tide_PoolMovement, 0.04 )
    call TriggerAddAction( gg_trg_Tide_PoolMovement, function Trig_Tide_PoolMovement_Actions )
endfunction

//third
//TESH.scrollpos=13
//TESH.alwaysfold=0
function Trig_Tide_Pool_damage_Func001C takes nothing returns boolean
    if ( not ( IsUnitEnemy(GetTriggerUnit(), GetOwningPlayer(udg_Vrtxcaster)) == true ) ) then
        return false
    endif
    return true
endfunction

function Trig_Tide_Pool_damage_Func001D takes nothing returns boolean
    if ( not ( IsUnitAlly(GetTriggerUnit(), GetOwningPlayer(udg_Vrtxcaster)) == true ) ) then
        return false
    endif
    return true
endfunction

function Trig_Tide_Pool_damage_Actions takes nothing returns nothing
    if ( Trig_Tide_Pool_damage_Func001C() ) then
        call CreateNUnitsAtLoc( 1, 'h001', GetOwningPlayer(udg_Vrtxcaster), GetUnitLoc(GetTriggerUnit()), 0.00 )
        call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
        call UnitAddAbilityBJ( 'A007', GetLastCreatedUnit() )
        call IssueTargetOrderBJ( GetLastCreatedUnit(), "frostnova", GetTriggerUnit() )
    else
        if ( Trig_Tide_Pool_damage_Func001D()) then
        call CreateNUnitsAtLoc( 1, 'h001', GetOwningPlayer(udg_Vrtxcaster), GetUnitLoc(GetTriggerUnit()), 0.00 )
        call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
        call UnitAddAbilityBJ( 'A006', GetLastCreatedUnit() )
        call IssueTargetOrderBJ( GetLastCreatedUnit(), "rejuvination", GetTriggerUnit() )
        endif
    endif
endfunction

//===========================================================================
function InitTrig_Tide_Pool_damage takes nothing returns nothing
    set gg_trg_Tide_Pool_damage = CreateTrigger(  )
    call TriggerRegisterUnitInRangeSimple(gg_trg_Tide_Pool_damage,800,udg_Vrtxcaster)
    call TriggerAddAction( gg_trg_Tide_Pool_damage, function Trig_Tide_Pool_damage_Actions )
endfunction

//last
//TESH.scrollpos=7
//TESH.alwaysfold=0
function Trig_TidePool_End_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A005' 
endfunction

function Trig_TidePool_End_Actions takes nothing returns nothing
      local integer i=1
    loop
        exitwhen i>45
        call RemoveUnit(udg_Vrtx_Unit[i])
        set udg_Vrtx_Unit[i] = null
        set i=i+1
    endloop
    set udg_Vrtxcaster = null
endfunction

//===========================================================================
function InitTrig_TidePool_End takes nothing returns nothing
    set gg_trg_TidePool_End = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_TidePool_End, EVENT_PLAYER_UNIT_SPELL_ENDCAST )
    call TriggerAddCondition( gg_trg_TidePool_End, Condition( function Trig_TidePool_End_Conditions ) )
    call TriggerAddAction( gg_trg_TidePool_End, function Trig_TidePool_End_Actions )
endfunction

these are the problems i have encountered
  • circle doesn't move
  • the damage doesn't work
  • it neither heals friendly units nor does it give them rejuvenation

additional help appreciated:
  • Making it into one trigger
  • making it follow the JESP Standard
  • How to make it in vJASS


any help GUI/JASS/vJASS will be appreciated
06-27-2008, 08:58 AM#2
Pyrogasm
Well, there's a lot I'd like to help you with here (I'll go through step-by-step building the spell), but I need to know one thing first: what do you know about vJASS/coding in vJASS, and do you mind if I introduce you to structs? Or would you rather I stuck to handle variables/that sort of thing?


Edit: One more thing: By "damages enemy units w/in range for 25x current level" did you mean it did 25xlevel damage per second, or just when the spell was cast?
06-27-2008, 09:03 AM#3
goldendercon
I meant the one you said 25x current level damage per second

and i have only the very basic knowledge or vJass but I'm a fast learner

I'm a consistent honor student in my school not that i'm bragging or anything
06-27-2008, 09:04 AM#4
Pyrogasm
lulz. Give me a bit.
06-27-2008, 09:14 AM#5
goldendercon
ok,
I reaaallllllyyyyyyy need this
06-27-2008, 11:46 AM#6
Pyrogasm
Okay, so the first thing to note is this: in JASS, 'trigger' generally only refers to the actual variable type trigger because, well, any and all JASS 'triggers' (meaning things with events, conditions, and actions) can all be condensed into one GUI 'trigger' (meaning the things you see in the Trigger Editor). To do that, we have to use a globals block for the other triggers (the variable type) and we end up with this:
Collapse JASS:
//Note that we've now renamed the 'GUI trigger' to "TidePool"
//And I've given all the functions relevant names
//And we're moving the globals out of the Variable Editor
globals
    trigger TidePoolMovementTrig
    trigger TidePoolDamageTrig
    trigger TidePoolEndTrig

    unit Caster
    unit Unit
    unit array Effect_Units
    integer Rotation2
    integer Rotation
endglobals

//1st trigger
function TidePool_CastConditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A005' 
endfunction

function TidePool_Cast takes nothing returns nothing
    local integer i = 1
    local unit F= GetTriggerUnit()
    local unit array E
    set Caster= GetTriggerUnit()
    set Rotation2= 0
    loop
        exitwhen i > 45
        call CreateNUnitsAtLoc( 1, 'h004', GetOwningPlayer(F), PolarProjectionBJ(GetUnitLoc(F), 800, ( 45 * I2R(i) )), bj_UNIT_FACING )
        set Effect_Units[i] = GetLastCreatedUnit()
        set i = i + 1
    endloop
    call EnableTrigger( TidePoolMovementTrig )
    call EnableTrigger( TidePoolDamageTrig )
endfunction

//2nd trigger
function TidePool_Movement takes nothing returns nothing
local integer i=1
    local location loc=GetUnitLoc(Caster)
    local location loc2

    set Rotation2=Rotation2+5   

    loop
        exitwhen i>45
        set loc2=GetUnitLoc(Effect_Units[i])
// Replace DistanceBetweenPoints with 400 ?
        call SetUnitPositionLoc( Effect_Units[i], PolarProjectionBJ(loc, 800,  Rotation + i*15  ))
        call RemoveLocation(loc2)
        set i=i+1
    endloop
endfunction

//third
function Trig_Tide_Pool_damage_Func001C takes nothing returns boolean
    if ( not ( IsUnitEnemy(GetTriggerUnit(), GetOwningPlayer(Caster)) == true ) ) then
        return false
    endif
    return true
endfunction

function Trig_Tide_Pool_damage_Func001D takes nothing returns boolean
    if ( not ( IsUnitAlly(GetTriggerUnit(), GetOwningPlayer(Caster)) == true ) ) then
        return false
    endif
    return true
endfunction

function TidePool_Damage takes nothing returns nothing
    if ( Trig_Tide_Pool_damage_Func001C() ) then
        call CreateNUnitsAtLoc( 1, 'h001', GetOwningPlayer(Caster), GetUnitLoc(GetTriggerUnit()), 0.00 )
        call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
        call UnitAddAbilityBJ( 'A007', GetLastCreatedUnit() )
        call IssueTargetOrderBJ( GetLastCreatedUnit(), "frostnova", GetTriggerUnit() )
    else
        if ( Trig_Tide_Pool_damage_Func001D()) then
        call CreateNUnitsAtLoc( 1, 'h001', GetOwningPlayer(Caster), GetUnitLoc(GetTriggerUnit()), 0.00 )
        call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
        call UnitAddAbilityBJ( 'A006', GetLastCreatedUnit() )
        call IssueTargetOrderBJ( GetLastCreatedUnit(), "rejuvination", GetTriggerUnit() )
        endif
    endif
endfunction

//last
function TidePool_EndCastConditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A005' 
endfunction

function TidePool_EndCast takes nothing returns nothing
      local integer i=1
    loop
        exitwhen i>45
        call RemoveUnit(Effect_Units[i])
        set Effect_Units[i] = null
        set i=i+1
    endloop
    set Caster = null
endfunction

//===========================================================================
function InitTrig_TidePool takes nothing returns nothing
    set gg_trg_TidePool = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(gg_trg_TidePool, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(gg_trg_TidePool, Condition(function TidePool_CastConditions))
    call TriggerAddAction(gg_trg_TidePool, function TidePool_Cast)

    set TidePoolMovementTrig = CreateTrigger()
    call TriggerRegisterTimerEventPeriodic(TidePoolMovementTrig, 0.04)
    call TriggerAddAction(TidePoolMovementTrig, function TidePool_Movement)

    set TidePoolDamageTrig = CreateTrigger()
    call TriggerRegisterUnitInRangeSimple(TidePoolDamageTrig,800,Caster)
    call TriggerAddAction(TidePoolDamageTrig, function TidePool_Damage)

    set TidePoolEndTrig = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(TidePoolEndTrig, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddCondition(TidePoolEndTrig, Condition(function TidePool_EndCastConditions))
    call TriggerAddAction(TidePoolEndTrig, function TidePool_EndCast)
endfunction
So now that that's out of the way, let's look at the general method you're using: you detect the cast of the spell, turn on periodic movement and a unit-in-range damage trigger, and then detect the end of the cast. That's the way someone using GUI would approach this spell, and in theory it should work... but we're using JASS so we've got more and better options. The way you have set up the damage trigger simply doesn't work. What you're going to want to do instead is to do the damage periodically to the units around the caster.

Before we go into fixing the periodic stuff, let's optimize your cast function (the red is what we'll change):
Collapse JASS:
function TidePool_Cast takes nothing returns nothing
    local integer i = 1
    local unit F= GetTriggerUnit()
    local unit array E
    set Caster= GetTriggerUnit()
    set Rotation2= 0
    loop
        exitwhen i > 45
        call CreateNUnitsAtLoc( 1, 'h004', GetOwningPlayer(F), PolarProjectionBJ(GetUnitLoc(F), 800, ( 45 * I2R(i) )), bj_UNIT_FACING )
        set Effect_Units[i] = GetLastCreatedUnit()
        set i = i + 1
    endloop
    call EnableTrigger(TidePoolMovementTrig)
    call EnableTrigger(TidePoolDamageTrig)
endfunction
There's no need for "F" if you're already setting "Caster", so we can use that instead. On that same line of thought, why does "E" exist at all? Additionally, there's no need to use GetLastCreatedUnit() because you can just set the variable directly. Why >45? If you're incrementing by 45 degrees for each new unit, you should exit when i>8. The I2R is stupid because integers can be considered reals, though in this case we might as well just change Rotation and Rotation2 to real variables. Finally, you're using a BJ function to create units (with a random global variable, bj_UNIT_FACING).
Collapse JASS:
function TidePool_Cast takes nothing returns nothing
    local integer i = 1

    set Caster = GetTriggerUnit()
    set Rotation2 = 0.00
    loop
        exitwhen i>8
        set Effect_Units[i] = CreateUnitAtLoc(GetOwningPlayer(Caster), 'h004', PolarProjectionBJ(GetUnitLoc(Caster), 800, 45*i)), 0.00)
        set i = i + 1
    endloop

    call EnableTrigger(TidePoolMovementTrig)
    call EnableTrigger(TidePoolDamageTrig)
endfunction
There's still something wrong, though. In GUI we use locations (points), in JASS, we use X/Y coordinates. This saves us from allocating handles all the time, it ensures we don't leak things, and code with X/Y coordinates is in general faster than code that uses locations. To do this, we'll have to pick apart the native PolarProjectionBJ:
Expand PolarProjectionBJ:
bj_DEGTORAD and bj_RADTODEG are simply conversion factors that convert from degrees to radians and back, respectively. The natives Sin and Cos assume the angle is given in radians, so we must convert from degrees to radians when we call Sin/Cos. As you see, PolarProjectionBJ projects the X and Y coordinates separately using a formula and then converts those coordinates to a point. We just need the coordinates, so we'll steal that:
Collapse JASS:
function TidePool_Cast takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)

    set Caster = GetTriggerUnit()
    set Rotation2 = 0
    loop
        exitwhen i>8
        set Effect_Units[i] = CreateUnit(GetOwningPlayer(Caster), 'h004', CX+800.00*Cos(45.00*i*bj_DEGTORAD), CY+800.00*Sin(45.00*i*bj_DEGTORAD), 0.00)
        set i = i + 1
    endloop

    call EnableTrigger(TidePoolMovementTrig)
    call EnableTrigger(TidePoolDamageTrig)
endfunction

That's great, so let's see what it looks like all together now:
Expand JASS:
Now we can work on the rest of the trigger (the parts that don't work). The first thing to fix, then, should be obvious: the movement and damage triggers can be consolidated into one periodic trigger that handles both. The next thing might not seem so obvious, but what we're going to do is instead of detecting the endcast of the spell with another trigger, we're going to use the periodic trigger to check to see if the unit's current order is that of the spell he's channeling: if it is, he's not done, and if it isn't, then channeling has stopped.

Then, a general flowchart of what the periodic spell will do is this:
Is the unit's current order the order of the spell?
Yes
Do damage
Do Healing
Do Movement
No
End the spell
Doing this, we'll reduce the number of triggers we must have to 2, so let's start building that periodic trigger (ignoring all the other stuff we've done so far):
Collapse JASS:
globals
    trigger TidePoolPeriodicTrig
    unit Caster
endglobals 

function TidePool_Periodic takes nothing returns nothing

endfunction


function InitTrig_TidePool takes nothing returns nothing
    set TidePoolPeriodicTrig = CreateTrigger()
    call TriggerRegisterTimerEventPeriodic(TidePoolPeriodicTrig, 0.04)
    call TriggerAddAction(TidePoolPeriodicTrig, function TidePool_Periodic)
endfunction
Now that we've got the framework, let's start filling it in (red being what will be fixed):
Collapse JASS:
globals //Only showing the new ones, the others remain
    trigger TidePoolPeriodicTrig
    //unit Caster
    //real Rotation
    //real Rotation2
endglobals 

function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local location loc = GetUnitLoc(Caster)
    local location loc2

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>45
        set loc2 = GetUnitLoc(Effect_Units[i])
        call SetUnitPositionLoc(Effect_Units[i], PolarProjectionBJ(loc, 800,  Rotation + i*15  ))
        call RemoveLocation(loc2)
        set i=i+1
    endloop
endfunction


function InitTrig_TidePool takes nothing returns nothing
    set TidePoolPeriodicTrig = CreateTrigger()
    call TriggerRegisterTimerEventPeriodic(TidePoolPeriodicTrig, 0.04)
    call TriggerAddAction(TidePoolPeriodicTrig, function TidePool_Periodic)
endfunction
As we've learned, we want to use X/Y coordinates instead of locations, so let's fix that:
Collapse JASS:
function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>8
        call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
        set i=i+1
    endloop
endfunction

Now we've got to add in stuff for getting the units near the caster, and that looks like this:
Collapse JASS:
function TidePool_True takes nothing returns boolean
    return true
endfunction

function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local group G = CreateGroup()
    local unit U

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>8
        call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
        set i=i+1
    endloop

    call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function TidePool_True))
    loop
        set U = FirstOfGroup(G)
        exitwhen U == null

        //Do stuff with U

        call GroupRemoveUnit(G, U)
    endloop

    call DestroyGroup(G)
    set G = null
    //set U = null [not needed because U is going to be null when the loop exits]
endfunction
The stuff with the function just returning true is due to a bug found by grim001. It turns out that if you pass "null" as a boolexpr argument it actually leaks and allocates memory. So we use the above fix instead. Anway, what to do with U? Well, let's add in the code from your damage trigger and see how that works out (red is bad):
Collapse JASS:
globals
    trigger TidePoolPeriodicTrig
    //unit Caster
    //real Rotation2
endglobals

function Trig_Tide_Pool_damage_Func001C takes nothing returns boolean
    if ( not ( IsUnitEnemy(GetTriggerUnit(), GetOwningPlayer(Caster)) == true ) ) then
        return false
    endif
    return true
endfunction

function Trig_Tide_Pool_damage_Func001D takes nothing returns boolean
    if ( not ( IsUnitAlly(GetTriggerUnit(), GetOwningPlayer(Caster)) == true ) ) then
        return false
    endif
    return true
endfunction

function TidePool_True takes nothing returns boolean
    return true
endfunction

function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local group G = CreateGroup()
    local unit U

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>8
        call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
        set i=i+1
    endloop

    call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function TidePool_True))
    loop
        set U = FirstOfGroup(G)
        exitwhen U == null

        if ( Trig_Tide_Pool_damage_Func001C() ) then
            call CreateNUnitsAtLoc( 1, 'h001', GetOwningPlayer(Caster), GetUnitLoc(GetTriggerUnit()), 0.00 )
            call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
            call UnitAddAbilityBJ( 'A007', GetLastCreatedUnit() )
            call IssueTargetOrderBJ( GetLastCreatedUnit(), "frostnova", GetTriggerUnit() )
        else
            if ( Trig_Tide_Pool_damage_Func001D()) then
                call CreateNUnitsAtLoc( 1, 'h001', GetOwningPlayer(Caster), GetUnitLoc(GetTriggerUnit()), 0.00 )
                call UnitApplyTimedLifeBJ( 3, 'BTLF', GetLastCreatedUnit() )
                call UnitAddAbilityBJ( 'A006', GetLastCreatedUnit() )
                call IssueTargetOrderBJ( GetLastCreatedUnit(), "rejuvination", GetTriggerUnit() )
            endif
        endif

        call GroupRemoveUnit(G, U)
    endloop

    call DestroyGroup(G)
    set G = null
endfunction
Lots of red, but no fear: most of it is simple stuff. The problem with those two extra functions at the top is that they're written retardedly, reference things that don't exist anymore (GetTriggerUnit()) and can be inlined into the script, so let's do that. In addition, let's fix all the BJs in the code:
Collapse JASS:
function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local group G = CreateGroup()
    local unit U

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>8
        call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
        set i=i+1
    endloop

    call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function TidePool_True))
    loop
        set U = FirstOfGroup(G)
        exitwhen U == null

        if (IsUnitEnemy(U, GetOwningPlayer(Caster))) then
            call CreateUnit(GetOwningPlayer(Caster), 'h001', CX, CY, 0.00)
            call UnitApplyTimedLife(GetLastCreatedUnit(), 'BTLF', 3.00)
            call UnitAddAbility(GetLastCreatedUnit(), 'A007')
            call IssueTargetOrder(GetLastCreatedUnit(), "frostnova", U)
        else
            if (IsUnitAlly(U, GetOwningPlayer(Caster))) then
                call CreateUnit(GetOwningPlayer(Caster), 'h001', CX, CY, 0.00)
                call UnitApplyTimedLife(GetLastCreatedUnit(), 'BTLF', 3.00)
                call UnitAddAbility(GetLastCreatedUnit(), 'A007')
                call IssueTargetOrder(GetLastCreatedUnit(), "rejuvination", U)
            endif
        endif

        call GroupRemoveUnit(G, U)
    endloop

    call DestroyGroup(G)
    set G = null
endfunction
You should notice that there is a lot of GetOwningPlayer(Caster) and that it's inside a loop, so it'll likely be called a lot more than expected. To remedy this, we'll simply use a variable that points to the owning player. You're also using GetLastCreatedUnit() which is dumb because you ought to use a variable again instead. Another thing that's wrong is the nested if statements; the second isn't needed, because if a unit isn't an enemy then it must be an ally. Thus, we can simplfy the function to:
Collapse JASS:
function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local group G = CreateGroup()
    local unit U
    local unit Dummy
    local player P = GetOwningPlayer(Caster)

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>8
        call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
        set i=i+1
    endloop

    call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function TidePool_True))
    loop
        set U = FirstOfGroup(G)
        exitwhen U == null

        if (IsUnitEnemy(U, P)) then
            set Dummy = CreateUnit(P, 'h001', CX, CY, 0.00)
            call UnitApplyTimedLife(Dummy, 'BTLF', 3.00)
            call UnitAddAbility(Dummy, 'A007')
            call IssueTargetOrder(Dummy, "frostnova", U)
        else
            set Dummy = CreateUnit(P, 'h001', CX, CY, 0.00)
            call UnitApplyTimedLife(Dummy, 'BTLF', 3.00)
            call UnitAddAbility(Dummy, 'A006')
            call IssueTargetOrder(Dummy, "rejuvination", U)
        endif

        call GroupRemoveUnit(G, U)
    endloop

    call DestroyGroup(G)
    set G = null
    set Dummy = null
endfunction
Now, I should have asked this earlier, but why are you creating dummy units? The only thing I can thing of is special effects/buffs because damage per second is simply not going to happen this way. So I'm going to assume that you're casting spells simply for looks, in which case this gets a lot simpler. Let's remove all that dummy crap and prepare for damaging units and healing manually (in yellow is what I added):
Collapse JASS:
function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local group G = CreateGroup()
    local unit U
    local player P = GetOwningPlayer(Caster)
    local integer Level = GetUnitAbilityLevel(U, 'A005')
    local real Damage = 25.00*Level*0.04 //We multiply by the timer period so that it heals per second properly
    local real Healing = 5.00*Level*0.04

    set Rotation2 = Rotation2+5.00   

    loop
        exitwhen i>8
        call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
        set i=i+1
    endloop

    call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function TidePool_True))
    loop
        set U = FirstOfGroup(G)
        exitwhen U == null

        if (IsUnitEnemy(U, P)) then
           call UnitDamageTarget(Caster, U, Damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        else
           call SetWidgetLife(U, GetWidgetLife(U)+Healing)
        endif

        call GroupRemoveUnit(G, U)
    endloop

    call DestroyGroup(G)
    set G = null
endfunction
But how shall we show buffs/whatnot? We're going to make two dummy auras (based off of something like Devotion Aura), use the disabled spellbook trick (search the forums to know what I mean if you don't already), and then do the graphics like that. So make one ability affect allied units and give it the proper buff, then do the same for the 2nd, but affecting enemies instead. Then set up the spellbooks and that shnaz and we'll modify our cast function like so (yellow is added):
Collapse JASS:
function TidePool_Cast takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local integer Level = GetUnitAbilityLevel(U, 'A005')

    set Caster = GetTriggerUnit()
    set Rotation2 = 0
    loop
        exitwhen i>8
        set Effect_Units[i] = CreateUnit(GetOwningPlayer(Caster), 'h004', CX+800.00*Cos(45.00*i*bj_DEGTORAD), CY+800.00*Sin(45.00*i*bj_DEGTORAD), 0.00)
        set i = i + 1
    endloop

    call UnitAddAbility(Caster, 'A009') //A009 being the allied disabled spellbook
    call SetUnitAbilityLevel(U, 'A009', Level)
    call UnitAddAbility(Caster, 'A00A') //A00A being the enemy disabled spellbook
    call SetUnitAbilityLevel(U, 'A00A', Level)

    call EnableTrigger(TidePoolPeriodicTrig)
endfunction
Finally, we need to add our order check to the periodic function. I don't know what your base orderid is, but I'm going to assume that it's starfall:
Collapse JASS:
function TidePool_Periodic takes nothing returns nothing
    local integer i = 1
    local real CX = GetUnitX(Caster)
    local real CY = GetUnitY(Caster)
    local group G = CreateGroup()
    local unit U
    local player P = GetOwningPlayer(Caster)
    local integer Level = GetUnitAbilityLevel(U, 'A005')
    local real Damage = 25.00*Level*0.04 //We multiply by the timer period so that it heals per second properly
    local real Healing = 5.00*Level*0.04

    if OrderId2String(GetUnitCurrentOrder(Caster)) == "starfall" then
        set Rotation2 = Rotation2+5.00   

        loop
            exitwhen i>8
            call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
            set i=i+1
        endloop

        call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function TidePool_True))
        loop
            set U = FirstOfGroup(G)
            exitwhen U == null

            if (IsUnitEnemy(U, P)) then
                call UnitDamageTarget(Caster, U, Damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
            else
                call SetWidgetLife(U, GetWidgetLife(U)+Healing)
            endif

            call GroupRemoveUnit(G, U)
        endloop
    else
        loop
            exitwhen i>8
            call KillUnit(Effect_Units[i])
            set i=i+1

            call DisableTrigger(TidePoolPeriodicTrig)
            call UnitRemoveAbility(Caster, 'A009')
            call UnitRemoveAbility(Caster, 'A00A')
        endloop
    endif

    call DestroyGroup(G)
    set G = null
endfunction
Alright, so all together that looks like:
Expand JASS:
Now, with that, you're just about done, except for a few minor optimizations:
  • Instead of a second trigger we can use a periodic timer that we pause and resume
  • We might want to use a scope for our spell
  • If we wanted, we could make it MUI... but that's a lesson for another day
  • We could optimize it for non-MUI casting
  • We could add configuration constants
Here's the code with the first two done, the other three are for some other time/place:
Collapse JASS:
scope TidePool
    globals
        private trigger TidePoolPeriodicTrig
        private unit Caster
        private unit Unit
        private unit array Effect_Units
        private real Rotation2
        private timer T
    endglobals

    private function CastConditions takes nothing returns boolean
        return GetSpellAbilityId() == 'A005' 
    endfunction

    private function True takes nothing returns boolean
        return true
    endfunction

    private function Cast takes nothing returns nothing
        local integer i = 1
        local real CX = GetUnitX(Caster)
        local real CY = GetUnitY(Caster)

        set Caster = GetTriggerUnit()
        set Rotation2 = 0
        loop
            exitwhen i>8
            set Effect_Units[i] = CreateUnit(GetOwningPlayer(Caster), 'h004', CX+800.00*Cos(45.00*i*bj_DEGTORAD), CY+800.00*Sin(45.00*i*bj_DEGTORAD), 0.00)
            set i = i + 1
        endloop

        call ResumeTimer(T)
    endfunction

    private function Periodic takes nothing returns nothing
        local integer i = 1
        local real CX = GetUnitX(Caster)
        local real CY = GetUnitY(Caster)
        local group G = CreateGroup()
        local unit U
        local player P = GetOwningPlayer(Caster)
        local integer Level = GetUnitAbilityLevel(U, 'A005')
        local real Damage = 25.00*Level*0.04 //We multiply by the timer period so that it heals per second properly
        local real Healing = 5.00*Level*0.04

        if OrderId2String(GetUnitCurrentOrder(Caster)) == "starfall" then
            set Rotation2 = Rotation2+5.00   

            loop
                exitwhen i>8
                call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
                set i=i+1
            endloop

            call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(    private function True))
            loop
                set U = FirstOfGroup(G)
                exitwhen U == null

                if (IsUnitEnemy(U, P)) then
                    call UnitDamageTarget(Caster, U, Damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                else
                    call SetWidgetLife(U, GetWidgetLife(U)+Healing)
                endif

                call GroupRemoveUnit(G, U)
            endloop
        else
            loop
                exitwhen i>8
                call KillUnit(Effect_Units[i])
                set i=i+1

                call PauseTimer(T)
                call UnitRemoveAbility(Caster, 'A009')
                call UnitRemoveAbility(Caster, 'A00A')
            endloop
        endif

        call DestroyGroup(G)
        set G = null
    endfunction

    //===========================================================================
    public function InitTrig takes nothing returns nothing
        set gg_trg_TidePool = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gg_trg_TidePool, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_TidePool, Condition(function CastConditions))
        call TriggerAddAction(gg_trg_TidePool, function Cast)

        set T = CreateTimer()
        call TimerStart(T, 0.04, true, function Periodic)
        call PauseTimer(T)
    endfunction
endscope
And that's about that...
06-27-2008, 11:49 AM#7
goldendercon
thanks and maybe you can also teach me how to make the other three when you have the time
06-27-2008, 12:08 PM#8
Deaod
[spam].summon Earth-Fury[/spam]
06-27-2008, 12:25 PM#9
Pyrogasm
He will come. Don't you worry.
06-27-2008, 10:21 PM#10
goldendercon
Thanks Pyrogasm, I really appreciate all the help, the spell works perfectly now, although i'm still having issues with making it MUI and having it follow the JESP standard
07-01-2008, 08:16 AM#11
Pyrogasm
Hmmz....

Okay, so you want to make it MUI and follow the JESP standard? Well, the first thing to make it follow the JESP standard is that it needs configuration constants, so let's see how those work.

Spells can have configuration through constant globals, constant functions, or a combination of both. Constant globals look much more elegant whereas constant functions usually look clunky and take up a lot of space. However, constant functions also allow arguments to be passed so that users can create their own formula for, say, calculating how much damage is done. Here are some examples:
Collapse Constant Globals:
//Configuration
globals
    private constant integer SpellId = 'A000'
    private constant real Damage = 25.00 //Damage per second, multiplied by the level of the ability
    private constant real AoE = 300.00
    private constant real TimerPeriod = 0.04
endglobals
Collapse Constant Functions:
//Configuration
private constant function SpellId takes nothing returns integer
    return 'A000'
endfunction

private constant function Damage takes integer Level returns real
    return Level*25.00 //Damage per second
endfunction

private constant function AoE takes integer Level returns real
    return 300.00
endfunction

private constant function TimerPeriod takes nothing returns real
    return 0.04
endfunction
Collapse Hybrid:
//Configuration
globals
    private constant integer SpellId = 'A000'
    private constant real TimerPeriod = 0.04
endglobals

private constant function Damage takes integer Level returns real
    return Level*25.00 //Damage per second
endfunction

private constant function AoE takes integer Level returns real
    return 300.00
The Hybrid way is the best because it allows us to vary the spell's effects based on level when we need to, but also allow for easy configuration of constant things. Now, for your spell, here's what we need to have configuration for:
  • The spell's rawcode
  • Rawcode of the 1st aura
  • Rawcode of the 2nd aura
  • The spell's AoE
  • The damage per second
  • The healing per second
  • The orderstring of the spell
  • The rawcode of the dummy effects
  • The numer of dummy effects
  • The timer interval
  • How much the dummies move per timer interval
  • The damage type
  • The attack type
  • The weapon type
  • What units are damaged by the spell
  • What units are healed by the spell
That's a lot of stuff, but it will be simple to add, so here's what our constant globals will be (things that are static/don't depend on the level of the spell):
Collapse JASS:
globals
    private constant integer SPELLID = 'A005'
    private constant integer AURAID1 = 'A009'
    private constant integer AURAID2 = 'A00A'
    private constant string CHANNELINGSTRING = "starfall"
    private constant integer EFFECTDUMMYID = 'h004'
    private constant real TIMERINTERVAL = 0.04
    private constant attacktype ATTACK = ATTACK_TYPE_CHAOS
    private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
    private constant weapontype WEAPON = null
endglobals
And the rest are going to be constant functions that take the level of the spell as an argument:
Collapse JASS:
private constant function AoE takes integer Level returns real
    return 300.00*Level
endfunction

private constant function DamagePerSecond takes integer Level returns real
    return 25.00*Level
endfunction

private constant function HealthPerSecond takes integer Level returns real
    return 5.00*Level
endfunction

private constant function NumberOfEffects takes integer Level returns integer
    return 8
endfunction

private constant function AngleOffsetPerInterval takes integer Level returns real
    return 5.00
endfunction

private constant function DamageFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
    return IsUnitEnemy(FilterUnit, CasterOwner)
endfunction

private constant function HealFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
    return IsUnitAlly(FilterUnit, CasterOwner)
endfunction
Now that we've got these constants set up, here's what in the code we're going to have to change so that the constants actually affect something:
Collapse JASS:
scope TidePool //I also fixed some minor technical errors in the code, so you ought to notice them
    //============================
    //Start Configuration
    //============================

    globals
        private constant integer SPELLID = 'A005'
        private constant integer AURAID1 = 'A009'
        private constant integer AURAID2 = 'A00A'
        private constant string CHANNELINGSRING = "starfall"
        private constant integer EFFECTDUMMYID = 'h004'
        private constant real TIMERINTERVAL = 0.04
        private constant attacktype ATTACK = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON = null
    endglobals

    private constant function AoE takes integer Level returns real
        return 300.00*Level
    endfunction

    private constant function DamagePerSecond takes integer Level returns real
        return 25.00*Level
    endfunction

    private constant function HealthPerSecond takes integer Level returns real
        return 5.00*Level
    endfunction

    private constant function NumberOfEffects takes integer Level returns integer
        return 8
    endfunction

    private constant function AngleOffsetPerInterval takes integer Level returns real
        return 5.00
    endfunction

    private constant function DamageFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
        return IsUnitEnemy(FilterUnit, CasterOwner)
    endfunction

    private constant function HealFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
        return IsUnitAlly(FilterUnit, CasterOwner)
    endfunction

    //============================
    //End Configuration
    //============================

    globals
        private trigger TidePoolPeriodicTrig
        private unit Caster
        private unit Unit
        private unit array Effect_Units
        private real Rotation2
        private timer T
    endglobals

    private function CastConditions takes nothing returns boolean
        return GetSpellAbilityId() == 'A005'
    endfunction

    private function True takes nothing returns boolean
        return true
    endfunction

    private function Cast takes nothing returns nothing
        local integer i = 1
        local real CX
        local real CY
        local player P

        set Caster = GetTriggerUnit()
        set CX = GetUnitX(Caster)
        set CY = GetUnitY(Caster)
        set P = GetOwningPlayer(Caster)
        set Rotation2 = 0
        loop
            exitwhen i>8
            set Effect_Units[i] = CreateUnit(P, 'h004', CX+800.00*Cos(45.00*i*bj_DEGTORAD), CY+800.00*Sin(45.00*i*bj_DEGTORAD), 0.00)
            set i = i + 1
        endloop

        call UnitAddAbility(Caster, 'A009')
        call UnitAddAbility(Caster, 'A00A')

        call ResumeTimer(T)
    endfunction

    private function Periodic takes nothing returns nothing
        local integer i = 1
        local real CX = GetUnitX(Caster)
        local real CY = GetUnitY(Caster)
        local group G = CreateGroup()
        local unit U
        local player P = GetOwningPlayer(Caster)
        local integer Level = GetUnitAbilityLevel(U, 'A005')
        local real Damage = 25.00*Level*0.04 //We multiply by the timer period so that it heals per second properly
        local real Healing = 5.00*Level*0.04

        if OrderId2String(GetUnitCurrentOrder(Caster)) == "starfall" then
            set Rotation2 = Rotation2+5.00 

            loop
                exitwhen i>8
                call SetUnitPosition(Effect_Units[i], CX+800.00*Cos((45.00*i+Rotation2)*bj_DEGTORAD), CY+800.00*Sin((45.00*i+Rotation2)*bj_DEGTORAD))
                set i=i+1
            endloop

            call GroupEnumUnitsInRange(G, CX, CY, 800.00, Condition(function True))
            loop
                set U = FirstOfGroup(G)
                exitwhen U == null

                if IsUnitEnemy(U, P) then
                    call UnitDamageTarget(Caster, U, Damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
                else
                    call SetWidgetLife(U, GetWidgetLife(U)+Healing)
                endif

                call GroupRemoveUnit(G, U)
            endloop
        else
            loop
                exitwhen i>8
                call KillUnit(Effect_Units[i])
                set i=i+1
            endloop

            call PauseTimer(T)
            call UnitRemoveAbility(Caster, 'A009')
            call UnitRemoveAbility(Caster, 'A00A')
        endif

        call DestroyGroup(G)
        set G = null
    endfunction

    //===========================================================================
    public function InitTrig takes nothing returns nothing
        set gg_trg_TidePool = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gg_trg_TidePool, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_TidePool, Condition(function CastConditions))
        call TriggerAddAction(gg_trg_TidePool, function Cast)

        set T = CreateTimer()
        call TimerStart(T, 0.04, true, function Periodic)
        call PauseTimer(T)
    endfunction
endscope
And here with those replaced, yellow is added:
Collapse JASS:
scope TidePool
    //============================
    //Start Configuration
    //============================

    globals
        private constant integer SPELLID = 'A005'
        private constant integer AURAID1 = 'A009'
        private constant integer AURAID2 = 'A00A'
        private constant string CHANNELINGSTRING = "starfall"
        private constant integer EFFECTDUMMYID = 'h004'
        private constant real TIMERINTERVAL = 0.04
        private constant attacktype ATTACK = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON = null
    endglobals

    private constant function AoE takes integer Level returns real
        return 300.00*Level
    endfunction

    private constant function DamagePerSecond takes integer Level returns real
        return 25.00*Level
    endfunction

    private constant function HealthPerSecond takes integer Level returns real
        return 5.00*Level
    endfunction

    private constant function NumberOfEffects takes integer Level returns integer
        return 8
    endfunction

    private constant function AngleOffsetPerInterval takes integer Level returns real
        return 5.00
    endfunction

    private constant function DamageFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
        return IsUnitEnemy(FilterUnit, CasterOwner)
    endfunction

    private constant function HealFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
        return IsUnitAlly(FilterUnit, CasterOwner)
    endfunction

    //============================
    //End Configuration
    //============================

    globals
        private trigger TidePoolPeriodicTrig
        private unit Caster
        private unit Unit
        private unit array Effect_Units
        private real Rotation2
        private timer T
    endglobals

    private function CastConditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELLID
    endfunction

    private function True takes nothing returns boolean
        return true
    endfunction

    private function Cast takes nothing returns nothing
        local integer i = 1
        local real CX
        local real CY
        local player P
        local integer Level
        local real Offset
        local integer Num
        local real AngleIncrement

        set Caster = GetTriggerUnit()
        set CX = GetUnitX(Caster)
        set CY = GetUnitY(Caster)
        set P = GetOwningPlayer(Caster)

        set Level = GetUnitAbilityLevel(Caster, SPELLID)
        set Offset = AoE(Level)
        set Num = NumberOfEffects(Level)
        set AngleIncrement = 360.00/Num

        set Rotation2 = 0
        loop
            exitwhen i>Num
            set Effect_Units[i] = CreateUnit(P, EFFECTDUMMYID, CX+Offset*Cos(AngleIncrement*i*bj_DEGTORAD), CY+Offset*Sin(AngleIncrement*i*bj_DEGTORAD), 0.00)
            set i = i + 1
        endloop

        call UnitAddAbility(Caster, AURAID1)
        call UnitAddAbility(Caster, AURAID2)

        call ResumeTimer(T)
    endfunction

    private function Periodic takes nothing returns nothing
        local integer i = 1
        local real CX = GetUnitX(Caster)
        local real CY = GetUnitY(Caster)
        local group G = CreateGroup()
        local unit U
        local player P = GetOwningPlayer(Caster)
        local integer Level = GetUnitAbilityLevel(U, SPELLID)
        local real Damage = DamagePerSecond(Level)*TIMERINTERVAL //We multiply by the timer period so that it heals per second properly
        local real Healing = HealthPerSecond(Level)*TIMERINTERVAL
        local integer Num = NumberOfEffects(Level)
        local integer AngleIncrement = 360.00/Num
        local real Offset = AoE(Level)

        if OrderId2String(GetUnitCurrentOrder(Caster)) == CHANNELINGSTRING then
            set Rotation2 = Rotation2+AngleOffsetPerInterval(Level)

            loop
                exitwhen i>Num
                call SetUnitPosition(Effect_Units[i], CX+Offset*Cos((AngleIncrement*i+Rotation2)*bj_DEGTORAD), CY+Offset*Sin((AngleIncrement*i+Rotation2)*bj_DEGTORAD))
                set i=i+1
            endloop

            call GroupEnumUnitsInRange(G, CX, CY, Offset, Condition(function True))
            loop
                set U = FirstOfGroup(G)
                exitwhen U == null

                if DamageFilter(U, P, Level) then
                    call UnitDamageTarget(Caster, U, Damage, true, false, ATTACK, DAMAGE, WEAPON)
                elseif HealFilter(U, P, Level) then
                    call SetWidgetLife(U, GetWidgetLife(U)+Healing)
                endif

                call GroupRemoveUnit(G, U)
            endloop
        else
            loop
                exitwhen i>Num
                call KillUnit(Effect_Units[i])
                set i=i+1
            endloop

            call PauseTimer(T)
            call UnitRemoveAbility(Caster, AURAID1)
            call UnitRemoveAbility(Caster, AURAID2)
        endif

        call DestroyGroup(G)
        set G = null
    endfunction

    //===========================================================================
    public function InitTrig takes nothing returns nothing
        set gg_trg_TidePool = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gg_trg_TidePool, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_TidePool, Condition(function CastConditions))
        call TriggerAddAction(gg_trg_TidePool, function Cast)

        set T = CreateTimer()
        call TimerStart(T, TIMERINTERVAL, true, function Periodic)
        call PauseTimer(T)
    endfunction
endscope
Now that that's all nicely done, you want to make it MUI. To do that, we're going to get rid of all those global variables except "T" and use these lovely things called structs. And a struct stack. If you want to learn more specifics about how structs work, then you should read the JASSHelper manual, but for now I'll assume that you know enough about them to continue. So we must ask ourselves what the important values we must store in our struct are:
  • The caster
  • The level of the spell
  • CX and CY, since the caster never moves
  • The player owning the caster
  • Damage and Healing so we don't have to calculate it every time
  • Rotation2 (the angle offset)
  • How much to add to the rotation each iteration
  • AngleIncrement
  • Offset
  • The number of effects
  • The effects
So our struct will look like this:
Collapse JASS:
private struct TPStruct
    unit Caster
    player P
    integer Level
    real CX
    real CY
    real Damage
    real Healing
    integer Num
    real Rotation = 0.00
    real RotationAdd
    real AngleIncrement
    real Offset
    unit array Effects[360] //We can do arrays this large because JASSHelper now supports arrays up to 65000 indexes
endstruct                   //360 should be more than enough
And now we need some sort of create method, so let's do that, so our struct will look like this:
Collapse JASS:
private struct TPStruct
    unit Caster
    player P
    integer Level
    real CX
    real CY
    real Damage
    real Healing
    integer Num
    real Rotation = 0.00
    real RotationAdd
    real AngleIncrement
    real Offset
    unit array Effects[360]

    static method create takes unit Caster, integer Level returns TPStruct
        local integer i = 0
        local TPStruct TP = TPStruct.allocate()

        set TP.Caster = Caster
        set TP.P = GetOwningPlayer(Caster)
        set TP.Level = Level
        set TP.CX = GetUnitX(Caster)
        set TP.CY = GetUnitY(Caster)
        set TP.Damage = DamagePerSecond(Level)*TIMERINTERVAL
        set TP.Healing = HealthPerSecond(Level)*TIMERINTERVAL
        set TP.Num = NumberOfEffects(Level)
        set TP.RotationAdd = AngleOffsetPerInterval(Level)
        set TP.AngleIncrement = 360.00/Num*bj_DEGTORAD //Converting to Radians now so we don't have to later
        set TP.Offset = AoE(Level)

        loop
            set i = i+1
            exitwhen i>TP.Num
            set TP.Effects[i] = CreateUnit(TP.P, EFFECTDUMMYID, TP.CX+TP.Offset*Cos(TP.AngleIncrement*i), TP.CY+TP.Offset*Sin(TP.AngleIncrement*i), 0.00)
        endloop

        call UnitAddAbility(Caster, AURAID1)
        call UnitAddAbility(Caster, AURAID2)

        return TP
    endmethod
endstruct
Now, this replaces most of the code in our Cast function, so the cast function will now look like this:
Collapse JASS:
    private function Cast takes nothing returns nothing
        local unit U = GetTriggerUnit()
        local TPStruct TP = TPStruct.create(U, GetUnitAbilityLevel(U, SPELLID))

        //Do some stuff regarding attaching the struct, but we're not there yet

        call ResumeTimer(T)
        set U = null
    endfunction
Now we've got to modify our "Periodic" function to account for the fact that we are now dealing with structs. The stuff in red is going to be either changed or removed entirely:
Collapse JASS:
    private function Periodic takes nothing returns nothing
        local integer i = 1
        local real CX = GetUnitX(Caster)
        local real CY = GetUnitY(Caster)
        local group G = CreateGroup()
        local unit U
        local player P = GetOwningPlayer(Caster)
        local integer Level = GetUnitAbilityLevel(U, SPELLID)
        local real Damage = DamagePerSecond(Level)*TIMERINTERVAL
        local real Healing = HealthPerSecond(Level)*TIMERINTERVAL
        local integer Num = NumberOfEffects(Level)
        local integer AngleIncrement = 360.00/Num
        local real Offset = AoE(Level)

        if OrderId2String(GetUnitCurrentOrder(Caster)) == CHANNELINGSTRING then
            set Rotation2 = Rotation2+AngleOffsetPerInterval(Level)

            loop
                exitwhen i>Num
                call SetUnitPosition(Effect_Units[i], CX+Offset*Cos((AngleIncrement*i+Rotation2)*bj_DEGTORAD), CY+Offset*Sin((AngleIncrement*i+Rotation2)*bj_DEGTORAD))
                set i=i+1
            endloop

            call GroupEnumUnitsInRange(G, CX, CY, Offset, Condition(function True))
            loop
                set U = FirstOfGroup(G)
                exitwhen U == null

                if DamageFilter(U, P, Level) then
                    call UnitDamageTarget(Caster, U, Damage, true, false, ATTACK, DAMAGE, WEAPON)
                elseif HealFilter(U, P, Level) then
                    call SetWidgetLife(U, GetWidgetLife(U)+Healing)
                endif

                call GroupRemoveUnit(G, U)
            endloop
        else
            loop
                exitwhen i>Num
                call KillUnit(Effect_Units[i])
                set i=i+1
            endloop

            call PauseTimer(T)
            call UnitRemoveAbility(Caster, AURAID1)
            call UnitRemoveAbility(Caster, AURAID2)
        endif

        call DestroyGroup(G)
        set G = null
    endfunction
Most of this, as you can guess, is getting removed because we have a struct that contains most of the values we need already, but some stuff is not so obvious. For instance, "G" is not going away, it's simply going to become a global group so that we don't keep creating/destroying them constantly, so we'll have:
Collapse JASS:
    globals
        private group G
    endglobals

    public function InitTrig takes nothing returns nothing
        set gg_trg_TidePool = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gg_trg_TidePool, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_TidePool, Condition(function CastConditions))
        call TriggerAddAction(gg_trg_TidePool, function Cast)

        set T = CreateTimer()
        call TimerStart(T, TIMERINTERVAL, true, function Periodic)
        call PauseTimer(T)

        set G = CreateGroup()
    endfunction
And we'll add an onDestroy method to take care of some stuff in the "else" for us:
Collapse JASS:
        method onDestroy takes nothing returns nothing
            local integer i = 0

            call UnitRemoveAbility(this.Caster, AURAID1)
            call UnitRemoveAbility(this.Caster, AURAID2)
            loop
                set i = i+1
                exitwhen i>this.Num
                call KillUnit(this.Effects[i])
            endloop
        endmethod
And our "else" part of the periodic function will look like this:
Collapse JASS:
        else
            call TP.destroy() //Ignore for now how we get "TP", that will be in a bit
            call PauseTimer(T)
        endif
Now, for the other movement we can easily add a method to do that for us:
Collapse JASS:
        method Move takes nothing returns nothing
            local integer i = 0

            set this.Rotation = this.Rotation+this.RotationAdd
            loop
                set i = i+1
                exitwhen i>this.Num
                call SetUnitPosition(this.Effects[i], this.CX+this.Offset*Cos(this.AngleIncrement*i+this.Rotation), this.CY+this.Offset*Sin(this.AngleIncrement*i+this.Rotation))
            endloop
        endmethod
Then we simply have to replace all those values with the values from the struct (note that I also changed an "elseif" to an "endif" + "if"):
Collapse JASS:
    private function Periodic takes nothing returns nothing
        local integer i = 0
        local unit U
        local TPStruct TP //We'll figure out how to get TP in a second

        if OrderId2String(GetUnitCurrentOrder(TP.Caster)) == CHANNELINGSTRING then
            call TP.Move()

            call GroupEnumUnitsInRange(G, TP.CX, TP.CY, TP.Offset, Condition(function True))
            loop
                set U = FirstOfGroup(G)
                exitwhen U == null

                if DamageFilter(U, TP.P, TP.Level) then
                    call UnitDamageTarget(TP.Caster, U, TP.Damage, true, false, ATTACK, DAMAGE, WEAPON)
                endif
                if HealFilter(U, TP.P, TP.Level) then
                    call SetWidgetLife(U, GetWidgetLife(U)+TP.Healing)
                endif

                call GroupRemoveUnit(G, U)
            endloop
        else
            call TP.destroy()
            call PauseTimer(T)
        endif
    endfunction
Now what we have is this:
Collapse JASS:
scope TidePool
    //============================
    //Start Configuration
    //============================

    globals
        private constant integer SPELLID = 'A005'
        private constant integer AURAID1 = 'A009'
        private constant integer AURAID2 = 'A00A'
        private constant string CHANNELINGSTRING = "starfall"
        private constant integer EFFECTDUMMYID = 'h004'
        private constant real TIMERINTERVAL = 0.04
        private constant attacktype ATTACK = ATTACK_TYPE_CHAOS
        private constant damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON = null
    endglobals

    private constant function AoE takes integer Level returns real
        return 300.00*Level
    endfunction

    private constant function DamagePerSecond takes integer Level returns real
        return 25.00*Level
    endfunction

    private constant function HealthPerSecond takes integer Level returns real
        return 5.00*Level
    endfunction

    private constant function NumberOfEffects takes integer Level returns integer
        return 8
    endfunction

    private constant function AngleOffsetPerInterval takes integer Level returns real
        return 5.00
    endfunction

    private constant function DamageFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
        return IsUnitEnemy(FilterUnit, CasterOwner)
    endfunction

    private constant function HealFilter takes unit FilterUnit, player CasterOwner, integer Level returns boolean
        return IsUnitAlly(FilterUnit, CasterOwner)
    endfunction

    //============================
    //End Configuration
    //============================

    globals
        private timer T
        private group G
    endglobals

    private struct TPStruct
        unit Caster
        player P
        integer Level
        real CX
        real CY
        real Damage
        real Healing
        integer Num
        real Rotation = 0.00
        real RotationAdd
        real AngleIncrement
        real Offset
        unit array Effects[360]

        static method create takes unit Caster, integer Level returns TPStruct
            local integer i = 0
            local TPStruct TP = TPStruct.allocate()

            set TP.Caster = Caster
            set TP.P = GetOwningPlayer(Caster)
            set TP.Level = Level
            set TP.CX = GetUnitX(Caster)
            set TP.CY = GetUnitY(Caster)
            set TP.Damage = DamagePerSecond(Level)*TIMERINTERVAL
            set TP.Healing = HealthPerSecond(Level)*TIMERINTERVAL
            set TP.Num = NumberOfEffects(Level)
            set TP.RotationAdd = AngleOffsetPerInterval(Level)
            set TP.AngleIncrement = 360.00/Num*bj_DEGTORAD
            set TP.Offset = AoE(Level)

            loop
                set i = i+1
                exitwhen i>TP.Num
                set TP.Effects[i] = CreateUnit(TP.P, EFFECTDUMMYID, TP.CX+TP.Offset*Cos(TP.AngleIncrement*i), TP.CY+TP.Offset*Sin(TP.AngleIncrement*i), 0.00)
            endloop

            call UnitAddAbility(Caster, AURAID1)
            call UnitAddAbility(Caster, AURAID2)

            return TP
        endmethod

        method Move takes nothing returns nothing
            local integer i = 0

            set this.Rotation = this.Rotation+this.RotationAdd
            loop
                set i = i+1
                exitwhen i>this.Num
                call SetUnitPosition(this.Effects[i], this.CX+this.Offset*Cos(this.AngleIncrement*i+this.Rotation), this.CY+this.Offset*Sin(this.AngleIncrement*i+this.Rotation))
            endloop
        endmethod

        method onDestroy takes nothing returns nothing
            local integer i = 0

            call UnitRemoveAbility(this.Caster, AURAID1)
            call UnitRemoveAbility(this.Caster, AURAID2)
            loop
                set i = i+1
                exitwhen i>this.Num
                call KillUnit(this.Effects[i])
            endloop
        endmethod
    endstruct

    private function CastConditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELLID
    endfunction

    private function True takes nothing returns boolean
        return true
    endfunction

    private function Cast takes nothing returns nothing
        local unit U = GetTriggerUnit()
        local TPStruct TP = TPStruct.create(U, GetUnitAbilityLevel(U, SPELLID))

        //Do some stuff regarding attaching the struct, but we're not there yet

        call ResumeTimer(T)
        set U = null
    endfunction

    private function Periodic takes nothing returns nothing
        local integer i = 0
        local unit U
        local TPStruct TP //We'll figure out how to get it in a second

        if OrderId2String(GetUnitCurrentOrder(TP.Caster)) == CHANNELINGSTRING then
            call TP.Move()

            call GroupEnumUnitsInRange(G, TP.CX, TP.CY, TP.Offset, Condition(function True))
            loop
                set U = FirstOfGroup(G)
                exitwhen U == null

                if DamageFilter(U, TP.P, TP.Level) then
                    call UnitDamageTarget(TP.Caster, U, TP.Damage, true, false, ATTACK, DAMAGE, WEAPON)
                endif
                if HealFilter(U, TP.P, TP.Level) then
                    call SetWidgetLife(U, GetWidgetLife(U)+TP.Healing)
                endif

                call GroupRemoveUnit(G, U)
            endloop
        else
            call TP.destroy()
            call PauseTimer(T)
        endif
    endfunction

    //===========================================================================
    public function InitTrig takes nothing returns nothing
        set gg_trg_TidePool = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(gg_trg_TidePool, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(gg_trg_TidePool, Condition(function CastConditions))
        call TriggerAddAction(gg_trg_TidePool, function Cast)

        set T = CreateTimer()
        call TimerStart(T, TIMERINTERVAL, true, function Periodic)
        call PauseTimer(T)

        set G = CreateGroup()
    endfunction
endscope
What have we left to do? Well, we need to find some way to access any and all instances of TPStruct there are out there at any given time. There are a lot of ways to do this; for example, we could use multiple timers and use something like CSData, ABC, or Local Handle Variables to attache each specific instance of the spell to its own timer. While that would work, we're trying to be efficient, and efficient means using one timer to handle all instances of the spell.

How might we do that, you say? Well, here's the basic layout of what we're going to add:
Collapse JASS:
globals
   timer T
   integer N = 0
   keyword Datas
   MyStruct array Datas
endglobals

struct MyStruct
    //...
endstruct

function Periodic takes nothing returns nothing //Looping through all the structs
    local integer i = 0
    local MyStruct MS

    loop
        set i = i+1
        exitwhen i>N

        set MS = Datas[i]
        if Finished(MS) then
            call MS.destroy()
            set N = N-1

            if N > 0 then
                set Datas[i] = Datas[N+1]
                set i = i-1
            else
                call PauseTimer(T)
                exitwhen true
            endif
        endif
    endloop
endfunction

function Add takes nothing returns nothing //Adding a new struct to the stack
    local MyStruct MS = MyStruct.create() //Or whatever

    set N = N+1
    set Datas[N] = MS
    if N == 1 then
       call ResumeTimer(T)
    endif
endfunction
As we are trying to implement this into our spell, we must add the "N" variable and the TPStruct array to our global, then modify our "Cast" function to look like this:
Collapse JASS:
    globals
        private timer T
        private group G
        private integer N = 0
        private keyword TPStruct
        private TPStruct array TPs
    endglobals

    private function Cast takes nothing returns nothing
        local unit U = GetTriggerUnit()
        local TPStruct TP = TPStruct.create(U, GetUnitAbilityLevel(U, SPELLID))

        set N = N+1
        set TPs[N] = TP
        if N == 1 then
            call ResumeTimer(T)
        endif

        set U = null
    endfunction
You might be wondering why I've highlighted the "keyword" lines, and, in fact, what the hell they mean. Well, take a look at the JASSHelper documentation to see what I mean:
JASSHelper Documentation

keyword
The keyword statement allows you to declare a replacement directive for an scope without declaring an actual function/variable/etc. It is useful for many reasons, the most important of the reasons being that you cannot use a private/public member in an scope before it is declared, in most cases this limitation is no more than an annoyance requiring you to change the position of declarations, in other situations though this is a limitator of other features.
For example two mutually recursive functions may use .evaluate (keep reading this readme) to call each other, but if you also want the functions to be private it is impossible to do it without using keyword:
Collapse JASS:
scope myScope

   private keyword B //we were able to declare B as private without having to actually
                     //include the statement of the function which would cause conflicts.

   private function A takes integer i returns nothing
       if(i!=0) then
           return B.evaluate(i-1)*2 //we can safely use B since it was already
                                    //declared as a private member of the scope
       endif
       return 0
   endfunction

   private function B takes integer i returns nothing
       if(i!=0) then
           return A(i-1)*3
       endif
       return 0
   endfunction

endscope


While that's a bit technical, the important part is this: we can use TPStruct before it is declared. This saves us from having to use another globals block below the struct, making things look retarded. Moving on, the "Periodic" function will be a bit more involved to modify, but still simple:
Collapse JASS:
    private function Periodic takes nothing returns nothing
        local integer i = 0
        local unit U
        local TPStruct TP

        loop
            set i = i+1
            exitwhen i>N

            set TP = TPs[N]
            if OrderId2String(GetUnitCurrentOrder(TP.Caster)) == CHANNELINGSTRING then
                call TP.Move()

                call GroupEnumUnitsInRange(G, TP.CX, TP.CY, TP.Offset, Condition(function True))
                loop
                    set U = FirstOfGroup(G)
                    exitwhen U == null

                    if DamageFilter(U, TP.P, TP.Level) then
                        call UnitDamageTarget(TP.Caster, U, TP.Damage, true, false, ATTACK, DAMAGE, WEAPON)
                    endif
                    if HealFilter(U, TP.P, TP.Level) then
                        call SetWidgetLife(U, GetWidgetLife(U)+TP.Healing)
                    endif

                    call GroupRemoveUnit(G, U)
                endloop
            else
                call TP.destroy()
                set N = N-1

                if N > 0 then
                    set TPs[i] = TPs[N+1]
                    set i = i-1
                else
                    call PauseTimer(T)
                    exitwhen true
                endif
            endif
        endloop
    endfunction
And with that, we're finished! You have a fully JESP'd, MUI spell that does precisely what you want:
Expand Tide Pool:
07-03-2008, 10:28 AM#12
goldendercon
thanks pyrogasm for all the help
07-03-2008, 10:32 PM#13
Pyrogasm
Sure thing... as long as you learned from it instead of just using the end result!
07-04-2008, 05:00 AM#14
goldendercon
heh. no I learned cuz even when I'm not listening I still absorb most of the stuff, even when skimming
07-06-2008, 01:57 AM#15
goldendercon
WAAAAHHHH!!!! problem with script i was able to save it once and it worked correctly then right after that things started getting out of hand
Attached Files
File type: w3xFall of Darkness1.w3x (890.9 KB)