Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope SummoningNexus initializer onInit
/**************************************************************
*
* v1.0.2 by TriggerHappy
*
* The caster conjurs orbs containing the souls of minions.
*
* Level 1 - Summons 4 minions for 10 seconds.
* Level 2 - Summons 6 minions for 20 seconds.
* Level 3 - Summons 8 minions for 25 seconds.
*
* Requirements
* TimerUtils - http://www.wc3c.net/showthread.php?t=101322
* xebasic (optional) - http://www.wc3c.net/showthread.php?t=101150
*
* Credits
* Vexorian for his scripts
* Moyack and Spec for the ParabolaZ function
*
**************************************************************/
globals
private constant integer RAW_ID = 'A000' // Spell raw code
private constant integer DUMMY = XE_DUMMY_UNITID // Dummy ID, incase you are not using xebasic.
private constant integer LOCUST = 'Aloc'
private constant integer CROW_FORM = 'Amrf'
// The max number of units that can be summoned.
// Increasing this number decreasing the instance limit.
private constant integer MAX_SUMMONS = 12
private constant real TIMER_PERIOD = 0.03 // How fast the timer is ran
private constant real SPEED = 10 // How fast the orbs move
private constant real SPEED_INC = 5 // Orbs speed increase
private constant real ARC = 0.8 // The arc of the orbs
private constant real MOVE_DISTANCE = 200 // How far away the orbs go
private constant string EFFECT = "Abilities\\Spells\\Other\\Parasite\\ParasiteTarget.mdl" // Orb Effect
private constant string EXPLODE_EFFECT = "Abilities\\Spells\\NightElf\\Blink\\BlinkTarget.mdl" // Summon FX
endglobals
// what unit is summoned.
private constant function SUMMONED_UNIT takes integer level returns integer
return 'ndr1'
endfunction
private constant function SPACING takes integer count returns real
return 360.0/count // looks bad if the number is not even
endfunction
// how long the summons live for
private constant function SUMMON_LIFE takes real level returns real
if (level > 2) then
set level = level - 0.5
endif
return 10.0*level
endfunction
private constant function ParabolaZ takes real d, real h, real x returns real // moyack/Spec
return ( 4 * h / d ) * ( d - x ) * ( x / d )
endfunction
// How many units are summoned
private constant function SUMMON_COUNT takes integer level returns integer
local integer i
if (level == 1) then
return 4
else
set i = (2*(level-1)) + 4
if (i > MAX_SUMMONS) then
return MAX_SUMMONS
endif
return i
endif
endfunction
/**************************************************************
*
* DON'T EDIT BELOW THIS LINE
*
**************************************************************/
private struct data
unit caster
real x
real y
real speed = SPEED
real dist = 0
real d2 = 0
unit array fx[MAX_SUMMONS]
real array cos[MAX_SUMMONS]
real array sin[MAX_SUMMONS]
real array dx[MAX_SUMMONS]
real array dy[MAX_SUMMONS]
integer count
integer level
player p
method destroy takes nothing returns nothing
local integer i = 0
loop
exitwhen i > this.count-1
call RemoveUnit(this.fx[i])
set this.fx[i] = null
set i = i + 1
endloop
set this.caster = null
call this.deallocate()
endmethod
static method callback takes nothing returns nothing
local data this = GetTimerData(GetExpiredTimer())
local integer b = 0
local boolean a = false
// Calculate how much distance is left and incrementally
// increase the speed
set this.dist = this.d2-this.speed
set this.speed = this.speed + SPEED_INC
// If effects are finished, destroy the instance
if (this.dist <= 0) then
call this.destroy()
call ReleaseTimer(GetExpiredTimer())
set a=true
endif
loop
exitwhen b > this.count-1
set this.dx[b] = this.x + this.speed * this.cos[b]
set this.dy[b] = this.y + this.speed * this.sin[b]
call SetUnitX(this.fx[b], this.dx[b])
call SetUnitY(this.fx[b], this.dy[b])
call SetUnitFlyHeight(this.fx[b], ParabolaZ(this.d2, this.d2 * ARC, this.speed), 0)
// If spell is finished then create the summoned units
if (a) then
call DestroyEffect(AddSpecialEffect(EXPLODE_EFFECT, this.dx[b], this.dy[b]))
call UnitApplyTimedLife(CreateUnit(this.p, SUMMONED_UNIT(this.level), this.dx[b], this.dy[b], 270), 'BTLF', SUMMON_LIFE(this.level))
endif
set b = b + 1
endloop
endmethod
static method create takes unit caster returns data
local data this = data.allocate()
local integer b = 0
local real i = 0
local real c
local real dx
local real dy
local integer id
// Store caster info and other data inside the struct to avoid
// extra function calls.
set this.caster = caster
set this.p = GetOwningPlayer(caster)
set this.level = GetUnitAbilityLevel(this.caster, RAW_ID)
set this.count = SUMMON_COUNT(this.level)
set this.x = GetUnitX(this.caster)
set this.y = GetUnitY(this.caster)
set c = SPACING(this.count)
// Create the circle of effects
loop
exitwhen i >= 360 or b > this.count-1
// Store the angle and direction of the effects
set this.cos[b] = Cos(i * bj_DEGTORAD)
set this.sin[b] = Sin(i * bj_DEGTORAD)
set this.dx[b] = this.x + 90 * this.cos[b]
set this.dy[b] = this.y + 90 * this.sin[b]
// Create a dummy unit to attach the effect
set this.fx[b] = CreateUnit(this.p, DUMMY, this.dx[b], this.dy[b], 0)
// Give the dummy crow form so we can change it's height
call UnitAddAbility(this.fx[b], CROW_FORM)
// And locust to make it unselectable
call UnitAddAbility(this.fx[b], LOCUST)
// Apply the effect
call AddSpecialEffectTarget(EFFECT, this.fx[b], "origin")
// If it's the first iteration of the loop, store some data
if (b==0) then
set dx = (this.x+MOVE_DISTANCE) - this.dx[b]
set dy = (this.y+MOVE_DISTANCE) - this.dy[b]
set this.dist = SquareRoot(dx * dx + dy * dy)
set this.d2 = this.dist
endif
// Apply the units height at an angle
call SetUnitFlyHeight(this.fx[b], ParabolaZ(SPEED, SPEED * ARC, this.dist), 0)
set b = b + 1
set i = i + c
endloop
call TimerStart(NewTimerEx(this), TIMER_PERIOD, true, function data.callback)
return this
endmethod
endstruct
private function Actions takes nothing returns boolean
if (GetSpellAbilityId() == RAW_ID) then
call data.create(GetTriggerUnit())
endif
return false
endfunction
private function onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Filter(function Actions))
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* 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)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* 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.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//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")
set tT[0]=CreateTimer()
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")
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
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
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
if ( didinit ) then
return
else
set didinit = true
endif
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
//TESH.scrollpos=7
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary