| 01-21-2009, 06:54 PM | #1 |
I recently made a nova spell since i needed one ...then i started optimizing it ...e.g. changing the code so that it uses only 1 timer, and since it wasnt much effort this here turned out ... What it does: It casts a wave-spell (like shockwave or breath of fire) circular around the caster, then checks (timer) when the units around the caster will be hit and then applies damage and additional effects (optional). Any criticsm or any suggestions for improvements ? JASS:library NovaSys initializer Init requires LinkedList //// Nova ...by Akolyt0r // Requirements: // * JassNewGenPack // * LinkedList script by Ammorth // * dummy unit // // Features: // * pretty customizable Novas // * uses only 1 global timer, which doesnt need a small timout (even 0.2 would still look ok) // // How to use: // * import the dummy unit // * check that the dummy-unit rawcode in the config section is correct // * create a custom wave ability (e.g based on shockwave or breathonfire) // for each different looking Nova you want // ...you should base them all on the same ability since one orderstring will be used for all Novas // those nova abilities should not deal any damage, since the damage will be dealt by this NovaSys (damage of those abilities would not be accurate, units which are near to the caster would get more damage than units which are more far away) // * check that the waveorder-string in the config section fits to your wave-abilities // * create custom single target abilites which will be applied to units hit by the nova // * Now you are ready to call it...the function takes following parameters: // // unit caster // well ...the caster // integer spellid // the rawcode of the casters ability.. // real damage // the damage, will be multiplied by the level of the casters ability // integer waveid // the rawcode of the custom wave ability // real radius // the radius of the Nova // //(= radius of the wave ability plus some bonus, because thos wave models tend to go farther) // real missilespeed //its missilespeed // real debugbonusdist //since most wavemodels start a little in front of the caster // //i added this ... experiement with this value a little, until the damage // //and the nova hit the units synchronous (you can adjust missilespeed aswell) // integer waves //the ammount of waves created (should divide 360) too high values will cause lag // //try something between 10 and 24 // // integer perTargetId //rawcode of an ability which will be cast units which were hit by the Nova // string perTargetOrder //orderstring to this ability // //you can use 0,null as parameter for both if you only want the damage and no other effect // // Example of Use: // call NovaSys_NovaCreate(GetTriggerUnit(),GetSpellAbilityId(),100,'A001',725,500,75,'A002',"slow") // //================================================= // CONFIG SECTION // globals private constant integer dummyid='u000' private constant string waveorder="breathoffire" private constant real TIME_OUT = 0.1 private constant integer MAX_INSTANCES=250 endglobals // FILTERFUNCTION private function NovaEffectFilter takes unit u, unit caster returns boolean return (IsPlayerEnemy(GetOwningPlayer(u),GetOwningPlayer(caster))and(GetUnitTypeId(u)!=dummyid)and GetWidgetLife(u)>0. ) endfunction //================================================= //////// END CONFIGURATION /////////////// ////// Do Not Edit Below //////// //// Unless you know what ur doing private struct Nova unit caster group alreadyHit real elapsed real cx real cy real nspeed real rad real bdist real dam integer abilvl unit cdummy string hitEffectString static method create takes unit u,real damage,integer spellid, real radius,real speed, real debugbonusdist,string perTargetOrder returns Nova local Nova n=Nova.allocate() set n.caster=u set n.cx=GetUnitX(u) set n.cy=GetUnitY(u) set n.alreadyHit=CreateGroup() set n.elapsed=0 set n.nspeed=speed set n.rad=radius set n.dam=damage set n.abilvl=GetUnitAbilityLevel(u,spellid) set n.bdist=debugbonusdist set n.hitEffectString=perTargetOrder return n endmethod private method onDestroy takes nothing returns nothing call DestroyGroup(.alreadyHit) set .caster=null set .cdummy=null endmethod endstruct globals private List NovaList private Nova TMP private timer NovaTimer private group g=CreateGroup() endglobals private function NovaEffectCallback takes nothing returns boolean local Nova n=TMP local unit u=GetFilterUnit() local real dist = 0 local unit dummy local real dx=GetUnitX(u) - n.cx local real dy=GetUnitY(u) - n.cy set dist = SquareRoot(dx * dx + dy * dy) if dist<n.rad and dist<(n.bdist+n.elapsed*n.nspeed) and IsUnitInGroup(u,n.alreadyHit)==false then if NovaEffectFilter(u,n.caster) then call GroupAddUnit(n.alreadyHit,u) call UnitDamageTarget(n.caster,u,n.dam*n.abilvl,true,true,ATTACK_TYPE_NORMAL ,DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS) if n.hitEffectString!=null then call IssueTargetOrder(n.cdummy,n.hitEffectString,u) endif endif endif set dummy=null set u=null return false endfunction private function NovaTimerCallback takes nothing returns nothing local integer i=1 local Link l=0 local Nova n loop exitwhen i>NovaList.size if l==0 then set l=NovaList.last else set l=l.prev endif set n=l set n.elapsed=n.elapsed+TIME_OUT set TMP=n call GroupEnumUnitsInRange(g,n.cx,n.cy,n.rad,Condition(function NovaEffectCallback)) if (n.elapsed>n.rad/n.nspeed)then call l.destroy() call n.destroy() endif set i=i+1 endloop if NovaList.size==0 then call PauseTimer(GetExpiredTimer()) endif endfunction public function NovaCreate takes unit cast,integer spellid,real damage,integer waveid, real radius,real missilespeed, real debugbonusdist,integer waves, integer perTargetId, string perTargetOrder returns nothing local integer i=0 local player p local unit dummy local real x local real y local Nova n=Nova.create(cast,damage,spellid,radius,missilespeed,debugbonusdist,perTargetOrder) set p=GetOwningPlayer(cast) call Link.create(NovaList,n) loop exitwhen i>360 set x=50*Cos(i*0.017) set y=50*Sin(i*0.017) set dummy=CreateUnit(p,dummyid,n.cx-x,n.cy-y,0) call UnitApplyTimedLife(dummy,'BTLF',0.3) call UnitAddAbility(dummy,waveid) call IssuePointOrder(dummy,waveorder,n.cx+x,n.cy+y) set i=i+360/waves endloop //Create Dummy for Nova-Hit-Effect-Cast (later used) if(perTargetId != null)then set n.cdummy=CreateUnit(p,dummyid,n.cx,n.cy,0) call UnitRemoveAbility(n.cdummy,'Amov') call UnitAddAbility(n.cdummy,perTargetId) call SetUnitAbilityLevel(n.cdummy,perTargetId,n.abilvl) call UnitApplyTimedLife(n.cdummy,'BTLF',n.rad/n.nspeed+TIME_OUT) endif if NovaList.size <= 1 then call TimerStart(NovaTimer,TIME_OUT,true,function NovaTimerCallback) endif //cleanup set dummy=null set p=null endfunction private function Init takes nothing returns nothing set NovaList=List.create() set NovaTimer=CreateTimer() endfunction endlibrary EDIT: who *** **** changed the thread title xDDD |
| 01-25-2009, 09:18 PM | #2 |
bump...4days currently this uses LinkedList, but i think since i worked a lil with "single timer ..blah ...loop trough structarrays" ...i could be able to code this without linked lists ... That would be better than usage of Linked Lists or ? ..since they are kinda slow ?! EDIT: What you think of providing the user the option to create and use a custom function for damage/endeffects instead of directly implementing the per target spell/damage in the system ? |
| 01-25-2009, 11:31 PM | #3 |
Use comparison of squares instead of Squareroot. I.E. check if the sum of the squares of dx and dy are greater than the square of the distance you are checking. Also, the code is pretty chunky, I'd recommend indenting those variable declarations by at least one space. Use TimerUtils or that global list method you mentioned, I think it'd make things simpler. EDIT: IsUnitInRangeXY would be even better for range confirmation because it factors in collision sizes. |
| 01-25-2009, 11:47 PM | #4 |
hmm ..yea..i will see ... But i will definitily not get TimerUtils ..i had them already in the stage of the system before this one ... Timer Utils dont make sense at all when i got only 1 timer ... ..hmm before i go over it and redo it i would like to know your opinions about my questions in the thread above...(the edit)..since i dont want to redo it too often ...i want to do all in one go ...whatever .. it would make the system extremly customizeable, however it would be more difficult to use for beginners ... |
| 01-26-2009, 12:11 AM | #5 |
As far as I can tell, systems are progressive and like any other script type. They can constantly be updated and or optimized. It depends on how flexible you want the system, whether or not people should be able to implement their own OnDamage functions. If you did so, operator overloading would be essential to the process, like in xe. Again it's up to you dude. |
| 01-26-2009, 12:05 PM | #6 |
@Zerzax ..i dont see where i would need custom operators here ..?! ============= stage 1 done ... doesnt use linked list anymore ....custom onTarget function implemented.. todo: maybe custom filterfunction dunno ... and group recycling .. critics ? JASS:library NovaSys globals private constant integer DUMMY_ID = 'u000' private constant string WAVE_ORDER = "breathoffire" private constant real TIME_OUT = 0.1 private constant real ADUMMY_LIFETIME = 5. private constant boolean CREATE_ADUMMY_Preset = false endglobals //================================================= //////// END CONFIGURATION /////////////// ////// Do Not Edit Below //////// //// Unless you know what ur doing private interface eventHandler method onUnitHit takes unit hitTarget, unit caster, unit afterEffectDummy returns nothing defaults nothing endinterface struct Nova extends eventHandler //public membervariables boolean createADummy=CREATE_ADUMMY_Preset //private membervariables private unit caster private group alreadyHit private real elapsed private real cx private real cy private real nspeed private real rad private boolean active=false private static Nova instance private static code timerCallback private static boolexpr enumFilter private static timer T private static group g private static Nova array V private static integer N=0 static method create takes unit u, integer waveid, real radius, real speed, integer waves returns Nova local Nova this=Nova.allocate() local integer i=0 local player p local unit dummy local real x local real y set this.caster = u set this.cx = GetUnitX(u) set this.cy = GetUnitY(u) set this.alreadyHit = CreateGroup() set this.elapsed = 0 set this.nspeed = speed set this.rad = radius set this.active = true set this.V[this.N]=this set this.N=this.N+1 set p=GetOwningPlayer(u) loop exitwhen i>360 set x=50*Cos(i*0.017) set y=50*Sin(i*0.017) set dummy=CreateUnit(p,DUMMY_ID,this.cx-x,this.cy-y,0) call UnitApplyTimedLife(dummy,'BTLF',0.3) call UnitAddAbility(dummy,waveid) call IssuePointOrder(dummy,WAVE_ORDER,this.cx+x,this.cy+y) set i=i+360/waves endloop if(.N==1) then call TimerStart(.T, TIME_OUT, true, .timerCallback ) //debug call BJDebugMsg("Timer Started") endif //cleanup set dummy=null set p=null return this endmethod private method onDestroy takes nothing returns nothing call DestroyGroup(.alreadyHit) endmethod private static method NovaTimerCallback takes nothing returns nothing local integer i = 0 local integer c = 0 local Nova this loop exitwhen i == .N set this=.V[i] set this.elapsed=this.elapsed+TIME_OUT if (this.active==false) then call this.destroy() else set Nova.instance=this call GroupEnumUnitsInRange(Nova.g,this.cx,this.cy,this.rad,.enumFilter) if(this.elapsed>this.rad/this.nspeed)then set this.active=false endif set .V[c]=this set c=c+1 endif set i=i+1 endloop set .N=c if(c==0) then call PauseTimer(.T) //debug call BJDebugMsg("Timer Paused") endif endmethod private static method NovaEffectCallback takes nothing returns boolean local Nova this=.instance local unit dummy local unit u=GetFilterUnit() if IsUnitInRangeXY(u,this.cx,this.cy,this.elapsed*this.nspeed) and IsUnitInGroup(u,this.alreadyHit)==false and GetUnitTypeId(u)!=DUMMY_ID and GetWidgetLife(u) > 0. then call GroupAddUnit(this.alreadyHit,u) //call BJDebugMsg("Impact! "+I2S(.N)+" "+GetUnitName(u)) if(this.createADummy)then set dummy=CreateUnit(GetOwningPlayer(this.caster),DUMMY_ID,this.cx,this.cy,270) call UnitApplyTimedLife(dummy,'BTLF',ADUMMY_LIFETIME) endif call this.onUnitHit(u,this.caster,dummy) set dummy=null endif set u=null return false endmethod static method onInit takes nothing returns nothing set Nova.g=CreateGroup() set Nova.T=CreateTimer() set Nova.enumFilter=Condition(function Nova.NovaEffectCallback) set Nova.timerCallback=function Nova.NovaTimerCallback endmethod endstruct endlibrary EDIT: added the possibility to automatically creating a dummy caster for endeffects ... |
| 01-26-2009, 12:12 PM | #7 | |
Quote:
That's faster than square root? |
| 01-26-2009, 12:37 PM | #8 |
sqareroot is pretty slow ..so ..i think yes ...in the last version i use IsUnitInRangeXY ...which works quite well.. |
| 01-26-2009, 03:35 PM | #9 |
Meh, I think you should be using units as projectiles instead of dummycasting wave spells. |
| 01-26-2009, 03:53 PM | #10 | |
Quote:
|
| 01-26-2009, 05:12 PM | #11 |
hmm...yes it would be more flexible (although its already pretty flexible...), but also it will decrease performance ... hmm...i would have to change quite a lot, since i wouldnt have to predict anymore where the nova currently is, i could just enum the units in range of the wavedummies * Well ..i think i will make a version using dummy projectiles instead of waveabilities ...and then i hope i will see which one is better :) EDIT: But whats your opinion about creating dummies to apply effects on the units ...should i do that ? ...or should i leave it to the user itself ... I am currently in favor of the second possibility ..since the users of this one already have to have some vJass experience, so they should be able to create a dummy to cast a spell on target unit themselves... EDIT2: hmm .. i think (*) still would be better ...needs a lot less groupenums ..at cost of some more math ... |
| 01-28-2009, 06:58 PM | #12 |
Update (requires xebasic now): JASS://////////////////////////////////////////////////////// //* * * * * * * * * * * * * * * * * * * * * * * * * * * //* NoVa System v1.0 //* by Akolyt0r //* //* requires xebasic by Vexorian //* //* its HIGHLY recommended to read the Documentation //* AND to check out the Examples included in this Map //* (especially when you dont have a clue what that //* extending stuff is all about). //* //* Credits: //* Vexorian for dummy.mdx //* Anitarf,Zerzax,... for feedback/some help //* //* * * * * * * * * * * * * * * * * * * * * * * * * * * //////////////////////////////////////////////////////// library NovaSys requires xebasic globals // for more info to these, check out the documentation private constant integer MAX_WAVES = 50 private constant real DEFAULT_HEIGHT = 65 private constant real DEFAULT_CSIZE = 50 //================================================= //////// END CONFIGURATION /////////////// ////// Do Not Edit Below //////////// //// Unless you know ////////// // what ur doing //////// private constant real PI2 = 6.28 endglobals private interface eventHandler method onUnitHit takes unit hitTarget, unit caster returns nothing defaults nothing method onTimerLoop takes unit hitTarget, unit caster returns nothing defaults nothing endinterface struct Nova extends eventHandler //public membervariables real csize=DEFAULT_CSIZE //private membervariables private unit caster private group alreadyHit private real elapsed private real cx private real cy private real nspeed private real rad private boolean active=false private unit array missiles[MAX_WAVES] private effect array fx[MAX_WAVES] private integer waves private static Nova instance private static code timerCallback private static boolexpr enumFilter private static timer T private static group g private static Nova array V private static integer N=0 static method create takes unit caster, string fxpath, real radius, real speed, integer waves returns Nova local Nova this=Nova.allocate() local integer i=0 local player p local unit u local real x local real y set this.caster = caster set this.cx = GetUnitX(caster) set this.cy = GetUnitY(caster) if this.alreadyHit==null then set this.alreadyHit = CreateGroup() endif set this.elapsed = 0 set this.nspeed = speed set this.rad = radius set this.active = true set this.V[this.N] = this set this.N = this.N+1 set this.waves = waves loop exitwhen i>waves set u=CreateUnit(GetOwningPlayer(caster),XE_DUMMY_UNITID,this.cx,this.cy,i*360/waves) call UnitAddAbility(u,XE_HEIGHT_ENABLER) call UnitRemoveAbility(u,XE_HEIGHT_ENABLER) call SetUnitFlyHeight(u,DEFAULT_HEIGHT,0) set this.fx[i]=AddSpecialEffectTarget(fxpath,u,"origin") set this.missiles[i]=u set i=i+1 endloop if(.N==1) then call TimerStart(.T, XE_ANIMATION_PERIOD, true, .timerCallback ) //debug call BJDebugMsg("Timer Started") endif //cleanup set u=null return this endmethod private method onDestroy takes nothing returns nothing call GroupClear(.alreadyHit) endmethod method operator scale= takes real s returns nothing local integer j=0 loop exitwhen j>this.waves call SetUnitScale(this.missiles[j],s,s,s) set j=j+1 endloop endmethod private static method NovaTimerCallback takes nothing returns nothing local integer i = 0 local integer c = 0 local integer j local unit u local real x local real y local real ang local real speed local Nova this loop exitwhen i == .N set this=.V[i] set this.elapsed=this.elapsed+XE_ANIMATION_PERIOD if (this.active==false) then call this.destroy() else set j=0 loop exitwhen j>this.waves set speed=XE_ANIMATION_PERIOD*this.nspeed set ang=j*PI2/this.waves set u=this.missiles[j] set x=speed*Cos(ang) set y=speed*Sin(ang) call SetUnitPosition(u,GetUnitX(u)+x,GetUnitY(u)+y) set j=j+1 endloop set Nova.instance=this call GroupEnumUnitsInRange(Nova.g,this.cx,this.cy,this.rad+this.csize+XE_MAX_COLLISION_SIZE,.enumFilter) if(this.elapsed>this.rad/this.nspeed)then set this.active=false set j=0 loop exitwhen j>this.waves call DestroyEffect(this.fx[j]) call SetUnitExploded(this.missiles[j], true) call KillUnit(this.missiles[j]) set j=j+1 endloop endif set .V[c]=this set c=c+1 endif set i=i+1 endloop set .N=c if(c==0) then call PauseTimer(.T) //debug call BJDebugMsg("Timer Paused") endif endmethod private static method NovaEffectCallback takes nothing returns boolean local Nova this=.instance local unit u=GetFilterUnit() if IsUnitInRangeXY(u,this.cx,this.cy,this.csize+RMinBJ(this.elapsed*this.nspeed,this.rad)) and IsUnitInGroup(u,this.alreadyHit)==false and GetUnitTypeId(u)!=XE_DUMMY_UNITID and GetWidgetLife(u) > 0. then call GroupAddUnit(this.alreadyHit,u) call this.onUnitHit(u,this.caster) endif set u=null return false endmethod static method onInit takes nothing returns nothing set Nova.g=CreateGroup() set Nova.T=CreateTimer() set Nova.enumFilter=Condition(function Nova.NovaEffectCallback) set Nova.timerCallback=function Nova.NovaTimerCallback endmethod endstruct endlibrary pretty finished i think ...however i am not quite sure about that stuff with collision size and enums .... GroupEnums wont find units with a quite big collision size, which are close to the end of the enumrange, or something like that ... and what will IsUnitInRangeXY return for those units ? true or false ? Hmm ..i think i will submit this soon ... I might make another version additionally using xefx aswell (for the sake of modularity). |
