//TESH.scrollpos=0
//TESH.alwaysfold=0
function FilterDest takes nothing returns boolean
return GetDestructableLife(GetFilterDestructable()) > 0
endfunction
Name | Type | is_array | initial_value |
AfterDamageEvent | real | No | |
AOEDamageEvent | real | No | |
AOEDamageSource | unit | No | |
ARMOR_TYPE_ETHEREAL | integer | No | |
ARMOR_TYPE_FLESH | integer | No | |
ARMOR_TYPE_METAL | integer | No | |
ARMOR_TYPE_NONE | integer | No | |
ARMOR_TYPE_STONE | integer | No | |
ARMOR_TYPE_WOOD | integer | No | |
ArmorDamageEvent | real | No | |
ArmorTypeDebugStr | string | Yes | |
ATTACK_TYPE_CHAOS | integer | No | |
ATTACK_TYPE_HERO | integer | No | |
ATTACK_TYPE_MAGIC | integer | No | |
ATTACK_TYPE_NORMAL | integer | No | |
ATTACK_TYPE_PIERCE | integer | No | |
ATTACK_TYPE_SIEGE | integer | No | |
ATTACK_TYPE_SPELLS | integer | No | |
AttackTypeDebugStr | string | Yes | |
CONVERTED_ATTACK_TYPE | attacktype | Yes | |
CONVERTED_DAMAGE_TYPE | damagetype | Yes | |
DAMAGE_TYPE_ACID | integer | No | |
DAMAGE_TYPE_COLD | integer | No | |
DAMAGE_TYPE_DEATH | integer | No | |
DAMAGE_TYPE_DEFENSIVE | integer | No | |
DAMAGE_TYPE_DEMOLITION | integer | No | |
DAMAGE_TYPE_DISEASE | integer | No | |
DAMAGE_TYPE_DIVINE | integer | No | |
DAMAGE_TYPE_ENHANCED | integer | No | |
DAMAGE_TYPE_FIRE | integer | No | |
DAMAGE_TYPE_FORCE | integer | No | |
DAMAGE_TYPE_LIGHTNING | integer | No | |
DAMAGE_TYPE_MAGIC | integer | No | |
DAMAGE_TYPE_MIND | integer | No | |
DAMAGE_TYPE_NORMAL | integer | No | |
DAMAGE_TYPE_PLANT | integer | No | |
DAMAGE_TYPE_POISON | integer | No | |
DAMAGE_TYPE_SHADOW_STRIKE | integer | No | |
DAMAGE_TYPE_SLOW_POISON | integer | No | |
DAMAGE_TYPE_SONIC | integer | No | |
DAMAGE_TYPE_SPIRIT_LINK | integer | No | |
DAMAGE_TYPE_UNIVERSAL | integer | No | |
DAMAGE_TYPE_UNKNOWN | integer | No | |
DamageEvent | real | No | |
DamageEventAmount | real | No | |
DamageEventAOE | integer | No | |
DamageEventAOEGroup | group | No | |
DamageEventArmorPierced | real | No | |
DamageEventArmorT | integer | No | |
DamageEventAttackT | integer | No | |
DamageEventDamageT | integer | No | |
DamageEventDefenseT | integer | No | |
DamageEventLevel | integer | No | |
DamageEventOverride | boolean | No | |
DamageEventPrevAmt | real | No | |
DamageEventSource | unit | No | |
DamageEventTarget | unit | No | |
DamageEventType | integer | No | |
DamageEventWeaponT | integer | No | |
DamageFilterAttackT | integer | No | |
DamageFilterDamageT | integer | No | |
DamageFilterFailChance | real | No | |
DamageFilterMinAmount | real | No | |
DamageFilterRunChance | real | No | |
DamageFilterSource | unit | No | |
DamageFilterSourceA | abilcode | No | |
DamageFilterSourceB | buffcode | No | |
DamageFilterSourceC | integer | No | |
DamageFilterSourceI | itemcode | No | |
DamageFilterSourceT | unitcode | No | |
DamageFilterTarget | unit | No | |
DamageFilterTargetA | abilcode | No | |
DamageFilterTargetB | buffcode | No | |
DamageFilterTargetC | integer | No | |
DamageFilterTargetI | itemcode | No | |
DamageFilterTargetT | unitcode | No | |
DamageFilterType | integer | No | |
DamageModifierEvent | real | No | |
DamageScalingUser | real | No | |
DamageScalingWC3 | real | No | |
DamageTypeBlocked | integer | No | |
DamageTypeCode | integer | No | |
DamageTypeCriticalStrike | integer | No | |
DamageTypeDebugStr | string | Yes | |
DamageTypeExplosive | integer | No | |
DamageTypeHeal | integer | No | |
DamageTypePure | integer | No | |
DamageTypePureExplosive | integer | No | |
DamageTypeReduced | integer | No | |
DEFENSE_TYPE_DIVINE | integer | No | |
DEFENSE_TYPE_FORTIFIED | integer | No | |
DEFENSE_TYPE_HEAVY | integer | No | |
DEFENSE_TYPE_HERO | integer | No | |
DEFENSE_TYPE_LIGHT | integer | No | |
DEFENSE_TYPE_MEDIUM | integer | No | |
DEFENSE_TYPE_NORMAL | integer | No | |
DEFENSE_TYPE_UNARMORED | integer | No | |
DefenseTypeDebugStr | string | Yes | |
EnhancedDamageTarget | unit | No | |
EverlastingDawn_Bool | boolean | Yes | |
EverlastingDawn_UG | group | No | |
FrenziedAssault_Interval | integer | Yes | |
FrenziedAssault_Level | integer | Yes | |
FrenziedAssault_UG | group | No | |
ID | integer | No | |
IsDamageAttack | boolean | No | |
IsDamageCode | boolean | No | |
IsDamageMelee | boolean | No | |
IsDamageRanged | boolean | No | |
IsDamageSpell | boolean | No | |
LethalDamageEvent | real | No | |
LethalDamageHP | real | No | |
NextDamageIsAttack | boolean | No | |
NextDamageIsMelee | boolean | No | |
NextDamageIsRanged | boolean | No | |
NextDamageType | integer | No | |
NextDamageWeaponT | integer | No | |
OnDamageEvent | real | No | |
PreDamageEvent | real | No | |
RemoveDamageEvent | boolean | No | |
SourceDamageEvent | real | No | |
TargetPoint | location | No | |
TargetUnit | unit | No | |
TempBool | boolean | No | |
TempFX | effect | No | |
TempInteger | integer | No | |
TempInteger2 | integer | No | |
TempInterger3 | integer | No | |
TempItemType | itemcode | No | |
TempPoint | location | No | |
TempPoint2 | location | No | |
TempPoint3 | location | No | |
TempReal | real | No | |
TempReal2 | real | No | |
TempString | string | No | |
TempUnit | unit | No | |
TempUnitGroup | group | No | |
UNIT_CLASS_ANCIENT | integer | No | |
UNIT_CLASS_ATTACKS_FLYING | integer | No | |
UNIT_CLASS_ATTACKS_GROUND | integer | No | |
UNIT_CLASS_DEAD | integer | No | |
UNIT_CLASS_ETHEREAL | integer | No | |
UNIT_CLASS_FLYING | integer | No | |
UNIT_CLASS_GIANT | integer | No | |
UNIT_CLASS_GROUND | integer | No | |
UNIT_CLASS_HERO | integer | No | |
UNIT_CLASS_MAGIC_IMMUNE | integer | No | |
UNIT_CLASS_MECHANICAL | integer | No | |
UNIT_CLASS_MELEE | integer | No | |
UNIT_CLASS_PEON | integer | No | |
UNIT_CLASS_PLAGUED | integer | No | |
UNIT_CLASS_POISONED | integer | No | |
UNIT_CLASS_POLYMORPHED | integer | No | |
UNIT_CLASS_RANGED | integer | No | |
UNIT_CLASS_RESISTANT | integer | No | |
UNIT_CLASS_SAPPER | integer | No | |
UNIT_CLASS_SLEEPING | integer | No | |
UNIT_CLASS_SNARED | integer | No | |
UNIT_CLASS_STRUCTURE | integer | No | |
UNIT_CLASS_STUNNED | integer | No | |
UNIT_CLASS_SUMMONED | integer | No | |
UNIT_CLASS_TAUREN | integer | No | |
UNIT_CLASS_TOWNHALL | integer | No | |
UNIT_CLASS_UNDEAD | integer | No | |
WEAPON_TYPE_AM_CHOP | integer | No | |
WEAPON_TYPE_CH_SLICE | integer | No | |
WEAPON_TYPE_CL_SLICE | integer | No | |
WEAPON_TYPE_CM_SLICE | integer | No | |
WEAPON_TYPE_MH_BASH | integer | No | |
WEAPON_TYPE_MH_CHOP | integer | No | |
WEAPON_TYPE_MH_SLICE | integer | No | |
WEAPON_TYPE_MH_STAB | integer | No | |
WEAPON_TYPE_ML_CHOP | integer | No | |
WEAPON_TYPE_ML_SLICE | integer | No | |
WEAPON_TYPE_MM_BASH | integer | No | |
WEAPON_TYPE_MM_CHOP | integer | No | |
WEAPON_TYPE_MM_SLICE | integer | No | |
WEAPON_TYPE_MM_STAB | integer | No | |
WEAPON_TYPE_NONE | integer | No | |
WEAPON_TYPE_RH_BASH | integer | No | |
WEAPON_TYPE_WH_BASH | integer | No | |
WEAPON_TYPE_WH_SLICE | integer | No | |
WEAPON_TYPE_WL_BASH | integer | No | |
WEAPON_TYPE_WL_SLICE | integer | No | |
WEAPON_TYPE_WL_STAB | integer | No | |
WEAPON_TYPE_WM_BASH | integer | No | |
WEAPON_TYPE_WM_SLICE | integer | No | |
WEAPON_TYPE_WM_STAB | integer | No | |
WeaponTypeDebugStr | string | Yes | |
ZeroDamageEvent | real | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
library NeutralHeroes initializer Init
function RestrictHeroToOne takes integer heroid returns nothing
local integer i = 0
loop
call SetPlayerTechMaxAllowed(Player(i),heroid,1)
set i = i+1
exitwhen i == bj_MAX_PLAYERS
endloop
endfunction
private function Init takes nothing returns nothing
// Neutral Heroes
call RestrictHeroToOne('Hlgr') // Dark Knight
call RestrictHeroToOne('Naka') // Elder Sage
endfunction
endlibrary
library HumanSetup initializer Init requires CustomRaceSystem
private function StartMining takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
// autoharvestgold
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 0), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 1), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 2), 852021)
// autoharvestlumber
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 3), 852022)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 4), 852022)
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Human",RACE_HUMAN,1.0)
call c.setTownHall('htow') // Town Hall
call c.addWorkerType('hpea',c.NEAR_MINE,5) // Peasant
call c.addHeroType('Hpal') // Paladin
call c.addHeroType('Hamg') // Archmage
call c.addHeroType('Hmkg') // Mountain King
call c.addHeroType('Hblm') // Blood Mage
call c.setCallback(CustomRaceCall.StartMining)
call c.setAIScript("human.ai")
endfunction
endlibrary
library OrcSetup initializer Init requires CustomRaceSystem
private function StartMining takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
// autoharvestgold
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 0), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 1), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 2), 852021)
// autoharvestlumber
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 3), 852022)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 4), 852022)
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Orc",RACE_ORC,1.0)
call c.setTownHall('ogre') // Great Hall
call c.addWorkerType('opeo',c.NEAR_MINE,5) // Peon
call c.addHeroType('Obla') // Blademaster
call c.addHeroType('Ofar') // Far Seer
call c.addHeroType('Otch') // Tauren Chieftain
call c.addHeroType('Oshd') // Shadow Hunter
call c.setCallback(CustomRaceCall.StartMining)
call c.setAIScript("orc.ai")
endfunction
endlibrary
library UndeadSetup initializer Init requires CustomRaceSystem
private function WorkerHideToggle takes nothing returns nothing
call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
endfunction
private function StartWorking takes nothing returns nothing
if GetUnitTypeId(GetEnumUnit()) == 'uaco' then
call IssueImmediateOrderById(GetEnumUnit(), 852021)
elseif GetUnitTypeId(GetEnumUnit()) == 'ugho' then
call IssueImmediateOrderById(GetEnumUnit(), 852022)
endif
endfunction
private function HauntGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
call ForGroup(workers, function WorkerHideToggle)
call BlightGoldMineForPlayerBJ(goldmine, play)
call ForGroup(workers, function WorkerHideToggle)
call ForGroup(workers, function StartWorking)
call DestroyGroup(workers)
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Undead",RACE_UNDEAD,1.)
call c.setTownHall('unpl') // Necropolis
call c.addWorkerType('uaco',c.NEAR_MINE,3) // Acolyte
call c.addWorkerType('ugho',c.NEAR_HALL,1) // Ghoul
call c.addHeroType('Udea') // Death Knight
call c.addHeroType('Ulic') // Lich
call c.addHeroType('Udre') // Dreadlord
call c.addHeroType('Ucrl') // Crypt Lord
call c.setCallback(CustomRaceCall.HauntGoldMine)
call c.setAIScript("undead.ai")
endfunction
endlibrary
library NightElfSetup initializer Init requires CustomRaceSystem
private function callback takes nothing returns nothing
call IssueImmediateOrderById(GetEnumUnit(), 852021) // autoharvestgold
endfunction
private function EntangleGoldMine takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
local unit entangledMine
call SetUnitPosition(townhall,GetUnitX(goldmine),GetUnitY(goldmine))
call IssueTargetOrder(townhall, "entangleinstant", goldmine)
// call ForGroup(workers, function callback)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 0), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 1), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 2), 852021)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 3), 852022)
call IssueImmediateOrderById(BlzGroupUnitAt(workers, 4), 852022)
call DestroyGroup(workers)
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Night Elf",RACE_NIGHTELF,1.0)
call c.setTownHall('etol') // Tree of Life
call c.addWorkerType('ewsp',c.NEAR_MINE,5) // Wisp
call c.addHeroType('Ekee') // Keeper of the Grove
call c.addHeroType('Emoo') // Priestess of the Moon
call c.addHeroType('Edem') // Demon Hunter
call c.addHeroType('Ewar') // Warden
call c.setCallback(CustomRaceCall.EntangleGoldMine)
call c.setAIScript("elf.ai")
endfunction
endlibrary
library SandbornSetup initializer Init requires CustomRaceSystem, DelayedOrder, GetClosestWidget, IsDestructableTree
private function WorkerHideToggle takes nothing returns nothing
call ShowUnit(GetEnumUnit(),IsUnitHidden(GetEnumUnit()))
endfunction
//===========================================================================
// Replaces a gold mine with a sunlit gold mine for the given player.
//
function IlluminateGoldMineForPlayer takes player owner, unit goldmine returns unit
local real mineX
local real mineY
local integer mineGold
local unit newMine
// Make sure we're replacing a Gold Mine and not some other type of unit.
if GetUnitTypeId(goldmine) != 'ngol' then
return null
endif
// Save the Gold Mine's properties and remove it.
set mineX = GetUnitX(goldmine)
set mineY = GetUnitY(goldmine)
set mineGold = GetResourceAmount(goldmine)
call RemoveUnit(goldmine)
// Create a Haunted Gold Mine to replace the Gold Mine.
// set newMine = CreateBlightedGoldmine(owner, mineX, mineY, bj_UNIT_FACING)
set newMine = CreateUnit(owner, 'u007', mineX, mineY, bj_UNIT_FACING)
call SetResourceAmount(newMine, mineGold)
return newMine
endfunction
private function isTreeValid takes nothing returns boolean
return IsTreeAlive(GetFilterDestructable())
endfunction
function PlaceStartingUnits takes player owner, unit goldmine, unit townhall, real offset returns nothing
local real x = GetUnitX(goldmine)
local real y = GetUnitY(goldmine)
local real xHall = GetUnitX(townhall)
local real yHall = GetUnitY(townhall)
local real a = Atan2(yHall - y, xHall - x)
// Monolith 1 - Lumber
local unit u = CreateUnit(owner, 'h006', xHall, yHall, bj_UNIT_FACING)
local destructable tree = GetClosestDestructable(xHall, yHall, Filter(function isTreeValid))
if tree != null then
call DelayedOrderTarget.start(u, 852146, .1, tree) // order eattree since immolation activation seems really bugged
endif
// Monolith 2 - Gold Mine
set u = CreateUnit(owner, 'h006', xHall, yHall, bj_UNIT_FACING)
call IssueTargetOrderById(u, 851971, goldmine) // right click gold mine
// Stele
set x = xHall + Cos(a) * offset
set y = yHall + Sin(a) * offset
call CreateUnit(owner, 'h000', x, y, bj_UNIT_FACING)
set u = null
endfunction
private function IlluminateGoldMine takes player owner, group workers, unit goldmine, unit townhall, unit randhero returns nothing
local unit newMine
call ForGroup(workers, function WorkerHideToggle)
set newMine = IlluminateGoldMineForPlayer(owner, goldmine)
call PlaceStartingUnits(owner, newMine, townhall, 300.)
call ForGroup(workers, function WorkerHideToggle)
call DestroyGroup(workers)
set newMine = null
endfunction
private function Init takes nothing returns nothing
local CustomRace c = CustomRace.create("Sandborn",RACE_UNDEAD,.9)
call c.setTownHall('o000') // Pyramid of Light
// call c.addWorkerType('h006',c.NEAR_MINE,1) // Monolith
// call c.addWorkerType('h006',c.NEAR_HALL,1) // Monolith
call c.addHeroType('U001') // Ancient Apostle
call c.addHeroType('N006') // Grand Priestess
call c.addHeroType('U000') // Reaper of the Dust
call c.addHeroType('N000') // Sandborn Avatar
call c.setCallback(CustomRaceCall.IlluminateGoldMine)
call c.setAIScript("undead.ai")
endfunction
endlibrary
//==============================================================================
// Custom Race System by Archmage Owenalacaster (Note: with "CustomGetAllyKeyStructureCount" 1.30 fix)
//==============================================================================
//
// Purpose:
// - Creates the starting units for custom races and replaces the standard
// Melee Initialization trigger. Custom races are selected by race and
// handicap.
//
// Usage:
// - Register a new custom race with CustomRace.create(name, RACE, handicap)
// Handicaps: Valid handicap values are 1.0, 0.9, 0.8, 0.7, 0.6 and 0.5.
// - Register a new custom race for all handicaps of a single race with
// CustomRace.createAll(name, RACE)
// - Extend the registration of a race with c.register(RACE, handicap)
// - Set the townhall type with c.setTownHall(unitid)
// - Add a new worker type with c.addWorkerType(unitid, priority, qty)
// Priorities: c.NEAR_MINE spawns workers near the mine, where workers
// typically spawn.
// c.NEAR_HALL spawns workers near the town hall, where
// Ghouls spawn.
// - Add a random hero type with c.addHeroType(unitid)
// - Set the ai script used by computer players with c.setAIScript(stringpath)
// - Set a callback function with c.setCallback(CustomRaceCall.function)
// Callbacks: The callback is executed after all the starting units for a
// player are created, and its purpose is to provide enhanced
// initial behaviour for a race. A good example of this with the
// standard races would be the Undead Goldmine Haunting and
// Night Elves Goldmine Entangling.
// The callback function passes as arguments all the units
// generated in addition to the nearest goldmine detected.
// Please note that if a random hero is not created, the last
// argument will have a null value, so always do a check.
// - Get a player's custom race name string with GetPlayerCustomRaceName(player)
//
// Notes:
// - Supports a maximum of 24 custom races.
// - Maximum for worker and hero types are configurable.
//
// Requirements:
// - JassHelper version 0.9.E.0 or newer (older versions may still work).
//
// Installation:
// - Create a new trigger called CustomRaceSystem.
// - Convert it to custom text and replace all the code with this code.
//
// Special Thanks:
// - Alevice: He practically co-wrote the code.
// - cosmicat: His formula for circular unit formation.
// Co-developing the single-array registry.
//
//==============================================================================
library CustomRaceSystem initializer Init
//===========================================================================
// CONFIGURATION SECTION
//===========================================================================
globals
// Unit Type Constants
private constant integer MAX_WORKERTYPES = 4
private constant integer MAX_HEROTYPES = 4
endglobals
//===========================================================================
// END CONFIGURATION SECTION
//===========================================================================
function interface CustomRaceCall takes player play, group workers, unit goldmine, unit townhall, unit randhero returns nothing
private function r2S takes race r returns string
if r == RACE_HUMAN then
return "Human"
elseif r == RACE_ORC then
return "Orc"
elseif r == RACE_UNDEAD then
return "Undead"
elseif r == RACE_NIGHTELF then
return "Night Elf"
endif
return "Unknown"
endfunction
private function r2I takes race r returns integer
if r == RACE_HUMAN then
return 1
elseif r == RACE_ORC then
return 2
elseif r == RACE_UNDEAD then
return 3
elseif r == RACE_NIGHTELF then
return 4
endif
return 5
endfunction
globals
// Victory Defeat Variables
private string array KEY_STRUCTURE
private integer KEY_STRUCTURE_COUNT = 0
endglobals
//===========================================================================
// STRUCT DATA
//===========================================================================
private keyword createStartingUnits
struct CustomRace
string name
// Town Hall Variable
integer townhallType = 0
//string townhallName
// Town Hall name is not currently supported.
// Worker Variables
integer totalWorkerTypes = 0
integer array workerType[MAX_WORKERTYPES]
integer array workerPriority[MAX_WORKERTYPES]
integer array workerQty[MAX_WORKERTYPES]
// Random Hero Variables
integer totalHeroTypes = 0
integer array heroType[MAX_HEROTYPES]
// AI Script Directory String Variable
string aiscript = ""
// Callback Variable
private CustomRaceCall c
// Registry Variable
static integer array REGISTRY
// Spawn Priority Variables
static integer NEAR_MINE = 0
static integer NEAR_HALL = 1
static method get takes race r, real h returns CustomRace
return CustomRace(.REGISTRY[((r2I(r)-1)*6)+(10-R2I(h*10.))])
endmethod
method register takes race r, real h returns boolean
local CustomRace c = CustomRace.get(r,h)
if c != 0 then
debug call BJDebugMsg("|cffff0000Registration of "+.name+" failed due to conflict with "+c.name+" registered for "+r2S(r)+" race Handicap "+R2S(h))
return false
endif
set .REGISTRY[((r2I(r)-1)*6)+(10-R2I(h*10.))] = integer(this)
return true
endmethod
static method create takes string name, race r, real h returns CustomRace
local CustomRace c = CustomRace.allocate()
set c.name = name
if not c.register(r,h) then
call c.destroy()
return 0
endif
return c
endmethod
static method createAll takes string name, race r returns CustomRace
local CustomRace c = CustomRace.allocate()
set c.name = name
if not c.register(r,1.0) and not c.register(r,0.9) and not c.register(r,0.8) and not c.register(r,0.7) and not c.register(r,0.6) and not c.register(r,0.5) then
call c.destroy()
return 0
endif
return c
endmethod
method setTownHall takes integer hallid returns nothing
set .townhallType = hallid
set KEY_STRUCTURE[KEY_STRUCTURE_COUNT] = UnitId2String(hallid)
set KEY_STRUCTURE_COUNT = KEY_STRUCTURE_COUNT+1
endmethod
method addWorkerType takes integer workerid, integer priority, integer quantity returns nothing
set .workerType[.totalWorkerTypes] = workerid
set .workerPriority[.totalWorkerTypes] = priority
set .workerQty[.totalWorkerTypes] = quantity
set .totalWorkerTypes = .totalWorkerTypes+1
endmethod
method addHeroType takes integer heroid returns nothing
local integer i = 0
set .heroType[.totalHeroTypes] = heroid
set .totalHeroTypes = .totalHeroTypes+1
loop
call SetPlayerTechMaxAllowed(Player(i),heroid,1)
set i = i+1
exitwhen i == bj_MAX_PLAYERS
endloop
endmethod
private method getRandomHeroType takes nothing returns integer
local integer randomindex = GetRandomInt(0,.totalHeroTypes-1)
return .heroType[randomindex]
endmethod
method setAIScript takes string s returns nothing
set .aiscript = s
endmethod
method setCallback takes CustomRaceCall callb returns nothing
set .c = callb
endmethod
private method createRandomHero takes player p, location loc returns unit
local unit h = CreateUnitAtLoc(p, .getRandomHeroType(), loc, bj_UNIT_FACING)
if bj_meleeGrantHeroItems then
call MeleeGrantItemsToHero(h)
endif
return h
endmethod
method createStartingUnits takes player p returns nothing
local location startLoc = GetPlayerStartLocationLoc(p)
local location nearMineLoc = startLoc
local location nearTownLoc = startLoc
local location spawnLoc = startLoc
local location heroLoc = startLoc
local unit nearestMine = MeleeFindNearestMine(startLoc, bj_MELEE_MINE_SEARCH_RADIUS)
local unit myTownhall = null
local unit myRandHero = null
local group workerGroup = CreateGroup()
local integer workertypeindex = 0
local integer workerqty = 0
local integer spawnPriority = 0
if nearestMine != null then
set nearMineLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine),startLoc,320,0)
set nearTownLoc = MeleeGetProjectedLoc(startLoc,GetUnitLoc(nearestMine),288,0)
set heroLoc = MeleeGetProjectedLoc(GetUnitLoc(nearestMine),startLoc,384,45)
endif
set myTownhall = CreateUnitAtLoc(p,.townhallType,startLoc,bj_UNIT_FACING)
loop
exitwhen workertypeindex == .totalWorkerTypes
set spawnPriority = .workerPriority[workertypeindex]
if (spawnPriority==.NEAR_HALL) then
set spawnLoc = nearTownLoc
elseif(spawnPriority==.NEAR_MINE) then
set spawnLoc = nearMineLoc
endif
loop
call GroupAddUnit(workerGroup, CreateUnitAtLoc(p,.workerType[workertypeindex],PolarProjectionBJ(spawnLoc,65,(I2R(workerqty)*(360.00 / I2R(.workerQty[workertypeindex]))) + 90),bj_UNIT_FACING))
set workerqty = workerqty + 1
exitwhen workerqty >= .workerQty[workertypeindex]
endloop
call RemoveLocation(spawnLoc)
set workerqty = 0
set workertypeindex = workertypeindex+1
endloop
if (IsMapFlagSet(MAP_RANDOM_HERO) and .totalHeroTypes>0 ) then
set myRandHero = .createRandomHero(p,heroLoc)
else
call SetPlayerState(p,PLAYER_STATE_RESOURCE_HERO_TOKENS,bj_MELEE_STARTING_HERO_TOKENS)
endif
if(.c!=0) then
call .c.evaluate(p,workerGroup,nearestMine,myTownhall,myRandHero)
else
call DestroyGroup(workerGroup)
endif
if nearMineLoc != startLoc then
call RemoveLocation(nearMineLoc)
call RemoveLocation(nearTownLoc)
call RemoveLocation(heroLoc)
endif
call RemoveLocation(startLoc)
set startLoc = null
set nearMineLoc = null
set nearTownLoc = null
set spawnLoc = null
set heroLoc = null
set nearestMine = null
set myTownhall = null
set myRandHero = null
set workerGroup = null
endmethod
endstruct
globals
private string array PLAYER_RACE
endglobals
function GetPlayerCustomRaceName takes player p returns string
return PLAYER_RACE[GetPlayerId(p)]
endfunction
//===========================================================================
// UNIT CREATION SECTION
//===========================================================================
private function CreateStartingUnitsForAllPlayers takes nothing returns nothing
local integer index = 0
local player indexPlayer
local race playerRace
local CustomRace c
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set playerRace = GetPlayerRace(indexPlayer)
set c = CustomRace.get(playerRace,GetPlayerHandicap(indexPlayer)+0.01)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_USER or (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER and c.aiscript != "" )) and c != 0 then
set PLAYER_RACE[index] = c.name
call c.createStartingUnits(indexPlayer)
elseif playerRace == RACE_HUMAN then
call MeleeStartingUnitsHuman(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_ORC then
call MeleeStartingUnitsOrc(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_NIGHTELF then
call MeleeStartingUnitsNightElf(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
elseif playerRace == RACE_UNDEAD then
call MeleeStartingUnitsUndead(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
else
call MeleeStartingUnitsUnknownRace(indexPlayer,GetStartLocationLoc(GetPlayerStartLocation(indexPlayer)),true,true,true)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
endfunction
//===========================================================================
// CUSTOM MELEE AI SECTION
//===========================================================================
private function CustomMeleeStartingAI takes nothing returns nothing
local integer index = 0
local player indexPlayer
local race indexRace
local CustomRace c
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set indexRace = GetPlayerRace(indexPlayer)
set c = CustomRace.get(indexRace,GetPlayerHandicap(indexPlayer)+0.01)
call SetPlayerHandicap(indexPlayer,1.0)
if (GetPlayerController(indexPlayer) == MAP_CONTROL_COMPUTER) then
// Run a race-specific melee AI script.
if c != 0 and c.aiscript != "" then
call StartMeleeAI(indexPlayer, c.aiscript)
elseif (indexRace == RACE_HUMAN) then
call PickMeleeAI(indexPlayer, "human.ai", null, null)
elseif (indexRace == RACE_ORC) then
call PickMeleeAI(indexPlayer, "orc.ai", null, null)
elseif (indexRace == RACE_UNDEAD) then
call PickMeleeAI(indexPlayer, "undead.ai", null, null)
call RecycleGuardPosition(bj_ghoul[index])
elseif (indexRace == RACE_NIGHTELF) then
call PickMeleeAI(indexPlayer, "elf.ai", null, null)
else
// Unrecognized race.
endif
call ShareEverythingWithTeamAI(indexPlayer)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
endfunction
//===========================================================================
// VICTORY DEFEAT SECTION
//===========================================================================
private function CustomGetAllyKeyStructureCount takes player whichPlayer returns integer
local integer i = 0
local integer keyStructs = 0
local integer playerIndex = 0
local player indexPlayer
loop
set indexPlayer = Player(playerIndex)
if (PlayersAreCoAllied(whichPlayer, indexPlayer)) then
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "townhall", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "greathall", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "necropolis", true, true)
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, "treeoflife", true, true)
loop
set keyStructs = keyStructs + GetPlayerTypedUnitCount(indexPlayer, KEY_STRUCTURE[i], true, true)
set i = i+1
exitwhen i == KEY_STRUCTURE_COUNT
endloop
endif
set i = 0 //1.30 fix
set playerIndex = playerIndex + 1
exitwhen playerIndex == bj_MAX_PLAYERS
endloop
return keyStructs
endfunction
private function CustomPlayerIsCrippled takes player whichPlayer returns boolean
local integer allyStructures = MeleeGetAllyStructureCount(whichPlayer)
local integer allyKeyStructures = CustomGetAllyKeyStructureCount(whichPlayer)
return (allyStructures > 0) and (allyKeyStructures <= 0)
endfunction
private function CustomCheckForCrippledPlayers takes nothing returns nothing
local integer playerIndex
local player indexPlayer
local boolean isNowCrippled
call MeleeCheckForLosersAndVictors()
if bj_finishSoonAllExposed then
return
endif
set playerIndex = 0
loop
set indexPlayer = Player(playerIndex)
set isNowCrippled = CustomPlayerIsCrippled(indexPlayer)
if (not bj_playerIsCrippled[playerIndex] and isNowCrippled) then
set bj_playerIsCrippled[playerIndex] = true
call TimerStart(bj_crippledTimer[playerIndex], bj_MELEE_CRIPPLE_TIMEOUT, false, function MeleeCrippledPlayerTimeout)
if (GetLocalPlayer() == indexPlayer) then
call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], true)
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_WARNING_HUMAN"))
endif
elseif (bj_playerIsCrippled[playerIndex] and not isNowCrippled) then
set bj_playerIsCrippled[playerIndex] = false
call PauseTimer(bj_crippledTimer[playerIndex])
if (GetLocalPlayer() == indexPlayer) then
call TimerDialogDisplay(bj_crippledTimerWindows[playerIndex], false)
if (MeleeGetAllyStructureCount(indexPlayer) > 0) then
if (bj_playerIsExposed[playerIndex]) then
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNREVEALED"))
else
call DisplayTimedTextToPlayer(indexPlayer, 0, 0, bj_MELEE_CRIPPLE_MSG_DURATION, GetLocalizedString("CRIPPLE_UNCRIPPLED"))
endif
endif
endif
call MeleeExposePlayer(indexPlayer, false)
endif
set playerIndex = playerIndex + 1
exitwhen playerIndex == bj_MAX_PLAYERS
endloop
endfunction
private function CustomInitVictoryDefeat takes nothing returns nothing
local trigger checker = CreateTrigger()
local trigger trig
local integer index
local player indexPlayer
set bj_finishSoonTimerDialog = CreateTimerDialog(null)
call TriggerAddAction(checker, function CustomCheckForCrippledPlayers)
set trig = CreateTrigger()
call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_SOON)
call TriggerAddAction(trig, function MeleeTriggerTournamentFinishSoon)
set trig = CreateTrigger()
call TriggerRegisterGameEvent(trig, EVENT_GAME_TOURNAMENT_FINISH_NOW)
call TriggerAddAction(trig, function MeleeTriggerTournamentFinishNow)
set index = 0
loop
set indexPlayer = Player(index)
if (GetPlayerSlotState(indexPlayer) == PLAYER_SLOT_STATE_PLAYING) then
set bj_meleeDefeated[index] = false
set bj_meleeVictoried[index] = false
set bj_playerIsCrippled[index] = false
set bj_playerIsExposed[index] = false
set bj_crippledTimer[index] = CreateTimer()
set bj_crippledTimerWindows[index] = CreateTimerDialog(bj_crippledTimer[index])
call TimerDialogSetTitle(bj_crippledTimerWindows[index], MeleeGetCrippledTimerMessage(indexPlayer))
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, null)
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_DEATH, null)
call TriggerRegisterPlayerUnitEvent(checker, indexPlayer, EVENT_PLAYER_UNIT_CONSTRUCT_START, null)
call TriggerRegisterPlayerAllianceChange(checker, indexPlayer, ALLIANCE_PASSIVE)
call TriggerRegisterPlayerStateEvent(checker, indexPlayer, PLAYER_STATE_ALLIED_VICTORY, EQUAL, 1)
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_DEFEAT)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerDefeated)
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
else
set bj_meleeDefeated[index] = true
set bj_meleeVictoried[index] = false
if (IsPlayerObserver(indexPlayer)) then
set trig = CreateTrigger()
call TriggerRegisterPlayerEvent(trig, indexPlayer, EVENT_PLAYER_LEAVE)
call TriggerAddAction(trig, function MeleeTriggerActionPlayerLeft)
endif
endif
set index = index + 1
exitwhen index == bj_MAX_PLAYERS
endloop
call TimerStart(CreateTimer(), 2.0, false, function CustomCheckForCrippledPlayers)
endfunction
private function TimerAction takes nothing returns nothing
call SetFloatGameState(GAME_STATE_TIME_OF_DAY, bj_MELEE_STARTING_TOD)
call MeleeStartingHeroLimit()
call MeleeGrantHeroItems()
call MeleeStartingResources()
call MeleeClearExcessUnits()
call CreateStartingUnitsForAllPlayers()
call CustomMeleeStartingAI()
call CustomInitVictoryDefeat()
call DestroyTimer(GetExpiredTimer())
endfunction
private function Init takes nothing returns nothing
call TimerStart(CreateTimer(),0,false,function TimerAction)
endfunction
endlibrary
library TerrainInfection/* v2.1 By IcemanBo
TerrainInfection will start at given coordinates and infect nearby tiles. (change terrain type)
TerrainInfection also allows to undo the infection.
Furthermore it provides events for(un-)infection.
EDIT by Spellbound to ignore blight and water.
*/ requires /*
*/ TileDefinition /* hiveworkshop.com/forums/submissions-414/snippet-tiledefinition-259347/
*/ WorldBounds /* github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
*/ List /* as Nestharus removed it check out the demo
*/ Table /* hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*/ PathingLib /*
API
¯¯¯¯¯¯
function CreateTerrainInfection takes integer t, real x, real y, real chance, real interval returns TerrainInfection
t = infected terrain type
x = x of start
y = y of start
chance = chance to infect nearby tiles between 0 and 1
interval = interval to potentialy infect nearby tiles
you also can set/read public members:
real maxDistance = max distance to RootInfection
real chance = chance on infection between 0 and 1
real maxX = maxX of Infection
real minX = minX of Infection
real maxY = maxY of Infection
real minY = minY of Infection
region bound = bound region for infection
boolean diagonal = should Infection act diagonal
boolean enabled = is Infection currently enabled
you can read readonly members:
real x = x of infectionStart
real y = y of infectionStart
integer size = amount of infected tiles
integer terrainTypeInfected = terrain type of infection
methods (TerrainInfection)
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
destroy() returns nothing
undoInfection() returns nothing
Will un-infect all tiles.
undoInfection_last() returns boolean
Will un-infect the last tile.
Returns false if there is no tile anymore.
static methods (TerrainInfection)
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
enableResistor(integer Infector, integer Resistor, boolean b returns nothing
Takes terrain types as paramater.
Resistor will be immune against Infector. (true/false)
enableInfections(boolean b) returns nothing
Enables/disables all current running Infections.
isTileInfected takes real x, real y returns boolean
Events
¯¯¯¯¯¯¯¯
You can use TriggerRegisterVariableEvent to catch following events:
s__TerrainInfection_infection_state == 1 -> onInfection
s__TerrainInfection_infection_state == 2 -> Infection has completly finished.
s__TerrainInfection_infection_state == 3 -> onUnInfection
s__TerrainInfection_infection_state == 4 -> Infection has been completly removed.
onEvents you can use TerrainInfection_currrent as current instance.
onEvents (1 and 3) you can use TerrainInfection_currentTileId to refer to operated tile.
Look into "Event Example" for more info.
Credits:
¯¯¯¯¯¯¯¯¯
List (Nestharus)
Table (Bribe)
WorldBounds (Nestharus)
*/
globals
private hashtable hash = InitHashtable()
private timer tim = CreateTimer()
private constant real TIMEOUT = .031250000
private constant real TILE_SIDE_LENGTH = 128.
private constant real TILE_DIAGONAL_LENGTH = TILE_SIDE_LENGTH*SquareRoot(2.)
endglobals
private struct InfectionData extends array
implement List
integer tileId
integer originTerrainType
integer stacks
endstruct
struct TerrainInfection
private InfectionData list
private real timeCountMax // Interval of Infecion.
private real timeCountCurrent // Time left until next Infection.
readonly real x // x of RootInfection
readonly real y // y of RootInfection
readonly integer size // size == amount of infected tiles
readonly integer terrainTypeInfected // new (infected) terrain type.
// public members
real maxDistance = -1
real chance = 1
real maxX = WorldBounds.maxX
real minX = WorldBounds.minX
real maxY = WorldBounds.maxY
real minY = WorldBounds.minY
region bound = null
boolean diagonal = true
boolean enabled = true
// for list
private thistype prev
private thistype next
private static thistype first = 0
// for events
static real infection_state = 0
static thistype current = 0
static integer currentTileId = 0
// to save if a tile is infected
private static key k
private static Table table = k
/*
* These methods check if a point matches all conditions,
* so it can be infected successfully.
*/
private method coordinateCheck takes real x, real y returns boolean
return (bound == null or IsPointInRegion(bound, x, y)) and(x <= maxX and x >= minX and y <= maxY and y >= minY)
endmethod
private method distanceCheck takes real x, real y returns boolean
return (maxDistance < 0 or SquareRoot((x-this.x)*(x-this.x) + (y-this.y)*(y-this.y)) <= maxDistance)
endmethod
private method terrainCheck takes real x, real y returns boolean
return (GetTerrainType(x, y) != terrainTypeInfected) and not(LoadBoolean(hash, terrainTypeInfected, GetTerrainType(x, y))) and not(IsPointBlighted(x, y)) and not(IsTerrainSwimmable(x, y))
endmethod
private method isPointValid takes real x, real y returns boolean
return coordinateCheck(x,y) and distanceCheck(x,y) and terrainCheck(x,y)
endmethod
/*
* This method will make a circular check around given
* coordinates to find a terrain tile which is not
* infected yet. It will return boolean, if there exists
* a (new/old) tile around for potential infection.
*/
private method checkNearbyTiles takes real x, real y returns boolean
local real angle = 0
local real angleChange
local real xNew
local real yNew
local real offset
local boolean diagonalCheck = true
local InfectionData element
if (this.diagonal) then
set angleChange = bj_PI/4
else
set angleChange = bj_PI/2
endif
loop
if (this.diagonal) then
// For 45/135/225/315 degrees the centre
// of the tile has an other distance, so
// we check if it's a digonal.
set diagonalCheck = not(diagonalCheck)
if (diagonalCheck) then
set offset = TILE_DIAGONAL_LENGTH
else
set offset = TILE_SIDE_LENGTH
endif
else
set offset = TILE_SIDE_LENGTH
endif
set xNew = x + offset * Cos(angle)
set yNew = y + offset * Sin(angle)
if (this.isPointValid(xNew, yNew)) then
if (GetRandomReal(0, 1) <= this.chance) then
set element = this.list.enqueue()
set element.tileId = GetTileId(xNew,yNew)
set element.originTerrainType = GetTerrainType(xNew, yNew)
call SetTerrainType(xNew, yNew, this.terrainTypeInfected, -1, 1, 1)
set thistype.table.boolean[element.tileId] = true
set this.size = (this.size + 1)
// Fire onInfection event.
set thistype.current = this
set thistype.currentTileId = element.tileId
set thistype.infection_state = 1
set thistype.infection_state = 0
set thistype.current = 0
set thistype.currentTileId = 0
endif
return true
endif
set angle = angle + angleChange
exitwhen (angle >= 2*bj_PI) // Full circular check was done.
endloop
return false
endmethod
// callback is called periodicly
private static method callback takes nothing returns nothing
local thistype this = thistype.first
local InfectionData element
local integer i
local boolean b // Will check if infection is completly finished.
// loop through all instances (root infections)
loop
exitwhen (this == 0)
if (this.enabled) then
set this.timeCountCurrent = this.timeCountCurrent - TIMEOUT
if (this.timeCountCurrent <= 0) then
set this.timeCountCurrent = this.timeCountMax
set element = this.list.first
set i = this.size
set b = false
// We use pre-loop defined integer to define
// the cancel condition, because the list itself
// is dynamic, not static. (size can be changed during loop)
// loop through all infected tiles of an instance
loop
exitwhen (i < 1)
if this.checkNearbyTiles(GetTileCenterXById(element.tileId), GetTileCenterYById(element.tileId)) then
set b = true
endif
set element = element.next
set i = (i - 1)
endloop
// if there is no potential tile left for infection
if not(b) then
// Fire onFinish event.
set thistype.current = this
set thistype.infection_state = 2
set thistype.infection_state = 0
set thistype.current = 0
endif
endif
endif
set this = this.next
endloop
endmethod
static method create takes integer t, real x, real y, real chance, real interval returns thistype
local thistype this = thistype.allocate()
local InfectionData element
set this.list = InfectionData.create()
set element = this.list.enqueue()
set element.tileId = GetTileId(x,y)
set element.originTerrainType = GetTerrainType(x, y)
set this.x = x
set this.y = y
set this.terrainTypeInfected = t
set this.chance = chance
set this. size = 1
if (interval < TIMEOUT) then
set this.timeCountMax = TIMEOUT
else
set this.timeCountMax = interval
endif
set this.timeCountCurrent = this.timeCountMax
call SetTerrainType(x, y, t, -1, 1, 1)
set thistype.table.boolean[element.tileId] = true
// Fire onInfection event.
set thistype.current = this
set thistype.currentTileId = element.tileId
set thistype.infection_state = 1
set thistype.infection_state = 0
set thistype.current = 0
set thistype.currentTileId = 0
if (thistype.first == 0) then
call TimerStart(tim, TIMEOUT, true, function thistype.callback)
endif
set this.next = thistype.first
set thistype.first.prev = this
set thistype.first = this
set this.prev = 0
return this
endmethod
method destroy takes nothing returns nothing
set this.bound = null
call this.deallocate()
call this.list.destroy()
if (this == thistype.first) then
set thistype.first = this.next
endif
set this.next.prev = this.prev
set this.prev.next = this.next
if (thistype.first == 0) then
call PauseTimer(tim)
endif
endmethod
method undoInfection_last takes nothing returns boolean
local InfectionData element = this.list.last
local integer tileId = element.tileId
if (element == 0) then
// Fire onRemoved event.
set thistype.current = this
set thistype.infection_state = 4
set thistype.infection_state = 0
set thistype.current = 0
return false
endif
call SetTerrainType(GetTileCenterXById(tileId), GetTileCenterYById(tileId), element.originTerrainType, -1, 1, 1)
set this.size = this.size - 1
call thistype.table.remove(tileId)
call element.remove()
// Fire onUnInfection event.
set thistype.current = this
set thistype.currentTileId = tileId
set thistype.infection_state = 3
set thistype.infection_state = 0
set thistype.current = 0
set thistype.currentTileId = 0
return true
endmethod
// loop until all infected tiles are removed
method undoInfection takes nothing returns nothing
loop
exitwhen not (this.undoInfection_last())
endloop
endmethod
static method isTileInfected takes real x, real y returns boolean
return thistype.table.has(GetTileId(x,y))
endmethod
static method enableResistor takes integer infector, integer resistor, boolean b returns nothing
call SaveBoolean(hash, infector, resistor, b)
endmethod
static method enableInfections takes boolean b returns nothing
if (b) then
call TimerStart(tim, TIMEOUT, true, function thistype.callback)
else
call PauseTimer(tim)
endif
endmethod
endstruct
function CreateTerrainInfection takes integer t, real x, real y, real chance, real interval returns TerrainInfection
return TerrainInfection.create(t, x, y, chance, interval)
endfunction
endlibrary
library TileDefinition /* v1.2b By IcemanBo - Credits to WaterKnight
*/ requires /*
*/ WorldBounds /* github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
**
**
** Information
** _____________
**
** TileDefinition provides an API to give information about a terrain tile.
**
**
** API
** _______
**
**
**
** function GetTileCenterCoordinate takes real a returns real
** Returns the cooridnate for the center of the tile.
** Works for x- and y coordiantes.
**
**
** function GetTileMax takes real a returns real
** Returns the max value, that is still in same terrain tile.
** Works for x- and y coordiantes.
**
**
** function GetTileMin takes real a returns real
** Returns the min value, that is still in same terrain tile.
** Works for x- and y coordiantes.
**
**
** function AreCoordinatesInSameTile takes real a, real b returns boolean
** Checks if two coordinates share the same terrain tile.
**
** Attention: Only makes sense if both coordinates are of same type. Or x- or y coordinates.
** May bring wrong result, if you compare x with y coordinates.
**
**
** function ArePointsInSameTile takes real x1, real y1, real x2, real y2 returns boolean
** Checks if two points share the same terrain tile.
**
**
** funtion GetTileId takes real x, real y returns integer
** Returns an unique index for tile of given coordinates.
** Will return "-1" if it's invalid.
**
** function GetTileCenterXById takes integer id returns real
**
** function GetTileCenterYById takes integer id returns real
**
**
***********************************************************************************************************/
globals
private integer WorldTilesX
private integer WorldTilesY
endglobals
function GetTileCenterCoordinate takes real a returns real
if (a >= 0.) then
return R2I((a/128) + .5)*128.
else
return R2I((a/128) - .5)*128.
endif
endfunction
function AreCoordinatesInSameTile takes real a, real b returns boolean
return GetTileCenterCoordinate(a) == GetTileCenterCoordinate(b)
endfunction
function AreLocationsInSameTile takes real x1, real y1, real x2, real y2 returns boolean
return AreCoordinatesInSameTile(x1, x2) and AreCoordinatesInSameTile(y1, y2)
endfunction
function GetTileMin takes real a returns real
return GetTileCenterCoordinate(a) - 64.
endfunction
function GetTileMax takes real a returns real
return GetTileCenterCoordinate(a) + 64.
endfunction
function GetTileId takes real x, real y returns integer
local integer xI = R2I(x - WorldBounds.minX + 64) / 128
local integer yI = R2I(y - WorldBounds.minY + 64) / 128
if ((xI < 0) or (xI >= WorldTilesX) or (yI < 0) or (yI >= WorldTilesY)) then
return -1
endif
return (yI * WorldTilesX + xI)
endfunction
function GetTileCenterXById takes integer id returns real
if ((id < 0) or (id >= WorldTilesX * WorldTilesY)) then
return 0.
endif
return (WorldBounds.minX + ModuloInteger(id, WorldTilesX) * 128.)
endfunction
function GetTileCenterYById takes integer id returns real
if ((id < 0) or (id >= WorldTilesX * WorldTilesY)) then
return 0.
endif
return (WorldBounds.minY + id / WorldTilesX * 128.)
endfunction
private module Init
private static method onInit takes nothing returns nothing
set WorldTilesX = R2I(WorldBounds.maxX - WorldBounds.minX) / 128 + 1
set WorldTilesY = R2I(WorldBounds.maxY - WorldBounds.minY) / 128 + 1
endmethod
endmodule
private struct TileDefinition extends array
implement Init
endstruct
endlibrary
library ErrorMessage /* v1.0.2.0
*************************************************************************************
*
* Issue Compliant Error Messages
*
************************************************************************************
*
* function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
* - In the event of an error the game will be permanently paused
*
* function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
*
************************************************************************************/
private struct Fields extends array
static constant string COLOR_RED = "|cffff0000"
static constant string COLOR_YELLOW = "|cffffff00"
static string lastError = null
endstruct
private function Pause takes nothing returns nothing
call PauseGame(true)
endfunction
private function ThrowMessage takes string libraryName, string functionName, string objectName, integer objectInstance, string description, string errorType, string color returns nothing
local string str
local string color_braces = "|cff66FF99"
local string orange = "|cffff6600"
set str = "->\n-> " + color_braces + "{|r " + "Library" + color_braces + "(" + orange + libraryName + color_braces + ")"
if (objectName != null) then
if (objectInstance != 0) then
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + " (|rinstance = " + orange + I2S(objectInstance) + color_braces + ") )" + "|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
else
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + ")|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
else
set str = str + "|r." + "Function" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
set str = str + color_braces + " }|r " + "has thrown an exception of type " + color_braces + "(" + color + errorType + color_braces + ")|r."
set Fields.lastError = str + "\n->\n" + "-> " + color + description + "|r\n->"
endfunction
function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Error", Fields.COLOR_RED)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
call TimerStart(CreateTimer(), 0, true, function Pause)
set objectInstance = 1/0
endif
endfunction
function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Warning", Fields.COLOR_YELLOW)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
set Fields.lastError = null
endif
endfunction
endlibrary
library List /* v1.0.0.3
************************************************************************************
*
* */uses/*
*
* */ ErrorMessage /* hiveworkshop.com/forums/submissions-414/snippet-error-message-239210/
*
************************************************************************************
*
* module List
*
* Description
* -------------------------
*
* NA
*
* Fields
* -------------------------
*
* readonly static integer sentinel
*
* readonly thistype list
*
* readonly thistype first
* readonly thistype last
*
* readonly thistype next
* readonly thistype prev
*
* Methods
* -------------------------
*
* static method create takes nothing returns thistype
* method destroy takes nothing returns nothing
* - May only destroy lists
*
* method push takes nothing returns thistype
* method enqueue takes nothing returns thistype
*
* method pop takes nothing returns nothing
* method dequeue takes nothing returns nothing
*
* method remove takes nothing returns nothing
*
* method clear takes nothing returns nothing
*
* debug static method calculateMemoryUsage takes nothing returns integer
* debug static method getAllocatedMemoryAsString takes nothing returns string
*
************************************************************************************/
module List
private static thistype collectionCount = 0
private static thistype nodeCount = 0
debug private boolean isNode
debug private boolean isCollection
private thistype _list
method operator list takes nothing returns thistype
debug call ThrowError(this == 0, "List", "list", "thistype", this, "Attempted To Read Null Node.")
debug call ThrowError(not isNode, "List", "list", "thistype", this, "Attempted To Read Invalid Node.")
return _list
endmethod
private thistype _next
method operator next takes nothing returns thistype
debug call ThrowError(this == 0, "List", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "List", "next", "thistype", this, "Attempted To Read Invalid Node.")
return _next
endmethod
private thistype _prev
method operator prev takes nothing returns thistype
debug call ThrowError(this == 0, "List", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "List", "prev", "thistype", this, "Attempted To Read Invalid Node.")
return _prev
endmethod
private thistype _first
method operator first takes nothing returns thistype
debug call ThrowError(this == 0, "List", "first", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "List", "first", "thistype", this, "Attempted To Read Invalid List.")
return _first
endmethod
private thistype _last
method operator last takes nothing returns thistype
debug call ThrowError(this == 0, "List", "last", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "List", "last", "thistype", this, "Attempted To Read Invalid List.")
return _last
endmethod
static method operator sentinel takes nothing returns integer
return 0
endmethod
private static method allocateCollection takes nothing returns thistype
local thistype this = thistype(0)._first
if (0 == this) then
debug call ThrowError(collectionCount == 8191, "List", "allocateCollection", "thistype", 0, "Overflow.")
set this = collectionCount + 1
set collectionCount = this
else
set thistype(0)._first = _first
endif
return this
endmethod
private static method allocateNode takes nothing returns thistype
local thistype this = thistype(0)._next
if (0 == this) then
debug call ThrowError(nodeCount == 8191, "List", "allocateNode", "thistype", 0, "Overflow.")
set this = nodeCount + 1
set nodeCount = this
else
set thistype(0)._next = _next
endif
return this
endmethod
static method create takes nothing returns thistype
local thistype this = allocateCollection()
debug set isCollection = true
set _first = 0
return this
endmethod
method push takes nothing returns thistype
local thistype node = allocateNode()
debug call ThrowError(this == 0, "List", "push", "thistype", this, "Attempted To Push On To Null List.")
debug call ThrowError(not isCollection, "List", "push", "thistype", this, "Attempted To Push On To Invalid List.")
debug set node.isNode = true
set node._list = this
if (_first == 0) then
set _first = node
set _last = node
set node._next = 0
else
set _first._prev = node
set node._next = _first
set _first = node
endif
set node._prev = 0
return node
endmethod
method enqueue takes nothing returns thistype
local thistype node = allocateNode()
debug call ThrowError(this == 0, "List", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
debug call ThrowError(not isCollection, "List", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
debug set node.isNode = true
set node._list = this
if (_first == 0) then
set _first = node
set _last = node
set node._prev = 0
else
set _last._next = node
set node._prev = _last
set _last = node
endif
set node._next = 0
return node
endmethod
method pop takes nothing returns nothing
local thistype node = _first
debug call ThrowError(this == 0, "List", "pop", "thistype", this, "Attempted To Pop Null List.")
debug call ThrowError(not isCollection, "List", "pop", "thistype", this, "Attempted To Pop Invalid List.")
debug call ThrowError(node == 0, "List", "pop", "thistype", this, "Attempted To Pop Empty List.")
debug set node.isNode = false
set _first._list = 0
set _first = _first._next
if (_first == 0) then
set _last = 0
else
set _first._prev = 0
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
method dequeue takes nothing returns nothing
local thistype node = _last
debug call ThrowError(this == 0, "List", "dequeue", "thistype", this, "Attempted To Dequeue Null List.")
debug call ThrowError(not isCollection, "List", "dequeue", "thistype", this, "Attempted To Dequeue Invalid List.")
debug call ThrowError(node == 0, "List", "dequeue", "thistype", this, "Attempted To Dequeue Empty List.")
debug set node.isNode = false
set _last._list = 0
set _last = _last._prev
if (_last == 0) then
set _first = 0
else
set _last._next = 0
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
method remove takes nothing returns nothing
local thistype node = this
set this = node._list
debug call ThrowError(node == 0, "List", "remove", "thistype", this, "Attempted To Remove Null Node.")
debug call ThrowError(not node.isNode, "List", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
debug set node.isNode = false
set node._list = 0
if (0 == node._prev) then
set _first = node._next
else
set node._prev._next = node._next
endif
if (0 == node._next) then
set _last = node._prev
else
set node._next._prev = node._prev
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
method clear takes nothing returns nothing
debug local thistype node = _first
debug call ThrowError(this == 0, "List", "clear", "thistype", this, "Attempted To Clear Null List.")
debug call ThrowError(not isCollection, "List", "clear", "thistype", this, "Attempted To Clear Invalid List.")
static if DEBUG_MODE then
loop
exitwhen node == 0
set node.isNode = false
set node = node._next
endloop
endif
if (_first == 0) then
return
endif
set _last._next = thistype(0)._next
set thistype(0)._next = _first
set _first = 0
set _last = 0
endmethod
method destroy takes nothing returns nothing
debug call ThrowError(this == 0, "List", "destroy", "thistype", this, "Attempted To Destroy Null List.")
debug call ThrowError(not isCollection, "List", "destroy", "thistype", this, "Attempted To Destroy Invalid List.")
static if DEBUG_MODE then
debug call clear()
debug set isCollection = false
else
if (_first != 0) then
set _last._next = thistype(0)._next
set thistype(0)._next = _first
set _last = 0
endif
endif
set _first = thistype(0)._first
set thistype(0)._first = this
endmethod
static if DEBUG_MODE then
static method calculateMemoryUsage takes nothing returns integer
local thistype start = 1
local thistype end = 8191
local integer count = 0
loop
exitwhen integer(start) > integer(end)
if (integer(start) + 500 > integer(end)) then
return count + checkRegion(start, end)
else
set count = count + checkRegion(start, start + 500)
set start = start + 501
endif
endloop
return count
endmethod
private static method checkRegion takes thistype start, thistype end returns integer
local integer count = 0
loop
exitwhen integer(start) > integer(end)
if (start.isNode) then
set count = count + 1
endif
if (start.isCollection) then
set count = count + 1
endif
set start = start + 1
endloop
return count
endmethod
static method getAllocatedMemoryAsString takes nothing returns string
local thistype start = 1
local thistype end = 8191
local string memory = null
loop
exitwhen integer(start) > integer(end)
if (integer(start) + 500 > integer(end)) then
if (memory != null) then
set memory = memory + ", "
endif
set memory = memory + checkRegion2(start, end)
set start = end + 1
else
if (memory != null) then
set memory = memory + ", "
endif
set memory = memory + checkRegion2(start, start + 500)
set start = start + 501
endif
endloop
return memory
endmethod
private static method checkRegion2 takes thistype start, thistype end returns string
local string memory = null
loop
exitwhen integer(start) > integer(end)
if (start.isNode) then
if (memory == null) then
set memory = I2S(start)
else
set memory = memory + ", " + I2S(start) + "N"
endif
endif
if (start.isCollection) then
if (memory == null) then
set memory = I2S(start)
else
set memory = memory + ", " + I2S(start) + "C"
endif
endif
set start = start + 1
endloop
return memory
endmethod
endif
endmodule
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library LastOrder initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* LastOrder is a library that was designed to allow interfacing with the last
//* N orders any unit on your map has received. This library was also designed
//* to be used as a means to reissue lost orders to a unit either after
//* preventing a spell cast or in many other situations.
//*
//* There are two configuration constants for you to play with in using this
//* script. ORDERS_TO_HOLD is basically the size of the game's memory for each
//* individual unit. The larger the number is, the further back you can retrace
//* a unit's order list. Setting this value to 3 suffices for all actual
//* mechanics purposes. Raise it only if you have a specific application where
//* you need more. Lowering it to 2 covers most cases, but may miss a few, and
//* lowering it to 1 prevents you from adequately canceling spells and properly
//* reissuing previous orders. I recommend leaving it alone at 3. The MAX_ORDERS
//* constant is the number of orders that the system holds over all units at a
//* given time. If you are worried about running out, go ahead and increase it,
//* but be aware that it will use big arrays (which are slower) if you use a
//* number over 8191. Don't lower it under 8191, there's no reason to. Don't
//* change the non-private constants, there's no reason to.
//*
//* function GetPastOrder takes unit u, integer whichOrder returns order
//* function GetPastOrderId takes unit u, integer whichOrder returns integer
//* function GetPastOrderString takes unit u, integer whichOrder returns string
//* function GetPastOrderType takes unit u, integer whichOrder returns integer
//* function GetPastOrderX takes unit u, integer whichOrder returns real
//* function GetPastOrderY takes unit u, integer whichOrder returns real
//* function GetPastOrderTarget takes unit u, integer whichOrder returns widget
//*
//* The above API is the main list of functions the user can use to interface
//* with past orders. If you want the immediate last order for a player, use 1
//* for the whichOrder integer. The 'order' returned by GetPastOrder is a struct
//* that contains all information of an issued order. If you want to interface
//* directly with the struct and skip all of the interfacing wrappers, you have
//* access to them via the following commands:
//*
//* [unit] .u The unit being ordered.
//* [integer] .id The order id of the past order.
//* [integer] .typ The type of the past order. (See constants)
//* [boolean] .fin A flag indicating whether the order was finished.
//* [widget] .tar The target widget of the past order.
//* [real] .x The x coordinate of the target point of the order.
//* [real] .y The y coordinate of the target point of the order.
//*
//* There is also a sizable API for backwards compatibility with older versions
//* of the library. This API assumes that you're talking about a specific past
//* order, either the lastmost order or the second lastmost. In most cases,
//* these are still useful because they remove an argument from the call.
//*
//* function GetLastOrder takes unit u returns order
//* function GetLastOrderId takes unit u returns integer
//* function GetLastOrderString takes unit u returns string
//* function GetLastOrderType takes unit u returns integer
//* function GetLastOrderX takes unit u returns real
//* function GetLastOrderY takes unit u returns real
//* function GetLastOrderTarget takes unit u returns widget
//* function IsLastOrderFinished takes unit u returns boolean
//*
//* Besides being able to get information about all of the past orders a unit
//* has been issued, the most useful part of this system is actually reissuing
//* those orders as a means to fix lost orders or intercept and prevent spell
//* casting. The following API is then available to the user:
//*
//* function IssuePastOrder takes unit u, integer whichOrder returns boolean
//* function IssueLastOrder takes unit u returns boolean
//* function IssueSecondLastOrder takes unit u returns boolean
//* function AbortOrder takes unit u returns boolean
//*
//* If you want to reissue a past order for a given unit, IssuePastOrder is the
//* function that you'll want to use on a unit. To issue the last or second last
//* orders, there are functions for those as well. (They mostly exist for
//* backwards compatibility) AbortOrder is a means for anyone using this script
//* to stop a unit from running any given order that they happen to have at any
//* moment. AbortOrder is used in conjunction with a supplementary library,
//* AbortSpell, to seamlessly replicate WC3's spell error messages.
//*
//* function IssueArbitraryOrder takes unit u, order o returns boolean
//*
//* IssueArbitraryOrder is special in that it ignores many of the normal checks
//* and automatically forces a unit to take on a specific order. This can be
//* convenient if you want to make one unit take on another unit's order, for
//* instance. Be forewarned that this overwrites whatever the unit was doing and
//* issues the order no matter what, so use only as needed.
//*
//* If you have any further questions regarding LastOrder or how to use it, feel
//* free to visit [url]www.wc3c.net[/url] and ask questions there. This library should only
//* ever be released at WC3C and at no other site. Please give credits if this
//* library finds its way into your maps, and otherwise thanks for reading!
//*
//* Enjoy!
//*
globals
//Order type variables
constant integer ORDER_TYPE_TARGET = 1
constant integer ORDER_TYPE_POINT = 2
constant integer ORDER_TYPE_IMMEDIATE = 3
//System constants
private constant integer ORDERS_TO_HOLD = 3 //How many past orders the
//system holds for each unit.
//Should be at least 2.
private constant integer MAX_ORDERS = 8191 //The max number of orders
//the system can maintain.
//Going over 8191 uses big
//arrays, which are slower
//than normal arrays.
endglobals
globals
private hashtable ht = InitHashtable()
endglobals
struct order[MAX_ORDERS]
unit u
integer id
integer typ
boolean fin
widget tar
real x
real y
static method create takes unit ordered, integer ordid, integer ordtyp, widget target, real ordx, real ordy returns order
local order o = order.allocate()
local integer i = ORDERS_TO_HOLD
local integer hid = GetHandleId(ordered)
set o.u = ordered
set o.id = ordid
set o.typ = ordtyp
set o.fin = false
set o.tar = target
set o.x = ordx
set o.y = ordy
//Handle stored orders in the hashtable
loop
//We hold up to the constant ORDERS_TO_HOLD in the table
exitwhen i == 1
//Moves the N-1th order to the Nth slot, etc. except storing new last order
if HaveSavedInteger(ht, hid, i-1) then
if i == ORDERS_TO_HOLD and HaveSavedInteger(ht, hid, i) then
//Destroy lastmost order struct
call order.destroy(order(LoadInteger(ht, hid, i)))
endif
//Can only do this if the N-1th order exists
call SaveInteger(ht, hid, i, LoadInteger(ht, hid, i-1))
endif
set i = i - 1
endloop
//Store the new order to the hashtable as 1th last order
call SaveInteger(ht, hid, 1, integer(o))
return o
endmethod
endstruct
//******************************************************************************
//! textmacro LastOrderDebug takes ORDER, RETURN
debug if $ORDER$ > ORDERS_TO_HOLD then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Order out of range")
debug return $RETURN$
debug endif
debug if not HaveSavedInteger(ht, GetHandleId(u), $ORDER$) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: The "+I2S($ORDER$)+"th order doesn't exist")
debug return $RETURN$
debug endif
//! endtextmacro
function GetPastOrder takes unit u, integer whichOrder returns order
//! runtextmacro LastOrderDebug("whichOrder", "0")
return order(LoadInteger(ht, GetHandleId(u), whichOrder))
endfunction
function GetPastOrderId takes unit u, integer whichOrder returns integer
//! runtextmacro LastOrderDebug("whichOrder", "0")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).id
endfunction
function GetPastOrderString takes unit u, integer whichOrder returns string
//! runtextmacro LastOrderDebug("whichOrder", "\"\"")
return OrderId2String(order(LoadInteger(ht, GetHandleId(u), whichOrder)).id)
endfunction
function GetPastOrderType takes unit u, integer whichOrder returns integer
//! runtextmacro LastOrderDebug("whichOrder", "0")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).typ
endfunction
function GetPastOrderX takes unit u, integer whichOrder returns real
//! runtextmacro LastOrderDebug("whichOrder", "0.")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).x
endfunction
function GetPastOrderY takes unit u, integer whichOrder returns real
//! runtextmacro LastOrderDebug("whichOrder", "0.")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).y
endfunction
function GetPastOrderTarget takes unit u, integer whichOrder returns widget
//! runtextmacro LastOrderDebug("whichOrder", "null")
return order(LoadInteger(ht, GetHandleId(u), whichOrder)).tar
endfunction
//******************************************************************************
function GetLastOrder takes unit u returns order
return GetPastOrder(u, 1)
endfunction
function GetLastOrderId takes unit u returns integer
return GetPastOrderId(u, 1)
endfunction
function GetLastOrderString takes unit u returns string
return GetPastOrderString(u, 1)
endfunction
function GetLastOrderType takes unit u returns integer
return GetPastOrderType(u, 1)
endfunction
function GetLastOrderX takes unit u returns real
return GetPastOrderX(u, 1)
endfunction
function GetLastOrderY takes unit u returns real
return GetPastOrderY(u, 1)
endfunction
function GetLastOrderTarget takes unit u returns widget
return GetPastOrderTarget(u, 1)
endfunction
function IsLastOrderFinished takes unit u returns boolean
//! runtextmacro LastOrderDebug("1", "false")
return GetUnitCurrentOrder(u) == 0 or order(LoadInteger(ht, GetHandleId(u), 1)).fin
endfunction
//******************************************************************************
private function OrderFilter takes unit u, integer id returns boolean
//* Excludes specific orders or unit types from registering with the system
//*
//* 851972: stop
//* Stop is excluded from the system because it is the order that
//* tells a unit to do nothing. It should be ignored by the system.
//*
//* 851971: smart
//* 851986: move
//* 851983: attack
//* 851984: attackground
//* 851990: patrol
//* 851993: holdposition
//* These are the UI orders that are passed to the system.
//*
//* 851973: stunned
//* This order is issued when a unit is stunned onto the stunner
//* It's ignored by the system, since you'd never want to reissue it
//*
//* >= 852055, <= 852762
//* These are all spell IDs from defend to incineratearrowoff with
//* a bit of leeway at the ends for orders with no strings.
//*
return id == 851971 or id == 851986 or id == 851983 or id == 851984 or id == 851990 or id == 851993 or (id >= 852055 and id <= 852762)
endfunction
private function IssuePastOrderFilter takes unit u, integer whichOrder returns boolean
//* Some criteria for whether or not a unit's last order should be given
//*
//* INSTANT type orders are excluded because generally, reissuing an instant
//* order doesn't make sense. You can remove that check below if you'd like,
//* though.
//*
//* The Type check is really just to ensure that no spell recursion can
//* occur with IssueLastOrder. The problem with intercepting the spell cast
//* event is that it happens after the order is 'caught' and registered to
//* this system. Therefore, to just IssueLastOrder tells it to recast the
//* spell! That's a problem, so we need a method to eliminate it.
//*
return GetUnitTypeId(u) != 0 and not IsUnitType(u, UNIT_TYPE_DEAD) and GetPastOrderType(u, whichOrder) != 0 and GetPastOrderType(u, whichOrder) != ORDER_TYPE_IMMEDIATE
endfunction
//******************************************************************************
function IssuePastOrder takes unit u, integer whichOrder returns boolean
local order o = GetPastOrder(u, whichOrder)
if IssuePastOrderFilter(u, whichOrder) and not o.fin then
if o.typ == ORDER_TYPE_TARGET then
return IssueTargetOrderById(u, o.id, o.tar)
elseif o.typ == ORDER_TYPE_POINT then
if o.id == 851971 then
//This adjusts for a bug in the point order's boolean return
//when issuing a smart order
call IssuePointOrderById(u, o.id, o.x, o.y)
return true
else
return IssuePointOrderById(u, o.id, o.x, o.y)
endif
elseif o.typ == ORDER_TYPE_IMMEDIATE then
return IssueImmediateOrderById(u, o.id)
endif
endif
return false
endfunction
function IssueLastOrder takes unit u returns boolean
return IssuePastOrder(u, 1)
endfunction
function IssueSecondLastOrder takes unit u returns boolean
return IssuePastOrder(u, 2)
endfunction
function IssueArbitraryOrder takes unit u, order o returns boolean
if o.typ == ORDER_TYPE_TARGET then
return IssueTargetOrderById(u, o.id, o.tar)
elseif o.typ == ORDER_TYPE_POINT then
if o.id == 851971 then
//This adjusts for a bug in the point order's boolean return
//when issuing a smart order
call IssuePointOrderById(u, o.id, o.x, o.y)
return true
else
return IssuePointOrderById(u, o.id, o.x, o.y)
endif
elseif o.typ == ORDER_TYPE_IMMEDIATE then
return IssueImmediateOrderById(u, o.id)
endif
return false
endfunction
function AbortOrder takes unit u returns boolean
if IsUnitPaused(u) then
return false
else
call PauseUnit(u, true)
call IssueImmediateOrder(u, "stop")
call PauseUnit(u, false)
endif
return true
endfunction
//**********************************************************
private function Conditions takes nothing returns boolean
return OrderFilter(GetTriggerUnit(), GetIssuedOrderId())
endfunction
private function Actions takes nothing returns nothing
local unit u = GetTriggerUnit()
local widget t = GetOrderTarget()
local integer oid = GetIssuedOrderId()
local integer oty = 0
if GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER then
call order.create(u, oid, ORDER_TYPE_TARGET, t, GetWidgetX(t), GetWidgetY(t))
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER then
call order.create(u, oid, ORDER_TYPE_POINT, null, GetOrderPointX(), GetOrderPointY())
elseif GetTriggerEventId() == EVENT_PLAYER_UNIT_ISSUED_ORDER then
call order.create(u, oid, ORDER_TYPE_IMMEDIATE, null, GetUnitX(u), GetUnitY(u))
debug else
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Invalid order type")
endif
set u = null
set t = null
endfunction
//**********************************************************
private function SpellActions takes nothing returns nothing
local order o = GetPastOrder(GetTriggerUnit(), 1)
set o.fin = true
endfunction
//**********************************************************
private function OnAdd takes nothing returns boolean
local integer hid = GetHandleId(GetFilterUnit())
local integer i = ORDERS_TO_HOLD
//Handle stored orders in the hashtable
loop
//We hold up to the constant ORDERS_TO_HOLD in the table
exitwhen i == 0
//If any of the N orders exist for this handle id, kill them all
if HaveSavedInteger(ht, hid, i) then
call order.destroy(order(LoadInteger(ht, hid, i)))
call RemoveSavedInteger(ht, hid, i)
endif
set i = i - 1
endloop
return false
endfunction
//**********************************************************
private function Init takes nothing returns nothing
local trigger trg = CreateTrigger()
local region re = CreateRegion()
local rect m = GetWorldBounds()
//Main order catching trigger
call TriggerAddAction(trg, function Actions)
call TriggerAddCondition(trg, Condition(function Conditions))
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_ISSUED_ORDER)
//Spell trigger to set a flag that indicates a spell order's completion
set trg = CreateTrigger()
call TriggerAddAction(trg, function SpellActions)
call TriggerRegisterAnyUnitEventBJ(trg, EVENT_PLAYER_UNIT_SPELL_EFFECT)
//Entering world trigger that clears old data from handle ids
set trg = CreateTrigger()
call RegionAddRect(re, m)
call TriggerRegisterEnterRegion(trg, re, Condition(function OnAdd))
call RemoveRect(m)
set trg = null
set re = null
set m = null
endfunction
endlibrary
//TESH.scrollpos=5
//TESH.alwaysfold=0
library SimError initializer init
//**************************************************************************************************
//*
//* SimError
//*
//* Mimic an interface error message
//* call SimError(ForPlayer, msg)
//* ForPlayer : The player to show the error
//* msg : The error
//*
//* To implement this function, copy this trigger and paste it in your map.
//* Unless of course you are actually reading the library from wc3c's scripts section, then just
//* paste the contents into some custom text trigger in your map.
//*
//**************************************************************************************************
//==================================================================================================
globals
private sound error
endglobals
//====================================================================================================
function SimError takes player ForPlayer, string msg returns nothing
set msg="\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n|cffffcc00"+msg+"|r"
if (GetLocalPlayer() == ForPlayer) then
call ClearTextMessages()
call DisplayTimedTextToPlayer( ForPlayer, 0.52, 0.96, 2.00, msg )
call StartSound( error )
endif
endfunction
private function init takes nothing returns nothing
set error=CreateSoundFromLabel("InterfaceError",false,false,false,10,10)
//call StartSound( error ) //apparently the bug in which you play a sound for the first time
//and it doesn't work is not there anymore in patch 1.22
endfunction
endlibrary
//TESH.scrollpos=6
//TESH.alwaysfold=0
library AbortSpell requires LastOrder, SimError
//******************************************************************************
//* BY: Rising_Dusk
//*
//* The AbortSpell function in this library works just like the normal
//* SimError, except that it gives options for reissuing the unit's last order
//* and forcing a UI key for its owner to fully simulate a WC3 error message as
//* close as humanly possible.
//*
//* AbortSpell is valuable when used in the ISSUED_ORDER trigger event
//* callbacks. It is specifically designed to be used as a means for preventing
//* a unit from casting a spell and still continuing with whatever their last
//* order was. It also works on the SPELL_CAST event callback, but using it on
//* the order callbacks prevents the caster from turning/walking towards the
//* target point/unit/etc.
//*
//* Sample Function Usage:
//* call AbortSpell(MyUnit, "Error Message", "G")
//*
function AbortSpell takes unit u, string msg, string key returns boolean
local boolean b = false
local real a = 0.
if AbortOrder(u) then
call SimError(GetOwningPlayer(u), "\n"+msg)
if GetLocalPlayer() == GetOwningPlayer(u) and StringLength(key) == 1 and key != " " then
call ForceUIKey(key)
endif
set b = IssueSecondLastOrder(u)
if not b and GetPastOrder(u, 2) != 0 then
//Failed, have to issue a dummy order to prevent spell recursion
//Have to project a point a fraction of a space ahead of the unit,
//otherwise it will turn to 0 degrees facing because Blizzard sucks
set a = GetUnitFacing(u)*bj_DEGTORAD
call IssuePointOrderById(u, 851971, GetUnitX(u)+0.01*Cos(a), GetUnitY(u)+0.01*Sin(a))
endif
endif
return b
endfunction
endlibrary
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//~~ Alloc ~~ By Sevion ~~ Version 1.09 ~~
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
// What is Alloc?
// - Alloc implements an intuitive allocation method for array structs
//
// =Pros=
// - Efficient.
// - Simple.
// - Less overhead than regular structs.
//
// =Cons=
// - Must use array structs (hardly a con).
// - Must manually call OnDestroy.
// - Must use Delegates for inheritance.
// - No default values for variables (use onInit instead).
// - No array members (use another Alloc struct as a linked list or type declaration).
//
// Methods:
// - struct.allocate()
// - struct.deallocate()
//
// These methods are used just as they should be used in regular structs.
//
// Modules:
// - Alloc
// Implements the most basic form of Alloc. Includes only create and destroy
// methods.
//
// Details:
// - Less overhead than regular structs
//
// - Use array structs when using Alloc. Put the implement at the top of the struct.
//
// - Alloc operates almost exactly the same as default structs in debug mode with the exception of onDestroy.
//
// How to import:
// - Create a trigger named Alloc.
// - Convert it to custom text and replace the whole trigger text with this.
//
// Thanks:
// - Nestharus for the method of allocation and suggestions on further merging.
// - Bribe for suggestions like the static if and method names.
// - PurgeandFire111 for some suggestions like the merging of Alloc and AllocX as well as OnDestroy stuff.
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
library Alloc
module Alloc
private static integer instanceCount = 0
private thistype recycle
static method allocate takes nothing returns thistype
local thistype this
if (thistype(0).recycle == 0) then
debug if (instanceCount == 8190) then
debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to allocate too many instances!")
debug return 0
debug endif
set instanceCount = instanceCount + 1
set this = instanceCount
else
set this = thistype(0).recycle
set thistype(0).recycle = thistype(0).recycle.recycle
endif
debug set this.recycle = -1
return this
endmethod
method deallocate takes nothing returns nothing
debug if (this.recycle != -1) then
debug call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "Alloc ERROR: Attempted to deallocate an invalid instance at [" + I2S(this) + "]!")
debug return
debug endif
set this.recycle = thistype(0).recycle
set thistype(0).recycle = this
endmethod
endmodule
endlibrary
library AutoFly initializer onInit requires UnitDex
/***************************************************************
*
* v1.0, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* AutoFly adds & removes crow of upon any unit entering the map. This
* allows modifying of the units height without having to do it manually.
*
* Credits go to azlier for the orginal idea.
*
****************************************************************/
globals
private constant integer CROW_FORM = 'Amrf'
endglobals
private function AddFly takes nothing returns boolean
return UnitAddAbility(GetIndexedUnit(), CROW_FORM) and UnitRemoveAbility(GetIndexedUnit(), CROW_FORM)
endfunction
private function onInit takes nothing returns nothing
call RegisterUnitIndexEvent(Filter(function AddFly), EVENT_UNIT_INDEX)
endfunction
endlibrary
library Colour
globals
Colour COLOUR_PHYSICAL_DMG = 0
Colour COLOUR_MAGICAL_DMG = 0
Colour COLOUR_HEAL = 0
Colour COLOUR_SHIELD = 0
Colour COLOUR_GOLD = 0
Colour COLOUR_LUMBER = 0
Colour COLOUR_RED = 0
Colour COLOUR_GREEN = 0
Colour COLOUR_BLUE = 0
string array playerColour
string array hexs
endglobals
private module InitColour
private static method initDefaultColours takes nothing returns nothing
//Setup colours
set COLOUR_PHYSICAL_DMG = Colour.create(255, 0, 50, 255)
set COLOUR_MAGICAL_DMG = Colour.create(50, 200, 255, 255)
set COLOUR_HEAL = Colour.create(50, 255, 100, 255)
set COLOUR_SHIELD = Colour.create(255, 40, 225, 255)
set COLOUR_GOLD = Colour.create(255, 220, 0, 255)
set COLOUR_LUMBER = Colour.create(0, 200, 80, 255)
set COLOUR_RED = Colour.create(255, 0, 0, 255)
set COLOUR_GREEN = Colour.create(0, 255, 0, 255)
set COLOUR_BLUE = Colour.create(0, 0, 255, 255)
endmethod
private static method onInit takes nothing returns nothing
set hexs[0] = "0"
set hexs[1] = "1"
set hexs[2] = "2"
set hexs[3] = "3"
set hexs[4] = "4"
set hexs[5] = "5"
set hexs[6] = "6"
set hexs[7] = "7"
set hexs[8] = "8"
set hexs[9] = "9"
set hexs[10] = "A"
set hexs[11] = "B"
set hexs[12] = "C"
set hexs[13] = "D"
set hexs[14] = "E"
set hexs[15] = "F"
set playerColour[0] = "|cffff0303"
set playerColour[1] = "|cff0042ff"
set playerColour[2] = "|cff1be7ba"
set playerColour[3] = "|cff550081"
set playerColour[4] = "|cfffefc00"
set playerColour[5] = "|cfffe890d"
set playerColour[6] = "|cff21bf00"
set playerColour[7] = "|cffe45caf"
set playerColour[8] = "|cff939596"
set playerColour[9] = "|cff7ebff1"
set playerColour[10] = "|cff106247"
set playerColour[11] = "|cff4f2b05"
set playerColour[12] = "|cff9c0000"
set playerColour[13] = "|cff0000c3"
set playerColour[14] = "|cff00ebff"
set playerColour[15] = "|cffbd00ff"
set playerColour[16] = "|cffecce87"
set playerColour[17] = "|cfff7a58b"
set playerColour[18] = "|cffbfff81"
set playerColour[19] = "|cffdbb8eb"
set playerColour[20] = "|cff4f5055"
set playerColour[21] = "|cffecf0ff"
set playerColour[22] = "|cff00781e"
set playerColour[23] = "|cffa56f34"
set playerColour[24] = "|cff2e2d2e"
set playerColour[25] = "|cff2e2d2e"
call thistype.initDefaultColours()
endmethod
endmodule
struct Colour
integer r
integer g
integer b
integer a
string asString
/* Converts and int to a hexnumber */
static method toHex takes integer number returns string
local integer firstpart = number / 16
local integer secondpart = number - firstpart * 16
return hexs[firstpart] + hexs[secondpart]
endmethod
static method create takes integer red, integer green, integer blue, integer alpha returns thistype
local thistype this = allocate()
set this.r = red
set this.g = green
set this.b = blue
set this.a = alpha
set this.asString = "|c" + thistype.toHex(alpha) + thistype.toHex(red) + thistype.toHex(green) + thistype.toHex(blue)
return this
endmethod
implement InitColour
endstruct
function getPlayerColourString takes playercolor pCol returns string
if pCol == PLAYER_COLOR_RED then
return "|cffff0303"
elseif pCol == PLAYER_COLOR_BLUE then
return "|cff0042ff"
elseif pCol == PLAYER_COLOR_CYAN then
return "|cff1be7ba"
elseif pCol == PLAYER_COLOR_PURPLE then
return "|cff550081"
elseif pCol == PLAYER_COLOR_YELLOW then
return "|cfffefc00"
elseif pCol == PLAYER_COLOR_ORANGE then
return "|cfffe890d"
elseif pCol == PLAYER_COLOR_GREEN then
return "|cff21bf00"
elseif pCol == PLAYER_COLOR_PINK then
return "|cffe45caf"
elseif pCol == PLAYER_COLOR_LIGHT_GRAY then
return "|cff939596"
elseif pCol == PLAYER_COLOR_LIGHT_BLUE then
return "|cff7ebff1"
elseif pCol == PLAYER_COLOR_AQUA then
return "|cff106247"
elseif pCol == PLAYER_COLOR_BROWN then
return "|cff4f2b05"
elseif pCol == PLAYER_COLOR_MAROON then
return "|cff9c0000"
elseif pCol == PLAYER_COLOR_NAVY then
return "|cff0000c3"
elseif pCol == PLAYER_COLOR_TURQUOISE then
return "|cff00ebff"
elseif pCol == PLAYER_COLOR_VIOLET then
return "|cffbd00ff"
elseif pCol == PLAYER_COLOR_WHEAT then
return "|cffecce87"
elseif pCol == PLAYER_COLOR_PEACH then
return "|cfff7a58b"
elseif pCol == PLAYER_COLOR_MINT then
return "|cffbfff81"
elseif pCol == PLAYER_COLOR_LAVENDER then
return "|cffdbb8eb"
elseif pCol == PLAYER_COLOR_COAL then
return "|cff4f5055"
elseif pCol == PLAYER_COLOR_SNOW then
return "|cffecf0ff"
elseif pCol == PLAYER_COLOR_EMERALD then
return "|cff00781e"
elseif pCol == PLAYER_COLOR_PEANUT then
return "|cffa56f34"
endif
return "|cff2e2d2e"
endfunction
function getPlayerColour takes player play returns string
return playerColour[GetPlayerId(play)]
endfunction
function getPlayerColourFromId takes integer id returns string
return playerColour[id]
endfunction
endlibrary
library CombatState requires UnitDex, RegisterNativeEvent, optional TimerUtils
//******** COMBAT STATE ******************************************//
// - This library registers whether or not some unit is in combat.
// - To be registered as in combat, the unit must attack or have been attacked by an enemy unit.
// - Any spell that is cast by an opposing player onto the unit will flag them as in combat.
// - Being in combat only lasts a specific duration, in this case 5 seconds, before the unit leaves combat.
// - Once a unit dies, the dying unit will be taken out of combat.
// Requirements:
// -- UnitDex by TriggerHappy
// -- RegisterNativeEvent by Banar
// - This will allow you to register when a unit enters or leaves combat.
// -- *OPTIONAL* TimerUtils by Vexorian
// - This will recycle timers instead of creating/destroying them, so it is a bit more optimal. As far as speed goes,
// it is pretty negligible. Without TimerUtils, it will use a hashtable.
// API:
// Configurables:
// - COMBAT_DURATION: This determines the time before a unit is considered to be out of combat, default 5 seconds.
// The unit must remain unattacked and attack no one for COMBAT_DURATION to leave combat.
// Data Modify/Retrieve:
// CombatState[whichUnit].inCombat -> returns boolean
// set CombatState[whichUnit].inCombat = flag
// Function Wrappers:
// function GetUnitCombatState takes unit whichUnit returns boolean
// - This returns the combat state of a unit. If it returns true, the unit is in combat. If it returns false, then the unit
// is not in combat.
// function SetUnitCombatState takes unit whichUnit, boolean flag returns nothing
// - This allows you to force a unit in or out of combat, and it will register the corresponding events.
// If you are using RegisterNativeEvent:
// call RegisterNativeEvent(EVENT_COMBAT_STATE_ENTER, function trigger)
// - Registers when some unit enters combat after being out of combat.
// call RegisterNativeEvent(EVENT_COMBAT_STATE_LEAVE, function trigger)
// - Registers when some unit leaves combat after having been just in combat.
// function GetTriggerCombatUnit takes nothing returns unit
// - When using registering an event, this will basically return the unit who entered or left combat. GetTriggerCombatUnit()
// Credits:
// - PurgeandFire for the original CombatState that I re-adapted to use UnitDex and RegisterNativeEvent
//****************************************************************//
globals
private constant real COMBAT_DURATION = 5
//**************DO NOT EDIT PAST THIS POINT***********************//
private unit combatUnit = null
private hashtable Hash
integer EVENT_COMBAT_STATE_ENTER = 0
integer EVENT_COMBAT_STATE_LEAVE = 0
endglobals
function GetTriggerCombatUnit takes nothing returns unit
return combatUnit
endfunction
private module Init
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
static if not LIBRARY_TimerUtils then
set Hash = InitHashtable()
endif
set EVENT_COMBAT_STATE_ENTER = CreateNativeEvent()
set EVENT_COMBAT_STATE_LEAVE = CreateNativeEvent()
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_ATTACKED)
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_DEATH)
call OnUnitDeindex(function thistype.deindex)
call TriggerAddCondition(t,Condition(function thistype.CombatEnter))
endmethod
endmodule
struct CombatState extends array
private timer combatTimer
private boolean inCombatV
static method operator [] takes unit u returns thistype
return GetUnitUserData(u)
endmethod
private static method CombatLeave takes nothing returns nothing
local timer t = GetExpiredTimer()
local unit prev = combatUnit
static if LIBRARY_TimerUtils then
local integer id = GetTimerData(t)
call ReleaseTimer(t)
else
local integer id = LoadInteger(Hash,GetHandleId(t),0)
call PauseTimer(t)
call DestroyTimer(t)
set t = null
endif
set combatUnit = GetUnitById(id)
set thistype(id).inCombatV = false
set thistype(id).combatTimer = null
call TriggerEvaluate(GetNativeEventTrigger(EVENT_COMBAT_STATE_LEAVE))
set combatUnit = prev
set prev = null
endmethod
method operator inCombat takes nothing returns boolean
return this.inCombatV
endmethod
method operator inCombat= takes boolean flag returns nothing
local unit prev = combatUnit
set combatUnit = GetUnitById(this)
if flag then
if this.combatTimer == null then
set this.inCombatV = true
call TriggerEvaluate(GetNativeEventTrigger(EVENT_COMBAT_STATE_ENTER))
static if LIBRARY_TimerUtils then
set this.combatTimer = NewTimer()
call SetTimerData(this.combatTimer,this)
else
set this.combatTimer = CreateTimer()
call SaveInteger(Hash,GetHandleId(this.combatTimer),0,this)
endif
endif
call TimerStart(this.combatTimer,COMBAT_DURATION,false,function thistype.CombatLeave)
elseif (this.inCombatV) then
set this.inCombatV = false
static if LIBRARY_TimerUtils then
call ReleaseTimer(this.combatTimer)
else
call PauseTimer(this.combatTimer)
call DestroyTimer(this.combatTimer)
endif
set this.combatTimer = null
call TriggerEvaluate(GetNativeEventTrigger(EVENT_COMBAT_STATE_LEAVE))
endif
set combatUnit = prev
set prev = null
endmethod
private static method CombatEnter takes nothing returns boolean
local unit u = GetAttacker()
if GetTriggerEventId()==EVENT_PLAYER_UNIT_DEATH then
set thistype[GetTriggerUnit()].inCombat=false
return false
elseif u == null then
set u = GetSpellTargetUnit()
endif
if u != null then
if IsUnitEnemy(u,GetTriggerPlayer()) then
set thistype[GetTriggerUnit()].inCombat=true
set thistype[u].inCombat=true
elseif CombatState[u].inCombat then
set thistype[GetTriggerUnit()].inCombat=true
endif
endif
set u = null
return false
endmethod
private static method deindex takes nothing returns boolean
set thistype(GetIndexedUnitId()).inCombat=false
return false
endmethod
implement Init
endstruct
function GetUnitCombatState takes unit whichUnit returns boolean
return CombatState[whichUnit].inCombat
endfunction
function SetUnitCombatState takes unit whichUnit, boolean flag returns nothing
set CombatState[whichUnit].inCombat = flag
endfunction
endlibrary
/*****************************************************************************
*
* ConstructEvent v2.2.0.1
* by Bannar
*
* Provides complete solution to construction events.
* Allows to retrieve unit which actually started construction of given structure.
*
******************************************************************************
*
* Requirements:
*
* RegisterPlayerUnitEvent by Bannar
* hiveworkshop.com/threads/snippet-registerevent-pack.250266/
*
* ListT by Bannar
* hiveworkshop.com/threads/containers-list-t.249011/
*
* UnitDex by TriggerHappy
* hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
*
******************************************************************************
*
* Event API:
*
* integer EVENT_UNIT_CONSTRUCTION_START
*
* integer EVENT_UNIT_CONSTRUCTION_CANCEL
* Intentional construction stop.
*
* integer EVENT_UNIT_CONSTRUCTION_FINISH
*
* integer EVENT_UNIT_CONSTRUCTION_INTERRUPT
* Undesired construction stop e.g. unit death.
*
* Use RegisterNativeEvent or RegisterIndexNativeEvent for event registration.
* GetNativeEventTrigger and GetIndexNativeEventTrigger provide access to trigger handles.
*
*
* function GetConstructingBuilder takes nothing returns unit
* Retrieves event builder unit, valid only for START event.
*
* function GetConstructingBuilderId takes nothing returns integer
* Returns index of builder unit.
*
* function GetTriggeringStructure takes nothing returns unit
* Retrieves event structure unit.
*
* function GetTriggeringStructureId takes nothing returns integer
* Returns index of constructed structure unit.
*
******************************************************************************
*
* Functions:
*
* function GetStructureBuilder takes unit whichUnit returns unit
* Gets unit which constructed given structure.
*
* function GetStructureBuilderById takes integer whichIndex returns unit
* Gets unit which constructed given structure.
*
* function IsStructureFinished takes unit whichUnit returns boolean
* Checks whether construction of provided structure has been completed.
*
*****************************************************************************/
library ConstructEvent requires RegisterPlayerUnitEvent, ListT, optional UnitDex optional UnitIndexerGUI
globals
integer EVENT_UNIT_CONSTRUCTION_START
integer EVENT_UNIT_CONSTRUCTION_FINISH
integer EVENT_UNIT_CONSTRUCTION_CANCEL
integer EVENT_UNIT_CONSTRUCTION_INTERRUPT
endglobals
native UnitAlive takes unit id returns boolean
globals
private IntegerList ongoing = 0
private timer looper = CreateTimer()
private unit eventBuilder = null
private unit eventConstruct = null
private unit array builders
private boolean array finished
private integer array instances
private boolean array cancelled
endglobals
function GetConstructingBuilder takes nothing returns unit
return eventBuilder
endfunction
function GetConstructingBuilderId takes nothing returns integer
return GetUnitId(eventBuilder)
endfunction
function GetTriggeringStructure takes nothing returns unit
return eventConstruct
endfunction
function GetTriggeringStructureId takes nothing returns integer
return GetUnitId(eventConstruct)
endfunction
function GetStructureBuilder takes unit whichUnit returns unit
return builders[GetUnitId(whichUnit)]
endfunction
function GetStructureBuilderById takes integer whichIndex returns unit
return builders[whichIndex]
endfunction
function IsStructureFinished takes unit whichUnit returns boolean
return finished[GetUnitId(whichUnit)]
endfunction
private function FireEvent takes integer evt, unit builder, unit structure returns nothing
local unit prevBuilder = eventBuilder
local unit prevConstruct = eventConstruct
local integer playerId = GetPlayerId(GetOwningPlayer(builder))
set eventBuilder = builder
set eventConstruct = structure
call TriggerEvaluate(GetNativeEventTrigger(evt))
if IsNativeEventRegistered(playerId, evt) then
call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, evt))
endif
set eventBuilder = prevBuilder
set eventConstruct = prevConstruct
set prevBuilder = null
set prevConstruct = null
endfunction
/*
* Unit with no path-texture can be placed in 'arbitrary' location, that is, its x and y
* won't have integral values. Whatmore, those values will be modified, depending on quarter of
* coordinate axis where unit is going to be built. This takes form of rounding up and down to
* 0.250-floor.
*
* Calculus is different for positive (+x/+y) and negative values (-x/-y).
* This function makes sure data is translated accordingly.
*/
private function TranslateXY takes real r returns real
if r >= 0 then
return R2I(r / 0.250) * 0.250
else
return (R2I(r / 0.250) - 1) * 0.250
endif
endfunction
/*
* Whether issued order can be counted as a build-ability order. Accounts for:
* orders between useslot1 and useslot6, plus build tiny building - item ability.
* Build type: start and forget, just like undead Acolytes do.
*/
private function IsBuildOrder takes integer o returns boolean
return (o >= 852008 and o <= 852013) or (o == 852619)
endfunction
/*
* On the contrary to what's described in TranslateXY regarding building position,
* builder will have his coords "almost" unchanged. This creates situation
* where builder and construct do not share the same location (x & y).
*
* Additionally, builder's coords will differ from order's by a small margin. It could be
* negligible if not for the fact that difference can be greater than or equal to 0.001.
* In consequence, this invalidates usage of '==' operator when comparing coords
* (i. e. jass reals - 3 significant digits displayed).
*/
private function IsUnitWithinCoords takes unit u, real x, real y returns boolean
return (RAbsBJ(GetUnitX(u) - x) == 0) and (RAbsBJ(GetUnitY(u) - y) == 0)
endfunction
private struct PeriodicData extends array
unit builder // unit which started construction
race btype // build-type, race dependant
real cx // future construction point x
real cy // future construction point y
real distance // for distance measurement, UD/HUM only
integer order // unit-type order id
real ox // order point x
real oy // order point y
implement Alloc
method destroy takes nothing returns nothing
set instances[GetUnitId(builder)] = 0
set builder = null
set btype = null
call ongoing.erase(ongoing.find(this))
if ongoing.empty() then
call PauseTimer(looper)
endif
call deallocate()
endmethod
static method create takes thistype this, unit u, integer orderId, boolean flag, real x, real y returns thistype
if this == 0 then
set this = allocate()
set builder = u
set instances[GetUnitId(u)] = this
call ongoing.push(this)
endif
set order = orderId
if flag then // ability based build order
set btype = RACE_UNDEAD
else
set btype = GetUnitRace(builder)
endif
set cx = x
set cy = y
set ox = cx
set oy = cy
if cx - I2R(R2I(cx)) != 0 then
set cx = TranslateXY(cx)
endif
if cy - I2R(R2I(cy)) != 0 then
set cy = TranslateXY(cy)
endif
return this
endmethod
endstruct
private function OnCallback takes nothing returns nothing
local IntegerListItem iter = ongoing.first
local PeriodicData obj
local real dx
local real dy
loop
exitwhen iter == 0
set obj = iter.data
if not UnitAlive(obj.builder) then
call obj.destroy()
elseif obj.btype == RACE_UNDEAD or obj.btype == RACE_HUMAN then
set dx = obj.cx - GetUnitX(obj.builder)
set dy = obj.cy - GetUnitY(obj.builder)
set obj.distance = dx*dx + dy*dy
endif
set iter = iter.next
endloop
endfunction
private function OnNonPointOrder takes nothing returns nothing
local unit u = GetTriggerUnit()
local unit target = GetOrderTargetUnit()
local integer index = GetUnitId(u)
local integer orderId = GetIssuedOrderId()
local PeriodicData obj = instances[index]
// Non handle-type orders usually take 852XXX form and are below 900000
if orderId > 900000 and target != null then
if ongoing.empty() then
call TimerStart(looper, 0.031250000, true, function OnCallback)
endif
call PeriodicData.create(obj, u, orderId, true, GetUnitX(target), GetUnitY(target))
set target = null
elseif obj != 0 then
call obj.destroy()
elseif orderId == 851976 and builders[index] != null and not finished[index] then // order cancel
if not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
set cancelled[index] = true
call FireEvent(EVENT_UNIT_CONSTRUCTION_CANCEL, null, u)
endif
endif
set u = null
endfunction
private function OnPointOrder takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer orderId = GetIssuedOrderId()
local PeriodicData obj = instances[GetUnitId(u)]
local boolean isBuildOrder = IsBuildOrder(orderId)
// Non handle-type orders usually take 852XXX form and are below 900000
if orderId > 900000 or isBuildOrder then
if ongoing.empty() then
call TimerStart(looper, 0.031250000, true, function OnCallback)
endif
call PeriodicData.create(obj, u, orderId, isBuildOrder, GetOrderPointX(), GetOrderPointY())
elseif obj != 0 then
call obj.destroy()
endif
set u = null
endfunction
private function OnConstructCancel takes nothing returns nothing
local unit u = GetTriggerUnit()
set cancelled[GetUnitId(u)] = true
call FireEvent(EVENT_UNIT_CONSTRUCTION_CANCEL, null, u)
set u = null
endfunction
private function OnConstructFinish takes nothing returns nothing
local unit u = GetTriggerUnit()
set finished[GetUnitId(u)] = true
call FireEvent(EVENT_UNIT_CONSTRUCTION_FINISH, null, u)
set u = null
endfunction
private function OnIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
local integer id = GetUnitTypeId(u)
local IntegerListItem iter = ongoing.first
local PeriodicData obj
local PeriodicData found = 0
local real d = 1000000
loop
exitwhen iter == 0
set obj = iter.data
if obj.order == id or IsBuildOrder(obj.order) then
if obj.cx == GetUnitX(u) and obj.cy == GetUnitY(u) then
if obj.btype == RACE_HUMAN or obj.btype == RACE_UNDEAD then
if obj.distance < d then
set d = obj.distance
set found = obj
endif
elseif IsUnitWithinCoords(obj.builder, obj.ox, obj.oy) then
set found = obj
exitwhen true
endif
endif
endif
set iter = iter.next
endloop
if found != 0 then
set builders[GetIndexedUnitId()] = found.builder
call FireEvent(EVENT_UNIT_CONSTRUCTION_START, found.builder, u)
call found.destroy()
endif
set u = null
endfunction
private function OnDeindex takes nothing returns nothing
local integer index = GetIndexedUnitId()
if instances[index] != 0 then
call PeriodicData(instances[index]).destroy()
elseif builders[index] != null then
if not (finished[index] or cancelled[index]) then
call FireEvent(EVENT_UNIT_CONSTRUCTION_INTERRUPT, null, GetIndexedUnit())
endif
set builders[index] = null
set finished[index] = false
set cancelled[index] = false
endif
endfunction
private module ConstructEventInit
private static method onInit takes nothing returns nothing
set EVENT_UNIT_CONSTRUCTION_START = CreateNativeEvent()
set EVENT_UNIT_CONSTRUCTION_CANCEL = CreateNativeEvent()
set EVENT_UNIT_CONSTRUCTION_FINISH = CreateNativeEvent()
set EVENT_UNIT_CONSTRUCTION_INTERRUPT = CreateNativeEvent()
set START = EVENT_UNIT_CONSTRUCTION_START
set CANCEL = EVENT_UNIT_CONSTRUCTION_CANCEL
set FINISH = EVENT_UNIT_CONSTRUCTION_FINISH
set INTERRUPT = EVENT_UNIT_CONSTRUCTION_INTERRUPT
set ongoing = IntegerList.create()
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, function OnNonPointOrder)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, function OnPointOrder)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function OnNonPointOrder)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_CANCEL, function OnConstructCancel)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_CONSTRUCT_FINISH, function OnConstructFinish)
call OnUnitIndex(function OnIndex)
call OnUnitDeindex(function OnDeindex)
//call RegisterUnitIndexEvent(Condition(function OnIndex), EVENT_UNIT_INDEX)
//call RegisterUnitIndexEvent(Condition(function OnDeindex), EVENT_UNIT_DEINDEX)
endmethod
endmodule
struct ConstructEvent
readonly static integer START
readonly static integer CANCEL
readonly static integer FINISH
readonly static integer INTERRUPT
implement ConstructEventInit
endstruct
function GetEventBuilder takes nothing returns unit
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventBuilder is obsolete, use GetContructingBuilder instead.")
return GetConstructingBuilder()
endfunction
function GetEventBuilderId takes nothing returns integer
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventBuilderId is obsolete, use GetContructingBuilderId instead.")
return GetConstructingBuilderId()
endfunction
function GetEventStructure takes nothing returns unit
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventStructure is obsolete, use GetContructedStructure instead.")
return GetTriggeringStructure()
endfunction
function GetEventStructureId takes nothing returns integer
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetEventStructureId is obsolete, use GetContructedStructureId instead.")
return GetTriggeringStructureId()
endfunction
function RegisterConstructEvent takes code func, integer whichEvent returns nothing
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function RegisterConstructEvent is obsolete, use RegisterNativeEvent instead.")
call RegisterNativeEvent(whichEvent, func)
endfunction
function GetConstructEventTrigger takes integer whichEvent returns trigger
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Function GetConstructEventTrigger is obsolete, use GetIndexNativeEventTrigger instead.")
return GetNativeEventTrigger(whichEvent)
endfunction
endlibrary
library DelayedOrder uses TimerUtils
/*
API
DelayedOrder
DelayedOrderXY
DelayedOrderTarget
*/
struct DelayedOrder
private unit u
private integer o
private static method clock takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call IssueImmediateOrderById(.u, .o)
call ReleaseTimer(t)
set .u = null
set t = null
call .deallocate()
endmethod
static method start takes unit u, integer ordr, real time returns nothing
local thistype this = allocate()
set .u = u
set .o = ordr
call TimerStart(NewTimerEx(this), time, false, function thistype.clock)
endmethod
endstruct
struct DelayedOrderXY
private unit u
private integer o
private real x
private real y
private static method clock takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call IssuePointOrderById(.u, .o, .x, .y)
call ReleaseTimer(t)
set .u = null
set t = null
call .deallocate()
endmethod
static method start takes unit u, integer ordr, real time, real x, real y returns nothing
local thistype this = allocate()
set .u = u
set .o = ordr
set .x = x
set .y = y
call TimerStart(NewTimerEx(this), time, false, function thistype.clock)
endmethod
endstruct
struct DelayedOrderTarget
private unit u
private integer o
private widget w
private static method clock takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call IssueTargetOrderById(.u, .o, .w)
call ReleaseTimer(t)
set .u = null
set .w = null
set t = null
call .deallocate()
endmethod
static method start takes unit u, integer ordr, real time, widget w returns nothing
local thistype this = allocate()
set .u = u
set .o = ordr
set .w = w
call TimerStart(NewTimerEx(this), time, false, function thistype.clock)
endmethod
endstruct
endlibrary
library DefaultTextTag uses Colour
/*
This mimics the floating text that appears over gold mines or workers as they harvest lumber.
*/
globals
private constant real TEXTTAG_FADEPOINT = 1.
private constant real TEXTTAG_LIFESTAPN = 2.
private constant real TEXTTAG_HEIGHT_OFFSET = 0.
private constant real TEXTTAG_FONT_SIZE = TextTagSize2Height(10.)
private constant real TEXTTAG_VEL_X = 0.0355 * Cos(1.570796)
private constant real TEXTTAG_VEL_Y = 0.0355 * Sin(1.570796)
endglobals
function defaultTextTag takes string text, real x, real y, player owner returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, TEXTTAG_FONT_SIZE)
call SetTextTagPos(tag, x, y, TEXTTAG_HEIGHT_OFFSET)
call SetTextTagVelocity(tag, TEXTTAG_VEL_X, TEXTTAG_VEL_Y)
call SetTextTagLifespan(tag, TEXTTAG_LIFESTAPN)
call SetTextTagFadepoint(tag, TEXTTAG_FADEPOINT)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
function defaultTextTagEx takes string text, real x, real y, player owner, Colour clr returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, TEXTTAG_FONT_SIZE)
call SetTextTagColor(tag, clr.r, clr.g, clr.b, clr.a)
call SetTextTagPos(tag, x, y, TEXTTAG_HEIGHT_OFFSET)
call SetTextTagVelocity(tag, TEXTTAG_VEL_X, TEXTTAG_VEL_Y)
call SetTextTagLifespan(tag, TEXTTAG_LIFESTAPN)
call SetTextTagFadepoint(tag, TEXTTAG_FADEPOINT)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
function customTextTag takes string text, real x, real y, player owner, real fontSize, real xVel, real yVel, real Lifespan, real fadePoint returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, fontSize)
call SetTextTagPos(tag, x, y, TEXTTAG_HEIGHT_OFFSET)
call SetTextTagVelocity(tag, xVel, yVel)
call SetTextTagLifespan(tag, Lifespan)
call SetTextTagFadepoint(tag, fadePoint)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
function customTextTagEx takes string text, real x, real y, player owner, Colour clr , real fontSize, real xVel, real yVel, real Lifespan, real fadePoint returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, fontSize)
call SetTextTagColor(tag, clr.r, clr.g, clr.b, clr.a)
call SetTextTagPos(tag, x, y, TEXTTAG_HEIGHT_OFFSET)
call SetTextTagVelocity(tag, xVel, yVel)
call SetTextTagLifespan(tag, Lifespan)
call SetTextTagFadepoint(tag, fadePoint)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
// ================================================================
// HEIGHT CUSTOMISABLE
//
function defaultTextTagHeight takes string text, real x, real y, real height, player owner returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, TEXTTAG_FONT_SIZE)
call SetTextTagPos(tag, x, y, height)
call SetTextTagVelocity(tag, TEXTTAG_VEL_X, TEXTTAG_VEL_Y)
call SetTextTagLifespan(tag, TEXTTAG_LIFESTAPN)
call SetTextTagFadepoint(tag, TEXTTAG_FADEPOINT)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
function defaultTextTagHeightEx takes string text, real x, real y, real height, player owner, Colour clr returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, TEXTTAG_FONT_SIZE)
call SetTextTagColor(tag, clr.r, clr.g, clr.b, clr.a)
call SetTextTagPos(tag, x, y, height)
call SetTextTagVelocity(tag, TEXTTAG_VEL_X, TEXTTAG_VEL_Y)
call SetTextTagLifespan(tag, TEXTTAG_LIFESTAPN)
call SetTextTagFadepoint(tag, TEXTTAG_FADEPOINT)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
function customTextTagHeight takes string text, real x, real y, real height, player owner, real fontSize, real xVel, real yVel, real Lifespan, real fadePoint returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, fontSize)
call SetTextTagPos(tag, x, y, height)
call SetTextTagVelocity(tag, xVel, yVel)
call SetTextTagLifespan(tag, Lifespan)
call SetTextTagFadepoint(tag, fadePoint)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
function customTextTagHeightEx takes string text, real x, real y, real height, player owner, Colour clr , real fontSize, real xVel, real yVel, real Lifespan, real fadePoint returns nothing
local texttag tag = CreateTextTag()
call SetTextTagText(tag, text, fontSize)
call SetTextTagColor(tag, clr.r, clr.g, clr.b, clr.a)
call SetTextTagPos(tag, x, y, height)
call SetTextTagVelocity(tag, xVel, yVel)
call SetTextTagLifespan(tag, Lifespan)
call SetTextTagFadepoint(tag, fadePoint)
call SetTextTagPermanent(tag, false)
if owner == null then
call SetTextTagVisibility(tag, true)
else
call SetTextTagVisibility(tag, GetLocalPlayer() == owner)
endif
set tag = null
endfunction
endlibrary
library DummyRecycler /*
// DummyRecycler v1.25
// by Flux
//
// A system that recycles dummy units while considering their facing angle.
// It can be used as attachment dummies for visual effects or as dummy caster.
//
// Why is recycling a unit important?
// Because creating a unit is is one of the slowest function in the game
// and there are reports that will always leave a permanent tiny bit of
// memory (0.04 KB).
// On average, retrieving a pending Dummy is approximately 4x faster compared
// to creating a new one and recycling a Dummy compared to removing it is
// approximately 1.3x faster.
// Furthermore, if you're using a lot of "Unit has entered map" events,
// using this system will even result to even more better performance
// because retrieving Dummy units does not cause that event to run.
*/ requires /*
nothing
*/ optional Table/*
if not found, this system will use a hashtable. Hashtables are limited to
255 per map.
*/ optional WorldBounds /*
if not found, this system will initialize its own Map Boundaries.
//
//
// Features:
//
// -- Dummy Sharing
// When a Dummy List gets low on unit count, it will borrow Dummy Units
// from the Dummy List with the highest unit count. The transfer is not
// instant because the shared Dummy Unit has to turn to the appropriate
// angle of its new Dummy List before it can be recycled.
// See BORROW_REQUEST.
//
// -- Self-balancing recycling algorithm
// Recycled Dummy Units will be thrown to the List having the least number
// of Dummy Units.
//
// -- Recycling least used
// Allows recycling a Dummy from the Dummy List with the highest
// unit count. It is useful when the facing angle of the Dummy Unit
// does not matter.
// See GetRecycledDummyAnyAngle.
//
// -- Self-adaptation
// When there are no free Dummy Units from a Dummy List, it will end up creating
// a new unit instead but that unit will be permanently added as a Dummy
// Unit to be recycled increasing the overall total Dummy Unit count.
//
// -- Count control
// Allows limiting the overall number of Dummy Units.
// See MAX_DUMMY_COUNT.
//
// -- Delayed Recycle
// Allows recycling Dummy Units after some delay to allocate time for the
// death animation of Special Effects to be seen.
// See DummyAddRecycleTimer.
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
// function GetRecycledDummy takes real x, real y, real z, real facing returns unit
// - Retrieve an unused Dummy Unit from the List.
// - The equivalent of CreateUnit.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
// function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
// - Use this function if the facing angle of the Dummy doesn't matter to you.
// - It will return a unit from the list having the highest number of unused Dummy Units.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false).
//
// function RecycleDummy takes unit u returns nothing
// - Recycle the Dummy unit for it to be used again later.
// - The equivalent of RemoveUnit.
//
// function DummyAddRecycleTimer takes unit u, real time returns nothing
// - Recycle the Dummy unit after a certain time.
// - Use this to allocate time for the the death animation of an effect attached to the
// Dummy Unit to finish..
// - The equivalent of UnitApplyTimedLife.
//
// function ShowDummy takes unit u, boolean flag returns nothing
// - Shows/hides Dummy Unit without conflicting with the Locust ability.
//
//--------------------
// CREDITS
//--------------------
// Bribe - for the MissileRecycler (vJASS) where I got this concept from
// http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
// - for the optional Table
// http://www.hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
// Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
// http://www.wc3c.net/showthread.php?t=101150
// Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
// http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
// Nestharus - for the data structure
// http://www.hiveworkshop.com/forums/2809461-post7.html
// - for the optional WorldBounds
// http://githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== */
globals
//The rawcode of the Dummy Unit
private constant integer DUMMY_ID = 'e000'
//The owner of the Dummy Unit
private constant player OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
//The number of indexed angle. The higher the value the:
// - Lesser the turning time for the Dummy Units.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 10 (Max difference of 18 degrees)
private constant integer ANGLES_COUNT = 10
//The number of Dummy units per ANGLES_COUNT. The higher the value the:
// - Higher the number of units that can be recycled per angle, when
// no more units are in queue, the system will resort to use CreateUnit.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
private constant integer STORED_UNIT_COUNT = 3
//The maximum number of Dummy units that can exist. When the system resort
//to using CreateUnit, the unit will be permanently added to the Dummy
//List. To avoid spamming Dummy Units and having too much free Dummy
//Units to allocate, the maximum number of Dummy Units is capped.
// Recommended Value: 80 to 120
private constant integer MAX_DUMMY_COUNT = 100
//When a certain angle have less than BORROW_REQUEST units in its list,
//it will start to borrow Dummy Units from the list with the highest
//Dummy Unit count.
// Recommended Value: Half of maximum STORED_UNIT_COUNT
private constant integer BORROW_REQUEST = 5
//It will only return a Dummy if the current dummy is close
//to it's appropriate facing angle. This is to avoid returning
//a Dummy which is still turning to face it's list angle.
private constant real ANGLE_TOLERANCE = 10.0
//An additional option to automatically hide recycled dummy units in the
//corner of the map camera bounds
private constant boolean HIDE_ON_MAP_CORNER = true
endglobals
//Every time a new dummy unit is retrieved, it will apply this resets
//If it is redundant/you dont need it, remove it.
//! textmacro DUMMY_UNIT_RESET
call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
call ShowDummy(bj_lastCreatedUnit, true)
//! endtextmacro
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
globals
private integer dummyCount = ANGLES_COUNT*STORED_UNIT_COUNT
private real array angle
private integer array count
private integer array countHead
private integer array countNext
private integer array countPrev
private integer array next
private integer array prev
private unit array dummy
private integer upper
private integer lower
private integer lastInstance
private constant real FACING_OFFSET = 180.0/ANGLES_COUNT
endglobals
static if HIDE_ON_MAP_CORNER and not LIBRARY_WorldBounds then
private module BoundsInit
readonly static real x
readonly static real y
private static method onInit takes nothing returns nothing
local rect map = GetWorldBounds()
set thistype.x = GetRectMaxX(map)
set thistype.y = GetRectMaxY(map)
call RemoveRect(map)
set map = null
endmethod
endmodule
private struct Bounds extends array
implement BoundsInit
endstruct
endif
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable hash = InitHashtable()
endif
private static method onInit takes nothing returns nothing
local real add = 360.0/ANGLES_COUNT
local real a = 0
local integer this = ANGLES_COUNT
local integer head = 0
local integer cHead = JASS_MAX_ARRAY_SIZE - 1 //avoid allocation collision
local integer i = R2I(MAX_DUMMY_COUNT/ANGLES_COUNT + 0.5)
set upper = STORED_UNIT_COUNT
set lower = STORED_UNIT_COUNT
static if LIBRARY_Table then
set tb = Table.create()
endif
//Initialize countHeads
loop
exitwhen i < 0
set countNext[cHead] = cHead
set countPrev[cHead] = cHead
set countHead[i] = cHead
set cHead = cHead - 1
set i = i - 1
endloop
set cHead = countHead[STORED_UNIT_COUNT] //All heads will be inserted here initially
//Create the Dummy units
loop
exitwhen a >= 360
//Initialize head
set next[head] = head
set prev[head] = head
set count[head] = STORED_UNIT_COUNT
set angle[head] = a
//Insert head in the Count List
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
set i = 0
loop
exitwhen i >= STORED_UNIT_COUNT
//Queued Linked List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, a)
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, Bounds.x, Bounds.y, a)
endif
else
set dummy[this] = CreateUnit(OWNER, DUMMY_ID, 0, 0, a)
endif
call PauseUnit(dummy[this], true)
static if LIBRARY_Table then
set tb[GetHandleId(dummy[this])] = this
else
call SaveInteger(hash, GetHandleId(dummy[this]), 0, this)
endif
set this = this + 1
set i = i + 1
endloop
set head = head + 1
set a = a + add
endloop
set lastInstance = this
endmethod
endmodule
private struct S extends array
implement M
endstruct
private function GetHead takes integer facing returns integer
if facing < 0 or facing >= 360 then
set facing = facing - (facing/360)*360
if facing < 0 then
set facing = facing + 360
endif
endif
return R2I((facing*ANGLES_COUNT/360.0))
endfunction
function ShowDummy takes unit u, boolean flag returns nothing
if IsUnitHidden(u) == flag then
call ShowUnit(u, flag)
if flag and GetUnitTypeId(u) == DUMMY_ID then
call UnitRemoveAbility(u, 'Aloc')
call UnitAddAbility(u, 'Aloc')
endif
endif
endfunction
function GetRecycledDummy takes real x, real y, real z, real facing returns unit
local integer head = GetHead(R2I(facing + FACING_OFFSET))
local integer this = next[head]
local integer cHead
//If there are Dummy Units in the Queue List already facing close to the appropriate angle
if this != head and RAbsBJ(GetUnitFacing(dummy[this]) - angle[head]) <= ANGLE_TOLERANCE then
//Remove from the Queue List
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//For double free protection
set next[this] = -1
//Unit Properties
set bj_lastCreatedUnit = dummy[this]
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call SetUnitFacing(bj_lastCreatedUnit, facing)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
//! runtextmacro DUMMY_UNIT_RESET()
//Update Count and Bounds
set count[head] = count[head] - 1
//------------------------------------------------
// Unit Sharing
//------------------------------------------------
if count[head] < BORROW_REQUEST and count[countNext[countHead[upper]]] > count[head] then
set count[head] = count[head] + 1
set this = next[countNext[countHead[upper]]]
call SetUnitFacing(dummy[this], angle[head])
//Remove
set next[prev[this]] = next[this]
set prev[next[this]] = prev[this]
//Add to the Current List
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
set head = countNext[countHead[upper]]
set count[head] = count[head] - 1
endif
//---------------------------
//Update Count Lists
//---------------------------
//Remove from the current Count List
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[upper]
if countNext[cHead] == cHead then
set upper = upper - 1
endif
if count[head] < lower then
set lower = count[head]
endif
else
set bj_lastCreatedUnit = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call PauseUnit(bj_lastCreatedUnit, true)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
if dummyCount < MAX_DUMMY_COUNT then
set this = lastInstance
//For double free protection
set next[this] = -1
set dummy[this] = bj_lastCreatedUnit
static if LIBRARY_Table then
set S.tb[GetHandleId(bj_lastCreatedUnit)] = this
else
call SaveInteger(S.hash, GetHandleId(bj_lastCreatedUnit), 0, this)
endif
set lastInstance = lastInstance + 1
endif
set dummyCount = dummyCount + 1
endif
return bj_lastCreatedUnit
endfunction
function RecycleDummy takes unit u returns nothing
static if LIBRARY_Table then
local integer this = S.tb[GetHandleId(u)]
else
local integer this = LoadInteger(S.hash, GetHandleId(u), 0)
endif
local integer head
local integer cHead
//If the unit is a legit Dummy Unit
if this > 0 and next[this] == -1 then
//Find where to insert based on the list having the least number of units
set head = countNext[countHead[lower]]
set next[this] = head
set prev[this] = prev[head]
set next[prev[this]] = this
set prev[next[this]] = this
//Update Status
call SetUnitFacing(u, angle[head])
call PauseUnit(u, true)
call SetUnitOwner(u, OWNER, false)
static if HIDE_ON_MAP_CORNER then
static if LIBRARY_WorldBounds then
call SetUnitX(u, WorldBounds.maxX)
call SetUnitY(u, WorldBounds.maxY)
else
call SetUnitX(u, Bounds.x)
call SetUnitY(u, Bounds.y)
endif
else
call SetUnitScale(u, 0, 0, 0)
call SetUnitVertexColor(u, 0, 0, 0, 0)
endif
set count[head] = count[head] + 1
//---------------------------
// Update Count Lists
//---------------------------
//Remove
set countNext[countPrev[head]] = countNext[head]
set countPrev[countNext[head]] = countPrev[head]
//Add to the new Count List
set cHead = countHead[count[head]]
set countNext[head] = cHead
set countPrev[head] = countPrev[cHead]
set countNext[countPrev[head]] = head
set countPrev[countNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set cHead = countHead[lower]
if countNext[cHead] == cHead then
set lower = lower + 1
endif
if count[head] > upper then
set upper = count[head]
endif
elseif this == 0 then
call RemoveUnit(u)
debug elseif next[this] != -1 then
debug call BJDebugMsg("|cffffcc00[DummyRecycler]:|r Attempted to recycle a pending/free Dummy Unit.")
endif
endfunction
private function Expires takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
static if LIBRARY_Table then
call RecycleDummy(S.tb.unit[id])
call S.tb.unit.remove(id)
else
call RecycleDummy(LoadUnitHandle(S.hash, id, 0))
call FlushChildHashtable(S.hash, id)
endif
call DestroyTimer(t)
set t = null
endfunction
function DummyAddRecycleTimer takes unit u, real time returns nothing
local timer t = CreateTimer()
static if LIBRARY_Table then
set S.tb.unit[GetHandleId(t)] = u
else
call SaveUnitHandle(S.hash, GetHandleId(t), 0, u)
endif
call TimerStart(t, time, false, function Expires)
set t = null
endfunction
function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
return GetRecycledDummy(x, y, z, angle[countNext[countHead[upper]]])
endfunction
// runtextmacro DUMMY_DEBUG_TOOLS()
endlibrary
library EffectUtils uses TimerUtils, WorldBounds, ListT
//! runtextmacro optional DEFINE_LIST("", "EffectList", "effect")
struct EffectUtils
effect vfx
boolean hide
private static method onTimedLife takes nothing returns nothing
local timer clock = GetExpiredTimer()
local thistype this = GetTimerData(clock)
if this.hide then
call BlzSetSpecialEffectPosition(this.vfx, WorldBounds.maxX, WorldBounds.maxY, 5000)
endif
call DestroyEffect(this.vfx)
set this.vfx = null
call ReleaseTimer(clock)
set clock = null
endmethod
static method addTimedLife takes effect vfx, real dur, boolean hideOnDeath returns nothing
local thistype this = allocate()
local timer clock = NewTimerEx(this)
set this.vfx = vfx
set this.hide = hideOnDeath
call TimerStart(clock, dur, false, function EffectUtils.onTimedLife)
endmethod
static method destroyHide takes effect vfx returns nothing
call BlzSetSpecialEffectPosition(vfx, WorldBounds.maxX, WorldBounds.maxY, 5000)
call DestroyEffect(vfx)
endmethod
static method flashGround takes string path, real x, real y returns nothing
local effect fx = AddSpecialEffect(path, x, y)
call DestroyEffect(fx)
endmethod
static method flashGroundEx takes string path, real x, real y, real scale returns nothing
local effect fx = AddSpecialEffect(path, x, y)
call BlzSetSpecialEffectScale(fx, scale)
call DestroyEffect(fx)
endmethod
static method flashGroundExYaw takes string path, real x, real y, real scale, real yaw returns nothing
local effect fx = AddSpecialEffect(path, x, y)
call BlzSetSpecialEffectScale(fx, scale)
call BlzSetSpecialEffectYaw(fx, yaw)
call DestroyEffect(fx)
endmethod
static method flash takes string path, real x, real y, real z returns nothing
local effect fx = AddSpecialEffect(path, x, y)
call BlzSetSpecialEffectPosition(fx, x, y, z)
call DestroyEffect(fx)
endmethod
static method flashEx takes string path, real x, real y, real z, real scale returns nothing
local effect fx = AddSpecialEffect(path, x, y)
call BlzSetSpecialEffectPosition(fx, x, y, z)
call BlzSetSpecialEffectScale(fx, scale)
call DestroyEffect(fx)
endmethod
static method flashExYaw takes string path, real x, real y, real z, real scale, real yaw returns nothing
local effect fx = AddSpecialEffect(path, x, y)
call BlzSetSpecialEffectPosition(fx, x, y, z)
call BlzSetSpecialEffectScale(fx, scale)
call BlzSetSpecialEffectYaw(fx, yaw)
call DestroyEffect(fx)
endmethod
endstruct
endlibrary
/*****************************************************************************
*
* GetClosestWidget v3.0.1.3
* by Bannar aka Spinnaker
*
* Allows finding closest widget with ease.
*
******************************************************************************
*
* Configurables:
*
* Choose which modules should or should not be implemented.
*
* constant boolean UNITS_MODULE
* constant boolean GROUP_MODULE
* constant boolean ITEMS_MODULE
* constant boolean DESTS_MODULE
*
* Define start and final distances for search iterations within generic GetClosest functions.
* If final value is reached, enumeration is performed on whole map.
*
* constant real START_DISTANCE
* constant real FINAL_DISTANCE
*
******************************************************************************
*
* Functions:
*
* Units:
* | function GetClosestUnit takes real x, real y, boolexpr filter returns unit
* | returns unit closest to coords(x, y)
* |
* | function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
* | returns unit closest to coords(x, y) within range radius
* |
* | function GetClosestUnitInGroup takes real x, real y, group g returns unit
* | returns unit closest to coords(x, y) within group g
*
*
* Group:
* | function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group dest, boolexpr filter returns nothing
* | adds to group dest up to N units, closest to coords(x, y) within range radius
* |
* | function GetClosestNUnitsInGroup takes real x, real y, integer n, group source, group dest returns nothing
* | adds to group dest up to N units, closest to coords(x, y) within group source
*
*
* Items:
* | function GetClosestItem takes real x, real y, boolexpr filter returns item
* | returns item closest to coords(x, y)
* |
* | function GetClosestItemInRange takes real x, real y, real radius, boolexpr filter returns item
* | returns item closest to coords(x, y) within range radius
*
*
* Destructables:
* | function GetClosestDestructable takes real x, real y, boolexpr filter returns destructable
* | returns destructable closest to coords(x, y)
* |
* | function GetClosestDestructableInRange takes real x, real y, real radius, boolexpr filter returns destructable
* | returns destructable closest to coords(x, y) within range radius
*
*
*****************************************************************************/
library GetClosestWidget
globals
private constant boolean UNITS_MODULE = true
private constant boolean GROUP_MODULE = true
private constant boolean ITEMS_MODULE = true
private constant boolean DESTS_MODULE = true
private constant real START_DISTANCE = 800
private constant real FINAL_DISTANCE = 3200
endglobals
globals
private real distance
private real coordX
private real coordY
endglobals
private keyword GroupModule
private function calcDistance takes real x, real y returns real
local real dx = x - coordX
local real dy = y - coordY
return ( (dx*dx + dy*dy) / 10000 )
endfunction
private struct ClosestWidget extends array
static if UNITS_MODULE then
static unit unit
static group group = CreateGroup()
endif
static if GROUP_MODULE then
static if not UNITS_MODULE then
static group group = CreateGroup()
endif
static integer count = 0
static unit array sorted
static real array vector
implement GroupModule
endif
static if ITEMS_MODULE then
static item item
static rect area = Rect(0, 0, 0, 0)
endif
static if DESTS_MODULE then
static destructable destructable
static if not ITEMS_MODULE then
static rect area = Rect(0, 0, 0, 0)
endif
endif
endstruct
private function Defaults takes real x, real y returns nothing
static if UNITS_MODULE then
set ClosestWidget.unit = null
endif
static if ITEMS_MODULE then
set ClosestWidget.item = null
endif
static if DESTS_MODULE then
set ClosestWidget.destructable = null
endif
set distance = 100000
set coordX = x
set coordY = y
endfunction
static if UNITS_MODULE then
//! runtextmacro DEFINE_GCW_UNIT_MODULE()
endif
static if GROUP_MODULE then
//! runtextmacro DEFINE_GCW_GROUP_MODULE()
endif
static if ITEMS_MODULE then
//! runtextmacro DEFINE_GCW_MODULE("Item", "item")
endif
static if DESTS_MODULE then
//! runtextmacro DEFINE_GCW_MODULE("Destructable", "destructable")
endif
//! textmacro DEFINE_GCW_UNIT_MODULE
private function doEnumUnits takes unit u returns nothing
local real dist = calcDistance(GetUnitX(u), GetUnitY(u))
if ( dist < distance ) then
set ClosestWidget.unit = u
set distance = dist
endif
endfunction
private function enumUnits takes nothing returns nothing
call doEnumUnits(GetEnumUnit())
endfunction
function GetClosestUnit takes real x, real y, boolexpr filter returns unit
local real r = START_DISTANCE
local unit u
call Defaults(x, y)
loop
if ( r > FINAL_DISTANCE ) then
call GroupEnumUnitsInRect(ClosestWidget.group, GetWorldBounds(), filter)
exitwhen true
else
call GroupEnumUnitsInRange(ClosestWidget.group, x, y, r, filter)
exitwhen FirstOfGroup(ClosestWidget.group) != null
endif
set r = 2*r
endloop
loop
set u = FirstOfGroup(ClosestWidget.group)
exitwhen u == null
call doEnumUnits(u)
call GroupRemoveUnit(ClosestWidget.group, u)
endloop
return ClosestWidget.unit
endfunction
function GetClosestUnitInRange takes real x, real y, real radius, boolexpr filter returns unit
local unit u
call Defaults(x, y)
if ( radius >= 0 ) then
call GroupEnumUnitsInRange(ClosestWidget.group, x, y, radius, filter)
loop
set u = FirstOfGroup(ClosestWidget.group)
exitwhen u == null
call doEnumUnits(u)
call GroupRemoveUnit(ClosestWidget.group, u)
endloop
endif
return ClosestWidget.unit
endfunction
function GetClosestUnitInGroup takes real x, real y, group g returns unit
call Defaults(x, y)
call ForGroup(g, function enumUnits)
return ClosestWidget.unit
endfunction
//! endtextmacro
//! textmacro DEFINE_GCW_GROUP_MODULE
private module GroupModule
static method doSaveUnits takes unit u returns nothing
set count = count + 1
set sorted[count] = u
set vector[count] = calcDistance(GetUnitX(u), GetUnitY(u))
endmethod
static method saveUnits takes nothing returns nothing
call doSaveUnits(GetEnumUnit())
endmethod
static method sortUnits takes integer lo, integer hi returns nothing
local integer i = lo
local integer j = hi
local real pivot = vector[(lo+hi)/2]
loop
loop
exitwhen vector[i] >= pivot
set i = i + 1
endloop
loop
exitwhen vector[j] <= pivot
set j = j - 1
endloop
exitwhen i > j
set vector[0] = vector[i]
set vector[i] = vector[j]
set vector[j] = vector[0]
set sorted[0] = sorted[i]
set sorted[i] = sorted[j]
set sorted[j] = sorted[0]
set i = i + 1
set j = j - 1
endloop
if ( lo < j ) then
call sortUnits(lo, j)
endif
if ( hi > i ) then
call sortUnits(i, hi)
endif
endmethod
static method fillGroup takes integer n, group dest returns nothing
loop
exitwhen count <= 0 or sorted[count] == null
if ( count <= n ) then
call GroupAddUnit(dest, sorted[count])
endif
set sorted[count] = null
set count = count - 1
endloop
endmethod
endmodule
function GetClosestNUnitsInRange takes real x, real y, real radius, integer n, group dest, boolexpr filter returns nothing
local unit u
call Defaults(x, y)
if ( radius >= 0 )then
call GroupEnumUnitsInRange(ClosestWidget.group, x, y, radius, filter)
loop
set u = FirstOfGroup(ClosestWidget.group)
exitwhen u == null
call ClosestWidget.doSaveUnits(u)
call GroupRemoveUnit(ClosestWidget.group, u)
endloop
call ClosestWidget.sortUnits(1, ClosestWidget.count)
call ClosestWidget.fillGroup(n, dest)
endif
endfunction
function GetClosestNUnitsInGroup takes real x, real y, integer n, group source, group dest returns nothing
local integer i = 0
call Defaults(x, y)
call ForGroup(source, function ClosestWidget.saveUnits)
call ClosestWidget.sortUnits(1, ClosestWidget.count)
call ClosestWidget.fillGroup(n, dest)
endfunction
//! endtextmacro
//! textmacro DEFINE_GCW_MODULE takes NAME, TYPE
private function enum$NAME$s takes nothing returns nothing
local $TYPE$ temp = GetEnum$NAME$()
local real dist = calcDistance(Get$NAME$X(temp), Get$NAME$Y(temp))
if ( dist < distance ) then
set ClosestWidget.$TYPE$ = temp
set distance = dist
endif
set temp = null
endfunction
function GetClosest$NAME$ takes real x, real y, boolexpr filter returns $TYPE$
local real r = START_DISTANCE
call Defaults(x, y)
loop
if ( r > FINAL_DISTANCE ) then
call Enum$NAME$sInRect(GetWorldBounds(), filter, function enum$NAME$s)
exitwhen true
else
call SetRect(ClosestWidget.area, x-r, y-r, x+r, y+r)
call Enum$NAME$sInRect(ClosestWidget.area, filter, function enum$NAME$s)
exitwhen ClosestWidget.$TYPE$ != null
endif
set r = 2*r
endloop
return ClosestWidget.$TYPE$
endfunction
function GetClosest$NAME$InRange takes real x, real y, real radius, boolexpr filter returns $TYPE$
call Defaults(x, y)
if ( radius > 0 ) then
call SetRect(ClosestWidget.area, x-radius, y-radius, x+radius, y+radius)
call Enum$NAME$sInRect(ClosestWidget.area, filter, function enum$NAME$s)
endif
return ClosestWidget.$TYPE$
endfunction
//! endtextmacro
endlibrary
library GetDummy requires TimerUtils, WorldBounds
// Original library by Peppersawce, modified by Spellbound.
/*
API
set u = GetDummy(owner, abilId, level, x, y, duration)
Alternatively, if you need the dummy to be retrieved to face a certain point, you can use
set u = GetDummyAngled(owner, abilId, level, x, y, facing, duration)
*/
globals
private constant integer DUMMY_ID = 'e000'
private constant player NEURTAL_OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
private integer availableDummies = 0
private group dummyGroup = CreateGroup()
private unit currentdummy = null
endglobals
private struct AbilRemoval
integer ab
unit dummy
static method create takes unit dumdum, integer index returns AbilRemoval
local AbilRemoval abrem = AbilRemoval.allocate()
set abrem.dummy = dumdum
set abrem.ab = index
return abrem
endmethod
method destroy takes nothing returns nothing
set this.dummy = null
call this.deallocate()
endmethod
endstruct
private function RecycleDummy takes nothing returns nothing
local AbilRemoval abrem = GetTimerData(GetExpiredTimer())
call UnitRemoveAbility(abrem.dummy, abrem.ab)
if GetOwningPlayer(abrem.dummy) != NEURTAL_OWNER then
call SetUnitOwner(abrem.dummy, NEURTAL_OWNER, false)
endif
call GroupAddUnit(dummyGroup, abrem.dummy)
call SetUnitX(abrem.dummy, WorldBounds.minX)
call SetUnitY(abrem.dummy, WorldBounds.minY)
call PauseUnit(abrem.dummy, true)
call ShowUnit(abrem.dummy, false)
set availableDummies = availableDummies + 1
call ReleaseTimer(GetExpiredTimer())
call abrem.destroy()
endfunction
function GetDummy takes player owner, integer abilId, integer abilLevel, real x, real y, real expiration returns unit
local AbilRemoval abrem
if availableDummies > 0 then
set availableDummies = availableDummies - 1
set currentdummy = BlzGroupUnitAt(dummyGroup, availableDummies)
call GroupRemoveUnit(dummyGroup, currentdummy)
if owner != NEURTAL_OWNER then
call SetUnitOwner(currentdummy, owner, false)
endif
call SetUnitX(currentdummy, x)
call SetUnitY(currentdummy, y)
call PauseUnit(currentdummy, false)
call ShowUnit(currentdummy, true)
else
set currentdummy = CreateUnit(owner, DUMMY_ID, x, y, bj_UNIT_FACING)
endif
call UnitAddAbility(currentdummy, abilId)
call SetUnitAbilityLevel(currentdummy, abilId, abilLevel)
set abrem = AbilRemoval.create(currentdummy, abilId)
call TimerStart(NewTimerEx(abrem), expiration, false, function RecycleDummy)
return currentdummy
endfunction
function GetDummyAngled takes player owner, integer abilId, integer abilLevel, real x, real y, real facing, real expiration returns unit
set currentdummy = GetDummy(owner, abilId, abilLevel, x, y, expiration)
call BlzSetUnitFacingEx(currentdummy, facing)
return currentdummy
endfunction
function GetDummyImmediate takes player owner, integer abilId, integer abilLevel, real x, real y, integer orderId, real expiration returns nothing
call IssueImmediateOrderById(GetDummy(owner, abilId, abilLevel, x, y, expiration), orderId)
endfunction
function GetDummyPoint takes player owner, integer abilId, integer abilLevel, real x, real y, integer orderId, real xTarget, real yTarget, real expiration returns nothing
call IssuePointOrderById(GetDummy(owner, abilId, abilLevel, x, y, expiration), orderId, xTarget, yTarget)
endfunction
function GetDummyTarget takes player owner, integer abilId, integer abilLevel, real x, real y, integer orderId, widget target, real expiration returns nothing
call IssueTargetOrderById(GetDummy(owner, abilId, abilLevel, x, y, expiration), orderId, target)
endfunction
endlibrary
library GetLocZ
globals
private constant location loc = Location(0., 0.)
endglobals
function GetLocZ takes real x, real y returns real
call MoveLocation(loc, x, y)
return GetLocationZ(loc)
endfunction
function GetUnitZ takes unit u returns real
return BlzGetLocalUnitZ(u) + GetUnitFlyHeight(u)
endfunction
endlibrary
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.
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
library Heal uses RegisterNativeEvent, Alloc
/*
Example usage
call Heal.make(healer, patient, amount, index, tag) // index is for event purposes
private static method onHeal takes nothing returns nothing
local Heal heal = Heal.event
if heal.amount > 1. then
// show heal amount
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterNativeEvent(EVENT_PLAYER_UNIT_HEAL_TARGET, function thistype.onHeal)
endmethod
*/
globals
integer EVENT_PLAYER_UNIT_HEAL_TARGET = 0
endglobals
private module Init
private static method onInit takes nothing returns nothing
set EVENT_PLAYER_UNIT_HEAL_TARGET = CreateNativeEvent()
endmethod
endmodule
struct Heal extends array
readonly static Heal event
readonly unit healer
readonly unit patient
readonly real overflow
readonly integer index
readonly string tag
readonly real originalAmount
real amount
private method destroy takes nothing returns nothing
set this.healer = null
set this.patient = null
call this.deallocate()
endmethod
private method fireEvent takes nothing returns nothing
local integer playerId = GetPlayerId(GetOwningPlayer(this.patient))
call TriggerEvaluate(GetNativeEventTrigger(EVENT_PLAYER_UNIT_HEAL_TARGET))
if IsNativeEventRegistered(playerId, EVENT_PLAYER_UNIT_HEAL_TARGET) then
call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, EVENT_PLAYER_UNIT_HEAL_TARGET))
endif
endmethod
static method make takes unit caster, unit mark, real amount, integer index, string tag returns nothing
local thistype this = thistype.allocate()
local real hp = GetWidgetLife(mark)
local real hpMax = I2R(BlzGetUnitMaxHP(mark))
local real hpMissing = hpMax - hp
set this.healer = caster
set this.patient = mark
set this.originalAmount = amount
if hpMissing >= amount then
set this.overflow = 0
set this.amount = amount
else
set this.overflow = amount - hpMissing
set this.amount = hpMissing
endif
set this.index = index
set this.tag = tag
set Heal.event = this
call this.fireEvent()
// Amount can be changed between heals.
call SetWidgetLife(mark, hp + this.amount)
call this.destroy()
set Heal.event = 0
endmethod
implement Init
implement Alloc
endstruct
endlibrary
// scope HealEventTest initializer init
// private function onHeal takes nothing returns nothing
// local Heal heal = Heal.event
// call BJDebugMsg("healEvent index: " + I2S(heal))
// call BJDebugMsg("Amount: " + R2S(heal.amount))
// endfunction
// private function init takes nothing returns nothing
// local unit foot = CreateUnit(Player(0), 'hfoo', -520., -512., 270.)
// call RegisterNativeEvent(EVENT_PLAYER_UNIT_HEAL_TARGET, function onHeal)
// call SetWidgetLife(foot, 1.)
// call Heal.make(foot, foot, 50., 0)
// call Heal.make(foot, foot, 12., 0)
// call Heal.make(foot, foot, 5., 0)
// endfunction
// endscope
native UnitAlive takes unit u returns boolean
native GetUnitGoldCost takes integer id returns integer
native GetUnitWoodCost takes integer id returns integer
native BlzGetUnitMovementType takes unit whichUnit returns integer
native BlzSetHeroPrimaryStat takes unit whichHero, integer whichValue returns nothing
library Interpolation uses Maths, TimerUtils, Table
struct Serp
unit source
timer clock
real rate
real x
real y
real xEnd
real yEnd
private static Table instance
method destroy takes nothing returns nothing
call instance.remove(GetHandleId(this.source))
call ReleaseTimer(this.clock)
set this.source = null
call this.deallocate()
endmethod
static method transfer takes unit oldUnit, unit newUnit returns nothing
local integer id = GetHandleId(oldUnit)
local thistype this = instance[id]
if this != 0 then
call instance.remove(id)
set instance[GetHandleId(newUnit)] = this
set this.source = newUnit
endif
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real gap = distanceBetweenCoords(this.x, this.y, this.xEnd, this.yEnd)
local real delta = gap * ANIMATION_PERIOD
local real ang = Atan2(this.yEnd - this.y, this.xEnd - this.x)
local real dist = delta * this.rate
set this.x = this.x + Cos(ang) * dist
set this.y = this.y + Sin(ang) * dist
call SetUnitX(this.source, this.x)
call SetUnitY(this.source, this.y)
if dist >= gap then
call this.destroy()
endif
endmethod
// recommended rate for a unit would be (GetUnitMoveSpeed(u) * .5) * ANIMATION_PERIOD
static method new takes unit u, real x, real y, real rate returns thistype
local integer id = GetHandleId(u)
local thistype this = instance[id]
if this != 0 then
call this.destroy()
endif
set this = allocate()
set instance[GetHandleId(u)] = this
set this.source = u
set this.rate = rate
set this.x = GetUnitX(u)
set this.y = GetUnitY(u)
set this.xEnd = x
set this.yEnd = y
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
set instance[id] = this
return this
endmethod
static method get takes unit u returns thistype
return instance[GetHandleId(u)]
endmethod
static method has takes unit u returns boolean
return instance.has(GetHandleId(u))
endmethod
private static method onInit takes nothing returns nothing
set instance = Table.create()
endmethod
endstruct
/** Interpolates a unit around a point from one angle to another. */
struct ArcSerp
unit source
unit orbitTarget
real xOrbit
real yOrbit
timer clock
real rate
real radius
real sourceAngle
real arc
private static Table instance
method destroy takes nothing returns nothing
call instance.remove(GetHandleId(this.source))
call ReleaseTimer(this.clock)
set this.source = null
set this.orbitTarget = null
call this.deallocate()
endmethod
static method transfer takes unit oldUnit, unit newUnit returns nothing
local integer id = GetHandleId(oldUnit)
local thistype this = instance[id]
if this != 0 then
call instance.remove(id)
set instance[GetHandleId(newUnit)] = this
set this.source = newUnit
endif
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real delta
local real ang
local real x
local real y
if this.orbitTarget != null then
set this.xOrbit = GetUnitX(this.orbitTarget)
set this.yOrbit = GetUnitY(this.orbitTarget)
endif
set delta = this.arc * ANIMATION_PERIOD
set ang = delta * this.rate
set this.sourceAngle = this.sourceAngle + ang
set x = this.xOrbit + Cos(this.sourceAngle) * this.radius
set y = this.yOrbit + Sin(this.sourceAngle) * this.radius
call SetUnitX(this.source, this.xOrbit)
call SetUnitX(this.source, this.yOrbit)
call BlzSetUnitFacingEx(this.source, this.sourceAngle * bj_RADTODEG)
if ang < 0. then
set ang = -ang
endif
set this.arc = this.arc - ang
// if this.arc <= 0.01745329
if this.arc <= 0.001 then // never quite goes down to zero /shrug
call this.destroy()
endif
endmethod
/** angle values should be in radians
If you want to ArcSerp around a coordinate instead of a unit, set orbitTarget to null.
*/
static method new takes unit u, unit orbitTarget, real x, real y, real startAngle, real endAngle, real radius, real rate returns thistype
local integer id = GetHandleId(u)
local thistype this = instance[id]
if this != 0 then
call this.destroy()
endif
set this.source = u
set this.xOrbit = x
set this.yOrbit = y
set this.rate = rate
set this.radius = radius
set this.sourceAngle = startAngle
set this.orbitTarget = orbitTarget
set this.arc = getArc(startAngle, endAngle, getSpinType(rate))
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
set instance[id] = this
return this
endmethod
static method get takes unit u returns thistype
return instance[GetHandleId(u)]
endmethod
static method has takes unit u returns boolean
return instance.has(GetHandleId(u))
endmethod
private static method onInit takes nothing returns nothing
set instance = Table.create()
endmethod
endstruct
/** Interpolates an effect a point from one angle to another. */
struct ArcSerpEffect
effect source
unit orbitTarget
real xOrbit
real yOrbit
real z
timer clock
real rate
real radius
real sourceAngle
real arc
private static Table instance
method destroy takes nothing returns nothing
call instance.remove(GetHandleId(this.source))
call ReleaseTimer(this.clock)
set this.source = null
set this.orbitTarget = null
call this.deallocate()
endmethod
static method transfer takes effect oldEffect, effect newEffect returns nothing
local integer id = GetHandleId(oldEffect)
local thistype this = instance[id]
if this != 0 then
call instance.remove(id)
set instance[GetHandleId(newEffect)] = this
set this.source = newEffect
endif
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real delta
local real ang
local real x
local real y
if this.orbitTarget != null then
set this.xOrbit = GetUnitX(this.orbitTarget)
set this.yOrbit = GetUnitY(this.orbitTarget)
endif
set delta = this.arc * ANIMATION_PERIOD
set ang = delta * this.rate
set this.sourceAngle = this.sourceAngle + ang
set x = this.xOrbit + Cos(this.sourceAngle) * this.radius
set y = this.yOrbit + Sin(this.sourceAngle) * this.radius
call BlzSetSpecialEffectPosition(this.source, this.xOrbit, this.yOrbit, this.z)
call BlzSetSpecialEffectYaw(this.source, this.sourceAngle)
if ang < 0. then
set ang = -ang
endif
set this.arc = this.arc - ang
// if this.arc <= 0.01745329
if this.arc <= 0.001 then // never quite goes down to zero /shrug
call this.destroy()
endif
endmethod
/** angle values should be in radians
If you want to ArcSerpEffect around a coordinate instead of a unit, set orbitTarget to null.
*/
static method new takes effect fx, unit orbitTarget, real x, real y, real startAngle, real endAngle, real radius, real rate returns thistype
local integer id = GetHandleId(fx)
local thistype this = instance[id]
if this != 0 then
call this.destroy()
endif
set this.source = fx
set this.xOrbit = x
set this.yOrbit = y
set this.z = BlzGetLocalSpecialEffectZ(fx)
set this.rate = rate
set this.radius = radius
set this.sourceAngle = startAngle
set this.orbitTarget = orbitTarget
set this.arc = getArc(startAngle, endAngle, getSpinType(rate))
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
set instance[id] = this
return this
endmethod
static method get takes unit u returns thistype
return instance[GetHandleId(u)]
endmethod
static method has takes unit u returns boolean
return instance.has(GetHandleId(u))
endmethod
private static method onInit takes nothing returns nothing
set instance = Table.create()
endmethod
endstruct
endlibrary
library IsDestructableTree uses optional UnitIndexer /* v1.3.1
*************************************************************************************
*
* Detect whether a destructable is a tree or not.
*
***************************************************************************
*
* Credits
*
* To PitzerMike
* -----------------------
*
* for IsDestructableTree
*
*************************************************************************************
*
* Functions
*
* function IsDestructableTree takes destructable d returns boolean
*
* function IsDestructableAlive takes destructable d returns boolean
*
* function IsDestructableDead takes destructable d returns boolean
*
* function IsTreeAlive takes destructable tree returns boolean
* - May only return true for trees.
*
* function KillTree takes destructable tree returns boolean
* - May only kill trees.
*
*/
globals
private constant integer HARVESTER_UNIT_ID = 'hpea'//* human peasant
private constant integer HARVEST_ABILITY = 'Ahrl'//* ghoul harvest
private constant integer HARVEST_ORDER_ID = 0xD0032//* harvest order ( 852018 )
private constant player NEUTRAL_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
private unit harvester = null
endglobals
function IsDestructableTree takes destructable d returns boolean
//* 851973 is the order id for stunned, it will interrupt the preceding harvest order.
return (IssueTargetOrderById(harvester, HARVEST_ORDER_ID, d)) and (IssueImmediateOrderById(harvester, 851973))
endfunction
function IsDestructableDead takes destructable d returns boolean
return (GetWidgetLife(d) <= 0.405)
endfunction
function IsDestructableAlive takes destructable d returns boolean
return (GetWidgetLife(d) > .405)
endfunction
function IsTreeAlive takes destructable tree returns boolean
return IsDestructableAlive(tree) and IsDestructableTree(tree)
endfunction
function KillTree takes destructable tree returns boolean
if (IsTreeAlive(tree)) then
call KillDestructable(tree)
return true
endif
return false
endfunction
private function Init takes nothing returns nothing
static if LIBRARY_UnitIndexer then//* You may adapt this to your own indexer.
set UnitIndexer.enabled = false
endif
set harvester = CreateUnit(NEUTRAL_PLAYER, HARVESTER_UNIT_ID, 0, 0, 0)
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = true
endif
call UnitAddAbility(harvester, HARVEST_ABILITY)
call UnitAddAbility(harvester, 'Aloc')
call ShowUnit(harvester, false)
endfunction
//* Seriously?
private module Inits
private static method onInit takes nothing returns nothing
call Init()
endmethod
endmodule
private struct I extends array
implement Inits
endstruct
endlibrary
library LineSegmentEnumeration /* v2.2b -- hiveworkshop.com/threads/line-segment-enumeration-v1-1.286552/
Information
¯¯¯¯¯¯¯¯¯¯¯
Allows to enumerate widgets inside a line segment with an offset.
So basicly it will result in a rect, but which is also allowed to be rotated.
Mechanics
¯¯¯¯¯¯¯¯¯
(Issue:)
The problem with normal jass rects is that they aren't defined by 4 points, but only by 4 values: x/y -min/max.
The result is that a jass rect is never rotated, so it's always paralel to the x/y axis.
But when we draw a line from point A to point B we might also create a non-axix-parelel rect, and then
we can't use the normal rect natives from jass anymore to find out if a point is inside the rect.
(Solution:)
To solve this problem the system does following:
jass rect: rectangular defined by 4 values (axis paralel)
custom rect: the real rectangular that is defined by user (not axis parelel)
1. Create a big jass rect that is big enough so we can ensure to enum all widgets that are potentialy inside our custom rect. (Enum_Group)
This Enum_Group will contain all wanted units, but may also contain not wanted units.
2. Construct the custom rect following a form with the same parameters as in this image, but in 2D:
https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/Ellipsoide.svg/800px-Ellipsoide.svg.png
3. Loop through Enum_Group and define each widget's coordinates relative to the center of the custom rect as a 2D vector
4. Get the components of the widget's position vector on the local (rotated) x-y axis of the custom rect
5. Check if the projected lengths (absolute value of components) of the widget's position is less than <a> and <b> as described in the
image linked above.
*/
// --- API ---
//! novjass
struct LineSegment
static constant real MAX_UNIT_COLLISION
static method EnumUnitsEx takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolean checkCollision, boolexpr filter returns nothing
static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolexpr filter returns nothing
static method EnumDestructables takes real ax, real ay, real bx, real by, real offset returns nothing
// after enumerated destructables you have access to:
static integer DestructableCounter // starts with index "0"
static destructable array Destructable
static method EnumItems takes real ax, real ay, real bx, real by, real offset returns nothing
// after enumerated items you have access to:
static integer ItemCounter // starts with index "0"
static destructable array Item
//! endnovjass
// ==== End API ====
struct LineSegment extends array
public static constant real MAX_UNIT_COLLISION = 197.00
private static constant rect RECT = Rect(0, 0, 0, 0)
private static constant group GROUP = CreateGroup()
private static real ox
private static real oy
private static real dx
private static real dy
private static real da
private static real db
private static real ui
private static real uj
private static real wdx
private static real wdy
private static method PrepareRect takes real ax, real ay, real bx, real by, real offset, real offsetCollision returns nothing
local real maxX
local real maxY
local real minX
local real minY
// get center coordinates of rectangle
set ox = 0.5*(ax + bx)
set oy = 0.5*(ay + by)
// get rectangle major axis as vector
set dx = 0.5*(bx - ax)
set dy = 0.5*(by - ay)
// get half of rectangle length (da) and height (db)
set da = SquareRoot(dx*dx + dy*dy)
set db = offset
// get unit vector of the major axis
set ui = dx/da
set uj = dy/da
// Prepare the bounding Jass Rect
set offset = offset + offsetCollision
if ax > bx then
set maxX = ax + offset
set minX = bx - offset
else
set maxX = bx + offset
set minX = ax - offset
endif
if ay > by then
set maxY = ay + offset
set minY = by - offset
else
set maxY = by + offset
set minY = ay - offset
endif
call SetRect(RECT, minX, minY, maxX, maxY)
endmethod
private static method RotateWidgetCoordinates takes widget w returns nothing
// distance of widget from rectangle center in vector form
set wdx = GetWidgetX(w) - ox
set wdy = GetWidgetY(w) - oy
set dx = wdx*ui + wdy*uj // get the component of above vector in the rect's major axis
set dy = wdx*(-uj) + wdy*ui // get the component of above vector in the rect's transverse axis
endmethod
private static method IsWidgetInRect takes widget w returns boolean
call RotateWidgetCoordinates(w)
// Check if the components above are less than half the length and height of the rectangle
// (Square them to compare absolute values)
return dx*dx <= da*da and dy*dy <= db*db
endmethod
private static method IsUnitInRect takes unit u, boolean checkCollision returns boolean
if checkCollision then
call RotateWidgetCoordinates(u)
// Check if the perpendicular distances of the unit from both axes of the rect are less than
// da and db
return IsUnitInRangeXY(u, ox - dy*uj, oy + dy*ui, RAbsBJ(da)) /*
*/ and IsUnitInRangeXY(u, ox + dx*ui, oy + dx*uj, RAbsBJ(db))
endif
return IsWidgetInRect(u)
endmethod
public static method EnumUnitsEx takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolean checkCollision, boolexpr filter returns nothing
local unit u
if checkCollision then
call PrepareRect(ax, ay, bx, by, offset, MAX_UNIT_COLLISION)
else
call PrepareRect(ax, ay, bx, by, offset, 0.00)
endif
call GroupEnumUnitsInRect(GROUP, RECT, filter)
// enum through all tracked units, and check if it's inside bounds
call GroupClear(whichgroup)
loop
set u = FirstOfGroup(GROUP)
exitwhen u == null
if IsUnitInRect(u, checkCollision) then
call GroupAddUnit(whichgroup, u)
endif
call GroupRemoveUnit(GROUP, u)
endloop
endmethod
public static method EnumUnits takes group whichgroup, real ax, real ay, real bx, real by, real offset, boolexpr filter returns nothing
call EnumUnitsEx(whichgroup, ax, ay, bx, by, offset, false, filter)
endmethod
//! textmacro LSE_WIDGET takes TYPE, NAME
public static integer $NAME$Counter = -1
public static $TYPE$ array $NAME$
private static method on$NAME$Filter takes nothing returns nothing
local $TYPE$ t = GetFilter$NAME$()
if IsWidgetInRect(t) then
set $NAME$Counter = $NAME$Counter + 1
set $NAME$[$NAME$Counter] = t
endif
set t = null
endmethod
public static method Enum$NAME$s takes real ax, real ay, real bx, real by, real offset returns nothing
call PrepareRect(ax, ay, bx, by, offset, 0.00)
set $NAME$Counter = -1
call Enum$NAME$sInRect(RECT, Filter(function thistype.on$NAME$Filter), null)
endmethod
//! endtextmacro
//! runtextmacro LSE_WIDGET("destructable", "Destructable")
//! runtextmacro LSE_WIDGET("item", "Item")
endstruct
endlibrary
/*****************************************************************************
*
* List<T> v2.1.2.3
* by Bannar
*
* Doubly-linked list.
*
******************************************************************************
*
* Requirements:
*
* Table by Bribe
* hiveworkshop.com/threads/snippet-new-table.188084/
*
* Alloc - choose whatever you like
* e.g.: by Sevion hiveworkshop.com/threads/snippet-alloc.192348/
*
******************************************************************************
*
* Implementation:
*
* macro DEFINE_LIST takes ACCESS, NAME, TYPE
*
* macro DEFINE_STRUCT_LIST takes ACCESS, NAME, TYPE
*
* ACCESS - encapsulation, choose restriction access
* NAME - name of list type
* TYPE - type of values stored
*
* Implementation notes:
*
* - DEFINE_STRUCT_LIST macro purpose is to provide natural typecasting syntax for struct types.
* - <NAME>Item structs inline directly into hashtable operations thus generate basically no code.
* - Lists defined with DEFINE_STRUCT_LIST are inlined nicely into single create method and single integer array.
*
******************************************************************************
*
* struct API:
*
* struct <NAME>Item:
*
* | <TYPE> data
* | <NAME>Item next
* | <NAME>Item prev
*
*
* General:
*
* | static method create takes nothing returns thistype
* | Default ctor.
* |
* | static method operator [] takes thistype other returns thistype
* | Copy ctor.
* |
* | method destroy takes nothing returns nothing
* | Default dctor.
* |
* | method empty takes nothing returns boolean
* | Checks whether the list is empty.
* |
* | method size takes nothing returns integer
* | Returns size of a list.
*
*
* Access:
*
* | readonly <NAME>Item first
* | readonly <NAME>Item last
* |
* | method front takes nothing returns $TYPE$
* | Retrieves first element.
* |
* | method back takes nothing returns $TYPE$
* | Retrieves last element.
*
*
* Modifiers:
*
* | method clear takes nothing returns nothing
* | Flushes list and recycles its nodes.
* |
* | method push takes $TYPE$ value returns thistype
* | Adds elements to the end.
* |
* | method unshift takes $TYPE$ value returns thistype
* | Adds elements to the front.
* |
* | method pop takes nothing returns thistype
* | Removes the last element.
* |
* | method shift takes nothing returns thistype
* | Removes the first element.
* |
* | method find takes $TYPE$ value returns $NAME$Item
* | Returns the first node which data equals value.
* |
* | method erase takes $NAME$Item node returns boolean
* | Removes node from the list, returns true on success.
* |
* | method removeElem takes $TYPE$ value returns thistype
* | Removes first element that equals value from the list.
*
*
*****************************************************************************/
library ListT requires Table, Alloc
//! runtextmacro DEFINE_LIST("", "IntegerList", "integer")
// Run here any global list types you want to be defined.
//! textmacro_once DEFINE_LIST takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$Item extends array
// No default ctor and dctor due to limited array size
method operator data takes nothing returns $TYPE$
return Table(this).$TYPE$[-1] // hashtable[ node, -1 ] = data
endmethod
method operator data= takes $TYPE$ value returns nothing
set Table(this).$TYPE$[-1] = value
endmethod
method operator next takes nothing returns thistype
return Table(this)[-2] // hashtable[ node, -2 ] = next
endmethod
method operator next= takes thistype value returns nothing
set Table(this)[-2] = value
endmethod
method operator prev takes nothing returns thistype
return Table(this)[-3] // hashtable[ node, -3 ] = prev
endmethod
method operator prev= takes thistype value returns nothing
set Table(this)[-3] = value
endmethod
endstruct
$ACCESS$ struct $NAME$ extends array
readonly $NAME$Item first
readonly $NAME$Item last
private integer count
implement Alloc
private static method setNodeOwner takes $NAME$Item node, $NAME$ owner returns nothing
set Table(node)[-4] = owner
endmethod
private static method getNodeOwner takes $NAME$Item node returns thistype
return Table(node)[-4]
endmethod
private method createNode takes $TYPE$ value returns $NAME$Item
local $NAME$Item node = Table.create()
set node.data = value
call setNodeOwner(node, this) // ownership
return node
endmethod
private method deleteNode takes $NAME$Item node returns nothing
call Table(node).destroy() // also removes ownership
endmethod
static method create takes nothing returns thistype
local thistype this = allocate()
set count = 0
return this
endmethod
method clear takes nothing returns nothing
local $NAME$Item node = first
local $NAME$Item temp
loop // recycle all Table indexes
exitwhen 0 == node
set temp = node.next
call deleteNode(node)
set node = temp
endloop
set first = 0
set last = 0
set count = 0
endmethod
method destroy takes nothing returns nothing
call clear()
call deallocate()
endmethod
method front takes nothing returns $TYPE$
return first.data
endmethod
method back takes nothing returns $TYPE$
return last.data
endmethod
method empty takes nothing returns boolean
return count == 0
endmethod
method size takes nothing returns integer
return count
endmethod
method push takes $TYPE$ value returns thistype
local $NAME$Item node = createNode(value)
if not empty() then
set last.next = node
set node.prev = last
else
set first = node
set node.prev = 0
endif
set last = node
set node.next = 0
set count = count + 1
return this
endmethod
method unshift takes $TYPE$ value returns thistype
local $NAME$Item node = createNode(value)
if not empty() then
set first.prev = node
set node.next = first
else
set last = node
set node.next = 0
endif
set first = node
set node.prev = 0
set count = count + 1
return this
endmethod
method pop takes nothing returns thistype
local $NAME$Item node
if not empty() then
set node = last
set last = last.prev
if last == 0 then
set first = 0
else
set last.next = 0
endif
call deleteNode(node)
set count = count - 1
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::pop failed for instance "+I2S(this)+". List is empty.")
endif
return this
endmethod
method shift takes nothing returns thistype
local $NAME$Item node
if not empty() then
set node = first
set first = first.next
if first == 0 then
set last = 0
else
set first.prev = 0
endif
call deleteNode(node)
set count = count - 1
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::shift failed for instance "+I2S(this)+". List is empty.")
endif
return this
endmethod
static method operator [] takes thistype other returns thistype
local thistype instance = create()
local $NAME$Item node = other.first
loop
exitwhen node == 0
call instance.push(node.data)
set node = node.next
endloop
return instance
endmethod
method find takes $TYPE$ value returns $NAME$Item
local $NAME$Item node = first
loop
exitwhen node == 0 or node.data == value
set node = node.next
endloop
return node
endmethod
method erase takes $NAME$Item node returns boolean
if getNodeOwner(node) == this then // match ownership
if node == first then
call shift()
elseif node == last then
call pop()
else
set node.prev.next = node.next
set node.next.prev = node.prev
call deleteNode(node)
set count = count - 1
endif
return true
debug else
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"$NAME$::erase failed for instance "+I2S(this)+". Attempted to remove invalid node "+I2S(node)+".")
endif
return false
endmethod
method remove takes $NAME$Item node returns boolean
debug call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Method $NAME$::remove is obsolete, use $NAME$::erase instead.")
return erase(node)
endmethod
method removeElem takes $TYPE$ value returns thistype
local $NAME$Item node = find(value)
if node != 0 then
call erase(node)
endif
return this
endmethod
endstruct
//! endtextmacro
//! textmacro_once DEFINE_STRUCT_LIST takes ACCESS, NAME, TYPE
$ACCESS$ struct $NAME$Item extends array
// Cannot inherit methods via delegate due to limited array size
method operator data takes nothing returns $TYPE$
return IntegerListItem(this).data
endmethod
method operator data= takes $TYPE$ value returns nothing
set IntegerListItem(this).data = value
endmethod
method operator next takes nothing returns thistype
return IntegerListItem(this).next
endmethod
method operator next= takes thistype value returns nothing
set IntegerListItem(this).next = value
endmethod
method operator prev takes nothing returns thistype
return IntegerListItem(this).prev
endmethod
method operator prev= takes thistype value returns nothing
set IntegerListItem(this).prev = value
endmethod
endstruct
$ACCESS$ struct $NAME$ extends array
private delegate IntegerList parent
static method create takes nothing returns thistype
local thistype this = IntegerList.create()
set parent = this
return this
endmethod
method front takes nothing returns $TYPE$
return parent.front()
endmethod
method back takes nothing returns $TYPE$
return parent.back()
endmethod
endstruct
//! endtextmacro
endlibrary
library Maths
globals
constant real REAL_MAX = 99999. * 99999.
constant real PI = bj_PI
constant real TAU = bj_PI * 2
constant real PIHALF = bj_PI / 2
constant integer SPIN_ANTICLOCKWISE = 0
constant integer SPIN_CLOCKWISE = 1
constant integer SPIN_NONE = 3
private integer currentSpin = 0
endglobals
function Ceiling takes real r returns integer
local integer i = R2I(r)
if r == i then
return i
endif
if r > 0 then
return i + 1
endif
return i
endfunction
function Sign takes real val returns integer
if val > 0 then
return 1
else
if val < 0 then
return -1
endif
endif
return 0
endfunction
function distanceBetweenCoords takes real x, real y, real xx, real yy returns real
return SquareRoot((xx - x) * (xx - x) + (yy - y) * (yy - y))
endfunction
function randomiseX takes real val, real lowBound, real highBound returns real
return val + Cos(GetRandomReal(0., TAU)) * GetRandomReal(lowBound, highBound)
endfunction
function randomiseY takes real val, real lowBound, real highBound returns real
return val + Cos(GetRandomReal(0., TAU)) * GetRandomReal(lowBound, highBound)
endfunction
/** if your value is positive, this returns a anticlockwise spin. Otherwise the spin is clockwise.*/
function getSpinType takes real val returns integer
if val == 0. then
return SPIN_NONE
elseif val > 0. then
return SPIN_ANTICLOCKWISE
endif
return SPIN_CLOCKWISE
endfunction
function getSpinTypeString takes integer spin returns string
if spin == SPIN_NONE then
return "No spin"
elseif spin == SPIN_ANTICLOCKWISE then
return "Anticlockwise"
endif
return "Clockwise"
endfunction
/** Use this function to find out what the angle between two points is, given a SPIN_CLOCKWISE or SPIN_ANTICLOCKWISE motion.
Returns angles in radians. You must normalise your angles to TAU, unless you want to get an arc that spans multiple circles.*/
function getArc takes real sourceAngle, real targetAngle, integer spin returns real
local real aCurrent
local real aFinal
local real arc
local real finalArc
if sourceAngle == targetAngle or spin == SPIN_NONE then
return 0.
endif
set aCurrent = sourceAngle
set aFinal = targetAngle
if aCurrent < 0. then
set aCurrent = aCurrent + TAU
endif
if aFinal < 0. then
set aFinal = aFinal + TAU
endif
set arc = 0.
set finalArc = 0.
set arc = aCurrent - aFinal
if spin == SPIN_ANTICLOCKWISE then
// A negative arc indicates the anticlocwise direction
if arc < 0. then
set finalArc = -arc
else
set finalArc = TAU - arc
endif
else
// A positive arc indicates the clockwise direction
if arc > 0. then
set finalArc = arc
else
set finalArc = TAU + arc
endif
endif
// call BJDebugMsg("Arc: " + R2S(Rad2Deg(finalArc)))
return finalArc
endfunction
function getCurrentSpin takes nothing returns integer
return currentSpin
endfunction
/** This will return an the smaller arc in radians between two angles. Use getCurrenSpin() after calling this function to find the spin direction. */
function getSmallArc takes real sourceAngle, real targetAngle returns real
local real aCurrent
local real aFinal
local real arc
local real finalArc
local integer direction
if sourceAngle == targetAngle then
set currentSpin = SPIN_NONE
return 0.
endif
set aCurrent = sourceAngle
set aFinal = targetAngle
if aCurrent < 0. then
set aCurrent = aCurrent + TAU
endif
if aFinal < 0. then
set aFinal = aFinal + TAU
endif
set arc = aCurrent - aFinal
if arc == PI then
set currentSpin = SPIN_NONE
return arc
endif
/*
if number is negative* and below PI, small arc is anticlockwise
if number is negative* and above PI, small arc is clockwise
if number is positive and below PI, small arc is clockwise
if number is positive and above PI, small arc is anticlockwise
*negative numbers are inverted into positives
*/
// check negatives
if arc < 0. then
set arc = -arc
if arc < PI then
set direction = SPIN_ANTICLOCKWISE
else
set arc = TAU - arc
set direction = SPIN_CLOCKWISE
endif
// check positives
else
if arc < PI then
set direction = SPIN_CLOCKWISE
else
set arc = TAU - arc
set direction = SPIN_ANTICLOCKWISE
endif
endif
// call BJDebugMsg("Arc: " + R2S(Rad2Deg(arc)))
// call BJDebugMsg("Direction: " + getSpinTypeString(direction))
// call BJDebugMsg("--")
set currentSpin = direction
return arc
endfunction
/** This will return the larger arc between source and targetAngle. Use getCurrenSpin() after calling this function to find the spin direction. */
function getLargeArc takes real sourceAngle, real targetAngle returns real
local real aCurrent
local real aFinal
local real arc
local real finalArc
local integer direction
if sourceAngle == targetAngle then
set currentSpin = SPIN_NONE
return 0.
endif
set aCurrent = sourceAngle
set aFinal = targetAngle
if aCurrent < 0. then
set aCurrent = aCurrent + TAU
endif
if aFinal < 0. then
set aFinal = aFinal + TAU
endif
set arc = aCurrent - aFinal
if arc == PI then
set currentSpin = SPIN_NONE
return arc
endif
/*
if number is negative* and below PI, large arc is clockwise
if number is negative* and above PI, large arc is anticlockwise
if number is positive and below PI, large arc is anticlockwise
if number is positive and above PI, large arc is clockwise
*negative numbers are inverted into positives
*/
// check negatives
if arc < 0. then
set arc = -arc
if arc > PI then
set direction = SPIN_ANTICLOCKWISE
else
set arc = TAU - arc
set direction = SPIN_CLOCKWISE
endif
// check positives
else
if arc > PI then
set direction = SPIN_CLOCKWISE
else
set arc = TAU - arc
set direction = SPIN_ANTICLOCKWISE
endif
endif
// call BJDebugMsg("Arc: " + R2S(Rad2Deg(arc)))
// call BJDebugMsg("Direction: " + getSpinTypeString(direction))
// call BJDebugMsg("--")
set currentSpin = direction
return arc
endfunction
function getArcToDegrees takes real sourceAngle, real targetAngle, integer direction returns real
return getArc(sourceAngle, targetAngle, direction) * bj_RADTODEG
endfunction
/** checks whether (x/y) is to the left or right of a line drawn from a to b
if the return value is > 0, (x/y) lies on the left [of the line drawn from a to b]
if the return value is < 0, (x/y) lies on the right [of the line drawn from a to b]
if the return value is == 0, (x/y) lies on the line itself
*/
function getPositionTo takes real x, real y, real ax, real ay, real bx, real by returns integer
return Sign((bx - ax) * (y - ay) - (by - ay) * (x - ax))
endfunction
function randomAngleRad takes nothing returns real
return GetRandomReal(0, TAU)
endfunction
endlibrary
//============================================================================
// OrderEvent by Bribe, special thanks to Nestharus and Azlier, version 3.0.1.1
//
// API
// ---
// RegisterOrderEvent(integer orderId, code eventFunc)
// RegisterAnyOrderEvent(code eventFunc) //Runs for point/target/instant for any order
//
// Requires
// --------
// RegisterPlayerUnitEvent: http://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
// Table: http://www.hiveworkshop.com/forums/showthread.php?t=188084
//
library OrderEvent requires RegisterPlayerUnitEvent, Table
globals
private Table t = 0
endglobals
//============================================================================
function RegisterAnyOrderEvent takes code c returns nothing
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, c)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, c)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, c)
/*static if RPUE_VERSION_NEW then
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, c)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, c)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, c)
else
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, c)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_TARGET_ORDER, c)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_POINT_ORDER, c)
endif*/
endfunction
//============================================================================
private function OnOrder takes nothing returns nothing
call TriggerEvaluate(t.trigger[GetIssuedOrderId()])
endfunction
//============================================================================
function RegisterOrderEvent takes integer orderId, code c returns nothing
local trigger trig
if integer(t) == 0 then
set t = Table.create()
call RegisterAnyOrderEvent(function OnOrder)
endif
set trig = t.trigger[orderId]
if trig == null then
set trig = CreateTrigger()
set t.trigger[orderId] = trig
endif
call TriggerAddCondition(trig, Filter(c))
set trig = null
endfunction
endlibrary
library PathingLib
// Configuration
globals
private constant integer PATH_CHECKER = 'hPLP'
private constant integer FLY_CHECKER = 'hPLF'
private constant integer WALK_CHECKER = 'hPLW'
private constant integer BUILD_CHECKER = 'hPLB'
private constant integer SHALLOWS_CHECKER = 'hPLL'
private constant integer DEEPS_CHECKER = 'hPLD'
private constant integer SWIM_CHECKER = 'hPLS'
private constant player DUMMY_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
endglobals
/*
Pathing Library v1.6
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Description
¯¯¯¯¯¯¯¯¯¯¯
Allows you to detect all pathability types:
walkablility, flyability, and buildability.
Warning!
Please keep informed that this system is more sensitive than
any other walkability checker systems out there, as it also
detects pathing map generated by normal units as well.
API
¯¯¯
| function IsTerrainFlyable takes real x, real y returns boolean
| function IsTerrainWalkable takes real x, real y returns boolean
| function IsTerrainBuildable takes real x, real y returns boolean
| function IsTerrainShallowWater takes real x, real y returns boolean
| function IsTerrainDeepWater takes real x, real y returns boolean
| function IsTerrainSwimmable takes real x, real y returns boolean
How to import
¯¯¯¯¯¯¯¯¯¯¯¯¯
- Copy Fly, Walk, Build, and Path Checker at object editor (Unit).
- Make sure Path Checker is able to build Fly, Walk, and Build Checker
(at object editor>unit>Path Checker>"Techtree - Structures built")
- Configure this system correctly.
Link: hiveworkshop.com/forums/spells-569/pathing-type-v1-2-a-263230/
*/
globals
private unit PathChecker
endglobals
function IsTerrainFlyable takes real x, real y returns boolean
return IssueBuildOrderById(PathChecker, FLY_CHECKER, x, y)
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
return IssueBuildOrderById(PathChecker, WALK_CHECKER, x, y)
endfunction
function IsTerrainBuildable takes real x, real y returns boolean
return IssueBuildOrderById(PathChecker, BUILD_CHECKER, x, y)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
return IssueBuildOrderById(PathChecker, SHALLOWS_CHECKER, x, y)
endfunction
function IsTerrainDeepWater takes real x, real y returns boolean
return (not IsTerrainShallowWater(x, y)) and IssueBuildOrderById(PathChecker, DEEPS_CHECKER, x, y)
endfunction
function IsTerrainSwimmable takes real x, real y returns boolean
return IssueBuildOrderById(PathChecker, SWIM_CHECKER, x, y)
endfunction
private module Init
private static method onInit takes nothing returns nothing
call init()
endmethod
endmodule
private struct InitStruct extends array
private static method init takes nothing returns nothing
set PathChecker = CreateUnit(DUMMY_PLAYER, PATH_CHECKER, 0, 0, 0)
call UnitRemoveAbility(PathChecker, 'Amov')
call ShowUnit(PathChecker, false)
if GetLocalPlayer() == DUMMY_PLAYER then
call FogEnable(false)
endif
endmethod
implement Init
endstruct
endlibrary
library PlayerUtils
globals
player array Play
force IS_PLAYING = CreateForce()
player array ITERATOR_User
integer ITERATOR_UserMAX = 0
player array ITERATOR_Computer
integer ITERATOR_ComputerMAX = 0
endglobals
private module Init
private static method onInit takes nothing returns nothing
local integer i = -1
local integer iUser = -1
local integer iComputer = -1
loop
set i = i + 1
exitwhen i > bj_MAX_PLAYER_SLOTS
set Play[i] = Player(i)
if GetPlayerSlotState(Play[i]) == PLAYER_SLOT_STATE_PLAYING then
call ForceAddPlayer(IS_PLAYING, Play[i])
if GetPlayerController(Play[i]) == MAP_CONTROL_USER then
set iUser = iUser + 1
set ITERATOR_User[iUser] = Play[i]
elseif GetPlayerController(Play[i]) == MAP_CONTROL_COMPUTER then
set iComputer = iComputer + 1
set ITERATOR_Computer[iComputer] = Play[i]
endif
endif
endloop
set ITERATOR_UserMAX = iUser
set ITERATOR_ComputerMAX = iComputer
endmethod
endmodule
struct PlayerUtilsInit
implement Init
endstruct
endlibrary
library PickRandomUnit uses GroupUtils
globals
boolexpr FILTER_ALLY_BOTH_ORGANIC_WOUNDED = null
boolexpr FILTER_ALLY_BOTH_ORGANIC_NOTFULLMANA = null
boolexpr FILTER_ALLY_BOTH_ORGANIC_WOUNDED_OR_NOTFULLMANA = null
// both
boolexpr FILTER_ENEMY_BOTH = null
boolexpr FILTER_ENEMY_BOTH_NOTIMMUNE = null
boolexpr FILTER_ENEMY_BOTH_NOTETHEREAL = null
boolexpr FILTER_ENEMY_BOTH_ORGANIC = null
boolexpr FILTER_ENEMY_BOTH_ORGANIC_NOTIMMUNE = null
boolexpr FILTER_ENEMY_BOTH_ORGANIC_NOTETHEREAL = null
boolexpr FILTER_ENEMY_BOTH_MECHANICAL = null
boolexpr FILTER_ENEMY_BOTH_MECHANICAL_NOTIMMUNE = null
boolexpr FILTER_ENEMY_BOTH_MECHANICAL_NOTETHEREAL = null
// ground
boolexpr FILTER_ENEMY_GROUND = null
boolexpr FILTER_ENEMY_GROUND_NOTIMMUNE = null
boolexpr FILTER_ENEMY_GROUND_NOTETHEREAL = null
boolexpr FILTER_ENEMY_GROUND_ORGANIC = null
boolexpr FILTER_ENEMY_GROUND_ORGANIC_NOTIMMUNE = null
boolexpr FILTER_ENEMY_GROUND_ORGANIC_NOTETHEREAL = null
boolexpr FILTER_ENEMY_GROUND_MECHANICAL = null
boolexpr FILTER_ENEMY_GROUND_MECHANICAL_NOTIMMUNE = null
boolexpr FILTER_ENEMY_GROUND_MECHANICAL_NOTETHEREAL = null
// flying
boolexpr FILTER_ENEMY_FLYING = null
boolexpr FILTER_ENEMY_FLYING_NOTIMMUNE = null
boolexpr FILTER_ENEMY_FLYING_NOTETHEREAL = null
boolexpr FILTER_ENEMY_FLYING_ORGANIC = null
boolexpr FILTER_ENEMY_FLYING_ORGANIC_NOTIMMUNE = null
boolexpr FILTER_ENEMY_FLYING_ORGANIC_NOTETHEREAL = null
boolexpr FILTER_ENEMY_FLYING_MECHANICAL = null
boolexpr FILTER_ENEMY_FLYING_MECHANICAL_NOTIMMUNE = null
boolexpr FILTER_ENEMY_FLYING_MECHANICAL_NOTETHEREAL = null
endglobals
struct PickRandomUnit
readonly static unit RANDO = null
private static unit UNIT = null
private static unit IGNORE = null
private static group G_IGNORE = null
private static player OWNER
private static real X
private static real Y
private static real RADIUS
private static unit array Units
private static method getRandom takes nothing returns unit
// BlzGroupUnitAt picks out the Xth unit from a unit group. This function is zero-indexed,
// however, so the first unit will use index 0, the second unit will use index 1, and so on.
local integer size = BlzGroupGetSize(ENUM_GROUP)
if size > 0 then
set RANDO = BlzGroupUnitAt(ENUM_GROUP, GetRandomInt(0, size-1))
call GroupClear(ENUM_GROUP)
else
set RANDO = null
endif
return RANDO
endmethod
//================================================================
//
// API
//
//================================================================
static method clearValues takes nothing returns nothing
set OWNER = null
set RADIUS = 0.
set IGNORE = null
set G_IGNORE = null
set X = 0.
set Y = 0.
endmethod
static method setValues takes player owner, real x, real y, real radius, unit ignore, group g returns nothing
set OWNER = owner
set RADIUS = radius
set IGNORE = ignore
set G_IGNORE = g
set X = x
set Y = y
endmethod
static method find takes player owner, real x, real y, real radius, boolexpr filter returns unit
call thistype.setValues(owner, x, y, radius, null, null)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, radius + MAX_COLLISION_SIZE, filter)
call thistype.clearValues()
return thistype.getRandom()
endmethod
static method findWithIgnore takes player owner, real x, real y, real radius, boolexpr filter, unit ignore returns unit
call thistype.setValues(owner, x, y, radius, ignore, null)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, radius + MAX_COLLISION_SIZE, filter)
call thistype.clearValues()
return thistype.getRandom()
endmethod
static method findWithIgnoreGroup takes player owner, real x, real y, real radius, boolexpr filter, group ignoreGroup returns unit
call thistype.setValues(owner, x, y, radius, null, ignoreGroup)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, radius + MAX_COLLISION_SIZE, filter)
call thistype.clearValues()
return thistype.getRandom()
endmethod
//================================================================
//
// FILTERS (create new ones as needed)
//
//================================================================
// ally - both - organic - wounded
private static method filter_allyBothOrganicWounded takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and GetWidgetLife(UNIT) < BlzGetUnitMaxHP(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// ally - both - organic - not full mana
private static method filter_allyBothOrganicNotFullMana takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and GetUnitState(UNIT, UNIT_STATE_MANA) < BlzGetUnitMaxMana(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// ally - both - organic - wounded OR not full mana
private static method filter_allyBothOrganicWoundedORNotFullMana takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and ((GetWidgetLife(UNIT) < BlzGetUnitMaxHP(UNIT)) or (GetUnitState(UNIT, UNIT_STATE_MANA) < BlzGetUnitMaxMana(UNIT))) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// BOTH
// enemy - both
private static method filter_enemyBoth takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - not immune
private static method filter_enemyBothNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - not ethereal
private static method filter_enemyBothNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - organic
private static method filter_enemyBothOrganic takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - organic - not immune
private static method filter_enemyBothOrganicNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - organic - not ethereal
private static method filter_enemyBothOrganicNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - mechanical
private static method filter_enemyBothMechanical takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - mechanical - not immune
private static method filter_enemyBothMechanicalNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - both - mechanical - not ethereal
private static method filter_enemyBothMechanicalNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// GROUND
// enemy - ground
private static method filter_enemyGround takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - not immune
private static method filter_enemyGroundNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - not ethereal
private static method filter_enemyGroundNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - organic
private static method filter_enemyGroundOrganic takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - organic - not immune
private static method filter_enemyGroundOrganicNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - organic - not ethereal
private static method filter_enemyGroundOrganicNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - mechanical
private static method filter_enemyGroundMechanical takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - mechanical - not immune
private static method filter_enemyGroundMechanicalNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - ground - mechanical - not ethereal
private static method filter_enemyGroundMechanicalNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// FLYING
// enemy - flying
private static method filter_enemyFlying takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - not immune
private static method filter_enemyFlyingNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - not ethereal
private static method filter_enemyFlyingNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not IsUnitType(UNIT, UNIT_TYPE_FLYING) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - organic
private static method filter_enemyFlyingOrganic takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - organic - not immune
private static method filter_enemyFlyingOrganicNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - organic - not ethereal
private static method filter_enemyFlyingOrganicNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and not IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - mechanical
private static method filter_enemyFlyingMechanical takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - mechanical - not immune
private static method filter_enemyFlyingMechanicalNotImmune takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_MAGIC_IMMUNE) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
// enemy - flying - mechanical - not ethereal
private static method filter_enemyFlyingMechanicalNotEthereal takes nothing returns boolean
set UNIT = GetFilterUnit()
return UnitAlive(UNIT) and UNIT != IGNORE and not IsUnitAlly(UNIT, OWNER) and IsUnitInRangeXY(UNIT, X, Y, RADIUS) and IsUnitType(UNIT, UNIT_TYPE_FLYING) and IsUnitType(UNIT, UNIT_TYPE_MECHANICAL) and not IsUnitType(UNIT, UNIT_TYPE_ETHEREAL) and not BlzIsUnitInvulnerable(UNIT) and not IsUnitInGroup(UNIT, G_IGNORE)
endmethod
//================================================================
//
// INITIALISER
//
//================================================================
private static method onInit takes nothing returns nothing
set FILTER_ALLY_BOTH_ORGANIC_WOUNDED = Condition(function thistype.filter_allyBothOrganicWounded)
set FILTER_ALLY_BOTH_ORGANIC_NOTFULLMANA = Condition(function thistype.filter_allyBothOrganicNotFullMana)
set FILTER_ALLY_BOTH_ORGANIC_WOUNDED_OR_NOTFULLMANA = Condition(function thistype.filter_allyBothOrganicWoundedORNotFullMana)
// both
set FILTER_ENEMY_BOTH = Condition(function thistype.filter_enemyBoth)
set FILTER_ENEMY_BOTH_NOTIMMUNE = Condition(function thistype.filter_enemyBothNotImmune)
set FILTER_ENEMY_BOTH_NOTETHEREAL = Condition(function thistype.filter_enemyBothNotEthereal)
set FILTER_ENEMY_BOTH_ORGANIC = Condition(function thistype.filter_enemyBothOrganic)
set FILTER_ENEMY_BOTH_ORGANIC_NOTIMMUNE = Condition(function thistype.filter_enemyBothOrganicNotImmune)
set FILTER_ENEMY_BOTH_ORGANIC_NOTETHEREAL = Condition(function thistype.filter_enemyBothOrganicNotEthereal)
set FILTER_ENEMY_BOTH_MECHANICAL = Condition(function thistype.filter_enemyBothMechanical)
set FILTER_ENEMY_BOTH_MECHANICAL_NOTIMMUNE = Condition(function thistype.filter_enemyBothMechanicalNotImmune)
set FILTER_ENEMY_BOTH_MECHANICAL_NOTETHEREAL = Condition(function thistype.filter_enemyBothMechanicalNotEthereal)
// ground
set FILTER_ENEMY_GROUND = Condition(function thistype.filter_enemyGround)
set FILTER_ENEMY_GROUND_NOTIMMUNE = Condition(function thistype.filter_enemyGroundNotImmune)
set FILTER_ENEMY_GROUND_NOTETHEREAL = Condition(function thistype.filter_enemyGroundNotEthereal)
set FILTER_ENEMY_GROUND_ORGANIC = Condition(function thistype.filter_enemyGroundOrganic)
set FILTER_ENEMY_GROUND_ORGANIC_NOTIMMUNE = Condition(function thistype.filter_enemyGroundOrganicNotImmune)
set FILTER_ENEMY_GROUND_ORGANIC_NOTETHEREAL = Condition(function thistype.filter_enemyGroundOrganicNotEthereal)
set FILTER_ENEMY_GROUND_MECHANICAL = Condition(function thistype.filter_enemyGroundMechanical)
set FILTER_ENEMY_GROUND_MECHANICAL_NOTIMMUNE = Condition(function thistype.filter_enemyGroundMechanicalNotImmune)
set FILTER_ENEMY_GROUND_MECHANICAL_NOTETHEREAL = Condition(function thistype.filter_enemyGroundMechanicalNotEthereal)
// flying
set FILTER_ENEMY_FLYING = Condition(function thistype.filter_enemyFlying)
set FILTER_ENEMY_FLYING_NOTIMMUNE = Condition(function thistype.filter_enemyFlyingNotImmune)
set FILTER_ENEMY_FLYING_NOTETHEREAL = Condition(function thistype.filter_enemyFlyingNotEthereal)
set FILTER_ENEMY_FLYING_ORGANIC = Condition(function thistype.filter_enemyFlyingOrganic)
set FILTER_ENEMY_FLYING_ORGANIC_NOTIMMUNE = Condition(function thistype.filter_enemyFlyingOrganicNotImmune)
set FILTER_ENEMY_FLYING_ORGANIC_NOTETHEREAL = Condition(function thistype.filter_enemyFlyingOrganicNotEthereal)
set FILTER_ENEMY_FLYING_MECHANICAL = Condition(function thistype.filter_enemyFlyingMechanical)
set FILTER_ENEMY_FLYING_MECHANICAL_NOTIMMUNE = Condition(function thistype.filter_enemyFlyingMechanicalNotImmune)
set FILTER_ENEMY_FLYING_MECHANICAL_NOTETHEREAL = Condition(function thistype.filter_enemyFlyingMechanicalNotEthereal)
endmethod
endstruct
endlibrary
library PreloadAbilities initializer onInit
private function onInit takes nothing returns nothing
local unit dummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'e000', 0., 0., 0.)
// preload things here
call UnitAddAbilityBJ( 'A018', dummy)
// end preloading
call RemoveUnit(dummy)
set dummy = null
endfunction
endlibrary
library Recolour uses TimerUtils, RegisterNativeEvent optional UnitData
/*
API
call Recolour.fadeIn(whichUnit, duration)
^ will take a unit from alpha 0 to alpha 255 over duration. Returns a Recolour instance.
call Recolour.fadeOut(whichUnit, duration)
^ will take a unit from alpha 255 to alpha 0 over duration. Returns a Recolour instance.
call Recolour.new(whichUnit, startingRed, endingRed, startingGreen, endingGreen, startingBlue, endingBlue, startingAlpha, endingAlpha, duration)
^ core constructor with the ability to modify coloration and alpha by specific channels, over time.
call Recolour.fadeInEx(whichUnit, duration, customId, customString)
^ will take a unit from alpha 0 to alpha 255 over duration. customId/customString is used as part of an event response to provide more information.
- Setting the customString to "Kill" will automatically kill the unit when it the Recolour ends.
- Setting the customString to "Remove" will automatically remove the unit from the game when Recolour ends.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
call Recolour.fadeOutEx(whichUnit, duration, customId, customString)
^ will take a unit from alpha 255 to alpha 0 over duration. customId/customString is used as part of an event response to provide more information.
- Setting the customString to "Kill" will automatically kill the unit when it the Recolour ends.
- Setting the customString to "Remove" will automatically remove the unit from the game when Recolour ends.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
call Recolour.newEx(whichUnit, startingRed, endingRed, startingGreen, endingGreen, startingBlue, endingBlue, startingAlpha, endingAlpha, duration, customId, customString)
^ core constructor with the ability to modify coloration and alpha by specific channels, over time. customId/customString is used as part of an event response to provide
more information.
- Setting a customString to "Remove" will automatically destroy on alpha 0. Returns a Recolour instance.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
EVENTS
call RegisterNativeEvent(EVENT_PLAYER_UNIT_RECOLOUR, function callback)
^ register a Recolour event to determine what happens to your unit when the Recolour is done.
Event Responses
local unit u = Recolour.getEventUnit()
^ returns the unit itself
local integer red = Recolour.getEventRed()
local integer green = Recolour.getEventGreen()
local integer blue = Recolour.getEventBlue()
local integer alpha = Recolour.getEventAlpha()
^ returns the various red/green/blue/alpha values
local integer customId = Recolour.getEventCustomId()
local integer customString = Recolour.getEventCustomString()
^ returns any input custom integer or strong. IMPORTANT: One of those MUST BE SET to not zero or not "" / "Kill" / "Remove" for events to fire, to avoid unnecessary overhead.
*/
globals
integer EVENT_PLAYER_UNIT_RECOLOUR = 0
endglobals
struct Recolour
private static unit eventRecolourUnit = null
private static integer eventRecolourRed = 0
private static integer eventRecolourGreen = 0
private static integer eventRecolourBlue = 0
private static integer eventRecolourAlpha = 0
private static integer eventCustomId = 0
private static string eventCustomString = ""
private static thistype array instance
private static thistype array recolourList
private static integer count = 0
private unit source
private integer index
private integer customId
private string customString
static if LIBRARY_UnitData then
private UnitData uData
endif
// red
readonly integer r
readonly integer rDisp
readonly integer rCurrent
private real rSpeed
private real rProgress
readonly boolean rDone
// green
readonly integer g
readonly integer gDisp
readonly integer gCurrent
private real gSpeed
private real gProgress
readonly boolean gDone
// blue
readonly integer b
readonly integer bDisp
readonly integer bCurrent
private real bSpeed
private real bProgress
readonly boolean bDone
// alpha
readonly integer a
readonly integer aDisp
readonly integer aCurrent
private real aSpeed
private real aProgress
readonly boolean aDone
// EVENT RESPONSES =================================================
static method getEventUnit takes nothing returns unit
return eventRecolourUnit
endmethod
static method getEventRed takes nothing returns integer
return eventRecolourAlpha
endmethod
static method getEventGreen takes nothing returns integer
return eventRecolourAlpha
endmethod
static method getEventBlue takes nothing returns integer
return eventRecolourAlpha
endmethod
static method getEventAlpha takes nothing returns integer
return eventRecolourAlpha
endmethod
// for storing specific data like a struct instance
static method getEventCustomId takes nothing returns integer
return eventCustomId
endmethod
// for storing specific data like a simple instruction
static method getEventCustomString takes nothing returns string
return eventCustomString
endmethod
// ================================================================
method destroy takes nothing returns nothing
local unit recolourUnit = eventRecolourUnit
// remove this from the list
set recolourList[this.index] = recolourList[count]
set recolourList[count].index = this.index
set recolourList[count] = 0
set count = count - 1
// null agent(s) and instance tracker
set instance[GetUnitUserData(this.source)] = 0
// event parameters
if not (this.customId == 0 and (this.customString == "" or this.customString == "Kill" or this.customString == "Remove")) then
set eventRecolourUnit = this.source
set eventRecolourRed = this.rCurrent
set eventRecolourGreen = this.gCurrent
set eventRecolourBlue = this.bCurrent
set eventRecolourAlpha = this.aCurrent
set eventCustomId = this.customId
set eventCustomString = this.customString
call TriggerEvaluate(GetNativeEventTrigger(EVENT_PLAYER_UNIT_RECOLOUR))
set eventRecolourUnit = recolourUnit
set eventRecolourRed = 0
set eventRecolourGreen = 0
set eventRecolourBlue = 0
set eventRecolourAlpha = 0
set eventCustomId = 0
set eventCustomString = ""
elseif this.customString == "Kill" then
call UnitApplyTimedLife(this.source, 'BTLF', .01)
elseif this.customString == "Remove" then
call RemoveUnit(this.source)
endif
set this.source = null
set recolourUnit = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local integer i = 0
local thistype this
local real s
loop
set i = i + 1
exitwhen i > count
set this = recolourList[i]
// red
if not this.rDone then
set this.rProgress = this.rProgress + this.rSpeed
set s = this.rProgress
// set this.rCurrent = R2I(this.r + (this.rDisp) * (s * s * s * (s * (s * 6 - 15) + 10))) //smootherstep
set this.rCurrent = R2I(this.r + (this.rDisp) * (s * s * (3 - 2 * s))) //smoothstep
if this.rProgress >= 1. then
set this.rDone = true
endif
endif
// green
if not this.gDone then
set this.gProgress = this.gProgress + this.gSpeed
set s = this.gProgress
// set this.gCurrent = R2I(this.g + (this.gDisp) * (s * s * s * (s * (s * 6 - 15) + 10))) //smootherstep
set this.gCurrent = R2I(this.g + (this.gDisp) * (s * s * (3 - 2 * s))) //smoothstep
if this.gProgress >= 1. then
set this.gDone = true
endif
endif
// blue
if not this.bDone then
set this.bProgress = this.bProgress + this.bSpeed
set s = this.bProgress
// set this.bCurrent = R2I(this.b + (this.bDisp) * (s * s * s * (s * (s * 6 - 15) + 10))) //smootherstep
set this.bCurrent = R2I(this.b + (this.bDisp) * (s * s * (3 - 2 * s))) //smoothstep
if this.bProgress >= 1. then
set this.bDone = true
endif
endif
// alpha
if not this.aDone then
set this.aProgress = this.aProgress + this.aSpeed
set s = this.aProgress
// set this.aCurrent = R2I(this.a + (this.aDisp) * (s * s * s * (s * (s * 6 - 15) + 10))) //smootherstep
set this.aCurrent = R2I(this.a + (this.aDisp) * (s * s * (3 - 2 * s))) //smoothstep
if this.aProgress >= 1. then
set this.aDone = true
endif
endif
static if LIBRARY_UnitData then
call this.uData.setColourValues(this.rCurrent, this.gCurrent, this.bCurrent, this.aCurrent)
else
call SetUnitVertexColor(this.source, this.rCurrent, this.gCurrent, this.bCurrent, this.aCurrent)
endif
if this.source == null or (this.rDone and this.gDone and this.bDone and this.aDone) then
call this.destroy()
set i = i - 1
endif
endloop
if count < 1 then
call ReleaseTimer(GetExpiredTimer())
endif
endmethod
method setCustomId takes integer i returns nothing
set this.customId = i
endmethod
method setCustomString takes string s returns nothing
set this.customString = s
endmethod
// wrapper to fade a unit in from zero alpha to 255 alpha.
static method fadeIn takes unit source, real duration returns thistype
return Recolour.new(source, 255, 255, 255, 255, 255, 255, 0, 255, duration)
endmethod
// wrapper to fade a unit out from 255 alpha to zero alpha.
static method fadeOut takes unit source, real duration returns thistype
return Recolour.new(source, 255, 255, 255, 255, 255, 255, 255, 0, duration)
endmethod
static method fadeInEx takes unit source, real duration, integer customId, string customString returns thistype
local thistype this = thistype.new(source, 255, 255, 255, 255, 255, 255, 0, 255, duration)
set this.customId = customId
set this.customString = customString
return this
endmethod
static method fadeOutEx takes unit source, real duration, integer customId, string customString returns thistype
local thistype this = thistype.new(source, 255, 255, 255, 255, 255, 255, 255, 0, duration)
set this.customId = customId
set this.customString = customString
return this
endmethod
// Use byte values (0 - 255). Integers only.
static method new takes unit source, integer redA, integer redB, integer greenA, integer greenB, integer blueA, integer blueB, integer alphaA, integer alphaB, real duration returns thistype
local integer id = GetUnitUserData(source)
local thistype this = instance[id]
local integer rDisp
local integer gDisp
local integer bDisp
local integer aDisp
local real dur
// if unit is currently undergoing Recolouring, retain the current values and then destroy the instance so a new one can start from where it stopped.
if this != 0 then
set redA = this.rCurrent
set greenA = this.gCurrent
set blueA = this.bCurrent
set alphaA = this.aCurrent
call this.destroy()
endif
set rDisp = redB - redA
set gDisp = greenB - greenA
set bDisp = blueB - blueA
set aDisp = alphaB - alphaA
if duration <= 0. or (rDisp == 0. and gDisp == 0. and bDisp == 0. and aDisp == 0.) then
return 0
endif
set this = allocate()
static if LIBRARY_UnitData then
set this.uData = UnitData[source]
endif
set instance[id] = this
set count = count + 1
set recolourList[count] = this
set this.source = source
set this.index = count
set this.customId = 0 // Optional. Used an an event response. Use Recolor.newEx(), Recolour.fadeInEx/fadeOutEx or this.setCustomId() to input directly.
set this.customString = "" // Optional. Used an an event response. Use Recolor.newEx(), Recolour.fadeInEx/fadeOutEx or this.setCustomString() to input directly.
set dur = ANIMATION_PERIOD / duration
// red
set this.rDone = (rDisp == 0.)
set this.rCurrent = redA
if not this.rDone then
set this.r = redA
set this.rDisp = rDisp
set this.rSpeed = dur
set this.rProgress = 0.
endif
// green
set this.gDone = (gDisp == 0.)
set this.gCurrent = greenA
if not this.gDone then
set this.g = greenA
set this.gDisp = gDisp
set this.gSpeed = dur
set this.gProgress = 0.
endif
// blue
set this.bDone = (bDisp == 0.)
set this.bCurrent = blueA
if not this.bDone then
set this.b = blueA
set this.bDisp = bDisp
set this.bSpeed = dur
set this.bProgress = 0.
endif
// alpha
set this.aDone = (aDisp == 0.)
set this.aCurrent = alphaA
if not this.aDone then
set this.a = alphaA
set this.aDisp = aDisp
set this.aSpeed = dur
set this.aProgress = 0.
endif
if count == 1 then
call TimerStart(NewTimer(), ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
static method newEx takes unit source, integer redA, integer redB, integer greenA, integer greenB, integer blueA, integer blueB, integer alphaA, integer alphaB, real duration, integer customId, string customString returns thistype
local thistype this = thistype.new(source, redA, redB, greenA, greenB, blueA, blueB, alphaA, alphaB, duration)
set this.customId = customId
set this.customString = customString
return this
endmethod
private static method onInit takes nothing returns nothing
set EVENT_PLAYER_UNIT_RECOLOUR = CreateNativeEvent()
endmethod
endstruct
endlibrary
/*****************************************************************************
*
* RegisterNativeEvent v1.1.1.5
* by Bannar
*
* Storage of trigger handles for native events.
*
******************************************************************************
*
* Optional requirements:
*
* Table by Bribe
* hiveworkshop.com/threads/snippet-new-table.188084/
*
******************************************************************************
*
* Important:
*
* Avoid using TriggerSleepAction within functions registered.
* Destroy native event trigger on your own responsibility.
*
******************************************************************************
*
* Core:
*
* function IsNativeEventRegistered takes integer whichIndex, integer whichEvent returns boolean
* Whether index whichIndex has already been attached to event whichEvent.
*
* function RegisterNativeEventTrigger takes integer whichIndex, integer eventId returns boolean
* Registers whichIndex within whichEvent scope and assigns new trigger handle for it.
*
* function GetIndexNativeEventTrigger takes integer whichIndex, integer whichEvent returns trigger
* Retrieves trigger handle for event whichEvent specific to provided index whichIndex.
*
* function GetNativeEventTrigger takes integer whichEvent returns trigger
* Retrieves trigger handle for event whichEvent.
*
*
* Custom events:
*
* function CreateNativeEvent takes nothing returns integer
* Returns unique id for new event and registers it with RegisterNativeEvent.
*
* function RegisterIndexNativeEvent takes integer whichIndex, integer whichEvent, code func returns triggercondition
* Registers new event handler func for event whichEvent specific to index whichIndex.
*
* function RegisterNativeEvent takes integer whichEvent, code func returns triggercondition
* Registers new event handler func for specified event whichEvent.
*
* function UnregisterNativeEventHandler takes integer whichEvent, triggercondition handler returns nothing
* Unregisters specified event handler for event whichEvent. Requires Warcraft 1.30.4+.
*
*****************************************************************************/
library RegisterNativeEvent uses optional Table
globals
private integer eventIndex = 500 // 0-499 reserved for Blizzard native events
endglobals
private module NativeEventInit
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set table = TableArray[0x2000]
endif
endmethod
endmodule
private struct NativeEvent extends array
static if LIBRARY_Table then
static TableArray table
else
static hashtable table = InitHashtable()
endif
implement NativeEventInit
endstruct
function IsNativeEventRegistered takes integer whichIndex, integer whichEvent returns boolean
static if LIBRARY_Table then
return NativeEvent.table[whichEvent].trigger.has(whichIndex)
else
return HaveSavedHandle(NativeEvent.table, whichEvent, whichIndex)
endif
endfunction
function RegisterNativeEventTrigger takes integer whichIndex, integer whichEvent returns boolean
if not IsNativeEventRegistered(whichIndex, whichEvent) then
static if LIBRARY_Table then
set NativeEvent.table[whichEvent].trigger[whichIndex] = CreateTrigger()
else
call SaveTriggerHandle(NativeEvent.table, whichEvent, whichIndex, CreateTrigger())
endif
return true
endif
return false
endfunction
function GetIndexNativeEventTrigger takes integer whichIndex, integer whichEvent returns trigger
static if LIBRARY_Table then
return NativeEvent.table[whichEvent].trigger[whichIndex]
else
return LoadTriggerHandle(NativeEvent.table, whichEvent, whichIndex)
endif
endfunction
function GetNativeEventTrigger takes integer whichEvent returns trigger
return GetIndexNativeEventTrigger(bj_MAX_PLAYER_SLOTS, whichEvent)
endfunction
function CreateNativeEvent takes nothing returns integer
local integer eventId = eventIndex
call RegisterNativeEventTrigger(bj_MAX_PLAYER_SLOTS, eventId)
set eventIndex = eventIndex + 1
return eventId
endfunction
function RegisterIndexNativeEvent takes integer whichIndex, integer whichEvent, code func returns triggercondition
call RegisterNativeEventTrigger(whichIndex, whichEvent)
return TriggerAddCondition(GetIndexNativeEventTrigger(whichIndex, whichEvent), Condition(func))
endfunction
function RegisterNativeEvent takes integer whichEvent, code func returns triggercondition
return RegisterIndexNativeEvent(bj_MAX_PLAYER_SLOTS, whichEvent, func)
endfunction
function UnregisterNativeEventHandler takes integer whichEvent, triggercondition handler returns nothing
call TriggerRemoveCondition(GetNativeEventTrigger(whichEvent), handler)
endfunction
endlibrary
/*****************************************************************************
*
* RegisterPlayerUnitEvent v1.0.3.2
* by Bannar
*
* Register version of TriggerRegisterPlayerUnitEvent.
*
* Special thanks to Magtheridon96, Bribe, azlier and BBQ for the original library version.
*
******************************************************************************
*
* Requirements:
*
* RegisterNativeEvent by Bannar
* hiveworkshop.com/threads/snippet-registerevent-pack.250266/
*
******************************************************************************
*
* Functions:
*
* function GetAnyPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* Retrieves trigger handle for playerunitevent whichEvent.
*
* function GetPlayerUnitEventTrigger takes player whichPlayer, playerunitevent whichEvent returns trigger
* Retrieves trigger handle for playerunitevent whichEvent specific to player whichPlayer.
*
* function RegisterAnyPlayerUnitEvent takes playerunitevent whichEvent, code func returns nothing
* Registers generic playerunitevent whichEvent adding code func as callback.
*
* function RegisterPlayerUnitEvent takes player whichPlayer, playerunitevent whichEvent, code func returns nothing
* Registers playerunitevent whichEvent for player whichPlayer adding code func as callback.
*
*****************************************************************************/
library RegisterPlayerUnitEvent requires RegisterNativeEvent
function GetAnyPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
return GetNativeEventTrigger(GetHandleId(whichEvent))
endfunction
function GetPlayerUnitEventTrigger takes player whichPlayer, playerunitevent whichEvent returns trigger
return GetIndexNativeEventTrigger(GetPlayerId(whichPlayer), GetHandleId(whichEvent))
endfunction
function RegisterAnyPlayerUnitEvent takes playerunitevent whichEvent, code func returns nothing
local integer eventId = GetHandleId(whichEvent)
local integer index = 0
local trigger t = null
if RegisterNativeEventTrigger(bj_MAX_PLAYER_SLOTS, eventId) then
set t = GetNativeEventTrigger(eventId)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(index), whichEvent, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
set t = null
endif
call RegisterNativeEvent(eventId, func)
endfunction
function RegisterPlayerUnitEvent takes player whichPlayer, playerunitevent whichEvent, code func returns nothing
local integer playerId = GetPlayerId(whichPlayer)
local integer eventId = GetHandleId(whichEvent)
if RegisterNativeEventTrigger(playerId, eventId) then
call TriggerRegisterPlayerUnitEvent(GetIndexNativeEventTrigger(playerId, eventId), whichPlayer, whichEvent, null)
endif
call RegisterIndexNativeEvent(playerId, eventId, func)
endfunction
endlibrary
library Relocate uses TimerUtils, RegisterNativeEvent optional UnitData
/*
v2.0
Added events with RegisterNativeEvent(EVENT_GENERAL_UNIT_RELOCATE, function whichCode).
Pass an integer or string in the constructor to add data to your unit as event responses.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
This is to prevent unnecessary overhead if all you want to do is a simple relocate with no events at the end.
Setting the customString to "Kill" will kill the unit on event trigger.
Setting the customString to "Remove" will remove the unit from the game on event trigger.
*/
globals
integer EVENT_GENERAL_UNIT_RELOCATE = 0
endglobals
struct Relocate
private static unit eventRelocatedUnit = null
private static real eventStartX = 0.
private static real eventStartY = 0.
private static real eventFinalX = 0.
private static real eventFinalY = 0.
private static integer eventCustomId = 0
private static string eventCustomString = ""
private static thistype array instance
private static thistype array relocateList
private static integer count = 0
private unit source
readonly real xCurrent
readonly real yCurrent
private real xStart
private real yStart
private real xDisp
private real yDisp
private real speed
private real progress
private integer index
private integer customId
private string customString
// EVENT RESPONSES =================================================
static method getEventUnit takes nothing returns unit
return eventRelocatedUnit
endmethod
static method getEventStartX takes nothing returns real
return eventStartX
endmethod
static method getEventStartY takes nothing returns real
return eventStartY
endmethod
static method getEventFinalX takes nothing returns real
return eventFinalX
endmethod
static method getEventFinalY takes nothing returns real
return eventFinalY
endmethod
// for storing specific data like a struct instance
static method getEventCustomId takes nothing returns integer
return eventCustomId
endmethod
// for storing specific data like a simple instruction
static method getEventCustomString takes nothing returns string
return eventCustomString
endmethod
// ================================================================
method destroy takes nothing returns nothing
local unit relocatedUnit = eventRelocatedUnit
// remove this from the list
set relocateList[this.index] = relocateList[count]
set relocateList[count].index = this.index
set relocateList[count] = 0
set count = count - 1
// null agent(s) and instance tracker
set instance[GetUnitUserData(this.source)] = 0
// event parameters
if not (this.customId == 0 and (this.customString == "" or this.customString == "Kill" or this.customString == "Remove")) then
set eventRelocatedUnit = this.source
set eventStartX = this.xStart
set eventStartY = this.yStart
set eventFinalX = this.xCurrent
set eventFinalY = this.yCurrent
set eventCustomId = this.customId
set eventCustomString = this.customString
call TriggerEvaluate(GetNativeEventTrigger(EVENT_GENERAL_UNIT_RELOCATE))
set eventRelocatedUnit = relocatedUnit
set eventStartX = 0.
set eventStartY = 0.
set eventFinalX = 0.
set eventFinalY = 0.
set eventCustomId = 0
set eventCustomString = ""
elseif this.customString == "Kill" then
call UnitApplyTimedLife(this.source, 'BTLF', .01)
elseif this.customString == "Remove" then
call RemoveUnit(this.source)
endif
set this.source = null
set relocatedUnit = null
call this.deallocate()
endmethod
method destroySilent takes nothing returns nothing
set this.customId = 0
set this.customString = ""
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local integer i = 0
local thistype this
local real x
local real y
local real s
local real sm
loop
set i = i + 1
exitwhen i > count
set this = relocateList[i]
set this.progress = this.progress + this.speed
set s = this.progress
set sm = (s * s * s * (s * (s * 6 - 15) + 10)) //smoothstep
set x = this.xStart + (this.xDisp) * sm
set y = this.yStart + (this.yDisp) * sm
call SetUnitX(this.source, x)
call SetUnitY(this.source, y)
set this.xCurrent = x
set this.yCurrent = y
if this.progress >= 1. then
call this.destroy()
set i = i - 1
endif
endloop
if count < 1 then
call ReleaseTimer(GetExpiredTimer())
endif
endmethod
static method newEx takes unit source, real xTarget, real yTarget, real duration, integer customId, string customString returns thistype
local integer id = GetUnitUserData(source)
local thistype this = instance[id]
local real x = GetUnitX(source)
local real y = GetUnitY(source)
// if unit is currently undergoing Relocate, destroy the instance so a new one can start from where it stopped.
if this != 0 then
call this.destroy()
endif
if x == xTarget and y == yTarget then
return 0
endif
set this = allocate()
set instance[id] = this
set count = count + 1
set relocateList[count] = this
set this.source = source
set this.xDisp = xTarget - x
set this.yDisp = yTarget - y
set this.xStart = x
set this.yStart = y
set this.xCurrent = x
set this.yCurrent = y
set this.speed = ANIMATION_PERIOD / duration
set this.index = count
set this.progress = 0.
set this.customId = customId
set this.customString = customString
if count == 1 then
call TimerStart(NewTimer(), ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
static method new takes unit source, real xTarget, real yTarget, real duration returns thistype
return Relocate.newEx(source, xTarget, yTarget, duration, 0, "")
endmethod
private static method onInit takes nothing returns nothing
set EVENT_GENERAL_UNIT_RELOCATE = CreateNativeEvent()
endmethod
endstruct
endlibrary
library RelocateEffect uses TimerUtils, RegisterNativeEvent, WorldBounds, GetLocZ
/*
v2.0
Added events with RegisterNativeEvent(EVENT_GENERAL_EFFECT_RELOCATE, function whichCode).
Pass an integer or string in the constructor to add data to your unit as event responses.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
This is to prevent unnecessary overhead if all you want to do is a simple relocate with no events at the end.
Setting the customString to "Kill" will kill the special effect on event trigger.
Setting the customString to "Remove" will remove the special effect from the game on event trigger.
*/
globals
integer EVENT_GENERAL_EFFECT_RELOCATE = 0
endglobals
struct RelocateEffect
private static effect eventRelocatedEffect = null
private static real eventStartX = 0.
private static real eventStartY = 0.
private static real eventFinalX = 0.
private static real eventFinalY = 0.
private static integer eventCustomId = 0
private static string eventCustomString = ""
private static thistype array relocateList
private static integer count = 0
private static Table instance
private effect source
readonly real xCurrent
readonly real yCurrent
private real xStart
private real yStart
private real xDisp
private real yDisp
private real speed
private real progress
private integer index
private integer customId
private string customString
// EVENT RESPONSES =================================================
static method getEventEffect takes nothing returns effect
return eventRelocatedEffect
endmethod
static method getEventStartX takes nothing returns real
return eventStartX
endmethod
static method getEventStartY takes nothing returns real
return eventStartY
endmethod
static method getEventFinalX takes nothing returns real
return eventFinalX
endmethod
static method getEventFinalY takes nothing returns real
return eventFinalY
endmethod
// for storing specific data like a struct instance
static method getEventCustomId takes nothing returns integer
return eventCustomId
endmethod
// for storing specific data like a simple instruction
static method getEventCustomString takes nothing returns string
return eventCustomString
endmethod
// ================================================================
method destroy takes nothing returns nothing
local effect relocatedEffect = eventRelocatedEffect
// remove this from the list
set relocateList[this.index] = relocateList[count]
set relocateList[count].index = this.index
set relocateList[count] = 0
set count = count - 1
// null agent(s) and instance tracker
set instance[GetHandleId(this.source)] = 0
// event parameters
if not (this.customId == 0 and (this.customString == "" or this.customString == "Kill" or this.customString == "Remove")) then
set eventRelocatedEffect = this.source
set eventStartX = this.xStart
set eventStartY = this.yStart
set eventFinalX = this.xCurrent
set eventFinalY = this.yCurrent
set eventCustomId = this.customId
set eventCustomString = this.customString
call TriggerEvaluate(GetNativeEventTrigger(EVENT_GENERAL_EFFECT_RELOCATE))
set eventRelocatedEffect = relocatedEffect
set eventStartX = 0.
set eventStartY = 0.
set eventFinalX = 0.
set eventFinalY = 0.
set eventCustomId = 0
set eventCustomString = ""
elseif this.customString == "Kill" then
call DestroyEffect(this.source)
elseif this.customString == "Remove" then
call DestroyEffect(this.source)
call BlzSetSpecialEffectPosition(this.source, WorldBounds.maxX, WorldBounds.maxY, 5000)
endif
set this.source = null
set relocatedEffect = null
call this.deallocate()
endmethod
method destroySilent takes nothing returns nothing
set this.customId = 0
set this.customString = ""
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local integer i = 0
local thistype this
local real x
local real y
local real s
local real sm
loop
set i = i + 1
exitwhen i > count
set this = relocateList[i]
set this.progress = this.progress + this.speed
set s = this.progress
set sm = (s * s * s * (s * (s * 6 - 15) + 10)) //smoothstep
set x = this.xStart + (this.xDisp) * sm
set y = this.yStart + (this.yDisp) * sm
call BlzSetSpecialEffectPosition(this.source, x, y, GetLocZ(x, y))
set this.xCurrent = x
set this.yCurrent = y
if this.progress >= 1. then
call this.destroy()
set i = i - 1
endif
endloop
if count < 1 then
call ReleaseTimer(GetExpiredTimer())
endif
endmethod
static method newEx takes effect fx, real xStart, real yStart, real xTarget, real yTarget, real duration, integer customId, string customString returns thistype
local integer id = GetHandleId(fx)
local thistype this = instance[id]
// if the effect is currently undergoing RelocateEffect, destroy the instance so a new one can start from where it stopped.
if this != 0 then
call this.destroy()
endif
if xStart == xTarget and yStart == yTarget then
return 0
endif
set this = allocate()
set instance[id] = this
set count = count + 1
set relocateList[count] = this
set this.source = fx
set this.xDisp = xTarget - xStart
set this.yDisp = yTarget - yStart
set this.xStart = xStart
set this.yStart = yStart
set this.xCurrent = xStart
set this.yCurrent = yStart
set this.speed = ANIMATION_PERIOD / duration
set this.index = count
set this.progress = 0.
set this.customId = customId
set this.customString = customString
if count == 1 then
call TimerStart(NewTimer(), ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
static method new takes effect fx, real xStart, real yStart, real xTarget, real yTarget, real duration returns thistype
return RelocateEffect.newEx(fx, xStart, yStart, xTarget, yTarget, duration, 0, "")
endmethod
private static method onInit takes nothing returns nothing
set instance = Table.create()
set EVENT_GENERAL_EFFECT_RELOCATE = CreateNativeEvent()
endmethod
endstruct
endlibrary
library RemoveAbilityTimed requires TimerUtils
//! novjass
-------- API --------
call RemoveAbilityTimed.new(unit Your Unit, integer Ability ID, real Timeout)
/*
If you want to recycle the unit right after the ability is removed, set the boolean
(the final argument) to true
*/
//! endnovjass
struct RemoveAbilityTimed
unit u
integer abilId
private method destroy takes nothing returns nothing
call this.deallocate()
set this.u = null
set this.abilId = 0
endmethod
private static method onTimer takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call UnitRemoveAbility(this.u, this.abilId)
call this.destroy()
call ReleaseTimer(t)
set t = null
endmethod
static method new takes unit source, integer abilId, real timeout returns thistype
local thistype this = allocate()
set this.u = source
set this.abilId = abilId
call TimerStart(NewTimerEx(this), timeout, false, function thistype.onTimer)
return this
endmethod
endstruct
endlibrary
library Rescale uses TimerUtils, RegisterNativeEvent optional UnitData
/*
v2.0
Added events with RegisterNativeEvent(EVENT_GENERAL_UNIT_RESCALE, function whichCode).
Pass an integer or string in the constructor to add data to your effect as event responses.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
This is to prevent unnecessary overhead if all you want to do is a simple rescale with no events at the end.
Setting the customString to "Kill" will kill the unit on event trigger.
Setting the customString to "Remove" will remove the unit from the game on event trigger.
*/
globals
integer EVENT_GENERAL_UNIT_RESCALE = 0
endglobals
struct Rescale
private static unit eventRescaleUnit = null
private static real eventScaleStart = 0.
private static real eventScaleFinal = 0.
private static integer eventCustomId = 0
private static string eventCustomString = ""
private static thistype array instance
private static thistype array rescaleList
private static integer count = 0
static if LIBRARY_UnitData then
private UnitData uData
endif
private unit source
private real startSize
private real displacement // end value - start value
private real speed
readonly real scale
private real progress
private integer index
private integer customId
private string customString
// EVENT RESPONSES =================================================
static method getEventUnit takes nothing returns unit
return eventRescaleUnit
endmethod
static method getEventScaleStart takes nothing returns real
return eventScaleStart
endmethod
static method getEventScaleFinal takes nothing returns real
return eventScaleFinal
endmethod
// for storing specific data like a struct instance
static method getEventCustomId takes nothing returns integer
return eventCustomId
endmethod
// for storing specific data like a simple instruction
static method getEventCustomString takes nothing returns string
return eventCustomString
endmethod
// ================================================================
method destroy takes nothing returns nothing
local unit rescaleUnit = eventRescaleUnit
// remove this from the list
set rescaleList[this.index] = rescaleList[count]
set rescaleList[count].index = this.index
set rescaleList[count] = 0
set count = count - 1
// null agent(s) and instance tracker
set instance[GetUnitUserData(this.source)] = 0
// event parameters
if not (this.customId == 0 and (this.customString == "" or this.customString == "Kill" or this.customString == "Remove")) then
set eventRescaleUnit = this.source
set eventScaleStart = this.startSize
set eventScaleFinal = this.scale
set eventCustomId = this.customId
set eventCustomString = this.customString
call TriggerEvaluate(GetNativeEventTrigger(EVENT_GENERAL_UNIT_RESCALE))
set eventRescaleUnit = rescaleUnit
set eventScaleStart = 0.
set eventScaleFinal = 0.
set eventCustomId = 0
set eventCustomString = ""
elseif this.customString == "Kill" then
call UnitApplyTimedLife(this.source, 'BTLF', .01)
elseif this.customString == "Remove" then
call RemoveUnit(this.source)
endif
set this.source = null
set rescaleUnit = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local integer i = 0
local thistype this
local real s
loop
set i = i + 1
exitwhen i > count
set this = rescaleList[i]
set this.progress = this.progress + this.speed
set s = this.progress
set s = this.startSize + (this.displacement) * (s * s * s * (s * (s * 6 - 15) + 10)) //smoothstep
static if LIBRARY_UnitData then
call this.uData.setScale(s, true)
else
call SetUnitScale(this.source, s, s, s)
endif
set this.scale = s
if this.progress >= 1. then
call this.destroy()
set i = i - 1
endif
endloop
if count < 1 then
call ReleaseTimer(GetExpiredTimer())
endif
endmethod
static method newEx takes unit source, real sizeStart, real sizeEnd, real duration, integer customId, string customString returns thistype
local integer id = GetUnitUserData(source)
local thistype this = instance[id]
local real disp
// if unit is currently undergoing Shrink, retain the current Rescale value and then destroy the instance so a new one can start from where it stopped.
if this != 0 then
set sizeStart = this.scale
call this.destroy()
endif
set disp = sizeEnd - sizeStart
if disp == 0. then
return 0
endif
set this = allocate()
static if LIBRARY_UnitData then
set this.uData = UnitData[source]
endif
set instance[id] = this
set count = count + 1
set rescaleList[count] = this
set this.source = source
set this.startSize = sizeStart
set this.scale = sizeStart
set this.displacement = disp
set this.speed = (1. / duration) * ANIMATION_PERIOD
set this.index = count
set this.progress = 0.
set this.customId = customId
set this.customString = customString
if count == 1 then
call TimerStart(NewTimer(), ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
static method new takes unit source, real sizeStart, real sizeEnd, real duration returns thistype
return Rescale.newEx(source, sizeStart, sizeEnd, duration, 0, "")
endmethod
private static method onInit takes nothing returns nothing
set EVENT_GENERAL_UNIT_RESCALE = CreateNativeEvent()
endmethod
endstruct
endlibrary
library RescaleEffect uses TimerUtils, Table, RegisterNativeEvent
/*
v2.0
Added events with RegisterNativeEvent(EVENT_GENERAL_EFFECT_RESCALE, function whichCode).
Pass an integer or string in the constructor to add data to your effect as event responses.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Remove"
This is to prevent unnecessary overhead if all you want to do is a simple rescale with no events at the end.
Setting the customString to "Remove" will destroy the effect on event trigger.
*/
globals
integer EVENT_GENERAL_EFFECT_RESCALE = 0
endglobals
struct RescaleEffect
private static effect eventRescaleEffect = null
private static real eventScaleStart = 0.
private static real eventScaleFinal = 0.
private static integer eventCustomId = 0
private static string eventCustomString = ""
private static thistype array rescaleList
private static integer count = 0
private static Table instance
readonly effect vfx
private real startSize
private real displacement // end value - start value
private real speed
readonly real scale
private real progress
private integer index
private integer customId
private string customString
// EVENT RESPONSES =================================================
static method getEventEffect takes nothing returns effect
return eventRescaleEffect
endmethod
static method getEventScaleStart takes nothing returns real
return eventScaleStart
endmethod
static method getEventScaleFinal takes nothing returns real
return eventScaleFinal
endmethod
// for storing specific data like a struct instance
static method getEventCustomId takes nothing returns integer
return eventCustomId
endmethod
// for storing specific data like a simple instruction
static method getEventCustomString takes nothing returns string
return eventCustomString
endmethod
// ================================================================
method destroy takes nothing returns nothing
local effect rescaleEffect = eventRescaleEffect
// remove this from the list
set rescaleList[this.index] = rescaleList[count]
set rescaleList[count].index = this.index
set rescaleList[count] = 0
set count = count - 1
// null agent(s) and instance tracker
call instance.remove(GetHandleId(this.vfx))
// event parameters
if not (this.customId == 0 and (this.customString == "" or this.customString == "Remove")) then
set eventRescaleEffect = this.vfx
set eventScaleStart = this.startSize
set eventScaleFinal = this.scale
set eventCustomId = this.customId
set eventCustomString = this.customString
call TriggerEvaluate(GetNativeEventTrigger(EVENT_GENERAL_EFFECT_RESCALE))
set eventRescaleEffect = rescaleEffect
set eventScaleStart = 0.
set eventScaleFinal = 0.
set eventCustomId = 0
set eventCustomString = ""
elseif this.customString == "Remove" then
call DestroyEffect(this.vfx)
endif
set this.vfx = null
set rescaleEffect = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local integer i = 0
local thistype this
local real s
loop
set i = i + 1
exitwhen i > count
set this = rescaleList[i]
set this.progress = this.progress + this.speed
set s = this.progress
set s = this.startSize + (this.displacement) * (s * s * s * (s * (s * 6 - 15) + 10)) //smoothstep
call BlzSetSpecialEffectScale(this.vfx, s)
set this.scale = s
if this.progress >= 1. then
call this.destroy()
set i = i - 1
endif
endloop
if count < 1 then
call ReleaseTimer(GetExpiredTimer())
endif
endmethod
static method new takes effect vfx, real sizeStart, real sizeEnd, real duration, integer customId, string customString returns thistype
local integer id = GetHandleId(vfx)
local thistype this = instance[id]
local real disp
// if effect is currently undergoing Rescaling, retain the current scale and then destroy the instance so a new one can start from where it stopped.
if this != 0 then
set sizeStart = this.scale
call this.destroy()
endif
set disp = sizeEnd - sizeStart
if disp == 0. then
return 0
endif
set this = allocate()
set instance[id] = this
set count = count + 1
set rescaleList[count] = this
set this.vfx = vfx
set this.startSize = sizeStart
set this.scale = sizeStart
set this.displacement = disp
set this.speed = ANIMATION_PERIOD / duration
set this.index = count
set this.progress = 0.
set this.customId = customId
set this.customString = customString
if count == 1 then
call TimerStart(NewTimer(), ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
private static method onInit takes nothing returns nothing
set instance = Table.create()
set EVENT_GENERAL_EFFECT_RESCALE = CreateNativeEvent()
endmethod
endstruct
endlibrary
library Respeed uses TimerUtils, RegisterNativeEvent optional UnitData
/*
Register events with RegisterNativeEvent(EVENT_GENERAL_UNIT_RESPPED, function whichCode).
Pass an integer or string in the constructor to add data to your effect as event responses.
IMPORTANT: events won't fire if customId == 0 and customString == "" / "Kill" / "Remove"
This is to prevent unnecessary overhead if all you want to do is a simple respeed with no events at the end.
Setting the customString to "Kill" will kill the unit on event trigger.
Setting the customString to "Remove" will remove the unit from the game on event trigger.
*/
globals
integer EVENT_GENERAL_UNIT_RESPPED = 0
endglobals
struct Respeed
private static unit eventRespedUnit = null
private static real eventSpeedStart = 0.
private static real eventSpeedFinal = 0.
private static integer eventCustomId = 0
private static string eventCustomString = ""
private static thistype array instance
private static thistype array respeedList
private static integer count = 0
static if LIBRARY_UnitData then
private UnitData uData
endif
private unit source
private real startSpeed
private real displacement // end value - start value
private real speed
readonly real speedVal
private real progress
private integer index
private integer customId
private string customString
// EVENT RESPONSES =================================================
static method getEventUnit takes nothing returns unit
return eventRespedUnit
endmethod
static method getEventSpeedStart takes nothing returns real
return eventSpeedStart
endmethod
static method getEventSpeedFinal takes nothing returns real
return eventSpeedFinal
endmethod
// for storing specific data like a struct instance
static method getEventCustomId takes nothing returns integer
return eventCustomId
endmethod
// for storing specific data like a simple instruction
static method getEventCustomString takes nothing returns string
return eventCustomString
endmethod
// ================================================================
method destroy takes nothing returns nothing
local unit respeedUnit = eventRespedUnit
// remove this from the list
set respeedList[this.index] = respeedList[count]
set respeedList[count].index = this.index
set respeedList[count] = 0
set count = count - 1
// null agent(s) and instance tracker
set instance[GetUnitUserData(this.source)] = 0
// event parameters
if not (this.customId == 0 and (this.customString == "" or this.customString == "Kill" or this.customString == "Remove")) then
set eventRespedUnit = this.source
set eventSpeedStart = this.startSpeed
set eventSpeedFinal = this.speedVal
set eventCustomId = this.customId
set eventCustomString = this.customString
call TriggerEvaluate(GetNativeEventTrigger(EVENT_GENERAL_UNIT_RESPPED))
set eventRespedUnit = respeedUnit
set eventSpeedStart = 0.
set eventSpeedFinal = 0.
set eventCustomId = 0
set eventCustomString = ""
elseif this.customString == "Kill" then
call UnitApplyTimedLife(this.source, 'BTLF', .01)
elseif this.customString == "Remove" then
call RemoveUnit(this.source)
endif
set this.source = null
set respeedUnit = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local integer i = 0
local thistype this
local real s
loop
set i = i + 1
exitwhen i > count
set this = respeedList[i]
set this.progress = this.progress + this.speed
set s = this.progress
set s = this.startSpeed + (this.displacement) * (s * s * s * (s * (s * 6 - 15) + 10)) //smoothstep
call SetUnitMoveSpeed(this.source, s)
set this.speedVal = s
if this.progress >= 1. then
call this.destroy()
set i = i - 1
endif
endloop
if count < 1 then
call ReleaseTimer(GetExpiredTimer())
endif
endmethod
static method newEx takes unit source, real speedStart, real speedEnd, real duration, integer customId, string customString returns thistype
local integer id = GetUnitUserData(source)
local thistype this = instance[id]
local real disp
// if unit is currently undergoing Respeed, retain the current move speed and then destroy the instance so a new one can start from where it stopped.
if this != 0 then
set speedStart = this.speedVal
call this.destroy()
endif
set disp = speedEnd - speedStart
if disp == 0. then
return 0
endif
set this = allocate()
static if LIBRARY_UnitData then
set this.uData = UnitData[source]
endif
set instance[id] = this
set count = count + 1
set respeedList[count] = this
set this.source = source
set this.startSpeed = speedStart
set this.speedVal = speedStart
set this.displacement = disp
set this.speed = (1. / duration) * ANIMATION_PERIOD
set this.index = count
set this.progress = 0.
set this.customId = customId
set this.customString = customString
if count == 1 then
call TimerStart(NewTimer(), ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
static method new takes unit source, real speedStart, real speedEnd, real duration returns thistype
return Respeed.newEx(source, speedStart, speedEnd, duration, 0, "")
endmethod
private static method onInit takes nothing returns nothing
set EVENT_GENERAL_UNIT_RESPPED = CreateNativeEvent()
endmethod
endstruct
endlibrary
library RiseAndFall requires optional Table
/* RiseAndFall v1.06 by Spellbound and revised by JAKEZINC & AGD. Special Thanks to Wareditor.
This simple library can be used to simulate an airborne effect by causing units to bob up and
down. It can also be used to make a unit fall from a certain height or rise in the air.
You can manipulate the height of the unit using this library or for your other purposes easily.
If you want units with movement types (no other than hover or fly) to be able to use fly height,
you may put Crow Form ability on them and then remove it at the same time. Alternatively, just get
AutoFly library and make your life easier. One is available in the test map. Requires UnitDex.
------------
INTERFACE
------------
Operator:
RiseAndFall[whichUnit] will return the RiseAndFall instance of the unit.
Static Methods:
RiseAndFall.create(unit whichUnit, real currentZ, real endZ, real duration, boolean flag)
^ This will cause a unit to changed its height from currentZ to endZ over duration.
The flag determines if the unit should accelerate/decelerate as they reach destination height.
This function returns the struct instance if you wish to store it.
RiseAndFall.levitate(unit whichUnit, real currentZ, real endZ, real duration)
^ This will make your unit appear to levitate between currentZ and endZ over duration.
This function returns the struct instance if you wish to store it.
RiseAndFall.isUnitAirborne(unit whichUnit)
^ This returns true/false if a unit is levitating/rising/falling.
Instance Methods:
RiseAndFall[whichUnit].end(real endZ, real speed)
^ Ends the Rise/Fall/Airborne. real endZ determines the height at which you
want the unit to end at and the speed determines how fast the unit will get there.
(calculated as: speed = PERIODIC_INTERVAL / ( (currentHeight - endHeight)/speed )
Set to the current height of the unit to terminate instantly.
*/
globals
private constant real PERIODIC_INTERVAL = 0.031250
endglobals
struct RiseAndFall
private unit unit
private real heightStart
private real heightEnd
private real speed
private real progress
private boolean useSmoothstep
private boolean isAirborne
private thistype prev
private thistype next
private static timer time = CreateTimer()
static if LIBRARY_Table then
private static key table
else
private static hashtable table = InitHashtable()
endif
static method operator [] takes unit whichUnit returns thistype
static if LIBRARY_Table then
return Table(table)[GetHandleId(whichUnit)]
else
return LoadInteger(table,GetHandleId(whichUnit),0)
endif
endmethod
static method isUnitAirborne takes unit whichUnit returns boolean
return thistype[whichUnit] != 0
endmethod
private method destroy takes nothing returns nothing
static if LIBRARY_Table then
call Table(table).remove( GetHandleId(.unit) )
else
call RemoveSavedInteger(table,GetHandleId(.unit),0)
endif
set this.next.prev = this.prev
set this.prev.next = this.next
if thistype(0).next == 0 then
call PauseTimer(time)
endif
call .deallocate()
set .unit = null
endmethod
private static method update takes nothing returns nothing
local real x
local thistype this = thistype(0).next
loop
exitwhen this == 0
set .progress = .progress + .speed
if .progress >= 1.0 then
call SetUnitFlyHeight(.unit,.heightEnd,0)
if .isAirborne then
set x = .heightStart
set .heightStart = .heightEnd
set .heightEnd = x
set .progress = 0.
else
if isUnitAirborne(.unit) then
call .destroy()
endif
endif
else
set x = .progress
if .useSmoothstep then
call SetUnitFlyHeight(.unit,.heightStart + (.heightEnd - .heightStart)*(x*x*x*(x*(x*6.0-15)+10) ),0)
else
call SetUnitFlyHeight(.unit,.heightStart + (.heightEnd - .heightStart)*(x*x),0)
endif
endif
set this = .next
endloop
endmethod
method end takes real endZ, real speed returns nothing
local real height = GetUnitFlyHeight(.unit)
if endZ != height then
set .heightStart = height
set .heightEnd = endZ
set .progress = 0.
set .isAirborne = false
set .useSmoothstep = false
if speed == 0. then // divide by zero prevention
set speed = 500.
endif
set .speed = PERIODIC_INTERVAL / ( (.heightStart - .heightEnd)/speed )
elseif isUnitAirborne(.unit) then
set .heightStart = height
set .heightEnd = endZ
set .progress = 1.0
set .isAirborne = false
set .useSmoothstep = false
call .destroy()
endif
endmethod
private static method createAirborne takes unit whichUnit, real currentZ, real endZ, real duration, boolean airborne, boolean smooth returns thistype
local thistype this = RiseAndFall[whichUnit]
if this != 0 then
call this.end(GetUnitFlyHeight(whichUnit),1.0)
endif
set this = allocate()
set this.unit = whichUnit
set this.heightStart = currentZ
set this.heightEnd = endZ
set this.progress = 0.
set this.speed = PERIODIC_INTERVAL/duration
set this.isAirborne = airborne
set this.useSmoothstep = smooth
static if LIBRARY_Table then
set Table(table)[GetHandleId(whichUnit)] = this
else
call SaveInteger(table,GetHandleId(whichUnit),0,this)
endif
if thistype(0).next == 0 then
call TimerStart(time,PERIODIC_INTERVAL,true,function thistype.update)
endif
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
return this
endmethod
static method create takes unit whichUnit, real currentZ, real endZ, real duration, boolean flag returns thistype
if RiseAndFall[whichUnit] == 0 then
return createAirborne(whichUnit,currentZ,endZ,duration,false,flag)
endif
return 0
endmethod
static method levitate takes unit whichUnit, real currentZ, real endZ, real duration returns thistype
if RiseAndFall[whichUnit] == 0 then
return createAirborne(whichUnit,currentZ,endZ,duration,true,true)
endif
return 0
endmethod
endstruct
endlibrary
library RootUprootEvent uses RegisterNativeEvent, OrderEvent, TimerUtils, Table
globals
integer EVENT_UNIT_ROOT = 0
integer EVENT_UNIT_UPROOT = 0
private unit eventUnit = null
private Table tab = 0
endglobals
// this is viable for both rooting and uprooting
function GetRootingUnit takes nothing returns unit
return eventUnit
endfunction
function IsUnitUprooted takes unit u returns boolean
return tab.has(GetHandleId(u))
endfunction
private function FireEvent takes integer evt, unit evtUnit returns nothing
local unit prevUnit = eventUnit
local integer playerId = GetPlayerId(GetOwningPlayer(evtUnit))
set eventUnit = evtUnit
call TriggerEvaluate(GetNativeEventTrigger(evt))
if IsNativeEventRegistered(playerId, evt) then
call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, evt))
endif
set eventUnit = prevUnit
set prevUnit = null
endfunction
private module init
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterOrderEvent(852166, function thistype.onUproot)
call RegisterOrderEvent(852165, function thistype.onRoot)
set EVENT_UNIT_ROOT = CreateNativeEvent()
set EVENT_UNIT_UPROOT = CreateNativeEvent()
endmethod
endmodule
struct RootUprootEvent
timer clock
unit source
trigger deathListener = CreateTrigger()
boolean timerOn = false
real rootX
real rootY
private method destroy takes nothing returns nothing
if this.timerOn then
call ReleaseTimer(this.clock)
endif
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.deathListener)
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if not UnitAlive(this.source) then
call this.destroy()
else
if GetUnitCurrentOrder(this.source) != 852165 then
set this.timerOn = false
call ReleaseTimer(this.clock)
else
if GetUnitX(this.source) == this.rootX and GetUnitY(this.source) == this.rootY then
call FireEvent(EVENT_UNIT_ROOT, this.source)
call this.destroy()
endif
endif
endif
endmethod
private static method onUproot takes nothing returns nothing
local thistype this = allocate()
local integer id = GetHandleId(GetTriggerUnit())
set tab[id] = this
set this.source = GetTriggerUnit()
call TriggerRegisterDeathEvent(this.deathListener, this.source)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call FireEvent(EVENT_UNIT_UPROOT, this.source)
endmethod
private static method onRoot takes nothing returns nothing
local integer id = GetHandleId(GetTriggerUnit())
local thistype this = tab[id]
set this.clock = NewTimerEx(this)
set this.timerOn = true
set this.rootX = GetOrderPointX()
set this.rootY = GetOrderPointY()
call TimerStart(this.clock, .05, true, function thistype.onPeriod)
endmethod
implement init
endstruct
endlibrary
//============================================================================
// SpellCastEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellCastEvent(integer abil, code onCast)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellCastEvent 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 RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellCastEvent 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
//============================================================================
// SpellCastEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellChannelEvent(integer abil, code onChannel)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellChannelEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onChannel 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 RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onChannel)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellChannelEvent takes integer abil, code onChannel 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(onChannel))
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(onChannel))
endif
endfunction
endlibrary
//============================================================================
// 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
//
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 RegisterAnyPlayerUnitEvent(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
//============================================================================
// SpellEndCastEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEndCastEvent(integer abil, code onEndCast)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellEndCastEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onEndCast 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 RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onEndCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEndCastEvent takes integer abil, code onEndCast 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(onEndCast))
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(onEndCast))
endif
endfunction
endlibrary
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 5.1.0.0
One map, one hashtable. Welcome to NewTable.
This latest version of Table introduces the following API:
Table2D (the new alias for the HashTable struct)
Table2DT (the new alias for the HashTableEx struct)
Table3D, Table4D and Table 5D.
More N-dimensional tables can be quickly generated if needed. Scroll to the
bottom of this script to find the textmacro TableXD.
Special thanks to @emperor_d3st for the inspiration for this update.
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 tableIDGenerator = 8190 //Index generation for Tables starts from here. Configure it if your map contains more than this many structs or 'key' objects.
private hashtable ht = InitHashtable() // The last hashtable.
private constant boolean TEST = true // set to `true` to enable error messages and `print`/`toString` API.
private constant boolean DEEP_TEST = false // set to `true` to enable informational messages.
private keyword instanceData
endglobals
private struct handles extends array
method operator []= takes integer key, handle h returns nothing
if h != null then
// "But I need hashtables to typecast generic handles into ..." - say no more. I got u fam.
call SaveFogStateHandle(ht, this, key, ConvertFogState(GetHandleId(h)))
elseif HaveSavedHandle(ht, this, key) then
// table.handle[key] = null becomes an alias for table.handle.remove(key)
call RemoveSavedHandle(ht, this, key)
endif
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 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
/*
Create API for stuff like:
set table.unit[key] = GetTriggerUnit()
local boolean b = table.handle.has(key)
local unit u = table.unit[key]
set table.handle.remove(key)
*/
//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")
//! 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")
//! runtextmacro NEW_ARRAY("Frame", "framehandle")
struct Table extends array
static Table instanceData = thistype.typeid
// Implement modules for handle/agent/integer/real/boolean/string/etc syntax.
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
implement framehandlem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
method operator [] takes integer key returns Table
return this.integer[key]
endmethod
method operator []= takes integer key, Table tab returns nothing
set this.integer[key] = tab
endmethod
method has takes integer key returns boolean
return this.integer.has(key)
endmethod
method remove takes integer key returns nothing
call this.integer.remove(key)
endmethod
// Remove all keys and values from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
// Returns a new Table instance that can store any hashtable-compatible data types.
static method create takes nothing returns Table
local Table this = instanceData[0]
if this == 0 then
set this = tableIDGenerator + 1
set tableIDGenerator = this
static if DEEP_TEST then
call BJDebugMsg("Creating Table: " + I2S(this))
endif
else
set instanceData[0] = instanceData[this]
static if DEEP_TEST then
call BJDebugMsg("Re-using Table: " + I2S(this))
endif
endif
set instanceData[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
method destroy takes nothing returns nothing
call flush()
if instanceData[this] != -1 then
static if TEST then
call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
endif
return
endif
static if DEEP_TEST then
call BJDebugMsg("Destroying Table: " + I2S(this))
endif
set instanceData[this] = instanceData[0]
set instanceData[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
private static integer keyGen = 0
private static Table arraySizes = thistype.typeid
//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 recycleList = arraySizes[array_size] //Get the unique recycle list for this array size
local TableArray this = recycleList[0] //The last-destroyed TableArray that had this array size
if array_size <= 0 then
static if TEST then
call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
endif
return 0
endif
if this == 0 then
set this = keyGen - array_size
set keyGen = this
else
set recycleList[0] = recycleList[this] //Set the last destroyed to the last-last destroyed
call recycleList.remove(this) //Clear hashed memory
endif
set arraySizes[this] = array_size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return arraySizes[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 `TEST` mode
//
method operator [] takes integer key returns Table
static if TEST then
local integer i = 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 recycleList = arraySizes[size]
if size == 0 then
static if TEST then
call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
endif
return
endif
if recycleList == 0 then
//Create a Table to index recycled instances with their array size
set recycleList = Table.create()
set arraySizes[size] = recycleList
endif
call arraySizes.remove(this) //Clear the array size from hash memory
set recycleList[this] = recycleList[0]
set recycleList[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 tab = tempTable
local integer end = tab + 0x1000
if end < tempEnd then
set tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = tempEnd
endif
loop
call tab.flush()
set tab = tab + 1
exitwhen tab == 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
if size == 0 then
static if TEST then
call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
endif
return
endif
set tempTable = this
set tempEnd = this + size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call destroy()
endmethod
endstruct
// Added in version 4.0, renamed from HashTable to Table2D in 5.1.
struct Table2D 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 tab = Table(this)[index]
if tab == 0 then
set tab = Table.create()
set Table(this)[index] = tab
endif
return tab
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the Table2D or simply no longer need that key.
method remove takes integer index returns nothing
local Table tab = Table(this)[index]
if tab != 0 then
call Table(this).remove(index)
if Table.instanceData[tab] == -1 then
call tab.destroy()
else
static if TEST then
call BJDebugMsg("Table2D Error: Inactive Table " + I2S(tab) + " used as key of Table2D " + I2S(this) + " at index " + I2S(index))
endif
endif
else
static if TEST then
call BJDebugMsg("Table2D Warning: " + I2S(tab) + " does not contain anything at index " + I2S(index))
endif
endif
endmethod
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
// Added in Table 5.0. Similar to the Table2D struct, but with the
// ability to log each value saved into the Table2DT to automate
// deallocation.
struct Table2DT extends array
private static Table2D tracker = Table2D.typeid
private static Table seenTables = thistype.typeid
method operator [] takes integer index returns Table
local integer i
local Table innerTable = Table(this)[index]
local Table trackingTable
if innerTable == 0 then
// If this key was never referenced before, create a table to handle this depth:
set innerTable = Table.create()
set Table(this)[index] = innerTable
set trackingTable = tracker[this] //get the tracking table
set i = trackingTable[0] + 1 //increase its size
set trackingTable[0] = i //save that size
set trackingTable[i] = index //index the user's index to the tracker's slot at 'size'
static if DEEP_TEST then
call BJDebugMsg("Increasing tracked table size to " + I2S(i))
endif
endif
static if DEEP_TEST then
call BJDebugMsg("Tracked-Table(" + I2S(this) + ")[" + I2S(index) + "] => " + I2S(innerTable))
endif
return innerTable
endmethod
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
private method flushAndDestroy takes nothing returns nothing
local Table trackTable = tracker[this]
local integer i = trackTable[0] //get the number of tracked indices
local Table tab
local integer tableStatus
// Mark this table as seen to avoid potentially-infinite recursion
set seenTables.boolean[this] = true
loop
exitwhen i == 0
set tab = Table(this)[trackTable[i]] // Get the actual table using the index from trackTable
if tab != 0 then
if Table.instanceData[tab] == -1 then
if tracker.has(tab) and not seenTables.boolean[tab] then
call thistype(tab).flushAndDestroy()
else
call tab.destroy()
endif
endif
endif
set i = i - 1
endloop
// Now destroy the tracking table and the table itself
call trackTable.destroy() //clear tracking sub-table
call Table(tracker).remove(this) //clear reference to that table
call Table(this).destroy()
endmethod
method destroy takes nothing returns nothing
if Table.instanceData[this] != -1 then
static if TEST then
call BJDebugMsg("Table2DT Error: Tried to double-free: " + I2S(this))
endif
return
endif
call flushAndDestroy()
call seenTables.flush()
endmethod
//Extremely inefficient, but gets the job done if needed.
method remove takes integer index returns nothing
local integer i
local integer j
local Table innerTable = Table(this)[index]
local Table trackingTable = tracker[this]
if Table.instanceData[this] != -1 then
static if TEST then
call BJDebugMsg("Table2DT Error: Tried to remove index " + I2S(index) + " from destroyed Tracked-Table(" + I2S(this) + ")")
endif
endif
if innerTable == 0 then
static if TEST then
call BJDebugMsg("Table2DT Error: " + I2S(this) + " does not have any indices to remove, so could not remove: " + I2S(index))
endif
return
endif
call Table(this).remove(index)
if Table.instanceData[innerTable] != -1 then
static if TEST then
call BJDebugMsg("Table2DT Error: " + I2S(this) + " contains destroyed table " + I2S(innerTable) + " at key: " + I2S(index))
endif
return
endif
if tracker.has(innerTable) then
call thistype(innerTable).destroy()
else
call innerTable.destroy()
endif
set i = trackingTable[0]
set j = i
loop
if (i == 0) then
static if TEST then
call BJDebugMsg("Table2DT Error: Tried to remove index: " + I2S(index) + " which does not exist in " + I2S(this))
endif
return
endif
exitwhen trackingTable[i] == index //removal is o(n) based
set i = i - 1
endloop
if i < j then
set trackingTable[i] = trackingTable[j] //pop last item in the stack and insert in place of this removed item
endif
call trackingTable.remove(j) //free reference to the index
set trackingTable[0] = j - 1 //decrease size of stack
endmethod
static method create takes nothing returns thistype
local thistype this = Table.create()
set tracker[this][0] = 0
return this
endmethod
static if TEST then
private method toStringFn takes integer depth returns string
local Table trackTable = Table(tracker)[this]
local integer i = trackTable[0]
local thistype tab
local string indent = ""
local integer k = 0
local string output
local integer index
local integer data
// Determine if this is a tracked table and if it's already been seen
if trackTable != 0 then
if seenTables.boolean[this] then
return "Tracked-Table(" + I2S(this) + ")"
endif
set seenTables.boolean[this] = true
if i == 0 then
return "Tracked-Table(" + I2S(this) + ")[-]"
endif
set output = "Tracked-Table(" + I2S(this) + ")["
else
set data = Table.instanceData[this]
if data == -1 then
return "Table(" + I2S(this) + ")[?]"
elseif data > 0 then
return "DESTROYED Table(" + I2S(this) + ")"
else
return I2S(this)
endif
endif
loop
exitwhen k == depth
set indent = indent + " "
set k = k + 1
endloop
loop
exitwhen i == 0
set index = trackTable[i]
set tab = Table(this)[index]
set output = output + "\n" + indent + " [" + thistype(index).toStringFn(depth) + "] = " + thistype(tab).toStringFn(depth + 1)
set i = i - 1
endloop
return output + "\n" + indent + "]"
endmethod
method toString takes nothing returns string
local string result = toStringFn(0)
call seenTables.flush()
return result
endmethod
method print takes nothing returns nothing
call BJDebugMsg(toString())
endmethod
endif
endstruct
//! textmacro TableXD takes NAME, SPEEDY_OR_TRACKED, MAP_TO_WHAT
struct $NAME$ extends array
method operator [] takes integer index returns $MAP_TO_WHAT$
return $SPEEDY_OR_TRACKED$(this)[index]
endmethod
method remove takes integer index returns nothing
call $SPEEDY_OR_TRACKED$(this).remove(index)
endmethod
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
method destroy takes nothing returns nothing
call $SPEEDY_OR_TRACKED$(this).destroy()
endmethod
static method create takes nothing returns thistype
return $SPEEDY_OR_TRACKED$.create()
endmethod
endstruct
//! endtextmacro
// Comment-out any of these if you don't need them. Note that the optimizer will inline alias methods.
//! runtextmacro TableXD("Table3D", "Table2D", "Table2D")
//! runtextmacro TableXD("Table4D", "Table2D", "Table3D")
//! runtextmacro TableXD("Table5D", "Table2D", "Table4D")
//! runtextmacro TableXD("Table3DT", "Table2DT", "Table2DT")
//! runtextmacro TableXD("Table4DT", "Table2DT", "Table3DT")
//! runtextmacro TableXD("Table5DT", "Table2DT", "Table4DT")
// Run these to support backwards-compatibility. Comment-out if you don't need them.
//! runtextmacro TableXD("HashTable", "Table2D", "Table")
//! runtextmacro TableXD("HashTableEx", "Table2DT", "Table")
endlibrary
library TileSpreader uses WorldBounds, TimerUtils, ListT, Table, PathingLib//, DefaultTextTag
//================================================================
//
// CONFIGURATION
//
globals
// config
private constant boolean IGNORE_BLIGHT = true
private constant boolean DIAGONAL_CHECK = false
private constant real GRID_SIZE = 128.
private constant real GRID_SIZE_DIAGONAL = GRID_SIZE * SquareRoot(2.)
// endconfig
private hashtable tileHash = InitHashtable()
private hashtable resistorsHash = InitHashtable()
private real array randomStarterAngle
endglobals
function ToGrid takes real val returns integer
return R2I((val / GRID_SIZE) * GRID_SIZE)
endfunction
function ToGridCenter takes real a returns integer
if (a >= 0.) then
return R2I(R2I((a/GRID_SIZE) + .5) * GRID_SIZE)
else
return R2I(R2I((a/GRID_SIZE) - .5) * GRID_SIZE)
endif
endfunction
//================================================================
//
// TILE
//
struct Tile
readonly integer x
readonly integer y
readonly integer tileId
readonly integer tilesetOriginal
readonly integer priority
readonly integer stacks = 0
private IntegerList spreadersList
private Table spreadersTab
static method get takes integer x, integer y returns thistype
return LoadInteger(tileHash, x, y)
endmethod
private method destroy takes nothing returns nothing
local IntegerListItem node = this.spreadersList.first
local TileSpreader tSpreader
call SetTerrainType(this.x, this.y, this.tilesetOriginal, -1, 1, 1)
loop
exitwhen node == 0
set tSpreader = node.data
call this.spreadersTab.remove(tSpreader)
set node = node.next
endloop
call this.spreadersList.destroy()
call RemoveSavedInteger(tileHash, this.x, this.y)
// call BJDebugMsg("Destroying Tile instance")
call this.deallocate()
endmethod
method unstack takes TileSpreader tSpreader returns boolean
local integer i = this.spreadersTab[tSpreader]
if i == 1 then
set this.stacks = this.stacks - 1
call this.spreadersTab.remove(tSpreader)
call this.spreadersList.removeElem(tSpreader)
if this.stacks == 0 then
call this.destroy()
endif
// call BJDebugMsg("|cffff3c00Unstacking | Stack count:|r " + I2S(this.stacks))
return true
endif
return false
endmethod
method stack takes TileSpreader tSpreader returns boolean
local integer i = this.spreadersTab[tSpreader]
if i == 0 then
// call BJDebugMsg("not a contributor")
set this.stacks = this.stacks + 1
set this.spreadersTab[tSpreader] = 1
call this.spreadersList.push(tSpreader)
return true
endif
// call BJDebugMsg("|cff2bff00Stacking | Stack count:|r " + I2S(this.stacks))
return false
endmethod
static method isContributedBy takes integer x, integer y, TileSpreader tSpreader returns boolean
local thistype this = LoadInteger(tileHash, x, y)
local integer i
if this != 0 then
set i = this.spreadersTab[tSpreader]
return (i != 0)
// return this.spreadersTab.has(tSpreader)
endif
return false
endmethod
static method new takes integer tileId, integer x, integer y returns thistype
local thistype this = allocate()
call SaveInteger(tileHash, x, y, this)
set this.tilesetOriginal = GetTerrainType(x, y)
set this.tileId = tileId
set this.x = x
set this.y = y
set this.spreadersList = IntegerList.create()
set this.spreadersTab = Table.create()
call SetTerrainType(x, y, tileId, -1, 1, 1)
return this
endmethod
endstruct
//================================================================
//
// TILE SPREADER
//
struct TileSpreader
// instanced members
readonly integer x
readonly integer y
readonly integer tileToSpread
readonly boolean isMuted
readonly real interval
readonly real maxRange
real spreadChance
integer priority // no implemented
private real maxRangeSqrd
private IntegerList tileList
private integer maxSize
private timer clock
private boolean timerOn
private integer unspreadCount
// prioritise this.unspread over destroy as this won't remove the tiles.
method destroy takes nothing returns nothing
local IntegerListItem node = this.tileList.last
local IntegerListItem nodePrev
local Tile tile
loop
exitwhen node == 0
set nodePrev = node.prev
set tile = node.data
call tile.unstack(this)
set node = nodePrev
endloop
call this.tileList.destroy()
if this.timerOn then
call ReleaseTimer(this.clock)
endif
call this.deallocate()
endmethod
private static method onUnspread takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local IntegerListItem node = this.tileList.last
local IntegerListItem nodePrev
local Tile tile
local integer i = this.unspreadCount
loop
set tile = node.data
exitwhen node == 0
set nodePrev = node.prev
call this.tileList.pop()
call tile.unstack(this)
if this.tileList.empty() then
call this.destroy()
exitwhen true
endif
set i = i - 1
exitwhen i <= 0
set node = nodePrev
endloop
endmethod
method unspread takes real interval, integer unspreadCount returns nothing
if not this.timerOn then
set this.timerOn = true
set this.clock = NewTimerEx(this)
endif
set this.unspreadCount = unspreadCount
call TimerStart(this.clock, interval, true, function thistype.onUnspread)
endmethod
/*
* These methods check if a point matches all conditions,
* so it can be infected successfully.
*/
private method coordinateCheck takes real x, real y returns boolean
return x <= WorldBounds.maxX and x >= WorldBounds.minX and y <= WorldBounds.maxY and y >= WorldBounds.minY
endmethod
private method distanceCheck takes real x, real y returns boolean
return (x - this.x) * (x - this.x) + (y - this.y) * (y - this.y) <= this.maxRangeSqrd
// return SquareRoot((x - this.x) * (x - this.x) + (y - this.y) * (y - this.y)) <= this.maxRange
endmethod
private method terrainCheck takes integer x, integer y returns boolean
if not Tile.isContributedBy(x, y, this) then
static if IGNORE_BLIGHT then
return not(LoadBoolean(resistorsHash, this.tileToSpread, GetTerrainType(x, y))) and not(IsPointBlighted(x, y)) and not(IsTerrainSwimmable(x, y))
else
return not(LoadBoolean(resistorsHash, this.tileToSpread, GetTerrainType(x, y))) and not(IsTerrainSwimmable(x, y))
endif
endif
return false
endmethod
private method isPointValid takes integer x, integer y returns boolean
return this.coordinateCheck(x,y) and this.terrainCheck(x,y) and this.distanceCheck(x,y)
endmethod
/*
* This method will make a circular check around given
* coordinates to find a terrain tile which is not
* infected yet. It will return boolean, if there exists
* a (new/old) tile around for potential infection.
*/
private method processNearbyTiles takes integer x, integer y returns boolean
local real angle = randomStarterAngle[GetRandomInt(0, 3)]
local integer xNew
local integer yNew
local integer i = 0
local Tile tile
static if DIAGONAL_CHECK then
// For 45/135/225/315 degrees the centre
// of the tile has an other distance, so
// we check if it's a digonal.
local real offset = GRID_SIZE_DIAGONAL
local real angleChange = bj_PI/4
else
local real offset = GRID_SIZE
local real angleChange = bj_PI/2
endif
if GetRandomInt(0, 1) == 1 then
set angleChange = -angleChange
endif
loop
set xNew = ToGridCenter(x + offset * Cos(angle))
set yNew = ToGridCenter(y + offset * Sin(angle))
if this.isPointValid(xNew, yNew) then
if GetRandomReal(0., 1.) <= this.spreadChance then
set tile = Tile.get(xNew, yNew)
if tile == 0 then
set tile = Tile.new(this.tileToSpread, xNew, yNew)
endif
if tile.stack(this) then
call this.tileList.push(tile)
endif
endif
return true
endif
set angle = angle + angleChange
set i = i + 1
exitwhen i == 4
endloop
return false
endmethod
private static method onSpread takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local IntegerListItem node = this.tileList.first
local Tile tile
local boolean hasSpread = false
if not this.isMuted then
loop
exitwhen node == 0
set tile = node.data
if this.processNearbyTiles(tile.x, tile.y) then
set hasSpread = true
endif
set node = node.next
endloop
if not hasSpread then
// call defaultTextTag("Ending spread", this.x, this.y, null)
// call BJDebugMsg("Ending spread")
// call BJDebugMsg("------------------------")
set this.timerOn = false
call ReleaseTimer(this.clock)
endif
endif
endmethod
static method new takes integer tileId, real x, real y, real interval, real maxRange, real spreadChance, integer priority returns thistype
local thistype this = allocate()
local Tile tile
set this.x = ToGridCenter(x)
set this.y = ToGridCenter(y)
set this.tileToSpread = tileId
set this.interval = interval
set this.maxRange = maxRange
set this.maxRangeSqrd = maxRange * maxRange
set this.maxSize = R2I(maxRange / GRID_SIZE)
set this.spreadChance = spreadChance
set this.priority = priority // not currently implemented.
set this.isMuted = false
set this.tileList = IntegerList.create()
set this.clock = NewTimerEx(this)
set this.timerOn = true
// call BJDebugMsg("========================")
// check if the tile under the spreader is currently being spread to by another one
set tile = Tile.get(this.x, this.y)
if tile == 0 then
set tile = Tile.new(tileId, this.x, this.y)
// call defaultTextTag("Creating new tile", this.x, this.y, null)
// call BJDebugMsg("Creating new tile")
endif
if tile.stack(this) then
// call defaultTextTagHeight("Stacking initial tile", this.x, this.y, 100., null)
// call BJDebugMsg("Stacking initial tile")
call this.tileList.push(tile)
endif
// call BJDebugMsg("------------------------")
call TimerStart(this.clock, interval, true, function thistype.onSpread)
return this
endmethod
method setResistor takes integer resistor, boolean b returns nothing
call SaveBoolean(resistorsHash, this.tileToSpread, resistor, b)
endmethod
private static method onInit takes nothing returns nothing
static if DIAGONAL_CHECK then
set randomStarterAngle[0] = 45. * bj_DEGTORAD
set randomStarterAngle[1] = 135. * bj_DEGTORAD
set randomStarterAngle[2] = 225. * bj_DEGTORAD
set randomStarterAngle[3] = 315. * bj_DEGTORAD
else
set randomStarterAngle[0] = 0.
set randomStarterAngle[1] = PIHALF
set randomStarterAngle[2] = PI
set randomStarterAngle[3] = PI + PIHALF
endif
endmethod
endstruct
endlibrary
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* 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)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* 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
// for when you need the smallest interval
constant real ANIMATION_PERIOD = .03215
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.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//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")
set tT[0]=CreateTimer()
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")
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
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
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
if ( didinit ) then
return
else
set didinit = true
endif
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
library UIUtilities uses Colour, PlayerUtils
globals
constant string EXPAND_SECTION_SIGN = "[+] "
constant string COLLAPSE_SECTION_SIGN = "[-] "
constant string GLOBAL_FONT = "fonts\\frizqt__.ttf"
constant real GLOBAL_FONT_SIZE_TITLE = .01
constant real GLOBAL_FONT_SIZE_BODY = .009
private real resolutionWitdh = 1920.
private real resolutionHeight = 1080.
private real PIXEL_WIDTH = .8 / resolutionWitdh
private real PIXEL_HEIGHT = .6 / resolutionHeight
framehandle ORIGIN_FRAME = null
framehandle CONSOLE_UI_BACKDROP = null
real array playerScreenWidthExtra
real array playerScreenEdgeMaxX
real array playerScreenEdgeMinX
endglobals
//! runtextmacro DEFINE_LIST("", "FrameHandleList", "framehandle")
function getUIWidth takes integer pixelValue returns real
return pixelValue * PIXEL_WIDTH
endfunction
function getUIHeight takes integer pixelValue returns real
return pixelValue * PIXEL_HEIGHT
endfunction
function resolutionCheck takes nothing returns nothing
local real w
local real h
local real ar
local integer i = -1
local integer id
loop
set i = i + 1
exitwhen i > ITERATOR_UserMAX
if GetLocalPlayer() == ITERATOR_User[i] then
set id = GetPlayerId(ITERATOR_User[i])
set w = I2R(BlzGetLocalClientWidth())
set h = I2R(BlzGetLocalClientHeight())
if h != 0. then // if h == 0. then the User is minimised. Do not update playerScreenWidthExtra for that player and wait for next timer tick
set ar = w/h * 0.6
set playerScreenWidthExtra[id] = (ar - .8) * .5
set playerScreenEdgeMaxX[id] = .8 + playerScreenWidthExtra[id]
set playerScreenEdgeMinX[id] = -playerScreenWidthExtra[id]
// potential event if playerScreenWidthExtra has changed?
endif
endif
endloop
endfunction
// Loading the TOC file.
private module InitTOCFileModule
private static method onInit takes nothing returns nothing
call BlzLoadTOCFile("UI\\CustomUI.toc")
set ORIGIN_FRAME = BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0)
set CONSOLE_UI_BACKDROP = BlzGetFrameByName("ConsoleUIBackdrop", 0)
// call TimerStart(CreateTimer(), 5., true, function resolutionCheck)
call resolutionCheck()
endmethod
endmodule
private struct InitTOCFile
implement InitTOCFileModule
endstruct
struct ClickableTextFrame
trigger onClickListener = CreateTrigger()
trigger onHoverOnListener = CreateTrigger()
trigger onHoverOffListener = CreateTrigger()
framehandle sourceFrame
method destroy takes nothing returns nothing
call DestroyTrigger(this.onClickListener)
call DestroyTrigger(this.onHoverOnListener)
call DestroyTrigger(this.onHoverOffListener)
call BlzDestroyFrame(this.sourceFrame)
call this.deallocate()
endmethod
static method new takes string text, string whichFrame, framehandle owner, code onHoverOn, code onHoverOff, code onClick returns thistype
local thistype this = allocate()
set this.sourceFrame = BlzCreateFrame(whichFrame, owner, 0, 0)
call BlzFrameSetText(this.sourceFrame, text)
call BlzTriggerRegisterFrameEvent(this.onHoverOnListener, this.sourceFrame, FRAMEEVENT_MOUSE_ENTER)
call BlzTriggerRegisterFrameEvent(this.onHoverOffListener, this.sourceFrame, FRAMEEVENT_MOUSE_LEAVE)
call BlzTriggerRegisterFrameEvent(this.onClickListener, this.sourceFrame, FRAMEEVENT_CONTROL_CLICK)
call TriggerAddCondition(this.onHoverOnListener, Condition(onHoverOn))
call TriggerAddCondition(this.onHoverOffListener, Condition(onHoverOff))
call TriggerAddCondition(this.onClickListener, Condition(onClick))
return this
endmethod
endstruct
endlibrary
library UnitData uses UnitDex, Table
/*
Use this to quickly retrieve/modify certain information about a unit like height, width, scale, and colour values.
Favour the setters in this library over things like SetUnitVertexColor as the vanilla functions will not modify those values.
CONSTANTS
AUTO_INDEX_SIZES
This will apply default values to the unit height, width and coreHeight based on the unit's collision size.
If set to false those values on a unit will always be zero and have to be set manually. Scale is always set
regardless.
DEFAULT_WIDTH_RATE
Only valid if AUTO_INDEX_SIZES is true. This determines the approximate girth of a unit based on its collision
size. The collision size is multiplied by DEFAULT_WIDTH_RATE to obtain said girth.
DEFAULT_HEIGHT_RATE
When a unit's core height is determined, its tallness is obtained by multiplying the core with DEFAULT_HEIGHT_RATE.
Default value of 2.
DEFAULT_CORE_Z_[TYPE]
DEFAULT_CORE_Z_[TYPE]_BIG
[TYPE]_BIG_THRESHOLD
Only valid if AUTO_INDEX_SIZES is true. Since there is currently no way to retrieve a unit's Projectil Z impact
with BlzGetRealUnitField() (currently on Warcraft v1.32), these constants will apply default values based on the
unit type. A unit is BIG if their collision size is greater than the [TYPE]_BIG_THRESHOLD.
NB: is [TYPE] is GROUND, that includes everything that's not a flying unit or a structure.
API
operator
local UnitData uData = UnitData[whichUnit]
^ returns the UnitData instance stored on that unit. Returns zero if unit has no instance stored.
creator
call UnitData.new(whichUnit)
^ this will generate the instance. Setup values afterwards. Does nothing if the unit already has an instance.
Will return the existing instance instead.
destructor
call .destroy()
^ the usual destroy method. Use the operator to obtain the instance. Instances are automatically destroyed
when a unit is deindexed so you'll likely never have to call this.
setters (non-static)
call .set[Value](whichReal/whichInteger)
^ sets a value to that real/integer. Check the methods below for more details. Prioritise those over vanilla
functions that do the same thing (eg SetUnitVertexColor) so that UnitData values are modified and stored.
call setDefaultSizes(whichHeight, whichWidth, whichCoreHeight)
^ since height/width/core are not innate values that can start out at zero, this function will allow you to
modify a unit's default parameters for these exclusively at any time.
Default setters exist for the other values but you shouldn't really need them unless a unit's default state is
meant to be somewhat 'fluid'.
call .automateDefaults()
^ this is a shortcut function that is used for manual UnitData setups. This will retrieve tints/alpha and scale
values directly from the world editor and apply them as defaults, leaving you with the task of only having to
manually entire height, core and width values.
call .initSizes(whichHeight, whichWidth, whichCoreHeight, coreOffset)
^ to be used in conjunction with .automateDefaults(), this applies current and default values to core, height
and width. If manually using on initialisation, use this instead of .setDefaultSizes() and setSizes()
exclusion
call UnitData.exclude(whichInteger)
^ have this run on initialisation prevent a unit id to be indexed with UnitData. Use this for special cases where
the unit's model/height dimensions are irregular and must be manually input.
If for some reason you do not wish to exclude an id anymore, do call UnitData.include(whichInteger) and it'll
reverse the effect.
*/
globals
// CONFIGURABLE
private constant boolean AUTO_INDEX_SIZES = true
private constant real DEFAULT_WIDTH_RATE = 1.5 // multipled to the collision size to obtain width
private constant real DEFAULT_HEIGHT_RATE = 2. // multipled to the core height to obtain height
private constant real DEFAULT_CORE_Z_FLYING = 20.
private constant real DEFAULT_CORE_Z_FLYING_BIG = 60.
private constant real FLYING_BIG_THRESHOLD = 8.
private constant real DEFAULT_HEIGHT_Z_FLYING = 100. // specifically for flying units with a core z of 20., this adjusts their height to a specific value instead.
private constant real DEFAULT_CORE_Z_GROUND = 60.
private constant real DEFAULT_CORE_Z_GROUND_BIG = 80.
private constant real GROUND_BIG_THRESHOLD = 32.
private constant real DEFAULT_CORE_Z_BUILDING = 120.
private constant real DEFAULT_CORE_Z_BUILDING_BIG= 160.
private constant real BUILDING_BIG_THRESHOLD = 72.
// END CONFIG
endglobals
private module initEvents
private static method onInit takes nothing returns nothing
set exclusionTable = Table.create()
call OnUnitIndex(function thistype.onIndexCheck)
call OnUnitDeindex(function thistype.onDeindex)
endmethod
endmodule
struct UnitData
private static thistype array instance
private static Table exclusionTable = 0
readonly unit source
readonly real coreHeightDefault
readonly real heightDefault
readonly real widthDefault
readonly real scaleDefault
readonly integer redDefault
readonly integer greenDefault
readonly integer blueDefault
readonly integer alphaDefault
readonly real coreHeight
readonly real height
readonly real width
readonly real scale
readonly integer red
readonly integer green
readonly integer blue
readonly integer alpha
readonly real coreOffset // depending on some models and their collision size, their x/y coordinates may actually be offset by 16. points. Use this to correct it.
// ================================================================================================================================
//
// Coordinates
//
method trueX takes nothing returns real
return GetUnitX(this.source) + this.coreOffset
endmethod
method trueY takes nothing returns real
return GetUnitY(this.source) + this.coreOffset
endmethod
method coreZ takes nothing returns real
return GetUnitFlyHeight(this.source) + this.coreHeight
endmethod
method coreZAbs takes nothing returns real
return BlzGetLocalUnitZ(this.source) + GetUnitFlyHeight(this.source) + this.coreHeight
endmethod
method overhead takes nothing returns real
return GetUnitFlyHeight(this.source) + this.height
endmethod
method overheadAbs takes nothing returns real
return BlzGetLocalUnitZ(this.source) + GetUnitFlyHeight(this.source) + this.height
endmethod
// ================================================================================================================================
//
// Scales
//
method setCoreHeight takes real coreHeight returns nothing
set this.coreHeight = coreHeight
endmethod
method setHeight takes real height returns nothing
set this.height = height
endmethod
method setWidth takes real width returns nothing
set this.width = width
endmethod
method setScale takes real scale, boolean applyScale returns nothing
set this.scale = scale
if applyScale then
call SetUnitScale(this.source, scale, scale, scale)
endif
endmethod
method setSizes takes real height, real width, real coreHeight, real scale, boolean applyScale returns nothing
set this.height = height
set this.width = width
set this.coreHeight = coreHeight
set this.scale = scale
if applyScale then
call SetUnitScale(this.source, scale, scale, scale)
endif
endmethod
// Defaults
method setDefaultCoreHeight takes real coreHeight returns nothing
set this.coreHeightDefault = coreHeight
endmethod
method setDefaultHeight takes real height returns nothing
set this.heightDefault = height
endmethod
method setDefaultWidth takes real width returns nothing
set this.widthDefault = width
endmethod
method setDefaultScale takes real scale, boolean applyScale returns nothing
set this.scaleDefault = scale
if applyScale then
call SetUnitScale(this.source, scale, scale, scale)
endif
endmethod
method setDefaultSizes takes real height, real width, real coreHeight returns nothing
set this.heightDefault = height
set this.widthDefault = width
set this.coreHeightDefault = coreHeight
endmethod
method setDefaultSizesEx takes real height, real width, real coreHeight, real scale, boolean applyScale returns nothing
set this.heightDefault = height
set this.widthDefault = width
set this.coreHeightDefault = coreHeight
set this.scaleDefault = scale
if applyScale then
call SetUnitScale(this.source, scale, scale, scale)
endif
endmethod
// ================================================================================================================================
//
// Tints & transparency
//
method setValueRed takes integer r returns nothing
set this.red = r
call SetUnitVertexColor(this.source, r, this.green, this.blue, this.alpha)
endmethod
method setValueGreen takes integer g returns nothing
set this.green = g
call SetUnitVertexColor(this.source, this.red, g, this.blue, this.alpha)
endmethod
method setValueBlue takes integer b returns nothing
set this.blue = b
call SetUnitVertexColor(this.source, this.red, this.green, b, this.alpha)
endmethod
method setValueAlpha takes integer a returns nothing
set this.alpha = a
call SetUnitVertexColor(this.source, this.red, this.green, this.blue, a)
endmethod
method setColourValues takes integer r, integer g, integer b, integer a returns nothing
set this.red = r
set this.green = g
set this.blue = b
set this.alpha = a
call SetUnitVertexColor(this.source, r, g, b, a)
endmethod
// Defaults
method setDefaultValueRed takes integer r, boolean applyValue returns nothing
set this.redDefault = r
if applyValue then
call SetUnitVertexColor(this.source, r, this.green, this.blue, this.alpha)
endif
endmethod
method setDefaultValueGreen takes integer g, boolean applyValue returns nothing
set this.greenDefault = g
if applyValue then
call SetUnitVertexColor(this.source, this.red, g, this.blue, this.alpha)
endif
endmethod
method setDefaultValueBlue takes integer b, boolean applyValue returns nothing
set this.blueDefault = b
if applyValue then
call SetUnitVertexColor(this.source, this.red, this.green, b, this.alpha)
endif
endmethod
method setDefaultValueAlpha takes integer a, boolean applyValue returns nothing
set this.alphaDefault = a
if applyValue then
call SetUnitVertexColor(this.source, this.red, this.green, this.blue, a)
endif
endmethod
method setDefaultColourValues takes integer r, integer g, integer b, integer a, boolean applyValues returns nothing
set this.redDefault = r
set this.greenDefault = g
set this.blueDefault = b
set this.alphaDefault = a
if applyValues then
call SetUnitVertexColor(this.source, r, g, b, a)
endif
endmethod
// ================================================================================================================================
//
// Partial default automation
//
method automateDefaults takes nothing returns nothing
local unit u = this.source
set this.scale = BlzGetUnitRealField(u, UNIT_RF_SCALING_VALUE)
set this.red = BlzGetUnitIntegerField(u, UNIT_IF_TINTING_COLOR_RED)
set this.green = BlzGetUnitIntegerField(u, UNIT_IF_TINTING_COLOR_GREEN)
set this.blue = BlzGetUnitIntegerField(u, UNIT_IF_TINTING_COLOR_BLUE)
set this.alpha = BlzGetUnitIntegerField(u, UNIT_IF_TINTING_COLOR_ALPHA)
set this.scaleDefault = this.scale
set this.redDefault = this.red
set this.greenDefault = this.green
set this.blueDefault = this.blue
set this.alphaDefault = this.alpha
set this.coreOffset = 0.
set u = null
endmethod
method initSizes takes real height, real width, real coreHeight, real coreOffset returns nothing
set this.coreHeight = coreHeight
set this.height = height
set this.width = width
set this.coreHeightDefault = coreHeight
set this.heightDefault = height
set this.widthDefault = width
set this.coreOffset = coreOffset
endmethod
// exlusion method
static method exclude takes integer id returns nothing
set exclusionTable.integer[id] = 1
endmethod
static method include takes integer id returns nothing
call exclusionTable.remove(id)
endmethod
// Creator, destructor and operators methods
method destroy takes nothing returns nothing
set instance[GetUnitId(this.source)] = 0
set this.source = null
call this.deallocate()
endmethod
static method new takes unit u returns thistype
local integer id = GetUnitUserData(u)
local thistype this = instance[id]
if this == 0 then
set this = allocate()
set this.source = u
set instance[id] = this
endif
return this
endmethod
static method operator[] takes unit u returns thistype
return instance[GetUnitId(u)]
endmethod
private static method onDeindex takes nothing returns nothing
local thistype this = thistype[GetIndexedUnit()]
if this != 0 then
call this.destroy()
endif
endmethod
private static method indexUnit takes unit u returns nothing
local thistype this = thistype.new(u)
static if AUTO_INDEX_SIZES then
local real col = BlzGetUnitCollisionSize(u)
local real core = 0.
local real h = 0.
if not IsUnitType(u, UNIT_TYPE_FLYING) then
if IsUnitType(u, UNIT_TYPE_STRUCTURE) then
if col > BUILDING_BIG_THRESHOLD then
set core = DEFAULT_CORE_Z_BUILDING_BIG
set h = core * DEFAULT_HEIGHT_RATE
else
set core = DEFAULT_CORE_Z_BUILDING
set h = core * DEFAULT_HEIGHT_RATE
endif
else
if col > GROUND_BIG_THRESHOLD then
set core = DEFAULT_CORE_Z_GROUND_BIG
set h = core * DEFAULT_HEIGHT_RATE
else
set core = DEFAULT_CORE_Z_GROUND
set h = core * DEFAULT_HEIGHT_RATE
endif
endif
else
if col > FLYING_BIG_THRESHOLD then
set core = DEFAULT_CORE_Z_FLYING_BIG
set h = core * DEFAULT_HEIGHT_RATE
else
set core = DEFAULT_CORE_Z_FLYING
set h = DEFAULT_HEIGHT_Z_FLYING
endif
endif
set this.coreHeight = core
set this.height = h
set this.width = col * DEFAULT_WIDTH_RATE
set this.coreHeightDefault = core
set this.heightDefault = this.height
set this.widthDefault = this.width
else
set this.coreHeight = 0.
set this.height = 0.
set this.width = 0.
set this.coreHeightDefault = 0.
set this.heightDefault = 0.
set this.widthDefault = 0.
endif
call this.automateDefaults()
endmethod
private static method onIndexCheck takes nothing returns nothing
if exclusionTable.integer[GetUnitTypeId(GetIndexedUnit())] == 1 then
return
endif
call thistype.indexUnit(GetIndexedUnit())
endmethod
implement initEvents
endstruct
endlibrary
library UnitDex uses TimerUtils, SpellEffectEvent optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.2, by TriggerHappy | MODIFIED TO INCLUDE CONSTRUCTION CHECKS
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
// //! external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* ________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// The raw code of detecting indexing for post-indexing.
static constant integer POST_INDEX_ABILITY_A = 'opCG'
static constant integer POST_INDEX_ABILITY_B = 'opID'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a global filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines.
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function IsConstructing takes unit u returns boolean
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeindex takes code func returns triggercondition
* function OnUnitPostIndex takes code func returns triggercondition // a unit under construction will only run this after finishing
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* constant integer EVENT_UNIT_POST_INDEX = 2 // this runs after a unit has completely entered scope, or after finishing constructing.
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
constant integer EVENT_UNIT_POST_INDEX = 2
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
// just to be more in-line with Blizzard naming conventions.
function GetIndexingUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function IsStillIndexing takes unit u returns boolean
return UnitDex.StillIndexing[GetUnitId(u)]
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
function OnUnitPostIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_POST_INDEX], Filter(func))
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
readonly static boolean array StillIndexing
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
call UnitRemoveAbility(u, thistype.POST_INDEX_ABILITY_B)
call UnitRemoveAbility(u, thistype.POST_INDEX_ABILITY_A)
set StillIndexing[i] = false
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetSpellAbilityUnit()
set LastIndex = GetUnitId(u)
set StillIndexing[LastIndex] = false
call TriggerEvaluate(IndexTrig[EVENT_UNIT_POST_INDEX])
set E = EVENT_UNIT_POST_INDEX
set E = -1
call UnitRemoveAbility(u, thistype.POST_INDEX_ABILITY_B)
call UnitRemoveAbility(u, thistype.POST_INDEX_ABILITY_A)
set u = null
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the post-index abilities
call UnitAddAbility(u, thistype.POST_INDEX_ABILITY_A)
call UnitAddAbility(u, thistype.POST_INDEX_ABILITY_B)
set StillIndexing[i] = true
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
set StillIndexing[i] = false
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_POST_INDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
// call SetPlayerAbilityAvailable(p, thistype.POST_INDEX_ABILITY_A, false)
// call SetPlayerAbilityAvailable(p, thistype.POST_INDEX_ABILITY_B, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
// Index preplaced units
set i = 0
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
set LastIndex = Counter
// run init triggers
call TimerStart(CreateTimer(), 0., false, function thistype.onGameStart)
call RegisterSpellEffectEvent(POST_INDEX_ABILITY_B, function thistype.onPostIndex)
endmethod
endmodule
endlibrary
/*
UnitEventEx v1.03.1
by Spellbound
Credits to Bribe for the excellent GUI Unit Event, of which this library is the vJASS
version of. He was also helpful in understanding how his system works so that I may code this.
Additional credits to Jesus4Lyf for Transport, Nestharus for UnitEvent and grim001 for AutoEvents.
*/
/*
_________________________________________________________________________
DESCRIPTION
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
UnitEventEx provides you with extra events not available in the vanilla game. Specifically,
you will be able to detect when a unit transforms, loads and unloads from a transport unit, is
reanimated by Reanimate Dead, reincarnates or is brought back to life.
Additionally, each of those events can be toggled on or off depending on whether you need them.
See // CONFIGURATION under the globals block below.
*/
/*
_________________________________________________________________________
INSTALLATION
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
UnitEventEx requires:
UnitDex by TriggerHappy (Make sure you have the Detect Leave (UnitDex) ability in your map)
https://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
RegisterEvent Pack by Bannar (both RegisterNativeEvent and RegisterPlayerUnitEvent)
https://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
Optionally uses:
ListT by Bannar
https://www.hiveworkshop.com/threads/containers-list-t.249011/
WorldBounds by Nestharus
https://github.com/nestharus/JASS/blob/master/jass/Systems/WorldBounds/script.j
Once you have the required libraries in place, either copy the Detect Transform (UnitEventEx)
ability to your map and set DETECT_TRANSFORM_ABILITY in the globals block below to the id of
the spell.
Alternatively, you can use the textmacro below. Import UnitEventEx, uncomment it, save your map,
close it, reload and comment it out again. The ability will have been created in your Object Editor.
*/
// //! external ObjectMerger w3a Adef EETD anam "Detect Transform" ansf "(UnitEventEx)" aart "" acat "" arac 0
/*
_________________________________________________________________________
CONFUGRATION
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
UnitEventEx can be configured to exclude those events you do not wish to use. Define which
events will be needed in the CONFIG block below.
*/
/*
_________________________________________________________________________
API
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
Event Registration
¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
call RegisterNativeEvent(yourEvent, function yourFunction )
This registers the event for all players.
call RegisterIndexNativeEvent(playerIndex, yourEvent, function yourFunction )
This registers the event for a specific player.
NB: Player 1 starts on zero.
call RegisterIndexNativeEvent(GetHandleId(event unit), yourEvent, function yourFunction )
This registers the event for a specific unit.
call GetIndexNativeEventTrigger(GetHandleId(event unit), yourEvent)
This returns the trigger associated with that unit id and event id. Destroy that trigger
to remove it. Useful for temporary events.
Also works with playerIndex.
The Events you can call are:
EVENT_ON_TRANSFORM
EVENT_ON_CARGO_LOAD
EVENT_ON_CARGO_UNLOAD
EVENT_ON_RESURRECTION
EVENT_ON_ANIMATE_DEAD
EVENT_ON_REINCARNATION_START
EVENT_ON_REINCARNATION_FINISH
EVENT_ON_UNIT_CREATED
Event Getters
¯¯¯¯¯¯¯¯¯¯¯¯¯
call GetEventUnit() returns unit
returns the event unit, similar to GetTriggerUnit().
MB: GetTriggerUnit() will NOT work with UnitEventEx.
call GetCargoUnit() returns unit
returns the transport unit.
NB: Only works on EVENT_ON_CARGO_LOAD and EVENT_ON_CARGO_UNLOAD
call GetCargoSize() returns integer
returns the number of units loaded in a transport.
NB: Only works on EVENT_ON_CARGO_LOAD and EVENT_ON_CARGO_UNLOAD
Cargo Getters
¯¯¯¯¯¯¯¯¯¯¯¯¯
call IsUnitInTransporter(whichUnit) returns boolean
returns true/false on whether a unit is loaded into a transporter.
call GetUnitTransporter(whichUnit) returns unit
returns the transport unit that is currently carrying whichUnit.
the value will be null if the unit isn't in any transport.
call GetCargoTransportedUnitGroup(transporter) returns group
returns a copy of the group of the units in the cargo of transporter.
NB: you must destroy the group that is returned after you're done with it.
**Only if you have ListT
call GetCargoTransportedUnitList(transporter) returne UEExList
returns a copy of the list of the units in the cargo of transporter.
NB: you must destroy the list that is returned after you're done with it.
*/
library UnitEventEx requires UnitDex, RegisterPlayerUnitEvent, optional ListT, WorldBounds
globals
// CONFIGURATION
// the transform detection ability. If DETECT_TRANSFORM is false, this is not needed.
private constant integer DETECT_TRANSFORM_ABILITY = 'UEdt'
// toggles the detection of transform events.
private constant boolean DETECT_TRANSFORM = true
// toggles the detection of load/unload of cargo units, except for dead units (eg Meat Wagon's Exhume Corpse)
private constant boolean DETECT_CARGO = false
// toggles the detection of unloading dead units in cargo (Exhume Corpse). Does nothing if DETECT_CARGO is false.
private constant boolean DETECT_CARGO_DEAD = false
// toggles the detection of when a unit begins and finishes reincarnating.
private constant boolean DETECT_REINCARNATION = false
// toggles the detection of when a unit is animated via animate dead.
private constant boolean DETECT_ANIMATE_DEAD = true
// toggles the detection of units that are brought back to life via resurrection.
private constant boolean DETECT_RESURRECTION = true
// this overrides reincarnation, animate dead and resurrection. Set to true if you want any of these events to work.
// for some reason setting DETECT_REVIVES = (DETECT_REINCARNATION or DETECT_ANIMATE_DEAD or DETECT_RESURRECTION) does not work.
private constant boolean DETECT_REVIVES = true
// this particular event will run after a zero-second delay so that units are able to fully enter the creation scope.
// it can be useful if you need something to run after both indexing and other events like EVENT_PLAYER_UNIT_CONSTRUCT_START
private constant boolean DETECT_CREATION = false
// END CONFIGURATION
private unit eventUnit = null
private unit eventOther = null
private integer eventPreType = 0
integer EVENT_ON_TRANSFORM
integer EVENT_ON_CARGO_LOAD
integer EVENT_ON_CARGO_UNLOAD
integer EVENT_ON_RESURRECTION
integer EVENT_ON_ANIMATE_DEAD
integer EVENT_ON_REINCARNATION_START
integer EVENT_ON_REINCARNATION_FINISH
integer EVENT_ON_UNIT_CREATED
private integer Stack = -1
private unit array IndexedUnit
private unit array CargoUnit
private group array CargoGroup
private unit array Transporter
private integer array PreTransformType
private real MaxX
private real MaxY
private boolean array IsNew
private boolean array IsAlive
private boolean array IsReincarnating
private boolean array IsTransforming
private timer AfterIndexTimer = CreateTimer()
private boolean rezCheck = true
endglobals
native UnitAlive takes unit u returns boolean
//! runtextmacro optional DEFINE_LIST("", "UEExList", "unit")
private function FireEvent takes integer ev, unit u, unit other returns nothing
local integer playerId = GetPlayerId(GetOwningPlayer(u))
local integer handleId = GetHandleId(u)
local integer id = GetUnitId(u)
local unit prevUnit = eventUnit
local unit prevOther = eventOther
local integer prevType = eventPreType
set eventUnit = u
set eventOther = other
set eventPreType = PreTransformType[id]
call TriggerEvaluate(GetNativeEventTrigger(ev))
if IsNativeEventRegistered(playerId, ev) then
call TriggerEvaluate(GetIndexNativeEventTrigger(playerId, ev))
elseif IsNativeEventRegistered(handleId, ev) then
call TriggerEvaluate(GetIndexNativeEventTrigger(handleId, ev))
endif
set eventUnit = prevUnit
set eventOther = prevOther
set eventPreType = prevType
set prevUnit = null
set prevOther = null
endfunction
private struct Cargo
static if LIBRARY_ListT then
static UEExList array CargoList
endif
static method delete takes integer transport_id returns nothing
static if LIBRARY_ListT then
call CargoList[transport_id].destroy()
else
call DestroyGroup(CargoGroup[transport_id])
endif
endmethod
static method remove takes unit u, unit transport returns nothing
local integer transport_id = GetUnitId(transport)
static if LIBRARY_ListT then
call CargoList[transport_id].removeElem(u)
else
call GroupRemoveUnit(CargoGroup[transport_id], u)
endif
set Transporter[GetUnitId(u)] = null
call FireEvent(EVENT_ON_CARGO_UNLOAD, u, transport)
endmethod
static method add takes unit u, unit transport returns nothing
local integer transport_id = GetUnitId(transport)
static if LIBRARY_ListT then
call CargoList[transport_id].push(u)
else
call GroupAddUnit(CargoGroup[transport_id], u)
endif
set Transporter[GetUnitId(u)] = transport
call FireEvent(EVENT_ON_CARGO_LOAD, u, transport)
endmethod
static method size takes integer transport_id returns integer
static if LIBRARY_ListT then
return CargoList[transport_id].size()
else
return CountUnitsInGroup(CargoGroup[transport_id])
endif
endmethod
static method exists takes integer transport_id returns boolean
static if LIBRARY_ListT then
return (CargoList[transport_id] != 0)
else
return (CargoGroup[transport_id] != null)
endif
endmethod
static method copyGroup takes integer transport_id returns group
local group g = CreateGroup()
static if LIBRARY_ListT then
local UEExListItem node = CargoList[transport_id].first
loop
exitwhen node == 0
call GroupAddUnit(g, node.data)
set node = node.next
endloop
else
call BlzGroupAddGroupFast(CargoGroup[transport_id], g)
endif
return g
endmethod
static if LIBRARY_ListT then
static method copyList takes integer transport_id returns UEExList
local UEExListItem node = CargoList[transport_id].first
local UEExList list = UEExList.create()
loop
exitwhen node == 0
call list.push(node.data)
set node = node.next
endloop
return list
endmethod
endif
static method create takes integer transport_id returns thistype
static if LIBRARY_ListT then
set CargoList[transport_id] = UEExList.create()
else
set CargoGroup[transport_id] = CreateGroup()
endif
return 0
endmethod
endstruct
// these functions are here so that they don't call functions below them.
function GetEventUnit takes nothing returns unit
return eventUnit
endfunction
function GetCargoUnit takes nothing returns unit
return eventOther
endfunction
function GetCargoSize takes unit transport returns integer
return Cargo.size(GetUnitId(transport))
endfunction
function IsUnitInTransporter takes unit whichUnit returns boolean
return Transporter[GetUnitId(whichUnit)] != null
endfunction
function GetUnitTransporter takes unit whichUnit returns unit
return Transporter[GetUnitId(whichUnit)]
endfunction
function GetCargoTransportedUnitGroup takes unit transporter returns group
return Cargo.copyGroup(GetUnitId(transporter))
endfunction
static if LIBRARY_ListT then
function GetCargoTransportedUnitList takes unit transporter returns UEExList
return Cargo.copyList(GetUnitId(transporter))
endfunction
endif
private module UnitEventExCore
/*/* afterIndex */*/
private static method afterIndex takes nothing returns nothing
local integer i = Stack
local integer id
local unit u
loop
exitwhen i < 0
set u = IndexedUnit[i]
set id = GetUnitId(u)
if IsNew[id] then
set IsNew[id] = false
static if DETECT_CREATION then
call FireEvent(EVENT_ON_UNIT_CREATED, u, null)
endif
elseif IsTransforming[id] then
static if DETECT_TRANSFORM then
call FireEvent(EVENT_ON_TRANSFORM, u, null)
set IsTransforming[id] = false
// The unit has finished transforming. Store it's new type id.
set PreTransformType[id] = GetUnitTypeId(u)
call UnitAddAbility(u, DETECT_TRANSFORM_ABILITY)
endif
elseif IsAlive[id] then
static if DETECT_REINCARNATION then
set IsReincarnating[id] = true
set IsAlive[id] = false
call FireEvent(EVENT_ON_REINCARNATION_START, u, null)
endif
endif
set IndexedUnit[i] = null
set i = i - 1
endloop
set Stack = -1
set u = null
endmethod
/*/* timerCheck */*/
private static method timerCheck takes unit u returns nothing
set Stack = Stack + 1
set IndexedUnit[Stack] = u
call TimerStart(AfterIndexTimer, 0., false, function thistype.afterIndex)
endmethod
/*/* unload */*/
private static method unload takes unit u returns nothing
local integer id = GetUnitId(u)
local integer cargo_id = GetUnitId(CargoUnit[id])
call Cargo.remove(u, CargoUnit[id])
if not IsUnitLoaded(u) or not UnitAlive(CargoUnit[id]) then
set CargoUnit[id] = null
endif
endmethod
/*/* onOrder */*/
private static method onOrder takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer id = GetUnitId(u)
// onOrder occurs after onEnter
/*
NB: when units are unloaded from a transport, they fire a stop order (see below)
When units are removed from the game or die, they fire an undefend order.
*/
// If id is not zero then the unit has been indexed.
if id > 0 then
// Detect Cargo
static if DETECT_CARGO then
if GetIssuedOrderId() == 851972 then // order stop
// This does not detect unloaded corpses.
if CargoUnit[id] != null and not IsUnitLoaded(u) or UnitAlive(u) then
call thistype.unload(u)
endif
set u = null
return
endif
endif
// Detect Morph
static if DETECT_TRANSFORM then
if GetUnitAbilityLevel(u, DETECT_TRANSFORM_ABILITY) == 0 and not IsTransforming[id] then
// re-adding DETECT_TRANSFORM_ABILITY immediately doesn't work, so it has to be
// done after a zero-second timer.
set IsTransforming[id] = true
call thistype.timerCheck(u)
set u = null
return
endif
endif
// Detect Revives
static if DETECT_REVIVES then
// If unit was not previously alive and ...
if not IsAlive[id] and UnitAlive(u) then
set IsAlive[id] = true
// ... it is also a summoned unit then it has been Reanimated.
if IsUnitType(u, UNIT_TYPE_SUMMONED) then
static if DETECT_ANIMATE_DEAD then
call FireEvent(EVENT_ON_ANIMATE_DEAD, u, null)
endif
// ... IsReincarnating[id] is true then it is done reincarnating.
elseif IsReincarnating[id] then
static if DETECT_REINCARNATION then
call FireEvent(EVENT_ON_REINCARNATION_FINISH, u, null)
endif
// ... neither of the above two conditions have been met, it has been resurrected.
elseif not IsNew[id] and not rezCheck then
static if DETECT_RESURRECTION then
call FireEvent(EVENT_ON_RESURRECTION, u, null)
endif
endif
// Else if the unit is dead then ...
elseif not UnitAlive(u) then
// if unit was just indexed then it was created as a corpse.
if IsNew[id] then
set IsAlive[id] = false
// else if either of these parameters are true then ...
elseif CargoUnit[id] == null or not IsUnitType(u, UNIT_TYPE_HERO) then
/* order undefend fires before a UNIT_DEATH event, so if the unit is not
alive, yet IsAlive[id] is true, that means the unit is potentially reincar-
nating. Fire a zero-second timer to give time for the UNIT_DEATH event to
trigger. If IsAlive[id] is still true, then the unit is reincarnating. */
if IsAlive[id] then
call thistype.timerCheck(u)
endif
endif
endif
endif // static if DETECT_REVIVES then
endif
set u = null
endmethod
/*/* onDeath */*/
private static method onDeath takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer id = GetUnitId(u)
// This checks if the unit has been indexed.
if id > 0 then
set IsAlive[id] = false
endif
static if DETECT_CARGO then
if CargoUnit[id] != null then
call FireEvent(EVENT_ON_CARGO_UNLOAD, u, CargoUnit[id])
set CargoUnit[id] = null
endif
endif
set u = null
endmethod
/*/* onLoad */*/
private static method onLoad takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer id = GetUnitId(u)
local integer cargo_id
// if unit somehow loaded into a transport while being inside another, unload it
if CargoUnit[id] != null then
call thistype.unload(u)
endif
static if DETECT_CARGO_DEAD then
// if a Meat Wagon loads up a corpse either by grabbing one on the ground or via Exhume
// Corpse, that corpse is sent to the edge of the map by this system. This way, when it
// is unloaded and placed back at the Meat Wagon's location, it fires an onEnter event,
// which allows the system to detect when a corpse was unloaded from a transport.
if not UnitAlive(u) then
if LIBRARY_WorldBounds then
call SetUnitX(u, WorldBounds.maxX)
call SetUnitY(u, WorldBounds.maxY)
else
call SetUnitX(u, MaxX)
call SetUnitY(u, MaxY)
endif
endif
endif
set CargoUnit[id] = GetTransportUnit()
set cargo_id = GetUnitId(CargoUnit[id])
if not Cargo.exists(cargo_id) then
call Cargo.create(cargo_id)
endif
call Cargo.add(u, CargoUnit[id])
set u = null
endmethod
/*/* onEnter */*/
private static method onEnter takes nothing returns nothing
local unit u = GetFilterUnit()
local integer id = GetUnitId(u)
local integer cargo_id = GetUnitId(CargoUnit[id])
// onEnter occurs AFTER onIndex
// The unit was dead, but has re-entered the map. Used to detect when a Meat Wagon unloads a corpse.
if id > 0 then
if not IsUnitLoaded(u) and CargoUnit[id] != null then
call thistype.unload(u)
endif
endif
set u = null
endmethod
/*/* onIndex */*/
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
local integer id = GetUnitId(u)
// onIndex occurs BEFORE onEnter
set IsNew[id] = true
static if DETECT_TRANSFORM then
set IsTransforming[id] = false
set PreTransformType[id] = GetUnitTypeId(u)
call UnitAddAbility(u, DETECT_TRANSFORM_ABILITY)
endif
static if DETECT_REINCARNATION and DETECT_REVIVES then
set IsReincarnating[id] = false
endif
if UnitAlive(u) then
set IsAlive[id] = true
else
set IsAlive[id] = false
endif
// This is called here so as to set the variable IsNew[] to false after 0. seconds.
call thistype.timerCheck(u)
set u = null
endmethod
/*/* onDeindex */*/
private static method onDeindex takes nothing returns nothing
local unit u = GetIndexedUnit()
local integer id = GetUnitId(u)
if Cargo.exists(id) then
call Cargo.delete(id)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
local integer i
static if DETECT_CARGO and DETECT_CARGO_DEAD then
static if (not LIBRARY_WorldBounds) then
local rect world = GetWorldBounds()
local region reg = CreateRegion()
set MaxX = GetRectMaxX(world)
set MaxY = GetRectMaxY(world)
endif
endif
set EVENT_ON_CARGO_LOAD = CreateNativeEvent()
set EVENT_ON_CARGO_UNLOAD = CreateNativeEvent()
set EVENT_ON_TRANSFORM = CreateNativeEvent()
set EVENT_ON_ANIMATE_DEAD = CreateNativeEvent()
set EVENT_ON_RESURRECTION = CreateNativeEvent()
set EVENT_ON_REINCARNATION_START = CreateNativeEvent()
set EVENT_ON_REINCARNATION_FINISH = CreateNativeEvent()
set EVENT_ON_UNIT_CREATED = CreateNativeEvent()
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_ISSUED_ORDER, function thistype.onOrder)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function thistype.onDeath)
static if DETECT_CARGO then
static if DETECT_CARGO_DEAD then
static if (not LIBRARY_WorldBounds) then
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, function thistype.onEnter)
call RemoveRect(world)
set world = null
set reg = null
else
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, function thistype.onEnter)
endif
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_LOADED, function thistype.onLoad)
endif
call RegisterUnitIndexEvent(Condition(function thistype.onDeindex), EVENT_UNIT_DEINDEX)
endif
call RegisterUnitIndexEvent(Condition(function thistype.onIndex), EVENT_UNIT_INDEX)
static if DETECT_TRANSFORM then
set i = 0
loop
call SetPlayerAbilityAvailable(Player(i), DETECT_TRANSFORM_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
endif
// see resurrectionTimer below.
call TimerStart(CreateTimer(), 0., false, function thistype.resurrectionTimer)
endmethod
// for some reason dummy recyclers creating dummies to store fires off a resurrection event, so
// this boolean rezCheck is set to false after a 0. second timer to prevent this from happening.
// rezCheck must be false for a resurrection event to happen.
private static method resurrectionTimer takes nothing returns nothing
set rezCheck = false
call DestroyTimer(GetExpiredTimer())
endmethod
endmodule
private struct UnitEventEx
implement UnitEventExCore
endstruct
endlibrary
library UnitRemoveAbilityTimed requires TimerUtils, DummyRecycler
//! novjass
-------- API --------
call UnitRemoveAbilityTimed.create(unit Your Unit, integer Ability ID, real Timeout, boolean Recycle Unit)
/*
If you want to recycle the unit right after the ability is removed, set the boolean
(the final argument) to true
*/
//! endnovjass
struct UnitRemoveAbilityTimed
unit u
integer abil
boolean recycle
private method destroy takes nothing returns nothing
call this.deallocate()
set this.u = null
set this.abil = 0
set this.recycle = false
endmethod
private static method removeAbil takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call UnitRemoveAbility(this.u, this.abil)
if this.recycle then
call RecycleDummy(this.u)
endif
call this.destroy()
call ReleaseTimer(t)
set t = null
endmethod
static method create takes unit source, integer abil_code, real timeout, boolean recycle_source returns UnitRemoveAbilityTimed
local thistype this = allocate()
local timer t = NewTimerEx(this)
set this.u = source
set this.abil = abil_code
set this.recycle = recycle_source
call TimerStart(t, timeout, false, function thistype.removeAbil)
set t = null
return this
endmethod
endstruct
endlibrary
library WorldBounds /* v2.0.0.0
************************************************************************************
*
* struct WorldBounds extends array
*
* Fields
* -------------------------
*
* readonly static integer maxX
* readonly static integer maxY
* readonly static integer minX
* readonly static integer minY
*
* readonly static integer centerX
* readonly static integer centerY
*
* readonly static rect world
* readonly static region worldRegion
*
************************************************************************************/
private module WorldBoundInit
private static method onInit takes nothing returns nothing
set world=GetWorldBounds()
set maxX = R2I(GetRectMaxX(world))
set maxY = R2I(GetRectMaxY(world))
set minX = R2I(GetRectMinX(world))
set minY = R2I(GetRectMinY(world))
set centerX = R2I((maxX + minX)/2)
set centerY = R2I((minY + maxY)/2)
set worldRegion = CreateRegion()
call RegionAddRect(worldRegion, world)
endmethod
endmodule
struct WorldBounds extends array
readonly static integer maxX
readonly static integer maxY
readonly static integer minX
readonly static integer minY
readonly static integer centerX
readonly static integer centerY
readonly static rect world
readonly static region worldRegion
implement WorldBoundInit
endstruct
endlibrary
library MissileEffect requires WorldBounds, Alloc
/* ------------------------------------- Missile Effect v2.8 ------------------------------------ */
// This is a simple helper library for the Relativistic Missiles system.
// Credits:
// Sevion for the Alloc module
// - www.hiveworkshop.com/threads/snippet-alloc.192348/
// Nestharus for World Bounds Library
/* ---------------------------------------- By Chopinski ---------------------------------------- */
/* ---------------------------------------------------------------------------------------------- */
/* System */
/* ---------------------------------------------------------------------------------------------- */
private module LinkedList
readonly thistype next
readonly thistype prev
method init takes nothing returns thistype
set next = this
set prev = this
return this
endmethod
method pushBack takes thistype node returns thistype
set node.prev = prev
set node.next = this
set prev.next = node
set prev = node
return node
endmethod
method pushFront takes thistype node returns thistype
set node.prev = this
set node.next = next
set next.prev = node
set next = node
return node
endmethod
method pop takes nothing returns nothing
set prev.next = next
set next.prev = prev
endmethod
endmodule
private struct Effect extends array
implement LinkedList
implement Alloc
real x
real y
real z
real size
real yaw
real pitch
real roll
string path
effect effect
method remove takes nothing returns nothing
call DestroyEffect(effect)
call pop()
call deallocate()
set effect = null
endmethod
method insert takes string fxpath, real x, real y, real z, real scale returns thistype
local thistype node = pushBack(allocate())
set node.x = x
set node.y = y
set node.z = z
set node.yaw = 0.
set node.pitch = 0.
set node.roll = 0.
set node.path = fxpath
set node.size = scale
set node.effect = AddSpecialEffect(fxpath, x, y)
call BlzSetSpecialEffectZ(node.effect, z)
call BlzSetSpecialEffectScale(node.effect, scale)
return node
endmethod
static method create takes nothing returns thistype
return thistype(allocate()).init()
endmethod
endstruct
struct MissileEffect
real size
real yaw
real pitch
real roll
real time
integer transparency
integer animtype
integer playercolor
string path
effect effect
Effect attachments
/* -------------------------------- Operators ------------------------------- */
method operator timeScale= takes real newTimeScale returns nothing
set time = newTimeScale
call BlzSetSpecialEffectTimeScale(effect, time)
endmethod
method operator timeScale takes nothing returns real
return time
endmethod
method operator alpha= takes integer newAlpha returns nothing
set transparency = newAlpha
call BlzSetSpecialEffectAlpha(effect, transparency)
endmethod
method operator alpha takes nothing returns integer
return transparency
endmethod
method operator playerColor= takes integer playerId returns nothing
set playercolor = playerId
call BlzSetSpecialEffectColorByPlayer(effect, Player(playerId))
endmethod
method operator playerColor takes nothing returns integer
return playercolor
endmethod
method operator animation= takes integer animType returns nothing
set animtype = animType
call BlzPlaySpecialEffect(effect, ConvertAnimType(animtype))
endmethod
method operator animation takes nothing returns integer
return animtype
endmethod
/* --------------------------------- Methods -------------------------------- */
method scale takes effect sfx, real scale returns nothing
set size = scale
call BlzSetSpecialEffectScale(sfx, scale)
endmethod
method orient takes real yaw, real pitch, real roll returns nothing
local Effect node = attachments.next
set .yaw = yaw
set .pitch = pitch
set .roll = roll
call BlzSetSpecialEffectOrientation(effect, yaw, pitch, roll)
loop
exitwhen node == attachments
set node.yaw = yaw
set node.pitch = pitch
set node.roll = roll
call BlzSetSpecialEffectOrientation(node.effect, yaw, pitch, roll)
set node = node.next
endloop
endmethod
method move takes real x, real y, real z returns boolean
local Effect node = attachments.next
if not (x > WorldBounds.maxX or x < WorldBounds.minX or y > WorldBounds.maxY or y < WorldBounds.minY) then
call BlzSetSpecialEffectPosition(effect, x, y, z)
loop
exitwhen node == attachments
call BlzSetSpecialEffectPosition(node.effect, x - node.x, y - node.y, z - node.z)
set node = node.next
endloop
return true
endif
return false
endmethod
method attach takes string fxpath, real dx, real dy, real dz, real scale returns effect
local Effect node = attachments.insert(fxpath, dx, dy, dz, scale)
call BlzSetSpecialEffectPosition(node.effect, BlzGetLocalSpecialEffectX(effect) - dx, BlzGetLocalSpecialEffectY(effect) - dy, BlzGetLocalSpecialEffectZ(effect) - dz)
return node.effect
endmethod
method detach takes effect sfx returns nothing
local Effect node = attachments.next
loop
exitwhen node == attachments
if GetHandleId(node.effect) == GetHandleId(sfx) then
call node.remove()
exitwhen true
endif
set node = node.next
endloop
endmethod
method setColor takes integer red, integer green, integer blue returns nothing
call BlzSetSpecialEffectColor(effect, red, green, blue)
endmethod
/* -------------------------- Contructor/Destructor ------------------------- */
method destroy takes nothing returns nothing
local Effect node = attachments.next
loop
exitwhen node == attachments
call node.remove()
set node = node.next
endloop
call DestroyEffect(effect)
call attachments.deallocate()
set effect = null
set path = null
set size = 1.
call deallocate()
endmethod
static method create takes real x, real y, real z returns thistype
local thistype this = thistype.allocate()
set effect = AddSpecialEffect("", x, y)
set path = ""
set size = 1
set time = 0
set transparency = 0
set animtype = 0
set playercolor = 0
set attachments = Effect.create()
call BlzSetSpecialEffectZ(effect, z)
return this
endmethod
endstruct
endlibrary
library Missiles requires MissileEffect, TimerUtils, WorldBounds
/* ---------------------------------------- Missiles v2.8 --------------------------------------- */
// Thanks and Credits to BPower, Dirac and Vexorian for the Missile Library's at which i based
// this Missiles library. Credits and thanks to AGD and for the effect orientation ideas.
// This version of Missiles requires patch 1.31+
// How to Import:
// 1 - Copy this, MissileEffect and optionaly the MissileUtils libraries to your map
/* ---------------------------------------- By Chopinski ---------------------------------------- */
/* ---------------------------------------------------------------------------------------------- */
/* System */
/* ---------------------------------------------------------------------------------------------- */
globals
// The update period of the system
public constant real PERIOD = 1./40.
// The max amount of Missiles processed in a PERIOD
// You can play around with both these values to find
// your sweet spot. If equal to 0, the system will
// process all missiles at once every period.
public constant real SWEET_SPOT = 150
// the avarage collision size compensation when detecting collisions
private constant real COLLISION_SIZE = 128.
// item size used in z collision
private constant real ITEM_SIZE = 16.
// Raw code of the dummy unit used for vision
private constant integer DUMMY = 'dumi'
// Needed, don't touch.
private location LOC = Location(0., 0.)
endglobals
private interface MissileEvents
method onHit takes unit hit returns boolean defaults false
method onMissile takes Missiles missile returns boolean defaults false
method onDestructable takes destructable dest returns boolean defaults false
method onItem takes item i returns boolean defaults false
method onCliff takes nothing returns boolean defaults false
method onTerrain takes nothing returns boolean defaults false
method onTileset takes integer tileset returns boolean defaults false
method onPeriod takes nothing returns boolean defaults false
method onFinish takes nothing returns boolean defaults false
method onBoundaries takes nothing returns boolean defaults false
method onPause takes nothing returns boolean defaults false
method onResume takes nothing returns boolean defaults false
method onRemove takes nothing returns nothing defaults nothing
endinterface
private function GetLocZ takes real x, real y returns real
call MoveLocation(LOC, x, y)
return GetLocationZ(LOC)
endfunction
private function GetUnitZ takes unit u returns real
return GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u)
endfunction
private function SetUnitZ takes unit u, real z returns nothing
call SetUnitFlyHeight(u, z - GetLocZ(GetUnitX(u), GetUnitY(u)), 0)
endfunction
private function GetMapCliffLevel takes nothing returns integer
return GetTerrainCliffLevel(WorldBounds.maxX, WorldBounds.maxY)
endfunction
private struct Pool
private static player player = Player(PLAYER_NEUTRAL_PASSIVE)
private static group group = CreateGroup()
timer timer
unit unit
static method recycle takes unit dummy returns nothing
if GetUnitTypeId(dummy) == DUMMY then
call GroupAddUnit(group, dummy)
call SetUnitX(dummy, WorldBounds.maxX)
call SetUnitY(dummy, WorldBounds.maxY)
call SetUnitOwner(dummy, player, false)
call PauseUnit(dummy, true)
endif
endmethod
static method retrieve takes real x, real y, real z, real face returns unit
if BlzGroupGetSize(group) > 0 then
set bj_lastCreatedUnit = FirstOfGroup(group)
call PauseUnit(bj_lastCreatedUnit, false)
call GroupRemoveUnit(group, bj_lastCreatedUnit)
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call SetUnitZ(bj_lastCreatedUnit, z)
call BlzSetUnitFacingEx(bj_lastCreatedUnit, face)
else
set bj_lastCreatedUnit = CreateUnit(player, DUMMY, x, y, face)
call SetUnitZ(bj_lastCreatedUnit, z)
call UnitRemoveAbility(bj_lastCreatedUnit, 'Amrf')
endif
return bj_lastCreatedUnit
endmethod
private static method onExpire takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call recycle(unit)
call ReleaseTimer(timer)
set timer = null
set unit = null
call deallocate()
endmethod
static method recycleTimed takes unit dummy, real delay returns nothing
local thistype this
if GetUnitTypeId(dummy) != DUMMY then
debug call BJDebugMsg("[DummyPool] Error: Trying to recycle a non dummy unit")
else
set this = thistype.allocate()
set timer = NewTimerEx(this)
set unit = dummy
call TimerStart(timer, delay, false, function thistype.onExpire)
endif
endmethod
private static method onInit takes nothing returns nothing
local integer i = 0
local unit u
loop
exitwhen i == SWEET_SPOT
set u = CreateUnit(player, DUMMY, WorldBounds.maxX, WorldBounds.maxY, 0)
call PauseUnit(u, false)
call GroupAddUnit(group, u)
call UnitRemoveAbility(u, 'Amrf')
set i = i + 1
endloop
set u = null
endmethod
endstruct
private struct Coordinates
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real square
readonly real slope
readonly real alpha
// Creates an origin - impact link.
private thistype ref
private static method math takes thistype a, thistype b returns nothing
local real dx
local real dy
loop
set dx = b.x - a.x
set dy = b.y - a.y
set dx = dx*dx + dy*dy
set dy = SquareRoot(dx)
exitwhen dx != 0. and dy != 0.
set b.x = b.x + .01
set b.z = b.z - GetLocZ(b.x -.01, b.y) + GetLocZ(b.x, b.y)
endloop
set a.square = dx
set a.distance = dy
set a.angle = Atan2(b.y - a.y, b.x - a.x)
set a.slope = (b.z - a.z)/dy
set a.alpha = Atan(a.slope)
// Set b.
if b.ref == a then
set b.angle = a.angle + bj_PI
set b.distance = dy
set b.slope = -a.slope
set b.alpha = -a.alpha
set b.square = dx
endif
endmethod
static method link takes thistype a, thistype b returns nothing
set a.ref = b
set b.ref = a
call math(a, b)
endmethod
method move takes real toX, real toY, real toZ returns nothing
set x = toX
set y = toY
set z = toZ + GetLocZ(toX, toY)
if ref != this then
call math(this, ref)
endif
endmethod
method destroy takes nothing returns nothing
call .deallocate()
endmethod
static method create takes real x, real y, real z returns Coordinates
local thistype this = thistype.allocate()
set ref = this
call move(x, y, z)
return this
endmethod
endstruct
/* -------------------------------------------------------------------------- */
/* System */
/* -------------------------------------------------------------------------- */
private module OnHit
set o = origin
set h = height
set c = open
set d = o.distance
if .onHit.exists then
if allocated and collision > 0 then
call GroupEnumUnitsInRange(group, x, y, collision + COLLISION_SIZE, null)
loop
set u = FirstOfGroup(group)
exitwhen u == null
if not HaveSavedBoolean(table, this, GetHandleId(u)) then
if IsUnitInRangeXY(u, x, y, collision) then
if collideZ then
set dx = GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u)
set dy = BlzGetUnitCollisionSize(u)
if dx + dy >= z - collision and dx <= z + collision then
call SaveBoolean(table, this, GetHandleId(u), true)
if allocated and .onHit(u) then
call terminate()
exitwhen true
endif
endif
else
call SaveBoolean(table, this, GetHandleId(u), true)
if allocated and .onHit(u) then
call terminate()
exitwhen true
endif
endif
endif
endif
call GroupRemoveUnit(group, u)
endloop
endif
endif
endmodule
private module OnMissile
if .onMissile.exists then
if allocated and collision > 0 then
set k = 0
loop
exitwhen k > count
set missile = collection[k]
if missile != this then
if not HaveSavedBoolean(table, this, missile) then
set dx = missile.x - x
set dy = missile.y - y
if SquareRoot(dx*dx + dy*dy) <= collision then
call SaveBoolean(table, this, missile, true)
if allocated and .onMissile(missile) then
call terminate()
exitwhen true
endif
endif
endif
endif
set k = k + 1
endloop
endif
endif
endmodule
private module OnDestructable
if .onDestructable.exists then
if allocated and collision > 0 then
set dx = collision
call SetRect(rect, x - dx, y - dx, x + dx, y + dx)
call EnumDestructablesInRect(rect, null, function thistype.onDest)
endif
endif
endmodule
private module OnItem
if .onItem.exists then
if allocated and collision > 0 then
set dx = collision
call SetRect(rect, x - dx, y - dx, x + dx, y + dx)
call EnumItemsInRect(rect, null, function thistype.onItems)
endif
endif
endmodule
private module OnCliff
if .onCliff.exists then
set dx = GetTerrainCliffLevel(nextX, nextY)
set dy = GetTerrainCliffLevel(x, y)
if dy < dx and z < (dx - GetMapCliffLevel())*bj_CLIFFHEIGHT then
if allocated and .onCliff() then
call terminate()
endif
endif
endif
endmodule
private module OnTerrain
if .onTerrain.exists then
if GetLocZ(x, y) > z then
if allocated and .onTerrain() then
call terminate()
endif
endif
endif
endmodule
private module OnTileset
if .onTileset.exists then
set k = GetTerrainType(x, y)
if k != tileset then
if allocated and .onTileset(k) then
call terminate()
endif
endif
set tileset = k
endif
endmodule
private module OnPeriod
if .onPeriod.exists then
if allocated and .onPeriod() then
call terminate()
endif
endif
endmodule
private module OnOrient
// Homing or not
set u = target
if u != null and GetUnitTypeId(u) != 0 then
call impact.move(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + toZ)
set dx = impact.x - nextX
set dy = impact.y - nextY
set a = Atan2(dy, dx)
set travel = o.distance - SquareRoot(dx*dx + dy*dy)
else
set a = o.angle
set target = null
endif
// turn rate
if turn != 0 and not (Cos(cA-a) >= Cos(turn)) then
if Sin(a-cA) >= 0 then
set cA = cA + turn
else
set cA = cA - turn
endif
else
set cA = a
endif
set vel = veloc*dilation
set yaw = cA
set s = travel + vel
set veloc = veloc + acceleration
set travel = s
set pitch = o.alpha
set prevX = x
set prevY = y
set prevZ = z
set x = nextX
set y = nextY
set z = nextZ
set nextX = x + vel*Cos(yaw)
set nextY = y + vel*Sin(yaw)
// arc calculation
if h != 0 or o.slope != 0 then
set nextZ = 4*h*s*(d-s)/(d*d) + o.slope*s + o.z
set pitch = pitch - Atan(((4*h)*(2*s - d))/(d*d))
endif
// curve calculation
if c != 0 then
set dx = 4*c*s*(d-s)/(d*d)
set a = yaw + bj_PI/2
set x = x + dx*Cos(a)
set y = y + dx*Sin(a)
set yaw = yaw + Atan(-((4*c)*(2*s - d))/(d*d))
endif
endmodule
private module OnFinish
if s >= d - 0.0001 then
set finished = true
if .onFinish.exists then
if allocated and .onFinish() then
call terminate()
else
if travel > 0 and not paused then
call terminate()
endif
endif
else
call terminate()
endif
else
if not roll then
call effect.orient(yaw, -pitch, 0)
else
call effect.orient(yaw, -pitch, Atan2(c, h))
endif
endif
endmodule
private module OnBoundaries
if not effect.move(x, y, z) then
if .onBoundaries.exists then
if allocated and .onBoundaries() then
call terminate()
endif
endif
else
if dummy != null then
call SetUnitX(dummy, x)
call SetUnitY(dummy, y)
endif
endif
endmodule
private module OnPause
set pid = pid + 1
set pkey = pid
set frozen[pid] = this
if .onPause.exists then
if allocated and .onPause() then
call terminate()
endif
endif
endmodule
private module OnResume
local thistype aux
set paused = flag
if not paused and pkey != -1 then
set id = id + 1
set missiles[id] = this
set aux = frozen[pid]
set aux.pkey = pkey
set frozen[pkey] = frozen[pid]
set pid = pid - 1
set pkey = -1
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
set dilation = (id + 1)/SWEET_SPOT
else
set dilation = 1.
endif
if id == 0 then
call TimerStart(timer, PERIOD, true, function thistype.move)
endif
if .onResume.exists then
if allocated and .onResume() then
call terminate()
else
if finished then
call terminate()
endif
endif
else
if finished then
call terminate()
endif
endif
endif
endmodule
private module OnRemove
local thistype aux
if allocated and launched then
set allocated = false
if pkey != -1 then
set aux = frozen[pid]
set aux.pkey = pkey
set frozen[pkey] = frozen[pid]
set pid = pid - 1
set pkey = -1
endif
if .onRemove.exists then
call .onRemove()
endif
if dummy != null then
call Pool.recycle(dummy)
endif
set aux = collection[count]
set aux.index = index
set collection[index] = collection[count]
set count = count - 1
set index = -1
call origin.destroy()
call impact.destroy()
call effect.destroy()
call reset()
call FlushChildHashtable(table, this)
endif
endmodule
private module Operators
/* -------------------------- Model of the missile -------------------------- */
method operator model= takes string fx returns nothing
call DestroyEffect(effect.effect)
set effect.path = fx
set effect.effect = AddSpecialEffect(fx, origin.x, origin.y)
call BlzSetSpecialEffectZ(effect.effect, origin.z)
call BlzSetSpecialEffectYaw(effect.effect, cA)
endmethod
method operator model takes nothing returns string
return effect.path
endmethod
/* ----------------------------- Curved movement ---------------------------- */
method operator curve= takes real value returns nothing
set open = Tan(value*bj_DEGTORAD)*origin.distance
endmethod
method operator curve takes nothing returns real
return Atan(open/origin.distance)*bj_RADTODEG
endmethod
/* ----------------------------- Arced Movement ----------------------------- */
method operator arc= takes real value returns nothing
set height = Tan(value*bj_DEGTORAD)*origin.distance/4
endmethod
method operator arc takes nothing returns real
return Atan(4*height/origin.distance)*bj_RADTODEG
endmethod
/* ------------------------------ Effect scale ------------------------------ */
method operator scale= takes real value returns nothing
set effect.size = value
call effect.scale(effect.effect, value)
endmethod
method operator scale takes nothing returns real
return effect.size
endmethod
/* ------------------------------ Missile Speed ----------------------------- */
method operator speed= takes real newspeed returns nothing
local real d = origin.distance
local real s
local real vel
set veloc = newspeed*PERIOD
set vel = veloc*dilation
set s = travel + vel
set nextX = x + vel*Cos(cA)
set nextY = y + vel*Sin(cA)
if height != 0 or origin.slope != 0 then
set nextZ = 4*height*s*(d-s)/(d*d) + origin.slope*s + origin.z
set z = nextZ
endif
endmethod
method operator speed takes nothing returns real
return veloc/PERIOD
endmethod
/* ------------------------------- Flight Time ------------------------------ */
method operator duration= takes real flightTime returns nothing
local real d = origin.distance
local real s
local real vel
set veloc = RMaxBJ(0.00000001, (origin.distance - travel)*PERIOD/RMaxBJ(0.00000001, flightTime))
set time = flightTime
set vel = veloc*dilation
set s = travel + vel
set nextX = x + vel*Cos(cA)
set nextY = y + vel*Sin(cA)
if height != 0 or origin.slope != 0 then
set nextZ = 4*height*s*(d-s)/(d*d) + origin.slope*s + origin.z
set z = nextZ
endif
endmethod
method operator duration takes nothing returns real
return time
endmethod
/* ------------------------------- Sight Range ------------------------------ */
method operator vision= takes real sightRange returns nothing
set sight = sightRange
if dummy == null then
if owner == null then
if source != null then
set dummy = Pool.retrieve(x, y, z, 0)
call SetUnitOwner(dummy, GetOwningPlayer(source), false)
call BlzSetUnitRealField(dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
endif
else
set dummy = Pool.retrieve(x, y, z, 0)
call SetUnitOwner(dummy, owner, false)
call BlzSetUnitRealField(dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
endif
else
call SetUnitOwner(dummy, owner, false)
call BlzSetUnitRealField(dummy, UNIT_RF_SIGHT_RADIUS, sightRange)
endif
endmethod
method operator vision takes nothing returns real
return sight
endmethod
/* ------------------------------- Time Scale ------------------------------- */
method operator timeScale= takes real newTimeScale returns nothing
set effect.timeScale = newTimeScale
endmethod
method operator timeScale takes nothing returns real
return effect.timeScale
endmethod
/* ---------------------------------- Alpha --------------------------------- */
method operator alpha= takes integer newAlpha returns nothing
set effect.alpha = newAlpha
endmethod
method operator alpha takes nothing returns integer
return effect.alpha
endmethod
/* ------------------------------ Player Color ------------------------------ */
method operator playerColor= takes integer playerId returns nothing
set effect.playerColor = playerId
endmethod
method operator playerColor takes nothing returns integer
return effect.playerColor
endmethod
/* -------------------------------- Animation ------------------------------- */
method operator animation= takes integer animType returns nothing
set effect.animation = animType
endmethod
method operator animation takes nothing returns integer
return effect.animation
endmethod
endmodule
private module Methods
/* --------------------------- Bounce and Deflect --------------------------- */
method bounce takes nothing returns nothing
call origin.move(x, y, z - GetLocZ(x, y))
set travel = 0
set finished = false
endmethod
method deflect takes real tx, real ty, real tz returns nothing
local real locZ = GetLocZ(x, y)
set target = null
set toZ = tz
if z < locZ and .onTerrain.exists then
set nextX = prevX
set nextY = prevY
set nextZ = prevZ
endif
call impact.move(tx, ty, tz)
call origin.move(x, y, z - locZ)
set travel = 0
set finished = false
endmethod
method deflectTarget takes unit u returns nothing
call deflect(GetUnitX(u), GetUnitY(u), toZ)
set target = u
endmethod
/* ---------------------------- Flush hit targets --------------------------- */
method flushAll takes nothing returns nothing
call FlushChildHashtable(table, this)
endmethod
method flush takes widget w returns nothing
if w != null then
call RemoveSavedBoolean(table, this, GetHandleId(w))
endif
endmethod
method hitted takes widget w returns boolean
return HaveSavedBoolean(table, this, GetHandleId(w))
endmethod
/* ----------------------- Missile attachment methods ----------------------- */
method attach takes string model, real dx, real dy, real dz, real scale returns effect
return effect.attach(model, dx, dy, dz, scale)
endmethod
method detach takes effect attachment returns nothing
if attachment != null then
call effect.detach(attachment)
endif
endmethod
/* ------------------------------ Missile Pause ----------------------------- */
method pause takes boolean flag returns nothing
implement OnResume
endmethod
/* ---------------------------------- Color --------------------------------- */
method color takes integer red, integer green, integer blue returns nothing
call effect.setColor(red, green, blue)
endmethod
/* ---------------------- Destructable collision method --------------------- */
static method onDest takes nothing returns nothing
local thistype this = temp
local destructable d = GetEnumDestructable()
local real dz
local real tz
if not HaveSavedBoolean(table, this, GetHandleId(d)) then
if collideZ then
set dz = GetLocZ(GetWidgetX(d), GetWidgetY(d))
set tz = GetDestructableOccluderHeight(d)
if dz + tz >= z - collision and dz <= z + collision then
call SaveBoolean(table, this, GetHandleId(d), true)
if allocated and .onDestructable(d) then
set d = null
call terminate()
return
endif
endif
else
call SaveBoolean(table, this, GetHandleId(d), true)
if allocated and .onDestructable(d) then
set d = null
call terminate()
return
endif
endif
endif
set d = null
endmethod
/* -------------------------- Item collision method ------------------------- */
static method onItems takes nothing returns nothing
local thistype this = temp
local item i = GetEnumItem()
local real dz
if not HaveSavedBoolean(table, this, GetHandleId(i)) then
if collideZ then
set dz = GetLocZ(GetItemX(i), GetItemY(i))
if dz + ITEM_SIZE >= z - collision and dz <= z + collision then
call SaveBoolean(table, this, GetHandleId(i), true)
if allocated and .onItem(i) then
set i = null
call terminate()
return
endif
endif
else
call SaveBoolean(table, this, GetHandleId(i), true)
if allocated and .onItem(i) then
set i = null
call terminate()
return
endif
endif
endif
set i = null
endmethod
/* -------------------------------- Terminate ------------------------------- */
method terminate takes nothing returns nothing
implement OnRemove
endmethod
endmodule
struct Missiles extends MissileEvents
private static timer timer = CreateTimer()
private static group group = CreateGroup()
private static rect rect = Rect(0., 0., 0., 0.)
private static hashtable table = InitHashtable()
private static integer last = 0
private static thistype temp = 0
private static integer id = -1
private static integer pid = -1
private static thistype array missiles
private static thistype array frozen
private static real dilation = 1
readonly static thistype array collection
readonly static integer count = -1
private real cA
private real height
private real open
private real toZ
private real time
private real sight
private unit dummy
private integer pkey
private integer index
Coordinates impact
Coordinates origin
MissileEffect effect
readonly real x
readonly real y
readonly real z
readonly real prevX
readonly real prevY
readonly real prevZ
readonly real nextX
readonly real nextY
readonly real nextZ
readonly real turn
readonly real veloc
readonly real travel
readonly boolean launched
readonly boolean allocated
readonly boolean finished
readonly boolean paused
readonly integer tileset
unit source
unit target
player owner
boolean collideZ
real collision
real damage
real acceleration
integer data
integer type
boolean roll
implement Operators
implement Methods
/* ------------------------------ Reset members ----------------------------- */
private method reset takes nothing returns nothing
set launched = false
set finished = false
set collideZ = false
set paused = false
set roll = false
set source = null
set target = null
set owner = null
set dummy = null
set open = 0.
set height = 0.
set veloc = 0.
set acceleration = 0.
set collision = 0.
set damage = 0.
set travel = 0.
set turn = 0.
set time = 0.
set sight = 0.
set data = 0
set type = 0
set tileset = 0
set pkey = -1
set index = -1
endmethod
/* -------------------------- Destroys the missile -------------------------- */
private method remove takes integer i returns integer
if paused then
implement OnPause
else
implement OnRemove
endif
set missiles[i] = missiles[id]
set id = id - 1
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
set dilation = (id + 1)/SWEET_SPOT
else
set dilation = 1
endif
if id == -1 then
call PauseTimer(timer)
endif
if not allocated then
call deallocate()
endif
return i - 1
endmethod
/* ---------------------------- Missiles movement --------------------------- */
private static method move takes nothing returns nothing
local integer j = 0
local integer i
local integer k
local unit u
local real a
local real d
local real s
local real h
local real c
local real dx
local real dy
local real vel
local real yaw
local real pitch
local Missiles missile
local Coordinates o
local thistype this
if SWEET_SPOT > 0 then
set i = last
else
set i = 0
endif
loop
exitwhen ((j >= SWEET_SPOT and SWEET_SPOT > 0) or j > id)
set this = missiles[i]
set temp = this
if allocated and not paused then
implement OnHit
implement OnMissile
implement OnDestructable
implement OnItem
implement OnCliff
implement OnTerrain
implement OnTileset
implement OnPeriod
implement OnOrient
implement OnFinish
implement OnBoundaries
else
set i = remove(i)
set j = j - 1
endif
set i = i + 1
set j = j + 1
if i > id and SWEET_SPOT > 0 then
set i = 0
endif
endloop
set last = i
set u = null
endmethod
/* --------------------------- Launch the Missile --------------------------- */
method launch takes nothing returns nothing
if not launched and allocated then
set launched = true
set id = id + 1
set missiles[id] = this
set count = count + 1
set index = count
set collection[count] = this
if id + 1 > SWEET_SPOT and SWEET_SPOT > 0 then
set dilation = (id + 1)/SWEET_SPOT
else
set dilation = 1.
endif
if id == 0 then
call TimerStart(timer, PERIOD, true, function thistype.move)
endif
endif
endmethod
/* --------------------------- Main Creator method -------------------------- */
static method create takes real x, real y, real z, real toX, real toY, real toZ returns thistype
local thistype this = thistype.allocate()
call .reset()
set .origin = Coordinates.create(x, y, z)
set .impact = Coordinates.create(toX, toY, toZ)
set .effect = MissileEffect.create(x, y, origin.z)
call Coordinates.link(origin, impact)
set .allocated = true
set .cA = origin.angle
set .x = x
set .y = y
set .z = impact.z
set .prevX = x
set .prevY = y
set .prevZ = impact.z
set .nextX = x
set .nextY = y
set .nextZ = impact.z
set .toZ = toZ
return this
endmethod
endstruct
endlibrary
library MissileUtils requires Missiles, Alloc
/* ------------------------------------- Missile Utils v2.8 ------------------------------------- */
// This is a simple Utils library for the Relativistic Missiles system.
// Credits:
// Sevion for the Alloc module
// - www.hiveworkshop.com/threads/snippet-alloc.192348/
/* ---------------------------------------- By Chopinski ---------------------------------------- */
/* ---------------------------------------------------------------------------------------------- */
/* JASS API */
/* ---------------------------------------------------------------------------------------------- */
function CreateMissileGroup takes nothing returns MissileGroup
return MissileGroup.create()
endfunction
function DestroyMissileGroup takes MissileGroup missiles returns nothing
if missiles != 0 then
call missiles.clear()
call missiles.destroy()
endif
endfunction
function MissileGroupGetSize takes MissileGroup missiles returns integer
if missiles != 0 then
return missiles.size
else
return 0
endif
endfunction
function GroupMissileAt takes MissileGroup missiles, integer position returns Missiles
if missiles != 0 then
return missiles.missileAt(position)
else
return 0
endif
endfunction
function ClearMissileGroup takes MissileGroup missiles returns nothing
if missiles != 0 then
call missiles.clear()
endif
endfunction
function IsMissileInGroup takes Missiles missile, MissileGroup missiles returns boolean
if missiles != 0 and missile != 0 then
if missiles.size > 0 then
return missiles.contains(missile)
else
return false
endif
else
return false
endif
endfunction
function GroupRemoveMissile takes MissileGroup missiles, Missiles missile returns nothing
if missiles != 0 and missile != 0 then
if missiles.size > 0 then
call missiles.remove(missile)
endif
endif
endfunction
function GroupAddMissile takes MissileGroup missiles, Missiles missile returns nothing
if missiles != 0 and missile != 0 then
if not missiles.contains(missile) then
call missiles.insert(missile)
endif
endif
endfunction
function GroupPickRandomMissile takes MissileGroup missiles returns Missiles
if missiles != 0 then
if missiles.size > 0 then
return missiles.missileAt(GetRandomInt(0, missiles.size - 1))
else
return 0
endif
else
return 0
endif
endfunction
function FirstOfMissileGroup takes MissileGroup missiles returns Missiles
if missiles != 0 then
if missiles.size > 0 then
return missiles.group.next.missile
else
return 0
endif
else
return 0
endif
endfunction
function GroupAddMissileGroup takes MissileGroup source, MissileGroup destiny returns nothing
if source != 0 and destiny != 0 then
if source.size > 0 and source != destiny then
call destiny.addGroup(source)
endif
endif
endfunction
function GroupRemoveMissileGroup takes MissileGroup source, MissileGroup destiny returns nothing
if source != 0 and destiny != 0 then
if source == destiny then
call source.clear()
elseif source.size > 0 then
call destiny.removeGroup(source)
endif
endif
endfunction
function GroupEnumMissilesOfType takes MissileGroup missiles, integer whichType returns nothing
local integer i
local Missiles missile
if missiles != 0 then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count
set missile = Missiles.collection[i]
if missile.type == whichType then
call missiles.insert(missile)
endif
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesOfTypeCounted takes MissileGroup missiles, integer whichType, integer amount returns nothing
local integer i
local integer j = amount
local Missiles missile
if missiles != 0 then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count or j == 0
set missile = Missiles.collection[i]
if missile.type == whichType then
call missiles.insert(missile)
endif
set j = j - 1
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesOfPlayer takes MissileGroup missiles, player p returns nothing
local integer i
local Missiles missile
if missiles != 0 then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count
set missile = Missiles.collection[i]
if missile.owner == p then
call missiles.insert(missile)
endif
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesOfPlayerCounted takes MissileGroup missiles, player p, integer amount returns nothing
local integer i
local integer j = amount
local Missiles missile
if missiles != 0 then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count or j == 0
set missile = Missiles.collection[i]
if missile.owner == p then
call missiles.insert(missile)
endif
set j = j - 1
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesInRect takes MissileGroup missiles, rect r returns nothing
local integer i
local Missiles missile
if missiles != 0 and r != null then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count
set missile = Missiles.collection[i]
if GetRectMinX(r) <= missile.x and missile.x <= GetRectMaxX(r) and GetRectMinY(r) <= missile.y and missile.y <= GetRectMaxY(r) then
call missiles.insert(missile)
endif
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesInRectCounted takes MissileGroup missiles, rect r, integer amount returns nothing
local integer i
local integer j = amount
local Missiles missile
if missiles != 0 and r != null then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count or j == 0
set missile = Missiles.collection[i]
if GetRectMinX(r) <= missile.x and missile.x <= GetRectMaxX(r) and GetRectMinY(r) <= missile.y and missile.y <= GetRectMaxY(r) then
call missiles.insert(missile)
endif
set j = j - 1
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesInRangeOfLoc takes MissileGroup missiles, location loc, real radius returns nothing
local real dx
local real dy
local integer i
local Missiles missile
if missiles != 0 and radius > 0 and loc != null then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count
set missile = Missiles.collection[i]
set dx = missile.x - GetLocationX(loc)
set dy = missile.y - GetLocationY(loc)
if SquareRoot(dx*dx + dy*dy) <= radius then
call missiles.insert(missile)
endif
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesInRangeOfLocCounted takes MissileGroup missiles, location loc, real radius, integer amount returns nothing
local real dx
local real dy
local integer i
local integer j = amount
local Missiles missile
if missiles != 0 and radius > 0 and loc != null then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count or j == 0
set missile = Missiles.collection[i]
set dx = missile.x - GetLocationX(loc)
set dy = missile.y - GetLocationY(loc)
if SquareRoot(dx*dx + dy*dy) <= radius then
call missiles.insert(missile)
endif
set j = j - 1
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesInRange takes MissileGroup missiles, real x, real y, real radius returns nothing
local real dx
local real dy
local integer i
local Missiles missile
if missiles != 0 and radius > 0 then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count
set missile = Missiles.collection[i]
set dx = missile.x - x
set dy = missile.y - y
if SquareRoot(dx*dx + dy*dy) <= radius then
call missiles.insert(missile)
endif
set i = i + 1
endloop
endif
endif
endfunction
function GroupEnumMissilesInRangeCounted takes MissileGroup missiles, real x, real y, real radius, integer amount returns nothing
local real dx
local real dy
local integer i
local integer j = amount
local Missiles missile
if missiles != 0 and radius > 0 then
if Missiles.count > -1 then
set i = 0
if missiles.size > 0 then
call missiles.clear()
endif
loop
exitwhen i > Missiles.count or j == 0
set missile = Missiles.collection[i]
set dx = missile.x - x
set dy = missile.y - y
if SquareRoot(dx*dx + dy*dy) <= radius then
call missiles.insert(missile)
endif
set j = j - 1
set i = i + 1
endloop
endif
endif
endfunction
/* ---------------------------------------------------------------------------------------------- */
/* System */
/* ---------------------------------------------------------------------------------------------- */
private module LinkedList
readonly thistype next
readonly thistype prev
method init takes nothing returns thistype
set next = this
set prev = this
return this
endmethod
method pushBack takes thistype node returns thistype
set node.prev = prev
set node.next = this
set prev.next = node
set prev = node
return node
endmethod
method pushFront takes thistype node returns thistype
set node.prev = this
set node.next = next
set next.prev = node
set next = node
return node
endmethod
method pop takes nothing returns nothing
set prev.next = next
set next.prev = prev
endmethod
endmodule
private struct MGroup extends array
implement LinkedList
implement Alloc
Missiles missile
method remove takes nothing returns nothing
call pop()
call deallocate()
endmethod
method insert takes Missiles m returns thistype
local thistype node = pushBack(allocate())
set node.missile = m
return node
endmethod
static method create takes nothing returns thistype
return thistype(allocate()).init()
endmethod
endstruct
struct MissileGroup
MGroup group
integer size
method destroy takes nothing returns nothing
call group.deallocate()
call deallocate()
endmethod
method missileAt takes integer i returns Missiles
local MGroup node = group.next
local integer j = 0
if size > 0 and i <= size - 1 then
loop
exitwhen j == i
set node = node.next
set j = j + 1
endloop
return node.missile
else
return 0
endif
endmethod
method remove takes Missiles missile returns nothing
local MGroup node = group.next
loop
exitwhen node == group
if node.missile == missile then
set size = size - 1
call node.remove()
exitwhen true
endif
set node = node.next
endloop
endmethod
method insert takes Missiles missile returns nothing
set size = size + 1
call group.insert(missile)
endmethod
method clear takes nothing returns nothing
local MGroup node = group.next
loop
exitwhen node == group
call node.remove()
set node = node.next
endloop
set size = 0
endmethod
method contains takes Missiles missile returns boolean
local MGroup node = group.next
local boolean found = false
loop
exitwhen node == group
if node.missile == missile then
set found = true
exitwhen true
endif
set node = node.next
endloop
return found
endmethod
method addGroup takes MissileGroup source returns nothing
local MGroup node = source.group.next
loop
exitwhen node == source.group
if not contains(node.missile) then
call insert(node.missile)
endif
set node = node.next
endloop
endmethod
method removeGroup takes MissileGroup source returns nothing
local MGroup node = source.group.next
loop
exitwhen node == source.group
if contains(node.missile) then
call remove(node.missile)
endif
set node = node.next
endloop
endmethod
static method create takes nothing returns thistype
local thistype this = thistype.allocate()
set group = MGroup.create()
set size = 0
return this
endmethod
endstruct
endlibrary
library CustomBuildSystem uses UnitDex, UIUtilities, Table, TimerUtils, CustomBuildData, GetClosestWidget, RootUprootEvent, Recolour, DefaultTextTag, DelayedOrder
globals
private constant integer BUILD_MENU_ABIL_ID = 'A04F'
private constant integer BUILD_MENU_ICON_ABIL_ID = 'A04I'
private constant integer RELEASE_ABIL_ID = 'A022'
private constant integer DUMMY_BUILDER = 'dumb'
private constant real DUMMY_LIFESPAN = 15.
private constant real DUMMY_SPAWN_DISTANCE = 128.
private constant string NO_VIABLE_SOURCE_MSG = "There are no nearby Monoliths"
// icons
private constant real OFFSCREEN_BORDER = .04
private constant real ICON_FRAME_OFFSET = -.0005
private constant real ICON_SIZE = .03
private constant real TOP_PADDING = .015
private constant real SIDE_PADDING = .012
private constant real SPACING = .01
private constant real BAR_HEIGHT = .006 // only relevant to MENU_HEIGHT at the end
private constant real BAR_OFFSET = .00065
private constant real BAR_FONT_SIZE = .0055
private constant real PAGE_NUM_LABEL_SPACE = .02
private constant real PAGE_NUM_LABEL_X_OFFSET = 0.
private constant real PAGE_NUM_LABEL_Y_OFFSET = .02
private constant real FONT_SIZE = .006
private constant string FONT = "fonts\\frizqt__.ttf"
// menu panel
private constant integer MAX_ITEMS_PER_PAGE = 8
private constant real MENU_LOC_X = 0.005
private constant real MENU_LOC_Y = .3675
private constant real MENU_WIDTH = SIDE_PADDING * 2 + ICON_SIZE
private constant real MENU_HEIGHT = BAR_HEIGHT + (TOP_PADDING * 2) + (ICON_SIZE * MAX_ITEMS_PER_PAGE) + /*
*/ (SPACING * (MAX_ITEMS_PER_PAGE - 1)) + PAGE_NUM_LABEL_SPACE
// bar
private constant integer DEFAULT_BAR_COLOUR = BlzConvertColor(255, 20, 160, 255)
private constant integer READY_BAR_COLOUR = BlzConvertColor(255, 120, 255, 0)
// page buttons
private constant real BUTTON_SIZE = .018
private constant real BUTTON_X_OFFSET = 0.
private constant real BUTTON_Y_OFFSET = -.002
// double clicking
private constant real INTERVAL = .05
private constant real DOUBLE_CLICK_TIMEOUT = .25
private trigger onSpellEffectListener = CreateTrigger()
private trigger onUpArrowListener = CreateTrigger()
private trigger onDownArrowListener = CreateTrigger()
private player FilterPlayer = Player(27)
framehandle masterAnchor = null
framehandle masterFrame = null
framehandle masterFrameBorder = null
// framehandle showHideButton = null // to do
framehandle pageLabel = null
framehandle pageText = null
framehandle upArrowButton = null
framehandle downArrowButton = null
framehandle upArrowButtonBackdrop = null
framehandle downArrowButtonBackdrop = null
Table structTable = 0
IntegerList array pFrameList
boolean array isFrameVisible
timer array doubleClickClock
unit array lastSelectedUnit
boolean array isTimerOn
real array doubleClickTimeOut
// display page
private integer array playerPageNumber
private integer array playerPageNumberMax
private integer array counterLowBound // if count goes below this, reduce playerPageNumberMax
private integer array counterHighBound // if count goes above this, increase playerPageNumberMax
private integer array counter
private integer array hiddenArrowsCounter
endglobals
/* ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//
// SANDBORN BATTERY
*/
struct SandbornBattery
unit dummy
player owner
integer goldCost
integer woodCost
boolean refundCost = true
trigger deathListener = CreateTrigger()
trigger spellEffectListener = CreateTrigger()
private method destroy takes nothing returns nothing
call structTable.remove(GetHandleId(this.dummy))
call DestroyTrigger(this.deathListener)
call DestroyTrigger(this.spellEffectListener)
set this.dummy = null
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
local real x = GetUnitX(this.dummy)
local real y = GetUnitY(this.dummy)
if this.refundCost then
call SetPlayerState(this.owner, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(this.owner, PLAYER_STATE_RESOURCE_GOLD) + this.goldCost)
call SetPlayerState(this.owner, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(this.owner, PLAYER_STATE_RESOURCE_LUMBER) + this.woodCost)
call defaultTextTagEx("+" + I2S(this.goldCost), x - 100., y, this.owner, COLOUR_GOLD)
call defaultTextTagEx("+" + I2S(this.woodCost), x, y, this.owner, COLOUR_LUMBER)
endif
call this.destroy()
endmethod
private static method onDummyBuild takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetSpellAbilityUnit())]
call ShowUnit(this.dummy, false)
call UnitApplyTimedLife(this.dummy, 'BTLF', .01)
set this.refundCost = false
call this.destroy()
endmethod
static method new takes player owner, real x, real y, real angle, integer abilId, real xTarget, real yTarget, integer goldRefund, integer woodRefund returns thistype
local thistype this = allocate()
set this.dummy = CreateUnit(owner, DUMMY_BUILDER, x, y, angle * bj_RADTODEG)
call UnitApplyTimedLife(this.dummy, 'BTLF', DUMMY_LIFESPAN)
call UnitAddAbility(this.dummy, abilId)
call SetUnitAbilityLevel(this.dummy, abilId, 2)
call Recolour.fadeIn(this.dummy, .5)
call DelayedOrderXY.start(this.dummy, 852619, 0., xTarget, yTarget) // order buildtiny?
set this.owner = owner
set this.goldCost = goldRefund
set this.woodCost = woodRefund
call TriggerRegisterDeathEvent(this.deathListener, this.dummy)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call TriggerRegisterUnitEvent(this.spellEffectListener, this.dummy, EVENT_UNIT_SPELL_EFFECT)
call TriggerAddAction(this.spellEffectListener, function thistype.onDummyBuild)
set structTable[GetHandleId(this.dummy)] = this
return this
endmethod
endstruct
/* ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//
// BUILDER POINTER ARROW
*/
struct ReadyPointerArrow
framehandle pointer
framehandle parent
player owner
integer pID
real progress = 0.
real speed
integer arrayIndex
boolean moveRight = true
boolean isVisible
private static integer count = 0
private static timer clock = null
private static thistype array pointerArray
private static constant real DISPLACEMENT_START_OFFSET = .015
private static constant real DISPLACEMENT_END_OFFSET = .03
private static constant real DISPLACEMENT = DISPLACEMENT_START_OFFSET - DISPLACEMENT_END_OFFSET
private static constant real RIGHT_DISPLACEMENT_TIME = ANIMATION_PERIOD / .5
private static constant real LEFT_DISPLACEMENT_TIME = ANIMATION_PERIOD / 1.2
private static constant real POINTER_WIDTH = .075
private static constant real POINTER_HEIGHT = .075
private static constant string POINTER_PATH = "GlowPointerArrowShadow.blp"
method destroy takes nothing returns nothing
local thistype lastId = thistype.pointerArray[thistype.count]
set lastId.arrayIndex = this.arrayIndex // set the arrayIndex of the last id to the one being destroyed
set thistype.pointerArray[this.arrayIndex] = thistype.pointerArray[thistype.count] // put the last id in the current slot
set thistype.pointerArray[thistype.count] = 0 // clear the last slot, effectively overwriting the current id with the last one.
set thistype.count = thistype.count - 1
if thistype.count < 1 then
call ReleaseTimer(thistype.clock)
endif
call BlzDestroyFrame(this.pointer)
set this.pointer = null
set this.parent = null
call this.deallocate()
endmethod
static method onPeriod takes nothing returns nothing
local thistype this
local integer i = 0
local real x = 0.
local real s
loop
set i = i + 1
exitwhen i > thistype.count
set this = thistype.pointerArray[i]
if BlzFrameIsVisible(this.parent) then
if not this.isVisible then
set this.isVisible = true
set hiddenArrowsCounter[this.pID] = hiddenArrowsCounter[this.pID] - 1
if hiddenArrowsCounter[this.pID] == 0 then
if GetLocalPlayer() == this.owner then
call BlzFrameSetTexture(upArrowButtonBackdrop, "war3mapImported\\GoldenButtonUpCircle.blp", 0, true)
call BlzFrameSetTexture(downArrowButtonBackdrop, "war3mapImported\\GoldenButtonDownCircle.blp", 0, true)
endif
endif
endif
if this.moveRight then
set this.progress = this.progress + this.speed
set s = this.progress
set x = DISPLACEMENT_START_OFFSET + DISPLACEMENT * (s * s * (3 - 2 * s))
if s >= 1. then
set this.moveRight = false
set this.speed = LEFT_DISPLACEMENT_TIME
set this.progress = 1.
endif
else
set this.progress = this.progress - this.speed
set s = this.progress
set x = DISPLACEMENT_START_OFFSET + DISPLACEMENT * (s * s * (3 - 2 * s))
if s <= 0. then
set this.moveRight = true
set this.speed = RIGHT_DISPLACEMENT_TIME
set this.progress = 0.
endif
endif
if GetLocalPlayer() == this.owner then
call BlzFrameSetPoint(this.pointer, FRAMEPOINT_RIGHT, this.parent, FRAMEPOINT_LEFT, x, 0.)
endif
else
if this.isVisible then
set this.isVisible = false
set this.progress = 0.
set this.moveRight = true
set this.speed = RIGHT_DISPLACEMENT_TIME
set hiddenArrowsCounter[this.pID] = hiddenArrowsCounter[this.pID] + 1
if hiddenArrowsCounter[this.pID] == 1 then
if GetLocalPlayer() == this.owner then
call BlzFrameSetTexture(upArrowButtonBackdrop, "war3mapImported\\GoldenButtonUpCircleHighlight.blp", 0, true)
call BlzFrameSetTexture(downArrowButtonBackdrop, "war3mapImported\\GoldenButtonDownCircleHighlight.blp", 0, true)
endif
endif
endif
endif
endloop
endmethod
static method new takes framehandle parentFrame, player owner returns thistype
local thistype this = allocate()
set thistype.count = thistype.count + 1
set this.arrayIndex = thistype.count
set this.speed = RIGHT_DISPLACEMENT_TIME
set this.owner = owner
set this.pID = GetPlayerId(owner)
set this.pointer = BlzCreateFrameByType("BACKDROP", "pointer", parentFrame, "", 0)
call BlzFrameSetSize(this.pointer, POINTER_WIDTH, POINTER_HEIGHT)
call BlzFrameSetTexture(this.pointer, POINTER_PATH, 0, true)
set this.parent = parentFrame
set pointerArray[thistype.count] = this
set this.isVisible = BlzFrameIsVisible(parentFrame)
if not this.isVisible then
set hiddenArrowsCounter[this.pID] = hiddenArrowsCounter[this.pID] + 1
if hiddenArrowsCounter[this.pID] == 1 then
if GetLocalPlayer() == this.owner then
call BlzFrameSetTexture(upArrowButtonBackdrop, "war3mapImported\\GoldenButtonUpCircleHighlight.blp", 0, true)
call BlzFrameSetTexture(downArrowButtonBackdrop, "war3mapImported\\GoldenButtonDownCircleHighlight.blp", 0, true)
endif
endif
endif
if thistype.count == 1 then
set thistype.clock = NewTimer()
call TimerStart(thistype.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
return this
endmethod
endstruct
/* ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//
// BUILD UI BUTTON
*/
struct BuilderButton
// state machine
integer state = 0
static integer STATE_IDLE = 0
static integer STATE_BUILDING = 1
static integer STATE_AWAITING_PLACEMENT = 2
// default values
unit source
player owner
boolean dequeueing
framehandle btnFrame
framehandle icon
framehandle iconImage
framehandle iconImagePushed
framehandle bar
framehandle barText
ReadyPointerArrow pointerArrow
trigger onClickListener = CreateTrigger()
trigger onDeathListener = CreateTrigger()
trigger onOrderListener = CreateTrigger()
trigger onDeselectListener = CreateTrigger()
trigger onTrainStartListener = CreateTrigger()
trigger onTrainFinishListener = CreateTrigger()
trigger onTrainCancelListener = CreateTrigger()
trigger onSpellEffectListener = CreateTrigger()
// build phase
timer clock
real buildTimeMax
real currentProgress
CustomBuildData currentData
private static method onDCTimerEnd takes nothing returns nothing
local integer pID = GetTimerData(GetExpiredTimer())
set doubleClickTimeOut[pID] = doubleClickTimeOut[pID] - INTERVAL
if doubleClickTimeOut[pID] <= 0. then
set isTimerOn[pID] = false
call ReleaseTimer(doubleClickClock[pID])
set lastSelectedUnit[pID] = null
endif
endmethod
private static method onClick takes nothing returns nothing
local thistype this = structTable[GetHandleId(BlzGetTriggerFrame())]
local player owner = GetTriggerPlayer()
local integer pID = GetPlayerId(owner)
local framehandle frame = BlzGetTriggerFrame()
call BlzFrameSetEnable(frame, false)
call BlzFrameSetEnable(frame, true)
set frame = null
if GetLocalPlayer() == owner then
call ClearSelection()
call SelectUnit(this.source, true)
if lastSelectedUnit[pID] == this.source then
call PanCameraToTimed(GetUnitX(this.source), GetUnitY(this.source), 0.)
endif
endif
set lastSelectedUnit[pID] = this.source
if not isTimerOn[pID] then
set isTimerOn[pID] = true
set doubleClickClock[pID] = NewTimerEx(pID)
set doubleClickTimeOut[pID] = DOUBLE_CLICK_TIMEOUT
call TimerStart(doubleClickClock[pID], INTERVAL, true, function thistype.onDCTimerEnd)
else
set doubleClickTimeOut[pID] = DOUBLE_CLICK_TIMEOUT
endif
endmethod
private method destroy takes nothing returns nothing
local integer pID = GetPlayerId(this.owner)
call structTable.remove(GetHandleId(this.source))
call structTable.remove(GetHandleId(this.icon))
if this.pointerArrow != 0 then
call this.pointerArrow.destroy()
endif
call BlzDestroyFrame(this.bar)
call BlzDestroyFrame(this.icon)
call BlzDestroyFrame(this.btnFrame)
set this.source = null
call DestroyTrigger(this.onClickListener)
call DestroyTrigger(this.onDeathListener)
call DestroyTrigger(this.onOrderListener)
call DestroyTrigger(this.onDeselectListener)
call DestroyTrigger(this.onTrainStartListener)
call DestroyTrigger(this.onTrainFinishListener)
call DestroyTrigger(this.onTrainCancelListener)
call DestroyTrigger(this.onSpellEffectListener)
call pFrameList[pID].removeElem(this)
set counter[pID] = counter[pID] - 1
// call BJDebugMsg("Counter: " + I2S(counter[pID]))
// call BJDebugMsg("Current LowBound: " + I2S(counterLowBound[pID]))
// call BJDebugMsg("Current HighBound: " + I2S(counterHighBound[pID]))
if counter[pID] < counterLowBound[pID] then
if playerPageNumber[pID] == playerPageNumberMax[pID] and playerPageNumber[pID] != 1 then // if viewing the last page, downgrade one.
set playerPageNumber[pID] = playerPageNumber[pID] - 1
endif
set playerPageNumberMax[pID] = playerPageNumberMax[pID] - 1
set counterHighBound[pID] = counterLowBound[pID] - 1
set counterLowBound[pID] = counterLowBound[pID] - MAX_ITEMS_PER_PAGE
endif
call thistype.refactorList(pID)
if this.state == STATE_BUILDING then
call ReleaseTimer(this.clock)
endif
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real rate = this.currentProgress / this.buildTimeMax
set this.currentProgress = this.currentProgress + INTERVAL
call BlzFrameSetValue(this.bar, rate)
call BlzFrameSetText(this.barText, I2S(R2I(rate * 100.)) + "%%")
// call BlzFrameSetVertexColor(this.bar, BlzConvertColor())
// if this.currentProgress >= this.buildTimeMax then
// set this.state = STATE_AWAITING_PLACEMENT
// call ReleaseTimer(this.clock)
// endif
endmethod
private static method onDeselect takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this = structTable[GetHandleId(u)]
if this.state == STATE_IDLE and GetUnitAbilityLevel(u, BUILD_MENU_ICON_ABIL_ID) == 0 then
call IssueImmediateOrderById(u, 852156) // order unravenform
endif
set u = null
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private method reset takes nothing returns nothing
if this.state == STATE_BUILDING then
call ReleaseTimer(this.clock)
elseif this.state == STATE_AWAITING_PLACEMENT then
call UnitRemoveAbility(this.source, this.currentData.placementAbil)
call UnitRemoveAbility(this.source, RELEASE_ABIL_ID)
call BlzUnitDisableAbility(this.source, 'A04K', false, false)
call BlzUnitDisableAbility(this.source, 'A01I', false, false)
call BlzUnitDisableAbility(this.source, 'h006', false, false)
endif
call BlzFrameSetValue(this.bar, 0.)
call BlzFrameSetText(this.barText, "")
call BlzFrameSetTexture(this.iconImage, BlzGetAbilityIcon(GetUnitTypeId(this.source)), 0, false)
call BlzFrameSetTexture(this.iconImagePushed, BlzGetAbilityIcon(GetUnitTypeId(this.source)), 0, false)
call BlzFrameSetVertexColor(this.bar, DEFAULT_BAR_COLOUR)
call this.pointerArrow.destroy()
set this.pointerArrow = 0
set this.state = STATE_IDLE
endmethod
private static method onTrainFinish takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
local integer unitTypeID = GetTrainedUnitType()
local CustomBuildData buildData = CustomBuildData.get(unitTypeID)
if buildData != 0 then
set this.state = STATE_AWAITING_PLACEMENT
call ReleaseTimer(this.clock)
call UnitAddAbility(this.source, buildData.placementAbil)
call UnitAddAbility(this.source, RELEASE_ABIL_ID)
call BlzUnitDisableAbility(this.source, 'A04K', true, false)
call BlzUnitDisableAbility(this.source, 'A01I', true, false)
call BlzUnitDisableAbility(this.source, 'h006', true, false)
call BlzFrameSetValue(this.bar, 1.)
call BlzFrameSetText(this.barText, "100%%")
call BlzFrameSetVertexColor(this.bar, READY_BAR_COLOUR)
call RemoveUnit(GetTrainedUnit())
// create ready arrow
set this.pointerArrow = ReadyPointerArrow.new(this.btnFrame, this.owner)
endif
endmethod
private static method onTrainCancel takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
if not this.dequeueing then
call this.reset()
endif
endmethod
private static method callbackStepThree takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call IssueImmediateOrderById(this.source, this.currentData.holoDummy)
set t = null
endmethod
private static method callbackStepTwo takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call IssueImmediateOrderById(this.source, 852156) // order unravenform
call TimerStart(NewTimerEx(this), 0., false, function thistype.callbackStepThree)
set t = null
endmethod
private static method callbackStepOne takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call ReleaseTimer(t)
set this.dequeueing = true
call IssueImmediateOrderById(this.source, 851976) // order cancel
set this.dequeueing = false
call TimerStart(NewTimerEx(this), 0., false, function thistype.callbackStepTwo)
set t = null
endmethod
private static method onTrainStart takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
local integer unitTypeID = GetTrainedUnitType() // also works for onTrainStart
local CustomBuildData buildData = CustomBuildData.get(unitTypeID)
if this.state != STATE_BUILDING then
set this.state = STATE_BUILDING
set this.currentData = buildData
set this.clock = NewTimerEx(this)
set this.buildTimeMax = buildData.buildTime
set this.currentProgress = 0.
call BlzFrameSetTexture(this.iconImage, buildData.iconPath, 0, false)
call BlzFrameSetTexture(this.iconImagePushed, buildData.iconPath, 0, false)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
//dequeue the training unit, morph out of the Coalescence mode, and order a hidden variant to train
call TimerStart(NewTimerEx(this), 0., false, function thistype.callbackStepOne)
endif
endmethod
private static method filter takes nothing returns boolean
return (GetUnitAbilityLevel(GetFilterUnit(), BUILD_MENU_ABIL_ID) > 0 and (not IsUnitUprooted(GetFilterUnit())) and not IsStillIndexing(GetFilterUnit()) and GetOwningPlayer(GetFilterUnit()) == FilterPlayer)
// return (GetUnitAbilityLevel(GetFilterUnit(), BUILD_MENU_ABIL_ID) > 0 and (not IsUnitUprooted(GetFilterUnit())) and not IsConstructing(GetFilterUnit()) and GetOwningPlayer(GetFilterUnit()) == FilterPlayer)
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
local integer spellID = GetSpellAbilityId()
local CustomBuildData buildData = CustomBuildData.get(spellID)
local unit dummySource
local real x
local real y
local real xx
local real yy
local real a
if buildData != 0 then
set xx = GetSpellTargetX()
set yy = GetSpellTargetY()
set FilterPlayer = this.owner
set dummySource = GetClosestUnit(xx, yy, Filter(function thistype.filter))
if dummySource != null then
set x = GetUnitX(dummySource)
set y = GetUnitY(dummySource)
set a = Atan2(yy - y, xx - x)
set x = x + Cos(a) * DUMMY_SPAWN_DISTANCE
set y = y + Sin(a) * DUMMY_SPAWN_DISTANCE
call SandbornBattery.new(this.owner, x, y, a, buildData.placementAbil, xx, yy, GetUnitGoldCost(buildData.unitType), GetUnitWoodCost(buildData.unitType))
set dummySource = null
call this.reset() // reset if the dummy is dispatched...
else
call BJDebugMsg(NO_VIABLE_SOURCE_MSG)
endif
elseif spellID == RELEASE_ABIL_ID then
call SetPlayerState(this.owner, PLAYER_STATE_RESOURCE_GOLD, GetPlayerState(this.owner, PLAYER_STATE_RESOURCE_GOLD) + R2I(GetUnitGoldCost(buildData.unitType) * .75))
call SetPlayerState(this.owner, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(this.owner, PLAYER_STATE_RESOURCE_LUMBER) + R2I(GetUnitWoodCost(buildData.unitType) * .75))
call this.reset() // ... or if the placement ability is released ONLY.
endif
endmethod
private static method onQuickCancel takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call ReleaseTimer(t)
set this.dequeueing = true
call IssueImmediateOrderById(this.source, 851976) // order cancel
set this.dequeueing = false
set t = null
endmethod
private static method onOrder takes nothing returns nothing
local thistype this = structTable[GetHandleId(GetTriggerUnit())]
local integer orderID = GetIssuedOrderId()
local CustomBuildData buildData = CustomBuildData.get(orderID)
if buildData != 0 and this.state == STATE_BUILDING and orderID != 851976 and orderID != buildData.holoDummy then
call TimerStart(NewTimerEx(this), 0., false, function thistype.onQuickCancel)
endif
endmethod
static method refactorList takes integer pID returns nothing
local thistype this
local IntegerListItem node
local IntegerListItem nodeFirst
local IntegerListItem nodeNext
local framehandle prevFrame
local integer i = 0 // +1 will make this start from 1
local integer lowBound = (playerPageNumber[pID] - 1) * MAX_ITEMS_PER_PAGE + 1
local integer highBound = lowBound + MAX_ITEMS_PER_PAGE - 1
local IntegerList list = pFrameList[pID]
if list.empty() then
call BlzFrameSetVisible(masterAnchor, false)
call BlzFrameSetVisible(pageLabel, false) // simpleframes do not inherit visibility from parents
else
set nodeFirst = list.first
set node = nodeFirst
set prevFrame = masterFrame
loop
exitwhen node == 0
set i = i + 1
set nodeNext = node.next
set this = node.data
call BlzFrameClearAllPoints(this.btnFrame)
if i >= lowBound and i <= highBound then
if i == lowBound then
if GetLocalPlayer() == this.owner then
call BlzFrameSetPoint(this.btnFrame, FRAMEPOINT_TOPLEFT, masterFrame, FRAMEPOINT_TOPLEFT, SIDE_PADDING, -TOP_PADDING)
call BlzFrameSetVisible(this.btnFrame, true)
call BlzFrameSetVisible(this.bar, true)
endif
else
if GetLocalPlayer() == this.owner then
call BlzFrameSetPoint(this.btnFrame, FRAMEPOINT_TOP, prevFrame, FRAMEPOINT_BOTTOM, 0., -SPACING)
call BlzFrameSetVisible(this.btnFrame, true)
call BlzFrameSetVisible(this.bar, true)
endif
endif
else
if GetLocalPlayer() == this.owner then
call BlzFrameSetVisible(this.btnFrame, false)
call BlzFrameSetVisible(this.bar, false)
endif
endif
set prevFrame = this.btnFrame
set node = nodeNext
endloop
endif
if GetLocalPlayer() == this.owner then
call BlzFrameSetText(pageText, "Page " + I2S(playerPageNumber[pID]) + "/" + I2S(playerPageNumberMax[pID]))
endif
endmethod
static method new takes unit u returns thistype
local thistype this = allocate()
local integer pID
set this.source = u
set this.owner = GetOwningPlayer(u)
set pID = GetPlayerId(this.owner)
call UnitRemoveAbility(u, 'ARal') // remove Rally because no need for that
// create frames
set this.btnFrame = BlzCreateFrameByType("FRAME", "Button Frame", masterFrame, "", 0)
call BlzFrameSetSize(this.btnFrame, ICON_SIZE, ICON_SIZE)
set this.icon = BlzCreateFrame("MenuItemButton", this.btnFrame, 0, 0)
call BlzFrameSetPoint(this.icon, FRAMEPOINT_TOPLEFT, this.btnFrame, FRAMEPOINT_TOPLEFT, ICON_FRAME_OFFSET, -ICON_FRAME_OFFSET)
call BlzFrameSetPoint(this.icon, FRAMEPOINT_BOTTOMRIGHT, this.btnFrame, FRAMEPOINT_BOTTOMRIGHT, -ICON_FRAME_OFFSET, ICON_FRAME_OFFSET)
set this.iconImage = BlzGetFrameByName("MenuItemButtonBackdrop", 0)
call BlzFrameSetTexture(this.iconImage, BlzGetAbilityIcon(GetUnitTypeId(u)), 0, false)
set this.iconImagePushed = BlzGetFrameByName("MenuItemButtonPushedBackdrop", 0)
call BlzFrameSetTexture(this.iconImagePushed, BlzGetAbilityIcon(GetUnitTypeId(u)), 0, false)
set this.bar = BlzCreateSimpleFrame("MyBar", this.btnFrame, 0)
set this.barText = BlzGetFrameByName("MyBarText", 0)
call BlzFrameSetPoint(this.barText, FRAMEPOINT_CENTER, this.bar, FRAMEPOINT_CENTER, 0., 0.)
call BlzFrameSetFont(this.barText, FONT, BAR_FONT_SIZE, 0)
call BlzFrameSetText(this.barText, "")
call BlzFrameSetTexture(BlzGetFrameByName("MyBarBackground", 0), "Textures\\Black32.blp", 0, false)
call BlzFrameSetPoint(this.bar, FRAMEPOINT_TOPLEFT, this.btnFrame, FRAMEPOINT_BOTTOMLEFT, 0., -BAR_OFFSET)
call BlzFrameSetPoint(this.bar, FRAMEPOINT_BOTTOMRIGHT, this.btnFrame, FRAMEPOINT_BOTTOMRIGHT, 0., -(BAR_OFFSET + BAR_HEIGHT))
call BlzFrameSetMinMaxValue(this.bar, 0., 1.)
call BlzFrameSetValue(this.bar, 0.)
call BlzFrameSetVisible(this.btnFrame, false)
call BlzFrameSetVertexColor(this.bar, DEFAULT_BAR_COLOUR)
// create events?
call BlzTriggerRegisterFrameEvent(this.onClickListener, this.icon, FRAMEEVENT_CONTROL_CLICK)
call TriggerAddAction(this.onClickListener, function thistype.onClick)
call TriggerRegisterUnitEvent(this.onOrderListener, u, EVENT_UNIT_ISSUED_ORDER)
call TriggerAddAction(this.onOrderListener, function thistype.onOrder)
call TriggerRegisterUnitEvent(this.onTrainStartListener, u, EVENT_UNIT_TRAIN_START)
call TriggerAddAction(this.onTrainStartListener, function thistype.onTrainStart)
call TriggerRegisterUnitEvent(this.onTrainFinishListener, u, EVENT_UNIT_TRAIN_FINISH)
call TriggerAddAction(this.onTrainFinishListener, function thistype.onTrainFinish)
call TriggerRegisterUnitEvent(this.onTrainCancelListener, u, EVENT_UNIT_TRAIN_CANCEL)
call TriggerAddAction(this.onTrainCancelListener, function thistype.onTrainCancel)
call TriggerRegisterUnitEvent(this.onSpellEffectListener, u, EVENT_UNIT_SPELL_EFFECT)
call TriggerAddAction(this.onSpellEffectListener, function thistype.onSpellEffect)
call TriggerRegisterDeathEvent(this.onDeathListener, u)
call TriggerAddAction(this.onDeathListener, function thistype.onDeath)
call TriggerRegisterUnitEvent(this.onDeselectListener, u, EVENT_UNIT_DESELECTED)
call TriggerAddAction(this.onDeselectListener, function thistype.onDeselect)
// store the things
set structTable[GetHandleId(this.icon)] = this
set structTable[GetHandleId(this.source)] = this
call pFrameList[pID].push(this)
set counter[pID] = counter[pID] + 1
// call BJDebugMsg("Counter: " + I2S(counter[pID]))
// call BJDebugMsg("Current LowBound: " + I2S(counterLowBound[pID]))
// call BJDebugMsg("Current HighBound: " + I2S(counterHighBound[pID]))
if counter[pID] > counterHighBound[pID] then
set playerPageNumberMax[pID] = playerPageNumberMax[pID] + 1
set counterLowBound[pID] = counterHighBound[pID] + 1
set counterHighBound[pID] = counterHighBound[pID] + MAX_ITEMS_PER_PAGE
endif
call thistype.refactorList(pID)
if not isFrameVisible[pID] then
if GetLocalPlayer() == this.owner then
call BlzFrameSetVisible(masterAnchor, true)
call BlzFrameSetVisible(pageLabel, true) // simpleframes do not inherit visibility from parents
endif
endif
return this
endmethod
endstruct
/* ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//
// CUSTOM BUILD UI
*/
struct CustomBuildUI
private static method onUpArrow takes nothing returns nothing
local player clickingPlayer = GetTriggerPlayer()
local integer pID = GetPlayerId(clickingPlayer)
call BlzFrameSetEnable(upArrowButton, false)
call BlzFrameSetEnable(upArrowButton, true)
// if playerPageNumber[pID] == playerPageNumberMax[pID] then
// set playerPageNumber[pID] = 1
// else
// set playerPageNumber[pID] = playerPageNumber[pID] + 1
// endif
if playerPageNumber[pID] == 1 then
set playerPageNumber[pID] = playerPageNumberMax[pID]
else
set playerPageNumber[pID] = playerPageNumber[pID] - 1
endif
call BuilderButton.refactorList(pID)
if GetLocalPlayer() == clickingPlayer then
call BlzFrameSetFocus(upArrowButton, false)
endif
endmethod
private static method onDownArrow takes nothing returns nothing
local player clickingPlayer = GetTriggerPlayer()
local integer pID = GetPlayerId(clickingPlayer)
call BlzFrameSetEnable(downArrowButton, false)
call BlzFrameSetEnable(downArrowButton, true)
if playerPageNumber[pID] == playerPageNumberMax[pID] then
set playerPageNumber[pID] = 1
else
set playerPageNumber[pID] = playerPageNumber[pID] + 1
endif
// if playerPageNumber[pID] == 1 then
// set playerPageNumber[pID] = playerPageNumberMax[pID]
// else
// set playerPageNumber[pID] = playerPageNumber[pID] - 1
// endif
call BuilderButton.refactorList(pID)
if GetLocalPlayer() == clickingPlayer then
call BlzFrameSetFocus(downArrowButton, false)
endif
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, BUILD_MENU_ABIL_ID) > 0 then
call BuilderButton.new(u)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
local integer i = -1
set structTable = Table.create()
call OnUnitPostIndex(function thistype.onPostIndex)
// create UI and then hide it. Reveal it when a unit with the relevant ability is indexed
// the masterAnchor is the element that moves in and out of view... if I'm not too lazy to make it move.
set masterAnchor = BlzCreateFrameByType("FRAME", "MasterAnchor", CONSOLE_UI_BACKDROP, "", 0)
call BlzFrameSetSize(masterAnchor, MENU_WIDTH, MENU_HEIGHT)
loop
set i = i + 1
exitwhen i > bj_MAX_PLAYER_SLOTS
set pFrameList[i] = IntegerList.create()
set isFrameVisible[i] = false
set playerPageNumber[i] = 1
set playerPageNumberMax[i] = 1
set counterLowBound[i] = 1
set counterHighBound[i] = MAX_ITEMS_PER_PAGE
if GetLocalPlayer() == Player(i) then
call BlzFrameSetAbsPoint(masterAnchor, FRAMEPOINT_RIGHT, playerScreenEdgeMaxX[i] + MENU_LOC_X, MENU_LOC_Y)
endif
endloop
set masterFrame = BlzCreateFrameByType("FRAME", "MasterFrame", masterAnchor, "", 0)
call BlzFrameSetPoint(masterFrame, FRAMEPOINT_TOPLEFT, masterAnchor, FRAMEPOINT_TOPLEFT, 0., 0.)
call BlzFrameSetPoint(masterFrame, FRAMEPOINT_BOTTOMRIGHT, masterAnchor, FRAMEPOINT_BOTTOMRIGHT, OFFSCREEN_BORDER, 0.) // extend the frame further right
set masterFrameBorder = BlzCreateFrame("EscMenuControlBackdropTemplate", masterFrame, 0, 0)
call BlzFrameSetAllPoints(masterFrameBorder, masterFrame)
set pageLabel = BlzCreateSimpleFrame("SimpleString", masterFrame, 0)
set pageText = BlzGetFrameByName("SimpleStringValue", 0)
call BlzFrameSetTextAlignment(pageText, TEXT_JUSTIFY_MIDDLE, TEXT_JUSTIFY_LEFT)
call BlzFrameSetFont(pageText, FONT, FONT_SIZE, 0)
call BlzFrameSetText(pageText, "Page 1/1")
call BlzFrameClearAllPoints(pageText)
call BlzFrameSetPoint(pageText, FRAMEPOINT_CENTER, pageLabel, FRAMEPOINT_CENTER, 0., 0.)
call BlzFrameSetPoint(pageLabel, FRAMEPOINT_BOTTOM, masterAnchor, FRAMEPOINT_BOTTOM, PAGE_NUM_LABEL_X_OFFSET, PAGE_NUM_LABEL_Y_OFFSET)
set upArrowButton = BlzCreateFrame("GoldenButton", masterFrame, 0, 0)
// call BlzFrameSetSize(upArrowButton, BUTTON_SIZE, BUTTON_SIZE)
set upArrowButtonBackdrop = BlzGetFrameByName("MenuItemButtonBackdrop", 0)
call BlzFrameSetTexture(upArrowButtonBackdrop, "war3mapImported\\GoldenButtonUpCircle.blp", 0, true)
call BlzFrameSetTexture(BlzGetFrameByName("MenuItemButtonPushedBackdrop", 0), "war3mapImported\\GoldenButtonUpCirclePressed.blp", 0, true)
call BlzFrameSetPoint(upArrowButton, FRAMEPOINT_CENTER, masterAnchor, FRAMEPOINT_TOP, BUTTON_X_OFFSET, BUTTON_Y_OFFSET)
call BlzTriggerRegisterFrameEvent(onUpArrowListener, upArrowButton, FRAMEEVENT_CONTROL_CLICK)
call TriggerAddAction(onUpArrowListener, function thistype.onUpArrow)
set downArrowButton = BlzCreateFrame("GoldenButton", masterFrame, 0, 0)
// call BlzFrameSetSize(upArrowButton, BUTTON_SIZE, BUTTON_SIZE)
set downArrowButtonBackdrop = BlzGetFrameByName("MenuItemButtonBackdrop", 0)
call BlzFrameSetTexture(downArrowButtonBackdrop, "war3mapImported\\GoldenButtonDownCircle.blp", 0, true)
call BlzFrameSetTexture(BlzGetFrameByName("MenuItemButtonPushedBackdrop", 0), "war3mapImported\\GoldenButtonDownCirclePressed.blp", 0, true)
call BlzFrameSetPoint(downArrowButton, FRAMEPOINT_CENTER, masterAnchor, FRAMEPOINT_BOTTOM, BUTTON_X_OFFSET, -BUTTON_Y_OFFSET)
call BlzTriggerRegisterFrameEvent(onDownArrowListener, downArrowButton, FRAMEEVENT_CONTROL_CLICK)
call TriggerAddAction(onDownArrowListener, function thistype.onDownArrow)
call BlzFrameSetVisible(masterAnchor, false)
call BlzFrameSetVisible(pageLabel, false) // simpleframes do not inherit visibility from parents
endmethod
endstruct
endlibrary
library CustomBuildData uses Table
private module initModule
private static method onInit takes nothing returns nothing
set tb = Table.create()
endmethod
endmodule
struct CustomBuildData
integer unitType
string iconPath
real buildTime
string modelPath
integer placementAbil
integer holoDummy
// Define a hashtable to store the unit data
private static Table tb
// Function to add unit data to the hashtable
static method new takes integer unitType, string iconPath, string modelPath, real buildTime, integer placementAbil, integer holoDummy returns thistype
local thistype this = allocate()
set this.unitType = unitType
set this.iconPath = iconPath
set this.modelPath = modelPath
set this.buildTime = buildTime
set this.placementAbil = placementAbil
set this.holoDummy = holoDummy
set tb[unitType] = this
set tb[placementAbil] = this
set tb[holoDummy] = this
return this
endmethod
// Function to retrieve unit data from the hashtable
static method get takes integer unitType returns thistype
return tb[unitType]
endmethod
implement initModule
endstruct
endlibrary
scope AltarOfSands
struct AltarOfSands
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o001', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNAltarOfSands.blp", /*
Model path */ "war3mapImported\\AltarOfDepthsBirthNew.mdl", /*
Build time */ 60., /*
placementAbil */ 'A021', /*
holoDummy */ 'e00C' /*
*/)
endmethod
endstruct
endscope
scope AncientShrine
struct AncientShrine
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o003', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNObelisk1.blp", /*
Model path */ "war3mapImported\\RuinedAltar.mdl", /*
Build time */ 70., /*
placementAbil */ 'A02F', /*
holoDummy */ 'e00K' /*
*/)
endmethod
endstruct
endscope
scope BuriedTemple
struct BuriedTemple
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o00B', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNBuriedTemple.blp", /*
Model path */ "war3mapImported\\BuriedTemple1.mdl", /*
Build time */ 60., /*
placementAbil */ 'A02G', /*
holoDummy */ 'e00G' /*
*/)
endmethod
endstruct
endscope
scope ConduitStone
struct ConduitStone
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o00D', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNBattleShrine.blp", /*
Model path */ "war3mapImported\\Voya_TowerBirthNew.mdl", /*
Build time */ 25., /*
placementAbil */ 'A02E', /*
holoDummy */ 'e00J' /*
*/)
endmethod
endstruct
endscope
scope Mausoleum
struct Mausoleum
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o004', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNMausoleum2.blp", /*
Model path */ "war3mapImported\\HighborneVaultOfRelics_BirthNew.mdl", /*
Build time */ 60., /*
placementAbil */ 'A02H', /*
holoDummy */ 'e00E' /*
*/)
endmethod
endstruct
endscope
scope PyramidOfLight
struct PyramidOfLight
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o000', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNPyramid.blp", /*
Model path */ "war3mapImported\\PyramidNexusLevel1.mdl", /*
Build time */ 140., /*
placementAbil */ 'A02I', /*
holoDummy */ 'e00I' /*
*/)
endmethod
endstruct
endscope
scope Reliquary
struct Reliquary
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o005', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNReliquary.blp", /*
Model path */ "war3mapImported\\SkyVault_FINAL_rewrap.mdl", /*
Build time */ 60., /*
placementAbil */ 'A02K', /*
holoDummy */ 'e00F' /*
*/)
endmethod
endstruct
endscope
scope SandPit
struct SandPit
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'o002', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNSandPit.blp", /*
Model path */ "war3mapImported\\SandPit.mdl", /*
Build time */ 60., /*
placementAbil */ 'A02L', /*
holoDummy */ 'e00D' /*
*/)
endmethod
endstruct
endscope
scope Stele
struct Stele
private static method onInit takes nothing returns nothing
call CustomBuildData.new(/*
Unit ID */ 'h000', /*
Icon path */ "ReplaceableTextures\\CommandButtons\\BTNMonolith.blp", /*
Model path */ "war3mapImported\\FloatyMonolithTweak.mdl", /*
Build time */ 30., /*
placementAbil */ 'A02J', /*
holoDummy */ 'e00H' /*
*/)
endmethod
endstruct
endscope
/*
vJass Damage Engine 5.A.0.0
This update enables compatibility with AttackIndexer.
*/
/*
JASS API:
struct Damage extends array
readonly static unit source // udg_DamageEventSource in real-time
readonly static unit target // udg_DamageEventTarget in real-time
static real amount // udg_DamageEventAmount in real-time
readonly unit sourceUnit // udg_DamageEventSource by index
readonly unit targetUnit // udg_DamageEventTarget by index
real damage // udg_DamageEventAmount by index
readonly real prevAmt // udg_DamageEventPrevAmt by index
attacktype attackType // udg_DamageEventAttackT by index
damagetype damageType // udg_DamageEventDamageT by index
weapontype weaponType // udg_DamageEventWeaponT by index
integer userType // udg_DamageEventType by index
readonly boolean isAttack // udg_IsDamageAttack by index
readonly boolean isCode // udg_IsDamageCode by index
readonly boolean isMelee // udg_IsDamageMelee by index
readonly boolean isRanged // udg_IsDamageRanged by index
readonly boolean isSpell // udg_IsDamageSpell by index
real armorPierced // udg_DamageEventArmorPierced by index
integer armorType // udg_DamageEventArmorT by index
integer defenseType // udg_DamageEventDefenseT by index
readonly integer eFilter
Set to false to disable the damage event triggers or to true to reverse that:
static boolean operator enabled
Same arguments as "UnitDamageTarget" but has the benefit of being performance-friendly during recursive events.
Will automatically cause the damage to be registered as Code damage.
static method apply takes
unit source,
unit target,
real amount,
boolean isAttack,
boolean isRanged,
attacktype at,
damagetype dt,
weapontype wt
returns Damage
A simplified version of the above function that autofills each boolean, attacktype and weapontype.
static method applySpell takes
unit src,
unit tgt,
real amt,
damagetype dt
returns Damage
A different variation of the above which autofills the "isAttack" boolean
and populates damagetype as DAMAGE_TYPE_NORMAL.
static method applyAttack takes
unit src,
unit tgt,
real amt,
boolean ranged,
attacktype at,
weapontype wt
returns Damage
struct DamageTrigger extends array
method operator filter= takes integer filter returns nothing
// Apply primary filters such as DamageEngine_FILTER_MELEE/RANGED/SPELL which are based off of limitop handles to enable easier access for GUI folks
// Full filter list:
- integer DamageEngine_FILTER_ATTACK
- integer DamageEngine_FILTER_MELEE
- integer DamageEngine_FILTER_OTHER
- integer DamageEngine_FILTER_RANGED
- integer DamageEngine_FILTER_SPELL
- integer DamageEngine_FILTER_CODE
boolean configured //set to True after configuring any filters listed below.
Apply custom filters after setting any desired udg_DamageFilter variables (for GUI).
Alternatively, vJass users can set these instead. Just be mindful to set the variable
"configured" to true after settings these:
unit source
unit target
integer sourceType
integer targetType
integer sourceBuff
integer targetBuff
real damageMin
integer attackType
integer damageType
integer userType
method configure takes nothing returns nothing
The string in the aruments below requires the following API:
"" for standard damage event
"Modifier(or Mod if you prefer)/After/Lethal/AOE" for the others
static method registerTrigger takes
trigger whichTrig,
string var,
real value
returns nothing
static method unregister takes
trigger whichTrig,
string eventName,
real value,
boolean reset
returns boolean
static method getIndex takes
trigger fromTrigger,
string eventName,
real value
returns integer
If you already have the index of the trigger you want to unregister:
method unregisterByIndex takes
boolean reset
returns boolean
Converts a code argument to a trigger, while checking if the same code had already been registered before.
Use it via DamageTrigger[function MyCallbackFunction]
static method operator [] takes
code callback
returns trigger
The accepted strings here use the same criteria as DamageTrigger.getIndex/registerTrigger/unregister:
function TriggerRegisterDamageEngineEx takes
trigger whichTrig,
string eventName,
real value,
integer opId
returns nothing
function TriggerRegisterDamageEngine takes
trigger whichTrig,
string eventName,
real value
returns nothing
function RegisterDamageEngineEx takes
code callback,
string eventName,
real value,
integer opId
returns nothing
function RegisterDamageEngine takes
code callback,
string eventName,
real value
returns nothing
*/
//===========================================================================
library DamageEngine requires optional AttackIndexer
globals
private constant boolean USE_GUI = false //If you don't use any of the GUI events, set to false to slightly improve performance
private constant boolean USE_SCALING = USE_GUI //If you don't need or want to use DamageScalingUser/WC3 then set this to false
private constant boolean USE_EXTRA = true //If you don't use DamageEventLevel or SourceDamageEvent, set this to false
private constant boolean USE_ARMOR_MOD = true //If you do not modify nor detect armor/defense, set this to false
private constant boolean USE_MELEE_RANGE = true //If you do not detect melee nor ranged damage, set this to false
private constant boolean USE_LETHAL = true //If you do not use LethalDamageEvent nor negative damage (explosive) types, set this to false
/*
When manually-enabled recursion is enabled via DamageEngine_inception,
the engine will never go deeper than MAX_RECURSIVE_TOLERANCE:
*/
private constant integer MAX_RECURSIVE_TOLERANCE = 16
public constant integer TYPE_CODE = 1 //Must be the same as udg_DamageTypeCode, or 0 if you prefer to disable the automatic flag.
public constant integer TYPE_PURE = 2 //Must be the same as udg_DamageTypePure
private constant real DEATH_VAL = 0.405 //In case M$ ever changes this, it'll be a quick fix here.
private timer async = null
private boolean timerStarted = false
//Values to track the original pre-spirit Link/defensive damage values
private Damage lastInstance = 0
private boolean isNotNativeRecursiveDamage = true
private boolean waitingForDamageEventToRun = false
private boolean array attacksImmune
private boolean array damagesImmune
//Primary triggers used to handle all damage events.
private trigger damagingTrigger = null
private trigger damagedTrigger = null
private trigger recursiveTrigger = null //Catches, stores recursive events
/*
These variables coincide with Blizzard's "limitop" type definitions
so as to enable GUI users with some performance perks - however,
these optimizations need to be tested
*/
public constant integer FILTER_ATTACK = 0 //LESS_THAN
public constant integer FILTER_MELEE = 1 //LESS_THAN_OR_EQUAL
public constant integer FILTER_OTHER = 2 //EQUAL
public constant integer FILTER_RANGED = 3 //GREATER_THAN_OR_EQUAL
public constant integer FILTER_SPELL = 4 //GREATER_THAN
public constant integer FILTER_CODE = 5 //NOT_EQUAL
public constant integer FILTER_MAX = 6
private integer eventFilter = FILTER_OTHER
/*
When true, it allows your trigger to go recursively up to
MAX_RECURSIVE_TOLERANCE (if needed). It must be set before dealing damage.
*/
public boolean inception = false
private boolean callbacksInProgress = false
private integer recursiveCallbackDepth = 0
private group recursionSources = null
private group recursionTargets = null
private boolean recursiveCallbaksInProgress = false
private boolean nativeEventsCompleted = false
private boolean atLeastOneLethalDamageEventRegistered = false
// Struct members made private to this library.
private keyword run
private keyword trigFrozen
private keyword ownRecursiveDepth
private keyword manualRecursionRequested
endglobals
native UnitAlive takes unit u returns boolean
//GUI Vars:
/*
Retained from 3.8 and prior:
----------------------------
unit udg_DamageEventSource
unit udg_DamageEventTarget
unit udg_EnhancedDamageTarget
group udg_DamageEventAOEGroup
integer udg_DamageEventAOE
integer udg_DamageEventLevel
real udg_DamageModifierEvent
real udg_DamageEvent
real udg_AfterDamageEvent
real udg_DamageEventAmount
real udg_DamageEventPrevAmt
real udg_AOEDamageEvent
boolean udg_DamageEventOverride
boolean udg_NextDamageType
boolean udg_DamageEventType
boolean udg_IsDamageSpell
//Added in 5.0:
boolean udg_IsDamageMelee
boolean udg_IsDamageRanged
unit udg_AOEDamageSource
real udg_LethalDamageEvent
real udg_LethalDamageHP
real udg_DamageScalingWC3
integer udg_DamageEventAttackT
integer udg_DamageEventDamageT
integer udg_DamageEventWeaponT
//Added in 5.1:
boolean udg_IsDamageCode
//Added in 5.2:
integer udg_DamageEventArmorT
integer udg_DamageEventDefenseT
//Addded in 5.3:
real DamageEventArmorPierced
real udg_DamageScalingUser
//Added in 5.4.2 to allow GUI users to re-issue the exact same attack and damage type at the attacker.
attacktype array udg_CONVERTED_ATTACK_TYPE
damagetype array udg_CONVERTED_DAMAGE_TYPE
//Added after Reforged introduced the new native BlzGetDamageIsAttack
boolean udg_IsDamageAttack
//Added in 5.6 to give GUI users control over the "IsDamageAttack", "IsDamageRanged" and "DamageEventWeaponT" field
boolean udg_NextDamageIsAttack //The first boolean value in the UnitDamageTarget native
boolean udg_NextDamageIsMelee //Flag the damage classification as melee
boolean udg_NextDamageIsRanged //The second boolean value in the UnitDamageTarget native
integer udg_NextDamageWeaponT //Allows control over damage sound effect
//Added in 5.7 to enable efficient, built-in filtering (see the below "checkConfig" method - I recommend commenting-out anything you don't need in your map)
integer udg_DamageFilterAttackT
integer udg_DamageFilterDamageT //filter for a specific attack/damage type
unit udg_DamageFilterSource
unit udg_DamageFilterTarget //filter for a specific source/target
integer udg_DamageFilterSourceT
integer udg_DamageFilterTargetT //unit type of source/target
integer udg_DamageFilterType //which DamageEventType was used
integer udg_DamageFilterSourceB
integer udg_DamageFilterTargetB //if source/target has a buff
real udg_DamageFilterMinAmount //only allow a minimum damage threshold
//Added in 5.8:
boolean udg_RemoveDamageEvent //Allow GUI users to more fully unregister a damage event trigger. Can only be used from within a damage event (of any kind).
integer udg_DamageFilterSourceA
integer udg_DamageFilterTargetA //Check if a source or target have a specific ability (will overwrite any source or target buff check, I need to use this because GUI differentiates ability ID and buff ID)
integer udg_DamageFilterSourceI
integer udg_DamageFilterTargetI //Check if a source or target have a specific type of item
integer udg_DamageFilterSourceC
integer udg_DamageFilterTargetC //Classification of source/target (e.g. hero, treant, ward)
//Added in 5.9
real udg_SourceDamageEvent //Like AOEDamageEvent, fires each time the source unit has finished dealing damage, but doesn't care if the damage hit multiple units.
real udg_PreDamageEvent //Like DamageModifierEvent 3.99 or less, except can be any real value.
real udg_ArmorDamageEvent //Like DamageModifierEvent 4.00 or more, except can be any real value.
real udg_OnDamageEvent //Like DamageEvent equal to 1.00 or some non-zero/non-2 value, except can be any real value.
real udg_ZeroDamageEvent //Like DamageEvent equal to 0.00 or 2.00, except can be any real value.
*/
struct DamageTrigger extends array
static method checkItem takes unit u, integer id returns boolean
local integer i
if IsUnitType(u, UNIT_TYPE_HERO) then
set i = UnitInventorySize(u)
loop
exitwhen i <= 0
set i = i - 1
if GetItemTypeId(UnitItemInSlot(u, i)) == id then
return true
endif
endloop
endif
return false
endmethod
/*
Map makers should probably not use these filters,
unless someone tests performance to see
if such an ugly hack is even worth it.
*/
method checkConfig takes nothing returns boolean
//call BJDebugMsg("Checking configuration")
if this.sourceType != 0 and GetUnitTypeId(udg_DamageEventSource) != this.sourceType then
elseif this.targetType != 0 and GetUnitTypeId(udg_DamageEventTarget) != this.targetType then
elseif this.sourceBuff != 0 and GetUnitAbilityLevel(udg_DamageEventSource, this.sourceBuff) == 0 then
elseif this.targetBuff != 0 and GetUnitAbilityLevel(udg_DamageEventTarget, this.targetBuff) == 0 then
elseif this.failChance > 0.00 and GetRandomReal(0.00, 1.00) <= this.failChance then
elseif this.userType != 0 and udg_DamageEventType != this.userType then
elseif this.source != null and this.source != udg_DamageEventSource then
elseif this.target != null and this.target != udg_DamageEventTarget then
elseif this.attackType >= 0 and this.attackType != udg_DamageEventAttackT then
elseif this.damageType >= 0 and this.damageType != udg_DamageEventDamageT then
elseif this.sourceItem != 0 and not .checkItem(udg_DamageEventSource, this.sourceItem) then
elseif this.targetItem != 0 and not .checkItem(udg_DamageEventTarget, this.targetItem) then
elseif this.sourceClass >= 0 and not IsUnitType(udg_DamageEventSource, ConvertUnitType(this.sourceClass)) then
elseif this.targetClass >= 0 and not IsUnitType(udg_DamageEventTarget, ConvertUnitType(this.targetClass)) then
elseif udg_DamageEventAmount >= this.damageMin then
//call BJDebugMsg("Configuration passed")
return true
endif
//call BJDebugMsg("Checking failed")
return false
endmethod
//The below variables are to be treated as constant
readonly static thistype MOD = 1
readonly static thistype SHIELD = 4
readonly static thistype DAMAGE = 5
readonly static thistype ZERO = 6
readonly static thistype AFTER = 7
readonly static thistype LETHAL = 8
readonly static thistype AOE = 9
private static integer count = 9
static thistype lastRegistered = 0
private static thistype array trigIndexStack
static thistype eventIndex = 0
static boolean array filters
readonly string eventStr
readonly real weight
boolean usingGUI
private thistype next
private trigger rootTrig
//The below variables are to be treated as private
boolean trigFrozen //Whether the trigger is currently disabled due to recursion
integer ownRecursiveDepth //How deep the user recursion currently is.
boolean manualRecursionRequested //Added in 5.4.2 to simplify the inception variable for very complex DamageEvent triggers.
//configuration variables:
boolean configured
unit source
unit target
integer sourceType
integer targetType
integer sourceBuff
integer targetBuff
integer sourceItem
integer targetItem
integer sourceClass
integer targetClass
real damageMin
real failChance
integer attackType
integer damageType
integer userType
// getter:
method operator runChance takes nothing returns real
return 1.00 - this.failChance
endmethod
// setter:
method operator runChance= takes real chance returns nothing
set this.failChance = 1.00 - chance
endmethod
method configure takes nothing returns nothing
set this.attackType = udg_DamageFilterAttackT
set this.damageType = udg_DamageFilterDamageT
set this.source = udg_DamageFilterSource
set this.target = udg_DamageFilterTarget
set this.sourceType = udg_DamageFilterSourceT
set this.targetType = udg_DamageFilterTargetT
set this.sourceItem = udg_DamageFilterSourceI
set this.targetItem = udg_DamageFilterTargetI
set this.sourceClass = udg_DamageFilterSourceC
set this.targetClass = udg_DamageFilterTargetC
set this.userType = udg_DamageFilterType
set this.damageMin = udg_DamageFilterMinAmount
set this.failChance = 1.00 - (udg_DamageFilterRunChance - udg_DamageFilterFailChance)
if udg_DamageFilterSourceA > 0 then
set this.sourceBuff = udg_DamageFilterSourceA
set udg_DamageFilterSourceA = 0
else
set this.sourceBuff = udg_DamageFilterSourceB
endif
if udg_DamageFilterTargetA > 0 then
set this.targetBuff = udg_DamageFilterTargetA
set udg_DamageFilterTargetA = 0
else
set this.targetBuff = udg_DamageFilterTargetB
endif
set udg_DamageFilterSource = null
set udg_DamageFilterTarget = null
//These handles can have a valid value of 0, so we need to distinguish them.
set udg_DamageFilterAttackT = -1
set udg_DamageFilterDamageT = -1
set udg_DamageFilterSourceC = -1
set udg_DamageFilterTargetC = -1
set udg_DamageFilterSourceT = 0
set udg_DamageFilterTargetT = 0
set udg_DamageFilterType = 0
set udg_DamageFilterSourceB = 0
set udg_DamageFilterTargetB = 0
set udg_DamageFilterSourceI = 0
set udg_DamageFilterTargetI = 0
set udg_DamageFilterMinAmount = 0.00
set udg_DamageFilterFailChance = 0.00
set udg_DamageFilterRunChance = 1.00
set this.configured = true
endmethod
static method setGUIFromStruct takes boolean full returns nothing
set udg_DamageEventAmount = Damage.index.damage
set udg_DamageEventAttackT = GetHandleId(Damage.index.attackType)
set udg_DamageEventDamageT = GetHandleId(Damage.index.damageType)
set udg_DamageEventWeaponT = GetHandleId(Damage.index.weaponType)
set udg_DamageEventType = Damage.index.userType
static if USE_ARMOR_MOD then
set udg_DamageEventArmorPierced = Damage.index.armorPierced
set udg_DamageEventArmorT = Damage.index.armorType
set udg_DamageEventDefenseT = Damage.index.defenseType
endif
if full then
set udg_DamageEventSource = Damage.index.sourceUnit
set udg_DamageEventTarget = Damage.index.targetUnit
set udg_DamageEventPrevAmt = Damage.index.prevAmt
set udg_IsDamageAttack = Damage.index.isAttack
set udg_IsDamageCode = Damage.index.isCode
set udg_IsDamageSpell = Damage.index.isSpell
//! runtextmacro optional ATTACK_INDEXER_GUI_VARS()
static if USE_MELEE_RANGE then
set udg_IsDamageMelee = Damage.index.isMelee
set udg_IsDamageRanged = Damage.index.isRanged
endif
endif
endmethod
static method setStructFromGUI takes nothing returns nothing
set Damage.index.damage = udg_DamageEventAmount
set Damage.index.attackType = ConvertAttackType(udg_DamageEventAttackT)
set Damage.index.damageType = ConvertDamageType(udg_DamageEventDamageT)
set Damage.index.weaponType = ConvertWeaponType(udg_DamageEventWeaponT)
set Damage.index.userType = udg_DamageEventType
static if USE_ARMOR_MOD then
set Damage.index.armorPierced = udg_DamageEventArmorPierced
set Damage.index.armorType = udg_DamageEventArmorT
set Damage.index.defenseType = udg_DamageEventDefenseT
endif
endmethod
static method getVerboseStr takes string eventName returns string
if eventName == "Modifier" or eventName == "Mod" then
return "udg_DamageModifierEvent"
endif
return "udg_" + eventName + "DamageEvent"
endmethod
private static method getStrIndex takes string var, real lbs returns thistype
local integer root = R2I(lbs)
if (var == "udg_DamageModifierEvent" and root < 4) or var == "udg_PreDamageEvent" then
set root = MOD
elseif var == "udg_DamageModifierEvent" or var == "udg_ArmorDamageEvent" then
set root = SHIELD
elseif (var == "udg_DamageEvent" and root == 2 or root == 0) or var == "udg_ZeroDamageEvent" then
set root = ZERO
elseif var == "udg_DamageEvent" or var == "udg_OnDamageEvent" then
set root = DAMAGE
elseif var == "udg_AfterDamageEvent" then
set root = AFTER
elseif var == "udg_LethalDamageEvent" then
set root = LETHAL
elseif var == "udg_AOEDamageEvent" or var == "udg_SourceDamageEvent" then
set root = AOE
else
set root = 0
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_GDD()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_PDD()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_REG_PLUGIN_05()
endif
return root
endmethod
private method toggleAllFilters takes boolean flag returns nothing
set filters[this + FILTER_ATTACK] = flag
set filters[this + FILTER_MELEE] = flag
set filters[this + FILTER_OTHER] = flag
set filters[this + FILTER_RANGED] = flag
set filters[this + FILTER_SPELL] = flag
set filters[this + FILTER_CODE] = flag
endmethod
method operator filter= takes integer opId returns nothing
set this = this * FILTER_MAX
if opId == FILTER_OTHER then
call this.toggleAllFilters(true)
else
if opId == FILTER_ATTACK then
set filters[this + FILTER_ATTACK] = true
set filters[this + FILTER_MELEE] = true
set filters[this + FILTER_RANGED] = true
else
set filters[this + opId] = true
endif
endif
endmethod
static method registerVerbose takes /*
*/ trigger whichTrig, /*
*/ string var, /*
*/ real lbs, /*
*/ boolean GUI, /*
*/ integer filt /*
*/ returns thistype
local thistype index = getStrIndex(var, lbs)
local thistype i = 0
local thistype id = 0
if index == 0 then
return 0
elseif lastRegistered.rootTrig == whichTrig and lastRegistered.usingGUI then
//allows GUI to register multiple different types of Damage filters to the same trigger
set filters[lastRegistered*FILTER_MAX + filt] = true
return 0
endif
set atLeastOneLethalDamageEventRegistered = /*
*/ atLeastOneLethalDamageEventRegistered or index == LETHAL
if trigIndexStack[0] == 0 then
set count = count + 1 //List runs from index 10 and up
set id = count
else
set id = trigIndexStack[0]
set trigIndexStack[0] = trigIndexStack[id]
endif
set lastRegistered = id
set id.filter = filt
set id.rootTrig = whichTrig
set id.usingGUI = GUI
set id.weight = lbs
set id.eventStr = var
//Next 2 lines added to fix a bug when using manual vJass configuration,
//discovered and solved by lolreported
set id.attackType = -1
set id.damageType = -1
//they will probably bug out with class types as well, so I should add them, just in case:
set id.sourceClass = -1
set id.targetClass = -1
loop
set i = index.next
exitwhen i == 0 or lbs < i.weight
set index = i
endloop
set index.next = id
set id.next = i
//call BJDebugMsg("Registered " + I2S(id) + " to " + I2S(index) + " and before " + I2S(i))
return lastRegistered
endmethod
static method registerTrigger takes trigger t, string var, real lbs returns thistype
return registerVerbose(t, DamageTrigger.getVerboseStr(var), lbs, false, FILTER_OTHER)
endmethod
private static thistype prev = 0
static method getIndex takes /*
*/ trigger t, /*
*/ string eventName, /*
*/ real lbs /*
*/ returns thistype
local thistype index = getStrIndex(getVerboseStr(eventName), lbs)
loop
set prev = index
set index = index.next
exitwhen index == 0 or index.rootTrig == t
endloop
return index
endmethod
method unregisterByIndex takes boolean reset returns boolean
if this == 0 then
return false
endif
set prev.next = this.next
set trigIndexStack[this] = trigIndexStack[0]
set trigIndexStack[0] = this
if reset then
call this.configure()
set this.configured = false
call thistype(this*FILTER_MAX).toggleAllFilters(false)
endif
return true
endmethod
static method unregister takes /*
*/ trigger t, /*
*/ string eventName, /*
*/ real lbs, /*
*/ boolean reset /*
*/ returns boolean
return getIndex(t, eventName, lbs).unregisterByIndex(reset)
endmethod
method run takes nothing returns nothing
local integer cat = this
local Damage d = Damage.index
static if USE_GUI then
local boolean structUnset = false
local boolean guiUnset = false
local boolean mod = cat <= DAMAGE
endif
if callbacksInProgress then
return
endif
set callbacksInProgress = true
call DisableTrigger(damagingTrigger)
call DisableTrigger(damagedTrigger)
call EnableTrigger(recursiveTrigger)
//call BJDebugMsg("Start of event running")
loop
set this = this.next
exitwhen this == 0
exitwhen cat == MOD and (udg_DamageEventOverride or udg_DamageEventType == TYPE_PURE)
exitwhen cat == SHIELD and udg_DamageEventAmount <= 0.00
static if USE_LETHAL then
exitwhen cat == LETHAL and udg_LethalDamageHP > DEATH_VAL
endif
set eventIndex = this
if (not this.trigFrozen) and /*
*/ filters[this*FILTER_MAX + d.eFilter] and /*
*/ IsTriggerEnabled(this.rootTrig) and /*
*/ ((not this.configured) or (this.checkConfig())) and /*
*/ (cat != AOE or udg_DamageEventAOE > 1 or this.eventStr == "udg_SourceDamageEvent") /*
*/ then
static if USE_GUI then
if mod then
if this.usingGUI then
if guiUnset then
set guiUnset = false
call setGUIFromStruct(false)
endif
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_PDD()
elseif structUnset then
set structUnset = false
call setStructFromGUI()
endif
endif
endif
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_FILTER_PLUGIN_05()
//JASS users who do not use actions can modify the below block to just evaluate.
//It should not make any perceptable difference in terms of performance.
if TriggerEvaluate(this.rootTrig) then
call TriggerExecute(this.rootTrig)
endif
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_05()
static if USE_GUI then
if mod then
if this.usingGUI then
//! runtextmacro optional DAMAGE_EVENT_MOD_PLUGIN_PDD()
if cat != MOD then
set d.damage = udg_DamageEventAmount
else
set structUnset = true
endif
elseif cat != MOD then
set udg_DamageEventAmount = d.damage
else
set guiUnset = true
endif
endif
if udg_RemoveDamageEvent then
set udg_RemoveDamageEvent = false
call this.unregisterByIndex(true)
endif
endif
endif
endloop
static if USE_GUI then
if structUnset then
call setStructFromGUI()
endif
if guiUnset then
call setGUIFromStruct(false)
endif
else
call setGUIFromStruct(false)
endif
//call BJDebugMsg("End of event running")
call DisableTrigger(recursiveTrigger)
call EnableTrigger(damagingTrigger)
call EnableTrigger(damagedTrigger)
set callbacksInProgress = false
endmethod
/*
Used by RegisterDamageEngineEx to create triggers behind-the-scenes,
allowing the user to simply pass the function they want to execute.
*/
static trigger array autoTriggers
static boolexpr array autoFuncs
static integer autoN = 0
static method operator [] takes code callback returns trigger
local integer i = 0
local boolexpr b = Filter(callback)
loop
if i == autoN then
set autoTriggers[i] = CreateTrigger()
set autoFuncs[i] = b
call TriggerAddCondition(autoTriggers[i], b)
exitwhen true
endif
set i = i + 1
exitwhen b == autoFuncs[i]
endloop
return autoTriggers[i]
endmethod
endstruct
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_USER_STRUCT_PLUGIN_05()
struct Damage extends array
readonly unit sourceUnit
readonly unit targetUnit
real damage
readonly real prevAmt
attacktype attackType
damagetype damageType
weapontype weaponType
integer userType
readonly boolean isAttack
readonly boolean isCode
readonly boolean isSpell
static if USE_MELEE_RANGE then
readonly boolean isMelee //stores udg_IsDamageMelee
endif
readonly boolean isRanged //stores udg_IsDamageRanged
readonly integer eFilter //stores the previous eventFilter variable
static if USE_ARMOR_MOD then
real armorPierced //stores udg_DamageEventArmorPierced
integer armorType //stores udg_DamageEventArmorT
integer defenseType //stores udg_DamageEventDefenseT
endif
readonly static Damage index = 0
private static Damage damageStack = 0
private static Damage prepped = 0
private static integer count = 0 //The number of currently-running queued or sequential damage instances
private Damage stackRef
private DamageTrigger recursiveTrig
private integer prevArmorT
private integer prevDefenseT
static method operator source takes nothing returns unit
return udg_DamageEventSource
endmethod
static method operator target takes nothing returns unit
return udg_DamageEventTarget
endmethod
static method operator amount takes nothing returns real
return Damage.index.damage
endmethod
static method operator amount= takes real r returns nothing
set Damage.index.damage = r
endmethod
static if USE_ARMOR_MOD then
private method setArmor takes boolean reset returns nothing
local real pierce
local integer at
local integer dt
if reset then
set pierce = udg_DamageEventArmorPierced
set at = Damage.index.prevArmorT
set dt = Damage.index.prevDefenseT
set udg_DamageEventArmorPierced = 0.00
set this.armorPierced = 0.00
else
set pierce = -udg_DamageEventArmorPierced
set at = udg_DamageEventArmorT
set dt = udg_DamageEventDefenseT
endif
if not (pierce == 0.00) then //Changed from != to not == due to bug reported by BLOKKADE
call BlzSetUnitArmor(udg_DamageEventTarget, BlzGetUnitArmor(udg_DamageEventTarget) + pierce)
endif
if Damage.index.prevArmorT != udg_DamageEventArmorT then
call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_ARMOR_TYPE, at)
endif
if Damage.index.prevDefenseT != udg_DamageEventDefenseT then
call BlzSetUnitIntegerField(udg_DamageEventTarget, UNIT_IF_DEFENSE_TYPE, dt)
endif
endmethod
endif
static if USE_EXTRA then
private static method onAOEEnd takes nothing returns nothing
call DamageTrigger.AOE.run()
set udg_DamageEventAOE = 1
set udg_DamageEventLevel = 1
set udg_EnhancedDamageTarget = null
set udg_AOEDamageSource = null
call GroupClear(udg_DamageEventAOEGroup)
endmethod
endif
private static method afterDamage takes nothing returns nothing
if udg_DamageEventDamageT != 0 and not (udg_DamageEventPrevAmt == 0.00) then
call DamageTrigger.AFTER.run()
set udg_DamageEventDamageT = 0
set udg_DamageEventPrevAmt = 0.00
endif
endmethod
private method runDamagingEvents takes boolean natural returns boolean
static if USE_ARMOR_MOD then
set this.armorType = BlzGetUnitIntegerField(this.targetUnit, UNIT_IF_ARMOR_TYPE)
set this.defenseType = BlzGetUnitIntegerField(this.targetUnit, UNIT_IF_DEFENSE_TYPE)
set this.prevArmorT = this.armorType
set this.prevDefenseT = this.defenseType
set this.armorPierced = 0.00
endif
set Damage.index = this
call DamageTrigger.setGUIFromStruct(true)
call GroupAddUnit(recursionSources, udg_DamageEventSource)
call GroupAddUnit(recursionTargets, udg_DamageEventTarget)
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_PRE_VARS_PLUGIN_05()
// Using not == instead of !=; the idea is to eliminate floating point bugs when two numbers are very close to 0,
// because JASS uses a less-strict comparison for checking if a number is equal than when it is unequal.
if not (udg_DamageEventAmount == 0.00) then
set udg_DamageEventOverride = udg_DamageEventDamageT == 0
call DamageTrigger.MOD.run()
static if not USE_GUI then
call DamageTrigger.setGUIFromStruct(false)
endif
if natural then
call BlzSetEventAttackType(this.attackType)
call BlzSetEventDamageType(this.damageType)
call BlzSetEventWeaponType(this.weaponType)
call BlzSetEventDamage(udg_DamageEventAmount)
endif
static if USE_ARMOR_MOD then
call this.setArmor(false)
endif
return false
endif
return true //return value is based on whether the event is a 0 damage event (true) or not (false).
endmethod
private static method unfreeze takes nothing returns nothing
local Damage i = damageStack
loop
exitwhen i == 0
set i = i - 1
set i.stackRef.recursiveTrig.trigFrozen = false
set i.stackRef.recursiveTrig.ownRecursiveDepth = 0
endloop
call EnableTrigger(damagingTrigger)
call EnableTrigger(damagedTrigger)
set recursiveCallbaksInProgress = false
set damageStack = 0
set prepped = 0
set callbacksInProgress = false
set recursiveCallbackDepth = 0
call GroupClear(recursionSources)
call GroupClear(recursionTargets)
//call BJDebugMsg("Cleared up the groups")
endmethod
static method runAfterDamageEvents takes nothing returns nothing
local Damage i = 0
local integer exit
if nativeEventsCompleted then
set nativeEventsCompleted = false
call afterDamage()
endif
if isNotNativeRecursiveDamage and not recursiveCallbaksInProgress then
if damageStack != 0 then
set recursiveCallbaksInProgress = true
loop
/*
Use two loops. The outer loop handles all normal event
execution, while the inner loop intelligently handles
recursive execution (when it's used).
*/
set recursiveCallbackDepth = recursiveCallbackDepth + 1
set exit = damageStack
loop
set prepped = i.stackRef
if UnitAlive(prepped.targetUnit) then
// We don't need to trigger `damagingTrigger` itself, so just call its handler directly.
call prepped.runDamagingEvents(false)
if prepped.damage > 0.00 then
call DisableTrigger(damagingTrigger) // Disallow `damagingTrigger` because we only want `damageTrigger` to run.
call EnableTrigger(damagedTrigger) // Re-enable `damagedTrigger` in case the user forgot to do so.
set waitingForDamageEventToRun = true
call UnitDamageTarget( /*
*/ prepped.sourceUnit, /*
*/ prepped.targetUnit, /*
*/ prepped.damage, /*
*/ prepped.isAttack, /*
*/ prepped.isRanged, /*
*/ prepped.attackType, /*
*/ prepped.damageType, /*
*/ prepped.weaponType /*
*/ )
else
if udg_DamageEventDamageT != 0 then
//No native events run at all in this case
call DamageTrigger.DAMAGE.run()
endif
if prepped.damage < 0.00 then
/*
No need for BlzSetEventDamage/UnitDamageTarget here,
because we can safely adjust the unit's life instead.
*/
call SetWidgetLife( /*
*/ prepped.targetUnit, /*
*/ GetWidgetLife(prepped.targetUnit) - prepped.damage /*
*/ )
endif
static if USE_ARMOR_MOD then
call prepped.setArmor(true)
endif
endif
call afterDamage()
endif
set i = i + 1
exitwhen i == exit
endloop
exitwhen i == damageStack
endloop
endif
call unfreeze()
endif
endmethod
private static method failsafeClear takes nothing returns nothing
static if USE_ARMOR_MOD then
call Damage.index.setArmor(true)
endif
set isNotNativeRecursiveDamage = true
set recursiveCallbaksInProgress = false
set waitingForDamageEventToRun = false
if udg_DamageEventDamageT != 0 then
call DamageTrigger.DAMAGE.run()
set nativeEventsCompleted = true
endif
call runAfterDamageEvents()
endmethod
static method operator enabled= takes boolean b returns nothing
if b then
if callbacksInProgress then
call EnableTrigger(recursiveTrigger)
else
call EnableTrigger(damagingTrigger)
call EnableTrigger(damagedTrigger)
endif
else
if callbacksInProgress then
call DisableTrigger(recursiveTrigger)
else
call DisableTrigger(damagingTrigger)
call DisableTrigger(damagedTrigger)
endif
endif
endmethod
static method operator enabled takes nothing returns boolean
return IsTriggerEnabled(damagingTrigger)
endmethod
private static boolean threadCompleted = false
private static method asyncCallbackSafeCallback takes nothing returns nothing
if waitingForDamageEventToRun then
/*
This means that WarCraft 3 didn't run the DAMAGED event despite running the DAMAGING event.
*/
call failsafeClear()
else
set isNotNativeRecursiveDamage = true
set recursiveCallbaksInProgress = false
call runAfterDamageEvents()
endif
static if USE_EXTRA then
call onAOEEnd()
endif
set threadCompleted = true
endmethod
private static method asyncCallback takes nothing returns nothing
set callbacksInProgress = false
set Damage.enabled = true
/*
Open a new thread in case of a thread crash during callback.
*/
call ForForce(bj_FORCE_PLAYER[0], function thistype.asyncCallbackSafeCallback)
if not threadCompleted then
//call BJDebugMsg("DamageEngine issue: thread crashed!")
call unfreeze()
else
set threadCompleted = false
endif
set Damage.count = 0
set Damage.index = 0
set timerStarted = false
//call BJDebugMsg("Timer wrapped up")
endmethod
private method addRecursive takes nothing returns nothing
local DamageTrigger currentIndex
if not (this.damage == 0.00) then
set currentIndex = DamageTrigger.eventIndex
set this.recursiveTrig = currentIndex
if not this.isCode then
/*
If the recursive damage trigger is executed, this can only
mean that the user has manually dealt damage from a trigger.
Hence flag the damage as being 'code' if they didn't already
manually do this.
*/
set this.isCode = true
set this.userType = TYPE_CODE
endif
set inception = inception or /*
*/ currentIndex.manualRecursionRequested
if recursiveCallbaksInProgress and /*
*/ IsUnitInGroup(this.sourceUnit, recursionSources) and /*
*/ IsUnitInGroup(this.targetUnit, recursionTargets) /*
*/ then
if not inception then
set currentIndex.trigFrozen = true
elseif not currentIndex.trigFrozen then
set currentIndex.manualRecursionRequested = true
if currentIndex.ownRecursiveDepth < recursiveCallbackDepth then
set currentIndex.ownRecursiveDepth = /*
*/ currentIndex.ownRecursiveDepth + 1
if currentIndex.ownRecursiveDepth >= MAX_RECURSIVE_TOLERANCE then
set currentIndex.trigFrozen = true
endif
endif
endif
endif
// push the reference to the top of the damage stack.
set damageStack.stackRef = this
set damageStack = damageStack + 1
//call BJDebugMsg("damageStack: " + I2S(damageStack) + " ownRecursiveDepth: " + I2S(currentIndex.ownRecursiveDepth) + " recursiveCallbackDepth: " + I2S(recursiveCallbackDepth))
endif
set inception = false
endmethod
private static method clearNexts takes nothing returns nothing
set udg_NextDamageIsAttack = false
set udg_NextDamageType = 0
set udg_NextDamageWeaponT = 0
static if USE_MELEE_RANGE then
set udg_NextDamageIsMelee = false
set udg_NextDamageIsRanged = false
endif
endmethod
static method create takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ boolean isAttack, /*
*/ attacktype at, /*
*/ damagetype dt, /*
*/ weapontype wt /*
*/ returns Damage
local Damage d = Damage.count + 1
set Damage.count = d
set d.sourceUnit = src
set d.targetUnit = tgt
set d.damage = amt
set d.prevAmt = amt
set d.damageType = dt
set d.attackType = at
set d.weaponType = wt
set d.isAttack = udg_NextDamageIsAttack or isAttack
set d.isSpell = d.attackType == null and not d.isAttack
return d
endmethod
private static method createFromEvent takes nothing returns Damage
local Damage d = thistype.create( /*
*/ GetEventDamageSource(), /*
*/ GetTriggerUnit(), /*
*/ GetEventDamage(), /*
*/ BlzGetEventIsAttack(), /*
*/ BlzGetEventAttackType(), /*
*/ BlzGetEventDamageType(), /*
*/ BlzGetEventWeaponType() /*
*/ )
set d.isCode = udg_NextDamageType != 0 or /*
*/ udg_NextDamageIsAttack or /*
*/ udg_NextDamageIsRanged or /*
*/ udg_NextDamageIsMelee or /*
*/ d.damageType == DAMAGE_TYPE_MIND or /*
*/ udg_NextDamageWeaponT != 0 or /*
*/ (d.damageType == DAMAGE_TYPE_UNKNOWN and not (d.damage == 0.00))
if d.isCode then
if udg_NextDamageType != 0 then
set d.userType = udg_NextDamageType
else
set d.userType = TYPE_CODE
endif
static if USE_MELEE_RANGE then
set d.isMelee = udg_NextDamageIsMelee
set d.isRanged = udg_NextDamageIsRanged
endif
set d.eFilter = FILTER_CODE
if udg_NextDamageWeaponT != 0 then
set d.weaponType = ConvertWeaponType(udg_NextDamageWeaponT)
set udg_NextDamageWeaponT = 0
endif
else
set d.userType = 0
if d.damageType == DAMAGE_TYPE_NORMAL and d.isAttack then
// Added in version 5.A in order to allow an optional external
// Attack Indexer system to reset the event weapon type to normal.
//! runtextmacro optional ATTACK_INDEXER_ADJUSTMENTS()
static if USE_MELEE_RANGE then
set d.isMelee = IsUnitType(d.sourceUnit, UNIT_TYPE_MELEE_ATTACKER)
set d.isRanged = IsUnitType(d.sourceUnit, UNIT_TYPE_RANGED_ATTACKER)
if d.isMelee and d.isRanged then
// Melee units always play a sound when damaging in WC3,
// so this is an easy check.
set d.isMelee = d.weaponType != null
// In the case where a unit is both ranged and melee,
// the ranged attack plays no sound.
set d.isRanged = not d.isMelee
endif
if d.isMelee then
set d.eFilter = FILTER_MELEE
elseif d.isRanged then
set d.eFilter = FILTER_RANGED
else
set d.eFilter = FILTER_ATTACK
endif
else
set d.eFilter = FILTER_ATTACK
endif
else
if d.isSpell then
set d.eFilter = FILTER_SPELL
else
set d.eFilter = FILTER_OTHER
endif
static if USE_MELEE_RANGE then
// Spells are neither melee nor ranged.
set d.isMelee = false
set d.isRanged = false
endif
endif
endif
call clearNexts()
return d
endmethod
private static method onRecursiveDamageCallback takes nothing returns boolean
local Damage d = Damage.createFromEvent()
call d.addRecursive()
call BlzSetEventDamage(0.00)
return false
endmethod
private static method onDamagingCallback takes nothing returns boolean
local Damage d = Damage.createFromEvent()
//call BJDebugMsg("Pre-damage event running for " + GetUnitName(GetTriggerUnit()))
if timerStarted then
if waitingForDamageEventToRun then
//WarCraft 3 didn't run the DAMAGED event despite running the DAMAGING event.
if d.damageType == DAMAGE_TYPE_SPIRIT_LINK or /*
*/ d.damageType == DAMAGE_TYPE_DEFENSIVE or /*
*/ d.damageType == DAMAGE_TYPE_PLANT /*
*/ then
set waitingForDamageEventToRun = false
set lastInstance = Damage.index
set isNotNativeRecursiveDamage = false
else
call failsafeClear() //Not an overlapping event - just wrap it up
endif
else
call runAfterDamageEvents() //wrap up any previous damage index
endif
static if USE_EXTRA then
if d.sourceUnit != udg_AOEDamageSource then
call onAOEEnd()
set udg_AOEDamageSource = d.sourceUnit
set udg_EnhancedDamageTarget = d.targetUnit
elseif d.targetUnit == udg_EnhancedDamageTarget then
set udg_DamageEventLevel= udg_DamageEventLevel + 1
elseif not IsUnitInGroup(d.targetUnit, udg_DamageEventAOEGroup) then
set udg_DamageEventAOE = udg_DamageEventAOE + 1
endif
endif
else
call TimerStart(async, 0.00, false, function Damage.asyncCallback)
set timerStarted = true
static if USE_EXTRA then
set udg_AOEDamageSource = d.sourceUnit
set udg_EnhancedDamageTarget= d.targetUnit
endif
endif
static if USE_EXTRA then
call GroupAddUnit(udg_DamageEventAOEGroup, d.targetUnit)
endif
if d.runDamagingEvents(true) then
call DamageTrigger.ZERO.run()
set isNotNativeRecursiveDamage = true
call runAfterDamageEvents()
endif
set waitingForDamageEventToRun = lastInstance == 0 or /*
*/ attacksImmune[udg_DamageEventAttackT] or /*
*/ damagesImmune[udg_DamageEventDamageT] or /*
*/ not IsUnitType(udg_DamageEventTarget, UNIT_TYPE_MAGIC_IMMUNE)
return false
endmethod
private static method onDamagedCallback takes nothing returns boolean
local real r = GetEventDamage()
local Damage d = Damage.index
//call BJDebugMsg("Second damage event running for " + GetUnitName(GetTriggerUnit()))
if prepped > 0 then
set prepped = 0
elseif callbacksInProgress or d.prevAmt == 0.00 then
return false
elseif waitingForDamageEventToRun then
set waitingForDamageEventToRun = false
else
/*
This should only happen for native recursive WarCraft 3 damage
such as Spirit Link, Thorns Aura, or Spiked Carapace / Barricades.
*/
call afterDamage()
set Damage.index = lastInstance
set lastInstance = 0
set d = Damage.index
/*
Since the native recursive damage has now wrapped up, we can resume
handling events as normal at this point. This means that the original
target that the DAMAGING event was triggered for is now finally getting
its DAMAGED event.
*/
set isNotNativeRecursiveDamage = true
call DamageTrigger.setGUIFromStruct(true)
endif
static if USE_ARMOR_MOD then
call d.setArmor(true)
endif
static if USE_SCALING then
if not (udg_DamageEventAmount == 0.00) and not (r == 0.00) then
set udg_DamageScalingWC3 = r / udg_DamageEventAmount
elseif udg_DamageEventAmount > 0.00 then
set udg_DamageScalingWC3 = 0.00
else
set udg_DamageScalingWC3 = 1.00
if udg_DamageEventPrevAmt == 0.00 then
set udg_DamageScalingUser = 0.00
else
set udg_DamageScalingUser = /*
*/ udg_DamageEventAmount / udg_DamageEventPrevAmt
endif
endif
endif
set udg_DamageEventAmount = r
set d.damage = r
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_GDD()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_PDD()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_VARS_PLUGIN_05()
if udg_DamageEventAmount > 0.00 then
call DamageTrigger.SHIELD.run()
static if not USE_GUI then
set udg_DamageEventAmount = d.damage
endif
static if USE_LETHAL then
if atLeastOneLethalDamageEventRegistered or udg_DamageEventType < 0 then
set udg_LethalDamageHP = /*
*/ GetWidgetLife(udg_DamageEventTarget) - udg_DamageEventAmount
if udg_LethalDamageHP <= DEATH_VAL then
if atLeastOneLethalDamageEventRegistered then
call DamageTrigger.LETHAL.run()
set udg_DamageEventAmount = /*
*/ GetWidgetLife(udg_DamageEventTarget) - udg_LethalDamageHP
set d.damage = udg_DamageEventAmount
endif
if udg_DamageEventType < 0 and /*
*/ udg_LethalDamageHP <= DEATH_VAL /*
*/ then
call SetUnitExploded(udg_DamageEventTarget, true)
endif
endif
endif
endif
static if USE_SCALING then
if udg_DamageEventPrevAmt == 0.00 or /*
*/ udg_DamageScalingWC3 == 0.00 /*
*/ then
set udg_DamageScalingUser = 0.00
else
set udg_DamageScalingUser = /*
*/ udg_DamageEventAmount / udg_DamageEventPrevAmt / udg_DamageScalingWC3
endif
endif
endif
if udg_DamageEventDamageT != 0 then
call DamageTrigger.DAMAGE.run()
endif
call BlzSetEventDamage(udg_DamageEventAmount)
set nativeEventsCompleted = true
if udg_DamageEventAmount == 0.00 then
call runAfterDamageEvents()
endif
// This return statement was needed years ago to avoid potential crashes on Mac.
// I am not sure if that's still a thing.
return false
endmethod
static method apply takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ boolean a, /*
*/ boolean r, /*
*/ attacktype at, /*
*/ damagetype dt, /*
*/ weapontype wt /*
*/ returns Damage
local Damage d
if udg_NextDamageType == 0 then
set udg_NextDamageType = TYPE_CODE
endif
if callbacksInProgress then
set d = create(src, tgt, amt, a, at, dt, wt)
set d.isCode = true
set d.eFilter = FILTER_CODE
set d.userType = udg_NextDamageType
static if USE_MELEE_RANGE then
if not d.isSpell then
set d.isRanged = udg_NextDamageIsRanged or r
set d.isMelee = not d.isRanged
endif
endif
call d.addRecursive()
else
call UnitDamageTarget(src, tgt, amt, a, r, at, dt, wt)
set d = Damage.index
call runAfterDamageEvents()
endif
call clearNexts()
return d
endmethod
static method applySpell takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ damagetype dt /*
*/ returns Damage
return apply(src, tgt, amt, false, false, null, dt, null)
endmethod
static method applyAttack takes /*
*/ unit src, /*
*/ unit tgt, /*
*/ real amt, /*
*/ boolean ranged, /*
*/ attacktype at, /*
*/ weapontype wt /*
*/ returns Damage
return apply(src, tgt, amt, true, ranged, at, DAMAGE_TYPE_NORMAL, wt)
endmethod
/*
This part is the most critical to get things kicked off. All the code we've seen up until now
is related to event handling, trigger assignment, edge cases, etc. But it's the following that
is really quite esesntial for any damage engine - not just this one.
*/
private static method onInit takes nothing returns nothing
set async = CreateTimer()
set recursionSources = CreateGroup()
set recursionTargets = CreateGroup()
set damagingTrigger = CreateTrigger()
set damagedTrigger = CreateTrigger()
set recursiveTrigger = CreateTrigger() //Moved from globals block as per request of user Ricola3D
call TriggerRegisterAnyUnitEventBJ(damagingTrigger, EVENT_PLAYER_UNIT_DAMAGING)
call TriggerAddCondition(damagingTrigger, Filter(function Damage.onDamagingCallback))
call TriggerRegisterAnyUnitEventBJ(damagedTrigger, EVENT_PLAYER_UNIT_DAMAGED)
call TriggerAddCondition(damagedTrigger, Filter(function Damage.onDamagedCallback))
//For recursion
call TriggerRegisterAnyUnitEventBJ(recursiveTrigger, EVENT_PLAYER_UNIT_DAMAGING)
call TriggerAddCondition(recursiveTrigger, Filter(function Damage.onRecursiveDamageCallback))
call DisableTrigger(recursiveTrigger) //starts disabled. Will be enabled during recursive event handling.
/*
For preventing Thorns/Defensive glitch.
Data gathered from https://www.hiveworkshop.com/threads/repo-in-progress-mapping-damage-types-to-their-abilities.316271/
*/
set attacksImmune[0] = false //ATTACK_TYPE_NORMAL
set attacksImmune[1] = true //ATTACK_TYPE_MELEE
set attacksImmune[2] = true //ATTACK_TYPE_PIERCE
set attacksImmune[3] = true //ATTACK_TYPE_SIEGE
set attacksImmune[4] = false //ATTACK_TYPE_MAGIC
set attacksImmune[5] = true //ATTACK_TYPE_CHAOS
set attacksImmune[6] = true //ATTACK_TYPE_HERO
set damagesImmune[0] = true //DAMAGE_TYPE_UNKNOWN
set damagesImmune[4] = true //DAMAGE_TYPE_NORMAL
set damagesImmune[5] = true //DAMAGE_TYPE_ENHANCED
set damagesImmune[8] = false //DAMAGE_TYPE_FIRE
set damagesImmune[9] = false //DAMAGE_TYPE_COLD
set damagesImmune[10] = false //DAMAGE_TYPE_LIGHTNING
set damagesImmune[11] = true //DAMAGE_TYPE_POISON
set damagesImmune[12] = true //DAMAGE_TYPE_DISEASE
set damagesImmune[13] = false //DAMAGE_TYPE_DIVINE
set damagesImmune[14] = false //DAMAGE_TYPE_MAGIC
set damagesImmune[15] = false //DAMAGE_TYPE_SONIC
set damagesImmune[16] = true //DAMAGE_TYPE_ACID
set damagesImmune[17] = false //DAMAGE_TYPE_FORCE
set damagesImmune[18] = false //DAMAGE_TYPE_DEATH
set damagesImmune[19] = false //DAMAGE_TYPE_MIND
set damagesImmune[20] = false //DAMAGE_TYPE_PLANT
set damagesImmune[21] = false //DAMAGE_TYPE_DEFENSIVE
set damagesImmune[22] = true //DAMAGE_TYPE_DEMOLITION
set damagesImmune[23] = true //DAMAGE_TYPE_SLOW_POISON
set damagesImmune[24] = false //DAMAGE_TYPE_SPIRIT_LINK
set damagesImmune[25] = false //DAMAGE_TYPE_SHADOW_STRIKE
set damagesImmune[26] = true //DAMAGE_TYPE_UNIVERSAL
endmethod
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_DMGPKG()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_01()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_02()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_03()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_04()
//! runtextmacro optional DAMAGE_EVENT_STRUCT_PLUGIN_05()
endstruct
// Called from the GUI configuration trigger once the assignments are in place.
public function DebugStr takes nothing returns nothing
local integer i = 0
loop
set udg_CONVERTED_ATTACK_TYPE[i] = ConvertAttackType(i)
exitwhen i == 6
set i = i + 1
endloop
set i = 0
loop
set udg_CONVERTED_DAMAGE_TYPE[i] = ConvertDamageType(i)
exitwhen i == 26
set i = i + 1
endloop
set udg_AttackTypeDebugStr[0] = "SPELLS" //ATTACK_TYPE_NORMAL in JASS
set udg_AttackTypeDebugStr[1] = "NORMAL" //ATTACK_TYPE_MELEE in JASS
set udg_AttackTypeDebugStr[2] = "PIERCE"
set udg_AttackTypeDebugStr[3] = "SIEGE"
set udg_AttackTypeDebugStr[4] = "MAGIC"
set udg_AttackTypeDebugStr[5] = "CHAOS"
set udg_AttackTypeDebugStr[6] = "HERO"
set udg_DamageTypeDebugStr[0] = "UNKNOWN"
set udg_DamageTypeDebugStr[4] = "NORMAL"
set udg_DamageTypeDebugStr[5] = "ENHANCED"
set udg_DamageTypeDebugStr[8] = "FIRE"
set udg_DamageTypeDebugStr[9] = "COLD"
set udg_DamageTypeDebugStr[10] = "LIGHTNING"
set udg_DamageTypeDebugStr[11] = "POISON"
set udg_DamageTypeDebugStr[12] = "DISEASE"
set udg_DamageTypeDebugStr[13] = "DIVINE"
set udg_DamageTypeDebugStr[14] = "MAGIC"
set udg_DamageTypeDebugStr[15] = "SONIC"
set udg_DamageTypeDebugStr[16] = "ACID"
set udg_DamageTypeDebugStr[17] = "FORCE"
set udg_DamageTypeDebugStr[18] = "DEATH"
set udg_DamageTypeDebugStr[19] = "MIND"
set udg_DamageTypeDebugStr[20] = "PLANT"
set udg_DamageTypeDebugStr[21] = "DEFENSIVE"
set udg_DamageTypeDebugStr[22] = "DEMOLITION"
set udg_DamageTypeDebugStr[23] = "SLOW_POISON"
set udg_DamageTypeDebugStr[24] = "SPIRIT_LINK"
set udg_DamageTypeDebugStr[25] = "SHADOW_STRIKE"
set udg_DamageTypeDebugStr[26] = "UNIVERSAL"
set udg_WeaponTypeDebugStr[0] = "NONE" //WEAPON_TYPE_WHOKNOWS in JASS
set udg_WeaponTypeDebugStr[1] = "METAL_LIGHT_CHOP"
set udg_WeaponTypeDebugStr[2] = "METAL_MEDIUM_CHOP"
set udg_WeaponTypeDebugStr[3] = "METAL_HEAVY_CHOP"
set udg_WeaponTypeDebugStr[4] = "METAL_LIGHT_SLICE"
set udg_WeaponTypeDebugStr[5] = "METAL_MEDIUM_SLICE"
set udg_WeaponTypeDebugStr[6] = "METAL_HEAVY_SLICE"
set udg_WeaponTypeDebugStr[7] = "METAL_MEDIUM_BASH"
set udg_WeaponTypeDebugStr[8] = "METAL_HEAVY_BASH"
set udg_WeaponTypeDebugStr[9] = "METAL_MEDIUM_STAB"
set udg_WeaponTypeDebugStr[10] = "METAL_HEAVY_STAB"
set udg_WeaponTypeDebugStr[11] = "WOOD_LIGHT_SLICE"
set udg_WeaponTypeDebugStr[12] = "WOOD_MEDIUM_SLICE"
set udg_WeaponTypeDebugStr[13] = "WOOD_HEAVY_SLICE"
set udg_WeaponTypeDebugStr[14] = "WOOD_LIGHT_BASH"
set udg_WeaponTypeDebugStr[15] = "WOOD_MEDIUM_BASH"
set udg_WeaponTypeDebugStr[16] = "WOOD_HEAVY_BASH"
set udg_WeaponTypeDebugStr[17] = "WOOD_LIGHT_STAB"
set udg_WeaponTypeDebugStr[18] = "WOOD_MEDIUM_STAB"
set udg_WeaponTypeDebugStr[19] = "CLAW_LIGHT_SLICE"
set udg_WeaponTypeDebugStr[20] = "CLAW_MEDIUM_SLICE"
set udg_WeaponTypeDebugStr[21] = "CLAW_HEAVY_SLICE"
set udg_WeaponTypeDebugStr[22] = "AXE_MEDIUM_CHOP"
set udg_WeaponTypeDebugStr[23] = "ROCK_HEAVY_BASH"
set udg_DefenseTypeDebugStr[0] = "LIGHT"
set udg_DefenseTypeDebugStr[1] = "MEDIUM"
set udg_DefenseTypeDebugStr[2] = "HEAVY"
set udg_DefenseTypeDebugStr[3] = "FORTIFIED"
set udg_DefenseTypeDebugStr[4] = "NORMAL" //Typically deals flat damage to all armor types
set udg_DefenseTypeDebugStr[5] = "HERO"
set udg_DefenseTypeDebugStr[6] = "DIVINE"
set udg_DefenseTypeDebugStr[7] = "UNARMORED"
set udg_ArmorTypeDebugStr[0] = "NONE" //ARMOR_TYPE_WHOKNOWS in JASS, added in 1.31
set udg_ArmorTypeDebugStr[1] = "FLESH"
set udg_ArmorTypeDebugStr[2] = "METAL"
set udg_ArmorTypeDebugStr[3] = "WOOD"
set udg_ArmorTypeDebugStr[4] = "ETHEREAL"
set udg_ArmorTypeDebugStr[5] = "STONE"
// Added 25 July 2017 to allow detection of things like Bash or Pulverize or AOE spread
set udg_DamageEventAOE = 1
set udg_DamageEventLevel = 1
/*
In-game World Editor doesn't allow Attack Type and Damage Type comparisons.
Therefore, I need to code them as integers into GUI
*/
set udg_ATTACK_TYPE_SPELLS = 0
set udg_ATTACK_TYPE_NORMAL = 1
set udg_ATTACK_TYPE_PIERCE = 2
set udg_ATTACK_TYPE_SIEGE = 3
set udg_ATTACK_TYPE_MAGIC = 4
set udg_ATTACK_TYPE_CHAOS = 5
set udg_ATTACK_TYPE_HERO = 6
// -
set udg_DAMAGE_TYPE_UNKNOWN = 0
set udg_DAMAGE_TYPE_NORMAL = 4
set udg_DAMAGE_TYPE_ENHANCED = 5
set udg_DAMAGE_TYPE_FIRE = 8
set udg_DAMAGE_TYPE_COLD = 9
set udg_DAMAGE_TYPE_LIGHTNING = 10
set udg_DAMAGE_TYPE_POISON = 11
set udg_DAMAGE_TYPE_DISEASE = 12
set udg_DAMAGE_TYPE_DIVINE = 13
set udg_DAMAGE_TYPE_MAGIC = 14
set udg_DAMAGE_TYPE_SONIC = 15
set udg_DAMAGE_TYPE_ACID = 16
set udg_DAMAGE_TYPE_FORCE = 17
set udg_DAMAGE_TYPE_DEATH = 18
set udg_DAMAGE_TYPE_MIND = 19
set udg_DAMAGE_TYPE_PLANT = 20
set udg_DAMAGE_TYPE_DEFENSIVE = 21
set udg_DAMAGE_TYPE_DEMOLITION = 22
set udg_DAMAGE_TYPE_SLOW_POISON = 23
set udg_DAMAGE_TYPE_SPIRIT_LINK = 24
set udg_DAMAGE_TYPE_SHADOW_STRIKE = 25
set udg_DAMAGE_TYPE_UNIVERSAL = 26
/*
The below variables don't affect damage amount, but do affect the sound played
They also give important information about the type of attack used.
They can differentiate between ranged and melee for units who are both
*/
set udg_WEAPON_TYPE_NONE = 0
// Metal Light/Medium/Heavy
set udg_WEAPON_TYPE_ML_CHOP = 1
set udg_WEAPON_TYPE_MM_CHOP = 2
set udg_WEAPON_TYPE_MH_CHOP = 3
set udg_WEAPON_TYPE_ML_SLICE = 4
set udg_WEAPON_TYPE_MM_SLICE = 5
set udg_WEAPON_TYPE_MH_SLICE = 6
set udg_WEAPON_TYPE_MM_BASH = 7
set udg_WEAPON_TYPE_MH_BASH = 8
set udg_WEAPON_TYPE_MM_STAB = 9
set udg_WEAPON_TYPE_MH_STAB = 10
// Wood Light/Medium/Heavy
set udg_WEAPON_TYPE_WL_SLICE = 11
set udg_WEAPON_TYPE_WM_SLICE = 12
set udg_WEAPON_TYPE_WH_SLICE = 13
set udg_WEAPON_TYPE_WL_BASH = 14
set udg_WEAPON_TYPE_WM_BASH = 15
set udg_WEAPON_TYPE_WH_BASH = 16
set udg_WEAPON_TYPE_WL_STAB = 17
set udg_WEAPON_TYPE_WM_STAB = 18
// Claw Light/Medium/Heavy
set udg_WEAPON_TYPE_CL_SLICE = 19
set udg_WEAPON_TYPE_CM_SLICE = 20
set udg_WEAPON_TYPE_CH_SLICE = 21
// Axe Medium
set udg_WEAPON_TYPE_AM_CHOP = 22
// Rock Heavy
set udg_WEAPON_TYPE_RH_BASH = 23
/*
Since GUI still doesn't provide Defense Type and Armor Types,
I needed to include the below:
*/
set udg_ARMOR_TYPE_NONE = 0
set udg_ARMOR_TYPE_FLESH = 1
set udg_ARMOR_TYPE_METAL = 2
set udg_ARMOR_TYPE_WOOD = 3
set udg_ARMOR_TYPE_ETHEREAL = 4
set udg_ARMOR_TYPE_STONE = 5
set udg_DEFENSE_TYPE_LIGHT = 0
set udg_DEFENSE_TYPE_MEDIUM = 1
set udg_DEFENSE_TYPE_HEAVY = 2
set udg_DEFENSE_TYPE_FORTIFIED = 3
set udg_DEFENSE_TYPE_NORMAL = 4
set udg_DEFENSE_TYPE_HERO = 5
set udg_DEFENSE_TYPE_DIVINE = 6
set udg_DEFENSE_TYPE_UNARMORED = 7
/*
The remaining stuff is an ugly 'optimization' that I did a long
time ago, thinking that it would improve performance for GUI users
by not having so many different triggerconditions evaluating per
damage event. I am not sure if it even worked; in Lua it might
perform worse, but in vJass it remains to be tested.
*/
set udg_UNIT_CLASS_HERO = 0
set udg_UNIT_CLASS_DEAD = 1
set udg_UNIT_CLASS_STRUCTURE = 2
set udg_UNIT_CLASS_FLYING = 3
set udg_UNIT_CLASS_GROUND = 4
set udg_UNIT_CLASS_ATTACKS_FLYING = 5
set udg_UNIT_CLASS_ATTACKS_GROUND = 6
set udg_UNIT_CLASS_MELEE = 7
set udg_UNIT_CLASS_RANGED = 8
set udg_UNIT_CLASS_GIANT = 9
set udg_UNIT_CLASS_SUMMONED = 10
set udg_UNIT_CLASS_STUNNED = 11
set udg_UNIT_CLASS_PLAGUED = 12
set udg_UNIT_CLASS_SNARED = 13
set udg_UNIT_CLASS_UNDEAD = 14
set udg_UNIT_CLASS_MECHANICAL = 15
set udg_UNIT_CLASS_PEON = 16
set udg_UNIT_CLASS_SAPPER = 17
set udg_UNIT_CLASS_TOWNHALL = 18
set udg_UNIT_CLASS_ANCIENT = 19
set udg_UNIT_CLASS_TAUREN = 20
set udg_UNIT_CLASS_POISONED = 21
set udg_UNIT_CLASS_POLYMORPHED = 22
set udg_UNIT_CLASS_SLEEPING = 23
set udg_UNIT_CLASS_RESISTANT = 24
set udg_UNIT_CLASS_ETHEREAL = 25
set udg_UNIT_CLASS_MAGIC_IMMUNE = 26
set udg_DamageFilterAttackT = -1
set udg_DamageFilterDamageT = -1
set udg_DamageFilterSourceC = -1
set udg_DamageFilterTargetC = -1
set udg_DamageFilterRunChance = 1.00
endfunction
public function RegisterFromHook takes /*
*/ trigger whichTrig, /*
*/ string var, /*
*/ limitop op, /*
*/ real value /*
*/ returns nothing
call DamageTrigger.registerVerbose(whichTrig, var, value, true, GetHandleId(op))
endfunction
hook TriggerRegisterVariableEvent RegisterFromHook
function TriggerRegisterDamageEngineEx takes /*
*/ trigger whichTrig, /*
*/ string eventName, /*
*/ real value, /*
*/ integer opId /*
*/ returns DamageTrigger
return DamageTrigger.registerVerbose( /*
*/ whichTrig, /*
*/ DamageTrigger.getVerboseStr(eventName), /*
*/ value, /*
*/ false, /*
*/ opId /*
*/ )
endfunction
function TriggerRegisterDamageEngine takes /*
*/ trigger whichTrig, /*
*/ string eventName, /*
*/ real value /*
*/ returns DamageTrigger
return DamageTrigger.registerTrigger(whichTrig, eventName, value)
endfunction
function RegisterDamageEngineEx takes /*
*/ code callback, /*
*/ string eventName, /*
*/ real value, /*
*/ integer opId /*
*/ returns DamageTrigger
return TriggerRegisterDamageEngineEx(DamageTrigger[callback], eventName, value, opId)
endfunction
//Similar to TriggerRegisterDamageEvent, but takes code instead of trigger as the first argument.
function RegisterDamageEngine takes /*
*/ code callback, /*
*/ string eventName, /*
*/ real value /*
*/ returns DamageTrigger
return RegisterDamageEngineEx(callback, eventName, value, FILTER_OTHER)
endfunction
/*
The below macros are for GUI to tap into more powerful vJass event filtering:
*/
//! textmacro DAMAGE_TRIGGER_CONFIG
if not DamageTrigger.eventIndex.configured then
//! endtextmacro
//! textmacro DAMAGE_TRIGGER_CONFIG_END
call DamageTrigger.eventIndex.configure()
endif
if not DamageTrigger.eventIndex.checkConfig() then
return
endif
//! endtextmacro
endlibrary
scope Arscurge
globals
private constant integer ABIL_ID = 'A04N'
private constant real AREA = 200.
private constant real DAMAGE_BASE = 30.
private constant real DAMAGE_LEVEL = 40.
private constant real MANA_DAMAGE_BASE = 20.
private constant real MANA_DAMAGE_LEVEL = 10.
private constant string AREA_FX_PATH = "war3mapImported\\Soul Discharge Blue.mdx"
private constant string DAMAGE_FX_PATH = "war3mapImported\\Runic Rocket.mdx"
endglobals
private struct Arscurge
private static method onSpellEffect takes nothing returns nothing
local unit caster = GetTriggerUnit()
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local player owner = GetOwningPlayer(caster)
local real damage = DAMAGE_BASE + DAMAGE_LEVEL * GetUnitAbilityLevel(caster, ABIL_ID)
local real manaDamageReset = MANA_DAMAGE_BASE + MANA_DAMAGE_LEVEL * GetUnitAbilityLevel(caster, ABIL_ID)
local real manaDamage
local real mana
local unit u
call DestroyEffect(AddSpecialEffect(AREA_FX_PATH, x, y))
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, AREA + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if UnitAlive(u) and not IsUnitAlly(u, owner) and IsUnitInRangeXY(u, x, y, AREA) then
set mana = GetUnitState(u, UNIT_STATE_MANA)
if mana > 0. then
if mana < manaDamageReset then
set manaDamage = mana
else
set manaDamage = manaDamageReset
endif
call UnitDamageTarget(caster, u, damage + manaDamage, false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
call SetUnitState(u, UNIT_STATE_MANA, mana - manaDamage)
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_FX_PATH, u, "chest"))
// call EffectUtils.flashGroundEx(DAMAGE_FX_PATH, GetUnitX(u), GetUnitY(u), .4)
else
call UnitDamageTarget(caster, u, damage, false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
endif
endif
endloop
set caster = null
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope DustDevils
globals
private integer DUST_DEVIL_ID = 'n00D'
private integer MINION_ID = 'n001'
private real MINION_DURATION = 30.
private real AREA = 900.
private real INTERVAL = 2.
private real DISTANCE_UPDATE = 150. * ANIMATION_PERIOD
private real HEIGHT_UPDATE = 30. * ANIMATION_PERIOD
private real ANGLE_UPDATE = (90. *bj_DEGTORAD) * ANIMATION_PERIOD
private string CORPSE_PATH = "Abilities\\Weapons\\MeatwagonMissile\\MeatwagonMissile.mdl"
endglobals
struct SpinningCorpse
real distance
real height
real angle
effect fx
method destroy takes nothing returns nothing
call DestroyEffect(this.fx)
call this.deallocate()
endmethod
static method new takes unit u, unit dustDevil returns thistype this
local thistype this = allocate()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xx = GetUnitX(dustDevil)
local real yy = GetUnitY(dustDevil)
local real dx = xx - x
local real dy = yy - y
set this.fx = AddSpecialEffect(CORPSE_PATH, x, y)
set this.distance = SquareRoot(dx * dx + dy * dy)
set this.height = GetLocZ(x, y)
set this.angle = Atan2(y - yy, x - xx)
return this
endmethod
endstruct
struct DustDevils
private unit source
private real x
private real y
private timer clock
private player owner
private real timeout = INTERVAL
private trigger deathListener = CreateTrigger()
private group randomGroup = CreateGroup()
private IntegerList corpseList = 0
private static Table tab = 0
private method destroy takes nothing returns nothing
local IntegerListItem node = this.corpseList.first
local IntegerListItem nodeNext
local SpinningCorpse cs
loop
exitwhen node == 0
set nodeNext = node.next
set cs = node.data
call this.corpseList.erase(node)
call cs.destroy()
set node = nodeNext
endloop
call tab.remove(GetHandleId(this.source))
call this.corpseList.destroy()
call ReleaseTimer(this.clock)
call DestroyGroup(this.randomGroup)
call DestroyTrigger(this.deathListener)
set this.source = null
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real x = GetUnitX(this.source)
local real y = GetUnitY(this.source)
local integer size
local unit u
local real a
local IntegerListItem node = this.corpseList.first
local IntegerListItem nodeNext
local SpinningCorpse cs
loop
exitwhen node == 0
set nodeNext = node.next
set cs = node.data
// update cs distance
set cs.distance = cs.distance - DISTANCE_UPDATE
set cs.height = cs.height + HEIGHT_UPDATE
set cs.angle = cs.angle + ANGLE_UPDATE
call BlzSetSpecialEffectPosition(cs.fx, x + Cos(cs.angle) * cs.distance, y + Sin(cs.angle) * cs.distance, cs.height)
if cs.distance <= 0. then
// create Guardian of the Dust
set a = GetRandomReal(0., 360.)
set u = CreateUnit(this.owner, MINION_ID, x, y, a)
call UnitApplyTimedLife(u, 'BTLF', MINION_DURATION)
set a = a * bj_DEGTORAD
call IssuePointOrderById(u, 851983, x + Cos(a) * AREA, y + Sin(a) * AREA) // order attack
call this.corpseList.erase(node)
call cs.destroy()
endif
set node = nodeNext
endloop
set this.timeout = this.timeout - ANIMATION_PERIOD
if this.timeout <= 0. then
set this.timeout = INTERVAL
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, AREA + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
if not UnitAlive(u) and BlzGetUnitBooleanField(u, UNIT_BF_RAISABLE) then
call GroupAddUnit(this.randomGroup, u)
endif
endloop
set size = BlzGroupGetSize(this.randomGroup)
if size > 0 then
set u = BlzGroupUnitAt(this.randomGroup, GetRandomInt(0, size - 1))
call corpseList.push(SpinningCorpse.new(u, this.source))
call RemoveUnit(u)
call GroupClear(this.randomGroup)
endif
endif
endmethod
private static method new takes unit source returns nothing
local thistype this = allocate()
set this.source = source
set this.owner = GetOwningPlayer(source)
set this.clock = NewTimerEx(this)
set this.corpseList = IntegerList.create()
call TriggerRegisterDeathEvent(this.deathListener, source)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
set tab[GetHandleId(source)] = this
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
endmethod
private static method onIndex takes nothing returns nothing
if GetUnitTypeId(GetIndexedUnit()) == DUST_DEVIL_ID then
call thistype.new(GetIndexedUnit())
endif
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call OnUnitIndex(function thistype.onIndex)
endmethod
endstruct
endscope
scope GoldenFan
globals
private constant integer ABIL_ID = 'A056'
private constant integer DUMMY_ABIL_ID = 'A055'
private constant real AREA = 160.
private constant real AREA_LEVEL = 0.
private constant real COLLISION = 60.
private constant real COLLISION_LEVEL = 0.
private constant real DAMAGE = 20.
private constant real DAMAGE_LEVEL = 20.
private constant real DISTANCE = 500.
private constant real DISTANCE_LEVEL = 50.
private constant real ARC = 52. * bj_DEGTORAD
private constant real ARC_LEVEL = 8. * bj_DEGTORAD
private constant integer COUNT = 7
private constant integer COUNT_LEVEL = 3
private constant real ANGLE_JITTER = 8. * bj_DEGTORAD
private constant real DISTANCE_JITTER = 15.
private constant real CHANNEL_TIME = 1.1
private constant real DETONATION_TIME = 1.4
// private constant string AREA_FX_PATH = "war3mapImported\\Soul Discharge Blue.mdx"
private constant string MISSILE_FX_PATH = "war3mapImported\\Deadly Plumage HD Gold.mdx"
endglobals
// Fan Projectile
private struct GoldenFanMissile extends Missiles
GoldenFanHandler handler
boolean isLast = false
boolean deflected = false
integer level
real area
private static method onUnpause takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
call this.flushAll()
call this.deflectTarget(this.source)
set this.deflected = true
call this.pause(false)
endmethod
method onHit takes unit hit returns boolean
if UnitAlive(hit) and not IsUnitAlly(hit, this.owner) then
if not this.deflected then
if not IsUnitInGroup(hit, this.handler.damageGroupA) then
call UnitDamageTarget(this.source, hit, this.damage, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
call GetDummyTarget(this.owner, DUMMY_ABIL_ID, this.level, this.x, this.y, 852095, hit, 1.) // order thunderbolt (storm bolt)
call GroupAddUnit(this.handler.damageGroupA, hit)
endif
else
if not IsUnitInGroup(hit, this.handler.damageGroupB) then
call UnitDamageTarget(this.source, hit, this.damage, true, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
call GetDummyTarget(this.owner, DUMMY_ABIL_ID, this.level, this.x, this.y, 852095, hit, 1.) // order thunderbolt (storm bolt)
call GroupAddUnit(this.handler.damageGroupB, hit)
endif
endif
endif
return false
endmethod
method onFinish takes nothing returns boolean
call this.pause(true)
call TimerStart(NewTimerEx(this), DETONATION_TIME, false, function thistype.onUnpause)
if this.deflected then
if this.isLast then
// call BJDebugMsg("Golden Fan complete")
call this.handler.destroy()
endif
return true
endif
return false
endmethod
endstruct
struct GoldenFanHandler
group damageGroupA = CreateGroup()
group damageGroupB = CreateGroup()
method destroy takes nothing returns nothing
call DestroyGroup(this.damageGroupA)
call DestroyGroup(this.damageGroupB)
endmethod
endstruct
struct GoldenFan
private unit source
private real damage
private real angle
private real angleInc
private real distance
private real area
private real collision
private real x
private real y
private integer count
private integer level
private timer clock
private player owner
private trigger endCastListner = CreateTrigger()
private GoldenFanHandler goldenFanHandler
private static Table tab = 0
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
call DestroyTrigger(this.endCastListner)
call ReleaseTimer(this.clock)
set this.source = null
call this.deallocate()
endmethod
private static method onEndCast takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local GoldenFanMissile missile
local real xTarget
local real yTarget
local real a
local real d
set this.angle = this.angle + this.angleInc
set a = this.angle + GetRandomReal(-ANGLE_JITTER, ANGLE_JITTER)
set d = this.distance + GetRandomReal(-DISTANCE_JITTER, DISTANCE_JITTER)
set xTarget = this.x + Cos(a) * d
set yTarget = this.y + Sin(a) * d
set missile = GoldenFanMissile.create(this.x, this.y, 60., xTarget, yTarget, 0.)
set missile.model = MISSILE_FX_PATH
set missile.speed = 2400.
set missile.scale = .8
set missile.source = this.source
set missile.damage = this.damage
set missile.owner = this.owner
set missile.area = this.area
set missile.level = this.level
set missile.collision = this.collision
set missile.handler = this.goldenFanHandler
set missile.arc = GetRandomReal(5., 7.)
call missile.launch()
set this.count = this.count - 1
if this.count <= 0 then
set missile.isLast = true
call this.destroy()
endif
endmethod
static method new takes unit source, real xTarget, real yTarget returns nothing
local thistype this = allocate()
local UnitData sData = UnitData[source]
local integer level = GetUnitAbilityLevel(source, ABIL_ID)
local real fullArc = ARC + ARC_LEVEL * level
local real aTarget
local real interval
set this.source = source
set this.owner = GetOwningPlayer(source)
set this.x = sData.trueX()
set this.y = sData.trueY()
set this.damage = DAMAGE + DAMAGE_LEVEL * level
set this.area = AREA + AREA_LEVEL * level
set this.collision = COLLISION + COLLISION_LEVEL * level
set aTarget = Atan2(yTarget - this.y, xTarget - this.x)
set this.level = level
set this.count = COUNT + COUNT_LEVEL * level
set this.angleInc = fullArc / this.count
set this.angle = aTarget - (fullArc * .5) - this.angleInc
set this.distance = DISTANCE + DISTANCE_LEVEL * level
set this.goldenFanHandler = GoldenFanHandler.create()
set interval = CHANNEL_TIME / I2R(this.count)
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, interval, true, function thistype.onPeriod)
call TriggerRegisterUnitEvent(this.endCastListner, source, EVENT_UNIT_SPELL_ENDCAST)
call TriggerAddAction(this.endCastListner, function thistype.onEndCast)
set tab[GetHandleId(source)] = this
endmethod
private static method onSpellEffect takes nothing returns nothing
call thistype.new(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
endmethod
private static method onInit takes nothing returns nothing
local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE),'N006', 0., 0., 0.)
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
//preload data
call UnitAddAbility(u, ABIL_ID)
call RemoveUnit(u)
set u = null
endmethod
endstruct
endscope
scope Radiance
globals
private constant integer ABIL_ID = 'A059'
private constant real INTERVAL = .1
private constant real FX_COUNT_INTERVAL = .1
private constant real COOLDOWN = 10.
private constant real SEGMENT_JITTER = 10. * bj_DEGTORAD
// Radiance DAMAGE
private constant real DAMAGE = 0.
private constant real DAMAGE_LEVEL = 40.
private constant real AREA = 100.
private constant real AREA_LEVEL = 0.
private constant real AREA_FINAL = 270.
private constant real AREA_FINAL_LEVEL = 30.
private constant real DURATION = 1.
private constant real DURATION_LEVEL = 0.
private constant string RADIANCE_DAMAGE_FX_PATH = "war3mapImported\\JudgementTarget.mdx"
// Radiance SUMMON
private constant integer GLOBE_ID = 'u005'
private constant integer LIMIT = 0
private constant integer LIMIT_LEVEL = 1
endglobals
struct RadianceDamage
private unit source
private player owner
private timer clock
private real area
private real areaNull = 0.
private real areaInc
private real damage
private real duration
private real x
private real y
private group damageGroup = CreateGroup()
private integer fxCount = 1
private real fxCountTimeout = FX_COUNT_INTERVAL
private method destroy takes nothing returns nothing
call ReleaseTimer(this.clock)
call DestroyGroup(this.damageGroup)
set this.source = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real a = GetRandomReal(0., TAU)
local real segment = TAU / this.fxCount
local real d
local real x
local real y
local unit u
local integer i = 0
local real newArea = this.area + this.areaInc
loop
set i = i + 1
exitwhen i > this.fxCount
set a = a + segment + GetRandomReal(-SEGMENT_JITTER, SEGMENT_JITTER)
set d = GetRandomReal(this.areaNull, newArea)
set x = this.x + Cos(a) * d
set y = this.y + Sin(a) * d
call DestroyEffect(AddSpecialEffect(RADIANCE_DAMAGE_FX_PATH, x, y))
endloop
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, newArea + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if UnitAlive(u) and not IsUnitAlly(u, this.owner) and not IsUnitInGroup(u, this.damageGroup) /*
*/ and not (IsUnitType(u, UNIT_TYPE_MECHANICAL) or IsUnitType(u, UNIT_TYPE_STRUCTURE) or IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) /*
*/ and IsUnitInRangeXY(u, x, y, this.area) and not IsUnitInRangeXY(u, x, y, this.areaNull) then
call UnitDamageTarget(this.source, u, this.damage, false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
call GroupAddUnit(this.damageGroup, u)
endif
endloop
set this.area = newArea
set this.areaNull = this.areaNull + this.areaInc
set this.duration = this.duration - INTERVAL
if this.duration <= 0. then
call this.destroy()
else
set this.fxCountTimeout = this.fxCountTimeout - INTERVAL
if this.fxCountTimeout <= 0. then
set this.fxCountTimeout = FX_COUNT_INTERVAL
set this.fxCount = this.fxCount + 1
endif
endif
endmethod
static method new takes unit caster, integer level, real x, real y, real cd returns nothing
local thistype this = allocate()
set this.source = caster
set this.owner = GetOwningPlayer(caster)
set this.damage = DAMAGE + DAMAGE_LEVEL * level
set this.duration = DURATION + DURATION_LEVEL * level
set this.area = AREA + AREA_LEVEL * level
set this.areaInc = (((AREA_FINAL + AREA_FINAL_LEVEL * level) - this.area) / this.duration) * INTERVAL
set this.x = x
set this.y = y
if cd > 0. then
call BlzStartUnitAbilityCooldown(caster, ABIL_ID, cd)
endif
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
endmethod
endstruct
//! runtextmacro DEFINE_LIST("private", "GlobesList", "unit")
struct Radiance
private static Table tab = 0
private unit source
private GlobesList globesList
private method destroy takes nothing returns nothing
local GlobesListItem node = this.globesList.first
local GlobesListItem nodeNext
loop
exitwhen node == 0
set nodeNext = node.next
// set globe = node.data
call KillUnit(node.data)
set node = nodeNext
endloop
call this.globesList.destroy()
set this.source = null
endmethod
private static method new takes unit source returns thistype
local thistype this = allocate()
set tab[GetHandleId(source)] = this
set this.globesList = GlobesList.create()
set this.source = source
return this
endmethod
private static method onDeath takes nothing returns nothing
local unit globe = GetTriggerUnit()
local thistype this = tab[GetHandleId(globe)]
// call RadianceDamage.new(this.source, GetUnitAbilityLevel(this.source, ABIL_ID), GetUnitX(globe), GetUnitY(globe))
call this.globesList.removeElem(globe)
if this.globesList.empty() then
call this.destroy()
endif
set globe = null
call DestroyTrigger(GetTriggeringTrigger())
endmethod
private static method onSpellEffect takes nothing returns nothing
local unit caster = GetSpellAbilityUnit()
local unit target = GetSpellTargetUnit()
local integer level = GetUnitAbilityLevel(caster, ABIL_ID)
local trigger deathListener
local unit globe
local thistype this
// if NOT targeting a unit
if target == null then
set this = tab[GetHandleId(caster)]
if this == null then
set this = Radiance.new(caster)
endif
set globe = CreateUnit(GetOwningPlayer(caster), GLOBE_ID, GetSpellTargetX(), GetSpellTargetY(), bj_UNIT_FACING)
set deathListener = CreateTrigger()
call TriggerRegisterDeathEvent(deathListener, globe)
call TriggerAddAction(deathListener, function thistype.onDeath)
set tab[GetHandleId(globe)] = this
set deathListener = null
call this.globesList.push(globe)
if this.globesList.size() > LIMIT + LIMIT_LEVEL * level then
call KillUnit(this.globesList.first.data)
endif
set globe = null
// if targeting a unit
else
if GetOwningPlayer(target) == GetOwningPlayer(caster) and GetUnitTypeId(target) == GLOBE_ID then
call KillUnit(target)
call RadianceDamage.new(caster, level, GetUnitX(target), GetUnitY(target), 0.)
else
call RadianceDamage.new(caster, level, GetUnitX(target), GetUnitY(target), COOLDOWN)
endif
set target = null
endif
set caster = null
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope FieldOfReeds
globals
private constant integer ABIL_ID = 'A052'
private constant integer DUMMY_ID = 'e001'
private constant integer FIELD_WEATHER = 'LRaa'
private constant real FIELD_RECT_SIZE = 700.
private constant real FIELD_RADIUS = 900.
private constant real FIELD_RADIUS_SQUARED = FIELD_RADIUS * FIELD_RADIUS
private constant real MIN_RADIUS_SQUARED = 0.
private constant integer REED_MIN = 300
private constant integer REED_MAX = 300
private constant real REED_TIME_MIN = 2.
private constant real REED_TIME_MAX = 4.
private constant real REED_SCALE_MIN = 1.1
private constant real REED_SCALE_MAX = 1.3
private constant real INTERVAL = .33
private constant boolean DEBUG_ABILITY = false
endglobals
//! runtextmacro DEFINE_LIST("private", "ReedsList", "effect")
struct FieldOfReeds
private unit source
private unit field
private weathereffect weather
private real x
private real y
private timer clock
private real timeout = 45.
private trigger deathListener = CreateTrigger()
private ReedsList reedsList = 0
private static Table tab = 0
private static string array REED_TYPE
method destroy takes nothing returns nothing
local ReedsListItem node = this.reedsList.first
local ReedsListItem nodeNext
local effect reed
call tab.remove(GetHandleId(this.source))
call DestroyTrigger(this.deathListener)
call ReleaseTimer(this.clock)
set this.source = null
loop
exitwhen node == 0
set nodeNext = node.next
set reed = node.data
// call RescaleEffect.new(reed, BlzGetSpecialEffectScale(reed), 0., GetRandomReal(REED_TIME_MIN, REED_TIME_MAX), 0, "Remove")
call DestroyEffect(reed)
call this.reedsList.erase(node)
set node = nodeNext
endloop
call this.reedsList.destroy()
call RemoveWeatherEffect(this.weather)
call KillUnit(this.field)
set this.field = null
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if not IsUnitInRangeXY(this.source, this.x, this.y, FIELD_RADIUS) then
call this.destroy()
else
set this.timeout = this.timeout - INTERVAL
if this.timeout <= 0. then
call this.destroy()
endif
endif
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this = allocate()
local unit caster = GetTriggerUnit()
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local real d
local real a
local effect reed
local real maxReeds = GetRandomInt(REED_MIN, REED_MAX)
local integer i = 0
local rect r = Rect(x - FIELD_RECT_SIZE, y - FIELD_RECT_SIZE, x + FIELD_RECT_SIZE, y + FIELD_RECT_SIZE)
set this.weather = AddWeatherEffect(r, FIELD_WEATHER)
call EnableWeatherEffect(this.weather, true)
call DestroyEffect(AddSpecialEffect("war3mapImported\\Sunbeam.mdx", x, y))
call RemoveRect(r)
call TriggerRegisterDeathEvent(this.deathListener, caster)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
set tab[GetHandleId(caster)] = this
set this.field = CreateUnit(GetOwningPlayer(caster), DUMMY_ID, x, y, GetRandomReal(0., 360.))
set this.source = caster
set this.x = x
set this.y = y
set this.reedsList = ReedsList.create()
loop
set i = i + 1
exitwhen i > maxReeds
set d = SquareRoot(GetRandomReal(0., 1.) * FIELD_RADIUS_SQUARED)
set a = GetRandomReal(0., 1.) * TAU
set reed = AddSpecialEffect(REED_TYPE[GetRandomInt(4,4)], this.x + Cos(a) * d, this.y + Sin(a) * d)
// call RescaleEffect.new(reed, 0., GetRandomReal(REED_SCALE_MIN, REED_SCALE_MAX), GetRandomReal(REED_TIME_MIN, REED_TIME_MAX), 0, "")
// call BlzSetSpecialEffectScale(reed, 0.)
call BlzSetSpecialEffectYaw(reed, GetRandomReal(225., 315.) * bj_DEGTORAD)
call this.reedsList.push(reed)
endloop
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
set caster = null
endmethod
private static method onInit takes nothing returns nothing
set REED_TYPE[0] = "Doodads\\Ruins\\Plants\\Ruins_Rush\\Ruins_Rush0.mdl"
set REED_TYPE[1] = "Doodads\\Ruins\\Plants\\Ruins_Rush\\Ruins_Rush1.mdl"
set REED_TYPE[2] = "war3mapImported\\Wheat.mdx"
set REED_TYPE[3] = "war3mapImported\\Wheat_WinterEdit.mdx"
set REED_TYPE[4] = "war3mapImported\\WheatSparkly.mdx"
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope Sunbeam
struct Sunbeam
private static integer ABIL_ID = 'A00P'
private static integer SUMMON_ID = 'n00A'
private static real AREA = 200.
private static real INTERVAL = .25
private static real DAMAGE_BASE = 10
private static real DAMAGE_LEVEL = 15
private static string IMPACT_FX_PATH = "Abilities\\Spells\\Undead\\ReplenishHealth\\ReplenishHealthCaster.mdl"
private static Table tab = 0
private trigger deathListener = CreateTrigger()
private unit summoner
private unit minion
private player owner
private timer clock
private real damage
private effect impactFx
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.minion))
set this.minion = null
set this.summoner = null
call DestroyEffect(this.impactFx)
set this.impactFx = null
call DestroyTrigger(this.deathListener)
call ReleaseTimer(this.clock)
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real x = GetUnitX(this.minion)
local real y = GetUnitY(this.minion)
local unit u
call BlzSetSpecialEffectPosition(this.impactFx, GetUnitX(this.minion), GetUnitY(this.minion), BlzGetUnitZ(this.minion))
call BlzPlaySpecialEffect(this.impactFx, ANIM_TYPE_BIRTH)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, AREA + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
if UnitAlive(u) and not IsUnitAlly(u, this.owner) and IsUnitInRangeXY(u, x, y, AREA) then
if IsUnitType(u, UNIT_TYPE_STRUCTURE) then
call UnitDamageTarget(this.summoner, u, this.damage*.5, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
else
call UnitDamageTarget(this.summoner, u, this.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
endif
endif
endloop
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
static method new takes unit summoner, unit minion returns nothing
local thistype this = allocate()
set tab[GetHandleId(minion)] = this
call TriggerRegisterDeathEvent(this.deathListener, minion)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
set this.owner = GetOwningPlayer(minion)
set this.summoner = summoner
set this.minion = minion
set this.damage = (DAMAGE_BASE + DAMAGE_LEVEL * GetUnitAbilityLevel(summoner, ABIL_ID)) * INTERVAL
set this.clock = NewTimerEx(this)
set this.impactFx = AddSpecialEffect(IMPACT_FX_PATH, GetUnitX(minion), GetUnitY(minion))
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
endmethod
private static method onCond takes nothing returns boolean
if GetUnitTypeId(GetTriggerUnit()) == SUMMON_ID then
call Sunbeam.new(GetSummoningUnit(), GetTriggerUnit())
return true
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SUMMON)
call TriggerAddCondition(t, Condition(function thistype.onCond))
set t = null
endmethod
endstruct
endscope
scope Sandman
struct Sandman
private static integer ABIL_ID = 'A04R'
private static real HEAL_BASE = 0.
private static real HEAL_LEVEL = 12.
private static real DUR_BASE = 15.
private static real DUR_LEVEL = 0.
private static real INTERVAL = .33
private static Table tab = 0
private unit source
private real heal
private real duration
private real period = INTERVAL
private trigger deathListener = CreateTrigger()
private timer clock
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.deathListener)
call ReleaseTimer(this.clock)
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
set this.period = this.period - ANIMATION_PERIOD
if this.period <= 0. then
set this.period = INTERVAL
call Heal.make(this.source, this.source, this.heal, 0, "")
set this.duration = this.duration - INTERVAL
if this.duration <= 0. then
call this.destroy()
endif
endif
endmethod
private static method new takes unit u returns nothing
local thistype this = allocate()
local integer lvl = GetUnitAbilityLevel(u, ABIL_ID)
set this.source = u
set this.heal = (HEAL_BASE + HEAL_LEVEL * lvl) * INTERVAL
set this.duration = DUR_BASE + DUR_LEVEL * lvl
set this.clock = NewTimerEx(this)
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
set tab[GetHandleId(u)] = this
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
endmethod
private static method onSpellEffect takes nothing returns nothing
local unit u = GetTriggerUnit()
local integer lvl
local thistype this = tab[GetHandleId(u)]
if this == 0 then
call Sandman.new(u)
else
set lvl = GetUnitAbilityLevel(u, ABIL_ID)
set this.heal = (HEAL_BASE + HEAL_LEVEL * lvl) * INTERVAL
set this.duration = DUR_BASE + DUR_LEVEL * lvl
endif
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope Rupture initializer init
private function onSpellEffect takes nothing returns nothing
local unit caster = GetTriggerUnit()
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local integer level = GetUnitAbilityLevel(caster, 'A05A')
local real area = 275. + 25. * level
local real damage = 40. + 50. * level
local player owner = GetOwningPlayer(caster)
local unit u
call GetDummyPoint(GetOwningPlayer(caster), 'A058', level, x, y, 852592, x, y, 1.)
call EffectUtils.flashGroundEx("abilities\\weapons\\DemolisherMissile\\DemolisherMissile.mdl", x, y, 2. + .25 * level)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, area + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if UnitAlive(u) and not IsUnitAlly(u, owner) and IsUnitInRangeXY(u, x, y, area) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
call UnitDamageTarget(caster, u, damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_ENHANCED, null)
endif
endloop
set caster = null
endfunction
private function init takes nothing returns nothing
call RegisterSpellEffectEvent('A05A', function onSpellEffect)
endfunction
endscope
scope DuneStorm
globals
private constant integer ABIL_ID = 'A04Z'
private constant integer DUMMY_ABIL_ID = 'A057'
private constant real FRONT_DISTANCE = 1400.
private constant real FRONT_DISTANCE_LEVEL = 0.
private constant real BACK_DISTANCE = 400.
private constant real BACK_DISTANCE_LEVEL = 0.
private constant real WIDTH = 500.
private constant real WIDTH_LEVEL = 0.
private constant real DAMAGE_LOW = 10.
private constant real DAMAGE_LOW_LEVEL = 0.
private constant real DAMAGE_HIGH = 50.
private constant real DAMAGE_HIGH_LEVEL = 0.
private constant real RAMP_UP_TIME = 10.
private constant real RAMP_UP_TIME_LEVEL = 0.
private constant real BUILDING_REDUCTION = 0.65
private constant real INTERVAL = .5
private constant real INTERVAL_LEVEL = 0.
private constant real WORM_SPEED_LOWBOUND = 800. * ANIMATION_PERIOD
private constant real WORM_SPEED_HIGHBOUND = 1100. * ANIMATION_PERIOD
private constant real WORM_SCALE = .45
private constant real Z_INCREASE = 560. * ANIMATION_PERIOD
private constant real Z_MAX = 120.
private constant string DUNE_WORM_PATH = "war3mapImported\\GiantSandWormStampede.mdx"
// private constant string DUST_PATH = "war3mapImported\\GiantSandWorm.mdx"
private constant boolean DEBUG_ABILITY = false
endglobals
struct DuneWorm
private static timer clock = null
private static integer count = 0
private static thistype array duneWormArray
private effect wormFx
private real distance
private real distanceEnd
private real speed
private real x
private real y
private real z
private real a
private integer arrayIndex
private method destroy takes nothing returns nothing
local thistype lastId = thistype.duneWormArray[thistype.count]
set thistype.duneWormArray[this.arrayIndex] = lastId
set lastId.arrayIndex = this.arrayIndex
set thistype.duneWormArray[thistype.count] = 0
set thistype.count = thistype.count - 1
if thistype.count == 0 then
call ReleaseTimer(thistype.clock)
endif
call DestroyEffect(this.wormFx)
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this
local integer i = 0
local real x
local real y
loop
set i = i + 1
exitwhen i > thistype.count
set this = thistype.duneWormArray[i]
set this.distance = this.distance + this.speed
set x = this.x + Cos(this.a) * this.distance
set y = this.y + Sin(this.a) * this.distance
if this.distance >= this.distanceEnd then
set this.z = this.z - Z_INCREASE
if this.z <= -500. then
call this.destroy()
else
call BlzSetSpecialEffectPosition(this.wormFx, x, y, GetLocZ(x, y) + this.z)
endif
else
if this.z < Z_MAX then
set this.z = this.z + Z_INCREASE
endif
call BlzSetSpecialEffectPosition(this.wormFx, x, y, GetLocZ(x, y) + this.z)
endif
endloop
endmethod
static method new takes real x, real y, real a, real distance, real speed returns nothing
local thistype this = allocate()
set this.wormFx = AddSpecialEffect(DUNE_WORM_PATH, x, y)
call BlzSetSpecialEffectScale(this.wormFx, WORM_SCALE)
call BlzSetSpecialEffectYaw(this.wormFx, a)
set this.distance = 0.
set this.distanceEnd = distance
set this.speed = speed
set this.x = x
set this.y = y
set this.z = 0.
set this.a = a
set thistype.count = thistype.count + 1
set thistype.duneWormArray[thistype.count] = this
set this.arrayIndex = thistype.count
if thistype.count == 1 then
set thistype.clock = NewTimer()
call TimerStart(thistype.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
endif
endmethod
endstruct
struct DuneStorm
private unit source
private player owner
private timer clock
private trigger endCastListener = CreateTrigger()
private real xA
private real yA
private real xB
private real yB
private real xCorner
private real yCorner
private real aCorner
private real width
private real fullDist
private real angle
private real interval
private real damage
private real damageMax
private real damageRampUp
static if DEBUG_ABILITY then
private lightning beamA
private lightning beamB
private lightning beamC
private lightning beamD
endif
private static Table tab = 0
method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
call DestroyTrigger(this.endCastListener)
call ReleaseTimer(this.clock)
set this.source = null
static if DEBUG_ABILITY then
call DestroyLightning(this.beamA)
call DestroyLightning(this.beamB)
call DestroyLightning(this.beamC)
call DestroyLightning(this.beamD)
endif
call this.deallocate()
endmethod
private static method onEndCast takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real dist = GetRandomReal(0., this.width)
local real x = this.xCorner + Cos(this.aCorner) * dist
local real y = this.yCorner + Sin(this.aCorner) * dist
local integer i = -1
local unit u
local destructable dest
// increase damage
if this.damage < this.damageMax then
set this.damage = this.damage + this.damageRampUp
if this.damage > this.damageMax then
set this.damage = this.damageMax
endif
endif
call DuneWorm.new(x, y, this.angle, this.fullDist, GetRandomReal(WORM_SPEED_LOWBOUND, WORM_SPEED_HIGHBOUND))
if GetRandomInt(0, 1) == 0 then
if GetRandomInt(0, 1) == 0 then
set dist = GetRandomReal(0., 20.)
else
set dist = GetRandomReal(this.width - 20., this.width)
endif
set x = this.xCorner + Cos(this.aCorner) * dist
set y = this.yCorner + Sin(this.aCorner) * dist
call DuneWorm.new(x, y, this.angle, this.fullDist, GetRandomReal(WORM_SPEED_LOWBOUND, WORM_SPEED_HIGHBOUND))
endif
// enum and deal damage
call LineSegment.EnumUnitsEx(ENUM_GROUP, this.xA, this.yA, this.xB, this.yB, this.width, true, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if UnitAlive(u) and not IsUnitAlly(u, this.owner) and not IsUnitType(u, UNIT_TYPE_FLYING) then
if IsUnitType(u, UNIT_TYPE_STRUCTURE) then
call UnitDamageTarget(this.source, u, this.damage * BUILDING_REDUCTION, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_ENHANCED, null)
else
call UnitDamageTarget(this.source, u, this.damage, false, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_ENHANCED, null)
call GetDummyTarget(this.owner, DUMMY_ABIL_ID, 1, GetUnitX(u), GetUnitY(u), 852662, u, 1.) // order acidbomb
endif
endif
endloop
// deal with trees
call LineSegment.EnumDestructables(this.xA, this.yA, this.xB, this.yB, this.width - 50.)
loop
set i = i + 1
exitwhen i > LineSegment.DestructableCounter
set dest = LineSegment.Destructable[i]
if IsTreeAlive(dest) then
call SetDestructableAnimation(dest, "stand hit")
call SetWidgetLife(dest, GetWidgetLife(dest) - this.damage)
endif
endloop
set dest = null
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this = allocate()
local unit caster = GetTriggerUnit()
local real x = GetUnitX(caster)
local real y = GetUnitY(caster)
local real a = Atan2(GetSpellTargetY() - y, GetSpellTargetX() - x)
local real aLeft = a + PIHALF
local real aRight = a - PIHALF
local integer level = GetUnitAbilityLevel(caster, ABIL_ID)
local real frontDist = FRONT_DISTANCE + FRONT_DISTANCE_LEVEL * level
local real backDist = BACK_DISTANCE + BACK_DISTANCE_LEVEL * level
local real xx
local real yy
call TriggerRegisterUnitEvent(this.endCastListener, caster, EVENT_UNIT_SPELL_ENDCAST)
call TriggerAddAction(this.endCastListener, function thistype.onEndCast)
set tab[GetHandleId(caster)] = this
set this.source = caster
set this.owner = GetOwningPlayer(caster)
set this.fullDist = frontDist + backDist
set this.angle = a
set this.width = WIDTH + WIDTH_LEVEL * level
set this.interval = INTERVAL + INTERVAL_LEVEL * level
set this.damage = (DAMAGE_LOW + DAMAGE_LOW_LEVEL * level) * this.interval
set this.damageMax = (DAMAGE_HIGH + DAMAGE_HIGH_LEVEL * level) * this.interval
set this.damageRampUp = (this.damageMax - this.damage / (RAMP_UP_TIME + RAMP_UP_TIME_LEVEL * level)) * this.interval
set xx = x + Cos(a) * frontDist
set yy = y + Sin(a) * frontDist
set this.xA = xx
set this.yA = yy
set a = a + PI
set xx = x + Cos(a) * backDist
set yy = y + Sin(a) * backDist
set this.xB = xx
set this.yB = yy
set a = a - PIHALF
set this.xCorner = this.xB + Cos(a) * (this.width * .5)
set this.yCorner = this.yB + Sin(a) * (this.width * .5)
set this.aCorner = a + PI
// debug
static if DEBUG_ABILITY then
set a = a - PIHALF
set xx = this.xCorner + Cos(a) * this.fullDist
set yy = this.yCorner + Sin(a) * this.fullDist
set this.beamA = AddLightning("DRAM", true, this.xCorner, this.yCorner, xx, yy)
set a = a - PIHALF
set x = xx + Cos(a) * this.width
set y = yy + Sin(a) * this.width
set this.beamB = AddLightning("DRAM", true, xx, yy, x, y)
set a = a - PIHALF
set xx = x + Cos(a) * this.fullDist
set yy = y + Sin(a) * this.fullDist
set this.beamC = AddLightning("DRAM", true, x, y, xx, yy)
set a = a - PIHALF
set x = xx + Cos(a) * this.width
set y = yy + Sin(a) * this.width
set this.beamD = AddLightning("DRAM", true, xx, yy, x, y)
endif
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, this.interval, true, function thistype.onPeriod)
set caster = null
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope Sandstride
struct Sandstride
private static integer ABIL_ID = 'A013'
private static real SPEED = 2000.
private static method onSpellEffect takes nothing returns nothing
local unit u = GetTriggerUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real xx = GetSpellTargetX()
local real yy = GetSpellTargetY()
call Relocate.new(u, xx, yy, SquareRoot(x * xx + y * y) / SPEED)
set u = null
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
library SandTile uses TileSpreader
globals
integer SAND_TILE = 'Clvg' // Felwood Leaves //'cBc2'// Barrens Desert Cliff
private integer array RESISTOR
private integer count
endglobals
// function SetSandTileResistors takes TerrainInfection ti returns nothing
// local integer i = -1
// loop
// set i = i + 1
// exitwhen i > count
// call ti.enableResistor(SAND_TILE, RESISTOR[i], true)
// endloop
// endfunction
function SetSandTileResistors takes TileSpreader ts returns nothing
local integer i = -1
loop
set i = i + 1
exitwhen i > count
call ts.setResistor(RESISTOR[i], true)
endloop
endfunction
private module init
private static method onInit takes nothing returns nothing
set RESISTOR[0] = 'Lrok' // Lordaeron Summer - Rock
set count = 0
endmethod
endmodule
private struct initSandTile
implement init
endstruct
endlibrary
scope SunlitGoldMineAbility
globals
private integer ABIL_ID = 'A02N'
private Table tab = 0
endglobals
struct SunlitGoldMineAbility
private unit source
private trigger deathListener = CreateTrigger()
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.deathListener)
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local unit dead = GetTriggerUnit()
local thistype this = tab[GetHandleId(dead)]
local real x = GetUnitX(dead)
local real y = GetUnitY(dead)
local integer gold = GetResourceAmount(dead)
local boolean foundMine = false
local group g = CreateGroup()
local unit u
if gold <= 0 then
return
endif
// enum nearby units and store in two separate groups.
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, 512., null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
call GroupAddUnit(g, u)// add to g to unhide
exitwhen u == null or foundMine
if GetUnitTypeId(u) == 'ngol' then
set foundMine = true
call GroupClear(ENUM_GROUP)
call GroupClear(g)
endif
endloop
// if mine is not found, hide all units from second group, create mine, unhide.
if not foundMine then
loop
set u = FirstOfGroup(g)
call GroupRemoveUnit(g, u)
call GroupAddUnit(ENUM_GROUP, u)// re-add to ENUM_GROUP to unhide
exitwhen u == null
call ShowUnit(u, false)
endloop
set u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'ngol', x, y, bj_UNIT_FACING)
call SetResourceAmount(u, gold)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
call ShowUnit(u, true)
endloop
endif
call DestroyGroup(g)
set dead = null
set u = null
call this.destroy()
endmethod
private static method new takes unit u returns thistype
local thistype this = allocate()
set this.source = u
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
set tab[GetHandleId(u)] = this
return this
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call SunlitGoldMineAbility.new(u)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call OnUnitPostIndex(function thistype.onPostIndex)
endmethod
endstruct
endscope
scope SteleMorph
struct SteleMorph
private static method onMorph takes nothing returns nothing
local unit morpher = GetEventUnit()
if GetUnitTypeId(morpher) == 'h001' then
call UnitRemoveAbility(morpher, 'ARal')
elseif GetUnitTypeId(morpher) == 'h000' then
call UnitRemoveAbility(morpher, 'ARal')
// call SetUnitAnimation(morpher, "stand alternate")
endif
set morpher = null
endmethod
private static method onInit takes nothing returns nothing
call RegisterNativeEvent(EVENT_ON_TRANSFORM, function thistype.onMorph)
endmethod
endstruct
endscope
scope AridLands
globals
private integer ABIL_ID = 'A00X'
private real CHANCE = .8
private real SPREAD_INTERVAL = 1.5
private real UNSPREAD_INTERVAL = .5
private integer UNSPREAD_COUNT = 3
private real MAX_DISTANCE = 128. * 6
private Table tab = 0
endglobals
struct AridLands
private unit source
private TileSpreader ts
private trigger deathListener = CreateTrigger()
private method destroy takes nothing returns nothing
call this.ts.unspread(UNSPREAD_INTERVAL, UNSPREAD_COUNT)
set this.ts = 0
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.deathListener)
call this.deallocate()
endmethod
private static method onDeindex takes nothing returns nothing
local thistype this = tab[GetHandleId(GetIndexedUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method new takes unit u returns thistype
local thistype this = allocate()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
set tab[GetHandleId(u)] = this
set this.ts = TileSpreader.new(SAND_TILE, x, y, SPREAD_INTERVAL, MAX_DISTANCE, CHANCE, 1)
call SetSandTileResistors(this.ts)
set this.source = u
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call SetBlight(GetOwningPlayer(u), x, y, MAX_DISTANCE, false)
return this
endmethod
private static method onRoot takes nothing returns nothing
local unit rooter = GetRootingUnit()
if GetUnitAbilityLevel(rooter, ABIL_ID) > 0 then
call AridLands.new(rooter)
endif
set rooter = null
endmethod
private static method onUproot takes nothing returns nothing
local unit rooter = GetRootingUnit()
local thistype this = tab[GetHandleId(rooter)]
if this != 0 then
call this.destroy()
endif
set rooter = null
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call AridLands.new(u)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
// create table
set tab = Table.create()
// create events
call RegisterNativeEvent(EVENT_UNIT_ROOT, function thistype.onRoot)
call RegisterNativeEvent(EVENT_UNIT_UPROOT, function thistype.onUproot)
call OnUnitPostIndex(function thistype.onPostIndex)
call OnUnitDeindex(function thistype.onDeindex)
endmethod
endstruct
endscope
scope AridLandsStatic
globals
private integer ABIL_ID = 'A01X'
private real CHANCE = 1.
private real SPREAD_INTERVAL = 1.5
private real UNSPREAD_INTERVAL = .5
private integer UNSPREAD_COUNT = 5
private real MAX_DISTANCE = 128. * 10
private Table tab = 0
endglobals
struct AridLandsStatic
private unit source
private TileSpreader ts
private trigger deathListener = CreateTrigger()
private method destroy takes nothing returns nothing
call this.ts.unspread(UNSPREAD_INTERVAL, UNSPREAD_COUNT)
set this.ts = 0
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.deathListener)
// call BJDebugMsg("AridLandsStatic end")
call this.deallocate()
endmethod
private static method onDeindex takes nothing returns nothing
local thistype this = tab[GetHandleId(GetIndexedUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method new takes unit u returns thistype
local thistype this = allocate()
set tab[GetHandleId(u)] = this
set this.ts = TileSpreader.new(SAND_TILE, GetUnitX(u), GetUnitY(u), SPREAD_INTERVAL, MAX_DISTANCE, CHANCE, 1)
call SetSandTileResistors(this.ts)
set this.source = u
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
return this
endmethod
private static method onRoot takes nothing returns nothing
local unit rooter = GetRootingUnit()
if GetUnitAbilityLevel(rooter, ABIL_ID) > 0 then
call AridLandsStatic.new(rooter)
endif
set rooter = null
endmethod
private static method onUproot takes nothing returns nothing
local unit rooter = GetRootingUnit()
local thistype this = tab[GetHandleId(rooter)]
if this != 0 then
call this.destroy()
endif
set rooter = null
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call AridLandsStatic.new(u)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
// create table
set tab = Table.create()
// create events
call RegisterNativeEvent(EVENT_UNIT_ROOT, function thistype.onRoot)
call RegisterNativeEvent(EVENT_UNIT_UPROOT, function thistype.onUproot)
call OnUnitPostIndex(function thistype.onPostIndex)
call OnUnitDeindex(function thistype.onDeindex)
endmethod
endstruct
endscope
scope EarthGuardian
globals
private constant integer SPELL_ID = 'A03D'
private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_ATTACK
private constant string EVENT_TYPE = "Mod"
private constant integer GOLEM_ID = 'n008'
private constant real LEASH_RANGE = 1200.
private constant integer NO_GOLEM_ABIL = 'A03E'
private constant string GOLEM_RETREAT_FX = "abilities\\weapons\\catapult\\catapultmissile.mdl"//"war3mapImported\\RisingDust.mdx"
private constant real GOLEM_RETREAT_SPEED = 1000.
private constant real RETREAT_DISTANCE = 128.
private constant real GRACE_PERIOD = 10.
private constant real INTERVAL = .33
private constant string EVENT_STRING = "EarthGuardian"
endglobals
struct EarthGuardian
readonly unit source
readonly unit golem
private effect fx
private timer clock
private trigger onDeathListener = CreateTrigger()
private RelocateEffect reloc
private real graceTime = GRACE_PERIOD
private static Table tab = 0
method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
call tab.remove(GetHandleId(this.golem))
call DestroyTrigger(this.onDeathListener)
if this.fx != null then
call SetUnitX(this.golem, this.reloc.xCurrent)
call SetUnitY(this.golem, this.reloc.yCurrent)
call ShowUnit(this.golem, true)
call BlzPauseUnitEx(this.golem, false)
call this.reloc.destroySilent() // prevents an event from being triggered
call DestroyEffect(this.fx)
set this.fx = null
set this.reloc = 0
endif
if UnitAlive(this.golem) then
call UnitApplyTimedLife(this.golem, 'BTLF', .001)
endif
call UnitAddAbility(this.source, NO_GOLEM_ABIL)
set this.source = null
set this.golem = null
call ReleaseTimer(this.clock)
call this.deallocate()
endmethod
private static method onRelocate takes nothing returns nothing
local thistype this = RelocateEffect.getEventCustomId()
if this != 0 and RelocateEffect.getEventCustomString() == EVENT_STRING then
call SetUnitX(this.golem, RelocateEffect.getEventFinalX())
call SetUnitY(this.golem, RelocateEffect.getEventFinalY())
call ShowUnit(this.golem, true)
call BlzPauseUnitEx(this.golem, false)
call DestroyEffect(this.fx)
set this.fx = null
set this.reloc = 0
endif
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real x
local real y
local real xx
local real yy
local real a
if this.fx == null then
if not IsUnitInRange(this.golem, this.source, LEASH_RANGE) then
set x = GetUnitX(this.golem)
set y = GetUnitY(this.golem)
set xx = GetUnitX(this.source)
set yy = GetUnitY(this.source)
set a = Atan2(y - yy, x - xx)
set xx = xx + Cos(a) * RETREAT_DISTANCE
set yy = yy + Sin(a) * RETREAT_DISTANCE
call IssueImmediateOrderById(this.golem, 851973) // order stunned
call ShowUnit(this.golem, false)
call BlzPauseUnitEx(this.golem, true)
call DestroyEffect(AddSpecialEffect(GOLEM_RETREAT_FX, x, y))
set this.fx = AddSpecialEffect(GOLEM_RETREAT_FX, x, y)
call BlzSetSpecialEffectYaw(this.fx, a + bj_PI)
set this.reloc = RelocateEffect.newEx(this.fx, x, y, xx, yy, SquareRoot(x * xx + y * yy) / GOLEM_RETREAT_SPEED , this, EVENT_STRING)
elseif not GetUnitCombatState(this.golem) then
set this.graceTime = this.graceTime - INTERVAL
if this.graceTime <= 0. then
call this.destroy()
endif
endif
endif
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onCombatEnter takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerCombatUnit())]
if this != 0 then
set this.graceTime = GRACE_PERIOD
endif
endmethod
static method new takes unit source, unit target returns nothing
local thistype this = allocate()
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local real xx = GetUnitX(target)
local real yy = GetUnitY(target)
local real a = Atan2(yy - y, xx - x)
set this.source = source
call UnitRemoveAbility(source, NO_GOLEM_ABIL)
set x = x + Cos(a) * 128.
set y = y + Sin(a) * 128.
set this.golem = CreateUnit(GetOwningPlayer(source), GOLEM_ID, x, y, a*bj_RADTODEG)
call IssuePointOrderById(this.golem, 851983, xx, yy)
call TriggerRegisterDeathEvent(this.onDeathListener, this.source)
call TriggerRegisterDeathEvent(this.onDeathListener, this.golem)
call TriggerAddAction(this.onDeathListener, function thistype.onDeath)
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
set tab[GetHandleId(source)] = this
set tab[GetHandleId(this.golem)] = this
endmethod
private static method onDamage takes nothing returns nothing
local unit source = Damage.source
local thistype this = tab[GetHandleId(source)]
set Damage.amount = 0.
if this == 0 then
call thistype.new(source, Damage.target)
endif
set source = null
endmethod
private static method onInit takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngineEx(function thistype.onDamage, EVENT_TYPE, 1.00, DAMAGE_TRIGGER)
set dt.sourceBuff = SPELL_ID
set dt.configured = true
set tab = Table.create()
call RegisterNativeEvent(EVENT_COMBAT_STATE_ENTER, function thistype.onCombatEnter)
call RegisterNativeEvent(EVENT_GENERAL_EFFECT_RELOCATE, function thistype.onRelocate)
endmethod
endstruct
endscope
scope WindBlastGround initializer init
globals
private constant integer SPELL_ID = 'A01T'
private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_ATTACK
private constant string EVENT_TYPE = "Mod"
private constant string EFFECT_PATH = "war3mapImported\\Wind Blast.mdx"
private constant real RADIUS = 280.
endglobals
private function onDamage takes nothing returns nothing
local unit source = Damage.source
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local unit u
local real dmg = Damage.amount
local player owner = GetOwningPlayer(source)
if not IsUnitAlly(Damage.target, owner) then
set Damage.amount = 0.
endif
// call DestroyEffect(AddSpecialEffect(EFFECT_PATH, x, y))
call EffectUtils.flash(EFFECT_PATH, x, y, GetUnitZ(source) + 180.)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, RADIUS + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
if UnitAlive(u) and not IsUnitAlly(u, owner) and not IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitInRangeXY(u, x, y, RADIUS) then
call UnitDamageTarget(source, u, dmg, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
endif
endloop
set source = null
endfunction
private function init takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngineEx(function onDamage, EVENT_TYPE, 1.00, DAMAGE_TRIGGER)
set dt.sourceBuff = SPELL_ID
set dt.configured = true
endfunction
endscope
scope Sandborn
globals
private integer ABIL_ID = 'A027'
private integer REGEN_ABIL_ID = 'A02D'
private integer ATKSPD_ABIL_ID = 'A03M'
private integer SANDFORMED_ABIL_ID = 'A02P'
private integer REEDS_BUFF_ID = 'B010'
private integer TECH_ID = 'R00I'
private real INTERVAL = .33
private real SPEED_BONUS = 100.
private real RESPEED_DUR = .5
private Table tab = 0
endglobals
struct Sandborn
private unit source
private timer clock
readonly boolean isActive
readonly boolean isMuted = false
private trigger deathListener = CreateTrigger()
private trigger reviveListener = CreateTrigger()
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
set this.source = null
if not this.isMuted then
call ReleaseTimer(this.clock)
endif
call DestroyTrigger(this.deathListener)
call DestroyTrigger(this.reviveListener)
call this.deallocate()
endmethod
private static method onDeindex takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private method activate takes nothing returns nothing
if not this.isActive then
call UnitAddAbility(this.source, REGEN_ABIL_ID)
call BlzUnitDisableAbility(this.source, ABIL_ID, false, false)
call Respeed.new(this.source, GetUnitMoveSpeed(source), GetUnitDefaultMoveSpeed(this.source) + SPEED_BONUS, RESPEED_DUR)
if GetUnitAbilityLevel(this.source, SANDFORMED_ABIL_ID) > 0 and GetPlayerTechCount(GetOwningPlayer(this.source), TECH_ID, true) > 0 then
call UnitAddAbility(this.source, ATKSPD_ABIL_ID)
endif
endif
set this.isActive = true
// call BJDebugMsg("Activating...")
endmethod
private method deactivate takes nothing returns nothing
if this.isActive then
call UnitRemoveAbility(this.source, REGEN_ABIL_ID)
call BlzUnitDisableAbility(this.source, ABIL_ID, true, false)
call Respeed.new(this.source, GetUnitMoveSpeed(source), GetUnitDefaultMoveSpeed(this.source), RESPEED_DUR)
endif
set this.isActive = false
// call BJDebugMsg("Deactivating...")
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
// local integer tile = GetTerrainType(GetUnitX(this.source), GetUnitY(this.source))
// local string s = "|cffff0000" + I2S(tile) + "|r"
// if tile == SAND_TILE then
// set s = "|cff2bff00" + I2S(tile) + "|r"
// endif
// call BJDebugMsg("Sand: " + I2S(SAND_TILE) + " | " + s)
if this.isActive then
if GetTerrainType(GetUnitX(this.source), GetUnitY(this.source)) != SAND_TILE and GetUnitAbilityLevel(this.source, REEDS_BUFF_ID) == 0 then
call this.deactivate()
endif
else
if GetTerrainType(GetUnitX(this.source), GetUnitY(this.source)) == SAND_TILE or GetUnitAbilityLevel(this.source, REEDS_BUFF_ID) > 0 then
call this.activate()
endif
endif
endmethod
method mute takes boolean flag returns nothing
if flag then
// call BJDebugMsg("mute == true")
if not this.isMuted then
// call BJDebugMsg("Not muted. Muting...")
set this.isMuted = true
call this.deactivate()
call ReleaseTimer(this.clock)
endif
else
// call BJDebugMsg("mute == false")
if this.isMuted then
// call BJDebugMsg("Muted. Unmuting...")
set this.isMuted = false
if GetTerrainType(GetUnitX(this.source), GetUnitY(this.source)) == SAND_TILE or GetUnitAbilityLevel(this.source, REEDS_BUFF_ID) > 0 then
call this.activate()
endif
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
endif
endif
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.mute(true)
endmethod
private static method onRevive takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.mute(false)
endmethod
private static method onResurrect takes nothing returns nothing
local thistype this = tab[GetHandleId(GetEventUnit())]
if this != 0 then
call this.mute(false)
endif
endmethod
private static method new takes unit u returns thistype
local thistype this = allocate()
set this.source = u
set this.clock = NewTimerEx(this)
if GetTerrainType(GetUnitX(u), GetUnitY(u)) == SAND_TILE then
set this.isActive = true
call SetUnitMoveSpeed(u, GetUnitDefaultMoveSpeed(u) + SPEED_BONUS)
call UnitAddAbility(u, REGEN_ABIL_ID)
if GetUnitAbilityLevel(u, SANDFORMED_ABIL_ID) > 0 and GetPlayerTechCount(GetOwningPlayer(u), TECH_ID, true) > 0 then
call UnitAddAbility(u, ATKSPD_ABIL_ID)
endif
else
set this.isActive = false
call BlzUnitDisableAbility(u, ABIL_ID, true, false)
endif
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call TriggerRegisterUnitEvent(this.reviveListener, u, EVENT_UNIT_HERO_REVIVE_FINISH)
call TriggerAddAction(this.reviveListener, function thistype.onRevive)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
set tab[GetHandleId(u)] = this
return this
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call Sandborn.new(u)
endif
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call OnUnitPostIndex(function thistype.onPostIndex)
call OnUnitDeindex(function thistype.onDeindex)
call RegisterNativeEvent(EVENT_ON_RESURRECTION, function thistype.onResurrect)
endmethod
endstruct
endscope
scope DeathPawn
globals
private constant integer ABIL_ID = 'A05J'
private constant integer ANIMATE_ABIL_ID = 'A05I'
private constant integer MORPH_ABIL_ID = 'A032'
private constant real DAMAGE_BOOST = 1.5
private constant real COOLDOWN = 40.
endglobals
struct DeathPawn
private trigger deathListener = CreateTrigger()
private unit source
private unit minion
private player owner
private static Table tab = 0
static method get takes unit u returns thistype
return tab[GetHandleId(u)]
endmethod
method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
call tab.remove(GetHandleId(this.minion))
call SetUnitX(this.source, GetUnitX(this.minion))
call SetUnitY(this.source, GetUnitY(this.minion))
call IssueImmediateOrderById(this.source, 852156) // unravenform
call RiseAndFall.create(this.source, 0., GetUnitDefaultFlyHeight(this.source), 1., true)
call ShowUnit(this.source, true)
call SetUnitPathing(this.source, true)
call SetUnitInvulnerable(this.source, false)
// call BlzPauseUnitEx(this.source, false)
if IsUnitSelected(this.minion, this.owner) then
if GetLocalPlayer() == this.owner then
// call SelectUnit(this.minion, false)
call SelectUnit(this.source, true)
endif
endif
call DestroyTrigger(this.deathListener)
set this.source = null
set this.minion = null
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method new takes unit summoner, unit minion returns thistype
local thistype this = allocate()
set this.source = summoner
set this.minion = minion
set this.owner = GetOwningPlayer(summoner)
if IsUnitSelected(summoner, this.owner) then
if GetLocalPlayer() == this.owner then
call SelectUnit(summoner, false)
call SelectUnit(minion, true)
endif
endif
call ShowUnit(summoner, false)
call SetUnitPathing(summoner, false)
call SetUnitInvulnerable(summoner, true)
// call BlzPauseUnitEx(this.source, true)
set tab[GetHandleId(summoner)] = this
set tab[GetHandleId(minion)] = this
call TriggerRegisterDeathEvent(this.deathListener, minion)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call BlzSetUnitBaseDamage(minion, R2I(I2R(BlzGetUnitBaseDamage(minion, 0)) * DAMAGE_BOOST), 0)
call UnitAddAbility(minion, 'ACm2') // spell immunity (1,1)
call UnitAddAbility(minion, 'A01P') // vision dust (1,2)
return this
endmethod
private static method onCond takes nothing returns boolean
if GetUnitAbilityLevel(GetSummoningUnit(), ANIMATE_ABIL_ID) > 0 then
call DeathPawn.new(GetSummoningUnit(), GetTriggerUnit())
return true
endif
return false
endmethod
private static method onMorph takes nothing returns nothing
local unit morpher = GetEventUnit()
if GetUnitAbilityLevel(morpher, ANIMATE_ABIL_ID) > 0 then
call IssueImmediateOrderById(morpher, 852217) // animatedead
elseif GetUnitAbilityLevel(morpher, ABIL_ID) > 0 then
call BlzStartUnitAbilityCooldown(morpher, ABIL_ID, COOLDOWN)
endif
set morpher = null
endmethod
private static method onSpellChannel takes nothing returns nothing
local unit caster = GetTriggerUnit()
local unit target = GetSpellTargetUnit()
call IssueImmediateOrderById(caster, 851973) // stunned
call IssueImmediateOrderById(caster, 852155) // ravenform
call Serp.new(caster, GetUnitX(target), GetUnitY(target), (GetUnitMoveSpeed(caster) * .5) * ANIMATION_PERIOD)
// call RiseAndFall.create(caster, GetUnitFlyHeight(caster), 0., 1., true)
set caster = null
set target = null
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SUMMON)
call TriggerAddCondition(t, Condition(function thistype.onCond))
set t = null
call RegisterSpellChannelEvent(ABIL_ID, function thistype.onSpellChannel)
call RegisterNativeEvent(EVENT_ON_TRANSFORM, function thistype.onMorph)
set tab = Table.create()
endmethod
endstruct
endscope
scope uAridWard
struct uAridWard
private static integer UNIT_ID = 'o00G'
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitTypeId(u) == UNIT_ID then
call UnitApplyTimedLife(u, 'BTLF', 60.)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
call OnUnitIndex(function thistype.onIndex)
endmethod
endstruct
endscope
scope uMonolith
struct uMonolith
private static integer UNIT_ID = 'h006'
private static real X = 0.
private static real Y = 40.
private static real Z = 30.
private static real Z_SWIM = 10.
private static real HEIGHT = 100.
private static real WIDTH = 32.
private static real CORE_HEIGHT = HEIGHT * .65
private static real CORE_OFFSET = -16.
private static real BUILD_TIME = 50.
private static real BUILD_TIME_ALT = BUILD_TIME / 2
private static real FLY_HEIGHT = 250.
private static integer FOOD_COST = 5
private static integer ASSIMILATION_CANCEL_ID = 'A05H'
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
local UnitData uData
if GetUnitTypeId(u) == UNIT_ID then
set uData = UnitData.new(u)
// call LaunchSites.new(u, X, Y, Z, Z_SWIM)
call uData.initSizes(HEIGHT, WIDTH, CORE_HEIGHT, CORE_OFFSET)
// disable Stop Assimilation
call UnitAddAbility(u, ASSIMILATION_CANCEL_ID)
call BlzUnitDisableAbility(u, ASSIMILATION_CANCEL_ID, true, false)
endif
set u = null
endmethod
private static method onTrain takes nothing returns nothing
local unit u = GetTrainedUnit()
local destructable dest
if GetUnitTypeId(u) == UNIT_ID then
// check if rallied to tree
set dest = GetUnitRallyDestructable(GetTriggerUnit())
if IsDestructableTree(dest) then
if IsTreeAlive(dest) then
call DelayedOrderTarget.start(u, 852146, 0.1, dest) // eattree
else
call DelayedOrder.start(u, 852100, 0.1) // berserk
endif
endif
set dest = null
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
call OnUnitIndex(function thistype.onIndex)
// call UnitTypeData.new(UNIT_ID, BUILD_TIME, BUILD_TIME_ALT, FLY_HEIGHT, FOOD_COST)
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_TRAIN_FINISH, function thistype.onTrain)
endmethod
endstruct
endscope
scope Assimilate
/*
Assimilate v3
This similar to v2, but with a bias towards which tree the player clicks on as
opposed to letting the system gather any random tree nearby. It will also activate
Assimilate the moment eattree order is given.
Mending Gloam and Khamsin will automatically deactivate Assimilate when ordered
but if the Monolith needs to harvest a tree from far away it will not deactivate
Assimilate to benefit from Flowing Sand speed if there is sand in its way.
*/
globals
private integer ABIL_ID = 'A05G'
private integer BUFF_ID = 'B017'
private integer ABIL_OFF_ID = 'A05H'
private integer RIGHT_CLICK_TREE_ID = 'A05B'
private integer MENDING_GLOAM_ID = 'A02B'
private string UPSTREAM_EFFECT_PATH = "war3mapImported\\MagicGatheringFunnelHeightedGreen.mdl"//"war3mapImported\\DeathBeamNegative.mdx"
private string GROUND_EFFECT_PATH = "war3mapImported\\DeathBeamNegativeTarget.mdx"//"war3mapImported\\DeathBeamNegative.mdx"
private real GROUND_EFFECT_SCALE = 1.
private string HARVEST_EFFECT_PATH = "Objects\\Spawnmodels\\NightElf\\EntBirthTarget\\EntBirthTarget.mdl"
private integer LUMBER_HARVESTED = 10
private real HARVEST_INTERVAL = 5.
private real SEARCH_INTERVAL = 2.5
private real HARVEST_RANGE = 184.
private constant boolean DEBUGGING = false
endglobals
struct Assimilate
private static Table tab = 0
// Harvester state
private static integer STATE_SEARCHING = 0
private static integer STATE_HARVESTING = 1
private static boolean ignoreOrder = false
readonly unit source
readonly destructable targetTree // for moving towards something (for order listeners)
readonly destructable currentTree // for harvesting
readonly effect fxUpstream = null
readonly effect fxGround = null
readonly integer state = STATE_SEARCHING
private timer clock
private boolean restoreType
private real timeout = HARVEST_INTERVAL
private trigger deathListener = CreateTrigger()
private trigger orderListner = CreateTrigger()
private method destroy takes nothing returns nothing
static if DEBUGGING then
call BJDebugMsg("Deactivating Assimilate.")
endif
if this.restoreType then
call UnitAddType(this.source, UNIT_TYPE_PEON)
endif
call tab.remove(GetHandleId(this.source))
set this.source = null
set this.targetTree = null
set this.currentTree = null
call DestroyTrigger(this.deathListener)
call DestroyTrigger(this.orderListner)
call ReleaseTimer(this.clock)
call this.deallocate()
endmethod
private method setState takes integer newState returns nothing
local real x
local real y
if newState == STATE_SEARCHING then
// if was previously harvesting
if this.state == STATE_HARVESTING then
static if DEBUGGING then
call BJDebugMsg("setting state to |cff00e1ffSEARCHING|r from HARVESTING...")
endif
call FlowingSand.mute(this.source, false)
call DestroyEffect(this.fxUpstream)
call DestroyEffect(this.fxGround)
set this.fxUpstream = null
set this.fxGround = null
set this.currentTree = null // null the currentTree
// else
// static if DEBUGGING then
// call BJDebugMsg("setting state to |cff00e1ffSEARCHING|r...")
// endif
endif
elseif newState == STATE_HARVESTING then
// if was previously searching or doing nothing...
if this.state == STATE_SEARCHING then
static if DEBUGGING then
call BJDebugMsg("setting state to |cff91ff00HARVESTING|r from SEARCHING...")
endif
call FlowingSand.mute(this.source, true)
set x = GetUnitX(this.source)
set y = GetUnitY(this.source)
set this.fxUpstream = AddSpecialEffect(UPSTREAM_EFFECT_PATH, x, y)
set this.fxGround = AddSpecialEffect(GROUND_EFFECT_PATH, x, y)
call BlzSetSpecialEffectScale(this.fxGround, GROUND_EFFECT_SCALE)
call UnitRemoveAbility(this.source, BUFF_ID)
set this.targetTree = null // null the targetTree once Harvesting begins
set this.timeout = HARVEST_INTERVAL
// else
// static if DEBUGGING then
// call BJDebugMsg("setting state to |cff91ff00HARVESTING...|r")
// endif
endif
endif
set this.state = newState
endmethod
private method deactivate takes nothing returns nothing
call BlzUnitDisableAbility(this.source, ABIL_OFF_ID, true, false)
call BlzUnitDisableAbility(this.source, ABIL_ID, false, false)
call this.setState(STATE_SEARCHING)
call this.destroy()
endmethod
private method activate takes nothing returns nothing
call BlzUnitDisableAbility(this.source, ABIL_OFF_ID, false, false)
call BlzUnitDisableAbility(this.source, ABIL_ID, true, false)
call UnitRemoveAbility(this.source, MENDING_GLOAM_ID)
call UnitAddAbility(this.source, MENDING_GLOAM_ID)
call this.setState(STATE_SEARCHING)
endmethod
private static method isTreeValidFilter takes nothing returns boolean
return IsTreeAlive(GetFilterDestructable())
endmethod
private method getTreeInRange takes real x, real y, boolean searchFar returns destructable
if not searchFar then
return GetClosestDestructableInRange(x, y, HARVEST_RANGE, Filter(function thistype.isTreeValidFilter))
else
return GetClosestDestructable(x, y, Filter(function thistype.isTreeValidFilter))
endif
return null
endmethod
private method isTreeValid takes destructable tree returns boolean
local real x = GetUnitX(this.source)
local real y = GetUnitY(this.source)
local real xx = GetDestructableX(tree)
local real yy = GetDestructableY(tree)
if not IsTreeAlive(tree) then
return false
endif
return ((xx - x) * (xx - x) + (yy - y) * (yy - y) <= HARVEST_RANGE * HARVEST_RANGE)
return (SquareRoot((xx - x) * (xx - x) + (yy - y) * (yy - y)) <= HARVEST_RANGE)
endmethod
private method harvest takes nothing returns boolean
local real x = GetDestructableX(this.currentTree)
local real y = GetDestructableY(this.currentTree)
local player owner = GetOwningPlayer(this.source)
local integer treeHp = R2I(GetWidgetLife(this.currentTree))
local integer lumber = LUMBER_HARVESTED
if treeHp < LUMBER_HARVESTED then
set lumber = treeHp
endif
call DestroyEffect(AddSpecialEffect(HARVEST_EFFECT_PATH, x, y))
call SetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER, GetPlayerState(owner, PLAYER_STATE_RESOURCE_LUMBER) + lumber)
call defaultTextTagEx("+" + I2S(lumber), x, y, owner, COLOUR_LUMBER)
call SetWidgetLife(this.currentTree, treeHp - lumber)
return IsTreeAlive(this.currentTree)
endmethod
private method findTree takes boolean harvest returns nothing
local real x = GetUnitX(this.source)
local real y = GetUnitY(this.source)
local destructable tree
// if tree is near
set tree = this.getTreeInRange(x, y, false)
if tree != null then
set this.currentTree = tree
call this.setState(STATE_HARVESTING)
if harvest then
if not this.harvest() then
call this.findTree(false)
endif
endif
else
// if tree is far, end the harvest order and order it to eattree.
set tree = this.getTreeInRange(x, y, true)
if tree != null then
call this.setState(STATE_SEARCHING)
call IssueTargetOrderById(this.source, 852146, tree) // order eattree
else
// there are no trees within the far search distance. Deactivate Assimilate.
call this.deactivate()
endif
endif
set tree = null
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local UnitData uData = UnitData[this.source]
local real x = uData.trueX()
local real y = uData.trueY()
local real z = BlzGetLocalUnitZ(this.source)
if this.state == STATE_HARVESTING then
call BlzSetSpecialEffectPosition(this.fxUpstream, x, y, z)
call BlzSetSpecialEffectPosition(this.fxGround, x, y, z)
endif
set this.timeout = this.timeout - ANIMATION_PERIOD
if this.timeout <= 0. then
// if moving towards a specific tree
if this.targetTree != null then
set this.timeout = SEARCH_INTERVAL
static if DEBUGGING then
call BJDebugMsg("|cffffcc00this.targetTree != null|r")
endif
else
set this.timeout = HARVEST_INTERVAL
static if DEBUGGING then
call BJDebugMsg("|cffffcc00Harvesting|r")
endif
// if currentTree is not null...
if this.currentTree != null then
// ... and is in valid, harvest it.
if this.isTreeValid(this.currentTree) then
if not this.harvest() then
call this.findTree(false)
endif
// ... but is not valid, find a new tree
else
call this.findTree(true)
endif
else
call this.findTree(true)
endif
endif
endif
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private static method onOrder takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this = tab[GetHandleId(u)]
local integer odr = GetIssuedOrderId()
local destructable tree = GetOrderTargetDestructable()
if not thistype.ignoreOrder then
// Mending Gloam == order channel | Khamsin == order charm
if odr == 852600 or odr == 852581 then
call this.deactivate()
// odr targets a tree
elseif tree != null then
// not equal to eattree or smart
if not (odr == 852146 or odr == 851971) and this.state == STATE_SEARCHING then
call this.deactivate()
endif
// if not targeting a tree, deactivate if SEARCHING
elseif this.state == STATE_SEARCHING then
call this.deactivate()
endif
endif
set u = null
endmethod
// ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ==== ====
//
// CONSTRUCTOR
//
private static method new takes unit source, destructable tree returns nothing
local thistype this = allocate()
set this.source = source
set this.targetTree = tree
static if DEBUGGING then
call BJDebugMsg("Activating Assimilate...")
endif
call this.activate()
set tab[GetHandleId(source)] = this
set this.restoreType = UnitRemoveType(source, UNIT_TYPE_PEON)
call TriggerRegisterDeathEvent(this.deathListener, source)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call TriggerRegisterUnitEvent(this.orderListner, source, EVENT_UNIT_ISSUED_POINT_ORDER)
call TriggerRegisterUnitEvent(this.orderListner, source, EVENT_UNIT_ISSUED_TARGET_ORDER)
call TriggerRegisterUnitEvent(this.orderListner, source, EVENT_UNIT_ISSUED_ORDER)
call TriggerAddAction(this.orderListner, function thistype.onOrder)
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
if tree == null then
call this.findTree(false)
else
if this.isTreeValid(tree) then
call this.setState(STATE_HARVESTING)
set this.currentTree = tree
else
call this.setState(STATE_SEARCHING)
endif
endif
endmethod
private static method onDeactivate takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.deactivate()
endif
endmethod
private static method onActivate takes nothing returns nothing
local unit caster = GetTriggerUnit()
local thistype this = tab[GetHandleId(caster)]
if this == 0 then
call Assimilate.new(caster, null)
endif
set caster = null
endmethod
/* On eattree, if Assimilate is active, find out if the tree is near.
If near, do nothing. If far, set state to SEARCHING to regain unmute
Flowing Sand.
If Assimilate inactive, activate.
*/
private static method onEatTreeOrder takes nothing returns nothing
local unit caster = GetOrderedUnit()
local destructable tree = GetOrderTargetDestructable()
local thistype this
if GetUnitAbilityLevel(caster, RIGHT_CLICK_TREE_ID) > 0 and tree != null then
set this = tab[GetHandleId(caster)]
// if the target tree is not in range/alive, enter SEARCHING state to unmute Flowing Sand
if this != 0 then
if this.isTreeValid(tree) then
static if DEBUGGING then
call BJDebugMsg("|cff09ff00tree is valid|r")
endif
call this.setState(STATE_HARVESTING)
set this.currentTree = tree
else
static if DEBUGGING then
call BJDebugMsg("|cffff0000tree is NOT valid|r")
endif
call this.setState(STATE_SEARCHING)
set this.targetTree = tree
endif
else
static if DEBUGGING then
call BJDebugMsg("activating from eattree/smart order")
endif
call Assimilate.new(caster, tree)
endif
endif
set caster = null
set tree = null
endmethod
// this this is cast, then the Monolith is already in range.
private static method onEatTreeChannelling takes nothing returns nothing
local unit caster = GetTriggerUnit()
local thistype this = tab[GetHandleId(caster)]
if this != 0 then
call this.setState(STATE_HARVESTING)
set this.currentTree = GetSpellTargetDestructable()
endif
set thistype.ignoreOrder = true
call IssueImmediateOrderById(caster, 851973) // order stunned
set thistype.ignoreOrder = false
set caster = null
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onActivate)
call RegisterSpellEffectEvent(ABIL_OFF_ID, function thistype.onDeactivate)
call RegisterSpellChannelEvent(RIGHT_CLICK_TREE_ID, function thistype.onEatTreeChannelling)
call RegisterOrderEvent(852146, function thistype.onEatTreeOrder) // eattree
call RegisterOrderEvent(851971, function thistype.onEatTreeOrder) // smart
endmethod
endstruct
endscope
scope FlowingSand
globals
private integer ABIL_ID = 'A01W'
private integer REEDS_BUFF_ID = 'B010'
private real INTERVAL = .5
private real SPEED_BONUS = 200.
private real RESPEED_DUR = 1.
private Table tab = 0
endglobals
struct FlowingSand
private unit source
private timer clock
readonly boolean isActive
readonly boolean isMuted = false
private trigger deathListener = CreateTrigger()
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
set this.source = null
if not this.isMuted then
call ReleaseTimer(this.clock)
endif
call DestroyTrigger(this.deathListener)
call this.deallocate()
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
call this.destroy()
endmethod
private method activate takes nothing returns nothing
if not this.isActive then
call BlzUnitDisableAbility(this.source, ABIL_ID, false, false)
call Respeed.new(this.source, GetUnitMoveSpeed(source), GetUnitDefaultMoveSpeed(this.source) + SPEED_BONUS, RESPEED_DUR)
endif
set this.isActive = true
// call BJDebugMsg("Activating...")
endmethod
private method deactivate takes nothing returns nothing
if this.isActive then
call BlzUnitDisableAbility(this.source, ABIL_ID, true, false)
call Respeed.new(this.source, GetUnitMoveSpeed(source), GetUnitDefaultMoveSpeed(this.source), RESPEED_DUR)
endif
set this.isActive = false
// call BJDebugMsg("Deactivating...")
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
// local integer tile = GetTerrainType(GetUnitX(this.source), GetUnitY(this.source))
// local string s = "|cffff0000" + I2S(tile) + "|r"
// if tile == SAND_TILE then
// set s = "|cff2bff00" + I2S(tile) + "|r"
// endif
// call BJDebugMsg("Sand: " + I2S(SAND_TILE) + " | " + s)
if this.isActive then
if GetTerrainType(GetUnitX(this.source), GetUnitY(this.source)) != SAND_TILE and GetUnitAbilityLevel(this.source, REEDS_BUFF_ID) == 0 then
call this.deactivate()
endif
else
if GetTerrainType(GetUnitX(this.source), GetUnitY(this.source)) == SAND_TILE and GetUnitAbilityLevel(this.source, REEDS_BUFF_ID) == 0 then
call this.activate()
endif
endif
endmethod
static method mute takes unit u, boolean flag returns nothing
local thistype this = tab[GetHandleId(u)]
if this != 0 then
if flag then
// call BJDebugMsg("mute == true")
if not this.isMuted then
// call BJDebugMsg("Not muted. Muting...")
set this.isMuted = true
call this.deactivate()
call ReleaseTimer(this.clock)
endif
else
// call BJDebugMsg("mute == false")
if this.isMuted then
// call BJDebugMsg("Muted. Unmuting...")
set this.isMuted = false
if GetTerrainType(GetUnitX(this.source), GetUnitY(this.source)) == SAND_TILE or GetUnitAbilityLevel(this.source, REEDS_BUFF_ID) == 0 then
call this.activate()
endif
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
endif
endif
endif
endmethod
private static method new takes unit u returns thistype
local thistype this = allocate()
set this.source = u
set this.clock = NewTimerEx(this)
if GetTerrainType(GetUnitX(u), GetUnitY(u)) == SAND_TILE then
set this.isActive = true
call SetUnitMoveSpeed(u, GetUnitDefaultMoveSpeed(u) + SPEED_BONUS)
else
set this.isActive = false
call BlzUnitDisableAbility(u, ABIL_ID, true, false)
endif
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
set tab[GetHandleId(u)] = this
return this
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call FlowingSand.new(u)
endif
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call OnUnitPostIndex(function thistype.onPostIndex)
endmethod
endstruct
endscope
scope Khamsin
struct Khamsin
private static constant integer ABIL_ID = 'A02C'
private static constant real DURATION = 7.9
private static Table tab = 0
private unit source
private boolean primed = false
private real xTarget
private real yTarget
private method destroy takes nothing returns nothing
// call Recolour.new(this.source, 0, 255, 0, 255, 0, 255, 0, 255, 1.)
call Recolour.fadeIn(this.source, 1.)
if this.primed then
call SetUnitX(this.source, this.xTarget)
call SetUnitY(this.source, this.yTarget)
endif
call tab.remove(GetHandleId(this.source))
set this.source = null
call this.deallocate()
endmethod
private static method onRecolour takes nothing returns nothing
local thistype this = Recolour.getEventCustomId()
if this != 0 and Recolour.getEventCustomString() == "Khamsin" then
set this.primed = true
call this.destroy()
endif
endmethod
private static method onEndCast takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this = allocate()
set this.source = GetSpellAbilityUnit()
set this.xTarget = GetSpellTargetX()
set this.yTarget = GetSpellTargetY()
set tab[GetHandleId(this.source)] = this
// call Recolour.newEx(this.source, 255, 0, 255, 0, 255, 0, 255, 0, DURATION, this, "Khamsin")
call Recolour.fadeOutEx(this.source, DURATION, this, "Khamsin")
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
call RegisterSpellEndCastEvent(ABIL_ID, function thistype.onEndCast)
call RegisterNativeEvent(EVENT_PLAYER_UNIT_RECOLOUR, function thistype.onRecolour)
endmethod
endstruct
endscope
scope MendingGloam
struct MendingGloam
private static constant integer ABIL_ID = 'A02B'
private static constant real INTERVAL = .25
private static constant real CD_MOD = 2.
private static constant real AREA = 200.
private static constant real AMOUNT = 5. * INTERVAL
private static constant string EFFECT_PATH = "war3mapImported\\GainLife.mdx"
private static Table tab = 0
private unit source
private real x
private real y
private timer clock
private player owner
private effect fx
private real cooldown = 0.
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
call EffectUtils.destroyHide(this.fx)
call BlzStartUnitAbilityCooldown(this.source, ABIL_ID, this.cooldown * CD_MOD)
set this.source = null
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local unit u
call GroupEnumUnitsInRange(ENUM_GROUP, this.x, this.y, AREA + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
if UnitAlive(u) and IsUnitAlly(u, this.owner) and IsUnitInRangeXY(u, this.x, this.y, AREA) then
call Heal.make(this.source, u, AMOUNT, 0, "")
endif
endloop
set this.cooldown = this.cooldown + INTERVAL
endmethod
private static method onEndCast takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this = allocate()
set this.source = GetSpellAbilityUnit()
set this.owner = GetOwningPlayer(this.source)
set this.x = GetSpellTargetX()
set this.y = GetSpellTargetY()
set this.fx = AddSpecialEffect(EFFECT_PATH, this.x, this.y)
set this.clock = NewTimerEx(this)
set tab[GetHandleId(this.source)] = this
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
call RegisterSpellEndCastEvent(ABIL_ID, function thistype.onEndCast)
endmethod
endstruct
endscope
scope Consume
struct Consume
private static constant integer ABIL_ID = 'A01R'
private static constant integer MAX_HP_PER_CAST = 10
private static constant integer DAMAGE_PER_CAST = 1
private static constant real INTERVAL = .33
private static constant real HP_PER_SECOND = 3. * INTERVAL
private static constant real HEAL_DURATION = 45.
private static Table tab = 0
private static string desc = "Consumes a corpse, permanently increasing maximum hit points by 10 and damage by 1, up to a maximum of +100 and +10. Also give temporary regeneration of 3 hit points per second.|nLasts 45 seconds."
readonly integer level = 0
readonly unit source
private timer clock
private real timeout
private real scale
private boolean timerOn
private integer hpBonus
private integer dmgBonus
private method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
set this.source = null
if this.timerOn then
set this.timerOn = false
call ReleaseTimer(this.clock)
endif
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if UnitAlive(this.source) then
call SetWidgetLife(this.source, GetWidgetLife(this.source) + 1.)
endif
set this.timeout = this.timeout - INTERVAL
if this.timeout <= 0. then
set this.timerOn = false
call ReleaseTimer(this.clock)
endif
endmethod
private method levelUp takes nothing returns nothing
local ability abil
if this.level < 10 then
set this.level = this.level + 1
call BlzSetUnitMaxHP(this.source, BlzGetUnitMaxHP(this.source) + MAX_HP_PER_CAST)
call BlzSetUnitBaseDamage(this.source, BlzGetUnitBaseDamage(this.source, 0) + DAMAGE_PER_CAST, 0)
call BlzSetUnitBaseDamage(this.source, BlzGetUnitBaseDamage(this.source, 1) + DAMAGE_PER_CAST, 1)
set this.scale = this.scale + .025
call SetUnitScale(this.source, this.scale, this.scale, this.scale)
set this.hpBonus = this.hpBonus + MAX_HP_PER_CAST
set this.dmgBonus = this.dmgBonus + DAMAGE_PER_CAST
set abil = BlzGetUnitAbility(this.source, ABIL_ID)
call BlzSetAbilityStringLevelField(abil, ABILITY_SLF_TOOLTIP_NORMAL, 0, "Consume |cff00ff00+" + I2S(this.level) + "|r")
call BlzSetAbilityStringLevelField(abil, ABILITY_SLF_TOOLTIP_NORMAL_EXTENDED, 0, desc + "|n|n|cff00ff00Bonus HP: " + I2S(this.hpBonus) /*
*/ + "|nBonus Damage: " + I2S(this.dmgBonus) + "|r")
endif
set this.timeout = HEAL_DURATION
if not this.timerOn then
set this.timerOn = true
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, INTERVAL, true, function thistype.onPeriod)
endif
endmethod
private static method new takes unit source returns thistype
local thistype this = allocate()
set tab[GetHandleId(source)] = this
set this.source = source
set this.scale = BlzGetUnitRealField(source, UNIT_RF_SCALING_VALUE)
return this
endmethod
private static method onSpellEffect takes nothing returns nothing
local unit caster = GetTriggerUnit()
local thistype this = tab[GetHandleId(caster)]
if this == 0 then
set this = thistype.new(caster)
endif
call this.levelUp()
set caster = null
endmethod
private static method onDeindex takes nothing returns nothing
local thistype this = tab[GetHandleId(GetIndexedUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
call OnUnitDeindex(function thistype.onDeindex)
endmethod
endstruct
endscope
scope ScorpionStrike
globals
private integer ABIL_ID = 'A00T'
private integer DUMMY_ABIL_ID = 'A03I'
private real PHASE_A_TIMEOUT = .5
private real PHASE_B_TIMEOUT = .3
private real COOLDOWN = 8.
endglobals
struct ScorpionStrike
timer clock
unit source
unit target
integer phase
private method destroy takes nothing returns nothing
set this.source = null
set this.target = null
call ReleaseTimer(this.clock)
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local unit dummy
set this.phase = this.phase + 1
if this.phase == 1 then
call PauseTimer(this.clock)
call TimerStart(this.clock, PHASE_B_TIMEOUT, false, function thistype.onPeriod)
call SetUnitPosition(this.source, GetUnitX(this.target), GetUnitY(this.target))
call SetUnitAnimation(this.source, "morph alternate")
call BlzPauseUnitEx(this.source, true)
else
call BlzPauseUnitEx(this.source, false)
call SetUnitTimeScale(this.source, 1.)
call IssueTargetOrderById(this.source, 851983, this.target)
call GetDummyTarget(GetOwningPlayer(this.source), DUMMY_ABIL_ID, 1, GetUnitX(this.source), GetUnitY(this.source), 852101, this.source, 1.)
// set dummy = GetDummy(GetOwningPlayer(this.source), DUMMY_ABIL_ID, 1, GetUnitX(this.source), GetUnitY(this.source))
// call IssueTargetOrderById(dummy, 852101, this.source)
// call UnitRemoveAbilityTimed.create(dummy, DUMMY_ABIL_ID, 1., true)
set dummy = null
call this.destroy()
endif
endmethod
private static method onCast takes nothing returns nothing
local thistype this = allocate()
set this.clock = NewTimerEx(this)
set this.source = GetTriggerUnit()
set this.target = GetSpellTargetUnit()
set this.phase = 0
call BlzStartUnitAbilityCooldown(this.source, ABIL_ID, COOLDOWN)
call SetUnitTimeScale(this.source, 2.)
call SetUnitAnimation(this.source, "morph")
call TimerStart(this.clock, PHASE_A_TIMEOUT, false, function thistype.onPeriod)
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpellCastEvent(ABIL_ID, function thistype.onCast)
endmethod
endstruct
endscope
scope
struct CanopicJar
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
local player owner
local real x
local real y
if GetUnitTypeId(u) == 'e00B' then
set x = GetUnitX(u)
set y = GetUnitY(u)
set owner = GetOwningPlayer(u)
call RemoveUnit(u)
set u = CreateUnit(owner, 'u00B', x, y, GetRandomReal(0., 360.))
call SetUnitPathing(u, false)
call SetUnitX(u, x)
call SetUnitY(u, y)
call IssueImmediateOrderById(u, 852155) // ravenform
call UnitApplyTimedLife(u, 'BTLF', 10.)
call RiseAndFall.create(u, 0., 220., 1., true)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
call OnUnitIndex(function thistype.onIndex)
endmethod
endstruct
endscope
scope Nestle
globals
private integer ABIL_ID = 'A01K'
private real CHANCE = 1.
private real INTERVAL = 1.5
private real MAX_DISTANCE = 128. * 6
private real CORPSE_INTERVAL = 5.
private integer CORPSE_MAX = 5
private integer CORPSE_ID = 'n00B'
private real SPREAD_INTERVAL = 1.5
private real UNSPREAD_INTERVAL = .5
private integer UNSPREAD_COUNT = 5
private Table tab = 0
endglobals
struct Nestle
private unit source
private TileSpreader ts
private timer clock
private group corpseGroup = CreateGroup()
private trigger deathListener = CreateTrigger()
private static method resumeDecay takes nothing returns nothing
call UnitSuspendDecay(GetEnumUnit(), false)
endmethod
private method destroy takes nothing returns nothing
// orphan the TerrainInfection and deconstruct it somewhere else
call this.ts.unspread(UNSPREAD_INTERVAL, UNSPREAD_COUNT)
set this.ts = 0
// allow corpses to decay
call ForGroup(this.corpseGroup, function thistype.resumeDecay)
call DestroyGroup(this.corpseGroup)
call ReleaseTimer(this.clock)
// finish destroying the AridLands instance
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.deathListener)
// call BJDebugMsg("AridLands end")
call this.deallocate()
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real x
local real y
local real a
local real d
local unit u
local integer i = 0
local integer iMax
call GroupRefresh(this.corpseGroup)
set iMax = BlzGroupGetSize(this.corpseGroup)
loop
set i = i + 1
exitwhen i > iMax
set u = BlzGroupUnitAt(this.corpseGroup, i-1)
if UnitAlive(u) then
set i = i - 1
call GroupRemoveUnit(this.corpseGroup, u)
// call BJDebugMsg("Removing Alive unit from group...")
endif
endloop
if BlzGroupGetSize(this.corpseGroup) < CORPSE_MAX then
set a = GetRandomReal(0., TAU)
set d = GetRandomReal(100., 300.)
set x = GetUnitX(this.source) + Cos(a) * d
set y = GetUnitY(this.source) + Sin(a) * d
set u = CreateCorpse(GetOwningPlayer(this.source), CORPSE_ID, x, y, GetRandomReal(0., 360.))
call UnitSuspendDecay(u, true)
call GroupAddUnit(this.corpseGroup, u)
endif
// call BJDebugMsg("Final Group size: " + I2S(BlzGroupGetSize(this.corpseGroup)))
// call BJDebugMsg("================")
endmethod
private static method onDeindex takes nothing returns nothing
local thistype this = tab[GetHandleId(GetIndexedUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onDeath takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method new takes unit u returns thistype
local thistype this = allocate()
local integer id = GetHandleId(u)
set this.ts = TileSpreader.new(SAND_TILE, GetUnitX(u), GetUnitY(u), SPREAD_INTERVAL, MAX_DISTANCE, CHANCE, 1)
call SetSandTileResistors(this.ts)
set this.source = u
call TriggerRegisterDeathEvent(this.deathListener, u)
call TriggerAddAction(this.deathListener, function thistype.onDeath)
set this.clock = NewTimerEx(this)
call TimerStart(this.clock, CORPSE_INTERVAL, true, function thistype.onPeriod)
set tab[id] = this
return this
endmethod
private static method onMorph takes nothing returns nothing
local unit morpher = GetEventUnit()
local thistype this = tab[GetHandleId(morpher)]
if this == 0 then
if GetUnitAbilityLevel(morpher, ABIL_ID) > 0 then
call Nestle.new(morpher)
endif
else
if GetUnitAbilityLevel(morpher, ABIL_ID) == 0 then
call this.destroy()
endif
endif
set morpher = null
endmethod
private static method onPostIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call Nestle.new(u)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
// create table
set tab = Table.create()
// create events
call OnUnitPostIndex(function thistype.onPostIndex)
call OnUnitDeindex(function thistype.onDeindex)
call RegisterNativeEvent(EVENT_ON_TRANSFORM, function thistype.onMorph)
endmethod
endstruct
endscope
scope Swathe initializer init
globals
private constant integer SPELL_ID = 'B00T'
// private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_ATTACK
private constant string EVENT_TYPE = "After"
private constant real TRIGGER_CHANCE = .3
private constant real REDUCTION_AMOUNT = .7
endglobals
private function onDamage takes nothing returns nothing
local unit source = Damage.source
local unit target = Damage.target
local player owner = GetOwningPlayer(target)
if GetRandomReal(0., 1.) <= TRIGGER_CHANCE then
set Damage.amount = Damage.amount * REDUCTION_AMOUNT
endif
set source = null
set target = null
endfunction
private function init takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngine(function onDamage, EVENT_TYPE, 1.00)
set dt.targetBuff = SPELL_ID
set dt.configured = true
endfunction
endscope
scope WindBlastAir initializer init
globals
private constant integer SPELL_ID = 'A024'
private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_ATTACK
private constant string EVENT_TYPE = "Mod"
private constant string EFFECT_PATH = "war3mapImported\\Wind Blast.mdx"
private constant real RADIUS = 280.
endglobals
private function onDamage takes nothing returns nothing
local unit source = Damage.source
local unit target = Damage.target
local real x = GetUnitX(source)
local real y = GetUnitY(source)
local unit u
local real dmg = Damage.amount
local player owner = GetOwningPlayer(source)
if IsUnitType(target, UNIT_TYPE_FLYING) then
if not IsUnitAlly(target, owner) then
set Damage.amount = 0.
endif
// call DestroyEffect(AddSpecialEffect(EFFECT_PATH, x, y))
call EffectUtils.flash(EFFECT_PATH, x, y, GetUnitZ(source) + 60.)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, RADIUS + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
if UnitAlive(u) and not IsUnitAlly(u, owner) and IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitInRangeXY(u, x, y, RADIUS) then
call UnitDamageTarget(source, u, dmg, true, false, ATTACK_TYPE_MAGIC, DAMAGE_TYPE_MAGIC, null)
endif
endloop
endif
set source = null
set target = null
endfunction
private function init takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngineEx(function onDamage, EVENT_TYPE, 1.00, DAMAGE_TRIGGER)
set dt.sourceBuff = SPELL_ID
set dt.configured = true
endfunction
endscope
scope Capture
globals
private constant integer ABIL_ID = 'A025'
private constant integer EJECT_ABIL_ID = 'A026'
private constant real DURATION = 15.
private constant string EVENT_STRING = "CaptureShrink"
private constant real VACUUM_DURATION = .6
private constant real DRAIN_AMOUNT = 3.
private constant real COOLDOWN = 15.
private constant real BAR_Z_OFFSET = 270.
private constant real BAR_SCALE = 1.2
private constant string PROGRESS_BAR_PATH = "war3mapImported\\Progressbar.mdx"
private constant string CAPTURE_FX_PATH = "Abilities\\Spells\\Human\\Invisibility\\InvisibilityTarget.mdl"
private constant string UNCAPTURE_FX_PATH = "Objects\\Spawnmodels\\Other\\ToonBoom\\ToonBoom.mdl"
endglobals
struct Capture
unit source
unit target
real scaleDefault
real timeout = DURATION
real drainTimeout = 1
real deploymentX
real deploymentY
effect pBar
timer clock
boolean timerOn = false
private static Table tab = 0
static method get takes unit u returns thistype
return tab[GetHandleId(u)]
endmethod
method destroy takes nothing returns nothing
call tab.remove(GetHandleId(this.source))
call ShowUnit(this.target, true)
call BlzPauseUnitEx(this.target, false)
call SetUnitPathing(this.target, true)
call SetUnitPosition(this.target, this.deploymentX, this.deploymentY)
call Rescale.new(this.target, 0., this.scaleDefault, .5)
call DestroyEffect(AddSpecialEffect(UNCAPTURE_FX_PATH, GetUnitX(this.target), GetUnitY(this.target)))
if this.timerOn then
call ReleaseTimer(this.clock)
endif
if this.pBar != null then
call EffectUtils.destroyHide(this.pBar)
set this.pBar = null
endif
set this.source = null
set this.target = null
call this.deallocate()
endmethod
method deployTarget takes real x, real y returns nothing
set this.deploymentX = x
set this.deploymentY = y
call this.destroy()
endmethod
method preDeployment takes nothing returns nothing
call BlzUnitDisableAbility(this.source, EJECT_ABIL_ID, true, false)
call BlzUnitDisableAbility(this.source, ABIL_ID, false, false)
call BlzStartUnitAbilityCooldown(this.source, ABIL_ID, COOLDOWN)
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local UnitData uData
local real mana
set this.timeout = this.timeout - ANIMATION_PERIOD
if this.timeout <= 0. or not UnitAlive(this.source) or not UnitAlive(this.target) then
set this.deploymentX = GetUnitX(this.source)
set this.deploymentY = GetUnitY(this.source)
call this.preDeployment()
call this.destroy()
else
set uData = UnitData[this.source]
call BlzSetSpecialEffectPosition(this.pBar, uData.trueX(), uData.trueY(), GetUnitZ(this.source) + BAR_Z_OFFSET)
call BlzSetSpecialEffectTime(this.pBar, this.timeout/DURATION)
set mana = GetUnitState(this.target, UNIT_STATE_MANA)
if mana > 0 then
set this.drainTimeout = this.drainTimeout - ANIMATION_PERIOD
if this.drainTimeout <= 0. then
set this.drainTimeout = 1.
if mana < DRAIN_AMOUNT then
call SetUnitState(this.target, UNIT_STATE_MANA, 0.)
call SetUnitState(this.source, UNIT_STATE_MANA, GetUnitState(this.source, UNIT_STATE_MANA) + mana)
else
call SetUnitState(this.target, UNIT_STATE_MANA, mana - DRAIN_AMOUNT)
call SetUnitState(this.source, UNIT_STATE_MANA, GetUnitState(this.source, UNIT_STATE_MANA) + DRAIN_AMOUNT)
endif
endif
endif
endif
endmethod
private static method onShrink takes nothing returns nothing
local thistype this = Rescale.getEventCustomId()
local UnitData uData
if this != 0 and Rescale.getEventCustomString() == EVENT_STRING then
if UnitAlive(this.source) and UnitAlive(this.target) then
// once target has reached the Cube, hide it and swap the abilities
call ShowUnit(this.target, false)
call BlzUnitDisableAbility(this.source, EJECT_ABIL_ID, false, false)
set uData = UnitData[this.source]
set this.pBar = AddSpecialEffect(PROGRESS_BAR_PATH, uData.trueX(), uData.trueY())
call BlzSetSpecialEffectScale(this.pBar, BAR_SCALE)
call BlzSetSpecialEffectHeight(this.pBar, GetUnitZ(this.source) + BAR_Z_OFFSET)
call BlzSetSpecialEffectTimeScale(this.pBar, 0.)
call BlzSetSpecialEffectTime(this.pBar, 0.)
set this.clock = NewTimerEx(this)
set this.timerOn = true
call TimerStart(this.clock, ANIMATION_PERIOD, true, function thistype.onPeriod)
else
set this.deploymentX = GetUnitX(this.source)
set this.deploymentY = GetUnitY(this.source)
call this.preDeployment()
call this.destroy()
endif
endif
endmethod
private static method onSpellEffect takes nothing returns nothing
local thistype this = allocate()
set this.source = GetTriggerUnit()
set this.target = GetSpellTargetUnit()
set this.scaleDefault = BlzGetUnitRealField(this.target, UNIT_RF_SCALING_VALUE)
call Rescale.newEx(this.target, this.scaleDefault, 0., VACUUM_DURATION, this, EVENT_STRING)
call Relocate.new(this.target, GetUnitX(this.source), GetUnitY(this.source), VACUUM_DURATION)
call EffectUtils.addTimedLife(AddSpecialEffectTarget(CAPTURE_FX_PATH, this.target, "chest"), VACUUM_DURATION, false)
call BlzPauseUnitEx(this.target, true)
call SetUnitPathing(this.target, false)
call BlzUnitDisableAbility(this.source, ABIL_ID, true, false)
set tab[GetHandleId(this.source)] = this
endmethod
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
if GetUnitAbilityLevel(u, ABIL_ID) > 0 then
call UnitAddAbility(u, EJECT_ABIL_ID) // add eject
call BlzUnitDisableAbility(u, EJECT_ABIL_ID, true, false) // disable eject
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
call RegisterNativeEvent(EVENT_GENERAL_UNIT_RESCALE, function thistype.onShrink)
call OnUnitIndex(function thistype.onIndex)
endmethod
endstruct
endscope
scope Eject
globals
private constant integer ABIL_ID = 'A026'
private constant real AREA = 200.
private constant real DAMAGE = 50.
endglobals
// The Projectile
private struct Thunderball extends Missiles
Capture capture
private static constant string IMPACT_FX = "Abilities\\Spells\\Orc\\WarStomp\\WarStompCaster.mdl"
private static constant real IMPACT_SCALE = .8
method onFinish takes nothing returns boolean
local unit u
call EffectUtils.flashEx(IMPACT_FX, this.x, this.y, 0., IMPACT_SCALE)
call this.capture.deployTarget(this.x, this.y)
call GroupEnumUnitsInRange(ENUM_GROUP, this.x, this.y, AREA + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if UnitAlive(u) and not IsUnitAlly(u, this.owner) and not IsUnitType(u, UNIT_TYPE_FLYING) and IsUnitInRangeXY(u, this.x, this.y, AREA) then
call UnitDamageTarget(this.source, u, this.damage, false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
endif
endloop
return false
endmethod
private static method onSpellEffect takes nothing returns nothing
local unit caster = GetTriggerUnit()
local thistype this = thistype.create(GetUnitX(caster), GetUnitY(caster), GetUnitFlyHeight(caster) + 60., GetSpellTargetX(), GetSpellTargetY(), 0.)
set this.model = "Units\\Creeps\\HeroTinkerFactory\\HeroTinkerFactoryMissle.mdl"
set this.speed = 1000.
// set this.scale = 1.
set this.source = caster
set this.damage = DAMAGE
set this.owner = GetOwningPlayer(caster)
set this.arc = GetRandomReal(7, 10)
set this.capture = Capture.get(caster)
call this.launch()
call this.capture.preDeployment()
set caster = null
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope Dissipate
globals
private constant integer ABIL_ID = 'A050'
private constant real AREA = 200.
private constant real DISTRIBUTION_AREA = 700.
private constant real MANA_PER_BUFF = 20.
private constant string MANA_RESTORE_PATH = "Abilities\\Spells\\Items\\AIma\\AImaTarget.mdl"
endglobals
struct Dissipate
private static method onSpellEffect takes nothing returns nothing
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real accumulatedMana = 0.
local real distributedAmount = 0.
local integer buffCount = 0
local player owner = GetOwningPlayer(GetTriggerUnit())
local integer size = 0
local unit u
local group g
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, AREA + MAX_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
set buffCount = UnitCountBuffsEx(u, true, true, true, false, false, false, false)
if buffCount > 0 and UnitAlive(u) and IsUnitInRangeXY(u, x, y, AREA) then
set accumulatedMana = accumulatedMana + MANA_PER_BUFF
// call UnitDamageTarget( , u, , false, true, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_UNIVERSAL, null)
endif
endloop
if accumulatedMana > 0. then
// call BJDebugMsg("Accumulated Mana: " + R2S(accumulatedMana))
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, DISTRIBUTION_AREA + MAX_COLLISION_SIZE, null)
set g = CreateGroup()
loop
set u = FirstOfGroup(ENUM_GROUP)
exitwhen u == null
call GroupRemoveUnit(ENUM_GROUP, u)
if UnitAlive(u) and (I2R(BlzGetUnitMaxMana(u)) - GetUnitState(u, UNIT_STATE_MANA)) > 0. and IsUnitInRangeXY(u, x, y, AREA) then
call GroupAddUnit(g, u)
endif
endloop
set size = BlzGroupGetSize(g)
if size > 0 then
set distributedAmount = accumulatedMana / size
// call BJDebugMsg("Friendly casters found: " + I2S(size))
// call BJDebugMsg("Mana per caster: " + R2S(distributedAmount))
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) + distributedAmount)
call DestroyEffect(AddSpecialEffectTarget(MANA_RESTORE_PATH, u, "origin"))
endloop
endif
call DestroyGroup(g)
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope Unleash
struct Unleash
private static integer ABIL_ID = 'A04G'
private static Table tab = 0
private unit source
private effect fx
private trigger endCastListener = CreateTrigger()
private method destroy takes nothing returns nothing
call DestroyEffect(this.fx)
set this.fx = null
call tab.remove(GetHandleId(this.source))
set this.source = null
call DestroyTrigger(this.endCastListener)
set this.endCastListener = null
call this.deallocate()
endmethod
private static method onEndCast takes nothing returns nothing
local thistype this = tab[GetHandleId(GetTriggerUnit())]
if this != 0 then
call this.destroy()
endif
endmethod
private static method onSpellEffect takes nothing returns nothing
local unit caster = GetTriggerUnit()
local UnitData uData = UnitData[caster]
call UnitApplyTimedLife(caster, 'BTLF', 0.1)
call DestroyEffect(AddSpecialEffect("war3mapImported\\JudgementTarget.mdx", uData.trueX(), uData.trueY()))
set caster = null
endmethod
private static method onSpellChannel takes nothing returns nothing
local unit caster = GetTriggerUnit()
local thistype this = allocate()
local UnitData uData = UnitData[caster]
set this.fx = AddSpecialEffect("war3mapImported\\Judgement.mdx", uData.trueX(), uData.trueY())
call BlzSetSpecialEffectScale(this.fx, .3)
set this.source = caster
set tab[GetHandleId(caster)] = this
call TriggerRegisterUnitEvent(this.endCastListener, caster, EVENT_UNIT_SPELL_ENDCAST)
call TriggerAddAction(this.endCastListener, function thistype.onEndCast)
set caster = null
endmethod
private static method onInit takes nothing returns nothing
set tab = Table.create()
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
call RegisterSpellChannelEvent(ABIL_ID, function thistype.onSpellChannel)
endmethod
endstruct
endscope
scope uEnigmaCube
struct uEnigmaCube
private static integer UNIT_ID = 'h003'
private static real X = 0.
private static real Y = 00.
private static real Z = 45.
private static real Z_SWIM = Z
private static real HEIGHT = 160.
private static real WIDTH = 16.
private static real CORE_HEIGHT = HEIGHT * .65
private static real CORE_OFFSET = -16.
private static real BUILD_TIME = 35.
private static real BUILD_TIME_ALT = BUILD_TIME / 2
private static real FLY_HEIGHT = 0.
private static integer FOOD_COST = 5
private static method onIndex takes nothing returns nothing
local unit u = GetIndexedUnit()
local UnitData uData
if GetUnitTypeId(u) == UNIT_ID then
set uData = UnitData.new(u)
// call LaunchSites.new(u, X, Y, Z, Z_SWIM)
call uData.initSizes(HEIGHT, WIDTH, CORE_HEIGHT, CORE_OFFSET)
endif
set u = null
endmethod
private static method onInit takes nothing returns nothing
call OnUnitIndex(function thistype.onIndex)
// call UnitTypeData.new(UNIT_ID, BUILD_TIME, BUILD_TIME_ALT, FLY_HEIGHT, FOOD_COST)
endmethod
endstruct
endscope
scope LuminousStrike initializer init
globals
private constant integer SPELL_ID = 'A036'
private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_OTHER
private constant string EVENT_TYPE = "Mod"
// private constant real TRIGGER_CHANGE = .25
private constant real HEAL_RATE_PER_LEVEL = .25
endglobals
// Heal Projectile
private struct CoruscateHealMissile extends Missiles
method onFinish takes nothing returns boolean
// call DestroyEffect(AddSpecialEffectTarget(HEAL_PATH, this.target, "origin"))
call Heal.make(this.source, this.target, this.damage, 0, "show")
return false
endmethod
endstruct
function coruscateHeal takes player owner, unit caster, unit mark, real amount, real x, real y, real z returns nothing
local UnitData mData = UnitData[mark]
local CoruscateHealMissile missile = CoruscateHealMissile.create(x, y, z, mData.trueX(), mData.trueY(), mData.coreZ())
set missile.model = "war3mapImported\\HallowedFalconMissile.mdl"
set missile.speed = 800.
// set missile.scale = 1.
set missile.target = mark
set missile.source = caster
set missile.damage = amount
set missile.owner = owner
set missile.arc = 7
// set missile.curve = GetRandomReal(-15, 15)
call missile.launch()
endfunction
private function onDamage takes nothing returns nothing
local unit source = Damage.source
local unit target = Damage.target
local player owner = GetOwningPlayer(source)
local real missingHP
local real healVal = 0.
local real hp
local unit u
local real x
local real y
if IsUnitAlly(target, owner) then
set hp = GetWidgetLife(target)
set missingHP = I2R(BlzGetUnitMaxHP(target)) - hp
if missingHP > 0. and not IsUnitType(target, UNIT_TYPE_MECHANICAL) and not IsUnitType(target, UNIT_TYPE_STRUCTURE) then
set healVal = Damage.amount * (HEAL_RATE_PER_LEVEL * GetUnitAbilityLevel(source, SPELL_ID))
if missingHP <= healVal then
set healVal = missingHP
endif
call Heal.make(source, target, healVal, 0, "show")
else
set x = GetUnitX(target)
set y = GetUnitY(target)
set u = PickRandomUnit.findWithIgnore(owner, x, y, 1000., FILTER_ALLY_BOTH_ORGANIC_WOUNDED, source)
if u != null then
set healVal = Damage.amount * (HEAL_RATE_PER_LEVEL * GetUnitAbilityLevel(source, SPELL_ID))
call coruscateHeal(owner, source, u, healVal, x, y, GetUnitFlyHeight(target))
set u = null
endif
endif
set Damage.amount = 0.
endif
set source = null
set target = null
endfunction
private function onHeal takes nothing returns nothing
local Heal heal = Heal.event
if heal.amount >= 1. and heal.tag == "show" then
call defaultTextTagHeightEx("+" + I2S(R2I(heal.amount)), GetUnitX(heal.patient) + GetRandomReal(-60., 10.), GetUnitY(heal.patient) + GetRandomReal(-30., 30.), GetUnitFlyHeight(heal.patient), GetOwningPlayer(heal.healer), COLOUR_GREEN)
endif
endfunction
private function init takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngineEx(function onDamage, EVENT_TYPE, 1.00, DAMAGE_TRIGGER)
set dt.sourceBuff = SPELL_ID
set dt.configured = true
call RegisterNativeEvent(EVENT_PLAYER_UNIT_HEAL_TARGET, function onHeal)
endfunction
endscope
scope Retarget
struct Retarget
private static integer ABIL_ID = 'A04D'
private static method onSpellEffect takes nothing returns nothing
local unit caster = GetTriggerUnit()
local unit target = null
local real hpRate
local real prevHpRate = 1.
local player owner = GetOwningPlayer(caster)
local unit u
call GroupEnumUnitsInRange(ENUM_GROUP, GetUnitX(caster), GetUnitY(caster), 1000., null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
set hpRate = GetWidgetLife(u) / I2R(BlzGetUnitMaxHP(u))
if UnitAlive(u) and IsUnitAlly(u, owner) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) and not IsUnitType(u, UNIT_TYPE_MECHANICAL) and hpRate < prevHpRate then
set target = u
set prevHpRate = hpRate
endif
if target != null then
call DelayedOrderTarget.start(caster, 851983, 0., target)
// call IssueTargetOrderById(caster, 851983, target) // for some reason doing this onSpellEffect breaks orders.
set target = null
endif
endloop
set caster = null
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpellEffectEvent(ABIL_ID, function thistype.onSpellEffect)
endmethod
endstruct
endscope
scope InnerLight initializer init
globals
private constant integer SPELL_ID = 'A037'
private constant integer LS_SPELL_ID = 'A036'
private constant integer TECH_ID = 'R008'
// private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_ATTACK
private constant string EVENT_TYPE = ""
// private constant real TRIGGER_CHANGE = .25
private constant real HEAL_RATE_BASE = 15.
private constant real HEAL_RATE_EXTRA = 25.
private constant real HEAL_RADIUS = 700.
private constant real HEAL_COOLDOWN = 1.5
private boolean array HAS_HEALED
private constant string HEAL_SOURCE_FX = "Abilities\\Spells\\Undead\\ReplenishHealth\\ReplenishHealthCaster.mdl"
private constant string HEAL_TARGET_FX = "Abilities\\Spells\\Undead\\VampiricAura\\VampiricAuraTarget.mdl"
endglobals
private function onTimerEnd takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetTimerData(t)
set HAS_HEALED[id] = false
call ReleaseTimer(t)
set t = null
endfunction
private function onDamage takes nothing returns nothing
local unit source = Damage.source
local unit target = Damage.target
local player owner = GetOwningPlayer(target)
local integer id = GetUnitUserData(target)
local real heal
local real hp
local real x
local real y
local unit u
if GetPlayerTechCount(owner, TECH_ID, false) > 0 and not HAS_HEALED[id] then
if GetUnitAbilityLevel(source, LS_SPELL_ID) > 0 then
set heal = HEAL_RATE_EXTRA
else
set heal = HEAL_RATE_BASE
endif
set HAS_HEALED[id] = true
call TimerStart(NewTimerEx(id), HEAL_COOLDOWN, false, function onTimerEnd)
set x = GetUnitX(target)
set y = GetUnitY(target)
call EffectUtils.addTimedLife(AddSpecialEffect(HEAL_SOURCE_FX, x, y), 1., false)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, HEAL_RADIUS, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
set hp = GetWidgetLife(u)
if not IsUnitEnemy(u, owner) and IsUnitInRangeXY(u, x, y, HEAL_RADIUS) and UnitAlive(u) and hp < I2R(BlzGetUnitMaxHP(u)) then
call DestroyEffect(AddSpecialEffectTarget(HEAL_TARGET_FX, u, "origin"))
call SetWidgetLife(u, hp + heal)
endif
endloop
endif
set source = null
set target = null
endfunction
private function init takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngine(function onDamage, EVENT_TYPE, 1.00)
set dt.targetBuff = SPELL_ID
set dt.configured = true
endfunction
endscope
scope SearingArmour initializer init
globals
private constant integer SPELL_ID = 'A028'
private constant integer LS_SPELL_ID = 'A036'
private constant integer TECH_ID = 'R00G'
// private constant integer DAMAGE_TRIGGER = DamageEngine_FILTER_ATTACK
private constant string EVENT_TYPE = ""
// private constant real TRIGGER_CHANGE = .25
private constant real DAMAGE_RATE_BASE = 15.
private constant real DAMAGE_RATE_EXTRA = 25.
private constant real DAMAGE_RADIUS = 400.
private constant real DAMAGE_COOLDOWN = 2.
private boolean array HAS_DAMAGED
private constant string DAMAGE_SOURCE_FX = "war3mapImported\\Radiance Holy.mdx"
private constant string DAMAGE_TARGET_FX = "war3mapImported\\Firebrand Shot Yellow.mdx"
endglobals
private function onTimerEnd takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetTimerData(t)
set HAS_DAMAGED[id] = false
call ReleaseTimer(t)
set t = null
endfunction
private function onDamage takes nothing returns nothing
local unit source = Damage.source
local unit target = Damage.target
local player owner = GetOwningPlayer(target)
local integer id = GetUnitUserData(target)
local real dmg
local real x
local real y
local unit u
if GetPlayerTechCount(owner, TECH_ID, false) > 0 and not HAS_DAMAGED[id] then
if GetUnitAbilityLevel(source, LS_SPELL_ID) > 0 then
set dmg = DAMAGE_RATE_EXTRA
else
set dmg = DAMAGE_RATE_BASE
endif
set HAS_DAMAGED[id] = true
call TimerStart(NewTimerEx(id), DAMAGE_COOLDOWN, false, function onTimerEnd)
set x = GetUnitX(target)
set y = GetUnitY(target)
call EffectUtils.addTimedLife(AddSpecialEffectTarget(DAMAGE_SOURCE_FX, target, "chest"), .8, false)
call GroupEnumUnitsInRange(ENUM_GROUP, x, y, DAMAGE_RADIUS, null)
loop
set u = FirstOfGroup(ENUM_GROUP)
call GroupRemoveUnit(ENUM_GROUP, u)
exitwhen u == null
if not IsUnitAlly(u, owner) and IsUnitInRangeXY(u, x, y, DAMAGE_RADIUS) and UnitAlive(u) then
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_TARGET_FX, u, "chest"))
call UnitDamageTarget(source, u, dmg, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_DIVINE, null)
endif
endloop
endif
set source = null
set target = null
endfunction
private function init takes nothing returns nothing
local DamageTrigger dt = RegisterDamageEngine(function onDamage, EVENT_TYPE, 1.00)
set dt.targetBuff = SPELL_ID
set dt.configured = true
endfunction
endscope