• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Call of Nature v1.1.6

  • Like
Reactions: Kam
Description
Select a target area to summon wisps. For every blooming tree will be summoned one wisp. If a wisp touches an enemy, it will explode and deal 50/75/100 damage in a small AoE.


Credits and Systems
GroupUtils by RisingDusk
GTrigger by Jesus4Lyf (optional)
T32 by Jesus4Lyf
model 'Navi' by Elenai

code
JASS:
library CallOfNature requires GroupUtils, T32, optional GT
//  v 1.1.6
//////////////////////////////////////////////
// Credits:
//      Spell made by 3yeballz
//          please give credits if you use :-)
//
//      GroupUtils by Rising Dusk
//      T32 by Jesus4Lyf
//      GTrigger by Jesus4Lyf
//      model: Navi by Elenai
///////////////////////////////////////////////


        /////////////////////////////////////////
        /////////// User Configuration //////////
        /////////////////////////////////////////
    globals
        private constant integer SPELLID   = 'A000'     // rawcode of spell
        private constant integer UNITID    = 'h000'     // rawcode of wisp
        private constant integer TREE1     = 'ATtr'     // rawcode for trees where wisps can spawn
        private constant integer TREE2     = 'ATtc'
            // if you require more different trees go to line 218 and change it

        
        private constant real    RADIUS    = 400.       // radius spell cast
        private constant real    RADIUSC   = 80.        // radius detonation init
        private constant real    RADIUSDMG = 150.       // radius detonation damage
        private constant integer ARRAYSIZE = 200        // -> 40 instances allowed
        private constant integer ENDCOUNT  = 650        // wisps will move outside the map after x callbacks
        private constant integer MAXCOUNT  = 1500       // should be much bigger than ENDCOUNT. after x callbacks struct will be destroyed (if it's not destroyed yet)
        private constant real    HEIGHT    = 60         // fly height of wisps
            ////////////////////////////////////
            // constants for flight route //////
            ////////////////////////////////////
        private constant real ACCELERATION = 0.5        //default: 0.5
        private constant real ACCURACY     = 0.002      //default: 0.002
        private constant real INERTIA      = 0.06       //default: 0.06, the lower the stronger is inertia, affects acceleration as well
        private constant real TOLERANCE    = 1.27       //default: 1.27
        private constant real MAXSPEED     = 30         //default: 30
        private constant real STARTSPEED   = 25         //default: 25
        
        
        private attacktype atktype = ATTACK_TYPE_NORMAL
        private damagetype dmgtype = DAMAGE_TYPE_MAGIC
        private weapontype wpntype = null
    endglobals
    
    private function GetDamageAmount takes integer level returns real
        return 25. + 25. * level
    endfunction
        /////////////////////////////////////////
        /////// End of User Configuration ///////
        /////////////////////////////////////////

    private module WispInit
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            static if LIBRARY_GT then
                call TriggerAddCondition(GT_RegisterStartsEffectEvent(t, SPELLID), Filter(function thistype.SpellCast))
            else
                call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
                call TriggerAddCondition(t, Filter(function thistype.SpellCast))
            endif
            set t = null
            set check = Filter(function thistype.CheckForEnemy)
            set filter = Filter(function thistype.DealDamage)
            set filt = Filter(function thistype.Init)
            set mapmaxX = GetRectMaxX(bj_mapInitialPlayableArea)
            set mapmaxY = GetRectMaxY(bj_mapInitialPlayableArea)
            set mapminX = GetRectMinX(bj_mapInitialPlayableArea)
            set mapminY = GetRectMinY(bj_mapInitialPlayableArea)
            set rct = Rect(-RADIUS,-RADIUS,RADIUS,RADIUS)
        endmethod
    endmodule
    
    private struct Wisp
        private static thistype temp
        private static boolean  explode = false
        private static boolexpr filter
        private static boolexpr filt
        private static boolexpr check
        private static real mapmaxX
        private static real mapmaxY
        private static real mapminX
        private static real mapminY
        private static real px
        private static real py
        private static rect rct
        
        private unit caster
        private player owner
        private real x
        private real y
        private integer i
        private integer count
        private integer timecount
        private integer level
        private real damage
        private unit array wisp [ARRAYSIZE]
        private real array facing [ARRAYSIZE]
        private real array speed [ARRAYSIZE]
        private real array locx [ARRAYSIZE]
        private real array locy [ARRAYSIZE]
       
        static method DealDamage takes nothing returns boolean
            local unit u = GetFilterUnit()
            if IsUnitEnemy(u, temp.owner) and not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u)!=0 and GetUnitAbilityLevel(u, 'Avul')==0 then
                call UnitDamageTarget(temp.caster, u, temp.damage, false, false, atktype, dmgtype, wpntype)
            endif
            set u = null
            return false
        endmethod

        static method CheckForEnemy takes nothing returns boolean
            local unit u
            if explode then
                return false
            endif
            set u = GetFilterUnit()
            if IsUnitEnemy(u, temp.owner) and not IsUnitType(u, UNIT_TYPE_DEAD) and GetUnitTypeId(u)!=0 and GetUnitAbilityLevel(u, 'Avul')==0 then
                set explode = true
            endif
            set u = null
            return false
        endmethod
        
        private method ClearData takes nothing returns nothing
            set .wisp[i] = .wisp[.count]
            set .locx[i] = .locx[.count]
            set .locy[i] = .locy[.count]
            set .facing[i] = .facing[.count]
            set .speed[i] = .speed[.count]
            set .wisp[.count] = null
            set .locx[.count] = 0.
            set .locy[.count] = 0.
            set .facing[.count] = 0.
            set .speed[.count] = 0.
            set .count = .count - 1
        endmethod
        
        private method periodic takes nothing returns nothing
            local real gravitation
            set .i = 1
            loop
                exitwhen i > .count
                if this.timecount <= ENDCOUNT then
                    if .facing[i] < 0 then
                        set .facing[i] = facing[i]+360.0
                    endif
                    set gravitation = Atan2(GetUnitY(.caster) - .locy[i], GetUnitX(.caster) - .locx[i]) * bj_RADTODEG
                    if gravitation < 0 then
                        if .facing[i] > 180.0 then
                            set gravitation = gravitation + 360.0
                        endif
                    endif
                    set .facing[i] = .facing[i] + (gravitation-.facing[i]) * ACCURACY * (MAXSPEED-.speed[i])
                    set .speed[i] = .speed[i] + ACCELERATION * (TOLERANCE - Pow(RAbsBJ(gravitation-.facing[i]), INERTIA))
                    if .speed[i] > MAXSPEED then
                        set .speed[i] = MAXSPEED
                    endif
                    if .speed[i] < 0 then
                        set .speed[i] = 0
                    endif
                    call SetUnitFacing(.wisp[i], .facing[i])
                else
                    if .speed[i] < MAXSPEED then
                        set .speed[i] = .speed[i] + 0.1
                    endif
                endif
                set .locx[i] = .locx[i] + Cos(.facing[i] * bj_DEGTORAD) * .speed[i]
                set .locy[i] = .locy[i] + Sin(.facing[i] * bj_DEGTORAD) * .speed[i]
                if .locx[i] > mapmaxX or .locy[i] > mapmaxY or .locx[i] < mapminX or .locy[i] < mapminY then
                    call RemoveUnit(.wisp[i])
                    call .ClearData()
                else
                    call SetUnitX(.wisp[i], .locx[i])
                    call SetUnitY(.wisp[i], .locy[i])
                    set temp = this
                    call GroupEnumUnitsInArea(ENUM_GROUP, .locx[i], .locy[i], RADIUSC, check)
                    if explode then
                        call GroupEnumUnitsInArea(ENUM_GROUP, .locx[i], .locy[i], RADIUSDMG, filter)
                        call UnitApplyTimedLife(.wisp[i], 'BTLF', 0.01)
                        call .ClearData()
                        set explode = false
                    else
                        set i = i + 1
                    endif
                endif
            endloop
            set .timecount = .timecount + 1
            if .count == 0 or .timecount >= MAXCOUNT then
                call .stopPeriodic()
                call .destroy()
            endif
        endmethod

        implement T32x
        
        private static method Init takes nothing returns boolean
            local thistype this = temp
            local destructable d = GetFilterDestructable()
            local real x = GetDestructableX(d)
            local real y = GetDestructableY(d)
            local real dx = x - .x
            local real dy = y - .y
            local integer destrID = GetDestructableTypeId(d)
            if SquareRoot(dx*dx+dy*dy) > RADIUS then
                return false
            endif
            if (destrID == TREE1 or destrID == TREE2) then     // change this if you require more trees
                set .count = .count + 1
                set .facing[.count] = GetRandomReal(0., 360.)
                set .wisp[.count] = CreateUnit(.owner, UNITID, x, y, .facing[.count])
                set .locx[.count] = x
                set .locy[.count] = y
                set .speed[.count] = STARTSPEED
                call UnitAddAbility(.wisp[.count], 'Amrf')
                call UnitRemoveAbility(.wisp[.count], 'Amrf')
                call SetUnitFlyHeight(.wisp[.count], HEIGHT, 0)
            endif
            set d = null
            return false
        endmethod  
        
        static method create takes nothing returns thistype
            local thistype this = thistype.allocate()
            set .caster = GetTriggerUnit()
            set .owner = GetTriggerPlayer()
            set .x = GetSpellTargetX()
            set .y = GetSpellTargetY()
            set .count = 0
            set .timecount = 0
            set .level = GetUnitAbilityLevel(.caster, SPELLID)
            set .damage = GetDamageAmount(.level)
            set temp = this
            call MoveRectTo(rct, .x, .y)
            call EnumDestructablesInRect(rct, filt, null)
            return this
        endmethod
    
        private static method SpellCast takes nothing returns boolean
            static if LIBRARY_GT then
                call thistype.create().startPeriodic()
            else
                if GetSpellAbilityId() == SPELLID then
                    call thistype.create().startPeriodic()
                endif
            endif
            return false
        endmethod
    
        implement WispInit
    
    endstruct

endlibrary
Please give Credits if you use this Spell in your map

Keywords:
call, nature, wisp, wisps, tree, trees, explode, explosion, mui, vjass, navi
Contents

Noch eine WARCRAFT-III-Karte (Map)

Reviews
12:10GMT, 17th Jun 2011 Bribe: This is an overall good spell. The only change to be made is to base the spell off of a different ability (instead of silence) because of the weird audio/visual effect it makes when enemies are close to the target...

Moderator

M

Moderator

12:10GMT, 17th Jun 2011
Bribe:

This is an overall good spell. The only change to be made is to base the spell off of a different ability (instead of silence) because of the weird audio/visual effect it makes when enemies are close to the target point. But users can do that on their own.

Approved
 
looks good for me but you can optimize it a bit
JASS:
if not IsUnitEnemy(u, temp.owner) then
            return false
        endif
        if GetWidgetLife(u)<0.405 then
            return false
        endif
        if GetUnitAbilityLevel(u, 'Avul') >= 1 then
            return false
        endif
better
JASS:
if not IsUnitEnemy(u, temp.owner) or GetWidgetLife(u)<0.405 or GetUnitAbilityLevel(u, 'Avul') >= 1 then
            return false
        endif
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
KeyTimers2 - not the best choice. Timer32 is a lot more efficient (also it doesn't need to be a static if then).

call EnumDestructablesInCircleBJ(RADIUS, loc, function Wisp.Init)

This uses a location and a BJ where coordinates and natives could be used instead.

set this.owner = GetOwningPlayer(this.caster)

Could be set this.owner = GetTriggerPlayer()

Running a textmacro for cleardata is a waste, that should be its own function so you're not re-generating text over and over (generating huge KB file size)

That is all for now.
 
1) In .create(), you forgot to do set rct = null.
2) this.owner = GetOwningPlayer(this.caster) can just be this.owner = GetTriggerPlayer()
3) x and y in the create method can be initially set to GetSpellTargetX()/GetSpellTargetY().
4) Instead of call SetUnitFacing(wisp[this.count], this.facing[this.count]), you may want to just set the unit's facing in the create unit function. Like so:
JASS:
set this.wisp[this.count] = CreateUnit(this.owner, UNITID, x, y, this.facing[this.count])
Otherwise, it is not instant.
5) GroupUtils? If you are going to use the single-group method, then you might as well remove the requirement and use the normal natives. xD (GroupUtils == recycling, boolexpr safeness, + null leak. The null leak has been fixed in 1.24, boolexpr safeness is only needed if you handle your boolexprs in an odd manner, and if you are using a single group, then you don't need the recycling)

Otherwise, nice job. :ogre_haosis:
 
looks good for me but you can optimize it a bit
JASS:
if not IsUnitEnemy(u, temp.owner) then
            return false
        endif
        if GetWidgetLife(u)<0.405 then
            return false
        endif
        if GetUnitAbilityLevel(u, 'Avul') >= 1 then
            return false
        endif
better
JASS:
if not IsUnitEnemy(u, temp.owner) or GetWidgetLife(u)<0.405 or GetUnitAbilityLevel(u, 'Avul') >= 1 then
            return false
        endif

You mean "and" (?).
 
Level 22
Joined
Nov 14, 2008
Messages
3,256
Shouldn't you use pitzermike's destlib so it supports all kind of trees? (Using IsDestTree)

All similar functions to this RectMaxX(bj_mapInitialPlayableArea) should be stored into globals non-constant and set at ini. (cannot be set at the globals section and made as constant because globals runs before the bj is set).
 
Level 4
Joined
Feb 28, 2011
Messages
37
Shouldn't you use pitzermike's destlib so it supports all kind of trees? (Using IsDestTree)
I didn't want to use that and I wrote in the spell description that wisps are only summoned for blooming trees, but there are some trees like the ones from northrend that doesn't fit to that description.

All similar functions to this RectMaxX(bj_mapInitialPlayableArea) should be stored into globals non-constant and set at ini. (cannot be set at the globals section and made as constant because globals runs before the bj is set).
Good idea ;-)
 
Level 4
Joined
Feb 28, 2011
Messages
37
Cos(this.facing * bj_DEGTORAD)
Sin(this.facing * bj_DEGTORAD)

^You can set both of these when the spell is first cast to "this.cos" and "this.sin"

What do you mean with that?

You should use a static rect (created once, never destroyed) and avoid using the destructable BJ in order to filter out destructables in a circle.
Ok, but why avoid using the destructable BJ?
Do you mean
JASS:
            set bj_enumDestructableCenter = loc
            set bj_enumDestructableRadius = RADIUS
            call EnumDestructablesInRect(rct, filterEnumDestructablesInCircleBJ, function thistype.Init)
?
If I don't use the BJ, I would have to check the distance for every filtered destructable to the center to get a circle.

You don't need "== true".
Where? I can't see any.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
What do you mean with that?
He meant making new struct members to store those values. But I don't think it can be done since this.facing[i] gets changed.
Ok, but why avoid using the destructable BJ?
Do you mean
JASS:
            set bj_enumDestructableCenter = loc
            set bj_enumDestructableRadius = RADIUS
            call EnumDestructablesInRect(rct, filterEnumDestructablesInCircleBJ, function thistype.Init)
?
If I don't use the BJ, I would have to check the distance for every filtered destructable to the center to get a circle.
Usually BJ's are less efficient than what you could code yourself.
I'm not sure about this but I think filterEnumDestructablesInCircleBJ refers to EnumDestructablesInCircleBJFilter.
Looking at that function, you can see this:
JASS:
local location destLoc = GetDestructableLoc(GetFilterDestructable())
local boolean result

set result = DistanceBetweenPoints(destLoc, bj_enumDestructableCenter) <= bj_enumDestructableRadius
call RemoveLocation(destLoc)
return result
As you can see it uses locations when it's not really necessary and does a pretty simple distance check for getting destructables in a circle. It would be better to have this done by yourself with coordinates instead.
Where? I can't see any.
It's in the CheckForEnemy method. The boolean explode.
 
Top