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

Scope Arrow v1.4

  • Like
Reactions: Bribe
Description
Fires a magic arrow that splits into 6 arrows upon hitting a target.

Level 1 - 50 initial damage. 30 split damage.
Level 2 - 100 initial damage. 40 split damage.
Level 3 - 150 initial damage. 50 split damage.
Level 4 - 200 initial damage. 60 split damage.

Cast Range: 2000
Target Type: Point
Cooldown: 11 seconds



Implementation
JASS:
//==============================================================================
//                           SCOPE ARROW v1.4                    
//                              BY Ayanami                             
//==============================================================================

//==============================================================================
//                            REQUIREMENTS                                      
//==============================================================================
// - JNGP
// - Alloc
// - SpellEffectEvent
//   * RegisterPlayerUnitEvent
//   * Table                                                     
// - Timer32     
// - WorldBounds                                                           
//==============================================================================

//==============================================================================
//                           IMPLEMENTATION                                     
//==============================================================================
// 1) Copy the whole "Required Systems" Trigger folder 
// 2) Copy the trigger "Scope Arrow"
// 3) Copy the ability "Scope Arrow" 
// 4) Copy the unit "Scope Arrow Dummy"
// 5) Go through the configuration section in "Scope Arrow" trigger                            
//==============================================================================


Spell Code

JASS:
library ScopeArrow uses Alloc, SpellEffectEvent, T32, WorldBounds

//===========================================================================
//                           CONFIGURABLES                        
//===========================================================================

globals
    private constant integer ABIL_ID = 'ABSA' // raw code of base ability
    private constant integer DUMMY_ID = 'dSCO' // raw code of unit "Scope Arrow Dummy"
    
    private constant integer COUNT = 6 // number of arrows split, setting it 0 will result in catastrophic turn of events
    private constant real COLLISION = 50. // collision size of arrow
    private constant real SPEED = 1000. // distance travelled by arrow per second
    private constant real SCALE_MAIN = 1.5 // scale size of main arrow
    private constant real SCALE_SPLIT = 1.0 // scale size of split arrow
    private constant real HEIGHT = 50. // flying height of arrow
    
    private constant string FX = "Abilities\\Spells\\Other\\BlackArrow\\BlackArrowMissile.mdl" // effect upon impact
    private constant string FX_AT = "chest" // FX attachment point
    
    private constant real ENUM_RADIUS = 176. // max collision size of a unit in your map
    
    private constant attacktype ATK = ATTACK_TYPE_NORMAL // attack type
    private constant damagetype DMG = DAMAGE_TYPE_MAGIC // damage type
endglobals

// initial damage
private constant function GetDamage takes integer level returns real
    return 50. * level
endfunction

// damage dealt per split arrow
private constant function GetDamageSplit takes integer level returns real
    return 10. * level + 20.
endfunction

// distance travelled by arrow
private constant function GetDistanceMain takes integer level returns real
    return 2000.
endfunction

private constant function GetDistanceSplit takes integer level returns real
    return 1000.
endfunction

// filter for allowed targets
private constant function GetFilter takes unit u, unit caster returns boolean
    return /*
    */ not IsUnitType(u, UNIT_TYPE_DEAD) /* // target is alive
    */ and IsUnitEnemy(u, GetOwningPlayer(caster)) /* // target is an enemy of caster
    */ and not IsUnitType(u, UNIT_TYPE_STRUCTURE) // target is not a structure
endfunction

//===========================================================================
//                          END CONFIGURABLES                        
//===========================================================================

globals
    private constant real TRUE_SPEED = SPEED * T32_PERIOD
    private constant real SPLIT_ANGLE = bj_PI * 2 / COUNT
    
    private group G = bj_lastCreatedGroup
endglobals

private struct Split extends array
    implement Alloc
    
    private unit u
    private unit dummy
    private group g
    private real cos
    private real sin
    private real dmg
    private real dist
    
    private method destroy takes nothing returns nothing
        call KillUnit(this.dummy)
        call DestroyGroup(this.g)
        call this.stopPeriodic()
        call this.deallocate()
    endmethod
    
    private method periodic takes nothing returns nothing
        local unit u
        local real x
        local real y
    
        if this.dist <= 0 then
            call this.destroy()
        else
            set this.dist = this.dist - TRUE_SPEED
            set x = GetUnitX(this.dummy) + TRUE_SPEED * this.cos
            set y = GetUnitY(this.dummy) + TRUE_SPEED * this.sin
            
            if x >= WorldBounds.minX and x <= WorldBounds.maxX and y >= WorldBounds.minY and y <= WorldBounds.maxY then
                call SetUnitX(this.dummy, x)
                call SetUnitY(this.dummy, y)
            endif
            
            call GroupEnumUnitsInRange(G, x, y, COLLISION + ENUM_RADIUS, null)
            
            loop
                set u = FirstOfGroup(G)
                exitwhen u == null
                call GroupRemoveUnit(G, u)
                
                if GetFilter(u, this.u) and not IsUnitInGroup(u, this.g) and IsUnitInRangeXY(u, x, y, COLLISION) then
                    call UnitDamageTarget(this.u, u, this.dmg, true, false, ATK, DMG, null)
                    call GroupAddUnit(this.g, u)
                endif
            endloop
        endif
    endmethod
    implement T32x
    
    public static method create takes unit u, real dmg, real dist, real a, real x, real y returns thistype
        local thistype this = thistype.allocate()
        
        set this.u = u
        set this.dummy = CreateUnit(GetOwningPlayer(this.u), DUMMY_ID, x, y, bj_RADTODEG * a)
        set this.g = CreateGroup()
        set this.cos = Cos(a)
        set this.sin = Sin(a)
        set this.dmg = dmg
        set this.dist = dist
        
        call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
        call SetUnitScale(this.dummy, SCALE_SPLIT, SCALE_SPLIT, SCALE_SPLIT)
        call this.startPeriodic()
        
        return this
    endmethod
endstruct

private struct Data extends array
    implement Alloc
    
    private unit u
    private unit dummy
    private real a
    private real cos
    private real sin
    private real dmg
    private real splitDmg
    private real dist
    private real splitDist
    
    private method destroy takes nothing returns nothing
        call KillUnit(this.dummy)
        call this.stopPeriodic()
        call this.deallocate()
    endmethod
    
    private method periodic takes nothing returns nothing
        local unit u
        local integer i
        local real x
        local real y
        local boolean b = true
    
        if this.dist <= 0 then
            call this.destroy()
        else
            set this.dist = this.dist - TRUE_SPEED
            set x = GetUnitX(this.dummy)
            set y = GetUnitY(this.dummy)
            
            call GroupEnumUnitsInRange(G, x, y, COLLISION + ENUM_RADIUS, null)
            
            loop
                set u = FirstOfGroup(G)
                exitwhen u == null
                call GroupRemoveUnit(G, u)
                
                if GetFilter(u, this.u) and IsUnitInRangeXY(u, x, y, COLLISION) then
                    set b = false
                    
                    call UnitDamageTarget(this.u, u, this.dmg, true, false, ATK, DMG, null)
                    call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_AT))
                    call GroupClear(G)
                endif
            endloop
            
            if b then
                set x = x + TRUE_SPEED * this.cos
                set y = y + TRUE_SPEED * this.sin
                
                if x >= WorldBounds.minX and x <= WorldBounds.maxX and y >= WorldBounds.minY and y <= WorldBounds.maxY then
                    call SetUnitX(this.dummy, x)
                    call SetUnitY(this.dummy, y)
                endif
            else
                set this.a = SPLIT_ANGLE
                set i = COUNT
                
                loop
                    exitwhen i == 0
                    
                    call Split.create(this.u, this.splitDmg, this.splitDist, this.a * i, x, y)
                    
                    set i = i - 1
                endloop
                
                call this.destroy()
            endif
        endif
    endmethod
    implement T32x
    
    private static method onCast takes nothing returns boolean
        local thistype this = thistype.allocate()
        local integer level
        local real x
        local real y
        
        set this.u = GetTriggerUnit()
        set level = GetUnitAbilityLevel(this.u, ABIL_ID)
        set x = GetUnitX(this.u)
        set y = GetUnitY(this.u)
        set this.a = Atan2(GetSpellTargetY() - y, GetSpellTargetX()- x)
        set this.cos = Cos(this.a)
        set this.sin = Sin(this.a)
        set this.dummy = CreateUnit(GetTriggerPlayer(), DUMMY_ID, x, y, bj_RADTODEG * this.a)
        set this.dmg = GetDamage(level)
        set this.splitDmg = GetDamageSplit(level)
        set this.dist = GetDistanceMain(level)
        set this.splitDist = GetDistanceSplit(level)
        
        call SetUnitFlyHeight(this.dummy, HEIGHT, 0)
        call SetUnitScale(this.dummy, SCALE_MAIN, SCALE_MAIN, SCALE_MAIN)
        call this.startPeriodic()
    
        return false
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent(ABIL_ID, function thistype.onCast)
    endmethod
endstruct

endlibrary


Credits
Sevion - Alloc
Bribe - SpellEffectEvent
Jesus4Lyf - Timer32
Nestharus - WorldBounds


Changelogs

- Initial relase


- Stored Sin(this.a) and Cos(this.a) as members to prevent using Cos and Sin natives so often
- Used a constant to store bj_PI * 2 / COUNT


- Removed Damage and GTrigger
- Replaced GTrigger with SpellEffectEvent


- Removed GroupUtils
- WorldBounds is now required
- Added a out-of-bound check for the projectile to prevent crashes
- Changed damage mechanics: has an initial damage followed by the split damage


- Fixed a group leak (forgot to destroy this.g)


Feedback will be appreciated.

Keywords:
scope, arrow, split, magic
Contents

Scope Arrow v1.4 (Map)

Reviews
13 Nov 2011 Bribe: Approved. Nice fixes. Couple extra things: "call GroupClear(G)" is not needed within the loop. You could use "exitwhen true" to just break the loop. SetUnitScale can take 0 as the last 2 parameters (y and z) because those...

Moderator

M

Moderator

13 Nov 2011
Bribe: Approved. Nice fixes.

Couple extra things:

"call GroupClear(G)" is not needed within the loop. You could use "exitwhen true" to just break the loop.

SetUnitScale can take 0 as the last 2 parameters (y and z) because those parameters do nothing.
 
Good Job bro :)
The code is quite optimal :D

A few things:
- GTrigger? Why not SpellEffectEvent by Bribe :p
- AIDS? UnitIndexer is much faster and much cleaner when it comes to the API :p

AIDS should be deprecated >.<
The API is horrible >.<

Also, I wouldn't recommend using stuff like Alloc cause most people would only implement it and never use it again, thus, it would only be taking up space.

Libraries like Alloc shouldnt be used in Public resources >:p
Same goes for GTrigger.. also SpellEffectEvent

Instead of alloc, just add these struct members:
JASS:
private static integer ic = 0
private static integer ir = 0
private thistype rn

Allocate an instance like this:
JASS:
if 0==ir then
    set ic=ic+1
    set this=ic
else
    set this=ir
    set ir=.rn
endif

And deallocate like this:
JASS:
set .rn=ir
set ir=this

edit
One other thing.... [highlight]THANK YOU FOR USING A LIBRARY!![/code]
 
Level 9
Joined
Dec 3, 2010
Messages
162
Good Job bro :)
The code is quite optimal :D

A few things:
- GTrigger? Why not SpellEffectEvent by Bribe :p
- AIDS? UnitIndexer is much faster and much cleaner when it comes to the API :p

AIDS should be deprecated >.<
The API is horrible >.<

Also, I wouldn't recommend using stuff like Alloc cause most people would only implement it and never use it again, thus, it would only be taking up space.

Libraries like Alloc shouldnt be used in Public resources >:p
Same goes for GTrigger.. also SpellEffectEvent

Instead of alloc, just add these struct members:
JASS:
private static integer ic = 0
private static integer ir = 0
private thistype rn

Allocate an instance like this:
JASS:
if 0==ir then
    set ic=ic+1
    set this=ic
else
    set this=ir
    set ir=.rn
endif

And deallocate like this:
JASS:
set .rn=ir
set ir=this

edit
One other thing.... [highlight]THANK YOU FOR USING A LIBRARY!![/code]

AIDS is a requirement for Damage, thus AIDS is requirement for Damage. Is it a must to remove Alloc and such? I mean so that it'll be approved.
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Public resources that have requirements should list those requirements.
Since scopes have no way to list the requirements properly, then the
scopes should be in a library if they need other libraries.

In the end it doesn't matter but the idea behind scopes was just simple
encapsulation. Just because you make it a library doesn't mean that
other libraries must require it (obviously spells will not be required),
and if you look at it that way you can pretty much code everything you
do in libraries.

I like this resource, there is very little room for improvement. I do
recommend caching these values into array members from the "onCast"
method:

Cos(this.a)
Sin(this.a)

I also recommend storing this into a constant:

bj_PI * 2 / COUNT

If the user sets the count to 0 it's his fault anyway right?
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
ಠ_ಠ

You could use DamageEvent by Nestharus

Nah, you can keep Alloc and it will still be approved :)

The thing if you got the objectmerger config fucked up (like mine) LUA is the issue in this case.

I use alloc a lot actually as I'm too lazy to type out the three statics :p

Yeah, but making implicit requirements is a bad programming practice :/

Bla bla bla bla bla, if you're good enough you know the differences between simple vJASS and heavier languages :)

Bribe said:
In the end it doesn't matter

VICTORY AT LAST!
 
The thing if you got the objectmerger config fucked up (like mine) LUA is the issue in this case.

DamageEvent doesn't require Lua :D
That's Adv DamageEvent ^^ (The extension)

Bla bla bla bla bla, if you're good enough you know the differences between simple vJASS and heavier languages :)

605-y-u-no-guy.jpg
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Just create a defend ability, change the icon to nothing, add a global in UnitIndexer called ABILITIES_UNIT_INDEXER, and set it to the id of the defend ability you just created.
p

Why this when few lines of lua can do it for me? :) But I'll probably need someone to send me their JNGP files because I cannot find the proper lines in the configs. Kinda desperate now as I cannot work on Master Arena v4.0.
 
Speed-wise, just a tiny bit.
RAM-wise, there will be a difference.

Oh and GroupUtils is totally redundant :p
Instead of GroupEnumUnitsInArea, just use GroupEnumUnitsInRange
with a null filter and increase the radius by 197. (configurable).
A FirstOfGroup loop should be used here to increase speed by
about 200% (according to some recent benchmarks by Anitarf)
Also, in the FirstOfGroup loop, for correct results, you should
check this: IsUnitInRangeXY(unit, x, y, radius without the maximum collision size) ENUM_GROUP is also useless. bj_lastCreatedGroup is way better.
 
Top