Name | Type | is_array | initial_value |
//TESH.scrollpos=85
//TESH.alwaysfold=0
//############################## ~AdvancedAura~ ######################################//
//##
//## Version: 1.0
//## System: Axarion
//## IndexerUtils: Axarion
//## AbilityEvent: Axarion
//## AutoIndex: grim001
//## AIDS: Jesus4Lyf
//## UnitIndexer: Nestharus
//## TimerUtils: Vexorian
//## GroupUtils: Rising_Dusk
//##
//############################### DESCRIPTION ###################################//
//##
//## This System allows to create custom auras. It was made, because ability
//## only auras arent very flexible and have bad filters. Also normal auras are
//## limited to specific bonuses.
//##
//############################### HOW DOES IT WORK ##############################//
//##
//## To use this system create an array struct and implement the AdvancedAura module.
//## You can optionally implement AdvancedAuraBuff and AdvancedAuraBonus.
//## In the structs onInit method you have to define the ability for the aura and
//## the buffs ability if you use UnitAuraBuff. When a unit acquires the ability the
//## aura will be added automatically so you don't have to do it yourself. To define
//## the ability just use:
//##
//## static constant integer ability = 'AURA'
//##
//## The aura will be paused if the unit is a hero and it dies. If a unit leaves
//## the map the aura will be removed, so you don't have to worry about leaking
//## struct instances.
//##
//################################# METHODS #####################################//
//##
//## You can implement these functions into your UnitAura struct. They are
//## all optional.
//##
//## - happens when a new aura is created (define the bonuses here)
//## method AuraInit takes nothing returns nothing
//##
//## - happens when a unit enters the aura
//## method onAffect takes unit u returns nothing
//##
//## - happens when a unit leaves the aura
//## method onUnaffect takes unit u returns nothing
//##
//## - happens when a unit stayed in the aura
//## method onLoop takes unit u returns nothing
//##
//## - checks if the unit is a valid target for the aura
//## method onFilter takes unit u returns boolean
//##
//## - happens when the aura leveled up (increase the bonuses here)
//## method onLevelUp takes nothing returns nothing
//##
//## - define the AoE here
//## method GetAuraAoE takes nothing returns real
//##
//################################# API & Variables ##############################//
//##
//## - the unit owning the aura:
//## .owner
//##
//## - the level of the aura:
//## .level
//##
//## - the aura ability
//## ability
//##
//## - the interval of the aura timer:
//## INTERVAL
//##
//## - the auras group with all currently affected units
//## .InstanceGroup
//##
//################################# KNOWN BUGS ###################################//
//##
//## AutoIndex:
//## - when using AutoIndex auras will not be created for preplaced units.
//## I don't know why if you know why tell me, please. I think its something
//## with TimerUtils and JassHelper's priorities.
//##
//## AIDS:
//## - you need to make TimerUtils a requirement for AIDS or it will bug for
//## preplaced units with the ability
//##
//## UnitIndexer
//## - none found yet, therefore i recommend you to use this indexer.
//##
//################################################################################//
native UnitAlive takes unit id returns boolean // may be removed if already declared.
library AdvancedAura requires TimerUtils, GroupUtils, AbilityEvent
module AdvancedAura
delegate AuraDelegate AuraDefault
private static thistype array Instances
private static integer InstancesTotal
static thistype curr
static real INTERVAL
private static timer AuraTimer
private integer Index
readonly group InstanceGroup
private unit InstanceUnit
private integer Level
private boolean pause
readonly integer affectedCount
method operator owner takes nothing returns unit
return .InstanceUnit
endmethod
method operator level takes nothing returns integer
return .Level
endmethod
method operator level= takes integer newlevel returns nothing
loop
exitwhen newlevel == .Level
set .Level = .Level + 1
call .onLevelUp()
endloop
endmethod
static method operator [] takes unit u returns thistype
static if LIBRARY_AIDS then
return GetUnitIndex(u)
else
return GetUnitId(u)
endif
endmethod
static method operator []= takes unit u, thistype this returns nothing
set thistype[u] = this
endmethod
private method affect takes unit u returns nothing
set .affectedCount = .affectedCount + 1
static if thistype.BUFF_VERIFIER then
call .addBuff(u)
endif
static if thistype.BONUS_VERIFIER then
call .addBonuses(u)
endif
static if thistype.EFFECT_VERIFIER then
call .addEffect(u)
endif
call curr.onAffectUnit(u)
endmethod
private method unaffect takes unit u returns nothing
set .affectedCount = .affectedCount - 1
static if thistype.BUFF_VERIFIER then
call .removeBuff(u)
endif
static if thistype.BONUS_VERIFIER then
call .removeBonuses(u)
endif
static if thistype.EFFECT_VERIFIER then
call .removeEffect(u)
endif
call curr.onUnaffectUnit(u)
endmethod
private static method unaffectenum takes nothing returns nothing
local unit u = GetEnumUnit()
call curr.unaffect(u)
call GroupRemoveUnit(curr.InstanceGroup, u)
endmethod
private static method GroupEnum takes nothing returns boolean
local unit u = GetFilterUnit()
local boolean b = curr.onFilter(u)
if IsUnitInGroup(u, curr.InstanceGroup) and b then
call curr.onLoopUnit(u)
elseif b then
call curr.affect(u)
elseif IsUnitInGroup(u, curr.InstanceGroup) then
call curr.unaffect(u)
endif
call GroupRemoveUnit(curr.InstanceGroup, u)
set u = null
return b
endmethod
private static method AuraLoop takes nothing returns nothing
local integer i = 0
local thistype this
loop
set this = .Instances[i]
set curr = this
if .pause == false then
if GetUnitAbilityLevel(.InstanceUnit, ability) == null then
call thistype.removeAura(.InstanceUnit)
set i = i - 1
elseif UnitAlive(.InstanceUnit) == false and IsUnitType(.InstanceUnit, UNIT_TYPE_DEAD) then
if IsUnitType(.InstanceUnit, UNIT_TYPE_HERO) then
set .pause = true
call ForGroup(.InstanceGroup, function thistype.unaffectenum)
else
call thistype.removeAura(.InstanceUnit)
set i = i - 1
endif
elseif UnitAlive(.InstanceUnit) and IsUnitType(.InstanceUnit, UNIT_TYPE_DEAD) == false and .pause then
set .pause = false
else
set .level = GetUnitAbilityLevel(.InstanceUnit, ability)
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(.InstanceUnit), GetUnitY(.InstanceUnit), .GetAuraAoE(), Filter(function thistype.GroupEnum))
call ForGroup(.InstanceGroup, function thistype.unaffectenum)
call GroupAddGroup(ENUM_GROUP, .InstanceGroup)
call .onLoop()
endif
endif
set i = i + 1
exitwhen i >= .InstancesTotal
endloop
endmethod
private static method removeAura takes unit u returns nothing
local thistype this = thistype[u]
if this != 0 then
set .InstanceUnit = null
call ReleaseGroup(.InstanceGroup)
set .Level = 0
set .Instances[.Index] = .Instances[.InstancesTotal]
set .Instances[.InstancesTotal] = 0
set .InstancesTotal = .InstancesTotal - 1
static if thistype.EFFECT_VERIFIER then
call .removeOwnerEffect()
endif
if .InstancesTotal <= 0 then
call PauseTimer(thistype.AuraTimer)
call ReleaseTimer(thistype.AuraTimer)
endif
endif
endmethod
private static method addAura takes unit u returns nothing
local thistype this = thistype[u]
set .InstanceUnit = u
set .InstanceGroup = NewGroup()
set .Level = GetUnitAbilityLevel(u, ability)
set .pause = false
static if thistype.EFFECT_VERIFIER then
call .addOwnerEffect()
endif
call .AuraInit()
set .Instances[.InstancesTotal] = this
set .Index = .InstancesTotal
if .InstancesTotal == 0 then
set thistype.AuraTimer = NewTimer()
call TimerStart(thistype.AuraTimer, thistype.INTERVAL ,true, function thistype.AuraLoop)
endif
set .InstancesTotal = .InstancesTotal + 1
endmethod
private static method onInit takes nothing returns nothing
set .InstancesTotal = 0
endmethod
static method onIndexWithAbility takes unit u returns nothing
call thistype.addAura(u)
endmethod
static method onDeindexWithAbility takes unit u returns nothing
if thistype[u] != 0 then
call thistype.removeAura(u)
endif
endmethod
static method onSkillAbility takes unit u, integer id returns nothing
if GetUnitAbilityLevel(u, ability) == 1 then
call thistype.addAura(u)
endif
endmethod
static method onAddAbility takes unit u, integer id returns nothing
call thistype.addAura(u)
endmethod
static method onRemoveAbility takes unit u, integer id returns nothing
call thistype.removeAura(u)
endmethod
implement AbilityEvent
endmodule
struct AuraDelegate
method AuraInit takes nothing returns nothing
endmethod
method onAffectUnit takes unit u returns nothing
endmethod
method GetAuraAoE takes nothing returns real
return 900.
endmethod
method onUnaffectUnit takes unit u returns nothing
endmethod
method onLoopUnit takes unit u returns nothing
endmethod
method onLoop takes nothing returns nothing
endmethod
method onFilter takes unit u returns boolean
return true
endmethod
method onLevelUp takes nothing returns nothing
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//############################### CREDITS #######################################//
//##
//## Version: 1.0
//## System: Axarion
//## IndexerUtils: Axarion
//## AbilityEvent: Axarion
//## AutoIndex: grim001
//## AIDS: Jesus4Lyf
//## UnitIndexer: Nestharus
//## TimerUtils: Vexorian
//## GroupUtils: Rising_Dusk
//##
//############################### DESCRIPTION ###################################//
//##
//## This library enables you to add buffs to your aura.
//##
//############################### HOW DOES IT WORK ##############################//
//##
//## Just implement AdvancedAuraBuff before you implement AdvancedAura
//## and define the BUFF in the onInit method of your struct and your done.
//## The ability should be a modified Tornado Slow Aura with targets set as
//## only self and range of 0.01.
//## Also you should modify the buff/create a new one.
//##
//## set BUFF = 'Haxx'
//##
//################################################################################//
library AdvancedAuraBuff requires AdvancedAura
module AdvancedAuraBuff
static integer BUFF
private static integer array LOCK
static constant boolean BUFF_VERIFIER = true // for checking if the module is implemented.
method addBuff takes unit u returns nothing
if BUFF != 0 then
if LOCK[GetUnitId(u)] == 0 then
call UnitAddAbility(u, thistype.BUFF)
call UnitMakeAbilityPermanent(u, true, thistype.BUFF)
endif
set LOCK[GetUnitId(u)] = LOCK[GetUnitId(u)] + 1
endif
endmethod
method removeBuff takes unit u returns nothing
if BUFF != 0 then
set LOCK[GetUnitId(u)] = LOCK[GetUnitId(u)] - 1
if LOCK[GetUnitId(u)] == 0 then
call UnitRemoveAbility(u, thistype.BUFF)
endif
endif
endmethod
endmodule
endlibrary
//TESH.scrollpos=18
//TESH.alwaysfold=0
//############################### CREDITS #######################################//
//##
//## Version: 1.0
//## System: Axarion
//## IndexerUtils: Axarion
//## AbilityEvent: Axarion
//## AutoIndex: grim001
//## AIDS: Jesus4Lyf
//## UnitIndexer: Nestharus
//## TimerUtils: Vexorian
//## GroupUtils: Rising_Dusk
//##
//############################### DESCRIPTION ###################################//
//##
//## This library enables you to add bonuses to your aura.
//##
//############################### HOW DOES IT WORK ##############################//
//##
//## Just implement AdvancedAuraBonus before you implement AdvancedAura and define
//## the bonuses in the AuraInit method of your struct and increase/decrease them
//## in the onLevelUp method if you want. (set .AURA_BONUS_DAMAGE = x * .level)
//## This library requires BonusMod and AbilityPreload to work.
//##
//## ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//## | Aura Bonus Type constants: | Minimum bonus: | Maximum bonus: |
//## ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//## | AURA_BONUS_SIGHT_RANGE | -2048 | +2047 |
//## | AURA_BONUS_ATTACK_SPEED | -512 | +511 |
//## | AURA_BONUS_ARMOR | -1024 | +1023 |
//## | AURA_BONUS_MANA_REGEN_PERCENT | -512% | +511% |
//## | AURA_BONUS_LIFE_REGEN | -256 | +255 |
//## | AURA_BONUS_DAMAGE | -1024 | +1023 |
//## | AURA_BONUS_STRENGTH | -256 | +255 |
//## | AURA_BONUS_AGILITY | -256 | +255 |
//## | AURA_BONUS_INTELLIGENCE | -256 | +255 |
//## ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//##
//################################################################################//
library AdvancedAuraBonus requires AdvancedAura, BonusMod
module AdvancedAuraBonus
static constant boolean BONUS_VERIFIER = true // for checking if the module is implemented.
//! runtextmacro AuraBonusCreate("DAMAGE")
//! runtextmacro AuraBonusCreate("ARMOR")
//! runtextmacro AuraBonusCreate("ATTACK_SPEED")
//! runtextmacro AuraBonusCreate("SIGHT_RANGE")
//! runtextmacro AuraBonusCreate("LIFE_REGEN")
//! runtextmacro AuraBonusCreate("MANA_REGEN_PERCENT")
//! runtextmacro AuraBonusCreate("STRENGTH")
//! runtextmacro AuraBonusCreate("AGILITY")
//! runtextmacro AuraBonusCreate("INTELLIGENCE")
method addBonuses takes unit u returns nothing
//! runtextmacro AuraBonusAdd("DAMAGE", "")
//! runtextmacro AuraBonusAdd("ARMOR", "")
//! runtextmacro AuraBonusAdd("ATTACK_SPEED", "")
//! runtextmacro AuraBonusAdd("SIGHT_RANGE", "")
//! runtextmacro AuraBonusAdd("LIFE_REGEN", "")
//! runtextmacro AuraBonusAdd("MANA_REGEN_PERCENT", "")
//! runtextmacro AuraBonusAddHero("STRENGTH", "")
//! runtextmacro AuraBonusAddHero("AGILITY", "")
//! runtextmacro AuraBonusAddHero("INTELLIGENCE", "")
endmethod
method removeBonuses takes unit u returns nothing
//! runtextmacro AuraBonusAdd("DAMAGE", "-")
//! runtextmacro AuraBonusAdd("ARMOR", "-")
//! runtextmacro AuraBonusAdd("ATTACK_SPEED", "-")
//! runtextmacro AuraBonusAdd("SIGHT_RANGE", "-")
//! runtextmacro AuraBonusAdd("LIFE_REGEN", "-")
//! runtextmacro AuraBonusAdd("MANA_REGEN_PERCENT", "-")
//! runtextmacro AuraBonusAddHero("STRENGTH", "-")
//! runtextmacro AuraBonusAddHero("AGILITY", "-")
//! runtextmacro AuraBonusAddHero("INTELLIGENCE", "-")
endmethod
endmodule
//! textmacro AuraBonusCreate takes NAME
private integer AURA_BONUS_$NAME$_SAVER
private static method $NAME$_UPDATE_REM takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this = curr
call AddUnitBonus(u, BONUS_$NAME$, -.AURA_BONUS_$NAME$_SAVER)
set u = null
endmethod
private static method $NAME$_UPDATE_ADD takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this = curr
call AddUnitBonus(u, BONUS_$NAME$, .AURA_BONUS_$NAME$_SAVER)
set u = null
endmethod
method operator AURA_BONUS_$NAME$= takes integer i returns nothing
if .AURA_BONUS_$NAME$_SAVER != 0 then
call ForGroup(.InstanceGroup, function thistype.$NAME$_UPDATE_REM)
endif
set .AURA_BONUS_$NAME$_SAVER = i
if .AURA_BONUS_$NAME$_SAVER != 0 then
call ForGroup(.InstanceGroup, function thistype.$NAME$_UPDATE_ADD)
endif
endmethod
method operator AURA_BONUS_$NAME$ takes nothing returns integer
return .AURA_BONUS_$NAME$_SAVER
endmethod
//! endtextmacro
//! textmacro AuraBonusAdd takes NAME, plusorminus
if .AURA_BONUS_$NAME$_SAVER != 0 then
call AddUnitBonus(u, BONUS_$NAME$, $plusorminus$.AURA_BONUS_$NAME$_SAVER)
endif
//! endtextmacro
//! textmacro AuraBonusAddHero takes NAME, plusorminus
if IsUnitType(u, UNIT_TYPE_HERO) and .AURA_BONUS_$NAME$_SAVER != 0 then
call AddUnitBonus(u, BONUS_$NAME$, $plusorminus$.AURA_BONUS_$NAME$_SAVER)
endif
//! endtextmacro
endlibrary
//TESH.scrollpos=38
//TESH.alwaysfold=0
//############################### CREDITS #######################################//
//##
//## Version: 1.0
//## System: Axarion
//## IndexerUtils: Axarion
//## AbilityEvent: Axarion
//## AutoIndex: grim001
//## AIDS: Jesus4Lyf
//## UnitIndexer: Nestharus
//## TimerUtils: Vexorian
//## GroupUtils: Rising_Dusk
//##
//############################### DESCRIPTION ###################################//
//##
//## This library enables you to add effects to your aura.
//##
//############################### HOW DOES IT WORK ##############################//
//##
//## Just implement AdvancedAuraEffect before you implement AdvancedAura
//## and define the TARGET_SFX, OWNER_SFX in the onInit method of your struct and your done.
//##
//## set TARGET_SFX = "Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl"
//## set TARGET_ATTACH = "origin"
//##
//## set OWNER_SFX = ""
//## set OWNER_ATTACH = "origin"
//##
//################################################################################//
library AdvancedAuraEffect requires AdvancedAura
module AdvancedAuraEffect
static constant boolean EFFECT_VERIFIER = true
private static integer array LOCK
static string TARGET_SFX = "Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl"
static string TARGET_ATTACH = "origin"
static string OWNER_SFX
static string OWNER_ATTACH = "origin"
private static effect array TARGET_EFFECT
private effect OWNER_EFFECT
method addEffect takes unit u returns nothing
if LOCK[GetUnitId(u)] == 0 then
set TARGET_EFFECT[GetUnitId(u)] = AddSpecialEffectTarget(TARGET_SFX, u, TARGET_ATTACH)
endif
set LOCK[GetUnitId(u)] = LOCK[GetUnitId(u)] + 1
endmethod
method removeEffect takes unit u returns nothing
set LOCK[GetUnitId(u)] = LOCK[GetUnitId(u)] - 1
if LOCK[GetUnitId(u)] == 0 then
call DestroyEffect(TARGET_EFFECT[GetUnitId(u)])
set TARGET_EFFECT[GetUnitId(u)] = null
endif
endmethod
method addOwnerEffect takes nothing returns nothing
set .OWNER_EFFECT = AddSpecialEffectTarget(OWNER_SFX, .owner, OWNER_ATTACH)
endmethod
method removeOwnerEffect takes nothing returns nothing
call DestroyEffect(.OWNER_EFFECT)
set .OWNER_EFFECT = null
endmethod
endmodule
endlibrary
//TESH.scrollpos=162
//TESH.alwaysfold=0
library BonusMod initializer OnInit requires optional AbilityPreload, optional xepreload
private keyword AbilityBonus
////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ BonusMod - v3.3.1
//@=============================================================================
//@ Credits:
//@-----------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@ Based on the work of:
//@ weaaddar
//@-----------------------------------------------------------------------------
//@ If you use this system, please at least credit weaaddar. Without him, this
//@ system would not exist. I would be happy if you credited me as well.
//@=============================================================================
//@ Requirements:
//@-----------------------------------------------------------------------------
//@ This library is written in vJass and thus requires JASS Helper in order to
//@ function correctly. This library also uses the ObjectMerger created by
//@ PitzerMike. The ObjectMerger must be configured as an external tool for
//@ JASS Helper.
//@
//@ All of these things are present in the NewGen world editor.
//@
//@=============================================================================
//@ Introduction:
//@-----------------------------------------------------------------------------
//@ BonusMod is a system for applying reversible bonuses to certain stats, such
//@ as attack speed or mana regen, for specific units. Most of the bonuses
//@ provided by BonusMod show green or red numbers in the command card, exactly
//@ like the bonuses provided by items.
//@
//@ BonusMod has two kinds of bonuses:
//@ 1. Ability based bonuses
//@ 2. Code based bonuses
//@
//@ All of the bonuses in the configuration section for the basic BonusMod
//@ library are ability-based bonuses. Code-based bonuses are provided by
//@ additional libraries.
//@
//@ Ability based bonuses have a limit to how much of a bonus they can apply.
//@ The actual limit depends on the number of abilities that type of bonus uses.
//@ See the "Default bonuses" section of this readme for the default limits
//@ of the bonuses that come with BonusMod. For changing the limits of the
//@ default bonuses, or for adding new types of bonuses, see the below
//@ configuration section.
//@
//@ Code based bonuses may or may not have a limit to how much of a bonus they
//@ can apply. The limits for code based bonuses depend entirely on how the
//@ bonus is implemented. See their documentation for more information.
//@
//@=============================================================================
//@ Adding BonusMod to your map:
//@-----------------------------------------------------------------------------
//@ First, you must place the BonusMod library in a custom-text trigger in your
//@ map.
//@
//@ You must then save your map with ability generation enabled. After you save
//@ your map with ability generation enabled, you must close your map in the
//@ editor, and reopen it. You can then disable ability generation.
//@ See the configuration section for information on how to enable and disable
//@ ability generation.
//@
//@=============================================================================
//@ Default bonuses:
//@-----------------------------------------------------------------------------
//@
//@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//@ | Bonus Type constants: | Minimum bonus: | Maximum bonus: |
//@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//@ | BONUS_SIGHT_RANGE | -2048 | +2047 |
//@ | BONUS_ATTACK_SPEED | -512 | +511 |
//@ | BONUS_ARMOR | -1024 | +1023 |
//@ | BONUS_MANA_REGEN_PERCENT | -512% | +511% |
//@ | BONUS_LIFE_REGEN | -256 | +255 |
//@ | BONUS_DAMAGE | -1024 | +1023 |
//@ | BONUS_STRENGTH | -256 | +255 |
//@ | BONUS_AGILITY | -256 | +255 |
//@ | BONUS_INTELLIGENCE | -256 | +255 |
//@ +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
//@
//@ Notes:
//@ - The bonuses for stength, agility, and intelligence can only be
//@ applied to heroes. Attempting to add them to normal units will
//@ fail to work completely.
//@ - Using a negative BONUS_STRENGTH bonus can give a unit negative
//@ maximum life. Don't do that. It really messes stuff up.
//@ - Using a negative BONUS_INTELLIGENCE bonus can remove a hero's
//@ mana. This is not a big issue, as mana will return when the
//@ bonus is removed.
//@ - The maximum effective sight range for a unit is 1800.
//@ - There is a maximum attack speed. I have no idea what it is.
//@
//@ See the configuration section for information on how to change the range of
//@ bonuses, as well as how to add new ability-based bonuses, and remove unused
//@ ones.
//@
//@=============================================================================
//@ Public API / Function list:
//@-----------------------------------------------------------------------------
//@ Note that BonusMod will only output error messages if JASS Helper is set to
//@ compile in debug mode.
//@
//@ Bonus constants such as BONUS_DAMAGE have .min and .max properties which
//@ are the minimum and maximum bonus that type of bonus can apply. Note that
//@ for code based bonuses, these constants may not reflect the minimum or
//@ maximum bonus for a specific unit. Use the IsBonusValid() function to check
//@ if the given bonus value is okay for a given unit.
//@
//@ function SetUnitBonus
//@ takes unit u, Bonus bonusType, integer amount
//@ returns integer
//@
//@ This function sets the bonus of the type bonusType for the given unit to
//@ the given amount. The returned integer is the unit's actual current
//@ bonus, after it has been changed. If the given amount is above the
//@ maximum possible bonus for this type, then the maximum possible bonus
//@ is applied to the unit. The same is true if the given value is below
//@ the minimum possible bonus.
//@
//@ function GetUnitBonus
//@ takes unit u, Bonus bonusType
//@ returns integer
//@
//@ Returns the given unit's current bonus of bonusType. A value of 0 means
//@ that the given unit does not have a bonus of the given type.
//@
//@ function AddUnitBonus
//@ takes unit u, Bonus bonusType, integer amount
//@ returns integer
//@
//@ Increases the unit's bonus by the given amount. You can use a negitive
//@ amount to subtract from the unit's current bonus. Note that the same
//@ rules SetUnitBonus has apply for going over/under the maximum bonus.
//@ The returned value is the unit's actual new bonus.
//@
//@ function RemoveUnitBonus
//@ takes unit u, Bonus bonusType
//@ returns nothing
//@
//@ Sets the bonus of the type bonusType to 0 for the given unit. This
//@ function is faster then using SetUnitBonus(u, bonusType, 0).
//@
//@ function IsBonusValid
//@ takes unit u, Bonus abstractBonus, integer value
//@ returns boolean
//@
//@ Returns true if the given value is a valid bonus value for the given
//@ unit. This will also return false if the given bonus type is a hero-
//@ only bonus type, and the given unit is not a hero.
//@
//@=============================================================================
//@ Writing code-based bonuses:
//@-----------------------------------------------------------------------------
//@ This section of the readme tells you how to create your own bonus types
//@ that apply their bonuses using vJass code instead of abilities. You do not
//@ need to read or understand this to use BonusMod as-is.
//@
//@ Creating a new bonus type is simple. Extend the Bonus struct, implement the
//@ methods provided within it, and create a single instance of your struct
//@ within a variable named BONUS_YOUR_BONUS_TYPES_NAME of the type Bonus.
//@
//@ The methods you must implement are:
//@
//@ method setBonus takes unit u, integer amount returns integer
//@ This method sets the given unit's current bonus to amount, returning
//@ the actual bonus that was applied. If the given amount is higher then
//@ the maximum amount your bonus type can apply to a unit, you must apply
//@ the maximum possible bonus, and return that amount. The same holds true
//@ for the minimum bonus.
//@
//@ method getBonus takes unit u returns integer
//@ This method returns the current bonus the given unit has.
//@
//@ method removeBonus takes unit u returns nothing
//@ This method sets the current bonus of the given unit to 0.
//@
//@ method isValueInRange takes integer value returns boolean
//@ This method returns true if the given integer is a valid bonus amount
//@ for this bonus type, and false otherwise.
//@
//@ Note that it is your responsibility to do any clean up in the event a unit
//@ dies or is removed with an active bonus on it. There is no guarantee that
//@ removeBonus() will be called before a unit dies or is removed.
//@
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
////////////////////////////////////////////////////////////////////////////////
//==============================================================================
// Configuration:
//==============================================================================
//------------------------------------------------------------------------------
// If the following constant is set to true, the abilities used by this library
// will be preloaded at map initialization. This will slightly increase loading
// time, but will prevent a slight to medium lag spike the first time a bonus
// of a type is applied.
//
// Note that your map must contain either the xepreload library, or the
// AbilityPreload library for preloading to work.
//
// It is highly recommended that you do not set this to false.
//------------------------------------------------------------------------------
globals
private constant boolean PRELOAD_ABILITIES = true
endglobals
//------------------------------------------------------------------------------
// The BonusMod_BeginBonuses macro takes a single boolean type parameter.
// If set to true, bonus abilities will be created (or recreated) on save.
// If set to false, abilities will not be generated.
//
// If you modify any of the bonus declaration macros, or add new ones, you must
// regenerate abilities.
//
// Note that if you remove a bonus, the abilities it had created will not be
// automatically removed. This is also true of reducing the number of abilities
// a bonus uses.
//
// After you generate abilities, you must close your map and reopen it in the
// editor. You can then disable ability generation until the next time you
// modify the bonus types.
//------------------------------------------------------------------------------
//! runtextmacro BonusMod_BeginBonuses("false")
//--------------------------------------------------------------------------
// Below are where bonus types are defined.
//
// The first parameter is the name of the bonus type. A constant will be
// generated for each bonus type, that will take the form: BONUS_NAME
//
// The second parameter is the maximum power of 2 the bonus type can add
// to a unit. For example, 8 abilities gives a range of -256 to +255.
//
// The third parameter is the base ability. The base ability must give the
// desired effect when the given field is changed.
//
// The fourth parameter is the rawcode prefix of the bonuses generated
// abilities. The prefix must be 3 characters long. Your map must not
// already contain bonuses which start with the given prefix.
//
// The fifth parameter is the object field to modify for each generated
// ability.
//
// The sixth parameter must be true of the bonus should only work on hero
// units, and false otherwise.
//
// The final parameter is the icon that will be displayed in the object
// editor. This has no effect on anything ingame.
//--------------------------------------------------------------------------
// | NAME |ABILITY|SOURCE |PREFIX|FIELD | HERO | ICON
// | | COUNT |ABILITY| | | ONLY |
//! runtextmacro BonusMod_DeclareBonus("ARMOR", "10", "AId1", "(A)", "Idef", "false", "BTNHumanArmorUpOne.blp")
//! runtextmacro BonusMod_DeclareBonus("DAMAGE", "10", "AItg", "(B)", "Iatt", "false", "BTNSteelMelee.blp")
//! runtextmacro BonusMod_DeclareBonus("SIGHT_RANGE", "11", "AIsi", "(C)", "Isib", "false", "BTNTelescope.blp")
//! runtextmacro BonusMod_DeclareBonus("LIFE_REGEN", "8", "Arel", "(E)", "Ihpr", "false", "BTNRingSkull.blp")
//! runtextmacro BonusMod_DeclareBonus("STRENGTH", "8", "AIa1", "(F)", "Istr", "true" , "BTNGoldRing.blp")
//! runtextmacro BonusMod_DeclareBonus("AGILITY", "8", "AIa1", "(G)", "Iagi", "true" , "BTNGoldRing.blp")
//! runtextmacro BonusMod_DeclareBonus("INTELLIGENCE", "8", "AIa1", "(H)", "Iint", "true" , "BTNGoldRing.blp")
// | NAME |ABILITY|SOURCE |PREFIX|FIELD |HERO | ICON
// | | COUNT |ABILITY| | | ONLY |
//! runtextmacro BonusMod_DeclarePercentBonus("ATTACK_SPEED", "9", "AIsx", "(I)", "Isx1", "false", "BTNGlove.blp")
//! runtextmacro BonusMod_DeclarePercentBonus("MANA_REGEN_PERCENT", "9", "AIrm", "(D)", "Imrp", "false", "BTNSobiMask.blp")
//! runtextmacro BonusMod_EndBonuses()
//==============================================================================
// End of configuration
//==============================================================================
//! textmacro BonusMod_BeginBonuses takes SHOULD_GENERATE_ABILITIES
private function Setup takes nothing returns nothing
// The following is a lua script for the ObjectMerger, used to generate abilities
//*
//! externalblock extension=lua ObjectMerger $FILENAME$
//! i if "$SHOULD_GENERATE_ABILITIES$" == "true" then
//! i function FormatName(name)
//! i name = string.lower(name)
//! i name = string.gsub(name, "_", " ")
//! i s = name
//! i name = ""
//! i for w in string.gmatch(s, "%a%w*") do
//! i name = name .. string.upper(string.sub(w, 1, 1)) .. string.sub(w, 2, -1)
//! i name = name .. " "
//! i end
//! i name = string.sub(name, 1, string.len(name) - 1)
//! i return name
//! i end
//! i function SetupAbility(name, suffix, icon, hero)
//! i makechange(current, "anam", "BonusMod - " .. FormatName(name))
//! i makechange(current, "ansf", "(" .. suffix .. ")")
//! i makechange(current, "aart", "ReplaceableTextures\\CommandButtons\\" .. icon)
//! i makechange(current, "aite", 0)
//! i if hero then
//! i makechange(current, "Iagi", 1, 0)
//! i makechange(current, "Iint", 1, 0)
//! i makechange(current, "Istr", 1, 0)
//! i end
//! i end
//! i function CreateAbility(sourceAbility, prefix, field, abilityCount, name, icon)
//! i powOf2 = abilityCount - 1
//! i lengthOfMax = string.len(tostring(2^abilityCount))
//! i for i = 0, powOf2 do
//! i padding = ""
//! i for k = 0, lengthOfMax - string.len(tostring(2^i)) - 1 do
//! i padding = padding .. "0"
//! i end
//! i createobject(sourceAbility, prefix .. string.sub(chars, i + 1, i + 1))
//! i SetupAbility(name, "+" .. padding .. tostring(2 ^ i), icon, true)
//! i makechange(current, field, 1, tostring(2^i))
//! i end
//! i createobject(sourceAbility, prefix .. "-")
//! i SetupAbility(name, "-" .. tostring(2 ^ abilityCount), icon, true)
//! i makechange(current, field, 1, tostring(-(2^abilityCount)))
//! i end
//! i function CreatePercentageAbility(sourceAbility, prefix, field, abilityCount, name, icon)
//! i powOf2 = abilityCount - 1
//! i lengthOfMax = string.len(tostring(2^abilityCount))
//! i for i = 0, powOf2 do
//! i padding = ""
//! i for k = 0, lengthOfMax - string.len(tostring(2^i)) - 1 do
//! i padding = padding .. "0"
//! i end
//! i createobject(sourceAbility, prefix .. string.sub(chars, i + 1, i + 1))
//! i SetupAbility(name, "+" .. padding .. tostring(2 ^ i) .. "%", icon, false)
//! i makechange(current, field, 1, tostring((2 ^ i) / 100))
//! i end
//! i createobject(sourceAbility, prefix .. "-")
//! i SetupAbility(name, "-" .. tostring(2 ^ abilityCount) .. "%", icon, false)
//! i makechange(current, field, 1, tostring(-((2 ^ abilityCount) / 100)))
//! i end
//! i setobjecttype("abilities")
//! i chars = "abcdefghijklmnopqrstuvwxyz"
//! i
// */
//! endtextmacro
//! textmacro BonusMod_DeclareBonus takes NAME, ABILITY_COUNT, SOURCE_ABILITY, RAWCODE_PREFIX, FIELD, HERO_ONLY, ICON
//! i CreateAbility("$SOURCE_ABILITY$", "$RAWCODE_PREFIX$", "$FIELD$", $ABILITY_COUNT$, "$NAME$", "$ICON$")
globals
Bonus BONUS_$NAME$
endglobals
set BONUS_$NAME$ = AbilityBonus.create('$RAWCODE_PREFIX$a', $ABILITY_COUNT$, '$RAWCODE_PREFIX$-', $HERO_ONLY$)
//! endtextmacro
//! textmacro BonusMod_DeclarePercentBonus takes NAME, ABILITY_COUNT, SOURCE_ABILITY, RAWCODE_PREFIX, FIELD, HERO_ONLY, ICON
//! i CreatePercentageAbility("$SOURCE_ABILITY$", "$RAWCODE_PREFIX$", "$FIELD$", $ABILITY_COUNT$, "$NAME$", "$ICON$")
globals
Bonus BONUS_$NAME$
endglobals
set BONUS_$NAME$ = AbilityBonus.create('$RAWCODE_PREFIX$a', $ABILITY_COUNT$, '$RAWCODE_PREFIX$-', $HERO_ONLY$)
//! endtextmacro
//! textmacro BonusMod_EndBonuses
//*
//! i end
//! endexternalblock
// */
endfunction
//! endtextmacro
// ===
// Precomputed integer powers of 2
// ===
globals
private integer array powersOf2
private integer powersOf2Count = 0
endglobals
// ===
// Utility functions
// ===
private function ErrorMsg takes string func, string s returns nothing
call BJDebugMsg("|cffFF0000BonusMod Error|r|cffFFFF00:|r |cff8080FF" + func + "|r|cffFFFF00:|r " + s)
endfunction
private function LoadAbility takes integer abilityId returns nothing
static if PRELOAD_ABILITIES then
static if LIBRARY_xepreload then
call XE_PreloadAbility(abilityId)
else
static if LIBRARY_AbilityPreload then
call AbilityPreload(abilityId)
endif
endif
endif
endfunction
// ===
// Bonus Types
// ===
private interface BonusInterface
integer minBonus = 0
integer maxBonus = 0
private method destroy takes nothing returns nothing defaults nothing
endinterface
private keyword isBonusObject
struct Bonus extends BonusInterface
boolean isBonusObject = false
public static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set this.isBonusObject = true
return this
endmethod
stub method setBonus takes unit u, integer amount returns integer
debug call ErrorMsg("Bonus.setBonus()", "I have no idea how or why you did this, but don't do it.")
return 0
endmethod
stub method getBonus takes unit u returns integer
debug call ErrorMsg("Bonus.getBonus()", "I have no idea how or why you did this, but don't do it.")
return 0
endmethod
stub method removeBonus takes unit u returns nothing
call this.setBonus(u, 0)
endmethod
stub method isValidBonus takes unit u, integer value returns boolean
return true
endmethod
method operator min takes nothing returns integer
return this.minBonus
endmethod
method operator max takes nothing returns integer
return this.maxBonus
endmethod
endstruct
private struct AbilityBonus extends Bonus
public integer count
public integer rawcode
public integer negativeRawcode
public integer minBonus = 0
public integer maxBonus = 0
public boolean heroesOnly
public static method create takes integer rawcode, integer count, integer negativeRawcode, boolean heroesOnly returns thistype
local thistype bonus = thistype.allocate()
local integer i
debug local boolean error = false
// Error messages
static if DEBUG_MODE then
if rawcode == 0 then
call ErrorMsg("AbilityBonus.create()", "Bonus constructed with a rawcode of 0?!")
call bonus.destroy()
return 0
endif
if count < 0 or count == 0 then
call ErrorMsg("AbilityBonus.create()", "Bonus constructed with an ability count <= 0?!")
call bonus.destroy()
return 0
endif
endif
// Grow powers of 2
if powersOf2Count < count then
set i = powersOf2Count
loop
exitwhen i > count
set powersOf2[i] = 2 * powersOf2[i - 1]
set i = i + 1
endloop
set powersOf2Count = count
endif
// Preload this bonus' abilities
static if PRELOAD_ABILITIES then
set i = 0
loop
exitwhen i == count
call LoadAbility(rawcode + i)
set i = i + 1
endloop
if negativeRawcode != 0 then
call LoadAbility(negativeRawcode)
endif
endif
// Set up this bonus object
set bonus.count = count
set bonus.negativeRawcode = negativeRawcode
set bonus.rawcode = rawcode
set bonus.heroesOnly = heroesOnly
// Calculate the minimum and maximum bonuses
if negativeRawcode != 0 then
set bonus.minBonus = -powersOf2[count]
else
set bonus.minBonus = 0
endif
set bonus.maxBonus = powersOf2[count] - 1
// Return the bonus object
return bonus
endmethod
// Interface methods:
method setBonus takes unit u, integer amount returns integer
return SetUnitBonus.evaluate(u, this, amount)
endmethod
method getBonus takes unit u returns integer
return GetUnitBonus.evaluate(u, this)
endmethod
method removeBonus takes unit u returns nothing
call RemoveUnitBonus.evaluate(u, this)
endmethod
public method isValidBonus takes unit u, integer value returns boolean
return (value >= this.minBonus) and (value <= this.maxBonus)
endmethod
endstruct
// ===
// Public API
// ===
function IsBonusValid takes unit u, Bonus abstractBonus, integer value returns boolean
local AbilityBonus bonus = AbilityBonus(abstractBonus)
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("IsBonusValid()", "Invalid bonus type given")
endif
endif
if abstractBonus.min > value or abstractBonus.max < value then
return false
endif
if abstractBonus.getType() != AbilityBonus.typeid then
return abstractBonus.isValidBonus(u, value)
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
return false
endif
return (value >= bonus.minBonus) and (value <= bonus.maxBonus)
endfunction
function RemoveUnitBonus takes unit u, Bonus abstractBonus returns nothing
local integer i = 0
local AbilityBonus bonus = AbilityBonus(abstractBonus)
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("RemoveUnitBonus()", "Invalid bonus type given")
endif
endif
if abstractBonus.getType() != AbilityBonus.typeid then
call abstractBonus.removeBonus(u)
return
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
debug call ErrorMsg("RemoveUnitBonus()", "Trying to remove a hero-only bonus from a non-hero unit")
return
endif
call UnitRemoveAbility(u, bonus.negativeRawcode)
loop
exitwhen i == bonus.count
call UnitRemoveAbility(u, bonus.rawcode + i)
set i = i + 1
endloop
endfunction
function SetUnitBonus takes unit u, Bonus abstractBonus, integer amount returns integer
local integer i
local integer output = 0
local AbilityBonus bonus = AbilityBonus(abstractBonus)
local boolean applyMinBonus = false
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("SetUnitBonus()", "Invalid bonus type given")
endif
endif
if amount == 0 then
call RemoveUnitBonus(u, bonus)
return 0
endif
if abstractBonus.getType() != AbilityBonus.typeid then
return abstractBonus.setBonus(u, amount)
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
debug call ErrorMsg("SetUnitBonus()", "Trying to set a hero-only bonus on a non-hero unit")
return 0
endif
if amount < bonus.minBonus then
debug call ErrorMsg("SetUnitBonus()", "Attempting to set a bonus to below its min value")
set amount = bonus.minBonus
elseif amount > bonus.maxBonus then
debug call ErrorMsg("SetUnitBonus()", "Attempting to set a bonus to above its max value")
set amount = bonus.maxBonus
endif
if amount < 0 then
set amount = -(bonus.minBonus - amount)
set applyMinBonus = true
endif
call UnitRemoveAbility(u, bonus.negativeRawcode)
set i = bonus.count - 1
loop
exitwhen i < 0
if amount >= powersOf2[i] then
call UnitAddAbility(u, bonus.rawcode + i)
call UnitMakeAbilityPermanent(u, true, bonus.rawcode + i)
static if DEBUG_MODE then
if GetUnitAbilityLevel(u, bonus.rawcode + i) <= 0 then
call ErrorMsg("SetUnitBonus()", "Failed to give the 2^" + I2S(i) + " ability to the unit!")
endif
endif
set amount = amount - powersOf2[i]
set output = output + powersOf2[i]
else
call UnitRemoveAbility(u, bonus.rawcode + i)
static if DEBUG_MODE then
if GetUnitAbilityLevel(u, bonus.rawcode + i) > 0 then
call ErrorMsg("SetUnitBonus()", "Unit still has the 2^" + I2S(i) + " ability after it was removed!")
endif
endif
endif
set i = i - 1
endloop
if applyMinBonus then
call UnitAddAbility(u, bonus.negativeRawcode)
call UnitMakeAbilityPermanent(u, true, bonus.negativeRawcode)
else
call UnitRemoveAbility(u, bonus.negativeRawcode)
endif
return output
endfunction
function GetUnitBonus takes unit u, Bonus abstractBonus returns integer
local integer i = 0
local integer amount = 0
local AbilityBonus bonus = AbilityBonus(abstractBonus)
static if DEBUG_MODE then
if not abstractBonus.isBonusObject then
call ErrorMsg("GetUnitBonus()", "Invalid bonus type given")
endif
endif
if abstractBonus.getType() != AbilityBonus.typeid then
return abstractBonus.getBonus(u)
endif
if bonus.heroesOnly and not IsUnitType(u, UNIT_TYPE_HERO) then
debug call ErrorMsg("GetUnitBonus()", "Trying to get a hero-only bonus from a non-hero unit")
return 0
endif
if GetUnitAbilityLevel(u, bonus.negativeRawcode) > 0 then
set amount = bonus.minBonus
endif
loop
exitwhen i == bonus.count
if GetUnitAbilityLevel(u, bonus.rawcode + i) > 0 then
set amount = amount + powersOf2[i]
endif
set i = i + 1
endloop
return amount
endfunction
function AddUnitBonus takes unit u, Bonus bonus, integer amount returns integer
return SetUnitBonus(u, bonus, GetUnitBonus(u, bonus) + amount)
endfunction
// ===
// Initialization
// ===
private function OnInit takes nothing returns nothing
local integer i
// Set up powers of 2
set powersOf2[0] = 1
set powersOf2Count = 1
static if DEBUG_MODE and PRELOAD_ABILITIES and not LIBRARY_xepreload and not LIBRARY_AbilityPreload then
call ErrorMsg("Initialization", "PRELOAD_ABILITIES is set to true, but neither usable preloading library is detected")
endif
// Setup bonuses
call Setup()
endfunction
endlibrary
//TESH.scrollpos=27
//TESH.alwaysfold=0
library AbilityPreload
//===========================================================================
// Information:
//==============
//
// Preloading removes the noticeable delay the first time an ability
// is loaded in a game. If an ability was not already on a pre-placed unit
// or a unit that was created during initialization, preloading is needed
// to prevent a delay.
//
//===========================================================================
// AbilityPreload API:
//=====================
//
// AbilityPreload(abilityid) :
// Call this before any time has elapsed to preload a specific
// ability. If debug mode is enabled, you will see an error message
// if you call this after initialization, or if you try to preload
// an ability that does not exist. Will inline to a UnitAddAbility
// call if debug mode is disabled.
//
// AbilityRangePreload(start, end) :
// Same as AbilityPreload, but preloads a range of abilities.
// It will iterates between the two rawcode values and preload
// every ability along the way. It will not show an error message
// for non-existent abilities.
//
//===========================================================================
// Configuration:
//================
globals
private constant integer PreloadUnitRawcode = 'zsmc'
//This is the rawcode for "Sammy!". It is never used and has no model,
//which makes an ideal preloading unit. Change it if you want to.
endglobals
//===========================================================================
globals
private unit PreloadUnit
endglobals
function AbilityPreload takes integer abilityid returns nothing
static if DEBUG_MODE then
if GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
endif
call UnitAddAbility(PreloadUnit, abilityid)
static if DEBUG_MODE then
if GetUnitAbilityLevel(PreloadUnit, abilityid) == 0 then
call BJDebugMsg("AbilityPreload error: Attempted to preload a non-existent ability")
endif
endif
endfunction
function AbilityRangePreload takes integer start, integer end returns nothing
local integer i = 1
static if DEBUG_MODE then
if GetUnitTypeId(PreloadUnit) == 0 then
call BJDebugMsg("AbilityPreload error: Can't preload an ability after initialization")
return
endif
endif
if start > end then
set i = -1
endif
loop
exitwhen start > end
call UnitAddAbility(PreloadUnit, start)
set start = start + i
endloop
endfunction
//===========================================================================
private struct Init extends array
private static method onInit takes nothing returns nothing
set PreloadUnit = CreateUnit(Player(15), PreloadUnitRawcode, 0., 0., 0.)
call UnitApplyTimedLife(PreloadUnit, 0, .001)
call ShowUnit(PreloadUnit, false)
call UnitAddAbility(PreloadUnit, 'Aloc')
endmethod
endstruct
endlibrary
//TESH.scrollpos=117
//TESH.alwaysfold=0
//############################## ~IndexerUtils ##################################//
//##
//## Version: 1.0
//## System: Axarion
//## AutoIndex: grim001
//## AIDS: Jesus4Lyf
//## UnitIndexer: Nestharus
//##
//############################### DESCRIPTION ###################################//
//##
//## This system combines the Enter- and Leave-Events of AutoIndex, AIDS and
//## UnitIndexer. I made this to save some time when doing systems which should
//## be compatible with all indexers.
//##
//############################### HOW DOES IT WORK ##############################//
//##
//## To use this system create a struct and implement the IndexerUtils module.
//##
//################################# METHODS #####################################//
//##
//## You can implement these functions into your struct. They are
//## all optional.
//##
//## - happens when a unit enters which has the ability.
//## static method onIndex takes unit u returns nothing
//##
//## - happens when a unit leaves the map with the ability
//## static method onDeindex takes unit u returns nothing
//##
//## - defines for which units the event should be fired:
//## static method onFilter takes unit u returns boolean
//##
//################################################################################//
library IndexerUtils requires optional UnitIndexer, optional AutoIndex, optional AIDS //one is required!
module IndexerUtils
//for AIDS
static if LIBRARY_AIDS then
static if thistype.onUnitDeindex.exists then
private static method onUnitDeindexed takes nothing returns boolean
static if thistype.onFilter.exists then
if thistype.onFilter(u) then
call thistype.onUnitDeindex(GetIndexUnit(AIDS_GetDecayingIndex()))
endif
else
call thistype.onUnitDeindex(GetIndexUnit(AIDS_GetDecayingIndex()))
endif
return false
endmethod
endif
static if thistype.onUnitIndex.exists then
private static method onUnitIndexed takes nothing returns boolean
static if thistype.onFilter.exists then
if thistype.onFilter(u) then
call thistype.onUnitIndex(AIDS_GetEnteringIndexUnit())
endif
else
call thistype.onUnitIndex(AIDS_GetEnteringIndexUnit())
endif
return false
endmethod
endif
static if thistype.onUnitIndex.exists then
static if thistype.onUnitDeindex.exists then
private static method onInit takes nothing returns nothing
call AIDS_RegisterOnDeallocate(Condition(function thistype.onUnitDeindexed))
call AIDS_RegisterOnEnter (Condition(function thistype.onUnitIndexed))
endmethod
else
private static method onInit takes nothing returns nothing
call AIDS_RegisterOnEnter (Condition(function thistype.onUnitIndexed))
endmethod
endif
elseif thistype.onUnitDeindex.exists then
private static method onInit takes nothing returns nothing
call AIDS_RegisterOnDeallocate(Condition(function thistype.onUnitDeindexed))
endmethod
endif
//for AutoIndex
elseif LIBRARY_AutoIndex then
static if thistype.onUnitDeindex.exists then
private static method onUnitDeindexed takes unit u returns nothing
static if thistype.onFilter.exists then
if thistype.onFilter(u) then
call thistype.onUnitDeindex(u)
endif
else
call thistype.onUnitDeindex(u)
endif
endmethod
endif
static if thistype.onUnitIndex.exists then
private static method onUnitIndexed takes unit u returns nothing
static if thistype.onFilter.exists then
if thistype.onFilter(u) then
call thistype.onUnitIndex(u)
endif
else
call thistype.onUnitIndex(u)
endif
endmethod
endif
static if thistype.onUnitIndex.exists then
static if thistype.onUnitDeindex.exists then
private static method onInit takes nothing returns nothing
call OnUnitDeindexed(thistype.onUnitDeindexed)
call OnUnitIndexed(thistype.onUnitIndexed)
endmethod
else
private static method onInit takes nothing returns nothing
call OnUnitIndexed(thistype.onUnitIndexed)
endmethod
endif
elseif thistype.onUnitDeindex.exists then
private static method onInit takes nothing returns nothing
call OnUnitDeindex (Condition(function thistype.onUnitIndexed))
endmethod
endif
// for UnitIndexer
elseif LIBRARY_UnitIndexer then
static if thistype.onUnitDeindex.exists then
private static method onUnitDeindexed takes nothing returns boolean
static if thistype.onFilter.exists then
if thistype.onFilter(u) then
call thistype.onUnitDeindex(GetIndexedUnit())
endif
else
call thistype.onUnitDeindex(GetIndexedUnit())
endif
return false
endmethod
endif
static if thistype.onUnitIndex.exists then
private static method onUnitIndexed takes nothing returns boolean
static if thistype.onFilter.exists then
if thistype.onFilter(u) then
call thistype.onUnitIndex(GetIndexedUnit())
endif
else
call thistype.onUnitIndex(GetIndexedUnit())
endif
return false
endmethod
endif
static if thistype.onUnitIndex.exists then
static if thistype.onUnitDeindex.exists then
private static method onInit takes nothing returns nothing
call OnUnitDeindex(Condition(function thistype.onUnitDeindexed))
call OnUnitIndex (Condition(function thistype.onUnitIndexed))
endmethod
else
private static method onInit takes nothing returns nothing
call OnUnitIndex (Condition(function thistype.onUnitIndexed))
endmethod
endif
elseif thistype.onUnitDeindex.exists then
private static method onInit takes nothing returns nothing
call OnUnitDeindex (Condition(function thistype.onUnitIndexed))
endmethod
endif
endif
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//############################## ~AbilityEvent~ ##################################//
//##
//## Version: 1.0
//## System: Axarion
//## IndexerUtils: Axarion
//## AutoIndex: grim001
//## AIDS: Jesus4Lyf
//## UnitIndexer: Nestharus
//##
//############################### DESCRIPTION ###################################//
//##
//## This system allows to track when a unit acquires an ability.
//##
//############################### HOW DOES IT WORK ##############################//
//##
//## To use this system create a struct and implement the AbilityEvent module.
//## In the structs onInit method you have to define the ability, for which the
//## event is executed:
//##
//## set ability = YourId
//##
//## If you want all abilities to register use:
//##
//## set ability = ABILITIES_ALL
//##
//## Another way is to create a static constant variable called ability.
//##
//################################# METHODS #####################################//
//##
//## You can implement these functions into your struct. They are
//## all optional.
//##
//## - happens when a unit enters which has the ability.
//## static method onIndexWithAbility takes unit u returns nothing
//##
//## - happens when a unit leaves the map with the ability
//## static method onDeindexWithAbility takes unit u returns nothing
//##
//## - happens when the ability is added to the unit
//## static method onAddAbility takes unit u, integer id returns nothing
//##
//## - happens when the ability is removed from the unit
//## static method onRemoveAbility takes unit u, integer id returns nothing
//##
//## - happens when the ability is skilled by a hero
//## static method onSkillAbility takes unit u, integer id returns nothing
//##
//################################################################################//
library AbilityEvent initializer onInit requires IndexerUtils
globals
// decide yourself if you want to use hooks or UnitAddAbilityEx and
// UnitRemoveAbilityEx instead of the calling UnitAddAbility and
// UnitRemoveAbility.
private constant boolean USE_HOOKS = true
//###################### DON'T TOUCH ANYTHING BELOW! ##############################//
private trigger ADD = CreateTrigger()
private trigger REM = CreateTrigger()
private trigger SKILL = CreateTrigger()
private unit currUnit = null
private integer currId = 0
public key ABILITIES_ALL
endglobals
module AbilityEvent
static if thistype.onIndexWithAbility.exists then
static method onUnitIndex takes unit u returns nothing
if GetUnitAbilityLevel(u, .ability) != 0 or ability == ABILITIES_ALL then
call onIndexWithAbility(u)
endif
endmethod
endif
static if thistype.onDeindexWithAbility.exists then
static method onUnitDeindex takes unit u returns nothing
if GetUnitAbilityLevel(u, ability) != 0 or ability == ABILITIES_ALL then
call onDeindexWithAbility(u)
endif
endmethod
endif
static if thistype.onSkillAbility.exists then
private static method REGISTER_SKILL takes nothing returns boolean
if GetLearnedSkill() == ability or ability == ABILITIES_ALL then
call onSkillAbility(GetTriggerUnit(), GetLearnedSkill())
endif
return false
endmethod
endif
static if thistype.onAddAbility.exists then
private static method REGISTER_ADD takes nothing returns boolean
if currId == ability or ability == ABILITIES_ALL then
call onAddAbility(currUnit, currId)
endif
return false
endmethod
endif
static if thistype.onRemoveAbility.exists then
private static method REGISTER_REM takes nothing returns boolean
if currId == ability or ability == ABILITIES_ALL then
call onRemoveAbility(currUnit, currId)
endif
return false
endmethod
endif
static if thistype.onAddAbility.exists then
static if thistype.onRemoveAbility.exists then
static if thistype.onSkillAbility.exists then
private static method onInit takes nothing returns nothing
call TriggerAddCondition(ADD, function thistype.REGISTER_ADD)
call TriggerAddCondition(REM, function thistype.REGISTER_REM)
call TriggerAddCondition(SKILL, function thistype.REGISTER_SKILL)
call TriggerRegisterAnyUnitEventBJ(SKILL, EVENT_PLAYER_HERO_SKILL)
endmethod
else
private static method onInit takes nothing returns nothing
call TriggerAddCondition(ADD, function thistype.REGISTER_ADD)
call TriggerAddCondition(REM, function thistype.REGISTER_REM)
endmethod
endif
elseif static if thistype.onSkillAbility.exists then
private static method onInit takes nothing returns nothing
call TriggerAddCondition(ADD, function thistype.REGISTER_ADD)
call TriggerAddCondition(SKILL, function thistype.REGISTER_SKILL)
call TriggerRegisterAnyUnitEventBJ(SKILL, EVENT_PLAYER_HERO_SKILL)
endmethod
else
private static method onInit takes nothing returns nothing
call TriggerAddCondition(ADD, function thistype.REGISTER_ADD)
endmethod
endif
elseif thistype.onRemoveAbility.exists then
static if thistype.onSkillAbility.exists then
private static method onInit takes nothing returns nothing
call TriggerAddCondition(REM, function thistype.REGISTER_REM)
call TriggerAddCondition(SKILL, function thistype.REGISTER_SKILL)
call TriggerRegisterAnyUnitEventBJ(SKILL, EVENT_PLAYER_HERO_SKILL)
endmethod
else
private static method onInit takes nothing returns nothing
call TriggerAddCondition(REM, function thistype.REGISTER_REM)
endmethod
endif
elseif thistype.onSkillAbility.exists then
private static method onInit takes nothing returns nothing
call TriggerAddCondition(REM, function thistype.REGISTER_REM)
call TriggerAddCondition(SKILL, function thistype.REGISTER_SKILL)
call TriggerRegisterAnyUnitEventBJ(SKILL, EVENT_PLAYER_HERO_SKILL)
endmethod
endif
implement IndexerUtils
endmodule
static if USE_HOOKS then
private function hook_UnitAddAbility takes unit u, integer id returns nothing
set currUnit = u
set currId = id
call TriggerEvaluate(ADD)
set currUnit = null
set currId = 0
endfunction
private function hook_UnitRemoveAbility takes unit u, integer id returns nothing
set currUnit = u
set currId = id
call TriggerEvaluate(REM)
set currUnit = null
set currId = 0
endfunction
hook UnitAddAbility hook_UnitAddAbility
hook UnitRemoveAbility hook_UnitRemoveAbility
else
function UnitAddAbilityEx takes unit u, integer id returns nothing
call UnitAddAbility(u, id)
set currUnit = u
set currId = id
call TriggerEvaluate(ADD)
set currUnit = null
set currId = 0
endfunction
function UnitRemoveAbilityEx takes unit u, integer id returns nothing
call UnitRemoveAbility(u, id)
set currUnit = u
set currId = id
call TriggerEvaluate(REM)
set currUnit = null
set currId = 0
endfunction
endif
endlibrary
//TESH.scrollpos=39
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//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=408
//TESH.alwaysfold=0
//
// _ ___ ___ ___ _______________________________________________
// /_\ |_ _| \/ __| || A D V A N C E D I N D E X I N G ||
// / _ \ | || |) \__ \ || A N D ||
// /_/ \_\___|___/|___/ || D A T A S T O R A G E ||
// By Jesus4Lyf ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// v 1.1.0
// What is AIDS?
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// AIDS assigns unique integers between 1 and 8191 to units which enter
// the map. These can be used for arrays and data attaching.
//
// AIDS also allows you to define structs which are created automatically
// when units enter the map, and filtering which units should be indexed
// as well as for which units these structs should be created.
//
// How to implement?
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Simply create a new trigger object called AIDS, go to 'Edit -> Convert
// to Custom Text', and replace everything that's there with this script.
//
// Save the map, close it, reopen it, and then delete the "!" from the
// FAR left side of the next lines (so "external" will line up with this line):
////! external ObjectMerger w3a Adef AIDS anam "State Detection" ansf "(AIDS)" aart "" arac 0
//
// At the top of the script, there is a 'UnitIndexingFilter' constant
// function. If the function returns true for the unit, then that unit
// will be automatically indexed. Setting this to true will automatically
// index all units. Setting it to false will disable automatic indexing.
//
// Functions:
// ¯¯¯¯¯¯¯¯¯¯¯¯
// function GetUnitId takes unit u returns integer
// - This returns the index of an indexed unit. This will return 0
// if the unit has not been indexed.
// - This function inlines. It does not check if the unit needs an
// index. This function is for the speed freaks.
// - Always use this if 'UnitIndexingFilter' simply returns true.
//
// function GetUnitIndex takes unit u returns integer
// - This will return the index of a unit if it has one, or assign
// an index if the unit doesn't have one (and return the new index).
// - Use this if 'UnitIndexingFilter' doesn't return true.
//
// function GetIndexUnit takes integer index returns unit
// - This returns the unit which has been assigned the 'index'.
//
// AIDS Structs:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// - Insert: //! runtextmacro AIDS() at the top of a struct to make it
// an AIDS struct.
// - AIDS structs cannot be created or destroyed manually. Instead, they
// are automatically created when an appropriate unit enters the map.
// - You cannot give members default values in their declaration.
// (eg: private integer i=5 is not allowed)
// - You cannot use array members.
// - AIDS structs must "extend array". This will remove some unused
// functions and enforce the above so there will be no mistakes.
// - There are four optional methods you can use in AIDS structs:
// - AIDS_onCreate takes nothing returns nothing
// - This is called when the struct is 'created' for the unit.
// - In here you can assign members their default values, which
// you would usually assign in their declarations.
// (eg: set this.i=5)
// - AIDS_onDestroy takes nothing returns nothing
// - This is called when the struct is 'destroyed' for the unit.
// - This is your substitute to the normal onDestroy method.
// - AIDS_filter takes unit u returns boolean
// - This is similar to the constant filter in the main system.
// - Each unit that enters the map will be tested by each AIDS
// struct filter. If it returns true for that unit, that unit
// will be indexed if it was not already, the AIDS struct will
// have its AIDS_onCreate method called, and later have its
// AIDS_onDestroy method called when the index is recycled.
// - Not declaring this will use the default AIDS filter instead.
// - AIDS_onInit takes nothing returns nothing
// - This is because I stole your onInit function with my textmacro.
// - You can use '.unit' from any AIDS struct to get the unit for which
// the struct is for.
// - The structs id will be the units index, so getting the struct for
// a unit inlines to a single native call, and you can typecast between
// different AIDS structs. This is the premise of AIDS.
// - Never create or destroy AIDS structs directly.
// - You can call .AIDS_addLock() and AIDS_removeLock() to increase or
// decrease the lock level on the struct. If a struct's lock level is
// not 0, it will not be destroyed until it is reduced to 0. Locks just
// put off AIDS struct destruction in case you wish to attach to a timer
// or something which must expire before the struct data disappears.
// Hence, not freeing all locks will leak the struct (and index).
//
// PUI and AutoIndex:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// - AIDS includes the PUI textmacros and the AutoIndex module, because
// these systems are not compatible with AIDS but have valid and distinct
// uses.
// - The PUI textmacros are better to use for spells than AIDS structs,
// because they are not created for all units, just those targetted by
// the spell (or whatever else is necessary).
// - The AutoData module is good for very simple array syntax for data
// attachment (although I don't recommend that people actually use it,
// it's here mostly for compatability). Note that unlike the PUI textmacros,
// units must pass the AIDS filter in order for this module to work with
// them. This is exactly as the same as in AutoIndex itself (AutoIndex
// has a filter too).
//
// Thanks:
// ¯¯¯¯¯¯¯¯¯
// - Romek, for writing 90% of this user documentation, challenging my
// interface, doing some testing, suggesting improvements and inspiring
// me to re-do my code to include GetUnitIndex as non-inlining.
// - grim001, for writing the AutoData module, and AutoIndex. I used the
// on-enter-map method that he used. Full credits for the AutoData module.
// - Cohadar, for writing his PUI textmacros. Full credits to him for these,
// except for my slight optimisations for this system.
// Also, I have used an optimised version of his PeriodicRecycler from
// PUI in this system to avoid needing a RemoveUnitEx function.
// - Vexorian, for helping Cohadar on the PUI textmacro.
// - Larcenist, for suggesting the AIDS acronym. Originally he suggested
// 'Alternative Index Detection System', but obviously I came up with
// something better. In fact, I'd say it looks like the acronym was
// an accident. Kinda neat, don't you think? :P
//
// Final Notes:
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// - With most systems, I wouldn't usually add substitutes for alternative
// systems. However, UnitData systems are an exception, because they
// are incompatible with eachother. Since using this system forbids
// people from using the real PUI or AutoIndex, and a lot of resources
// use either of these, it made sense not to break them all.
//
// - If this documentation confused you as to how to use the system, just
// leave everything as default and use GetUnitId everywhere.
//
// - To use this like PUI (if you don't like spamming indices) simply
// make the AIDS filter return false, and use GetUnitIndex.
//
library AIDS initializer InitAIDS requires TimerUtils
//==============================================================================
// Configurables
//
globals
private constant boolean USE_PERIODIC_RECYCLER = false
private constant real PERIOD = 0.03125 // Recycles 32 units/second max.
// Lower to be able to recycle faster.
// Only used if USE_PERIODIC_RECYCLER
// is set to true.
private constant integer LEAVE_DETECTION_ABILITY = 'AIDS'
endglobals
private function UnitIndexingFilter takes unit u returns boolean
return true
endfunction
//==============================================================================
// System code
//
globals
// The unit stored at an index.
private unit array IndexUnit
private integer array LockLevel
endglobals
//==============================================================================
globals
// Recycle stack
private integer array RecycledIndex
private integer MaxRecycledIndex = 0
// Previous highest index
private integer MaxIndex = 0
endglobals
//==============================================================================
globals
private integer array DecayingIndex
private integer MaxDecayingIndex=0
private integer DecayChecker=0
endglobals
globals
private timer UndefendTimer=CreateTimer()
private integer array UndefendIndex
private integer UndefendStackIndex=0
endglobals
globals
private integer array UndefendExpiringIndex
private integer UndefendExpiringIndexLevel=0
endglobals
//==============================================================================
globals
// The Add/Remove stack (or assign/recycle stack).
//
// Indexing can become recusive since units can be created on index
// assignment or deallocation.
// To support this, a stack is used to store the event response results.
private integer ARStackLevel=0
private integer array ARStackIndex
private unit array ARStackUnit
// A later discovery revealed that the Add/Remove stack did not need to be
// used for deallocation. The alternative used works fine...
endglobals
public constant function GetEnteringIndexUnit takes nothing returns unit
return ARStackUnit[ARStackLevel]
endfunction
public function GetIndexOfEnteringUnit takes nothing returns integer
// Called in AIDS structs when units do not pass the initial AIDS filter.
if ARStackIndex[ARStackLevel]==0 then
// Get new index, from recycler first, else new.
// Store the current index on the (new) top level of the AR stack.
if MaxRecycledIndex==0 then // Get new.
set MaxIndex=MaxIndex+1
set ARStackIndex[ARStackLevel]=MaxIndex
else // Get from recycle stack.
set ARStackIndex[ARStackLevel]=RecycledIndex[MaxRecycledIndex]
set MaxRecycledIndex=MaxRecycledIndex-1
endif
// Store index on unit.
call SetUnitUserData(ARStackUnit[ARStackLevel],ARStackIndex[ARStackLevel])
set IndexUnit[ARStackIndex[ARStackLevel]]=ARStackUnit[ARStackLevel]
// Add index to recycle list.
set MaxDecayingIndex=MaxDecayingIndex+1
set DecayingIndex[MaxDecayingIndex]=ARStackIndex[ARStackLevel]
endif
return ARStackIndex[ARStackLevel]
endfunction
public constant function GetIndexOfEnteringUnitAllocated takes nothing returns integer
// Called in AIDS structs when units have passed the initial AIDS filter.
return ARStackIndex[ARStackLevel]
endfunction
public constant function GetDecayingIndex takes nothing returns integer
static if USE_PERIODIC_RECYCLER then
return DecayingIndex[DecayChecker]
else
return UndefendExpiringIndex[UndefendExpiringIndexLevel]
endif
endfunction
//==============================================================================
globals
// For structs and such which need to do things on unit index assignment.
private trigger OnEnter=CreateTrigger()
// The same, but for when units pass the initial filter anyway.
private trigger OnEnterAllocated=CreateTrigger()
// For structs and such which need to do things on unit index deallocation.
private trigger OnDeallocate=CreateTrigger()
endglobals
public function RegisterOnEnter takes boolexpr b returns triggercondition
return TriggerAddCondition(OnEnter,b)
endfunction
public function RegisterOnEnterAllocated takes boolexpr b returns triggercondition
return TriggerAddCondition(OnEnterAllocated,b)
endfunction
public function RegisterOnDeallocate takes boolexpr b returns triggercondition
return TriggerAddCondition(OnDeallocate,b)
endfunction
//==============================================================================
function GetIndexUnit takes integer index returns unit
debug if index==0 then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the unit of index 0.")
debug elseif IndexUnit[index]==null then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the unit of unassigned index.")
debug endif
return IndexUnit[index]
endfunction
function GetUnitId takes unit u returns integer
debug if u==null then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the id (inlines) of null unit.")
debug elseif GetUnitUserData(u)==0 then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to use GetUnitId (inlines) when you should be using GetUnitIndex (unit didn't pass filter).")
debug endif
return GetUnitUserData(u)
endfunction
globals//locals
private integer getindex
endglobals
function GetUnitIndex takes unit u returns integer // Cannot be recursive.
debug if u==null then
debug call BJDebugMsg("|cFFFF0000Error using AIDS:|r Trying to get the index of null unit.")
debug endif
set getindex=GetUnitUserData(u)
if getindex==0 then
// Get new index, from recycler first, else new.
// Store the current index in getindex.
if MaxRecycledIndex==0 then // Get new.
set MaxIndex=MaxIndex+1
set getindex=MaxIndex
else // Get from recycle stack.
set getindex=RecycledIndex[MaxRecycledIndex]
set MaxRecycledIndex=MaxRecycledIndex-1
endif
// Store index on unit.
call SetUnitUserData(u,getindex)
set IndexUnit[getindex]=u
static if USE_PERIODIC_RECYCLER then
// Add index to recycle list.
set MaxDecayingIndex=MaxDecayingIndex+1
set DecayingIndex[MaxDecayingIndex]=getindex
else
// Add leave detection ability.
call UnitAddAbility(ARStackUnit[ARStackLevel],LEAVE_DETECTION_ABILITY)
call UnitMakeAbilityPermanent(ARStackUnit[ARStackLevel],true,LEAVE_DETECTION_ABILITY)
endif
// Do not fire things here. No AIDS structs will be made at this point.
endif
return getindex
endfunction
//==============================================================================
public function AddLock takes integer index returns nothing
set LockLevel[index]=LockLevel[index]+1
endfunction
public function RemoveLock takes integer index returns nothing
set LockLevel[index]=LockLevel[index]-1
static if not USE_PERIODIC_RECYCLER then
if GetUnitUserData(IndexUnit[index])==0 and LockLevel[index]==0 then
// Increment stack for recursion.
set UndefendExpiringIndexLevel=UndefendExpiringIndexLevel+1
set UndefendExpiringIndex[UndefendExpiringIndexLevel]=index
// Fire things.
call TriggerEvaluate(OnDeallocate)
// Decrement stack for recursion.
set UndefendExpiringIndexLevel=UndefendExpiringIndexLevel-1
// Add the index to the recycler stack.
set MaxRecycledIndex=MaxRecycledIndex+1
set RecycledIndex[MaxRecycledIndex]=index
// Null the unit.
set IndexUnit[index]=null
endif
endif
endfunction
//==============================================================================
static if USE_PERIODIC_RECYCLER then
private function PeriodicRecycler takes nothing returns nothing
if MaxDecayingIndex>0 then
set DecayChecker=DecayChecker+1
if DecayChecker>MaxDecayingIndex then
set DecayChecker=1
endif
if GetUnitUserData(IndexUnit[DecayingIndex[DecayChecker]])==0 then
if LockLevel[DecayingIndex[DecayChecker]]==0 then
// Fire things.
call TriggerEvaluate(OnDeallocate)
// Add the index to the recycler stack.
set MaxRecycledIndex=MaxRecycledIndex+1
set RecycledIndex[MaxRecycledIndex]=DecayingIndex[DecayChecker]
// Null the unit.
set IndexUnit[DecayingIndex[DecayChecker]]=null
// Remove index from decay list.
set DecayingIndex[DecayChecker]=DecayingIndex[MaxDecayingIndex]
set MaxDecayingIndex=MaxDecayingIndex-1
endif
endif
endif
endfunction
else
private function UndefendFilter takes nothing returns boolean
return IsUnitType(GetFilterUnit(),UNIT_TYPE_DEAD)
endfunction
private function OnUndefendTimer takes nothing returns nothing
loop
exitwhen UndefendStackIndex==0
set UndefendStackIndex=UndefendStackIndex-1
set UndefendExpiringIndex[0]=UndefendIndex[UndefendStackIndex]
if IndexUnit[UndefendExpiringIndex[0]]!=null then
if GetUnitUserData(IndexUnit[UndefendExpiringIndex[0]])==0 then
if LockLevel[UndefendExpiringIndex[0]]==0 then
// Fire things.
call TriggerEvaluate(OnDeallocate)
// Add the index to the recycler stack.
set MaxRecycledIndex=MaxRecycledIndex+1
set RecycledIndex[MaxRecycledIndex]=UndefendExpiringIndex[0]
// Null the unit.
set IndexUnit[UndefendExpiringIndex[0]]=null
endif
endif
endif
endloop
endfunction
globals//locals
private integer UndefendFilterIndex
endglobals
private function OnUndefend takes nothing returns boolean
if GetIssuedOrderId()==852056 then // If undefend then...
set UndefendFilterIndex=GetUnitUserData(GetOrderedUnit())
if UndefendIndex[UndefendStackIndex-1]!=UndefendFilterIndex then // Efficiency perk.
set UndefendIndex[UndefendStackIndex]=UndefendFilterIndex
set UndefendStackIndex=UndefendStackIndex+1
call TimerStart(UndefendTimer,0,false,function OnUndefendTimer)
endif
endif
return false
endfunction
endif
//==============================================================================
public function IndexEnum takes nothing returns boolean // Can be recursive...
// Start by adding another level on the AR stack (for recursion's sake).
set ARStackLevel=ARStackLevel+1
// Store the current unit on the (new) top level of the AR stack.
set ARStackUnit[ARStackLevel]=GetFilterUnit()
if GetUnitUserData(ARStackUnit[ARStackLevel])==0 then // Has not been indexed.
if UnitIndexingFilter(ARStackUnit[ARStackLevel]) then
// Get new index, from recycler first, else new.
// Store the current index on the (new) top level of the AR stack.
if MaxRecycledIndex==0 then // Get new.
set MaxIndex=MaxIndex+1
set ARStackIndex[ARStackLevel]=MaxIndex
else // Get from recycle stack.
set ARStackIndex[ARStackLevel]=RecycledIndex[MaxRecycledIndex]
set MaxRecycledIndex=MaxRecycledIndex-1
endif
// Store index on unit.
call SetUnitUserData(ARStackUnit[ARStackLevel],ARStackIndex[ARStackLevel])
set IndexUnit[ARStackIndex[ARStackLevel]]=ARStackUnit[ARStackLevel]
static if USE_PERIODIC_RECYCLER then
// Add index to recycle list.
set MaxDecayingIndex=MaxDecayingIndex+1
set DecayingIndex[MaxDecayingIndex]=ARStackIndex[ARStackLevel]
else
// Add leave detection ability.
call UnitAddAbility(ARStackUnit[ARStackLevel],LEAVE_DETECTION_ABILITY)
call UnitMakeAbilityPermanent(ARStackUnit[ARStackLevel],true,LEAVE_DETECTION_ABILITY)
endif
// Fire things.
call TriggerEvaluate(OnEnter)
else
// The unit did not pass the filters, so does not need to be auto indexed.
// However, for certain AIDS structs, it may still require indexing.
// These structs may index the unit on their creation.
// We flag that an index must be assigned by setting the current index to 0.
set ARStackIndex[ARStackLevel]=0
// Fire things.
call TriggerEvaluate(OnEnter)
endif
endif
// Decrement the stack.
set ARStackLevel=ARStackLevel-1
return false
endfunction
//==============================================================================
private function InitAIDS takes nothing returns nothing
local region r=CreateRegion()
local group g=CreateGroup()
local integer n=15
static if USE_PERIODIC_RECYCLER then
call TimerStart(UndefendTimer,PERIOD,true,function PeriodicRecycler)
else
local trigger t=CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t,Player(n),EVENT_PLAYER_UNIT_ISSUED_ORDER,Filter(function UndefendFilter))
call SetPlayerAbilityAvailable(Player(n),LEAVE_DETECTION_ABILITY,false)
// Capture "undefend" orders.
exitwhen n==0
set n=n-1
endloop
set n=15
call TriggerAddCondition(t,Filter(function OnUndefend))
set t=null
endif
// This must be done first, due to recursion. :)
call RegionAddRect(r,GetWorldBounds())
call TriggerRegisterEnterRegion(CreateTrigger(),r,Filter(function IndexEnum))
set r=null
loop
call GroupEnumUnitsOfPlayer(g,Player(n),Filter(function IndexEnum))
//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.
exitwhen n==0
set n=n-1
endloop
call DestroyGroup(g)
set g=null
endfunction
//==============================================================================
public struct DEFAULT extends array
method AIDS_onCreate takes nothing returns nothing
endmethod
method AIDS_onDestroy takes nothing returns nothing
endmethod
static method AIDS_filter takes unit u returns boolean
return UnitIndexingFilter(u)
endmethod
static method AIDS_onInit takes nothing returns nothing
endmethod
endstruct
//===========================================================================
// Never create or destroy AIDS structs directly.
// Also, do not initialise members except by using the AIDS_onCreate method.
//===========================================================================
//! textmacro AIDS
// This magic line makes default methods get called which do nothing
// if the methods are otherwise undefined.
private static delegate AIDS_DEFAULT AIDS_DELEGATE=0
//-----------------------------------------------------------------------
// Gotta know whether or not to destroy on deallocation...
private boolean AIDS_instanciated
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns thistype
return GetUnitId(whichUnit)
endmethod
method operator unit takes nothing returns unit
// Allows structVar.unit to return the unit.
return GetIndexUnit(this)
endmethod
//-----------------------------------------------------------------------
method AIDS_addLock takes nothing returns nothing
call AIDS_AddLock(this)
endmethod
method AIDS_removeLock takes nothing returns nothing
call AIDS_RemoveLock(this)
endmethod
//-----------------------------------------------------------------------
private static method AIDS_onEnter takes nothing returns boolean
// At this point, the unit might not have been assigned an index.
if thistype.AIDS_filter(AIDS_GetEnteringIndexUnit()) then
// Flag it for destruction on deallocation.
set thistype(AIDS_GetIndexOfEnteringUnit()).AIDS_instanciated=true
// Can use inlining "Assigned" function now, as it must be assigned.
call thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_onCreate()
endif
return false
endmethod
private static method AIDS_onEnterAllocated takes nothing returns boolean
// At this point, the unit must have been assigned an index.
if thistype.AIDS_filter(AIDS_GetEnteringIndexUnit()) then
// Flag it for destruction on deallocation. Slightly faster!
set thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_instanciated=true
// Can use inlining "Assigned" function now, as it must be assigned.
call thistype(AIDS_GetIndexOfEnteringUnitAllocated()).AIDS_onCreate()
endif
return false
endmethod
private static method AIDS_onDeallocate takes nothing returns boolean
if thistype(AIDS_GetDecayingIndex()).AIDS_instanciated then
call thistype(AIDS_GetDecayingIndex()).AIDS_onDestroy()
// Unflag destruction on deallocation.
set thistype(AIDS_GetDecayingIndex()).AIDS_instanciated=false
endif
return false
endmethod
//-----------------------------------------------------------------------
private static method onInit takes nothing returns nothing
call AIDS_RegisterOnEnter(Filter(function thistype.AIDS_onEnter))
call AIDS_RegisterOnEnterAllocated(Filter(function thistype.AIDS_onEnterAllocated))
call AIDS_RegisterOnDeallocate(Filter(function thistype.AIDS_onDeallocate))
// Because I robbed you of your struct's onInit method.
call thistype.AIDS_onInit()
endmethod
//! endtextmacro
endlibrary
library PUI uses AIDS
//===========================================================================
// Allowed PUI_PROPERTY TYPES are: unit, integer, real, boolean, string
// Do NOT put handles that need to be destroyed here (timer, trigger, ...)
// Instead put them in a struct and use PUI textmacro
//===========================================================================
//! textmacro PUI_PROPERTY takes VISIBILITY, TYPE, NAME, DEFAULT
$VISIBILITY$ struct $NAME$
private static unit array pui_unit
private static $TYPE$ array pui_data
//-----------------------------------------------------------------------
// Returns default value when first time used
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns $TYPE$
local integer pui = GetUnitId(whichUnit) // Changed from GetUnitIndex.
if .pui_unit[pui] != whichUnit then
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = $DEFAULT$
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, $TYPE$ whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
endmethod
endstruct
//! endtextmacro
//===========================================================================
// Never destroy PUI structs directly.
// Use .release() instead, will call .destroy()
//===========================================================================
//! textmacro PUI
private static unit array pui_unit
private static integer array pui_data
private static integer array pui_id
//-----------------------------------------------------------------------
// Returns zero if no struct is attached to unit
//-----------------------------------------------------------------------
static method operator[] takes unit whichUnit returns integer
local integer pui = GetUnitId(whichUnit) // Changed from GetUnitIndex.
// Switched the next two lines for optimisation.
if .pui_unit[pui] != whichUnit then
if .pui_data[pui] != 0 then
// recycled index detected
call .destroy(.pui_data[pui])
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endif
endif
return .pui_data[pui]
endmethod
//-----------------------------------------------------------------------
// This will overwrite already attached struct if any
//-----------------------------------------------------------------------
static method operator[]= takes unit whichUnit, integer whichData returns nothing
local integer pui = GetUnitIndex(whichUnit)
if .pui_data[pui] != 0 then
call .destroy(.pui_data[pui])
endif
set .pui_unit[pui] = whichUnit
set .pui_data[pui] = whichData
set .pui_id[whichData] = pui
endmethod
//-----------------------------------------------------------------------
// If you do not call release struct will be destroyed when unit handle gets recycled
//-----------------------------------------------------------------------
method release takes nothing returns nothing
local integer pui= .pui_id[integer(this)]
call .destroy()
set .pui_unit[pui] = null
set .pui_data[pui] = 0
endmethod
//! endtextmacro
endlibrary
library AutoIndex uses AIDS
module AutoData
private static thistype array data
// Fixed up the below to use thsitype instead of integer.
static method operator []= takes unit u, thistype i returns nothing
set .data[GetUnitId(u)] = i //Just attaching a struct to the unit
endmethod //using the module's thistype array.
static method operator [] takes unit u returns thistype
return .data[GetUnitId(u)] //Just returning the attached struct.
endmethod
endmodule
endlibrary
//TESH.scrollpos=215
//TESH.alwaysfold=0
library UnitIndexer initializer init
//Primary index filter
private function IndexFilter takes unit u returns boolean
return true
endfunction
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ Unit Indexer ~~ By Nestharus ~~ Version 3.0.1.1 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//Description:
// Unit Indexer is a system for indexing units with Unit User Data. It is made
// to have minimal features while retaining safety.
//
// Basic features include an indexing filter, indexer enable/disable, and global indexer events
//
// To apply more filters and minimize evaluation calls (like unit type index event filter),
// just create your own filter and run through that.
//
// ex-
// local trigger unitTypeEvent = CreateTrigger()
// if (GetUnitTypeId(GetIndexedUnit()) == 'hpea') then
// call TriggerEvaluate(unitTypeEvent)
//
//Installation:
// When running for the first time, hit save and reopen the map, then comment out the line below.
////! external ObjectMerger w3a Adef OUIN anam "Unit Indexing" ansf "(Unit Indexing)" aart "" arac 0
//
//Functions:
//////////////////////////////////////////////////////////////
// -function EnableUnitIndexing takes boolean val returns nothing
// Disables/Enables the unit indexer
//
// -function IsUnitIndexingEnabled takes nothing returns boolean
// Returns a boolean that determines if the unit
// indexer is enabled
//
// -function IndexUnit takes unit u returns integer
// Indexes unit u if the unit is not already indexed and
// returns its index.
//
// -function GetUnitById takes integer index returns unit
// Returns a unit given an index
//
// -function GetUnitId takes unit u returns integer
// Gets a unit's index
//
// -function IsUnitIndexed takes unit u returns boolean
// Returns a boolean that determines if the unit
// is indexed
//
//Events:
//////////////////////////////////////////////////////////////
// -function OnUnitIndex takes boolexpr c returns nothing
// Fires c when a unit is indexed
//
// -function OnUnitDeindex takes boolexpr c returns nothing
// Fires c when a unit is deindexed
//
// -function GetIndexedUnit takes nothing returns unit
// Returns the indexed unit.
//
// -function GetIndexedUnitId takes nothing returns integer
// Returns the indexed index.
//
//Unit Index Struct Module:
//////////////////////////////////////////////////////////////
// A module that will automatically call indexing/deindexing methods for you as well
// as automatically check against your own filter. It runs completely off of static ifs,
// so there are minimal calls and there is minimal code.
//
// module UnitIndexStruct
// readonly unit unit
// The indexed unit of the struct
//
// unitIndexAllocated
// Use this to determine whether an index is allocated or not. Useful
// if you only want to run event code for a struct if that struct is
// allocated.
//
// ex- if (indexedAllocated) then
// //run code
// endif
//
// Interface:
// private method unitIndex takes nothing returns nothing
// This method is called when a unit is indexed. Not having
// this method in your struct will just remove the indexing event
// code within the module.
//
// If there is a filter, this will only run if the filter returns true.
//
// private method unitDeindex takes nothing returns nothing
// This method is called when a unit is deindexed. Not having
// this method in your struct will just remove the deindexing event
// code within the module.
//
// If there is a filter and a unitIndex, this will only run if the struct is allocated.
// If there is no unitIndex method, this will run if the filter returns true.
//
// private static method unitFilter takes unit u returns boolean
// This method is called when attempting to index a unit. Not having
// this method in your struct will just remove the filter check.
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
globals
private boolean indexerEnabled = true
private trigger enter = CreateTrigger()
private trigger undefend = CreateTrigger()
private trigger onIndex = CreateTrigger()
private trigger onDeindex = CreateTrigger()
private unit array indexedUnit
private integer instanceCount = 0
private integer array recycle
private integer recycleCount = 0
private integer array indexedArray
private integer indexed = 0
endglobals
function EnableUnitIndexing takes boolean val returns nothing
set indexerEnabled = val
endfunction
function IsUnitIndexingEnabled takes nothing returns boolean
return indexerEnabled
endfunction
function GetUnitById takes integer index returns unit
return indexedUnit[index]
endfunction
function GetUnitId takes unit u returns integer
return GetUnitUserData(u)
endfunction
function IsUnitIndexed takes unit u returns boolean
return (indexedUnit[GetUnitUserData(u)] == u)
endfunction
function OnUnitIndex takes boolexpr c returns nothing
call TriggerAddCondition(onIndex, c)
endfunction
function OnUnitDeindex takes boolexpr c returns nothing
call TriggerAddCondition(onDeindex, c)
endfunction
function GetIndexedUnit takes nothing returns unit
return indexedUnit[indexedArray[indexed]]
endfunction
function GetIndexedUnitId takes nothing returns integer
return indexedArray[indexed]
endfunction
function IndexUnit takes unit u returns integer
set indexed = indexed + 1
set indexedArray[indexed] = GetUnitUserData(u)
if (indexedUnit[indexed] != u) then
if (recycleCount != 0) then
set recycleCount = recycleCount - 1
set indexedArray[indexed] = recycle[recycleCount]
else
set instanceCount = instanceCount + 1
set indexedArray[indexed] = instanceCount
endif
//set fields, add ability, set index data
set indexedUnit[indexedArray[indexed]] = u
call UnitAddAbility(u, 'OUIN')
call UnitMakeAbilityPermanent(u, true, 'OUIN')
call SetUnitUserData(u, indexedArray[indexed])
call TriggerEvaluate(onIndex)
endif
set indexed = indexed - 1
return indexedArray[indexed+1]
endfunction
private module DeindexUnit
set indexed = indexed + 1
static if LIBRARY_UnitEvent then
set indexedArray[indexed] = i
else
set indexedArray[indexed] = GetUnitUserData(u)
endif
if (indexedUnit[indexedArray[indexed]] == u) then
call TriggerEvaluate(onDeindex)
set recycle[recycleCount] = indexedArray[indexed]
set recycleCount = recycleCount + 1
set indexedUnit[indexedArray[indexed]] = null
endif
set indexed = indexed - 1
endmodule
//! runtextmacro optional UNIT_EVENT_MACRO()
private struct UnitIndexing extends array
public static method onEnter takes nothing returns boolean
//retrieve unit and unit type id
local unit filterUnit
if (indexerEnabled) then
set filterUnit = GetFilterUnit()
//make sure not allocated and enabled
if (indexedUnit[GetUnitUserData(filterUnit)] != filterUnit and IndexFilter(filterUnit)) then
//allocate
set indexed = indexed + 1
if (recycleCount != 0) then
set recycleCount = recycleCount - 1
set indexedArray[indexed] = recycle[recycleCount]
else
set instanceCount = instanceCount + 1
set indexedArray[indexed] = instanceCount
endif
//set fields, add ability, set index data
set indexedUnit[indexedArray[indexed]] = filterUnit
call UnitAddAbility(filterUnit, 'OUIN')
call UnitMakeAbilityPermanent(filterUnit, true, 'OUIN')
call SetUnitUserData(filterUnit, indexedArray[indexed])
implement optional EventOnUnitIndex
call TriggerEvaluate(onIndex)
set indexed = indexed - 1
endif
set filterUnit = null
endif
return false
endmethod
public static method onUndefend takes nothing returns boolean
static if not LIBRARY_UnitEvent then
local unit u = GetFilterUnit()
if (GetUnitAbilityLevel(u, 'OUIN') == 0) then
implement DeindexUnit
endif
set u = null
else
implement optional UnitEventModule
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local region world = CreateRegion()
local integer i = 15
local boolexpr bc = Condition(function thistype.onUndefend)
local rect r = GetWorldBounds()
call RegionAddRect(world, r)
call TriggerRegisterEnterRegion(enter, world, Condition(function thistype.onEnter))
loop
call TriggerRegisterPlayerUnitEvent(undefend, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, bc)
call SetPlayerAbilityAvailable(Player(i), 'OUIN', false)
implement optional EventOnUnitIniLoop
exitwhen i == 0
set i = i - 1
endloop
implement optional EventOnUnitIni1
call RemoveRect(r)
set r = null
set world = null
set bc = null
endmethod
endstruct
private function init takes nothing returns nothing
local integer i = 15
local boolexpr bc = Condition(function UnitIndexing.onEnter)
local group g = CreateGroup()
static if LIBRARY_UnitEvent then
local rect r = GetWorldBounds()
endif
loop
call GroupEnumUnitsOfPlayer(g, Player(i), bc)
exitwhen i == 0
set i = i - 1
endloop
//! runtextmacro optional EVENT_ON_UNIT_INI_2()
call DestroyGroup(g)
static if LIBRARY_UnitEvent then
call RemoveRect(r)
set r = null
endif
set bc = null
set g = null
endfunction
module UnitIndexStruct
static if thistype.unitFilter.exists then
static if thistype.unitIndex.exists then
private static boolean array allocated
public method operator unitIndexAllocated takes nothing returns boolean
return allocated[this]
endmethod
else
public method operator unitIndexAllocated takes nothing returns boolean
return unitFilter(GetUnitById(this))
endmethod
endif
else
public static constant boolean unitIndexAllocated = true
endif
static if thistype.unitIndex.exists then
private static method onIndexEvent takes nothing returns boolean
static if thistype.unitFilter.exists then
if (unitFilter(GetIndexedUnit())) then
set allocated[GetIndexedUnitId()] = true
call thistype(GetIndexedUnitId()).unitIndex()
endif
else
call thistype(GetIndexedUnitId()).unitIndex()
endif
return false
endmethod
static if thistype.unitDeindex.exists then
public method operator unit takes nothing returns unit
return indexedUnit[this]
endmethod
else
public method operator unit takes nothing returns unit
return indexedUnit(this)
endmethod
endif
endif
static if thistype.unitDeindex.exists then
private static method onDeindexEvent takes nothing returns boolean
static if thistype.unitFilter.exists then
static if thistype.unitDeindex.exists then
if (allocated[GetIndexedUnitId()]) then
set allocated[GetIndexedUnitId()] = false
call thistype(GetIndexedUnitId()).unitDeindex()
endif
else
if (unitFilter(GetIndexedUnit())) then
call thistype(GetIndexedUnitId()).unitDeindex()
endif
endif
else
call thistype(GetIndexedUnitId()).unitDeindex()
endif
return false
endmethod
endif
static if thistype.unitIndex.exists then
static if thistype.unitDeindex.exists then
private static method onInit takes nothing returns nothing
call OnUnitIndex(Condition(function thistype.onIndexEvent))
call OnUnitDeindex(Condition(function thistype.onDeindexEvent))
endmethod
else
private static method onInit takes nothing returns nothing
call OnUnitIndex(Condition(function thistype.onIndexEvent))
endmethod
endif
elseif thistype.unitDeindex.exists then
private static method onInit takes nothing returns nothing
call OnUnitDeindex(Condition(function thistype.onDeindexEvent))
endmethod
endif
endmodule
//! runtextmacro optional UNIT_EVENT_STRUCT_MACRO()
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AutoIndex requires TimerUtils
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
// to get get the unique value assigned to a particular unit. The GetUnitId
// function is extremely fast because it inlines directly to a GetUnitUserData
// call. AutoIndex automatically assigns an ID to each unit as it enters the
// map, and instantly frees that ID as the unit leaves the map. Detection of
// leaving units is accomplished in constant time without a periodic scan.
//
// AutoIndex uses UnitUserData by default. If something else in your map
// would conflict with that, you can set the UseUnitUserData configuration
// constant to false, and a hashtable will be used instead. Note that hash-
// tables are about 60% slower.
//
// If you turn on debug mode, AutoIndex will be able to display several
// helpful error messages. The following issues will be detected:
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game. Also
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
// you to fully utilize AutoIndex's enter/leave detection capabilities in
// conjunction with your structs.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the delay while saving.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
/*
globals
integer array IntegerData
real array RealData
SomeStruct array SomeStructData
englobals
function Example takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local integer id = GetUnitId(u)
//You now have a unique index for the unit, so you can
//attach or retrieve data about the unit using arrays.
set IntegerData[id] = 5
set RealData[id] = 25.0
set SomeStructData[id] = SomeStruct.create()
//If you have access to the same unit in another function, you can
//retrieve the data by using GetUnitId() and reading the arrays.
endfunction
*/
// The UnitFilter function in the configuration section is provided so that
// you can make AutoIndex completely ignore certain unit-types. Ignored units
// won't be indexed or fire indexed/deindexed events. You may want to filter out
// dummy casters or system-private units, especially ones that use UnitUserData
// internally. xe dummy units are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
/*
function UnitEntersMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
endfunction //Using GetUnitId() during Indexed events works fine...
function UnitLeavesMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
endfunction //So does using GetUnitId() during Deindexed events.
function Init takes nothing returns nothing
call OnUnitIndexed(UnitEntersMap)
call OnUnitDeindexed(UnitLeavesMap)
endfunction
*/
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from the need
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Returns 0 if a null unit was passed. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, this function will print
// an error message when passed a decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit has been indexed. The only time this will return false is
// for units you have filtered using the UnitFilter function, or
// for xe dummy units. You can use this function to easily detect
// dummy units and avoid performing certain actions on them.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// How to use AutoData:
//======================
//
// The AutoData module allows you to associate one or more instances
// of the implementing struct with units, as well as iterate through all
// of the instances associated with each unit.
//
// This association is accomplished through the "me" instance member,
// which the module will place in the implementing struct. Whichever unit
// you assign to "me" becomes the owner of that instance. You may change
// ownership by reassigning "me" to another unit at any time, or you may
// make the instance unowned by assigning "me" to null.
//
// AutoData implements the static method operator [] in your struct
// to allow you to access instances from their owning units. For example,
// you may type: local StructName s = StructName[u]. If u has been set
// to own an instance of StructName, s will be set to that instance.
//
// So, what happens if you assign the same owning unit to multiple
// instances? You may use 2D array syntax to access instances assigned to
// the same unit: local StructName s = StructName[u][n], where u is the
// owning unit, and n is the index beginning with 0 for each unit. You
// can access the size of a unit's instance list (i.e. the number of
// instances belonging to the unit) by using the .size instance member.
/*
struct Example
implement AutoData
static method create takes unit u returns Example
local Example this = allocate()
set me = u //Assigning the "me" member from AutoData.
return this
endmethod
endstruct
function Test takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local Example e1 = Example.create(u)
local Example e2 = Example.create(u)
local Example e3 = Example.create(u)
local Example e
call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
set e = Example[u] //This is the fastest way to iterate the instances belonging
loop //to a specific unit, starting with the first instance.
exitwhen e == 0 //e will be assigned to 0 when no instances remain.
call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
endloop //Thus, index + 1 is next, and index - 1 is previous.
endfunction //This trick allows you to avoid a local counter.
*/
// AutoData restrictions:
// -You may not implement AutoData in any struct which has already
// declared static or non-static method operator [].
// -AutoData will conflict with anything named "me", "size", or
// "index" in the implementing struct.
// -AutoData may not be implemented in structs that extend array.
// -You may not declare your own destroy method. (This restriction
// can be dropped as soon as JassHelper supports module onDestroy).
//
// AutoData information:
// -You do not need to null the "me" member when destroying an
// instance. That is done for you automatically during destroy().
// (But if you use deallocate(), you must null "me" manually.)
// -StructName[u] and StructName[u][0] refer to the same instance,
// which is the first instance that was associated with unit u.
// -StructName[u][StructName[u].size - 1] refers to the instance that
// was most recently associated with unit u.
// -Instances keep their relative order in the list when one is removed.
//
//===========================================================================
// How to use AutoCreate:
//=======================
//
// The AutoCreate module allows you to automatically create instances
// of the implementing struct for units as they enter the game. AutoCreate
// automatically implements AutoData into your struct. Any time an instance
// is automatically created for a unit, that instance's "me" member will be
// assigned to the entering unit.
//
// AutoCreate restrictions:
// -All of the same restrictions as AutoData.
// -If your struct's allocate() method takes parameters (i.e. the parent
// type's create method takes parameters), you must declare a create
// method and pass those extra parameters to allocate yourself.
//
// AutoCreate information:
// -You may optionally declare the createFilter method, which specifies
// which units should recieve an instance as they enter the game. If
// you do not declare it, all entering units will recieve an instance.
// -You may optionally declare the onCreate method, which will run when
// AutoCreate automatically creates an instance. (This is just a stand-
// in until JassHelper supports the onCreate method.)
// -You may declare your own create method, but it must take a single
// unit parameter (the entering unit) if you do so.
/*
struct Example
private static method createFilter takes unit u returns boolean
return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
endmethod
private method onCreate takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" entered the game!")
endmethod
implement AutoCreate
endstruct
*/
//===========================================================================
// How to use AutoDestroy:
//=========================
//
// The AutoDestroy module allows you to automatically destroy instances
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
// automatically implements AutoData into your struct. You must assign a unit
// to the "me" member of an instance for this module to have any effect.
//
// AutoDestroy restrictions:
// -All of the same restrictions as AutoData.
//
// AutoDestroy information:
// -If you also implement AutoCreate in the same struct, remember that it
// assigns the "me" unit automatically. That means you can have fully
// automatic creation and destruction.
/*
struct Example
static method create takes unit u returns Example
local Example this = allocate()
set me = u //You should assign a unit to "me",
return this //otherwise AutoDestroy does nothing.
endmethod //Not necessary if using AutoCreate.
private method onDestroy takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" left the game!")
endmethod
implement AutoDestroy
endstruct
*/
//===========================================================================
// Configuration:
//================
////! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMerger macro above. You may change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
private constant boolean SafeMode = true
//This is set to true by default so that GetUnitId() will ALWAYS work.
//If if this is set to false, GetUnitId() may fail to work in a very
//rare circumstance: creating a unit that has a default-on autocast
//ability, and using GetUnitId() on that unit as it enters the game,
//within a trigger that detects any order. Set this to false for a
//performance boost only if you think you can avoid this issue.
private constant boolean AutoDataFastMode = true
//If this is set to true, AutoData will utilize one hashtable per time
//it is implemented. If this is set to false, all AutoDatas will share
//a single hashtable, but iterating through the instances belonging to
//a unit will become about 12.5% slower. Your map will break if you
//use more than 255 hashtables simultaneously. Only set this to false
//if you suspect you will run out of hashtable instances.
endglobals
private function UnitFilter takes unit u returns boolean
return true
endfunction
//Make this function return false for any unit-types you want to ignore.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. The unit parameter "u" to refers to the unit being filtered.
//Do not filter out xe dummy units; they are automatically filtered.
//===========================================================================
// AutoData / AutoCreate / AutoDestroy modules:
//==============================================
function interface AutoCreator takes unit u returns nothing
function interface AutoDestroyer takes unit u returns nothing
globals
hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
endglobals //initialized and shared between all AutoData implementations.
module AutoData
private static hashtable ht
private static thistype array data
private static integer array listsize
private static key typeid //Good thing keys exist to identify each implementing struct.
private unit meunit
private integer id
readonly integer index //The user can avoid using a local counter because this is accessable.
static method operator [] takes unit u returns thistype
return data[GetUnitId(u)]
endmethod //This is as fast as retrieving an instance from a unit gets.
method operator [] takes integer index returns thistype
static if AutoDataFastMode then //If fast mode is enabled...
return LoadInteger(ht, id, index)
else //Each instance has its own hashtable to associate unit and index.
return LoadInteger(AutoData, id, index*8190+typeid)
endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
endmethod //Somehow, this version is 12.5% slower just because of the math.
private method setIndex takes integer index, thistype data returns nothing
static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
call SaveInteger(ht, id, index, data)
else
call SaveInteger(AutoData, id, index*8190+typeid, data)
endif
endmethod
private method remove takes nothing returns nothing
if meunit == null then //If the struct doesn't have an owner...
return //Nothing needs to be done.
endif
loop
exitwhen index == listsize[id] //The last value gets overwritten by 0.
call setIndex(index, this[index + 1]) //Shift each element down by one.
set this[index].index = index //Update the shifted instance's index.
set index = index + 1
endloop
set listsize[id] = listsize[id] - 1
set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
set meunit = null
endmethod
private method add takes unit u returns nothing
if meunit != null then //If the struct has an owner...
call remove() //remove it first.
endif
set meunit = u
set id = GetUnitId(u) //Cache GetUnitId for slight performance boost.
if data[id] == 0 then //If this is the first instance for this unit...
set data[id] = this //Update the value that thistype[u] returns.
endif
set index = listsize[id] //Remember the index for removal.
call setIndex(index, this) //Add to the array.
set listsize[id] = index + 1
endmethod
method operator me takes nothing returns unit
return meunit
endmethod
method operator me= takes unit u returns nothing
if u != null then //If assigning "me" a non-null value...
call add(u) //Add this instance to that unit's array.
else //If assigning "me" a null value...
call remove() //Remove this instance from that unit's array.
endif
endmethod
method operator size takes nothing returns integer
return listsize[id]
endmethod
method destroy takes nothing returns nothing
call deallocate()
call remove() //This makes removal automatic when an instance is destroyed.
endmethod
private static method onInit takes nothing returns nothing
static if AutoDataFastMode then //If fast mode is enabled...
set ht = InitHashtable() //Initialize one hashtable per instance.
else //If fast mode is disabled...
if AutoData == null then //If the hashtable hasn't been initialized yet...
set AutoData = InitHashtable() //Initialize the shared hashtable.
endif
endif
endmethod
endmodule
module AutoCreate
implement AutoData //AutoData is necessary for AutoCreate.
private static method creator takes unit u returns nothing
local thistype this
local boolean b = true //Assume that the instance will be created.
static if thistype.createFilter.exists then //If createFilter exists...
set b = createFilter(u) //evaluate it and update b.
endif
if b then //If the instance should be created...
static if thistype.create.exists then //If the create method exists...
set this = create(u) //Create the instance, passing the entering unit.
else //If the create method doesn't exist...
set this = allocate() //Just allocate the instance.
endif
set me = u //Assign the instance's owner as the entering unit.
static if thistype.onCreate.exists then //If onCreate exists...
call onCreate() //Call it, because JassHelper should do this anyway.
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoCreate(thistype.creator)
endmethod //During module initialization, pass the creator function to AutoIndex.
endmodule
module AutoDestroy
implement AutoData //AutoData is necessary for AutoDestroy.
static method destroyer takes unit u returns nothing
loop
exitwhen thistype[u] == 0
call thistype[u].destroy()
endloop
endmethod //Destroy each instance owned by the unit until none are left.
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoDestroy(thistype.destroyer)
endmethod //During module initialization, pass the destroyer function to AutoIndex.
endmodule
//===========================================================================
// AutoIndex struct:
//===================
function interface IndexFunc takes unit u returns nothing
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
private keyword getIndex
private keyword getIndexDebug
private keyword isUnitIndexed
private keyword onUnitIndexed
private keyword onUnitDeindexed
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger order = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static AutoCreator array creators
private static integer creators_n = -1
private static AutoDestroyer array destroyers
private static integer destroyers_n = -1
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
if u == null then
return 0
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return getIndex(u)
endmethod //If debug mode is enabled, use the getIndex method that shows errors.
private static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
static method addAutoCreate takes AutoCreator func returns nothing
set creators_n = creators_n + 1
set creators[creators_n] = func
endmethod
static method addAutoDestroy takes AutoDestroyer func returns nothing
set destroyers_n = destroyers_n + 1
set destroyers[destroyers_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if idunit[getIndex(whichUnit)] == whichUnit then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
endmethod //Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod //Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunAutoEvent("Resurrect")
//If AutoEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunAutoEvent("AnimateDead")
//If AutoEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunAutoEvent("Death")
//If AutoEvents is in the map, run the Death events.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
set n = destroyers_n
loop //Destroy AutoDestroy structs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
call 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 unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then
return //Don't index a unit that already has an ID.
endif
static if LIBRARY_xebasic then
if GetUnitTypeId(u) == XE_DUMMY_UNITID then
return //Don't index xe dummy units.
endif
endif
if not UnitFilter(u) then
return //Don't index units that fail the unit filter.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering unit.
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //Each of these flags are necessary to detect
set animated[index] = false //when a unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
if duringinit then //If a unit enters the map during initialization...
call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
endif //all units are noticed by OnUnitIndexed during initialization.
loop //Create AutoCreate structs for the entering unit.
exitwhen n > creators_n
call creators[n].evaluate(u)
set n = n + 1
endloop
set n = 0
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method onIssuedOrder takes nothing returns boolean
static if SafeMode then //If SafeMode is enabled, perform this extra check.
if getIndex(GetTriggerUnit()) == 0 then //If the unit doesn't already have
call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
endif
endif
return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
endmethod
private static method initEnteringUnit takes nothing returns boolean
call unitEntersMap(GetFilterUnit())
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = 15
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(creepdeath, function 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() includes the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
//The filter function of an EnterRegion trigger runs instantly when a unit is created.
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
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then //If debug mode is enabled...
return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
else //If debug mode is disabled...
return AutoIndex.getIndex(u) //call the normal, inlinable version.
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AuraOfBravery initializer onInit requires AdvancedAura
private struct AuraOfBravery extends array
implement AdvancedAuraBonus
implement AdvancedAuraBuff
implement AdvancedAuraEffect
implement AdvancedAura
static constant integer ability = 'A000'
/*
private method GetAuraAoE takes nothing returns real
return 900.
endmethod
*/
private method onLoop takes nothing returns nothing
set .AURA_BONUS_DAMAGE = 2 * .affectedCount * .level
endmethod
/*
private method onLoopUnit takes unit u returns nothing
endmethod
private method onAffectUnit takes unit u returns nothing
endmethod
private method onUnaffectUnit takes unit u returns nothing
endmethod
*/
private method onFilter takes unit u returns boolean
return IsUnitType(u, UNIT_TYPE_DEAD) == false and IsUnitAlly(u, GetOwningPlayer(.owner))
endmethod
/*
private method onLevelUp takes nothing returns nothing
endmethod
private method AuraInit takes nothing returns nothing
endmethod
*/
private static method onInit takes nothing returns nothing
set INTERVAL = 0.5
set BUFF = 'aud0'
set OWNER_SFX = "Abilities\\Spells\\Human\\DevotionAura\\DevotionAura.mdl"
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AuraOfDoom initializer onInit requires AdvancedAura
private struct AuraOfDoom extends array
implement AdvancedAuraBuff
implement AdvancedAuraBonus
implement AdvancedAuraEffect
implement AdvancedAura
static constant integer ability = 'A001'
/*
private method GetAuraAoE takes nothing returns real
return 900.
endmethod
private method onLoop takes nothing returns nothing
endmethod
private method onLoopUnit takes unit u returns nothing
endmethod
*/
private method onAffectUnit takes unit u returns nothing
call SetUnitScale(u, 0.5, 0.5, 0.5)
endmethod
private method onUnaffectUnit takes unit u returns nothing
call SetUnitScale(u, 1., 1., 1.)
endmethod
private method onFilter takes unit u returns boolean
return IsUnitType(u, UNIT_TYPE_DEAD) == false and IsUnitAlly(u, GetOwningPlayer(.owner)) == false
endmethod
private method onLevelUp takes nothing returns nothing
set .AURA_BONUS_ARMOR = .level * -2
set .AURA_BONUS_DAMAGE = .level * -3
endmethod
private method AuraInit takes nothing returns nothing
set .AURA_BONUS_ARMOR = .level * -2
set .AURA_BONUS_DAMAGE = .level * -3
endmethod
private static method onInit takes nothing returns nothing
set INTERVAL = 0.5
set BUFF = 'aud1'
set TARGET_SFX = "Abilities\\Spells\\Undead\\Cripple\\CrippleTarget.mdl"
set OWNER_SFX = "Abilities\\Spells\\Undead\\UnholyAura\\UnholyAura.mdl"
endmethod
endstruct
endlibrary