HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Need Advice on Missile System

09-01-2007, 12:52 AM#1
Toink
So I was starting to write up my own Spell System, and the first thing to come to my mind was spell missiles like Storm Bolt and Acid Bomb, but have the ability to do alot of things with them than you normally won't see in Warcraft 3, but all of the functions that manipulate the missile are still in progress. The Missile System is in one if its earliest stage, the core movement engine is done and registering an event when a missile hits a unit is still in progress.

I need some advice or workarounds with registering the events, here is the code, I will explain further :

Collapse JASS:
library SpellSystem initializer Init_SpellSystem requires CSCache, CSSafety

struct MissileData
//  Do not customize this or you will screw up the missile's movement
    boolean Destroyed
    boolean Paused
    boolean Reflected
    trigger trig
//
//  These are what you customize
    unit caster
    unit missile
    unit target
    string periodicfxpath
    string attachedfxpath
    effect attachedfx
    real x
    real y
    real x2
    real y2
    real a
    real speed
endstruct


globals
    private trigger array Triggers
    private integer Index =0
endglobals


function GetTriggeringMissile takes nothing returns unit
    return s__MissileData_missile[GetCSData(GetTriggeringTrigger())]
endfunction

function Missile_HitEvent takes nothing returns nothing
local integer i = 0
    loop
        set i = i + 1
        exitwhen i > Index
        call ConditionalTriggerExecute( Triggers[i] )
    endloop
endfunction

function RemoveMissile_HitEvent takes trigger t returns nothing
local integer i = GetCSData(t)
    call CleanAttachedVars(Triggers[i])
    call DestroyTrigger(Triggers[i])
    loop
        exitwhen i > Index
        set i = i+1
        set Triggers[(i-1)] = Triggers[i]
        call AttachInt(Triggers[(i-1)], "index", i)
    endloop
    call CleanAttachedVars(Triggers[Index])
    call DestroyTrigger(Triggers[Index])
    set Index = Index - 1
endfunction

function RegisterMissile_HitEvent takes code Func, code CondFunc returns nothing
local trigger t = CreateTrigger()
    call TriggerAddAction(t, Func)
    call TriggerAddCondition(t, Condition(CondFunc))
    set Index = Index+1
    set Triggers[Index] = t
    call SetCSData(t, Index)
endfunction

function Missile_Condition takes unit u returns boolean
    return GetUnitAbilityLevel(u, 'Avul') == 0 and GetWidgetLife(u) > .405 and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
endfunction

function MoveMissile takes nothing returns nothing
local timer t = GetExpiredTimer()
local MissileData dat = MissileData(GetCSData(t))
    if dat.Destroyed == true or (dat.x == dat.x2 and dat.y == dat.y2) or GetWidgetLife(dat.target) < .405 then
        call CleanAttachedVars(t)
        call ReleaseTimer(t)
        call DestroyEffect(dat.attachedfx)
        call RemoveUnit(dat.missile)
        if not Missile_Condition(dat.target) then
            call MissileData.destroy(dat)
            call DestroyTrigger(dat.trig)
        else
            call ConditionalTriggerExecute(dat.trig)
            call Missile_HitEvent()
        endif
    elseif dat.Paused == false then
        call DestroyEffect(AddSpecialEffectTarget(dat.periodicfxpath, dat.missile, "chest"))
        if dat.Reflected == true and dat.target == null then
           set dat.x2 = dat.x + dat.speed * Cos((dat.a+180) * 0.01745)
           set dat.y2 = dat.y + dat.speed * Sin((dat.a+180) * 0.01745)
        else
            set dat.x2 = GetUnitX(dat.target)
            set dat.y2 = GetUnitY(dat.target)
        endif
        set dat.a = SquareRoot((dat.x-dat.x2) + (dat.y-dat.y2))
        set dat.x = dat.x + dat.speed * Cos(dat.a * 0.01745)
        set dat.y = dat.y + dat.speed * Sin(dat.a * 0.01745)
        call SetUnitFacing(dat.missile, dat.a)
        call SetUnitX(dat.missile, dat.x)
        call SetUnitY(dat.missile, dat.y)
    endif
    set t = null
endfunction

function DestroyMissile takes unit missile returns nothing
    set MissileData(GetUnitUserData(missile)).Destroyed = true
endfunction

function UnpauseMissile takes unit missile returns nothing
    set MissileData(GetUnitUserData(missile)).Paused = false
endfunction

function PauseMissile_Callback takes nothing returns nothing
local timer t = GetExpiredTimer()
    call UnpauseMissile(s__MissileData_missile[GetCSData(t)])
    call ReleaseTimer(t)
    set t = null
endfunction

function ReflectMissileToCaster takes unit missile returns nothing
    set MissileData(GetUnitUserData(missile)).target = MissileData(GetUnitUserData(missile)).caster
endfunction

function ReflectMissileBack takes unit missile returns nothing
    set MissileData(GetUnitUserData(missile)).target = null
    set MissileData(GetUnitUserData(missile)).Reflected = true
    set MissileData(GetUnitUserData(missile)).a = (MissileData(GetUnitUserData(missile)).a+180)
endfunction

function PauseMissile takes unit missile, real dur returns nothing
local timer t = NewTimer()
    set MissileData(GetUnitUserData(missile)).Paused = true
    call SetCSData(t,GetUnitUserData(missile))
    call TimerStart(t,dur, false, function PauseMissile_Callback)
    set t = null
endfunction

function CreateMissile takes MissileData dat, code Func returns nothing
local timer t = NewTimer()
    set dat.attachedfx = AddSpecialEffectTarget(dat.attachedfxpath, dat.missile, "chest")
    set dat.trig = CreateTrigger()
    call TriggerAddAction(dat.trig, Func)
    call SetUnitUserData(dat.missile, dat)

    call SetCSData(t, dat)
    call SetCSData(dat.trig, dat)
    call TimerStart(t, 0.035, true, function MoveMissile)
    set t = null
endfunction


function Init_SpellSystem takes nothing returns nothing
endfunction

endlibrary

That's the whole code, now on to the explanation..

Collapse JASS:
function Missile_HitEvent takes nothing returns nothing
local integer i = 0
    loop
        set i = i + 1
        exitwhen i > Index
        call ConditionalTriggerExecute( Triggers[i] )
    endloop
endfunction

function RemoveMissile_HitEvent takes trigger t returns nothing
local integer i = GetCSData(t)
    call CleanAttachedVars(Triggers[i])
    call DestroyTrigger(Triggers[i])
    loop
        exitwhen i > Index
        set i = i+1
        set Triggers[(i-1)] = Triggers[i]
        call AttachInt(Triggers[(i-1)], "index", i)
    endloop
    call CleanAttachedVars(Triggers[Index])
    call DestroyTrigger(Triggers[Index])
    set Index = Index - 1
endfunction

function RegisterMissile_HitEvent takes code Func, code CondFunc returns nothing
local trigger t = CreateTrigger()
    call TriggerAddAction(t, Func)
    call TriggerAddCondition(t, Condition(CondFunc))
    set Index = Index+1
    set Triggers[Index] = t
    call SetCSData(t, Index)
endfunction

These event-registering functions, I think, need some recoding. But I haven't seen any workaround for registering events like this yet, so I will need some advice.

Basically what it does is loop through a trigger array and execute each trigger. Registering an event just adds a trigger to the array. I felt that I should remove registering events like these in some cases, so I made a function that removes the trigger from the array and then reorganizes the array, to avoid calling a null trigger variable when the event fires off and begins looping and calling triggers with missilehit events. Like for example, you removed the event for a trigger whose index is 20 and the Index var is now 30(meaning 30 triggers have missile events), if the Missile_HitEvent were to fire off again there would be a useless call when it loops.

Soon there will be functions to manipulate missiles, like changing their angles temporarily or permanently, slowing their speed or hastening it, etc,.

The point of the spell system is to make advanced, triggered spells more easily and allow alot of things to happen to them like a War Stomp spell that reflects all nearby missiles, etc,. But in its current state, I cannot do that yet and I have yet to find some workarounds to do fancy things for spells.

So, again, I need some advice on my coding. If there are some things you want to point out, please do so.

~Thank you for your time.
09-01-2007, 12:57 AM#2
grim001
You should be using an "event handler" interface, not triggers...

In fact it seems you don't know what interfaces are yet, so go read about that and see if any applications come to mind for this system.
09-01-2007, 12:59 AM#3
Toink
Well yes, I am not familiar with interfaces.. I'll try to use those after I learn to use them. Thanks for pointing that out :)
09-01-2007, 07:13 AM#4
Toink
I rewrote the system. I am very new to interfaces, so please point out things you think I did wrong..

Collapse JASS:
library MissileSystem initializer InitTrig_Missile_System requires CSCache, CSSafety

//*******************************************************************************
//*
//*              C  O  N  F  I  G  U  R  A  T  I  O  N
//*                      S  E  C  T  I  O  N
//*                 (Basic JASS knowledge required)
//*
//*******************************************************************************

globals
    constant integer DummyUnitId = 'e004' //Rawcode of the dummy unit
endglobals


//*******************************************************************************
//*
//*
//*            S  Y  S  T  E  M    I  N  T  E  R  F  A  C  E
//*      (This is just a reference to the interface and it's variables)
//*     (If you aren't familiar with them and you are making your struct)
//*         (Do not modify this unless you know what you are doing)
//*
//*******************************************************************************
interface Missile
    // Missile's State

    boolean SingleTarget
    boolean Reflected = false
    boolean Paused = false
    boolean Destroyed = false

    real PauseDur = 0.

    // Missile's Unit Datas

    unit source
    unit target
    unit missile

    // Missile's Real Datas

    real speed //Speed is the distance moved in the missile every interval
    real currentd
    real maxdist = 9999 //Default maximum distance a missile can travel
    real angle
    real sourcex
    real sourcey
    real targetx
    real targety

    // Events

    method OnMissileCreate takes unit source, unit missile returns nothing
       //^- You may want to have some special effects and such when a missile is created, use this method for that.
    method OnMissileHit takes unit source, unit missile, unit target returns nothing
       //^- The function called when the missile hits the target unit.
    method OnMissileDestroy takes unit source, unit missile returns nothing 
       //^- Sometimes, you may want different things to happen when a missile is destroyed. Use this method to do whatever you want when the missile is destroyed
endinterface


//********************************************************************************
//*
//*               S  Y  S  T  E  M    E  N  G  I  N  E
//*          (Do not modify unless you know what you are doing)
//*
//*
//********************************************************************************

globals
    private Missile array M
    private Missile array P
    private group PausedMissiles = null
    private integer Index = 0
    private integer PIndex = 0
    private timer T = null
    private timer T2 = null
    private boolean IsMainTimerPaused = true
    private boolean IsMissileHandlerPaused = true
endglobals

private function PausedMissile_Handler takes nothing returns nothing
local integer i = 0
local integer index = 0
    if PIndex == 0 then
        call PauseTimer(T)
        set IsMissileHandlerPaused = true
    else
        loop
            set i = i+1
            exitwhen i > PIndex
            if P[i].PauseDur <= 0. then
                set P[i].Paused = false

                // Reorganizes the list/array of missiles to be looped through, to avoid looping through a null/unused missile.
                loop
                    set index = i
                    exitwhen index > PIndex+1
                    set P[index+1] = P[index]
                    set index = index + 1
                endloop

                set PIndex = PIndex - 1
                set i = i - 1
            else
                set P[i].PauseDur = P[i].PauseDur - 0.05
            endif
        endloop
    endif
endfunction

private function MissileEngine takes nothing returns nothing
local integer i= 0
local integer index= 0
    if Index == 0 then
        call PauseTimer(T)
    else
        loop
            set i = i+1
            exitwhen i > Index
            //Missile State checking, whether it's reflected, paused or in normal state
            if M[i].Paused == false then
                if  M[i].Destroyed == true then
                   call Missile.destroy(M[i])
                   call M[i].OnMissileDestroy(M[i].source, M[i].missile)
                   loop
                       set index = i
                       exitwhen index > Index+1
                       set M[index] = M[(index+1)]
                       set index = index+1
                   endloop
                elseif M[i].Reflected == true and GetWidgetLife(M[i].source) > .405 then
                    set M[i].targetx = GetUnitX(M[i].source)
                    set M[i].targety = GetUnitY(M[i].source)
                    set M[i].angle = SquareRoot((M[i].sourcex - M[i].targetx) + (M[i].sourcey - M[i].targety))
                    set M[i].currentd = M[i].currentd + M[i].speed
                    set M[i].sourcex = M[i].sourcex + M[i].speed * Cos(M[i].angle * 0.01745)
                    set M[i].sourcey = M[i].sourcey + M[i].speed * Sin(M[i].angle * 0.01745)
                    if M[i].targetx == M[i].sourcex and M[i].targety == M[i].sourcey then
                        call M[i].OnMissileHit(M[i].source, M[i].missile, M[i].source)
                    else
                        call SetUnitX(M[i].missile, M[i].sourcex)
                        call SetUnitY(M[i].missile, M[i].sourcey)
                        call SetUnitFacing(M[i].missile, M[i].angle)
                    endif
                else
                    set M[i].targetx = GetUnitX(M[i].source)
                    set M[i].targety = GetUnitY(M[i].source)
                    set M[i].angle = SquareRoot((M[i].sourcex - M[i].targetx) + (M[i].sourcey - M[i].targety))
                    set M[i].currentd = M[i].currentd + M[i].speed
                    set M[i].sourcex = M[i].sourcex + M[i].speed * Cos(M[i].angle * 0.01745)
                    set M[i].sourcey = M[i].sourcey + M[i].speed * Sin(M[i].angle * 0.01745)
                    if M[i].targetx == M[i].sourcex and M[i].targety == M[i].sourcey then
                        call M[i].OnMissileHit(M[i].source, M[i].missile, M[i].target)
                    else
                        call SetUnitX(M[i].missile, M[i].sourcex)
                        call SetUnitY(M[i].missile, M[i].sourcey)
                        call SetUnitFacing(M[i].missile, M[i].angle)
                    endif
               endif
            endif
        endloop
    endif
endfunction

private function StartEngine takes nothing returns nothing
    call TimerStart(T, 0.025, true, function MissileEngine)
    call TimerStart(T2, 0.05, true, function PausedMissile_Handler)
    set IsMainTimerPaused = false
    set IsMissileHandlerPaused = false
endfunction


//===========================================================================
function InitTrig_Missile_System takes nothing returns nothing
local trigger StartupTrigger = CreateTrigger()
    set PausedMissiles = CreateGroup()
    set T = CreateTimer()
    set T2 = CreateTimer()
    call TriggerRegisterTimerEventSingle( StartupTrigger, 1.00 )
    call TriggerAddAction( StartupTrigger, function StartEngine )
endfunction
//===========================================================================


//********************************************************************************
//*
//*
//*               U  S  A  B  L  E    F  U  N  C  T  I  O  N  S
//*          (Functions you can call to modify a missile's speed, etc,.)
//*
//********************************************************************************

function PauseMissile takes Missile m, real dur returns nothing
    set m.Paused = true
    set PIndex = PIndex + 1
    set P[PIndex] = m
    if IsMissileHandlerPaused == true then
        call TimerStart(T, 0.05, true, function PausedMissile_Handler)
    endif
endfunction

function CreateMissile takes Missile dat returns nothing
    set Index = Index+1
    set M[Index] = dat
    if IsMainTimerPaused == true then
        call TimerStart(T, 0.025, true, function MissileEngine)
    endif
    call dat.OnMissileCreate(dat.source, dat.missile)
endfunction

function CreateMissileDummy takes player owner, real x, real y, real face returns unit
    return CreateUnit(owner, DummyUnitId, x, y, face)
endfunction

endlibrary

I'm still not sure if my method works..
09-01-2007, 07:37 AM#5
grim001
The formula for angle is Atan2(y2-y1, x2-x1) in radians, you might want to fix that

Instead of always doing M[i] just set the current missile to a local missile var

Reals will almost never exactly equal each other, your current collision check should never work.

You need to do a distance check to see if distance between the target and the missile is less than the radius of the missile plus the radius of the unit (32 is a good approximation for unit radius). You might also want to incorporate GroupEnum so it can run into things other than the target.

As far as the interface goes, you did that well, but I would separate the internal variables and the user variables into two different structs. The interface becomes "missiledata" or something, and the main "missile" struct contains it.
09-01-2007, 09:07 AM#6
Toink
Oh, I'm glad to hear I got the interface thingy right =)

Quote:
Originally Posted by grim001
You need to do a distance check to see if distance between the target and the missile is less than the radius of the missile plus the radius of the unit (32 is a good approximation for unit radius).

Oh, I just realized that :O I'll get to that ASAP, I guess coding in 2 am is bad :p

Quote:
Originally Posted by grim001
might also want to incorporate GroupEnum so it can run into things other than the target.

Well at first I just wanted "normal" missiles, like Storm Bolt for example. But upgrading it to that kind of missile shouldn't be a problem, thanks.

Hmm, I guess you've cleared up everything for me, thank you very much, repped ofc and... Discussion closed.

<3