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

[vJASS] Minor-Flawed Missile System

Status
Not open for further replies.
Level 7
Joined
Apr 30, 2011
Messages
359
Perfect Missile System

I editted this system from Element of Water's Missile System
I have added more functions, and as well as the length of the calls
The problem of this system is, when you modify anything of the missile during its creation (after firing the missile, but before its onLoop actions), the missile bugged and it screws all of my other systems used along with it.
Please anyone help me, and if anyone can rewrite the system, it's gladly welcomed with +reps from me

The Code:
JASS:
library Missile initializer Init
    
    //CONSTANTS - change these suitably.
    
    globals
        public constant real FPS = 100.00
        private constant integer DUMMY_ID = 'e001'
        private constant player OWNER = Player(15)
    endglobals
    
    //END CONSTANTS
    
    //MAIN SYSTEM CODE - do NOT alter unless you know what you're doing.
    
    globals
        private Missile array Dat
        private integer Index = 0
        private timer Tim = CreateTimer()
        
        private boolexpr Conds
        private group Missiles = CreateGroup()
        
        Missile GetEventMissile
    endglobals
    
    struct MissileActions
        stub method onHit takes nothing returns boolean
            return false
        endmethod
        
        stub method onLoop takes nothing returns boolean
            return true
        endmethod
    endstruct
    
    //kinda transfers all the units in a group into a different group.
    private function AddGroup takes group a, group b returns nothing
        local unit u
        loop
            set u = FirstOfGroup(b)
            exitwhen u == null
            call GroupAddUnit(a, u)
            call GroupRemoveUnit(b, u)
        endloop
    endfunction
    
    private function CondsFunc takes nothing returns boolean
        return (not IsUnitInGroup(GetFilterUnit(), GetEventMissile.hg)) and GetEventMissile.Caster != GetFilterUnit() and GetUnitTypeId(GetFilterUnit()) != DUMMY_ID and GetWidgetLife(GetFilterUnit()) > 0.405
    endfunction
    
    private function Execute takes nothing returns nothing
        local integer i = 0
        
        local location zl
        
        local real angle
        local real dx
        local real dy
        local real dist
        
        loop
            exitwhen i >= Index
            
            set GetEventMissile = Dat[i]
            
            //calculate how far the missile the missile has travelled
            set GetEventMissile.CurrentDist = GetEventMissile.CurrentDist + GetEventMissile.sp
            
            //If the missile is homing onto a target, recalculate a few values
            if GetEventMissile.Homing then
                //get the new target x/y
                set GetEventMissile.TargetX = GetUnitX(GetEventMissile.Target)
                set GetEventMissile.TargetY = GetUnitY(GetEventMissile.Target)
                
                //get the angle between the missile and the target
                set angle = Atan2(GetEventMissile.TargetY - GetEventMissile.Y, GetEventMissile.TargetX - GetEventMissile.X)
                
                //calculate the sin/cos of the angle
                set GetEventMissile.sn = Sin(angle)
                set GetEventMissile.cs = Cos(angle)
                
                //make the missile face towards the target
                call SetUnitFacing(GetEventMissile.Dummy, angle * bj_RADTODEG)
                
                //calcuclate the distance between the missile and its target
                set dx = GetEventMissile.TargetX - GetEventMissile.X
                set dy = GetEventMissile.TargetY - GetEventMissile.Y
                set dist = SquareRoot(dx * dx + dy * dy)
            else
                set dist = GetEventMissile.StartDist - GetEventMissile.CurrentDist
            endif
            
            //calculate the new x/y/z coordinates
            set GetEventMissile.X = GetEventMissile.X + GetEventMissile.sp * GetEventMissile.cs // set x = x + speed * cos
            set GetEventMissile.Y = GetEventMissile.Y + GetEventMissile.sp * GetEventMissile.sn // set y = y + speed * sin
            set zl = Location(GetEventMissile.X,GetEventMissile.Y)
            set GetEventMissile.Z = GetLocationZ(zl)+GetEventMissile.StartZ
            call RemoveLocation(zl)
            
            //calculate the absolute value of the distance remaining
            if dist < 0 then
                set dist = -dist
            endif
            
            //if the distance remaining is less than the speed of the missile then...
            if dist < GetEventMissile.sp then
                //tell the user the missile has reached its target and...
                set GetEventMissile.TargetReached = true
                set GetEventMissile.OnTarget = true
                //calculate the new x/y/z coordinates
                set GetEventMissile.X = GetEventMissile.TargetX
                set GetEventMissile.Y = GetEventMissile.TargetY
                set zl = Location(GetEventMissile.X,GetEventMissile.Y)
                set GetEventMissile.Z = GetLocationZ(zl)+GetEventMissile.StartZ
                call RemoveLocation(zl)
                //run the onHit actions, destroying the missile if they return false
                if not GetEventMissile.ma.onHit() and GetEventMissile.ma != 0 then
                    call GetEventMissile.destroy()
                endif
                set GetEventMissile.OnTarget = false
            endif
            
            //if the missile is collideable, check for collision
            if GetEventMissile.Radius > 0.00 and ( (GetEventMissile.TargetReached == false and GetEventMissile.Homing == true) or (GetEventMissile.TargetReached == true and GetEventMissile.Homing == false) ) then
                //enumerate the units within range of the missile, which aren't missiles
                call GroupEnumUnitsInRange(GetEventMissile.hu, GetEventMissile.X, GetEventMissile.Y, GetEventMissile.Radius, Conds)
                //if the enumeration picked up any units, then...
                if FirstOfGroup(GetEventMissile.hu) != null then
                    //run the onHit actions, and if they return false then...
                    if not GetEventMissile.ma.onHit() and GetEventMissile.ma != 0 then
                        //destroy the missile
                        call GetEventMissile.destroy()
                    endif
                    //add the hit units to the hit units group so the system doesn't falsely
                    //think they're hit more than once
                    call AddGroup(GetEventMissile.hg, GetEventMissile.hu)
                endif
            endif
            
            //move the missile.
            call SetUnitX(GetEventMissile.Dummy, GetEventMissile.X)
            call SetUnitY(GetEventMissile.Dummy, GetEventMissile.Y)
            call SetUnitFlyHeight(GetEventMissile.Dummy, GetEventMissile.Z,0)
            
            //run the onLoop actions, destroying the missile if they return false
            if GetEventMissile.ma != 0 then
                if not GetEventMissile.ma.onLoop() then
                    call GetEventMissile.destroy()
                endif
            endif
            
            set i = i + 1
        endloop
    endfunction
    
    struct Missile
        unit                Dummy = null            //the actual missile unit
        string              fs = ""                 //the model path of the missile
        effect              SFX = null              //the actual model
        
        unit                Caster = null           //missile caster or jumper
        unit                Target = null           //homing target
        
        boolean             dm = false              //dummy, or preplaced unit?
        boolean             Homing = false          //homing?
        real                Radius = 0.             //radius for collision
        
        real                StartX = 0.             //start x
        real                StartY = 0.             //start y
        real                StartZ = 0.             //start z
        real                X = 0.                  //current x
        real                Y = 0.                  //current y
        real                Z = 0.                  //current z
        real                TargetX = 0.            //target x
        real                TargetY = 0.            //target y
        
        real                sn = 0.                 //sin
        real                cs = 0.                 //cos
        real                StartDist = 0           //starting distance from the target
        real                CurrentDist = 0         //distance from target
        
        real                sp = 0.                 //speed
        
        group               hu = null               //the units hit this loop
        group               hg = null               //units which have already been hit
        
        boolean             TargetReached = false   //target reached?
        boolean             OnTarget = false        //on target now?
        
        MissileActions      ma = 0                  //the onHit and onLoop actions
        
        integer             data = 0                //attached data
        integer             id = 0                  //the array index of the missile
        
        //internal create method
        private static method coreCreate takes real radius, MissileActions ma returns Missile
            local Missile d = Missile.allocate()
            
            set d.hg = CreateGroup()
            set d.hu = CreateGroup()
            set d.Radius = radius
            set d.ma = ma
            
            return d
        endmethod
        
        //creates a new missile with the given model
        static method create takes string sfx, real x, real y, real z, real radius, MissileActions actions returns Missile
            local Missile d = Missile.coreCreate(radius, actions)
            
            set d.fs = sfx
            set d.StartX = x
            set d.StartY = y
            set d.StartZ = z
            set d.X = x
            set d.Y = y
            set d.Z = z
            set d.dm = true
            
            return d
        endmethod
        
        //creates a missile from an existing unit
        static method createFromUnit takes unit u, real radius, MissileActions actions returns Missile
            local Missile d = Missile.coreCreate(radius, actions)
            
            set d.Dummy = u
            set d.StartX = GetUnitX(u)
            set d.StartY = GetUnitY(u)
            set d.StartZ = GetUnitFlyHeight(u)
            set d.X = d.StartX
            set d.Y = d.StartY
            set d.Z = d.StartZ
            
            return d
        endmethod
        
        method operator HitUnits takes nothing returns group
            local group g = CreateGroup()
            set bj_groupAddGroupDest = g
            call ForGroup(.hu, function GroupAddGroupEnum)
            return g
        endmethod
        
        method operator Speed takes nothing returns real
            return .sp * FPS
        endmethod
        
        method operator Speed= takes real speed returns nothing
            set .sp = speed / FPS
        endmethod
        
        //Internal function to fire the missile
        private method fire takes unit caster, real speed returns nothing
            local real angle = Atan2(.TargetY - .Y, .TargetX - .X)
            local real dx = .TargetX - .X
            local real dy = .TargetY - .Y
            
            set .sn = Sin(angle)
            set .cs = Cos(angle)
            set .StartDist = SquareRoot(dx * dx + dy * dy)
            
            if .Dummy == null then
                set .Dummy = CreateUnit(OWNER, DUMMY_ID, .X, .Y, angle * bj_RADTODEG)
                set .SFX = AddSpecialEffectTarget(.fs, .Dummy, "origin")
            endif
            
            call SetUnitFlyHeight(.Dummy,.Z,0)
            
            set .Caster = caster
            set .sp = speed / FPS
            
            call GroupAddUnit(Missiles, .Dummy)
            
            set Dat[Index] = this
            set .id = Index
            set Index = Index + 1
            
            if Index == 1 then
                call TimerStart(Tim, 1./FPS, true, function Execute)
            endif
        endmethod
        
        //fires the missile at a point!
        method fireFixed takes unit caster, real targetX, real targetY, real speed returns nothing
            set .Homing = false
            
            set .TargetX = targetX
            set .TargetY = targetY
            
            call .fire(caster, speed)
        endmethod
        
        //fires the missile at a unit!
        method fireTarget takes unit caster, unit target, real speed returns nothing
            set .Homing = true
            set .Target = target
            
            set .TargetX = GetUnitX(.Target)
            set .TargetY = GetUnitY(.Target)
            
            call .fire(caster, speed)
        endmethod
        
        method onDestroy takes nothing returns nothing
            set Index = Index - 1
            set Dat[.id] = Dat[Index]
            set Dat[.id].id = .id
            
            call .ma.destroy()
            
            call GroupClear(.hg)
            call DestroyGroup(.hg)
            call DestroyGroup(.hu)
            
            if .SFX != null then
                call DestroyEffect(.SFX)
            endif
            
            call GroupRemoveUnit(Missiles, .Dummy)
            
            if .dm then
                call KillUnit(.Dummy)
            endif
        endmethod
    endstruct
    
    private function Init takes nothing returns nothing
        set Conds = Filter(function CondsFunc)
    endfunction
endlibrary

Now, it's its add-on . . .
The add-on (Custom Missile) is editted from Element of Water's add-on
I add the same things as above
The most brilliant thing here is the missile-parabola, it's not only change the height of the missile, it also change its Z-facing (an object facing to a Z coordinate), making the missile extremely smooth
But there's still some flaws,
1. I don't know how to make a filter/function as an argument [highlight](EDIT: SOLVED)[/code]
2. If you took a closer look, the missile use 0 degree Z-facing during its creation (made to parabola Z-facing in onLoop functions); and we already knew the System's prob . . . Now how can we fix this?

The Code
JASS:
library CustMissile needs Missile
    globals
        private boolexpr array actlib[8190]
        private boolexpr act
        
        private group ENUM_GROUP = CreateGroup()
        private real Damage = 0.00
        private boolean destroy
    endglobals
    
    private struct DamageData extends MissileActions
        real damage
        real aoe
        real z
        
        method getz takes nothing returns real
            local location l=GetUnitLoc(GetEventMissile.Dummy)
            local real z= GetLocationZ(l)
            call RemoveLocation(l)
            return z
        endmethod
        
        method onHit takes nothing returns boolean
            local unit target = null
            
            if .aoe <= 0 then
                if GetEventMissile.OnTarget then
                    set target = GetEventMissile.Target
                    set destroy=false
                else
                    set target = FirstOfGroup(GetEventMissile.HitUnits)
                endif
                
                if target != null then
                    call UnitDamageTarget(GetEventMissile.Caster, target, .damage, false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
                endif
                
                set target = null
            else
                if GetEventMissile.OnTarget then
                    set destroy=false
                endif
                set Damage = .damage
                call GroupEnumUnitsInRange(ENUM_GROUP, GetEventMissile.X, GetEventMissile.Y, .aoe, act)
            endif
            
            return destroy
        endmethod
        
        method onLoop takes nothing returns boolean
            call SetUnitFlyHeight(GetEventMissile.Dummy, .getz()+.z, 0.00)
            return true
        endmethod
    endstruct
    
    private struct ParabolaData extends MissileActions
        real arc
        unit target = null
        real damage
        real aoe
        real z
        real x1
        real x2
        real y1
        real y2
        
        method parabola takes nothing returns real
            local location l1=GetUnitLoc(GetEventMissile.Target)
            local location l2=GetUnitLoc(GetEventMissile.Dummy)
            local real z0= GetLocationZ(l1)
            local real z1= GetLocationZ(l2)
            local real dx = GetEventMissile.TargetX - GetEventMissile.StartX
            local real dy = GetEventMissile.TargetY - GetEventMissile.StartY
            local real h= .arc*SquareRoot(dx * dx + dy * dy)
            local real d= SquareRoot(dx * dx + dy * dy)
            local real x= GetEventMissile.CurrentDist
            call RemoveLocation(l1)
            call RemoveLocation(l2)
            return 4 * h * x * (d -x) / (d * d) + x * (z1-z0) / d + z0
        endmethod
        
        method parabolafacing takes nothing returns nothing
            local integer zangle
            set .x1 = .x2
            set .x2 = GetUnitX(GetEventMissile.Dummy)
            set .y1 = .y2
            set .y2 = .parabola()+.arc
            set zangle = R2I(Atan2(.y2 - .y1, .x2 - .x1) + 90.5)
            
            if zangle >= 180 then
                set zangle = 179
            elseif zangle < 0 then
                set zangle = 0
            endif
            
            call SetUnitAnimationByIndex(GetEventMissile.Dummy, zangle)
       endmethod
        
        method onHit takes nothing returns boolean
            if .aoe <= 0 and .target != null then
                call UnitDamageTarget(GetEventMissile.Caster, .target, .damage, false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_MAGIC, null)
            else
                set Damage = .damage
                call GroupEnumUnitsInRange(ENUM_GROUP, GetEventMissile.X, GetEventMissile.Y, .aoe, act)
            endif
            
            return false
        endmethod
        
        method onLoop takes nothing returns boolean
            call SetUnitFlyHeight(GetEventMissile.Dummy, .parabola()+.z, 0.00)
            call .parabolafacing()
            return true
        endmethod
    endstruct
        
    function CreateTargetMissile takes unit caster, unit target, string sfx, real x, real y, real z, real speed, real damage, real radius, real aoe, real arc, code acode, boolean boold returns nothing
        local ParabolaData d0
        local DamageData   d1
        local MissileActions d
        local Missile m
        set destroy=boold
        set act=Filter(acode)
        if arc <= 0. then
            set d1 = DamageData.create()
            set d1.z = z
            set d1.damage = damage
            set d1.aoe = aoe
            
            set d = d1
        else
            set radius = 0.
            set d0 = ParabolaData.create()
            set d0.arc = arc
            set d0.x1 = x
            set d0.x2 = x
            set d0.y1 = arc
            set d0.y2 = arc
            set d0.z = z
            set d0.target = target
            set d0.damage = damage
            set d0.aoe = aoe
            
            set d = d0
        endif
        set m = Missile.create(sfx, x, y, z, radius, d)
        call m.fireTarget(caster, target, speed)
    endfunction
    
    function CreatePointMissile takes unit caster, real targetX, real targetY, string sfx, real x, real y, real z, real speed, real damage, real radius, real aoe, real arc, code acode, boolean boold returns nothing
        local ParabolaData d0
        local DamageData   d1
        local MissileActions d
        local Missile m
        set destroy=boold
        set act=Filter(acode)
        if arc <= 0. then
            set d1 = DamageData.create()
            set d1.z = z
            set d1.damage = damage
            set d1.aoe = aoe
            
            set d = d1
        else
            set radius = 0.
            set d0 = ParabolaData.create()
            set d0.arc = arc
            set d0.x1 = x
            set d0.x2 = x
            set d0.y1 = arc
            set d0.y2 = arc
            set d0.z = z
            set d0.damage = damage
            set d0.aoe = aoe
            
            set d = d0
        endif
        set m = Missile.create(sfx, x, y, z, radius, d)
        call m.fireFixed(caster, targetX, targetY, speed)
    endfunction    
endlibrary

Fixes and Rewrites are gladly welcome exchanged with reps
Thanks for your helps ^_^
 
Last edited:
provide a test map on why this should not work...

and code should use globals, for example;
JASS:
method getz takes nothing returns real
   local location l=GetUnitLoc(GetEventMissile.Dummy)
   local real z= GetLocationZ(l)
   call RemoveLocation(l)
   return z
endmethod

should be;
JASS:
method getz2 takes nothing returns real
   call MoveLocation(LOC, GetUnitX(GetEventMissile.Dummy), GetUnitY(GetEventMissile.Dummy))
   return GetLocationZ(LOC)
endmethod
 
Status
Not open for further replies.
Top