| 10-04-2007, 01:10 AM | #1 |
Blaze v1.1 by emjlr3 Explanations:
![]() Spell code:scope Blaze //****************************************************************************************** //* //* Blaze v1.1 //* //* //* A neat ability which throws homing fireballs all around at random enemies and //* deals damage upon collision with them. //* //* Requires: //* - A blaze dummy and blaze abilty in your map, similar to the ones found here //* - This code copied to a trigger in your map //* //****************************************************************************************** globals //Config Options: //General: private constant integer abil_id = 'A001' //blaze ability rawcode private constant real int = .025 //timer interval for effect movement private constant integer dummy_id = 'n001' //blaze dummy rawcode private constant integer max_missiles = 6 //max number of missiles + 1, ever created for an instance of the spell //Twirl Portion: private constant real start_dist = 150. //start distance of missiles before they twirl, away from the caster private constant real end_height = 280. //end height of missiles when they reach the top of the twirl private constant real twirl_time = 1. //length in seconds of the twirl portion of the ability private constant real twirl_speed = 5. //rate of change per timer interval for the angle change of the missiles during the twirl //Homing Portion: private constant real speed = 15. //missile speed per timer interval while homing in on a target private constant boolean new_target = true //whether missiles can get a new target if their original dies while in route private constant real height_diff = 35. //difference between targets current fly height and the height you want the missile to lower/raise to private constant attacktype a_type = ATTACK_TYPE_MAGIC //attack type for damage done private constant damagetype d_type = DAMAGE_TYPE_FIRE //damage type for damage done endglobals private function damage takes integer lvl returns real return lvl*25. //damage done per missile endfunction private function range takes integer lvl returns real return (lvl*50.)+750. //range for missiles to check for targets endfunction private function count takes integer lvl returns integer return 4+lvl //number of missiles created endfunction //****************************************************************************************** //DON"T TOUCH PAST THIS POINT, UNLESS YOU PAIN FOR DEATH! globals //needed globals: private unit U = null private group G = CreateGroup() private timer Tball = CreateTimer() private integer Total1 = 0 private integer Total2 = 0 private Blaze_data1 array structArray1 private Blaze_data2 array structArray2 public trigger Trigger = CreateTrigger() endglobals //angle between units private function ABU takes unit a, unit b returns real return bj_RADTODEG * Atan2(GetUnitY(b) - GetUnitY(a), GetUnitX(b) - GetUnitX(a)) endfunction //distance between units private function DBU takes unit a, unit b returns real return SquareRoot(Pow(GetUnitX(b) - GetUnitX(a), 2) + Pow(GetUnitY(b)-GetUnitY(a),2)) endfunction //filter for missiles, this can be edited as desired for further configuration private function Filt takes nothing returns boolean return GetWidgetLife(GetFilterUnit())>.405 and IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(U))==true endfunction //needed struct 2 struct Blaze_data2 unit ball = null unit targ = null real height = end_height integer lvl = 0 //init struct method Start takes unit ball, integer lvl returns nothing //update missile set .ball = ball //grab first target set .targ = GroupPickRandomUnit(G) set .lvl = lvl endmethod //move missile towards target method Effects takes nothing returns nothing local real ang = ABU(.ball,.targ) call SetUnitX(.ball,GetUnitX(.ball) + speed * Cos(ang * bj_DEGTORAD)) call SetUnitY(.ball,GetUnitY(.ball) + speed * Sin(ang * bj_DEGTORAD)) call SetUnitFacing(.ball,ang) set .height = .height - 10. //this makes it look pretty fluent IMO if .height<GetUnitFlyHeight(.targ)+height_diff then set .height = GetUnitFlyHeight(.targ)+height_diff endif call SetUnitFlyHeight(.ball,.height,0.0) endmethod //update globals method Update takes integer i returns nothing set structArray2[i] = structArray2[Total2] set Total2 = Total2 - 1 endmethod //end struct method onDestroy takes nothing returns nothing //damage target, destroy missile if .targ!=null then call UnitDamageTarget(.ball,.targ,damage(.lvl),false,false,a_type,d_type,null) endif call KillUnit(.ball) endmethod endstruct //needed struct 1 struct Blaze_data1 integer runs = 0 unit tower = null unit array ball[max_missiles] real array ang[max_missiles] real xtower real ytower integer total = 0 real increment = 0. real change = 0. real Hchange = 0. integer total_runs = 0 //initiate all needed struct data method Start takes unit tower returns nothing local integer i = 1 local real x = GetUnitX(tower) local real x2 local real y = GetUnitY(tower) local real y2 local player p = GetOwningPlayer(tower) set .total = count(GetUnitAbilityLevel(tower,abil_id)) set .increment = 360/.total set .total_runs = R2I(twirl_time/int) set .change = start_dist/.total_runs set .Hchange = end_height/.total_runs //loop from 1-total, create balls, store angles loop exitwhen i>.total set .ang[i] = i*.increment set x2 = x + start_dist * Cos(.ang[i] * bj_DEGTORAD) set y2 = y + start_dist * Sin(.ang[i] * bj_DEGTORAD) set .ball[i] = CreateUnit(p,dummy_id,x2,y2,0.) set i = i + 1 endloop //store tower and target set .tower = tower set .xtower = x set .ytower = y endmethod //effects for each run, update angle, move ball, update fly height method Effects takes nothing returns nothing local integer i = 1 local real height = .runs * .Hchange local real distance = start_dist-(.runs*.change) //get closer as we twirl up, eventually meeting in the middle loop exitwhen i>.total set .ang[i] = .ang[i] + twirl_speed call SetUnitX(.ball[i], .xtower + distance * Cos(.ang[i] * bj_DEGTORAD)) call SetUnitY(.ball[i], .ytower + distance * Sin(.ang[i] * bj_DEGTORAD)) call SetUnitFlyHeight(.ball[i],height,0.0) set i = i + 1 endloop endmethod //update globals method Update takes integer i returns nothing set structArray1[i] = structArray1[Total1] set Total1 = Total1 - 1 endmethod //when our struct is destroyed, loop through balls and possibly destroy method onDestroy takes nothing returns nothing local integer i = 1 local Blaze_data2 dat local integer lvl //tower is dead, end if GetWidgetLife(.tower)<.405 then loop exitwhen i>.total call KillUnit(.ball[i]) set i = i + 1 endloop else //tower is not dead, add missiles to new struct, and begin the detonation phase set lvl = GetUnitAbilityLevel(.tower,abil_id) call GroupClear(G) set U = .tower call GroupEnumUnitsInRange(G,.xtower,.ytower,range(lvl),Condition(function Filt)) loop exitwhen i>.total set dat = Blaze_data2.create() call dat.Start(.ball[i],lvl) set Total2 = Total2 + 1 set structArray2[Total2] = dat set i = i + 1 endloop endif endmethod endstruct //filter for attackers private function Conditions takes nothing returns boolean return GetSpellAbilityId()==abil_id endfunction //movement for missiles private function Movement takes nothing returns nothing local integer i = 1 local Blaze_data1 dat1 local Blaze_data2 dat2 loop //loop through all current structs 1 exitwhen i > Total1 set dat1 = structArray1[i] //update our runs total set dat1.runs = dat1.runs + 1 //effects are over, remove struct and clean up if dat1.runs>dat1.total_runs or GetWidgetLife(dat1.tower)<.405 then call dat1.destroy() call dat1.Update(i) set i = i - 1 else //runs are not over, continue with effects call dat1.Effects() endif set i = i + 1 endloop set i = 1 loop //loop through all current structs 2 exitwhen i > Total2 set dat2 = structArray2[i] //target is nothing, or is dead if dat2.targ==null or GetWidgetLife(dat2.targ)<.405 then //if the missile can get another target, try and do so if new_target then call GroupClear(G) set U = dat2.ball call GroupEnumUnitsInRange(G,GetUnitX(dat2.ball),GetUnitY(dat2.ball),range(dat2.lvl),Condition(function Filt)) set dat2.targ = GroupPickRandomUnit(G) //no new target available if dat2.targ==null then call dat2.destroy() call dat2.Update(i) endif //so it runs this missile again, though it is now what was the last missile set i = i + 1 else call dat2.destroy() call dat2.Update(i) set i = i - 1 endif //unit is close enough to damage, remove struct and clean up elseif DBU(dat2.ball,dat2.targ)<speed then //distance between missile and target is less then speed, therefore another movement would most likely carry us past the target call dat2.destroy() call dat2.Update(i) set i = i - 1 else //runs are not over, continue with effects call dat2.Effects() endif set i = i + 1 endloop //no more active structs, pause timer if Total1==0 and Total2==0 then call PauseTimer(Tball) endif endfunction //init for missiles private function Actions takes nothing returns nothing local unit u = GetTriggerUnit() local Blaze_data1 dat = Blaze_data1.create() //init struct data, add it to array, start timer if not already done so if Total1==0 and Total2==0 then call TimerStart(Tball,int,true,function Movement) endif call dat.Start(u) set Total1 = Total1 + 1 set structArray1[Total1] = dat set u = null endfunction //=========================================================================== public function InitTrig takes nothing returns nothing call TriggerRegisterAnyUnitEventBJ( Trigger, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( Trigger, Condition( function Conditions ) ) call TriggerAddAction( Trigger, function Actions ) endfunction endscope Updates:
Thoughts? Suggestions? Concerns? Mal contempt? |
| 10-04-2007, 02:24 AM | #3 |
My first concern is the upper limit of how many of these can be out before lag becomes noticeable. No matter how it is made, enough of these is going to kill FPS. EDIT: And for what it's worth, it does look well-presented and cool. |
| 10-04-2007, 03:02 AM | #4 |
could we please begin to be modular when making spells? You are just making things harder if every spell has to use its own gamecache, there is a gamecache file limit, you know... at least don't call the variable cscache... It is great you are using an scope, but you are not taking full advantage of it. When you use an scope you can make the code totally multi instanceable without any effort by just changing the scope name after duplicating the code, in your case there's some things that are redundant although they should work correctly. gg_trg_Blaze is a variable outside the scope, and you don't really need it to be a global you could simply use a local trigger variable to declare the events... Things like _Blaze_ inside a private function's name are pretty redundant as well. I2S(H2I(tower))+"Blaze" : SCOPE_PRIVATE+I2S(H2I(tower)) makes a lot more sense. I seriously think you can/should get rid of gamecache in this one, it is just adding a lot of code bloat, the problem with the lack of modularization and the gamecache limit, and if I am not mistaking it is all for a single tag? There are plenty of things you can fix this, the easiest would be to use 3 arrays and just perform substraction, but you can also use a trie or hash table... |
| 10-04-2007, 04:07 AM | #5 | ||
Quote:
This is very true, as I mentioned. I have a very fast computer, and at around 15 or so I start to feel the heat pretty good. Unless someone has some big idea of how to make this incredibly more optimized, I don't see a work around, however, aside from completely remaking it, or changing the spell itself, I am up for suggestions. Quote:
what is wrong with the name cscache, its private, so it'll be called Blaze something or other. Unless you have some neat idea for a built in game cache variable in NewGen, I do not see away around it other then requiring your CSCache system. I left the global trigger in case for some reason anyone ever wanted to access it, to say, turn off the ability, or turn it on, or something along those lines, I can't see how it would hurt, and I do realize I do no longer need a case sensitive prefix built into function names when in a scope ( and I usually do not, not really sure why I do here actually), though again, who is it hurting? @I2S(H2I(tower))+"Blaze" : SCOPE_PRIVATE+I2S(H2I(tower)) makes a lot more sense. >not real sure at all what that is supposed to mean.... as for removing GC, I do not think I am versed in "subtraction", nor in tables or trees....care to point me in the right direction, that I may be learned? As far as setting a cooldown, aside from using the units userdata, I wasn't sure how else to achieve the needed effect |
| 10-04-2007, 04:40 AM | #6 | |
Quote:
It is unlikely they would ever do that, but if so, make the trigger public a.k,a add the scope prefix, these gg things just make the spell a little more complicated to implement if you don't use the trigger editor for your scripts... -- use cscache, make your own library for gamecache that is outside the spell's code, something like that, modularizing stuff is healthy. It is not good that a single spell comes with its own gamecache system. The deal is that since it is scope based, you are few steps away of having a super template, but these little details are not making it possible, so I think it is not an actual problem but more of seeing the full potential of scopes not getting exploited. subtraction: if (cooldown[H2I(u)-0x10000]) then Or you can use multiple arrays. |
| 10-04-2007, 06:34 AM | #7 |
using attachments on units, ccc. |
| 10-04-2007, 11:11 AM | #8 |
a found something interesting, when it fires if the initial target is killed then the fireballs will find another target, a few times ive had fireballs racing around the map looking for targets, only to have that target destroyed, and have to find a new target, this all happening well outside the towers usual range, you may want to have a look at this... |
| 10-04-2007, 11:29 AM | #9 |
First of all, this is an eye candy spell. The actual gameplay effect it has can be summed up into "damage 5 random units in an area", which isn't particularly impressive. As such, I'm not entirely sure it qualifies for the spell resource section (then again, not all eye candy spells are banned, only "cinematic" ones). I find it very lacking in the calibration department. You can't choose the number of projectiles, duration of the rotation, starting&end radius&height, turn speed, targeting options, etc... Furthermore, you can't make it a multi-level ability, and the falling speed of the projectile is completely arbitrarily chosen (what if it targets an air unit?) It also needs to be optimized more. Sure, it's already easy on gamecache thanks to structs but if it's causing a drop in my framerate then it's not optimized enough. You have to move to other things besides reducing game cache use, like reducing the number of function calls by inlining stuff and not doing silly stuff like getting the x and y of the tower every time instead of storing them to a variable. Perhaps, once the code is inlined, having a seperate struct for each projectile from the start might be more efficient. It's not smart to use the attack event. I could spam stop and get triple the attack speed out of my towers. Sure, you could increase the triggered cooldown to counter that, but then you're preventing the use of any attack speed increasing abilities which kind of defeats the purpose of using the attack event, you could just use a periodic event instead. You could also use the damage event, but we all know how annoying that is. This would work much better as a instant cast spell, or maybe a channeling one. Also, you need to specify which of the systems that are in the map does your spell need. Doesn't a struct array member of size [5] go from 0 to 4? Gorman: if you looked at the code, you would see that's expected behaviour. |
| 10-04-2007, 11:40 AM | #10 |
Just curious, but is it lag on the graphical or technical end? It really seems (in my opinion) to be on the graphical end, with a model like this that spews out lots of particles. |
| 10-04-2007, 12:53 PM | #11 | ||
Quote:
Yes. I think I am going to have to wrap the array member accesses on debug mode to report access violations... Quote:
|
| 10-04-2007, 01:06 PM | #12 |
The current lag is graphical to a large extent, but there are still many things you can improve on it. Using SetUnitX/Y for example. And when the missiles reach the top of the spiral, you do a full group enum for each individual one, whereas you only actually need to do one for the whole group. And using so many function calls (ie methods) probably doesn't help. |
| 10-04-2007, 02:24 PM | #13 | |||||
Quote:
its not really on the unit, it is completely MUI, and there is no leak....what is the big deal? in any case, as stated earlier, I didn't know of another way to do it, now I do Quote:
you can specify the distance the missiles can look for new targets, and there is no limit to the times this can be done, if a missile finds 5 different targets, which all die, it could end up pretty far away, considering the "look" distance is currently 1000. or so Quote:
not a fan of eyecandy? it is very lacking in the calibration department, I agree, it originally was not made to be completely calibrated, just to be used as is, with some general needed changes you cannot really make the assumption that because it is dropping your fps it is not optimized enough. every physics system, no matter how well written, is going to lag with 100 or 200 projectiles on the screen, or there abouts and what is inling?? the "cooldown" is basically just there to stop being able to trigegr this over and over really fast, the cooldown should probably be set as the minimal attack cd this unit could have I do agree it would be better as an active ability, but then, that kind of was not the point of the spell in the first place, I made iit as a pretty attack replacement as for what system are needed, I was under the impression that my implementation instruction covered that, as did the note that "Also, do not mind all the other triggers in the map, this was the test map I used for the ability, and I think it is an easy way to demo the spell as it is, simply look in the Spells folder for the abilities trigger." Quote:
hrmm, so declaring array[5] only gives you 0-4? then how is it that I used 1-5 w/o any problems I wonder? Quote:
is setx/y really that much faster then setunitposition? considering I do not run a path ability detection, there is probably always that risk the projectile could fly out of bounds... and what is this about methods = bad? I always thought they were encourage to keep things straight, or thereabouts In any case, come next week sometime, unless I manage to find some time before then, I will go about trying to make some changes on this possibly, add more config., change some of the optimization things people have suggested, as well as possibly how it fires(from on attack to active cast) |
| 10-04-2007, 02:30 PM | #14 | |
Quote:
If you always used 1-5, then you will only have problems if by some way you get to the last index. the 1638th instance would try to access index 8191 when you use 5, and then it would cause a little bug when loading the map. Not a big bug, but it is better to use the indexes correctly, I think. |
| 10-04-2007, 04:29 PM | #15 | ||
Quote:
Except this is lagging at 50 projectiles. Inlining is putting all the code from a function into the script in place of the function call. Quote:
Yes SetUnitX/Y is much faster than SetUnitPosition (precisely because it doesn't do the pathing checks etc). If the missile is flying after the unit, I don't see how it could go out of map bounds (it would have to target a unit that is out of map bounds). But yes it is generally a good idea to put the check in just in case something goes wrong. Methods like you are using them aren't bad outside of wc3. However, function calls are very slow in jass, so those extra 5 function calls per object add up. |
