• 🏆 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!

Forestborn 2.1b

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
Spell Description:

Born in the wild woodlands of the Elves, the Ranger is home among the trees. Gains bonus life regeneration, move speed, and evasion while close to trees.

The spell is very useful and fits any "Ranger/Woodland" theme very well! The spell is very customizable, every aspect can be altered. Don't want evasion? Say the word. Want 1000 hitpoint regeneration per second? You got it. Want the caster to remain stationary? Done.

I am a learning vJass user, and this is my first spell coded in vJass. I am fairly active over at The Helper, but I thought I would upload it here too.

The spell is fully MUI, lagless and leakless.

Required systems: (all included in the demo map)
- vJass preprocessor
- TimerUtils
- DestructibleLib

Code:
JASS:
//=============================//
//  F O R E S T B O R N        //
//       by Tommerbob v2.1b    //
//=============================//
//
//Required: Jass NewGen, DestructableLib, TimerUtils  (All included)
//
//Implementation:
//  1. Copy the "Spell" Category from the demo map.  It includes everything you need.
//  2. Copy the custom buff in the Object Editor.
//  3. Copy the 4 custom abilities in the Object Editor.
//  4. In the Configeration Section below, make sure all 4 ability ID's and the buff ID match up with the rawcodes 
//  in the Object Editor.  You can see the rawcodes by pressing CTRL+D.
//  5.  Edit the Configeration Section below to your liking and enjoy!
//
//Note: If you want to use the leaves SFX, you need to import it into your map.
//
//Credits: Vexorian for TimerUtils, PitzerMike for DestructableLib, Leaves.mdx from DotA
//Thanks to many others for helping me with the code!    
//
scope Forestborn initializer init
//
//CONFIGERATION SECTION
//
globals
    private constant integer SPELL_ID =  'A000'  //rawcode of the Forestborn spell
    private constant integer BOOK_ID =   'A001'  //rawcode of the Evasion spell book
    private constant integer EVADE_ID =  'A002'  //rawcode of the evasion ability
    private constant integer MOVE_ID =   'A003'  //rawcode of the move speed bonus ability 
    private constant integer BUFF_ID =   'B000'  //rawcode of the Forestborn buff
        
    private constant real    INTERVAL =  0.5     //How often it checks for trees around caster.
    private constant real    AOE =       200.    //Max distance from a tree the caster may be.
        
    private constant real    DURATION_BASE = 10. //Base duration of spell.
    private constant real    DURATION_LVL =  5.  //Added duration for each level of spell.
        
    private constant integer EVADE_BASE =  50    //Base chance for evasion.  Set to 0 if you don't want the caster gaining evasion.
    private constant integer EVADE_LVL  =  0     //Amount added to base evasion for each level of spell.
        
    private constant real    HEAL_BASE =  8.     //Base amount that the caster is healed for each second.
    private constant real    HEAL_LVL =   4.     //Adds this amount to the base heal amount for each level of spell.
                                                 //If you don't want the caster healed, set both HEAL_BASE and HEAL_LVL to 0.
                                                 
    private constant integer MOVE_BASE = 25   //This is the move speed percent bonus the caster receives.  Set to 0 if no bonus.
    private constant integer MOVE_LVL =  0    //Amount added to base speed bonus for each level of spell.
    
    private constant boolean CAN_MOVE =  true  //Setting this to false will require the caster to remain stationary.

    private constant string  SFX1 = "Leaves.mdx" //Bling bling
    private constant string  SFX2 = "Abilities\\Spells\\NightElf\\TargetArtLumber\\TargetArtLumber.mdl"  //More bling bling!
    private constant string  SFX1_ATTACH = "chest"  //Where the first SFX is attached to the caster
    private constant string  SFX2_ATTACH = "origin" //Where the second SFX is attached to the caster
    private constant integer TRANSPARENCY = 30  //Caster becomes this transparent (even more bling bling!)
endglobals
//
//END CONFIGERATION SECTION.  Do not edit past this point unless you know what you are doing.  
//  
private function Duration takes integer level returns real
    return DURATION_BASE + DURATION_LVL * (level)
endfunction

private function Evasion takes integer level returns integer
    return 1 + EVADE_BASE + EVADE_LVL * (level)
endfunction

private function Heal takes integer level returns real
    return (HEAL_BASE + HEAL_LVL * (level)) * INTERVAL
endfunction

private function MoveBonus takes integer level returns integer
    return 1 + MOVE_BASE + MOVE_LVL * (level)
endfunction

private struct Data
    unit caster
    integer level
    integer evasion
    integer movebonus
    boolean tree
    boolean hassfx
    effect sfx1
    effect sfx2
    real heal
    real move
    real time
    real duration
    real oldx
    real oldy
    real newx
    real newy
    timer t
    
    static method create takes unit u returns thistype
        local thistype this = thistype.allocate()
    
        set this.caster = u
        set this.level = GetUnitAbilityLevel(this.caster, SPELL_ID)
        set this.time = 0.
        set this.duration = Duration(this.level)
        set this.evasion = Evasion(this.level)
        set this.heal = Heal(this.level)
        set this.move = GetUnitMoveSpeed(this.caster)
        set this.movebonus = MoveBonus(this.level)
        set this.oldx = GetUnitX(this.caster)
        set this.oldy = GetUnitY(this.caster)
        set this.tree = false
        set this.hassfx = false
        set this.t = NewTimer()
            
        return this
    endmethod
        
    method destroy takes nothing returns nothing
        call ReleaseTimer(.t)
    endmethod
endstruct

globals
    private Data D
endglobals
    
private function TreeCheck takes nothing returns nothing
    local destructable dest = GetEnumDestructable()
    
    if IsDestructableTree(dest) and not IsDestructableDead(dest) then
        set D.tree = true
    endif
    
    set dest = null
endfunction
        
    
private function NearTree takes nothing returns nothing
    local Data data = GetTimerData(GetExpiredTimer())
    local location l = GetUnitLoc(data.caster)
 
    set D = data
    
    set D.tree = false

    call EnumDestructablesInCircleBJ(AOE, l, function TreeCheck) //Picks every destructable in range
    
    if D.tree then
        if CAN_MOVE then                //If the caster should not move, updates his location.
            set D.newx = GetUnitX(D.caster)
            set D.newy = GetUnitY(D.caster)
            if D.newx != D.oldx or D.newy != D.oldy then
                set D.oldx = GetUnitX(D.caster)
                set D.oldy = GetUnitY(D.caster)
                call UnitRemoveAbility(D.caster, BOOK_ID)        //Remove bonuses if not stationary
                call UnitRemoveAbility(D.caster, BUFF_ID)        //Remove buff
                if D.hassfx then
                    call SetUnitVertexColor(D.caster, 255, 255, 255, 255)  //Reset caster transparency
                    call DestroyEffect(D.sfx1)                             //Remove SFX
                    call DestroyEffect(D.sfx2)
                    set D.hassfx = false
                endif
            else
                if not D.hassfx then
                    call SetUnitVertexColor(D.caster, 255, 255, 255, (100 - TRANSPARENCY))  //Set caster transparency
                    set D.sfx1 = AddSpecialEffectTarget(SFX1, D.caster, SFX1_ATTACH)  //Add SFX
                    set D.sfx2 = AddSpecialEffectTarget(SFX2, D.caster, SFX2_ATTACH)
                    set D.hassfx = true
                endif
                call UnitAddAbility(D.caster, BOOK_ID)
                call SetUnitAbilityLevel(D.caster, EVADE_ID, D.evasion) //Sets evasion if near a tree and is stationary. 
                if HEAL_BASE > 0. then                                  //Heals caster if he is near a tree and is stationary.
                    call SetWidgetLife(D.caster, GetWidgetLife(D.caster) + D.heal) 
                endif
            endif
        else
            if not D.hassfx then
                call SetUnitVertexColor(D.caster, 255, 255, 255, (100 - TRANSPARENCY))  //Set caster transparency
                set D.sfx1 = AddSpecialEffectTarget(SFX1, D.caster, SFX1_ATTACH)        //Add SFX
                set D.sfx2 = AddSpecialEffectTarget(SFX2, D.caster, SFX2_ATTACH)
                set D.hassfx = true
            endif
            call UnitAddAbility(D.caster, BOOK_ID)
            call SetUnitAbilityLevel(D.caster, MOVE_ID, D.movebonus)  //Sets the move bonus if near a tree
            call SetUnitAbilityLevel(D.caster, EVADE_ID, D.evasion) //Sets evasion if near a tree    
            if HEAL_BASE > 0. then                            //Heals caster if near a tree
                call SetWidgetLife(D.caster, GetWidgetLife(D.caster) + D.heal) 
            endif
        endif
    else 
        if D.hassfx then
            call SetUnitVertexColor(D.caster, 255, 255, 255, 255)  //Reset unit transparency
            call DestroyEffect(D.sfx1)                             //Remove SFX
            call DestroyEffect(D.sfx2)
            set D.hassfx = false
        endif
        call UnitRemoveAbility(D.caster, BOOK_ID)     //Remove bonuses if not near a tree
        call UnitRemoveAbility(D.caster, BUFF_ID)     //Remove buff
    endif
    
    set D.time = D.time + INTERVAL
    if D.time >= D.duration then
        set D.tree = false
        set D.hassfx = false
        call DestroyEffect(D.sfx1)                      //Remove SFX
        call DestroyEffect(D.sfx2)
        call SetUnitVertexColor(D.caster, 255, 255, 255, 255)  //Reset caster transparency
        call UnitRemoveAbility(D.caster, BOOK_ID)  //Removes evasion at end of duration
        call UnitRemoveAbility(D.caster, BUFF_ID)  //Remove buff
        call D.destroy()
    endif
    
    call RemoveLocation(l)
    set l = null
endfunction

private function Actions takes nothing returns boolean
    local Data data
    
    if GetSpellAbilityId() != SPELL_ID then
        return false
    endif
    
    set data = Data.create(GetTriggerUnit())
    
    call SetPlayerAbilityAvailable(GetOwningPlayer(GetTriggerUnit()), BOOK_ID, false) //Remove icons from hero UI
    call SetTimerData(data.t, data)
    call TimerStart(data.t, INTERVAL, true, function NearTree)
    
    return false
endfunction

//======================================================

private function init takes nothing returns nothing
    local trigger t = CreateTrigger()
    local unit u
    
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
    call TriggerAddCondition(t, Condition(function Actions))
    
    set u = CreateUnit(Player(15), 'Hpal', 0., 0., 0.)  //Preload ability
    call UnitAddAbility(u, BOOK_ID)
    call SetUnitAbilityLevel(u, MOVE_ID, MOVE_BASE)
    call SetUnitAbilityLevel(u, EVADE_ID, EVADE_BASE+1)
    call RemoveUnit(u)
    
    set u = null
    set t = null
endfunction

endscope

How to Import:
See Documentation at the top of the code.

I still have the GUI version, and will upload it on request.

Version history:
2.1b: Slightly optimized the code.
2.1: Optimized the code, added special effects for some bling bling.
2.0: Initial Jass release (originally coded in GUI)

Credits:
- Vexorian for TimerUtils
- PitzerMike for DestructibleLib
- Demo Map made by Tinki3
- Leaves.mdx model from DotA (original author unknown. If you can point me in his direction, I'd be grateful.)
- Thanks to everyone who helped with the code

Keywords:
Forest, Evasion, Ranger, Archer, Rogue, Trees, Wood
Contents

Spells Test Map Template (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 11 Nov 2011 Bribe: You forgot to call "deallocate" from the "destroy" method in struct "Data". It leaks. You still need to get rid of "EnumDestructablesInCircleBJ", if you look at...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.


11 Nov 2011
Bribe: You forgot to call "deallocate" from the "destroy" method in struct "Data". It leaks.

You still need to get rid of "EnumDestructablesInCircleBJ", if you look at JassHelper's API database you will see how to make it yourself.

Instead of setting HEAL_BASE and HEAL_LVL to 0, make a constant boolean that says "HEAL_ENABLED" which is compiled as a static-if, so if it is unwanted the code is not generated either.

In your "init" you actually need to add the ability, you can't just set the ability level.
 
call EnumDestructablesInCircleBJ(AOE, l, function TreeCheck) //Picks every destructable in range
You should get rid of that :)

edit
You should also change the scope to a library (to make the requirements explicit)
And while you're at it, remove that initializer, delete the "init" function and just C'n'P this at the end of your script :)
JASS:
private module Init
    private static method onInit takes nothing returns nothing
        local unit u
        set ForestbornTrigger  = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ForestbornTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(ForestbornTrigger, Condition(function Actions))
        set u = CreateUnit(Player(15), 'Hpal', 0., 0., 0.)  //Preload ability
        call UnitAddAbility(u, BOOK_ID)
        call SetUnitAbilityLevel(u, MOVE_ID, MOVE_BASE)
        call SetUnitAbilityLevel(u, EVADE_ID, EVADE_BASE+1)
        call RemoveUnit(u)
        set u = null
    endmethod
endmodule
private struct Inits extends array
    implement Init
endstruct

Or, If you want, just implement that module directly into your Data struct :)
 
Level 2
Joined
Jan 7, 2010
Messages
16
call EnumDestructablesInCircleBJ(AOE, l, function TreeCheck) //Picks every destructable in range
You should get rid of that :)

The native that BJ calls runs through several checks anyway, so isn't calling the BJ function pretty much just as fast? Similar to comparing it to the "TriggerRegisterAnyUnitEventBJ"?

You should also change the scope to a library (to make the requirements explicit)

What do you mean by making the requirements explicit?

And while you're at it, remove that initializer, delete the "init" function and just C'n'P this at the end of your script :)
JASS:
private module Init
    private static method onInit takes nothing returns nothing
        local unit u
        set ForestbornTrigger  = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(ForestbornTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
        call TriggerAddCondition(ForestbornTrigger, Condition(function Actions))
        set u = CreateUnit(Player(15), 'Hpal', 0., 0., 0.)  //Preload ability
        call UnitAddAbility(u, BOOK_ID)
        call SetUnitAbilityLevel(u, MOVE_ID, MOVE_BASE)
        call SetUnitAbilityLevel(u, EVADE_ID, EVADE_BASE+1)
        call RemoveUnit(u)
        set u = null
    endmethod
endmodule
private struct Inits extends array
    implement Init
endstruct

Or, If you want, just implement that module directly into your Data struct :)

I'm still new to vJass, whats a module? Whats wrong with how I'm already doing it? How is your way faster or better?
 
Level 2
Joined
Jan 7, 2010
Messages
16
@ Bribe:

- Don't use == true or == false, use "boolean" or "not boolean" respectively.

Why? What's wrong with using "== true" or "== false" ? Could you give me an example of how to use "boolean" or "not boolean" in this spell, I'm kinda confused.

I just got very busy with a few things in real life, will update as soon as I can.
 
Level 2
Joined
Jan 7, 2010
Messages
16
*Updated*

Made all the suggested changes.

I tried for about 2 hours to inline that EnumDestructibleBJ call, but I can't figure it out. Honestly, I don't think it will be that much faster than simply calling the BJ function. Anyway, for the time being I am leaving it as it is. Enjoy.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
The spell is pretty nice. However, the effect disappears if the caster moves, regardless of CAN_MOVE.
  • I think it would be easier if you just made the user rely on configuring the functions instead of using the globals (I'm talking about Duration, Heal, Evasion, and MoveBonus).
  • The tree and hassfx members in Data can be initialized to false like globals so you don't need to set them in create.
  • You really should work on inlining EnumDestructablesInCircleBJ since it's pretty wasteful.
    I'll get you started a bit:
    JASS:
    globals
        private rect TempRect = Rect(0, 0, 0, 0) // Just use on rect so you don't need to remove it all the time
    endglobals
    ...
    // When you call that function:
    local real x = GetUnitX(data.caster)
    local real y = GetUnitY(data.caster)
    call SetRect(TempRect, x - AOE, y - AOE, x + AOE, y + AOE)
    call EnumDestructablesInRect(TempRect, Filter(function EnumDestructablesInCircleBJFilter), function TreeCheck)
    Now all you need to do is inline EnumDestructablesInCircleBJFilter by making your own filter function.
  • You can use a static if for CAN_MOVE.
  • The way you set up TRANSPARENCY is rather confusing. Why don't you just let the user specify the exact alpha value they want instead of subtracting their value by 100?
  • The duration check for the spell should be made at the beginning of NearTree rather than at the end.
  • For Actions, you could have made the condition GetSpellAbilityId() == SPELL_ID so you could put everything else in there. It looks better in my opinion.
  • SetPlayerAbilityAvailable should have been done in init for every player rather than doing it when the spell is cast.
  • I don't understand why you set the levels for the movement speed ability and evasion ability for the dummy unit when you're preloading the spellbook ability.
 
Top