Name | Type | is_array | initial_value |
Creep_Face_Ang | real | Yes | |
Creep_Loc | location | Yes | |
Integer | integer | No | |
Temp_Point | location | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
//=============================//
// 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
//TESH.scrollpos=10
//TESH.alwaysfold=0
library DestructableLib initializer Initialization
//* ============================================================================ *
//* Made by PitzerMike *
//* *
//* I made this to detect if a destructable is a tree or not. It works not only *
//* for the standard trees but also for custom destructables created with the *
//* object editor. It uses a footie as a dummy with the goul's harvest ability. *
//* The dummy ids can be changed though. I also added the IsDestructableDead *
//* function for completeness. *
//* ============================================================================ *
globals
private constant integer DUMMY_UNIT_ID = 'hfoo' // footman
private constant integer HARVEST_ID = 'Ahrl' // ghouls harvest
private constant player OWNING_PLAYER = Player(15)
private unit dummy = null
endglobals
function IsDestructableDead takes destructable dest returns boolean
return GetDestructableLife(dest) <= 0.405
endfunction
function IsDestructableTree takes destructable dest returns boolean
local boolean result = false
if (dest != null) then
call PauseUnit(dummy, false)
set result = IssueTargetOrder(dummy, "harvest", dest)
call PauseUnit(dummy, true) // stops order
endif
return result
endfunction
private function Initialization takes nothing returns nothing
set dummy = CreateUnit(OWNING_PLAYER, DUMMY_UNIT_ID, 0.0, 0.0, 0.0)
call ShowUnit(dummy, false) // cannot enumerate
call UnitAddAbility(dummy, HARVEST_ID)
call UnitAddAbility(dummy, 'Aloc') // unselectable, invulnerable
call PauseUnit(dummy, true)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary