Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* 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.wc3campaigns.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.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
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
set tT[0]=CreateTimer()
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==8191) 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
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
This is the JESP standard document, if a map contains this document it means that there are
spells that follow this standard.
Spells of this map that follow the standard:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- "Golem Invocation"
- "Forest Call"
Advantages of the Standard
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- Implementing spells that follow the standard is relatively easier than implementing JASS
spells that don't follow the standard.
- Configuring/Balancing spells that follow the standard is relatively easier than
implementing JASS spells that don't follow the standard.
- Users may do the following procedure to make a new ability that uses the spell's script :
* Create a new Trigger with a name (case sensitive)
* Convert that trigger to custom text.
* Copy the spell's script to a text editor like Notepad or your OS equivalent.
* Replace the spell's Code name with the name you used on the trigger.
* Copy the new text to the new trigger
* Duplicate the Spell's original objects to have new ones for the new spell script.
You are now able to use that new version of the spell.
- In case two guys give the same name to 2 different spells, there are no conflict problems
because you can easily change the name of one of them
What is the JESP Standard?
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The JESP standard was designed to make spell sharing much better. And to make sure JASS
enhanced spells follow a rule, to prevent chaos.
What does JESP Standard stands for?
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
JASS
Enhanced
Spell
Pseudotemplate
Requirements for a spell to follow the JESP Standard
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- The spell is written in JASS
- The spell is 100% multi instanceable.
- The spell script is ready to support spells of any number of levels.
(default config header is not required to support all of them)
- The Spell has an specific code name.
- The Spell's trigger must have the spell's codename as name
- The Spell's InitTrig function must be named: InitTrig_<CodeName>
- The spell has a configuration header.
- It is mandatory that rawcodes of objects are configurable in the header.
- All the spell's specific code is inside the spell's "Trigger" (Trigger== that custom text
slot that world editor calls Trigger, the spell may use as many 'trigger' OBJECTS as needed)
- Every spell-specific single identifier or key works in such a way that reproducing the
spell's trigger but after performing a text-replace of codename with another name (and thus
renaming the cloned trigger to the new code name) it won't cause compile errors / conflicts
when playing the map.
- There is no code inside the spell's "Trigger" that is not specific to the spell.
- There are no requirements for GUI variables that are specific to the spell. If a system
used by the spell requires GUI variables the code for the system must be outside the "Trigger"
- Eyecandy and spell's balance have to be easy to configure
- The name of the author should be included in the spell's script.
- The reason to exist of this standard is spell sharing. This document should be included
within the map. And it should specify which spell follows the standard, in the top list.
//TESH.scrollpos=0
//TESH.alwaysfold=0
//===========================================================================
//A spell that creates an Earthquake. This earthquake creates dust and spawns
//golems inside the target area. Each golem has a period of life, and weaker
//golems have more chances to be spawned.
//
//Requires TimerUtils and Table
//
//@author Flame_Phoenix
//
//@credits
//- the-thingy, Kyrbi0, Pyrogasm, Anitarf
//- Rising_Dusk for the dust model
//- My first teacher of vJASS: Blue_Jeans
//- All other people I forgot or ignored
//
//@version 1.6
//===========================================================================
scope GolemInvocation initializer Init
//This will be needed for spell's core, don't change
private keyword golemChances
private keyword golemType
private keyword GOLEM_POOLS
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer AID = 'A001' //the rawcode of the ability
private constant string ANIMATION = "birth" //the animation that will be played when the unit is created
private constant integer MAX_TYPES = 3 //The maximum number of unit types of the spell
private constant integer MAX_LEVELS = 3 //the maximum number of levels of the spell
private constant integer ARRAY_SIZE = 4 //MAX_TYPES + 1. This will be needed for the spell's core, and is important that is correct.
endglobals
//===========================================================================
//============================GOLEM SETUP====================================
//===========================================================================
private function GolemCalibration takes nothing returns nothing
//here we type all types of units this spell can have
//in this case I only use 3 types of units
set golemType[1] = 'ngrk' //Mud Golem lv2
set golemType[2] = 'ngst' //Rock Golem lv6
set golemType[3] = 'nggr' //Granite Golem lv9
//Here we type the chances that each unit has to be spawned
//Note that the chances start in 0 and go to 1.0
//All chances together must be equal to 1.0, for the spell to work
//properly.
//level 1 chances
set golemChances[1][1] = 0.5 //Mud Golem has 50% chance to be spawned in lv1
set golemChances[1][2] = 0.3 //Rock Golem has 30% chance to be spawned in lv1
set golemChances[1][3] = 0.2 //Granite Golem has 20% chance to be spawned in lv1
//level 2 chances
set golemChances[2][1] = 0.4 //Mud Golem has 40% chance to be spawned in lv2
set golemChances[2][2] = 0.4 //Rock Golem has 40% chance to be spawned in lv2
set golemChances[2][3] = 0.2 //Granite Golem has 20% chance to be spawned in lv2
//level 3 chances
set golemChances[3][1] = 0.4 //Mud Golem has 40% chance to be spawned in lv3
set golemChances[3][2] = 0.3 //Rock Golem has 30% chance to be spawned in lv3
set golemChances[3][3] = 0.3 //Granite Golem has 30% chance to be spawned in lv3
//PS: note that in all levels, chanceGolem1 + chanceGolem2 + chanceGoem3 = 1 ALWAYS.
endfunction
private constant function GolemLife takes integer level returns real
//the amount of time each golem will have, if ChannelGolems() is false
return level * 20.
endfunction
private constant function ChannelGolems takes integer level returns boolean
//if true it will make all spawned golems die when the caster stops the channel
//if false, it allows the golems to have GolemLife() seconds of life and they
//will not die when the caster stops the channel
//in this case my golems have 20, 40 and 60 seconds of life for each level.
return false
//example of usage
// if (level == 3) then
// return false
// endif
// return true
//in this example the golems will die when the channel ends in level 1 and 2
//in level 3, the golems will live 60 seconds of life
endfunction
private constant function GolemsPerCycle takes integer level returns integer
//the number of golems that will be created each cycle
return 1 + (level * 0)
endfunction
private constant function Cycle takes integer level returns real
//the cycle which will determine when we create golems
//this means, GolemsPerCycle() golems are created every cycle
//in this case, we create 1 golem per second (in this case a second
//is a cycle)
return 1. + (level * 0)
endfunction
//===========================================================================
//============================DUST SETUP=====================================
//===========================================================================
private constant function DummyEffectId takes integer level returns integer
//the rawcode of the dummy unit that will be dust
//this is in a function so you can have different effects in different
//levels
return 'h000'
endfunction
private constant function MinRange takes integer level returns real
//in this case we don't have a minimum range, which means we can
//create golems at the center of the circle
//the minimum range is smaller circle, inside the area of the spell
//in which golems will not be created
return 0.
endfunction
private constant function MaxRange takes integer level returns real
//this ensures golems are not created outside the AOE of the spell
//this gives the AOE of the spell. This is the max AOE of the spell.
return 200. + (level * 100)
endfunction
private constant function CircleCount takes integer level returns integer
//the number of circles that will be created to give the illusion of the dust
//effect. The more circles the more realistic, but more CPU will be needed.
return 3 + level
endfunction
private constant function InnerCyrcleUnits takes integer level returns integer
//the number of units that the most inner circle will have
return level + 5
endfunction
private constant function InnerCyrcleUnitsIncrease takes integer level returns integer
//the increment of units per circle
return level + 2
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
type chancesPerType extends real array[ARRAY_SIZE]
globals
private group GolemInvocationCasters
private HandleTable activeTable
private chancesPerType array golemChances
private integer array golemType
private unitpool array GOLEM_POOLS
endglobals
private struct MyStruct
unit caster
integer level
timer cycleTimer
real spellLocX
real spellLocY
group dummyEffects
group golems
static method create takes unit caster, real locX, real locY returns MyStruct
local MyStruct data = MyStruct.allocate()
//set variables about the caster
set data.caster = caster
set data.level = GetUnitAbilityLevel(data.caster, AID)
//variables about the location
set data.spellLocX = locX
set data.spellLocY = locY
//the timer which will determine when we create golems
//creates a golem everytime it expires
set data.cycleTimer = NewTimer()
//This groups will save the dumy dust effects, so we can kill them
//when the caster stops the channeling
set data.dummyEffects = CreateGroup()
//if we want the golems to die when the caster stops channel
//then we create this group so we can add all golems to it
//then we kill the golems when the caster stops the channel
if (ChannelGolems(data.level)) then
set data.golems = CreateGroup()
endif
return data
endmethod
method onDestroy takes nothing returns nothing
//here we select all units from the dust group and we kill them all ! =P
//thus meaning that the dust effects will disappear !
local unit f
loop
set f = FirstOfGroup(.dummyEffects)
exitwhen(f == null)
call GroupRemoveUnit(.dummyEffects, f)
call KillUnit(f)
endloop
//if the variable is true, then golems group was created and it has
//units, and so now we kill them all.
if (ChannelGolems(.level)) then
loop
set f = FirstOfGroup(.golems)
exitwhen(f == null)
call GroupRemoveUnit(.golems, f)
call KillUnit(f)
endloop
call DestroyGroup(.golems)
endif
//since the spell is not active anymore, we clean the Table
call activeTable.flush(.caster)
//the units are not anymore in the active units group.
call GroupRemoveUnit(GolemInvocationCasters, .caster)
//releasing the timer for TimerUitls to use one day later =D
call ReleaseTimer(.cycleTimer)
call DestroyGroup(.dummyEffects)
endmethod
endstruct
//===========================================================================
private function onStop takes nothing returns boolean
local MyStruct data
local unit u = GetTriggerUnit()
//when a unit stops the channel, we verify if that unit is inside the
//catsers group. If so, than it is because it is our caster casting this spell
//and so we recover information about him and we make the spell end
if(IsUnitInGroup(u, GolemInvocationCasters)) then
//recover the data from the caster
set data = activeTable[u]
//now, time to clean the mess once again
call data.destroy()
endif
set u = null
return false
endfunction
//===========================================================================
//Function made by Themerion and adapted by Flame_Phoenix
//Creates circles of dummy units to give the illusion of the dust effect
private function createDust takes integer aStruct returns nothing
local MyStruct data = aStruct
// forPlayer -> Create the units for which player?
local player forPlayer = GetOwningPlayer(data.caster)
// innerRadius -> The radius of the innermost circle
// outerRadius -> The radius of the outmost circle
local real innerRadius = MinRange(data.level)
local real outerRadius = MaxRange(data.level)
// x and y -> Center of circles
local real x = data.spellLocX
local real y = data.spellLocY
local real radiusInc = (outerRadius - innerRadius) / I2R(CircleCount(data.level)-1)
//see functions they call
local integer fxcount = InnerCyrcleUnits(data.level)
local integer fxcountinc = InnerCyrcleUnitsIncrease(data.level)
local real phi = 0
local real phiInc
loop
exitwhen (innerRadius + 0.001 >= outerRadius)
set phi = 0
set phiInc = 2*bj_PI/I2R(fxcount)
loop
exitwhen (phi + 0.001 >= 2 * bj_PI)
//we add the units to a group, so we can kill them when we want ! =P
call GroupAddUnit(data.dummyEffects, CreateUnit(forPlayer, DummyEffectId(data.level), x + innerRadius * Cos(phi), y + innerRadius * Sin(phi), 180 + Rad2Deg(phi)))
set phi = phi + phiInc
endloop
set innerRadius = innerRadius + radiusInc
set fxcount = fxcount + fxcountinc
endloop
set forPlayer = null
endfunction
//===========================================================================
//Function made by Vexorian, it selects a random region in a disk.
//All regions have the same chance of beeing choosen
private function GetRandomPointInDisk takes real centerx, real centery, real minradius, real maxradius returns location
local real d = SquareRoot(GetRandomReal(minradius * minradius, maxradius * maxradius))
local real a = GetRandomReal(0, 2 * bj_PI)
return Location(centerx + d * Cos(a), centery + d * Sin(a))
endfunction
//===========================================================================
private function createGolems takes nothing returns nothing
//we get the structure from the timer
local MyStruct data = MyStruct(GetTimerData(GetExpiredTimer()))
//variables for us to know where the golems will born
local location point
local real randomX
local real randomY
//just to keep count about how many golems we created
local integer loopCounter = 0
//variables about the golems
local unit golem
local real golemFacing = GetUnitFacing(data.caster)
//Here we spawn the golems
loop
exitwhen(loopCounter >= GolemsPerCycle(data.level))
set point = GetRandomPointInDisk(data.spellLocX, data.spellLocY, MinRange(data.level), MaxRange(data.level))
set randomX = GetLocationX(point)
set randomY = GetLocationY(point)
set golem = PlaceRandomUnit(GOLEM_POOLS[data.level], GetOwningPlayer(data.caster), randomX, randomY, golemFacing)
call SetUnitAnimation( golem, ANIMATION )
//if ChannelGolems(data.level) is true we add the golem to it
//so we can keep track of it and kill it later in the "onDestroy"
//method
//else if the variable is false, it means that the group was not
//created, and we ant the units to have a LifeTime
if (ChannelGolems(data.level)) then
call GroupAddUnit(data.golems, golem)
else
call UnitApplyTimedLife(golem, 'BTLF', GolemLife(data.level))
endif
call RemoveLocation(point)
set point = null
set loopCounter = loopCounter + 1
endloop
set golem = null
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
local MyStruct data
local location spellLoc //the location of the spell
if (GetSpellAbilityId() == AID) then
//setting variables for the struct
set spellLoc = GetSpellTargetLoc()
set data = MyStruct.create(GetTriggerUnit(), GetLocationX(spellLoc), GetLocationY(spellLoc))
//put the struct in the Table, we just use the caster's handle adress as
//the key which tells us where in the Table the struct is stored
set activeTable[data.caster] = data
// we add the casting unit to some sort of "pool"
call GroupAddUnit(GolemInvocationCasters, data.caster)
//now we create the dusty effect
call createDust(data)
//we attach the struct to the timer
call SetTimerData(data.cycleTimer, integer(data))
call TimerStart(data.cycleTimer, Cycle(data.level), true, function createGolems)
//cleaning up the mess
call RemoveLocation(spellLoc)
endif
set spellLoc = null
return false
endfunction
//==========================================================================
private function Init takes nothing returns nothing
//these integers are counters for a loop and will be used later
local integer i
local integer j
//here we set up the triggers we will need
//Creates the trigger for when the unit start the effect of the spell
local trigger GolemInvocationTrigger = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ(GolemInvocationTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(GolemInvocationTrigger, Condition( function Conditions ) )
//Creates the trigger for when the unit ceases the channel
set GolemInvocationTrigger = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(GolemInvocationTrigger, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(GolemInvocationTrigger, Condition(function onStop))
set GolemInvocationTrigger = null
//set our globals
set activeTable = HandleTable.create()
set GolemInvocationCasters = CreateGroup()
//now we will Create UnitPools using loops and the
//counters
//in this loop we create the 2D dynamic arrays, and create an UnitPool
//for each level
set i = 1
loop
exitwhen(i > MAX_LEVELS)
set golemChances[i] = chancesPerType.create()
set GOLEM_POOLS[i] = CreateUnitPool()
set i = i + 1
endloop
//here we fill all our arrays
call GolemCalibration()
//here we add the units to the UnitPools with a respective weight per level
//PS: "weitgh" is the chances a unit has of being spawned.
set i = 1
set j = 1
loop
exitwhen( i > MAX_LEVELS)
loop
exitwhen(j > MAX_TYPES)
call UnitPoolAddUnitType(GOLEM_POOLS[i], golemType[j], golemChances[i][j])
set j = j + 1
endloop
set j = 1
set i = i + 1
endloop
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
//===========================================================================
//A spell that creates a Green Paradise. This paradise spawns friendly trees
//and ancients to aid you. While inside the paradise, friendly units also get
//extra abilities. The weaker the tree, the more chances it has to be spawned.
//
//Requires TimerUtils and Table
//
//@author Flame_Phoenix
//
//@credits
//- the-thingy, Kyrbi0, Pyrogasm, Anitarf
//- My first teacher of vJASS: Blue_Jeans
//- All other people I forgot or ignored
//
//@version 1.6
//===========================================================================
scope ForestCall initializer Init
//This will be needed for spell's core, don't change
private keyword treeChances
private keyword treeType
private keyword TREE_POOLS
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer AID = 'A002' //the rawcode of the ability
private constant string ANIMATION = "attack" //the animation that will be played when the unit is created
private constant integer MAX_TYPES = 9 //The maximum number of unit types of the spell
private constant integer MAX_LEVELS = 3 //the maximum number of levels of the spell
private constant integer ARRAY_SIZE = 10 //MAX_TYPES + 1. This will be needed for the spell's core, and is important that is correct.
private constant integer FOREST_AURA = 'A004'
endglobals
//===========================================================================
//============================TREE SETUP=====================================
//===========================================================================
private function TreeCalibration takes nothing returns nothing
//here we type all types of units this spell can have
//in this case I use 6 types of units
set treeType[1] = 'e000' //Treant
set treeType[2] = 'e001' //Ancient Protector
set treeType[3] = 'e006' //Tree of Life
set treeType[4] = 'e003' //Ancient of Wonders
set treeType[5] = 'e004' //Ancient of Lore
set treeType[6] = 'e007' //Tree of Ages
set treeType[7] = 'e002' //Ancient of War
set treeType[8] = 'e005' //Ancient of Wind
set treeType[9] = 'e008' //Tree of Eternity
//Here we type the chances that each unit has to be spawned
//Note that the chances start in 0 and go to 1.0
//All chances together must be equal to 1.0, for the spell to work
//properly.
//level 1 chances
set treeChances[1][1] = 0.5
set treeChances[1][2] = 0.3
set treeChances[1][3] = 0.2
set treeChances[1][4] = 0.
set treeChances[1][5] = 0.
set treeChances[1][6] = 0.
set treeChances[1][7] = 0.
set treeChances[1][8] = 0.
set treeChances[1][9] = 0.
//level 2 chances
set treeChances[2][1] = 0.
set treeChances[2][2] = 0.
set treeChances[2][3] = 0.
set treeChances[2][4] = 0.5
set treeChances[2][5] = 0.3
set treeChances[2][6] = 0.2
set treeChances[2][7] = 0.
set treeChances[2][8] = 0.
set treeChances[2][9] = 0.
//level 3 chances
set treeChances[3][1] = 0.
set treeChances[3][2] = 0.
set treeChances[3][3] = 0.
set treeChances[3][4] = 0.
set treeChances[3][5] = 0.
set treeChances[3][6] = 0.
set treeChances[3][7] = 0.4
set treeChances[3][8] = 0.3
set treeChances[3][9] = 0.3
//PS: note that in all levels, if we sum the chances, we ALWAYS get 1
endfunction
private constant function TreeLife takes integer level returns real
//the amount of time each tree will have, if ChannelTrees() is false
return level * 10.
endfunction
private constant function ChannelTrees takes integer level returns boolean
//if true it will make all spawned trees die when the caster stops the channel
//if false, it allows the trees to have TreeLife() seconds of life and they
//will not die when the caster stops the channel
//in this case my trees have die when the channel stops in levels 1 and 2, but
//in level 3 they get 30 seconds of life.
if (level == 3) then
return false
endif
return true
endfunction
private constant function TreesPerCycle takes integer level returns integer
//the number of trees that will be created each cycle
return 1 + (level * 0)
endfunction
private constant function Cycle takes integer level returns real
//the cycle which will determine when we create trees
//this means, TreesPerCycle() trees are created every cycle
//in this case, we create 1 tree per second (in this case a second
//is a cycle)
return 1. + (level * 0)
endfunction
//===========================================================================
//============================EFFECT SETUP===================================
//===========================================================================
private constant function DummyEffectId takes integer level returns integer
//the rawcode of the dummy unit that will be effect
//this is in a function so you can have different effects in different
//levels
return 'h001'
endfunction
private constant function MinRange takes integer level returns real
//in this case we don't have a minimum range, which means we can
//create trees at the center of the circle
//the minimum range is smaller circle, inside the area of the spell
//in which trees will not be created
return 0.
endfunction
private constant function MaxRange takes integer level returns real
//this ensures trees are not created outside the AOE of the spell
//this gives the AOE of the spell. This is the max AOE of the spell.
return 500. + (level * 0)
endfunction
private constant function CircleCount takes integer level returns integer
//the number of circles that will be created to give the illusion of the effect
//effect. The more circles the more realistic, but more CPU will be needed.
return 3 + level
endfunction
private constant function InnerCyrcleUnits takes integer level returns integer
//the number of units that the most inner circle will have
return level + 5
endfunction
private constant function InnerCyrcleUnitsIncrease takes integer level returns integer
//the increment of units per circle
return level + 2
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
type chancesPerTreeType extends real array[ARRAY_SIZE]
globals
private group TreeInvocationCasters
private HandleTable activeTable
private chancesPerTreeType array treeChances
private integer array treeType
private unitpool array TREE_POOLS
endglobals
private struct MyStruct
unit caster
integer level
timer cycleTimer
real spellLocX
real spellLocY
group dummyEffects
group trees
static method create takes unit caster, real locX, real locY returns MyStruct
local MyStruct data = MyStruct.allocate()
//set variables about the caster
set data.caster = caster
set data.level = GetUnitAbilityLevel(data.caster, AID)
//variables about the location
set data.spellLocX = locX
set data.spellLocY = locY
//the timer which will determine when we create trees
//creates a treeeverytime it expires
set data.cycleTimer = NewTimer()
//This groups will save the dumy effect effects, so we can kill them
//when the caster stops the channeling
set data.dummyEffects = CreateGroup()
//if we want the trees to die when the caster stops channel
//then we create this group so we can add all trees to it
//then we kill the trees when the caster stops the channel
if (ChannelTrees(data.level)) then
set data.trees = CreateGroup()
endif
return data
endmethod
method onDestroy takes nothing returns nothing
//here we select all units from the effect group and we kill them all ! =P
//thus meaning that the effect effects will disappear !
local unit f
loop
set f = FirstOfGroup(.dummyEffects)
exitwhen(f == null)
call GroupRemoveUnit(.dummyEffects, f)
call KillUnit(f)
endloop
//if the variable is true, then trees group was created and it has
//units, and so now we kill them all.
if (ChannelTrees(.level)) then
loop
set f = FirstOfGroup(.trees)
exitwhen(f == null)
call GroupRemoveUnit(.trees, f)
call KillUnit(f)
endloop
call DestroyGroup(.trees)
endif
//since the spell is not active anymore, we clean the Table
call activeTable.flush(.caster)
//the units are not anymore in the active units group.
call GroupRemoveUnit(TreeInvocationCasters, .caster)
//releasing the timer for TimerUitls to use one day later =D
call ReleaseTimer(.cycleTimer)
call DestroyGroup(.dummyEffects)
endmethod
endstruct
//===========================================================================
private function onStop takes nothing returns boolean
local MyStruct data
local unit u = GetTriggerUnit()
//when a unit stops the channel, we verify if that unit is inside the
//catsers group. If so, than it is because it is our caster casting this spell
//and so we recover information about him and we make the spell end
if(IsUnitInGroup(u, TreeInvocationCasters)) then
//recover the data from the caster
set data = activeTable[u]
//now, time to clean the mess once again
call data.destroy()
endif
set u = null
return false
endfunction
//===========================================================================
//Function made by Themerion and adapted by Flame_Phoenix
//Creates circles of dummy units to give the illusion of the effect effect
private function createGreenEffect takes integer aStruct returns nothing
local MyStruct data = aStruct
// forPlayer -> Create the units for which player?
local player forPlayer = GetOwningPlayer(data.caster)
// innerRadius -> The radius of the innermost circle
// outerRadius -> The radius of the outmost circle
local real innerRadius = MinRange(data.level)
local real outerRadius = MaxRange(data.level)
// x and y -> Center of circles
local real x = data.spellLocX
local real y = data.spellLocY
local real radiusInc = (outerRadius - innerRadius) / I2R(CircleCount(data.level)-1)
//see functions they call
local integer fxcount = InnerCyrcleUnits(data.level)
local integer fxcountinc = InnerCyrcleUnitsIncrease(data.level)
local unit greenEffect
local real phi = 0
local real phiInc
loop
exitwhen (innerRadius + 0.001 >= outerRadius)
set phi = 0
set phiInc = 2*bj_PI/I2R(fxcount)
loop
exitwhen (phi + 0.001 >= 2 * bj_PI)
//we add the units to a group, so we can kill them when we want ! =P
set greenEffect = CreateUnit(forPlayer, DummyEffectId(data.level), x + innerRadius * Cos(phi), y + innerRadius * Sin(phi), 180 + Rad2Deg(phi))
call GroupAddUnit(data.dummyEffects, greenEffect)
if (data.level == 1) then
call SetUnitAbilityLevel(greenEffect, FOREST_AURA, 1)
elseif (data.level == 2) then
call SetUnitAbilityLevel(greenEffect, FOREST_AURA, 2)
elseif (data.level == 3) then
call SetUnitAbilityLevel(greenEffect, FOREST_AURA, 3)
endif
set phi = phi + phiInc
endloop
set innerRadius = innerRadius + radiusInc
set fxcount = fxcount + fxcountinc
endloop
set forPlayer = null
set greenEffect = null
endfunction
//===========================================================================
//Function made by Vexorian, it selects a random region in a disk.
//All regions have the same chance of beeing choosen
private function GetRandomPointInDisk takes real centerx, real centery, real minradius, real maxradius returns location
local real d = SquareRoot(GetRandomReal(minradius * minradius, maxradius * maxradius))
local real a = GetRandomReal(0, 2 * bj_PI)
return Location(centerx + d * Cos(a), centery + d * Sin(a))
endfunction
//===========================================================================
private function createTrees takes nothing returns nothing
//we get the structure from the timer
local MyStruct data = MyStruct(GetTimerData(GetExpiredTimer()))
//variables for us to know where the trees will born
local location point
local real randomX
local real randomY
//just to keep count about how many trees we created
local integer loopCounter = 0
//variables about the trees
local unit tree
local real treeFacing = GetUnitFacing(data.caster)
//Here we spawn the trees
loop
exitwhen(loopCounter >= TreesPerCycle(data.level))
set point = GetRandomPointInDisk(data.spellLocX, data.spellLocY, MinRange(data.level), MaxRange(data.level))
set randomX = GetLocationX(point)
set randomY = GetLocationY(point)
set tree = PlaceRandomUnit(TREE_POOLS[data.level], GetOwningPlayer(data.caster), randomX, randomY, treeFacing)
call SetUnitAnimation( tree, ANIMATION )
//if ChannelTrees(data.level) is true we add the treeto it
//so we can keep track of it and kill it later in the "onDestroy"
//method
//else if the variable is false, it means that the group was not
//created, and we ant the units to have a LifeTime
if (ChannelTrees(data.level)) then
call GroupAddUnit(data.trees, tree)
else
call UnitApplyTimedLife(tree, 'BTLF', TreeLife(data.level))
endif
call RemoveLocation(point)
set point = null
set loopCounter = loopCounter + 1
endloop
set tree= null
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
local MyStruct data
local location spellLoc //the location of the spell
if (GetSpellAbilityId() == AID) then
//setting variables for the struct
set spellLoc = GetSpellTargetLoc()
set data = MyStruct.create(GetTriggerUnit(), GetLocationX(spellLoc), GetLocationY(spellLoc))
//put the struct in the Table, we just use the caster's handle adress as
//the key which tells us where in the Table the struct is stored
set activeTable[data.caster] = data
// we add the casting unit to some sort of "pool"
call GroupAddUnit(TreeInvocationCasters, data.caster)
//now we create the green effects
call createGreenEffect(data)
//we attach the struct to the timer
call SetTimerData(data.cycleTimer, integer(data))
call TimerStart(data.cycleTimer, Cycle(data.level), true, function createTrees)
//cleaning up the mess
call RemoveLocation(spellLoc)
endif
set spellLoc = null
return false
endfunction
//==========================================================================
private function Init takes nothing returns nothing
//these integers are counters for a loop and will be used later
local integer i
local integer j
//here we set up the triggers we will need
//Creates the trigger for when the unit start the effect of the spell
local trigger ForestCallTrigger = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ(ForestCallTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition(ForestCallTrigger, Condition( function Conditions ) )
//Creates the trigger for when the unit ceases the channel
set ForestCallTrigger = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(ForestCallTrigger, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
call TriggerAddCondition(ForestCallTrigger, Condition(function onStop))
set ForestCallTrigger = null
//set our globals
set activeTable = HandleTable.create()
set TreeInvocationCasters = CreateGroup()
//now we will Create UnitPools using loops and the
//counters
//in this loop we create the 2D dynamic arrays, and create an UnitPool
//for each level
set i = 1
loop
exitwhen(i > MAX_LEVELS)
set treeChances[i] = chancesPerTreeType.create()
set TREE_POOLS[i] = CreateUnitPool()
set i = i + 1
endloop
//here we fill all our arrays
call TreeCalibration()
//here we add the units to the UnitPools with a respective weight per level
//PS: "weitgh" is the chances a unit has of being spawned.
set i = 1
set j = 1
loop
exitwhen( i > MAX_LEVELS)
loop
exitwhen(j > MAX_TYPES)
call UnitPoolAddUnitType(TREE_POOLS[i], treeType[j], treeChances[i][j])
set j = j + 1
endloop
set j = 1
set i = i + 1
endloop
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
//===========================================================================
//This is the trigger responsable for giving friendly units the special
//abilities.
//
//@author Flame_Phoenix
//
//@credits
//- Blade.dk (for CopyGroup function)
//- All other people I forgot or ignored
//
//@version 1.0
//===========================================================================
scope ExtraAbility initializer Init
globals
private constant integer FOREST_AURA_BUFF1_ID = 'B000'
private constant integer FOREST_AURA_BUFF2_ID = 'B001'
private constant integer FOREST_AURA_BUFF3_ID = 'B002'
private constant integer EVASIONBOOK_ID = 'A003'
private constant integer CRITICBOOK_ID = 'A007'
private constant integer MISCBOOK_ID = 'A008'
private constant integer EVASION_ID = 'A000'
private constant integer CRITIC_ID = 'A005'
private constant integer MISC_ID = 'A006'
endglobals
globals
private group UnitsLv1
private group UnitsLv2
private group UnitsLv3
endglobals
//===========================================================================
private function CopyGroup takes group g returns group
set bj_groupAddGroupDest = CreateGroup()
call ForGroup(g, function GroupAddGroupEnum)
return bj_groupAddGroupDest
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local group map = CreateGroup()
local unit f
local group g = CreateGroup()
//here we pick all units with the aura and add them to the respective groups
call GroupEnumUnitsInRect(map, bj_mapInitialPlayableArea, null)
loop
set f = FirstOfGroup(map)
exitwhen (f == null)
call GroupRemoveUnit(map, f)
if (GetUnitAbilityLevel(f, FOREST_AURA_BUFF1_ID) > 0) then
call GroupAddUnit(UnitsLv1, f)
call UnitAddAbility(f, EVASIONBOOK_ID)
call SetUnitAbilityLevel(f, EVASION_ID, 1)
call SetPlayerAbilityAvailable(GetOwningPlayer(f), EVASIONBOOK_ID, false)
elseif(GetUnitAbilityLevel(f, FOREST_AURA_BUFF2_ID) > 0) then
call GroupAddUnit(UnitsLv2, f)
call UnitAddAbility(f, CRITICBOOK_ID)
call SetUnitAbilityLevel(f, CRITIC_ID, 1)
call SetPlayerAbilityAvailable(GetOwningPlayer(f), CRITICBOOK_ID, false)
elseif(GetUnitAbilityLevel(f, FOREST_AURA_BUFF3_ID) > 0) then
call GroupAddUnit(UnitsLv3, f)
call UnitAddAbility(f, MISCBOOK_ID)
call SetUnitAbilityLevel(f, MISC_ID, 1)
call SetPlayerAbilityAvailable(GetOwningPlayer(f), MISCBOOK_ID, false)
endif
endloop
//Here we check each unit in the groups, to see if they still have the aura's buff
//if not, we remove them the abilities
set g = CopyGroup(UnitsLv1)
loop
set f = FirstOfGroup(g)
exitwhen(f == null)
call GroupRemoveUnit(g, f)
if (GetUnitAbilityLevel(f, FOREST_AURA_BUFF1_ID) <= 0) then
call UnitRemoveAbility(f, EVASIONBOOK_ID)
call GroupRemoveUnit(UnitsLv1, f)
endif
endloop
set g = CopyGroup(UnitsLv2)
loop
set f = FirstOfGroup(g)
exitwhen(f == null)
call GroupRemoveUnit(g, f)
if (GetUnitAbilityLevel(f, FOREST_AURA_BUFF2_ID) <= 0) then
call UnitRemoveAbility(f, EVASIONBOOK_ID)
call GroupRemoveUnit(UnitsLv2, f)
endif
endloop
set g = CopyGroup(UnitsLv3)
loop
set f = FirstOfGroup(g)
exitwhen(f == null)
call GroupRemoveUnit(g, f)
if (GetUnitAbilityLevel(f, FOREST_AURA_BUFF3_ID) <= 0) then
call UnitRemoveAbility(f, EVASIONBOOK_ID)
call GroupRemoveUnit(UnitsLv3, f)
endif
endloop
call DestroyGroup(g)
set g = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger ExtraAbTrg = CreateTrigger()
call TriggerRegisterTimerEvent(ExtraAbTrg, 0.1, true)
call TriggerAddAction( ExtraAbTrg, function Actions )
set ExtraAbTrg = null
//setting globals
set UnitsLv1 = CreateGroup()
set UnitsLv2 = CreateGroup()
set UnitsLv3 = CreateGroup()
endfunction
endscope