| 08-01-2009, 10:57 PM | #1 |
Hi guys, recently I develop a Chain Missile system that allows users to make known spells such as Frost Chain and Paralyzing Casks (from Dota) and many other things. This system allows the user to customize most things such as the effect when hitting a target, the search algorithm and the movement algorithm (the last one, only if he wants). I am happy with this system by many reasons, basing myself on Chain Lightning I was able to develop a new approach based on past mistakes, thus making a better system. It is also my chance to prove to Anitarf and Pyrogasm that they did the right thing by approving the spell and by helping me. I hope they also get happy with this modest and simple system. So, now with the help of chobibo and other people, I've (finally) concluded the system. Basically, now I need an opinion from the user. Also, I have a question: 1 - In my create method I receive a boolean value that tells me if the user has or doesn't have a custom moveMissile method. Is there a way to check if the user has moveMissile method in his code and then if he doesn't have it, to run the system moveMissile code? I just don't like that boolean value on the create method, but it does it's job well =P Anyway, here is the code: JASS://=========================================================================== //Version 1.1.3 // // Chain Missile system // //Author: // - Flame_Phoenix // //Special Thanks to: // - chobibo, he really helped on the math formulas, without him, the system wouldn't be possible. // //Description: // - This system allows the user to easily make a chain missile spell. He can //configurate many settings and command the important sections of sending a chain //missile. // //Requires: // - TimedLoop // - xefx // //Use: // - Before using teh system you should configure the SETUP section, as it may contain //some important values for you. For more information, see the spells I made. // //Methods: // //static method create takes string fxpath, unit caster, unit target, integer targetsNumber, //boolean costumMove returns ChainMissile - obligatpry // - The create method creates a ChainMissile object. It takes the path of the missile that will //be used, the caster, the target, the number of targets for the chain, and finally a boolean value //which indicates if the user wants the system to call costumMoveMissile method instead of the default //method. If you do NOT want to use the default movement method, the boolean is true, else it is false. // //method onTarget takes unit target returns nothing - obligatpry // - This method is called when the missile finds his target. There is little to say, //except that you can do anything to the bad guy here. // //method searchTarget takes unit currentTarget returns unit - obligatpry // - After hitting a unit, we must look for a new unit. This method is the search //algorithm you can implement. I decided to allow the user to have this possibility //because this way you can target the closest unit, the weakest unit, the unit with //more/less mana, etc. This method returns the unit that will be the next target. // //method onEnd takes unit lastTarget returns nothing - optional // - After hitting all units, this method is called on the last target. Just use //your imagination to what you want to do to him =P // //method costumMoveMissile takes nothing returns nothing - optional // - This is for people that know what they do. Technically I allow the user to define //the movement algorithm the projectile will follow. Unlike the other methods above, this //method is not obligatory, and if you do not implement it, the system will use the default //method I created. Note that even if you implement this method in your code, the system will //only run it if you set the costumMove flag on the create method to true when creating the object. // //method onTimerLoop takes nothing returns nothing - optional // - I don't see how this can be useful, but I know some people will make good use of it. //Basically it allows the user to do something on each iteration of the system. Every time //the system runs, he calls this method. // //Members: // //xefx missile: // - the missile the caster will shoot. // //integer targetsNum: // - number of targets of the chain. //real speed // - the speed of the missile. If you do not set this value, the DEFAULT value applies. // //real collisionSize: // - the collision size of the missile.If you do not set this value, the DEFAULT value applies. // //real lifeTime: // - the life time a missile has for searching and hitting an enemy.If you do not set this value, //the DEFAULT value applies. If the lifeTime of a missile expires, the missile will die and so will teh chain. // //real startZ: // - tells you the starting fly heigh of teh missile. It is usefull if you want to make a customMove method. // //group pickedUnits: // - group of units that were already affected by the chain. Every time the missile hits a unit, the unit is //added to this group automatically. However, having in mind you may want to do funny stuff to the units of a //chain (add buffs per example) I decided to allow you to have access to it. // //Credits: // - chobibo, for reports on typos and maths formulas (lots of them xD ) // - Vexorian, for TimedLoop and xefx // - Anitarf, Pyro and (even) Captain_Griffen, for teaching me how to make my first //chain spell that orginated this system // - Ak0litor, for making a Nova system that inspired me into making this system // - Opossum, for a tip on how to improve performance // //=========================================================================== library ChainMissile requires xefx, TimedLoop //=========================================================================== //=============================SETUP START=================================== //=========================================================================== globals private constant real DEFAULT_SPEED = 8. //the default speed a missile has private constant real DEFAULT_START_HEIGH = 100. //the default height of a missile private constant real DEFAULT_COLLISION_SIZE = 20. //the default collision size of a missile private constant real DEFAULT_LIFE_TIME = 10. //the deafult time a missile lives before hitting a target in seconds endglobals //=========================================================================== //=============================SETUP END===================================== //=========================================================================== private interface userInterface method onTarget takes unit target returns nothing defaults nothing method searchTarget takes unit currentTarget returns unit defaults null method onEnd takes unit lastTarget returns nothing defaults nothing method costumMoveMissile takes nothing returns nothing defaults nothing method onTimerLoop takes nothing returns nothing defaults nothing endinterface struct ChainMissile extends userInterface //the user can change these members xefx missile integer targetsNum real speed real lifeTime group pickedUnits real startZ //but he should not ever change these or hell may come !!! private boolean customMoveOn private unit currentTarget private real currentTimedLife private boolean setLife private real missileCollisionSize private boolean isStartZSet //missile movement in 3d, thx to chobibo for all the hardcore formulas and code private method moveMissile takes nothing returns nothing local real x = GetUnitX(.currentTarget)-.missile.x local real y = GetUnitY(.currentTarget)-.missile.y local real z = (GetUnitFlyHeight(.currentTarget) - .missile.z) + 25 local real distance = SquareRoot(x*x + y*y + z*z) local real angle1 = Atan2(y, x) local real angle2 = Acos(z / distance) local real angle3 = Atan2(z, SquareRoot(x*x + y*y)) set .missile.xyangle = angle1 set .missile.x = .missile.x + .speed * Cos(angle1) * Sin(angle2) set .missile.y = .missile.y + .speed * Sin(angle1) * Sin(angle2) set .missile.z = .missile.z + .speed * Cos(angle2) set .missile.zangle = angle3 endmethod //missile impact in 3d, thx to chobibo and Opossum for hardcore formulas and code private method isOnTarget takes nothing returns boolean local real x = .missile.x - GetUnitX(.currentTarget) local real y = .missile.y - GetUnitY(.currentTarget) local real z = (GetUnitFlyHeight(.currentTarget) - .missile.z) + 25 //Because SquareRoot is a heavy operation, I avoid it by playing with "*" local real distance_3d = x*x + y*y + z*z//the distance between "a" and "b" ^2 //this is when the missile gets to the unit if distance_3d < .missileCollisionSize then //if our victim died before the missile arrived, we will want to search for //another target. To be fair, we increment the number of targets so this one //doesn't count if GetWidgetLife(.currentTarget) < .405 then set .targetsNum = .targetsNum + 1 endif //we reset the lifeTime of the missile set .currentTimedLife = .lifeTime //we also move the missile to the direct position of the victim //it makes it look nice imo set .missile.x = GetUnitX(.currentTarget) set .missile.y = GetUnitY(.currentTarget) return true endif return false endmethod method lifeTimeExpired takes nothing returns boolean //this allows the user to set the lifeTime of a missile rigth from the start //it is not an elegant solution, but it does the job. if not(.setLife) then if .lifeTime == -1 then set .lifeTime = DEFAULT_LIFE_TIME endif set .currentTimedLife = .lifeTime set .setLife = true endif set .currentTimedLife = .currentTimedLife - TimedLoop_PERIOD //if the lifetime of the missile expired we end with him! if .currentTimedLife < 0 then return true endif return false endmethod //method required by TimedLoop private method onTimedLoop takes nothing returns boolean //this is necessary in order to make our missile move up or down correctly if not(.isStartZSet) then set .startZ = .missile.z set .isStartZSet = true endif //first of all, we check if the missile should be alive or not! if .lifeTimeExpired() then return TimedLoop_STOP endif //if the user did not specify a custom movement for his projectiles we //continue with the system's normal movement if .customMoveOn then call .costumMoveMissile() else call .moveMissile() endif //if we hit the target we make the effect on him if .isOnTarget() then call .onTarget(.currentTarget) //we add this unit to the group of pickedUnits call GroupAddUnit(.pickedUnits, .currentTarget) //knowning that we hit our target we update the number of current //targets that need to be hit set .targetsNum = .targetsNum - 1 //if we are not over, we search a new target and the proccess continues if .targetsNum > 0 then set .currentTarget = .searchTarget(.currentTarget) //if we have no targets, there is no reason to look for them //and we stop here if .currentTarget == null then return TimedLoop_STOP endif else //if we are over, we call out special onEnd method and stop evetyhing else call .onEnd(.currentTarget) return TimedLoop_STOP endif endif //in case the user wants to do something special we always call this call .onTimerLoop() return TimedLoop_CONTINUE endmethod implement TimedLoop //This does the module magic static method create takes string fxpath, unit caster, unit target, integer targetsNumber, boolean costumMove returns ChainMissile local ChainMissile this = ChainMissile.allocate() //setting members set .missile = xefx.create(GetUnitX(caster), GetUnitY(caster), GetUnitFacing(caster)) set .missile.fxpath = fxpath set .missile.z = DEFAULT_START_HEIGH set .missile.xyangle = Atan2(GetUnitY(target) - GetUnitY(caster), GetUnitX(target) - GetUnitX(caster)) set .currentTarget = target set .targetsNum = targetsNumber set .customMoveOn = costumMove set .speed = DEFAULT_SPEED set .missileCollisionSize = DEFAULT_COLLISION_SIZE * DEFAULT_COLLISION_SIZE * DEFAULT_COLLISION_SIZE set .setLife = false set .lifeTime = -1 set .isStartZSet = false if .pickedUnits == null then set .pickedUnits = CreateGroup() endif //The TimedLoop script works by // creating a startTimedLoop method that will // do all the dirty work and end up calling // .onTimedLoop... call .startTimedLoop() return this endmethod method onDestroy takes nothing returns nothing call .missile.destroy() call GroupClear(.pickedUnits) endmethod //setters and getters for collision size method operator collisionSize= takes real newVal returns nothing set .missileCollisionSize = newVal * newVal * newVal endmethod method operator collisionSize takes nothing returns real return SquareRoot(.missileCollisionSize) endmethod endstruct endlibrary And what is a system without a demo map? Here is one too =D Hope you like it and give feedback too. |
| 08-01-2009, 11:17 PM | #2 | |
Quote:
JASS:.collisionSize *.collisionSize There's an error on the tooltip of Frost Chain. Nice system. |
| 08-02-2009, 09:21 AM | #3 | |
Quote:
Thx for the typo report. Well, I am submitting it soon too, I just need to know how to fix that fly height thing =P |
| 08-02-2009, 10:33 AM | #4 | |
Quote:
Just overload that member: JASS:method operator collisionSize= takes real size returns nothing set .collisionSize = size*size endmethod |
| 08-02-2009, 01:08 PM | #5 |
I think you should first try to make a properly working missile system and then try to build a chain missile system on top of that, if it turns out to be even worth it since once you have a missile system making a chain spell would be a piece of cake without the need for a special system. |
| 08-02-2009, 01:42 PM | #6 | |
like your idea but 1) finish one system, then start a new one 2) Quote:
*zzz* nowadays not important a normal calculator has a ~10kHz processor and takes not even a sec for it a modern pc-processor has 2*3000000kHz --> takes nearly no time |
| 08-02-2009, 02:20 PM | #7 | ||||
Quote:
Quote:
Quote:
Quote:
|
| 08-02-2009, 02:44 PM | #8 | |
Quote:
efficient :thumb_up: stupid efficiency :thub_down: Root operations don't take nowadays not so much res, cause algorithm was improved |
| 08-02-2009, 04:12 PM | #9 | |
Quote:
Anyway, any opinions on this before I submit? |
| 08-02-2009, 04:55 PM | #10 |
I agree with Opposum with overloading the set method of collision size, saves 1 mul operation. I mean you're not using the collision size for anything else, just for checking distances, so it would be practical to just store the product rather than multiplying it every time you need to compare values. I agree with F_P for not using Sqrt, the system will be using that function a lot, and that would lower the system's efficiency. That's just my opinion. |
| 08-02-2009, 05:34 PM | #11 | |
Quote:
That method overloads (replaces) the usual variable setting with a function. So whenever someone uses set cm.collisionSize (cm being any ChainMissile instance) it will not directly set the member collisionSize but it will instead call the declared method. Not sure if it inlines though... probably not. |
| 08-02-2009, 06:14 PM | #12 |
Ahhh, now I understand your idea Opossum. I wonder why I didn't think of it before =P Thx for the opinion chobibo. before I submit, can anyone help me with the Z fly height formula for the missile ? The default move method doesn't take care of it, so the spell only acts on ground units. I had the formula, but it takes a unit and not an xefx object, so instead I have to set the fly height of the xefx object gradually as he approaches the target. However I am too dumb to find it out =S EDIT EDIT EDIT Ok guys, updated first post, keep your suggestions coming ! |
| 08-02-2009, 06:48 PM | #13 |
3D Math That could help you. |
| 08-02-2009, 08:27 PM | #14 | ||
Quote:
Quote:
|
| 08-02-2009, 09:00 PM | #15 | |
Lol Anitarf .... my second quote is something "optional" (from my point of view) I would like to add. If I can't add it, I will still submit what I have, but until then, I will try to add it by every means necessary. Quote:
Thx anyway. EDIT EDIT EDIT I found this on scrips section: JASS:function ParabolicMovement takes real h, real d, real x returns real local real a = -4*h/(d*d) local real b = 4*h/d return a*x*x + b*x endfunction This is sort of what I want, but without the parabolic movement thing and only until it reaches half ... Moyack seems really good with maths (so not like me xD ) maybe I should ask for his help, I bet this is piece of cake for him. What do you guys think? |
