Name | Type | is_array | initial_value |
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library SeedOfLife initializer Init uses GroupUtils, DamageModifiers, SpellEvent, AutoIndex, IntuitiveBuffSystem, optional TimedHandles
private keyword Data // DO NOT TOUCH!
globals
private constant integer AID = 'A001' // ability triggering this. Any unit targeting ability is fine. Used Bloodlust
private constant integer BUFF_PLACER_AID = 'A004' // Ability placing BID on the unit this ability is added to. Based off of Slow Aura (Tornado).
private constant integer BID = 'B000' // buff placed by BUFF_PLACER_AID on the target unit
private constant string FX = "Abilities\\Spells\\Human\\Heal\\HealTarget.mdl" // displayed on the target unit when the effect of this ability is triggered.
private constant string FX_ATTPT = "origin"
private constant real FX_DURATION = 1.833 // 0 or less only plays the death animation // ignored if TimedHandles is not present in the map
private constant string TARGET_FX = "" // displayed on the target of this spell
private constant string TARGET_FX_ATTPT = "" // where to display the effect on the target of this spell
private constant integer PRIORITY = 0 // when to trigger the effects of an instance of this spell when a unit receives damage // refer to DamageModifiers documentation
private real array DURATION // how long does the buff last?
private real array AOE // Heals all units around the damaged target in this area
private real array CHANCE // Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
private integer array MAX_HEALINGS // heal at most this many times // zero or less for no limit
endglobals
// how long does the buff last?
private function Duration takes integer level returns real
return DURATION[level]
endfunction
// Heals all units around the damaged target in this area
private function Aoe takes integer level returns real
return AOE[level]
endfunction
// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
private function Chance takes integer level returns real
return CHANCE[level]
endfunction
// heal at most this many times // zero or less for no limit
private function Max_Healings takes integer level returns integer
return MAX_HEALINGS[level]
endfunction
private function SetUpSpellData takes nothing returns nothing
// how long does the buff last?
set DURATION[1]=7
set DURATION[2]=7
set DURATION[3]=7
// Heals all units around the damaged target in this area
set AOE[1]=200
set AOE[2]=275
set AOE[3]=350
// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
set CHANCE[1]=1
set CHANCE[2]=1
set CHANCE[3]=1
// heal at most this many times // zero or less for no limit
set MAX_HEALINGS[1]=0
set MAX_HEALINGS[2]=0
set MAX_HEALINGS[3]=0
endfunction
// if you want to get the targets current HP, use GetUnitLife(whichUnit)
// damage dealt to a unit with this buff active needs to be greater than what this function returns to be blocked
private function Minimum_Damage takes integer level, unit target returns real
return 0.
endfunction
// how much damage to block
private function Damage_Blocked takes integer level, unit target, real damage returns real
return 0.
endfunction
// how much health to restore after blocking
private function Health_Restored takes integer level, unit target, real damage returns real
return 10.+level*10.
endfunction
// how much health to heal units in the area
private function Aoe_Heal takes integer level, unit target, real damage returns real
return 10.+level*5.
endfunction
// only units matching these criteria will get healed
private function ValidTarget takes unit u, unit caster returns boolean
return IsUnitAlly(u, GetOwningPlayer(caster))/*
*/ and IsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/ and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/ and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/
endfunction
//
globals
private integer BuffType
endglobals
private struct Data extends DamageModifier
unit caster
unit target
effect targetFx
integer level
integer healings=0
static thistype Tmps
static real CurrentDamage
static thistype array Instance
private method onDestroy takes nothing returns nothing
set Instance[GetUnitId(.target)]=0
set .caster=null
set .target=null
if TARGET_FX!="" then
call DestroyEffect(.targetFx)
set .targetFx=null
endif
endmethod
private static method AoeHealEnum takes nothing returns boolean
local unit u=GetFilterUnit()
if u!=.Tmps.target and ValidTarget(u, .Tmps.caster) then
call SetUnitLife(u, GetUnitLife(u)+Aoe_Heal(.Tmps.level, .Tmps.target, .CurrentDamage))
static if LIBRARY_TimedHandles then
if FX_DURATION>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(FX, u, FX_ATTPT), FX_DURATION)
else
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endif
else
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endif
endif
set u=null
return false
endmethod
private method onDamageTaken takes unit origin, real damage returns real
local real blocked=0
if GetRandomReal(0,1)<=Chance(.level) and damage+0.406>=Minimum_Damage(.level, .target) then
if ValidTarget(.target, .caster) then
call SetUnitLife(.target, GetUnitLife(.target)+Health_Restored(.level, .target, damage))
static if LIBRARY_TimedHandles then
if FX_DURATION>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(FX, .target, FX_ATTPT), FX_DURATION)
else
call DestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endif
else
call DestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endif
set blocked=Damage_Blocked(.level, .target, damage)
endif
if Aoe(.level)>0. then
set .Tmps=this
set .CurrentDamage=damage
call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), Aoe(.level), Condition(function thistype.AoeHealEnum))
endif
set .healings=.healings+1
if Max_Healings(.level)>0 and .healings>=Max_Healings(.level) then
call UnitRemoveBuff(.target, BuffType)
endif
return -blocked
endif
return 0.
endmethod
static method create takes unit caster, unit target returns thistype
local thistype s=Instance[GetUnitId(target)]
if s==0 then
set s=.allocate(target, PRIORITY)
set s.caster=caster
set s.target=target
if TARGET_FX!="" then
set s.targetFx=AddSpecialEffectTarget(TARGET_FX, target, TARGET_FX_ATTPT)
endif
set s.level=GetUnitAbilityLevel(caster, AID)
set Instance[GetUnitId(target)]=s
else
if s.level<GetUnitAbilityLevel(caster, AID) then
set s.caster=caster
set s.healings=0
set s.level=GetUnitAbilityLevel(caster, AID)
endif
endif
set UnitAddBuff(caster, target, BuffType, Duration(s.level), s.level).data=s
return s
endmethod
static method BuffRemoved takes nothing returns nothing
call thistype(GetEventBuff().data).destroy()
endmethod
endstruct
private function CastResponse takes nothing returns nothing
call Data.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(AID, CastResponse)
set BuffType=DefineBuffType(BUFF_PLACER_AID, BID, 0, false, true, 0,0,Data.BuffRemoved)
call SetUpSpellData()
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library ProlongedLife initializer Init uses GroupUtils, DamageModifiers, SpellEvent, AutoIndex, IntuitiveBuffSystem, optional TimedHandles
private keyword Data // DO NOT TOUCH!
globals
private constant integer AID = 'A000' // ability triggering this. Any unit targeting ability is fine. Used Channel
private constant integer BUFF_PLACER_AID = 'A005' // Ability placing BID on the unit this ability is added to. Based off of Slow Aura (Tornado).
private constant integer BID = 'B001' // buff placed by BUFF_PLACER_AID on the target unit
private constant string FX = "Abilities\\Spells\\Items\\AIre\\AIreTarget.mdl" // displayed on the target unit when the effect of this ability is triggered.
private constant string FX_ATTPT = "origin"
private constant real FX_DURATION = 0. // 0 or less only plays the death animation // ignored if TimedHandles is not present in the map
private constant string TARGET_FX = "Abilities\\Spells\\Items\\HealingSalve\\HealingSalveTarget.mdl" // displayed on the target of this spell
private constant string TARGET_FX_ATTPT = "head" // where to display the effect on the target of this spell
private constant integer PRIORITY = 0 // when to trigger the effects of an instance of this spell when a unit receives damage // refer to DamageModifiers documentation
private real array DURATION // how long does the buff last?
private real array AOE // Heals all units around the damaged target in this area
private real array CHANCE // Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
private integer array MAX_HEALINGS // heal at most this many times // zero or less for no limit
endglobals
// how long does the buff last?
private function Duration takes integer level returns real
return DURATION[level]
endfunction
// Heals all units around the damaged target in this area
private function Aoe takes integer level returns real
return AOE[level]
endfunction
// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
private function Chance takes integer level returns real
return CHANCE[level]
endfunction
// heal at most this many times // zero or less for no limit
private function Max_Healings takes integer level returns integer
return MAX_HEALINGS[level]
endfunction
private function SetUpSpellData takes nothing returns nothing
// how long does the buff last?
set DURATION[1]=8
set DURATION[2]=8
set DURATION[3]=8
// Heals all units around the damaged target in this area
set AOE[1]=0
set AOE[2]=0
set AOE[3]=0
// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
set CHANCE[1]=1
set CHANCE[2]=1
set CHANCE[3]=1
// heal at most this many times // zero or less for no limit
set MAX_HEALINGS[1]=1
set MAX_HEALINGS[2]=1
set MAX_HEALINGS[3]=1
endfunction
// if you want to get the targets current HP, use GetUnitLife(whichUnit)
// damage dealt to a unit with this buff active needs to be greater than what this function returns to be blocked
private function Minimum_Damage takes integer level, unit target returns real
return GetUnitLife(target)
endfunction
// how much damage to block
private function Damage_Blocked takes integer level, unit target, real damage returns real
return damage
endfunction
// how much health to restore after blocking
private function Health_Restored takes integer level, unit target, real damage returns real
return GetUnitLife(target)*0.1*level
endfunction
// how much health to heal units in the area
private function Aoe_Heal takes integer level, unit target, real damage returns real
return 0.
endfunction
// only units matching these criteria will get healed
private function ValidTarget takes unit u, unit caster returns boolean
return IsUnitAlly(u, GetOwningPlayer(caster))/*
*/ and IsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/ and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/ and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/
endfunction
//
globals
private integer BuffType
endglobals
private struct Data extends DamageModifier
unit caster
unit target
effect targetFx
integer level
integer healings=0
static thistype Tmps
static real CurrentDamage
static thistype array Instance
private method onDestroy takes nothing returns nothing
set Instance[GetUnitId(.target)]=0
set .caster=null
set .target=null
if TARGET_FX!="" then
call DestroyEffect(.targetFx)
set .targetFx=null
endif
endmethod
private static method AoeHealEnum takes nothing returns boolean
local unit u=GetFilterUnit()
if u!=.Tmps.target and ValidTarget(u, .Tmps.caster) then
call SetUnitLife(u, GetUnitLife(u)+Aoe_Heal(.Tmps.level, .Tmps.target, .CurrentDamage))
static if LIBRARY_TimedHandles then
if FX_DURATION>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(FX, u, FX_ATTPT), FX_DURATION)
else
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endif
else
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endif
endif
set u=null
return false
endmethod
private method onDamageTaken takes unit origin, real damage returns real
local real blocked=0
if GetRandomReal(0,1)<=Chance(.level) and damage+0.406>=Minimum_Damage(.level, .target) then
if ValidTarget(.target, .caster) then
call SetUnitLife(.target, GetUnitLife(.target)+Health_Restored(.level, .target, damage))
static if LIBRARY_TimedHandles then
if FX_DURATION>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(FX, .target, FX_ATTPT), FX_DURATION)
else
call DestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endif
else
call DestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endif
set blocked=Damage_Blocked(.level, .target, damage)
endif
if Aoe(.level)>0. then
set .Tmps=this
set .CurrentDamage=damage
call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), Aoe(.level), Condition(function thistype.AoeHealEnum))
endif
set .healings=.healings+1
if Max_Healings(.level)>0 and .healings>=Max_Healings(.level) then
call UnitRemoveBuff(.target, BuffType)
endif
return -blocked
endif
return 0.
endmethod
static method create takes unit caster, unit target returns thistype
local thistype s=Instance[GetUnitId(target)]
if s==0 then
set s=.allocate(target, PRIORITY)
set s.caster=caster
set s.target=target
if TARGET_FX!="" then
set s.targetFx=AddSpecialEffectTarget(TARGET_FX, target, TARGET_FX_ATTPT)
endif
set s.level=GetUnitAbilityLevel(caster, AID)
set Instance[GetUnitId(target)]=s
else
if s.level<GetUnitAbilityLevel(caster, AID) then
set s.caster=caster
set s.healings=0
set s.level=GetUnitAbilityLevel(caster, AID)
endif
endif
set UnitAddBuff(caster, target, BuffType, Duration(s.level), s.level).data=s
return s
endmethod
static method BuffRemoved takes nothing returns nothing
call thistype(GetEventBuff().data).destroy()
endmethod
endstruct
private function CastResponse takes nothing returns nothing
call Data.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(AID, CastResponse)
set BuffType=DefineBuffType(BUFF_PLACER_AID, BID, 0, false, true, 0,0,Data.BuffRemoved)
call SetUpSpellData()
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library SmitingZeal initializer Init uses SpellEvent, GroupUtils
globals
private constant integer AID = 'A002' // any passive ability should do fine, preferably one that doesnt place a buff.
private constant string DAMAGE_FX = "Abilities\\Spells\\Human\\Feedback\\SpellBreakerAttack.mdl" // effect displayed when a unit gets damaged by this ability
private constant string DAMAGE_FX_ATTPT = "overhead" // where to display the effect
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC // what type of damage is dealt to enemy units surrounding the caster (magic, physical, pure, ...)
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC // what type of damage is dealt to enemy units surrounding the caster (magic, physical, pure, ...)
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS // sound played when a unit is damaged by this ability, WHOKNOWS or null for no sound.
private real array AOE // enemy units within this radius get damaged
private real array MANA_COST // this ability drains this much additional mana from the caster, values >0 but <1 are values relative to the maximum mana of the caster
private constant boolean ENFORCE_MANA_COST = false // if true, the effects of this ability will only be triggered if the hero has enough mana,
// if false, it will be triggered whether or not the caster has enough mana, and if he doesnt,
// it will subtract the remaining mana points
endglobals
private function Aoe takes integer level returns real
return AOE[level]
endfunction
private function Mana_Cost takes integer level returns real
return MANA_COST[level]
endfunction
private function Damage takes unit caster, integer level returns real
return GetHeroInt(caster, true)*(1.+level)
endfunction
private function SetUpSpellData takes nothing returns nothing
set AOE[1]=300.
set AOE[2]=375.
set AOE[3]=450.
set MANA_COST[1]=20.
set MANA_COST[2]=20.
set MANA_COST[3]=20.
endfunction
private function ValidateTarget takes unit u, unit caster returns boolean
return IsUnitEnemy(u, GetOwningPlayer(caster))/*
*/ and IsUnitType(u, UNIT_TYPE_DEAD)==false/*
*/ and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/ and IsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/ and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/ and IsUnitVisible(u, GetOwningPlayer(caster))/*
*/
endfunction
//
globals
private boolexpr DamageFilter
private real tmpdam
endglobals
private function DamageFilterFunc takes nothing returns boolean
local unit u=GetFilterUnit()
if ValidateTarget(u, SpellEvent.CastingUnit) then
call UnitDamageTarget(SpellEvent.CastingUnit, u, tmpdam, true, true, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_FX, u, DAMAGE_FX_ATTPT))
endif
set u=null
return false
endfunction
private function CastResponse takes nothing returns nothing
local integer level = GetUnitAbilityLevel(SpellEvent.CastingUnit, AID)
local real cost=0
if level>0 then
if Mana_Cost(level)>=1 then
set cost=Mana_Cost(level)
elseif Mana_Cost(level)>0 then
set cost=Mana_Cost(level)*GetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA)
endif
if not ENFORCE_MANA_COST or GetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA)>=cost then
call SetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA, RMaxBJ(GetUnitState(SpellEvent.CastingUnit, UNIT_STATE_MANA)-Mana_Cost(level), 0))
set tmpdam=Damage(SpellEvent.CastingUnit, level)
call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(SpellEvent.CastingUnit), GetUnitY(SpellEvent.CastingUnit), Aoe(level), DamageFilter)
endif
endif
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(0, CastResponse)
set DamageFilter=Condition(function DamageFilterFunc)
call SetUpSpellData()
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library ReverseDamage initializer Init uses GroupUtils, DamageModifiers, SpellEvent, AutoIndex, IntuitiveBuffSystem, optional TimedHandles
private keyword Data // DO NOT TOUCH!
globals
private constant integer AID = 'A003' // ability triggering this. Any unit targeting ability is fine. Used Bloodlust
private constant integer BUFF_PLACER_AID = 'A006' // Ability placing BID on the unit this ability is added to. Based off of Slow Aura (Tornado).
private constant integer BID = 'B002' // buff placed by BUFF_PLACER_AID on the target unit
private constant string FX = "Abilities\\Spells\\Undead\\ReplenishHealth\\ReplenishHealthCasterOverhead.mdl" // displayed on the target unit when the effect of this ability is triggered.
private constant string FX_ATTPT = "head"
private constant real FX_DURATION = 0. // 0 or less only plays the death animation // ignored if TimedHandles is not present in the map
private constant string TARGET_FX = "Abilities\\Spells\\Items\\HealingSalve\\HealingSalveTarget.mdl" // displayed on the target of this spell
private constant string TARGET_FX_ATTPT = "head" // where to display the effect on the target of this spell
private constant integer PRIORITY = 0 // when to trigger the effects of an instance of this spell when a unit receives damage // refer to DamageModifiers documentation
private real array DURATION // how long does the buff last?
private real array AOE // Heals all units around the damaged target in this area
private real array CHANCE // Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
private integer array MAX_HEALINGS // heal at most this many times // zero or less for no limit
endglobals
// how long does the buff last?
private function Duration takes integer level returns real
return DURATION[level]
endfunction
// Heals all units around the damaged target in this area
private function Aoe takes integer level returns real
return AOE[level]
endfunction
// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
private function Chance takes integer level returns real
return CHANCE[level]
endfunction
// heal at most this many times // zero or less for no limit
private function Max_Healings takes integer level returns integer
return MAX_HEALINGS[level]
endfunction
private function SetUpSpellData takes nothing returns nothing
// how long does the buff last?
set DURATION[1]=5
set DURATION[2]=5
set DURATION[3]=5
// Heals all units around the damaged target in this area
set AOE[1]=0
set AOE[2]=0
set AOE[3]=0
// Chance to trigger this ability // 1.0 = 100% // 0.0 = 0%
set CHANCE[1]=1
set CHANCE[2]=1
set CHANCE[3]=1
// heal at most this many times // zero or less for no limit
set MAX_HEALINGS[1]=1
set MAX_HEALINGS[2]=1
set MAX_HEALINGS[3]=1
endfunction
// if you want to get the targets current HP, use GetUnitLife(whichUnit)
// damage dealt to a unit with this buff active needs to be greater than what this function returns to be blocked
private function Minimum_Damage takes integer level, unit target returns real
return 20.
endfunction
// how much damage to block
private function Damage_Blocked takes integer level, unit target, real damage returns real
return RMinBJ(damage, 100.)
endfunction
// how much health to restore after blocking
private function Health_Restored takes integer level, unit target, real damage returns real
return RMinBJ(damage, 100.)
endfunction
// how much health to heal units in the area
private function Aoe_Heal takes integer level, unit target, real damage returns real
return 0.
endfunction
// only units matching these criteria will get healed
private function ValidTarget takes unit u, unit caster returns boolean
return IsUnitAlly(u, GetOwningPlayer(caster))/*
*/ and IsUnitType(u, UNIT_TYPE_MECHANICAL)==false/*
*/ and IsUnitType(u, UNIT_TYPE_STRUCTURE)==false/*
*/ and IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)==false/*
*/
endfunction
//
globals
private integer BuffType
endglobals
private struct Data extends DamageModifier
unit caster
unit target
effect targetFx
integer level
integer healings=0
static thistype Tmps
static real CurrentDamage
static thistype array Instance
private method onDestroy takes nothing returns nothing
set Instance[GetUnitId(.target)]=0
set .caster=null
set .target=null
if TARGET_FX!="" then
call DestroyEffect(.targetFx)
set .targetFx=null
endif
endmethod
private static method AoeHealEnum takes nothing returns boolean
local unit u=GetFilterUnit()
if u!=.Tmps.target and ValidTarget(u, .Tmps.caster) then
call SetUnitLife(u, GetUnitLife(u)+Aoe_Heal(.Tmps.level, .Tmps.target, .CurrentDamage))
static if LIBRARY_TimedHandles then
if FX_DURATION>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(FX, u, FX_ATTPT), FX_DURATION)
else
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endif
else
call DestroyEffect(AddSpecialEffectTarget(FX, u, FX_ATTPT))
endif
endif
set u=null
return false
endmethod
private method onDamageTaken takes unit origin, real damage returns real
local real blocked=0
if GetRandomReal(0,1)<=Chance(.level) and damage+0.406>=Minimum_Damage(.level, .target) then
if ValidTarget(.target, .caster) then
call SetUnitLife(.target, GetUnitLife(.target)+Health_Restored(.level, .target, damage))
static if LIBRARY_TimedHandles then
if FX_DURATION>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(FX, .target, FX_ATTPT), FX_DURATION)
else
call DestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endif
else
call DestroyEffect(AddSpecialEffectTarget(FX, .target, FX_ATTPT))
endif
set blocked=Damage_Blocked(.level, .target, damage)
endif
if Aoe(.level)>0. then
set .Tmps=this
set .CurrentDamage=damage
call GroupEnumUnitsInArea(ENUM_GROUP, GetUnitX(.target), GetUnitY(.target), Aoe(.level), Condition(function thistype.AoeHealEnum))
endif
set .healings=.healings+1
if Max_Healings(.level)>0 and .healings>=Max_Healings(.level) then
call UnitRemoveBuff(.target, BuffType)
endif
return -blocked
endif
return 0.
endmethod
static method create takes unit caster, unit target returns thistype
local thistype s=Instance[GetUnitId(target)]
if s==0 then
set s=.allocate(target, PRIORITY)
set s.caster=caster
set s.target=target
if TARGET_FX!="" then
set s.targetFx=AddSpecialEffectTarget(TARGET_FX, target, TARGET_FX_ATTPT)
endif
set s.level=GetUnitAbilityLevel(caster, AID)
set Instance[GetUnitId(target)]=s
else
if s.level<GetUnitAbilityLevel(caster, AID) then
set s.caster=caster
set s.healings=0
set s.level=GetUnitAbilityLevel(caster, AID)
endif
endif
set UnitAddBuff(caster, target, BuffType, Duration(s.level), s.level).data=s
return s
endmethod
static method BuffRemoved takes nothing returns nothing
call thistype(GetEventBuff().data).destroy()
endmethod
endstruct
private function CastResponse takes nothing returns nothing
call Data.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(AID, CastResponse)
set BuffType=DefineBuffType(BUFF_PLACER_AID, BID, 0, false, true, 0,0,Data.BuffRemoved)
call SetUpSpellData()
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library IntuitiveBuffSystem initializer Init requires Stack, TimerUtils, AutoIndex, AbilityPreload
//******************************************************************************
//* BY: Rising_Dusk
//* (Intuitive) Buff System 1.05
//*
//* This library is a simple and easy to understand implementation of custom
//* buff handling in JASS. I call it the Intuitive Buff System because anyone
//* should be able to pick it up and use it right away. It uses a callback
//* driven style where the user specifies which callbacks run either
//* periodically over a buff's duration or when the buff is removed. This covers
//* all possible types of buffs that one might want to create.
//*
//******************************************************************************
//*
//* In order to make a buff work, one must first create a custom aura ability in
//* the Object Editor and a custom buff for that aura. The raw ids for these
//* objects need to be supplied to the IBS via the DefineBuffType function.
//*
//* function DefineBuffType takes integer AbilId, integer BuffId, ...
//* ... real period, boolean isPeriodic, boolean isPositive, callback onAdd, ...
//* ... callback onPeriodic, callback onEnd returns integer
//*
//* The arguments for this function are as follows:
//* [integer] AbilId Owner of the buff being applied
//* [integer] BuffId Target for the buff to be applied to
//* [real] period Type of the buff from DefineBuffType
//* [boolean] isPeriodic Whether the buff is periodic or not
//* [boolean] isPositive Whether the buff is positive or not
//* [callback] onAdd Callback to run when a buff is added
//* [callback] onPeriodic Callback to run every buff iteration
//* [callback] onEnd Callback to run when a buff ends
//*
//* NOTE: All callbacks must follow the below structural convention
//* function MyCallback takes nothing returns nothing
//*
//* The callback functions may be private, public, or whatever. When using them
//* in the DefineBuffType function call, simply put the function's name for the
//* respective argument (no function keyword). If you do not want any function
//* to be called for one of the callbacks, simply use 0 for that argument.
//*
//* Once the bufftype is registered with the system, you are free to use it
//* whenever you want to. It returns an integer, which you should set to a
//* global integer in a scope initializer or some initialization function. To
//* apply a buff to a unit, simple call the UnitAddBuff function. All of these
//* values are effectively constant for any buff created of the given bufftype.
//*
//* The period argument tells the system the interval for running the
//* onPeriodic callback function. The period and onPeriodic arguments only
//* matter if the isPeriodic argument is true. The onEnd argument runs when a
//* buff is removed either manually, by it expiring, or by the unit it is on
//* dying. The onAdd argument runs when a buff is either applied or refreshed.
//* Please do not destroy the dbuff struct inside your callback functions; that
//* will cause doublefrees. The system automatically cleans them up.
//*
//* Inside the callbacks, you get one event response:
//* function GetEventBuff takes nothing returns dbuff
//*
//* Usage:
//* local dbuff db = GetEventBuff()
//*
//* Within the above struct, the following information can be retrieved (or
//* modified, but I recommend not changing most of the parameters). At any given
//* time during the buff's existence, these values can also be retrieved with a
//* call to GetUnitBuff, GetRandomBuff, or GetRandomBuffSigned on the unit.
//*
//* [unit] .source Owning unit of the buff
//* [unit] .target Unit the buff is applied to
//* [real] .duration Total duration for buff expiration
//* [real] .timeElapsed() Total elapsed time of the buff
//* [real] .timeRemaining() Remaining time before buff expiration
//* .setDuration(NewDur) Sets the current buff duration to NewDur
//* .addDuration(ModDur) Adds ModDur to the current buff duration
//* [integer] .btype.period Periodicity for the buff
//* [integer] .level Level of the buff used on the unit
//* [integer] .data An integer the user can store data to
//* [boolean] .isPeriodic Whether the buff is periodic or not
//* [boolean] .isExpired Whether the buff expired naturally or not
//* Can only return true in the 'OnEnd' callback
//* [boolean] .isRemoved Whether the buff was purged or removed by death or not
//* Can only return true in the 'OnEnd' callback
//* [boolean] .isRefreshed Whether the buff was refreshed or not
//* Can only return true in the 'OnAdd' callback
//*
//******************************************************************************
//*
//* function UnitAddBuff takes unit source, unit target, integer btype, ...
//* ... real dur, integer lvl returns dbuff
//*
//* The arguments for this function are as follows:
//* [unit] source The 'owner' of the buff being applied
//* [unit] target The target for the buff to be applied to
//* [integer] btype The type of the buff from DefineBuffType
//* [real] dur The total duration of the buff
//* [integer] lvl The 'level' of the buff
//*
//* Constants usable in this function call:
//* [real] BUFF_PERMANENT This is the value you should use for the
//* duration argument if you want a buff
//* that never ends.
//*
//* Additional options of note:
//* set mybuff.data = SomeInteger This allows you to attach an integer to
//* a buff that you can get in the event
//* callbacks.
//*
//* The lvl argument is there for reapplication of buff purposes. A buff being
//* applied of a higher level than the old buff will always overwrite the old
//* one, it doesn't matter if duration is higher or not. This way, if you want a
//* buff to get shorter per level, the system will support that.
//*
//******************************************************************************
//*
//* Available system functions:
//*
//* > function GetUnitBuff takes unit target, integer whichType returns dbuff
//*
//* This function allows the user to get a specific buff on a unit at any point
//* if the user knows the bufftype they want. Will return 0 if the buff does not
//* exist.
//*
//* > function GetRandomBuff takes unit target returns dbuff
//*
//* This function allows the user to get a random buff on a unit at any point.
//* Getting a random buff is most useful for stealing or moving of arbitrary
//* buffs on a unit. Will return 0 if the unit has no buffs.
//*
//* > function GetRandomBuffSigned takes unit target, boolean isPos returns dbuff
//*
//* This function, like GetRandomBuff, returns a random buff on a unit given a
//* 'sign'. The isPos argument lets you specify to either get a random positive
//* buff if true or a random negative buff if false. Will return 0 if the unit
//* has no buffs of the desired alignment.
//*
//* > function GetLastAppliedBuff takes unit target returns dbuff
//*
//* This function returns the most recently applied buff on a unit. Will return
//* 0 if the unit has no buffs.
//*
//* > function GetFirstAppliedBuff takes unit target returns dbuff
//*
//* This function returns the first buff in a user's bufflist that is still
//* active. Will return 0 if the unit has no buffs.
//*
//* > function UnitHasBuff takes unit target, integer whichType returns boolean
//*
//* This function will return true if the given unit has the specified buff.
//*
//* > function UnitHasAnyBuff takes unit target returns boolean
//*
//* This function will return true if the given unit has any buff managed by
//* the system.
//*
//* > function UnitRemoveBuff takes unit target, integer whichType returns boolean
//*
//* This function removes a buff of a specified type from a given unit. Will
//* return false if the buff did not exist to be removed.
//*
//* > function UnitRemoveAllBuffs takes unit target returns nothing
//*
//* This function removes all buffs of any type from a given unit.
//*
//******************************************************************************
//*
//* Finally, there is the inherent limit that you can only have 8192 buffs and
//* 8192 bufftypes at once. If you ever get this high, your map will have worse
//* problems than this system working improperly.
//*
//* I hope you find this library as useful as I do. Thanks for reading the
//* documentation. If you have any questions for me about the IBS, how it works,
//* or if you have suggestions for it, please contact me at WC3C by private
//* message. This system is only authorized for release at Warcraft 3 Campaigns.
//*
//* Enjoy!
//*
globals
//Constant for use with infinite duration buffs
constant real BUFF_PERMANENT = 0x800000
endglobals
private keyword TimeOut
private keyword TempBuff
private function interface callback takes nothing returns nothing
globals
private hashtable ht = InitHashtable() //Hashtable for all data attachment
private integer array lists
endglobals
private struct bufftype
//All readonly so the user doesn't mess with them
integer abilid
integer buffid
real period
boolean isPeriodic
boolean isPositive
//All of the callback behavior for the bufftype
callback onAdd
callback onPeriodic
callback onEnd
static method create takes integer abilid, integer buffid, real period, boolean isPeriodic, boolean isPositive, callback onAdd, callback onPeriodic, callback onEnd returns bufftype
local bufftype bt = bufftype.allocate()
set bt.abilid = abilid
set bt.buffid = buffid
set bt.period = period
set bt.isPeriodic = isPeriodic
set bt.onAdd = onAdd
set bt.onPeriodic = onPeriodic
set bt.onEnd = onEnd
return bt
endmethod
endstruct
private struct bufflist
Stack sta
unit target
static method create takes unit target returns bufflist
local bufflist bl = bufflist.allocate()
set bl.target = target
set bl.sta = Stack.create()
//Attach it to the unit
set lists[GetUnitId(target)] = integer(bl)
return bl
endmethod
private method onDestroy takes nothing returns nothing
call .sta.destroy() //Destroy the stack
set lists[GetUnitId(.target)] = 0 //Remove array value
endmethod
endstruct
struct dbuff
unit source
unit target
real elapsed = 0.
real duration
integer level
integer data = 0
bufftype btype
timer tmr
//Behavior variables inside the struct
boolean isPeriodic
boolean isExpired = false
boolean isRemoved = false
boolean isRefreshed = false
//Handy methods that save the user math they don't want to do
method timeElapsed takes nothing returns real
if .btype.isPeriodic then
return .elapsed+TimerGetElapsed(.tmr)
else
return TimerGetElapsed(.tmr)
endif
endmethod
method timeRemaining takes nothing returns real
if .btype.isPeriodic then
return .duration-.elapsed-TimerGetElapsed(.tmr)
else
return TimerGetRemaining(.tmr)
endif
endmethod
method setDuration takes real newDur returns nothing
local real oldDur = .duration
if .timeElapsed() >= newDur then
//This is equivalent to force removing the buff, so kill it
set .isExpired = false
set .isRemoved = true
set .isRefreshed = false
call .destroy() //Timer gets recycled in here!
else
if .isPeriodic then
if .timeElapsed() + .btype.period > newDur then
//Update timeout to cover last segment of buff duration
call TimerStart(.tmr, newDur-.timeElapsed(), false, TimeOut)
//Since it won't clear a full period, we don't let it run onPeriodic
set .isPeriodic = false
endif
else
//Run for however long you have to so total duration is the new duration
call TimerStart(.tmr, newDur-.timeElapsed() , false, TimeOut)
endif
set .duration = newDur
endif
endmethod
method addDuration takes real modDur returns nothing
//Just calls the setDuration method
call .setDuration(.duration+modDur)
endmethod
//Some callback running methods
method runAdd takes nothing returns nothing
set .isExpired = false
set .isRemoved = false
set .isRefreshed = false
call .btype.onAdd.execute()
endmethod
method runRefreshed takes nothing returns nothing
set .isRefreshed = true
set .isExpired = false
set .isRemoved = false
call .btype.onAdd.execute()
endmethod
method runPeriodic takes nothing returns nothing
set .isExpired = false
set .isRemoved = false
set .isRefreshed = false
call .btype.onPeriodic.execute()
endmethod
method runExpired takes nothing returns nothing
//This destroys the dbuff
set .isExpired = true
set .isRemoved = false
set .isRefreshed = false
call .destroy() //Timer gets recycled in here!
endmethod
method runRemoved takes nothing returns nothing
//This destroys the dbuff
set .isExpired = false
set .isRemoved = true
set .isRefreshed = false
call .destroy() //Timer gets recycled in here!
endmethod
static method create takes unit source, unit target, bufftype btype, real dur, integer lvl returns dbuff
local dbuff db = dbuff.allocate()
local integer id = GetUnitId(target)
set db.source = source
set db.target = target
set db.duration = dur
set db.btype = btype
set db.isPeriodic = btype.isPeriodic
set db.level = lvl
set db.tmr = NewTimer()
//Hook the dbuff to the timer
call SetTimerData(db.tmr, integer(db))
//Add ability to the target of the buff
call UnitAddAbility(target, db.btype.abilid)
call UnitMakeAbilityPermanent(target, true, db.btype.abilid)
//Load to table for future referencing
call SaveInteger(ht, btype, id, integer(db))
//Push the buff into the bufflist stack on the target
call bufflist(lists[id]).sta.add(integer(db))
return db
endmethod
private method onDestroy takes nothing returns nothing
local integer id = GetUnitId(.target)
//Clear the table value so the system knows it's done
//Done before the onRemove call to prevent potential double frees with death trigger
call RemoveSavedInteger(ht, .btype, id)
//Remove the buff from the bufflist stack on the target
call bufflist(lists[id]).sta.remove(integer(this))
//Set up and run the onRemove callback
set TempBuff = this
call .btype.onEnd.execute()
//Clear stuff inside the struct and on the unit
call UnitRemoveAbility(.target, .btype.abilid)
call UnitRemoveAbility(.target, .btype.buffid)
call ReleaseTimer(.tmr)
endmethod
endstruct
globals
private code TimeOut = null //Code callback for buff timeouts
private integer BuffTypeCount = 0 //Counter for how many buff types exist
private bufftype array BuffTypes //Array for all defined buff types
private dbuff TempBuff = 0 //Temp buff for callback data
endglobals
private function BuffTimeout takes nothing returns nothing
local timer t = GetExpiredTimer()
local dbuff db = dbuff(GetTimerData(t))
//Different behavior for periodic buffs than one-timers
if db.isPeriodic then
//Run the onPeriod callback no matter what
set TempBuff = db
call db.runPeriodic()
//Check if this iteration kills the buff
set db.elapsed = db.elapsed + db.btype.period
if db.elapsed >= db.duration and not db.isRemoved then
//Kill the buff completely if it hasn't been cleared elsewhere
call db.runExpired()
elseif db.elapsed + db.btype.period > db.duration and not db.isRemoved then
//Update timeout to cover last segment of buff duration
call TimerStart(db.tmr, db.duration-db.elapsed, false, function BuffTimeout)
//Since it won't clear a full period, we don't let it run onPeriodic
set db.isPeriodic = false
elseif TimerGetElapsed(db.tmr) < db.btype.period then
//Update period of timer to normal value
call TimerStart(db.tmr, db.btype.period, true, function BuffTimeout)
endif
elseif not db.isRemoved then
//Kill the buff completely, set it to expired naturally
//Shouldn't run if it was force removed by the on-death trigger
call db.runExpired()
endif
set t = null
endfunction
//******************************************************************************
function GetEventBuff takes nothing returns dbuff
return TempBuff
endfunction
//******************************************************************************
function UnitHasBuff takes unit target, integer whichType returns boolean
return HaveSavedInteger(ht, whichType, GetUnitId(target))
endfunction
function UnitHasAnyBuff takes unit target returns boolean
return bufflist(lists[GetUnitId(target)]).sta.size > 0
endfunction
function UnitRemoveBuff takes unit target, integer whichType returns boolean
local dbuff db = 0
if HaveSavedInteger(ht, whichType, GetUnitId(target)) then
set db = dbuff(LoadInteger(ht, whichType, GetUnitId(target)))
//Buff exists, clear it
call db.runRemoved()
return true
endif
return false
endfunction
private function RemoveAllBuffsEnum takes integer value returns nothing
call dbuff(value).runRemoved()
endfunction
function UnitRemoveAllBuffs takes unit target returns nothing
call bufflist(lists[GetUnitId(target)]).sta.enum(RemoveAllBuffsEnum, true)
endfunction
function GetUnitBuff takes unit target, integer whichType returns dbuff
return dbuff(LoadInteger(ht, whichType, GetUnitId(target)))
endfunction
function GetRandomBuff takes unit target returns dbuff
local bufflist bl = bufflist(lists[GetUnitId(target)])
if bl.sta.size > 0 then
return dbuff(bl.sta.random)
endif
return 0 //No buff to return
endfunction
function GetRandomBuffSigned takes unit target, boolean isPos returns dbuff
local bufflist bl = bufflist(lists[GetUnitId(target)])
local Stack s1 = 0
local Stack s2 = 0
local integer v = 0
//Only do this stuff if the unit has any buffs at all
if bl.sta.size > 0 then
//Build the needed stacks
set s1 = bl.sta.copy()
set s2 = Stack.create()
//Loop through and build a new stack of buffs of desired type
loop
exitwhen s1.size == 0
set v = s1.first
if dbuff(v).btype.isPositive == isPos then
//Found one, add to #2 stack
call s2.add(v)
endif
call s1.remove(v)
endloop
if s2.size > 0 then
//Get random member of generated stack
set v = s2.random
else
set v = 0
endif
//Destroy the generated stacks
call s1.destroy()
call s2.destroy()
//Return our buff or 0
if v > 0 then
return dbuff(v) //Return our buff
endif
endif
return 0 //No buff to return
endfunction
function GetLastAppliedBuff takes unit target returns dbuff
local bufflist bl = bufflist(lists[GetUnitId(target)])
if bl.sta.size > 0 then
return dbuff(bl.sta.first)
endif
return 0 //No buff to return
endfunction
function GetFirstAppliedBuff takes unit target returns dbuff
local bufflist bl = bufflist(lists[GetUnitId(target)])
local Stack s = 0
local integer v = 0
//Only do this stuff if the unit has any buffs at all
if bl.sta.size > 0 then
//Build the stack we need
set s = bl.sta.copy()
//Loop through until you find the bottom of the stack
loop
exitwhen s.size == 0
set v = s.first
call s.remove(v)
endloop
return dbuff(v)
endif
return 0 //No buff to return
endfunction
function UnitAddBuff takes unit source, unit target, integer btype, real dur, integer lvl returns dbuff
local timer t = null
local integer id = GetUnitId(target)
local bufftype bt = bufftype(btype)
local integer d = 0
local dbuff db = 0
//Standard debugging procedure
if source == null or target == null or btype < 0 or lvl < 0 or dur <= 0. then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Invalid buff creation parameters")
return 0
endif
//Find if this previous instance exists in the Table
if HaveSavedInteger(ht, btype, id) then
//Exists, use its data
set db = dbuff(LoadInteger(ht, btype, id))
if (lvl == db.level and db.timeRemaining() < dur) or lvl > db.level then
//Update all applicable values to the newly supplied ones if new instance overwrites
//Elapsed goes to 0 because it's as if a new buff has been cast, run timer again
set db.source = source
set db.target = target
set db.elapsed = 0.
set db.duration = dur
set db.level = lvl
if not db.isPeriodic and db.btype.isPeriodic then
//If it was on the last segment of a periodic timer, reset periodicity
set db.isPeriodic = true
endif
//Run the onAdd callback
set TempBuff = db
//It becomes a refreshed buff now
call db.runRefreshed()
if db.isPeriodic then
call TimerStart(db.tmr, db.btype.period-TimerGetElapsed(db.tmr), true , function BuffTimeout)
else
call TimerStart(db.tmr, dur , false, function BuffTimeout)
endif
endif
else
//Doesn't exist, create it
set db = dbuff.create(source, target, bt, dur, lvl)
//Run the onAdd callback
set TempBuff = db
call db.runAdd()
if db.isPeriodic then
call TimerStart(db.tmr, db.btype.period, true , function BuffTimeout)
else
call TimerStart(db.tmr, dur , false, function BuffTimeout)
endif
endif
return db
endfunction
function DefineBuffType takes integer AbilId, integer BuffId, real period, boolean isPeriodic, boolean isPositive, callback onAdd, callback onPeriodic, callback onEnd returns integer
local bufftype bt = bufftype.create(AbilId, BuffId, period, isPeriodic, isPositive, onAdd, onPeriodic, onEnd)
//Preload abilities to prevent first-buff lag
call AbilityPreload(AbilId)
set BuffTypeCount = BuffTypeCount + 1
set BuffTypes[BuffTypeCount] = bt
return integer(bt)
endfunction
//******************************************************************************
private function DeathActions takes nothing returns nothing
call UnitRemoveAllBuffs(GetDyingUnit())
endfunction
//******************************************************************************
private function UnitEntersMap takes unit u returns nothing
call bufflist.create(u)
endfunction
private function UnitLeavesMap takes unit u returns nothing
if lists[GetUnitId(u)] != 0 then
call bufflist(lists[GetUnitId(u)]).destroy()
endif
endfunction
private function Init takes nothing returns nothing
local trigger trg = CreateTrigger()
//Trigger for removing buffs on death
call TriggerAddAction(trg, function DeathActions)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_DEATH)
//Indexing callback to create bufflists for indexed units
call OnUnitIndexed(UnitEntersMap)
//Removal callback to remove bufflists for indexed units
call OnUnitDeindexed(UnitLeavesMap)
//Initialize the callback code var
set TimeOut = function BuffTimeout
endfunction
endlibrary
//TESH.scrollpos=-1
//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=-1
//TESH.alwaysfold=0
library AbilityPreload
//===========================================================================
// Information:
//==============
//
// Preloading removes the noticeable delay the first time an ability
// is loaded in a game. If an ability was not already on a pre-placed unit
// or a unit that was created during initialization, preloading is needed
// to prevent a delay.
//
//===========================================================================
// AbilityPreload API:
//=====================
//
// AbilityPreload(abilityid) :
// Call this before any time has elapsed to preload a specific
// ability. If debug mode is enabled, you will see an error message
// if you call this after initialization, or if you try to preload
// an ability that does not exist. Will inline to a UnitAddAbility
// call if debug mode is disabled.
//
// AbilityRangePreload(start, end) :
// Same as AbilityPreload, but preloads a range of abilities.
// It will iterates between the two rawcode values and preload
// every ability along the way. It will not show an error message
// for non-existent abilities.
//
//===========================================================================
// Configuration:
//================
globals
private constant integer PreloadUnitRawcode = 'zsmc'
//This is the rawcode for "Sammy!". It is never used and has no model,
//which makes an ideal preloading unit. Change it if you want to.
endglobals
//===========================================================================
globals
private unit PreloadUnit
endglobals
function AbilityPreload takes integer abilityid returns nothing
static if DEBUG_MODE then
if GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
endif
call UnitAddAbility(PreloadUnit, abilityid)
static if DEBUG_MODE then
if GetUnitAbilityLevel(PreloadUnit, abilityid) == 0 then
call BJDebugMsg("AbilityPreload error: Attempted to preload a non-existent ability")
endif
endif
endfunction
function AbilityRangePreload takes integer start, integer end returns nothing
local integer i = 1
static if DEBUG_MODE then
if GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
endif
if start > end then
set i = -1
endif
loop
exitwhen start > end
call UnitAddAbility(PreloadUnit, start)
set start = start + i
endloop
endfunction
//===========================================================================
private struct Init extends array
private static method onInit takes nothing returns nothing
set PreloadUnit = CreateUnit(Player(15), PreloadUnitRawcode, 0., 0., 0.)
call UnitApplyTimedLife(PreloadUnit, 0, .001)
call ShowUnit(PreloadUnit, false)
call UnitAddAbility(PreloadUnit, 'Aloc')
endmethod
endstruct
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
//***************************************************************************
//*
//* TimedHandles - By TriggerHappy187
//*
//***************************************************************************
//*
//* Installation
//* Simply copy this script into your map, as well as TimerUtils.
//*
//***************************************************************************
//*
//* Documentation
//*
//* All this script does it associates a handle to a
//* timer and then destroys the handle upon the timer's expiration.
//*
//***************************************************************************
//*
//* The Public Functions
//*
//* $DESTROY$Timed - Starts the handle and it's timer, once it expires
//* the handle will be destroyed.
//*
//***************************************************************************
//*
//* Examples
//*
//* call DestroyEffectTimed(AddSpecialEffect("MODELNAME", X, X), 2)
//* call DestroyEffectTimed(AddSpecialEffectTarget("MODELNAME", unit, "attachment-point"), 2)
//*
//***************************************************************************
library TimedHandles requires TimerUtils
//! textmacro TIMEDHANDLES takes HANDLE,DESTROY
struct $HANDLE$timed
$HANDLE$ $HANDLE$_var
private static method remove takes nothing returns nothing
local timer t = GetExpiredTimer()
local $HANDLE$timed d = GetTimerData(t)
call $DESTROY$(d.$HANDLE$_var)
call ReleaseTimer(t)
call d.destroy()
endmethod
static method create takes $HANDLE$ h, real timeout returns $HANDLE$timed
local $HANDLE$timed t = $HANDLE$timed.allocate()
local timer ti = NewTimer()
set t.$HANDLE$_var = h
call SetTimerData(ti, t)
call TimerStart(ti, timeout, false, function $HANDLE$timed.remove)
return t
endmethod
endstruct
function $DESTROY$Timed takes $HANDLE$ h, real duration returns nothing
call $HANDLE$timed.create(h, duration)
endfunction
//! endtextmacro
//! runtextmacro TIMEDHANDLES("effect","DestroyEffect")
//! runtextmacro TIMEDHANDLES("lightning","DestroyLightning")
//! runtextmacro TIMEDHANDLES("weathereffect","RemoveWeatherEffect")
//! runtextmacro TIMEDHANDLES("item","RemoveItem")
//! runtextmacro TIMEDHANDLES("ubersplat","DestroyUbersplat")
endlibrary
//TESH.scrollpos=-1
//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=-1
//TESH.alwaysfold=0
library SpellEvent initializer Init requires Table
//*****************************************************************
//* SPELL EVENT LIBRARY
//*
//* written by: Anitarf
//* requires: -Table
//*
//* Maps with many triggered spells require many triggers that run
//* on spell events and whenever a spell is cast, all those
//* triggers need to be evaluated by the game even though only one
//* actually needs to run. This library has been written to reduce
//* the number of triggers in such maps; instead of having a
//* trigger per spell, you can use this library's single trigger
//* to only run the code associated with the spell that's actually
//* being cast.
//*
//* Perhaps more significant than the marginal speed gain is the
//* feature that allows you to access all the spell event
//* responses from all spell events, something that the native
//* functions senselessly do not support. With this system you can
//* for example easily get the target unit of the spell on the
//* casting finish event.
//*
//* All functions following the Response function interface that
//* is defined at the start of this library can be used to respond
//* to damage events. Simply put them on the call list for any of
//* the spell events using the appropriate function:
//*
//* function RegisterSpellChannelResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellCastResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellEffectResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellFinishResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellEndCastResponse takes integer spellId, Response r returns nothing
//*
//* The first event occurs at the very start of the spell, when
//* the spell's casting time begins; most spells have 0 casting
//* time, so in most cases this first event occurs at the same
//* time as the second one, which runs when the unit actually
//* begins casting a spell by starting it's spell animation. The
//* third event occurs when the spell effect actually takes place,
//* which happens sometime into the unit's spell animation
//* depending on the unit's Animation - Cast Point property.
//* The fourth event runs if the unit finishes casting the spell
//* uninterrupted, which might be important for channeling spells.
//* The last event runs when the unit stops casting the spell,
//* regardless of whether it finished casting or was interrupted.
//*
//* If you specify a spell id when registering a function then
//* that function will only run when that ability is cast; only
//* one function per ability per event is supported, if you
//* register more functions then only the last one registered will
//* be called. If, however, you pass 0 as the ability id parameter
//* then the registered function will run for all spells. Up to
//* 8190 functions can be registered this way for each event.
//* These functions will be called before the ability's specific
//* function in the order they were registered.
//*
//* This library provides it's own event responses that work
//* better than the Blizzard's bugged native cast event responses.
//* They still aren't guaranteed to work after a wait, but aside
//* from that they will work in response functions no matter what
//* event they are registered to.
//*
//* Here are usage examples for all event responses:
//*
//* local integer a = SpellEvent.AbilityId
//* local unit u = SpellEvent.CastingUnit
//* local unit t = SpellEvent.TargetUnit
//* local item i = SpellEvent.TargetItem
//* local destructable d = SpellEvent.TargetDestructable
//* local location l = SpellEvent.TargetLoc
//* local real x = SpellEvent.TargetX
//* local real y = SpellEvent.TargetY
//* local boolean b = SpellEvent.CastFinished
//*
//* SpellEvent.TargetLoc is provided for odd people who insist on
//* using locations, note that if you use it you have to cleanup
//* the returned location yourself.
//*
//* SpellEvent.CastFinished boolean is intended only for the
//* EndCast event as it tells you whether the spell finished or
//* was interrupted.
//*
//*****************************************************************
// use the RegisterSpell*Response functions to add spell event responses to the library
public function interface Response takes nothing returns nothing
// ================================================================
private keyword casterTable
private keyword effectDone
private keyword init
private keyword get
private struct spellEvent
static HandleTable casterTable
boolean effectDone=false
integer AbilityId
unit CastingUnit
unit TargetUnit
item TargetItem=null
destructable TargetDestructable=null
real TargetX=0.0
real TargetY=0.0
boolean CastFinished=false
method operator TargetLoc takes nothing returns location
return Location(.TargetX, .TargetY)
endmethod
private static method create takes nothing returns spellEvent
return spellEvent.allocate()
endmethod
static method init takes nothing returns spellEvent
local spellEvent s=spellEvent.allocate()
set s.AbilityId = GetSpellAbilityId()
set s.CastingUnit = GetTriggerUnit()
set s.TargetUnit = GetSpellTargetUnit()
if s.TargetUnit != null then
set s.TargetX = GetUnitX(s.TargetUnit)
set s.TargetY = GetUnitY(s.TargetUnit)
else
set s.TargetDestructable = GetSpellTargetDestructable()
if s.TargetDestructable != null then
set s.TargetX = GetDestructableX(s.TargetDestructable)
set s.TargetY = GetDestructableY(s.TargetDestructable)
else
set s.TargetItem = GetSpellTargetItem()
if s.TargetItem != null then
set s.TargetX = GetItemX(s.TargetItem)
set s.TargetY = GetItemY(s.TargetItem)
else
set s.TargetX = GetSpellTargetX()
set s.TargetY = GetSpellTargetY()
endif
endif
endif
set spellEvent.casterTable[s.CastingUnit]=integer(s)
return s
endmethod
static method get takes unit caster returns spellEvent
return spellEvent(spellEvent.casterTable[caster])
endmethod
method onDestroy takes nothing returns nothing
call spellEvent.casterTable.flush(.CastingUnit)
set .CastingUnit=null
endmethod
endstruct
globals
spellEvent SpellEvent=0
endglobals
// ================================================================
//! textmacro spellEvent_make takes name
globals
private Response array $name$CallList
private integer $name$CallCount=0
private Table $name$Table
endglobals
private function $name$Calls takes nothing returns nothing
local integer i=0
local integer id=GetSpellAbilityId()
local spellEvent previous=SpellEvent
set SpellEvent=spellEvent.get(GetTriggerUnit())
loop
exitwhen i>=$name$CallCount
call $name$CallList[i].evaluate()
set i=i+1
endloop
if $name$Table.exists(id) then
call Response($name$Table[id]).evaluate()
endif
set SpellEvent=previous
endfunction
function RegisterSpell$name$Response takes integer spellId, Response r returns nothing
if spellId==0 then
set $name$CallList[$name$CallCount]=r
set $name$CallCount=$name$CallCount+1
else
set $name$Table[spellId]=integer(r)
endif
endfunction
//! endtextmacro
//! runtextmacro spellEvent_make("Channel")
//! runtextmacro spellEvent_make("Cast")
//! runtextmacro spellEvent_make("Effect")
//! runtextmacro spellEvent_make("Finish")
//! runtextmacro spellEvent_make("EndCast")
private function Channel takes nothing returns nothing
call spellEvent.init()
call ChannelCalls()
endfunction
private function Cast takes nothing returns nothing
call CastCalls()
endfunction
private function Effect takes nothing returns nothing
local spellEvent s=spellEvent.get(GetTriggerUnit())
if s!=0 and not s.effectDone then
set s.effectDone=true
call EffectCalls()
endif
endfunction
private function Finish takes nothing returns nothing
set spellEvent.get(GetTriggerUnit()).CastFinished=true
call FinishCalls()
endfunction
private function EndCast takes nothing returns nothing
call EndCastCalls()
call spellEvent.get(GetTriggerUnit()).destroy()
endfunction
// ================================================================
private function InitTrigger takes playerunitevent e, code c returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, e )
call TriggerAddAction(t, c)
set t=null
endfunction
private function Init takes nothing returns nothing
set spellEvent.casterTable=HandleTable.create()
set ChannelTable=Table.create()
set CastTable=Table.create()
set EffectTable=Table.create()
set FinishTable=Table.create()
set EndCastTable=Table.create()
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function Channel)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CAST, function Cast)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_EFFECT, function Effect)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_FINISH, function Finish)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function EndCast)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
// to get get the unique value assigned to a particular unit. The GetUnitId
// function is extremely fast because it inlines directly to a GetUnitUserData
// call. AutoIndex automatically assigns an ID to each unit as it enters the
// map, and instantly frees that ID as the unit leaves the map. Detection of
// leaving units is accomplished in constant time without a periodic scan.
//
// AutoIndex uses UnitUserData by default. If something else in your map
// would conflict with that, you can set the UseUnitUserData configuration
// constant to false, and a hashtable will be used instead. Note that hash-
// tables are about 60% slower.
//
// If you turn on debug mode, AutoIndex will be able to display several
// helpful error messages. The following issues will be detected:
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game. Also
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
// you to fully utilize AutoIndex's enter/leave detection capabilities in
// conjunction with your structs.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the delay while saving.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
/*
globals
integer array IntegerData
real array RealData
SomeStruct array SomeStructData
englobals
function Example takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local integer id = GetUnitId(u)
//You now have a unique index for the unit, so you can
//attach or retrieve data about the unit using arrays.
set IntegerData[id] = 5
set RealData[id] = 25.0
set SomeStructData[id] = SomeStruct.create()
//If you have access to the same unit in another function, you can
//retrieve the data by using GetUnitId() and reading the arrays.
endfunction
*/
// The UnitFilter function in the configuration section is provided so that
// you can make AutoIndex completely ignore certain unit-types. Ignored units
// won't be indexed or fire indexed/deindexed events. You may want to filter out
// dummy casters or system-private units, especially ones that use UnitUserData
// internally. xe dummy units are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
/*
function UnitEntersMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
endfunction //Using GetUnitId() during Indexed events works fine...
function UnitLeavesMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
endfunction //So does using GetUnitId() during Deindexed events.
function Init takes nothing returns nothing
call OnUnitIndexed(UnitEntersMap)
call OnUnitDeindexed(UnitLeavesMap)
endfunction
*/
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from the need
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Returns 0 if a null unit was passed. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, this function will print
// an error message when passed a decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit has been indexed. The only time this will return false is
// for units you have filtered using the UnitFilter function, or
// for xe dummy units. You can use this function to easily detect
// dummy units and avoid performing certain actions on them.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// How to use AutoData:
//======================
//
// The AutoData module allows you to associate one or more instances
// of the implementing struct with units, as well as iterate through all
// of the instances associated with each unit.
//
// This association is accomplished through the "me" instance member,
// which the module will place in the implementing struct. Whichever unit
// you assign to "me" becomes the owner of that instance. You may change
// ownership by reassigning "me" to another unit at any time, or you may
// make the instance unowned by assigning "me" to null.
//
// AutoData implements the static method operator [] in your struct
// to allow you to access instances from their owning units. For example,
// you may type: local StructName s = StructName[u]. If u has been set
// to own an instance of StructName, s will be set to that instance.
//
// So, what happens if you assign the same owning unit to multiple
// instances? You may use 2D array syntax to access instances assigned to
// the same unit: local StructName s = StructName[u][n], where u is the
// owning unit, and n is the index beginning with 0 for each unit. You
// can access the size of a unit's instance list (i.e. the number of
// instances belonging to the unit) by using the .size instance member.
/*
struct Example
implement AutoData
static method create takes unit u returns Example
local Example this = allocate()
set me = u //Assigning the "me" member from AutoData.
return this
endmethod
endstruct
function Test takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local Example e1 = Example.create(u)
local Example e2 = Example.create(u)
local Example e3 = Example.create(u)
local Example e
call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
set e = Example[u] //This is the fastest way to iterate the instances belonging
loop //to a specific unit, starting with the first instance.
exitwhen e == 0 //e will be assigned to 0 when no instances remain.
call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
endloop //Thus, index + 1 is next, and index - 1 is previous.
endfunction //This trick allows you to avoid a local counter.
*/
// AutoData restrictions:
// -You may not implement AutoData in any struct which has already
// declared static or non-static method operator [].
// -AutoData will conflict with anything named "me", "size", or
// "index" in the implementing struct.
// -AutoData may not be implemented in structs that extend array.
// -You may not declare your own destroy method. (This restriction
// can be dropped as soon as JassHelper supports module onDestroy).
//
// AutoData information:
// -You do not need to null the "me" member when destroying an
// instance. That is done for you automatically during destroy().
// (But if you use deallocate(), you must null "me" manually.)
// -StructName[u] and StructName[u][0] refer to the same instance,
// which is the first instance that was associated with unit u.
// -StructName[u][StructName[u].size - 1] refers to the instance that
// was most recently associated with unit u.
// -Instances keep their relative order in the list when one is removed.
//
//===========================================================================
// How to use AutoCreate:
//=======================
//
// The AutoCreate module allows you to automatically create instances
// of the implementing struct for units as they enter the game. AutoCreate
// automatically implements AutoData into your struct. Any time an instance
// is automatically created for a unit, that instance's "me" member will be
// assigned to the entering unit.
//
// AutoCreate restrictions:
// -All of the same restrictions as AutoData.
// -If your struct's allocate() method takes parameters (i.e. the parent
// type's create method takes parameters), you must declare a create
// method and pass those extra parameters to allocate yourself.
//
// AutoCreate information:
// -You may optionally declare the createFilter method, which specifies
// which units should recieve an instance as they enter the game. If
// you do not declare it, all entering units will recieve an instance.
// -You may optionally declare the onCreate method, which will run when
// AutoCreate automatically creates an instance. (This is just a stand-
// in until JassHelper supports the onCreate method.)
// -You may declare your own create method, but it must take a single
// unit parameter (the entering unit) if you do so.
/*
struct Example
private static method createFilter takes unit u returns boolean
return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
endmethod
private method onCreate takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" entered the game!")
endmethod
implement AutoCreate
endstruct
*/
//===========================================================================
// How to use AutoDestroy:
//=========================
//
// The AutoDestroy module allows you to automatically destroy instances
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
// automatically implements AutoData into your struct. You must assign a unit
// to the "me" member of an instance for this module to have any effect.
//
// AutoDestroy restrictions:
// -All of the same restrictions as AutoData.
//
// AutoDestroy information:
// -If you also implement AutoCreate in the same struct, remember that it
// assigns the "me" unit automatically. That means you can have fully
// automatic creation and destruction.
/*
struct Example
static method create takes unit u returns Example
local Example this = allocate()
set me = u //You should assign a unit to "me",
return this //otherwise AutoDestroy does nothing.
endmethod //Not necessary if using AutoCreate.
private method onDestroy takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" left the game!")
endmethod
implement AutoDestroy
endstruct
*/
//===========================================================================
// Configuration:
//================
//! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMerger macro above. You may change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
private constant boolean SafeMode = true
//This is set to true by default so that GetUnitId() will ALWAYS work.
//If if this is set to false, GetUnitId() may fail to work in a very
//rare circumstance: creating a unit that has a default-on autocast
//ability, and using GetUnitId() on that unit as it enters the game,
//within a trigger that detects any order. Set this to false for a
//performance boost only if you think you can avoid this issue.
private constant boolean AutoDataFastMode = true
//If this is set to true, AutoData will utilize one hashtable per time
//it is implemented. If this is set to false, all AutoDatas will share
//a single hashtable, but iterating through the instances belonging to
//a unit will become about 12.5% slower. Your map will break if you
//use more than 255 hashtables simultaneously. Only set this to false
//if you suspect you will run out of hashtable instances.
endglobals
private function UnitFilter takes unit u returns boolean
return true
endfunction
//Make this function return false for any unit-types you want to ignore.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. The unit parameter "u" to refers to the unit being filtered.
//Do not filter out xe dummy units; they are automatically filtered.
//===========================================================================
// AutoData / AutoCreate / AutoDestroy modules:
//==============================================
function interface AutoCreator takes unit u returns nothing
function interface AutoDestroyer takes unit u returns nothing
globals
hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
endglobals //initialized and shared between all AutoData implementations.
module AutoData
private static hashtable ht
private static thistype array data
private static integer array listsize
private static key typeid //Good thing keys exist to identify each implementing struct.
private unit meunit
private integer id
readonly integer index //The user can avoid using a local counter because this is accessable.
static method operator [] takes unit u returns thistype
return data[GetUnitId(u)]
endmethod //This is as fast as retrieving an instance from a unit gets.
method operator [] takes integer index returns thistype
static if AutoDataFastMode then //If fast mode is enabled...
return LoadInteger(ht, id, index)
else //Each instance has its own hashtable to associate unit and index.
return LoadInteger(AutoData, id, index*8190+typeid)
endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
endmethod //Somehow, this version is 12.5% slower just because of the math.
private method setIndex takes integer index, thistype data returns nothing
static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
call SaveInteger(ht, id, index, data)
else
call SaveInteger(AutoData, id, index*8190+typeid, data)
endif
endmethod
private method remove takes nothing returns nothing
if meunit == null then //If the struct doesn't have an owner...
return //Nothing needs to be done.
endif
loop
exitwhen index == listsize[id] //The last value gets overwritten by 0.
call setIndex(index, this[index + 1]) //Shift each element down by one.
set this[index].index = index //Update the shifted instance's index.
set index = index + 1
endloop
set listsize[id] = listsize[id] - 1
set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
set meunit = null
endmethod
private method add takes unit u returns nothing
if meunit != null then //If the struct has an owner...
call remove() //remove it first.
endif
set meunit = u
set id = GetUnitId(u) //Cache GetUnitId for slight performance boost.
if data[id] == 0 then //If this is the first instance for this unit...
set data[id] = this //Update the value that thistype[u] returns.
endif
set index = listsize[id] //Remember the index for removal.
call setIndex(index, this) //Add to the array.
set listsize[id] = index + 1
endmethod
method operator me takes nothing returns unit
return meunit
endmethod
method operator me= takes unit u returns nothing
if u != null then //If assigning "me" a non-null value...
call add(u) //Add this instance to that unit's array.
else //If assigning "me" a null value...
call remove() //Remove this instance from that unit's array.
endif
endmethod
method operator size takes nothing returns integer
return listsize[id]
endmethod
method destroy takes nothing returns nothing
call deallocate()
call remove() //This makes removal automatic when an instance is destroyed.
endmethod
private static method onInit takes nothing returns nothing
static if AutoDataFastMode then //If fast mode is enabled...
set ht = InitHashtable() //Initialize one hashtable per instance.
else //If fast mode is disabled...
if AutoData == null then //If the hashtable hasn't been initialized yet...
set AutoData = InitHashtable() //Initialize the shared hashtable.
endif
endif
endmethod
endmodule
module AutoCreate
implement AutoData //AutoData is necessary for AutoCreate.
private static method creator takes unit u returns nothing
local thistype this
local boolean b = true //Assume that the instance will be created.
static if thistype.createFilter.exists then //If createFilter exists...
set b = createFilter(u) //evaluate it and update b.
endif
if b then //If the instance should be created...
static if thistype.create.exists then //If the create method exists...
set this = create(u) //Create the instance, passing the entering unit.
else //If the create method doesn't exist...
set this = allocate() //Just allocate the instance.
endif
set me = u //Assign the instance's owner as the entering unit.
static if thistype.onCreate.exists then //If onCreate exists...
call onCreate() //Call it, because JassHelper should do this anyway.
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoCreate(thistype.creator)
endmethod //During module initialization, pass the creator function to AutoIndex.
endmodule
module AutoDestroy
implement AutoData //AutoData is necessary for AutoDestroy.
static method destroyer takes unit u returns nothing
loop
exitwhen thistype[u] == 0
call thistype[u].destroy()
endloop
endmethod //Destroy each instance owned by the unit until none are left.
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoDestroy(thistype.destroyer)
endmethod //During module initialization, pass the destroyer function to AutoIndex.
endmodule
//===========================================================================
// AutoIndex struct:
//===================
function interface IndexFunc takes unit u returns nothing
static if LIBRARY_AutoEvent then
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
endif
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
private keyword getIndex
private keyword getIndexDebug
private keyword isUnitIndexed
private keyword onUnitIndexed
private keyword onUnitDeindexed
private keyword AutoIndexInit
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger order = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static AutoCreator array creators
private static integer creators_n = -1
private static AutoDestroyer array destroyers
private static integer destroyers_n = -1
private static AutoCreator autocreator
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
if u == null then
return 0
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return getIndex(u)
endmethod //If debug mode is enabled, use the getIndex method that shows errors.
private static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
private static method addAutoCreate_sub takes nothing returns nothing
call autocreator.evaluate(GetEnumUnit())
endmethod
static method addAutoCreate takes AutoCreator func returns nothing
set creators_n = creators_n + 1
set creators[creators_n] = func
if duringinit then //During initialization, evaluate AutoCreate.creator for every preplaced unit.
set autocreator=func
call ForGroup(preplaced, function AutoIndex.addAutoCreate_sub)
endif
endmethod
static method addAutoDestroy takes AutoDestroyer func returns nothing
set destroyers_n = destroyers_n + 1
set destroyers[destroyers_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if idunit[getIndex(whichUnit)] == whichUnit then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
endmethod //Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod //Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
static if LIBRARY_AutoEvent then
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunAutoEvent("Resurrect")
//If AutoEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunAutoEvent("AnimateDead")
//If AutoEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunAutoEvent("Death")
//If AutoEvents is in the map, run the Death events.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
set n = destroyers_n
loop //Destroy AutoDestroy structs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
call thistype(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
else
if GetUnitAbilityLevel(u, LeaveDetectAbilityID)==0 then
//The unit has been removed from the game.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
set n = destroyers_n
loop //Destroy AutoDestroy structs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
call thistype(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
endif
set u = null
return false
endmethod
//===========================================================================
private static method unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then
return //Don't index a unit that already has an ID.
endif
static if LIBRARY_xebasic then
if GetUnitTypeId(u) == XE_DUMMY_UNITID then
return //Don't index xe dummy units.
endif
endif
if not UnitFilter(u) then
return //Don't index units that fail the unit filter.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering unit.
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //Each of these flags are necessary to detect
set animated[index] = false //when a unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
if duringinit then //If a unit enters the map during initialization...
call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
endif //all units are noticed by OnUnitIndexed during initialization.
loop //Create AutoCreate structs for the entering unit.
exitwhen n > creators_n
call creators[n].evaluate(u)
set n = n + 1
endloop
set n = 0
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method onIssuedOrder takes nothing returns boolean
static if SafeMode then //If SafeMode is enabled, perform this extra check.
if getIndex(GetTriggerUnit()) == 0 then //If the unit doesn't already have
call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
endif
endif
return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
endmethod
private static method initEnteringUnit takes nothing returns boolean
call unitEntersMap(GetFilterUnit())
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
call GroupEnumUnitsOfPlayer(g, Player(i), function thistype.initEnteringUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(order, And(function thistype.onIssuedOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(creepdeath, function thistype.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, function thistype.initEnteringUnit)
//The filter function of an EnterRegion trigger runs instantly when a unit is created.
call TimerStart(CreateTimer(), 0., false, function thistype.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
implement AutoIndexInit // need to take advantage of vJass initializing all module onInits before struct onInits
endstruct
private module AutoIndexInit
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
call GroupEnumUnitsOfPlayer(g, Player(i), function thistype.initEnteringUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(order, And(function thistype.onIssuedOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(creepdeath, function thistype.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, function thistype.initEnteringUnit)
//The filter function of an EnterRegion trigger runs instantly when a unit is created.
call TimerStart(CreateTimer(), 0., false, function thistype.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
endmodule
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then //If debug mode is enabled...
return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
else //If debug mode is disabled...
return AutoIndex.getIndex(u) //call the normal, inlinable version.
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=-1
//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=-1
//TESH.alwaysfold=0
library DamageEvent initializer Init requires optional DamageModifiers, optional LightLeaklessDamageDetect, optional IntuitiveDamageSystem, optional xedamage
//*****************************************************************
//* DAMAGE EVENT LIBRARY
//*
//* written by: Anitarf
//* supports: -DamageModifiers
//*
//* This is a damage detection library designed to compensate for
//* the lack of a generic "unit takes damage" event in JASS.
//*
//* All functions following the Response function interface that
//* is defined at the end of the calibration section of this
//* library can be used to respond to damage events. Simply add
//* such functions to the system's call list with the
//* RegisterDamageResponse function.
//*
//* function RegisterDamageResponse takes Response r returns nothing
//*
//* DamageEvent fully supports the use of DamageModifiers. As
//* long as you have the DamageModifiers library in your map
//* DamageEvent will use it to modify the damage before calling
//* the response functions.
//*
//* If the map contains another damage detection library,
//* DamageEvent will interface with it instead of creating
//* it's own damage event triggers to improve performance.
//* Currently supported libraries are LightLeaklessDamageDetect
//* and IntuitiveDamageSystem.
//*
//* DamageEvent is also set up to automatically ignore dummy
//* damage events sometimes caused by xedamage when validating
//* targets (only works with xedamage 0.7 or higher).
//*****************************************************************
globals
// In wc3, damage events sometimes occur when no real damage is dealt,
// for example when some spells are cast that don't really deal damage,
// so this system will only consider damage events where damage is
// higher than this threshold value.
private constant real DAMAGE_THRESHOLD = 0.0
// The following calibration options are only used if the system uses
// it's own damage detection triggers instead of interfacing with other
// damage event engines:
// If this boolean is true, the damage detection trigger used by this
// system will be periodically destroyed and remade, thus getting rid
// of damage detection events for units that have decayed/been removed.
private constant boolean REFRESH_TRIGGER = true
// Each how many seconds should the trigger be refreshed?
private constant real TRIGGER_REFRESH_PERIOD = 300.0
endglobals
private function CanTakeDamage takes unit u returns boolean
// You can filter out which units need damage detection events with this function.
// For example, dummy casters will never take damage so the system doesn't need to register events for them,
// by filtering them out you are reducing the number of handles the game will create, thus increasing performance.
//return GetUnitTypeId(u)!='e000' // This is a sample return statement that lets you ignore a specific unit type.
return true
endfunction
// This function interface is included in the calibration section
// for user reference only and should not be changed in any way.
public function interface Response takes unit damagedUnit, unit damageSource, real damage returns nothing
// END OF CALIBRATION SECTION
// ================================================================
globals
private Response array responses
private integer responsesCount = 0
endglobals
function RegisterDamageResponse takes Response r returns nothing
set responses[responsesCount]=r
set responsesCount=responsesCount+1
endfunction
private function Damage takes nothing returns nothing
// Main damage event function.
local unit damaged=GetTriggerUnit()
local unit damager=GetEventDamageSource()
local real damage=GetEventDamage()
local integer i = 0
loop
exitwhen not (damage>DAMAGE_THRESHOLD)
static if LIBRARY_xedamage then
exitwhen xedamage.isDummyDamage
endif
static if LIBRARY_DamageModifiers then
set damage=RunDamageModifiers()
endif
loop
exitwhen i>=responsesCount
call responses[i].execute(damaged, damager, damage)
set i=i+1
endloop
exitwhen true
endloop
set damaged=null
set damager=null
endfunction
private function DamageC takes nothing returns boolean
call Damage() // Used to interface with LLDD.
return false
endfunction
// ================================================================
globals
private group g
private boolexpr b
private boolean clear
private trigger currentTrg
private triggeraction currentTrgA
private trigger oldTrg = null
private triggeraction oldTrgA = null
endglobals
private function TriggerRefreshEnum takes nothing returns nothing
// Code "borrowed" from Captain Griffen's GroupRefresh function.
// This clears the group of any "shadows" left by removed units.
if clear then
call GroupClear(g)
set clear = false
endif
call GroupAddUnit(g, GetEnumUnit())
// For units that are still in the game, add the event to the new trigger.
call TriggerRegisterUnitEvent( currentTrg, GetEnumUnit(), EVENT_UNIT_DAMAGED )
endfunction
private function TriggerRefresh takes nothing returns nothing
// The old trigger is destroyed with a delay for extra safety.
// If you get bugs despite this then turn off trigger refreshing.
if oldTrg!=null then
call TriggerRemoveAction(oldTrg, oldTrgA)
call DestroyTrigger(oldTrg)
endif
// The current trigger is prepared for delayed destruction.
call DisableTrigger(currentTrg)
set oldTrg=currentTrg
set oldTrgA=currentTrgA
// The current trigger is then replaced with a new trigger.
set currentTrg = CreateTrigger()
set currentTrgA = TriggerAddAction(currentTrg, function Damage)
set clear = true
call ForGroup(g, function TriggerRefreshEnum)
if clear then
call GroupClear(g)
endif
endfunction
// ================================================================
private function DamageRegister takes nothing returns boolean
local unit u = GetFilterUnit()
if CanTakeDamage(u) then
call TriggerRegisterUnitEvent( currentTrg, u, EVENT_UNIT_DAMAGED )
call GroupAddUnit(g, u)
endif
set u = null
return false
endfunction
private function Init takes nothing returns nothing
local rect rec
local region reg
local trigger t
static if LIBRARY_IntuitiveDamageSystem then
// IDDS initialization code
set t=CreateTrigger()
call TriggerAddAction(t, function Damage)
call TriggerRegisterDamageEvent(t, 0)
else
static if LIBRARY_LightLeaklessDamageDetect then
// LLDD initialization code
call AddOnDamageFunc(Condition(function DamageC))
else
// DamageEvent initialization code
set rec = GetWorldBounds()
set reg = CreateRegion()
set t = CreateTrigger()
call RegionAddRect(reg, rec)
call TriggerRegisterEnterRegion(t, reg, Condition(function DamageRegister))
set currentTrg = CreateTrigger()
set currentTrgA = TriggerAddAction(currentTrg, function Damage)
set g = CreateGroup()
call GroupEnumUnitsInRect(g, rec, Condition(function DamageRegister))
if REFRESH_TRIGGER then
call TimerStart(CreateTimer(), TRIGGER_REFRESH_PERIOD, true, function TriggerRefresh)
endif
call RemoveRect(rec)
set rec = null
set b = null
endif
endif
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library DamageModifiers initializer Init requires AutoIndex, optional AbilityPreload, optional xepreload
//*****************************************************************
//* DAMAGE MODIFIERS LIBRARY (AutoIndex version)
//*
//* written by: Anitarf
//* requires: -AutoIndex
//*
//* This is a library that allows you to modify the damage taken
//* by units. It is the most robust system of this kind available,
//* it can both prevent and increase damage, when increasing
//* damage it still awards experience correctly, it can tolerate
//* users dealing additional damage on the damage event as well as
//* allow them to get the correct life of the unit on this event
//* rather than a value inflated by damage prevention.
//*
//* It is important to note that different damage modifying
//* systems can not operate in the same map, so you can not use
//* this system if you are already using a different one.
//*
//* Damage modifiers are implemented as structs that extend the
//* system's DamageModifier struct. A damage modifier is created
//* for a specific unit and can modify both the damage that the
//* unit deals and receives (using the onDamageDealt and
//* onDamageTaken methods respectively, see the interface defined
//* at the end of the calibration section).
//*
//* Keep in mind that when a struct extends DamageModifier, the
//* allocate method of that struct must match the create method of
//* DamageModifier:
//*
//* static method create takes unit u, integer priority returns DamageModifier
//*
//* The unit argument determines on which unit will the modifier
//* be applied and the priority argument determines the order in
//* which the modifiers will be evaluated. The system always
//* evaluates the modifiers on the unit dealing the damage and on
//* the unit taking the damage simultaneously, starting with the
//* modifiers with the highest priority. If two modifiers share
//* the same priority, the one on the unit dealing the damage
//* is evaluated first, if the two modifiers are on the same unit
//* then the NEW_MODIFIER_ON_TOP calibration constant determines
//* which gets evaluated first.
//*
//* Here is an example of a modifier struct:
//*
//* struct armour extends DamageModifier
//* // All additional struct members are optional:
//* static integer PRIORITY=0 // Default priority
//* real power // This lets us give different units differently strong armour.
//*
//* // create method is optional, if you don't declare one then you must use
//* // the .allocate parameters (unit, integer) when creating a modifier of this kind.
//* static method create takes unit u, real power returns armour
//* // Note the parameters for .allocate, this is because this struct extends
//* // DamageModifier which asks for these parameters in it's create method:
//* local armour this = armour.allocate(u, armour.PRIORITY)
//* set this.power = power
//* return this
//* endmethod
//*
//* // This is the method that runs when damage is dealt to the unit with the modifier.
//* // The damage parameter tells how much damage got to this modifier past any modifiers
//* // with a higher priority that the unit may have.
//* // The value that the method returns tells the system by how much to modify the damage,
//* // a positive return value increases damage while a negative value reduces it.
//* method onDamageTaken takes unit damageSource, real damage returns real
//* // This is a simple modifier that just gives a flat damage reduction.
//* if this.power>damage then
//* return -damage
//* endif
//* return -this.power
//* endmethod
//* // There is no onDamageDealt method so this modifier does not affect the damage that the unit deals.
//* // onDestroy method is optional, in this case we don't need it.
//* endstruct
//*
//* In order for the system to modify damage, you need to call the
//* RunDamageModifiers function whenever a damage event occurs.
//* Since there is no generic "unit takes damage" event in JASS,
//* you need to use a damage detection system such as DamageEvent,
//* which is already designed to support DamageModifiers.
//*
//* function RunDamageModifiers takes nothing returns real
//*
//* The real value that the RunDamageModifiers function returns
//* equals what the damage is after it has been affected by all
//* modifiers.
//*
//* To be able to modify damage in situations where it exceeds the
//* max hp of the damaged unit, this system must temporarily add a
//* bonus hp ability to the unit. An external Objectmerger call is
//* provided in the calibration section that generates this
//* ability in a map automatically when the map is saved. The
//* ability will also get automatically preloaded to prevent
//* first-use lag as long as either the xepreload library or the
//* AbilityPreload library is present in the map.
//*
//* The library also provides the functions GetUnitLife and
//* SetUnitLife. When used outside damage detection, these
//* functions work the same as the natives GetWidgetLife and
//* SetWidgetLife, however when called on a unit that has damage
//* stacked on it these functions will return/modify the life the
//* unit will have after the damage resolves, rather than it's
//* current life which may or may not be inflated by damage
//* prevention. Again, the functions are:
//*
//* function GetUnitLife takes unit u returns real
//* function SetUnitLife takes unit u, real newLife returns nothing
//*****************************************************************
globals
// If two modifiers have the same priority, which one should
// modify the damage first? If true, the newer modifier will.
private constant boolean NEW_MODIFIER_ON_TOP = true
// If more damage is dealt to the unit than the unit's max hp, then this
// system must temporarily add a bonus hp ability to the unit in order to
// facilitate damage prevention.
// An objectmerger call is provided below that automatically generates
// the ability when you save the map in the NewGen world editor.
private constant integer SURVIVAL_ABILITY = 'DMsa'
endglobals
// This objectmerger call only needs to run once to generate the ability in a map,
// just save the map to run it, then close the map and re-open it and the ability
// should be there, after that you can disable the objectmerget call to speed up
// the map saving process in the future.
// (To disable the call, delete the "!" so it turns from a command into a comment.)
// external ObjectMerger w3a AIl1 DMsa anam "LifeBonus" ansf "(DamageModifiers)" Ilif 1 100000 aite 0
// This interface is included in the calibration section
// for user reference only and should not be changed in any way.
// It is private because your shields shouldn't extend it directly,
// but should extend the DamageModifer struct instead.
private interface DamageModiferTemplate
// Returned real determines by how much the damage the unit deals should be modified.
method onDamageDealt takes unit damagedUnit, real damage returns real defaults 0.0
// Returned real determines by how much the damage the unit is dealt should be modified.
method onDamageTaken takes unit damageSource, real damage returns real defaults 0.0
endinterface
// END OF CALIBRATION SECTION
// ================================================================
private keyword Evaluate
struct DamageModifier extends DamageModiferTemplate
private unit target
private DamageModifier next=0
integer priority
private static DamageModifier array first
private method listInsert takes nothing returns nothing
local DamageModifier dm=DamageModifier.first[GetUnitId(.target)]
if dm==0 or dm.priority<.priority or (NEW_MODIFIER_ON_TOP and dm.priority==.priority) then
set .next=DamageModifier.first[GetUnitId(.target)]
set DamageModifier.first[GetUnitId(.target)]=this
else
loop
exitwhen dm.next == 0 or dm.next.priority<.priority or (NEW_MODIFIER_ON_TOP and dm.next.priority==.priority)
set dm=dm.next
endloop
set .next=dm.next
set dm.next=this
endif
endmethod
private method listRemove takes nothing returns nothing
local DamageModifier dm=DamageModifier.first[GetUnitId(.target)]
if dm==this then
set DamageModifier.first[GetUnitId(.target)]=.next
else
loop
exitwhen dm.next==this
set dm=dm.next
endloop
set dm.next=.next
endif
endmethod
static method create takes unit u, integer priority returns DamageModifier
local DamageModifier this
if u==null then
return 0
endif
set this=DamageModifier.allocate()
set .target=u
set .priority = priority
call .listInsert()
return this
endmethod
method onDestroy takes nothing returns nothing
call .listRemove()
endmethod
static method Evaluate takes unit damaged, unit damager, real damage returns real
// Loops through all the modifiers involved in a damage event and
// returns the total amount by which the damage must be modified.
// Positive values meaning a damage increase and negative a damage decrease.
local real modifier=0.0
local DamageModifier this=DamageModifier(DamageModifier.first[GetUnitId(damager)])
local DamageModifier that=DamageModifier(DamageModifier.first[GetUnitId(damaged)])
loop
exitwhen this==0 and that==0
if this!=0 and (that==0 or this.priority>=that.priority) then
set modifier=modifier+this.onDamageDealt(damaged, damage+modifier)
set this=this.next
elseif that !=0 then
set modifier=modifier+that.onDamageTaken(damager, damage+modifier)
set that=that.next
endif
endloop
return modifier
endmethod
endstruct
// ================================================================
private struct Damage
unit u
real hp=0.0
real dmg=0.0
real temp=0.0
private Damage next
private static Damage first=0
private static timer delay=CreateTimer()
private static Damage array unitlist
private static method get takes unit u returns Damage
return Damage.unitlist[GetUnitId(u)]
endmethod
private static method create takes unit u returns Damage
// The only time a Damage is created is when it doesn't yet exist for the unit.
local Damage this=Damage.allocate()
set .u = u
set .next = Damage.first
set Damage.first=this
set Damage.unitlist[GetUnitId(.u)]=this
return this
endmethod
method onDestroy takes nothing returns nothing
// The only time a Damage is destroyed is when it is first, so this works.
set Damage.first=.next
set Damage.unitlist[GetUnitId(.u)]=0
set .u = null
endmethod
private static method end takes nothing returns nothing
// This method is called with a very slight delay, which allows it to run
// after the life of the damaged unit has already been reduced by the damage.
loop
exitwhen Damage.first==0
call UnitRemoveAbility(Damage.first.u, SURVIVAL_ABILITY)
call SetWidgetLife(Damage.first.u, Damage.first.hp)
call Damage.first.destroy()
endloop
endmethod
static method RunModifiers takes nothing returns real
local unit damaged = GetTriggerUnit()
local unit damager = GetEventDamageSource()
local real damage = GetEventDamage()
local real modifier = 0.0
local real life = GetWidgetLife(damaged)
local real maxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
local Damage d
if damaged==null then
// Method not called from a damage event, terminate.
set damager=null
set damaged=null
return 0.0
endif
// Calculate by how much to modify the damage.
set modifier=DamageModifier.Evaluate(damaged, damager, damage)
// Modify the damage.
set d=Damage.get(damaged)
if d==0 then
// First damage, create a damage struct.
set d=Damage.create(damaged)
set d.hp=life
call TimerStart(Damage.delay, 0.0, false, function Damage.end)
endif
// Update the damage struct.
set d.hp=d.hp-(damage+modifier)
set d.dmg=d.dmg+damage
// If the unit's life couldn't be reduced or increased as much as we wanted
// on a previous damage event, it's time we compensate for that now.
set modifier=modifier+d.temp
set d.temp=0.0
if maxlife-d.dmg<=0.405 then
// Enough damage stacked to potentially kill the unit, need a survival ability.
call UnitAddAbility(damaged, SURVIVAL_ABILITY)
set maxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
endif
if life-modifier>0.405 then
// Set the unit's life so that after the damage is dealt, it will be correct.
call SetWidgetLife(damaged, life-modifier)
if life-modifier>maxlife then
// The unit's maxlife is preventing us from increasing life as needed,
// we need to remember that in the case the survival ability is ever added
// to the unit on another damage event before this damage resolves.
set d.temp=modifier-(life-maxlife)
endif
else
// If we have a damage modifier value that would kill the unit,
// we instead reduce the unit's hp so the actual damage can kill it,
// thus awarding proper bounty.
call SetWidgetLife(damaged, 0.406)
// We need to remember that we failed to reduce the life as needed in case
// damage is dealt in response to this damage and modified into the negative.
set d.temp=modifier-(life-0.406)
endif
set damaged=null
set damager=null
return damage+modifier
endmethod
static method getLife takes unit u returns real
local Damage d=Damage.get(u)
if d==0 then
return GetWidgetLife(u)
endif
return d.hp
endmethod
static method setLife takes unit u, real newLife returns nothing
local Damage d=Damage.get(u)
local real modifier
local real delta
if d==0 then
// Unit not taking damage, proceed with setting the unit's life.
call SetWidgetLife(u, newLife)
else
// Calculate by how much the unit's life will change after the damage.
set modifier=newLife-d.hp
if modifier<0 or d.hp>0 then
// Nothing to worry about, the rest of the code will handle everything.
set d.hp=newLife
set d.temp=d.temp-modifier
else
// Increasing life in a situation where the damage is about to kill the unit,
// so we must intervene and increase the unit's actual life as well.
set d.hp=newLife
set delta=GetUnitState(u,UNIT_STATE_MAX_LIFE)-GetWidgetLife(u)
if delta>modifier then
call SetWidgetLife(u, GetWidgetLife(u)+modifier)
else
call SetWidgetLife(u, GetWidgetLife(u)+delta)
set d.temp=d.temp-(modifier-delta)
endif
endif
endif
endmethod
endstruct
private function Init takes nothing returns nothing
static if LIBRARY_xepreload then
call XE_PreloadAbility(SURVIVAL_ABILITY)
else
static if LIBRARY_AbilityPreload then
call AbilityPreload(SURVIVAL_ABILITY)
endif
endif
endfunction
// ================================================================
// To make this system work, call this function once whenever a damage event occurs.
function RunDamageModifiers takes nothing returns real
return Damage.RunModifiers()
endfunction
// These functions allow you to get/set the proper life of a unit even when it's taking damage.
function GetUnitLife takes unit u returns real
return Damage.getLife(u)
endfunction
function SetUnitLife takes unit u, real newLife returns nothing
call Damage.setLife(u, newLife)
endfunction
endlibrary
//TESH.scrollpos=23
//TESH.alwaysfold=0
library Messages initializer Init uses TimerUtils
private keyword Credits
private function CreditsProxy takes nothing returns nothing
call Credits.execute()
endfunction
private function Commands takes nothing returns nothing
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, "|cff00bfffCommands:")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-reset|r: Spawns some footmen around you.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-level |cff0000e0<level>|r: Sets the level of your hero to the specified level.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " Note that you cannot decrease your hero's level this way.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-handleid|r: Creates a location, displays its index - 0x1000001 and destroys it.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-commands|r: Shows this list.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-credits|r: Shows the credits.")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " |cffffcc00-clear|r: Clears all text-messages.")
if GetExpiredTimer()!=null then
call TimerStart(GetExpiredTimer(), 60., false, function CreditsProxy)
endif
endfunction
private function Credits takes nothing returns nothing
call ClearTextMessages()
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, "|cff00bfffCredits:")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00KelThuzad|r for the icons of Prolonged Life, Smiting Zeal and Reverse Damage")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Blizzard Entertainment|r for the icon of Seed of Life")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Anitarf|r for SpellEvent, DamageEvent and DamageModifiers")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Rising_Dusk|r for GroupUtils")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00grim001|r for AutoIndex")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Captain_Griffen|r for LightLeaklessDamageDetect")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00TriggerHappy187|r for TimedHandles")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Vexorian|r for JassHelper, Table and TimerUtils")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Pipedream|r for Grimoire")
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,30, " - |cffffcc00Pitzermike|r and |cffffcc00MindWorX|r for JassNewGenPack")
if GetExpiredTimer()!=null then
call TimerStart(GetExpiredTimer(), 60., false, function Commands)
endif
endfunction
private function ClearActions takes nothing returns nothing
call ClearTextMessages()
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-clear", true)
call TriggerAddAction(t, function ClearActions)
set t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-commands", true)
call TriggerAddAction(t, function Commands)
set t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-credits", true)
call TriggerAddAction(t, function Credits)
call TimerStart(NewTimer(), 0., false, function Commands)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Basic initializer Init
globals
unit HERO
endglobals
function H2I takes handle h returns integer
return GetHandleId(h)
endfunction
private function Init takes nothing returns nothing
set HERO=CreateUnit(Player(0), 'H000', 0,0,0)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Reset initializer Init
private function Actions takes nothing returns nothing
local real r
set r=0
loop
call CreateUnit(Player(12), 'hfoo', 0+(256*Cos(r)), 512+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
set r=0
loop
call CreateUnit(Player(12), 'hfoo', 512+(256*Cos(r)), 0+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
set r=0
loop
call CreateUnit(Player(12), 'hfoo', 0+(256*Cos(r)), -512+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
set r=0
loop
call CreateUnit(Player(12), 'hfoo', -512+(256*Cos(r)), 0+(256*Sin(r)), 0)
set r=r+(bj_PI/4)
exitwhen r>=(7*bj_PI)/4
endloop
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerAddAction(t, function Actions)
call TriggerRegisterPlayerChatEvent(t, Player(0), "-reset", true)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library LevelUp initializer Init uses Basic
private function LevelAction takes nothing returns nothing
local integer newlvl
local string s = GetEventPlayerChatString()
if SubString(s, 0, 7)=="-level " then
set newlvl=S2I(SubString(s, 7, StringLength(s)))
if newlvl>GetUnitLevel(HERO) and newlvl<=10 then
call SetHeroLevel(HERO, newlvl, true)
else
debug call BJDebugMsg("LevelUp - Wrong new level!")
endif
else
debug call BJDebugMsg("LevelUp - Don't try to trick my triggers!")
endif
endfunction
private function Init takes nothing returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterPlayerChatEvent(t, Player(0), "-level ", false)
call TriggerAddAction(t, function LevelAction)
set t=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
function Trig_handleid_Actions takes nothing returns nothing
local location l=Location(0,0)
call BJDebugMsg("Max Handle ID: "+I2S(GetHandleId(l)-0x100001))
call RemoveLocation(l)
set l=null
endfunction
//===========================================================================
function InitTrig_handleid takes nothing returns nothing
set gg_trg_handleid = CreateTrigger( )
call TriggerRegisterPlayerChatEvent(gg_trg_handleid, Player(0), "-handleid", true)
call TriggerAddAction( gg_trg_handleid, function Trig_handleid_Actions )
endfunction