Name | Type | is_array | initial_value |
//TESH.scrollpos=304
//TESH.alwaysfold=0
//==========================================================================================
// Tainted Fountain v1.02a by watermelon_1234
//******************************************************************************************
// Libraries required: (Libraries with * are optional)
// - TimerUtils
// - SoundUtils
// * GroupUtils
//##########################################################################################
// Importing:
// 1. Copy the ability, Tainted Fountain.
// 2. Copy the unit, Tainted Fountain
// 3. Implement the required libraries.
// 4. Copy this trigger.
// 5. Configure the spell.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Notes:
// - The spell will target wards if they aren't mechanical
// - The spell will bug if the fountain gets moved from where it is placed
//==========================================================================================
scope TaintedFountain initializer Init
native UnitAlive takes unit id returns boolean // Don't touch this
//==========================================================================================
// CONSTANTS
//==========================================================================================
globals
// General Spell Settings
private constant integer SPELL_ID = 'A000' // Raw id of the spell.
private constant integer FOUNTAIN_ID = 'o000' // Raw id of the Tainted Fountain unit
private constant integer FOUNTAIN_ANIM = 2 // The animation index the fountain will play
private constant integer BUFF_ID = 'BTLF' // Buff given to the fountain for expiration timer
private constant real TIMER_LOOP = 0.1 // Determines how often the timer will loop
private constant real FOUNTAIN_FACE = 270 // Determines the facing angle of hte fountain
// Damage settings
private constant attacktype ATK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMG_TYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WPN_TYPE = null
// Special Effect Settings
private constant boolean PRELOAD = true // Determines if the effects and fountain unit should be preloaded or not.
private constant string HEAL_SFX = "Abilities\\Spells\\Other\\Drain\\DrainCaster.mdl" // The effect that is attached to units healing from the spell
private constant string HEAL_ATTACH = "origin" // Attachment point for HEAL_SFX
private constant string DRAIN_SFX = "Abilities\\Spells\\Other\\Drain\\DrainTarget.mdl" // The effect that is attached to units getting drained from the spell
private constant string DRAIN_ATTACH = "origin" // Attachment point for DRAIN_SFX
private constant string EXCESS_SFX = "Abilities\\Spells\\Undead\\DarkRitual\\DarkRitualTarget.mdl" // The sfx that will be played on the fountain when it has too much mana.
private constant string EXCESS_ATTACH = "origin" // Attachment point for EXCESS_SFX
private constant real DELAY_DUR = 1.5 // Duration before EXCESS_SFX will play again
private constant string LIGHTNING = "DRAL" // Lightning path
private constant real OFFSET = 0. // The offset that lightning will be created.
// Vertex color for lightning. Note that they are expressed in decimals.
private constant real RED = 1
private constant real GREEN = 1
private constant real BLUE = 1
private constant real ALPHA = 1
// Sound settings:
private constant string SOUND_PATH = "Abilities\\Spells\\Other\\ANrl\\FountainOfLifeLoop1.wav" // The sound path that will be looped
private constant integer SOUND_DURATION = 3315 // The duration of the sound
endglobals
//==========================================================================================
// CONFIGURATIONS
//==========================================================================================
// The spell's area of effect
private constant function Area takes integer lvl returns real
return 500.
endfunction
// Determines how long a fountain will last
private constant function Duration takes integer lvl returns real
return 30.
endfunction
// Damage done to enemy units per second
private constant function Damage takes integer lvl returns real
return 10.
endfunction
// Determines how much mana the fountain will gain from one point of damage
private constant function DamageRate takes integer lvl returns real
return 1.5
endfunction
// Determines how much a unit can be healed per second
private constant function Heal takes integer lvl returns real
return 10. + 10*lvl
endfunction
// Determines how much life the fountain can heal from one mana point
private constant function ManaRate takes integer lvl returns real
return 0.5 + 0.25*lvl
endfunction
// Determines how much damage the fountain will deal from excess mana
private constant function ExcessManaRate takes integer lvl returns real
return 0.25
endfunction
// Targets that are affected by the fountain, both healing and damaging
private function AffectedTargets takes unit u, player owner returns boolean
// General settings
return UnitAlive(u) and not IsUnitType(u, UNIT_TYPE_MECHANICAL) and /*
// Settings to damage enemy units
*/ (IsUnitEnemy(u, owner) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) or /*
// Settings to heal allied units
*/ (IsUnitAlly(u, owner) and GetWidgetLife(u) < GetUnitState(u, UNIT_STATE_MAX_LIFE))
endfunction
// Targets that get hurt by excess mana damage
private function ExcessManaAffectedTargets takes unit u, player owner returns boolean
return UnitAlive(u) and IsUnitEnemy(u, owner) and not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
endfunction
//==========================================================================================
// END OF CONFIGURATION
//==========================================================================================
private keyword Fountain
globals
private location l = Location(0, 0)
private group g // Used if GroupUtils isn't present
endglobals
private function GetPointZ takes real x, real y returns real
call MoveLocation(l, x, y)
return GetLocationZ(l)
endfunction
// Main struct that affects the target units
private struct Target
unit targ
Fountain f // Gets info from fountain
real delay = 0 // The count that will determine when to play the EXCESS_SFX
effect sfx = null // Effect attached to target unit.
string path = DRAIN_SFX // The path for the sfx. Changes if the targ unit is an ally or an enemy.
string attach = DRAIN_ATTACH // The attachment point for the sfx. Changes if the targ unit is an ally or an enemy.
lightning light // Lightning that will be attached to the targ unit and fount unit.
boolean show = false // Determines when to show the special effects.
timer t
static thistype temp // Used to pass data for group enumerations
static real TempReal // Used to pass the excess mana damage
static method damageUnits takes nothing returns boolean
local unit u = GetFilterUnit()
if ExcessManaAffectedTargets(u, GetOwningPlayer(temp.f.u)) then
call UnitDamageTarget(temp.f.u, u, TempReal, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
endif
set u = null
return false
endmethod
method showEffects takes nothing returns nothing
set .sfx = AddSpecialEffectTarget(.path, .targ, .attach)
call SetLightningColor(.light, RED, GREEN, BLUE, ALPHA)
set .show = true
endmethod
method hideEffects takes nothing returns nothing
call DestroyEffect(.sfx)
set .sfx = null
call SetLightningColor(.light, RED, GREEN, BLUE, 0)
set .show = false
endmethod
// Method deals with the effects for any targets affected by the spell
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real tx
local real ty
local real tz
local real life
local real mana
local real max
local real need // Mana needed for healing
local real gain
if UnitAlive(.targ) and UnitAlive(.f.u) then
if IsUnitInRange(.targ, .f.u, Area(.f.lvl)) then
// Only set variables if the target is in range
// Target unit settings
set tx = GetUnitX(.targ)
set ty = GetUnitY(.targ)
set tz = GetUnitFlyHeight(.targ) + GetPointZ(tx, ty) + OFFSET
set life = GetWidgetLife(.targ)
// Fountain unit settings
set mana = GetUnitState(.f.u, UNIT_STATE_MANA)
set max = GetUnitState(.f.u, UNIT_STATE_MAX_MANA)
// Healing effects here
if IsUnitAlly(.targ, GetOwningPlayer(.f.u)) then
set need = Heal(.f.lvl) / ManaRate(.f.lvl) * TIMER_LOOP
// Don't do anything if the fountain does not have any mana or the target has full health
if mana > 0.405 and life < GetUnitState(.targ, UNIT_STATE_MAX_LIFE) then
if mana - need > 0 then
// Has enough mana, don't worry about healing over
call SetWidgetLife(.targ, life + Heal(.f.lvl) * TIMER_LOOP)
else
// Can only heal with the mana that the fountain has
call SetWidgetLife(.targ, life + Heal(.f.lvl) * mana / need * TIMER_LOOP)
endif
// Reduces mana based on how much the target was healed by
call SetUnitState(.f.u, UNIT_STATE_MANA, mana - (GetWidgetLife(.targ)-life) / ManaRate(.f.lvl))
call MoveLightningEx(.light, true, .f.x, .f.y, .f.z, tx, ty, tz)
if not .show then
call .showEffects()
endif
// Don't show effects if the unit doesn't need healing
elseif .show then
call .hideEffects()
endif
else // Damaging effects here
call UnitDamageTarget(.f.u, .targ, Damage(.f.lvl)*TIMER_LOOP, false, true, ATK_TYPE, DMG_TYPE, WPN_TYPE)
set gain = DamageRate(.f.lvl)*(life-GetWidgetLife(.targ))
if mana + gain < max then
// No excess mana, give mana normally to the fountain
call SetUnitState(.f.u, UNIT_STATE_MANA, mana+gain)
else
// Damage enemy units with excess mana damage
if mana < max then
call SetUnitState(.f.u, UNIT_STATE_MANA, max)
endif
set temp = this
set TempReal = ExcessManaRate(.f.lvl) * (mana + gain - max) // Stores the damage to be dealt
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .f.x, .f.y, Area(.f.lvl), Filter(function thistype.damageUnits))
else
call GroupEnumUnitsInRange(g, .f.x, .f.y, Area(.f.lvl), Filter(function thistype.damageUnits))
endif
// This tells the spell when to start counting for delay between playing EXCESS_SFX
if .delay == 0 then
call DestroyEffect(AddSpecialEffectTarget(EXCESS_SFX, .f.u, EXCESS_ATTACH))
set .delay = .01
endif
endif
call MoveLightningEx(.light, true, .f.x, .f.y, .f.z, tx, ty, tz)
if not .show then
call .showEffects()
endif
// Done to prevent EXCESS_SFX from playing too often.
if .delay > 0 then
if .delay == .01 then
set .delay = TIMER_LOOP
else
set .delay = .delay + TIMER_LOOP
endif
endif
if .delay > DELAY_DUR then
set .delay = 0
endif
endif
// Hide effects if they are shown and if the target is out of range
elseif .show then
call .hideEffects()
endif
else
// This is done in case the target revives so that the spell will properly target it again
if not UnitAlive(.targ) then
call GroupRemoveUnit(.f.affect, .targ)
endif
if .sfx != null then
call DestroyEffect(.sfx)
endif
call DestroyLightning(.light)
call ReleaseTimer(.t)
call .destroy()
endif
endmethod
static method create takes unit t, Fountain f returns thistype
local thistype this = thistype.allocate()
set .targ = t
set .f = f
call GroupAddUnit(f.affect, t)
// Create the ligtning and hides it
set .light = AddLightning(LIGHTNING, true, f.x, f.y, GetUnitX(t), GetUnitY(t))
call SetLightningColor(.light, RED, GREEN, BLUE, 0)
if IsUnitAlly(.targ, GetOwningPlayer(.f.u)) then
set .path = HEAL_SFX
set .attach = HEAL_ATTACH
endif
set .t = NewTimer()
call SetTimerData(.t, this)
call TimerStart(.t, TIMER_LOOP, true, function thistype.onLoop)
return this
endmethod
endstruct
private struct Fountain
unit u // Refers to the fountain unit that is created
integer lvl
real x
real y
real z
group affect // Store all the units currently affected by the spell.
sound sndLoop // Sound that loops when fountain is alive
timer t // Timer used to do looping actions by the spell
private static thistype temp // Used to pass data for group enumerations
// Creates the Target struct for affected units
static method filter takes nothing returns boolean
local unit u = GetFilterUnit()
if not IsUnitInGroup(u, temp.affect) and AffectedTargets(u, GetOwningPlayer(temp.u)) then
call Target.create(u, temp)
endif
set u = null
return false
endmethod
// Periodic method to enumerate units in range of the fountain
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if UnitAlive(.u) then
set temp = this
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInArea(ENUM_GROUP, .x, .y, Area(.lvl), Condition(function thistype.filter))
else
call GroupEnumUnitsInRange(g, .x, .y, Area(.lvl), Condition(function thistype.filter))
endif
else // Fountain is dead, clean up struct stuff
static if LIBRARY_GroupUtils then
call ReleaseGroup(.affect)
endif
call ReleaseSound(.sndLoop)
call ReleaseTimer(.t)
call .destroy()
endif
endmethod
static method create takes unit c, real x, real y returns thistype
local thistype this = thistype.allocate()
set .lvl = GetUnitAbilityLevel(c, SPELL_ID)
static if LIBRARY_GroupUtils then
set .affect = NewGroup()
else
if .affect == null then
set .affect = CreateGroup()
else
call GroupClear(.affect)
endif
endif
// Fountain settings
set .u = CreateUnit(GetOwningPlayer(c), FOUNTAIN_ID, x, y, FOUNTAIN_FACE)
call UnitAddType(.u, UNIT_TYPE_SUMMONED)
call SetUnitAnimationByIndex(.u, FOUNTAIN_ANIM)
set .x = GetUnitX(.u)
set .y = GetUnitY(.u)
set .z = GetUnitFlyHeight(.u) + GetPointZ(.x, .y) + OFFSET
call UnitApplyTimedLife(.u ,BUFF_ID, Duration(.lvl))
// Sound creation
set .sndLoop = RunSoundAtPoint(DefineSound(SOUND_PATH, SOUND_DURATION, true, true), .x, .y, 0)
// Timer settings
set .t = NewTimer()
call SetTimerData(.t,this)
call TimerStart(.t, TIMER_LOOP, true, function thistype.onLoop)
return this
endmethod
endstruct
private function SpellActions takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call Fountain.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function SpellActions))
static if not LIBRARY_GroupUtils then
set g = CreateGroup()
endif
static if PRELOAD then
call Preload(HEAL_SFX)
call Preload(DRAIN_SFX)
call Preload(EXCESS_SFX)
call Preload(LIGHTNING)
call RemoveUnit(CreateUnit(Player(15),FOUNTAIN_ID,0,0,0))
endif
set t = null
endfunction
endscope
//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
//TESH.scrollpos=69
//TESH.alwaysfold=0
library SoundUtils requires Stack, TimerUtils
//******************************************************************************
//* BY: Rising_Dusk
//*
//* Sounds are a very picky datatype in WC3. They have many quirks that one must
//* account for in order to use them, and simply using the internal WE Sound
//* Editor isn't enough because the sounds it makes can't be played multiple
//* times at once. 3-D sounds are also very tricky because there are different
//* WC3 sound options that a user can have activated where certain sounds will
//* or will not work. This library attempts to streamline the handling of sounds
//* so that it is less likely to confuse you or cause problems.
//*
//* The .mp3 format can be used for 3-D sounds, but there is one problem that
//* must be noted. If your computer supports the "Dolby Surround" sound option
//* in WC3 and you have it selected, then .mp3 files will work for 3-D sounds.
//* If you don't, however, they may not work depending on what you do have
//* selected and what is available for your computer. The .wav format works on
//* all possible settings, making them excellent for general use. This library
//* can interface with sounds of either type.
//*
//* Known issues with sounds that this library resolves:
//* - A given sound variable can only be played once at a time. In order to
//* play a sound type multiple times at once, you need multiple variables.
//* - A sound cannot be played at the same instant that it is created.
//*
//* The DefineSound function defines a sound type based on some basic parameters
//* the user provides. DefineSoundEx is available if the user wants control over
//* all possible parameters, though they won't have an impact most of the time.
//* The duration parameter for DefineSound and DefineSoundEx is in milliseconds,
//* which is consistent with Blizzard's natives. To get the duration of a given
//* sound, open up the WE's Sound Editor, navigate to your sound, and select
//* "Add as Sound." In doing so, it will show its duration in seconds. Multiply
//* that number by 1000 and use it as the duration argument.
//*
//* This library returns a sound variable with RunSound that you can change the
//* settings of using the standard JASS sound API. The library assigns default
//* values to the parameters for 2-D and 3-D sounds, that way they will run
//* without any further help.
//*
//* The library automatically allocates, runs, and recycles a sound when you
//* call RunSound. This library will not automatically recycle looping sounds,
//* so you will need to call ReleaseSound on the looping sound when you want it
//* to end.
//*
//******************************************************************************
//*
//* > function DefineSound takes string fileName, integer duration, ...
//* boolean looping, boolean is3D returns integer
//*
//* This function defines a sound type with a short list of parameters. The
//* returned integer serves as a SOUND_TYPE for running this type of sound at
//* any other point in a map.
//*
//* > function DefineSoundEx takes string fileName, integer duration, ...
//* boolean looping, boolean is3D, boolean stopwhenoutofrange, ...
//* integer fadeInRate, integer fadeOutRate, string eaxSetting ...
//* returns integer
//*
//* This function serves an identical purpose to DefineSound, but gives the user
//* full control over the entire list of parameters. Similar to DefineSound, the
//* returned integer serves as a SOUND_TYPE for running this type of sound.
//*
//* > function RunSound takes integer soundRef returns sound
//*
//* This function runs a sound with the parameters held within the soundRef
//* integer argument. The soundRef argument is the returned value of DefineSound
//* or DefineSoundEx.
//*
//* > function RunSoundOnUnit takes integer soundRef, unit whichUnit returns sound
//*
//* The same as RunSound, just this function runs a sound of a given type on a
//* specified unit.
//*
//* > function RunSoundAtPoint takes integer soundRef, real x, real y, real z returns sound
//*
//* The same as RunSound, just this function runs a sound of a given type at a
//* specified point in 3D space.
//*
//* > function RunSoundForPlayer takes integer soundRef, player p returns sound
//*
//* The same as RunSound, just this function runs a sound of a given type only
//* for the specified player.
//*
//* > function ReleaseSound takes sound s returns boolean
//*
//* This function need only be called on looping sounds. If a sound is not
//* looping, it will be released and recycled on its own. This function should
//* be used on looping sounds when you want them to end.
//*
//* Example usage:
//* set SOUND_TYPE = DefineSound("Sound\\Path.wav", 300, false, true)
//* call RunSound(SOUND_TYPE)
//* call RunSoundOnUnit(SOUND_TYPE, SomeUnit)
//* call RunSoundAtPoint(SOUND_TYPE, x, y, z)
//* call RunSoundForPlayer(SOUND_TYPE, Player(5))
//* call ReleaseSound(SomeLoopingSound)
//*
globals
private hashtable ht = InitHashtable() //Attach sound types to sounds
private hashtable st = InitHashtable() //Sound hashtable
private hashtable rt = InitHashtable() //Attach soundrecyclers to sounds
private hashtable kt = InitHashtable() //Attach StopSound data
endglobals
//Struct for each sound type
private struct soundhelper
//Stack associated to each struct
Stack sta
//Sound Settings for this sound type
string fileName = ""
integer duration = 0
boolean looping = false
boolean is3D = false
boolean stopwhenoutofrange = false
integer fadeInRate = 0
integer fadeOutRate = 0
string eaxSetting = ""
static method create takes string fileName, integer duration, boolean looping, boolean is3D, boolean stopwhenoutofrange, integer fadeInRate, integer fadeOutRate, string eaxSetting returns soundhelper
local soundhelper sh = soundhelper.allocate()
//Load the parameters so the sound can be created later as necessary
set sh.fileName = fileName
set sh.duration = duration
set sh.looping = looping
set sh.is3D = is3D
set sh.stopwhenoutofrange = stopwhenoutofrange
set sh.fadeInRate = fadeInRate
set sh.fadeOutRate = fadeOutRate
set sh.eaxSetting = eaxSetting
//Create the stack for the struct
set sh.sta = Stack.create()
return sh
endmethod
endstruct
//Struct for holding data for the sound recycling
private struct soundrecycler
timer t = null
sound s = null
integer sh = 0
boolean stopped = false //Only gets used if StopSound is called on a new sound
static method create takes sound whichSound, integer soundRef returns soundrecycler
local soundrecycler sr = soundrecycler.allocate()
set sr.t = NewTimer()
set sr.s = whichSound
set sr.sh = soundRef
call SetTimerData(sr.t, integer(sr))
//Hook the value to the soundRef and whichSound
call SaveInteger(rt, soundRef, GetHandleId(whichSound), integer(sr))
return sr
endmethod
private method onDestroy takes nothing returns nothing
call RemoveSavedInteger(rt, .sh, GetHandleId(.s))
call ReleaseTimer(.t)
endmethod
endstruct
//******************************************************************************
private function HookStopSound takes sound soundHandle, boolean killWhenDone, boolean fadeOut returns nothing
local integer id = GetHandleId(soundHandle)
local integer soundRef = 0
local soundrecycler sr = 0
if HaveSavedInteger(ht, 0, id) then //Sound is from stacks
set soundRef = LoadInteger(ht, 0, id)
if HaveSavedInteger(rt, soundRef, id) then //Sound has a recycler
set sr = soundrecycler(LoadInteger(rt, soundRef, id))
set sr.stopped = true
endif
if killWhenDone then
debug call BJDebugMsg(SCOPE_PREFIX+"Warning: (StopSound) Destroying a sound in the stack")
endif
endif
endfunction
hook StopSound HookStopSound
private function HookKillSoundWhenDone takes sound soundHandle returns nothing
if HaveSavedInteger(ht, 0, GetHandleId(soundHandle)) then
call BJDebugMsg(SCOPE_PREFIX+"Warning: (KillSoundWhenDone) Destroying a sound in the stack")
endif
endfunction
debug hook KillSoundWhenDone HookKillSoundWhenDone
//******************************************************************************
function DefineSoundEx takes string fileName, integer duration, boolean looping, boolean is3D, boolean stopwhenoutofrange, integer fadeInRate, integer fadeOutRate, string eaxSetting returns integer
return integer(soundhelper.create(fileName, duration, looping, is3D, stopwhenoutofrange, fadeInRate, fadeOutRate, eaxSetting))
endfunction
function DefineSound takes string fileName, integer duration, boolean looping, boolean is3D returns integer
return DefineSoundEx(fileName, duration, looping, is3D, true, 10, 10, "CombatSoundsEAX")
endfunction
function ReleaseSound takes sound s returns boolean
local integer id = GetHandleId(s)
local integer soundRef = 0
local soundhelper sh = 0
local soundrecycler sr = 0
if s == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Cannot recycle a null sound")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Cannot recycle a sound not allocated by RunSound")
return false
endif
set soundRef = LoadInteger(ht, 0, id)
set sh = soundhelper(soundRef)
call StopSound(s, false, true) //Stop the sound
call sh.sta.push(id) //Return it to the stack
call SaveSoundHandle(st, soundRef, id, s) //Save it to hashtable
if not sh.looping then
//soundrecycler only exists for non-looping sounds
set sr = soundrecycler(LoadInteger(rt, soundRef, id))
call sr.destroy() //Destroy recycler helper
endif
return true
endfunction
private function Recycle takes nothing returns nothing
local soundrecycler sr = soundrecycler(GetTimerData(GetExpiredTimer()))
local soundhelper sh = soundhelper(sr.sh)
local integer id = GetHandleId(sr.s)
call StopSound(sr.s, false, true) //Stop the sound
call sh.sta.push(id) //Return it to the stack
call SaveSoundHandle(st, integer(sh), id, sr.s) //Save it to hashtable
call sr.destroy() //Destroy recycler helper
endfunction
private function Run takes nothing returns nothing
local soundrecycler sr = soundrecycler(GetTimerData(GetExpiredTimer()))
local soundhelper sh = soundhelper(sr.sh)
if not sr.stopped then
call StartSound(sr.s) //Play sound here
endif
if not sh.looping and not sr.stopped then
call TimerStart(sr.t, sh.duration*0.001, false, function Recycle)
else
call sr.destroy()
endif
endfunction
function RunSound takes integer soundRef returns sound
local sound s = null
local integer i = 0
local soundhelper sh = soundhelper(soundRef)
local soundrecycler sr = 0
if soundRef <= 0 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Cannot run sound of undefined type")
return null
endif
//Check if the stack is empty
if sh.sta.peek() == Stack.EMPTY then
//Create a new sound for the stack
set s = CreateSound(sh.fileName, sh.looping, sh.is3D, sh.stopwhenoutofrange, sh.fadeInRate, sh.fadeOutRate, sh.eaxSetting)
//Attach the type to the sound for future reference
call SaveInteger(ht, 0, GetHandleId(s), integer(sh))
call SetSoundDuration(s, sh.duration)
//Stuff that must be performed immediately upon creation of sounds
call SetSoundChannel(s, 5)
call SetSoundVolume(s, 127)
call SetSoundPitch(s, 1.)
if sh.is3D then
//These are settings necessary for 3-D sounds to function properly
//You can change them at will outside of this function
call SetSoundDistances(s, 600., 10000.)
call SetSoundDistanceCutoff(s, 3000.)
call SetSoundConeAngles(s, 0., 0., 127)
call SetSoundConeOrientation(s, 0., 0., 0.)
endif
//Start sound after a delay because it was created here
set sr = soundrecycler.create(s, soundRef)
call TimerStart(sr.t, 0.001, false, function Run)
else
//Allocate a sound from the stack
set i = sh.sta.pop()
if not HaveSavedHandle(st, soundRef, i) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: No sound in given stack member")
return null
endif
set s = LoadSoundHandle(st, soundRef, i)
call RemoveSavedInteger(st, soundRef, i)
call SetSoundVolume(s, 127) //Start volume at max
//Start it here since it wasn't created here
call StartSound(s)
//Recycle the sound in a timer callback after it's finished if nonlooping
if not sh.looping then
set sr = soundrecycler.create(s, soundRef)
call TimerStart(sr.t, sh.duration*0.001, false, function Recycle)
endif
endif
return s
endfunction
function RunSoundOnUnit takes integer soundRef, unit whichUnit returns sound
local sound s = RunSound(soundRef)
call AttachSoundToUnit(s, whichUnit)
return s
endfunction
function RunSoundAtPoint takes integer soundRef, real x, real y, real z returns sound
local sound s = RunSound(soundRef)
call SetSoundPosition(s, x, y, z)
return s
endfunction
function RunSoundForPlayer takes integer soundRef, player p returns sound
local sound s = RunSound(soundRef)
if GetLocalPlayer() != p then
call SetSoundVolume(s, 0)
else
call SetSoundVolume(s, 127)
endif
return s
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Stack
//*****************************************************************
//* STACK
//*
//* written by: Anitarf
//*
//* This is an efficient implementation of a stack in vJass. Since
//* it is based on a linked list, I decided to add common list
//* methods to the data structure so it can function both as
//* a stack and a simple linked list.
//*
//* As a linked list, it has less functionality than Ammorth's
//* LinkedList, but is considerably faster. Note only that most of
//* the list methods have O(n) time complexity so they may not be
//* suitable for operations on very large lists, however due to
//* their simplicity the list would need to be really large for
//* this to become a problem.
//*
//* All stack methods are of course O(1) and as fast as possible.
//* If you just need a stack, this is definitely the best choice.
//*
//* set s=Stack.create() - Instanceates a new Stack object.
//* call s.destroy() - Destroys the Stack.
//*
//* Stack syntax:
//* call s.push(123) - Pushes the value 123 on the stack.
//* A stack may contain multiple
//* instances of the same value.
//* set i=s.peek() - Reads the top value of the stack
//* and stores it to the variable i.
//* set i=s.pop() - Removes the top value from the stack
//* and stores it to the variable i.
//* s.peek()==Stack.EMPTY - Checks if the stack is empty.
//*
//* List syntax:
//* call s.add(123) - Adds the value 123 to the list.
//* A list may contain multiple
//* instances of the same value.
//* s.size - The total number of values on the list.
//* s.contains(123) - Checks if the value 123 is on the list.
//* set n=s.count(123) - Stores the number of times the value
//* 123 is on the list to the variable n.
//* call s.remove(123) - Removes one instance of the value 123
//* from the list. Returns false if
//* the value was not found on the list.
//* call s.purge(123) - Removes all instances of the value 123
//* from the list. Returns the number of
//* values that were removed.
//* set i=s.first - Reads the first value from the list
//* and stores it to the variable i.
//* set i=s.random - Reads a random value from the list
//* and stores it to the variable i.
//* set s2=s.copy() - Makes a copy of the list and stores
//* it to the variable s2.
//* call s.enum(Func,b) - Calls function Func for all values
//* on the list. The function must follow
//* the Enum function interface.
//* b is a boolean value, if it is true
//* then the values will be enumerated
//* top to bottom, if false then bottom
//* to top.
//*****************************************************************
public function interface Enum takes integer value returns nothing
struct Stack
// Use a totally random number here, the more improbable someone uses it, the better.
// This is the value that is returned by .pop and .peek methods and .first and .random operators when called on an empty stack.
public static constant integer EMPTY=0x28829022
// End of calibration.
readonly integer size = 0
private integer top = 0
private static integer free = 1
private static integer array next
private static integer array value
method push takes integer i returns nothing
// Get an index from the list of free indexes.
local integer n=Stack.free
set Stack.free=Stack.next[n]
// Extend the list of free indexes if needed.
if Stack.free==0 then
set Stack.free=n+1
endif
// Store the value to the index.
set Stack.value[n]=i
// Add index to the top of the stack.
set Stack.next[n]=.top
set .top=n
set .size=.size+1
endmethod
method pop takes nothing returns integer
// Get the top index of stack.
local integer n=.top
// Safety check in case a user pops an empty stack.
if n==0 then
debug call BJDebugMsg("stack warning: .pop called on an empty stack!")
return Stack.EMPTY
endif
// Remove the top index from stack.
set .top=Stack.next[n]
set .size=.size-1
// Add the index to the list of free indexes.
set Stack.next[n]=Stack.free
set Stack.free=n
// Return the value.
return Stack.value[n]
endmethod
method peek takes nothing returns integer
// Read the value of the top index.
return Stack.value[.top]
endmethod
method add takes integer value returns nothing
call .push(value)
endmethod
method contains takes integer value returns boolean
// Get the first index of the list.
local integer i=.top
// Search through the list.
loop
// Stop the search when the end of the list is reached.
exitwhen i==0
// Stop the search if the value is found.
if Stack.value[i]==value then
return true
endif
// Get the next index of the list.
set i=Stack.next[i]
endloop
return false
endmethod
method count takes integer value returns integer
local integer count=0
// Get the first index of the list.
local integer i=.top
// Search through the list.
loop
// Stop the search when the end of the list is reached.
exitwhen i==0
// Increase the count if the value is found.
if Stack.value[i]==value then
set count=count+1
endif
// Get the next index of the list.
set i=Stack.next[i]
endloop
return count
endmethod
method operator first takes nothing returns integer
return .peek()
endmethod
method operator random takes nothing returns integer
local integer r=GetRandomInt(1,.size)
// Get the first index of the list.
local integer i=.top
// Loop through the list.
loop
// Stop the loop after a random amount of repeats.
set r=r-1
exitwhen r==0 or i==0
// Get the next index of the list.
set i=Stack.next[i]
endloop
return Stack.value[i]
endmethod
method remove takes integer value returns boolean
// Get the first index of the list.
local integer i1=.top
local integer i2
// Check if the first index holds the value.
if Stack.value[i1]==value then
call .pop()
return true
endif
// Search through the rest of the list.
loop
set i2=Stack.next[i1]
// Stop the search when the end of the list is reached.
exitwhen i2==0
// Check if the next index holds the value.
if Stack.value[i2]==value then
// Remove the index from the stack.
set Stack.next[i1]=Stack.next[i2]
// Add the removed index to the list of free indexes.
set Stack.next[i2]=Stack.free
set Stack.free=i2
set .size=.size-1
return true
endif
set i1=i2
endloop
return false
endmethod
method purge takes integer value returns integer
local integer count=0
local integer i1
local integer i2
// If the first index holds the value, pop it.
loop
// If the list is empty, return.
if .top==0 then
return count
endif
// Repeat until the first index doesn't hold the value.
exitwhen Stack.value[.top]!=value
call .pop()
set count=count+1
endloop
// Get the first index of the list.
set i1=.top
// Search through the rest of the list.
loop
set i2=Stack.next[i1]
// Stop the search when the end of the list is reached.
exitwhen i2==0
// Check if the next index holds the value.
if Stack.value[i2]==value then
// Remove the index from the stack.
set Stack.next[i1]=Stack.next[i2]
// Add the removed index to the list of free indexes.
set Stack.next[i2]=Stack.free
set Stack.free=i2
set .size=.size-1
set count=count+1
else
set i1=i2
endif
endloop
return count
endmethod
method enum takes Enum f, boolean top2bottom returns nothing
local integer array value
// Get the first index of the list.
local integer i1=.top
local integer i2=0
// Populate the array.
loop
exitwhen i1==0
set value[i2]=Stack.value[i1]
set i2=i2+1
set i1=Stack.next[i1]
endloop
// Call the Enum function for each value in the array.
set i1=i2-1
loop
exitwhen i2==0
set i2=i2-1
// Enumerate in which direction?
if top2bottom then
call f.evaluate(value[i1-i2])
else
call f.evaluate(value[i2])
endif
endloop
endmethod
method copy takes nothing returns Stack
local Stack that=Stack.create()
// Get the first index of the list.
local integer i1=.top
local integer i2
// Add a dummy index to the top of the new list.
call that.push(0)
set i2=that.top
loop
exitwhen i1==0
// Get an index from the list of free indexes and add it at the end of the list.
set Stack.next[i2]=Stack.free
set i2=Stack.next[i2]
set Stack.free=Stack.next[i2]
// Extend the list of free indexes if needed.
if Stack.free==0 then
set Stack.free=i2+1
endif
// Copy the value to the new index.
set Stack.value[i2]=Stack.value[i1]
set i1=Stack.next[i1]
endloop
// End the new list correctly.
set Stack.next[i2]=0
// Remove the dummy index.
call that.pop()
// Copy the size value to the new list.
set that.size=this.size
return that
endmethod
method onDestroy takes nothing returns nothing
local integer n
// Remove all remaining indexes from the stack.
loop
// Get the top index.
set n=.top
exitwhen n==0
// Remove it from the stack.
set .top=Stack.next[n]
// Add it to the list of free indexes.
set Stack.next[n]=Stack.free
set Stack.free=n
endloop
endmethod
static method onInit takes nothing returns nothing
// Store the EMPTY value to index 0 to make .peek inline friendly.
set Stack.value[0]=Stack.EMPTY
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library InitMap initializer Init
globals
unit Hero
endglobals
private function Init takes nothing returns nothing
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, 12.)
set Hero = CreateUnit(Player(0),'Ulic',0,0,270)
call SelectUnit(Hero,true)
call DisplayTextToPlayer(Player(0),0,0,"Tainted Fountain v1.02a\nType -lvl x to set hero's level to x\nPress Esc to refresh your hero.")
endfunction
endlibrary
//TESH.scrollpos=40
//TESH.alwaysfold=0
scope ReviveUnits initializer Init
globals
private hashtable Revive = InitHashtable()
private constant real DELAY = 30.
private constant real HERO_DELAY = 5.
endglobals
private function SaveData takes unit u returns nothing
call SaveReal(Revive,0,GetHandleId(u),GetUnitX(u))
call SaveReal(Revive,1,GetHandleId(u),GetUnitY(u))
call SaveReal(Revive,2,GetHandleId(u),GetUnitFacing(u))
endfunction
private function ReviveFilter takes nothing returns boolean
local unit u = GetFilterUnit()
call SaveData(u)
set u = null
return true
endfunction
private struct Data
unit d
static method create takes unit d returns Data
local Data D = Data.allocate()
set D.d = d
return D
endmethod
static method onRevive takes nothing returns nothing
local timer t = GetExpiredTimer()
local Data D = Data(GetTimerData(t))
local real x = LoadReal(Revive,0,GetHandleId(D.d))
local real y = LoadReal(Revive,1,GetHandleId(D.d))
local real ang = LoadReal(Revive,2,GetHandleId(D.d))
if IsUnitType(D.d,UNIT_TYPE_HERO) == true then
call ReviveHero(D.d,x,y,true)
call SetUnitFacing(D.d,ang)
if GetLocalPlayer() == GetOwningPlayer(D.d) then
call PanCameraTo(x,y)
endif
call SetUnitState( D.d,UNIT_STATE_MANA,GetUnitState(D.d,UNIT_STATE_MAX_MANA))
else
call SaveData(CreateUnit(GetOwningPlayer(D.d),GetUnitTypeId(D.d),x,y,ang))
endif
set D.d = null
call D.destroy()
call ReleaseTimer(t)
set t = null
endmethod
endstruct
private function Actions takes nothing returns boolean
local timer t = NewTimer()
local unit d = GetTriggerUnit()
local real delay = DELAY
if IsUnitType(d,UNIT_TYPE_STRUCTURE) == false and IsUnitType(d,UNIT_TYPE_SUMMONED) == false then
call SetTimerData(t,Data.create(d))
if IsUnitType(d,UNIT_TYPE_HERO) == true then
set delay = HERO_DELAY
endif
call TimerStart(t,delay,false,function Data.onRevive)
set t = null
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger( )
local group g = CreateGroup()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DEATH )
call TriggerAddCondition( t, Condition(function Actions))
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, Condition(function ReviveFilter))
call DestroyGroup(g)
set g = null
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Refresh_Actions takes nothing returns nothing
call SetWidgetLife( Hero,GetUnitState(Hero,UNIT_STATE_MAX_LIFE))
call SetUnitState( Hero,UNIT_STATE_MANA,GetUnitState(Hero,UNIT_STATE_MAX_MANA))
call UnitResetCooldown( Hero )
endfunction
function InitTrig_Refresh takes nothing returns nothing
set gg_trg_Refresh = CreateTrigger( )
call TriggerRegisterPlayerEvent( gg_trg_Refresh, Player(0),EVENT_PLAYER_END_CINEMATIC )
call TriggerAddAction( gg_trg_Refresh, function Trig_Refresh_Actions )
endfunction
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_Level_Actions takes nothing returns nothing
call SetHeroLevel( Hero, S2I(SubString(GetEventPlayerChatString(), 5, StringLength(GetEventPlayerChatString()))), false )
endfunction
function InitTrig_LvlUp takes nothing returns nothing
set gg_trg_LvlUp = CreateTrigger()
call TriggerRegisterPlayerChatEvent( gg_trg_LvlUp, Player(0), "-lvl", false )
call TriggerAddAction( gg_trg_LvlUp, function Trig_Level_Actions )
endfunction