//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library MapInit initializer Init
private function ShowHelp takes nothing returns nothing
call BJDebugMsg("|cffff0000To see how 'Deadly Seduction' and 'Dark Curse' work cast it on your own units!!!|r")
call BJDebugMsg("|cffff0000Hints: Try to combine the 'Polarisation' and 'Magnetic Field'|r")
call BJDebugMsg("|cffff0000If you cast 'Dark Curse' and 'Deadly Seduction' on the same unit one of the buffs and effects will be removed!!|r")
call BJDebugMsg("|cffff0000Now have fun with the spells.|r")
call BJDebugMsg("|cffff0000Press Esc to reset the Dread Lords and the Electricians Health,Mana and Cooldowns!|r")
call BJDebugMsg(" ")
call BJDebugMsg("~Inferior")
endfunction
private function Init takes nothing returns nothing
local integer i = 0
loop
call FogModifierStart(CreateFogModifierRect(Player(i),FOG_OF_WAR_VISIBLE,bj_mapInitialPlayableArea,false,false))
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TimerStart(CreateTimer(),3,false,function ShowHelp)
endfunction
endlibrary
//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=35
//TESH.alwaysfold=0
library TimedHandles requires TimerUtils
//***************************************************************************
//*
//* TimedHandles - By TriggerHappy187
//*
//***************************************************************************
//*
//* Installation
//* Simply copy this script into your map, as well as TimerUtils.
//*
//***************************************************************************
//*
//* Documentation
//*
//* All this script does it associates a handle to a
//* timer and then destroys the handle upon the timer's expiration.
//*
//***************************************************************************
//*
//* The Public Functions
//*
//* $DESTROY$Timed - Starts the handle and it's timer, once it expires
//* the handle will be destroyed.
//*
//***************************************************************************
//*
//* Examples
//*
//* call DestroyEffectTimed(AddSpecialEffect("MODELNAME", X, X), 2)
//* call DestroyEffectTimed(AddSpecialEffectTarget("MODELNAME", unit, "attachment-point"), 2)
//*
//***************************************************************************
//! textmacro TIMEDHANDLES takes HANDLE,DESTROY
struct $HANDLE$timed
$HANDLE$ $HANDLE$_var
private static method remove takes nothing returns nothing
local timer t = GetExpiredTimer()
local $HANDLE$timed d = GetTimerData(t)
call $DESTROY$(d.$HANDLE$_var)
call ReleaseTimer(t)
call d.destroy()
endmethod
static method create takes $HANDLE$ h, real timeout returns $HANDLE$timed
local $HANDLE$timed t = $HANDLE$timed.allocate()
local timer ti = NewTimer()
set t.$HANDLE$_var = h
call SetTimerData(ti, t)
call TimerStart(ti, timeout, false, function $HANDLE$timed.remove)
return t
endmethod
endstruct
function $DESTROY$Timed takes $HANDLE$ h, real duration returns nothing
call $HANDLE$timed.create(h, duration)
endfunction
//! endtextmacro
//! runtextmacro TIMEDHANDLES("effect","DestroyEffect")
//! runtextmacro TIMEDHANDLES("lightning","DestroyLightning")
//! runtextmacro TIMEDHANDLES("weathereffect","RemoveWeatherEffect")
//! runtextmacro TIMEDHANDLES("item","RemoveItem")
//! runtextmacro TIMEDHANDLES("ubersplat","DestroyUbersplat")
endlibrary
//TESH.scrollpos=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=46
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
library Logarithm
globals
private constant integer ITERATIONS=20
endglobals
function Log takes real x returns real
local real min=-88.0
local real max= 88.0
local real mid
local integer i=ITERATIONS
loop
set mid=(min+max)/2
exitwhen(i<=0)
set i=i-1
if (Pow(bj_E,mid)>=x) then
set max=mid
else
set min=mid
endif
endloop
return mid
endfunction
function Logarithm takes real base, real x returns real
local real min=-88.0
local real max= 88.0
local real mid
local integer i=ITERATIONS
loop
set mid=(min+max)/2
exitwhen(i<=0)
set i=i-1
if (Pow(base,mid)>=x) then
set max=mid
else
set min=mid
endif
endloop
return mid
endfunction
endlibrary
library Rounding
//*************************************************************
//* Rounding by Inferior
//*
//* The purpose of this library is to correct the Rounding bug.
//* Normally if we have e.g. 1.5 and we round it we should get 2,
//* but Blizzard is rounding down to 1. Even if we have 1.9999....
//* it will be rounded down to 1.
//* If you want a value to be rounded correctly use the following:
//*
//* function RealRounding takes real r returns nothing
//*
//***************************************************************
// returns the Absolute value of a real
function AbsReal takes real a returns real
return SquareRoot(a*a)
endfunction
// returns the Absolute value of an integer
function AbsInt takes integer i returns integer
return R2I(SquareRoot(i*i))
endfunction
// stupid blizzard >.<
function RealRounding takes real r returns integer
return R2I(r+0.5)
endfunction
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
//=========================================================================================
globals
private constant boolean ALLOW_OUTSIDE_PLAYABLE_MAP_AREA = false
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns boolean
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
return false
endfunction
private function init takes nothing returns nothing
local trigger t = CreateTrigger()
local region r = CreateRegion()
local rect map
local rect rc
if ALLOW_OUTSIDE_PLAYABLE_MAP_AREA then
set map = GetWorldBounds()
else
set map = bj_mapInitialPlayableArea
endif
set minx = GetRectMinX(map)
set miny = GetRectMinY(map)
set maxx = GetRectMaxX(map)
set maxy = GetRectMaxY(map)
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
if ALLOW_OUTSIDE_PLAYABLE_MAP_AREA then
call RemoveRect(map)
endif
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddCondition(t, Condition(function dis))
set rc=null
set map = null
endfunction
endlibrary
//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=184
//TESH.alwaysfold=0
library UnitStatus initializer Init requires TimerUtils, AutoIndex, optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library exists because oftentimes, a mapmaker needs to apply a specific
//* status effect to a unit. If he were to do it on his own, he'd need to find a
//* way to get the status effects to stack properly with one another and with
//* multiple instances of themselves. This script does just that with the five
//* most useful status options in WC3 that cannot be reproduced perfectly with
//* just code and not actual in-game buffs.
//*
//* WARNING: This library uses the following buffs. If you use any of the listed
//* buffs in your map, they will not stack with this script's buffs and
//* it may not work. If you need these effects, then only use this
//* script to achieve them.
//* - Drunken Haze
//* - Soul Burn
//* - Ensnare
//* - Storm Bolt
//* - Hurl Boulder
//*
//******************************************************************************
//*
//* The following status effects are supported by this library.
//*
//* - Silence
//* This status disables a given unit's ability to cast spells. This works
//* properly on spell immune and normal units.
//*
//* Example Usage:
//* call SilenceUnit(u, true) //Silences a unit
//* call SilenceUnit(u, false) //Removes silence from a unit
//* call SilenceUnitTimed(u, 4.0) //Adds a timed silence to a unit
//*
//* - Disarm
//* This status disables a given unit's ability to attack and attack ground.
//* This will not work properly on spell immune units because of a Blizzard
//* bug. The function calls will return false when used on units that are
//* spell immune. This status disables both ranged and melee attacks.
//*
//* Example Usage:
//* call DisarmUnit(u, true) //Disarms a unit
//* call DisarmUnit(u, false) //Removes disarm from a unit
//* call DisarmUnitTimed(u, 4.0) //Adds a timed disarm to a unit
//*
//* - Ensnare
//* This status disables a given unit's ability to move. This works properly
//* on spell immune and normal units. Because of the nature of the ability
//* this is based upon, it will exhibit strange behavior on flying units. If
//* this is used on flying units, they will retain their flying height, but
//* be treated as ground units by the game. There is unfortunately no
//* workaround for this behavior. It is recommended to not use it on flying
//* units because of that behavior.
//*
//* Example Usage:
//* call EnsnareUnit(u, true) //Ensnares a unit
//* call EnsnareUnit(u, false) //Removes ensnare from a unit
//* call EnsnareUnitTimed(u, 4.0) //Adds a timed ensnare to a unit
//*
//* - Stun
//* This status is identical in nature to the standard melee stun. A stunned
//* unit cannot move, attack, or cast spells. Both spell immune and normal
//* units can be stunned.
//*
//* Example Usage:
//* call StunUnit(u, true) //Ensnares a unit
//* call StunUnit(u, false) //Removes ensnare from a unit
//* call StunUnitTimed(u, 4.0) //Adds a timed ensnare to a unit
//*
//* - Disable
//* Disable is a unique status meant to be used as a replacement for pausing a
//* unit using the PauseUnit native. Pausing a unit has the negative side
//* effects of removing the unit's command card and not preserving queued
//* orders. Disabling a unit retains both of those sought features.
//*
//* Disable also interacts with stun in a unique way.
//* - If stun is used on a disabled unit, the unit becomes stunned instead.
//* - If stun ends on a disabled unit, the unit is disabled until the disable
//* ends.
//* - If disable is used on a stunned unit, the unit remains visibly stunned.
//*
//* Example Usage:
//* call DisableUnit(u, true) //Disables a unit
//* call DisableUnit(u, false) //Removes disable from a unit
//*
//* WARNING: These status effects, when used on invulnerable units, will have
//* absolutely no effect.
//*
//******************************************************************************
//*
//* There are ObjectMerger calls below that you should enable to create all of
//* the necessary abilities in your map. Enable them by uncommenting them,
//* saving your map, closing your map, reopening your map, and commenting the
//* lines again.
//*
//* WARNING: The ObjectMerger call for Disarm's ability seems to be unable to
//* properly configure the "Data - Attacks Prevented" field. If you
//* find that the DisarmUnit call isn't functioning as intended, then
//* set that field to "None", save your map, set the field back to
//* "Melee, Ranged", and then save your map again. It should now work
//* properly.
//*
//* You may change the raw ids of any of the generated abilities as needed for
//* your map. (If there are conflicts) If you want to do so, then make sure that
//* you change the raw ids inside all affected ObjectMerger calls and the
//* constants in the globals block below.
//*
//* WARNING: If you choose to change the raw id for the Silence ability, do NOT
//* let the buff raw id match the ability raw id. If you do, the buff's
//* special effect fields will not show in-game. (This may or may not
//* even affect you, but it is worth noting regardless)
//*
//* xebasic is an optional requirement. If you have xebasic in your map, this
//* script will use xebasic's dummy unit id instead of the constant below. If
//* you do not have xebasic in your map, for this to work you will need to make
//* (if you have not already) a dummy unit caster for your map. Put it's raw id
//* below in the DUMMY_UNITID constant field.
//*
//* Enjoy!
//*
//* The ObjectMerger calls below are for the DUMMY ABILITIES used by the script
//******************************************************************************
////! external ObjectMerger w3a AHtb stun anam "Stun Ability" aani "" aart "" amat "" amsp 0 Htb1 1 0.0 aran 1 99999.0 acdn 1 0.0 ahdu 1 0.0 adur 1 0.0 arlv 6 alev 1 amcs 1 0 arac "other" atar 1 "notself"
////! external ObjectMerger w3a ANso &sil anam "Silence Ability" aart "" Nso1 1 0.0 Nso3 1 0.0 Nso2 1 99999.0 aran 1 99999.0 abuf 1 "&SIL" acdn 1 0.0 ahdu 1 0.0 adur 1 0.0 arlv 6 alev 1 amcs 1 0 arac "other" atar 1 "notself"
////! external ObjectMerger w3a ANdh &arm anam "Disarm Ability" aart "" amat "" amac 0.0 amsp 0 Nsi1 1 3 Nsi2 1 0.0 Nsi3 1 0.0 aare 1 0.0 aran 1 99999.0 abuf 1 "&ARM" acdn 1 0.0 ahdu 1 0.0 adur 1 0.0 arlv 6 alev 1 amcs 1 0 arac "other" atar 1 "notself"
////! external ObjectMerger w3a ACen &ens anam "Ensnare Ability" aart "" amat "" amsp 0 Ens1 1 -1.0 Ens2 1 -1.0 aran 1 99999.0 abuf 1 "&EN1,&EN2" acdn 1 0.0 ahdu 1 0.0 aher 1 adur 1 0.0 arlv 6 alev 1 amcs 1 0 arac "other" atar 1 "notself" areq "" ansf ""
////! external ObjectMerger w3a ACtb &dis anam "Disable Ability" aani "" aart "" amat "" amsp 0 Ctb1 1 0.0 aran 1 99999.0 abuf 1 "&DIS" acdn 1 0.0 ahdu 1 0.0 adur 1 0.0 aher 1 arlv 6 alev 1 amcs 1 0 arac "other" atar 1 "notself"
//******************************************************************************
//* The ObjectMerger calls below are for the DUMMY BUFFS used by the script
//******************************************************************************
////! external ObjectMerger w3h BNso &SIL fnam "Disabled (Spells)" ftip "Disabled (Spells)" fube "This unit cannot cast spells." fart "ReplaceableTextures\CommandButtons\BTNCancel.blp" ftat "" fta0 ""
////! external ObjectMerger w3h BNdh &ARM fnam "Disabled (Attacks)" ftip "Disabled (Attacks)" fube "This unit cannot attack." fart "ReplaceableTextures\CommandButtons\BTNCancel.blp" ftat "" fta0 ""
////! external ObjectMerger w3h Beng &EN1 fnam "Disabled (Movement)" ftip "Disabled (Movement)" fube "This unit cannot move." fart "ReplaceableTextures\CommandButtons\BTNCancel.blp" ftat "" frac "other"
////! external ObjectMerger w3h Bena &EN2 fnam "Disabled (Movement)" ftip "Disabled (Movement)" fube "This unit cannot move." fart "ReplaceableTextures\CommandButtons\BTNCancel.blp" ftat "" fta0 "" frac "other"
////! external ObjectMerger w3h BPSE &DIS fnam "Disabled" ftip "Disabled" fube "This unit cannot do anything." fart "ReplaceableTextures\CommandButtons\BTNCancel.blp" ftat "" fta0 ""
//******************************************************************************
globals
//General constants
private constant integer DUMMY_UNITID = 'e000' //Replace this with your dummy's id
//Stun constants
private constant integer STUN_ID = 'stun' //This needs to match the ObjectMerger call above
private constant integer STUN_ORDER_ID = 852095 //Order id to stun a unit
private constant integer STUN_BUFF_ID = 'BPSE' //Normal storm bolt stun buff
//Silence constants
private constant integer SILENCE_ID = '&sil' //This needs to match the ObjectMerger call above
private constant integer SILENCE_ORDER_ID = 852668 //Order id to soul burn a unit
private constant integer SILENCE_BUFF_ID = '&SIL' //Generated soul burn based buff
//Disarm constants
private constant integer DISARM_ID = '&arm' //This needs to match the ObjectMerger call above
private constant integer DISARM_ORDER_ID = 852585 //Order id to drunken haze a unit
private constant integer DISARM_BUFF_ID = '&ARM' //Generated drunken haze based buff
//Ensnare constants
private constant integer ENSNARE_ID = '&ens' //This needs to match the ObjectMerger call above
private constant integer ENSNARE_ORDER_ID = 852106 //Order id to ensnare a unit
private constant integer ENSNARE_BUFF_ID = '&EN1' //Generated ensnare based buff (ground)
private constant integer ENSNARE_BUFF_ID2 = '&EN2' //Generated ensnare based buff (air)
//Disable constants
private constant integer DISABLE_ID = '&dis' //This needs to match the ObjectMerger call above
private constant integer DISABLE_ORDER_ID = 852252 //Order id to hurl boulder at a unit
private constant integer DISABLE_BUFF_ID = '&DIS' //Generated hurl boulder based buff
endglobals
globals
private unit Caster = null //Dummy caster
endglobals
//! textmacro UnitStatus_GenerateBase takes TYPE, CONSTANT, INJECT
globals
private integer array $TYPE$Counter //Ref counter for this type
endglobals
function $TYPE$Unit takes unit whichUnit, boolean flag returns boolean
local integer id = GetUnitId(whichUnit)
local boolean b = true
if whichUnit == null then
//Target can't be null
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null unit given to $TYPE$Unit")
return false
endif
if flag then
if $TYPE$Counter[id] == 0 then
//Buff the unit
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), true)
set b = IssueTargetOrderById(Caster, $CONSTANT$_ORDER_ID, whichUnit)
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), false)
endif
if not b then
//Cast failed somehow
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Unit could not be buffed ($TYPE$Unit)")
return false
endif
set $TYPE$Counter[id] = $TYPE$Counter[id] + 1
elseif $TYPE$Counter[id] > 0 then //Only run this if unit is buffed at all
//Decrement Counter
set $TYPE$Counter[id] = $TYPE$Counter[id] - 1
if $TYPE$Counter[id] == 0 then
//Clear the buff
call UnitRemoveAbility(whichUnit, $CONSTANT$_BUFF_ID)
$INJECT$
endif
endif
return true
endfunction
//! endtextmacro
//! textmacro UnitStatus_GenerateTime takes TYPE, STRUCTNAME
private struct $STRUCTNAME$
timer t
unit tar
real dur
private static method end takes nothing returns nothing
call thistype(GetTimerData(GetExpiredTimer())).destroy()
endmethod
static method start takes unit target, real duration returns boolean
local thistype s
local boolean b = $TYPE$Unit(target, true)
if not b then
//Failed, return false
return false
endif
set s = thistype.allocate()
set s.tar = target
set s.dur = duration
set s.t = NewTimer()
call SetTimerData(s.t, integer(s))
call TimerStart(s.t, duration, false, function $STRUCTNAME$.end)
return true
endmethod
private method onDestroy takes nothing returns nothing
call $TYPE$Unit(.tar, false)
call ReleaseTimer(.t)
endmethod
endstruct
function $TYPE$UnitTimed takes unit whichUnit, real duration returns boolean
return $STRUCTNAME$.start(whichUnit, duration)
endfunction
//! endtextmacro
//Disable and stun (non-timed) are special cases and can't use the textmacro
globals
private integer array StunCounter //Ref counter for this type
private integer array DisableCounter //Ref counter for this type
endglobals
function DisableUnit takes unit whichUnit, boolean flag returns boolean
local integer id = GetUnitId(whichUnit)
local boolean b = true
if whichUnit == null then
//Target can't be null
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null unit given to $TYPE$Unit")
return false
endif
if flag then
if DisableCounter[id] == 0 and StunCounter[id] == 0 then
//Buff the unit
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), true)
set b = IssueTargetOrderById(Caster, DISABLE_ORDER_ID, whichUnit)
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), false)
endif
if not b then
//Cast failed somehow
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Unit could not be buffed ($TYPE$Unit)")
return false
endif
set DisableCounter[id] = DisableCounter[id] + 1
elseif DisableCounter[id] > 0 then //Only run this if unit is buffed at all
//Decrement Counter
set DisableCounter[id] = DisableCounter[id] - 1
if DisableCounter[id] == 0 and StunCounter[id] == 0 then
//Clear the buff
call UnitRemoveAbility(whichUnit, DISABLE_BUFF_ID)
endif
endif
return true
endfunction
function StunUnit takes unit whichUnit, boolean flag returns boolean
local integer id = GetUnitId(whichUnit)
local boolean b = true
if whichUnit == null then
//Target can't be null
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null unit given to $TYPE$Unit")
return false
endif
if flag then
if StunCounter[id] == 0 then
//Buff the unit
if DisableCounter[id] > 0 then
//Remove the disable buff for first refcount stun
call UnitRemoveAbility(whichUnit, DISABLE_BUFF_ID)
endif
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), true)
set b = IssueTargetOrderById(Caster, STUN_ORDER_ID, whichUnit)
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), false)
endif
if not b then
//Cast failed somehow
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Unit could not be buffed ($TYPE$Unit)")
return false
endif
set StunCounter[id] = StunCounter[id] + 1
elseif StunCounter[id] > 0 then //Only run this if unit is buffed at all
//Decrement Counter
set StunCounter[id] = StunCounter[id] - 1
if StunCounter[id] == 0 then
//Clear the buff
call UnitRemoveAbility(whichUnit, STUN_BUFF_ID)
if DisableCounter[id] > 0 then
//Add the disabled buff because it lingers on
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), true)
call IssueTargetOrderById(Caster, DISABLE_ORDER_ID, whichUnit)
call UnitShareVision(whichUnit, GetOwningPlayer(Caster), false)
endif
endif
endif
return true
endfunction
//Generates all of the base code
//! runtextmacro UnitStatus_GenerateBase("Silence", "SILENCE", "")
//! runtextmacro UnitStatus_GenerateBase("Disarm" , "DISARM" , "")
//! runtextmacro UnitStatus_GenerateBase("Ensnare", "ENSNARE", "call UnitRemoveAbility(whichUnit, ENSNARE_BUFF_ID2)")
//Generates all of the timed function code
//! runtextmacro UnitStatus_GenerateTime("Stun" , "stun" )
//! runtextmacro UnitStatus_GenerateTime("Silence", "silence")
//! runtextmacro UnitStatus_GenerateTime("Disarm" , "disarm" )
//! runtextmacro UnitStatus_GenerateTime("Ensnare", "ensnare")
private function Init takes nothing returns nothing
static if LIBRARY_xebasic then
set Caster = CreateUnit(Player(15), XE_DUMMY_UNITID, 0., 0., 0.)
else
set Caster = CreateUnit(Player(15), DUMMY_UNITID , 0., 0., 0.)
endif
call UnitRemoveAbility(Caster, 'Amov')
if GetUnitAbilityLevel(Caster, 'Aloc') == 0 then
call UnitAddAbility(Caster, 'Aloc') //xe dummies don't have this automatically
endif
//Add the abilities
call UnitAddAbility(Caster, STUN_ID)
call UnitAddAbility(Caster, SILENCE_ID)
call UnitAddAbility(Caster, DISARM_ID)
call UnitAddAbility(Caster, ENSNARE_ID)
call UnitAddAbility(Caster, DISABLE_ID)
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library UnitMaxState initializer Initialize
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ UnitMaxState Readme
//@=============================================================================
//@ Credits:
//@-----------------------------------------------------------------------------
//@ Written By:
//@ Earth-Fury
//@
//@ Original System By:
//@ Blade.dk
//@
//@ Intermittent Version By:
//@ Deaod
//@
//@ With Thanks To:
//@ - weaaddar for BonusMod and thus inspiration
//@ - PitzerMike for the ObjectMerger
//@ - Vexorian for vJass and JASSHelper
//@ - PipeDream for Grimoire
//@ - SFilip for TESH
//@ - MindWorX for maintaining NewGen
//@-----------------------------------------------------------------------------
//@ If you use this system in your map, please at least give credit to Blade.dk.
//@ Without him, this library would not exist.
//@=============================================================================
//@ Requirements:
//@-----------------------------------------------------------------------------
//@ This library is written in vJass and thus requires JASSHelper in order to
//@ function correctly. The included ObjectMerger macros require both JASSHelper
//@ and PitzerMike's ObjectMerger. All of these things are included in the
//@ NewGen world editor.
//@=============================================================================
//@ Adding UnitMaxState to your map:
//@-----------------------------------------------------------------------------
//@ Copy this library in to a custom text trigger to add it to your map.
//@
//@ You must also create a large number of abilities in order for this library
//@ to work. The simplest way to do this is to use the ObjectMerger macros which
//@ are distributed alongside this system. See their documentation for
//@ instructions on how to use them to generate the needed abilities.
//@
//@ If your map contains abilities with rawcodes that begin with 'Zx', you have
//@ to change the rawcode prefixes of the abilities. This needs to be done both
//@ in the configuration section of this library, and in the included
//@ ObjectMerger macros. See below the Configuration section for changing the
//@ values in the library, and see the ObjectMerger macros' readme for changing
//@ the values for the abilities generated by the macros.
//@
//@ See the configuration section below for configuration options.
//@=============================================================================
//@ Using UnitMaxState:
//@-----------------------------------------------------------------------------
//@ nothing SetUnitMaxState(unit <target>, unitstate <state>, real <value>)
//@
//@ This function changes <target>'s unitstate <state> to be eqal to <value>.
//@-----------------------------------------------------------------------------
//@ nothing AddUnitMaxState(unit <target>, unitstate <state>, real <value>)
//@
//@ This function adds <value> to <target>'s unitstate <state>. Note that
//@ <value> can be less than 0.
//@-----------------------------------------------------------------------------
//@ Note that these functions accept unitstates other than UNIT_STATE_MAX_LIFE,
//@ and UNIT_STATE_MAX_MANA. There is, however, a minor performance penalty
//@ for unit states other than the named two, compared to SetUnitState().
//@
//@ Also note that attempting to set a unit's max life below 1, or a unit's max
//@ mana below 0 will do nothing.
//@=============================================================================
//@ Changing the precision and number of abilities of the library
//@-----------------------------------------------------------------------------
//@ You never _have_ to change anything for this library to function correctly.
//@ If you _want_ to change the number of abilities this library uses, or if
//@ you want to learn more about how this system works, read on.
//@
//@ Note that changing the number of abilities this library uses only has two
//@ possible uses: To speed it up for adding insanely high bonuses, or to
//@ slow it down for higher bonuses, while reducing the number of abilities
//@ that it uses.
//@
//@ To add to or remove from the number of abilities, you must create or remove
//@ the properly conforming abilities, and modify the constants below. You can
//@ use the ObjectMerger to generate the abilities for you by adding or removing
//@ lines from the ObjectMerger macros which came with this library. See the
//@ macros' documentation for more help with this.
//@-----------------------------------------------------------------------------
//@ Description of conforming abilities:
//@-----------------------------------------------------------------------------
//@ This library abuses a bug in Warcraft 3. That bug causes certain abilities
//@ to only ever add the bonus life/mana for the first level of the ability.
//@ However, when the ability is removed, the amount of life/mana that should
//@ have been added is taken away, no matter the level.
//@
//@ This library uses abilities of that vein which have 4 levels, with level 1
//@ always containing a bonus of 0. The reason this library does not simply use
//@ two abilities with more than 4 levels is that the Widgitizer tool can not
//@ convert abilities which have more than 4 levels.
//@
//@ The way a bonus is added or removed is via powers of 2. You can create any
//@ number, by simply adding together powers of 2. Eg:
//@ 25 = 2^4 + 2^3 + 2^0
//@ 25 = 16 + 8 + 1
//@
//@ The abilities this system uses are ordered. The lowest-ordered ability
//@ contains the lowest bonuses. The lowest level of each ability contains the
//@ lowest bonus for that ability, with the highest level containing the highest
//@ bonus. The value of the bonus each ability grants at each level are
//@ sequential powers of 2.
//@
//@ The rawcodes of the abilities this system uses matter. The first ability of
//@ a type (Life/Mana Addition/Subtraction) has a defined rawcode. The abilities
//@ that follow it have incrementing rawcodes starting from the first. Eg:
//@ Zxl0 has the bonuses: 0, 1, 2, 4
//@ Zxl1 has the bonuses: 0, 8, 16, 32
//@ Zxl2 has the bonuses: 0, 64, 128, 256
//@
//@ Note that abilities which add a bonus must have a negitive bonus declared
//@ in the ability data, while the inverse is true for abilities which subtract
//@ a bonus. This is due to the nature of the bug that is being abused.
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
globals
//==============================================================================
// Configuration:
//==============================================================================
//------------------------------------------------------------------------------
// The following constants declare the rawcode prefix of each of the four
// needed ability types. If you change these constants, you must also change
// the rawcodes of the abilities this system uses. This can be done by
// changing the included ObjectMerger macros. See their documentation for more
// help with that.
//
// Note that the ObjectMerger lines take a 3-letter prefix. These constants
// must be set to '<prefix>0'.
//------------------------------------------------------------------------------
private constant integer RAWCODE_LIFE_ADD_START = 'ZxL0'
private constant integer RAWCODE_LIFE_SUB_START = 'Zxl0'
private constant integer RAWCODE_MANA_ADD_START = 'ZxM0'
private constant integer RAWCODE_MANA_SUB_START = 'Zxm0'
//------------------------------------------------------------------------------
// The following constant declares the number of abilities there are in each
// group of abilities. You should never have to modify this.
//
// See the "Changing the precision and number of abilities of the library"
// section of the readme for more information.
//------------------------------------------------------------------------------
public constant integer ABILITY_COUNT = 6
//------------------------------------------------------------------------------
// If set to false, the system's abilities will not be preloaded.
// Not preloading abilities reduces loading time, but causes lag during the game
// as abilities are loaded on the fly.
//------------------------------------------------------------------------------
private constant boolean PRELOAD_ABILITIES = true
//------------------------------------------------------------------------------
// The rawcode of the unit to use for preloading abilities.
// This can be any unit, as it should be removed before the loading screen is
// gone.
//------------------------------------------------------------------------------
private constant integer PRELOAD_UNIT_ID = 'hfoo'
//------------------------------------------------------------------------------
// The player that will own the unit used for preloading abilities.
//------------------------------------------------------------------------------
private constant integer PRELOAD_PLAYER_ID = 15
//------------------------------------------------------------------------------
// The location to place the unit used for preloading abilities. This can be
// any pathable location on the map.
//------------------------------------------------------------------------------
private constant real PRELOAD_UNIT_X = 0.0
private constant real PRELOAD_UNIT_Y = 0.0
//------------------------------------------------------------------------------
// End of configuration
//------------------------------------------------------------------------------
endglobals
globals
public integer array ABILITIES
private integer array POWERS_OF_2
endglobals
private function ErrorMsg takes string s returns nothing
debug call BJDebugMsg("SetUnitMaxState: " + s)
endfunction
function SetUnitMaxState takes unit target, unitstate state, real targetValue returns nothing
local integer difference
local integer offset
local integer abilityId
local integer abilityLevel
local integer currentAbility
if state == UNIT_STATE_MAX_LIFE then
set offset = 0
if targetValue < 1 then
call ErrorMsg("You can not set a unit's max life to below 1")
return
endif
elseif state == UNIT_STATE_MAX_MANA then
set offset = ABILITY_COUNT * 2
if targetValue < 0 then
call ErrorMsg("You can not set a unit's max mana to below 0")
return
endif
else
call SetUnitState(target, state, targetValue)
return
endif
set difference = R2I(targetValue) - R2I(GetUnitState(target, state))
if difference < 0 then
set offset = offset + ABILITY_COUNT
set difference = -difference
endif
set abilityId = ABILITY_COUNT - 1
set abilityLevel = 4
set currentAbility = ABILITIES[offset + abilityId]
loop
exitwhen difference == 0
if difference >= POWERS_OF_2[abilityId * 3 + (abilityLevel - 2)] then
call UnitAddAbility(target, currentAbility)
call SetUnitAbilityLevel(target, currentAbility, abilityLevel)
call UnitRemoveAbility(target, currentAbility)
set difference = difference - POWERS_OF_2[abilityId * 3 + (abilityLevel - 2)]
else
set abilityLevel = abilityLevel - 1
if abilityLevel <= 1 then
set abilityId = abilityId - 1
set abilityLevel = 4
set currentAbility = ABILITIES[offset + abilityId]
endif
endif
endloop
endfunction
function AddUnitMaxState takes unit target, unitstate state, real additionalValue returns nothing
call SetUnitMaxState(target, state, GetUnitState(target, state) + additionalValue)
endfunction
private function Initialize takes nothing returns nothing
local integer i
local integer k
local integer array prefixes
local unit preloadUnit
set i = 1
set POWERS_OF_2[0] = 1
loop
exitwhen i == ABILITY_COUNT * 2 * 2 * 3 + 1
set POWERS_OF_2[i] = POWERS_OF_2[i - 1] * 2
set i = i + 1
endloop
set prefixes[0] = RAWCODE_LIFE_ADD_START
set prefixes[1] = RAWCODE_LIFE_SUB_START
set prefixes[2] = RAWCODE_MANA_ADD_START
set prefixes[3] = RAWCODE_MANA_SUB_START
set i = 0
loop
exitwhen i == 4
set k = 0
if PRELOAD_ABILITIES then
set preloadUnit = CreateUnit(Player(PRELOAD_PLAYER_ID), PRELOAD_UNIT_ID, PRELOAD_UNIT_X, PRELOAD_UNIT_Y, 0)
if preloadUnit == null then
call ErrorMsg("Failed to create preload unit! PRELOAD_UNIT_ID is likely invalid!")
endif
loop
exitwhen k == ABILITY_COUNT
set ABILITIES[(ABILITY_COUNT * i) + k] = prefixes[i] + k
call UnitAddAbility(preloadUnit, prefixes[i] + k)
debug if GetUnitAbilityLevel(preloadUnit, prefixes[i] + k) == 0 then
debug call ErrorMsg("Failed to add ability " + I2S(prefixes[i] + k) + " to the preload unit!")
debug endif
set k = k + 1
endloop
call RemoveUnit(preloadUnit)
else
loop
exitwhen k == ABILITY_COUNT
set ABILITIES[(ABILITY_COUNT * i) + k] = prefixes[i] + k
set k = k + 1
endloop
endif
set i = i + 1
endloop
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ UnitMaxState - Objer Merger Macros
//@=============================================================================
//@ See the UnitMaxState library for credit list and further documentation
//@=============================================================================
//@ Using the ObjectMerger macros
//@-----------------------------------------------------------------------------
//@ This "library" is used to generate the abilities which are required by the
//@ UnitMaxState library.
//@
//@ To copy all the needed abilities in to your map:
//@ Copy this "library" in to a custom-text trigger in your map while using the
//@ NewGen world editor, save the map, close the map or the editor, and reopen
//@ the map. You should then disable the trigger this library is in, as the
//@ ObjectMerger macros only need to be run once.
//@
//@ Note that saving a map with this "library" in an enabled trigger will take
//@ some time. It is normal for the save process to take up to a few minutes.
//@
//@=============================================================================
//@ Changing the ObjectMerger macros
//@-----------------------------------------------------------------------------
//@ Most users will not need to change anything. It is recomended that you do
//@ not attempt changing anything below unless you understand the way the
//@ UnitMaxState library changes maximum life/mana.
//@
//@ The UnitMaxState_DefineAbility text macro is used to ease the process of
//@ adding or removing precision from the library.
//@
//@ To remove precision, and thus slow the addition/subtraction of large values,
//@ remove one of the //! external ObjectMerger lines from the textmacro.
//@
//@ To add precision, and thus speed up the addition/subtraction of large
//@ values, add an additional Object Merger line. Read the ObjectMerger's
//@ documentation for information on the syntax.
//@
//@ Note that any additional abilities must have 4 levels, with a bonus of 0 at
//@ level 1, and a bonus of 2^(n + level - 2), where n is the number of the
//@ ability, and level is the current level of the ability being created.
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
//! textmacro UnitMaxState_DefineAbility takes RAWCODE_PREFIX, ABILITY_ID, VALUE_FIELD, NEG, NAME, ICON
//! external ObjectMerger w3a $ABILITY_ID$ $RAWCODE_PREFIX$0 alev 4 aite 0 $VALUE_FIELD$ 1 0 $VALUE_FIELD$ 2 $NEG$1 $VALUE_FIELD$ 3 $NEG$2 $VALUE_FIELD$ 4 $NEG$4 anam "UnitMaxState - $NAME$ (1)" ansf "" aart $ICON$
//! external ObjectMerger w3a $ABILITY_ID$ $RAWCODE_PREFIX$1 alev 4 aite 0 $VALUE_FIELD$ 1 0 $VALUE_FIELD$ 2 $NEG$8 $VALUE_FIELD$ 3 $NEG$16 $VALUE_FIELD$ 4 $NEG$32 anam "UnitMaxState - $NAME$ (2)" ansf "" aart $ICON$
//! external ObjectMerger w3a $ABILITY_ID$ $RAWCODE_PREFIX$2 alev 4 aite 0 $VALUE_FIELD$ 1 0 $VALUE_FIELD$ 2 $NEG$64 $VALUE_FIELD$ 3 $NEG$128 $VALUE_FIELD$ 4 $NEG$256 anam "UnitMaxState - $NAME$ (3)" ansf "" aart $ICON$
//! external ObjectMerger w3a $ABILITY_ID$ $RAWCODE_PREFIX$3 alev 4 aite 0 $VALUE_FIELD$ 1 0 $VALUE_FIELD$ 2 $NEG$512 $VALUE_FIELD$ 3 $NEG$1024 $VALUE_FIELD$ 4 $NEG$2048 anam "UnitMaxState - $NAME$ (4)" ansf "" aart $ICON$
//! external ObjectMerger w3a $ABILITY_ID$ $RAWCODE_PREFIX$4 alev 4 aite 0 $VALUE_FIELD$ 1 0 $VALUE_FIELD$ 2 $NEG$4096 $VALUE_FIELD$ 3 $NEG$8192 $VALUE_FIELD$ 4 $NEG$16384 anam "UnitMaxState - $NAME$ (5)" ansf "" aart $ICON$
//! external ObjectMerger w3a $ABILITY_ID$ $RAWCODE_PREFIX$5 alev 4 aite 0 $VALUE_FIELD$ 1 0 $VALUE_FIELD$ 2 $NEG$32768 $VALUE_FIELD$ 3 $NEG$65536 $VALUE_FIELD$ 4 $NEG$131072 anam "UnitMaxState - $NAME$ (6)" ansf "" aart $ICON$
//! endtextmacro
// Life abilities:
//! runtextmacro UnitMaxState_DefineAbility("ZxL", "AIlf", "Ilif", "-", "Life+", "ReplaceableTextures\CommandButtons\BTNHealthStone.blp")
//! runtextmacro UnitMaxState_DefineAbility("Zxl", "AIlf", "Ilif", "", "Life-", "ReplaceableTextures\CommandButtons\BTNHealthStone.blp")
// Mana abilities:
//! runtextmacro UnitMaxState_DefineAbility("ZxM", "AImz", "Iman", "-", "Mana+", "ReplaceableTextures\CommandButtons\BTNManaStone.blp")
//! runtextmacro UnitMaxState_DefineAbility("Zxm", "AImz", "Iman", "", "Mana-", "ReplaceableTextures\CommandButtons\BTNManaStone.blp")
// =============================================================================
// =!= DO NOT LET A //! external BE THE LAST LINE IN A TRIGGER =!=
// =============================================================================
//TESH.scrollpos=0
//TESH.alwaysfold=0
library ArmorUtils needs Logarithm
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This is used to get the exact real-value for armor of a given unit. It
//* deals damage to the unit in order to determine armor, meaning that damage
//* detection systems can detect it. However, the code disables the triggering
//* trigger, so this will not cause any problems so long as only one DAMAGED
//* event is registered to your units at a time. Hopefully all standardized
//* damage detection systems these days only register units once, so this should
//* handle it perfectly.
//*
//* Also, it should be noted that changing the ATTACK_TYPE_CHAOS table in
//* gameplay constants will make this return weird values. Chaos is used because
//* it deals 100% to all types, so either keep it as is or use another type with
//* 100% damage.
//*
//* The GetUnitArmor function will return ARMOR_INVULNERABLE as the value for
//* the unit's armor if it is invulnerable. The reason invulnerability isn't
//* removed from the unit is that not all sources of invulnerability come from
//* the 'Avul' ability and in those cases it cannot be removed. A check can be
//* made against ARMOR_INVULNERABLE to see if the function returned such. The
//* value is about as random as I could think of at the time in the hopes that
//* no one will actually give any of their heroes armor coinciding with it.
//* (since only heroes can have decimal armor)
//*
//* Negative armor in WC3 is treated differently than positive armor. Also, WC3
//* has a negative damage reduction cap at -71%, which corresponds to -20 armor.
//* If you use GetUnitArmor on a unit with less armor than -20, it will always
//* return -20, since WC3 caps there. This is an unfortunate but unavoidable
//* limitation of WC3's armor system.
//*
//* The most important value that you change for this code coincides with the
//* Game Constant 'Armor Damage Reduction Multiplier' and for this system is
//* called ARMOR_REDUCTION_MULTIPLIER. This value typically ranges from 0.05 to
//* 0.10, but really depends on your map. Be sure to change it in this code to
//* match your map or it will not return correct armor values.
//*
//* The other variables in the code do not ever need to be changed. DAMAGE_LIFE
//* must only be large enough to survive a hit with DAMAGE_TEST damage dealt.
//* (Also factoring in negative armor damage bonus) NATLOG_094 is necessary for
//* the calculation of negative armor values.
//*
//* Other functions available for use are GetFullDamage and GetReducedDamage.
//* GetFullDamage, when passed the actual damage a unit takes (In most cases,
//* GetEventDamage from EVENT_UNIT_DAMAGED event callbacks) and a unit's armor,
//* it will return how much damage was dealt before armor reduction. Similarly,
//* GetReducedDamage, when given the base damage and armor, will return how much
//* damage will be dealt after armor is considered. These functions DO NOT
//* consider armor types in their calculations, so any further reductions or
//* bonuses due to that will need to be considered BEFORE using these functions.
//* I recommend using your damage detection system to modify and build your own
//* armor types anyways.
//*
//* You can use the ObjectMerger call below in order to generate the ability for
//* keeping units with maximum life lower than DAMAGE_TEST from dying when
//* using GetUnitArmor on them. If you do not plan on editing the 'AIlz' ability
//* in your map, you can keep the ObjectMerger call commented out and replace
//* 'lif&' in the configuration constants with 'AIlz'. The 'AIlz' ability adds
//* 50 max life, which is plenty for the script.
//*
//* Function Listing --
//* function GetUnitArmor takes unit u returns real
//* function GetReducedDamage takes real baseDamage, real armor returns real
//* function GetFullDamage takes real damage, real armor returns real
//*
globals
//Values that should be changed for your map
private constant real ARMOR_REDUCTION_MULTIPLIER = 0.06
private constant integer LIFE_BONUS_SPELL_ID = 'lif&'
//Values that do not need to be changed
constant real ARMOR_INVULNERABLE = 917451.519
private constant real DAMAGE_TEST = 16.
private constant real DAMAGE_LIFE = 30.
private constant real NATLOG_094 =-0.061875
endglobals
////! external ObjectMerger w3a AIlz lif& anam "GetUnitArmorLifeBonus" ansf "" Ilif 1 30 aite 0
function GetUnitArmor takes unit u returns real
local real life = GetWidgetLife(u)
local real test = life
local real redc = 0.
local boolean enab = false
local trigger trig = GetTriggeringTrigger()
if u != null and life >= 0.405 then
if GetUnitState(u, UNIT_STATE_MAX_LIFE) <= DAMAGE_TEST then
//Add max life to keep it alive
call UnitAddAbility(u, LIFE_BONUS_SPELL_ID)
endif
if life <= DAMAGE_LIFE then
//If under the threshold, heal it for the moment
call SetWidgetLife(u, DAMAGE_LIFE)
set test = DAMAGE_LIFE
endif
if trig != null and IsTriggerEnabled(trig) then
//Disable the trigger to prevent it registering with damage detection systems
call DisableTrigger(trig)
set enab = true
endif
call UnitDamageTarget(u, u, DAMAGE_TEST, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_NORMAL, null)
set redc = (DAMAGE_TEST-test+GetWidgetLife(u))/DAMAGE_TEST
if enab then
//Re-enable the trigger
call EnableTrigger(trig)
endif
//Remove the max life ability
call UnitRemoveAbility(u, LIFE_BONUS_SPELL_ID)
call SetWidgetLife(u, life)
set trig = null
if redc >= 1. then
//Invulnerable
return ARMOR_INVULNERABLE
elseif redc < 0. then
//Negative Armor
return -Log(redc+1.)/NATLOG_094
else
//Positive Armor
return redc/(ARMOR_REDUCTION_MULTIPLIER*(1.-redc))
endif
endif
set trig = null
return 0.
endfunction
function GetReducedDamage takes real baseDamage, real armor returns real
if armor >= 0. then
return baseDamage*(1.-((armor*ARMOR_REDUCTION_MULTIPLIER)/(1.+ARMOR_REDUCTION_MULTIPLIER*armor)))
else
return baseDamage*(2.-Pow(0.94,-armor))
endif
endfunction
function GetFullDamage takes real damage, real armor returns real
if armor >= 0. then
return damage/(1.-((armor*ARMOR_REDUCTION_MULTIPLIER)/(1.+ARMOR_REDUCTION_MULTIPLIER*armor)))
else
return damage/(2.-Pow(0.94,-armor))
endif
endfunction
endlibrary
//TESH.scrollpos=24
//TESH.alwaysfold=0
library StatusModifier initializer Init needs optional TimerUtils, Table, xepreload
//*****************************************************************************
//* StatusModifier by Inferior
//*
//* I know, i know i should not create a library that does allready exist.
//* And I really recommend you to use the BonusMod library by Earth-Fury.
//* The only reason why I use this Library is that I didn't want to include a system
//* where I only use a little part of it.
//* Anyways I will explain this template.
//* With this System you can increase or decrease a units Damage, Armor or Attackspeed.
//*
//* If you really want to use this template copy the code and generate the different abilities.
//* To make sure you don't forget any ability, remove the delimited comment above the
//* textmacro with the ObjectMerger calls. Save your map. Close JNGP, reopen your map and put
//* the delimited comments above the textmacro again.
//*
//* API:
//* To add Damage, Armor or Attackspeed to a Unit use one of the following Functions:
//* function UnitAddDamage takes unit u , integer value returns boolean
//* function UnitAddArmor takes unit u , integer value returns boolean
//* function UnitAddSpeed takes unit u , integer value returns boolean
//* To Remove the Bonus just use the same function, just use the negative value you added befor
//* Vice Versa for negative Bonus. You may reduce a Units Damage, Armor or AttackSpeed,
//* just use a negative value. To reduce it use the positive value of it.
//*
//* You may also add a timed Bonus. To use this feature just call one of the following Functions:
//* function UnitAddDamageTimed takes unit u , integer value , real time returns nothing
//* function UnitAddArmorTimed takes unit u , integer value , real time returns nothing
//* function UnitAddSpeedTimed takes unit u , integer value , real time returns nothing
//* After the time expires the Bonus gets cleaned up automatically
//*
//* Examples:
//* We want to reduce a Units Damage by 10 so we have to use:
//* call UnitAddDamage( whichUnit, -10 )
//* To clean it up we use:
//* call UnitAddDamage( whichUnit, 10 )
//* Another Example - We want to increase a Units Attackspeed by 50% for 20 Seconds, so we use:
//* call UnitAddSpeedTimed( whichUnit , 50 , 20 )
//*
//* Minimum/Maximum Bonuses:
//* - Damage: -2047 / +2047
//* - Armor: -2047 / +2047
//* - Speed: -1023 / +1023
//*
//******************************************************************************************
/*
//! textmacro StatusMod takes type,pre,field,op,name
//! external ObjectMerger w3a $type$ $pre$A $field$ 1 $op$1 anam "$name$ ($op$1)"
//! external ObjectMerger w3a $type$ $pre$B $field$ 1 $op$2 anam "$name$ ($op$2)"
//! external ObjectMerger w3a $type$ $pre$C $field$ 1 $op$4 anam "$name$ ($op$4)"
//! external ObjectMerger w3a $type$ $pre$D $field$ 1 $op$8 anam "$name$ ($op$8)"
//! external ObjectMerger w3a $type$ $pre$E $field$ 1 $op$16 anam "$name$ ($op$16)"
//! external ObjectMerger w3a $type$ $pre$F $field$ 1 $op$32 anam "$name$ ($op$32)"
//! external ObjectMerger w3a $type$ $pre$G $field$ 1 $op$64 anam "$name$ ($op$64)"
//! external ObjectMerger w3a $type$ $pre$H $field$ 1 $op$128 anam "$name$ ($op$128)"
//! external ObjectMerger w3a $type$ $pre$I $field$ 1 $op$256 anam "$name$ ($op$256)"
//! external ObjectMerger w3a $type$ $pre$J $field$ 1 $op$512 anam "$name$ ($op$512)"
//! external ObjectMerger w3a $type$ $pre$K $field$ 1 $op$1024 anam "$name$ ($op$1024)"
//! endtextmacro
//! runtextmacro StatusMod("AId1","Dfu","Idef","+","Armor")
//! runtextmacro StatusMod("AId1","Dfd","Idef","-","Armor")
//! runtextmacro StatusMod("AItg","Dmu","Iatt","+","Damage")
//! runtextmacro StatusMod("AItg","Dmd","Iatt","-","Damage")
//! textmacro PercentStatusMod takes type,pre,field,op,name
//! external ObjectMerger w3a $type$ $pre$A $field$ 1 $op$0.01 anam "$name$ ($op$0.01)"
//! external ObjectMerger w3a $type$ $pre$B $field$ 1 $op$0.02 anam "$name$ ($op$0.02)"
//! external ObjectMerger w3a $type$ $pre$C $field$ 1 $op$0.04 anam "$name$ ($op$0.04)"
//! external ObjectMerger w3a $type$ $pre$D $field$ 1 $op$0.08 anam "$name$ ($op$0.08)"
//! external ObjectMerger w3a $type$ $pre$E $field$ 1 $op$0.16 anam "$name$ ($op$0.16)"
//! external ObjectMerger w3a $type$ $pre$F $field$ 1 $op$0.32 anam "$name$ ($op$0.32)"
//! external ObjectMerger w3a $type$ $pre$G $field$ 1 $op$0.64 anam "$name$ ($op$0.64)"
//! external ObjectMerger w3a $type$ $pre$H $field$ 1 $op$1.28 anam "$name$ ($op$1.28)"
//! external ObjectMerger w3a $type$ $pre$I $field$ 1 $op$2.56 anam "$name$ ($op$2.56)"
//! external ObjectMerger w3a $type$ $pre$J $field$ 1 $op$5.12 anam "$name$ ($op$5.12)"
//! endtextmacro
//! runtextmacro PercentStatusMod("AIsx","Asu","Isx1","+","Attackspeed")
//! runtextmacro PercentStatusMod("AIsx","Asd","Isx1","-","Attackspeed")
*/
globals
private constant integer ArmorInc='DfuA'
private constant integer ArmorDec='DfdA'
private constant integer DamageInc='DmuA'
private constant integer DamageDec='DmdA'
private constant integer SpeedInc='AsuA'
private constant integer SpeedDec='AsdA'
private hashtable ht=InitHashtable()
endglobals
//! textmacro StatusAdder takes type,max,stack
private function UnitGet$type$ takes unit u returns integer
local integer max=$max$
local integer rtrn=0
local integer stack=$stack$
loop
if GetUnitAbilityLevel(u,$type$Inc+stack)==1 then
set rtrn=rtrn+max
endif
exitwhen max==1
set max=max/2
set stack=stack-1
endloop
set stack=$stack$
set max=$max$
loop
if GetUnitAbilityLevel(u,$type$Dec+stack)==1 then
set rtrn=rtrn-max
endif
exitwhen max==1
set max=max/2
set stack=stack-1
endloop
return rtrn
endfunction
private function UnitClear$type$ takes unit u returns nothing
local integer stack=$stack$
loop
call UnitRemoveAbility(u,$type$Inc+stack)
call UnitRemoveAbility(u,$type$Dec+stack)
exitwhen stack==0
set stack=stack-1
endloop
endfunction
function UnitAdd$type$ takes unit u , integer value returns boolean
local integer curVal=UnitGet$type$(u)
local integer max=$max$
local integer stack=$stack$
set curVal=curVal+value
call UnitClear$type$(u)
if curVal==0 then
return false
elseif curVal>0 then
loop
exitwhen curVal==0
if curVal-max>=0 then
call UnitAddAbility(u,$type$Inc+stack)
set curVal=curVal-max
endif
set stack=stack-1
set max=max/2
endloop
elseif curVal<0 then
loop
exitwhen curVal==0
if curVal+max<=0 then
call UnitAddAbility(u,$type$Dec+stack)
set curVal=curVal+max
endif
set stack=stack-1
set max=max/2
endloop
endif
return true
endfunction
private function Reset$type$ takes nothing returns nothing
local timer t=GetExpiredTimer()
local unit u=LoadUnitHandle(ht,GetHandleId(t),StringHash("targ"))
call UnitAdd$type$(u,-GetTimerData(t))
static if LIBRARY_TimerUtils then
call ReleaseTimer(t)
else
call DestroyTimer(t)
endif
endfunction
function UnitAdd$type$Timed takes unit u , integer value , real time returns nothing
local timer t
static if LIBRARY_TimerUtils then
set t=NewTimer()
else
set t=CreateTimer()
endif
call SaveUnitHandle(ht,GetHandleId(t),StringHash("targ"),u)
call SetTimerData(t,value)
call TimerStart(t,time,false,function Reset$type$)
call UnitAdd$type$(u,value)
endfunction
//! endtextmacro
//! runtextmacro StatusAdder("Damage","1024","10")
//! runtextmacro StatusAdder("Armor","1024","10")
//! runtextmacro StatusAdder("Speed","512","9")
private function Init takes nothing returns nothing
local integer i=0
loop
exitwhen i==11
call XE_PreloadAbility(ArmorInc+i)
call XE_PreloadAbility(ArmorDec+i)
call XE_PreloadAbility(DamageInc+i)
call XE_PreloadAbility(DamageDec+i)
call XE_PreloadAbility(SpeedInc+i)
call XE_PreloadAbility(SpeedDec+i)
set i=i+1
endloop
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
library LastOrder initializer Init needs AutoIndex
//******************************************************************************
//* 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=260
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit) to
// get get the unique value assigned to a particular unit. AutoIndex differs from
// other unit indexing libraries because it automatically assigns an ID to each
// unit as it enters the map, and automatically frees that ID as the unit leaves
// the map. This gives you several advantages as the user:
//
// 1.) The GetUnitId function inlines directly to a GetUnitUserData call (or a
// LoadInteger call if UseUnitUserData is disabled.)
// 2.) You don't need to manually free IDs as units leave the map.
// 3.) Detecting removing units to free their indexes is O(1), and less costly
// performance-wise than a timer scanning the map for removed units.
//
// If you turn on debug mode, AutoIndex will become slower, but it will show
// you a variety of helpful error messages. It can detect the following problems:
// -Passing a null unit to GetUnitId
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex also provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game, and
// handle the creation or destruction of attached data or other things.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the save delay.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
//
// globals
// integer array IntegerData
// real array RealData
// SomeStruct array SomeStructData
// englobals
//
// function Example takes nothing returns nothing
// local unit u = CreateUnit(...)
// local integer id = GetUnitId(u)
// //You now have a unique index for the unit, so you can
// //attach or retrieve data about the unit using arrays.
// set IntegerData[id] = 5
// set RealData[id] = 25.0
// set SomeStructData[id] = SomeStruct.create()
// //If you have access to the same unit in another function, you can
// //retrieve the data by using GetUnitId() and reading the arrays.
// endfunction
//
// The UnitFilter function in the config section is provided so that you can
// make AutoIndex completely ignore any unit-types that don't want to be indexed.
// You may want to ignore dummy casters or system-private units, especially ones
// that use UnitUserData internally. You don't need to worry about xe dummy units,
// as those are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
//
// function UnitEntersMap takes unit u returns nothing
// call BJDebugMsg(GetUnitName(u)+" was indexed with the ID "+I2S(GetUnitId(u)))
// endfunction
//
// function UnitLeavesMap takes unit u returns nothing
// call BJDebugMsg(GetUnitName(u)+" was deindexed with the ID "+I2S(GetUnitId(u)))
// endfunction
//
// function Init takes nothing returns nothing
// call OnUnitIndexed(UnitEntersMap)
// call OnUnitDeindexed(UnitLeavesMap)
// endfunction
//
// As you can see, it works perfectly fine to call GetUnitId() on a unit
// during either of these events.
//
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from needing
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Use it to attach data to the unit. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, it can display error mess-
// ages when passed a null, decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit is indexed or not. A unit would not be indexed if you ignored
// it using the UnitFilter function, or if it is a xe dummy unit.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// Configuration:
//================
////! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMergermacro above. You can change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
endglobals
public function UnitFilter takes nothing returns boolean
return true
endfunction
//Any units you filter out in this function will not be indexed.
//Use GetFilterUnit() to refer to the filtered unit. You do not
//need to filter out xe dummy units; they are already filtered.
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then
return AutoIndex.getIndexDebug(u)
else
return AutoIndex.getIndex(u)
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function interface IndexFunc takes unit u returns nothing
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
//===========================================================================
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger status = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod
//Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
local integer index = getIndex(u)
if u == null then
call BJDebugMsg("AutoIndex error: Null unit passed to GetUnitId.")
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[index] != u then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return index
endmethod
//If debug mode is enabled, use the getIndex method that shows errors.
static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod
//Resolves to an inlinable one-liner after the static if.
//===========================================================================
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod
//Don't use this; use IsUnitAnimateDead from StatusEvents instead.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
//During initialization, evaluate the indexfunc for every preplaced unit.
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod
//Intercepts whenever RemoveUnit or ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if IsUnitIndexed(whichUnit) then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif
endmethod
//In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
//Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod
//Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunStatusEvent("Resurrect")
//If StatusEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunStatusEvent("AnimateDead")
//If StatusEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunStatusEvent("Death")
//If StatusEvents is in the map, run the Death events.
//! runtextmacro optional TransportUnload()
//If TransportEvents is in the map, remove the dead unit from whatever transport it's in.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
//! runtextmacro optional TransportUnload()
//If TransportEvents is in the map, remove the leaving unit from whatever transport it's in.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
//! runtextmacro optional TransportClean()
//If TransportEvents is in the map, and the leaving unit is a
//transport, clean the transport- related data from the unit.
call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
endif
set u = null
return false
endmethod
private static method isUndefendOrder takes nothing returns boolean
return GetIssuedOrderId() == 852056
endmethod
//===========================================================================
private static method unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then //If a unit already has an ID, don't assign a new one.
return //This only happens if a unit leaves the entire map area.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //unit. These flags are necessary to detect
set animated[index] = false //when the unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method initPreplacedUnit takes nothing returns nothing
static if LIBRARY_xebasic then
if GetUnitTypeId(GetEnumUnit()) == XE_DUMMY_UNITID then
return //Don't index xe dummy units, and don't add
endif //them to the group of preplaced units.
endif
call GroupAddUnit(preplaced, GetEnumUnit()) //Assemble a group of all the preplaced units.
call unitEntersMap(GetEnumUnit()) //Initialize each preplaced unit.
return
endmethod
private static method initEnteringUnit takes nothing returns boolean
static if LIBRARY_xebasic then
if GetUnitTypeId(GetFilterUnit()) == XE_DUMMY_UNITID then
return false //Don't index xe dummy units, and don't add
endif //them to the group of preplaced units.
endif
if duringinit then
call GroupAddUnit(preplaced, GetFilterUnit())
//Add units that are created during initialization to the preplaced units group.
//This ensures that all units are noticed by OnUnitIndexed during initialization.
endif
call unitEntersMap(GetFilterUnit()) //Initialize each unit that enters the map.
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(status, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, function UnitFilter)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event to notice Undefend orders. Ignore filtered units.
call GroupEnumUnitsOfPlayer(g, Player(i), Condition(function UnitFilter))
call ForGroup(g, function AutoIndex.initPreplacedUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(status, And(function AutoIndex.isUndefendOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, function UnitFilter)
call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() contains the entire map area, including the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, And(function UnitFilter, function AutoIndex.initEnteringUnit))
//Only the filter function of an EnterRegion trigger runs instantly when a unit is created.
//Using And() lets both the UnitFilter and indexing function run when a unit enters the map.
call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
endstruct
endlibrary
//TESH.scrollpos=28
//TESH.alwaysfold=0
library SpellEvent initializer Init requires Table
//*****************************************************************
//* SPELL EVENT LIBRARY
//*
//* written by: Anitarf
//* requires: -Table
//*
//* Maps with many triggered spells require many triggers that run
//* on spell events and whenever a spell is cast, all those
//* triggers need to be evaluated by the game even though only one
//* actually needs to run. This library has been written to reduce
//* the number of triggers in such maps; instead of having a
//* trigger per spell, you can use this library's single trigger
//* to only run the code associated with the spell that's actually
//* being cast.
//*
//* Perhaps more significant than the marginal speed gain is the
//* feature that allows you to access all the spell event
//* responses from all spell events, something that the native
//* functions senselessly do not support. With this system you can
//* for example easily get the target unit of the spell on the
//* casting finish event.
//*
//* All functions following the Response function interface that
//* is defined at the start of this library can be used to respond
//* to damage events. Simply put them on the call list for any of
//* the spell events using the appropriate function:
//*
//* function RegisterSpellChannelResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellCastResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellEffectResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellFinishResponse takes integer spellId, Response r returns nothing
//* function RegisterSpellEndCastResponse takes integer spellId, Response r returns nothing
//*
//* The first event occurs at the very start of the spell, when
//* the spell's casting time begins; most spells have 0 casting
//* time, so in most cases this first event occurs at the same
//* time as the second one, which runs when the unit actually
//* begins casting a spell by starting it's spell animation. The
//* third event occurs when the spell effect actually takes place,
//* which happens sometime into the unit's spell animation
//* depending on the unit's Animation - Cast Point property.
//* The fourth event runs if the unit finishes casting the spell
//* uninterrupted, which might be important for channeling spells.
//* The last event runs when the unit stops casting the spell,
//* regardless of whether it finished casting or was interrupted.
//*
//* If you specify a spell id when registering a function then
//* that function will only run when that ability is cast; only
//* one function per ability per event is supported, if you
//* register more functions then only the last one registered will
//* be called. If, however, you pass 0 as the ability id parameter
//* then the registered function will run for all spells. Up to
//* 8190 functions can be registered this way for each event.
//* These functions will be called before the ability's specific
//* function in the order they were registered.
//*
//* This library provides it's own event responses that work
//* better than the Blizzard's bugged native cast event responses.
//* They still aren't guaranteed to work after a wait, but aside
//* from that they will work in response functions no matter what
//* event they are registered to.
//*
//* Here are usage examples for all event responses:
//*
//* local integer a = SpellEvent.AbilityId
//* local unit u = SpellEvent.CastingUnit
//* local unit t = SpellEvent.TargetUnit
//* local item i = SpellEvent.TargetItem
//* local destructable d = SpellEvent.TargetDestructable
//* local location l = SpellEvent.TargetLoc
//* local real x = SpellEvent.TargetX
//* local real y = SpellEvent.TargetY
//* local boolean b = SpellEvent.CastFinished
//*
//* SpellEvent.TargetLoc is provided for odd people who insist on
//* using locations, note that if you use it you have to cleanup
//* the returned location yourself.
//*
//* SpellEvent.CastFinished boolean is intended only for the
//* EndCast event as it tells you whether the spell finished or
//* was interrupted.
//*
//*****************************************************************
// use the RegisterSpell*Response functions to add spell event responses to the library
public function interface Response takes nothing returns nothing
// ================================================================
private keyword casterTable
private keyword effectDone
private keyword init
private keyword get
private struct spellEvent
static HandleTable casterTable
boolean effectDone=false
integer AbilityId
unit CastingUnit
unit TargetUnit
item TargetItem=null
destructable TargetDestructable=null
real TargetX=0.0
real TargetY=0.0
boolean CastFinished=false
method operator TargetLoc takes nothing returns location
return Location(.TargetX, .TargetY)
endmethod
private static method create takes nothing returns spellEvent
return spellEvent.allocate()
endmethod
static method init takes nothing returns spellEvent
local spellEvent s=spellEvent.allocate()
set s.AbilityId = GetSpellAbilityId()
set s.CastingUnit = GetTriggerUnit()
set s.TargetUnit = GetSpellTargetUnit()
if s.TargetUnit != null then
set s.TargetX = GetUnitX(s.TargetUnit)
set s.TargetY = GetUnitY(s.TargetUnit)
else
set s.TargetDestructable = GetSpellTargetDestructable()
if s.TargetDestructable != null then
set s.TargetX = GetDestructableX(s.TargetDestructable)
set s.TargetY = GetDestructableY(s.TargetDestructable)
else
set s.TargetItem = GetSpellTargetItem()
if s.TargetItem != null then
set s.TargetX = GetItemX(s.TargetItem)
set s.TargetY = GetItemY(s.TargetItem)
else
set s.TargetX = GetSpellTargetX()
set s.TargetY = GetSpellTargetY()
endif
endif
endif
set spellEvent.casterTable[s.CastingUnit]=integer(s)
return s
endmethod
static method get takes unit caster returns spellEvent
return spellEvent(spellEvent.casterTable[caster])
endmethod
method onDestroy takes nothing returns nothing
call spellEvent.casterTable.flush(.CastingUnit)
set .CastingUnit=null
endmethod
endstruct
globals
spellEvent SpellEvent=0
endglobals
// ================================================================
//! textmacro spellEvent_make takes name
globals
private Response array $name$CallList
private integer $name$CallCount=0
private Table $name$Table
endglobals
private function $name$Calls takes nothing returns nothing
local integer i=0
local integer id=GetSpellAbilityId()
local spellEvent previous=SpellEvent
set SpellEvent=spellEvent.get(GetTriggerUnit())
loop
exitwhen i>=$name$CallCount
call $name$CallList[i].evaluate()
set i=i+1
endloop
if $name$Table.exists(id) then
call Response($name$Table[id]).evaluate()
endif
set SpellEvent=previous
endfunction
function RegisterSpell$name$Response takes integer spellId, Response r returns nothing
if spellId==0 then
set $name$CallList[$name$CallCount]=r
set $name$CallCount=$name$CallCount+1
else
set $name$Table[spellId]=integer(r)
endif
endfunction
//! endtextmacro
//! runtextmacro spellEvent_make("Channel")
//! runtextmacro spellEvent_make("Cast")
//! runtextmacro spellEvent_make("Effect")
//! runtextmacro spellEvent_make("Finish")
//! runtextmacro spellEvent_make("EndCast")
private function Channel takes nothing returns nothing
call spellEvent.init()
call ChannelCalls()
endfunction
private function Cast takes nothing returns nothing
call CastCalls()
endfunction
private function Effect takes nothing returns nothing
local spellEvent s=spellEvent.get(GetTriggerUnit())
if s!=0 and not s.effectDone then
set s.effectDone=true
call EffectCalls()
endif
endfunction
private function Finish takes nothing returns nothing
set spellEvent.get(GetTriggerUnit()).CastFinished=true
call FinishCalls()
endfunction
private function EndCast takes nothing returns nothing
call EndCastCalls()
call spellEvent.get(GetTriggerUnit()).destroy()
endfunction
// ================================================================
private function InitTrigger takes playerunitevent e, code c returns nothing
local trigger t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( t, e )
call TriggerAddAction(t, c)
set t=null
endfunction
private function Init takes nothing returns nothing
set spellEvent.casterTable=HandleTable.create()
set ChannelTable=Table.create()
set CastTable=Table.create()
set EffectTable=Table.create()
set FinishTable=Table.create()
set EndCastTable=Table.create()
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function Channel)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_CAST, function Cast)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_EFFECT, function Effect)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_FINISH, function Finish)
call InitTrigger(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function EndCast)
endfunction
endlibrary
//TESH.scrollpos=13
//TESH.alwaysfold=0
library DamageDetection initializer Init needs AutoIndex
private keyword onAttackDamage
private keyword onSpellDamage
globals
private constant trigger Detection =CreateTrigger()
private constant integer OrbID ='AOrb'
private constant integer OrbBuffID ='BOrb'
private integer ACount = 0
private integer SCount = 0
private onAttackDamage array Attacks[8190]
private onSpellDamage array Spells[8190]
private boolean Flag = true
endglobals
function interface onSpellDamage takes unit whichTarget, unit whichSource, real damage returns nothing
function interface onAttackDamage takes unit whichTarget, unit whichCaster, real damage returns nothing
function UnitDamageTargetEx takes unit whichUnit, widget whichWidget, real amount, boolean attack, boolean ranged, attacktype attackType, damagetype damageType, weapontype weaponType returns nothing
if GetWidgetLife(whichWidget) > 0.405 then
set Flag=false
call UnitDamageTarget(whichUnit, whichWidget, amount, attack, ranged, attackType, damageType, weaponType)
else
debug call BJDebugMsg("Warning: Damaging a dead widget is not possible!!")
endif
endfunction
private function DamageEvent takes nothing returns boolean
local unit damaged=GetTriggerUnit()
local unit damager=GetEventDamageSource()
local real damage=GetEventDamage()
local integer i=0
if Flag then
if GetUnitAbilityLevel(damaged,OrbBuffID)>0 then
call UnitRemoveAbility(damaged,OrbBuffID)
loop
exitwhen i==ACount
call Attacks[i].evaluate(damaged,damager,damage)
set i=i+1
endloop
return true
else
loop
exitwhen i==SCount
call Spells[i].evaluate(damaged,damager,damage)
set i=i+1
endloop
return true
endif
else
set Flag=true
endif
set damaged=null
set damager=null
return false
endfunction
function AddAttackResponse takes onAttackDamage response returns boolean
if ACount<8191 then
set Attacks[ACount]=response
set ACount=ACount+1
return true
else
debug call BJDebugMsg("Warning: Attack Response Stack Full!!")
return false
endif
endfunction
function AddSpellResponse takes onSpellDamage response returns boolean
if SCount<8191 then
set Spells[SCount]=response
set SCount=SCount+1
return true
else
debug call BJDebugMsg("Warning: Spell Response Stack Full!!")
return false
endif
endfunction
private function AttachDetectionBuff takes unit whichUnit returns nothing
call TriggerRegisterUnitEvent(Detection,whichUnit,EVENT_UNIT_DAMAGED)
call UnitAddAbility(whichUnit,'AOrb')
endfunction
private function Init takes nothing returns nothing
call OnUnitIndexed(AttachDetectionBuff)
call TriggerAddCondition(Detection,Condition(function DamageEvent))
endfunction
endlibrary
//TESH.scrollpos=29
//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=6
//TESH.alwaysfold=0
library xepreload initializer init
//******************************************************************************
// xepreload 0.8
// ---------
// Ah, the joy of preloading abilities, it is such a necessary evil...
// Notice you are not supposed to use this system in places outside map init
//
// This one does the preloading and tries to minimize the hit on loading time
// for example, it only needs one single native call per ability preloaded.
//
//******************************************************************************
//==============================================================================
globals
private unit dum=null
endglobals
private keyword DebugIdInteger2IdString
//inline friendly (when debug mode is off..)
function XE_PreloadAbility takes integer abilid returns nothing
call UnitAddAbility(dum, abilid)
static if DEBUG_MODE then
if(dum==null) then
call BJDebugMsg("XE_PreloadAbility: do not load abilities after map init or during structs' onInit")
elseif GetUnitAbilityLevel(dum, abilid) == 0 then
call BJDebugMsg("XE_PreloadAbility: Ability "+DebugIdInteger2IdString.evaluate(abilid)+" does not exist.")
endif
endif
endfunction
// ................................................................................
//================================================================================
// Convert a integer id value into a 4-letter id code.
// * Taken from cheats.j so I don't have to code it again.
// * Used only on debug so making a whole library for it seemed silly
// * Private so people don't begin using xepreload just to call this function....
// * It will not work correctly if you paste this code in the custom script section
// due to the infamous % bug. Then again, if you do that then you probably
// deserve it....
//
private function DebugIdInteger2IdString takes integer value returns string
local string charMap = ".................................!.#$%&'()*+,-./0123456789:;<=>.@ABCDEFGHIJKLMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~................................................................................................................................."
local string result = ""
local integer remainingValue = value
local integer charValue
local integer byteno
set byteno = 0
loop
set charValue = ModuloInteger(remainingValue, 256)
set remainingValue = remainingValue / 256
set result = SubString(charMap, charValue, charValue + 1) + result
set byteno = byteno + 1
exitwhen byteno == 4
endloop
return result
endfunction
//--------------------------------
private function kill takes nothing returns nothing
call RemoveUnit(dum)
set dum=null
static if (LIBRARY_TimerUtils ) then
call ReleaseTimer( GetExpiredTimer() )
else
call DestroyTimer(GetExpiredTimer())
endif
endfunction
private function init takes nothing returns nothing
local timer t
set dum = CreateUnit( Player(15),'e000', 0,0,0)
if( dum == null) then
debug call BJDebugMsg("xePreload : XE_DUMMY_UNITID ("+DebugIdInteger2IdString.evaluate(XE_DUMMY_UNITID)+") not added correctly to the map.")
endif
static if (LIBRARY_TimerUtils) then
set t=NewTimer()
else
set t=CreateTimer()
endif
call TimerStart(t,0.0,false,function kill)
set t=null
endfunction
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
library xefx initializer init requires xebasic
//**************************************************
// xefx 0.7
// --------
// Recommended: ARGB (adds ARGBrecolor method)
// For your movable fx needs
//
//**************************************************
//==================================================
globals
private constant integer MAX_INSTANCES = 8190 //change accordingly.
private constant real RECYCLE_DELAY = 4.0
//recycling, in order to show the effect correctly, must wait some time before
//removing the unit.
private timer recycler
private timer NOW
endglobals
private struct recyclebin extends array
unit u
real schedule
static recyclebin end=0
static recyclebin begin=0
static method Recycle takes nothing returns nothing
call RemoveUnit(.begin.u) //this unit is private, systems shouldn't mess with it.
set .begin.u=null
set .begin=recyclebin(integer(.begin)+1)
if(.begin==.end) then
set .begin=0
set .end=0
else
call TimerStart(recycler, .begin.schedule-TimerGetElapsed(NOW), false, function recyclebin.Recycle)
endif
endmethod
endstruct
private function init takes nothing returns nothing
set recycler=CreateTimer()
set NOW=CreateTimer()
call TimerStart(NOW,43200,true,null)
endfunction
struct xefx[MAX_INSTANCES]
public integer tag=0
public unit dummy
private effect fx=null
private real zang=0.0
private integer r=255
private integer g=255
private integer b=255
private integer a=255
private integer abil=0
static method create takes real x, real y, real facing returns xefx
local xefx this=xefx.allocate()
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, facing*bj_RADTODEG)
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
return this
endmethod
method operator owner takes nothing returns player
return GetOwningPlayer(this.dummy)
endmethod
method operator owner= takes player p returns nothing
call SetUnitOwner(this.dummy,p,false)
endmethod
method operator teamcolor= takes playercolor c returns nothing
call SetUnitColor(this.dummy,c)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(this.dummy,value,value,value)
endmethod
//! textmacro XEFX_colorstuff takes colorname, colorvar
method operator $colorname$ takes nothing returns integer
return this.$colorvar$
endmethod
method operator $colorname$= takes integer value returns nothing
set this.$colorvar$=value
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
//! endtextmacro
//! runtextmacro XEFX_colorstuff("red","r")
//! runtextmacro XEFX_colorstuff("green","g")
//! runtextmacro XEFX_colorstuff("blue","b")
//! runtextmacro XEFX_colorstuff("alpha","a")
method recolor takes integer r, integer g , integer b, integer a returns nothing
set this.r=r
set this.g=g
set this.b=b
set this.a=a
call SetUnitVertexColor(this.dummy,this.r,this.g,this.b,this.a)
endmethod
implement optional ARGBrecolor
method operator abilityid takes nothing returns integer
return this.abil
endmethod
method operator abilityid= takes integer a returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(a!=0) then
call UnitAddAbility(this.dummy,a)
endif
set this.abil=a
endmethod
method operator abilityLevel takes nothing returns integer
return GetUnitAbilityLevel( this.dummy, this.abil)
endmethod
method operator abilityLevel= takes integer newLevel returns nothing
call SetUnitAbilityLevel(this.dummy, this.abil, newLevel)
endmethod
method flash takes string fx returns nothing
call DestroyEffect(AddSpecialEffectTarget(fx,this.dummy,"origin"))
endmethod
method operator xyangle takes nothing returns real
return GetUnitFacing(this.dummy)*bj_DEGTORAD
endmethod
method operator xyangle= takes real value returns nothing
call SetUnitFacing(this.dummy,value*bj_RADTODEG)
endmethod
method operator zangle takes nothing returns real
return this.zang
endmethod
method operator zangle= takes real value returns nothing
local integer i=R2I(value*bj_RADTODEG+90.5)
set this.zang=value
if(i>=180) then
set i=179
elseif(i<0) then
set i=0
endif
call SetUnitAnimationByIndex(this.dummy, i )
endmethod
method operator x takes nothing returns real
return GetUnitX(this.dummy)
endmethod
method operator y takes nothing returns real
return GetUnitY(this.dummy)
endmethod
method operator z takes nothing returns real
return GetUnitFlyHeight(this.dummy)
endmethod
method operator z= takes real value returns nothing
call SetUnitFlyHeight(this.dummy,value,0)
endmethod
method operator x= takes real value returns nothing
call SetUnitX(this.dummy,value)
endmethod
method operator y= takes real value returns nothing
call SetUnitY(this.dummy,value)
endmethod
method operator fxpath= takes string newpath returns nothing
if (this.fx!=null) then
call DestroyEffect(this.fx)
endif
if (newpath=="") then
set this.fx=null
else
set this.fx=AddSpecialEffectTarget(newpath,this.dummy,"origin")
endif
endmethod
method hiddenReset takes string newfxpath, real newfacing returns nothing
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
local real z = this.z
local real za = this.zangle
local integer level = this.abilityLevel
set fxpath=null
call RemoveUnit(this.dummy)
set this.dummy= CreateUnit(Player(15), XE_DUMMY_UNITID, x,y, newfacing*bj_RADTODEG)
if(level != 0) then
call UnitAddAbility(this.dummy, abilityid)
endif
call UnitAddAbility(this.dummy,XE_HEIGHT_ENABLER)
call UnitAddAbility(this.dummy,'Aloc')
call UnitRemoveAbility(this.dummy,XE_HEIGHT_ENABLER)
call SetUnitX(this.dummy,x)
call SetUnitY(this.dummy,y)
set this.z = z
set zangle = za
endmethod
private method onDestroy takes nothing returns nothing
if(this.abil!=0) then
call UnitRemoveAbility(this.dummy,this.abil)
endif
if(this.fx!=null) then
call DestroyEffect(this.fx)
set this.fx=null
endif
if (recyclebin.end==MAX_INSTANCES) then
//I'd like to see this happen...
call TimerStart(recycler,0,false,function recyclebin.Recycle)
call ExplodeUnitBJ(this.dummy)
else
set recyclebin.end.u=this.dummy
set recyclebin.end.schedule=TimerGetElapsed(NOW)+RECYCLE_DELAY
set recyclebin.end= recyclebin( integer(recyclebin.end)+1)
if( recyclebin.end==1) then
call TimerStart(recycler, RECYCLE_DELAY, false, function recyclebin.Recycle)
endif
call SetUnitOwner(this.dummy,Player(15),false)
endif
set this.dummy=null
endmethod
method hiddenDestroy takes nothing returns nothing
call ShowUnit(dummy,false)
call destroy()
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xecast initializer init requires xebasic
//******************************************************************************
// xecast 0.7
// ------
// Because dummy casters REALLY ARE this complicated!
//
//******************************************************************************
//==============================================================================
globals
private constant integer MAXINSTANCES = 8190
//this is a lot, unless you leak xecast objects (which is a bad thing)
private constant integer INITIAL_DUMMY_COUNT = 12
//don't allow to keep more than DUMMY_STACK_LIMIT innactive dummy units :
private constant integer DUMMY_STACK_LIMIT = 50
// If your map does not give visibility to all players, or
// for other reasons, you might want xecast to work on
// units that are not visible to the player, in that case
// change this to true, else it is just a performance loss.
private constant boolean FORCE_INVISIBLE_CAST = false
//When AUTO_RESET_MANA_COOLDOWN is set to true, xecast will reset
// the dummy unit's cooldown and mana before casting every spell.
// it is a performance penalty, so if you are sure that all dummy spells
// in your map got 0 mana and cooldown cost, you may set it to false.
private constant boolean AUTO_RESET_MANA_COOLDOWN = true
endglobals
//=========================================================================
// Please notice all textmacros in this library are considered private.
// in other words: DON'T RUN THOSE TEXTMACROS!
//
private keyword structinit
globals
private real EPSILON=0.001 //noticed in war3 this is the sort of precision we want...
endglobals
struct xecast[MAXINSTANCES]
public integer abilityid = 0 //ID (rawcode) of the ability to cast
public integer level = 1 //Level of the ability to cast
public real recycledelay = 0.0 //Please notice, some spells need a recycle delay
// This is, a time period before they get recycle.
// For example, some spells are not instant, there is
// also the problem with damaging spells, this recycle
// delay must be large enough to contain all the time
// in which the spell can do damage.
public player owningplayer=Player(15) //which player to credit for the ability cast?
//notice this can also affect what units are targeteable
//==================================================================================================
// You need an order id for the ability so the dummy unit is able to cast it, two ways to assign it
// set instance.orderid = 288883 //would assign an integer orderid
// set instance.orderstring = "chainlightning" //would assign an orderstring
// (as those in the object editor)
//
method operator orderid= takes integer v returns nothing
set .oid=v
endmethod
method operator orderstring= takes string s returns nothing
set .oid=OrderId(s)
endmethod
//=================================================================================================
// Finally, you can determine from which point to cast the ability: z is the height coordinate.
//
public boolean customsource=false //Use a custom casting source?
public real sourcex // Determine the casting source for the dummy spell, require customsource =true
public real sourcey // You might prefer to use the setSourcePoint method
public real sourcez=0.0 //
method setSourcePoint takes real x, real y, real z returns nothing
set .sourcex=x
set .sourcey=y
set .sourcez=z
set .customsource=true
endmethod
method setSourceLoc takes location loc, real z returns nothing
set .sourcex=GetLocationX(loc)
set .sourcey=GetLocationY(loc)
set .sourcez=z
set .customsource=true
endmethod
private boolean autodestroy = false
//========================================================================================================
// you are always allowed to use .create() but you can also use createBasic which sets some things that
// are usually necessary up.
//
public static method createBasic takes integer abilityID, integer orderid, player owner returns xecast
local xecast r=xecast.allocate()
if(r==0) then
debug call BJDebugMsg("Warning: unbelievable but you actually used all xecast instances in your map! Please make sure you are not forgetting to destroy those what you create intensively, if that's not the case, then you'll have to increase xecast MAXINSTANCES")
endif
set r.oid=orderid
set r.abilityid=abilityID
set r.owningplayer=owner
return r
endmethod
//========================================================================================================
// Just like the above one, but the instance will self destruct after a call to any cast method
// (recommended)
//
public static method createBasicA takes integer abilityID, integer orderid, player owner returns xecast
local xecast r=xecast.allocate()
if(r==0) then
debug call BJDebugMsg("Warning: unbelievable but you actually used all xecast instances in your map! Please make sure you are not forgetting to destroy those what you create intensively, if that's not the case, then you'll have to increase xecast MAXINSTANCES")
endif
set r.oid=orderid
set r.abilityid=abilityID
set r.owningplayer=owner
set r.autodestroy=true
return r
endmethod
//==========================================================================================================
// Just like create, but the struct instance self destructs after a call to any cast method
// (Recommended)
//
public static method createA takes nothing returns xecast
local xecast r=xecast.allocate()
set r.autodestroy=true
return r
endmethod
//==========================================================================================================
// So, create the dummy, assign options and cast the skill!
// .castOnTarget(w) : If you want to hit a widget w with the ability
// .castOnPoint(x,y) : If you want to hit a point (x,y) with the ability
// .castInPoint(x,y) : For spells like warstomp which do not have a target.
// .castOnAOE(x,y,radius) : Classic area of effect cast. Considers collision size
// .castOnGroup(g) : Cast unit the unit group g, notice it will empty the group yet not destroy it.
//
//**********************************************************************************************************
// The implementation of such methods follows:
private static unit array dummystack
private static integer top=0
private static unit instantdummy
private integer oid=0
private static timer gametime
private static timer T
private static unit array recycle
private static real array expiretime
private static integer rn=0
//==========================================================================================================
// private dorecycle method, sorry but I need this up here.
//
private static method dorecycle takes nothing returns nothing
local unit u =.recycle[0]
local integer l
local integer r
local integer p
local real lt
call UnitRemoveAbility(u,GetUnitUserData(u))
call SetUnitUserData(u,0)
call SetUnitFlyHeight(u,0,0)
call PauseUnit(u,false)
if(.top==DUMMY_STACK_LIMIT) then
call RemoveUnit(u)
else
set .dummystack[.top]=u
set .top=.top+1
endif
set .rn=.rn-1
if(.rn==0) then
return
endif
set p=0
set lt=.expiretime[.rn]
loop
set l=p*2+1
exitwhen l>=.rn
set r=p*2+2
if(r>=.rn)then
if(.expiretime[l]<lt) then
set .expiretime[p]=.expiretime[l]
set .recycle[p]=.recycle[l]
set p=l
else
exitwhen true
endif
elseif (lt<=.expiretime[l]) and (lt<=.expiretime[r]) then
exitwhen true
elseif (.expiretime[l]<.expiretime[r]) then
set .expiretime[p]=.expiretime[l]
set .recycle[p]=.recycle[l]
set p=l
else
set .expiretime[p]=.expiretime[r]
set .recycle[p]=.recycle[r]
set p=r
endif
endloop
set .recycle[p]=.recycle[.rn]
set .expiretime[p]=lt
call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
endmethod
private static trigger abilityRemove
// Repetitive process and no inline implemented for large functions, so for now it is a textmacro:
//! textmacro xecast_allocdummy
if(.recycledelay<EPSILON) then
set dummy=.instantdummy
call SetUnitOwner(dummy,.owningplayer,false)
elseif (.top>0) then
set .top=.top-1
set dummy=.dummystack[.top]
call SetUnitOwner(dummy,.owningplayer,false)
else
set dummy=CreateUnit(.owningplayer,XE_DUMMY_UNITID,0,0,0)
call TriggerRegisterUnitEvent(.abilityRemove,dummy,EVENT_UNIT_SPELL_ENDCAST)
call UnitAddAbility(dummy,'Aloc')
call UnitAddAbility(dummy,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(dummy,XE_HEIGHT_ENABLER)
endif
call UnitAddAbility(dummy, abilityid)
static if AUTO_RESET_MANA_COOLDOWN then
call UnitResetCooldown(dummy)
call SetUnitState(dummy, UNIT_STATE_MANA, 10000.0)
endif
if(level>1) then
call SetUnitAbilityLevel(dummy, abilityid, level)
endif
//! endtextmacro
private static integer cparent
private static integer current
private static real cexpire
//! textmacro xecast_deallocdummy
if(.recycledelay>=EPSILON) then
set .cexpire=TimerGetElapsed(.gametime)+.recycledelay
set .current=.rn
set .rn=.rn+1
loop
exitwhen (.current==0)
set .cparent=(.current-1)/2
exitwhen (.expiretime[.cparent]<=.cexpire)
set .recycle[.current]=.recycle[.cparent]
set .expiretime[.current]=.expiretime[.cparent]
set .current=.cparent
endloop
set .expiretime[.current]=.cexpire
set .recycle[.current]=dummy
call SetUnitUserData(dummy,.abilityid)
call TimerStart(.T, .expiretime[0]-TimerGetElapsed(.gametime), false, function xecast.dorecycle)
else
call SetUnitUserData(dummy,0)
call SetUnitFlyHeight(dummy,0,0)
call UnitRemoveAbility(dummy,.abilityid)
endif
//! endtextmacro
method castOnTarget takes unit target returns nothing
local unit dummy
local unit tar
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,GetWidgetX(target))
call SetUnitY(dummy,GetWidgetY(target))
endif
if (FORCE_INVISIBLE_CAST) then
call UnitShareVision(target, .owningplayer, true)
call IssueTargetOrderById(dummy,this.oid,target)
call UnitShareVision(target, .owningplayer, false)
else
call IssueTargetOrderById(dummy,this.oid,target)
endif
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
//accepts units, items and destructables, if you know it is
// a unit it is better to use castOnTarget since that would
// be able to use FORCE_INVISIBLE_CAST if necessary.
//
method castOnWidgetTarget takes widget target returns nothing
local unit dummy
local unit tar
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,GetWidgetX(target))
call SetUnitY(dummy,GetWidgetY(target))
endif
call IssueTargetOrderById(dummy,this.oid,target)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castOnPoint takes real x, real y returns nothing
local unit dummy
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitX(dummy,.sourcex)
call SetUnitY(dummy,.sourcey)
call SetUnitFlyHeight(dummy,.sourcez,0.0)
else
call SetUnitX(dummy,x)
call SetUnitY(dummy,y)
endif
call IssuePointOrderById(dummy,this.oid,x,y)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castOnLoc takes location loc returns nothing
//debug call BJDebugMsg("Warning: Locations are in use")
//nah but I should
call .castOnPoint(GetLocationX(loc),GetLocationY(loc))
endmethod
//ignores custom source x and y (for obvious reasons)
method castInPoint takes real x, real y returns nothing
local unit dummy
//! runtextmacro xecast_allocdummy()
if (.customsource) then
call SetUnitFlyHeight(dummy,.sourcez,0.0)
endif
call SetUnitX(dummy, x)
call SetUnitY(dummy, y)
call IssueImmediateOrderById(dummy,this.oid)
//! runtextmacro xecast_deallocdummy()
if(.autodestroy ) then
call this.destroy()
endif
endmethod
method castInLoc takes location loc returns nothing
//debug call BJDebugMsg("Warning: Locations are in use")
//nah but I should
call .castInPoint(GetLocationX(loc),GetLocationY(loc))
endmethod
//===================================================================================================
// For method castOnAOE:
//
private static group enumgroup
private static real aoex
private static real aoey
private static real aoeradius
private static xecast cinstance
private static boolexpr aoefunc
// Might look wrong, but this is the way to make it consider collision size, a spell that
// got a target circle and uses this method will let the user know which units it will
// hit with the mass cast.
static method filterAOE takes nothing returns boolean
local unit u=GetFilterUnit()
if IsUnitInRangeXY(u, .aoex, .aoey, .aoeradius) then
call .cinstance.castOnTarget(u)
endif
set u=null
return false
endmethod
//
method castOnAOE takes real x, real y, real radius returns nothing
local boolean ad=this.autodestroy
if(ad) then
set this.autodestroy=false
endif
set .aoex=x
set .aoey=y
set .aoeradius=radius
set .cinstance=this
call GroupEnumUnitsInRange(.enumgroup,x,y,radius + XE_MAX_COLLISION_SIZE , .aoefunc)
if(ad) then
call this.destroy()
endif
endmethod
method castOnAOELoc takes location loc,real radius returns nothing
call .castOnAOE(GetLocationX(loc),GetLocationY(loc),radius)
endmethod
//==================================================================================================
// A quick and dirt castOnGroup method, perhaps it'll later have castOntarget inlined, but not now
//
method castOnGroup takes group g returns nothing
local boolean ad=this.autodestroy
local unit t
if(ad) then
set this.autodestroy=false
endif
loop
set t=FirstOfGroup(g)
exitwhen(t==null)
call GroupRemoveUnit(g,t)
call .castOnTarget(t)
endloop
if(ad) then
call this.destroy()
endif
endmethod
private static method removeAbility takes nothing returns boolean
local unit u=GetTriggerUnit()
if(GetUnitUserData(u)!=0) then
call PauseUnit(u,true)
endif
//This is necessary, picture a value for recycle delay that's higher than the casting time,
//for example if the spell does dps, if you leave the dummy caster with the ability and it
//is owned by an AI player it will start casting the ability on player units, so it is
// a good idea to pause it...
set u=null
return true
endmethod
//===================================================================================================
// structinit is a scope private keyword.
//
static method structinit takes nothing returns nothing
local integer i=INITIAL_DUMMY_COUNT+1
local unit u
set .aoefunc=Condition(function xecast.filterAOE)
set .enumgroup=CreateGroup()
set .abilityRemove = CreateTrigger()
loop
exitwhen (i==0)
set u=CreateUnit(Player(15),XE_DUMMY_UNITID,0,0,0)
call TriggerRegisterUnitEvent(.abilityRemove,u,EVENT_UNIT_SPELL_ENDCAST)
call UnitAddAbility(u,'Aloc')
call UnitAddAbility(u,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(u,XE_HEIGHT_ENABLER)
set .dummystack[.top]=u
set .top=.top+1
set i=i-1
endloop
call TriggerAddCondition(.abilityRemove, Condition(function xecast.removeAbility ) )
set .top=.top-1
set .instantdummy=.dummystack[.top]
set .T=CreateTimer()
set .gametime=CreateTimer()
call TimerStart(.gametime,12*60*60,false,null)
endmethod
endstruct
private function init takes nothing returns nothing
call xecast.structinit()
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library xedamage initializer init requires xebasic
//************************************************************************
// xedamage 0.8
// --------
// For all your damage and targetting needs.
//
//************************************************************************
//===========================================================================================================
globals
private constant integer MAX_SUB_OPTIONS = 3
private constant real FACTOR_TEST_DAMAGE = 0.01
// a low damage to do on units to test their damage factors for specific
// attacktype/damagetype combos.
// You'll need something as high as 20.0 to make it work well with armor resistances.
// (Yes, you read it correctly, 20 ...
//
// If you use such a large value, there may be conflicts with some things relying on damage
// (ie they are not away of the isDummyDamage tag that xedamage posseses.) which may be quite bad if you think about it...
// then maybe it is better to change it to 0.01 ... then will work fine, just fine - but it will generally ignore armor -
// I am leaving it as 0.01 by default, because honestly, I'd rather make it ignore armor than have a lot of people sending me
// rants about how they detect 20.0 damage where none is visible...
private constant real MAX_DAMAGE_FACTOR = 3.00
// The maximum damage factor in the map. I think 3 is fine.
//=======================================================
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
readonly static boolean isDummyDamage = false
//========================================================================================================
// 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
local real fc = FACTOR_TEST_DAMAGE
//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< FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR) then
call SetWidgetLife(u, hp + FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR )
set r = hp + FACTOR_TEST_DAMAGE*MAX_DAMAGE_FACTOR
set fc = GetWidgetLife(u)-hp + EPSILON
endif
set isDummyDamage = true
call UnitDamageTarget(dmger,u, fc ,false,false,a,d,null)
static if DEBUG_MODE then
if IsUnitType(u, UNIT_TYPE_DEAD) and (hp>0.405) then
call BJDebugMsg("xedamage: For some reason, the unit being tested by getDamageTypeFactor has died. Verify MAX_DAMAGE_FACTOR is set to a correct value. ")
endif
endif
set isDummyDamage = false
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)) / fc
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.getTargetFactorCore(source,target,false)!=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
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
if(pl != GetWidgetLife(target) ) then
if(usefx) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
return true
endif
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
local real hp
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
set hp = GetWidgetLife(target)
endif
call UnitDamageTarget(source,target, f*damage, true, .ranged, .atype, .dtype, .wtype )
if(usefx and (hp > GetWidgetLife(target)) ) then
call DestroyEffect( AddSpecialEffectTarget(this.fxpath, target, this.fxattach) )
endif
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
local real hp
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
if(this.usefx) then
set hp =GetWidgetLife(target)
endif
call UnitDamageTarget(.sourceAOE,target, f*this.AOEdamage, true, .ranged, .atype, .dtype, .wtype )
if(this.usefx and (hp > GetWidgetLife(target) ) ) 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=133
//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 = true // Requires DamageEvent.
private constant boolean USE_ATTACK_EVENTS = true
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=99
//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=133
//TESH.alwaysfold=0
'Deadly Seduction'
- How to Import:
- Copy the Spell called 'Deadly Seduction' and create 2 new Buffs, the first for
your the Mainability, the second for the DiseaseCloud effect
- Remove the exclamation in front of the ObjectMerger execution.
Save the map, reopen it and put the exclamations again to remove saving delay.
- Copy the whole Triggerscript into your map
- Import the 'Dummy.mdx' included in that map to your map
- Create a dummy unit and change the model into the 'Dummy.mdx'
- Now change the values of the 'ABILID', 'DUMMYID', 'DISEASEID' and 'BUFFID'
to the Rawcodes of the Spells, Buff and Dummy in your map
- Required Libraries:
- TimerUtils by 'Vexorian'
- Table by 'Vexorian'
- GroupUtils by 'Rising_Dusk'
- SpellEvent by 'Anitarf'
- LastOrder by 'Rising_Dusk'
- ABuff by 'Anitarf'
- Spelldescription:
The Caster infects the Target Unit with a deadly virus, that makes the Target lose
his mind and even attack his allies. Each time the Target gets a new Order from
its Owner, the virus destroys the Targets cells and force it to attack his allies.
If the Target dies, nearby allied units get infected by the Poison flodding out of
the Targts dead body. The Poison deals damage over time. If the Infected moves the
Poison destroys the body faster and deals doubled damage. If an Infected unit dies,
its dead body releases a deadly Cloud of poison that deals damage to allied units
nearby.
'Dark Curse'
- How to Import:
- Copy the Spell called 'Dark Curse' and create a Buff for your Effects
- Remove the exclamation in front of the ObjectMerger execution.
Save the map, reopen it and put the exclamations again to remove saving delay.
- Copy the whole Triggerscript into your map
- Import the 'Dummy.mdx' included in that map to your map
- Create a dummy unit and change the model into the 'Dummy.mdx'
- Now change the values of the 'ABILID', 'DUMMYID', 'SHRINKID' and 'BUFFID'
to the Rawcodes of the Spells, Buff and Dummy in your map
- Required Libraries:
- TimerUtils by 'Vexorian'
- LastOrder by 'Rising_Dusk'
- UnitStatus by 'Rising_Dusk'
- Table by 'Vexorian'
- SpellEvent by 'Anitarf'
- ABuff by 'Anitarf'
- xecast by 'Vexorian'
- xepreload by 'Vexorian'
- Spelldescription:
The Caster curses the Target with Dark Magic reducing its Size.
With this Size reduction, the damage taken is amplified and its Movement- and
Attackspeed is reduced, but is harder to hit.
Each time the Target takes damage its head is shattered, which makes it unable to
attack and confuses it. Each time the Target gets an Point Order within that
Confusion it will execute that Order in the complete other direction.
'Haunting Ghosts'
- How To Import:
- Copy the Spell called 'Haunting Ghosts' into your map
- Copy the whole Triggerscript into your map
- Now change the values of the 'ABILID' and 'DECAYTIME' to the Values in your map
- Required Libraries:
- TimerUtils by 'Vexorian'
- GroupUtils by 'Rising_Dusk'
- UnitMaxState by 'Earth-Fury'
- AutoIndex by 'grim001'
- StatusModifier by 'Inferior'
- Rounding by 'Inferior'
- TimedHandles by 'TriggerHappy187'
- SpellEvent by 'Anitarf'
- Spelldescription:
The Caster owns over the souls of the dead units nearby. For each Unit the Killer
of that unit gains a life and damage bonus over a short duration. If the Killer is
an enemy of the Caster its MaxLife is reduced and its damage aswell. If the Killer
is an ally its MaxLife and damage are increased. The MaxLife of the Killer
regenerates to its default after a specific duration. The regeneration has its
own duration. The more units were killed by one unit the higher is the bonus the
Killer gains.
'Polarisation'
- How To Import:
- Copy the Spell called 'Polarisation' into your map
- Copy the whole Triggerscript into your map
- Now change the values of the 'ABILID' to the Rawcode of the Mainability in your map
- Required Libraries:
- TimerUtils by 'Vexorian'
- UnitStatus by 'Rising_Dusk'
- SpellEvent by 'Anitarf'
- Spelldescription:
The Caster creates a static link between the target and himself absorbing the
Targets life and converting it to Mana. The Target and the caster are pulled
to the middle between their positions, while the target is disabled. The Health is
absorbed during the pullphase. After they reached each other the Caster keeps up the
static link between him and the target. The target is now unable to move farther
away than the link allows him.
'Magnetic Field'
- How To Import:
- Copy the Spell called 'Magnetic Field' and 'Magnet Aura(Slow)' into your map and
create 2 buffs like those in the Object Editor with 'Magnetic Field' in its name
- Copy the whole Triggerscript into your map
- Now change the values of the 'ABILID' and 'SLOWID' to the RawCodes in your map
- Required Libraries:
- TimerUtils by 'Vexorian'
- GroupUtils by 'Rising_Dusk'
- ArmorUtils by 'Rising_Dusk'
- StatusModifier by 'Inferior'
- Rounding by 'Inferior'
- BoundSentinel by 'Vexorian'
- UnitStatus by 'Rising_Dusk'
- IsUnitSpellResistant by 'Anitarf'
- TimedHandles by 'TriggerHappy187'
- xepreload by 'Vexorian'
- xedamage by 'Vexorian'
- Spelldescription:
The Caster fills the air arround him with strong static energy, creating a strong
magnetic Field arround his body that slows every enemy unit and slightly pulls them
to the caster. If an enemy unit comes in contact with the caster, the unit gets
shocked, losing an specific amount of its current Life and half of its armor, and
pushed away. For each contact the caster loses Mana and passively burns Mana over
Time as long as the Magnetic Field is active.
'Robot Arms'
- How To Import:
- Copy the Spell called 'Robot Arms', the dummy called facingdummy into your map
and import the "fdummy.mdl" and "RoboticArmsHand.mdl" included in this map into yours
- Copy the whole Triggerscript into your map
- Change the model of your new Dummymodel and change the RawCodes of the ABILID
and CHAINID to the Rawcodes in your map
- Required Libraries:
- Table by 'Vexorian'
- GroupUtils by 'Rising_Dusk'
- TimerUtils by 'Vexorian'
- ABuff by 'Anitarf'
- ABuffHeroSkill by 'Anitarf'
- BoundSentinel by 'Vexorian'
- UnitStatus by 'Rising_Dusk'
- IsUnitSpellResistant by 'Anitarf'
- xebasic by 'Vexorian'
- xefx by 'Vexorian'
- xedamage by 'Vexorian'
- Rounding by 'Inferior'
- Spelldescription:
The Electrician uses his technical knowlegdes to create 3 Robot Arms that act on
their own. If the Arm detects an enemy, it moves to that enemy and knocks it out on
contact, dealing absorbing the enemies Life. If the Electrician moves out of the
enemies Range the Arm moves back. After every action, the Arms have to move back into
their default Position.
//TESH.scrollpos=273
//TESH.alwaysfold=0
library DeadlySeduction initializer Init needs TimerUtils, Table, GroupUtils, SpellEvent, LastOrder, ABuff
//*******************************************************************************************\\
//*******************************************************************************************\\
//* Deadly Seducetion By Inferior *\\
//* *\\
//* v1.1 *\\
//* *\\
//* ************************* *\\
//* * Requirements: * *\\
//* * - JassNewGenPack * *\\
//* * - TimerUtils * *\\
//* * - Table * *\\
//* * - GroupUtils * *\\
//* * - SpellEvent * *\\
//* * - LastOrder * *\\
//* * - ABuff * *\\
//* ************************* *\\
//* *\\
//* *\\
//* *\\
//* How To Import: *\\
//* - Copy the Spell called 'Deadly Seduction' and create 2 new Buffs, the first for *\\
//* your the Mainability, the second for the DiseaseCloud effect *\\
//* - Remove the exclamation in front of the ObjectMerger execution. *\\
//* Save the map, reopen it and put the exclamations again to remove saving delay. *\\
//* - Copy the whole Triggerscript into your map *\\
//* - Import the 'Dummy.mdx' included in that map to your map *\\
//* - Create a dummy unit and change the model into the 'Dummy.mdx' *\\
//* - Now change the values of the 'ABILID', 'DUMMYID', 'DISEASEID' and 'BUFFID *\\
//* to the Rawcodes of the Spells, Buff and Dummy in your map *\\
//* *\\
//* Spelldescription: *\\
//* The Caster infects the Target Unit with a deadly virus, that makes the Target lose *\\
//* his mind and even attack his allies. Each time the Target gets a new Order from *\\
//* its Owner, the virus destroys the Targets cells and force it to attack his allies. *\\
//* If the Target dies, nearby allied units get infected by the Poison flodding out of *\\
//* the Targts dead body. The Poison deals damage over time. If the Infected moves the *\\
//* Poison destroys the body faster and deals doubled damage. If an Infected unit dies, *\\
//* its dead body releases a deadly Cloud of poison that deals damage to allied units *\\
//* nearby. *\\
//* *\\
//*******************************************************************************************\\
//*******************************************************************************************\\
// Globals Setting
// //! external ObjectMerger w3a ANpi DisC anam DiseaseCloud alev 3 Eim1 1 1.0 Eim1 2 1.0 Eim1 3 1.0 ahdu 1 0.1 ahdu 2 0.1 ahdu 3 0.1 adur 1 0.1 adur 2 0.1 adur 3 0.1 aare 1 250. aare 2 250. aare 3 250. atar 1 ground,enemies,neutral,organic atar 2 ground,friend,neutral,organic atar 3 ground,enemies,friend,neutral,organic
globals
private constant integer ABILID = 'A008'
// The AbilityId of the MainAbility
private constant integer DUMMYID = 'e000'
// The UnitId of the DummyUnit ( Uses imported Dummymodel ) ; you may use your own dummy here
private constant integer DISEASEID = 'DisC'
// The AbilityId of the DiseaseCloud ( Based on 'Permanent Immolation' )
private constant integer BUFFID = 'B000'
// The Rawcode of the Buff
private constant real DURATION = 2.5
// The Base Duration for the MainAbility
private constant real DETECTRADIUS = 500.
// The Radius in which the possible Targets for the MainAbility shall be detected
private constant real INFECTDURATION = 10.
// The Duration of the Infection after the Explosion of the Main Target
private constant real INFECTPERIOD = .02
// The Period of the Infection Timer
private constant real EXPLODEFACTOR = .1
// The Factor of the Targets MAX_HP at which it explodes if it falls below
private constant real HEALTHFACTOR = .01
// The Factor of the Targets CURRENT_HP that is dealt as Damage per INFECTPERIOD
private constant real PAINDAMAGE = .01
// The Factor of the Targets MAX_HP that is dealt as Damage if its AttackOrder gets interrupted
private constant real INSTANTKILL = 10000000.
// The value to instantly kill an Unit. Insert here any value that is big enough to kill any Unit on your map
private constant real EXPLODERADIUS = 350.
// The Radius in which Units get Infected after the Main Target explodes
private constant boolean INFECTENEMIES = false
// The value to check the possible Targets for the Infection
private constant boolean INFECTALLIES = true
// The value to check the possible Targets for the Infection
// The 2 values above are weird, because INFECTENEMIES means the enemies of the Main Target
// and INFECTALLIES means the allies of the Main Target
private constant string INFECTCLOUD = "Abilities\\Spells\\Undead\\PlagueCloud\\PlagueCloudCaster.mdl"
// The Effect for the Dummy
private constant string INFECTTARGET = "Units\\Undead\\PlagueCloud\\PlagueCloudtarget.mdl"
// The Effect attached to the Infected Units
private constant string INFECTATTACH = "head"
// The AttachPoint for the TargetEffect
private constant string PAINEFFECT = "Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodRifleman.mdl"
// The Effect attached to the Main Target when it feels Pain ( interrupts AttackOrder )
private constant string PAINATTACH = "origin"
// The AttachPoint for the PAINEFFECT or the DummyEffect
// The following values only refer to all Damage that is dealt in this Spell
private constant boolean DAMAGEDMEELE = true
private constant boolean DAMAGEDRANGED = false
private constant attacktype ATTACKTYPE = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGETYPE = DAMAGE_TYPE_MAGIC
private constant weapontype WEAPONTYPE = WEAPON_TYPE_WHOKNOWS
// DO NOT CHANGE THESE VALUES
private constant integer ATTACKORDER = 851983
// The OrderId for common Attack Order
private constant integer STOPORDER = 851972
// The OrderId for common Stop Order
private constant integer DISARMBUFF = '&ARM'
// Do not change any of the following variables
private unit TEMPUNIT
endglobals
// function that returns the Duration of the Main Ability for each level
private constant function GetRealDuration takes integer level returns real
return DURATION+level
endfunction
// DO NOT TOUCH ANYTHING BELOW THIS COMMENT, ALTHOUGH YOU KNOW WHAT YOU DO.
// HERE BEGINS THE SPELLS SCRIPT.
private keyword Infection // DO NOT CHANGE THIS
// DO NOT CHANGE THIS FUNCTION
// this function returns an important value
private constant function GetDiseaseLevel takes nothing returns integer
if INFECTENEMIES and INFECTALLIES then
return 3
elseif INFECTENEMIES and not INFECTALLIES then
return 2
elseif not INFECTENEMIES and INFECTALLIES then
return 1
else
return 0
endif
endfunction
// the Filter for the Units for the MainAbility
private function GroupFilter takes nothing returns boolean
return IsUnitAlly(GetFilterUnit(),GetOwningPlayer(TEMPUNIT)) and GetWidgetLife(GetFilterUnit())>0.405 and IsUnitType(GetFilterUnit(),UNIT_TYPE_GROUND) and GetFilterUnit()!=TEMPUNIT
endfunction
// DO NOT CHANGE THIS FUNCTION
// you may add more Filters if you want but do not remove any of those
private function InfectionFilter takes nothing returns boolean
if INFECTENEMIES and not INFECTALLIES then
return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(TEMPUNIT)) and GetWidgetLife(GetFilterUnit())>0.405 and Infection.InfectTable[GetFilterUnit()]==0 and GetFilterUnit()!=TEMPUNIT
elseif not INFECTENEMIES and INFECTALLIES then
return IsUnitAlly(GetFilterUnit(),GetOwningPlayer(TEMPUNIT)) and GetWidgetLife(GetFilterUnit())>0.405 and Infection.InfectTable[GetFilterUnit()]==0 and GetFilterUnit()!=TEMPUNIT
elseif INFECTENEMIES and INFECTALLIES then
return GetWidgetLife(GetFilterUnit())>0.405 and Infection.InfectTable[GetFilterUnit()]==0 and GetFilterUnit()!=TEMPUNIT
else
return false
endif
endfunction
//**********************************************//
// Struct for the Infection //
//**********************************************//
private struct Infection
unit infected
unit caster
real dps
real duration
real x
real y
effect smoke
static integer count = 0
static timer InfectTimer
static boolean array active
static Infection array Data
static HandleTable InfectTable
// Deals Damage over Time and checks if the Infected unit moved.
// If it moved it deals doubled Damage
static method Disease takes nothing returns nothing
local integer int=0
local unit dum
local effect e
loop
exitwhen int>=.count
if .active[int] then
if .Data[int].duration>=0 then
// Checks if the Infected moved. If it did it takes doubled Damage
if GetUnitX(.Data[int].infected)==.Data[int].x and GetUnitY(.Data[int].infected)==.Data[int].y then
call UnitDamageTarget(.Data[int].caster,.Data[int].infected,.Data[int].dps*INFECTPERIOD*2,DAMAGEDMEELE,DAMAGEDRANGED,ATTACKTYPE,DAMAGETYPE,WEAPONTYPE)
else
call UnitDamageTarget(.Data[int].caster,.Data[int].infected,.Data[int].dps*2*INFECTPERIOD*2,DAMAGEDMEELE,DAMAGEDRANGED,ATTACKTYPE,DAMAGETYPE,WEAPONTYPE)
endif
set .Data[int].x=GetUnitX(.Data[int].infected)
set .Data[int].y=GetUnitY(.Data[int].infected)
set .Data[int].duration=.Data[int].duration-INFECTPERIOD
// Checks if the Infected is still alive or not.
// If it is, it creates a Cloud that deals damage to nearby units.
if GetWidgetLife(.Data[int].infected)<0.405 then
set dum=CreateUnit(GetOwningPlayer(.Data[int].caster),DUMMYID,GetUnitX(.Data[int].infected),GetUnitY(.Data[int].infected),0)
call UnitAddAbility(dum,DISEASEID)
call SetUnitAbilityLevel(dum,DISEASEID,GetDiseaseLevel())
call AddSpecialEffectTarget(INFECTCLOUD,dum,PAINATTACH)
call UnitApplyTimedLife(dum,'Bapl',GetRealDuration(GetUnitAbilityLevel(.Data[int].caster,ABILID)))
call DestroyEffect(.Data[int].smoke)
set .active[int]=false
call .Data[int].destroy()
endif
else
set .InfectTable[.Data[int].infected]=0
call DestroyEffect(.Data[int].smoke)
set .active[int]=false
call .Data[int].destroy()
endif
endif
set int=int+1
endloop
endmethod
static method create takes nothing returns Infection
local Infection data=Infection.allocate()
if .count==0 then
set .InfectTimer=NewTimer()
call TimerStart(.InfectTimer,INFECTPERIOD,true,function Infection.Disease)
endif
set .active[.count]=true
set .Data[.count]=data
set .count=.count+1
return data
endmethod
endstruct
//**********************************************//
// Struct for the Main Spell //
//**********************************************//
private struct Spell
unit caster
unit target
unit victim
timer Time
timer Dead
boolean Infection
static trigger Order
static HandleTable SpellTable
static method Infect takes nothing returns nothing
local Infection data
if Infection.InfectTable[GetEnumUnit()]==0 then
set data=Infection.create()
set data.infected=GetEnumUnit()
set data.caster=TEMPUNIT
set data.x=GetUnitX(data.infected)
set data.y=GetUnitY(data.infected)
set data.dps=GetWidgetLife(data.infected)*HEALTHFACTOR
set data.smoke=AddSpecialEffectTarget(INFECTTARGET,data.infected,INFECTATTACH)
set data.duration=INFECTDURATION
set Infection.InfectTable[data.infected]=data
else
set data.duration=INFECTDURATION
endif
endmethod
// The method for the Spells End. Picks nearby units and infects them.
static method EndSpell takes nothing returns nothing
local Spell data=GetTimerData(GetExpiredTimer())
if data.Infection then
set TEMPUNIT=data.caster
call GroupEnumUnitsInArea(ENUM_GROUP,GetUnitX(data.target),GetUnitY(data.target),EXPLODERADIUS,Condition(function InfectionFilter))
call UnitDamageTarget(data.caster,data.target,INSTANTKILL,DAMAGEDMEELE,DAMAGEDRANGED,ATTACKTYPE,DAMAGETYPE,WEAPONTYPE)
call ForGroup(ENUM_GROUP,function Spell.Infect)
call GroupClear(ENUM_GROUP)
else
call SetUnitExploded(data.target,false)
endif
set Spell.SpellTable[data.target]=0
call IssueImmediateOrderById(data.target,STOPORDER)
call UnitRemoveAbility(data.target,BUFFID)
call ReleaseTimer(data.Dead)
call ReleaseTimer(data.Time)
set data.Dead=null
set data.Time=null
set data.target=null
set data.caster=null
set data.victim=null
set data.Infection=false
call data.destroy()
endmethod
// Checks if the Target Unit is dead or not. Checks if the allied target that is attacked(victim) is dead or not.
// If the victim is dead it searchs for a new one. If there is none the Spell ends and the virus is destroyed.
static method CheckDead takes nothing returns nothing
local Spell data=GetTimerData(GetExpiredTimer())
if GetWidgetLife(data.target)>GetUnitState(data.target,UNIT_STATE_MAX_LIFE)*EXPLODEFACTOR then
if GetWidgetLife(data.victim)<0.405 then
set TEMPUNIT=data.caster
call GroupEnumUnitsInArea(ENUM_GROUP,GetUnitX(data.target),GetUnitY(data.target),DETECTRADIUS,Condition(function GroupFilter))
set data.victim=GroupPickRandomUnit(ENUM_GROUP)
if data.victim==data.target then
call GroupRemoveUnit(ENUM_GROUP,data.victim)
set data.victim=GroupPickRandomUnit(ENUM_GROUP)
endif
call GroupClear(ENUM_GROUP)
if data.victim!=null then
call IssueTargetOrderById(data.target,ATTACKORDER,data.victim)
else
call PauseTimer(data.Time)
call PauseTimer(data.Dead)
call TimerStart(data.Time,0.0,false,function Spell.EndSpell)
endif
endif
else
set data.Infection=true
call PauseTimer(data.Time)
call PauseTimer(data.Dead)
call TimerStart(data.Time,0.0,false,function Spell.EndSpell)
endif
endmethod
// Checks if the target is ordered another order than the Attackorder and if the ordered unit is equal to victim.
// There is one thing to mention. If the target is unable to attack, that means it does not have attack ability
// or it is disarmed, then this spell has no effect and is removed from the Target
// E.g. you cast Deadly Seduction on a target and after that Dark Curse and attack it. Its ability to attack is removed
// and then the Effect of this Spell is removed aswell. That means any disarm abilities have a higher priority than this
// Spell. And I definitly recommend not to remove the 'attack' Ability other than with disarms!!!!
static method CheckOrder takes nothing returns boolean
local unit ordered=GetOrderedUnit()
local Spell data
if GetUnitAbilityLevel(ordered,BUFFID)>0 then
if GetUnitAbilityLevel(ordered,DISARMBUFF)==0 then
set data=Spell.SpellTable[ordered]
if GetWidgetLife(data.target)>GetUnitState(data.target,UNIT_STATE_MAX_LIFE)*EXPLODEFACTOR then
if GetWidgetLife(data.victim)>0.405 then
if GetOrderTargetUnit()!=data.victim or GetLastOrderId(data.target)!=ATTACKORDER then
call IssueTargetOrderById(data.target,ATTACKORDER,data.victim)
call UnitDamageTarget(data.caster,data.target,GetUnitState(data.target,UNIT_STATE_MAX_LIFE)*PAINDAMAGE,DAMAGEDMEELE,DAMAGEDRANGED,ATTACKTYPE,DAMAGETYPE,WEAPONTYPE)
call DestroyEffect(AddSpecialEffectTarget(PAINEFFECT,data.target,PAINATTACH))
endif
else
set TEMPUNIT=data.caster
call GroupEnumUnitsInArea(ENUM_GROUP,GetUnitX(data.target),GetUnitY(data.target),DETECTRADIUS,Condition(function GroupFilter))
set data.victim=GroupPickRandomUnit(ENUM_GROUP)
if data.victim==data.target then
call GroupRemoveUnit(ENUM_GROUP,data.victim)
set data.victim=GroupPickRandomUnit(ENUM_GROUP)
endif
call GroupClear(ENUM_GROUP)
if data.victim!=null then
call IssueTargetOrderById(data.target,ATTACKORDER,data.victim)
else
call PauseTimer(data.Time)
call PauseTimer(data.Dead)
call TimerStart(data.Time,0.0,false,function Spell.EndSpell)
endif
endif
else
set data.Infection=true
call PauseTimer(data.Time)
call PauseTimer(data.Dead)
call TimerStart(data.Time,0.0,false,function Spell.EndSpell)
endif
else
call PauseTimer(data.Time)
call PauseTimer(data.Dead)
call TimerStart(data.Time,0.0,false,function Spell.EndSpell)
endif
endif
return false
endmethod
endstruct
// Buff declaration
globals
private aBuffType buffType = 0
endglobals
// executed when the Buff is removed anyhow, e.g. with a dispel
private function Dispel takes aBuff buffType returns nothing
local Spell data=buffType.data
if GetUnitAbilityLevel(data.target,BUFFID)==0 then
set data.Infection=false
call PauseTimer(data.Time)
call PauseTimer(data.Dead)
call TimerStart(data.Time,0.0,false,function Spell.EndSpell)
call ABuffRemove(buffType)
endif
endfunction
// executed Function on Spellcast. Checks if there is an ally of the target unit.
// If there is none the spell has no effect.
private function SeductionSpell takes nothing returns nothing
local Spell data
if Spell.SpellTable[SpellEvent.TargetUnit]==0 then
set data=Spell.create()
set data.caster=SpellEvent.CastingUnit
set data.target=SpellEvent.TargetUnit
set data.Time=NewTimer()
set data.Dead=NewTimer()
set data.Infection=false
call SetTimerData(data.Time,data)
call SetTimerData(data.Dead,data)
set TEMPUNIT=data.caster
call GroupEnumUnitsInArea(ENUM_GROUP,GetUnitX(data.target),GetUnitY(data.target),DETECTRADIUS,Condition(function GroupFilter))
set data.victim=GroupPickRandomUnit(ENUM_GROUP)
if data.victim==data.target then
call GroupRemoveUnit(ENUM_GROUP,data.victim)
set data.victim=GroupPickRandomUnit(ENUM_GROUP)
endif
call GroupClear(ENUM_GROUP)
set TEMPUNIT=null
if data.victim!=null then
call ABuffApply(buffType,data.target,data.caster,GetRealDuration(GetUnitAbilityLevel(data.caster,ABILID)),GetUnitAbilityLevel(data.caster,ABILID),data)
call SetUnitExploded(data.target,true)
call IssueTargetOrderById(data.target,ATTACKORDER,data.victim)
set Spell.SpellTable[data.target]=data
call TimerStart(data.Time,GetRealDuration(GetUnitAbilityLevel(data.caster,ABILID)),false,function Spell.EndSpell)
call TimerStart(data.Dead,0.01,true,function Spell.CheckDead)
else
call UnitRemoveAbility(data.target,BUFFID)
call ReleaseTimer(data.Time)
call ReleaseTimer(data.Dead)
call data.destroy()
endif
else
set data=Spell.SpellTable[SpellEvent.TargetUnit]
call ABuffApply(buffType,data.target,data.caster,GetRealDuration(GetUnitAbilityLevel(data.caster,ABILID)),GetUnitAbilityLevel(data.caster,ABILID),data)
call PauseTimer(data.Time)
call TimerStart(data.Time,GetRealDuration(GetUnitAbilityLevel(data.caster,ABILID)),false,function Spell.EndSpell)
endif
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(ABILID,SeductionSpell)
set Spell.SpellTable=HandleTable.create()
set Infection.InfectTable=HandleTable.create()
set Spell.Order=CreateTrigger()
set buffType = aBuffType.create()
set buffType.eventPeriodic = Dispel
call Preload(INFECTCLOUD)
call Preload(INFECTTARGET)
call Preload(PAINEFFECT)
call PreloadStart()
call TriggerRegisterAnyUnitEventBJ(Spell.Order,EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerRegisterAnyUnitEventBJ(Spell.Order,EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(Spell.Order,EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerAddCondition(Spell.Order,Condition(function Spell.CheckOrder))
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library DarkCurse initializer Init needs TimerUtils, LastOrder, UnitStatus, Table, SpellEvent, ABuff, xecast, xepreload
//*******************************************************************************************\\
//*******************************************************************************************\\
//* Dark Curse By Inferior *\\
//* *\\
//* v1.0 *\\
//* *\\
//* ************************* *\\
//* * Requirements: * *\\
//* * - JassNewGenPack * *\\
//* * - TimerUtils * *\\
//* * - LastOrder * *\\
//* * - UnitStatus * *\\
//* * - Table * *\\
//* * - SpellEvent * *\\
//* * - ABuff * *\\
//* * - xecast * *\\
//* * - xepreload * *\\
//* ************************* *\\
//* *\\
//* *\\
//* *\\
//* How To Import: *\\
//* - Copy the Spell called 'Dark Curse' and create a Buff for your Effects *\\
//* - Remove the exclamation in front of the ObjectMerger execution. *\\
//* Save the map, reopen it and put the exclamations again to remove saving delay. *\\
//* - Copy the whole Triggerscript into your map *\\
//* - Import the 'Dummy.mdx' included in that map to your map *\\
//* - Create a dummy unit and change the model into the 'Dummy.mdx' *\\
//* - Now change the values of the 'ABILID', 'DUMMYID', 'SHRINKID' and 'BUFFID *\\
//* to the Rawcodes of the Spells, Buff and Dummy in your map *\\
//* *\\
//* Spelldescription: *\\
//* The Caster curses the Target with Dark Magic reducing its Size. *\\
//* With this Size reduction, the damage taken is amplified and its Movement- and *\\
//* Attackspeed is reduced, but is harder to hit. *\\
//* Each time the Target takes damage its head is shattered, which makes it unable to *\\
//* attack and confuses it. Each time the Target gets an Point Order within that *\\
//* Confusion it will execute that Order in the complete other direction. *\\
//* *\\
//*******************************************************************************************\\
//*******************************************************************************************\\
// Globals Setting
// //! external ObjectMerger w3a Ablo ShrA anam "Shrink/Slow" alev 4 areq null arqa 0 Blo1 1 -0.2 Blo1 2 -0.3 Blo1 3 -0.4 Blo1 4 -0.5 Blo2 1 -0.05 Blo2 2 -0.1 Blo2 3 -0.15 Blo2 4 -0.2 Blo3 1 -0.1 Blo3 2 -0.15 Blo3 3 -0.2 Blo3 4 -0.25 acdn 1 0.0 ahdu 1 4. ahdu 2 6. ahdu 3 8. ahdu 4 10. adur 1 2. adur 2 3. adur 3 4. adur 4 5. amcs 1 0 aran 1 9999.0 aran 2 9999.0 aran 3 9999.0 aran 4 9999.0 abuf 1 Bblo abuf 2 Bblo abuf 3 Bblo abuf 4 Bblo atar 1 "ground,enemies,air,neutral,organic" atar 2 "ground,enemies,air,neutral,organic" atar 3 "ground,enemies,air,neutral,organic" atar 4 "ground,enemies,air,neutral,organic"
// //! external ObjectMerger w3a ANth $Def anam "DefenseFactor" Uts2 1 1.5 Uts1 1 0
// //! external ObjectMerger w3a ANde $Atk anam "DamageFactor" Nde2 1 0.5 Nde3 1 0.5 Nde4 1 0.5 aher 0
// //! external ObjectMerger w3a ACev $Eva anam "EvasionFactor" Eev1 1 0.75
// //! external ObjectMerger w3a Aspb Book anam "CurseEffectBook" spb2 1 0 spb1 1 EvaA,AtkR,DefR aite 0
globals
private constant integer ABILID = 'A001'
// The AbilityId of the Mainability
private constant integer SHRINKID = 'ShrA'
// The AbilityId of the Slow- and Shrinkability
private constant integer BUFFID = 'B001'
// The Buff of the Slow and Shrink
private constant integer EFFECTBOOK = 'Book'
// The Spellbook which contains the Evasion and Damageamplify Abilities
private constant integer DUMMYID = 'e000'
// The Id of your Dummyunit
private constant integer BLOODLUST = 852101
// The casting Order for the Slow and Shrink ( Here based on bloodlust )
private constant integer STOPORDER = 851972
// The common StopOrder
private constant real GROWTHDELAY = 1.
// The time the Target is growing. Will influence the possible duration to confuse the Target. Set this to 0
// if you want it possible until the Target regains its default size
private constant string CONFUSED = "Abilities\\Spells\\Items\\HealingSalve\\HealingSalveTarget.mdl"
private constant string CONFUSEATTACH = "origin"
// The First Confusion Effect
private constant string CONFUSEEFFECT = "Abilities\\Spells\\Orc\\StasisTrap\\StasisTotemTarget.mdl"
private constant string EFFECTATTACH = "overhead"
// The Second Confusion Effect
// For Descriptions for the following Variables see below
private real array DURATION
private real array HERODURATION
private real array CONFUSEDTIME
endglobals
private function SetupSpellVariables takes nothing returns nothing
// The duration for normal targets
set DURATION[1]=4.-GROWTHDELAY
set DURATION[2]=6.-GROWTHDELAY
set DURATION[3]=8.-GROWTHDELAY
set DURATION[4]=10.-GROWTHDELAY
// The duration for Heros
set HERODURATION[1]=2.-GROWTHDELAY
set HERODURATION[2]=3.-GROWTHDELAY
set HERODURATION[3]=4.-GROWTHDELAY
set HERODURATION[4]=5.-GROWTHDELAY
// The duration a unit is confused and disarmed
set CONFUSEDTIME[1]=0.75
set CONFUSEDTIME[2]=1.25
set CONFUSEDTIME[3]=1.75
set CONFUSEDTIME[4]=2.5
endfunction
// DO NOT TOUCH ANYTHING BELOW THIS COMMENT, ALTHOUGH YOU KNOW WHAT YOU DO.
// HERE BEGINS THE SPELLS SCRIPT.
private struct DarkCurse
unit caster
unit target
integer lvl
trigger damage
trigger order
timer end
timer disarm
boolean disarmed
boolean moved
effect confused
effect head
static HandleTable CurseTable
// Ends the Spell
static method EndSpell takes nothing returns nothing
local DarkCurse data=.CurseTable[GetExpiredTimer()]
call UnitRemoveAbility(data.target,EFFECTBOOK)
call ReleaseTimer(data.end)
call ReleaseTimer(data.disarm)
call DestroyTrigger(data.damage)
call DestroyTrigger(data.order)
call DestroyEffect(data.confused)
call DestroyEffect(data.head)
set data.disarmed=false
set data.moved=false
set data.lvl=0
call data.destroy()
endmethod
// Removes the Disarm and Confusion
static method RemoveDisarm takes nothing returns nothing
local DarkCurse data=.CurseTable[GetExpiredTimer()]
call DestroyTrigger(data.order)
set data.disarmed=false
call ReleaseTimer(GetExpiredTimer())
call DestroyEffect(data.confused)
call DestroyEffect(data.head)
endmethod
// Changes the LastOrders Coordinates
static method ConfusedMove takes nothing returns boolean
local DarkCurse data=.CurseTable[GetTriggeringTrigger()]
local real x=GetUnitX(data.target)
local real y=GetUnitY(data.target)
local real dx=x-GetOrderPointX()
local real dy=y-GetOrderPointY()
set x=x+dx
set y=y+dy
if not data.moved then
call AbortOrder(data.target)
set data.moved=true
call IssuePointOrderById(data.target,GetLastOrderId(data.target),x,y)
else
set data.moved=false
endif
return false
endmethod
// Executed when the target is damaged. If the damage taken is higher than 0 it is disabled and confused
static method DisarmTarget takes nothing returns boolean
local DarkCurse data=.CurseTable[GetTriggeringTrigger()]
if GetEventDamage()>0 then
if not data.disarmed then
call DisarmUnitTimed(data.target,CONFUSEDTIME[data.lvl])
set data.disarm=NewTimer()
call TimerStart(data.disarm,CONFUSEDTIME[data.lvl],false,function DarkCurse.RemoveDisarm)
set .CurseTable[data.disarm]=data
set data.order=CreateTrigger()
call TriggerRegisterUnitEvent(data.order,data.target,EVENT_UNIT_ISSUED_POINT_ORDER)
call TriggerAddCondition(data.order,Condition(function DarkCurse.ConfusedMove))
set .CurseTable[data.order]=data
set data.disarmed=true
set data.moved=false
set data.confused=AddSpecialEffectTarget(CONFUSED,data.target,CONFUSEATTACH)
set data.head=AddSpecialEffectTarget(CONFUSEEFFECT,data.target,EFFECTATTACH)
endif
endif
return false
endmethod
static method create takes nothing returns DarkCurse
local DarkCurse data=DarkCurse.allocate()
return data
endmethod
endstruct
// Buff Declaration
globals
private aBuffType buffType = 0
endglobals
// executed when the Buff is removed anyhow, e.g. with a dispel
private function Dispel takes aBuff buffType returns nothing
local DarkCurse data=buffType.data
if GetUnitAbilityLevel(data.target,BUFFID)==0 then
call PauseTimer(data.end)
call TimerStart(data.end,0.0,false,function DarkCurse.EndSpell)
call ABuffRemove(buffType)
endif
endfunction
// Executed on Spellcast
private function CurseSpell takes nothing returns nothing
local unit caster=SpellEvent.CastingUnit
local unit target=SpellEvent.TargetUnit
local integer lvl=GetUnitAbilityLevel(caster,ABILID)
local xecast dummy
local DarkCurse data
if GetUnitAbilityLevel(target,BUFFID)==0 then
call UnitAddAbility(target,EFFECTBOOK)
set dummy=xecast.createBasicA(SHRINKID,BLOODLUST,GetOwningPlayer(caster))
set dummy.level=lvl
call dummy.castOnTarget(target)
set data=DarkCurse.create()
set data.caster=caster
set data.target=target
set data.lvl=lvl
set data.disarmed=false
set data.moved=false
set data.end=NewTimer()
set DarkCurse.CurseTable[target]=data
// Two possibilities: If the target is a hero or not
if IsUnitSpellResistant(target) then
call TimerStart(data.end,HERODURATION[lvl],false,function DarkCurse.EndSpell)
call ABuffApply(buffType,target,caster,HERODURATION[lvl],lvl,data)
else
call TimerStart(data.end,DURATION[lvl],false,function DarkCurse.EndSpell)
call ABuffApply(buffType,target,caster,DURATION[lvl],lvl,data)
endif
set DarkCurse.CurseTable[data.end]=data
set data.damage=CreateTrigger()
call TriggerRegisterUnitEvent(data.damage,target,EVENT_UNIT_DAMAGED)
call TriggerAddCondition(data.damage,Condition(function DarkCurse.DisarmTarget))
set DarkCurse.CurseTable[data.damage]=data
else
set dummy=xecast.createBasicA(SHRINKID,BLOODLUST,GetOwningPlayer(caster))
set dummy.level=lvl
call dummy.castOnTarget(target)
set data=DarkCurse.CurseTable[target]
call PauseTimer(data.end)
if IsUnitSpellResistant(target) then
call TimerStart(data.end,HERODURATION[lvl],false,function DarkCurse.EndSpell)
call ABuffApply(buffType,target,caster,HERODURATION[lvl],lvl,data)
else
call TimerStart(data.end,DURATION[lvl],false,function DarkCurse.EndSpell)
call ABuffApply(buffType,target,caster,DURATION[lvl],lvl,data)
endif
endif
set caster=null
set target=null
endfunction
private function Init takes nothing returns nothing
local integer int=0
loop
exitwhen int>=bj_MAX_PLAYER_SLOTS
call SetPlayerAbilityAvailable(Player(int),EFFECTBOOK,false)
set int=int+1
endloop
call SetupSpellVariables()
call RegisterSpellEffectResponse(ABILID,CurseSpell)
set DarkCurse.CurseTable=HandleTable.create()
call XE_PreloadAbility(SHRINKID)
call XE_PreloadAbility(EFFECTBOOK)
set buffType=aBuffType.create()
set buffType.eventPeriodic=Dispel
call Preload(CONFUSED)
call Preload(CONFUSEEFFECT)
call PreloadStart()
endfunction
endlibrary
//TESH.scrollpos=72
//TESH.alwaysfold=0
library HauntingGhosts initializer Init needs TimerUtils, GroupUtils, UnitMaxState, AutoIndex, StatusModifier, Rounding, TimedHandles, SpellEvent
//*******************************************************************************************\\
//*******************************************************************************************\\
//* Haunting Ghosts By Inferior *\\
//* *\\
//* v1.1 *\\
//* *\\
//* ************************* *\\
//* * Requirements: * *\\
//* * - JassNewGenPack * *\\
//* * - TimerUtils * *\\
//* * - GroupUtils * *\\
//* * - UnitMaxState * *\\
//* * - AutoIndex * *\\
//* * - StatusModifier * *\\
//* * - Rounding * *\\
//* * - TimedHandles * *\\
//* * - SpellEvent * *\\
//* ************************* *\\
//* *\\
//* *\\
//* *\\
//* How To Import: *\\
//* - Copy the Spell called 'Haunting Ghosts' into your map *\\
//* - Copy the whole Triggerscript into your map *\\
//* - Now change the values of the 'ABILID' and 'DECAYTIME' to the Values in your map *\\
//* *\\
//* Spelldescription: *\\
//* The Caster owns over the souls of the dead units nearby. For each Unit the Killer *\\
//* of that unit gains a life and damage bonus over a short duration. If the Killer is *\\
//* an enemy of the Caster its MaxLife is reduced and its damage aswell. If the Killer *\\
//* is an ally its MaxLife and damage are increased. The MaxLife of the Killer *\\
//* regenerates to its default after a specific duration. The regeneration has its *\\
//* own duration. The more units were killed by one unit the higher is the bonus the *\\
//* Killer gains. *\\
//* *\\
//*******************************************************************************************\\
//*******************************************************************************************\\
// Globals Setting
globals
private constant integer ABILID = 'A000'
// The AbilityId of the MainSpell
private constant real DECAYTIME = 88.
// The time after which bones vanish. Change this to the value found in Map properties of your map
private constant real TIMERPERIOD = 0.5
// Do not set this value lower than 0.2. Does not really influence performance.
// The matter is that you cannot decrease the Health by less than 1, that means every decreased Health by 0.00 up to 0.9999 will be 1
private constant string SOULEFFECT = "Objects\\Spawnmodels\\Undead\\UndeadDissipate\\UndeadDissipate.mdl"
private constant string HAUNTEFFECT = "Abilities\\Spells\\Orc\\EtherealForm\\SpiritWalkerChange.mdl"
private constant string HAUNTATTACH = "origin"
private constant string LEACHEFFECT = "Abilities\\Spells\\NightElf\\shadowstrike\\shadowstrike.mdl"
private constant string LEACHATTACH = "overhead"
private constant boolean MORETHANONCE = false
// Allows dead units to give bonus or leached Health and Damage more than once.
// For Descriptions for the following Variables see below
private real array HPLEACHED
private integer array DMGLEACHED
private real array ADDLEACH
private real array DURATION
private real array REGAINDURATION
private real array AOE
// Do not touch the following
private unit TEMPUNIT
private timer TEMPTIMER
private unit array KILLERUNIT
// Only used if MORETHANONCE=false
private boolean array ALLREADYUSED
endglobals
private function SetupSpellVariables takes nothing returns nothing
// The Healthpoints leached or added for each killed unit
set HPLEACHED[1]=5.
set HPLEACHED[2]=15.
set HPLEACHED[3]=30.
set HPLEACHED[4]=50.
set DMGLEACHED[1]=2
set DMGLEACHED[2]=3
set DMGLEACHED[3]=4
set DMGLEACHED[4]=5
// A bonus of leached or added Healthpoints, the more units were killed
set ADDLEACH[1]=0.
set ADDLEACH[2]=10.
set ADDLEACH[3]=25.
set ADDLEACH[4]=45.
set ADDLEACH[5]=70.
set ADDLEACH[6]=100.
// The Duration until the Healthpoints fall down
set DURATION[1]=5.
set DURATION[2]=6.
set DURATION[3]=7.
set DURATION[4]=8.
// The Duration after which the origin Health is reached
set REGAINDURATION[1]=3.
set REGAINDURATION[2]=6.
set REGAINDURATION[3]=9.
set REGAINDURATION[4]=12.
// The Spells Area of Effect
set AOE[1]=550.
set AOE[2]=625.
set AOE[3]=700.
set AOE[4]=800.
endfunction
// DO NOT TOUCH ANYTHING BELOW THIS COMMENT, ALTHOUGH YOU KNOW WHAT YOU DO.
// HERE BEGINS THE SPELLS SCRIPT.
// This two functions save the Killer of a dying Unit, because we need to know the Killer.
// So we save the Killer in an Unit array. Now we use the AutoIndex so we can easily say that this unit
// was killed by that unit. We Refresh this value when the unit is removed from the map. That means when its bones vanish
// So you have to Change the DECAYTIME value to the time after which the bones get removed in your map.
private function ClearSavedKiller takes nothing returns nothing
local timer t=GetExpiredTimer()
set KILLERUNIT[GetTimerData(t)]=null
call ReleaseTimer(t)
endfunction
private function SaveKiller takes nothing returns boolean
local unit killer=GetKillingUnit()
local unit dying=GetDyingUnit()
set KILLERUNIT[GetUnitId(dying)]=killer
set TEMPTIMER=NewTimer()
call SetTimerData(TEMPTIMER,GetUnitId(dying))
call TimerStart(TEMPTIMER,DECAYTIME,false,function ClearSavedKiller)
set killer=null
set dying=null
return false
endfunction
private struct LeachedLife
unit leached
real hp
real health
real down
real reg
integer level
integer dmg
real duration
real factor
boolean done
integer counter
boolean active
static integer count=0
static timer Timer
static LeachedLife array Data
method clear takes nothing returns nothing
set .leached=null
set .hp=0
set .health=0
set .reg=0
set .level=0
set .down=0
set .duration=0
set .factor=0
set .done=false
set .counter=0
set .active=false
endmethod
// this method resets the MaxLife of a target over a specific time
static method ResetLife takes nothing returns nothing
local integer int=0
local LeachedLife data
loop
exitwhen int==.count
set data=.Data[int]
if data.active then
if not data.done and data.duration>0 then
set data.duration=data.duration-TIMERPERIOD
elseif not data.done and data.duration<=0 then
set data.done=true
set data.duration=REGAINDURATION[data.level]
set data.reg=data.hp/data.duration * TIMERPERIOD
set data.down=R2I(RealRounding(data.reg+0.5))*data.duration / TIMERPERIOD
set data.down=data.hp-data.down
if data.reg<1 then
set data.reg=1
endif
elseif data.done and data.duration>0 then
call DestroyEffectTimed(AddSpecialEffectTarget(HAUNTEFFECT,data.leached,HAUNTATTACH),1.5)
set data.duration=data.duration-TIMERPERIOD
set data.health=data.health-data.reg
call AddUnitMaxState(data.leached,UNIT_STATE_MAX_LIFE,(data.hp-data.health)*data.factor)
set data.hp=data.health
if AbsReal(data.down)>data.reg then
call AddUnitMaxState(data.leached,UNIT_STATE_MAX_LIFE,data.reg)
if data.down>0 then
set data.down=data.down-R2I(data.reg)
else
set data.down=data.down+R2I(data.reg)
endif
endif
if data.duration==0 then
call AddUnitMaxState(data.leached,UNIT_STATE_MAX_LIFE,-data.down)
endif
elseif data.done and data.duration<=0 then
call UnitAddDamage(data.leached,-data.dmg)
call data.clear()
endif
endif
set int=int+1
endloop
endmethod
static method create takes nothing returns LeachedLife
local LeachedLife data=LeachedLife.allocate()
if .count==0 then
set .Timer=NewTimer()
call TimerStart(.Timer,TIMERPERIOD,true,function LeachedLife.ResetLife)
endif
set .Data[.count]=data
set data.done=false
set data.active=true
set .count=.count+1
return data
endmethod
endstruct
private struct Spell
player caster
unit killer
integer level
integer counter
real factor
effect devil
static Spell array Data
method clear takes nothing returns nothing
call DestroyEffect(.devil)
set .devil=null
set .killer=null
set .caster=null
set .level=0
set .factor=0
set .counter=0
call .destroy()
endmethod
// Executed for each killer on Spellcast
method StartSpell takes nothing returns nothing
local LeachedLife data=LeachedLife.create()
set data.leached=.killer
set data.level=.level
set data.factor=.factor
set data.hp=HPLEACHED[.level]*.counter
set data.dmg=DMGLEACHED[.level]*.counter
call UnitAddDamage(.killer,data.dmg)
if .counter>6 then
set .counter=6
endif
set data.counter=.counter
set data.hp=data.hp+ADDLEACH[.counter]
set data.health=data.hp
set data.down=0
set data.reg=0
set data.duration=DURATION[.level]
call AddUnitMaxState(.killer,UNIT_STATE_MAX_LIFE,data.hp*.factor)
set data.factor=data.factor*-1
call .clear()
endmethod
static method create takes nothing returns Spell
return Spell.allocate()
endmethod
endstruct
// Executed on Spellcast.
// checks how much dead units are in a specific range and checks who is there killer.
// If the Killer is an enemy its MaxLife and damage is reduced otherwise it is increased.
// If the MORETHANONCE variable is set to true, dead units souls can be used more than once by this spell,
// otherwise they will only once give bonuses.
private function HauntingSpell takes nothing returns nothing
local unit caster=SpellEvent.CastingUnit
local real x=GetUnitX(caster)
local real y=GetUnitY(caster)
local unit killer
local integer int=0
local Spell data
call GroupUnitsInArea(ENUM_GROUP,x,y,AOE[GetUnitAbilityLevel(caster,ABILID)])
loop
set TEMPUNIT=FirstOfGroup(ENUM_GROUP)
exitwhen TEMPUNIT==null
if MORETHANONCE then
set ALLREADYUSED[GetUnitId(TEMPUNIT)]=false
endif
if GetWidgetLife(TEMPUNIT)<0.405 and not ALLREADYUSED[GetUnitId(TEMPUNIT)] then
call AddSpecialEffect(HAUNTEFFECT,GetUnitX(TEMPUNIT),GetUnitY(TEMPUNIT))
set killer=KILLERUNIT[GetUnitId(TEMPUNIT)]
if not IsUnitType(killer,UNIT_TYPE_DEAD) and not IsUnitType(killer,UNIT_TYPE_STRUCTURE) and IsUnitAlly(killer,GetOwningPlayer(caster)) then
set data=Spell.Data[GetUnitId(killer)]
set ALLREADYUSED[GetUnitId(TEMPUNIT)]=true
if data.counter==0 then
set data=Spell.create()
set data.caster=GetOwningPlayer(caster)
set data.killer=killer
set data.level=GetUnitAbilityLevel(caster,ABILID)
set data.factor=1
set data.counter=data.counter+1
set Spell.Data[GetUnitId(killer)]=data
call DestroyEffect(AddSpecialEffectTarget(SOULEFFECT,killer,HAUNTATTACH))
else
set data.counter=data.counter+1
endif
elseif not IsUnitType(killer,UNIT_TYPE_DEAD) and not IsUnitType(killer,UNIT_TYPE_STRUCTURE) and IsUnitEnemy(killer,GetOwningPlayer(caster)) then
set data=Spell.Data[GetUnitId(killer)]
set ALLREADYUSED[GetUnitId(TEMPUNIT)]=true
if data.counter==0 then
set data=Spell.create()
set data.killer=killer
set data.devil=AddSpecialEffectTarget(LEACHEFFECT,killer,LEACHATTACH)
set data.level=GetUnitAbilityLevel(caster,ABILID)
set data.factor=-1
set data.counter=data.counter+1
set Spell.Data[GetUnitId(killer)]=data
call DestroyEffect(AddSpecialEffectTarget(SOULEFFECT,killer,HAUNTATTACH))
else
set data.counter=data.counter+1
endif
endif
endif
call GroupRemoveUnit(ENUM_GROUP,TEMPUNIT)
endloop
call GroupClear(ENUM_GROUP)
loop
exitwhen int>8190
if Spell.Data[int].counter!=0 then
call Spell.Data[int].StartSpell()
endif
set int=int+1
endloop
set caster=null
set killer=null
endfunction
private function Init takes nothing returns nothing
local trigger trig=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(trig,Condition(function SaveKiller))
call RegisterSpellEffectResponse(ABILID,HauntingSpell)
call SetupSpellVariables()
call Preload(SOULEFFECT)
call Preload(HAUNTEFFECT)
call Preload(LEACHEFFECT)
call PreloadStart()
endfunction
endlibrary
//TESH.scrollpos=94
//TESH.alwaysfold=0
library Polarisation initializer Init needs TimerUtils, UnitStatus, SpellEvent
//*******************************************************************************************\\
//*******************************************************************************************\\
//* Polarisation By Inferior *\\
//* *\\
//* v1.0 *\\
//* *\\
//* ************************* *\\
//* * Requirements: * *\\
//* * - JassNewGenPack * *\\
//* * - TimerUtils * *\\
//* * - UnitStatus * *\\
//* * - SpellEvent * *\\
//* ************************* *\\
//* *\\
//* *\\
//* *\\
//* How To Import: *\\
//* - Copy the Spell called 'Polarisation' into your map *\\
//* - Copy the whole Triggerscript into your map *\\
//* - Now change the values of the 'ABILID' to the Rawcode of the Mainability in your map *\\
//* *\\
//* Spelldescription: *\\
//* The Caster creates a static link between the target and himself absorbing the *\\
//* Targets life and converting it to Mana. The Target and the caster are pulled *\\
//* to the middle between their positions, while the target is disabled. The Health is *\\
//* absorbed during the pullphase. After they reached each other the Caster keeps up the *\\
//* static link between him and the target. The target is now unable to move farther *\\
//* away than the link allows him. *\\
//* *\\
//*******************************************************************************************\\
//*******************************************************************************************\\
// Globals Setting
globals
private constant integer ABILID = 'A009'
// The AbilityId of your Mainability
private constant string LIGHTNING = "CHIM"
// The Lightning Effect of the static link
private constant real DEFAULTHEIGHT = 50.
// The Height of the Lightning
private constant real DEFAULTDIST = 150.
// The Minimum Distance which has to be reached until the Pullphase is stopped
private constant real HEALTHTOMANA = 0.4
// The absorbed Health to Mana Factor
private constant real PULLPERIOD = 0.025
// The Timer Period of this Spell
// The 3 following variables are just important for the damage dealt in this spell
private constant attacktype ATKTYPE = ATTACK_TYPE_NORMAL
private constant damagetype DMGTYPE = DAMAGE_TYPE_UNIVERSAL
private constant weapontype WPNTYPE = WEAPON_TYPE_WHOKNOWS
private constant boolean PULLDOWNAIR = true
// Set this to true weather Air units shall be pulled down to the ground.
private constant boolean CASTERACTION = true
// Set this to false weather the caster should be disabled aswell duration the Pullphase
// For Descriptions for the following Variables see below
private real array PULLDURATION
private real array FIXDURATION
private real array MAXRANGE
private real array DAMAGEDEALT
endglobals
private function SetupSpellVariables takes nothing returns nothing
// The time in which the target is pulled in the direction of the caster
set PULLDURATION[1]=1.25
set PULLDURATION[2]=1.75
set PULLDURATION[3]=2.25
set PULLDURATION[4]=3.
// The time how long the target is fixed to the caster by the static link
set FIXDURATION[1]=1.5
set FIXDURATION[2]=2.
set FIXDURATION[3]=2.5
set FIXDURATION[4]=3.
// The Maximum range the target can move away from the caster with the static link
set MAXRANGE[1]=600.
set MAXRANGE[2]=500.
set MAXRANGE[3]=400.
set MAXRANGE[4]=300.
// The Damage dealt during the Pullphase
set DAMAGEDEALT[1]=60.
set DAMAGEDEALT[2]=125.
set DAMAGEDEALT[3]=185.
set DAMAGEDEALT[4]=250.
endfunction
// DO NOT TOUCH ANYTHING BELOW THIS COMMENT, ALTHOUGH YOU KNOW WHAT YOU DO.
// HERE BEGINS THE SPELLS SCRIPT.
private struct MagnetPull
unit caster
unit target
real castX
real castY
real targX
real targY
real duration
integer level
lightning light
static integer count=0
static timer MagnetTimer
static MagnetPull array Data
static boolean array done
// checks if the target is to far away from the caster
// stops the spell when the static link time expires
static method callback takes nothing returns nothing
local integer int=0
local MagnetPull data
local real dx
local real dy
loop
exitwhen int==.count
set data=.Data[int]
if data.duration>0 and not .done[int] and GetWidgetLife(data.target)>0.405 then
set dx=data.castX-data.targX
set dy=data.castY-data.targY
if SquareRoot(dx*dx+dy*dy)>MAXRANGE[data.level] then
set data.targX=data.castX-MAXRANGE[data.level]*Cos(Atan2(dy,dx))
set data.targY=data.castY-MAXRANGE[data.level]*Sin(Atan2(dy,dx))
call SetUnitX(data.target,data.targX)
call SetUnitY(data.target,data.targY)
else
set data.targX=GetUnitX(data.target)
set data.targY=GetUnitY(data.target)
endif
set data.castX=GetUnitX(data.caster)
set data.castY=GetUnitY(data.caster)
set data.duration=data.duration-PULLPERIOD
call MoveLightningEx(data.light,true,data.castX,data.castY,DEFAULTHEIGHT,data.targX,data.targY,DEFAULTHEIGHT)
elseif data.light!=null and not .done[int] or GetWidgetLife(data.target)<0.405 and not .done[int] then
call DestroyLightning(data.light)
if PULLDOWNAIR and IsUnitType(data.target,UNIT_TYPE_FLYING) then
call SetUnitFlyHeight(data.target,GetUnitDefaultFlyHeight(data.target),100)
endif
set .done[int]=true
call data.destroy()
endif
set int=int+1
endloop
endmethod
static method create takes nothing returns MagnetPull
local MagnetPull data=MagnetPull.allocate()
if .count==0 then
set .MagnetTimer=NewTimer()
call TimerStart(.MagnetTimer,PULLPERIOD,true,function MagnetPull.callback)
endif
set .Data[.count]=data
set .count=.count+1
return data
endmethod
endstruct
private struct PolarPull
unit caster
unit target
real castX
real castY
real targX
real targY
real velZ
real angle
real distance
real speed
integer level
lightning light
static integer count=0
static timer PullTimer
static PolarPull array Data
static boolean array done
// pulls the target and the caster to they position between each other
// Flying Units are pulled down if PULLDOWNAIR=true
// When the Pullphase is over Flying units regain their default height
static method callback takes nothing returns nothing
local integer int=0
local PolarPull data
local MagnetPull dat
loop
exitwhen int==.count
set data=.Data[int]
if data.distance>DEFAULTDIST and not .done[int] and GetWidgetLife(data.target)>0.405 then
set data.castX=data.castX-data.speed*Cos(data.angle)
set data.castY=data.castY-data.speed*Sin(data.angle)
call SetUnitX(data.caster,data.castX)
call SetUnitY(data.caster,data.castY)
set data.targX=data.targX+data.speed*Cos(data.angle)
set data.targY=data.targY+data.speed*Sin(data.angle)
call SetUnitX(data.target,data.targX)
call SetUnitY(data.target,data.targY)
call UnitDamageTarget(data.caster,data.target,(DAMAGEDEALT[data.level]/PULLDURATION[data.level])*PULLPERIOD,true,false,ATKTYPE,DMGTYPE,WPNTYPE)
call SetUnitState(data.caster,UNIT_STATE_MANA,GetUnitState(data.caster,UNIT_STATE_MANA)+(DAMAGEDEALT[data.level]*HEALTHTOMANA/PULLDURATION[data.level])*PULLPERIOD)
if PULLDOWNAIR and IsUnitType(data.target,UNIT_TYPE_FLYING) then
call SetUnitFlyHeight(data.target,GetUnitFlyHeight(data.target)-data.velZ,0)
call MoveLightningEx(data.light,true,data.castX,data.castY,DEFAULTHEIGHT,data.targX,data.targY,GetUnitFlyHeight(data.target)+DEFAULTHEIGHT)
else
call MoveLightningEx(data.light,true,data.castX,data.castY,DEFAULTHEIGHT,data.targX,data.targY,DEFAULTHEIGHT)
endif
set data.distance=data.distance-2*data.speed
elseif data.distance<=DEFAULTDIST and not .done[int] and GetWidgetLife(data.target)>0.405 then
set dat=MagnetPull.create()
set dat.caster=data.caster
set dat.target=data.target
set dat.castX=data.castX
set dat.castY=data.castY
set dat.targX=data.targX
set dat.targY=data.targY
set dat.light=data.light
set dat.level=data.level
set dat.duration=FIXDURATION[data.level]
if not CASTERACTION then
call DisableUnit(data.caster,false)
endif
call DisableUnit(data.target,false)
set .done[int]=true
call data.destroy()
elseif GetWidgetLife(data.target)<0.405 and not .done[int] then
call DestroyLightning(data.light)
if not CASTERACTION then
call DisableUnit(data.caster,false)
endif
call DisableUnit(data.target,false)
set .done[int]=true
call data.destroy()
endif
set int=int+1
endloop
endmethod
static method create takes nothing returns PolarPull
local PolarPull data=PolarPull.allocate()
if .count==0 then
set .PullTimer=NewTimer()
call TimerStart(.PullTimer,PULLPERIOD,true,function PolarPull.callback)
endif
set .Data[.count]=data
set .count=.count+1
return data
endmethod
endstruct
// Executed on Spellcast
// Disables the caster when CASTERACTION=false
private function PolarisationSpell takes nothing returns nothing
local unit caster=SpellEvent.CastingUnit
local unit target=SpellEvent.TargetUnit
local PolarPull data=PolarPull.create()
local real dx=0.
local real dy=0.
set data.caster=caster
set data.target=target
set data.castX=GetUnitX(caster)
set data.castY=GetUnitY(caster)
set data.targX=GetUnitX(target)
set data.targY=GetUnitY(target)
set dx=data.castX-data.targX
set dy=data.castY-data.targY
set data.angle=Atan2(dy,dx)
set data.distance=SquareRoot(dx*dx+dy*dy)
set data.level=GetUnitAbilityLevel(caster,ABILID)
set data.speed=(data.distance/PULLDURATION[data.level])*PULLPERIOD*0.5
call DisableUnit(data.target,true)
if not CASTERACTION then
call DisableUnit(data.caster,true)
endif
call SetUnitAnimation(data.caster,"spell channel")
if PULLDOWNAIR and IsUnitType(target,UNIT_TYPE_FLYING) then
set data.velZ=(GetUnitFlyHeight(target)/PULLDURATION[data.level])*PULLPERIOD
set data.light=AddLightningEx(LIGHTNING,true,data.castX,data.castY,DEFAULTHEIGHT,data.targX,data.targY,GetUnitFlyHeight(target)+DEFAULTHEIGHT)
else
set data.light=AddLightningEx(LIGHTNING,true,data.castX,data.castY,DEFAULTHEIGHT,data.targX,data.targY,DEFAULTHEIGHT)
set data.velZ=0
endif
set caster=null
set target=null
endfunction
private function Init takes nothing returns nothing
call RegisterSpellEffectResponse(ABILID,PolarisationSpell)
call SetupSpellVariables()
endfunction
endlibrary
//TESH.scrollpos=85
//TESH.alwaysfold=0
library MagneticField initializer Init needs TimerUtils, GroupUtils, LastOrder, ArmorUtils, StatusModifier, Rounding, BoundSentinel, UnitStatus, IsUnitSpellResistant, TimedHandles, xepreload, xedamage
//********************************************************************************************\\
//********************************************************************************************\\
//* Magnetic Field By Inferior *\\
//* *\\
//* v1.1 *\\
//* *\\
//* ************************** *\\
//* * Requirements: * *\\
//* * - JassNewGenPack * *\\
//* * - TimerUtils * *\\
//* * - GroupUtils * *\\
//* * - ArmorUtils * *\\
//* * - StatusModifier * *\\
//* * - Rounding * *\\
//* * - BoundSentinel * *\\
//* * - UnitStatus * *\\
//* * - IsUnitSpellResistant * *\\
//* * - TimedHandles * *\\
//* * - xepreload * *\\
//* * - xedamage * *\\
//* ************************** *\\
//* *\\
//* *\\
//* *\\
//* How To Import: *\\
//* - Copy the Spell called 'Magnetic Field' and 'Magnet Aura(Slow)' into your map and *\\
//* create 2 buffs like those in the Object Editor with 'Magnetic Field' in its name *\\
//* - Copy the whole Triggerscript into your map *\\
//* - Now change the values of the 'ABILID' and 'SLOWID' to the RawCodes in your map *\\
//* *\\
//* Spelldescription: *\\
//* The Caster fills the air arround him with strong static energy, creating a strong *\\
//* magnetic Field arround his body that slows every enemy unit and slightly pulls them *\\
//* to the caster. If an enemy unit comes in contact with the caster, the unit gets *\\
//* shocked, losing an specific amount of its current Life and half of its armor, and *\\
//* pushed away. For each contact the caster loses Mana and passively burns Mana over *\\
//* Time as long as the Magnetic Field is active. *\\
//* *\\
//********************************************************************************************\\
//********************************************************************************************\\
// Globals Setting
globals
private constant integer ABILID = 'A00A'
// The AbilityId of your Mainability
private constant integer SLOWID = 'A00B'
// The AbilityId of your Slowing Aura
private constant integer ORDERID = 852177
// The OrderId to activate the Spell
private constant integer UNORDERID = 852178
// The OrderId to deactive the Spell
private constant real MAGNETPERIOD = .025
// The Timerperiod of this Spell
private constant real MINDISTANCE = 120.
// The Minimum Distance between the caster and an enemy until it gets pushed away
private constant real SHOCKDAMAGE = 0.05
// The percentage of Life an enemy loses on contact
private constant real ARMORDOWNTIME = 5.
// The Time the enemy loses half of its armor
private constant string IMPACTEFFECT = "Abilities\\Weapons\\Bolt\\BoltImpact.mdl"
private constant string IMPACTATTACH = "origin"
// The Effect on impact
// For Descriptions for the following Variables see below
private real array IMPACTMANABURN
private real array BUFFERMANA
private real array PUSHDISTANCE
private real array PULLSPEED
private real array PULLRADIUS
// Do not change any of the following variables
private unit TEMPUNIT
private xedamage dmgOptions
endglobals
private function MagnetFilter takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(TEMPUNIT)) and not IsUnitSpellResistant(GetFilterUnit()) and not IsUnitSpellImmune(GetFilterUnit()) and GetWidgetLife(GetFilterUnit())>0.405
endfunction
private function SetupSpellVariables takes nothing returns nothing
// The Mana burnt for each Contact with an enemy
set IMPACTMANABURN[1]=25.
set IMPACTMANABURN[2]=20.
set IMPACTMANABURN[3]=15.
set IMPACTMANABURN[4]=10.
// The Speed with which the enemies are pulled to the caster
set PULLSPEED[1]=.2
set PULLSPEED[2]=.3
set PULLSPEED[3]=.4
set PULLSPEED[4]=.5
// The Area of Effect of the Magnetic Field
set PULLRADIUS[1]=400.
set PULLRADIUS[2]=550.
set PULLRADIUS[3]=650.
set PULLRADIUS[4]=800.
// The Distance the enemies are pushed away after contact
set PUSHDISTANCE[1]=300.
set PUSHDISTANCE[2]=350.
set PUSHDISTANCE[3]=400.
set PUSHDISTANCE[4]=450.
endfunction
private function ConfigureDamageOptions takes nothing returns nothing
set dmgOptions=xedamage.create()
set dmgOptions.atype=ATTACK_TYPE_CHAOS
set dmgOptions.dtype=DAMAGE_TYPE_UNIVERSAL
set dmgOptions.wtype=WEAPON_TYPE_WHOKNOWS
endfunction
// DO NOT TOUCH ANYTHING BELOW THIS COMMENT, ALTHOUGH YOU KNOW WHAT YOU DO.
// HERE BEGINS THE SPELLS SCRIPT.
private struct MagnetPush
unit caster
unit target
real speed
real x
real y
real angle
real distance
integer dec
integer level
static integer count=0
static timer PushTimer
static MagnetPush array Data
static boolean array done
// pushes the enemies away after contact
// Disarms them during the Pushphase
static method callback takes nothing returns nothing
local integer int=0
local MagnetPush data
loop
exitwhen int==.count
set data=.Data[int]
if not .done[int] then
if data.distance>0 then
set data.x=data.x-data.speed*Cos(data.angle)
set data.y=data.y-data.speed*Sin(data.angle)
call SetUnitX(data.target,data.x)
call SetUnitY(data.target,data.y)
set data.distance=data.distance-data.speed
else
set .done[int]=true
call DisarmUnit(data.target,false)
call data.destroy()
endif
endif
set int=int+1
endloop
endmethod
static method create takes nothing returns MagnetPush
local MagnetPush data=MagnetPush.allocate()
if .count==0 then
set .PushTimer=NewTimer()
call TimerStart(.PushTimer,MAGNETPERIOD,true,function MagnetPush.callback)
endif
set .done[.count]=false
set .Data[.count]=data
set .count=.count+1
return data
endmethod
endstruct
private struct MagnetField
unit caster
real speed
integer level
static integer count=0
static integer array Index
static timer MagnetTimer
static MagnetField array Data
static boolean array done
// Slightly pulls enemies in range to the caster
// if they are to near to the caster they get pushed away, disarmed, and lose half of their Armor
method operator pos= takes unit a returns nothing
local real x=GetUnitX(a)
local real y=GetUnitY(a)
local real dx=GetUnitX(.caster)-x
local real dy=GetUnitY(.caster)-y
local real angle=Atan2(dy,dx)
local MagnetPush data
set x=x+.speed*Cos(angle)
set y=y+.speed*Sin(angle)
call SetUnitX(a,x)
call SetUnitY(a,y)
if SquareRoot(dx*dx+dy*dy)<MINDISTANCE then
set data=MagnetPush.create()
set data.caster=.caster
set data.target=a
set data.level=.level
set data.x=x
set data.y=y
set data.angle=angle
set data.distance=PUSHDISTANCE[data.level]
set data.speed=25
call DisarmUnit(a,true)
call dmgOptions.damageTarget(data.caster,data.target,GetUnitState(data.target,UNIT_STATE_LIFE)*SHOCKDAMAGE)
call UnitAddArmorTimed(data.target,-RealRounding(GetUnitArmor(data.target)/2),ARMORDOWNTIME)
call DestroyEffectTimed(AddSpecialEffectTarget(IMPACTEFFECT,a,IMPACTATTACH),1.5)
call SetUnitState(data.caster,UNIT_STATE_MANA,GetUnitState(data.caster,UNIT_STATE_MANA)-IMPACTMANABURN[data.level])
endif
endmethod
static method callback takes nothing returns nothing
local integer int=0
local unit a=null
local MagnetField data
loop
exitwhen int==.count
set data=.Data[int]
if not .done[int] then
set TEMPUNIT=data.caster
call GroupEnumUnitsInArea(ENUM_GROUP,GetUnitX(data.caster),GetUnitY(data.caster),PULLRADIUS[data.level],Condition(function MagnetFilter))
loop
set a=FirstOfGroup(ENUM_GROUP)
exitwhen a==null
set data.pos=a
call GroupRemoveUnit(ENUM_GROUP,a)
endloop
call GroupClear(ENUM_GROUP)
set TEMPUNIT=null
endif
set int=int+1
if GetUnitState(data.caster,UNIT_STATE_MANA)<=0 then
set .done[.Index[GetUnitId(data.caster)]]=true
call UnitRemoveAbility(data.caster,SLOWID)
call data.destroy()
endif
endloop
set a=null
endmethod
static method create takes unit u returns MagnetField
local MagnetField data=MagnetField.allocate()
if .count==0 then
set .MagnetTimer=NewTimer()
call TimerStart(.MagnetTimer,MAGNETPERIOD,true,function MagnetField.callback)
endif
set .Data[.count]=data
set .Index[GetUnitId(u)]=.count
set .done[.count]=false
set .count=.count+1
return data
endmethod
endstruct
// Executed on Spellcast
private function MagnetSpell takes nothing returns boolean
local unit caster=GetOrderedUnit()
local MagnetField data
if GetLastOrderId(caster)==ORDERID then
set data=MagnetField.create(caster)
set data.caster=caster
set data.level=GetUnitAbilityLevel(caster,ABILID)
set data.speed=PULLSPEED[data.level]
call UnitAddAbility(data.caster,SLOWID)
call SetUnitAbilityLevel(data.caster,SLOWID,data.level)
call UnitAddAbility(data.caster,'A00C')
call SetUnitAbilityLevel(data.caster,'A00C',2)
elseif GetLastOrderId(caster)==UNORDERID then
set data=MagnetField.Data[MagnetField.Index[GetUnitId(caster)]]
set MagnetField.done[MagnetField.Index[GetUnitId(caster)]]=true
call UnitRemoveAbility(data.caster,SLOWID)
call data.destroy()
endif
set caster=null
return false
endfunction
private function Init takes nothing returns nothing
local trigger trig=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_ISSUED_ORDER)
call TriggerAddCondition(trig,Condition(function MagnetSpell))
call ConfigureDamageOptions()
call SetupSpellVariables()
call XE_PreloadAbility(SLOWID)
call Preload(IMPACTEFFECT)
call PreloadStart()
set trig=null
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library RobotArms initializer Init needs Table, GroupUtils, TimerUtils, ABuff, ABuffHeroSkill, BoundSentinel, UnitStatus, IsUnitSpellResistant, xebasic, xefx, xedamage, Rounding
//********************************************************************************************\\
//********************************************************************************************\\
//* Robot Arms By Inferior *\\
//* *\\
//* v1.3 *\\
//* *\\
//* ************************** *\\
//* * Requirements: * *\\
//* * - JassNewGenPack * *\\
//* * - Table * *\\
//* * - GroupUtils * *\\
//* * - TimerUtils * *\\
//* * - ABuff * *\\
//* * - ABuffHeroSkill * *\\
//* * - BoundSentinel * *\\
//* * - UnitStatus * *\\
//* * - IsUnitSpellResistant * *\\
//* * - xebasic * *\\
//* * - xefx * *\\
//* * - xedamage * *\\
//* * - Rounding * *\\
//* ************************** *\\
//* *\\
//* *\\
//* *\\
//* How To Import: *\\
//* - Copy the Spell called 'Robot Arms', the dummy called facingdummy into your map *\\
//* and import the "fdummy.mdl" and "RoboticArmsHand" included in this map into yours *\\
//* - Copy the whole Triggerscript into your map *\\
//* - Change the model of your new Dummymodel and change the RawCodes of the ABILID *\\
//* and CHAINID to the Rawcodes in your map *\\
//* *\\
//* Spelldescription: *\\
//* The Electrician uses his technical knowlegdes to create 3 Robot Arms that act on *\\
//* their own. If the Arm detects an enemy, it moves to that enemy and knocks it out on *\\
//* contact, dealing absorbing the enemies Life. If the Electrician moves out of the *\\
//* enemies Range the Arm moves back. After every action, the Arms have to move back into *\\
//* their default Position. *\\
//* *\\
//********************************************************************************************\\
//********************************************************************************************\\
// Globals Setting
globals
private constant integer ABILID = 'A003'
// The AbilityId of your Main Ability
private constant integer CHAINID = 'e001'
// The Rawcode for your dummy. This dummy needs a specific model included in that map. You need this model if you want the full eyecandy
private constant integer MAXARMS = 3
// The Number of Arms. The more arms you use, the higher is the performance lack.
private constant integer CHAINPARTS = 16
// The Parts of Dummies for the Chain of the arm. Do not forget to change the .chain array in the struct.
private constant real CHAINSIZE = 1.3
// The Size of the Chain parts
private constant real BIRTHSIZE = 0.5
// The Size at which the Chain parts start
private constant real BIRTHDURATION = 1.
// The Duration until the Chain reach its full size
private constant real ARMSPEED = 12.
// The Speed an arm is moving back or to a target
private constant real DMGTOHEALTH = 1.
// Conversionfactor from damage dealt to absorbed Health. This value is only active if USEABSORB = true
private constant real HEALTHFACTOR = 0.05
// The Factor of the targets MaxHealth that is absorbed. This value is only active if USEABSORB = true
private constant real DEALTDAMAGE = 1.5
// Damage dealt per Period: 0.025*40=1 -> 40*1.5= 60 damage dealt per second. This value is only active if USEABSORB = false
private constant real DETECTIONRADIUS = 500.
// The Radius in which an Arm detects an enemy
private constant real DEFAULTOFFSET = 250.
// The Default offset from the Electricians Position. Also defines the DefaultPosition where the arms move back to
private constant real DEFAULTHEIGHT = 75.
// The Default Height of the Chains
private constant real MAXHEIGHT = 350.
// The Maximum Height that can be reached by the arm
private constant real MINHEIGHT = 150.
// The Minimum Height that can be reached by the arm
private constant real MAXOFFSET = 750.
// The Maximum Range an arm can bent
private constant string CHAINMDL = "RoboticArmsHand.mdx"
// The Chains Model effect
private constant string CHAINATTACH = "origin"
// DO NOT CHANGE THIS (the dummy model has no other attachement)
private constant string CHAINHAND = "Abilities\\Weapons\\FarseerMissile\\FarseerMissile.mdl"
// The End of the Arms effect
private constant string ABSORBMDL = "Abilities\\Spells\\Undead\\AbsorbMana\\AbsorbManaBirthMissile.mdl"
// The Effect that is used if USEABSORB = true
private constant boolean USEABSORB = true
// This value defines the type of damage that is dealt. if USEABSORB = false then the damage is dealt periodicly.
// For more informations check the .OnAbsorb method.
private real array SHOCKTIME
// The Value that defines the time an arm deals damage to a target.
// DO NOT CHANGE THIS. Defines the Damage types.
private xedamage dmgOptions
// DO NOT CHANGE THIS.
private unit TEMPUNIT
private constant integer StunCode = 'BPSE'
endglobals
private keyword Arm
private function SetupSpellVariables takes nothing returns nothing
set SHOCKTIME[1]=1.5
set SHOCKTIME[2]=2.
set SHOCKTIME[3]=2.75
set SHOCKTIME[4]=3.5
endfunction
private function ConfigureDamageOptions takes nothing returns nothing
set dmgOptions=xedamage.create()
set dmgOptions.atype=ATTACK_TYPE_CHAOS
set dmgOptions.dtype=DAMAGE_TYPE_UNIVERSAL
set dmgOptions.wtype=WEAPON_TYPE_WHOKNOWS
endfunction
private function UnitFilter takes nothing returns boolean
return IsUnitEnemy(GetFilterUnit(),GetOwningPlayer(TEMPUNIT)) and IsUnitType(GetFilterUnit(),UNIT_TYPE_GROUND) and not IsUnitSpellImmune(GetFilterUnit()) and not Arm.stunned[GetUnitId(GetFilterUnit())] and GetWidgetLife(GetFilterUnit())>0.405 and GetUnitTypeId(GetFilterUnit())!=CHAINID
endfunction
// DO NOT TOUCH ANYTHING BELOW THIS COMMENT, ALTHOUGH YOU KNOW WHAT YOU DO.
// HERE BEGINS THE SPELLS SCRIPT.
private function AngleConv takes real angle returns real
local integer factor=0
if angle>=360 then
set factor=R2I(angle/360)
return angle-360*factor
elseif angle<0 then
set factor=R2I(AbsReal(angle)/360)
return angle+360*(factor+1)
endif
return angle
endfunction
private function HeightCalc takes real d returns real
return ((MINHEIGHT-MAXHEIGHT+DEFAULTHEIGHT)/MAXOFFSET)*d+MAXHEIGHT-DEFAULTHEIGHT
endfunction
// This function defines the Arm parts height
private function ArmHeight takes real d, integer z returns real
return HeightCalc(d)*Sin( bj_PI/CHAINPARTS * z ) + DEFAULTHEIGHT
endfunction
// Defines the Arm parts Z-Facing.
// THIS FUNCTION STILL NEEDS PERFECTION. IM STILL SEARCHING FOR A BETTER FORMULA
private function UnitZFacing takes unit u, real d, integer z returns nothing
local real s=(MAXHEIGHT-(MAXHEIGHT-MINHEIGHT)/2)/HeightCalc(d)
local real a=s*HeightCalc(d) * bj_PI/CHAINPARTS * Cos(bj_PI/CHAINPARTS * z)
local boolean b=a<0
local integer v=RealRounding(AbsReal(a))
if not b then
call SetUnitAnimationByIndex(u,v)
else
call SetUnitAnimationByIndex(u,252-v)
endif
endfunction
// Defines an arms Defaultpositions Facing
private constant function Angles takes integer int returns real
return 360./MAXARMS * int
endfunction
private struct Arm
unit caster
unit array chain[17] // Add here the value of the CHAINPARTS + 1
unit hand
unit targ
integer pos=CHAINPARTS
integer phase=0
// 0=birth
// 1=search after an enemy
// 2=haunt an enemy
// 3=shock an enemy
// 4=out of range
real angle
real startangle
real offset
real timed
xefx absorb
// this effect is only used if USEABSORB = true
readonly integer index
static integer count=0
static boolean array done
static boolean array stunned
static timer ArmTimer
static Arm array Data
static Table array ArmTable
static Table array HTable
// You may change this function if you want.
method OnAbsorb takes nothing returns nothing
if USEABSORB then
// the time between these too actions is proportional to the number of Chainparts
if .pos==CHAINPARTS then
// this is executed when the Absorption effect runs off the target.
call dmgOptions.damageTarget(.caster,.targ,GetUnitState(.targ,UNIT_STATE_MAX_LIFE)*HEALTHFACTOR)
elseif .pos<0 then
// this is executed when the Absorption effect reaches the caster
call SetWidgetLife(.caster,GetWidgetLife(.caster)+GetUnitState(.targ,UNIT_STATE_MAX_LIFE)*HEALTHFACTOR*DMGTOHEALTH)
endif
else
// this is executed when USEABSORB=false
// this is executed once per Timerperiod: every 0.025 seconds
call dmgOptions.damageTarget(.caster,.targ,DEALTDAMAGE)
endif
endmethod
// destroys the Arm on Casters death or Unlearning of the MainAbility
method KillArm takes nothing returns boolean
local integer int=0
loop
exitwhen int>CHAINPARTS
call KillUnit(.chain[int])
set int=int+1
endloop
call KillUnit(.hand)
return true
endmethod
// sets an Arms Position and the parts Height and facing
method PosNormal takes real offset returns nothing
local real dx=GetUnitX(.hand)-GetUnitX(.caster)
local real dy=GetUnitY(.hand)-GetUnitY(.caster)
local integer i=0
local real step=0.
set .offset=offset
set .angle=Atan2(dy,dx)
if .offset>MAXOFFSET then
set .offset=MAXOFFSET
endif
set step=.offset/CHAINPARTS
loop
exitwhen i>CHAINPARTS
call SetUnitX(.chain[i],GetUnitX(.caster)+step*i*Cos(.angle))
call SetUnitY(.chain[i],GetUnitY(.caster)+step*i*Sin(.angle))
call SetUnitFacing(.chain[i],.angle*180/bj_PI)
call SetUnitFlyHeight(.chain[i],ArmHeight(.offset,i),0)
set i=i+1
endloop
call SetUnitX(.hand,GetUnitX(.chain[CHAINPARTS]))
call SetUnitY(.hand,GetUnitY(.chain[CHAINPARTS]))
call SetUnitFlyHeight(.hand,GetUnitFlyHeight(.chain[CHAINPARTS]),0)
endmethod
// Creates an arm on Casters revive or Learning of the Ability
method createChain takes nothing returns nothing
local real x=GetUnitX(.caster)
local real y=GetUnitY(.caster)
local integer count=CHAINPARTS
local real dx=.offset/count
local integer int=0
loop
exitwhen int>count
set .chain[int]=CreateUnit(GetOwningPlayer(.caster),CHAINID,x,y,.angle)
call SetUnitScale(.chain[int],CHAINSIZE,CHAINSIZE,CHAINSIZE)
call SetUnitTimeScale(.chain[int],0.)
call UnitZFacing(.chain[int],.offset,int)
call UnitAddAbility(.chain[int],XE_HEIGHT_ENABLER)
call UnitRemoveAbility(.chain[int],XE_HEIGHT_ENABLER)
call SetUnitX(.chain[int],x+dx*Cos(.angle*bj_PI/180)*int)
call SetUnitY(.chain[int],y+dx*Sin(.angle*bj_PI/180)*int)
call SetUnitFlyHeight(.chain[int],ArmHeight(.offset,int),0)
call AddSpecialEffectTarget(CHAINMDL,.chain[int],CHAINATTACH)
set int=int+1
endloop
set .hand=CreateUnit(GetOwningPlayer(.caster),CHAINID,GetUnitX(.chain[CHAINPARTS]),GetUnitY(.chain[CHAINPARTS]),.angle)
call UnitAddAbility(.hand,XE_HEIGHT_ENABLER)
call UnitRemoveAbility(.hand,XE_HEIGHT_ENABLER)
call SetUnitFlyHeight(.hand,GetUnitFlyHeight(.chain[CHAINPARTS]),0)
call AddSpecialEffectTarget(CHAINHAND,.hand,CHAINATTACH)
endmethod
static method callback takes nothing returns nothing
local integer int=0
local Arm data
local group g
local unit a
local real dx
local real dy
loop
exitwhen int==Arm.count
if not Arm.done[int] then
set data=Arm.Data[int]
// Searchs possible targets for the Arm
if data.phase==0 then
set TEMPUNIT=data.caster
call GroupEnumUnitsInArea(ENUM_GROUP,GetUnitX(data.hand),GetUnitY(data.hand),DETECTIONRADIUS,Condition(function UnitFilter))
set data.targ=GroupPickRandomUnit(ENUM_GROUP)
set TEMPUNIT=null
call GroupClear(ENUM_GROUP)
if data.targ!=null then
set data.phase=1
else
set data.angle=GetUnitFacing(data.caster)+data.startangle
call SetUnitX(data.hand,GetUnitX(data.caster)+DEFAULTOFFSET*Cos(data.angle*bj_DEGTORAD))
call SetUnitY(data.hand,GetUnitY(data.caster)+DEFAULTOFFSET*Sin(data.angle*bj_DEGTORAD))
call data.PosNormal(DEFAULTOFFSET)
endif
// Moves the Arm to an target. If the target is out of range the arm moves back into DefaultPosition
elseif data.phase==1 then
set dx=GetUnitX(data.targ)-GetUnitX(data.hand)
set dy=GetUnitY(data.targ)-GetUnitY(data.hand)
set data.angle=Atan2(dy,dx)
call SetUnitX(data.hand,GetUnitX(data.hand)+ARMSPEED*Cos(data.angle))
call SetUnitY(data.hand,GetUnitY(data.hand)+ARMSPEED*Sin(data.angle))
set dx=GetUnitX(data.hand)-GetUnitX(data.caster)
set dy=GetUnitY(data.hand)-GetUnitY(data.caster)
set data.angle=Atan2(dy,dx)
set data.offset=SquareRoot(dx*dx+dy*dy)
call data.PosNormal(data.offset)
if data.offset>=MAXOFFSET then
set data.phase=3
endif
if Arm.stunned[GetUnitId(data.targ)] then
set data.phase=3
else
if IsUnitInRange(data.hand,data.targ,5.0) then
call SetUnitX(data.hand,GetUnitX(data.targ))
call SetUnitY(data.hand,GetUnitY(data.targ))
set dx=GetUnitX(data.hand)-GetUnitX(data.caster)
set dy=GetUnitY(data.hand)-GetUnitY(data.caster)
set data.angle=Atan2(dy,dx)
set data.offset=SquareRoot(dx*dx+dy*dy)
if data.offset>=MAXOFFSET then
set data.phase=3
else
call data.PosNormal(data.offset)
call StunUnit(data.targ,true)
set Arm.stunned[GetUnitId(data.targ)]=true
set data.phase=2
if USEABSORB then
set data.absorb=xefx.create(GetUnitX(data.chain[.chain.size]),GetUnitY(data.chain[.chain.size]),GetUnitFacing(data.chain[.chain.size]))
set data.absorb.fxpath=ABSORBMDL
endif
endif
endif
endif
// If the arm reached an enemy, the enemy is stunned and is sucked hp or damaged based on the USEABSORB value.
// The Arm moves back to default Position if the target moves out of range, or the Buff on the target is removed.
elseif data.phase==2 then
set dx=GetUnitX(data.targ)-GetUnitX(data.caster)
set dy=GetUnitY(data.targ)-GetUnitY(data.caster)
set data.angle=Atan2(dy,dx)
set data.offset=SquareRoot(dx*dx+dy*dy)
if data.offset>=MAXOFFSET then
set data.phase=3
call StunUnit(data.targ,false)
set Arm.stunned[GetUnitId(data.targ)]=false
set data.timed=0
set data.pos=CHAINPARTS
set data.targ=null
if USEABSORB then
call data.absorb.hiddenDestroy()
endif
else
call SetUnitX(data.hand,GetUnitX(data.targ))
call SetUnitY(data.hand,GetUnitY(data.targ))
call data.PosNormal(data.offset)
set data.timed=data.timed+0.025
if USEABSORB then
set data.absorb.x=GetUnitX(data.chain[data.pos])
set data.absorb.y=GetUnitY(data.chain[data.pos])
set data.absorb.z=GetUnitFlyHeight(data.chain[data.pos])
if data.pos<0 then
call data.absorb.hiddenDestroy()
call data.OnAbsorb()
set data.pos=CHAINPARTS
set data.absorb=xefx.create(GetUnitX(data.chain[.chain.size]),GetUnitY(data.chain[.chain.size]),GetUnitFacing(data.chain[.chain.size]))
set data.absorb.fxpath=ABSORBMDL
endif
if data.pos==CHAINPARTS then
call data.OnAbsorb()
endif
else
call data.OnAbsorb()
endif
set data.pos=data.pos-1
if data.timed>SHOCKTIME[GetUnitAbilityLevel(data.caster,ABILID)] or GetWidgetLife(data.targ)<0.405 then
set data.phase=3
call StunUnit(data.targ,false)
set Arm.stunned[GetUnitId(data.targ)]=false
set data.timed=0
set data.pos=CHAINPARTS
if USEABSORB then
call data.absorb.hiddenDestroy()
endif
else
call data.PosNormal(data.offset)
endif
endif
// Moves the Arm back to DefaultPosition
elseif data.phase==3 then
set dx=GetUnitX(data.hand)-GetUnitX(data.caster)-DEFAULTOFFSET*Cos((GetUnitFacing(data.caster)+data.startangle)*bj_DEGTORAD)
set dy=GetUnitY(data.hand)-GetUnitY(data.caster)-DEFAULTOFFSET*Sin((GetUnitFacing(data.caster)+data.startangle)*bj_DEGTORAD)
set data.angle=Atan2(dy,dx)
call SetUnitX(data.hand,GetUnitX(data.hand)-ARMSPEED*Cos(data.angle))
call SetUnitY(data.hand,GetUnitY(data.hand)-ARMSPEED*Sin(data.angle))
set dx=GetUnitX(data.caster)+DEFAULTOFFSET*Cos((GetUnitFacing(data.caster)+data.startangle)*bj_DEGTORAD)
set dy=GetUnitY(data.caster)+DEFAULTOFFSET*Sin((GetUnitFacing(data.caster)+data.startangle)*bj_DEGTORAD)
if IsUnitInRangeXY(data.hand,dx,dy,10) then
set data.phase=0
set data.targ=null
set data.offset=DEFAULTOFFSET
else
set dx=GetUnitX(data.hand)-GetUnitX(data.caster)
set dy=GetUnitY(data.hand)-GetUnitY(data.caster)
set data.angle=Atan2(dy,dx)
set data.offset=SquareRoot(dx*dx+dy*dy)
call data.PosNormal(data.offset)
endif
endif
endif
set int=int+1
endloop
endmethod
static method create takes nothing returns Arm
local Arm data=Arm.allocate()
if Arm.count==0 then
set Arm.ArmTimer=NewTimer()
call TimerStart(Arm.ArmTimer,0.025,true,function Arm.callback)
endif
set data.index=Arm.count
set Arm.Data[Arm.count]=data
set Arm.done[Arm.count]=false
set Arm.count=Arm.count+1
return data
endmethod
endstruct
globals
private aBuffType buffType = 0
endglobals
private function AbilLearn takes aBuff buffType returns nothing
local integer int=0
local Arm data
if Arm.HTable[GetHandleId(buffType.caster)][int]==0 then
if Arm.ArmTable[buffType]==0 then
set Arm.ArmTable[buffType]=Table.create()
endif
loop
exitwhen int==MAXARMS
set Arm.HTable[GetHandleId(buffType.caster)][int]=Table.create()
set data=Arm.create()
set Arm.ArmTable[buffType][int]=data
set data.caster=buffType.caster
set data.angle=GetUnitFacing(data.caster)+Angles(int)
set data.startangle=Angles(int)
set data.offset=DEFAULTOFFSET
set Arm.HTable[GetHandleId(buffType.caster)][int]=data
set data.phase=0
call data.createChain()
set int=int+1
endloop
else
loop
exitwhen int==MAXARMS
set data=Arm.HTable[GetHandleId(buffType.caster)][int]
set Arm.done[data.index]=false
set data.phase=0
set data.angle=GetUnitFacing(data.caster)+Angles(int)
set data.startangle=Angles(int)
set data.offset=DEFAULTOFFSET
call data.createChain()
set int=int+1
endloop
endif
endfunction
private function CleanArms takes aBuff buffType returns nothing
local integer int=0
local Arm data
loop
exitwhen int==MAXARMS
set data=Arm.ArmTable[buffType][int]
call data.KillArm()
set Arm.done[data.index]=true
set int=int+1
endloop
endfunction
private function Init takes nothing returns nothing
set buffType=aBuffType.create()
set buffType.eventCreate=AbilLearn
set buffType.eventCleanup=CleanArms
set buffType.countsAsBuff=false
call NewABuffHeroSkill(ABILID,buffType)
call SetupSpellVariables()
call ConfigureDamageOptions()
call Preload(CHAINMDL)
call Preload(CHAINHAND)
call Preload(ABSORBMDL)
call PreloadStart()
endfunction
endlibrary