//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
//TESH.scrollpos=11
//TESH.alwaysfold=0
scope Bewilder initializer Init
globals
private constant integer DUMMY = 'jtw!' // 'jtw!'
private constant integer NUM_ILLUSIONS = 1
private constant integer SPELL = 'JTh3'
private constant integer EFFECT = 'jtx1' // 'jtx1'
private constant integer ORDER = 852274 // 852231
endglobals
// private constant function IllusionLevel takes integer level returns integer
// return 1
// endfunction
private constant function Area takes integer level returns real
return 200. + (50. * level)
endfunction
private function EnumCast takes nothing returns nothing
local unit dummy = CreateUnitAtLoc(GetOwningPlayer(SpellEvent.CastingUnit),DUMMY,GetUnitLoc(GetEnumUnit()),bj_UNIT_FACING)
// call UnitAddAbility(dummy, EFFECT)
// call SetUnitAbilityLevel(dummy, EFFECT, IllusionLevel(GetUnitAbilityLevel(SpellEvent.CastingUnit,SPELL)))
call IssueTargetOrderById(dummy, ORDER, GetEnumUnit())
// call UnitApplyTimedLife(dummy, 'Bphx', 2)
set dummy = null
endfunction
private function SpellCast takes nothing returns nothing
local group targets = CreateGroup()
local player caster = GetOwningPlayer(SpellEvent.CastingUnit)
local integer level = GetUnitAbilityLevel(SpellEvent.CastingUnit,SPELL)
call GroupEnumUnitsInArea(targets, SpellEvent.TargetX, SpellEvent.TargetY, Area(level), null)
call ForGroup(targets, function EnumCast)
call DestroyGroup(targets)
set targets = null
set caster = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
endfunction
endscope
//TESH.scrollpos=33
//TESH.alwaysfold=0
scope Convalescent initializer Init
globals
private constant integer AURA = 'jtxh'
private constant integer SPELL = 'JTh4'
private constant integer BUFF = 'jtb6'
private constant real STANDARD_DURATION = 18.
private constant real CHECK_PERIOD = .5
endglobals
private function ExtendedDuration takes integer level returns real
return level - 1.
endfunction
private struct curse
private unit caster
private unit target
private real duration
private timer timeout
private timer check
private static method stop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call .destroy()
endmethod
private static method checkHealth takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if GetUnitState(.target, UNIT_STATE_LIFE) >= GetUnitState(.target, UNIT_STATE_MAX_LIFE) then
call TimerStart(.check, .duration, false, function thistype.stop)
endif
endmethod
static method create takes unit caster, unit target, integer level returns thistype
local thistype this = .allocate()
set .caster = caster
set .target = target
set .duration = ExtendedDuration(level)
set .timeout = NewTimer()
set .check = NewTimer()
call UnitAddAbility(.target, AURA)
call SetUnitAbilityLevel(.target, AURA, level)
call SetTimerData(.timeout, this)
call TimerStart(.timeout, STANDARD_DURATION + .duration, false, function thistype.stop)
call SetTimerData(.check, this)
call TimerStart(.check, CHECK_PERIOD, true, function thistype.checkHealth)
return this
endmethod
private method onDestroy takes nothing returns nothing
call UnitRemoveAbility(.target, AURA)
call UnitRemoveAbility(.target, BUFF)
call ReleaseTimer(.timeout)
call ReleaseTimer(.check)
endmethod
endstruct
private function SpellCast takes nothing returns nothing
call curse.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit, GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL))
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
endfunction
endscope
//TESH.scrollpos=2
//TESH.alwaysfold=0
scope Distort initializer Init
globals
private constant integer BUFF = 'jtb8'
private constant integer DUMMY = 'jtw!'
private constant integer ORDER = 852502
endglobals
// 1 - 1/x
//
// 5, 9, 13
private function Chance takes integer level returns boolean
if level > 0 then
return GetRandomReal(0.,100.) < 1 + (level * 4)
else
return false
endif
endfunction
private function Actions takes nothing returns boolean
local unit target = GetAttacker()
local unit dummy
if Chance(GetUnitAbilityLevel(target, BUFF)) then
set dummy = CreateUnit(GetOwningPlayer(GetTriggerUnit()), DUMMY, GetUnitX(target), GetUnitY(target), bj_UNIT_FACING)
call IssueTargetOrderById(dummy, ORDER, target)
set dummy = null
endif
set target = null
return false
endfunction
//===========================================================================
private function Init takes nothing returns nothing
set gg_trg_Distort = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ(gg_trg_Distort, EVENT_PLAYER_UNIT_ATTACKED)
call TriggerAddCondition(gg_trg_Distort,Condition(function Actions))
endfunction
endscope
//TESH.scrollpos=36
//TESH.alwaysfold=0
scope ZombieWall initializer Init
globals
private constant integer SPELL = 'JThf'
private constant integer TIMED_LIFE = 'jtbs'
endglobals
private constant function UnitTypeId takes integer level returns integer
if level == 1 then
return 'jtwg'
elseif level == 2 then
return 'jtwh'
elseif level == 3 then
return 'jtwi'
endif
return 0
endfunction
private constant function WallLength takes integer lvl returns real
return 500.+lvl*100.
endfunction
private constant function NumberOfZombies takes integer lvl returns integer
return 6+lvl
endfunction
private constant function Duration takes integer lvl returns real
return 30.
endfunction
//===========================================================================
private function SpellCast takes nothing returns nothing
local player p = GetOwningPlayer(SpellEvent.CastingUnit)
local integer i = 0
local integer lvl = GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL)
local integer n = NumberOfZombies(lvl)
local real x = GetUnitX(SpellEvent.CastingUnit)
local real y = GetUnitY(SpellEvent.CastingUnit)
local real tx = SpellEvent.TargetX
local real ty = SpellEvent.TargetY
local real angle = Atan2(ty-y, tx-x)
local real d = WallLength(lvl)
set x = Cos(angle+bj_PI/2)*d
set y = Sin(angle+bj_PI/2)*d
set tx = tx + x/2
set ty = ty + y/2
set x = x/(n-1)
set y = y/(n-1)
loop
exitwhen i >= n
call UnitApplyTimedLife( CreateUnit(p, UnitTypeId(lvl), tx, ty, angle * bj_RADTODEG) , TIMED_LIFE, Duration(lvl))
set tx = tx-x
set ty = ty-y
set i = i + 1
endloop
set p = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
endfunction
endscope
//TESH.scrollpos=39
//TESH.alwaysfold=0
scope Augury initializer Init
globals
private constant integer PEEPER = 'jtw~'
private constant integer SPELL = 'JTh1'
private constant real DURATION = 30.
private constant real PEEP_TIME = .5
endglobals
private function Period takes integer level returns real
return 20. - (5. * level)
endfunction
private function isHero takes nothing returns boolean
return IsUnitType(GetFilterUnit(),UNIT_TYPE_HERO)
endfunction
private struct aug
private unit caster
private player owner
private real period
private timer timeout
private timer reveal
private static method stop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call .destroy()
endmethod
private static method revealHeroes takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local group g = CreateGroup()
local unit u
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, Condition(function isHero))
loop
set u = FirstOfGroup (g)
exitwhen u == null
call GroupRemoveUnit(g, u)
call UnitApplyTimedLife(CreateUnit(.owner,PEEPER,GetUnitX(u),GetUnitY(u),0),0,.5)
endloop
call DestroyGroup(g)
endmethod
static method create takes unit caster, integer level returns thistype
local thistype this = .allocate()
set .caster = caster
set .owner = GetOwningPlayer(caster)
set .period = Period(level)
set .timeout = NewTimer()
set .reveal = NewTimer()
call SetTimerData(.timeout, this)
call SetTimerData(.reveal, this)
call TimerStart(.timeout, DURATION, false, function thistype.stop)
call TimerStart(.reveal, .period, true, function thistype.revealHeroes)
return this
endmethod
private method onDestroy takes nothing returns nothing
call ReleaseTimer(.timeout)
call ReleaseTimer(.reveal)
endmethod
endstruct
private function SpellCast takes nothing returns nothing
call aug.create(SpellEvent.CastingUnit, GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL))
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
endfunction
endscope
//TESH.scrollpos=57
//TESH.alwaysfold=0
scope Mesmer initializer Init
globals
private constant integer spell_ID = 'JTha'
private constant integer locust_ID = 'Aloc'
private constant real period = .1
private constant real minDist = 250.
// private constant real duration = 8.
private hashtable table
endglobals
private function TargetNearCaster takes nothing returns boolean
return GetUnitAbilityLevel(GetFilterUnit(), spell_ID) > 0
endfunction
private function Stop takes nothing returns nothing
local unit caster = GetTriggerUnit()
local unit dummy = LoadUnitHandle(table, GetHandleId(caster), 0)
local unit target = LoadUnitHandle(table, GetHandleId(caster), 1)
local location walkLoc = LoadLocationHandle(table, GetHandleId(caster), 2)
local timer t = LoadTimerHandle(table, GetHandleId(caster), 3)
if SpellEvent.AbilityId == spell_ID then
call ReleaseTimer(t)
call SetUnitVertexColor(target, 255, 255, 255, 255)
call SetUnitX(target, GetUnitX(dummy))
call SetUnitY(target, GetUnitY(dummy))
call ShowUnit(dummy, false)
call SetUnitPathing(target, true)
call KillUnit(dummy)
call RemoveUnit(dummy)
call RemoveLocation(walkLoc)
endif
set walkLoc = null
set target = null
set dummy = null
set caster = null
endfunction
private function Move takes nothing returns nothing
local timer t = GetExpiredTimer()
local unit caster = LoadUnitHandle(table, GetHandleId(t), 0)
local unit target = LoadUnitHandle(table, GetHandleId(t), 1)
local unit dummy = LoadUnitHandle(table, GetHandleId(t), 2)
local location walkLoc = LoadLocationHandle(table, GetHandleId(t), 3)
local integer h = GetTerrainCliffLevel(GetUnitX(dummy), GetUnitY(dummy))
call SetUnitX(target, GetUnitX(dummy))
call SetUnitY(target, GetUnitY(dummy))
if DistanceBetweenPoints(GetUnitLoc(target), walkLoc) <= minDist then
call SetUnitFacing(dummy, AngleBetweenPoints(GetUnitLoc(dummy), walkLoc))
call PauseUnit(dummy, true)
call PauseTimer(t)
endif
set walkLoc = null
set caster = null
set target = null
set dummy = null
set t = null
endfunction
private function Walk takes nothing returns nothing
local timer t = GetExpiredTimer()
local unit dummy = LoadUnitHandle(table, GetHandleId(t), 2)
local location walkLoc = LoadLocationHandle(table, GetHandleId(t), 3)
call IssuePointOrderLoc(dummy, "move", walkLoc)
call TimerStart(t, period, true, function Move)
set walkLoc = null
set dummy = null
set t = null
endfunction
private function Start takes nothing returns nothing
local unit caster = SpellEvent.CastingUnit
local unit target = SpellEvent.TargetUnit
local unit dummy
local location walkLoc
local timer t
if SpellEvent.AbilityId == spell_ID then
call SetUnitVertexColor(target, 255, 255, 255, 0)
set dummy = CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), GetUnitTypeId(target), 0, 0, 0)
call UnitAddAbility(dummy, locust_ID)
call SetUnitX(dummy, GetUnitX(target))
call SetUnitY(dummy, GetUnitY(target))
set walkLoc = GetUnitLoc(caster)
call SetUnitColor(dummy, GetPlayerColor(GetOwningPlayer(target)))
call SaveUnitHandle(table, GetHandleId(caster), 0, dummy)
call SaveUnitHandle(table, GetHandleId(caster), 1, target)
call SaveLocationHandle(table, GetHandleId(caster), 2, walkLoc)
if DistanceBetweenPoints(GetUnitLoc(target), GetUnitLoc(caster)) > minDist then
set t = NewTimer()
call SaveTimerHandle(table, GetHandleId(caster), 3, t)
call SaveUnitHandle(table, GetHandleId(t), 0, caster)
call SaveUnitHandle(table, GetHandleId(t), 1, target)
call SaveUnitHandle(table, GetHandleId(t), 2, dummy)
call SaveLocationHandle(table, GetHandleId(t), 3, walkLoc)
call TimerStart(t, 0., false, function Walk)
endif
endif
set caster = null
set target = null
set dummy = null
set walkLoc = null
set t = null
endfunction
private function Init takes nothing returns nothing
set table = InitHashtable()
// set gg_trg_Mesmerize = CreateTrigger()
// call TriggerRegisterAnyUnitEventBJ(gg_trg_Mesmerize, EVENT_PLAYER_UNIT_SPELL_ENDCAST)
// call TriggerAddCondition(gg_trg_Mesmerize, Condition(function Stop))
// set gg_trg_Mesmerize = CreateTrigger()
// call TriggerRegisterAnyUnitEventBJ(gg_trg_Mesmerize, EVENT_PLAYER_UNIT_SPELL_CHANNEL)
// call TriggerAddCondition(gg_trg_Mesmerize, Condition(function Start))
call RegisterSpellEffectResponse(spell_ID, Start)
call RegisterSpellEndCastResponse(spell_ID, Stop)
endfunction
endscope
//TESH.scrollpos=33
//TESH.alwaysfold=0
scope MarkOfTheViper initializer Init
globals
private constant integer SPELL = 'JTh9'
private constant integer DEBUFF = 'jtbc'
private constant integer DEBUFF_AURA = 'jtxo'
private constant integer SPELLBOOK = 'jtxp'
private constant integer EFFECT = 'jtxn'
private constant real DURATION = 60.
private constant real DURATION_HERO = 15.
private constant real RANGE = 400.
private player stupid_global_filter_helper
private aBuffType buffType = 0
endglobals
private function MaxPenalty takes integer level returns integer
return level + 3
endfunction
private function CountAllies takes nothing returns boolean
return IsUnitAlly(GetFilterUnit(),stupid_global_filter_helper)
endfunction
private function Create takes aBuff eventBuff returns nothing
call UnitAddAbility(eventBuff.target.u, SPELLBOOK)
call SetUnitAbilityLevel(eventBuff.target.u, DEBUFF_AURA, eventBuff.level)
call UnitMakeAbilityPermanent(eventBuff.target.u, true, DEBUFF_AURA) //prevent morphing from removing the ability
endfunction
private function Refresh takes aBuff eventBuff returns nothing
call SetUnitAbilityLevel(eventBuff.target.u, DEBUFF_AURA, eventBuff.level)
endfunction
private function Cleanup takes aBuff eventBuff returns nothing
call UnitRemoveAbility(eventBuff.target.u, SPELLBOOK)
call UnitRemoveAbility(eventBuff.target.u, DEBUFF)
endfunction
private function Periodic takes aBuff eventBuff returns nothing
local group g = CreateGroup()
local integer penalty = 0
set stupid_global_filter_helper = GetOwningPlayer(eventBuff.target.u)
call GroupEnumUnitsInRangeOfLoc(g, GetUnitLoc(eventBuff.target.u), RANGE, Filter(function CountAllies))
set bj_wantDestroyGroup = true
set penalty = CountUnitsInGroup(g)
if penalty > MaxPenalty(eventBuff.level) then
set penalty = MaxPenalty(eventBuff.level)
endif
call SetUnitAbilityLevel(eventBuff.target.u, EFFECT, eventBuff.level * 2 + 3 - penalty)
endfunction
private function SpellCast takes nothing returns nothing
local integer level = GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL)
local real duration = DURATION
if IsUnitSpellResistant(SpellEvent.TargetUnit) then
set duration = DURATION_HERO
endif
call ABuffApply(buffType, SpellEvent.TargetUnit, SpellEvent.CastingUnit, duration, level, 0)
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
set buffType.category = ABuff_STANDARD
set buffType.eventCreate = ABuffEvent_Create.Create
set buffType.eventRefresh = ABuffEvent_Refresh.Refresh
set buffType.eventCleanup = ABuffEvent_Cleanup.Cleanup
set buffType.eventPeriodic = ABuffEvent_Periodic.Periodic
endfunction
endscope
//TESH.scrollpos=122
//TESH.alwaysfold=0
scope SerpentSteel initializer Init
//By Michael Peppers, uses Table
//Spell Globals and blabla
globals
private constant integer SERPENTSKILL = 'JThc'
private constant integer DUMMYID = 'jtw!'
private constant integer BUFF = 'jtbk'
private constant integer SPELL = 'jtxu'
private constant integer HITSLIMIT = 5
private constant real DURATION = 5
private constant string POISONEFFECT = "Objects\\Spawnmodels\\NightElf\\NightElfBlood\\NightElfBloodArcher.mdl"
private group ENUM
private HandleTable gc
private HandleTable gd
endglobals
//Spell blabla
private function PeriodicDamage takes unit attacker returns real
return 2 + (I2R(GetUnitAbilityLevel(attacker, SERPENTSKILL)) * 2)
endfunction
private function SlowLevel takes integer numberhits, unit attacker returns integer
return (numberhits) + (GetUnitAbilityLevel(attacker, SERPENTSKILL)) -1
endfunction
//End of blabla section
//Spell stuff
private struct values
unit t
integer n
static method create takes unit targetted, integer n returns values
local values v = values.allocate()
set v.t = targetted
set v.n = n
return v
endmethod
endstruct
private struct damage
unit a
real t
static method create takes unit attacker, real t returns damage
local damage d = damage.allocate()
set d.a = attacker
set d.t = t
return d
endmethod
endstruct
private function DoT takes nothing returns boolean
local damage d
if GetUnitAbilityLevel(GetFilterUnit(), SPELL) != 0 and GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0.405 then
set d = gd[GetFilterUnit()]
set d.t = d.t -1
// call BJDebugMsg(R2S(d.t))
call UnitDamageTarget(d.a, GetFilterUnit(), PeriodicDamage(d.a), true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_POISON, WEAPON_TYPE_WHOKNOWS)
call DestroyEffect(AddSpecialEffect(POISONEFFECT, GetUnitX(GetFilterUnit()), GetUnitY(GetFilterUnit())))
if d.t == 0 then
call UnitRemoveAbility(GetFilterUnit(), SPELL)
call UnitRemoveAbility(GetFilterUnit(), BUFF)
call gd.flush(GetFilterUnit())
call damage.destroy(d)
else
set gd[GetFilterUnit()] = d
endif
else
if gd[GetFilterUnit()] != 0 then
call gd.flush(GetFilterUnit())
endif
if d != 0 then
call damage.destroy(d)
endif
endif
return false
endfunction
private function BuffCheck takes nothing returns boolean
call GroupEnumUnitsInRect(ENUM, bj_mapInitialPlayableArea, Condition(function DoT))
return false
endfunction
private function Main takes nothing returns boolean
local values v
local damage d
local unit dummy = null
if GetUnitAbilityLevel(GetAttacker(), SERPENTSKILL) != 0 and (IsUnitType(GetTriggerUnit(), UNIT_TYPE_MECHANICAL) and IsUnitType(GetTriggerUnit(), UNIT_TYPE_ANCIENT)) == false then
if gc[GetAttacker()] != 0 then
set v = gc[GetAttacker()]
if v.t != GetTriggerUnit() or GetUnitAbilityLevel(GetTriggerUnit(), SPELL) == 0 then
set v.t = GetTriggerUnit()
set v.n = 1
else
if v.n < HITSLIMIT then
set v.n = v.n + 1
endif
endif
else
set v = values.create(GetTriggerUnit(), 1)
endif
if GetUnitAbilityLevel(GetTriggerUnit(), SPELL) == 0 then
call UnitAddAbility(v.t, SPELL)
endif
if GetUnitAbilityLevel(GetTriggerUnit(), SPELL) != SlowLevel(v.n, GetAttacker()) then
call SetUnitAbilityLevel(v.t, SPELL, SlowLevel(v.n, GetAttacker()))
call SetUnitAbilityLevel(v.t, BUFF, SlowLevel(v.n, GetAttacker()))
endif
call DestroyEffect(AddSpecialEffect(POISONEFFECT, GetUnitX(v.t), GetUnitY(v.t)))
if gd[GetTriggerUnit()] != 0 then
call gd.flush(GetTriggerUnit())
endif
set d = damage.create(GetAttacker(), DURATION)
set gd[GetTriggerUnit()] = d
set gc[GetAttacker()] = v
else
if v != 0 then
call values.destroy(v)
endif
if d != 0 then
call damage.destroy(d)
endif
endif
set dummy = null
return false
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
set gc = HandleTable.create()
set gd = HandleTable.create()
set ENUM = CreateGroup()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_ATTACKED)
call TriggerAddCondition(t, Condition(function Main))
set t = CreateTrigger()
call TriggerRegisterTimerEventPeriodic(t, 1)
call TriggerAddCondition(t, Condition(function BuffCheck))
endfunction
endscope
//TESH.scrollpos=49
//TESH.alwaysfold=0
scope KukriToss initializer Init
struct KukriToss
static constant integer SPELL = 'jtad'
private static constant integer SPELL_DISABLER = 'jtx~'
private static constant integer HEADHUNT = 'jta9'
private static constant integer BUFF = 'jtba'
private static constant real DAMAGE = 100.
private static constant real PERIOD_CHECK = .05
unit caster
unit target
timer t
private static method TimedDamage takes nothing returns nothing
local KukriToss kt = GetTimerData(GetExpiredTimer())
if IsUnitType (kt.target, UNIT_TYPE_DEAD) then
call kt.destroy()
elseif GetUnitAbilityLevel(kt.target, BUFF) > 0 then
call UnitDamageTarget(kt.caster, kt.target, DAMAGE, /*
*/true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_SHADOW_STRIKE, /*
*/WEAPON_TYPE_METAL_MEDIUM_SLICE)
if IsUnitType(kt.target, UNIT_TYPE_DEAD) then
call IncUnitAbilityLevel(kt.caster, HEADHUNT)
call IncUnitAbilityLevel(kt.caster, HEADHUNT)
endif
call kt.destroy()
endif
endmethod
static method create takes unit caster, unit target returns KukriToss
local KukriToss kt = KukriToss.allocate()
local group selected
local player owner = GetOwningPlayer (caster)
set kt.t = NewTimer()
set kt.caster = caster
set kt.target = target
/* not working
debug call BJDebugMsg("Cast start")
call SyncSelections()
call GroupEnumUnitsSelected (selected, owner, null)
if GetLocalPlayer() == owner then
call ClearSelection()
endif
call SyncSelections()
call UnitRemoveAbility(kt.caster, SPELL)
if GetLocalPlayer() == owner then
call SelectGroupBJ (selected)
endif
call SyncSelections()
debug call BJDebugMsg("Cast end")
not working */
//call SetUnitState(kt.caster, UNIT_STATE_MAX_MANA, 0.)
call UnitAddAbility (kt.caster, SPELL_DISABLER)
call SetTimerData(kt.t, kt)
call TimerStart(kt.t, PERIOD_CHECK, true, function KukriToss.TimedDamage)
return kt
endmethod
private method onDestroy takes nothing returns nothing
call ReleaseTimer(this.t)
endmethod
static method SpellCast takes nothing returns nothing
call KukriToss.create(SpellEvent.CastingUnit, SpellEvent.TargetUnit)
endmethod
endstruct
//===========================================================================
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(KukriToss.SPELL, KukriToss.SpellCast)
endfunction
endscope
//TESH.scrollpos=45
//TESH.alwaysfold=0
//==============================================================================
// Custom Race System by Archmage Owenalacaster
//==============================================================================
//
// Purpose:
// - Creates the starting units for custom races and replaces the standard
// Melee Initialization trigger. Custom races are selected by race and
// handicap.
//
// Usage:
// - Register a new custom race with CustomRace.create(name, RACE, handicap)
// Handicaps: Valid handicap values are 1.0, 0.9, 0.8, 0.7, 0.6 and 0.5.
// - Register a new custom race for all handicaps of a single race with
// CustomRace.registerAll(name, RACE)
// - Extend the registration of a race with c.register(RACE, handicap)
// - Set the townhall type with c.setTownHall(unitid)
// - Add a new worker type with c.addWorkerType(unitid, priority, qty)
// Priorities: c.NEAR_MINE spawns workers near the mine, where workers
// typically spawn.
// c.NEAR_HALL spawns workers near the town hall, where
// Ghouls spawn.
// - Add a random hero type with c.addHeroType(unitid)
// - Set the ai script used by computer players with c.setAIScript(stringpath)
// - Set a callback function with c.setCallback(CustomRaceCall.function)
// Callbacks: The callback is executed after all the starting units for a
// player are created, and its purpose is to provide enhanced
// initial behaviour for a race. A good example of this with the
// standard races would be the Undead Goldmine Haunting and
// Night Elves Goldmine Entangling.
// The callback function passes as arguments all the units
// generated in addition to the nearest goldmine detected.
// Please note that if a random hero is not created, the last
// argument will have a null value, so always do a check.
// - Get a player's custom race name string with GetPlayerCustomRaceName(player)
//
// Notes:
// - Supports a maximum of 24 custom races.
// - Each race may have a maximum of 4 worker types and 4 hero types.
//
// Requirements:
// - JassHelper version 0.9.E.0 or newer (older versions may still work).
//
// Installation:
// - Create a new trigger called CustomRaceSystem.
// - Convert it to custom text and replace all the code with this code.
//
// Special Thanks:
// - Alevice: He practically co-wrote the code.
// - cosmicat: His formula for circular unit formation.
// Co-developing the single-array registry.
//
//==============================================================================
library CustomRaceSystem initializer Init
function interface CustomRaceCall takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
private function r2S takes race r returns string
if r == RACE_HUMAN then
return "Human"
elseif r == RACE_ORC then
return "Orc"
elseif r == RACE_UNDEAD then
return "Undead"
elseif r == RACE_NIGHTELF then
return "Night Elf"
endif
return "Unknown"
endfunction
private function r2I takes race r returns integer
if r == RACE_HUMAN then
return 1
elseif r == RACE_ORC then
return 2
elseif r == RACE_UNDEAD then
return 3
elseif r == RACE_NIGHTELF then
return 4
endif
return 5
endfunction
globals
// Unit Type Constants
private constant integer MAX_WORKERTYPES = 4
private constant integer MAX_HEROTYPES = 4
// Victory Defeat Constants
private string array KEY_STRUCTURE
private integer KEY_STRUCTURE_COUNT = 0
endglobals
//===========================================================================
// STRUCT DATA
//===========================================================================
struct CustomRace
string name
// Town Hall Variable
integer townhallType = 0
//string townhallName
// Town Hall name is not currently supported.
// Worker Variables
integer totalWorkerTypes = 0
integer array workerType[MAX_WORKERTYPES]
integer array workerPriority[MAX_WORKERTYPES]
integer array workerQty[MAX_WORKERTYPES]
// Random Hero Variables
integer totalHeroTypes = 0
integer array heroType[MAX_HEROTYPES]
// AI Script Directory String Variable
string aiscript = ""
// Callback Variable
private CustomRaceCall c
// Registry Variable
static integer array REGISTRY
// Spawn Priority Variables
static integer NEAR_MINE = 0
static integer NEAR_HALL = 1
//static integer NEAR_TREES = 2
static method get takes race r, real h returns CustomRace
return CustomRace(.REGISTRY[((r2I(r)-1)*6)+(10-R2I(h*10.))])
endmethod
method register takes race r, real h returns boolean
local CustomRace c = CustomRace.get(r,h)
if c != 0 then
debug call BJDebugMsg("|cffff0000Registration of "+.name+" failed due to conflict with "+c.name+" registered for "+r2S(r)+" race Handicap "+R2S(h))
return false
endif
set .REGISTRY[((r2I(r)-1)*6)+(10-R2I(h*10.))] = integer(this)
return true
endmethod
static method create takes string name, race r, real h returns CustomRace
local CustomRace c = CustomRace.allocate()
set c.name = name
if not c.register(r,h) then
call c.destroy()
endif
return c
endmethod
static method registerAll takes string name, race r returns CustomRace
local CustomRace c = CustomRace.allocate()
set c.name = name
if not c.register(r,1.0) and not c.register(r,0.9) and not c.register(r,0.8) and not c.register(r,0.7) and not c.register(r,0.6) and not c.register(r,0.5) then
call c.destroy()
endif
return c
endmethod
method setTownHall takes integer hallid returns nothing
set .townhallType = hallid
set KEY_STRUCTURE[KEY_STRUCTURE_COUNT] = UnitId2String(hallid)
set KEY_STRUCTURE_COUNT = KEY_STRUCTURE_COUNT+1
endmethod
method addWorkerType takes integer workerid, integer priority, integer quantity returns nothing
set .workerType[.totalWorkerTypes] = workerid
set .workerPriority[.totalWorkerTypes] = priority
set .workerQty[.totalWorkerTypes] = quantity
set .totalWorkerTypes = .totalWorkerTypes+1
endmethod
method addHeroType takes integer heroid returns nothing
local integer i = 0
set .heroType[.totalHeroTypes] = heroid
set .totalHeroTypes = .totalHeroTypes+1
loop
call SetPlayerTechMaxAllowed(Player(i),heroid,1)
set i = i+1
exitwhen i == bj_MAX_PLAYERS
endloop
endmethod
method getRandomHeroType takes nothing returns integer
local integer randomindex = GetRandomInt(0,.totalHeroTypes-1)
return .heroType[randomindex]
endmethod
method setAIScript takes string s returns nothing
set .aiscript = s
endmethod
method setCallback takes CustomRaceCall callb returns nothing
set .c = callb
endmethod
method createRandomHero takes player p, location loc returns unit
local unit h = CreateUnitAtLoc(p, .getRandomHeroType(), loc, bj_UNIT_FACING)
if bj_meleeGrantHeroItems then
call MeleeGrantItemsToHero(h)
endif
return h
endmethod
method createStartingUnits takes player p returns nothing
local location startLoc = GetPlayerStartLocationLoc(p)
local location nearMineLoc = startLoc
local location nearTownLoc = startLoc
local location spawnLoc = startLoc
local location heroLoc = startLoc
local unit nearestMine = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
local unit myTownhall = null
local unit myRandHero = null
local group workerGroup = CreateGroup()
local integer workertypeindex = 0
local integer workerqty = 0
local integer spawnPriority = 0
if nearestMine != null then
set nearMineLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine),startLoc,320,0)
set nearTownLoc = MeleeGetProjectedLoc(startLoc,GetUnitLoc(nearestMine),288,0)
set heroLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine),startLoc,384,45)
endif
set myTownhall = CreateUnitAtLoc(p,.townhallType,startLoc,bj_UNIT_FACING)
loop
exitwhen workertypeindex == .totalWorkerTypes
set spawnPriority = .workerPriority[workertypeindex]
if (spawnPriority==.NEAR_HALL) then
set spawnLoc = nearTownLoc
elseif(spawnPriority==.NEAR_MINE) then
set spawnLoc = nearMineLoc
endif
loop
call GroupAddUnit(workerGroup, CreateUnitAtLoc(p,.workerType[workertypeindex],PolarProjectionBJ(spawnLoc,65,(I2R(workerqty)*(360.00 / I2R(.workerQty[workertypeindex]))) + 90),bj_UNIT_FACING))
set workerqty = workerqty + 1
exitwhen workerqty >= .workerQty[workertypeindex]
endloop
set workerqty = 0
set workertypeindex = workertypeindex+1
endloop
if (IsMapFlagSet(MAP_RANDOM_HERO) and .totalHeroTypes>0 ) then
set myRandHero = .createRandomHero(p,heroLoc)
else
call SetPlayerState(p,PLAYER_STATE_RESOURCE_HERO_TOKENS,bj_MELEE_STARTING_HERO_TOKENS)
endif
if(.c!=0) then
call .c.evaluate(p,workerGroup,nearestMine,myTownhall,myRandHero)
else
call DestroyGroup(workerGroup)
endif
if nearMineLoc != startLoc then
call RemoveLocation(nearMineLoc)
call RemoveLocation(nearTownLoc)
call RemoveLocation(heroLoc)
endif
call RemoveLocation(startLoc)
set startLoc = null
set nearMineLoc = null
set nearTownLoc = null
set spawnLoc = null
set heroLoc = null
set nearestMine = null
set myTownhall = null
set myRandHero = null
set workerGroup = null
endmethod
endstruct
globals
private string array PLAYER_RACE
endglobals
function GetPlayerCustomRaceName takes player p returns string
return PLAYER_RACE[GetPlayerId(p)]
endfunction
//===========================================================================
// UNIT CREATION SECTION
//===========================================================================
function CreateStartingUnitsForAllPlayers takes nothing returns nothing
local integer index = 0
local player indexPlayer
local race playerRace
local CustomRace c
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set playerRace = GetPlayerRace(indexPlayer)
set c = CustomRace.get(playerRace,GetPlayerHandicap(indexPlayer)+0.01)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_USER or (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER and c.aiscript != "" )) and c != 0 then
set PLAYER_RACE[index] = c.name
call c.createStartingUnits(indexPlayer)
elseif playerRace == RACE_HUMAN then
call MeleeStartingUnitsHuman(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_ORC then
call MeleeStartingUnitsOrc(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_NIGHTELF then
call MeleeStartingUnitsNightElf(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_UNDEAD then
call MeleeStartingUnitsUndead(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
else
call MeleeStartingUnitsUnknownRace(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
endfunction
//===========================================================================
// CUSTOM MELEE AI SECTION
//===========================================================================
private function CustomMeleeStartingAI takes nothing returns nothing
local integer index = 0
local player indexPlayer
local race indexRace
local CustomRace c
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set indexRace = GetPlayerRace(indexPlayer)
set c = CustomRace.get(indexRace,GetPlayerHandicap(indexPlayer)+0.01)
call SetPlayerHandicap(indexPlayer,1.0)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER) then
// Run a race-specific melee AI script.
if c != 0 and c.aiscript != "" then
call StartMeleeAI(indexPlayer, c.aiscript)
elseif (indexRace == RACE_HUMAN) then
call PickMeleeAI(indexPlayer, "human.ai", null, null)
elseif (indexRace == RACE_ORC) then
call PickMeleeAI(indexPlayer, "orc.ai", null, null)
elseif (indexRace == RACE_UNDEAD) then
call PickMeleeAI(indexPlayer, "undead.ai", null, null)
call RecycleGuardPosition(bj_ghoul[index])
elseif (indexRace == RACE_NIGHTELF) then
call PickMeleeAI(indexPlayer, "elf.ai", null, null)
else
// Unrecognized race.
endif
call ShareEverythingWithTeamAI(indexPlayer)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
endfunction
//===========================================================================
// VICTORY DEFEAT SECTION
//===========================================================================
private function CustomGetAllyKeyStructureCount takes player whichPlayer returns integer
local integer i = 0
local integer keyStructs = 0
local integer playerIndex = 0
local player indexPlayer
loop
set indexPlayer = Player(playerIndex)
if (PlayersAreCoAllied(whichPlayer, indexPlayer)) then
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "townhall", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "greathall", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "necropolis", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "treeoflife", true, true)
loop
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, KEY_STRUCTURE[i], true, true)
set i = i+1
exitwhen i == KEY_STRUCTURE_COUNT
endloop
endif
set i = 0 //1.30 fix
set playerIndex = playerIndex + 1
exitwhen playerIndex == bj_MAX_PLAYERS
endloop
return keyStructs
endfunction
private function CustomPlayerIsCrippled takes player whichPlayer returns boolean
local integer allyStructures = MeleeGetAllyStructureCount(whichPlayer)
local integer allyKeyStructures = CustomGetAllyKeyStructureCount(whichPlayer)
return (allyStructures > 0) and (allyKeyStructures <= 0)
endfunction
private function CustomCheckForCrippledPlayers takes nothing returns nothing
local integer playerIndex
local player indexPlayer
local boolean isNowCrippled
call MeleeCheckForLosersAndVictors()
if bj_finishSoonAllExposed then
return
endif
set playerIndex = 0
loop
set indexPlayer = Player(playerIndex)
set isNowCrippled = CustomPlayerIsCrippled(indexPlayer)
if (not bj_playerIsCrippled[playerIndex] and isNowCrippled) then
set bj_playerIsCrippled[playerIndex] = true
call TimerStart(bj_crippledTimer[playerIndex], bj_MELEE_CRIPPLE_TIMEOUT, false, function MeleeCrippledPlayerTimeout)
if (GetLocalPlayer() == indexPlayer) then
call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], true)
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_WARNING_HUMAN"))
endif
elseif (bj_playerIsCrippled[playerIndex] and not isNowCrippled) then
set bj_playerIsCrippled[playerIndex] = false
call PauseTimer(bj_crippledTimer[playerIndex])
if (GetLocalPlayer() == indexPlayer) then
call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], false)
if (MeleeGetAllyStructureCount(indexPlayer) > 0) then
if (bj_playerIsExposed[playerIndex]) then
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
else
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
endif
endif
endif
call MeleeExposePlayer(indexPlayer, false)
endif
set playerIndex = playerIndex + 1
exitwhen playerIndex == bj_MAX_PLAYERS
endloop
endfunction
function CustomInitVictoryDefeat takes nothing returns nothing
local trigger checker = CreateTrigger()
local trigger trig
local integer index
local player indexPlayer
set bj_finishSoonTimerDialog = CreateTimerDialog(null)
call TriggerAddAction(checker, function CustomCheckForCrippledPlayers)
set trig = CreateTrigger()
call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_SOON)
call TriggerAddAction(trig, function MeleeTriggerTournamentFinishSoon)
set trig = CreateTrigger()
call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_NOW)
call TriggerAddAction(trig, function MeleeTriggerTournamentFinishNow)
set index = 0
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set bj_meleeDefeated[index] = false
set bj_meleeVictoried[index] = false
set bj_playerIsCrippled[index] = false
set bj_playerIsExposed[index] = false
set bj_crippledTimer[index] = CreateTimer()
set bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
call TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(indexPlayer))
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
call TriggerRegisterPlayerAllianceChange(checker, indexPlayer, ALLIANCE_PASSIVE)
call TriggerRegisterPlayerStateEvent(checker, indexPlayer, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_DEFEAT)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerDefeated)
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
else
set bj_meleeDefeated[index] = true
set bj_meleeVictoried[index] = false
if (IsPlayerObserver(indexPlayer)) then
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
call TimerStart(CreateTimer(), 2.0, false, function CustomCheckForCrippledPlayers)
endfunction
private function TimerAction takes nothing returns nothing
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
call MeleeStartingHeroLimit()
call MeleeGrantHeroItems()
call MeleeStartingResources()
call MeleeClearExcessUnits()
call CreateStartingUnitsForAllPlayers()
call CustomMeleeStartingAI()
call CustomInitVictoryDefeat()
call DestroyTimer(GetExpiredTimer())
endfunction
private function Init takes nothing returns nothing
call TimerStart(CreateTimer(),0,false,function TimerAction)
endfunction
endlibrary
//TESH.scrollpos=3
//TESH.alwaysfold=0
scope JungleInit initializer Init
private function DisableSpellbooks takes nothing returns nothing
//call SetPlayerAbilityAvailable(GetEnumPlayer(), 'jtxk', false) // Laceration Spell
//call SetPlayerAbilityAvailable(GetEnumPlayer(), 'jtxm', false) // Laceration Spellbook
//call SetPlayerAbilityAvailable(GetEnumPlayer(), 'jtxv', false) // Tracking active cast thing (OoL)
//call SetPlayerAbilityAvailable(GetEnumPlayer(), 'jtxx', false) // Tracking Spellbook
call SetPlayerAbilityAvailable(GetEnumPlayer(), 'jtxp', false) // Mark of the Viper Spellbook
call SetPlayerAbilityAvailable(GetEnumPlayer(), '##x1', false) // Inner Fire Spellbook
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local quest q
local CustomRace c = CustomRace.create("Jungle Trolls",RACE_ORC,0.9)
call c.setTownHall('jts2') // Grisly Message
call c.addWorkerType('jtu2',c.NEAR_MINE,5) // Groundling
call c.addHeroType('JTUC') // Headshrinker
call c.addHeroType('JTUD') // Medicine Mon
call c.addHeroType('JTUE') // Viper Priestess
call c.addHeroType('JTUF') // Warbringer
call c.setAIScript("jungletroll.ai")
call ForForce(GetPlayersAll(), function DisableSpellbooks)
call DisplayTextToForce(GetPlayersAll(), "Jungle Troll original concepts by Kyrbi0")
call DisplayTextToForce(GetPlayersAll(), "This version released 20 January 2011 by Cosmicat")
call DisplayTextToForce(GetPlayersAll(), "Check for the latest version at Wc3C.net & the Hive Workshop")
call DisplayTextToForce(GetPlayersAll(), "Report bugs/concerns via PM to Kyrbi0 or Cosmicat on wc3c.net, or to [email protected] or [email protected]")
call DisplayTextToForce(GetPlayersAll(), "Please check the Quest Log (F9) for credits, and remember to be extra nice to all the wonderful people whose names you see there!")
// CREDITS
// Primary Creators ("Required Quests")
set q = CreateQuestBJ (bj_QUESTTYPE_REQ_DISCOVERED, "Kyrbi0", /*
*/ "Original Concepts, Primary 'Coder', Designer/Implementer", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeadshrinker.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_REQ_DISCOVERED, "Cosmicat", /*
*/ "Follow-Up Implementer, Coder, Tester, Original 'Releaser'", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroTinker.blp")
//Additional Help ("Optional Quests")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Alexander244", /*
*/ "Medicine Mon: Wall of Zombies", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroPaladin.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "AnemicRoyalty", /*
*/ "Headshrinker: Skin, Combat Upgrades: Icons ('Axe_1/2/3', 'Voodoo Sigil_1/2/3')", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNElfVillager.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Anitarf", /*
*/ "ABuff library, SpellEvent library", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroAvatarOfFlame.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Archmage Owenalacaster", /*
*/ "Custom Race System", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroArchMage.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Blizzard", /*
*/ "Some Models & Most Icons Not Otherwise Credited (having been Edited by Kyrbi0)", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNBlizzard.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Cavman", /*
*/ "Marauder: Model (Mount)", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNJungleRaptor.BLP")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Cosmicat (Coding)", /*
*/ "Medicine Mon: Convalescent Curse, Wall of Zombies fix\nHeadshrinker: Bewilder, Shrink fix\nViper Priestess: Mark of the Viper, Mesmerize, Aspect of Hethiss\nKukri Toss", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroTinker.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "deepstrasz", /*
*/ "Viper Priestess: Icon\nJungle Dartmon: Icon", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNChime.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "deolrin", /*
*/ "Grisly Message: Icon, Model (edits)\nLurid Warning: Icon, Model (edits)\nGruesome Testament: Icon, Model (edits)\nAltar of Blood: Icon, Model\nTraining Log: Model\nJungle Bungalow: Model", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNForestTroll.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "[Ð]", /*
*/ "Groundling: Model\nDartmon: Model", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNArchimonde.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Falcoknight", /*
*/ "Ravager: Model, Skin", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNIceTrollShadowPriest.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "GreyArchon", /*
*/ "Vampire Bat: Model", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNOrbOfLightning.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Jigrael", /*
*/ "Headshrinker: Model, Animations, Icon\nViper Priestess: Model, Skin, Animation\nRavager: Icon, Model, Skin", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNJungleTrollRavager.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Juice_F", /*
*/ "Warbringer: Skin", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeadHunterBerserker.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "levigeorge1716", /*
*/ "Marauder: Icon, Model (Troll)", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNIceTroll.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Metal_Sonic64", /*
*/ "Hexxer: Model (Troll)", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNIceTrollBeserker.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Michael Peppers", /*
*/ "Jungle Troll AI, Viper Priestess: Serpent Steel", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroBloodElfPrince.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Norinrad", /*
*/ "Warbringer: Model (Troll)", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNPandarenBrewmaster.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Ominous Horizons", /*
*/ "Headshrinker: Base 3dsMax Model", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNMassTeleport.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Tales of Raviganion", /*
*/ "Tiki Warrior: Icon, Model, Grisly Message/Lurid Warning/Gruesome Testament: Model & Custom Textures", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNDarkSummoning.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Tijil", /*
*/ "Basilisk: Icon, Model", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNBasilisk.BLP")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Vexorian", /*
*/ "Table library, TimerUtils library", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNHeroAlchemist.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Testers", /*
*/ "Alevice, Anopob, Archmage Owenalacaster, Cosmicat, Michael Peppers", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNSheep.blp")
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "Special Thanks", /*
*/ "Tim., Vexorian, Rising_Dusk, deolrin, RenegadeMushroom, [Ð], Archmage Owenalacaster, Michael Peppers, Alevice, Anopob", /*
*/ "ReplaceableTextures\\CommandButtons\\BTNTemp.blp")
// =====
set q = CreateQuestBJ (bj_QUESTTYPE_OPT_DISCOVERED, "TODO", /*
*/ "- Crawl animations for Hunter and Tiki Warrior (forthcoming from deolrin) \n- Terrify needs polish, as in \"lots of\" \n- ", /*
*/ "ReplaceableTextues\\CommandButtons\\BTNEntanglingRoots.blp")
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table
//***************************************************************
//* Table object 3.0
//* ------------
//*
//* set t=Table.create() - instanceates a new table object
//* call t.destroy() - destroys it
//* t[1234567] - Get value for key 1234567
//* (zero if not assigned previously)
//* set t[12341]=32 - Assigning it.
//* call t.flush(12341) - Flushes the stored value, so it
//* doesn't use any more memory
//* t.exists(32) - Was key 32 assigned? Notice
//* that flush() unassigns values.
//* call t.reset() - Flushes the whole contents of the
//* Table.
//*
//* call t.destroy() - Does reset() and also recycles the id.
//*
//* If you use HandleTable instead of Table, it is the same
//* but it uses handles as keys, the same with StringTable.
//*
//* You can use Table on structs' onInit if the struct is
//* placed in a library that requires Table or outside a library.
//*
//* You can also do 2D array syntax if you want to touch
//* mission keys directly, however, since this is shared space
//* you may want to prefix your mission keys accordingly:
//*
//* set Table["thisstring"][ 7 ] = 2
//* set Table["thisstring"][ 5 ] = Table["thisstring"][7]
//*
//***************************************************************
//=============================================================
globals
private constant integer MAX_INSTANCES=8100 //400000
//Feel free to change max instances if necessary, it will only affect allocation
//speed which shouldn't matter that much.
//=========================================================
private hashtable ht
endglobals
private struct GTable[MAX_INSTANCES]
method reset takes nothing returns nothing
call FlushChildHashtable(ht, integer(this) )
endmethod
private method onDestroy takes nothing returns nothing
call this.reset()
endmethod
//=============================================================
// initialize it all.
//
private static method onInit takes nothing returns nothing
set ht = InitHashtable()
endmethod
endstruct
//Hey: Don't instanciate other people's textmacros that you are not supposed to, thanks.
//! textmacro Table__make takes name, type, key
struct $name$ extends GTable
method operator [] takes $type$ key returns integer
return LoadInteger(ht, integer(this), $key$)
endmethod
method operator []= takes $type$ key, integer value returns nothing
call SaveInteger(ht, integer(this) ,$key$, value)
endmethod
method flush takes $type$ key returns nothing
call RemoveSavedInteger(ht, integer(this), $key$)
endmethod
method exists takes $type$ key returns boolean
return HaveSavedInteger( ht, integer(this) ,$key$)
endmethod
static method flush2D takes string firstkey returns nothing
call $name$(- StringHash(firstkey)).reset()
endmethod
static method operator [] takes string firstkey returns $name$
return $name$(- StringHash(firstkey) )
endmethod
endstruct
//! endtextmacro
//! runtextmacro Table__make("Table","integer","key" )
//! runtextmacro Table__make("StringTable","string", "StringHash(key)" )
//! runtextmacro Table__make("HandleTable","handle","GetHandleId(key)" )
endlibrary
//TESH.scrollpos=63
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library GroupEnumUnitsInArea
//==================================================================================================
constant function Global_MaxCollisionSize takes nothing returns real
// You should change this to the maximum collision size in your map
return 55.
endfunction
//==================================================================================================
function GroupEnumUnitsInArea_Filter takes nothing returns boolean
return IsUnitInRangeLoc(GetFilterUnit(), bj_enumDestructableCenter ,bj_enumDestructableRadius)
endfunction
//==================================================================================================
// Use this version when you only have coordinates of the point.
//
function GroupEnumUnitsInArea takes group g, real x, real y, real range, boolexpr bx returns nothing
local boolexpr cond
local boolexpr aux=Condition(function GroupEnumUnitsInArea_Filter)
if (bx==null) then
set cond=aux
else
set cond=And(aux,bx)
endif
set bj_enumDestructableCenter=Location(x,y)
set bj_enumDestructableRadius=range
call GroupEnumUnitsInRange(g,x,y,Global_MaxCollisionSize()+range,cond)
call DestroyBoolExpr(cond)
if (bx!=null) then
call DestroyBoolExpr(aux)
endif
call RemoveLocation(bj_enumDestructableCenter)
set aux=null
set cond=null
endfunction
//==================================================================================================
// Use this version whenever you already have a location for that point, to save some steps
//
function GroupEnumUnitsInAreaLoc takes group g, location loc, real range, boolexpr bx returns nothing
local boolexpr cond
local boolexpr aux=Condition(function GroupEnumUnitsInArea_Filter)
if (bx==null) then
set cond=aux
else
set cond=And(aux,bx)
endif
set bj_enumDestructableCenter=loc
set bj_enumDestructableRadius=range
call GroupEnumUnitsInRangeOfLoc(g,loc,Global_MaxCollisionSize()+range,cond)
call DestroyBoolExpr(cond)
if (bx!=null) then
call DestroyBoolExpr(aux)
endif
set aux=null
set cond=null
endfunction
endlibrary
//TESH.scrollpos=33
//TESH.alwaysfold=0
scope ABuffInnerF initializer Init
// CALIBRATION SECTION
globals
private constant integer BUFF_AURA = 'A04X' //the buffaura that places the buff in the unit's status card
private constant integer BUFF = 'B00T' //the buff bestowed upon the unit by the buffaura
private constant integer SPELL = 'Ainf' //the single-target spell that triggers the abuff creation
endglobals
private constant function Duration takes integer level returns real
return 60.0 //the duration of the buff
endfunction
private constant function HeroDuration takes integer level returns real
return 60.0 //the duration of the buff for heroes
endfunction
// SPELL CODE
globals
public aBuffType id = 0
endglobals
private function Create takes aBuff eventBuff returns nothing
call UnitAddAbility(eventBuff.target.u, BUFF_AURA)
call SetUnitAbilityLevel(eventBuff.target.u, BUFF_AURA, eventBuff.level)
call UnitMakeAbilityPermanent(eventBuff.target.u, true, BUFF_AURA) //prevent morphing from removing the ability
endfunction
private function Refresh takes aBuff eventBuff returns nothing
call SetUnitAbilityLevel(eventBuff.target.u, BUFF_AURA, eventBuff.level)
endfunction
private function Cleanup takes aBuff eventBuff returns nothing
call UnitRemoveAbility(eventBuff.target.u, BUFF_AURA)
call UnitRemoveAbility(eventBuff.target.u, BUFF)
endfunction
private function SpellCast takes nothing returns nothing
local integer level =GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL)
local real duration = Duration(level)
if IsUnitSpellResistant(SpellEvent.TargetUnit) then
set duration = HeroDuration(level)
endif
call ABuffApply(id, SpellEvent.TargetUnit, SpellEvent.CastingUnit, duration, level, 0)
endfunction
// SPELL INITIALIZATION
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
set id = aBuffType.create()
set id.eventCreate = ABuffEvent_Create.Create
set id.eventRefresh = ABuffEvent_Refresh.Refresh
set id.eventCleanup = ABuffEvent_Cleanup.Cleanup
//set id.eventDestroy = ABuffEvent_Destroy.functionname
//set id.eventExpire = ABuffEvent_Expire.functionname
//set id.eventDeath = ABuffEvent_BUnitDeath.functionname
//set id.eventKill = ABuffEvent_BUnitKill.functionname
//set id.eventPeriodic = ABuffEvent_Periodic.functionname
//set id.eventOtherCreate = ABuffEvent_OtherCreate.functionname
//set id.eventOtherRefresh = ABuffEvent_OtherRefresh.functionname
//set id.eventOtherDestroy = ABuffEvent_OtherDestroy.functionname
//set id.eventOtherExpire = ABuffEvent_OtherExpire.functionname
//set id.eventSpellCast = ABuffEvent_BUnitSpellCast.functionname
//set id.eventSpellTargeted = ABuffEvent_BUnitSpellTargeted.functionname
//set id.eventAttack = ABuffEvent_BUnitAttack.functionname
//set id.eventAttacked = ABuffEvent_BUnitAttacked.functionname
//set id.eventDamage = ABuffEvent_BUnitDamage.functionname
//set id.eventDamaged = ABuffEvent_BUnitDamaged.functionname
//set id.eventCustom1 = ABuffEvent_Custom1.functionname
//set id.eventCustom2 = ABuffEvent_Custom2.functionname
//set id.eventCustom3 = ABuffEvent_Custom3.functionname
endfunction
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library IsUnitSpellResistant
//*****************************************************************
//* IsUnitSpellResistant
//*
//* written by: Anitarf
//*
//* In WC3, most debuff and stun spells have a decreased duration
//* against heroes, creeps with a high enough level and units with
//* resistant skin, while other spells such as Polymorph don't
//* even work against such units. This function checks if a unit
//* matches any of these criteria that would make it resistant to
//* such spells, so you can make triggered spells work that way.
//*****************************************************************
globals
private constant integer CREEP_RESISTANCE_LEVEL = 6 //the level at which creeps gain spell resistance
endglobals
function IsUnitSpellResistant takes unit u returns boolean
if IsUnitType(u, UNIT_TYPE_HERO) then
return true //unit is a hero
elseif IsUnitType(u, UNIT_TYPE_RESISTANT) then
return true //unit has a resistant-skin-type ability
elseif GetPlayerId(GetOwningPlayer(u))>11 and GetUnitLevel(u)>=CREEP_RESISTANCE_LEVEL then
return true //unit is a high-level creep
endif
return false
endfunction
function IsUnitSpellImmune takes unit u returns boolean
return IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)
endfunction
endlibrary
//TESH.scrollpos=187
//TESH.alwaysfold=0
library SpellEvent initializer Init requires Table
//*****************************************************************
//* SPELL EVENT LIBRARY 1.1
//*
//* 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.
//*
//* Note that a few spells such as Berserk and Wind Walk behave
//* somewhat differently from regular spells: they are cast
//* instantly without regard for cast animation times, they do not
//* interrupt the unit's current order, as well as any spell it
//* may be casting. SpellEvent can now handle such spells without
//* errors provided they are truly instant (without casting time).
//*
//*****************************************************************
// 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
private spellEvent interrupt
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 s.interrupt=spellEvent.casterTable[s.CastingUnit]
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
if .interrupt==0 then
call spellEvent.casterTable.flush(.CastingUnit)
else
set spellEvent.casterTable[.CastingUnit]=.interrupt
endif
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=6
//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=12
//TESH.alwaysfold=0
library ABuff initializer Init requires Table, TimerUtils, optional SpellEvent, optional DamageEvent, optional ABuffDisplay
//*****************************************************************
//* ABUFF SYSTEM 1.5
//*
//* written by: Anitarf
//* requires: -Table
//* -TimerUtils
//* optional: -SpellEvent
//* -DamageEvent
//*
//* A system for creating, managing and manipulating triggered
//* buffs and their effects. More detailed documentation can be
//* found in separate "triggers". If the map doesn't include them
//* then you can find them in the official release map here:
//* http://www.wc3campaigns.net/showthread.php?t=95521
//*****************************************************************
// ***************************
// ** CALIBRATION SECTION **
// ***************************
globals
private constant boolean USE_BUFF_EVENTS = true
private constant boolean USE_SPELL_EVENTS = true // Requires SpellEvent.
private constant boolean USE_DAMAGE_EVENTS = false // Requires DamageEvent.
private constant boolean USE_ATTACK_EVENTS = false
private constant boolean USE_PERIODIC_EVENTS = true
private constant integer MAX_CUSTOM_EVENTS = 1
public constant real PERIODIC_EVENT_PERIOD = 0.1
private constant real REFRESH_DURATION_FACTOR = 0.0
private constant boolean REFRESH_NEVER_REDUCE_DURATION = true
// ABuffType categories:
public constant key STANDARD
public constant key STACKING
public constant key REFCOUNT
endglobals
private constant function UnitRunsEvents takes unit u returns boolean
return true
endfunction
// ================================================================
// These function interfaces are included in the calibration section
// for user reference only and should not be changed in any way:
function interface ABuffEvent_Create takes aBuff eventBuff returns nothing
function interface ABuffEvent_Refresh takes aBuff eventBuff returns nothing
function interface ABuffEvent_Cleanup takes aBuff eventBuff returns nothing
function interface ABuffEvent_Destroy takes aBuff eventBuff returns nothing
function interface ABuffEvent_Expire takes aBuff eventBuff returns nothing
function interface ABuffEvent_BUnitDeath takes aBuff eventBuff, unit killer returns nothing
function interface ABuffEvent_BUnitKill takes aBuff eventBuff, unit killed returns nothing
function interface ABuffEvent_Periodic takes aBuff eventBuff returns nothing
function interface ABuffEvent_OtherCreate takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_OtherRefresh takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_OtherDestroy takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_OtherExpire takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_BUnitAttack takes aBuff eventBuff, unit attacked returns nothing
function interface ABuffEvent_BUnitAttacked takes aBuff eventBuff, unit attacker returns nothing
function interface ABuffEvent_BUnitDamage takes aBuff eventBuff, real damage, unit damagedUnit returns nothing
function interface ABuffEvent_BUnitDamaged takes aBuff eventBuff, real damage, unit damageSource returns nothing
function interface ABuffEvent_BUnitSpellCast takes aBuff eventBuff, integer spellId, unit target returns nothing
function interface ABuffEvent_BUnitSpellTargeted takes aBuff eventBuff, integer spellId, unit caster returns nothing
function interface ABuffEvent_Custom takes aBuff eventBuff, integer data returns nothing
// END OF CALIBRATION SECTION
// ================================================================
// *************************
// ** BUFF DATA STRUCTS **
// *************************
constant function PeriodicEventPeriod takes nothing returns real
// Function maintained for backwards compatibility, new code should use the constant directly
return PERIODIC_EVENT_PERIOD
endfunction
globals
private HandleTable cache //initialized in Init
private aBuff array aBuffList
private integer aBuffListMax = 0
endglobals
// ================================================================
// UNIT DATA
struct aBuffUnit
unit u
aBuff firstBuff = 0
integer numberOfBuffs = 0
boolean isDying = false
static method check takes unit u returns boolean
return cache.exists(u)
endmethod
static method get takes unit u returns aBuffUnit
local aBuffUnit abu = aBuffUnit(cache[u])
if abu==0 and u!=null then
set abu = aBuffUnit.create()
set cache[u]=integer(abu)
set abu.u = u
endif
return abu
endmethod
method onDestroy takes nothing returns nothing
//only gets destroyed when no buffs are attached
call cache.flush(.u)
set this.u = null
endmethod
endstruct
// ================================================================
// BUFFTYPE DATA
struct aBuffType
ABuffEvent_Create eventCreate = 0
ABuffEvent_Refresh eventRefresh = 0
ABuffEvent_Cleanup eventCleanup = 0
ABuffEvent_Destroy eventDestroy = 0
ABuffEvent_Expire eventExpire = 0
ABuffEvent_BUnitDeath eventDeath = 0
ABuffEvent_BUnitKill eventKill = 0
ABuffEvent_Periodic eventPeriodic = 0
ABuffEvent_OtherCreate eventOtherCreate = 0
ABuffEvent_OtherRefresh eventOtherRefresh = 0
ABuffEvent_OtherDestroy eventOtherDestroy = 0
ABuffEvent_OtherExpire eventOtherExpire = 0
ABuffEvent_BUnitAttack eventAttack = 0
ABuffEvent_BUnitAttacked eventAttacked = 0
ABuffEvent_BUnitDamage eventDamage = 0
ABuffEvent_BUnitDamaged eventDamaged = 0
ABuffEvent_BUnitSpellCast eventSpellCast = 0
ABuffEvent_BUnitSpellTargeted eventSpellTargeted = 0
ABuffEvent_Custom array eventCustom[MAX_CUSTOM_EVENTS]
boolean countsAsBuff = true
boolean ignoreAsBuff = false
integer data = 0
integer category = STANDARD
implement optional ABuffDisplay
endstruct
// ================================================================
// BUFF DATA
private keyword next
private keyword prev
private keyword beingDestroyed
private keyword referenceCount
private function ABuffExpire takes nothing returns nothing
local aBuff a = aBuff(GetTimerData(GetExpiredTimer()))
local aBuff b
set a.beingDestroyed=true
call a.id.eventExpire.execute(a)
if USE_BUFF_EVENTS and not(a.id.ignoreAsBuff) then
set b = a.target.firstBuff
loop
exitwhen b == 0
if a!=b then
call b.id.eventOtherExpire.execute(b,a)
endif
set b = b.next
endloop
endif
if a.beingDestroyed then
static if LIBRARY_ABuffDisplay then
if a.id.display!=0 then
call a.id.display.remove(a.target.u)
endif
endif
call a.id.eventCleanup.execute(a)
call a.destroy()
endif
endfunction
struct aBuff
private timer expiration = null
private integer aBuffListPlace = 0
aBuff next = 0
aBuff prev = 0
readonly aBuffType id
readonly integer level = 1
integer data = 0
integer olddata = 0
integer data1 = 0
integer data2 = 0
integer data3 = 0
readonly aBuffUnit target
readonly unit caster = null
readonly boolean friendly = true
boolean beingDestroyed = false
integer referenceCount = 0
method operator refcount takes nothing returns integer
return this.referenceCount
endmethod
static method create takes aBuffType id, unit u, unit caster, real duration, integer level, integer data returns aBuff
local aBuff a = aBuff.allocate()
local aBuffUnit abu= aBuffUnit.get(u)
set aBuffList[aBuffListMax]=integer(a)
set a.aBuffListPlace = aBuffListMax
set aBuffListMax=aBuffListMax+1
set a.id = id
set a.target = abu
set a.next = abu.firstBuff
if abu.firstBuff != 0 then
set abu.firstBuff.prev = a
endif
set abu.firstBuff = a
set a.data = data
set a.level = level
if caster !=null then
set a.caster = caster
if not(IsPlayerAlly(GetOwningPlayer(a.target.u),GetOwningPlayer(a.caster))) then
set a.friendly=false
endif
endif
if duration > 0 and a.id.category!=REFCOUNT then
set a.expiration = NewTimer()
call SetTimerData(a.expiration, integer(a))
call TimerStart(a.expiration, duration, false, function ABuffExpire)
endif
if id.countsAsBuff and not(id.ignoreAsBuff) then
set a.target.numberOfBuffs=a.target.numberOfBuffs+1
endif
return a
endmethod
method refresh takes unit caster, real newDuration, integer level, integer data returns nothing
local real prevDur = 0.0
if .expiration != null then
set prevDur = TimerGetRemaining(.expiration)
call ReleaseTimer(.expiration)
set .expiration = null
endif
if newDuration > 0 and .id.category!=REFCOUNT then
set newDuration = prevDur * REFRESH_DURATION_FACTOR + newDuration
if REFRESH_NEVER_REDUCE_DURATION and prevDur > newDuration then
set newDuration=prevDur
endif
set .expiration = NewTimer()
call SetTimerData(.expiration, integer(this))
call TimerStart(.expiration, newDuration, false, function ABuffExpire)
endif
if caster !=null then
set .caster = caster
if not(IsPlayerAlly(GetOwningPlayer(.target.u),GetOwningPlayer(.caster))) then
set .friendly=false
endif
endif
set .level=level
set .olddata=.data
set .data=data
set this.beingDestroyed=false
endmethod
method onDestroy takes nothing returns nothing
if .id.countsAsBuff and not(.id.ignoreAsBuff)then
set .target.numberOfBuffs=.target.numberOfBuffs-1
endif
if this.prev == 0 and this.next == 0 then
call .target.destroy()
else
if this.next != 0 then
set this.next.prev = this.prev
endif
if this.prev != 0 then
set this.prev.next = this.next
else
set this.target.firstBuff = this.next
endif
endif
set .caster = null
set aBuffListMax=aBuffListMax-1
set aBuffList[.aBuffListPlace]=aBuffList[aBuffListMax]
set aBuffList[.aBuffListPlace].aBuffListPlace=.aBuffListPlace
if .expiration != null then
call ReleaseTimer(.expiration)
endif
endmethod
method setTimeRemaining takes real time returns nothing
if .expiration != null then
call ReleaseTimer(.expiration)
set .expiration = null
endif
if time > 0 and .id.category!=REFCOUNT then
set .expiration = NewTimer()
call SetTimerData(.expiration, integer(this))
call TimerStart(.expiration, time, false, function ABuffExpire)
endif
endmethod
method getTimeRemaining takes nothing returns real
if .expiration != null then
return TimerGetRemaining(.expiration)
else
return 0.0
endif
endmethod
endstruct
// ================================================================
// *****************************
// ** ENUMERATION INTERFACE **
// *****************************
function interface ABuffEnum takes aBuff enumBuff, integer data returns nothing
function ABuffEnumerateAll takes ABuffEnum f, integer data returns nothing
local integer i=0
loop
exitwhen i >= aBuffListMax
if not(aBuffList[i].id.ignoreAsBuff) then
call f.evaluate(aBuffList[i], data)
endif
set i=i+1
endloop
endfunction
function ABuffEnumerateByType takes ABuffEnum f, aBuffType id, integer data returns nothing
local integer i=0
loop
exitwhen i >= aBuffListMax
if aBuffList[i].id==id then
call f.evaluate(aBuffList[i], data)
endif
set i=i+1
endloop
endfunction
function ABuffEnumerateUnit takes ABuffEnum f, unit u, integer data returns nothing
local aBuffUnit abu
local aBuff a
if aBuffUnit.check(u) then
set abu = aBuffUnit.get(u)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if not(a.id.ignoreAsBuff) then
call f.evaluate(a, data)
endif
set a = a.next
endloop
endif
endfunction
// ================================================================
// ********************
// ** EVENT ENGINE **
// ********************
private function ABuffDelayedCleanup takes nothing returns nothing
local aBuff a = aBuff(GetTimerData(GetExpiredTimer()))
if a.beingDestroyed then
static if LIBRARY_ABuffDisplay then
if a.id.display!=0 then
call a.id.display.remove(a.target.u)
endif
endif
call a.id.eventCleanup.execute(a)
call a.destroy()
endif
call ReleaseTimer(GetExpiredTimer())
endfunction
private function DestroyEvent takes aBuff a returns nothing
local aBuff b
call a.id.eventDestroy.execute(a)
if USE_BUFF_EVENTS and not(a.id.ignoreAsBuff) then
set b = a.target.firstBuff
loop
exitwhen b == 0
if a!=b then
call b.id.eventOtherDestroy.execute(b,a)
endif
set b = b.next
endloop
endif
endfunction
private function CreateEvent takes aBuff a returns nothing
local aBuff b
static if LIBRARY_ABuffDisplay then
if a.id.display!=0 then
call a.id.display.apply(a.target.u, a.level)
endif
endif
call a.id.eventCreate.execute(a)
if USE_BUFF_EVENTS and not(a.id.ignoreAsBuff) then
set b = a.target.firstBuff
loop
exitwhen b == 0
if a!=b then
call b.id.eventOtherCreate.execute(b,a)
endif
set b = b.next
endloop
endif
endfunction
private function RefreshEvent takes aBuff a returns nothing
local aBuff b
static if LIBRARY_ABuffDisplay then
if a.id.display!=0 then
call a.id.display.apply(a.target.u, a.level)
endif
endif
call a.id.eventRefresh.execute(a)
if USE_BUFF_EVENTS and not(a.id.ignoreAsBuff) then
set b = a.target.firstBuff
loop
exitwhen b == 0
if a!=b then
call b.id.eventOtherRefresh.execute(b,a)
endif
set b = b.next
endloop
endif
endfunction
private function AttackEventCondition takes nothing returns boolean
return UnitRunsEvents(GetAttacker()) and UnitRunsEvents(GetTriggerUnit())
endfunction
private function DamageEventCondition takes unit damaged, unit damager returns boolean
return UnitRunsEvents(damaged) and UnitRunsEvents(damager)
endfunction
private function SpellCastEvent takes nothing returns nothing
local integer id
local unit caster
local unit target
local aBuffUnit abu
local aBuff a
static if LIBRARY_SpellEvent then
set id=SpellEvent.AbilityId
set caster=SpellEvent.CastingUnit
set target=SpellEvent.TargetUnit
endif
if UnitRunsEvents(caster) and (target==null or UnitRunsEvents(target)) then
if aBuffUnit.check(caster) then
set abu = aBuffUnit.get(caster)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventSpellCast!=0 then
call a.id.eventSpellCast.execute(a, id, target)
endif
set a = a.next
endloop
endif
if target != null and aBuffUnit.check(target) then
set abu = aBuffUnit.get(caster)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventSpellTargeted!=0 then
call a.id.eventSpellTargeted.execute(a, id, caster)
endif
set a = a.next
endloop
endif
endif
set caster = null
set target = null
endfunction
private function AttackEvent takes nothing returns nothing
local unit attacker=GetAttacker()
local unit attacked=GetTriggerUnit()
local aBuffUnit abu
local aBuff a
if aBuffUnit.check(attacker) then
set abu = aBuffUnit.get(attacker)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventAttack!=0 then
call a.id.eventAttack.execute(a, attacked)
endif
set a = a.next
endloop
endif
if aBuffUnit.check(attacked) then
set abu = aBuffUnit.get(attacked)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventAttacked!=0 then
call a.id.eventAttacked.execute(a, attacker)
endif
set a = a.next
endloop
endif
set attacker = null
set attacked = null
endfunction
private function DeathEvent takes nothing returns nothing
local unit killed=GetTriggerUnit()
local unit killer=GetKillingUnit()
local timer t
local aBuffUnit abu
local aBuff a
if killer!=null and aBuffUnit.check(killer) then
set abu = aBuffUnit.get(killer)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventKill!=0 then
call a.id.eventKill.execute(a, killed)
endif
set a = a.next
endloop
endif
if aBuffUnit.check(killed) then
set abu = aBuffUnit.get(killed)
set a = abu.firstBuff
set abu.isDying = true
loop
exitwhen (a == 0)
set a.beingDestroyed=true
if a.id.eventDeath!=0 then
call a.id.eventDeath.execute(a, killer)
endif
set t = NewTimer()
call SetTimerData(t, integer(a))
call TimerStart(t, 0.0, false, function ABuffDelayedCleanup)
set a = a.next
endloop
endif
set killed = null
set killer = null
endfunction
private function DamageEvent takes unit damagedUnit, unit damageSource, real damage returns nothing
local aBuffUnit abu
local aBuff a
if not(DamageEventCondition(damagedUnit, damageSource)) then
return
endif
if damageSource!=null and aBuffUnit.check(damageSource) then
set abu = aBuffUnit.get(damageSource)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventDamage!=0 then
call a.id.eventDamage.execute(a, damage, damagedUnit)
endif
set a = a.next
endloop
endif
if aBuffUnit.check(damagedUnit) then
set abu = aBuffUnit.get(damagedUnit)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventDamaged!=0 then
call a.id.eventDamaged.execute(a, damage, damageSource)
endif
set a = a.next
endloop
endif
endfunction
private function PeriodicEvent takes nothing returns nothing
local integer i=0
loop
exitwhen i >= aBuffListMax
if aBuffList[i].id.eventPeriodic!=0 then
call aBuffList[i].id.eventPeriodic.execute(aBuffList[i])
endif
set i=i+1
endloop
endfunction
public function CustomEvent takes unit u, integer eventId, integer data returns nothing
local aBuffUnit abu
local aBuff a
if u!=null and aBuffUnit.check(u) then
set abu = aBuffUnit.get(u)
set a = abu.firstBuff
loop
exitwhen (a == 0)
if a.id.eventCustom[eventId]!=0 then
call a.id.eventCustom[eventId].execute(a, data)
endif
set a = a.next
endloop
endif
endfunction
// ================================================================
// EVENT INITIALIZER
private function Init takes nothing returns nothing
local trigger t
// TABLE
set cache=HandleTable.create()
// ATTACK EVENT
if USE_ATTACK_EVENTS then
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_ATTACKED )
call TriggerAddCondition( t, Condition( function AttackEventCondition ) )
call TriggerAddAction( t, function AttackEvent )
endif
// DAMAGE EVENT
static if LIBRARY_DamageEvent then
if USE_DAMAGE_EVENTS then
call RegisterDamageResponse( DamageEvent )
endif
endif
// SPELLCAST EVENT
static if LIBRARY_SpellEvent then
if USE_SPELL_EVENTS then
call RegisterSpellEffectResponse( 0, SpellCastEvent )
endif
endif
// DEATH EVENT
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DEATH )
call TriggerAddAction( t, function DeathEvent )
// PERIODIC EVENT
if USE_PERIODIC_EVENTS then
set t = CreateTrigger()
call TriggerRegisterTimerEventPeriodic( t, PERIODIC_EVENT_PERIOD )
call TriggerAddAction( t, function PeriodicEvent )
endif
endfunction
// ================================================================
// **********************
// ** USER FUNCTIONS **
// **********************
function GetABuffFromUnitByType takes unit u, aBuffType id returns aBuff
local aBuffUnit abu
local aBuff a = 0
if u!=null and aBuffUnit.check(u) then
set abu = aBuffUnit.get(u)
set a = abu.firstBuff
loop
exitwhen (a == 0 or a.id == id)
set a = a.next
endloop
endif
return a
endfunction
function GetABuffFromBuffedUnitByType takes aBuff ab, aBuffType id returns aBuff
local aBuff a = ab.target.firstBuff
loop
exitwhen (a == 0 or a.id == id)
set a = a.next
endloop
return a
endfunction
function UnitHasABuff takes unit u, aBuffType id returns boolean
return GetABuffFromUnitByType(u, id) != 0
endfunction
function BuffedUnitHasABuff takes aBuff ab, aBuffType id returns boolean
return GetABuffFromBuffedUnitByType(ab, id) != 0
endfunction
// ================================================================
function ABuffApply takes aBuffType id, unit u, unit caster, real duration, integer level, integer data returns boolean
local aBuff a = GetABuffFromUnitByType(u, id)
if u==null or IsUnitType(u, UNIT_TYPE_DEAD) or GetUnitTypeId(u)==0 then
return false
elseif ((id.category==STANDARD or id.category==REFCOUNT) and a==0) or id.category==STACKING then
set a = aBuff.create(id, u, caster, duration, level, data)
if id.category==REFCOUNT then
set a.referenceCount=a.referenceCount+1
endif
call CreateEvent(a)
return true
elseif id.category==STANDARD or id.category==REFCOUNT then
call a.refresh(caster, duration, level, data)
if id.category==REFCOUNT then
set a.referenceCount=a.referenceCount+1
endif
call RefreshEvent(a)
return true
endif
return false
endfunction
function ABuffRemove takes aBuff a returns boolean
local timer t
if a.beingDestroyed then
return false
elseif a.id.category==STANDARD or a.id.category==STACKING then
set a.beingDestroyed=true
call DestroyEvent(a)
set t = NewTimer()
call SetTimerData(t, integer(a))
call TimerStart(t, 0.0, false, function ABuffDelayedCleanup)
return true
elseif a.id.category==REFCOUNT then
set a.referenceCount=a.referenceCount-1
if a.referenceCount==0 then
set a.beingDestroyed=true
call DestroyEvent(a)
set t = NewTimer()
call SetTimerData(t, integer(a))
call TimerStart(t, 0.0, false, function ABuffDelayedCleanup)
return true
else
call a.refresh(a.caster, 0.0, a.level, a.data)
call RefreshEvent(a)
return true
endif
else
return false
endif
endfunction
// Maintained for backwards compatibility:
function ABuffDestroy takes aBuff a returns boolean
return ABuffRemove(a)
endfunction
// ================================================================
private function EnumerateRemove takes aBuff enumBuff, integer data returns nothing
if enumBuff.id.countsAsBuff and enumBuff.id.category!=REFCOUNT then
call ABuffRemove(enumBuff)
endif
endfunction
function RemoveAllBuffsOnUnit takes unit u returns nothing
call ABuffEnumerateUnit( ABuffEnum.EnumerateRemove, u, 0)
endfunction
// Maintained for backwards compatibility:
function DestroyAllBuffsOnUnit takes unit u returns nothing
call RemoveAllBuffsOnUnit( u )
endfunction
// ================================================================
function UnitGetNumberOfBuffs takes unit u returns integer
if u!=null and aBuffUnit.check(u) then
return aBuffUnit.get(u).numberOfBuffs
endif
return 0
endfunction
// ================================================================
function GetABuffTimeRemaining takes aBuff a returns real
return a.getTimeRemaining()
endfunction
function SetABuffTimeRemaining takes aBuff a, real newDuration returns nothing
call a.setTimeRemaining(newDuration)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library ABuffAura requires ABuff
//*****************************************************************
//* ABUFF AURA SYSTEM 1.5
//*
//* written by: Anitarf
//* requires: -ABuff
//*
//* This library allows you to create efficient triggered auras
//* using the ABuff system. To use it, you must first define a
//* STANDARD aBuffType, then you can add the aura to a unit (or
//* update it if the unit already has it) using this function:
//*
//* function UnitSetABuffAura takes unit u, aBuffType appliedBuff, integer level, ABuffAuraTargets targets, real range returns nothing
//*
//* The unit argument passed tot he function will be the source of
//* the aura, appliedBuff is the buff type used by the aura, level
//* and range should be self-explanatory and the targets argument
//* is a function that follows the ABuffAuraTargets function
//* interface and determines whether a unit should be affected by
//* the aura. Note that the buff type used by an aura must be in
//* the STANDARD category.
//*
//* To remove an aura from a unit, use this function:
//*
//* function UnitRemoveABuffAura takes unit u, aBuffType removedBuff returns boolean
//*
//* The returned boolean will be true if the aura was removed and
//* false if the unit did not have an aura granting that buff. If
//* you remove the aura's aBuff from a unit, that buff will not
//* be reapplied until the unit exits and re-enters the range of
//* the aura, so be careful not to remove aura aBuffs with
//* triggered dispel abilities unless that is the effect you want
//* to achieve.
//*
//* When a unit that is already under the effect of the aura from
//* one source comes in range of another source, the aBuff on the
//* unit will be refreshed to the new source if that source has a
//* higher level, otherwise it will not be refreshed.
//*****************************************************************
globals
private constant real AURA_REFRESH_PERIOD = 0.25
private constant real MAXIMUM_COLLISION_SIZE = 55.
endglobals
// This function interface is included in the calibration section
// for user reference only and should not be changed in any way.
function interface ABuffAuraTargets takes unit affected, unit auraGiver returns boolean
// ================================================================
// AURA UNIT
private struct aBuffAuraUnit
aBuffAuraUnit next = 0
aBuffAuraUnit prev = 0
unit u = null
integer level = 0
real range = 0
group affected
ABuffAuraTargets targets = 0
boolean torefresh=false
boolean todestroy=false
static method create takes unit u, integer level, ABuffAuraTargets targets, real range returns aBuffAuraUnit
local aBuffAuraUnit this = aBuffAuraUnit.allocate()
set .u = u
set .level=level
set .range=range
set .targets=targets
if .affected == null then
set .affected = CreateGroup()
endif
return this
endmethod
method onDestroy takes nothing returns nothing
call GroupClear(.affected)
endmethod
endstruct
// ================================================================
// AURA TYPE
private struct aBuffAura
aBuffType appliedBuff = 0
aBuffAuraUnit first = 0
private static aBuffAura array table
private static aBuffAura array list
private static integer count = 0
private integer listIndex = 0
private static method create takes aBuffType abt returns aBuffAura
local aBuffAura this=aBuffAura.allocate()
set aBuffAura.list[aBuffAura.count] = this
set aBuffAura.table[abt] = this
set .listIndex = aBuffAura.count
set .appliedBuff = abt
set aBuffAura.count=aBuffAura.count+1
return this
endmethod
static method get takes aBuffType abt returns aBuffAura
if aBuffAura.table[abt] == 0 then
return aBuffAura.create(abt)
endif
return aBuffAura.table[abt]
endmethod
method onDestroy takes nothing returns nothing
//this is only called if the aura has no units linked so no need to cleanup .first
set aBuffAura.count=aBuffAura.count-1
set aBuffAura.table[.appliedBuff] = 0
set aBuffAura.list[.listIndex]=aBuffAura.list[aBuffAura.count]
set aBuffAura.list[.listIndex].listIndex = .listIndex
endmethod
// ================================================================
method getAuraUnit takes unit u returns aBuffAuraUnit
local aBuffAuraUnit abau = .first
loop
exitwhen abau==0
if abau.u==u then
return abau
endif
set abau = abau.next
endloop
return 0
endmethod
private method insertAuraUnit takes aBuffAuraUnit insert returns nothing
local aBuffAuraUnit abau = .first
if insert.level>=abau.level then
set .first=insert
set abau.prev=insert
set insert.next=abau
else
loop
exitwhen insert.level>=abau.next.level or abau.next==0
set abau=abau.next
endloop
if abau.next!=0 then
set abau.next.prev=insert
set insert.next=abau.next
endif
set insert.prev=abau
set abau.next=insert
endif
endmethod
private method removeAuraUnit takes aBuffAuraUnit remove returns nothing
if .first==remove then
set .first = remove.next
else
set remove.prev.next = remove.next
endif
if remove.next != 0 then
set remove.next.prev = remove.prev
endif
endmethod
// ================================================================
private static aBuffAuraUnit current
private static boolean updating = false
private static group inrange = CreateGroup()
private static group toremove = CreateGroup()
private static group processed = CreateGroup()
private static boolexpr enum
private static method groupenum takes nothing returns boolean
// get eligible targets by picking units in range that:
// - are not dead,
// - match the target criteria and
// - are not already affected by a higher level of the aura.
local unit u=GetFilterUnit()
local boolean b=(not IsUnitType(u, UNIT_TYPE_DEAD)) and (aBuffAura.current.targets.evaluate(u, current.u)) and (not IsUnitInGroup(u, aBuffAura.processed))
set u=null
return b
endmethod
private static method updateAffectedUnits takes nothing returns nothing
local unit u=GetEnumUnit()
if IsUnitInGroup(u, aBuffAura.inrange) then
call GroupAddUnit(aBuffAura.processed, u)
call GroupRemoveUnit(aBuffAura.inrange, u)
else
if not IsUnitInGroup(u, aBuffAura.processed) then
call GroupAddUnit(aBuffAura.toremove, u)
endif
call GroupRemoveUnit(aBuffAura.current.affected, u)
endif
set u=null
endmethod
method update takes nothing returns nothing
local aBuffAuraUnit abau=.first
local unit u
set aBuffAura.updating=true
call GroupClear(aBuffAura.inrange)
call GroupClear(aBuffAura.processed)
call GroupClear(aBuffAura.toremove)
loop
exitwhen abau==0
set aBuffAura.current=abau
if abau.torefresh or abau.todestroy then
call ForGroup(abau.affected, function aBuffAura.updateAffectedUnits)
set abau.torefresh=false
endif
if abau.todestroy then
call .removeAuraUnit(abau)
call abau.destroy()
else
call GroupEnumUnitsInRange(aBuffAura.inrange, GetUnitX(abau.u), GetUnitY(abau.u), abau.range, aBuffAura.enum)
call ForGroup(abau.affected, function aBuffAura.updateAffectedUnits)
loop
set u=FirstOfGroup(aBuffAura.inrange)
exitwhen u==null
call ABuffApply(.appliedBuff, u, abau.u, 0.0, abau.level, 0)
call GroupAddUnit(abau.affected, u)
call GroupAddUnit(aBuffAura.processed, u)
call GroupRemoveUnit(aBuffAura.toremove, u)
call GroupRemoveUnit(aBuffAura.inrange, u)
endloop
endif
set abau=abau.next
endloop
loop
set u=FirstOfGroup(aBuffAura.toremove)
exitwhen u==null
call ABuffRemove(GetABuffFromUnitByType(u, .appliedBuff))
call GroupRemoveUnit(aBuffAura.toremove, u)
endloop
set aBuffAura.updating=false
endmethod
static method updateAll takes nothing returns nothing
local integer i=0
loop
exitwhen i>=aBuffAura.count
call aBuffAura.list[i].update()
if aBuffAura.list[i].first==0 then
call aBuffAura.list[i].destroy()
else
set i=i+1
endif
endloop
endmethod
private static method onInit takes nothing returns nothing
call TimerStart(CreateTimer(), AURA_REFRESH_PERIOD, true, function aBuffAura.updateAll)
set aBuffAura.enum = Condition(function aBuffAura.groupenum)
endmethod
// ================================================================
method setUnit takes unit u, integer level, ABuffAuraTargets targets, real range returns nothing
local aBuffAuraUnit abau=.getAuraUnit(u)
if abau==0 then
call .insertAuraUnit(aBuffAuraUnit.create(u, level, targets, range))
else
if abau.level != level then
call .removeAuraUnit(abau)
set abau.level=level
call .insertAuraUnit(abau)
endif
if abau.targets != targets then
set abau.targets=targets
set abau.torefresh=true
endif
set abau.range=range
set abau.todestroy=false
endif
if not aBuffAura.updating then
call .update()
endif
endmethod
method removeUnit takes unit u returns boolean
local aBuffAuraUnit abau=.getAuraUnit(u)
if abau!=0 then
set abau.todestroy=true
if not aBuffAura.updating then
call .update()
endif
return true
endif
return false
endmethod
endstruct
// ================================================================
// MAIN USER FUNCTIONS
function UnitSetABuffAura takes unit u, aBuffType appliedBuff, integer level, ABuffAuraTargets targets, real range returns nothing
if appliedBuff.category==ABuff_STANDARD then
call aBuffAura.get(appliedBuff).setUnit(u, level, targets, range)
else
debug call BJDebugMsg("UnitSetABuffAura error: the function only accepts buff types in the STANDARD category.")
endif
endfunction
function UnitRemoveABuffAura takes unit u, aBuffType removedBuff returns boolean
return aBuffAura.get(removedBuff).removeUnit(u)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library ABuffItem requires Table, ABuff, optional StatusEvents
//*****************************************************************
//* ABUFF ITEM
//*
//* written by: Anitarf
//* requires: -Table
//* -ABuff
//*
//* This library automates the use of aBuffs as passive effects of
//* items. Once an aBuffType is linked to an item type using the
//* NewABuffItem function, a buff of that type will be
//* automatically applied on any hero that picks up an item of
//* that type as well as any hero that revives carrying that item.
//* If you have the StatusEvents library in your map, then this
//* library will also detect reincarnation events and correctly
//* reapply the buff on those events as well. It will also remove
//* the buff(s) from the hero when he drops the item.
//*
//* To define an aBuff item, use this function:
//*
//* function NewABuffItem takes integer itemTypeId, aBuffType buffType returns nothing
//*
//* The function only accepts buff types in the STACKING and
//* REFCOUNT categories.
//*****************************************************************
private struct itype
integer itemTypeId
aBuffType buffType
private static Table table
static method create takes integer itemTypeId, aBuffType buffType returns itype
local itype this=itype.allocate()
set .itemTypeId=itemTypeId
set .buffType=buffType
set itype.table[itemTypeId]=this
return this
endmethod
static method get takes integer itemTypeId returns itype
return itype.table[itemTypeId]
endmethod
private static method pickup takes nothing returns boolean
local itype this = itype.get(GetItemTypeId(GetManipulatedItem()))
if this!=0 then
call ABuffApply(.buffType, GetTriggerUnit(), null, 0.0, 1, 0)
endif
return false
endmethod
private static method ressurect takes unit u returns nothing
local integer i=0
local itype this
loop
exitwhen i>5
set this = itype.get(GetItemTypeId(UnitItemInSlot(u, i)))
if this!=0 then
call ABuffApply(.buffType, GetTriggerUnit(), null, 0.0, 1, 0)
endif
set i=i+1
endloop
endmethod
private static method revive takes nothing returns boolean
call itype.ressurect(GetTriggerUnit())
return false
endmethod
private static method drop takes nothing returns boolean
local itype this = itype.get(GetItemTypeId(GetManipulatedItem()))
if this!=0 then
call ABuffRemove(GetABuffFromUnitByType(GetTriggerUnit(), .buffType))
endif
return false
endmethod
static method onInit takes nothing returns nothing
local trigger t
set t=CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_PICKUP_ITEM )
call TriggerAddCondition( t, Condition(function itype.pickup) )
set t = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_DROP_ITEM )
call TriggerAddCondition( t, Condition(function itype.drop) )
static if LIBRARY_StatusEvents then
call OnUnitResurrect(itype.ressurect)
else
set t=CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_HERO_REVIVE_FINISH )
call TriggerAddCondition( t, Condition(function itype.revive) )
endif
set itype.table=Table.create()
endmethod
endstruct
// ================================================================
function NewABuffItem takes integer itemTypeId, aBuffType buffType returns nothing
if buffType.category==ABuff_STACKING or buffType.category==ABuff_REFCOUNT then
call itype.create(itemTypeId, buffType)
else
debug call BJDebugMsg("NewABuffItem error: the function only accepts buff types in the STACKING and REFCOUNT categories.")
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library ABuffHeroSkill requires Table, ABuff, optional StatusEvents
//*****************************************************************
//* ABUFF HERO SKILL
//*
//* written by: Anitarf
//* requires: -Table
//* -ABuff
//*
//* This library automates the use of aBuffs as passive hero
//* skills. Once an aBuffType is linked to a hero skill using the
//* NewABuffHeroSkill function, a buff of that type will be
//* automatically applied on any hero that learns that skill as
//* well as any hero that revives with that skill already learned.
//* If you have the StatusEvents library in your map, then this
//* library will also detect reincarnation events and correctly
//* reapply the buff on those events as well. It will also remove
//* the buff(s) from the hero if he uses a tome of retraining.
//*
//* To define an aBuff hero skill, use this function:
//*
//* function NewABuffHeroSkill takes integer abilityId, aBuffType buffType returns nothing
//*
//* The function only accepts buff types in the STANDARD category.
//*****************************************************************
private function TomeOfRetraining takes integer itemType returns boolean
// When a hero uses an item this function is called for that item's type,
// if it returns true then all aBuff hero skills on that hero are removed.
// If you have a custom tome of retraining in your map, you need to update this function.
return itemType=='tret'
endfunction
// END OF CALIBRATION SECTION
// ================================================================
private struct skill
integer abilityId
aBuffType buffType
private static Table table
static method create takes integer abilityId, aBuffType buffType returns skill
local skill this=skill.allocate()
set .abilityId=abilityId
set .buffType=buffType
set skill.table[abilityId]=this
return this
endmethod
static method get takes integer abilityId returns skill
return skill.table[abilityId]
endmethod
static method onInit takes nothing returns nothing
set skill.table=Table.create()
endmethod
endstruct
// ================================================================
private struct hero
private unit u
private skill array skills[5]
private integer skillCount=0
private static HandleTable table
private static method create takes unit u returns hero
local hero this=hero.allocate()
set .u=u
set hero.table[u]=this
return this
endmethod
private static method get takes unit u returns hero
return hero.table[u]
endmethod
method onDestroy takes nothing returns nothing
call hero.table.flush(u)
endmethod
// ================================================================
private static method learn takes nothing returns boolean
local integer i=0
local hero this
local skill s = skill.get(GetLearnedSkill())
if s!=0 then
set this=hero.get(GetTriggerUnit())
if this==0 then
set this=hero.create(GetTriggerUnit())
endif
loop
exitwhen i>=.skillCount or s==.skills[i]
set i=i+1
endloop
if i>=.skillCount then
set .skills[i]=s
set .skillCount=i+1
endif
call ABuffApply(s.buffType, .u, .u, 0.0, GetUnitAbilityLevel(.u, s.abilityId), 0)
endif
return false
endmethod
private static method ressurect takes unit u returns nothing
local integer i=0
local hero this=hero.get(u)
if this!=0 then
loop
exitwhen i>=.skillCount
call ABuffApply(skills[i].buffType, .u, .u, 0.0, GetUnitAbilityLevel(.u, skills[i].abilityId), 0)
set i=i+1
endloop
endif
endmethod
private static method revive takes nothing returns boolean
call hero.ressurect(GetTriggerUnit())
return false
endmethod
private static method retrain takes nothing returns boolean
local integer i=0
local hero this=hero.get(GetTriggerUnit())
if this!=0 and TomeOfRetraining(GetItemTypeId(GetManipulatedItem())) then
loop
exitwhen i>=.skillCount
call ABuffRemove(GetABuffFromUnitByType(.u, skills[i].buffType))
set i=i+1
endloop
call .destroy()
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t
set t = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_HERO_SKILL )
call TriggerAddCondition( t, Condition(function hero.learn) )
static if LIBRARY_StatusEvents then
call OnUnitResurrect(hero.ressurect)
else
set t = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_HERO_REVIVE_FINISH )
call TriggerAddCondition( t, Condition(function hero.revive) )
endif
set t = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_USE_ITEM )
call TriggerAddCondition( t, Condition(function hero.retrain) )
set hero.table=HandleTable.create()
endmethod
endstruct
// ================================================================
function NewABuffHeroSkill takes integer abilityId, aBuffType buffType returns nothing
if buffType.category==ABuff_STANDARD then
call skill.create(abilityId, buffType)
else
debug call BJDebugMsg("NewABuffHeroSkill error: the function only accepts buff types in the STANDARD category.")
endif
endfunction
endlibrary
//TESH.scrollpos=11
//TESH.alwaysfold=0
library ABuffDisplay requires optional xepreload, optional AbilityPreload
//*****************************************************************
//* ABUFF DISPLAY
//*
//* written by: Anitarf
//* requires:
//* optional: -xepreload
//* -AbilityPreload
//*
//* This is a simple struct that simplifies displaying buffs in
//* the unit status bar with slow-aura-based abilities.
//*
//* For each buff that you want to display on the unit using this
//* method, you must first create an ability in the object editor
//* based on slow aura ('Aasl') and change it's "targets allowed"
//* field to "self" only. You will probably also want to create a
//* custom buff for the aura. Then, on map initialization, create
//* an aBuffDisplay struct using the aura and buff ids and you
//* can then use the struct's .apply method to display the buff
//* on the unit or update the level of an already displayed buff
//* and the .remove method to remove the buff from the unit. You
//* can also assign the aBuffDisplay to an aBuffType and the
//* ABuff system will call these methods automatically when a
//* buff of that type is created, refreshed or destroyed.
//*
//* The library automatically preloads the aura ability when a
//* new aBuffDisplay is created as long as you have at least one
//* of the optionally required preload libraries in your map. By
//* preloading the ability, you can avoid the lag that otherwise
//* occurs when the ability is first added to a unit.
//*
//* An example of the syntax:
//*
//* set d = aBuffDisplay.create( auraId, buffId )
//*
//* call d.apply( u, level )
//* call d.remove( u )
//*****************************************************************
struct aBuffDisplay
private integer auraId
private integer buffId
static method create takes integer auraId, integer buffId returns aBuffDisplay
local aBuffDisplay this=aBuffDisplay.allocate()
set .auraId=auraId
set .buffId=buffId
static if LIBRARY_xepreload then
call XE_PreloadAbility(auraId)
elseif LIBRARY_AbilityPreload then
call AbilityPreload(auraId)
endif
return this
endmethod
method apply takes unit u, integer level returns nothing
call UnitAddAbility(u, .auraId)
call SetUnitAbilityLevel(u, .auraId, level)
call UnitMakeAbilityPermanent(u, true, .auraId) //prevent morphing from removing the ability
endmethod
method remove takes unit u returns nothing
call UnitRemoveAbility(u, .auraId)
call UnitRemoveAbility(u, .buffId)
endmethod
endstruct
module ABuffDisplay
aBuffDisplay display = 0
endmodule
endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
VERSION 1.5
INTRODUCTION
This system was designed to provide a framework within which you can easily
create all your buffs. You can make your buffs respond to all sorts of events,
including custom ones that you specify, thus you can make your buffs interact
with any other systems you may have in your map. Custom user data can be
attached to both buff types and specific buffs to make sure that the engine
does not represent a limiting factor in buff design.
This system was developed to support and promote the creation of interesting
new buff spells, in hope that this will lead to new, more exciting gameplay
in established map genres. Use your creativity.
REQUIREMENTS
This system requires a vJass compiler such as JassHelper, the easiest way
to get it is as a part of the Jass NewGen pack which you can get here:
http://www.wc3c.net/showthread.php?t=90999
At the time of this release, the JassHelper version included with the latest
NewGen pack is outdated and does not have all the features required to
process this version of ABuff, you may need to get the latest version of
JassHelper here:
http://www.wc3c.net/showthread.php?t=88142
It is recommended that the user has a basic knowledge of vJass structs and
function interfaces, but it is not required because the documentation of this
system and added samples explain it well enough.
The system also requires the following external libraries:
- Table (http://www.wc3c.net/showthread.php?t=101246)
- TimerUtils (http://www.wc3c.net/showthread.php?t=101322)
- SpellEvent (http://www.wc3c.net/showthread.php?t=105374)
- DamageEvent (http://www.wc3c.net/showthread.php?t=108009)
You can find them in this map as well, but they may not be the latest versions.
Note that this system supports a maximum of 8191 buff types, 8191 buffs and
8191 units affected by buffs, it is very very unlikely that anyone will ever
reach or even get anywhere near this limit without doing something incredibly
silly, but I thought I would mention it anyway.
ACKNOWLEDGEMENTS
This system was created by Anitarf, please give credit if you use it.
Thanks to Vexorian for creating vJass.
Thanks to Vexorian and PipeDream for helpful suggestions.
Thanks to other users of wc3campaigns.net for their input.
//TESH.scrollpos=0
//TESH.alwaysfold=0
Version 1.5
- Added support for three different buff categories: STANDARD, STACKING, REFCOUNT.
- Added the ABuffDisplay library that simplifies displaying buffs with tornado-based auras.
- Added the ABuffHeroSkill library that simplifies making passive hero skills.
- Added the ABuffItem library that simplifies making abuffs applied by items.
- ABuff now uses DamageEvent instead of ADamage. It now also uses SpellEvent.
- ABuff still works without these two libraries, it just does not use these events without them.
- Completely recoded the ABuffAura library, it should be much more efficient now.
- Renamed ABuffDestroy to ABuffRemove, the old function name is still supported.
- Changed how custom events work, the new code is not backwards compatible.
- Recoded the samples to utilize other useful libraries besides ABuff.
Version 1.4b
- Updated the test map to use 1.24-compatible Table and TimerUtils, ABuff remains unchanged.
Version 1.4
- The system now uses standard libraries, Table for gamecache use and TimerUtils for timers.
- Damage detection has been moved to an independant library, which also supports damage prevention.
- The dummy units section has been replaced with the UnitRunsEvents function.
- The aBuff core engine has been reworked and optimised.
- The aura subsystem has been reworked, removing dynamic trigger use and some bugs.
- Fixed a bug in the SetABuffTimeRemaining function that prevented the duration from being decreased.
Version 1.3
- Added effect delay safety checks to the stun system and to examples with dummy casters.
- Added the functions GetABuffFromBuffedUnitByType and BuffedUnitHasABuff.
- Did minor changes to the code to make use of new vJass syntax.
Version 1.2
- Added an aura subsystem.
- Fixed some non critical coding errors.
- Made the system more robust by using .execute instead of .evaluate for user event response functions.
- Added a safety check so buffs that are reapplied while being destroyed are not immediately cleaned up.
- Added a safety check so buffs can no longer be applied to dying units on the unit dies event.
Version 1.1
- Fixed an error which caused damage events not to be ingored even if the user specified that.
- Added the RefreshNeverReduceDuration function.
Version 1.0
- Initial release
//TESH.scrollpos=37
//TESH.alwaysfold=0
IMPORTING THE SYSTEM INTO A MAP
Thanks to vJass, the implementation of this system is a very simple and
painless two-step process:
Step one: Copy the ABuff libraries and the libraries they require to your map.
Step two: Edit the calibration section of the ABuff library to suit your needs.
SYSTEM CALIBRATION
The first thing you can calibrate are the USE EVENTS constants. These can be
used to turn off a certain frequently occuring event, for example, if none of
your buffs responds to the buffed unit attacking or being attacked, then
the attack event serves no purpose other than slowing your map down, in such
case you can turn it off with the corresponding constant. The USE_BUFF_EVENTS
refers to "other" buff efents, these are the events that run for a buff when
another buff is applied/destroyed... on the same unit (see the Event response
functions part of the documentation).
A note to spellmakers: if you make a public spell where a buff uses any of
these events, make sure that is clearly stated in the documentation of the
spell so users do not turn off an event the spell needs by mistake.
The constant MAX_CUSTOM_EVENTS determines how many custom events does the
system support. Note that the higher this number, the fewer buff types you can
declare, so you should not set it higher than the number of custom events your
map is actually using.
The constant PERIODIC_EVENT_PERIOD specifies how frequently the buff periodic
event occurs. The default value of 0.1 means it will run ten times per second,
The faster it runs the smoother the buff effects that are run by it will be,
however making it run too fast could affect performance, if there are enough
buffs that have enough periodic effects of course.
A note to spellmakers: if you make a public spell where a buff uses the
periodic event, make sure it works the same for all users, no matter what
period they set. If a spell is supposed to regenerate X mana per second,
then the periodic function should regenerate (X*ABuff_PERIODIC_EVENT_PERIOD)
whenever it runs.
The constant REFRESH_DURATION_FACTOR specifies how much of a buff's remaining
duration is preserved when the buff is reapplied. By default, this values is
0, since that is how normal wc3 buffs behave, when you cast Inner Fire on a
unit that already has this buff, duration of the new buff will be 60 seconds
no matter how much duration the old buff had remaining. However, for ABuffs,
you can specify that a portion of the previous buff's remaining duration is
added to the new buff. If you set this factor to 1, then the old buff's
duration will be fully preserved and the new duration will be added on top
of it.
If the constant boolean REFRESH_NEVER_REDUCE_DURATION is true, then refreshing
a buff will never be able to reduce it's duration. If the new duration would
be shorter than the buff's current remaining duration, then the remaining
duration of the buff is not changed in this case. This is how normal Warcraft
buffs function as well, if you stun a unit for 6 seconds and 1 second later,
it is stunned by bash for 2 seconds, the stun will end 5 seconds later, not 2.
The UnitRunsEvents() function allows you to specify conditions under which
units will not trigger events. This is especialy useful for ignoring dummy
units, since they are just helper units and not actual gameplay elements you
typicaly do not care when they deal or receive damage, kill units or cast
spells. The only event that runs even if UnitRunsEvents() returns false for
one of the units involved is the death event, that is because aBuffs get
cleaned up from the dying unit on this event, so it must run even if the
dying or the killing unit otherwise does not trigger events.
//TESH.scrollpos=55
//TESH.alwaysfold=0
DECLARING BUFF TYPES
To declare a new type of buff to be used by the system, you have to create a
new aBuffType. aBuffType is a struct and when creating it, you should store it
to a global variable so you can reference it later, for example when wanting
to create an ABuff of this type. The proper syntax for this, as can be seen in
examples, is:
globals
aBuffType buffname
endglobals
...
set buffname = aBuffType.create()
Now the variable buffname points to a newly created buff type. For it to mean
anything, we must now define this bufftype. There are some parameters that can
be set for any buff type.
The first parameter you should set is the category of the buff type. There are
three categories: ABuff_STANDARD, ABuff_STACKING and ABuff_REFCOUNT.
ABuffs in the standard category function the same as regular WC3 buffs, when
you apply them on a unit, if the unit already has one of that type, it will be
refreshed, otherwise, a new buff will be created. The buffs are destroyed when
their timer runs out or when the user removes them.
ABuffs in the stacking category function similarly to standard buffs, except
they are never refreshed, instead a new buff is applied on a unit every time
so a unit can have multiple buffs of the same type this way. The buffs are
destroyed when their timer runs out or when the user removes them.
ABuffs in the reference counted category do not support a timer, they must be
removed manually. However, they keep a count of how many times they have been
applied so you must remove them an equal number of times for them to be
actually destroyed, otherwise they will be just refreshed.
The default value is the standard category. The syntax for changing it is:
set buffname.category = ABuff_STACKING
The second parameter is the boolean countsAsBuff. The default value of this
parameter is true, if set to false then buffs of this type won't increase a
unit's buffcount. A user can use this value to differentiate between magical
buffs and status ailments such as poison. The syntax for setting it is:
set buffname.countsAsBuff = false
The third parameter is the boolean ignoreAsBuff. The default value of this
parameter is false, but if set to true, the engine will ignore the buff in
many respects. Regardless of the countsAsBuff boolean, the buff will not add
to a unit's buffcount, it also won't get picked when enumerating buffs (see
the Manipulating buffs section of the documentation) and the "other" ABuff
events (see the Event response functions of the documentation) won't trigger
when such a buff is created/refreshed/destroyed... This is especialy useful
when you want to use the ABuff to trigger something other than a buff, like
for example a passive skill. The syntax:
set buffname.ignoreAsBuff = true
The data integer parameter can be used to attach any user-data to the buff
type. This integer does not affect how the engine handles buffs of this type,
but users can use it to differentiate between buffs of various elements or
schools of magic or any other generic characteristic that they want their
buffs to have. Since it is an integer, an index of a struct can also be
stored here, thus allowing any amount of data to be attached to a buff type.
Syntax:
set buffname.data = 5
or...
set buffname.data = integer(someOtherStructVariable)
The last set of parameters to be covered here are the event response
functions, the core of the ABuff system. Each buff type can be assigned a
function for each of the events the engine can handle. More can be read about
them in a seperate section of this documentation, here is just the syntax for
assigning them to buff types:
set buffname.eventCreate = functionname //The function must follow the ABuffEvent_Create function interface.
set buffname.eventRefresh = functionname //The function must follow the ABuffEvent_Refresh function interface.
set buffname.eventCleanup = functionname //The function must follow the ABuffEvent_Cleanup function interface.
set buffname.eventDestroy = functionname //The function must follow the ABuffEvent_Destroy function interface.
set buffname.eventExpire = functionname //The function must follow the ABuffEvent_Expire function interface.
set buffname.eventDeath = functionname //The function must follow the ABuffEvent_BUnitDeath function interface.
set buffname.eventKill = functionname //The function must follow the ABuffEvent_BUnitKill function interface.
set buffname.eventPeriodic = functionname //The function must follow the ABuffEvent_Periodic function interface.
set buffname.eventOtherCreate = functionname //The function must follow the ABuffEvent_OtherCreate function interface.
set buffname.eventOtherRefresh = functionname //The function must follow the ABuffEvent_OtherRefresh function interface.
set buffname.eventOtherDestroy = functionname //The function must follow the ABuffEvent_OtherDestroy function interface.
set buffname.eventOtherExpire = functionname //The function must follow the ABuffEvent_OtherExpire function interface.
set buffname.eventSpellCast = functionname //The function must follow the ABuffEvent_BUnitSpellCast function interface.
set buffname.eventSpellTargeted = functionname //The function must follow the ABuffEvent_BUnitSpellTargeted function interface.
set buffname.eventAttack = functionname //The function must follow the ABuffEvent_BUnitAttack function interface.
set buffname.eventAttacked = functionname //The function must follow the ABuffEvent_BUnitAttacked function interface.
set buffname.eventDamage = functionname //The function must follow the ABuffEvent_BUnitDamage function interface.
set buffname.eventDamaged = functionname //The function must follow the ABuffEvent_BUnitDamaged function interface.
set buffname.eventCustom[0] = functionname //The function must follow the ABuffEvent_Custom function interface.
//TESH.scrollpos=0
//TESH.alwaysfold=0
GENERAL EVENT RESPONSE INFORMATION
Event response functions are implemented through function interfaces, this
means any function a user writes can be used as a buff event response as long
as it follows a predefined format for that event, meaning that it must take
the same parameters and have the same return type. The event response function
interfaces are:
function interface ABuffEvent_Create takes aBuff eventBuff returns nothing
function interface ABuffEvent_Refresh takes aBuff eventBuff returns nothing
function interface ABuffEvent_Cleanup takes aBuff eventBuff returns nothing
function interface ABuffEvent_Destroy takes aBuff eventBuff returns nothing
function interface ABuffEvent_Expire takes aBuff eventBuff returns nothing
function interface ABuffEvent_BUnitDeath takes aBuff eventBuff, unit killer returns nothing
function interface ABuffEvent_BUnitKill takes aBuff eventBuff, unit killed returns nothing
function interface ABuffEvent_Periodic takes aBuff eventBuff returns nothing
function interface ABuffEvent_OtherCreate takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_OtherRefresh takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_OtherDestroy takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_OtherExpire takes aBuff eventBuff, aBuff otherBuff returns nothing
function interface ABuffEvent_BUnitAttack takes aBuff eventBuff, unit attacked returns nothing
function interface ABuffEvent_BUnitAttacked takes aBuff eventBuff, unit attacker returns nothing
function interface ABuffEvent_BUnitDamage takes aBuff eventBuff, real damage, unit damagedUnit returns nothing
function interface ABuffEvent_BUnitDamaged takes aBuff eventBuff, real damage, unit damageSource returns nothing
function interface ABuffEvent_BUnitSpellCast takes aBuff eventBuff, integer spellId, unit target returns nothing
function interface ABuffEvent_BUnitSpellTargeted takes aBuff eventBuff, integer spellId, unit caster returns nothing
function interface ABuffEvent_Custom takes aBuff eventBuff, integer data returns nothing
Each event response function takes an aBuff parameter eventBuff, that is the
ABuff for which the event runs. In addition to this, some functions take other
parameters, their names should be pretty self-descriptive. If you use waits in
these functions or use timers to call other function with a delay, keep in
mind that it is not healthy to refer to the eventBuff after the wait because
the aBuff may be long gone and the aBuff index recycled by then. Instead,
store all values you want to use after the wait in local varaibles, or get
the buff from the unit again after the wait to verify that it still exists.
See the "Declaring buff types" section of the documentation for the syntax
of assigning event response functions to aBuff types.
SPECIFIC EVENT RESPONSES
The Create event occurs immediately after the buff has been created, this is
the function in which you establish the buff, give the buffed unit all the
abilities that the buff is supposed to give and apply any other effects that
come with the buff. You could do the initial effects within the same function
that applies it, but it makes more sense to have this in a function attached
to the buff type, that way buffs are truly independant and you could have
another spell that duplicates the buff on another unit without needing to know
which function created it in the first place.
The Refresh event occurs immediately after the buff has been refreshed, a buff
is refreshed when it is applied on a unit that already has a buff of that
type. This event is required because although the buff was already on the unit
before and was thus already initialized by it's Create function, there may be
some things that you need to update if the buff was recast under different
circumstances or at a different level.
The Cleanup event happens just before a buff is destroyed. As the name
suggests, the function's intended purpose is to allow the user to clean up
the buff's effects, such as removing abilities that the buff gives to a unit
in it's Create function etc.
When destroying a buff prematurely with the ABuffDestroy function, the Destroy
event occurs. This event response function is called before Cleanup, there is
a minimal delay between Destroy and Cleanup so that if you loop through all
buffs on a unit and destroy them, when the last buff's Destroy event response
function runs the first one will still not have been cleaned, thus it's custom
data would still be avaliable to the last buff's Destroy event response, in
case the user wants to have some interaction between buffs on this event.
The Expire event occurs when the buff's timer expires. It happens before
Cleanup and allows the buff to have an effect upon expiration that it wouldn't
have if it were destroyed before that by a dispel ability.
The BUnitDeath event is the last of the three events that occur right before
a buff is cleaned up and removed. Similar to Destroy, the BUnitDeath event
response will be called for all buffs on a unit before any of them are cleaned
up and destroyed, so every ABuff will still know of all other ABuffs on the
dying unit. Do not try to apply any new buffs on the dying unit in the
BUnitDeath event response function, it would have caused bad bugs before
version 1.2 but now the system simply doesn't allow it anymore.
Right before the BUnitDeath event occurs for the buffs on the dying unit, the
BUnitKill event occurs for all buffs on the killing unit.
The Periodic event occurs for all buffs periodicaly, you can specify how often
that happens in the system's calibration section (see the Setting up the
system section of the documentation).
The OtherCreate, OtherRefresh, OtherDestroy and OtherExpire events occur for
a buff whenever a corresponding basic buff event occurs for another buff on
the same unit. The OtherCreate event runs right after Create, OtherRefresh
right after Refresh, etc. OtherDestroy and OtherExpire both run for all buffs
on the unit before Cleanup runs for the destroyed/expiring buff.
The BUnitAttack and BUnitAttacked events occur when a unit is attacked, first
the BUnitAttack event will run for all buffs on the attacking unit, then
BUnitAttacked runs for the attacked unit. Same goes for BUnitDamage and
BUnitDamaged, whenever damage is dealt to a unit, the BUnitDamage event
response will be called for all buffs on the damage source, and then the
BUnitDamaged event response for the buffs on the damaged unit.
The BUnitSpellCast and BUnitSpellTargeted work similarly, when a unit starts
the effect of an ability BUnitSpellCast will run for all buffs on that unit,
then, if the spell targets any unit, the BUnitSpellTargeted will run for all
buffs on that unit.
The Custom events are up to the user to define so they can fill in any
map-specific events that are not covered by the generic aBuff events. For
example, what if your map has spells that can change the classification of
a unit and you have some buffs that only work on some classifications? Then
you could run a custom event whenever a classification of a unit changes
and then any buffs that can only affect units with specific classifications
can respond to this even and check if their target is still valid, if it is
not then the buff can remove itself.
You can have any number of custom events in your map, just make sure you
set the MAX_CUSTOM_EVENTS constant correctly. The custom event indexes start
at 0 and end at (MAX_CUSTOM_EVENTS-1), so if your map uses three custom
events then their indexes would be 0, 1 and 2. To run a custom event for
a unit, use this function:
public function CustomEvent takes unit u, integer eventId, integer data returns nothing
//TESH.scrollpos=129
//TESH.alwaysfold=0
APPLYING ABUFFS
To create an ABuff, you should always use the following function:
function ABuffApply takes aBuffType id, unit u, unit caster, real duration, integer level, integer data returns boolean
The aBuffType parameter id defines what kind of buff will the new buff be. For
more information about buff types, see the Declaring buff types section of the
documentation.
The unit parameter u is the unit to which the buff should be applied. The unit
parameter caster is optional, it allows the engine to determine if it's a
friendly or enemy buff. The duration parameter specifies how long before the
buff expires measured in seconds, if it's 0 then the buff will not be given an
expiration timer. The level and data integer parameters are optional, they can
be used to attach data to the ABuff, the level parameter is obviously intended
to store the level of the spell that created the buff while the data parameter
can be used for any buff specific data that the buff-type requires.
The function returns a boolean, if it's true then that means the buff was
successfuly created or refreshed if the unit already had a buff of this type,
if it's false then that means the buff couldn't be created because the unit is
dead or dying, since when a unit dies all buffs are automaticaly cleaned from
it and the system stops tracking it, thus freeing memory for new units. If the
system allowed users to create buffs on dead units then it wouldn't know when
to stop tracking them and would eventualy run out of space to store buffed
units, this is why this limitation was added. Besides, normal warcraft buffs
get removed from units when they die too.
Creating or refreshing a buff with the ABuffApply function also executes the
corresponding ABuff event responses.
ENUMERATING ABUFFS
You can then further manipulate the ABuffs from within the ABuff event
response functions and the ABuff enumeration functions. The event response
functions are described in more detail in other parts of the documentation, so
I will only describe how to use enumeration functions here. Enumeration is,
like event responses, implemented through a function interface:
function interface ABuffEnum takes aBuff enumBuff, integer data returns nothing
All functions that follow this format can be used as enumeration functions.
When enumerating buffs, such a function will be called for each buff, and the
aBuff parameter enumBuff will point to the buff for which the function is
being executed. We can use enumeration functions to either enumerate all
ABuffs on a unit or all ABuffs in the game or all ABuffs of a certain type in
the game with the following three functions:
function ABuffEnumerateUnit takes ABuffEnum f, unit u, integer data returns nothing
function ABuffEnumerateAll takes ABuffEnum f, integer data returns nothing
function ABuffEnumerateByType takes ABuffEnum f, aBuffType id, integer data returns nothing
The last function can also be used to enumerate buffs that otherwise have
their ignoreAsBuff property set to true (see the Declaring buff types part
of the documentation). The data parameter can be used to pass information
to the enumeration function, as can be seen in the Arcane Cleansing example.
The best example of how to use enumeration would be the RemoveAllBuffsOnUnit
function which is already included in this system:
private function EnumerateRemove takes aBuff enumBuff, integer data returns nothing
if enumBuff.id.countsAsBuff then
call ABuffDestroy(enumBuff)
endif
endfunction
function RemoveAllBuffsOnUnit takes unit u returns nothing
call ABuffEnumerateUnit( ABuffEnum.EnumerateRemove, u, 0)
endfunction
REMOVING ABUFFS
This example also shows you how to destroy a buff, by calling the ABuffRemove
function:
function ABuffRemove takes aBuff a returns boolean
The ABuffRemove function returns a boolean, it will be true if the ABuff was
successfuly destroyed and false if it couldn't be destroyed. This could occur
if you tried to destroy a buff that is already in the process of being
destroyed, for example if you tried to destroy it from an ABuff event response
function which itself ran when that buff was destroyed. This safety measure
prevents an infinite loop if you do something silly like using the following
function as a buff's destroy event response function:
function Destroy takes aBuff eventBuff returns nothing
call ABuffRemove(eventBuff) //do not do this at home
endfunction
Speaking of infinite loops, there are other ways to get them so be careful
and don't do stupid things, such as a buff that applies another buff whenever
any buff on the unit is refreshed, or a buff that deals damage whenever a unit
takes damage, if you do anything that you suspect could cause recursion then
add in safety checks to make sure it can't happen.
GETTING ABUFFS FROM UNITS
There's one more way to get a buff, all you need is a local variable of the
aBuff type and then you can get a specific buff from a unit with the following
function, note that it returns 0 if the unit doesn't have a buff of that type:
function GetABuffFromUnitByType takes unit u, aBuffType id returns aBuff
This function also allows you to get buffs which have their ignoreAsBuff
property set to true, which you could not get otherwise with enumeration
interfaces (unless you enumerated them by type). You can check if a unit has
a buff with the following function:
function UnitHasABuff takes unit u, aBuffType id returns boolean
There are two more functions similar to the last two listed, they are:
function GetABuffFromBuffedUnitByType takes aBuff ab, aBuffType id returns aBuff
function BuffedUnitHasABuff takes aBuff ab, aBuffType id returns boolean
In case you already know of a buff that is on the unit you want to check, for
example in an aBuffs event response function, you could use these two functions
because they are slightly faster. It's not a big difference but if a common
buff such as one applied by an aura uses it in it's periodic event then every
bit of optimization matters.
OBTANING INFORMATION ABOUT ABUFFS
You can get and set the remaining time on the expiration timer of a buff. The
functions to do that are:
function GetABuffTimeRemaining takes aBuff a returns real
function SetABuffTimeRemaining takes aBuff a, real newDuration returns nothing
If the buff is permanent and doesn't have an expiration timer, then the
function GetABuffTimeRemaining returns 0.0. If you pass 0.0 as the newDuration
then the buff will become permanent.
You also have access to other ABuff data, the examples in the list show how to
get it for the eventBuff in ABuffEvent functions, simply replace it with
otherBuff or enumBuff depending on where you use it.
Readonly data fields initialized by the system, do not modifly:
eventBuff.id aBuffType, the type of this ABuff
eventBuff.target.u unit, the target of the buff
eventBuff.target.numberOfBuffs integer, number of buffs currently on the buffed unit
eventBuff.level integer, the level of the buff
eventBuff.data integer, the custom data that is attached to an ABuff when it is applied
eventBuff.olddata integer, intended to be used in the Refresh event response functions to cleanup old structs
eventBuff.caster unit, this is the unit that the user specified as the caster of the buff
eventBuff.friendly boolean, even if the caster unit is no longer ingame the buff can remember if it was friend or foe
User-defined data fields ignored by the system, use as needed:
eventBuff.data1 integer
eventBuff.data2 integer
eventBuff.data3 integer
You can also get the number of buffs a unit has directly from the unit, with
the following function:
function UnitGetNumberOfBuffs takes unit u returns integer
As mentioned elsewhere, this will only include buffs that have their
property countsAsBuff set to true and property ignoreAsBuff to false.
//TESH.scrollpos=30
//TESH.alwaysfold=0
// This is a simple piece of code that makes a unit-target buff spell, in
// this case the Priest's Inner Fire, count as an aBuff.
// You can simply copy this "trigger", rename the scope, edit the
// calibration section with new values and you have it working for
// another spell.
// This ability uses the following libraries:
// - library ABuff
// - library SpellEvent
// - library IsUnitSpellResistant
scope PriestInnerFire initializer Init
// CALIBRATION SECTION
globals
private constant integer BUFF = 'Binf' //the buff bestowed upon the unit by the spell
private constant integer SPELL = 'Ainf' //the spell that applies the buff
endglobals
private constant function Duration takes integer level returns real
return 60.0 //the duration of the buff
endfunction
private constant function HeroDuration takes integer level returns real
return 60.0 //the duration of the buff for heroes
endfunction
// ================================================================
// BUFF DEFINITIONS
globals
public aBuffType buffType = 0
endglobals
private function Cleanup takes aBuff eventBuff returns nothing
call UnitRemoveAbility(eventBuff.target.u, BUFF)
endfunction
// SPELL CODE
private function SpellCast takes nothing returns nothing
local integer level =GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL)
local real duration = Duration(level)
if IsUnitSpellResistant(SpellEvent.TargetUnit) then
set duration = HeroDuration(level)
endif
call ABuffApply(buffType, SpellEvent.TargetUnit, SpellEvent.CastingUnit, duration, level, 0)
endfunction
// SPELL INITIALIZATION
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(SPELL, SpellCast)
set buffType = aBuffType.create()
set buffType.eventCleanup = Cleanup
endfunction
endscope