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

Need some help with adding AOE to abilities

Status
Not open for further replies.
Level 4
Joined
Dec 29, 2009
Messages
69
Hey guys i'm having a few problems with custom abilities and AOE at the moment, just wondering if anyone could kindly help me :p

First of all, I have a custom spell based on searing arrow, I want the unit to do splash damage while the ability is active (right clicked), I have set the AOE to about 300 and it's only hitting one unit

The second spell, is based off the Fire bolt spell, the unit fires a projetile from its rifle, the projectile will travel in an arc and do splash damage once it hits targeted units. Also it would be really helpful if anyone could tell me how to add a knockback effect to the spell which will also change the target's altitude

Thanks in advance :D
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Do you have any idea how much work that is just to understand functionality of a knockback effect? You should start with something smaller, for now, because you're not even warmed up to the fact that single-target spells cannot be made into area of effect. Attacks, yes. Spells, no.

Later tonight, unless someone beats me to it, I'll tell you how to make the first two spells do what you want and then I'll link you to a tutorial on knockback spells just so you'll know how outstandingly beaurocratic that system has to be.
 
Level 18
Joined
Jan 21, 2006
Messages
2,552
For the "Searing Arrows" you could base it off of a different auto-cast ability that places a buff on the attacked unit. In conjunction with this, you would use a damage-detection engine to detect whenever any unit is damaged.

Once you've got the damage-detection setup, simply detect when a unit takes damage and whether that unit has the buff placed by the auto-cast spell I mentioned above. If it does, then deal damage to an area of units (using GroupEnumUnitsInRange).

The second spell would require something like a projectile system, that allows you to simulate a WarCraft III projectile but still manipulate information stored in the projectile.

Oh wait! I have a system in development that is just for that!

JASS:
library Projectiles requires Vectors optional TimerUtils
globals
//**********************
//* Configuration
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Many default constants.
//*
//*
    private constant integer        FLY_ABIL_ID                 = 'Amrf'    //* This is medihv's crow-form ability.
//*
    public constant real            COLLISION_DEFAULT           = 50        //* This is the range at which units/projectiles will "collide".
    public constant real            COLLISION_MAX               = 180       //* This is the maximum value for projectile collision. If 'COLLISION_DEFAULT'
                                                                            //* is higher then this it will not over-ride it.
//*
    public constant boolean         EXPIRATION_DEFAULT          = true      //* The projectile's tendency to die once it has "finished". Setting this to false
                                                                            //* should only be done if you are managing the projectiles yourself.
//*
    private constant boolean        hitDead                     = false     //* Projectiles will "strike" dead units.
    private constant boolean        hitEnemy                    = true      //* Projectiles will "strike" enemy units.
    private constant boolean        hitAllied                   = false     //* Projectiles will "strike" allied units.
    private constant boolean        hitStructure                = false     //* Projectiles will "strike" structures.
//*
//*
//*****************************************
endglobals


interface projectileinterface
//*************
//* Projectile Interface
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* The following functions can be re-declared in child-structs and gain their logical functionality. For example, declaring
//* 'onUnitCollision' will allow you to customize the actions executed when the projectile comes into contact with a unit.
//* 
//*
    method onFinish takes nothing returns nothing defaults nothing                      //* Once the projectile reaches the end of its path (unless otherwise specified).
    method onBirth takes nothing returns nothing defaults nothing                       //* When the projectile starts its projection path.
    method onUnitCollision takes unit u returns nothing defaults nothing                //* When the projectile collides with a unit (u = collide with -unit-).
    method onCollision takes projectile p returns nothing defaults nothing              //* When the projectile collides with another projectile (p = other projectile).
    method onGround takes nothing returns nothing defaults nothing                      //* Projectile has struck the ground.
//*
//*
endinterface



struct projectile extends projectileinterface
//***************************
//* Projectiles 
//* ¯¯¯¯¯¯¯¯¯¯¯
//* Data-structure that supports the creation/launch (as well as other functions) of in-game projectiles. The projectiles
//* are meant to mimic the basic ones as close as possible.
//*
//*

    // Primary handle-base for the projectile.
    readonly unit           toUnit          = null              //* This unit is "projected" as if it were a projectile.
    
    readonly real           speed          
    readonly real           arc 
    real                    collision       = COLLISION_DEFAULT 
    real                    timescale       = 1.00              //* Time-scale is at 100% by default.
    private real            p_scale         = 1.00              //* Size-scale is at 100% by default.
    
    method operator scale= takes real s returns nothing
        set .p_scale=s
        call SetUnitScale(.toUnit, s, s, s)
    endmethod
    method operator scale takes nothing returns real
        return .p_scale
    endmethod
    
    // Active feature data
    boolean                 activeUnitCollision     = true      //* The projectile can collide with units.
    boolean                 activeCollision         = false     //* The projectile can collide with other projectiles.
    boolean                 activePitchRotation     = true      //* The projectile will "lean" in the direction that it is moving.
    
    private static location p_sampleLoc     = Location(0, 0)    //* Location used often for 'MoveLocatin' and 'GetLocationZ'
    private static group    p_sampleGroup   = CreateGroup()     //* Group used to enumerate nearby units.
    private static thistype p_sampleProj                        //* Used to store 'this' when doing a group enum.
    
    // These vectors store information including projectile motion.
    private s_vector3d      p_currentPos
    private s_vector3d      p_currentVelocity
    private s_vector3d      p_currentAcceleration
    private s_vector3d      p_currentTargetPos                      //* When the target-position vector is changed during flight, it will activate the projectile's 
                                                                    //* "homing" capabilities, which will eliminate 'arc' and move directly towards the target.
    private real            p_timeRemaining
    
    // --------
    // The following method operators allow the user more access to the projectile's vectors, without being able to
    // do anything "illegal".
    //
    // Returns a COPY of the position vector.
    method operator position takes nothing returns s_vector3d
        return .p_currentPos.copy()
    endmethod
    // Returns a COPY of the velocity vector.
    method operator velocity takes nothing returns s_vector3d
        return .p_currentVelocity.copy()
    endmethod
    // Returns a COPY of the acceleration vector.
    method operator acceleration takes nothing returns s_vector3d
        return .p_currentAcceleration.copy()
    endmethod
    // Returns a COPY of the target-position vector.
    method operator targetposition takes nothing returns s_vector3d
        return .p_currentTargetPos.copy()
    endmethod
    //
    
    
    // Destroy removes projectile from the stack, but it does not kill the unit.
    method onDestroy takes nothing returns nothing
        set thistype.p_stackTrack = thistype.p_stackTrack - 1
        set thistype.p_stack[this.p_index] = thistype.p_stack[thistype.p_stackTrack]
        set thistype.p_stack[this.p_index].p_index = this.p_index
        if (thistype.p_stackTrack==0) then
            call PauseTimer(thistype.p_loop)                    //* Pause the timer if it is no longer needed.
        endif
        // Don't forget to destroy the vectors!
        call .p_currentPos.destroy()
        call .p_currentTargetPos.destroy()
        call .p_currentVelocity.destroy()
        call .p_currentAcceleration.destroy()
    endmethod
    // We don't want the projectile struct to leak so we need to remove it once its trajectory has completed.
    method onFinish takes nothing returns nothing
        call this.destroy()
    endmethod
    
    
    
    // In order to keep track of the projectile within a stack of projectiles, it must have necessary indexing mechanisms. When
    // a projectile is created it is added to this stack, which is looped through on each iteration of the timer.
    private integer     p_index                                 //* Keeps track of 'this' index so that the stack can be maintained.
    
    private static thistype array       p_stack
    private static integer              p_stackTrack    = 0     //* There are no projectiles in the stack to begin with.
    
    // In order to update the stack, we must use a timer. If the 'TimerUtils' library is available then it will use 'NewTimer' instead of 'CreateTimer'.
    private static constant real        p_loopPeriod    = 0.03  //* Rate that the timer will update the stack. Increasing it will decrease the performance.
    static if (LIBRARY_TimerUtils) then
        private static timer p_loop = NewTimer()                //* Uses TimerUtils. The timer is never destroyed so its not that important, I just wanted to
    else                                                        //* use the 'optional' keyword and try out static if statements.
        private static timer p_loop = CreateTimer()             //* This does not.
    endif
        
        
        
    boolean         toDestroy           = EXPIRATION_DEFAULT    //* If this is 'false' then the projectile will not run .onFinish() and its remaining time
                                                                //* will not decrease.
                                                                
    static method onLoopUnitEnum takes nothing returns boolean
        local thistype this = .p_sampleProj
        local unit filt = GetFilterUnit()
        local integer c = 0
        local real x
        local real y
        local real z
        
        set x = GetUnitX(filt)-this.p_currentPos.x
        set y = GetUnitY(filt)-this.p_currentPos.y
        call MoveLocation(.p_sampleLoc, GetUnitX(filt), GetUnitY(filt))
        set z = GetLocationZ(.p_sampleLoc)+GetUnitFlyHeight(filt) - this.p_currentPos.z
        
        if (SquareRoot(x*x + y*y + z*z) <= this.collision) and (filt!=this.toUnit)  then
            call this.onUnitCollision(filt)
        endif
        set filt = null

        return false
    endmethod
    // Updates stack .p_stack on a periodic timer.
    static method onLoopStatic takes nothing returns nothing
        local integer i = .p_stackTrack - 1
        local integer j
        local thistype this
        local s_vector3d vec1
        local s_vector3d vec2
        local real x                //* These are not necessary, but they allocate some of the processor's
        local real y                //* work-load onto the memory.
        local real z
        
        loop
            exitwhen (i < 0)
            set this = .p_stack[i]
            
            if (this != 0) then
                
                set vec1 = this.p_currentVelocity
                set vec2 = this.p_currentAcceleration
                // Scale the added acceleration based on the time-scale of the projectile.
                set vec1.x = vec1.x+(vec2.x*this.timescale*this.timescale)
                set vec1.y = vec1.y+(vec2.y*this.timescale*this.timescale)
                set vec1.z = vec1.z+(vec2.z*this.timescale*this.timescale)
                
                set vec2 = this.p_currentPos
                // Scale the added velocity (to position) based on time-scale.
                set vec2.x = vec2.x+(vec1.x*this.timescale)
                set vec2.y = vec2.y+(vec1.y*this.timescale)
                set vec2.z = vec2.z+(vec1.z*this.timescale)
      
                // Update the projectile.
                set x = vec2.x
                set y = vec2.y
                call SetUnitX(this.toUnit, x)
                call SetUnitY(this.toUnit, y)
                call MoveLocation(.p_sampleLoc, x, y)
                call SetUnitFlyHeight(this.toUnit, vec2.z-GetLocationZ(.p_sampleLoc), 0)
                
                // Update the projectile's pitch-rotation, if applicable.
                if (this.activePitchRotation and vec1.magnitude()!=0.0) then
                    set x = vec1.x
                    set y = vec1.y
                    set z = vec1.z
                    call SetUnitFacingTimed(this.toUnit, Atan2(y, x)*bj_RADTODEG, 0)
                    call SetUnitAnimationByIndex(this.toUnit, R2I(bj_RADTODEG*Atan2(z, SquareRoot(x*x + y*y)+0.5)+90))
                endif
                
                // Since 'doSync' was just called, .p_sampeLoc will still have the correct coordinates.
                if (this.p_currentPos.z <= GetLocationZ(.p_sampleLoc)) then
                    // Indicates that the projectile has struck the ground.
                    call this.onGround()                       
                endif
                // Only execute the projectile-collision if it is activated. Currently this system does not
                // support projectile-projectile collision.
                if (this.activeCollision) then
                    // >>>>
                endif
                if (this.toDestroy) then
                    set this.p_timeRemaining = this.p_timeRemaining - .p_loopPeriod
                    if (this.p_timeRemaining <= 0.00) then
                        // Indicates the projectile's trajectory is complete.
                        call this.onFinish()             
                    endif
                endif
                // Only execute unit-collision segment if the projectile unit-collision is activated.
                if (this.activeUnitCollision) then
                    // Enumerate nearby units so that they can be "detected". Store 'this' in global variable for referencing in enum response.
                    set .p_sampleProj = this
                    call GroupEnumUnitsInRange(.p_sampleGroup, vec2.x, vec2.y, COLLISION_MAX, Filter(function thistype.onLoopUnitEnum))
                    // The enumeration returns false, so the group will always remain empty.
                endif
            endif
            set i = i - 1
        endloop
    endmethod
    
    
    // Launches the projectile with provided input. If the projectile cannot be created with specified input then this
    // method will return false.
    method doLaunch takes s_vector3d start, s_vector3d target, real speed, real pitchArc returns boolean
        local real d
        local real a
        if (start==0) or (target==0) then
            return false
        endif
        // Initialize vectors and specific info.
        set .p_currentPos = start.copy()
        set .p_currentTargetPos = target.copy()
        set .speed = speed                                  //* Speed/arc are never used internally, but they are there for user reference in any
        set .arc = pitchArc                                 //* case.
        // The timer must be started if this is the first projectile in-stack.
        if (thistype.p_stackTrack==0) then
            call TimerStart(thistype.p_loop, thistype.p_loopPeriod, true, function thistype.onLoopStatic)
        endif
        // Add the projectile to the stack.
        set .p_index = thistype.p_stackTrack
        set thistype.p_stack[thistype.p_stackTrack] = this
        set thistype.p_stackTrack = thistype.p_stackTrack + 1
        // 
        set d = SquareRoot((start.x-target.x)*(start.x-target.x) + (start.y-target.y)*(start.y-target.y))       //* This is a 2D distance.
        set a = Atan2(target.y-start.y, target.x-start.x)
        
        if (d==0.0) or (speed==0) then
            set .p_currentPos.x = .p_currentTargetPos.x
            set .p_currentPos.y = .p_currentTargetPos.y
            set .p_currentPos.z = .p_currentTargetPos.z
            set .p_timeRemaining = 0.0
            // This indicates the projectile will be instant.
            set .p_currentAcceleration = s_vector3d.createEx(0, 0, 0)
            set .p_currentVelocity = s_vector3d.createEx(0, 0, 0)
            // The projectile should be destroyed immediately (ignoring the inaccuracy of the timer).
        else
            set .p_timeRemaining = d/speed
            set .p_currentAcceleration = s_vector3d.createEx(0, 0, -8*pitchArc*speed*speed/d)
            set .p_currentVelocity = s_vector3d.createEx(speed*Cos(a), speed*Sin(a), -.p_currentAcceleration.z * (d/speed)/2 + (target.z-start.z)/(d/speed))
        endif

        // Scale projectile vectors
        set .p_currentAcceleration.x=.p_currentAcceleration.x*thistype.p_loopPeriod*thistype.p_loopPeriod
        set .p_currentAcceleration.y=.p_currentAcceleration.y*thistype.p_loopPeriod*thistype.p_loopPeriod
        set .p_currentAcceleration.z=.p_currentAcceleration.z*thistype.p_loopPeriod*thistype.p_loopPeriod
        set .p_currentVelocity.x=.p_currentVelocity.x*thistype.p_loopPeriod
        set .p_currentVelocity.y=.p_currentVelocity.y*thistype.p_loopPeriod
        set .p_currentVelocity.z=.p_currentVelocity.z*thistype.p_loopPeriod
     
        // Sync projectile and call .onBirth user-method. If a projectile is "instant" it will not be destroyed until the first
        // timer-loop. This is a maximum inaccuracy of the timer's loop timeout.
        call SetUnitX(.toUnit, .p_currentPos.x)
        call SetUnitY(.toUnit, .p_currentPos.y)
        call MoveLocation(thistype.p_sampleLoc, .p_currentPos.x, .p_currentPos.y)
        call SetUnitFlyHeight(.toUnit, .p_currentPos.z-GetLocationZ(thistype.p_sampleLoc), 0)
        
        call .onBirth()
        return true
    endmethod
    
    
    // Creates a projectile based on a unit.
    static method create takes unit u returns thistype
        local thistype proj = thistype.allocate()
        set proj.toUnit = u
        call UnitAddAbility(u, FLY_ABIL_ID)
        call UnitRemoveAbility(u, FLY_ABIL_ID)
        return proj
    endmethod
    
endstruct
endlibrary

Its in the process of a (final, maybe) re-write, and it also requires quite a bit of vJass knowledge to use, so I doubt its "up your alley".
 
Status
Not open for further replies.
Top