Name | Type | is_array | initial_value |
int | integer | No | |
unit | unit | No |
//TESH.scrollpos=141
//TESH.alwaysfold=0
scope SleightOfFistSpell/* v1.22
by Flux
http://www.hiveworkshop.com/forums/members/flux/
DESCRIPTION:
Ember Spirit dashes around with blazing speed, attacking all
enemies in the targeted area of effect, then returning to his
start location. Deals bonus damage to heroes, and less damage
to creeps.
NOTES:
Required:
- BonusMod
http://www.hiveworkshop.com/forums/graveyard-418/system-bonus-mod-setunitmaxstate-65622/
optionally requires:
- Table
http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
- MissileRecycler
http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
- SpellEffectEvent
http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/
CREDITS:
Earth-Fury: BonusMod
Bribe: Missile Recycler, SpellEffectEvent, Table
ILH: Imported Slash Effect
Vexorian: Attachable Dummy model
*/
native UnitAlive takes unit u returns boolean
//==================================================================
//====================== CONFIGURATION ===========================
//==================================================================
globals
//------------------------------------------------------
// ----------------- RAWCODES ----------------------
//------------------------------------------------------
//The Rawcode of the Spell
private constant integer SPELL_ID = 'Asof'
//The Rawcode of a Spell that will immediately ends
//Sleight of Fist when cast, e.g. Fire Remnant
private constant integer SPELL_END_ID = 'A000'
//While in Sleight of Fist, caster have maximum attack speed
//provided by this ability
private constant integer SPELL_BONUS_ATTACKSPEED = 'SfSB'
//Deals 50% damage to non-hero units
//Spell Book Rawcodes
private constant integer DAMAGE_DEC = 'SfHL'
//Command Aura based Ability
private constant integer DECREASE_DAMAGE = 'SfLD'
//Buff
private constant integer DAMAGE_BUFF = 'Bsof'
//Ability that transform the caster to a new unit with no
//Attack Damage Point allowing him to attack instantly
private constant integer SLEIGHT_OF_FIST_TRANSFORM = 'SfTr'
//Ability that transform caster back to normal
private constant integer SLEIGHT_OF_FIST_BACK = 'SfBa'
//To prevent caster from attacking twice, his attack is temporarily
//disable when waiting for the next jump
private constant integer DISABLE_ATTACK = 'SfNA'
//This spell creates a remnant at your location since the caster
//will eventually return to that position after slashing all targets
//Dummy unit for remnant
private constant integer DUMMY_ID = 'dumi'
//------------------------------------------------------
// -------------- REMNANT PROPERTIES ------------------
//------------------------------------------------------
//Remnant color
private constant integer REMNANT_RED = 255
private constant integer REMNANT_GREEN = 120
private constant integer REMNANT_BLUE = 20
private constant integer REMNANT_ALPHA = 220
//Value between 0 and 255, where 0 means fully transparent
//Opacity of the caster casting Sleight of Fist
private constant integer OPACITY = 130
//------------------------------------------------------
// ----------------- VISUAL EFFECTS -------------------
//------------------------------------------------------
//Units that will be attacked will be marked
private constant string TARGET_MARKING = "Abilities\\Spells\\Orc\\TrollBerserk\\HeadhunterWEAPONSLeft.mdl"
//Where the TARGET_MARKING will appear
private constant string TARGET_MARKING_ATTACHMENT = "overhead"
//Area of Effect Border
private constant string AOE_BORDER = "Environment\\LargeBuildingFire\\LargeBuildingFire2.mdl"
//How far away AOE_BORDER are to each other
private constant integer AOE_BORDER_SPACING = 64
//While casting Sleight of Fist, caster will have an attached sfx
private constant string CASTER_SFX = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile.mdl"
//When Sleight of Fist is cast, it leaves a remnant in the casting position
private constant string REMNANT_MODEL = "Units\\Creeps\\FirePandarenBrewmaster\\FirePandarenBrewmaster.mdl"
private constant string SLICED_SFX = "war3mapImported\\Slash.mdx"
//The caster's animation when no unit is hit
private constant string ANIMATION = "spell"
//------------------------------------------------------
// ----------- SPELL TIMING PROPERTIES ---------------
//------------------------------------------------------
//How long the caster will wait before it attacks the next target.
//Do not use values lower than 0.15 (based on Tested Values at Reaction Delay = 0)
private constant real JUMP_INTERVAL = 0.2
//The delay before attacking the first unit and before returning to the original location
//Can be any value, the purpose is to create a tail in the Sfx
private constant real FIRST_JUMP_DELAY = 0.05
//How much faster the attack animation when in Sleight of Fist
private constant real TIME_SCALE = 5.0
//------------------------------------------------------
// -------------- PRELOADING OPTION ------------------
//------------------------------------------------------
//Preload abilities so that it won't lag the first time the spell is used
private constant boolean PRELOAD = true
endglobals
//------------------------------------------------------------------
// -------------------- SPELL PROPERTIES -------------------------
//------------------------------------------------------------------
//Must match Object Editor Data
private function AreaOfEffect takes integer lvl returns real
return lvl*100.0 + 150.0
endfunction
//Bonus Damage to Units belonging in BonusFilter
private function BonusDamage takes integer lvl returns integer
return lvl*20 //20, 40, 60, 80
endfunction
//Determines what gets targeted
private function TargetFilter takes player owner, unit target returns boolean
return (UnitAlive(target) and IsUnitEnemy(target, owner) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) and not IsUnitType(target, UNIT_TYPE_FLYING))
endfunction
//Determines what gets extra damage
private function BonusFilter takes unit target returns boolean
return IsUnitType(target, UNIT_TYPE_HERO)
endfunction
//Other properties can be configured in Object Editor
//==================================================================
//==================== END CONFIGURATION =========================
//==================================================================
private struct spell
//Handles
private unit caster
private unit remnant
private player owner
private group g
private timer t
private effect casterSfx1
private effect casterSfx2
private effect remnantModel
private integer lvl
private real x
private real y
private integer bonus
private integer bonusDmg
private boolean firstTarget
private static thistype instance
static if LIBRARY_Table then
private static Table tb
else
private static hashtable hash = InitHashtable()
endif
private static real targetX
private static real targetY
private static real aoe
private static boolean groupHasUnits
private method destroy takes nothing returns nothing
local unit u
local integer id
//Revert caster properties back to normal
call UnitAddAbility(.caster, SLEIGHT_OF_FIST_BACK)
call UnitRemoveAbility(.caster, SLEIGHT_OF_FIST_BACK)
call UnitClearBonus(.caster, BONUS_DAMAGE)
call SetUnitTimeScale(.caster, 1)
//If all units were not attacked for whatever
//reason, remove the markings on unattacked units
loop
set u = FirstOfGroup(.g)
exitwhen u == null
call GroupRemoveUnit(.g, u)
//Remove Target Marking
static if LIBRARY_Table then
set id = GetHandleId(u)
call DestroyEffect(tb.effect[id])
call tb.remove(id)
else
set id = GetHandleId(u)
call DestroyEffect(LoadEffectHandle(hash, id, 0))
call FlushChildHashtable(hash, id)
endif
endloop
//Clean Table/Hash
static if LIBRARY_Table then
call tb.remove(GetHandleId(.caster))
call tb.remove(GetHandleId(.t))
else
call FlushChildHashtable(hash, GetHandleId(.caster))
call FlushChildHashtable(hash, GetHandleId(.t))
endif
//Remove the Damage Buff
call UnitRemoveAbility(.caster, DAMAGE_BUFF)
//destroy handles
call DestroyEffect(.casterSfx1)
call DestroyEffect(.casterSfx2)
call DestroyGroup(.g)
call DestroyTimer(.t)
call DestroyEffect(.remnantModel)
static if LIBRARY_MissileRecycler then
call RecycleMissile(.remnant)
call SetUnitScale(.remnant, 0, 0, 0)
else
call KillUnit(.remnant)
endif
//not necessary, but I'll do it anyway
set .casterSfx1 = null
set .casterSfx2 = null
set .remnantModel = null
set .caster = null
set .remnant = null
set .owner = null
set .g = null
set .t = null
//necessary
call .deallocate()
endmethod
private static method delayedDestroy takes nothing returns nothing
local integer id = GetHandleId(GetExpiredTimer())
static if LIBRARY_Table then
if tb.has(id) then
call thistype(tb[id]).destroy()
endif
else
if HaveSavedInteger(hash, id, 0) then
call thistype(LoadInteger(hash, id, 0)).destroy()
endif
endif
endmethod
private static method removeAtk takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
local thistype this
//Only remove the attack if there is still an existing
//instance of the spell
static if LIBRARY_Table then
if tb.has(id) then
set this = tb[id]
call UnitAddAbility(.caster, DISABLE_ATTACK)
endif
else
if HaveSavedInteger(hash, id, 0) then
set this = LoadInteger(hash, id, 0)
call UnitAddAbility(.caster, DISABLE_ATTACK)
endif
endif
call DestroyTimer(t)
set t = null
endmethod
private static method periodic takes nothing returns nothing
static if LIBRARY_Table then
local thistype this = tb[GetHandleId(GetExpiredTimer())]
else
local thistype this = LoadInteger(hash, GetHandleId(GetExpiredTimer()), 0)
endif
local integer id
local unit u
local real x
local real y
local real angle
local boolean exit = false
local integer hid
local timer delay
local unit slicedSfx
local fogmodifier fog
loop
set u = FirstOfGroup(.g)
call GroupRemoveUnit(.g, u)
if u == null then
//Return caster back to his casting location
call SetUnitX(.caster, .x)
call SetUnitY(.caster, .y)
call IssueImmediateOrderById(.caster, 851972)
call TimerStart(.t, FIRST_JUMP_DELAY, false, function thistype.delayedDestroy)
exitwhen true
else
set x = GetUnitX(u)
set y = GetUnitY(u)
//Reveal Target
set fog = CreateFogModifierRadius(.owner, FOG_OF_WAR_VISIBLE, x, y, 150, false, false)
call FogModifierStart(fog)
//If the target happens to die, check agan using TargetFilter
if IsUnitVisible(u, .owner) and TargetFilter(.owner, u) then
//Enable the caster to attack
call UnitRemoveAbility(.caster, DISABLE_ATTACK)
//Altering Damage based on Target
if BonusFilter(u) then
if .bonus != 1 then
call UnitRemoveAbility(.caster, DAMAGE_DEC)
call UnitRemoveAbility(.caster, DAMAGE_BUFF)
call UnitSetBonus(.caster, BONUS_DAMAGE, .bonusDmg)
set .bonus = 1
endif
else
if .bonus != -1 then
call UnitClearBonus(.caster, BONUS_DAMAGE)
call UnitAddAbility(.caster, DAMAGE_DEC)
set .bonus = -1
endif
endif
set angle = GetRandomReal(0, 360) //random angle for the sfx
static if LIBRARY_MissileRecycler then
set slicedSfx = GetRecycledMissile(x, y, 0, angle)
call DestroyEffect(AddSpecialEffectTarget(SLICED_SFX, slicedSfx, "origin"))
call RecycleMissile(slicedSfx)
else
set slicedSfx = CreateUnit(.owner, DUMMY_ID, x, y, angle)
call DestroyEffect(AddSpecialEffectTarget(SLICED_SFX, slicedSfx, "origin"))
call UnitApplyTimedLife(slicedSfx, 'BTLF', 1.0)
endif
set slicedSfx = null
//Position the caster where he doesn't need to turn.
set angle = -GetUnitFacing(.caster)*bj_DEGTORAD
call SetUnitX(.caster, x + 50*Cos(angle))
call SetUnitY(.caster, y + 50*Sin(angle))
//Order to attack
call IssueTargetOrderById(.caster, 851983, u)
//Remove Attack timer
set delay = CreateTimer()
static if LIBRARY_Table then
set tb[GetHandleId(delay)] = this
else
call SaveInteger(hash, GetHandleId(delay), 0, this)
endif
//0.103 is enough time to make the caster attack the target once
//See here http://www.hiveworkshop.com/forums/pastebin_data/scr8y9/Damage%20Delay.jpg
// ^Those results were consistent/precise.
call TimerStart(delay, 0.103, false, function thistype.removeAtk)
set delay = null
if .firstTarget then
set .firstTarget = false
call TimerStart(.t, JUMP_INTERVAL, true, function thistype.periodic)
endif
//Only exit the loop when a visible target is attacked
set exit = true
endif
call DestroyFogModifier(fog)
set fog = null
//Remove Target Marking
static if LIBRARY_Table then
set id = GetHandleId(u)
call DestroyEffect(tb.effect[id])
call tb.remove(id)
else
set id = GetHandleId(u)
call DestroyEffect(LoadEffectHandle(hash, id, 0))
call FlushChildHashtable(hash, id)
endif
endif
set u = null
exitwhen exit
endloop
endmethod
private static method enumGroup takes nothing returns nothing
local unit u = GetEnumUnit()
local thistype this = instance
local fogmodifier fog
if TargetFilter(.owner, u) and IsUnitInRangeXY(u, targetX, targetY, aoe) then
set fog = CreateFogModifierRadius(.owner, FOG_OF_WAR_VISIBLE, GetUnitX(u), GetUnitY(u), 150, false, false)
call FogModifierStart(fog)
if not IsUnitInvisible(u, .owner) then
//Mark unit with SFX
static if LIBRARY_Table then
set tb.effect[GetHandleId(u)] = AddSpecialEffectTarget(TARGET_MARKING, u, TARGET_MARKING_ATTACHMENT)
else
call SaveEffectHandle(hash, GetHandleId(u), 0, AddSpecialEffectTarget(TARGET_MARKING, u, TARGET_MARKING_ATTACHMENT))
endif
if not groupHasUnits then
set groupHasUnits = true
endif
else
call GroupRemoveUnit(.g, u)
endif
call DestroyFogModifier(fog)
set fog = null
else
call GroupRemoveUnit(.g, u)
endif
set u = null
endmethod
private static method noHitAnimation takes nothing returns nothing
local timer delay = GetExpiredTimer()
local integer id = GetHandleId(delay)
static if LIBRARY_Table then
call SetUnitAnimation(tb.unit[id], ANIMATION)
call tb.remove(id)
else
call SetUnitAnimation(LoadUnitHandle(hash, id, 0), ANIMATION)
call FlushChildHashtable(hash, id)
endif
call DestroyTimer(delay)
set delay = null
endmethod
private static method onCast takes nothing returns boolean
local thistype this = allocate()
local integer id
local real end = 2*bj_PI
local real i = 0
local timer delay
set .caster = GetTriggerUnit()
set id = GetHandleId(.caster)
//If the caster is currently in Sleight of Fist, destroy it
static if LIBRARY_Table then
if tb.has(id) then
call thistype(tb[id]).destroy()
endif
else
if HaveSavedInteger(hash, id, 0) then
call thistype(LoadInteger(hash, id, 0)).destroy()
endif
endif
set .owner = GetTriggerPlayer()
set .lvl = GetUnitAbilityLevel(caster, SPELL_ID)
set targetX = GetSpellTargetX()
set targetY = GetSpellTargetY()
set .x = GetUnitX(.caster)
set .y = GetUnitY(.caster)
set aoe = AreaOfEffect(.lvl)
set .firstTarget = true
set .g = CreateGroup()
//Group globals
set instance = this
set groupHasUnits = false
call GroupEnumUnitsInRange(.g, targetX, targetY, aoe + 128.0, null)
call ForGroup(.g, function thistype.enumGroup)
if groupHasUnits then
//Bonus Damage
set .bonus = 0
set .bonusDmg = BonusDamage(.lvl)
//Create ATTACH_SFX
set .casterSfx1 = AddSpecialEffectTarget(CASTER_SFX, .caster, "right,weapon")
set .casterSfx2 = AddSpecialEffectTarget(CASTER_SFX, .caster, "left,weapon")
//Transform
call UnitAddAbility(.caster, SLEIGHT_OF_FIST_TRANSFORM)
call UnitRemoveAbility(.caster, SLEIGHT_OF_FIST_TRANSFORM)
//Add Bonus Attack Speed
call UnitAddAbility(.caster, SPELL_BONUS_ATTACKSPEED)
//Disable Attack
call UnitAddAbility(.caster, DISABLE_ATTACK)
//Disable Movement
call UnitRemoveAbility(.caster, 'Amov')
//Make the caster invulnerable
call UnitAddAbility(.caster, 'Avul')
//Other Properties
call SetUnitPathing(.caster, false)
call SetUnitTimeScale(.caster, TIME_SCALE)
call SetUnitVertexColor(.caster, 255, 255, 255, OPACITY)
//Create REMNANT_SFX in your caster's location
static if LIBRARY_MissileRecycler then
set .remnant = GetRecycledMissile(x, y, 0, GetUnitFacing(.caster))
set .remnantModel = AddSpecialEffectTarget(REMNANT_MODEL, .remnant, "origin")
call SetUnitVertexColor(.remnant, REMNANT_RED, REMNANT_GREEN, REMNANT_BLUE, REMNANT_ALPHA)
else
set .remnant = CreateUnit(.owner, DUMMY_ID, x, y, GetUnitFacing(.caster))
set .remnantModel = AddSpecialEffectTarget(REMNANT_MODEL, .remnant, "origin")
call SetUnitVertexColor(.remnant, REMNANT_RED, REMNANT_GREEN, REMNANT_BLUE, REMNANT_ALPHA)
call PauseUnit(.remnant, true)
endif
set .t = CreateTimer()
//Store in Table/Hash
static if LIBRARY_Table then
set tb[id] = this
set tb[GetHandleId(.t)] = this
else
call SaveInteger(hash, id, 0, this)
call SaveInteger(hash, GetHandleId(.t), 0, this)
endif
//Start Timer
call TimerStart(.t, FIRST_JUMP_DELAY, false, function thistype.periodic)
else
//When no unit is hit, perform some animation
set delay = CreateTimer()
static if LIBRARY_Table then
set tb.unit[GetHandleId(delay)] = .caster
else
call SaveUnitHandle(hash, GetHandleId(delay), 0, .caster)
endif
call TimerStart(delay, 0.01, false, function thistype.noHitAnimation)
set delay = null
//Clean stuffs
call DestroyGroup(.g)
set .g = null
set .owner = null
set .caster = null
call .deallocate()
endif
//Create AOE Border, i = 0
loop
exitwhen i > end
call DestroyEffect(AddSpecialEffect(AOE_BORDER, targetX + aoe*Cos(i), targetY + aoe*Sin(i)))
set i = i + AOE_BORDER_SPACING/aoe
endloop
return false
endmethod
private static method onEnd takes nothing returns boolean
local thistype this
local integer id = GetHandleId(GetTriggerUnit())
static if LIBRARY_Table then
if tb.has(id) then
call thistype(tb[id]).destroy()
endif
else
if HaveSavedInteger(hash, id, 0) then
call thistype(LoadInteger(hash, id, 0)).destroy()
endif
endif
return false
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method condition takes nothing returns boolean
return (GetSpellAbilityId() == SPELL_ID and thistype.onCast() )
endmethod
endif
static if not LIBRARY_SpellEffectEvent then
private static method endCondition takes nothing returns boolean
return (GetSpellAbilityId() == SPELL_END_ID and thistype.onEnd() )
endmethod
endif
private static method onInit takes nothing returns nothing
local integer i = 0
static if PRELOAD then
local unit u = CreateUnit(Player(0), DUMMY_ID, 0, 0, 0)
endif
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
call RegisterSpellEffectEvent(SPELL_END_ID, function thistype.onEnd)
else
local trigger t1 = CreateTrigger()
local trigger t2 = CreateTrigger()
local player p
loop
set p = Player(i)
call TriggerRegisterPlayerUnitEvent(t1, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
call TriggerRegisterPlayerUnitEvent(t2, p, EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(t1, Condition(function thistype.condition))
call TriggerAddCondition(t2, Condition(function thistype.endCondition))
set t1 = null
set t2 = null
set i = 0
endif
static if LIBRARY_Table then
set tb = tb.create()
endif
loop
call SetPlayerAbilityAvailable(Player(i), DAMAGE_DEC, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
//Preload
static if PRELOAD then
call UnitAddAbility(u, DAMAGE_DEC)
call UnitAddAbility(u, DISABLE_ATTACK)
call UnitAddAbility(u, SPELL_BONUS_ATTACKSPEED)
call UnitAddAbility(u, SLEIGHT_OF_FIST_TRANSFORM)
call UnitAddAbility(u, SLEIGHT_OF_FIST_BACK)
call RemoveUnit(u)
set u = null
endif
endmethod
endstruct
endscope
//The studied warrior must whip and weave through
//its enemies, burning each without pause.
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
HOW TO IMPORT:
1. Copy "Sleight of Fist" hero ability from Object Editor to your map.
2. Copy the 'Sleight of Fist' trigger to your map.
3. If you don't want to copy paste each custom abilities/buffs/units this spell needs,
copy this 'How to import' trigger into your map.
3.1 Configure the lua script Object Merger data below. Then save the map.
If Object Merger throws an error saying "No object has been loaded, use 'modifyobject'
or 'createobject' to do so", it means the configured rawcode of the caster is not existing.
3.2 After successfully saving, close the map, you'll see new abilities, units,
buffs created, then delete/disable this 'How to import' trigger window.
4. Create a Missile Dummy if you're not using one yet, make sure its rawcode matches
the configured rawcode in the spell (usually 'dumi'). Make sure you import the dummy.mdx
model too (Press F12).
5. Set the Reaction Delay to 0.00 in Gameplay Constants.
********************************************************************************
THE CODES BELOW WILL AUTOMATICALLY CREATE THE OBJECT EDITOR ABILITIES
SO THAT YOU DON'T HAVE TO COPY EACH OF THEM WHEN IMPORTING THIS SPELL
TO YOUR MAP.
********************************************************************************
*/
//! externalblock extension=lua ObjectMerger $FILENAME$
//################################################################
//######################## CONFIGURATION #########################
//################################################################
//Rawcode of caster
//! i local caster = "N000"
//Name of the caster
//! i local casterName = "Ember Spirit"
//Proper Name of the caster
//! i local casterProperName = "Xin"
//Race of the custom abilities created (for organization purposes)
//! i local race = "creeps"
//Rawcode of the new caster unit when under Sleight of Fist
//! i local newCaster = "Nsof"
//Rawcode of the Damage Alter Buff,
//! i local buffRawCode = "Bsof"
//Rawcode of the Ability that decreases damage
//! i local lessDmgRawCode = "SfLD"
//Rawcode of the Damage Alter Spell Book ability that will hide
//the icon from appearing in the tooltip
//! i local hideDmg = "SfHD"
//! i local hideLessDmg = "SfHL"
//Attack Speed bonus rawcode
//! i local spdBonus = "SfSB"
//Disable Attack Ability rawcode
//! i local noAtk = "SfNA"
//Transform Abilities rawcodes
//! i local transform = "SfTr"
//! i local back = "SfBa"
//################################################################
//####################### END CONFIGURATION ####################
//################################################################
//NEW UNIT
//! i setobjecttype("units")
//! i createobject(caster,newCaster)
//! i makechange(current,"ucpt","0")
//! i makechange(current,"udp1","0")
//! i makechange(current,"ua1c","0")
//! i makechange(current,"umvr","3")
//! i makechange(current,"unam",casterName)
//! i makechange(current,"upro",casterProperName)
//! i makechange(current,"unsf"," (In Sleigh of Fist)")
//DAMAGE BUFF
//! i setobjecttype("buffs")
//! i createobject("BOac",buffRawCode)
//! i makechange(current,"fart","ReplaceableTextures\\CommandButtons\\BTNDeathPact.blp")
//! i makechange(current,"ftat","")
//! i makechange(current,"ftip","Sleight of Fist Less Damage")
//! i makechange(current,"fube","Deals less damage to non-Hero units")
//LESS DAMAGE
//! i setobjecttype("abilities")
//! i createobject("ACac",lessDmgRawCode)
//! i makechange(current,"anam","[SOF] Less Damage")
//! i makechange(current,"Ear2","1","1")
//Percentage Bonus Damage
//! i makechange(current,"Cac1","1","-0.5")
//Buff
//! i makechange(current,"abuf","1",buffRawCode)
//Targets allowed
//! i makechange(current,"atar","1","invulnerable,self")
//AOE
//! i makechange(current,"aare","1","1")
//! i makechange(current,"arac",race)
//TRANSFORM ABILITIES
//Transform caster to newCaster (newCaster can attack very fast!!)
//! i createobject("Abrf",transform)
//! i makechange(current,"anam","[SOF] Transform")
//! i makechange(current,"Eme1","1",newCaster)
//! i makechange(current,"Emeu","1",caster)
//! i makechange(current,"adur","1","0")
//! i makechange(current,"amcs","1","0")
//! i makechange(current,"achd","0")
//! i makechange(current,"arac",race)
//Transform newCaster to caster
//! i createobject("Abrf",back)
//! i makechange(current,"anam","[SOF] Transform back")
//! i makechange(current,"Eme1","1",caster)
//! i makechange(current,"Emeu","1",newCaster)
//! i makechange(current,"adur","1","0")
//! i makechange(current,"amcs","1","0")
//! i makechange(current,"achd","0")
//! i makechange(current,"arac",race)
//SPELL BOOKS
//HIDE THE COMMAND AURA ABILITY IN UI
//! i createobject("Aspb",hideLessDmg)
//! i makechange(current,"spb1","1",lessDmgRawCode)
//! i makechange(current,"anam","[SOF] Hide Less Damage Icon")
//! i makechange(current,"arac",race)
//! i makechange(current,"aite","0")
//ATTACK SPEED BONUS
//! i createobject("AIsx",spdBonus)
//! i makechange(current,"anam","[SOF] Bonus Attack Speed")
//! i makechange(current,"Isx1","1","99999")
//! i makechange(current,"ansf","")
//! i makechange(current,"arac",race)
//! i makechange(current,"aite","0")
//DISABLE ATTACK
//! i createobject("Abun",noAtk)
//! i makechange(current,"Car1","1","0")
//! i makechange(current,"anam","[SOF] Disable Attack")
//! i makechange(current,"arac",race)
//! endexternalblock
//TESH.scrollpos=149
//TESH.alwaysfold=0
library MissileRecycler initializer PreInit requires optional UnitIndexer, optional UnitDex, optional UnitIndexerGUI /*
MissileRecycler v 1.4.0.1
=========================================================================
Credits:
-------------------------------------------------------------------------
Written by Bribe
Vexorian, Anitarf and iNfraNe for the dummy.mdx model file
Nestharus for the Queue data structure and for finding that paused units
consume very few CPU resources.
=========================================================================
Introduction:
-------------------------------------------------------------------------
Recycling dummy units is important because the CreateUnit call is one of,
if not the, most processor-intensive native in the entire game. Creating
just a couple dozen dummy units in a single thread causes a visible frame
glitch for that instant. The overhead is even higher if you are using a
Unit Indexing library in the map which causes some extra evaluations per
new unit.
There are also reports of removed units leaving a little trail of RAM
surplus in their wake. I have not been able to reproduce this so I don't
know how serious it is.
I was motivated to create this system because removed units might be un-
safe in very large numbers and CreateUnit is a very heavy process.
The thing that makes this system different than others is the fact that
it considers the facing angle of the dummies being recycled, which I have
never seen another system even attempt before this. Since then,
MissileRecycler has inspired Anitarf to update XE with the same angle-retaining
capability and Nestharus has created Dummy. Considering the facing angle is
important because it takes 0.73 seconds for the unit to turn around,
which - when overlooked - looks especially weird if you are creating a unit-trail
or if you are shooting arrow-shaped objects as projectiles. For fireball effects or
effects that generally don't depend on facing angle, this system would be
a bit wasteful.
With default settings and the worst-case-scenario, it will take 0.09 seconds for
the projectile to turn to the angle you need. This is 1/8 of the normal worst case
scenario if you weren't recycling dummies considering facing. On average, it takes
roughly 0.045 seconds to turn to the angle you need (which is not noticable).
However, I have made this completely configurable and you are
able to change the values to whatever needs you have.
=========================================================================
Calibration Guide:
-------------------------------------------------------------------------
The thing that surprised me the most about this system was, no matter how
complex it turned out, it became very configurable. So I should let you
know what the constants do so you know if/how much you want to modify.
constant real DEATH_TIME = 2.0 //seconds
- Should not be less than the maximum time a death animation needs to play.
Should not be lower than .73 to ensure enough time to turn.
Should not be too high otherwise the dummies will take too long to recycle.
constant integer ANG_N = 8
- How many different angles are recognized by the system. Don't do
360 different angles because then you're going to have thousands of dummy
units stored and that's ridiculous, the game lags enough at 1000 units.
Increasing ANG_N increases realism but decreases the chance that a dummy
unit will be available to be recycled. I don't recommend making this any
lower, and the max I'd recommend would be 16.
constant integer ANG_STORAGE_MAX = 12
- How many dummy units are stored per angle. This limit is important
because you might have a spike at one point in the game where many units
are created, which could result in too high of a dummy population.
In general, I advise that the product of ANG_N x ANG_STORAGE_MAX does
not exceed 100 or 200. More than that is excessive, but you can
hypothetically have it up to 8190 if Warcraft 3's memory management
were better.
Preloads ANG_N x ANG_STORAGE_MAX dummy units. Preloading dummies is
useful as it dumps a lot of CreateUnit calls in initialization where you
won't see a frame glitch. In the 1.4 update, preloading is done 0 seconds
into the game to ensure any Indexers have already initialized.
private function ToggleIndexer takes boolean flag returns nothing
- Put what you need in here to disable/enable any indexer in your
map. if flag is true, enable indexer. If false, disable.
=========================================================================
API Guide:
-------------------------------------------------------------------------
You obviously need some functions so you can get a recycled dummy unit or
recycle it. Therefore I provide these:
function GetRecycledMissile
takes real x, real y, real z, real facing
returns unit
Returns a new dummy unit that acts as a projectile missile. The args
are simply the last three arguments you'd use for a CreateUnit call,
with the addition of a z parameter to represent the flying height -
it isn't the absolute z but relative to the ground because it uses
SetUnitFlyHeight on that value directly.
function RecycleMissile
takes unit u
returns nothing
When you are done with that dummy unit, recycle it via this function.
This function is pretty intelligent and resets that unit's animation
and its facing angle so you don't have to.
*/
//=======================================================================
// Save the map, then delete the exclaimation mark in the following line.
// Make sure that you don't have an object in your map with the rawcode
// 'dumi' and also configure the model path (war3mapImported\dummy.mdl)
// to the dummy.mdx model created by Vexorian.
//! external ObjectMerger w3u ewsp dumi unam "Missile Dummy" ubui "" uhom 1 ucol 0.01 umvt "None" umvr 1.00 utar "" uspa "" umdl "war3mapImported\dummy.mdl" umxr 0.00 umxp 0.00 ushr 0 uerd 0.00 udtm 0.00 ucbs 0.00 uble 0.00 uabi "Aloc,Amrf"
//Thanks to Vexorian that Optimizer 5.0 no longer kills natives
native UnitAlive takes unit id returns boolean
globals
//-------------------------------------------------------------------
// You must configure the dummy unit with the one created from the
// ObjectMerger statement above.
//
private constant integer DUMMY_ID = 'dumi' //The rawcode of the dummy unit.
private player OWNER = Player(15) //The owner of the dummy unit.
private constant integer ANG_N = 8 //# of indexed angles. Higher value increases realism but decreases recycle frequency.
private constant integer ANG_STORAGE_MAX = 20 //Max dummies per indexed angle. I recommend lowering this if you increase ANG_N.
private constant real DEATH_TIME = 2. //Allow the special effect on
//the unit to complete its "death" animation in this timeframe. Must
//be higher than 0.74 seconds to allow the unit time to turn. This
//number should not be lower than the maximum death-animation time of
//your missile-units' effect attachments, just to be safe.
endglobals
private function ToggleIndexer takes boolean flag returns nothing
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = flag
elseif LIBRARY_UnitIndexerGUI then
set udg_UnitIndexerEnabled = flag
elseif LIBRARY_UnitDex then
set UnitDex.Enabled = flag
endif
endfunction
globals
private constant integer ANG_VAL = 360 / ANG_N //Generate angle value from ANG_N.
private constant integer ANG_MID = ANG_VAL / 2 //The middle value of angle value.
//Misc vars
private unit array stack //Recycled dummy units.
private real array timeStamp //Prevents early recycling of units.
private integer array queueNext
private integer array queueLast
private integer recycle = 0
private timer gameTime = CreateTimer() //Used for visual continuity.
private integer array queueStack
private integer queueStackN = 0 //Used to avoid searching the queues.
endglobals
static if DEBUG_MODE then
private function Print takes string s returns nothing
//Un-comment this next line if you want to know how the system works:
//call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 999, "[MissileRecycler] " + s)
endfunction
endif
//=======================================================================
// Get a recycled dummy missile unit. If there are no recycled dummies
// that are already facing the angle you need, it creates a new dummy for
// you.
//
function GetRecycledMissile takes real x, real y, real z, real facing returns unit
local integer i = ModuloInteger(R2I(facing), 360) / ANG_VAL
local integer this = queueNext[i]
local unit u
if this != 0 and TimerGetElapsed(gameTime) >= timeStamp[this] then
//Dequeue this
set queueNext[i] = queueNext[this]
if queueNext[i] == 0 then
set queueLast[i] = i
endif
//Recycle this index
set queueLast[this] = recycle
set recycle = this
//Add queue index to available stack
set queueStack[queueStackN] = i
set queueStackN = queueStackN + 1
//Old unit will return as new
set u = stack[this]
call SetUnitFacing(u, facing)
call SetUnitUserData(u, 0)
//Reset the dummy's properties.
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitAnimationByIndex(u, 90)
call SetUnitScale(u, 1, 0, 0)
//call PauseUnit(u, false) -- you can disable "resets" that you don't need to worry about.
debug call Print("Recycling")
else
debug call Print("Creating new")
call ToggleIndexer(false)
set u = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call ToggleIndexer(true)
call PauseUnit(u, true)
endif
call SetUnitX(u, x)
call SetUnitY(u, y)
call SetUnitFlyHeight(u, z, 0)
set bj_lastCreatedUnit = u
set u = null
return bj_lastCreatedUnit
endfunction
//=======================================================================
// You should recycle the dummy missile unit when its job is done.
//
function RecycleMissile takes unit u returns nothing
local integer i
local integer this = recycle
if GetUnitTypeId(u) == DUMMY_ID and UnitAlive(u) and GetUnitUserData(u) != -1 then
if queueStackN == 0 then
debug call Print("Stack is full - removing surplus unit")
call UnitApplyTimedLife(u, 'BTLF', DEATH_TIME)
return
endif
//Recycle this
set recycle = queueLast[this]
//Index the dummy unit to an available facing angle.
//Get the last vacant angle index.
set queueStackN = queueStackN - 1
set i = queueStack[queueStackN]
//Enqueue this
set queueNext[queueLast[i]] = this
set queueLast[i] = this
set queueNext[this] = 0
//Allow a time barrier for the effect to destroy/turn to complete.
set timeStamp[this] = TimerGetElapsed(gameTime) + DEATH_TIME
set stack[this] = u
call SetUnitFacing(u, i * ANG_VAL + ANG_MID)
//Prevent double-free of this unit.
call SetUnitUserData(u, -1)
debug else
debug call BJDebugMsg("[MissileRecycler] Error: Attempt to recycle invalid unit.")
endif
endfunction
//=======================================================================
// I didn't need this function after all
//
function RecycleMissileDelayed takes unit u, real r returns nothing
call RecycleMissile(u)
endfunction
//=======================================================================
// Map the dummy units to their facing angles (map below is if ANG_N is
// 4 and ANG_STORAGE_MAX is 3).
//
// angle[0] (0) - [4] [5] [6]
// angle[1] (90) - [7] [8] [9]
// angle[2] (180) - [10][11][12]
// angle[3] (270) - [13][14][15]
//
private function Init takes nothing returns nothing
local integer end
local integer i = ANG_N
local integer n = i
local integer angle
local real x = GetRectMaxX(bj_mapInitialPlayableArea)
local real y = GetRectMaxY(bj_mapInitialPlayableArea)
local unit u
call ToggleIndexer(false)
loop
set i = i - 1
set queueNext[i] = n
set angle = i * ANG_VAL + ANG_MID
set end = n + ANG_STORAGE_MAX
set queueLast[i] = end - 1
loop
set queueNext[n] = n + 1
set u = CreateUnit(OWNER, DUMMY_ID, x, y, angle)
set stack[n] = u
call PauseUnit(u, true)
call SetUnitUserData(u, -1)
set n = n + 1
exitwhen n == end
endloop
set queueNext[n - 1] = 0
exitwhen i == 0
endloop
call ToggleIndexer(true)
call TimerStart(gameTime, 1000000., false, null)
set u = null
endfunction
private function PreInit takes nothing returns nothing
static if LIBRARY_UnitIndexerGUI then
call OnUnitIndexerInitialized(function Init)
else
call Init()
endif
endfunction
endlibrary
//TESH.scrollpos=16
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
/*
============= Why this is important? =================
1. Does not generate 16 events per spell.
2. This uses one trigger evaluation instead of one for each
individual spell (some maps have quite a few). This helps keep
framerate high and fluid when a spell is cast.
*/
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
//TESH.scrollpos=22
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary
//TESH.scrollpos=397
//TESH.alwaysfold=0
//////////////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ Bonus Mod
//@=======================================================================================
//@ Credits:
//@---------------------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@ Based on the work of:
//@ weaaddar
//@
//@ If you use this system, please credit all of the people mentioned above in your map.
//@=======================================================================================
//@ Bonus Mod Readme
//@---------------------------------------------------------------------------------------
//@
//@ BonusMod is a system for adding bonuses to a single unit. For example, you may wish
//@ to add a +40 damage bonus, or a -3 armor 'bonus'. Bonus mod works by adding abilitys
//@ to a unit which effect the particular stat by a power of two. By combining diffrent
//@ powers of two, you can reach any number between 0 and 2^(n+1) - 1, where n is the
//@ largest power of 2 used. Bonus mod can also apply negative bonuses, by adding an
//@ ability which has a 'bonus' of -2^(n+1), where again, n is the maximum power of 2.
//@ With the negative bonus, you can add anywhere between 1 and 2^(n+1)-1 of a bonus. This
//@ gives bonus mod a range of bonuses between -2^(n+1) and 2^(n+1)-1. By default, n is
//@ set at 11, giving us a range of bonuses between -4096 and +4095.
//@
//@---------------------------------------------------------------------------------------
//@ Adding Bonus Mod to your map:
//@
//@ Copy this library in to a trigger named "BonusMod" in your map.
//@
//@ After the script is copied, the hard part begins. You will have to transfer all of the
//@ bonus abilitys found in this map to yours. However, this is really easy to do if you
//@ are using the JASS NewGen editor. (Which you will have to be anyway, considering this
//@ system is written in vJASS.) Included with this library are macros for the Object
//@ Merger included in NewGen. Simply copy the Object Merger script included with this
//@ system in to your map in its own trigger. Save your map. (Saving will take a while.
//@ Up to 5 min if you have a slow computer.) Close your map, and reopen it. Disable the
//@ trigger you copied the ObjectMerger script in to.
//@ Your map now has all the abilitys it needs!
//@
//@---------------------------------------------------------------------------------------
//@ Functions:
//@
//@ boolean UnitSetBonus(unit <target unit>, integer <bonus type>, integer <bonus ammount>)
//@
//@ This function clears any previously applied bonus on <target unit>, setting the
//@ unit's bonus for <bonus type> to <bonus ammount>. <bonus type> should be one of the
//@ integer type constants below. This function will return false if the desired bonus is
//@ not a valid bonus type, or out of the range of bonuses that can be applied.
//@
//@ integer UnitGetBonus(unit <target unit>, integer <bonus type>)
//@
//@ Returns the bonus ammount of <bonus type> currently applied to <target unit>.
//@
//@ boolean UnitAddBonus(unit <target unit>, integer <bonus type>, integer <bonus ammount>)
//@
//@ This function will add <bonus ammount> to the bonus of type <bonus type> on the
//@ unit <target unit>. <bonus ammount> can be a negative value. This function will return
//@ false if the new bonus will be out of the range which bonus mod can apply.
//@
//@ nothing UnitClearBonus(unit <target unit>, integer <bonus type>)
//@
//@ This function will effectively set the bonus of type <bonus type> for the unit
//@ <target unit> to 0. It is advised you use this function over UnitSetBonus(..., ..., 0)
//@
//@---------------------------------------------------------------------------------------
//@ Variables:
//@
//@ BonusMod_MaxBonus
//@ The maximum bonus that Bonus Mod can apply
//@ BonusMod_MinBonus
//@ The minimum bonus that Bonus Mod can apply
//@---------------------------------------------------------------------------------------
//@ Increasing the Range of Bonuses:
//@
//@ By default, bonus mod uses 13 abilitys per bonus type. This gives each bonus type a
//@ range of -4096 to +4095. To increase this range, you will have to create one new
//@ ability for each ability, for each power of two you increase bonus mod by. You will
//@ also have to edit the negative bonus ability to apply a bonus of -2^(n+1), where n is
//@ the largest power of two you will be using for positive bonuses. You will need to edit
//@ the ABILITY_COUNT constant found below to reflect the new total number of abilitys
//@ each individual bonus will use. You will also have to add the abilitys to the function
//@ InitializeAbilitys. Note that the number in the array index indicates which power of
//@ 2 is held there. So, for instance, set BonusAbilitys[i + 15] would hold an ability
//@ which changes the relivent stat by 32768. (2^15 = 32768) The last ability in the array
//@ must apply a negative bonus.
//@
//@ Here is an example of the bonus BONUS_ARMOR using 15 abilitys instead of 12:
//@
//@ set i = BONUS_ARMOR * ABILITY_COUNT
//@ set BonusAbilitys[i + 0] = 'ZxA0' // +1
//@ set BonusAbilitys[i + 1] = 'ZxA1' // +2
//@ set BonusAbilitys[i + 2] = 'ZxA2' // +4
//@ set BonusAbilitys[i + 3] = 'ZxA3' // +8
//@ set BonusAbilitys[i + 4] = 'ZxA4' // +16
//@ set BonusAbilitys[i + 5] = 'ZxA5' // +32
//@ set BonusAbilitys[i + 6] = 'ZxA6' // +64
//@ set BonusAbilitys[i + 7] = 'ZxA7' // +128
//@ set BonusAbilitys[i + 8] = 'ZxA8' // +256
//@ set BonusAbilitys[i + 9] = 'ZxA9' // +512
//@ set BonusAbilitys[i + 10] = 'ZxAa' // +1024
//@ set BonusAbilitys[i + 11] = 'ZxAb' // +2048
//@ set BonusAbilitys[i + 12] = 'ZxAc' // +4096
//@ set BonusAbilitys[i + 13] = 'ZxAd' // +8192
//@ set BonusAbilitys[i + 14] = 'ZxAe' // +16384
//@ set BonusAbilitys[i + 15] = 'ZxAf' // -32768
//@
//@---------------------------------------------------------------------------------------
//@ Adding and Removing Bonus Types:
//@
//@ Removing a bonus type is simple. First, delete it from the list of constants found
//@ below. Make sure the constants are numberd 0, 1, 2, 3, etc. without any gaps. Change
//@ the BONUS_TYPES constant to reflect the new number of bonuses. You must then remove
//@ the lines of array initialization for the bonus you removed from the
//@ InitializeAbilitys function. You can then delete the abilitys for that bonus type, and
//@ you are then done removing a bonus type.
//@
//@ Adding a bonus type is done in much the same way. Add a constant for it to the list of
//@ constants below, ensuring they are numberd 0, 1, 2, 3 etc. withour any gaps. Change
//@ the BONUS_TYPES constant to reflect the new number of bonuses. You must then create
//@ all the needed abilitys for the new bonus type. Ensure the bonus they each apply is a
//@ power of 2, as with the already included bonuses. See the section Increasing the Range
//@ of Bonuses for more information. After all the abilitys are added, you must add the
//@ needed lines to the InitializeAbilitys function. The existing lines should be a clear
//@ enogh example.
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//////////////////////////////////////////////////////////////////////////////////////////
library BonusMod initializer Initialize
globals
//========================================================================================
// Bonus Type Constants
//========================================================================================
constant integer BONUS_ARMOR = 0 // Armor Bonus
constant integer BONUS_DAMAGE = 1 // Damage Bonus
constant integer BONUS_SIGHT_RANGE = 2 // Sight Range Bonus
constant integer BONUS_MANA_REGEN = 3 // Mana Regeneration Bonus (A % value)
constant integer BONUS_LIFE_REGEN = 4 // Life Regeneration Bonus (An absolute value)
constant integer BONUS_HERO_STR = 5 // Strength Bonus
constant integer BONUS_HERO_AGI = 6 // Agility Bonus
constant integer BONUS_HERO_INT = 7 // Intelligence Bonus
// The number of bonus type constants above:
constant integer BONUS_TYPES = 8
//========================================================================================
// Other Configuration
//========================================================================================
// The number of abilitys used per bonus type:
private constant integer ABILITY_COUNT = 13
// Note: Setting the following to false will decrease loading time, but will cause a
// small ammount of lag when a bonus is first applied. (Especially a negative bonus)
// If set to true, all BonusMod abilitys will be preloaded:
private constant boolean PRELOAD_ABILITYS = true
// Only applies if PRELOAD_ABILITYS is set to true.
// The unit type used to preload abilitys on:
private constant integer PRELOAD_DUMMY_UNIT = 'hpea'
endglobals
//========================================================================================
// Ability Initialization
//----------------------------------------------------------------------------------------
// The following function is used to define the rawcodes for all the abilitys bonus mod
// uses. If you use the text macros included with BonusMod, and if you do not wish to add,
// remove, or change the range of bonuses, you will not have to edit the following.
//
// Note that if your map already has abilitys with rawcodes that begin with Zx followed by
// an upper-case letter, the ObjectMerger macros included with this library will not work
// and you will have to edit the lines below. However, you could use the find and replace
// feature in JASS NewGen's Trigger Editor Syntax Highlighter to replace all occurances of
// Zx both here and in the ObjectMerger macros to ease configuration.
//========================================================================================
private keyword BonusAbilitys
private function InitializeAbilitys takes nothing returns nothing
local integer i
// Bonus Mod - Armor abilitys
set i = BONUS_ARMOR * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxA0' // +1
set BonusAbilitys[i + 1] = 'ZxA1' // +2
set BonusAbilitys[i + 2] = 'ZxA2' // +4
set BonusAbilitys[i + 3] = 'ZxA3' // +8
set BonusAbilitys[i + 4] = 'ZxA4' // +16
set BonusAbilitys[i + 5] = 'ZxA5' // +32
set BonusAbilitys[i + 6] = 'ZxA6' // +64
set BonusAbilitys[i + 7] = 'ZxA7' // +128
set BonusAbilitys[i + 8] = 'ZxA8' // +256
set BonusAbilitys[i + 9] = 'ZxA9' // +512
set BonusAbilitys[i + 10] = 'ZxAa' // +1024
set BonusAbilitys[i + 11] = 'ZxAb' // +2048
set BonusAbilitys[i + 12] = 'ZxAc' // -4096
// Bonus Mod - Damage abilitys
set i = BONUS_DAMAGE * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxB0' // +1
set BonusAbilitys[i + 1] = 'ZxB1' // +2
set BonusAbilitys[i + 2] = 'ZxB2' // +4
set BonusAbilitys[i + 3] = 'ZxB3' // +8
set BonusAbilitys[i + 4] = 'ZxB4' // +16
set BonusAbilitys[i + 5] = 'ZxB5' // +32
set BonusAbilitys[i + 6] = 'ZxB6' // +64
set BonusAbilitys[i + 7] = 'ZxB7' // +128
set BonusAbilitys[i + 8] = 'ZxB8' // +256
set BonusAbilitys[i + 9] = 'ZxB9' // +512
set BonusAbilitys[i + 10] = 'ZxBa' // +1024
set BonusAbilitys[i + 11] = 'ZxBb' // +2048
set BonusAbilitys[i + 12] = 'ZxBc' // -4096
// Bonus Mod - Sight Range abilitys
set i = BONUS_SIGHT_RANGE * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxC0' // +1
set BonusAbilitys[i + 1] = 'ZxC1' // +2
set BonusAbilitys[i + 2] = 'ZxC2' // +4
set BonusAbilitys[i + 3] = 'ZxC3' // +8
set BonusAbilitys[i + 4] = 'ZxC4' // +16
set BonusAbilitys[i + 5] = 'ZxC5' // +32
set BonusAbilitys[i + 6] = 'ZxC6' // +64
set BonusAbilitys[i + 7] = 'ZxC7' // +128
set BonusAbilitys[i + 8] = 'ZxC8' // +256
set BonusAbilitys[i + 9] = 'ZxC9' // +512
set BonusAbilitys[i + 10] = 'ZxCa' // +1024
set BonusAbilitys[i + 11] = 'ZxCb' // +2048
set BonusAbilitys[i + 12] = 'ZxCc' // -4096
// Bonus Mod - Mana Regen abilitys
set i = BONUS_MANA_REGEN * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxD0' // +1
set BonusAbilitys[i + 1] = 'ZxD1' // +2
set BonusAbilitys[i + 2] = 'ZxD2' // +4
set BonusAbilitys[i + 3] = 'ZxD3' // +8
set BonusAbilitys[i + 4] = 'ZxD4' // +16
set BonusAbilitys[i + 5] = 'ZxD5' // +32
set BonusAbilitys[i + 6] = 'ZxD6' // +64
set BonusAbilitys[i + 7] = 'ZxD7' // +128
set BonusAbilitys[i + 8] = 'ZxD8' // +256
set BonusAbilitys[i + 9] = 'ZxD9' // +512
set BonusAbilitys[i + 10] = 'ZxDa' // +1024
set BonusAbilitys[i + 11] = 'ZxDb' // +2048
set BonusAbilitys[i + 12] = 'ZxDc' // -4096
// Bonus Mod - Life Regen abilitys
set i = BONUS_LIFE_REGEN * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxE0' // +1
set BonusAbilitys[i + 1] = 'ZxE1' // +2
set BonusAbilitys[i + 2] = 'ZxE2' // +4
set BonusAbilitys[i + 3] = 'ZxE3' // +8
set BonusAbilitys[i + 4] = 'ZxE4' // +16
set BonusAbilitys[i + 5] = 'ZxE5' // +32
set BonusAbilitys[i + 6] = 'ZxE6' // +64
set BonusAbilitys[i + 7] = 'ZxE7' // +128
set BonusAbilitys[i + 8] = 'ZxE8' // +256
set BonusAbilitys[i + 9] = 'ZxE9' // +512
set BonusAbilitys[i + 10] = 'ZxEa' // +1024
set BonusAbilitys[i + 11] = 'ZxEb' // +2048
set BonusAbilitys[i + 12] = 'ZxEc' // -4096
// Bonus Mod - Hero STR abilitys
set i = BONUS_HERO_STR * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxF0' // +1
set BonusAbilitys[i + 1] = 'ZxF1' // +2
set BonusAbilitys[i + 2] = 'ZxF2' // +4
set BonusAbilitys[i + 3] = 'ZxF3' // +8
set BonusAbilitys[i + 4] = 'ZxF4' // +16
set BonusAbilitys[i + 5] = 'ZxF5' // +32
set BonusAbilitys[i + 6] = 'ZxF6' // +64
set BonusAbilitys[i + 7] = 'ZxF7' // +128
set BonusAbilitys[i + 8] = 'ZxF8' // +256
set BonusAbilitys[i + 9] = 'ZxF9' // +512
set BonusAbilitys[i + 10] = 'ZxFa' // +1024
set BonusAbilitys[i + 11] = 'ZxFb' // +2048
set BonusAbilitys[i + 12] = 'ZxFc' // -4096
// Bonus Mod - Hero AGI abilitys
set i = BONUS_HERO_AGI * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxG0' // +1
set BonusAbilitys[i + 1] = 'ZxG1' // +2
set BonusAbilitys[i + 2] = 'ZxG2' // +4
set BonusAbilitys[i + 3] = 'ZxG3' // +8
set BonusAbilitys[i + 4] = 'ZxG4' // +16
set BonusAbilitys[i + 5] = 'ZxG5' // +32
set BonusAbilitys[i + 6] = 'ZxG6' // +64
set BonusAbilitys[i + 7] = 'ZxG7' // +128
set BonusAbilitys[i + 8] = 'ZxG8' // +256
set BonusAbilitys[i + 9] = 'ZxG9' // +512
set BonusAbilitys[i + 10] = 'ZxGa' // +1024
set BonusAbilitys[i + 11] = 'ZxGb' // +2048
set BonusAbilitys[i + 12] = 'ZxGc' // -4096
// Bonus Mod - Hero INT abilitys
set i = BONUS_HERO_INT * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxH0' // +1
set BonusAbilitys[i + 1] = 'ZxH1' // +2
set BonusAbilitys[i + 2] = 'ZxH2' // +4
set BonusAbilitys[i + 3] = 'ZxH3' // +8
set BonusAbilitys[i + 4] = 'ZxH4' // +16
set BonusAbilitys[i + 5] = 'ZxH5' // +32
set BonusAbilitys[i + 6] = 'ZxH6' // +64
set BonusAbilitys[i + 7] = 'ZxH7' // +128
set BonusAbilitys[i + 8] = 'ZxH8' // +256
set BonusAbilitys[i + 9] = 'ZxH9' // +512
set BonusAbilitys[i + 10] = 'ZxHa' // +1024
set BonusAbilitys[i + 11] = 'ZxHb' // +2048
set BonusAbilitys[i + 12] = 'ZxHc' // -4096
endfunction
//========================================================================================
// System Code
//----------------------------------------------------------------------------------------
// Do not edit below this line unless you wish to change the way the system works.
//========================================================================================
globals
// Contains all abilitys in a two-dimensional structure:
private integer array BonusAbilitys
// Precomputed powers of two to avoid speed and rounding issues with Pow():
private integer array PowersOf2
// Range constants (Read only please):
public integer MaxBonus
public integer MinBonus
endglobals
function UnitClearBonus takes unit u, integer bonusType returns nothing
local integer i = 0
loop
call UnitRemoveAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + i])
set i = i + 1
exitwhen i == ABILITY_COUNT - 2
endloop
endfunction
function UnitSetBonus takes unit u, integer bonusType, integer ammount returns boolean
local integer i = ABILITY_COUNT - 2
if ammount < MinBonus or ammount > MaxBonus then
debug call BJDebugMsg("BonusSystem Error: Bonus too high or low (" + I2S(ammount) + ")")
return false
elseif bonusType < 0 or bonusType >= BONUS_TYPES then
debug call BJDebugMsg("BonusSystem Error: Invalid bonus type (" + I2S(bonusType) + ")")
return false
endif
if ammount < 0 then
set ammount = MaxBonus + ammount + 1
call UnitAddAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1])
call UnitMakeAbilityPermanent(u, true, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1])
else
call UnitRemoveAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1])
endif
loop
if ammount >= PowersOf2[i] then
call UnitAddAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + i])
call UnitMakeAbilityPermanent(u, true, BonusAbilitys[bonusType * ABILITY_COUNT + i])
set ammount = ammount - PowersOf2[i]
else
call UnitRemoveAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + i])
endif
set i = i - 1
exitwhen i < 0
endloop
return true
endfunction
function UnitGetBonus takes unit u, integer bonusType returns integer
local integer i = 0
local integer ammount = 0
if GetUnitAbilityLevel(u, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1]) > 0 then
set ammount = MinBonus
endif
loop
if GetUnitAbilityLevel(u, BonusAbilitys[bonusType * ABILITY_COUNT + i]) > 0 then
set ammount = ammount + PowersOf2[i]
endif
set i = i + 1
exitwhen i == ABILITY_COUNT - 2
endloop
return ammount
endfunction
function UnitAddBonus takes unit u, integer bonusType, integer ammount returns boolean
return UnitSetBonus(u, bonusType, UnitGetBonus(u, bonusType) + ammount)
endfunction
private function Initialize takes nothing returns nothing
local integer i = 1
local unit u
set PowersOf2[0] = 1
loop
set PowersOf2[i] = PowersOf2[i - 1] * 2
set i = i + 1
exitwhen i == ABILITY_COUNT
endloop
set MaxBonus = PowersOf2[ABILITY_COUNT - 1] - 1
set MinBonus = -PowersOf2[ABILITY_COUNT - 1]
call InitializeAbilitys()
if PRELOAD_ABILITYS then
set u = CreateUnit(Player(15), PRELOAD_DUMMY_UNIT, 0, 0, 0)
set i = 0
loop
exitwhen i == BONUS_TYPES * ABILITY_COUNT
call UnitAddAbility(u, BonusAbilitys[i])
set i = i + 1
endloop
call RemoveUnit(u)
endif
endfunction
endlibrary
//TESH.scrollpos=38
//TESH.alwaysfold=0
// About these macros:
//
// The first paramiter is the rawcode for the ability. The convention used here by default is:
// Zx followed by an uppercase letter, unique to the bonus, followed by a 0-9-a-z representing
// the base of two the ability applys.
//
// The second paramiter is the display value of the ability. This is so that they all line up
// neatly in the object editor.
//
// The third paramiter is the actual value the bonus ability applys.
//
// Note that you should copy this in to its own trigger, save once, close the map and re-open
// it in the World Editor, then disable the trigger you copied this in to. If you do not
// disable the trigger, saving will take an insane ammount of time every time you save. If you
// make any changes below, you can simply enable the trigger again, save again, then disable
// the trigger again.
// BonusMod - Armor
//============================================================================================
//! textmacro ArmorAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AId1 $RAWCODE$ Idef 1 $VALUE$ anam "BonusMod - Armor ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNHumanArmorUpOne.blp
//! endtextmacro
//! runtextmacro ArmorAbility("ZxA0", "+0001", "1")
//! runtextmacro ArmorAbility("ZxA1", "+0002", "2")
//! runtextmacro ArmorAbility("ZxA2", "+0004", "4")
//! runtextmacro ArmorAbility("ZxA3", "+0008", "8")
//! runtextmacro ArmorAbility("ZxA4", "+0016", "16")
//! runtextmacro ArmorAbility("ZxA5", "+0032", "32")
//! runtextmacro ArmorAbility("ZxA6", "+0064", "64")
//! runtextmacro ArmorAbility("ZxA7", "+0128", "128")
//! runtextmacro ArmorAbility("ZxA8", "+0256", "256")
//! runtextmacro ArmorAbility("ZxA9", "+0512", "512")
//! runtextmacro ArmorAbility("ZxAa", "+1024", "1024")
//! runtextmacro ArmorAbility("ZxAb", "+2048", "2048")
//! runtextmacro ArmorAbility("ZxAc", "-4096", "-4096")
// BonusMod - Damage
//============================================================================================
//! textmacro DamageAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AItg $RAWCODE$ Iatt 1 $VALUE$ anam "BonusMod - Damage ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNSteelMelee.blp
//! endtextmacro
//! runtextmacro DamageAbility("ZxB0", "+0001", "1")
//! runtextmacro DamageAbility("ZxB1", "+0002", "2")
//! runtextmacro DamageAbility("ZxB2", "+0004", "4")
//! runtextmacro DamageAbility("ZxB3", "+0008", "8")
//! runtextmacro DamageAbility("ZxB4", "+0016", "16")
//! runtextmacro DamageAbility("ZxB5", "+0032", "32")
//! runtextmacro DamageAbility("ZxB6", "+0064", "64")
//! runtextmacro DamageAbility("ZxB7", "+0128", "128")
//! runtextmacro DamageAbility("ZxB8", "+0256", "256")
//! runtextmacro DamageAbility("ZxB9", "+0512", "512")
//! runtextmacro DamageAbility("ZxBa", "+1024", "1024")
//! runtextmacro DamageAbility("ZxBb", "+2048", "2048")
//! runtextmacro DamageAbility("ZxBc", "-4096", "-4096")
// BonusMod - Sight Range
//============================================================================================
//! textmacro SightRangeAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIsi $RAWCODE$ Isib 1 $VALUE$ anam "BonusMod - Sight Range ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNTelescope.blp
//! endtextmacro
//! runtextmacro SightRangeAbility("ZxC0", "+0001", "1")
//! runtextmacro SightRangeAbility("ZxC1", "+0002", "2")
//! runtextmacro SightRangeAbility("ZxC2", "+0004", "4")
//! runtextmacro SightRangeAbility("ZxC3", "+0008", "8")
//! runtextmacro SightRangeAbility("ZxC4", "+0016", "16")
//! runtextmacro SightRangeAbility("ZxC5", "+0032", "32")
//! runtextmacro SightRangeAbility("ZxC6", "+0064", "64")
//! runtextmacro SightRangeAbility("ZxC7", "+0128", "128")
//! runtextmacro SightRangeAbility("ZxC8", "+0256", "256")
//! runtextmacro SightRangeAbility("ZxC9", "+0512", "512")
//! runtextmacro SightRangeAbility("ZxCa", "+1024", "1024")
//! runtextmacro SightRangeAbility("ZxCb", "+2048", "2048")
//! runtextmacro SightRangeAbility("ZxCc", "-4096", "-4096")
// BonusMod - Mana Regeneration
//============================================================================================
//! textmacro ManaRegenAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIrm $RAWCODE$ Imrp 1 $VALUE$ anam "BonusMod - Mana Regen ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNSobiMask.blp
//! endtextmacro
//! runtextmacro ManaRegenAbility("ZxD0", "+0001", "0.01")
//! runtextmacro ManaRegenAbility("ZxD1", "+0002", "0.02")
//! runtextmacro ManaRegenAbility("ZxD2", "+0004", "0.04")
//! runtextmacro ManaRegenAbility("ZxD3", "+0008", "0.08")
//! runtextmacro ManaRegenAbility("ZxD4", "+0016", "0.16")
//! runtextmacro ManaRegenAbility("ZxD5", "+0032", "0.32")
//! runtextmacro ManaRegenAbility("ZxD6", "+0064", "0.64")
//! runtextmacro ManaRegenAbility("ZxD7", "+0128", "1.28")
//! runtextmacro ManaRegenAbility("ZxD8", "+0256", "2.56")
//! runtextmacro ManaRegenAbility("ZxD9", "+0512", "5.12")
//! runtextmacro ManaRegenAbility("ZxDa", "+1024", "10.24")
//! runtextmacro ManaRegenAbility("ZxDb", "+2048", "20.48")
//! runtextmacro ManaRegenAbility("ZxDc", "-4096", "-40.96")
// BonusMod - Life Regenration
//============================================================================================
//! textmacro LifeRegenAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a Arel $RAWCODE$ Ihpr 1 $VALUE$ anam "BonusMod - Life Regen ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNRingSkull.blp
//! endtextmacro
//! runtextmacro LifeRegenAbility("ZxE0", "+0001", "1")
//! runtextmacro LifeRegenAbility("ZxE1", "+0002", "2")
//! runtextmacro LifeRegenAbility("ZxE2", "+0004", "4")
//! runtextmacro LifeRegenAbility("ZxE3", "+0008", "8")
//! runtextmacro LifeRegenAbility("ZxE4", "+0016", "16")
//! runtextmacro LifeRegenAbility("ZxE5", "+0032", "32")
//! runtextmacro LifeRegenAbility("ZxE6", "+0064", "64")
//! runtextmacro LifeRegenAbility("ZxE7", "+0128", "128")
//! runtextmacro LifeRegenAbility("ZxE8", "+0256", "256")
//! runtextmacro LifeRegenAbility("ZxE9", "+0512", "512")
//! runtextmacro LifeRegenAbility("ZxEa", "+1024", "1024")
//! runtextmacro LifeRegenAbility("ZxEb", "+2048", "2048")
//! runtextmacro LifeRegenAbility("ZxEc", "-4096", "-4096")
// BonusMod - Strength
//============================================================================================
//! textmacro HeroStrAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIa1 $RAWCODE$ Iagi 1 0 Iint 1 0 Istr 1 $VALUE$ anam "BonusMod - Hero STR ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNGoldRing.blp
//! endtextmacro
//! runtextmacro HeroStrAbility("ZxF0", "+0001", "1")
//! runtextmacro HeroStrAbility("ZxF1", "+0002", "2")
//! runtextmacro HeroStrAbility("ZxF2", "+0004", "4")
//! runtextmacro HeroStrAbility("ZxF3", "+0008", "8")
//! runtextmacro HeroStrAbility("ZxF4", "+0016", "16")
//! runtextmacro HeroStrAbility("ZxF5", "+0032", "32")
//! runtextmacro HeroStrAbility("ZxF6", "+0064", "64")
//! runtextmacro HeroStrAbility("ZxF7", "+0128", "128")
//! runtextmacro HeroStrAbility("ZxF8", "+0256", "256")
//! runtextmacro HeroStrAbility("ZxF9", "+0512", "512")
//! runtextmacro HeroStrAbility("ZxFa", "+1024", "1024")
//! runtextmacro HeroStrAbility("ZxFb", "+2048", "2048")
//! runtextmacro HeroStrAbility("ZxFc", "-4096", "-4096")
// BonusMod - Agility
//============================================================================================
//! textmacro HeroAgiAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIa1 $RAWCODE$ Iagi 1 $VALUE$ Iint 1 0 Istr 1 0 anam "BonusMod - Hero AGI ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNGoldRing.blp
//! endtextmacro
//! runtextmacro HeroAgiAbility("ZxG0", "+0001", "1")
//! runtextmacro HeroAgiAbility("ZxG1", "+0002", "2")
//! runtextmacro HeroAgiAbility("ZxG2", "+0004", "4")
//! runtextmacro HeroAgiAbility("ZxG3", "+0008", "8")
//! runtextmacro HeroAgiAbility("ZxG4", "+0016", "16")
//! runtextmacro HeroAgiAbility("ZxG5", "+0032", "32")
//! runtextmacro HeroAgiAbility("ZxG6", "+0064", "64")
//! runtextmacro HeroAgiAbility("ZxG7", "+0128", "128")
//! runtextmacro HeroAgiAbility("ZxG8", "+0256", "256")
//! runtextmacro HeroAgiAbility("ZxG9", "+0512", "512")
//! runtextmacro HeroAgiAbility("ZxGa", "+1024", "1024")
//! runtextmacro HeroAgiAbility("ZxGb", "+2048", "2048")
//! runtextmacro HeroAgiAbility("ZxGc", "-4096", "-4096")
// BonusMod - Intelligence
//============================================================================================
//! textmacro HeroIntAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIa1 $RAWCODE$ Iagi 1 0 Iint 1 $VALUE$ Istr 1 0 anam "BonusMod - Hero INT ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNGoldRing.blp
//! endtextmacro
//! runtextmacro HeroIntAbility("ZxH0", "+0001", "1")
//! runtextmacro HeroIntAbility("ZxH1", "+0002", "2")
//! runtextmacro HeroIntAbility("ZxH2", "+0004", "4")
//! runtextmacro HeroIntAbility("ZxH3", "+0008", "8")
//! runtextmacro HeroIntAbility("ZxH4", "+0016", "16")
//! runtextmacro HeroIntAbility("ZxH5", "+0032", "32")
//! runtextmacro HeroIntAbility("ZxH6", "+0064", "64")
//! runtextmacro HeroIntAbility("ZxH7", "+0128", "128")
//! runtextmacro HeroIntAbility("ZxH8", "+0256", "256")
//! runtextmacro HeroIntAbility("ZxH9", "+0512", "512")
//! runtextmacro HeroIntAbility("ZxHa", "+1024", "1024")
//! runtextmacro HeroIntAbility("ZxHb", "+2048", "2048")
//! runtextmacro HeroIntAbility("ZxHc", "-4096", "-4096")
// Don't let a //! external command be the last line in a trigger!