| 08-13-2009, 10:36 AM | #1 |
So, you all know what happened last time, but this time I am back baby ! After some people asking I decided I should continue my tutorial about strutcs, timers and modularity. So, this is a spell I am making. I currently have 3 forms of the spell - form 1 works, form 2 is better, form 3 is the best of them all. I want to make sure form 3 is really good, so I am posting it here for critics. Please note there will also be "special form 4, Xtreme modularity" where I will use xecollider or xefx and make this spell the most radical and modular possible way I know. Technically it will be like a curve where efficiency and modularity go up together. Special form 4 is not done yet. JASS://=========================================================================== //Sends an arrow that will travel and damage enemy units in a line. // //@Requires: // - TimedLoop // - GroupUtils // //@author Flame_Phoenix // //@Credits: // - Vexorian, for TimedLoop // - Rising_Dusk, for GroupUtils // //@version 1.2.0 //=========================================================================== scope ForestArrowThree initializer Init //=========================================================================== //=============================SETUP START=================================== //=========================================================================== globals private constant integer AID = 'A001' //id of the hero ability private constant integer ARROW_ID = 'h000' //id of the arrow unit private constant real ARROW_COLLISION = 40. //collision size of the arrow private constant real ARROW_SPEED = 30. //the speed of the arrow private constant attacktype ATT_TYPE = ATTACK_TYPE_MAGIC //attack type of arrow private constant damagetype DAM_TYPE = DAMAGE_TYPE_NORMAL //damage type of arrow endglobals private constant function DamageReduction takes integer level returns real //the damage reduction the arrow will suffer after hiting a target return 30. //+ level * 0 endfunction private constant function Damage takes integer level returns real //the damage the arrow will make to each target return 60. * level endfunction private constant function Distance takes integer level returns real //the distance the arrow will run before dying return 750. * level endfunction private function Targets takes unit caster, unit target returns boolean //the targets the arrow will affect return IsUnitEnemy(target, GetOwningPlayer(caster)) and (GetWidgetLife(target) > .405) and (IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE) == false) and (IsUnitType(target, UNIT_TYPE_STRUCTURE) == false) and (IsUnitType(target, UNIT_TYPE_MECHANICAL) == false) endfunction //=========================================================================== //=============================SETUP END===================================== //=========================================================================== globals private boolexpr chooseVictims private group victims private unit tmpCaster private real mapXmin private real mapXmax private real mapYmin private real mapYmax endglobals private function PickVictims takes nothing returns boolean return Targets(tmpCaster, GetFilterUnit()) endfunction private struct anArrow unit caster unit arrow real x_constant real y_constant integer level group oldVictims real distanceMade = 0. integer unitsHit = 0 //method required by TimedLoop private method onTimedLoop takes nothing returns boolean //we get the structure from the timer local real newX = GetUnitX(.arrow) + .x_constant local real newY = GetUnitY(.arrow) + .y_constant local unit f //if the arrow is outside the map, we simple kill it ! if (newX > mapXmax) or (newY > mapYmax) or (newX < mapXmin) or (newY < mapYmin) then return TimedLoop_STOP endif //moveArrow call SetUnitX(.arrow, newX) call SetUnitY(.arrow, newY) //increment the distance set .distanceMade = .distanceMade + ARROW_SPEED //cause the damage set tmpCaster = .caster call GroupEnumUnitsInRange(victims, newX, newY, ARROW_COLLISION, chooseVictims) loop set f = FirstOfGroup(victims) exitwhen(f == null) call GroupRemoveUnit(victims, f) //we only damage the target if it is the first time we meet him if (IsUnitInGroup(f, .oldVictims)==false) then //damage the bad guy! xD call UnitDamageTarget(.caster, f, Damage(.level) - .unitsHit * DamageReduction(.level), true, false, ATT_TYPE, DAM_TYPE, null) set .unitsHit = .unitsHit + 1 //add the bad guy to the .oldVictims group so we don't damage him again 0.03 seconds after call GroupAddUnit(.oldVictims, f) endif endloop //if the distance made by the arrow is greater then the distance the arrow needs to run, we kill it! if .distanceMade >= Distance(.level) then return TimedLoop_STOP endif return TimedLoop_CONTINUE endmethod implement TimedLoop //This does the module magic static method create takes unit aCaster, location spellLoc returns anArrow local anArrow this = anArrow.allocate() local real casterX = GetUnitX(aCaster) local real casterY = GetUnitY(aCaster) local real angle = Atan2(GetLocationY(spellLoc) - casterY, GetLocationX(spellLoc) - casterX) //set some members set .caster = aCaster set .level = GetUnitAbilityLevel(.caster, AID) set .arrow = CreateUnit(GetOwningPlayer(.caster), ARROW_ID, casterX, casterY, bj_RADTODEG * angle) //Instead of saving the angle and the targets position //use the difference from A to B, since the movement's //velocity is constant, so is it's increments per //interval. Only works for linear movement though. set .x_constant = ( casterX + ARROW_SPEED * Cos(angle) ) - casterX set .y_constant = ( casterY + ARROW_SPEED * Sin(angle) ) - casterY //now we create the group to save teh old victims set .oldVictims = NewGroup() //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 //Kill the arrow call KillUnit(.arrow) //we recycle the group! call ReleaseGroup(.oldVictims) endmethod endstruct //=========================================================================== private function Conditions takes nothing returns boolean local location spellLoc if GetSpellAbilityId() == AID then //to prevent a leak set spellLoc = GetSpellTargetLoc() //we create our arrow and we are done ! call anArrow.create(GetTriggerUnit(), spellLoc) call RemoveLocation(spellLoc) endif set spellLoc = null return false endfunction //=========================================================================== private function Init takes nothing returns nothing local trigger ForestArrowThreeTrg = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ(ForestArrowThreeTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition(ForestArrowThreeTrg, Condition(function Conditions)) //setting our globals set victims = CreateGroup() set chooseVictims = Condition(function PickVictims) set mapXmin = GetRectMinX(bj_mapInitialPlayableArea) set mapXmax = GetRectMaxX(bj_mapInitialPlayableArea) set mapYmin = GetRectMinY(bj_mapInitialPlayableArea) set mapYmax = GetRectMaxY(bj_mapInitialPlayableArea) endfunction endscope Thx in advance =) If you guys want to see form 1 and 2, just tell me and I will post the map here. The idea is to make some sort of "evolution over time" for the reader and not to simply splash a solution into his face. PS: I know I could use GetTargetSpellX in the new patch, but I don't think I have the update done on JNGP so I am using a location instead. |
| 08-13-2009, 11:37 AM | #2 | |
Quote:
|
| 08-13-2009, 11:46 AM | #3 | |
Quote:
If you remember well, when xe first came I also didn't like it. But then, having an entire project with only 1 dummy unit (per example) that can do anything you want, is awesome, so you converted me. I am open to more suggestions. |
| 08-13-2009, 11:59 AM | #4 |
BoundSentinel would save you some work. Also, why are casterX/Y even in here?: JASS:set .x_constant = ( casterX + ARROW_SPEED * Cos(angle) ) - casterX set .y_constant = ( casterY + ARROW_SPEED * Sin(angle) ) - casterY JASS:struct anArrow integer distanceMade = 0 integer unitsHit = 0 endstruct |
| 08-13-2009, 01:07 PM | #5 | |||
Quote:
1 - It only works if I use SetUnitX(who) or SetUnitY(who). In my case I use SetUnitPosition that prevents the case where my arrow leaves the map (though, having in mind I make a previous check for it, I could replace it with SetUnitX and SetUnitY). 2 - When the arrow leaves the map, BoundSentinel changes it's X and Y to inside the map. That's not what I want, I want the arrow to die, so I make a manual check and return false to make such a thing happen. Quote:
http://www.wc3c.net/showpost.php?p=1095205&postcount=10 Quote:
JASS:struct Data integer waves = 7 unit caster = GetTriggerUnit() //more stuff endstruct And I remember people saying I shouldn't do that, that I should use the create method instead to set those variables. That why I set them in the create method, because I think it's safer. Can you please explain me the difference between doing what you do, and doing what I do? |
| 08-13-2009, 01:11 PM | #6 |
How stupid do you think I am? Jesus. I meant "why are they there in that line" because you're adding and then subtracting them in the same line, effectively meaning they're not even used. Only do it to constant values, such as 0, null, "", or false; the point of default values is that they are default and are the same every time. If you do stupid things like GetTriggerUnit() then yeah, don't do that. |
| 08-13-2009, 01:18 PM | #7 | ||
Quote:
No need to be offended ... Quote:
|
| 08-13-2009, 01:19 PM | #8 | |
BoundSentinel only applies to SetUnitX/Y because they are the only ones that can crash a map. If you are using SetUnitPosition, then it is a waste to check if it's outside the map area. (Like you do) Quote:
|
| 08-13-2009, 01:21 PM | #9 |
Yeah, I didn't see the usage of SetUnitPosition; I wonder why you're not using SetUnitX/Y, though. |
| 08-13-2009, 01:31 PM | #10 | ||
Thank you for all your comments. But I insist on my previous quote: Quote:
As for my second question: Quote:
|
| 08-13-2009, 01:39 PM | #11 |
Because that's what it implies. Initiating a variable with a constant value is something you do; initiating a variable with a non-constant value is not something you do. And what if someone called .create() somewhere that wasn't in an ActionFunc and then GetTriggerUnit() didn't return anything? So don't init variables to values that might or might not exist. |
| 08-13-2009, 01:50 PM | #12 | |
Quote:
Any other suggestions? PS: changing the spell soon ! |
| 08-13-2009, 02:18 PM | #13 | |
Quote:
|
| 08-13-2009, 02:36 PM | #14 | |
Quote:
Thank you for your critics guys. I attach the map with the current 3 versions done. My idea when you see the code is: code 1 - Arrgghh what the hell !? This works but it is inefficient ! FP I hate you ! code 2 - Meh ... not bad, but it seriously need some improvements code 3 - good, but it could be more modular. code 4 (yet to come) - This rocks =P |
| 08-13-2009, 04:58 PM | #15 |
why not simply use standard ingame shockwave with an arrow model? |
