[Log in / Register]
| News | Chat | Pastebin | Donations | Tutorials | Rules | Forums |
| Maps | Skins | Icons | Models | Spells | Tools | Jass | Packs | Hosted Projects | Starcraft II Modding | Starcraft II Resources | Galaxy Wiki |
(Keeps Hive Alive)
Go Back   The Hive Workshop > Spells


Reply
 
Thread Tools
The Hive Workshop Spells:
Boomerang! 1.4.2
by Deaod
Images
Highslide JS
Details
Uploaded:06:56, 4th Jun 2009
Last Updated:05:24, 7th Jan 2015
Keywords:None
Type:Target Ground, Target Object
Category:vJASS

Throw a deadly boomerang at your enemies.

This uses vJass (JassHelper 0.9.H.0 and up), GroupUtils, xefx, DestructableLib and IsTerrainWalkable.

In-Game-Commands:
- "-reset": spawns some footmen
- "-level <level>": Set the heros level to the level you specified. Note that you cannot decrease it that way.
- "-handleid": creates a location, displays its handlid-0x100001, and then destroys it.
- "-credits": displays credits
- "-commands": displays this list
- "-clear": removes all messages ingame


Changelog:
06/03/2009 - Version 1.0.0
- initial release

06/04/2009 - Version 1.1.0
- stopped the spell from triggering when using unrelated abilities
- added a minimum range
- added support for trees
- added damage reduction when hitting multiple targets
- added a hit sound
- switched from direct dummy unit usage to xe

06/05/2009 - Version 1.2.0
- fixed two bugs reported by -JonNny
- enabled damaging units more than once

06/08/2009 - Version 1.2.1
- some optimizations and the final touches (i hope this really is the final version)

06/15/2009 - Version 1.3.0
- added an option to launch a boomerang to the left side, either additionally or exclusively
- made the boomerang collide with unwalkable terrain (after checking for trees)

07/12/2009 - Version 1.3.1
- more and better comments
- made configuring valid targets easier
- boomerangs colliding with unwalkable terrain is now optional
- made an additional version where the calibration section uses formulae (for those who cant/dont want to configure arrays)

08/07/2009 - Version 1.3.3
- Compatibility with 1.24
- 1.3.2 was a private build for a request

06/10/2011 - Version 1.3.4
- fixed a few double frees

07/11/2011 - Version 1.4.0
- you can now change the width of the path of the boomerang ( BOOMERANG_FOCUS )
- you can now configure the spell to simply ignore trees.
- a few optimizations in the background (mainly using static ifs) and a bit of cleanup

01/28/2012 - Version 1.4.1
- boomerangs now follow unit targets
- each boomerang now has its own group of units it has already hit
- cleaned up the code with more meaningful names

01/07/2015 - Version 1.4.2
- rewrote the entire spell, no functionality was changed
- fixed a bug involving bad logic which excluded all units from being dealt damage


Code (Array version) + Credits:
Jass:
library_once Boomerang initializer Init requires GroupUtils, xefx, DestructableLib, IsTerrainWalkable

private keyword Boomerang // DO NOT TOUCH; configuration is below

// Credits:
//  - Ciebron for the inspiration
//  - -JonNny for reporting some bugs
//  - Atideva for reporting a bug
//  - Rising_Dusk for his GroupUtils library
//  - Anitarf for his IsTerrainWalkable library
//  - Vexorian for JassHelper and xe
//  - PipeDream for Grimoire
//  - PitzerMike for JassNewGenPack and DestructableLib
//  - MindWorX for JassNewGenPack
//  - SFilip for TESH

globals
    private constant    real                    TICK                    = 1./40 // granulation of boomerang movement
    private constant    integer                 AID                     = 'A000' // the ability triggering this spell
    private             real            array   DAMAGE                  // damage dealt by the boomerang to units it hits
    private             real            array   DAMAGE_ABSORPTION       // the damage dealt is reduced by this much everytime the boomerang hits a unit or a tree
    private constant    boolean                 DAMAGE_ABSORPTION_RELATIVE = true // is DAMAGE_ABSORPTION to be treated as an absolute value or a value relative to the current damage
    private constant    real                    DAMAGE_BOUNDARY         = 10. // once the damage is lower or equal to this, the boomerang stops flying
    private constant    string                  BOOMERANG_MODEL         = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl" // this is the model representing the boomerang
    private constant    real                    BOOMERANG_COLLSIZE      = 96. // the AoE in which units are damaged by the boomerang
    private constant    real                    BOOMERANG_SIZE          = 1.25 // the scaling of the boomerang
    private constant    real                    BOOMERANG_HEIGHT        = 64. // the Z height of the boomerang
    private constant    real            array   BOOMERANG_SPEED         // the speed at which the boomerang moves // due to limitations this is only an approximation
    private constant    real                    BOOMERANG_FOCUS         = 2. // the higher this number the more focused is the path of the boomerang. Any value greater than 0 should work. Values below 2 might not look so good.
    private constant    boolean                 ALLOW_MULTIPLE_HITS     = true // if this is true, the boomerang can hit units more than once on his path
    private constant    boolean                 USE_RIGHT_BOOMERANG     = true // just avoid setting both to false, okay?
    private constant    boolean                 USE_LEFT_BOOMERANG      = true
    private constant    boolean                 COLLIDE_WITH_GROUND     = true // do boomerangs collide with unwalkable terrain?
    private             real            array   MIN_RANGE               // minimum throwing distance for the boomerang
    private constant    boolean                 IGNORE_TREES            = false // if true, the boomerang will just fly through the trees without doing anything
    private constant    boolean                 KILL_TREES              = true // if true, trees are killed once the boomerang hits one, if this is false, the boomerang stops flying
    private constant    string                  HIT_FX                  = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" // when the boomerang hits a unit, this effect is spawned on the unit hit
    private constant    string                  HIT_FX_ATTPT            = "chest" // the beforementioned effect will be attached to this point
    private constant    attacktype              ATTACK_TYPE             = ATTACK_TYPE_MAGIC // the attack type of the damage the boomerang deals
    private constant    damagetype              DAMAGE_TYPE             = DAMAGE_TYPE_MAGIC // the damage type of the damage the boomerang deals
    private constant    weapontype              WEAPON_TYPE             = WEAPON_TYPE_METAL_MEDIUM_SLICE // sound when boomerang hits a unit
endglobals

private function Damage takes integer level returns real // PROXY
    return DAMAGE[level]
endfunction

private function Damage_Absorption takes integer level returns real // PROXY
    return DAMAGE_ABSORPTION[level]
endfunction

private function Boomerang_Speed takes integer level returns real // PROXY
    return BOOMERANG_SPEED[level]
endfunction

private function Min_Range takes integer level returns real // PROXY
    return MIN_RANGE[level]
endfunction

private function SetUpSpellData takes nothing returns nothing
    set DAMAGE[1] = 200. // initially deals 200 damage
    set DAMAGE[2] = 275.
    set DAMAGE[3] = 350.
   
    set DAMAGE_ABSORPTION[1] = 0.16 // lowers damage dealt by 16% of current damage.
    set DAMAGE_ABSORPTION[2] = 0.12
    set DAMAGE_ABSORPTION[3] = 0.08
   
    set BOOMERANG_SPEED[1] = 600.
    set BOOMERANG_SPEED[2] = 600.
    set BOOMERANG_SPEED[3] = 600.
   
    set MIN_RANGE[1] = 200.
    set MIN_RANGE[2] = 200.
    set MIN_RANGE[3] = 200.
endfunction

private function ValidTarget takes unit target, Boomerang this returns boolean
    return IsUnitType(target, UNIT_TYPE_DEAD) == false /*
    */
and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false /*
    */
and IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false /*
    */
and not IsUnitInGroup(target, this.damagedUnits) /*
    */
and IsUnitEnemy(target, GetOwningPlayer(this.caster))
endfunction

// This is shit. Don't touch shit.

globals
    private rect R
    private Boomerang tmpBoomerang
   
    private constant boolean ANGLE_DIRECTION_FORWARD = true
    private constant boolean ANGLE_DIRECTION_REVERSE = false
endglobals

private struct Boomerang
    unit caster
    unit target
    integer level
   
    real angleCurrent
    boolean angleDirection
   
    real targetX
    real targetY
   
    xefx dummy
    boolean active
    real damage
    group damagedUnits
   
    boolean overTree
    boolean markedForDestruction
   
    static boolexpr DamageFilter
    static boolexpr TreeFilter
    static boolexpr LightTreeFilter
   
    private integer index
   
    private static thistype array Structs
    private static timer T = CreateTimer()
    private static integer Count = 0
   
    private static method onInit takes nothing returns nothing
        set DamageFilter = Filter(function thistype.DamageFilterFunc)
        set TreeFilter = Filter(function thistype.TreeFilterFunc)
        set LightTreeFilter = Filter(function thistype.LightTreeFilterFunc)
    endmethod
   
    method reduceDamage takes nothing returns nothing
        static if DAMAGE_ABSORPTION_RELATIVE then
            set damage = damage * (1 - Damage_Absorption(level))
        else
            set damage = damage - Damage_Absorption(level)
        endif
       
        if damage <= DAMAGE_BOUNDARY then
            set markedForDestruction = true
        endif
    endmethod
   
    method onDestroy takes nothing returns nothing
        set caster = null
        set target = null
        call dummy.destroy()
        call ReleaseGroup(damagedUnits)
        // clean your struct here
        set Count = Count - 1
        set Structs[index] = Structs[Count]
        set Structs[index].index = .index
        if Count == 0 then
            call PauseTimer(T)
        endif
    endmethod
   
    static method UnitDistCheck takes nothing returns nothing
        local unit u = GetEnumUnit()
        local real dx = tmpBoomerang.dummy.x - GetUnitX(u)
        local real dy = tmpBoomerang.dummy.y - GetUnitY(u)
       
        if (dx*dx + dy*dy) > (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
            call GroupRemoveUnit(tmpBoomerang.damagedUnits, u)
        endif
        set u = null
    endmethod
   
    static method DamageFilterFunc takes nothing returns boolean
        local unit u = GetFilterUnit()
       
        if tmpBoomerang.markedForDestruction then
            return false
        endif
       
        // check if unit is a valid target for damage
        if ValidTarget(u, tmpBoomerang) then
            // damage the unit; if the unit for some reason cant be damaged, dont continue
            if UnitDamageTarget(tmpBoomerang.caster, u, tmpBoomerang.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then
                call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT))
                call GroupAddUnit(tmpBoomerang.damagedUnits, u)
               
                call tmpBoomerang.reduceDamage()
            endif
        endif
       
        set u = null
        return false
    endmethod
   
    static method TreeFilterFunc takes nothing returns boolean
        local destructable d = GetFilterDestructable()
        local real x
        local real y
        local real dx
        local real dy
       
        if tmpBoomerang.markedForDestruction then
            // short circuit logic after the damage boundary has been reached
            return false
        endif
       
        // filter out dead and non-tree destructables
        if (not IsDestructableDead(d)) and IsDestructableTree(d) then
            set tmpBoomerang.overTree = true
           
            set x = GetWidgetX(d)
            set y = GetWidgetY(d)
           
            set dx = tmpBoomerang.dummy.x - x
            set dy = tmpBoomerang.dummy.y - y
           
            // tree must be inside the collision radius
            if (dx*dx + dy*dy) <= (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
                static if KILL_TREES then
                    call KillDestructable(d)
                   
                    call tmpBoomerang.reduceDamage()
                else
                    // if Trees may not be destroyed, destroy the boomerang instead
                    set tmpBoomerang.markedForDestruction = true
                endif
            endif
        endif
       
        set d = null
        return false
    endmethod
   
    static method LightTreeFilterFunc takes nothing returns boolean
        local destructable d = GetFilterDestructable()
       
        if not IsDestructableDead(d) and IsDestructableTree(d) then
            set tmpBoomerang.overTree = true
        endif
       
        set d = null
        return false
    endmethod
   
    private static method Callback takes nothing returns nothing
        local integer i = Count - 1
        local thistype this
        local real x
        local real y
        local real launchX
        local real launchY
        local real deltaX
        local real deltaY
        local real distance
        local real angleBase
        local real angleIncrement
        local real offset
       
        loop
            exitwhen i < 0
            set this = Structs[i]
            set tmpBoomerang = this
            //
            // make the boomerang home, even if the caster moves
            if this.target != null then
                set this.targetX = GetUnitX(this.target)
                set this.targetY = GetUnitY(this.target)
            endif
           
            set launchX = GetUnitX(this.caster)
            set launchY = GetUnitY(this.caster)
           
            set deltaX = this.targetX - launchX
            set deltaY = this.targetY - launchY
           
            set distance = SquareRoot(deltaX*deltaX + deltaY*deltaY)
            set angleBase = Atan2(deltaY, deltaX) - ((bj_PI / BOOMERANG_FOCUS) / 2)
            // functions for moving the boomerang:
            // r(a)=distance*Sin(BOOMERANG_FOCUS*a) // a is the angle and goes from 90 to 0 // distance from center point
            // x(a)=Cos(a)*r(a) // x and y coordinates in relation to the location it was cast.
            // y(a)=Sin(a)*r(a) // note that i inlined some things to allow casting the boomerang in all directions from any point on the map
            set offset = distance * Sin(BOOMERANG_FOCUS * this.angleCurrent)
           
            set x = launchX + (Cos(angleBase + this.angleCurrent) * offset)
            set y = launchY + (Sin(angleBase + this.angleCurrent) * offset)
           
            set this.dummy.x = x
            set this.dummy.y = y
           
            static if ALLOW_MULTIPLE_HITS then
                call ForGroup(this.damagedUnits, function thistype.UnitDistCheck)
            endif
           
            call GroupEnumUnitsInRange(ENUM_GROUP, x, y, BOOMERANG_COLLSIZE, DamageFilter)
           
            static if not IGNORE_TREES then
                set this.overTree = false
               
                call MoveRectTo(R, x, y)
                call EnumDestructablesInRect(R, TreeFilter, null)
            endif
           
            static if COLLIDE_WITH_GROUND then
                static if IGNORE_TREES then
                    set this.overTree = false
                   
                    call MoveRectTo(R, x, y)
                    call EnumDestructablesInRect(R, LightTreeFilter, null)
                endif
               
                if not IsTerrainWalkable(x, y) and not this.overTree then
                    set this.markedForDestruction = true
                endif
            endif
           
            set angleIncrement = TICK * ((bj_PI / BOOMERANG_FOCUS) / 2) * (Boomerang_Speed(this.level) / distance)
            if this.angleDirection == ANGLE_DIRECTION_FORWARD then
                set this.angleCurrent = this.angleCurrent + angleIncrement
            else
                set this.angleCurrent = this.angleCurrent - angleIncrement
            endif
           
            if this.angleCurrent <= 0 or this.angleCurrent >= (bj_PI / BOOMERANG_FOCUS) or IsUnitType(this.caster, UNIT_TYPE_DEAD) == true then
                set this.markedForDestruction = true
            endif
           
            if this.markedForDestruction then
                call this.destroy()
            endif
           
            set i = i - 1
        endloop
    endmethod
   
    static method create takes unit caster, unit target, real targetX, real targetY, boolean direction returns thistype
        local thistype this = allocate()
        local real dx
        local real dy
        local real distance
        local real angleBase
       
        set this.caster = caster
        if target == null or target == caster then
            set this.target = null
            set this.targetX = targetX
            set this.targetY = targetY
        else
            set this.target = target
            set this.targetX = GetUnitX(target)
            set this.targetY = GetUnitY(target)
        endif
       
        set dx = this.targetX - GetUnitX(caster)
        set dy = this.targetY - GetUnitY(caster)
       
        set this.level = GetUnitAbilityLevel(this.caster, AID)
        set distance = SquareRoot(dx*dx + dy*dy)
        if distance == 0.0 then
            set angleBase = (GetUnitFacing(this.caster) * bj_DEGTORAD)
        else
            set angleBase = Atan2(dy, dx)
        endif
       
        if distance < Min_Range(this.level) then
            // enforce the minimum distance
            set distance = Min_Range(this.level)
            set this.targetX = GetUnitX(caster) + (distance * Cos(angleBase))
            set this.targetY = GetUnitY(caster) + (distance * Sin(angleBase))
        endif
       
        if direction == ANGLE_DIRECTION_FORWARD then
            set this.angleCurrent = 0.
        else
            set this.angleCurrent = (bj_PI / BOOMERANG_FOCUS)
        endif
        set this.angleDirection = direction
       
        set this.damage = Damage(this.level)
       
        set this.dummy = xefx.create(GetUnitX(caster), GetUnitY(caster), 0)
        set this.dummy.fxpath = BOOMERANG_MODEL
        set this.dummy.scale = BOOMERANG_SIZE
        set this.dummy.z = BOOMERANG_HEIGHT
       
        set this.damagedUnits = NewGroup()
       
        set this.overTree = false
        set this.markedForDestruction = false
       
        // initialize the struct here
        set Structs[Count] = this
        set this.index = Count
        if Count == 0 then
            call TimerStart(T, TICK, true, function thistype.Callback)
        endif
        set Count = Count + 1
       
        return this
    endmethod
endstruct

private function SpellCond takes nothing returns boolean
    return GetSpellAbilityId() == AID
endfunction

private function SpellAction takes nothing returns nothing
    local unit caster = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
    local real targetX = GetSpellTargetX()
    local real targetY = GetSpellTargetY()
   
    static if USE_RIGHT_BOOMERANG then
        call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_FORWARD)
    endif
   
    static if USE_LEFT_BOOMERANG then
        call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_REVERSE)
    endif
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 SpellCond))
    call TriggerAddAction(t, function SpellAction)
   
    call SetUpSpellData()
   
    set R = Rect(0, 0, 2 * BOOMERANG_COLLSIZE, 2 * BOOMERANG_COLLSIZE)
endfunction

endlibrary

Code (Formula version) + Credits:
Jass:
library_once Boomerang initializer Init requires GroupUtils, xefx, DestructableLib, IsTerrainWalkable

private keyword Boomerang // DO NOT TOUCH; configuration is below

// Credits:
//  - Ciebron for the inspiration
//  - -JonNny for reporting some bugs
//  - Atideva for reporting a bug
//  - Rising_Dusk for his GroupUtils library
//  - Anitarf for his IsTerrainWalkable library
//  - Vexorian for JassHelper and xe
//  - PipeDream for Grimoire
//  - PitzerMike for JassNewGenPack and DestructableLib
//  - MindWorX for JassNewGenPack
//  - SFilip for TESH

globals
    private constant    real                    TICK                    = 1./40 // granulation of boomerang movement
    private constant    integer                 AID                     = 'A000' // the ability triggering this spell
    private constant    boolean                 DAMAGE_ABSORPTION_RELATIVE = true // is DAMAGE_ABSORPTION to be treated as an absolute value or a value relative to the current damage
    private constant    real                    DAMAGE_BOUNDARY         = 10. // once the damage is lower or equal to this, the boomerang stops flying
    private constant    string                  BOOMERANG_MODEL         = "Abilities\\Weapons\\SentinelMissile\\SentinelMissile.mdl" // this is the model representing the boomerang
    private constant    real                    BOOMERANG_COLLSIZE      = 96. // the AoE in which units are damaged by the boomerang
    private constant    real                    BOOMERANG_SIZE          = 1.25 // the scaling of the boomerang
    private constant    real                    BOOMERANG_HEIGHT        = 64. // the Z height of the boomerang
    private constant    real                    BOOMERANG_FOCUS         = 2. // the higher this number the more focused is the path of the boomerang. Any value greater than 0 should work. Values below 2 might not look so good.
    private constant    boolean                 ALLOW_MULTIPLE_HITS     = true // if this is true, the boomerang can hit units more than once on his path
    private constant    boolean                 USE_RIGHT_BOOMERANG     = true // just avoid setting both to false, okay?
    private constant    boolean                 USE_LEFT_BOOMERANG      = true
    private constant    boolean                 COLLIDE_WITH_GROUND     = true // do boomerangs collide with unwalkable terrain?
    private constant    boolean                 IGNORE_TREES            = false // if true, the boomerang will just fly through the trees without doing anything
    private constant    boolean                 KILL_TREES              = true // if true, trees are killed once the boomerang hits one, if this is false, the boomerang stops flying
    private constant    string                  HIT_FX                  = "Objects\\Spawnmodels\\Critters\\Albatross\\CritterBloodAlbatross.mdl" // when the boomerang hits a unit, this effect is spawned on the unit hit
    private constant    string                  HIT_FX_ATTPT            = "chest" // the beforementioned effect will be attached to this point
    private constant    attacktype              ATTACK_TYPE             = ATTACK_TYPE_MAGIC // the attack type of the damage the boomerang deals
    private constant    damagetype              DAMAGE_TYPE             = DAMAGE_TYPE_MAGIC // the damage type of the damage the boomerang deals
    private constant    weapontype              WEAPON_TYPE             = WEAPON_TYPE_METAL_MEDIUM_SLICE // sound when boomerang hits a unit
endglobals

private function Damage takes integer level returns real
    return 125. + level * 75
endfunction

private function Damage_Absorption takes integer level returns real
    return 0.2 - level * 0.04
endfunction

private function Boomerang_Speed takes integer level returns real
    return 600.
endfunction

private function Min_Range takes integer level returns real
    return 200.
endfunction

private function ValidTarget takes unit target, Boomerang this returns boolean
    return IsUnitType(target, UNIT_TYPE_DEAD) == false /*
    */
and IsUnitType(target, UNIT_TYPE_STRUCTURE) == false /*
    */
and IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false /*
    */
and not IsUnitInGroup(target, this.damagedUnits) /*
    */
and IsUnitEnemy(target, GetOwningPlayer(this.caster))
endfunction

// This is shit. Don't touch shit.

globals
    private rect R
    private Boomerang tmpBoomerang
   
    private constant boolean ANGLE_DIRECTION_FORWARD = true
    private constant boolean ANGLE_DIRECTION_REVERSE = false
endglobals

private struct Boomerang
    unit caster
    unit target
    integer level
   
    real angleCurrent
    boolean angleDirection
   
    real targetX
    real targetY
   
    xefx dummy
    boolean active
    real damage
    group damagedUnits
   
    boolean overTree
    boolean markedForDestruction
   
    static boolexpr DamageFilter
    static boolexpr TreeFilter
    static boolexpr LightTreeFilter
   
    private integer index
   
    private static thistype array Structs
    private static timer T = CreateTimer()
    private static integer Count = 0
   
    private static method onInit takes nothing returns nothing
        set DamageFilter = Filter(function thistype.DamageFilterFunc)
        set TreeFilter = Filter(function thistype.TreeFilterFunc)
        set LightTreeFilter = Filter(function thistype.LightTreeFilterFunc)
    endmethod
   
    method reduceDamage takes nothing returns nothing
        static if DAMAGE_ABSORPTION_RELATIVE then
            set damage = damage * (1 - Damage_Absorption(level))
        else
            set damage = damage - Damage_Absorption(level)
        endif
       
        if damage <= DAMAGE_BOUNDARY then
            set markedForDestruction = true
        endif
    endmethod
   
    method onDestroy takes nothing returns nothing
        set caster = null
        set target = null
        call dummy.destroy()
        call ReleaseGroup(damagedUnits)
        // clean your struct here
        set Count = Count - 1
        set Structs[index] = Structs[Count]
        set Structs[index].index = .index
        if Count == 0 then
            call PauseTimer(T)
        endif
    endmethod
   
    static method UnitDistCheck takes nothing returns nothing
        local unit u = GetEnumUnit()
        local real dx = tmpBoomerang.dummy.x - GetUnitX(u)
        local real dy = tmpBoomerang.dummy.y - GetUnitY(u)
       
        if (dx*dx + dy*dy) > (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
            call GroupRemoveUnit(tmpBoomerang.damagedUnits, u)
        endif
        set u = null
    endmethod
   
    static method DamageFilterFunc takes nothing returns boolean
        local unit u = GetFilterUnit()
       
        if tmpBoomerang.markedForDestruction then
            return false
        endif
       
        // check if unit is a valid target for damage
        if ValidTarget(u, tmpBoomerang) then
            // damage the unit; if the unit for some reason cant be damaged, dont continue
            if UnitDamageTarget(tmpBoomerang.caster, u, tmpBoomerang.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE) then
                call DestroyEffect(AddSpecialEffectTarget(HIT_FX, u, HIT_FX_ATTPT))
                call GroupAddUnit(tmpBoomerang.damagedUnits, u)
               
                call tmpBoomerang.reduceDamage()
            endif
        endif
       
        set u = null
        return false
    endmethod
   
    static method TreeFilterFunc takes nothing returns boolean
        local destructable d = GetFilterDestructable()
        local real x
        local real y
        local real dx
        local real dy
       
        if tmpBoomerang.markedForDestruction then
            // short circuit logic after the damage boundary has been reached
            return false
        endif
       
        // filter out dead and non-tree destructables
        if (not IsDestructableDead(d)) and IsDestructableTree(d) then
            set tmpBoomerang.overTree = true
           
            set x = GetWidgetX(d)
            set y = GetWidgetY(d)
           
            set dx = tmpBoomerang.dummy.x - x
            set dy = tmpBoomerang.dummy.y - y
           
            // tree must be inside the collision radius
            if (dx*dx + dy*dy) <= (BOOMERANG_COLLSIZE * BOOMERANG_COLLSIZE) then
                static if KILL_TREES then
                    call KillDestructable(d)
                   
                    call tmpBoomerang.reduceDamage()
                else
                    // if Trees may not be destroyed, destroy the boomerang instead
                    set tmpBoomerang.markedForDestruction = true
                endif
            endif
        endif
       
        set d = null
        return false
    endmethod
   
    static method LightTreeFilterFunc takes nothing returns boolean
        local destructable d = GetFilterDestructable()
       
        if not IsDestructableDead(d) and IsDestructableTree(d) then
            set tmpBoomerang.overTree = true
        endif
       
        set d = null
        return false
    endmethod
   
    private static method Callback takes nothing returns nothing
        local integer i = Count - 1
        local thistype this
        local real x
        local real y
        local real launchX
        local real launchY
        local real deltaX
        local real deltaY
        local real distance
        local real angleBase
        local real angleIncrement
        local real offset
       
        loop
            exitwhen i < 0
            set this = Structs[i]
            set tmpBoomerang = this
            //
            // make the boomerang home, even if the caster moves
            if this.target != null then
                set this.targetX = GetUnitX(this.target)
                set this.targetY = GetUnitY(this.target)
            endif
           
            set launchX = GetUnitX(this.caster)
            set launchY = GetUnitY(this.caster)
           
            set deltaX = this.targetX - launchX
            set deltaY = this.targetY - launchY
           
            set distance = SquareRoot(deltaX*deltaX + deltaY*deltaY)
            set angleBase = Atan2(deltaY, deltaX) - ((bj_PI / BOOMERANG_FOCUS) / 2)
            // functions for moving the boomerang:
            // r(a)=distance*Sin(BOOMERANG_FOCUS*a) // a is the angle and goes from 90 to 0 // distance from center point
            // x(a)=Cos(a)*r(a) // x and y coordinates in relation to the location it was cast.
            // y(a)=Sin(a)*r(a) // note that i inlined some things to allow casting the boomerang in all directions from any point on the map
            set offset = distance * Sin(BOOMERANG_FOCUS * this.angleCurrent)
           
            set x = launchX + (Cos(angleBase + this.angleCurrent) * offset)
            set y = launchY + (Sin(angleBase + this.angleCurrent) * offset)
           
            set this.dummy.x = x
            set this.dummy.y = y
           
            static if ALLOW_MULTIPLE_HITS then
                call ForGroup(this.damagedUnits, function thistype.UnitDistCheck)
            endif
           
            call GroupEnumUnitsInRange(ENUM_GROUP, x, y, BOOMERANG_COLLSIZE, DamageFilter)
           
            static if not IGNORE_TREES then
                set this.overTree = false
               
                call MoveRectTo(R, x, y)
                call EnumDestructablesInRect(R, TreeFilter, null)
            endif
           
            static if COLLIDE_WITH_GROUND then
                static if IGNORE_TREES then
                    set this.overTree = false
                   
                    call MoveRectTo(R, x, y)
                    call EnumDestructablesInRect(R, LightTreeFilter, null)
                endif
               
                if not IsTerrainWalkable(x, y) and not this.overTree then
                    set this.markedForDestruction = true
                endif
            endif
           
            set angleIncrement = TICK * ((bj_PI / BOOMERANG_FOCUS) / 2) * (Boomerang_Speed(this.level) / distance)
            if this.angleDirection == ANGLE_DIRECTION_FORWARD then
                set this.angleCurrent = this.angleCurrent + angleIncrement
            else
                set this.angleCurrent = this.angleCurrent - angleIncrement
            endif
           
            if this.angleCurrent <= 0 or this.angleCurrent >= (bj_PI / BOOMERANG_FOCUS) or IsUnitType(this.caster, UNIT_TYPE_DEAD) == true then
                set this.markedForDestruction = true
            endif
           
            if this.markedForDestruction then
                call this.destroy()
            endif
           
            set i = i - 1
        endloop
    endmethod
   
    static method create takes unit caster, unit target, real targetX, real targetY, boolean direction returns thistype
        local thistype this = allocate()
        local real dx
        local real dy
        local real distance
        local real angleBase
       
        set this.caster = caster
        if target == null or target == caster then
            set this.target = null
            set this.targetX = targetX
            set this.targetY = targetY
        else
            set this.target = target
            set this.targetX = GetUnitX(target)
            set this.targetY = GetUnitY(target)
        endif
       
        set dx = this.targetX - GetUnitX(caster)
        set dy = this.targetY - GetUnitY(caster)
       
        set this.level = GetUnitAbilityLevel(this.caster, AID)
        set distance = SquareRoot(dx*dx + dy*dy)
        if distance == 0.0 then
            set angleBase = (GetUnitFacing(this.caster) * bj_DEGTORAD)
        else
            set angleBase = Atan2(dy, dx)
        endif
       
        if distance < Min_Range(this.level) then
            // enforce the minimum distance
            set distance = Min_Range(this.level)
            set this.targetX = GetUnitX(caster) + (distance * Cos(angleBase))
            set this.targetY = GetUnitY(caster) + (distance * Sin(angleBase))
        endif
       
        if direction == ANGLE_DIRECTION_FORWARD then
            set this.angleCurrent = 0.
        else
            set this.angleCurrent = (bj_PI / BOOMERANG_FOCUS)
        endif
        set this.angleDirection = direction
       
        set this.damage = Damage(this.level)
       
        set this.dummy = xefx.create(GetUnitX(caster), GetUnitY(caster), 0)
        set this.dummy.fxpath = BOOMERANG_MODEL
        set this.dummy.scale = BOOMERANG_SIZE
        set this.dummy.z = BOOMERANG_HEIGHT
       
        set this.damagedUnits = NewGroup()
       
        set this.overTree = false
        set this.markedForDestruction = false
       
        // initialize the struct here
        set Structs[Count] = this
        set this.index = Count
        if Count == 0 then
            call TimerStart(T, TICK, true, function thistype.Callback)
        endif
        set Count = Count + 1
       
        return this
    endmethod
endstruct

private function SpellCond takes nothing returns boolean
    return GetSpellAbilityId() == AID
endfunction

private function SpellAction takes nothing returns nothing
    local unit caster = GetTriggerUnit()
    local unit target = GetSpellTargetUnit()
    local real targetX = GetSpellTargetX()
    local real targetY = GetSpellTargetY()
   
    static if USE_RIGHT_BOOMERANG then
        call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_FORWARD)
    endif
   
    static if USE_LEFT_BOOMERANG then
        call Boomerang.create(caster, target, targetX, targetY, ANGLE_DIRECTION_REVERSE)
    endif
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 SpellCond))
    call TriggerAddAction(t, function SpellAction)
   
    set R = Rect(0, 0, 2 * BOOMERANG_COLLSIZE, 2 * BOOMERANG_COLLSIZE)
endfunction

endlibrary
Rating - 4.33 (3 votes)
(Hover and click)
Moderator Comments
Highly Recommended
17:17, 4th Jun 2009
hvo-busterkomo:
The effect looks great, and the scripting is done well. My only complaint is (more of a personal preference) is that you use arrays for the range and damage. It seems easier to do it the standard way (return level * 0.25 + 1, etc..).

If you have any questions regarding this review, feel free to send a PM to hvo-busterkomo.

This spell is approved and works properly.


Download Boomerang_1.4.2.w3x
(58.4 KB, 1419 Downloads)

Old 06-04-2009, 12:02 PM   #2 (permalink)
Registered User Aspard
The Witcher
 
Aspard's Avatar
 
Join Date: Jul 2008
Posts: 874
Aspard is a jewel in the rough (217)Aspard is a jewel in the rough (217)Aspard is a jewel in the rough (217)
In-Game commands are shown too late, I've already wanted to leave the game, but decided to look for commands here, I typed some in and only after that the system shown me them ;(

Nicely done, performs well, has good settings. Can't anything to you, cause working just with GUI.

In case there are already many boomerangs, 4/5
__________________
Aspard is offline   Reply With Quote
Old 06-04-2009, 01:48 PM   #3 (permalink)
Registered User f0rsAk3n
~Self-proclaimed VIP
 
f0rsAk3n's Avatar
 
Join Date: Nov 2008
Posts: 649
f0rsAk3n has a spectacular aura about (144)f0rsAk3n has a spectacular aura about (144)f0rsAk3n has a spectacular aura about (144)f0rsAk3n has a spectacular aura about (144)
I believe Hanky also made a Boomerang, though not exactly like this.

Good work
__________________
f0rsAk3n is offline   Reply With Quote
Old 06-04-2009, 04:40 PM   #4 (permalink)
Registered User xD.Schurke
10100111001
 
xD.Schurke's Avatar
 
Join Date: Feb 2006
Posts: 976
xD.Schurke is just really nice (352)xD.Schurke is just really nice (352)xD.Schurke is just really nice (352)
Hero Contest #2 - Winner: Archmaiden 
code looks good, only thing I would change is the TriggerRegisterAnyUnitEventBJ, because it leaks 14 boolexpr
hehe what I see in your create method is that you use thistype for the local var but not for the returned value ;) (but realy, who cares)

I give you 4/5
__________________
My Spells & Systems


xD.Schurke is offline   Reply With Quote
Old 06-04-2009, 10:13 PM   #5 (permalink)
Registered User -JonNny
Great Project comin soon
 
Join Date: Jan 2007
Posts: 614
-JonNny is just really nice (342)-JonNny is just really nice (342)-JonNny is just really nice (342)
I love the movement of the boomerang , awesome <3
May add some units to bash in the testmap

-But if its casted into trees or any other position near there it seems to malfunction due the boomerang flies into another dirrection with another range (may it ha sth to do with the min. castrange)

-If casted on the caster directly (e.g. hero icon) it buggs (missile does not move / is not destroyed )

When trying to save the testmap Jasshelpes displays an error which makes it not possible for me to test any changes (i guess its in XE, dunno - never used the dummy system)
error

Line 504 , Unexpected : "ARGBrecolor"
implement optional ARGBrecolor


As mention by hvo-busterkomo i would also prefer calculations instead of that array for the levels

Else, great work ! [4,5/5]
-JonNny is offline   Reply With Quote
Old 06-04-2009, 11:19 PM   #6 (permalink)
Registered User Deaod
GUI sucks. Don't use it.
 
Join Date: Nov 2007
Posts: 859
Deaod has disabled reputation
Thanks, -JonNny. I fixed both bugs.

as for the problem when saving: Im using JH 0.9.G.3, try updating JNGP (v5c) and JH (0.9.G.3).

If you prefer calculations, change the PROXY functions to your formula. You can then delete the arrays and the SetUp functions (dont forget to delete the calls to the SetUp function in the onInit method).

I also made the command list show directly after starting the map.

I dont really care about those 16 leaked null boolexprs. You should use an event stack for your map anyway.


Version 1.2.0
Deaod is offline   Reply With Quote
Old 06-06-2009, 09:54 PM   #7 (permalink)
Registered User creativeRPG
User
 
Join Date: May 2009
Posts: 31
creativeRPG is an unknown quantity at this point (0)
Hi and thx for programming such a nice spell,
but i am too stupid to import it into my map - and i want it, i need it *g* it fits perfectly into it.

i have importet the DUMMY, importet the ability boomerang! and importet all three trigger-trees.
then i added bommerang! to my hero,

but he only throws a piece of air... :(

thx
creativeRPG is offline   Reply With Quote
Old 06-07-2009, 05:49 AM   #8 (permalink)
Registered User Deaod
GUI sucks. Don't use it.
 
Join Date: Nov 2007
Posts: 859
Deaod has disabled reputation
okay, let me see:
  1. Copy over the Boomerang trigger, and all Libraries under External Libraries if you dont already have those in your map
  2. Either copy the Boomerang! ability over or recreate it yourself
  3. add the boomerang ability to your hero
  4. adjust the AID constant in the Boomerang trigger to the rawcode of your Boomerang! ability (Press CTRL+D in the Object Editor to display raw values; The Boomerang! ability should look like this: "A000:ANcl (Boomerang!)". The four characters before the double point (here: "A000") are the rawcode of the ability).
  5. Additionally make sure you adjusted the rawcodes in the xebasic library, especially the one for the dummy unit.

Last edited by Deaod; 02-19-2015 at 07:40 PM. Reason: spelling
Deaod is offline   Reply With Quote
Old 06-07-2009, 09:25 AM   #9 (permalink)
Registered User creativeRPG
User
 
Join Date: May 2009
Posts: 31
creativeRPG is an unknown quantity at this point (0)
Really thx,
this was short and very understoodable
hope i didn't put you out

best regards
(hope you will enjoy my map )
creativeRPG is offline   Reply With Quote
Old 07-01-2009, 04:15 PM   #10 (permalink)
Registered User Deuterium
-
 
Deuterium's Avatar
 
Join Date: Mar 2009
Posts: 1,981
Deuterium is a name known to all (669)Deuterium is a name known to all (669)Deuterium is a name known to all (669)
I'd be testing later :)
__________________
Deuterium is offline   Reply With Quote
Old 07-02-2009, 12:04 AM   #11 (permalink)
Registered User gunggang777
Learn vJass little to +er
 
gunggang777's Avatar
 
Join Date: Jul 2008
Posts: 202
gunggang777 is on a distinguished road (82)gunggang777 is on a distinguished road (82)
yeah good one 5/5
__________________
MasterHaosis In memoriam......
I very" inactive.... only browsing the site and not posting. so if i have a time i come again now i'am very busy
gunggang777 is offline   Reply With Quote
Old 07-03-2009, 03:51 PM   #12 (permalink)
Registered User Beast(TrOg)
Very Very Beastly
 
Beast(TrOg)'s Avatar
 
Join Date: Nov 2006
Posts: 156
Beast(TrOg) has little to show at this moment (36)Beast(TrOg) has little to show at this moment (36)Beast(TrOg) has little to show at this moment (36)Beast(TrOg) has little to show at this moment (36)
It's good but when you use boomerang and then blink somewhere, the boomerang distorts a bit.
__________________
Maps: BeasTD (Tower Defense), Hold the Line 2 (Team Hero Defense), Bombtastic (Minigame), AoES (AoS)
Other: FuryDarts (Spell)
Currently doing: AoES and Bombtastic final versions coming
Beast(TrOg) is offline   Reply With Quote
Old 07-05-2009, 06:54 AM   #13 (permalink)
Registered User Snowman
User
 
Join Date: Jul 2009
Posts: 3
Snowman is an unknown quantity at this point (0)
how do i import spells to my own map? the easy way plz
Snowman is offline   Reply With Quote
Old 07-12-2009, 06:04 AM   #14 (permalink)
Registered User Deaod
GUI sucks. Don't use it.
 
Join Date: Nov 2007
Posts: 859
Deaod has disabled reputation
Quote:
  1. Copy over the Boomerang trigger, and all Libraries under External Libraries if you dont already have those in your map
  2. Either copy the Boomerang! ability over or recreate it yourself
  3. add the boomerang ability to your hero
  4. adjust the AID constant in the Boomerang trigger to the rawcode of your Boomerang! ability (Press CTRL+D in the Object Editor to display raw values; The Boomerang! ability should look like this: "A000:ANcl (Boomerang!)". The four characters before the double point (here: "A000") are the rawcode of the ability).
  5. Additionally make sure you adjusted the rawcodes in the xebasic library, expecially the one for the dummy unit.
Version 1.3.1

Comiled with JNGP v5d (that means you need to update).
Deaod is offline   Reply With Quote
Old 08-05-2009, 11:16 AM   #15 (permalink)
Registered User DoOs_101
Original and Proud
 
DoOs_101's Avatar
 
Join Date: Aug 2004
Posts: 837
DoOs_101 has little to show at this moment (31)DoOs_101 has little to show at this moment (31)DoOs_101 has little to show at this moment (31)DoOs_101 has little to show at this moment (31)
TRhis doesn't work anymore in 1.24
__________________

My Website:

DoOs_101 is offline   Reply With Quote
Reply

Bookmarks

Thread Tools

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are Off
Pingbacks are Off
Refbacks are Off


Similar Threads
Thread Thread Starter Forum Replies Last Post
[JASS] formula for a boomerang Ciebron Triggers & Scripts 15 06-05-2009 05:20 AM

All times are GMT. The time now is 05:17 PM.




Powered by vBulletin
Copyright 2000 - 2008, Jelsoft Enterprises Ltd.
Search Engine Optimization by vBSEO 3.5.1 PL2
Copyright © Ralle