| 09-01-2007, 12:52 AM | #1 |
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 : 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.. 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 |
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 |
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 |
I rewrote the system. I am very new to interfaces, so please point out things you think I did wrong.. 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 |
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 | ||
Oh, I'm glad to hear I got the interface thingy right =) Quote:
Oh, I just realized that :O I'll get to that ASAP, I guess coding in 2 am is bad :p Quote:
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 |
