• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

Laggy Spell (leaks?)

Status
Not open for further replies.
Level 10
Joined
Jun 6, 2007
Messages
392
Hello. I have created a spell, in which the caster charges energy for up to 10 seconds and then releases a beam damaging units in a line. The spell itself works fine, but the map is very laggy all the time (also when not casting the spell). I believe I have removed all leaks, but I'd greatly appreciate it if someone could check my triggers in case of leaks. Here's the code, Solar Beam Effect is set initally off:
  • Solar Beam
    • Events
      • Unit - A unit Starts the effect of an ability
    • Conditions
      • (Ability being cast) Equal to Solar Beam
    • Actions
      • Set Damage = (800.00 + (Real((Level of Unknown (A03Q) for (Triggering unit)))))
      • Set TempUnit = (Casting unit)
      • Set TempPoint = (Target point of ability being cast)
      • Set Counter = 0
      • Set TempGroup2 = (Units in (Playable map area))
      • Set TempGroup = (Random 0 units from TempGroup2)
      • Set TempGroup3 = (Random 0 units from TempGroup2)
      • Custom script: call DestroyGroup (udg_TempGroup2)
      • Hashtable - Save Damage as 0 of (Key (Casting unit)) in SolarBeamTable
      • Hashtable - Save Handle OfTempUnit as 1 of (Key (Casting unit)) in SolarBeamTable
      • Hashtable - Save Counter as 2 of (Key (Casting unit)) in SolarBeamTable
      • Hashtable - Save False as 3 of (Key (Casting unit)) in SolarBeamTable
      • Hashtable - Save Handle OfTempPoint as 4 of (Key (Casting unit)) in SolarBeamTable
      • Hashtable - Save Handle OfTempGroup as 5 of (Key (Casting unit)) in SolarBeamTable
      • Hashtable - Save Handle OfTempGroup3 as 7 of (Key (Casting unit)) in SolarBeamTable
      • Unit Group - Add TempUnit to SolarBeamGroup
      • Trigger - Turn on Solar Beam Effect <gen>
  • Solar Beam Stop
    • Events
      • Unit - A unit Finishes casting an ability
      • Unit - A unit Stops casting an ability
    • Conditions
      • (Ability being cast) Equal to Solar Beam
    • Actions
      • Hashtable - Save True as 3 of (Key (Casting unit)) in SolarBeamTable
  • Solar Beam Effect
    • Events
      • Time - Every 0.05 seconds of game time
    • Conditions
    • Actions
      • Unit Group - Pick every unit in SolarBeamGroup and do (Actions)
        • Loop - Actions
          • Set Damage = (Load 0 of (Key (Picked unit)) from SolarBeamTable)
          • Set TempUnit = (Load 1 of (Key (Picked unit)) in SolarBeamTable)
          • Set Counter = (Load 2 of (Key (Picked unit)) from SolarBeamTable)
          • Set TempBool = (Load 3 of (Key (Picked unit)) from SolarBeamTable)
          • Set TempPoint = (Load 4 of (Key (Picked unit)) in SolarBeamTable)
          • Set TempPoint2 = (Position of TempUnit)
          • Set TempGroup = (Load 5 of (Key (Picked unit)) in SolarBeamTable)
          • Set TempGroup3 = (Load 7 of (Key (Picked unit)) in SolarBeamTable)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • TempBool Equal to False
              • ((Real(Counter)) mod 0.15) Equal to 0.00
            • Then - Actions
              • Set Damage = (Damage + ((Real((Intelligence of (Picked unit) (Include bonuses)))) x 0.10))
              • Hashtable - Save Damage as 0 of (Key (Picked unit)) in SolarBeamTable
              • Set TempPoint3 = (TempPoint2 offset by 500.00 towards (Random angle) degrees)
              • Unit - Create 1 SolarBeamChargeDummy for (Owner of TempUnit) at TempPoint3 facing Default building facing degrees
              • Unit - Add a 1.00 second Generic expiration timer to (Last created unit)
              • Unit Group - Add (Last created unit) to TempGroup
              • Hashtable - Save Handle OfTempGroup as 5 of (Key (Picked unit)) in SolarBeamTable
              • Custom script: call RemoveLocation (udg_TempPoint3)
            • Else - Actions
          • Unit Group - Pick every unit in TempGroup and do (Actions)
            • Loop - Actions
              • Set TempPoint3 = (Position of (Picked unit))
              • Set TempPoint4 = (TempPoint3 offset by 25.00 towards (Angle from TempPoint3 to TempPoint2) degrees)
              • Unit - Move (Picked unit) instantly to TempPoint4
              • Custom script: call RemoveLocation (udg_TempPoint3)
              • Custom script: call RemoveLocation (udg_TempPoint4)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • TempBool Equal to True
            • Then - Actions
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • Counter Equal to 0
                • Then - Actions
                  • Set TempPoint3 = TempPoint2
                • Else - Actions
                  • Set TempPoint3 = (Load 6 of (Key (Picked unit)) in SolarBeamTable)
              • Set TempPoint4 = (TempPoint3 offset by 25.00 towards (Angle from TempPoint2 to TempPoint) degrees)
              • Hashtable - Save Handle OfTempPoint4 as 6 of (Key (Picked unit)) in SolarBeamTable
              • Unit - Create 1 SolarBeamDummy for (Owner of TempUnit) at TempPoint4 facing Default building facing degrees
              • Unit - Add a 0.50 second Generic expiration timer to (Last created unit)
              • Set TempGroup2 = (Units within 100.00 of TempPoint3 matching (((Owner of (Matching unit)) is an ally of (Owner of TempUnit)) Equal to False))
              • Unit Group - Pick every unit in TempGroup3 and do (Unit Group - Remove (Picked unit) from TempGroup2)
              • Unit Group - Pick every unit in TempGroup2 and do (Actions)
                • Loop - Actions
                  • Unit - Cause TempUnit to damage (Picked unit), dealing Damage damage of attack type Spells and damage type Magic
                  • Unit Group - Add (Picked unit) to TempGroup3
                  • Special Effect - Create a special effect attached to the origin of (Picked unit) using Abilities\Spells\Human\HolyBolt\HolyBoltSpecialArt.mdl
                  • Special Effect - Destroy (Last created special effect)
              • Hashtable - Save Handle OfTempGroup3 as 7 of (Key (Picked unit)) in SolarBeamTable
              • Custom script: call DestroyGroup (udg_TempGroup2)
              • Custom script: call RemoveLocation (udg_TempPoint3)
              • Set Counter = (Counter + 1)
              • Hashtable - Save Counter as 2 of (Key (Picked unit)) in SolarBeamTable
            • Else - Actions
          • Custom script: call RemoveLocation (udg_TempPoint2)
          • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
            • If - Conditions
              • Counter Equal to 50
            • Then - Actions
              • Custom script: call RemoveLocation (udg_TempPoint4)
              • Custom script: call DestroyGroup (udg_TempGroup)
              • Custom script: call DestroyGroup (udg_TempGroup3)
              • Hashtable - Clear all child hashtables of child (Key (Picked unit)) in SolarBeamTable
              • Unit Group - Remove (Picked unit) from SolarBeamGroup
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Number of units in SolarBeamGroup) Equal to 0
                • Then - Actions
                  • Trigger - Turn off (This trigger)
                • Else - Actions
            • Else - Actions
 
Man I know why is laggy one reason I think to I see here. Once one moderator told me when you make triggers which use many and huge If/then/else actions they will make everything very slow(that's happened to my map to). Solar beam effect has to many and huge if/then/else actions plus it's very fast looping trigger so try to make trigger with maybe Event - each 0.20 second and try to use lesser action in action if/then/else
Second reason can be if your spell use some imported huge effected effects. I gona check spell once more and if I see something new I will change my post. But I think to I have told you everything
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
You're using many Groups, and you're using repetitive calling

Trigger1 : Solar beam
Add these in the beggining of the trigger
---
Set TempUnit = Triggering Unit
Custom script: set udg_i = GetHandleId(udg_TempUnit)
---
--> From there on replace all the "Casting Unit" with the variabe "TempUnit"
--> Also replace all the "Key ( Casting Unit)) for the "i" variable.

You're using waaaay too many Unit Groups. Group handling is one of the slowests things in Warcraft. If you're going to use about 4 Unit Groups every 0.05 just for one ability... you have some things to improve. You're also picking every unit on X group Inside the Pick Every unit in Solar Group, so.. You're using 4 Unit Groups FOR EACH UNIT in Solar Group... That's wrong, obviously :p

You're using way too many times "Picked Unit". Declare it into a variable and use the variable instead. You're also using many locations. You could improve that using X/Y Coordenates

In the beginning of the second trigger loop
Set TempUnit = (Picked Unit)
Custom script: set udg_i = GetHandleId(udg_TempUnit)
--- From there one replace all the (Picked Unit) with the variable, and replace all the Key (Picked Unit) with the i variable.


Try to describe us with words what you want to achieve so we can think based on the idea, and not in your trigger. We could propably found a better way around all this.

BTW, this could be greatly improved with jass :D
 
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for suggestions. Now I've improved the spell slightly, although I believe that referring to picked unit is a small issue compared to groups.

The reason why I need so many groups is that I don't want one unit to be hit twice by this spell. So one group is used for all units, who are casting this ability, one group contains information of units that have been hit and one group includes all units that are in the spell aoe. And units that should be damaged are aoe group - hit group.

I've added a test map as an attachment, so it's easier to see what kind of spell it is. And if you have a suggestion to improve the beam effect (it looks quite stupid), I'd like to hear.

EDIT: If you know a solar beam from pokemon games, this spell is supposed to be quite similar. So if there's a simplier way to make a good looking spell like that, I'm open for suggestions.

EDIT2: If I have multiple spells like this on my map, is the event registered separately for each one? In other words, would it be better to have one trigger that is run every 0.05 seconds, and make it run all loop triggers?
 

Attachments

  • Solar Beam.w3x
    25.4 KB · Views: 50
Last edited:
- Running an effect using a lower value loop lags...
- Instead of another trigger (Solar Beam Stop), just check in the loop if the Order of the casting unit is still the same (channeling), else end the spell...
- You should put all your *loadings* in a condition if the caster is alive coz it will still fire even if unit is dead...
- INstead of ((Number of units in SolarBeamGroup) Equal to 0), you could use and integer counter for it during casting, e.g. set count = count + 1 then reduce it when finish casting, when count is 0, then off the trigger, that way it's faster to execute...
- You dont need to have multiple Point variable in all spells, 1 or 2 is enough for the whole map...
 
Last edited:
Level 20
Joined
Jul 14, 2011
Messages
3,213
EDIT: Well... It's not something i'm proud of. It was either this, or bashing my head against the wall and wait for slow and painfull dead. I definitely got overhwelmed by this.

· I managed to create the Channeling Effect and move the Charges towards the caster and have a Charge Count (tough I didn't made use of it).
· I also managed to pick and damage enemy units in a straigth line (120 wide) of about 1120 distance from the caster
· I create the Light Ray dummt, but didn't made the function to create it and remove it 3 seconds later. I'm just too tired for that
· I think there are some leaks around there, though I tried to avoid them all.
· I don't it's neccesary to have 3 Unit Groups (Caster, Charges, Damaged)

I... I'm just tired. I spent the whole day in this and didn't found the way to make it work as it should, with improvements, no leaks, and else... Now I would really want to have someone help me (us) with this issue.
 

Attachments

  • Wicked Solar Beam.w3x
    23.7 KB · Views: 86
Last edited:
Level 10
Joined
Jun 6, 2007
Messages
392
Wow, thanks for looking into this. I started remaking the spell in vJASS, I'll post here when I have something complete.

Edit: Here's the first version of the charging effect. It may be a mess, since this is my first JASS spell. I'd like to hear people's opinions/suggestions for it. It's still a little laggy, but not as bad as before, probably mostly because of the number of effects.

JASS:
struct Caster
    unit u
    real xc
    real yc
    real xt
    real yt
    boolean b
endstruct



struct Dummy
    unit u
    real x
    real y
    integer life
    real x_dist
    real y_dist
endstruct

globals
    timer T = CreateTimer()
    Dummy array Dummy_Array
    Caster array SB_Group
    integer SB_Count = 0
    integer Dummy_Count = 0
endglobals


function move_dummies takes integer i returns integer 
    set Dummy_Array[i].x = Dummy_Array[i].x + Dummy_Array[i].x_dist
    set Dummy_Array[i].y = Dummy_Array[i].y + Dummy_Array[i].y_dist

    
    call SetUnitPosition(Dummy_Array[i].u, Dummy_Array[i].x, Dummy_Array[i].y)
    
    
    set Dummy_Array[i].life = Dummy_Array[i].life - 1
    if  Dummy_Array[i].life < 1 then
        call RemoveUnit (Dummy_Array[i].u)
        call Dummy_Array[i].destroy()
        set Dummy_Array[i] = Dummy_Array[Dummy_Count - 1]
        set Dummy_Count = Dummy_Count - 1
        return 1
    endif

    return 0
endfunction



function create_dummies takes integer i returns nothing
    local real angle = GetRandomReal(0, 360) * bj_DEGTORAD
    local real x_dummy = SB_Group[i].xc + 500*Cos(angle)
    local real y_dummy = SB_Group[i].yc + 500*Sin(angle)
    local real facing = Atan2( SB_Group[i].yc - y_dummy, SB_Group[i].xc - x_dummy)
    local unit dummy = CreateUnit (GetOwningPlayer(SB_Group[i].u), 'h000',  x_dummy, y_dummy, facing)


    
    set Dummy_Array[Dummy_Count] = Dummy.create()
    set Dummy_Array[Dummy_Count].u = dummy
    set Dummy_Array[Dummy_Count].x = x_dummy
    set Dummy_Array[Dummy_Count].y = y_dummy
    set Dummy_Array[Dummy_Count].life = 20
    set Dummy_Array[Dummy_Count].x_dist = 25*Cos(facing)
    set Dummy_Array[Dummy_Count].y_dist = 25*Sin(facing)
    set Dummy_Count = Dummy_Count + 1
endfunction

function spell_effect takes nothing returns nothing
    local integer i = 0
    
    loop
        exitwhen i >= SB_Count  
        
        if IsUnitAliveBJ(SB_Group[i].u) and SB_Group[i].b then
            call create_dummies (i)
            set i = i + 1
        else
            call SB_Group[i].destroy()
            set SB_Group[i] = SB_Group[SB_Count - 1]
            set SB_Count = SB_Count - 1
        endif
        
        
    endloop
    
    if Dummy_Count > 0 then
        
        set i = 0
        
        loop
            exitwhen i >= Dummy_Count
            set i = i - move_dummies(i) 
            set i = i + 1
        endloop
    
    else
        call PauseTimer(T)
    endif
    
    
endfunction



function Trig_SB_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then
        return false
    endif
    return true
endfunction

function Trig_SB_Actions takes nothing returns nothing
    local location p1 = GetSpellTargetLoc()
    local location p2 = GetUnitLoc(GetTriggerUnit())
    set SB_Group[SB_Count] = Caster.create()
    set SB_Group[SB_Count].u = GetTriggerUnit()
    set SB_Group[SB_Count].xt = GetLocationX(p1)
    set SB_Group[SB_Count].yt = GetLocationY(p1)
    call RemoveLocation (p1)
    set SB_Group[SB_Count].xc = GetLocationX(p2)
    set SB_Group[SB_Count].yc = GetLocationY(p2)
    call RemoveLocation (p2)
    
    set SB_Group[SB_Count].b = true
    set SB_Count = SB_Count + 1
    call TimerStart (T, 0.05, true, function spell_effect)  
endfunction


function SB_stop_Conditions takes nothing returns boolean
    if ( not ( GetSpellAbilityId() == 'A000' ) ) then
        return false
    endif
    return true
endfunction

function SB_stop_Actions takes nothing returns nothing
    local integer index = 0
    
    loop 
        exitwhen GetTriggerUnit() == SB_Group[index].u
        set index = index + 1    
    endloop
    
    set SB_Group[index].b = false

    
endfunction





//===========================================================================
function InitTrig_SB takes nothing returns nothing
    local trigger SB_stop = CreateTrigger(  )
    set gg_trg_SB = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_SB, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_SB, Condition( function Trig_SB_Conditions ) )
    call TriggerAddAction( gg_trg_SB, function Trig_SB_Actions )
    
    
    call TriggerRegisterAnyUnitEventBJ( SB_stop, EVENT_PLAYER_UNIT_SPELL_FINISH )
    call TriggerRegisterAnyUnitEventBJ( SB_stop, EVENT_PLAYER_UNIT_SPELL_ENDCAST )
    call TriggerAddCondition( SB_stop, Condition( function SB_stop_Conditions ) )
    call TriggerAddAction( SB_stop, function SB_stop_Actions )
endfunction
 
Last edited:
Level 20
Joined
Jul 14, 2011
Messages
3,213
I'm not sure about how vJASS works, structs, and else...but I know yo can avoid using Locations and work just with X/Y (GetUnitX / GetUnitY / GetWidgetX / GetWidgetY)

Also, as someone said before, you can add the caster to the SolarGroup and do the function IF he's charging the spell (current order is "channel") do the charge stuff, else you release the beam (or null everything) and remove the caster from the group. That way you can get rid of the TriggerRegisterAnyUnitEventBJ and make just 1 trigger to set up stuff, and another for the loops.

BTW, I'm still working on this. I already got most of it.
 
a little bit of Tip, when you use structs, merge it into ONE, specially when using spells,
caster and dummy should be in one place and use static methods and methods inside it
instead of functions...
also, when using timers it's always best to use timer libraries such as TimerUtils/T32/CTL
instead of creating your own timer system, it can save lots of time and space...
 
Level 20
Joined
Jul 14, 2011
Messages
3,213
Ok, this is my best so far. I think it has several flaws, but works "nearly" as desired (as least what I wanted based on yours)

I tried to pause the unit for 2 seconds in order to retain the solar beam, but I couldn't =/. Damage increases based on Int and the amount of Charges that reaches the caster. I couldn't find the way to remove the Charges that remain around the caster, since they have Locus and can't enum them. I couldn't find a way to remove only that caster charges (so multiple units from the same player can cast it safely)

You just need to create the Unit Groups:
· SolarBeamGroup
· SolarBeamChargesGroup
· SolarBeamDamagedGroup

Setup
JASS:
function Trig_Solar_Beam_Conditions takes nothing returns boolean
    return GetSpellAbilityId() == 'A000'
endfunction

function Trig_Solar_Beam_Actions takes nothing returns nothing
    local unit u = GetTriggerUnit() // Triggering Unit
    local integer Id = GetHandleId(u) // Triggering Unit ID
    local integer lvl = GetUnitAbilityLevel(u, 'A000')
    local real Damage = 300 * lvl
    
    call SaveInteger(udg_Hash, Id, 1, 0) // Charges (Counter) // I Make no use of this. You can delete it or give it some use.
    call SaveReal(udg_Hash, Id, 2, Damage) // Damage
    call GroupAddUnit(udg_SolarBeamGroup, u)

    set u = null

    call EnableTrigger( gg_trg_Solar_Beam_Charge_Loops )
endfunction

//===========================================================================
function InitTrig_Solar_Beam takes nothing returns nothing
    set gg_trg_Solar_Beam = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Solar_Beam, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition( gg_trg_Solar_Beam, Condition( function Trig_Solar_Beam_Conditions ) )
    call TriggerAddAction( gg_trg_Solar_Beam, function Trig_Solar_Beam_Actions )
endfunction

Loop
JASS:
function Solar_Beam_Charges_Loop takes nothing returns nothing
    local unit Charge = GetEnumUnit()
    local integer Id = GetHandleId(Charge)
    local real ChargeX = GetUnitX(Charge)
    local real ChargeY = GetUnitY(Charge)
    local unit Caster = LoadUnitHandle(udg_Hash, Id, 2)
    local integer CasterId = GetHandleId(Caster)
    local real CasterX = GetUnitX(Caster)
    local real CasterY = GetUnitY(Caster)
    local real Angle = (bj_RADTODEG * Atan2(CasterY - ChargeY, CasterX - ChargeX) * bj_DEGTORAD) // Angle between points
    local real OffsetX = ChargeX + 25 * Cos(Angle) // Polar Offset X
    local real OffsetY = ChargeY + 25 * Sin(Angle) // Polar Offset Y
    local real dx = OffsetX - CasterX // Distance Between Points
    local real dy = OffsetY - CasterY // Distance Between Points
    local real DBP = SquareRoot(dx * dx + dy * dy) // Distace Between Points
    local real Damage = LoadReal(udg_Hash, CasterId, 2)
        
    call SetUnitPosition(Charge, OffsetX, OffsetY) // Charge Move
   
    if DBP < 20.00 then
        set Damage = Damage + (R2I(GetHeroInt(Caster, true) * 0.1)) // Damage Increase per Charge
        call SaveReal(udg_Hash, CasterId, 2, Damage)
        call FlushChildHashtable(udg_Hash, Id)
        call RemoveUnit(Charge)
    endif

    set Charge = null
    set Caster = null
endfunction



function Solar_Beam_Loop takes nothing returns nothing
    local unit Caster = GetEnumUnit() // The Caster
    local real CasterX = GetUnitX(Caster) // The Caster Position X
    local real CasterY = GetUnitY(Caster) // The Caster Position Y
    local integer Id = GetHandleId(Caster) // The Handle of the Caster
    local player p = GetOwningPlayer(Caster) // The Owner of The Caster
    local real DummyX = CasterX + 500 * Cos(GetRandomReal(0, 360) * bj_DEGTORAD) // X to Create the Charge
    local real DummyY = CasterY + 500 * Sin(GetRandomReal(0, 360) * bj_DEGTORAD) // Y to create the Charge
    local real Facing = GetUnitFacing(Caster) // Facing Angle of the Caster
    local real Angle = Facing * bj_DEGTORAD // X/Y Offset improvement
    local real Damage = LoadReal(udg_Hash, Id, 2)
    local group g = CreateGroup() // Dummy Group
    local unit u
    local integer i = 0
    local integer Id2 = 0    
    local real OffsetX
    local real OffsetY
    
    if GetUnitCurrentOrder(Caster) == OrderId("channel") then    
        // Beam Charge creation
        set bj_lastCreatedUnit = CreateUnit(p, 'h000', DummyX, DummyY, bj_UNIT_FACING)
        call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', 1.5)
        call GroupAddUnit(udg_SolarBeamChargesGroup, bj_lastCreatedUnit) // Charge add to Group
        call UnitAddAbility(bj_lastCreatedUnit, 'Aloc') // Charge Locust
        set Id2 = GetHandleId(bj_lastCreatedUnit)
        call SaveUnitHandle(udg_Hash, Id2, 2, Caster)
    else // Do the Charged Damage
        loop // Through Small circles in a line to add these units to the SolarBeamDamageGroup
            exitwhen i > 15
            set OffsetX = CasterX + (i*75) * Cos(Angle)
            set OffsetY = CasterY + (i*75) * Sin(Angle)
            call GroupEnumUnitsInRange(g, OffsetX, OffsetY, 100, null)
            loop // Add the Enum units to the SolarBeamDamagedGroup
                set u = FirstOfGroup(g)
                exitwhen u == null
                if IsUnitEnemy(u, p) == true and IsUnitType(u, UNIT_TYPE_DEAD) == false then
                    call GroupAddUnit(udg_SolarBeamDamagedGroup, u)
                endif
                call GroupRemoveUnit(g, u)
            endloop 
            set i = i + 1
        endloop
        set bj_lastCreatedUnit = CreateUnit(p, 'h001', CasterX, CasterY, Facing)
        call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', 3)
        call UnitAddAbility(bj_lastCreatedUnit, 'Aloc')
        loop // Damage the Units in the SolarBeamGroup
            set u = FirstOfGroup(udg_SolarBeamDamagedGroup)
            exitwhen u == null
            call DestroyEffect(AddSpecialEffectTarget("Abilities\\Weapons\\ProcMissile\\ProcMissile.mdl", u, "chest"))
            call UnitDamageTarget(Caster, u, Damage, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
            call GroupRemoveUnit(udg_SolarBeamDamagedGroup, u)
        endloop
        call SetUnitAnimation(Caster, "channel")
        call FlushChildHashtable(udg_Hash, Id)
        call GroupRemoveUnit(udg_SolarBeamGroup, Caster) // Remove the Caster from the group.
    endif

    set Caster = null
    set p = null
    call DestroyGroup(g)
    
endfunction

function Solar_Beam_Group takes nothing returns nothing
    if CountUnitsInGroup(udg_SolarBeamGroup) > 0 then
        call ForGroup(udg_SolarBeamGroup, function Solar_Beam_Loop )
    elseif CountUnitsInGroup(udg_SolarBeamChargesGroup) > 0 then
        call DisableTrigger(gg_trg_Solar_Beam_Charge_Loops)
    endif
endfunction

function Solar_Beam_Charges_Group takes nothing returns nothing
    if CountUnitsInGroup(udg_SolarBeamChargesGroup) > 0 then
        call ForGroup(udg_SolarBeamChargesGroup, function Solar_Beam_Charges_Loop )
    endif
endfunction

//===========================================================================
function InitTrig_Solar_Beam_Charge_Loops takes nothing returns nothing
    set gg_trg_Solar_Beam_Charge_Loops = CreateTrigger(  )
    call TriggerRegisterTimerEvent(gg_trg_Solar_Beam_Charge_Loops, 0.05, true)
    call TriggerAddAction( gg_trg_Solar_Beam_Charge_Loops, function Solar_Beam_Group )
    call TriggerAddAction( gg_trg_Solar_Beam_Charge_Loops, function Solar_Beam_Charges_Group )
    call DisableTrigger(gg_trg_Solar_Beam_Charge_Loops)
endfunction
 

Attachments

  • Wicked Solar Beam.w3x
    25.9 KB · Views: 37
Level 40
Joined
Dec 14, 2005
Messages
10,532
Great to see someone getting into vJass.

I cleaned up your code a lot, although I left the methodology the same. It should work but it's untested.

JASS:
library SolarBeam initializer init
private struct Caster
    unit u
    real xc
    real yc
    real xt
    real yt
    boolean b
endstruct

private struct Dummy
    unit u
    real x
    real y
    integer life
    real x_dist
    real y_dist
endstruct

globals
    private timer T = CreateTimer()
    private Dummy array DummyList
    private Caster array DataList
    private integer Count = 0
    private integer DummyCount = 0
endglobals

private function Loop takes nothing returns nothing
    local integer i = 0
	local real ang
	local Caster dat
	local Dummy dum
	loop
	    exitwhen i >= Count
		set dat = DataList[i]
		if GetWidgetLife(dat.u) > 0 and dat.b then
		    set ang = GetRandomReal(0, 2*bj_PI)
			set dum = Dummy.create()
			set DummyList[DummyCount] = dum
			set dum.x = dat.xc + 500 * Cos(ang)
			set dum.y = dat.yc + 500 * Sin(ang)
			set dum.u = CreateUnit(GetOwningPlayer(dat.u), 'h000', dum.x, dum.y, ang * bj_RADTODEG + 180)
			set dum.life = 20
			set dum.x_dist = 25 * Cos(ang + bj_PI)
			set dum.y_dist = 25 * Sin(ang + bj_PI)
			set DummyCount = DummyCount + 1
			set i = i + 1
		else
		    call dat.destroy()
			set Count = Count - 1
			set DataList[i] = DataList[Count]
		endif
	endloop
	if DummyCount > 0 then
	    set i = 0
		loop
		    exitwhen i >= DummyCount
			set dum = DummyList[i]
			set dum.x = dum.x + dum.x_dist
			set dum.y = dum.y + dum.y_dist
			call SetUnitX(dum.u, dum.x)
			call SetUnitY(dum.u, dum.y)
			set dum.life = dum.life - 1
			if dum.life < 1 then
			    call RemoveUnit(dum.u)
				set dum.u = null
				call dum.destroy()
				set DummyCount = DummyCount - 1
				set DummyList[i] = DummyList[DummyCount]
				set i = i - 1
			endif
			set i = i + 1
		endloop
	else
	    call PauseTimer(T)
	endif
endfunction

private function Actions takes nothing returns nothing
    local location loc = GetSpellTargetLoc()
	local Caster dat = Caster.create()
	set DataList[Count] = dat
	set dat.u = GetTriggerUnit()
	set dat.xt = GetLocationX(loc)
	set dat.yt = GetLocationY(loc)
	set dat.xc = GetUnitX(dat.u)
	set dat.yc = GetUnitY(dat.u)
	set dat.b = true
	set Count = Count + 1
	if Count == 1 then
	    call TimerStart(T, 0.05, true, function Loop)
	endif

	call RemoveLocation(loc)
	set loc = null
endfunction

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

private function StopSpell takes nothing returns boolean
    local integer i = 0
	if GetSpellAbilityId() == 'A000' then
	    loop
		    exitwhen GetTriggerUnit() == DataList[i].u
			set i = i + 1
		endloop
		set DataList[i].b = false
	endif
	return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
	local trigger t2 = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition(t, Condition(function Conditions))
	call TriggerAddAction(t, function Actions)
    
    call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
	call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
	call TriggerAddCondition(t2, Condition(function StopSpell))
endfunction
endlibrary

Some notes:

-It's a lot cleaner to have the exit condition for the spell be an order check every time you loop than a separate trigger which has to manually find the unit's struct in the list. I didn't include this since I didn't know the orderid you are using.

JASS:
library SolarBeam initializer init
private struct Caster
    unit u
    real xc
    real yc
    real xt
    real yt
endstruct

private struct Dummy
    unit u
    real x
    real y
    integer life
    real x_dist
    real y_dist
endstruct

globals
    private timer T = CreateTimer()
    private Dummy array DummyList
    private Caster array DataList
    private integer Count = 0
    private integer DummyCount = 0
endglobals

private function Loop takes nothing returns nothing
    local integer i = 0
	local real ang
	local Caster dat
	local Dummy dum
	loop
	    exitwhen i >= Count
		set dat = DataList[i]
		if GetWidgetLife(dat.u) > 0 and GetUnitCurrentOrder(dat.u) == ORDERID then
		    set ang = GetRandomReal(0, 2*bj_PI)
			set dum = Dummy.create()
			set DummyList[DummyCount] = dum
			set dum.x = dat.xc + 500 * Cos(ang)
			set dum.y = dat.yc + 500 * Sin(ang)
			set dum.u = CreateUnit(GetOwningPlayer(dat.u), 'h000', dum.x, dum.y, ang * bj_RADTODEG + 180)
			set dum.life = 20
			set dum.x_dist = 25 * Cos(ang + bj_PI)
			set dum.y_dist = 25 * Sin(ang + bj_PI)
			set DummyCount = DummyCount + 1
			set i = i + 1
		else
		    call dat.destroy()
			set Count = Count - 1
			set DataList[i] = DataList[Count]
		endif
	endloop
	if DummyCount > 0 then
	    set i = 0
		loop
		    exitwhen i >= DummyCount
			set dum = DummyList[i]
			set dum.x = dum.x + dum.x_dist
			set dum.y = dum.y + dum.y_dist
			call SetUnitX(dum.u, dum.x)
			call SetUnitY(dum.u, dum.y)
			set dum.life = dum.life - 1
			if dum.life < 1 then
			    call RemoveUnit(dum.u)
				set dum.u = null
				call dum.destroy()
				set DummyCount = DummyCount - 1
				set DummyList[i] = DummyList[DummyCount]
				set i = i - 1
			endif
			set i = i + 1
		endloop
	else
	    call PauseTimer(T)
	endif
endfunction

private function Actions takes nothing returns nothing
    local location loc = GetSpellTargetLoc()
	local Caster dat = Caster.create()
	set DataList[Count] = dat
	set dat.u = GetTriggerUnit()
	set dat.xt = GetLocationX(loc)
	set dat.yt = GetLocationY(loc)
	set dat.xc = GetUnitX(dat.u)
	set dat.yc = GetUnitY(dat.u)
	set Count = Count + 1
	if Count == 1 then
	    call TimerStart(T, 0.05, true, function Loop)
	endif

	call RemoveLocation(loc)
	set loc = null
endfunction

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

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
	call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
	call TriggerAddCondition(t, Condition(function Conditions))
	call TriggerAddAction(t, function Actions)
endfunction
endlibrary

-SetUnitPosition does a lot of things you probably don't want to do for dummy units, some of which don't matter (such as resetting the unit's orders) and some of which do (such as checking for pathing). SetUnitX/Y are much faster and much better for free movement. I fixed this in my code.

-Using proper scoping can make your code a lot cleaner and more reliable rather than having to rely on long prefixes which you hope won't conflict with code elsewhere, and if there is a conflict it's a lot easier to rename a scope/library than to rename every function and global variable.

-I saved some arithmetic by moving things around a bit.

It was pretty decent overall though.
 
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for improving it. If I only check orderid, what will happen if a unit recasts the same spell when it's still channelling the previous one?

I've done some basic programming only in c++, so I expected structs to be only for storage. But in vJASS, they are more like classes, right? I think I'll try using methods in my next spell, I have quite a few GUI spells to improve:grin:. Afterall it was a lot easier to remake the spell in vJASS than I had thought. But well, first I'm going to finish this spell and make it as good as I can, because I don't want to copy my mistakes to other spells.

I replaced the original beam effect with a lightning, so it's no longer necessary to use so many groups. I will post here when I have improved the whole spell similarly to your example.

EDIT: I tested it, and I noticed the same thing as I did when I tried to use SetUnitX/Y: Units are not moved, they just spawn in a circle and disappear. I changed it to SetUnitPosition, and it works. I wonder why? Other than that, it works perfectly.

EDIT2: I want the lightning to remain visible for 1 second, so I thought to add a timer and lightning fields to Caster struct. I'm going to start the timer when the lightning is created, but the problem is, how I can get the correct lightning in DestroyLight function.

EDIT3: I found a solution, it probably isn't the best one, but it works. Here's the first version of the complete spell:
JASS:
library SolarBeam initializer init
private struct Caster
    unit u
    real xc
    real yc
    real xt
    real yt
    boolean b
    lightning light
    timer tim = null
    real damage
endstruct

private struct Dummy
    unit u
    real x
    real y
    integer life
    real x_dist
    real y_dist
endstruct

globals
    private timer T = CreateTimer()
    private Dummy array DummyList
    private Caster array DataList
    private integer Count = 0
    private integer DummyCount = 0
endglobals

//function originally by Maker
function IsPointInRect takes real px , real py , real cx , real cy , real ax , real ay , real bx , real by returns boolean        
    local real dot1 = (px-cx)*(ax-cx) + (py-cy)*(ay-cy)
    local real dot2 = (ax-cx)*(ax-cx) + (ay-cy)*(ay-cy)
    local real dot3 = (px-cx)*(bx-cx) + (py-cy)*(by-cy)
    local real dot4 = (bx-cx)*(bx-cx) + (by-cy)*(by-cy)
        
    return dot1 >= 0 and dot1 <= dot2 and dot3 >= 0 and dot3 <= dot4
endfunction

function DestroyLight takes nothing returns nothing
    local integer i = 0
    loop
        exitwhen DataList[i].tim != null and TimerGetRemaining(DataList[i].tim) == 0
        set i = i + 1
    endloop
    call DestroyLightning (DataList[i].light)
    call DestroyTimer(DataList[i].tim)
    set DataList[i].tim = null
endfunction

function beam takes integer i returns nothing
    local Caster dat = DataList[i]
    local real dist = 800
    local real angle = Atan2( dat.yt - dat.yc, dat.xt - dat.xc)
    local real x_target = dat.xc + dist*Cos(angle)
    local real y_target = dat.yc + dist*Sin(angle)
    local lightning l = AddLightning("FORK", true, dat.xc, dat.yc, x_target, y_target)
    local location casterloc = Location(dat.xc, dat.yc)
    local group targetgroup = GetUnitsInRangeOfLocMatching(dist, casterloc, null)
    local unit u
    local real cos = 150*Cos(angle + bj_PI/2)
    local real sin = 150*Sin(angle + bj_PI/2)
    local real x1 = dat.xc + cos
    local real y1 = dat.yc + sin
    local real x2 = dat.xc - cos
    local real y2 = dat.yc - sin
    local real x3 = x_target - cos
    local real y3 = y_target - sin
    local real px 
    local real py
    local location p
    set dat.tim = CreateTimer()
    set dat.light = l
    call TimerStart(dat.tim, 1, false, function DestroyLight)
    call SetLightningColor(l, 0, 1, 0.4, 1)
    call RemoveLocation(casterloc)
    loop
        exitwhen CountUnitsInGroup(targetgroup) == 0
        set u = FirstOfGroup(targetgroup)
        set p = GetUnitLoc(u)
        set px = GetLocationX(p)
        set py = GetLocationY(p)
        call RemoveLocation(p)
        
        if IsPointInRect(px,py,x1,y1,x2,y2,x3,y3) and IsPlayerEnemy(GetOwningPlayer(u), GetOwningPlayer(dat.u)) then
            call UnitDamageTargetBJ( dat.u, u, dat.damage, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC )
        endif
        
        call GroupRemoveUnit(targetgroup, u)
        endloop
endfunction

private function Loop takes nothing returns nothing
    local integer i = 0
    local real ang
    local Caster dat
    local Dummy dum
    loop
        exitwhen i >= Count
        set dat = DataList[i]
        if GetWidgetLife(dat.u) > 0 and dat.b then
            set ang = GetRandomReal(0, 2*bj_PI)
            set dum = Dummy.create()
            set DummyList[DummyCount] = dum
            set dum.x = dat.xc + 500 * Cos(ang)
            set dum.y = dat.yc + 500 * Sin(ang)
            set dum.u = CreateUnit(GetOwningPlayer(dat.u), 'h000', dum.x, dum.y, ang * bj_RADTODEG + 180)
            set dum.life = 20
            set dum.x_dist = 25 * Cos(ang + bj_PI)
            set dum.y_dist = 25 * Sin(ang + bj_PI)
            set dat.damage = dat.damage + 0.1*GetHeroInt(dat.u,true)
            set DummyCount = DummyCount + 1
            set i = i + 1
        else
            call beam(i)
            call dat.destroy()
            set Count = Count - 1
            set DataList[i] = DataList[Count]
        endif
    endloop
    if DummyCount > 0 then
        set i = 0
        loop
            exitwhen i >= DummyCount
            set dum = DummyList[i]
            set dum.x = dum.x + dum.x_dist
            set dum.y = dum.y + dum.y_dist
            call SetUnitPosition(dum.u, dum.x, dum.y)
            set dum.life = dum.life - 1
            if dum.life < 1 then
                call RemoveUnit(dum.u)
                set dum.u = null
                call dum.destroy()
                set DummyCount = DummyCount - 1
                set DummyList[i] = DummyList[DummyCount]
                set i = i - 1
            endif
            set i = i + 1
        endloop
    else
        call PauseTimer(T)
    endif
endfunction

private function Actions takes nothing returns nothing
    local location loc = GetSpellTargetLoc()
    local Caster dat = Caster.create()
    set DataList[Count] = dat
    set dat.u = GetTriggerUnit()
    set dat.xt = GetLocationX(loc)
    set dat.yt = GetLocationY(loc)
    set dat.xc = GetUnitX(dat.u)
    set dat.yc = GetUnitY(dat.u)
    set dat.b = true
    set dat.damage = 500 * GetUnitAbilityLevel(GetTriggerUnit(), 'A000')
    set Count = Count + 1
    if Count == 1 then
        call TimerStart(T, 0.05, true, function Loop)
    endif

    call RemoveLocation(loc)
    set loc = null
endfunction

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

private function StopSpell takes nothing returns boolean
    local integer i = 0
    if GetSpellAbilityId() == 'A000' then
        loop
            exitwhen GetTriggerUnit() == DataList[i].u
            set i = i + 1
        endloop
        set DataList[i].b = false
    endif
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger t2 = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function Conditions))
    call TriggerAddAction(t, function Actions)
    
    call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
    call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddCondition(t2, Condition(function StopSpell))
endfunction
endlibrary
 
Last edited:
Level 40
Joined
Dec 14, 2005
Messages
10,532
Thanks for improving it. If I only check orderid, what will happen if a unit recasts the same spell when it's still channelling the previous one?
That's definitely an issue if your cast time is longer than your cooldown time, so if that's the case stick with the two-trigger method.

I've done some basic programming only in c++, so I expected structs to be only for storage. But in vJASS, they are more like classes, right? I think I'll try using methods in my next spell, I have quite a few GUI spells to improve:grin:. Afterall it was a lot easier to remake the spell in vJASS than I had thought. But well, first I'm going to finish this spell and make it as good as I can, because I don't want to copy my mistakes to other spells.
Structs have a strange place in vJass. In many different languages they serve many different purposes, anything from fullblown classes to convenient storage devices. In vJass they can technically behave like fullblown classes, although they're usually used in the sense of C structs because passing information between functions with a delay can be a huge pain in the ass so passing the least information possible (a single struct index) is a lot easier than passing each bit of information individually.

EDIT: I tested it, and I noticed the same thing as I did when I tried to use SetUnitX/Y: Units are not moved, they just spawn in a circle and disappear. I changed it to SetUnitPosition, and it works. I wonder why? Other than that, it works perfectly.
Perhaps your units have a movespeed of 0? Change it to 1.

EDIT2: I want the lightning to remain visible for 1 second, so I thought to add a timer and lightning fields to Caster struct. I'm going to start the timer when the lightning is created, but the problem is, how I can get the correct lightning in DestroyLight function.

EDIT3: I found a solution, it probably isn't the best one, but it works. Here's the first version of the complete spell:
Yeah, that's a pretty bad solution. Use TimerUtils to get a timer, start it for 1 second non-repeating with DestroyLightning as a callback, and clip the appropriate struct to it via SetTimerData.

Something like this:

JASS:
private function DestroyLight takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Caster dat = GetTimerData(t)
    //do stuff
    call ReleaseTimer(t)
endfunction

private function theFunc takes nothing returns nothing
    local timer t
    local Caster dat
    //...stuff
    if WeWantToStartTheTimer then
        set t = NewTimer()
        call SetTimerData(t,dat)
        call TimerStart(t,1.,false,function DestroyLight)
    endif
    //...stuff
endfunction

However, note that your solution is decent (with some tweaks) if you want to fade the lightning, since you'll need to be doing an operation on every lightning effect every interval regardless.
 
Level 10
Joined
Jun 6, 2007
Messages
392
Ahh that's a very useful library. I changed the library to scope to make TimerUtils compile first. Now the spell looks like this:
JASS:
scope SolarBeam initializer init
private struct Caster
    unit u
    real xc
    real yc
    real xt
    real yt
    boolean b
    lightning light
    real damage
endstruct

private struct Dummy
    unit u
    real x
    real y
    integer life
    real x_dist
    real y_dist
endstruct

globals
    private timer T = CreateTimer()
    private Dummy array DummyList
    private Caster array DataList
    private integer Count = 0
    private integer DummyCount = 0
endglobals

//function originally by Maker
function IsPointInRect takes real px , real py , real cx , real cy , real ax , real ay , real bx , real by returns boolean        
    local real dot1 = (px-cx)*(ax-cx) + (py-cy)*(ay-cy)
    local real dot2 = (ax-cx)*(ax-cx) + (ay-cy)*(ay-cy)
    local real dot3 = (px-cx)*(bx-cx) + (py-cy)*(by-cy)
    local real dot4 = (bx-cx)*(bx-cx) + (by-cy)*(by-cy)
        
    return dot1 >= 0 and dot1 <= dot2 and dot3 >= 0 and dot3 <= dot4
endfunction

function DestroyLight takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local Caster dat = GetTimerData(t)
    call DestroyLightning (dat.light)
    call ReleaseTimer(t)
endfunction

function beam takes integer i returns nothing
    local Caster dat = DataList[i]
    local real dist = 800
    local real angle = Atan2( dat.yt - dat.yc, dat.xt - dat.xc)
    local real x_target = dat.xc + dist*Cos(angle)
    local real y_target = dat.yc + dist*Sin(angle)
    local lightning l = AddLightning("FORK", true, dat.xc, dat.yc, x_target, y_target)
    local location casterloc = Location(dat.xc, dat.yc)
    local group targetgroup = GetUnitsInRangeOfLocMatching(dist, casterloc, null)
    local unit u
    local real cos = 150*Cos(angle + bj_PI/2)
    local real sin = 150*Sin(angle + bj_PI/2)
    local real x1 = dat.xc + cos
    local real y1 = dat.yc + sin
    local real x2 = dat.xc - cos
    local real y2 = dat.yc - sin
    local real x3 = x_target - cos
    local real y3 = y_target - sin
    local real px 
    local real py
    local location p
    local timer t = NewTimer()
    call SetTimerData(t, dat)

    set dat.light = l
    call TimerStart(t, 1, false, function DestroyLight)
    call SetLightningColor(l, 0, 1, 0.4, 1)
    call RemoveLocation(casterloc)
    loop
        exitwhen CountUnitsInGroup(targetgroup) == 0
        set u = FirstOfGroup(targetgroup)
        set p = GetUnitLoc(u)
        set px = GetLocationX(p)
        set py = GetLocationY(p)
        call RemoveLocation(p)
        
        if IsPointInRect(px,py,x1,y1,x2,y2,x3,y3) and IsPlayerEnemy(GetOwningPlayer(u), GetOwningPlayer(dat.u)) then
            call UnitDamageTargetBJ( dat.u, u, dat.damage, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC )
        endif
        
        call GroupRemoveUnit(targetgroup, u)
        endloop
endfunction

private function Loop takes nothing returns nothing
    local integer i = 0
    local real ang
    local Caster dat
    local Dummy dum
    loop
        exitwhen i >= Count
        set dat = DataList[i]
        if GetWidgetLife(dat.u) > 0 and dat.b then
            set ang = GetRandomReal(0, 2*bj_PI)
            set dum = Dummy.create()
            set DummyList[DummyCount] = dum
            set dum.x = dat.xc + 500 * Cos(ang)
            set dum.y = dat.yc + 500 * Sin(ang)
            set dum.u = CreateUnit(GetOwningPlayer(dat.u), 'h000', dum.x, dum.y, ang * bj_RADTODEG + 180)
            set dum.life = 20
            set dum.x_dist = 25 * Cos(ang + bj_PI)
            set dum.y_dist = 25 * Sin(ang + bj_PI)
            set dat.damage = dat.damage + 0.1*GetHeroInt(dat.u,true)
            set DummyCount = DummyCount + 1
            set i = i + 1
        else
            call beam(i)
            call dat.destroy()
            set Count = Count - 1
            set DataList[i] = DataList[Count]
        endif
    endloop
    if DummyCount > 0 then
        set i = 0
        loop
            exitwhen i >= DummyCount
            set dum = DummyList[i]
            set dum.x = dum.x + dum.x_dist
            set dum.y = dum.y + dum.y_dist
            call SetUnitX(dum.u, dum.x)
            call SetUnitY(dum.u, dum.y)
            set dum.life = dum.life - 1
            if dum.life < 1 then
                call RemoveUnit(dum.u)
                set dum.u = null
                call dum.destroy()
                set DummyCount = DummyCount - 1
                set DummyList[i] = DummyList[DummyCount]
                set i = i - 1
            endif
            set i = i + 1
        endloop
    else
        call PauseTimer(T)
    endif
endfunction

private function Actions takes nothing returns nothing
    local location loc = GetSpellTargetLoc()
    local Caster dat = Caster.create()
    set DataList[Count] = dat
    set dat.u = GetTriggerUnit()
    set dat.xt = GetLocationX(loc)
    set dat.yt = GetLocationY(loc)
    set dat.xc = GetUnitX(dat.u)
    set dat.yc = GetUnitY(dat.u)
    set dat.b = true
    set dat.damage = 500 * GetUnitAbilityLevel(GetTriggerUnit(), 'A000')
    set Count = Count + 1
    if Count == 1 then
        call TimerStart(T, 0.05, true, function Loop)
    endif

    call RemoveLocation(loc)
    set loc = null
endfunction

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

private function StopSpell takes nothing returns boolean
    local integer i = 0
    if GetSpellAbilityId() == 'A000' then
        loop
            exitwhen GetTriggerUnit() == DataList[i].u
            set i = i + 1
        endloop
        set DataList[i].b = false
    endif
    return false
endfunction

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local trigger t2 = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddCondition(t, Condition(function Conditions))
    call TriggerAddAction(t, function Actions)
    
    call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_FINISH)
    call TriggerRegisterAnyUnitEventBJ(t2, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
    call TriggerAddCondition(t2, Condition(function StopSpell))
endfunction
endscope
It would be great if this spell could also take collision sizes into consideration, it would make it more "realistic", but I guess that's not possible. Other than that, the spell seems complete to me. Thanks to all people who have helped me in this thread!
 
Level 40
Joined
Dec 14, 2005
Messages
10,532
Oh, scopes have initializers now? It's been far too long since I wrote stuff in vJass...

Not that you shouldn't be using a scope here (you should), but if you need a library to compile before another library you can do library A needs B (needs can be replaced with "uses" or "requires" if you prefer either), so in this case you'd have library SolarBeam initializer init needs TimerUtils.

By the way, you should make some of your more generically-named functions (beam, DestroyLight) private. It would also be a good idea to put IsPointInRect into its own library in a different trigger (maybe a "geometry" library") to keep yourself sane as your map gets more complicated and you start using more general utility functions.
 
Status
Not open for further replies.
Top