HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Opinion about simple Dota spell 2 !

08-13-2009, 10:36 AM#1
Flame_Phoenix
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.

Collapse 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
Anitarf
Quote:
Originally Posted by Flame_Phoenix
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.
That's not "Xtreme", that's the freaking standard.
08-13-2009, 11:46 AM#3
Flame_Phoenix
Quote:
That's not "Xtreme", that's the freaking standard.
lol... well, this is suited for newbs. As you know, newbs HATE importing libraries and use stuff other people do. In this tutorial I will try to show them that sometimes modularity can be good. Why "Xtreme" ? Because with xecollider this spell can be done with half of the work achieving same results. My main idea here is "see, if you like modularity, you can make Dota spells in less then 10 minutes" thus the word Xtreme. Therefore the standard is Xtreme =D
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
Pyrogasm
BoundSentinel would save you some work.

Also, why are casterX/Y even in here?:
Collapse JASS:
set .x_constant = ( casterX + ARROW_SPEED * Cos(angle) ) - casterX
set .y_constant = ( casterY + ARROW_SPEED * Sin(angle) ) - casterY
.distanceMade and .unitsHit could be defaulted to 0:
Collapse JASS:
struct anArrow
    integer distanceMade = 0
    integer unitsHit = 0
endstruct
08-13-2009, 01:07 PM#5
Flame_Phoenix
Quote:
BoundSentinel would save you some work.
I studied that library and I honestly don't see a use for it by 2 reasons:
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:
Also, why are casterX/Y even in here?:
Because that is where the arrow starts, the original creation of the arrow. For more information about this algorithm please read chobibo's post here:
http://www.wc3c.net/showpost.php?p=1095205&postcount=10
Quote:
.distanceMade and .unitsHit could be defaulted to 0:
Ahh, this is a question I always asked myself. I remember the first time I made my spell Apocalypse and submitted it here. The code was:
Collapse 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
Pyrogasm
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
Flame_Phoenix
Quote:
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.
... you realize I didn't want to insult you, it is my way of explaining thins and I may have miss interpreted your question.
No need to be offended ...

Quote:
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.
I know you will hate me for this but ... why only default values?
08-13-2009, 01:19 PM#8
Rising_Dusk
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:
Originally Posted by Anitarf
That's not "Xtreme", that's the freaking standard.
This is why I don't submit spells anymore. I don't like xe, and it is the standard.
08-13-2009, 01:21 PM#9
Pyrogasm
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
Flame_Phoenix
Thank you for all your comments. But I insist on my previous quote:
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).
Meaning I realized it myself a few seconds ago. I will change that small thing soon.

As for my second question:


Quote:
Quote:
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.
I know you will hate me for this but ... why only default values?
I want to know this kind of stuff so I can tell people why. Thanks in advance.
08-13-2009, 01:39 PM#11
Pyrogasm
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
Flame_Phoenix
Quote:
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.
Ha ! good answer. Thanks. I thought it was something complicated within the arrays of structs and stuff like that. =D

Any other suggestions?
PS: changing the spell soon !
08-13-2009, 02:18 PM#13
Anitarf
Quote:
Originally Posted by Flame_Phoenix
Any other suggestions?
Yeah. Use xecollider.
08-13-2009, 02:36 PM#14
Flame_Phoenix
Quote:
Yeah. Use xecollider.
yes... but that is for form 4 of the spell.
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
Attached Files
File type: w3xForestArrow.w3x (177.6 KB)
08-13-2009, 04:58 PM#15
Tot
why not simply use standard ingame shockwave with an arrow model?