//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once TimerUtils initializer init
//*********************************************************************
//* TimerUtils (Blue flavor for 1.23b or later)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3campaigns.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Blue Flavor: Slower than the red flavor, it got a 408000 handle id
//* limit, which means that if more than 408000 handle ids
//* are used in your map, TimerUtils might fail, this
//* value is quite big and it is much bigger than the
//* timer limit in Red flavor.
//*
//********************************************************************
//==================================================================================================
globals
private hashtable hasht //I <3 blizz
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
call SaveInteger(hasht,0, GetHandleId(t), value)
endfunction
function GetTimerData takes timer t returns integer
return LoadInteger(hasht, 0, GetHandleId(t))
endfunction
//==========================================================================================
globals
private timer array tT
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
set tT[0]=CreateTimer()
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==8191) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
set hasht = InitHashtable()
endfunction
endlibrary
//TESH.scrollpos=38
//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=189
//TESH.alwaysfold=0
library ABuff initializer Init requires Table, TimerUtils, ADamage
//*****************************************************************
//* ABUFF SYSTEM 1.4
//*
//* written by: Anitarf
//* requires: -Table
//* -TimerUtils
//* -ADamage
//*
//* 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_ATTACK_EVENTS = true
private constant boolean USE_DAMAGE_EVENTS = true
private constant boolean USE_PERIODIC_EVENTS = true
public constant real PERIODIC_EVENT_PERIOD = 0.1
private constant real REFRESH_DURATION_FACTOR = 0.0
private constant boolean REFRESH_NEVER_REDUCE_DURATION = true
endglobals
private constant function UnitRunsEvents takes unit u returns boolean
return true
endfunction
// *************************
// ** 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
// EVENT INTERFACES
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_Custom1 takes aBuff eventBuff, integer data returns nothing
function interface ABuffEvent_Custom2 takes aBuff eventBuff, integer data returns nothing
function interface ABuffEvent_Custom3 takes aBuff eventBuff, integer data returns nothing
// 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_Custom1 eventCustom1 = 0
ABuffEvent_Custom2 eventCustom2 = 0
ABuffEvent_Custom3 eventCustom3 = 0
boolean countsAsBuff = true
boolean ignoreAsBuff = false
integer data = 0
endstruct
// BUFF DATA
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
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
aBuffType id
integer level = 1
integer data = 0
integer olddata = 0
aBuffUnit target
unit caster = null
boolean friendly = true
boolean beingDestroyed = false
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 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 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 .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 .prev == 0 and .next == 0 then
call .target.destroy()
else
if .next != 0 then
set .next.prev = .prev
endif
if .prev != 0 then
set .prev.next = .next
else
set .target.firstBuff = .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 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
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
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
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 SpellCastEventCondition takes nothing returns boolean
return UnitRunsEvents(GetSpellTargetUnit()) and UnitRunsEvents(GetTriggerUnit())
endfunction
private function SpellCastEvent takes nothing returns nothing
local integer id=GetSpellAbilityId()
local unit caster=GetTriggerUnit()
local unit target=GetSpellTargetUnit()
local aBuffUnit abu
local aBuff a
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
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, real prevented 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
function CustomEvent1 takes unit u, 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.eventCustom1!=0 then
call a.id.eventCustom1.execute(a, data)
endif
set a = a.next
endloop
endif
endfunction
function CustomEvent2 takes unit u, 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.eventCustom2!=0 then
call a.id.eventCustom2.execute(a, data)
endif
set a = a.next
endloop
endif
endfunction
function CustomEvent3 takes unit u, 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.eventCustom3!=0 then
call a.id.eventCustom3.execute(a, data)
endif
set a = a.next
endloop
endif
endfunction
// EVENT INITIALIZER
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
if USE_DAMAGE_EVENTS then
call ADamage_AddResponse( ADamage_Response.DamageEvent)
endif
// SPELLCAST EVENT
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( t, Condition( function SpellCastEventCondition ) )
call TriggerAddAction( t, function SpellCastEvent )
// 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 GetWidgetLife(u) <= 0.405 then
return false
elseif a==0 then
set a = aBuff.create(id, u, caster, duration, level, data)
call CreateEvent(a)
return true
else
call a.refresh(caster, duration, level, data)
call RefreshEvent(a)
return true
endif
endfunction
function ABuffDestroy takes aBuff a returns boolean
local timer t
if a.beingDestroyed then
return false
else
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
endif
endfunction
private function EnumerateDestroy takes aBuff enumBuff, integer data returns nothing
if enumBuff.id.countsAsBuff then
call ABuffDestroy(enumBuff)
endif
endfunction
function DestroyAllBuffsOnUnit takes unit u returns nothing
call ABuffEnumerateUnit( ABuffEnum.EnumerateDestroy, u, 0)
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=407
//TESH.alwaysfold=0
library ADamage initializer Init requires Table, TimerUtils
//*****************************************************************
//* ADAMAGE SYSTEM 1.2 - damage detection and prevention system.
//*
//* written by: Anitarf
//* requires: -Table
//* -TimerUtils
//*
//* Damage detection is implemented with a single trigger to which
//* damage events for specific units are added over time as new
//* units enter the map. The trigger gets periodically destroyed
//* and remade to clean up events for units that are no longer in
//* the game. Note that there are some obscure bugs associated
//* with destroying triggers, the system takes every precaution
//* that is believed to help avoid such bugs but in the unlikely
//* event that you get such bugs you can turn off this feature to
//* check if it is the one causing the bugs.
//*
//* All functions following the Response function interface that
//* is defined at the start of this system can be used to respond
//* to damage events. Simply add such functions to the system's
//* call list with the AddResponse function.
//*
//* public function AddResponse takes Response r returns nothing
//*
//* To prevent damage, you must apply shields to units. Shields
//* are implemented as structs that extend the system's shield
//* struct, using the damage method to reduce the damage dealt
//* by the unit and the damaged method to reduce the damage
//* dealt to the unit. An example:
//*
//* struct armour extends ADamage_shield
//* //all additional struct members are optional:
//* static integer PRIORITY=0 //default priority
//* real power //this lets us give different units differently strong armour
//*
//* // create method is optional, if you don't declare one then you must use
//* // the .allocate parameters (unit, integer) when creating a shield of this kind
//* static method create takes unit u, real power returns armour
//* //note the parameters for .allocate, this is because this struct extends
//* //the shield struct which asks for those parameters in it's create method
//* local armour ar = armour.allocate(u, armour.PRIORITY)
//* set ar.power = power
//* return ar
//* endmethod
//* // this is the method that runs when damage is dealt to the shielded unit
//* // the damage parameter tells how much damage got to this shield past any shields with a higher priority that the unit may have
//* // the real that the method returns is how much of that damage should be prevented by this shield
//* // this is all that this method is intended for, calculating damage reduction, don't try to do anything funny like dealing more damage here
//* method damaged takes unit damageSource, real damage returns real
//* // this is a simple shield that just gives a flat damage reduction
//* if this.power>damage then
//* return damage
//* endif
//* return this.power
//* endmethod
//* // onDestroy method is optional, in this case we don't need it
//* endstruct
//*
//* As has been mentioned, shields with a higher priority will
//* be "on top", meaning they block damage first, so the only
//* damage that shields with lower priority can prevent is the
//* damage that those with higher priority don't. If two shields
//* on a unit have the same priority, if the NEW_SHIELD_ON_TOP
//* boolean is set to true, the newer of the two shields will be
//* on top, if false then the older one will be on top.
//*
//* When preventing damage that is higher than a unit's max hp,
//* the system needs to temporarily add a bonus hp ability to the
//* unit; that's the SURVIVAL_ABILITY. This ability should add
//* more bonus hp to the unit than any single source of damage
//* in your map can deal.
//*
//* It is recommended to use a 0 second timer delay when dealing
//* damage in response to detecting damage (for example if you
//* make a spiked carapace or critical strike type ability), this
//* allows the damage that was originaly detected to actually be
//* dealt and prevented before new damage occurs. You can still
//* choose to deal damage immediately and not bother with a delay,
//* the system is pretty robust and can handle that in most cases;
//* the only problem is if you stack more damage than a unit's max
//* hp this way in chunks that are each smaller than it's max hp,
//* then the unit will die even it's shields would have prevented
//* the damage otherwise.
//*
//*****************************************************************
// use the AddResponse function to add damage event responses to the system
public function interface Response takes unit damagedUnit, unit damageSource, real damage, real prevented returns nothing
// this is the interface that the shield struct extends, it is private because your shields
// are not supposed to extend it directly, but must extend the shield struct instead
private interface shield_template
method damage takes unit damagedUnit, real damage returns real //returned real says how much of the damage the unit deals to prevent
method damaged takes unit damageSource, real damage returns real //returned real says how much of the damage the unit takes to prevent
endinterface
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
// you can use this boolean to tell the system to ignore such events
private constant boolean IGNORE_ZERO_DAMAGE_EVENTS = true
// 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
private constant real TRIGGER_REFRESH_PERIOD = 300.0 //each how many seconds should the trigger be refreshed
private constant boolean NEW_SHIELD_ON_TOP = true //what happens if two shields have the same priority
private constant integer SURVIVAL_ABILITY = '7A00' //the bonus hp ability that lets units survive critical damage
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
// END OF CALIBRATION SECTION
// ================================================================
globals
private Response array responses
private integer responsesCount = 0
private trigger detectorOld = null
private triggeraction detectOld = null
//initialized in Init
private trigger detector
private triggeraction detect
private group g
private boolean clear
private HandleTable scache
private HandleTable dcache
endglobals
public function AddResponse takes Response r returns nothing
set responses[responsesCount]=r
set responsesCount=responsesCount+1
endfunction
// ================================================================
public keyword shield
private struct shieldedUnit
unit u
shield firstShield = 0
static method check takes unit u returns boolean
return scache.exists(u)
endmethod
static method get takes unit u returns shieldedUnit
local shieldedUnit su = shieldedUnit(scache[u])
if su==0 and u!=null then
set su = shieldedUnit.create()
set scache[u]=integer(su)
set su.u = u
endif
return su
endmethod
method onDestroy takes nothing returns nothing
//only gets destroyed when no shields are attached
call scache.flush(.u)
set this.u = null
endmethod
endstruct
public struct shield extends shield_template
private shieldedUnit target
shield prev=0
shield next=0
integer priority
static method create takes unit u, integer priority returns shield
local shield s
local shield sf
local shieldedUnit su = shieldedUnit.get(u)
if su==0 then
return 0
endif
set s = shield.allocate()
set sf = su.firstShield
// add shield to the linked list
if sf==0 then
set su.firstShield = s
elseif sf.priority<priority or (NEW_SHIELD_ON_TOP and sf.priority==priority) then
set s.next = sf
set sf.prev = s
set su.firstShield = s
else
loop
if sf.next == 0 then
set sf.next=s
set s.prev=sf
exitwhen true
else
set sf = sf.next
if sf.priority<priority or (NEW_SHIELD_ON_TOP and sf.priority==priority) then
set s.next = sf
set s.prev = sf.prev
set sf.prev.next = s
set sf.prev = s
exitwhen true
endif
endif
endloop
endif
set s.target = su
set s.priority = priority
return s
endmethod
method damage takes unit damagedUnit, real damage returns real //returned real says how much damage to prevent
return 0.0 //this is just a dummy method, structs that extend this struct will override it with their own
endmethod
method damaged takes unit damageSource, real damage returns real //returned real says how much damage to prevent
return 0.0 //this is just a dummy method, structs that extend this struct will override it with their own
endmethod
method onDestroy takes nothing returns nothing
if .prev == 0 and .next == 0 then
call .target.destroy()
else
if .next != 0 then
set .next.prev = .prev
endif
if .prev != 0 then
set .prev.next = .next
else
set .target.firstShield = .next
endif
endif
endmethod
endstruct
// ================================================================
private struct damagedUnit
unit u
real hp = 0.0
static method check takes unit u returns boolean
return dcache.exists(u)
endmethod
static method get takes unit u returns damagedUnit
local damagedUnit du = damagedUnit(dcache[u])
if du==0 then
set du = damagedUnit.create()
set dcache[u]=integer(du)
set du.u = u
endif
return du
endmethod
method update takes real additionalDmg returns nothing
set .hp=.hp-additionalDmg
if .hp<=0.405 then
//in case this additional damage would kill the unit, allow it to do so
call SetWidgetLife(.u, 0.5)
endif
endmethod
method onDestroy takes nothing returns nothing
call dcache.flush(.u)
set this.u = null
endmethod
endstruct
private function DamageRestore takes nothing returns nothing
local damagedUnit du = damagedUnit(GetTimerData(GetExpiredTimer()))
call ReleaseTimer(GetExpiredTimer())
call UnitRemoveAbility(du.u, SURVIVAL_ABILITY)
call SetWidgetLife(du.u, du.hp)
call du.destroy()
endfunction
private function DamageResponse takes nothing returns nothing
local unit damaged = GetTriggerUnit()
local unit damager = GetEventDamageSource()
local real damage = GetEventDamage()
local real prevented = 0.0
local real life = GetWidgetLife(damaged)
local real maxlife = GetUnitState(damaged,UNIT_STATE_MAX_LIFE)
local damagedUnit du
local timer t
local shield s
local integer i = 0
if IGNORE_ZERO_DAMAGE_EVENTS and damage<=0.0 then
set damager=null
set damaged=null
return
endif
//calculate how much damage to prevent
if shieldedUnit.check(damager) then
set s=shieldedUnit.get(damager).firstShield
loop
exitwhen s==0
set prevented=prevented+s.damage(damaged, damage-prevented)
set s=s.next
endloop
endif
if shieldedUnit.check(damaged) then
set s=shieldedUnit.get(damaged).firstShield
loop
exitwhen s==0
set prevented=prevented+s.damaged(damager, damage-prevented)
set s=s.next
endloop
endif
//prevent damage
if life-damage+prevented>0.405 then
//unit should survive, bother about it
if maxlife < life+prevented then
//need a delayed hp restoration
call SetWidgetLife(damaged, maxlife)
if maxlife-damage <=0.405 then
//need a survival ability
call UnitAddAbility(damaged, SURVIVAL_ABILITY)
endif
set du = damagedUnit.get(damaged)
if du.hp==0.0 then
//no damage on the stack, start a new delayed hp restoration
set du.hp=life-damage+prevented
set t = NewTimer()
call SetTimerData(t, integer(du))
call TimerStart(t, 0.0, false, function DamageRestore)
else
//damage being dealt in response to other damage, just update the existing delayed hp restoration
call du.update(damage-prevented)
endif
else
//hp may be restored immediately
call SetWidgetLife(damaged, life+prevented)
if damagedUnit.check(damaged) then
//a delayed hp restoration is in progress
call damagedUnit.get(damaged).update(damage-prevented)
endif
endif
//no "else" needed, if the unit won't survive there's no point in restoring some of the damage
endif
//call external functions
loop
exitwhen i>=responsesCount
call responses[i].execute(damaged, damager, damage, prevented)
set i=i+1
endloop
set damaged=null
set damager=null
endfunction
// ================================================================
private function DetectTriggerRefreshEnum takes nothing returns nothing
if clear then //code "borrowed" from Captain Griffen's GroupRefresh function to prevent performance degradation
call GroupClear(g)
set clear = false
endif
call GroupAddUnit(g, GetEnumUnit())
call TriggerRegisterUnitEvent( detector, GetEnumUnit(), EVENT_UNIT_DAMAGED )
endfunction
private function DetectTriggerRefresh takes nothing returns nothing
if detectorOld!=null then
call TriggerRemoveAction(detectorOld, detectOld)
call DestroyTrigger(detectorOld)
endif
call DisableTrigger(detector)
set detectorOld=detector
set detectOld=detect
set detector = CreateTrigger()
set detect = TriggerAddAction(detector, function DamageResponse)
set clear = true
call ForGroup(g, function DetectTriggerRefreshEnum)
if clear then
call GroupClear(g)
endif
endfunction
// ================================================================
private function DamageRegister takes nothing returns nothing
local unit u = GetTriggerUnit()
if CanTakeDamage(u) then
call TriggerRegisterUnitEvent( detector, u, EVENT_UNIT_DAMAGED )
call GroupAddUnit(g, u)
endif
set u = null
endfunction
private function DamageRegisterEnum takes nothing returns boolean
if CanTakeDamage(GetFilterUnit()) then
call TriggerRegisterUnitEvent( detector, GetFilterUnit(), EVENT_UNIT_DAMAGED )
return true
endif
return false
endfunction
private function Init takes nothing returns nothing
local rect rec = GetWorldBounds()
local region reg = CreateRegion()
local boolexpr b = Condition(function DamageRegisterEnum)
local trigger t = CreateTrigger()
call RegionAddRect(reg, rec)
call TriggerRegisterEnterRegion(t, reg, null)
call TriggerAddAction(t, function DamageRegister)
set detector = CreateTrigger()
set detect = TriggerAddAction(detector, function DamageResponse)
set g = CreateGroup()
call GroupEnumUnitsInRect(g, rec, b)
set scache = HandleTable.create()
set dcache = HandleTable.create()
if REFRESH_TRIGGER then
call TimerStart(NewTimer(), TRIGGER_REFRESH_PERIOD, true, function DetectTriggerRefresh)
endif
call RemoveRect(rec)
call DestroyBoolExpr(b)
set rec = null
set b = null
endfunction
endlibrary
//TESH.scrollpos=20
//TESH.alwaysfold=0
library xebasic
//**************************************************************************
//
// xebasic 0.4
// =======
// XE_DUMMY_UNITID : Rawcode of the dummy unit in your map. It should
// use the dummy.mdx model, so remember to import it as
// well, just use copy&paste to copy the dummy from the
// xe map to yours, then change the rawcode.
//
// XE_HEIGHT_ENABLER: Medivh's raven form ability, you may need to change
// this rawcode to another spell that morphs into a flier
// in case you modified medivh's spell in your map.
//
// XE_TREE_RECOGNITION: The ancients' Eat tree ability, same as with medivh
// raven form, you might have to change it.
//
// XE_ANIMATION_PERIOD: The global period of animation used by whatever
// timer that depends on it, if you put a low value
// the movement will look good but it may hurt your
// performance, if instead you use a high value it
// will not lag but will be fast.
//
// XE_MAX_COLLISION_SIZE: The maximum unit collision size in your map, if
// you got a unit bigger than 197.0 it would be
// a good idea to update this constant, since some
// enums will not find it. Likewise, if none of
// your units can go bellow X and X is much smaller
// than 197.0, it would be a good idea to update
// as well, since it will improve the performance
// those enums.
//
// Notice you probably don't have to update this library, unless I specify
// there are new constants which would be unlikely.
//
//**************************************************************************
//===========================================================================
globals
constant integer XE_DUMMY_UNITID = 'e000'
constant integer XE_HEIGHT_ENABLER = 'Amrf'
constant integer XE_TREE_RECOGNITION = 'Aeat'
constant real XE_ANIMATION_PERIOD = 0.025
constant real XE_MAX_COLLISION_SIZE = 197.0
endglobals
endlibrary
//TESH.scrollpos=451
//TESH.alwaysfold=0
library xedamage initializer init requires xebasic
//************************************************************************
// xedamage 0.4
// --------
// For all your damage and targetting needs.
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAX_SUB_OPTIONS = 3
//=======================================================
private constant real EPSILON = 0.000000001
private unit dmger
private constant integer MAX_SPACE = 8190 // MAX_SPACE/MAX_SUB_OPTIONS is the instance limit for xedamage, usually big enough...
endglobals
private keyword structInit
struct xedamage[MAX_SPACE]
//----
// fields and methods for a xedamage object, they aid determining valid targets and special
// damage factor conditions.
//
// Notice the default values.
//
boolean damageSelf = false // the damage and factor methods usually have a source unit parameter
// xedamage would consider this unit as immune unless you set damageSelf to true
boolean damageAllies = false // Alliance dependent target options.
boolean damageEnemies = true // *
boolean damageNeutral = true // *
boolean ranged = true // Is the attack ranged? This has some effect on the AI of the affected units
// true by default, you may not really need to modify this.
boolean visibleOnly = false // Should only units that are visible for source unit's owner be affected?
boolean deadOnly = false // Should only corpses be affected by "the damage"? (useful when using xedamage as a target selector)
boolean alsoDead = false // Should even corpses and alive units be considered?
boolean damageTrees = false //Also damage destructables? Notice this is used only in certain methods.
//AOE for example targets a circle, so it can affect the destructables
//in that circle, a custom spell using xedamage for targetting configuration
//could also have an if-then-else implemented so it can verify if it is true
//then affect trees manually.
//
// Damage type stuff:
// .dtype : the "damagetype" , determines if the spell is physical, magical or ultimate.
// .atype : the "attacktype" , deals with armor.
// .wtype : the "weapontype" , determines the sound effect to be played when damage is done.
//
// Please use common.j/blizzard.j/ some guide to know what damage/attack/weapon types can be used
//
damagetype dtype = DAMAGE_TYPE_UNIVERSAL
attacktype atype = ATTACK_TYPE_NORMAL
weapontype wtype = WEAPON_TYPE_WHOKNOWS
//
// Damage type 'tag' people might use xedamage.isInUse() to detect xedamage usage, there are other static
// variables like xedamage.CurrentDamageType and xedamage.CurrentDamageTag. The tag allows you to specify
// a custom id for the damage type ** Notice the tag would aid you for some spell stuff, for example,
// you can use it in a way similar to Rising_Dusk's damage system.
//
integer tag = 0
//
// if true, forceDamage will make xedamage ignore dtype and atype and try as hard as possible to deal 100%
// damage.
boolean forceDamage = false
//
// Ally factor! Certain spells probably have double purposes and heal allies while harming enemies. This
// field allows you to do such thing.
//
real allyfactor = 1.0
//
// field: .exception = SOME_UNIT_TYPE
// This field adds an exception unittype (classification), if the unit belongs to this unittype it will
// be ignored.
//
method operator exception= takes unittype ut returns nothing
set this.use_ex=true
set this.ex_ut=ut
endmethod
//
// field: .required = SOME_UNIT_TYPE
// This field adds a required unittype (classification), if the unit does not belong to this unittype
// it will be ignored.
//
method operator required= takes unittype ut returns nothing
set this.use_req=true
set this.req_ut=ut
endmethod
private boolean use_ex = false
private unittype ex_ut = null
private boolean use_req = false
private unittype req_ut = null
private unittype array fct[MAX_SUB_OPTIONS]
private real array fc[MAX_SUB_OPTIONS]
private integer fcn=0
//
// method .factor(SOME_UNIT_TYPE, factor)
// You might call factor() if you wish to specify a special damage factor for a certain classification,
// for example call d.factor(UNIT_TYPE_STRUCTURE, 0.5) makes xedamage do half damage to structures.
//
method factor takes unittype ut, real fc returns nothing
if(this.fcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to factor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of factor() calls")
return
endif
set this.fct[this.fcn] = ut
set this.fc[this.fcn] = fc
set this.fcn = this.fcn+1
endmethod
private integer array abifct[MAX_SUB_OPTIONS]
private real array abifc[MAX_SUB_OPTIONS]
private integer abifcn=0
//
// method .abilityFactor('abil', factor)
// You might call abilityFactor() if you wish to specify a special damage factor for units that have a
// certain ability/buff.
// for example call d.abilityFactor('A000', 1.5 ) makes units that have the A000 ability take 50% more
// damage than usual.
//
method abilityFactor takes integer abilityId, real fc returns nothing
if(this.abifcn==MAX_SUB_OPTIONS) then
debug call BJDebugMsg("In one instance of xedamage, you are doing too much calls to abilityFactor(), please increase MAX_SUB_OPTIONS to allow more, or cut the number of abilityFactor() calls")
return
endif
set this.abifct[this.abifcn] = abilityId
set this.abifc[this.abifcn] = fc
set this.abifcn = this.abifcn+1
endmethod
private boolean usefx = false
private string fxpath
private string fxattach
//
// method .useSpecialEffect("effect\\path.mdl", "origin")
// Makes it add (and destroy) an effect when damage is performed.
//
method useSpecialEffect takes string path, string attach returns nothing
set this.usefx = true
set this.fxpath=path
set this.fxattach=attach
endmethod
//********************************************************************
//* Now, the usage stuff:
//*
//================================================================================
// static method xedamage.isInUse() will return true during a unit damaged
// event in case this damage was caused by xedamage, in this case, you can
// read variables like CurrentDamageType, CurrentAttackType and CurrentDamageTag
// to be able to recognize what sort of damage was done.
//
readonly static damagetype CurrentDamageType=null
readonly static attacktype CurrentAttackType=null
readonly static integer CurrentDamageTag =0
private static integer inUse = 0
static method isInUse takes nothing returns boolean
return (.inUse>0) //inline friendly.
endmethod
//========================================================================================================
// This function calculates the damage factor caused by a certain attack and damage
// type, it is static : xedamage.getDamageTypeFactor(someunit, ATTAcK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, 100)
//
static method getDamageTypeFactor takes unit u, attacktype a, damagetype d returns real
local real hp=GetWidgetLife(u)
local real mana=GetUnitState(u,UNIT_STATE_MANA)
local real r
//Since a unit is in that point, we don't need checks.
call SetUnitX(dmger,GetUnitX(u))
call SetUnitY(dmger,GetUnitY(u))
call SetUnitOwner(dmger,GetOwningPlayer(u),false)
set r=hp
if (hp<1) then
call SetWidgetLife(u,1)
set r=1
endif
call UnitDamageTarget(dmger,u,0.01,false,false,a,d,null)
call SetUnitOwner(dmger,Player(15),false)
if (mana>GetUnitState(u,UNIT_STATE_MANA)) then
//Unit had mana shield, return 1 and restore mana too.
call SetUnitState(u,UNIT_STATE_MANA,mana)
set r=1
else
set r= (r-GetWidgetLife(u))*100
endif
call SetWidgetLife(u,hp)
return r
endmethod
private method getTargetFactorCore takes unit source, unit target, boolean usetypes returns real
local player p=GetOwningPlayer(source)
local boolean allied=IsUnitAlly(target,p)
local boolean enemy =IsUnitEnemy(target,p)
local boolean neutral=allied
local real f
local real negf=1.0
local integer i
if(this.damageAllies != this.damageNeutral) then
set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_REQUEST ))
//I thought accuracy was not as important as speed , I think that REQUEST is false is enough to consider
// it neutral.
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_HELP_RESPONSE ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_XP ))
//set neutral= allied and not (GetPlayerAlliance(GetOwningPlayer(target),p, ALLIANCE_SHARED_SPELLS ))
set allied= allied and not(neutral)
endif
if (not this.damageAllies) and allied then
return 0.0
elseif (not this.damageEnemies) and enemy then
return 0.0
elseif( (not this.damageSelf) and (source==target) ) then
return 0.0
elseif (not this.damageNeutral) and neutral then
return 0.0
elseif( this.use_ex and IsUnitType(target, this.ex_ut) ) then
return 0.0
elseif( this.visibleOnly and not IsUnitVisible(target,p) ) then
return 0.0
elseif ( this.deadOnly and not IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
elseif ( not(this.alsoDead) and IsUnitType(target,UNIT_TYPE_DEAD) ) then
return 0.0
endif
set f=1.0
if ( IsUnitAlly(target,p) ) then
set f=f*this.allyfactor
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
if (this.use_req and not IsUnitType(target,this.req_ut)) then
return 0.0
endif
set i=.fcn-1
loop
exitwhen (i<0)
if( IsUnitType(target, this.fct[i] ) ) then
set f=f*this.fc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set i=.abifcn-1
loop
exitwhen (i<0)
if( GetUnitAbilityLevel(target,this.abifct[i] )>0 ) then
set f=f*this.abifc[i]
if(f<=-EPSILON) then
set f=-f
set negf=-1.0
endif
endif
set i=i-1
endloop
set f=f*negf
if ( f<=EPSILON) and (f>=-EPSILON) then
return 0.0
endif
if( this.forceDamage or not usetypes ) then
return f
endif
set f=f*xedamage.getDamageTypeFactor(target,this.atype,this.dtype)
if ( f<=EPSILON) and (f>=-EPSILON) then
return 0.0
endif
return f
endmethod
//====================================================================
// With this you might decide if a unit is a valid target for a spell.
//
method getTargetFactor takes unit source, unit target returns real
return this.getTargetFactorCore(source,target,true)
endmethod
//======================================================================
// a little better, I guess
//
method allowedTarget takes unit source, unit target returns boolean
return (this.getTargetFactor(source,target)!=0.0)
endmethod
//=======================================================================
// performs damage to the target unit, for unit 'source'.
//
method damageTarget takes unit source, unit target, real damage returns boolean
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local real f = this.getTargetFactorCore(source,target,false)
local real pl
if(f!=0.0) then
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
set pl=GetWidgetLife(target)
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return (pl!=GetWidgetLife(target))
endif
return false
endmethod
//=======================================================================================
// The same as damageTarget, but it forces a specific damage value, good if you already
// know the target.
//
method damageTargetForceValue takes unit source, unit target, real damage returns nothing
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
set .inUse = .inUse +1
call UnitDamageTarget(source,target, damage, true, .ranged, null, null, .wtype )
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
endmethod
//=====================================================================================
// Notice: this will not Destroy the group, but it will certainly empty the group.
//
method damageGroup takes unit source, group targetGroup, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
local unit target
local real f
local integer count=0
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
loop
set target=FirstOfGroup(targetGroup)
exitwhen (target==null)
call GroupRemoveUnit(targetGroup,target)
set f= this.getTargetFactorCore(source,target,false)
if (f!=0.0) then
set count=count+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
endif
endloop
set .inUse = .inUse -1
set .CurrentDamageTag=tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return count
endmethod
private static xedamage instance
private integer countAOE
private unit sourceAOE
private real AOEx
private real AOEy
private real AOEradius
private real AOEdamage
private static boolexpr filterAOE
private static boolexpr filterDestAOE
private static group enumgroup
private static rect AOERect
private static method damageAOE_Enum takes nothing returns boolean
local unit target=GetFilterUnit()
local xedamage this=.instance //adopting a instance.
local real f
if( not IsUnitInRangeXY(target,.AOEx, .AOEy, .AOEradius) ) then
set target=null
return false
endif
set f=.getTargetFactorCore(.sourceAOE, target, false)
if(f!=0.0) then
set .countAOE=.countAOE+1
call UnitDamageTarget(.sourceAOE,target, f*this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
endif
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
private static method damageAOE_DestructablesEnum takes nothing returns boolean
local destructable target=GetFilterDestructable()
local xedamage this=.instance //adopting a instance.
local real dx=.AOEx-GetDestructableX(target)
local real dy=.AOEy-GetDestructableY(target)
if( dx*dx + dy*dy >= .AOEradius+EPSILON ) then
set target=null
return false
endif
set .countAOE=.countAOE+1
if(.usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
call UnitDamageTarget(.sourceAOE,target, this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
set .instance= this //better restore, nesting IS possible!
set target=null
return false
endmethod
//==========================================================================================
// will affect trees if damageTrees is true!
//
method damageAOE takes unit source, real x, real y, real radius, real damage returns integer
local damagetype dt=.CurrentDamageType
local attacktype at=.CurrentAttackType
local integer tg=.CurrentDamageTag
set .CurrentDamageType = .dtype
set .CurrentAttackType = .atype
set .CurrentDamageTag = .tag
set .inUse = .inUse +1
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius
set .AOEy=y
set .AOEdamage=damage
call GroupEnumUnitsInRange(.enumgroup,x,y,radius+XE_MAX_COLLISION_SIZE, .filterAOE)
if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
set .AOEradius=.AOEradius*.AOEradius
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
endif
set .inUse = .inUse -1
set .CurrentDamageTag = tg
set .CurrentDamageType = dt
set .CurrentAttackType = at
return .countAOE
endmethod
method damageAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageAOE(source, GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//==========================================================================================
// only affects trees, ignores damageTrees
//
method damageDestructablesAOE takes unit source, real x, real y, real radius, real damage returns integer
set .instance=this
set .countAOE=0
set .sourceAOE=source
set .AOEx=x
set .AOEradius=radius*radius
set .AOEy=y
set .AOEdamage=damage
//if(.damageTrees) then
call SetRect(.AOERect, x-radius, y-radius, x+radius, y+radius)
call EnumDestructablesInRect(.AOERect, .filterDestAOE, null)
//endif
return .countAOE
endmethod
method damageDestructablesAOELoc takes unit source, location loc, real radius, real damage returns integer
return .damageDestructablesAOE(source,GetLocationX(loc), GetLocationY(loc), radius, damage)
endmethod
//'friend' with the library init
static method structInit takes nothing returns nothing
set .AOERect= Rect(0,0,0,0)
set .filterAOE= Condition(function xedamage.damageAOE_Enum)
set .filterDestAOE = Condition( function xedamage.damageAOE_DestructablesEnum)
set .enumgroup = CreateGroup()
endmethod
endstruct
private function init takes nothing returns nothing
set dmger=CreateUnit(Player(15), XE_DUMMY_UNITID , 0.,0.,0.)
call UnitAddAbility(dmger,'Aloc')
call xedamage.structInit()
endfunction
endlibrary
//TESH.scrollpos=14
//TESH.alwaysfold=0
library SimError initializer init
//**************************************************************************************************
//*
//* SimError
//*
//* Mimic an interface error message
//* call SimError(ForPlayer, msg)
//* ForPlayer : The player to show the error
//* msg : The error
//*
//* To implement this function, copy this trigger and paste it in your map.
//* Unless of course you are actually reading the library from wc3c's scripts section, then just
//* paste the contents into some custom text trigger in your map.
//*
//**************************************************************************************************
//==================================================================================================
globals
private sound error
endglobals
//====================================================================================================
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
//call StartSound( error ) //apparently the bug in which you play a sound for the first time
//and it doesn't work is not there anymore in patch 1.22
endfunction
endlibrary
//TESH.scrollpos=27
//TESH.alwaysfold=0
library LastOrder initializer Init needs UnitIndexingUtils
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library has a lot of usefulness for when you want to interface with the
//* last order a unit was given. This can be useful for simulating spell errors
//* and where you'd want to give them back the order they had prior to the spell
//* cast (whereas without this library, they'd just forget their orders).
//*
//* There are some handy interfacing options for your use here --
//* function GetLastOrderId takes unit u returns integer
//* function GetLastOrderString takes unit u returns string
//* function GetLastOrderType takes unit u returns integer
//* function GetLastOrderX takes unit u returns real
//* function GetLastOrderY takes unit u returns real
//* function GetLastOrderTarget takes unit u returns widget
//* function AbortOrder takes unit u returns boolean
//*
//* There are also some order commands that can be useful --
//* function IssueLastOrder takes unit u returns boolean
//* function IssueSecondLastOrder takes unit u returns boolean
//* function IsLastOrderFinished takes unit u returns boolean
//*
//* You can access any information you'd like about the orders for your own
//* order handling needs.
//*
globals
//* Storage for last order
private integer array Order
private integer array Type
private widget array Targ
private boolean array Flag
private real array X
private real array Y
//* Storage for second last order
private integer array P_Order
private integer array P_Type
private widget array P_Targ
private boolean array P_Flag
private real array P_X
private real array P_Y
//* Order type variables
constant integer ORDER_TYPE_TARGET = 1
constant integer ORDER_TYPE_POINT = 2
constant integer ORDER_TYPE_IMMEDIATE = 3
//* Trigger for the order catching
private trigger OrderTrg = CreateTrigger()
endglobals
//**********************************************************
function GetLastOrderId takes unit u returns integer
return Order[GetUnitId(u)]
endfunction
function GetLastOrderString takes unit u returns string
return OrderId2String(Order[GetUnitId(u)])
endfunction
function GetLastOrderType takes unit u returns integer
return Type[GetUnitId(u)]
endfunction
function GetLastOrderX takes unit u returns real
return X[GetUnitId(u)]
endfunction
function GetLastOrderY takes unit u returns real
return Y[GetUnitId(u)]
endfunction
function GetLastOrderTarget takes unit u returns widget
return Targ[GetUnitId(u)]
endfunction
//**********************************************************
private function OrderExclusions takes unit u, integer id returns boolean
//* Excludes specific orders or unit types from registering with the system
//*
//* 851972: stop
//* Stop is excluded from the system, but you can change it by
//* adding a check for it below. id == 851972
//*
//* 851971: smart
//* 851986: move
//* 851983: attack
//* 851984: attackground
//* 851990: patrol
//* 851993: holdposition
//* These are the UI orders that are passed to the system.
//*
//* >= 852055, <= 852762
//* These are all spell IDs from defend to incineratearrowoff with
//* a bit of leeway at the ends for orders with no strings.
//*
return id == 851971 or id == 851986 or id == 851983 or id == 851984 or id == 851990 or id == 851993 or (id >= 852055 and id <= 852762)
endfunction
private function LastOrderFilter takes unit u returns boolean
//* Some criteria for whether or not a unit's last order should be given
//*
//* INSTANT type orders are excluded because generally, reissuing an instant
//* order doesn't make sense. You can remove that check below if you'd like,
//* though.
//*
//* The Type check is really just to ensure that no spell recursion can
//* occur with IssueLastOrder. The problem with intercepting the spell cast
//* event is that it happens after the order is 'caught' and registered to
//* this system. Therefore, to just IssueLastOrder tells it to recast the
//* spell! That's a problem, so we need a method to eliminate it.
//*
local integer id = GetUnitId(u)
return u != null and GetWidgetLife(u) > 0.405 and Type[id] != ORDER_TYPE_IMMEDIATE
endfunction
private function SecondLastOrderFilter takes unit u returns boolean
//* Same as above but with regard to the second last order issued
local integer id = GetUnitId(u)
return u != null and GetWidgetLife(u) > 0.405 and P_Type[id] != ORDER_TYPE_IMMEDIATE and P_Order[id] != Order[id]
endfunction
//**********************************************************
function IsLastOrderFinished takes unit u returns boolean
return (GetUnitCurrentOrder(u) == 0 and Order[GetUnitId(u)] != 851972) or Flag[GetUnitId(u)]
endfunction
function IssueLastOrder takes unit u returns boolean
local integer id = GetUnitId(u)
local boolean b = false
if LastOrderFilter(u) and Order[id] != 0 and not Flag[id] then
if Type[id] == ORDER_TYPE_TARGET then
set b = IssueTargetOrderById(u, Order[id], Targ[id])
elseif Type[id] == ORDER_TYPE_POINT then
set b = IssuePointOrderById(u, Order[id], X[id], Y[id])
elseif Type[id] == ORDER_TYPE_IMMEDIATE then
set b = IssueImmediateOrderById(u, Order[id])
endif
endif
return b
endfunction
function IssueSecondLastOrder takes unit u returns boolean
//* This function has to exist because of spell recursion
local integer id = GetUnitId(u)
local boolean b = false
if SecondLastOrderFilter(u) and P_Order[id] != 0 and not P_Flag[id] then
if P_Type[id] == ORDER_TYPE_TARGET then
set b = IssueTargetOrderById(u, P_Order[id], P_Targ[id])
elseif P_Type[id] == ORDER_TYPE_POINT then
set b = IssuePointOrderById(u, P_Order[id], P_X[id], P_Y[id])
elseif P_Type[id] == ORDER_TYPE_IMMEDIATE then
set b = IssueImmediateOrderById(u, P_Order[id])
endif
endif
return b
endfunction
function AbortOrder takes unit u returns boolean
local boolean b = true
if IsUnitPaused(u) then
set b = false
else
call PauseUnit(u, true)
call IssueImmediateOrder(u, "stop")
call PauseUnit(u, false)
endif
return b
endfunction
//**********************************************************
private function Conditions takes nothing returns boolean
return OrderExclusions(GetTriggerUnit(), GetIssuedOrderId())
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer id = GetUnitId(u)
//* Store second to last order to eliminate spell recursion
set P_Order[id] = Order[id]
set P_Targ[id] = Targ[id]
set P_Type[id] = Type[id]
set P_Flag[id] = Flag[id]
set P_X[id] = X[id]
set P_Y[id] = Y[id]
set Flag[id] = false
set Order[id] = GetIssuedOrderId()
if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
set Targ[id] = GetOrderTarget()
set Type[id] = ORDER_TYPE_TARGET
set X[id] = GetWidgetX(GetOrderTarget())
set Y[id] = GetWidgetY(GetOrderTarget())
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
set Targ[id] = null
set Type[id] = ORDER_TYPE_POINT
set X[id] = GetOrderPointX()
set Y[id] = GetOrderPointY()
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
set Targ[id] = null
set Type[id] = ORDER_TYPE_IMMEDIATE
set X[id] = GetUnitX(u)
set Y[id] = GetUnitY(u)
debug else
debug call BJDebugMsg(SCOPE_PREFIX+" Error: Order Doesn't Exist")
endif
set u = null
endfunction
//**********************************************************
private function SpellActions takes nothing returns nothing
set Flag[GetUnitId(GetTriggerUnit())] = true
endfunction
//**********************************************************
private function Init takes nothing returns nothing
local trigger trg = CreateTrigger()
call TriggerAddAction(OrderTrg, function Actions)
call TriggerAddCondition(OrderTrg, Condition(function Conditions))
call TriggerRegisterAnyUnitEventBJ(OrderTrg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterAnyUnitEventBJ(OrderTrg, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(OrderTrg, EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddAction(trg, function SpellActions)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
set trg = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library UnitIndexingUtils initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* -: BLUE FLAVOR :-
//*
//* This can be used to index units with a unique integer for use with arrays
//* and things like that. This has a limit of 8191 indexes allocated at once in
//* terms of actually being usable in arrays. It won't give you an error if you
//* exceed 8191, but that is an unrealistic limit anyways.
//*
//* The blue flavor uses a trigger that fires on death of a unit to release the
//* indexes of units. This is useful for maps where ressurection is not an issue
//* and doesn't require an O(n) search inside of a timer callback, making it
//* potentially less taxing on the game.
//*
//* To use, call GetUnitId on a unit to retrieve its unique integer id. This
//* library allocates a unique index to a unit the instant it is created, which
//* means you can call GetUnitId immediately after creating the unit with no
//* worry.
//*
//* Function Listing --
//* function GetUnitId takes unit u returns integer
//*
private struct unitindex
endstruct
//Function to get the unit's unique integer id, inlines to getting its userdata
function GetUnitId takes unit u returns integer
return GetUnitUserData(u)
endfunction
//Filter for units to index
private function UnitFilter takes nothing returns boolean
return true
endfunction
//Filter for what units to remove indexes for on death
private function Check takes nothing returns boolean
return not IsUnitType(GetTriggerUnit(), UNIT_TYPE_HERO)
endfunction
private function Clear takes nothing returns nothing
local unit u = GetTriggerUnit()
call unitindex(GetUnitId(u)).destroy()
call SetUnitUserData(u,-1)
set u = null
endfunction
private function Add takes nothing returns boolean
call SetUnitUserData(GetFilterUnit(),unitindex.create())
return true
endfunction
private function GroupAdd takes nothing returns nothing
call SetUnitUserData(GetEnumUnit(),unitindex.create())
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local group g = CreateGroup()
local integer i = 0
//Use a filterfunc so units are indexed immediately
call RegionAddRect(r, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(t, r, And(Condition(function UnitFilter), Condition(function Add)))
//Loop and group per player to grab all units, including those with locust
loop
exitwhen i > 15
call GroupEnumUnitsOfPlayer(g, Player(i), Condition(function UnitFilter))
call ForGroup(g, function GroupAdd)
set i = i + 1
endloop
//Set up the on-death trigger to clear custom values
set t = CreateTrigger()
call TriggerAddAction(t, function Clear)
call TriggerAddCondition(t, Condition(function Check))
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call DestroyGroup(g)
set r = null
set t = null
set g = null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AbortSpell needs LastOrder, SimError
//******************************************************************************
//* BY: Rising_Dusk
//*
//* The AbortSpell function in this library works just like the normal
//* SimError, except that it gives options for reissuing the unit's last order
//* and forcing a UI key for its owner to fully simulate a WC3 error message as
//* close as humanly possible.
//*
//* AbortSpell is valuable when used in the ISSUED_ORDER trigger event
//* callbacks. It is specifically designed to be used as a means for preventing
//* a unit from casting a spell and still continuing with whatever their last
//* order was. It also works on the SPELL_CAST event callback, but using it on
//* the order callbacks prevents the caster from turning/walking towards the
//* target point/unit/etc.
//*
//* Sample Function Usage:
//* call AbortSpell(MyUnit, "Error Message", "G")
//*
function AbortSpell takes unit u, string msg, string key returns boolean
if AbortOrder(u) then
call SimError(GetOwningPlayer(u), msg)
if GetLocalPlayer() == GetOwningPlayer(u) and StringLength(key) == 1 and key != " " then
call ForceUIKey(key)
endif
return IssueSecondLastOrder(u)
endif
return false
endfunction
endlibrary
//TESH.scrollpos=45
//TESH.alwaysfold=0
Version 1.0:
- Start of the spell, release to the forums in order to upgrade and search solutions
Version 1.1, 1.2, 1.3, 1.4:
- Testing and construction of the spell, fix of bugs and add of functionalities
- Public release as ready
Version 1.5
- Decided to join the community standards and so the spell now uses ADamage and Abuff
Version 1.6, 1.7:
- Improved many other aspects of the spell, including personalized error messages
among other new features
Version 1.8:
- Improved map to use AborSpell better
- Reduced the two error messages to only 1, thus simplifying the code and the SETUP
section
- Fixed a bug on the targets, now choosing targets work fine
- Corrected a few documentation typos
Version 1.9:
- Removed the alone version, it was buggy and inferior when compared to the other
- Improved the code, now the extra damage considers enemy armor when dealt
- Added w3gamer to credits
Version 2.0:
- Quick fix to a bug. Now when you cast the spell with chainlightning as an
order string it wont conflict
- Corrected a typo in the documentation and updated the JESP document
- Updated the requirements of the spell
Version 2.0.1:
- Removed the use of tags, after all, they are useless for this spell
- Improved the code in a line
- Updated the credits
- Changed the attack type to chaos
- Corrected the requirements of the spell
Version 2.0.2:
- Added another function to the SETUP that allows use to make a better use of the
spell
- Eliminated a useless variable ".lastDamage", this allowed me to greatly simplify
the if statements of some sections and allowed me to clean up the code a lot better
- Minos fixes in the change log
Version 2.1:
- Added another xedamage object that allows the use to calibrate the lastDamage
caused by the destruction of the shield in a different way
- Added new globals that allow the user to define the attachment points of the
effects on the units
- Fixed a leak problem
- Added another function that allows the to chose what he wants to do when the shield
dies
- Increased and corrected code documentation
Version 2.1.1:
- Fixed the capitalization conventions for the SETUP section
- Improved comments
Version 2.1.2:
- Fixed the description of the ability
- Fixed the description of the map
- Fixed the description of the loading screen
- Added more levels to the aura
- Fixed the tooltip of the buff, now it is not red, it is green
Version 2.1.3:
- Updated for patch 1.24.
//TESH.scrollpos=0
//TESH.alwaysfold=0
This is the JESP standard document, if a map contains this document it means that there are
spells that follow this standard.
Spells of this map that follow the standard:
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- "Light shield"
Advantages of the Standard
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- Implementing spells that follow the standard is relatively easier than implementing JASS
spells that don't follow the standard.
- Configuring/Balancing spells that follow the standard is relatively easier than
implementing JASS spells that don't follow the standard.
- Users may do the following procedure to make a new ability that uses the spell's script :
* Create a new Trigger with a name (case sensitive)
* Convert that trigger to custom text.
* Copy the spell's script to a text editor like Notepad or your OS equivalent.
* Replace the spell's Code name with the name you used on the trigger.
* Copy the new text to the new trigger
* Duplicate the Spell's original objects to have new ones for the new spell script.
You are now able to use that new version of the spell.
- In case two guys give the same name to 2 different spells, there are no conflict problems
because you can easily change the name of one of them
What is the JESP Standard?
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
The JESP standard was designed to make spell sharing much better. And to make sure JASS
enhanced spells follow a rule, to prevent chaos.
What does JESP Standard stands for?
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
JASS
Enhanced
Spell
Pseudotemplate
Requirements for a spell to follow the JESP Standard
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
- The spell is written in JASS
- The spell is 100% multi instanceable.
- The spell script is ready to support spells of any number of levels.
(default config header is not required to support all of them)
- The Spell has an specific code name.
- The Spell's trigger must have the spell's codename as name
- The Spell's InitTrig function must be named: InitTrig_<CodeName>
- The spell has a configuration header.
- It is mandatory that rawcodes of objects are configurable in the header.
- All the spell's specific code is inside the spell's "Trigger" (Trigger== that custom text
slot that world editor calls Trigger, the spell may use as many 'trigger' OBJECTS as needed)
- Every spell-specific single identifier or key works in such a way that reproducing the
spell's trigger but after performing a text-replace of codename with another name (and thus
renaming the cloned trigger to the new code name) it won't cause compile errors / conflicts
when playing the map.
- There is no code inside the spell's "Trigger" that is not specific to the spell.
- There are no requirements for GUI variables that are specific to the spell. If a system
used by the spell requires GUI variables the code for the system must be outside the "Trigger"
- Eyecandy and spell's balance have to be easy to configure
- The name of the author should be included in the spell's script.
- The reason to exist of this standard is spell sharing. This document should be included within the map. And it should specify which spell follows the standard, in the top list.
//TESH.scrollpos=0
//TESH.alwaysfold=0
//===========================================================================
//A JESP spell that allows the hero to cast a shield on a not Ud ally or on an
//Ud enemy. The shield will absorb an amount of damage for the allied unit, or will
//amplify it if the target is an undead enemy. In the end, if the shield is alive,
//it will heal or damage the target depending on its type.
//
//Requires:
// - TimerUtils
// - ABuff
// - ADamage
// - AbortSpell
// - xedamage
//
//@author Flame_Phoenix
//
//@credits
// - Vexorian, for TimerUtils, Table and SimError
// - Anitarf, for ABuff and ADamage
// - Rising_Dusk, for LastOrder and AbortSpell
// - Moyack, for the tip on how to prevent infinite loops
// - the_Immortal, the original creator of the spell gave me the idea for making my own
//
//@version 2.1.3
//===========================================================================
scope LightShield initializer Init
//===========================================================================
//=============================SETUP START===================================
//===========================================================================
globals
private constant integer AID = 'A000' //raw of the ability of the hero
private constant integer AURA_ID = 'A002' //raw of the aura with the buff
private constant integer AURA_BUFF = 'B000' //raw of the buff of the aura
private constant string HEAL_EFFECT = "Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl" //the effect that appears to heal allies or damage Ud
private constant string BLOCK_EFFECT = "Abilities\\Weapons\\FlyingMachine\\FlyingMachineImpact.mdl" //the effect that appears to block damage
private constant string AMPLIFY_EFFECT = "Objects\\Spawnmodels\\Orc\\Orcblood\\BattrollBlood.mdl" //the effect that appears when damage is amplified
private constant string HEAL_EFFECT_ATTACH = "chest" //the poisition of the effect
private constant string BLOCK_EFFECT_ATTACH = "chest" //the poisition of the effect
private constant string AMPLIFY_EFFECT_ATTACH = "chest" //the poisition of the effect
private constant integer SHIELD_PRIORITY = 0 //the priority of the damage prevention shield
private constant string ERR_MESSAGE = "\n|cffffcc00Must target friendly living units or enemy Undead units.|r" //error message that appears if target is not valid
private constant string HOTKEY = "L" //hotkey of the spell
private constant string ORDER = "chainlightning" //stringorder of the spell
endglobals
private constant function Duration takes integer level returns real
//the duration of the spell
return level * 15.
endfunction
private constant function ShieldAbsorb takes integer level returns real
//the damage the shield can absorb
return level * 100.
endfunction
private constant function BlockPercent takes integer level returns real
//the percentage of the damage that will be blocked
return .2 * level
endfunction
private constant function AmplifyPercent takes integer level returns real
//the percentage of extra damage that the undead unit will take
return .2 * level
endfunction
private function MakeDamage takes boolean triggered, integer tag returns boolean
//Is the damage recursive? You can also use this to make damage use tags. For
//more information see how xedamage works
return not(triggered)
endfunction
private function ShieldConds takes unit caster, unit targ returns boolean
//here I choose the targets that will not be allowed. In this case I don't allow Undead allies
//or enemies that are not Undead
return (IsUnitAlly(targ, GetOwningPlayer(caster)) and (IsUnitType(targ, UNIT_TYPE_UNDEAD)==true)) or (IsUnitEnemy(targ, GetOwningPlayer(caster)) and (IsUnitType(targ, UNIT_TYPE_UNDEAD) == false))
endfunction
private function ShieldDyingEffect takes integer level returns boolean
//if true, when the shield is refreshed the shielded unit will suffer the
//destroying effects of the old shield and get a new shield, if false, the
//old shield dying effects won't happen. You can make the ability depend on
//the level using an if statement.
return true
endfunction
private function SetupDamageOptions takes xedamage extraDamage, xedamage lastDamage returns nothing
//the damage and attack types
//the attack and damage types of the extra damage when the unit is undead
set extraDamage.dtype = DAMAGE_TYPE_UNIVERSAL
set extraDamage.atype = ATTACK_TYPE_CHAOS
//the attack and damage types that the shield causes to an Ud unit when it
//is destroyed
set lastDamage.dtype = DAMAGE_TYPE_MAGIC
set lastDamage.atype = ATTACK_TYPE_MAGIC
endfunction
//===========================================================================
//=============================SETUP END=====================================
//===========================================================================
globals
public aBuffType id = 0
private xedamage damageOptions
private xedamage lastDamage
endglobals
//===========================================================================
//damage shield struct
private struct lightShield extends ADamage_shield
real prevented //the prevented damage
real life //life of the shield
unit shielded //the unit with the shield
integer level
boolean isUndead
unit caster
//this allows me to know if the shield is dying due a natural proccess
//(running our of life) or if the shield is being refreshed
boolean onRefresh
static method create takes unit u, unit cast, integer level returns lightShield
local lightShield shieldData = lightShield.allocate(u, SHIELD_PRIORITY)
set shieldData.caster = cast
set shieldData.shielded = u
set shieldData.level = level
set shieldData.life = ShieldAbsorb(level)
set shieldData.isUndead = IsUnitType(u, UNIT_TYPE_UNDEAD)
set shieldData.onRefresh = false
return shieldData
endmethod
method damaged takes unit damageSource, real damage returns real
//here we see if the unit is ud or not.
//if Ud, we set prevented to 0, because we won't prevent anything
//instead we calculate the extra damage in the Damage function
//if not Ud it means it is an ally, and so we calculate the damage
//it will block
if .isUndead then
set .prevented = 0
else
set .prevented = damage * BlockPercent(.level)
endif
//If the damage would be enough to kill our shield, then we only block
//the damage that the shield can, and we let the rest pass.
if .prevented > .life then
set .prevented = .life
endif
set .life = .life - .prevented
return this.prevented
endmethod
method onDestroy takes nothing returns nothing
//here we heal the unit with the shield with the remaining
//energies of the shield, or damage it for the remaining energies
//of the shield if it is Ud
if (.life > 0) and (GetWidgetLife(.shielded) > 0.405) and not(.onRefresh) then
if .isUndead then
call lastDamage.damageTarget(.caster, .shielded, .life)
else
call SetWidgetLife(.shielded, GetWidgetLife(.shielded) + .life)
endif
call DestroyEffect(AddSpecialEffectTarget(HEAL_EFFECT, .shielded, HEAL_EFFECT_ATTACH))
endif
endmethod
endstruct
//this is an abstract concept "damage" that allows us to cause damage
//using ADamage and xedamage
private struct damageUnit
unit target
unit source
real damage
endstruct
//===========================================================================
private function Create takes aBuff eventBuff returns nothing
//Add the aura to the shielded unit
call UnitAddAbility(eventBuff.target.u, AURA_ID)
//the aura has the same levels as the hero spell!
call SetUnitAbilityLevel(eventBuff.target.u, AURA_ID, eventBuff.level)
//prevent morphing from removing the ability
call UnitMakeAbilityPermanent(eventBuff.target.u, true, AURA_ID)
//create the shield and attach it to the buff
set eventBuff.data = integer(lightShield.create(eventBuff.target.u, eventBuff.caster, eventBuff.level))
endfunction
//===========================================================================
private function Refresh takes aBuff eventBuff returns nothing
call SetUnitAbilityLevel(eventBuff.target.u, AURA_ID, eventBuff.level)
//create the shield and attach it to the buff
set eventBuff.data = lightShield.create(eventBuff.target.u, eventBuff.caster, eventBuff.level)
//by remaking the shield instead of keeping the old one we make sure that
//the shields follow the order defined by the NEW_SHIELD_ON_TOP boolean
//this of course only matters if the map has multiple shields with the same priority
//get the old shield from the buff and destroy it
if not(ShieldDyingEffect(lightShield(eventBuff.olddata).level)) then
set lightShield(eventBuff.olddata).onRefresh = true
endif
call lightShield(eventBuff.olddata).destroy()
endfunction
//===========================================================================
private function Cleanup takes aBuff eventBuff returns nothing
//remove the aura ability
call UnitRemoveAbility(eventBuff.target.u, AURA_ID)
//we also remove the buff, so we don't have to wait 2 seconds for it to disappear
call UnitRemoveAbility(eventBuff.target.u, AURA_BUFF)
//get the shield from the buff data and destroy it
call lightShield(eventBuff.data).destroy()
endfunction
//===========================================================================
private function DelayedDamage takes nothing returns nothing
local damageUnit d = damageUnit(GetTimerData(GetExpiredTimer()))
call ReleaseTimer(GetExpiredTimer())
call damageOptions.damageTarget(d.source, d.target, d.damage)
endfunction
//===========================================================================
private function Damaged takes aBuff eventBuff, real damage, unit damageSource returns nothing
local lightShield shieldData = lightShield(eventBuff.data)
local damageUnit d
local real vicLife = GetWidgetLife(shieldData.shielded)
local real bonus = damage * AmplifyPercent(shieldData.level)
local timer t
//if the shielded unit is undead, we damage it even more
//else we do nothing because ADamage will heal it
if shieldData.isUndead and MakeDamage(xedamage.isInUse(), xedamage.CurrentDamageTag) then//not(shieldData.lastDamage) and MakeDamage(xedamage.isInUse(), xedamage.CurrentDamageTag) then
set t = NewTimer()
set d = damageUnit.create()
set d.damage = bonus
set d.target = shieldData.shielded
set d.source = damageSource
call SetTimerData(t, integer(d))
call TimerStart(t, 0.0, false, function DelayedDamage)
//here we damage the shield for the bonus amount of damage caused
set shieldData.life = shieldData.life - bonus
call DestroyEffect(AddSpecialEffectTarget(AMPLIFY_EFFECT, shieldData.shielded, AMPLIFY_EFFECT_ATTACH))
set t = null
else
if shieldData.prevented > 0.0 then
call DestroyEffect(AddSpecialEffectTarget(BLOCK_EFFECT, eventBuff.target.u, BLOCK_EFFECT_ATTACH))
endif
endif
//if the shield has no life, we destroy it
if shieldData.life <= 0. then
call ABuffDestroy(eventBuff)
endif
endfunction
//===========================================================================
private function Conditions takes nothing returns boolean
return GetSpellAbilityId() == AID
endfunction
//===========================================================================
private function Actions takes nothing returns nothing
local unit caster = GetTriggerUnit()
local integer level = GetUnitAbilityLevel(caster, AID)
call ABuffApply(id, GetSpellTargetUnit(), caster, Duration(level), level, 0)
set caster = null
endfunction
//===========================================================================
private function B4_Conditions takes nothing returns boolean
return OrderId2String(GetIssuedOrderId()) == ORDER and (GetUnitAbilityLevel(GetOrderedUnit(), AID) > 0)
endfunction
//===========================================================================
private function B4_Actions takes nothing returns nothing
local unit caster = GetOrderedUnit()
local unit targ = GetOrderTargetUnit()
if ShieldConds(caster, targ) then
call AbortSpell(caster, ERR_MESSAGE, HOTKEY)
endif
set caster = null
set targ = null
endfunction
//===========================================================================
private function Init takes nothing returns nothing
//when the hero casts the spell
local trigger LightShieldTrg = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ( LightShieldTrg, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddCondition( LightShieldTrg, Condition( function Conditions ) )
call TriggerAddAction(LightShieldTrg, function Actions)
//in case the targets are wrong
set LightShieldTrg = CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ(LightShieldTrg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER )
call TriggerAddCondition(LightShieldTrg, Condition(function B4_Conditions ))
call TriggerAddAction(LightShieldTrg, function B4_Actions )
//Setting ABuff
set id = aBuffType.create()
set id.eventCreate = ABuffEvent_Create.Create
set id.eventRefresh = ABuffEvent_Refresh.Refresh
set id.eventCleanup = ABuffEvent_Cleanup.Cleanup
set id.eventDamaged = ABuffEvent_BUnitDamaged.Damaged
// Initializing the damage options:
set damageOptions = xedamage.create() // first instanciate a xeobject.
set lastDamage = xedamage.create() //this allows us to calibrate differently the damage the shield causes when it dies
call SetupDamageOptions(damageOptions, lastDamage) // now we configurate the damage objects we created
//Preloading our effects
call Preload(HEAL_EFFECT)
call Preload(BLOCK_EFFECT)
call Preload(AMPLIFY_EFFECT)
endfunction
endscope