// *************************************************************
// * OrbStacking -- Version 1.8.1
// * by Deaod
// *************************************************************
// *
// * CREDITS:
// * - Anitarf (DamageEvent, DamageModifiers)
// * - grim001 (AutoIndex)
// * - Rising_Dusk (GroupUtils)
// * - Vexorian (JassHelper, Table)
// * - MindWorX and PitzerMike (JassNewGenPack)
// * - Pipedream (Grimoire)
// * - SFilip (TESH)
// *
// * HOW TO IMPORT:
// * * Copy over this library and save. Its that easy.
// *
// * HOW TO USE:
// * * use Orb.create(integer AID, integer Method, integer Priority, OrbCallback Func)
// * to create a new Orb.
// * - AID is the ID of the ability representing the orb
// * (in the case of an item representing the orb, pass
// * the rawcode of the item)
// * - Method is the type of ability triggerin the orb
// * (either ORB_TYPE_SKILL, if the ability is a
// * skill, or ORB_TYPE_ITEM, if an item triggers
// * the orb)
// * - Priority is the priority of the orb. When stacking
// * different orbs on the same unit, the orb with the
// * highest priority will get executed first.
// * If a unit has two orbs of equal priority, the one
// * acquired first will be executed first.
// * - Func is the function being run when the orb is
// * triggered. It has to follow the OrbCallback
// * interface defined below. A function following that
// * interface has to return a boolean, either
// * ORB_APPLIED or ORB_NOT_APPLIED.
// * If you return ORB_APPLIED, all Categories of type
// * WRITE will be written (their execution-count will
// * be increased)
// *
// * * add that Orb to a category using YourOrb.addToCategory(string Label, integer Type)
// * - Label is the label of the category you want to
// * add that orb to.
// * Every Orb can belong to up to MAX_CATEGORIES
// * categories
// * - Type defines the behaviour of the orb.
// * ORB_CATEGORY_TYPE_READ makes the orb only "read" from
// * the Category. That means it only checks whether
// * that category has been written in any orb before.
// * ORB_CATEGORY_TYPE_WRITE makes the orb only "write" to
// * the Category. That means it only marks the Category
// * as executed but does not depend on the Category not
// * being executed before.
// * ORB_CATEGORY_TYPE_READWRITE combines READ and WRITE.
// * That means, the orb will not be executed, if the
// * Category has been executed before and it will mark
// * the Category as executed when the orb is triggered
// *
// * * each Category has an execution limit and cant be executed
// * more often than that limit. You can change that limit
// * using this: set OrbCategory[label].execLimit=some_new_limit
// * - label is the label of the Category which's execution
// * limit you want to change
// *
// * * you can add a missile modifier to each Orb by using
// * YourOrb.setMissileModifier(integer Ability)
// * - Ability is the rawcode of an ability that places BID
// * on the attacked unit and modifies the missile.
// * You should base this ability off of AIcb for optimum
// * performance.
// * If a unit has an orb with a missile modifier, that
// * modifier gets used. If the unit has more than one, the
// * one of the orb with highest priority gets used.
// * Missile modifiers currently dont get added to units already
// * having the ability/item for the orb when the orb is created
// * in the code. Though i dont think this will ever be an issue
// * please tell me if you run into a situation where it is.
// * Note that you wont be able to change the missile if the
// * dummy ability isnt able to change the missile under the
// * given circumstances.
// *
// * * GetDamagingUnit(), GetDamagedUnit() and GetDamage() can be
// * used to access GetEventDamageSource(), GetTriggerUnit()
// * and GetEventDamage() inside OrbCallback functions
// * executed by this script
// *
// * * if you want to prevent damage or deal additional damage
// * through orbs, use the SetDamage(real new) function.
// * Pass the new amount of damage that attack should deal.
// * Using it outside Orb Callbacks wont have any effect.
// *
// * * by default, all units that get indexed by AutoIndex upon
// * entering the map, automatically get the dummy ability for
// * OrbStacking. If there are specific types you want to
// * exclude from acquiring orbs, filter them out using the
// * UnitFilter function right below the first globals block.
// * If you add the dummy ability to a unit, that unit wont
// * be able to use other orbs anymore. They also should not
// * be able to attack ground as that can cause crashes.
// *
// *************************************************************
library OrbStacking uses Table, DamageEvent, DamageModifiers, AutoIndex, optional GroupUtils
// these are the objects required for this library to function
// you can deactivate these after saving and immediately reopening the map (by removing the ! or adding a / in the front).
//! external ObjectMerger w3a AIcb AOrb anam "Generic Orb" aart "" arac "0" amat "" asat "" aspt "" atat "" ata0 "" Iarp 1 0 Idic 1 0 Iob5 1 0 abuf 1 "BORB" ahdu 1 "0.01" adur 1 "0.01" aite "0"
//! external ObjectMerger w3h Bfro BORB fnam "Proxy Orb" ftat "" ftip "Proxy Orb" fube ""
globals
constant key ORB_TYPE_SKILL //
constant key ORB_TYPE_ITEM //
constant key ORB_CATEGORY_TYPE_READ // categories of this type for the orb must not have been written before
constant key ORB_CATEGORY_TYPE_WRITE // whenever an orb belonging to this type of category is triggered that category becomes unusable
constant key ORB_CATEGORY_TYPE_READWRITE // combines READ and WRITE
constant boolean ORB_APPLIED = true
constant boolean ORB_NOT_APPLIED = false
private constant integer AID = 'AOrb' // Based off of AIcb, places BID on attacked unit.
private constant integer BID = 'BORB' // the buff placed by the dummy orb ability // based on Orb of Corruption
private constant integer MAX_CATEGORIES = 5 // the maximum categories an orb can belong to
private constant integer DEFAULT_CATEGORY_EXEC_LIMIT = 1 // the default execution limit of a newly created OrbCategory
private constant boolean REMOVE_BUFF_BEFORE = true // change to false, if you need the buff in one of your callbacks
private constant integer ORB_PRIORITY = 0x7FFFFFFF // highest possible value, meaning all orbs will get executed before any other damage modifiers get a chance
endglobals
// If theres any specific unit-type you want to exclude from using Orbs at all, filter it out here.
// Note that AutoIndex already filters out xe's dummies, and that units not indexed by AutoIndex wont be able to use Orbs
private function UnitFilter takes unit u returns boolean
return true
endfunction
// Dont touch it, its just here for you to see
function interface OrbCallback takes nothing returns boolean
// Dont touch anything below.
globals
private unit Damaging
private unit Damaged
private real Damage
endglobals
struct OrbCategory
private integer execlimit
private static StringTable CategoryTable
method operator execLimit takes nothing returns integer
return execlimit
endmethod
method operator execLimit= takes integer new returns nothing
debug if new<=0 then
debug call BJDebugMsg("OrbCategory: New execution limit too low!")
debug return
debug endif
set execlimit=new
endmethod
private static method Create takes string label returns thistype
local thistype s=allocate()
set s.execLimit=DEFAULT_CATEGORY_EXEC_LIMIT
set CategoryTable[label]=s
return s
endmethod
static method create takes string label returns thistype
if CategoryTable.exists(label) then
return CategoryTable[label]
else
return Create(label)
endif
endmethod
static method operator [] takes string label returns thistype // a cleaner wrapper
return create(label)
endmethod
private method destroy takes nothing returns nothing
// i hope this prevents idiots from destroying a Category
endmethod
private static method onInit takes nothing returns nothing
set OrbCategory.CategoryTable=StringTable.create()
endmethod
endstruct
private keyword UnitOrb
// the following are terrible hacks to create library-private struct members
private keyword aid
private keyword Method
private keyword Cat
private keyword CatType
private keyword CatCnt
private keyword cb
private keyword Priority
private keyword MissileModAbil
private struct OrbUnit extends DamageModifier
private unit self
private integer id
readonly UnitOrb missileMod
private static integer array OrbCount
private static thistype array UnitInstance
method destroy takes nothing returns nothing
set OrbCount[id]=OrbCount[id]-1
if OrbCount[id]==0 then
set self=null
set UnitInstance[id]=0
call deallocate()
endif
endmethod
method newMissileMod takes UnitOrb newmod returns nothing
call UnitRemoveAbility(self, missileMod.orb.MissileModAbil)
call UnitAddAbility(self, newmod.orb.MissileModAbil)
set missileMod=newmod
endmethod
method resetMissileMod takes nothing returns nothing
call UnitRemoveAbility(self, missileMod.orb.MissileModAbil)
call UnitAddAbility(self, AID)
set missileMod=UnitOrb[self]
endmethod
private method onDamageDealt takes unit target, real damage returns real
local integer i
local Orb s
local integer array b // counts the execution of each category
local boolean t
local UnitOrb o
if GetUnitAbilityLevel(target, BID)<=0 then
// not triggered by an attack.
return 0.
endif
static if REMOVE_BUFF_BEFORE then
call UnitRemoveAbility(target, BID)
endif
set Damaging=self
set Damaged=target
set Damage=damage
set o=UnitOrb[self]
loop
exitwhen o==0
set s=o.orb
set i=0
set t=true
loop
exitwhen i>=s.CatCnt
// lets see if were allowed to trigger the Orb
if (s.CatType[I]==ORB_CATEGORY_TYPE_READ or s.CatType[I]==ORB_CATEGORY_TYPE_READWRITE) and b[s.Cat[I]]>=s.Cat[I].execLimit then
set t=false // we arent
exitwhen true
endif
set i=i+1
endloop
if t and s.cb.evaluate()==ORB_APPLIED then // additionally check if the Orb actually triggered (for chance based Orbs)
set i=0
loop
exitwhen i>=s.CatCnt
// now, lets mark the categories as execute once more
if s.CatType[I]==ORB_CATEGORY_TYPE_WRITE or s.CatType[I]==ORB_CATEGORY_TYPE_READWRITE then
set b[s.Cat[I]]=b[s.Cat[I]]+1
endif
set i=i+1
endloop
endif
set o=o[o.index+1] // and loop through the Orbs of the unit
endloop
return Damage-damage // return newdamage-olddamage
endmethod
static method create takes unit u returns thistype
local integer id=GetUnitId(u)
if UnitInstance[id]==0 then
set UnitInstance[id]=allocate(u, ORB_PRIORITY)
set UnitInstance[id].id=id
set UnitInstance[id].self=u
set UnitInstance[id].missileMod=AID
endif
set OrbCount[id]=OrbCount[id]+1
return UnitInstance[id]
endmethod
endstruct
private struct UnitOrb
Orb orb
OrbUnit orbunit
implement AutoDestroy
static method clear takes unit u returns nothing
local thistype o
local thistype p
set o=UnitOrb[/I][/I][/I][/I][/I][/I][/I][/I]
loop
exitwhen o==0
set p=o[o.index+1]
call o.destroy()
set o=p
endloop
endmethod
private method onDestroy takes nothing returns nothing
local thistype o
if orbunit.missileMod==this then
set o=o[o.index+1] // start with the next orb in line
loop
exitwhen o==0
if o.orb.MissileModAbil!=AID then
call o.orbunit.newMissileMod(o)
endif
set o=o[o.index+1]
endloop
if o.orbunit.missileMod==this then
call o.orbunit.resetMissileMod()
endif
endif
call orbunit.destroy()
endmethod
static method create takes unit u, Orb orb returns thistype
local thistype s=allocate()
local thistype o
local boolean b // theres a missile modifier with higher priority or the orb does not have a missile modifier.
set s.orb=orb
set s.orbunit=OrbUnit.create(u)
set o=UnitOrb
set b=s.orb.MissileModAbil==AID
// search the right place to insert
loop
exitwhen o==0 or o.orb.Priority<orb.Priority
if not b and o.orb.MissileModAbil!=AID then
set b=true
endif
set o=o[o.index+1]
endloop
set s.me=u // insert at end
if not b then
// apply new Missile Modifier
call s.orbunit.newMissileMod(s)
endif
loop // shift instances moving new instance to the right place
exitwhen o==0
set o.me=u
set o=o[o.index+1]
endloop
return s
endmethod
endstruct
struct Orb
integer aid
integer Method
OrbCategory array Cat[MAX_CATEGORIES]
integer array CatType[MAX_CATEGORIES]
integer CatCnt=0
OrbCallback cb
integer Priority=0
integer MissileModAbil=AID
private integer i
private static thistype array Structs
private static integer Count=0
private static Table OrbTable
private static rect WorldRect
private static group InitGroup
private static thistype TempOrb
private static method OnDestroyCheck takes nothing returns boolean
local UnitOrb o
local UnitOrb next
set o=UnitOrb[GetFilterUnit()]
loop
exitwhen o<=0
if o.orb==TempOrb then
set next=o[o.index+1]
call o.destroy()
set o=next
else
set o=o[o.index+1]
endif
endloop
return false
endmethod
method onDestroy takes nothing returns nothing
set TempOrb=this
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInRect(ENUM_GROUP, WorldRect, function thistype.OnDestroyCheck)
else
call GroupEnumUnitsInRect(InitGroup, WorldRect, function thistype.OnDestroyCheck)
endif
if Method==ORB_TYPE_SKILL then
set Count=Count-1
set Structs=Structs[Count]
set Structs.i=i
endif
call OrbTable.flush(.aid)
endmethod
private static method OnCreateCheck takes nothing returns boolean
local unit u=GetFilterUnit()
if UnitFilter(u) then
if TempOrb.Method==ORB_TYPE_SKILL and GetUnitAbilityLevel(u, TempOrb.aid)>0 then
call UnitOrb.create(u, TempOrb)
elseif TempOrb.Method==ORB_TYPE_ITEM and UnitHasItemOfTypeBJ(u, TempOrb.aid) then
call UnitOrb.create(u, TempOrb)
endif
endif
set u=null
return false
endmethod
static method create takes integer AID, integer Method, integer Priority, OrbCallback Func returns Orb
local thistype s=allocate()
set s.aid=AID
set s.cb=Func
if Method==ORB_TYPE_SKILL then
set Structs[Count]=s
set s.i=Count
set Count=Count+1
endif
set s.Method=Method
set s.Priority=Priority
set OrbTable[AID]=s
set TempOrb=s
static if LIBRARY_GroupUtils then
call GroupEnumUnitsInRect(ENUM_GROUP, WorldRect, function thistype.OnCreateCheck)
else
call GroupEnumUnitsInRect(InitGroup, WorldRect, function thistype.OnCreateCheck)
endif
return s
endmethod
method addToCategory takes string Label, integer Type returns nothing
debug if CatCnt>=MAX_CATEGORIES or Label=="" or Label==null or (Type!=ORB_CATEGORY_TYPE_READ and Type!=ORB_CATEGORY_TYPE_WRITE and Type!=ORB_CATEGORY_TYPE_READWRITE) then
debug call BJDebugMsg("OrbStacking: Can't add Orb["+I2S(this)+"] to another OrbCategory (wrong label, invalid type, or maximum categories exceeded)!")
debug return
debug endif
set Cat[CatCnt]=OrbCategory.create(Label)
set CatType[CatCnt]=Type
set CatCnt=CatCnt+1
endmethod
method setMissileModifier takes integer Ability returns nothing
set MissileModAbil=Ability
endmethod
private static method UnitCreated takes unit u returns nothing
local integer i=Orb.Count-1
local thistype s
if UnitFilter(u) then
call UnitAddAbility(u, AID)
loop
exitwhen i<0
set s=Structs
if GetUnitAbilityLevel(u, s.aid)>0 then
call UnitOrb.create(u, s)
endif
set i=i-1
endloop
endif
endmethod
private static method OnUpgrade takes nothing returns nothing
local unit u=GetTriggerUnit()
call UnitOrb.clear(u)
call UnitCreated(u)
set u=null
endmethod
private static method OnSkill takes nothing returns nothing
local thistype s=OrbTable[GetLearnedSkill()]
local UnitOrb o
if UnitFilter(GetTriggerUnit()) and s>0 and GetLearnedSkillLevel()==1 then
set o=UnitOrb.create(GetTriggerUnit(), s)
endif
endmethod
private static method OnItemPickup takes nothing returns nothing
local thistype s=OrbTable[GetItemTypeId(GetManipulatedItem())]
local UnitOrb o
if UnitFilter(GetTriggerUnit()) and s>0 then
set o=UnitOrb.create(GetTriggerUnit(), s)
endif
endmethod
private static method OnItemDrop takes nothing returns nothing
local thistype s=OrbTable[GetItemTypeId(GetManipulatedItem())]
local UnitOrb o
if s>0 then
set o=UnitOrb[GetTriggerUnit()]
loop
exitwhen o<=0
if o.orb==s then
call o.destroy()
return
endif
set o=o[o.index+1]
endloop
endif
endmethod
private static method OnUnitAddAbility takes unit u, integer aid returns nothing
local thistype s=OrbTable[aid]
local UnitOrb o
if UnitFilter(u) and s>0 and GetUnitAbilityLevel(u, aid)<=0 then
set o=UnitOrb.create(u, s)
endif
endmethod
private static method OnUnitAddAbilityBJ takes integer aid, unit u returns nothing
call OnUnitAddAbility(u, aid)
endmethod
private static method OnUnitRemoveAbility takes unit u, integer aid returns nothing
local thistype s=OrbTable[aid]
local UnitOrb o
if s>0 and GetUnitAbilityLevel(u, aid)>0 then
set o=UnitOrb
loop
exitwhen o<=0
if o.orb==s then
call o.destroy()
return
endif
set o=o[o.index+1]
endloop
endif
endmethod
private static method OnUnitRemoveAbilityBJ takes integer aid, unit u returns nothing
call OnUnitRemoveAbility(u, aid)
endmethod
private static method onInit takes nothing returns nothing
local trigger t
// Listener for units entering the map
call OnUnitIndexed(UnitCreated)
// Listener for upgrading a unit
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_UPGRADE_FINISH)
call TriggerAddAction(t, function thistype.OnUpgrade)
// Listener for learning an ability
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
call TriggerAddAction(t, function thistype.OnSkill)
// Listener for picking up and item
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
call TriggerAddAction(t, function thistype.OnItemPickup)
// Listener for dropping an item
set t=CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DROP_ITEM)
call TriggerAddAction(t, function thistype.OnItemDrop)
set OrbTable=Table.create()
set WorldRect=GetWorldBounds() // sadly, you cant initialize rects using this function inside the globals block, unless you want the game to crash
static if not LIBRARY_GroupUtils then
set InitGroup=CreateGroup() // only initialize if necessary.
endif
endmethod
endstruct
hook UnitAddAbility Orb.OnUnitAddAbility
hook UnitAddAbilityBJ Orb.OnUnitAddAbilityBJ
hook UnitRemoveAbility Orb.OnUnitRemoveAbility
hook UnitRemoveAbilityBJ Orb.OnUnitRemoveAbilityBJ
hook UnitRemoveBuffBJ Orb.OnUnitRemoveAbilityBJ
function GetDamagingUnit takes nothing returns unit
return Damaging
endfunction
function GetDamagedUnit takes nothing returns unit
return Damaged
endfunction
function GetDamage takes nothing returns real
return Damage
endfunction
function SetDamage takes real new returns nothing
set Damage=new
endfunction
endlibrary