• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Charged Bolt v1.0

  • Like
Reactions: Vengeancekael
So, it has been a while since I submitted a spell. Therefor I bring you Charged Bolt.
It is fairly simple I guess.

Charges the ions in the air, discharging a burst of electrical energy. These missiles flit about randomly through an area, chasing down ground enemies in their path, shocking them to the core.

Level 1 - 3 bolts, 50 damage per bolt.
Level 2 - 5 bolts, 75 damage per bolt.
Level 3 - 7 bolts, 100 damage per bolt.
Level 4 - 9 bolts, 125 damage per bolt.


2ev9bfm.png


JASS:
    //---------------------------------------------------
    // Spell: Charged Bolt v1.0 (2010-01-24)
    //
    // Implementation instructions:
    //  Copy this trigger/code or paste it in yours
    //  Copy the xebasic library and read the info there
    //  Copy the universal dummy unit and use the
    //   imported dummy model as said in xebasic
    //  Copy the spell ability to your map
    //  Alter all constants to fit your needs
    //  Do not forget the ability rawcode and the dummy
    //
    // Credits:
    //  Vexorian                - JassHelper, xebasic
    //  PitzerMike, MindWorX    - JNGP
    //  Blizzard                - Tooltip inspired
    //
    // The spell has been created by Eccho
    // Give proper credits when used
    //---------------------------------------------------

JASS:
library ChargedBolt initializer Init requires xebasic

    //---------------------------------------------------
    // Native including
    // If you have this in your code somewhere else,
    // make sure to not double define it
    //---------------------------------------------------
    native UnitAlive takes unit id returns boolean

    //---------------------------------------------------
    // Configuration section
    //---------------------------------------------------
    
    globals
        private constant integer    ABILITY_ID          = 'A000'    //Id of the casting ability
        private constant integer    ABILITY_MAX_LVL     = 4         //Max level of the casting ability. Match these with the level value field of the ability in the ability editor
        
        private constant integer    MISSILE_QTY_BASE    = 3         //Amount of missile bolts released on level 1
        private constant integer    MISSILE_QTY_INC     = 2         //Amount increased on levels > 1, eg, in this case, lvl 2 gives 3+2*(level-1) bolts
        private constant integer    MISSILE_QTY_MAX     = 50        //Due to a JassHelper bug, set this constant to be at least your qty_base+qty_inc*(max_lvl-1). Note that this should be a fixed integer value.
        
                                                                    //Self explanatory, hopefully
        private constant string     MISSILE_ART         = "Abilities\\Spells\\Orc\\LightningBolt\\LightningBoltMissile.mdl"
        private constant integer    MISSILE_SPEED       = 500
        private constant integer    MISSILE_RANGE       = 800
        private constant integer    MISSILE_HEIGHT      = 32
        private constant real       MISSILE_SCALE       = 0.5
        
        private constant real       MISSILE_VIB_PER_MIN = 6*bj_PI   //The min value factor describing how fast the missile vibrates. (see a formula as y = A*sin(kx) where k is this vibration factor.)
        private constant real       MISSILE_VIB_PER_MAX = 12*bj_PI  //The max value factor describing how fast the missile vibrates.
        private constant integer    MISSILE_VIB_RADIUS  = 32        //The altitude factor of the missile vibration. (see the formula above, where A is this radius factor.)
        
        private constant real       MISSILE_QTY_SPREAD  = bj_PI/16  //The max radian angle possible between the missiles.
        private constant real       MISSILE_MAX_SPREAD  = bj_PI/4   //The max radian angle spread, in which all missiles are moving in. For instance bj_PI/4 means that all missiles move within the caster_facing+-spread
        
        private constant string     IMPACT_ART          = "Abilities\\Spells\\Items\\AIlb\\AIlbSpecialArt.mdl"
        private constant string     IMPACT_ATTACH_POINT = "chest"
        private constant integer    IMPACT_DAMAGE_BASE  = 50        //Amount of damage at level 1
        private constant integer    IMPACT_DAMAGE_INC   = 25        //Amount of damage added per each increased level
        private constant integer    IMPACT_RADIUS       = 32        //See this as the missile collision size. Note that the missile will only collide and deal damage to one unit.
        
                                                                    //Self explanatory, hopefully
        private constant attacktype ATTACK_TYPE         = ATTACK_TYPE_MAGIC
        private constant damagetype DAMAGE_TYPE         = DAMAGE_TYPE_UNIVERSAL
        private constant weapontype WEAPON_TYPE         = WEAPON_TYPE_WHOKNOWS

    endglobals
    
                                                                    // Target filter, used to determinate what enemies the spell will hit.
    private function TargetFilter takes unit enemy, player caster, real x, real y returns boolean
        return UnitAlive(enemy) and IsUnitEnemy(enemy, caster) and IsUnitType(enemy, UNIT_TYPE_GROUND) and IsUnitInRangeXY(enemy, x, y, IMPACT_RADIUS)
    endfunction
        
        
    //---------------------------------------------------
    // Main spell code below 
    // Don't edit unless you know what you are doing
    //---------------------------------------------------
    
        
    globals
                                                                    //Preserved in case of JassHelper being fixed
        //private constant integer    MISSILE_QTY_MAX     = (MISSILE_QTY_BASE+(MISSILE_QTY_INC*(ABILITY_MAX_LVL-1)))
        private constant real       MISSILE_TIME_MAX    = 1.0*MISSILE_RANGE/MISSILE_SPEED
        private constant group      ENUM_GROUP          = CreateGroup()
        private constant timer      ANIM_TIMER          = CreateTimer()
    endglobals
    

    //Missile struct used upon creating then charged missiles
    private struct missile
        unit mob
        effect gfx
        real xvel
        real yvel
        real vvel
        
        static method create takes player sp, real sx, real sy, real ra returns missile
            local missile m = missile.allocate()
            
            set m.mob = CreateUnit(sp, XE_DUMMY_UNITID, sx, sy, ra)
            set m.gfx = AddSpecialEffectTarget(MISSILE_ART, m.mob, "origin")
            call UnitAddAbility(m.mob, XE_HEIGHT_ENABLER)
            call UnitRemoveAbility(m.mob, XE_HEIGHT_ENABLER)
            call SetUnitAnimationByIndex(m.mob, 90)
            call SetUnitFlyHeight(m.mob, MISSILE_HEIGHT, 0)
            call SetUnitScale(m.mob, MISSILE_SCALE, MISSILE_SCALE, MISSILE_SCALE)
            
            set m.xvel = MISSILE_SPEED*Cos(ra)
            set m.yvel = MISSILE_SPEED*Sin(ra)
            set m.vvel = GetRandomReal(MISSILE_VIB_PER_MIN, MISSILE_VIB_PER_MAX)
            
            return m
        endmethod
    
        
        method onDestroy takes nothing returns nothing
            call DestroyEffect(.gfx)
            call KillUnit(.mob)
            set .mob = null
            set .gfx = null
        endmethod
        
    endstruct
    
    //Main struct used to handle and run spell data
    private struct data
        player sp 
        integer slvl
        real sx
        real sy
        real t1
        
        missile array mis[MISSILE_QTY_MAX]
        integer mqty
        
        static data array dat
        static integer dqty = 0
        
        static method callback takes nothing returns nothing
            local data d
            local missile m
            local integer i = 0
            local integer j
            local real x
            local real y
            local unit u
            
            loop
                exitwhen i >= data.dqty
                
                set d = data.dat[i]
                
                set d.t1 = d.t1+XE_ANIMATION_PERIOD
                
                set j = 0
                loop
                    exitwhen j >= d.mqty
                    
                    set m = d.mis[j]
                    
                    set x = d.sx+m.xvel*d.t1
                    set y = d.sy+m.yvel*d.t1
                    
                    call GroupEnumUnitsInRange(ENUM_GROUP, x, y, XE_MAX_COLLISION_SIZE+IMPACT_RADIUS, null)
                    loop
                        set u = FirstOfGroup(ENUM_GROUP)
                        exitwhen u == null 
                        call GroupRemoveUnit(ENUM_GROUP, u)
                        exitwhen TargetFilter(u, d.sp, x, y)
                    endloop
                    
                    call GroupClear(ENUM_GROUP)
                    
                    //                              RectContainsCoords is a basic bj inline for GetRectMinX < x... blabla
                    if d.t1 <= MISSILE_TIME_MAX and RectContainsCoords(bj_mapInitialPlayableArea, x, y) and u == null then
                        call SetUnitX(m.mob, x+MISSILE_VIB_RADIUS*Sin(m.vvel*d.t1))
                        call SetUnitY(m.mob, y+MISSILE_VIB_RADIUS*Sin(m.vvel*d.t1))
                    
                        set j = j+1
                    else
                        if u != null then
                            call UnitDamageTarget(m.mob, u, IMPACT_DAMAGE_BASE+IMPACT_DAMAGE_INC*(d.slvl-1), true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
                            call DestroyEffect(AddSpecialEffectTarget(IMPACT_ART, u, IMPACT_ATTACH_POINT))
                        endif
                        
                        call m.destroy()
                        set d.mqty = d.mqty-1 
                        set d.mis[j] = d.mis[d.mqty]
                    endif
                    

                endloop
                
                if d.mqty == 0 then
                    call d.destroy()
                    set data.dqty = data.dqty-1
                    set data.dat[i] = data.dat[data.dqty]
                    
                    if data.dqty == 0 then
                        call PauseTimer(ANIM_TIMER)
                    endif
                else
                    set i = i+1
                endif
                
            endloop
            
            set u = null
        endmethod
        
        static method create takes unit su, real nx, real ny returns data
            local data d = data.allocate()
            local integer i = 0
            local real ra
            local real spr
            local real ramin
            
            set d.sx = GetUnitX(su)
            set d.sy = GetUnitY(su)
            
            set ra = Atan2(ny-d.sy, nx-d.sx)
            
            set d.sp = GetOwningPlayer(su)
            set d.slvl = GetUnitAbilityLevel(su, ABILITY_ID)
            
            set d.mqty = MISSILE_QTY_BASE+MISSILE_QTY_INC*(d.slvl-1)
            
            set spr = MISSILE_QTY_SPREAD*(d.mqty-1)
            
            if spr > MISSILE_MAX_SPREAD then
                set spr = MISSILE_MAX_SPREAD
            endif
            
            set ramin = ra-spr/2
            set spr = spr/(d.mqty-1)
            
            loop
                exitwhen i >= d.mqty
            
                set d.mis[i] = missile.create(d.sp, d.sx, d.sy, ramin+spr*i)
                
                set i = i+1
            endloop
            
            set d.t1 = 0
            
            set data.dat[data.dqty] = d
            if data.dqty == 0 then
                call TimerStart(ANIM_TIMER, XE_ANIMATION_PERIOD, true, function data.callback)
            endif
            set data.dqty = data.dqty+1
            
            return d
        endmethod
        
    endstruct
    
    private function Evaluate takes nothing returns boolean
        if GetSpellAbilityId() == ABILITY_ID then
            call data.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
        endif
        return false
    endfunction
    
    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function Evaluate))
        set t = null
    endfunction
    
endlibrary

Don't forget to give me proper credits if used in your map=)
~Eccho~


Keywords:
charged, bolt, ion, lightning, diablo, 2, II, sorceress, thunder, electrical, electric, charge, energy, shock, missile
Contents

Charged Bolt v1.0 (Map)

Reviews
20:31, 25th Jan 2010 The_Reborn_Devil: The coding looks pretty good and the effects are nice. Status: Approved Rating; Useful

Moderator

M

Moderator

20:31, 25th Jan 2010
The_Reborn_Devil:
The coding looks pretty good and the effects are nice.

Status: Approved
Rating; Useful
 
Level 23
Joined
Nov 29, 2006
Messages
2,482
Do your stuff in the GroupEnum filter instead of using a FirstOfGroup loop.
Also, GroupEnum functions clear your group before adding new units, you don't have to do that manually.

Last question: Why not use xecollider?

Yeah, but group enums would require some additional temp globals, like tempplayer, and tempx, tempy. If it is a matter of efficiency, meh :> Considering that it enums a very small area (like 200), not alot of units are enumerated.
I didn't know groupenums cleared the group, I mean really clears it? I thought the unit shadowing still were left behind.

Xecollider, do I need it that much? What would it simplify in the spell? (I ask because I haven't read about it enough to remember yet.)
Edit: Actually, the reason why I can't use xecollider is because the missiles are vibrating, not moving straight forward

why you use library and not scope? plz change this
EDIT: oh i see you require xebasic.... ok this is a reason :p

Yeah, library... scope. Whatever =)
 
Last edited:
Level 11
Joined
Apr 29, 2007
Messages
826
GroupEnums do clear the group before filling it up again.

Xecollider, do I need it that much? What would it simplify in the spell? (I ask because I haven't read about it enough to remember yet.)
Edit: Actually, the reason why I can't use xecollider is because the missiles are vibrating, not moving straight forward
Ah well, it would've easified your code abit, but it's not really neccessary. And I don't know if you can make them vibrate though, I've never used xecollider :p

why you use library and not scope? plz change this
There's a good reason that zinc doesn't use scopes anymore.
 
Level 16
Joined
Jun 9, 2008
Messages
734
Xecollider, do I need it that much? What would it simplify in the spell? (I ask because I haven't read about it enough to remember yet.)
Edit: Actually, the reason why I can't use xecollider is because the missiles are vibrating, not moving straight forward

- so use xefx then, instead create your own :p.
- you don't need to null a trigger that will never be destroyed.
 
Top