//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
//TESH.scrollpos=193
//TESH.alwaysfold=0
library Retro requires AutoIndex, AutoEvents
//******************************************
// ___ ___ ___ ___ ___
// /__/ /__ / /__/ / /
// / | /__ / / \ /__/
// Created by Bribe
//******************************************
// Original AutoIndex Version
//
/* Requirements
* ¯¯¯¯¯¯¯¯¯¯¯¯
* AutoIndex by grim001 ~ http://www.wc3c.net/showthread.php?t=105643
* AutoEvents by grim001 ~ http://www.wc3c.net/showthread.php?t=106250
*
* Description
* ¯¯¯¯¯¯¯¯¯¯¯
* Retro provides data retrieval and callback operators for use in simulating a time-travel effect for units.
* While there is no promise that this is an easy system to implement, I will try to label everything as easy
* to read and as simple as I can. If you have any questions, just ask.
*
* To Know
* ¯¯¯¯¯¯¯
* On its own, the retro struct indexes every unit that passes through AutoEvents. It records a unit's data
* during each lapse of RETRO_TIMEOUT and saves it all in a three-dimensional hashtable. Simple array values
* record the current-moment's data aspects: x, y, z and, if you desire, facing angle, HP and MP.
*
* To allocate your own struct and have access to Retro's excellent callback features, the struct(s) you use
* must extend array and implement the module named RETRO. This module uses allocate and deallocate as method
* names to regulate those events. You are welcome to call allocate/deallocate freely as they have built-in
* foolproofs. <deallocate> is also automatically called when a unit is loaded into a transport or dies.
*
* You can only instanciate for a given unit once per struct-type. You can create numerous structs which all
* implement the RETRO module.
*
* Events
* ¯¯¯¯¯¯
method onLoop -> called during each iteration of RETRO_TIMEOUT.
method onLoopForward -> called for only one struct per iteration, if it's in forward-mode.
method onLoopReverse -> called for only one struct per iteration, if it's in reverse-mode.
method onStart -> if you specify a "capture" real value, this will be called after that time has expired.
method onFinish -> is called by the deallocate method. deallocate is called automatically if you specify a
real value for "capture" or "initiate". It's basically your "onDestroy" method.
*
* Syntax Example
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
struct timeStruct extends array
method onFinish takes nothing returns nothing
call BJDebugMsg(GetUnitName(.subject)+"'s stats:\n")
call BJDebugMsg("X-coordinate is "+R2S(.x))
call BJDebugMsg("Y-coordinate is "+R2S(.y))
call BJDebugMsg("Z-coordinate is "+R2S(.z))
call BJDebugMsg("facing is "+R2S(.ang))
call BJDebugMsg("hit points are "+R2S(.hp))
call BJDebugMsg("mana points are "+R2S(.mp)+"\n")
endmethod
implement RETRO
static method create takes unit newSubject returns thistype
local thistype this = thistype.allocate(newSubject, true, false)
return this
endmethod
static method onInit takes nothing returns nothing
local thistype this = thistype.create(newSubject)
call this.initiate(0.03)
endmethod
endstruct
*
*
*/
globals
// Values are in seconds;
constant real RETRO_TIMEOUT = 0.03125 /* This value is a tribute to Jesus4Lyf's helpful Timer32 */
constant real RETRO_MEMORY_MAX = 20.0 /* The hashtable will deallocate data past this threshold */
// Static-if controllers;
private constant boolean BREAKS_CHANNEL = true /* <true> uses SetUnitPosition for smoother motion */
private constant boolean RECORD_FACING = true /* If you're an efficiency-freak and don't care, set to false */
private constant boolean RECORD_HP = true /* Can save the health-points of units */
private constant boolean RECORD_MP = false /* Can save the mana-points of units */
endglobals
private module Validate
static method create takes unit whichSubject returns retro
if GetUnitDefaultMoveSpeed(whichSubject) == 0.00 or GetUnitAbilityLevel(whichSubject, 'Aloc') > 0 or IsUnitType(whichSubject, UNIT_TYPE_STRUCTURE) then
return 0
else
return GetUnitId(whichSubject)
endif
endmethod // Anything that returns 0 will under no condition be added to the system.
endmodule
globals
private hashtable retro_memory = InitHashtable() /* Serves as a three-dimensional array */
private trigger retro_launch = CreateTrigger() /* So this system only needs one timer */
private trigger retro_expire = CreateTrigger() /* RETRO uses 5 fewer handles this way */
constant integer RETRO_ERASER = R2I(RETRO_MEMORY_MAX/RETRO_TIMEOUT)
private keyword RetroInitializer
endglobals
struct retro extends array
private retro prev
private retro next
readonly unit subject
readonly integer save
real x
real y
real z
static if RECORD_FACING then // Static ifs for maximum efficiency.
real ang
endif
static if RECORD_HP then
real hp
endif
static if RECORD_MP then
real mp
endif
implement Validate
method destroy takes nothing returns nothing
debug call BJDebugMsg("Error: You cannot manually destroy a retro struct.")
endmethod
private static integer tick = 0
private static method periodic takes nothing returns nothing
local retro this = retro(0).next
set retro.tick = retro.tick + 1
if (retro.tick == 4) then
set retro.tick = 0
loop
exitwhen this == 0
set .save = .save + 1
set .x = GetUnitX(.subject)
set .y = GetUnitY(.subject)
set .z = GetUnitFlyHeight(.subject)
call SaveReal(retro_memory, this * 6 + 0, .save, .x)
call SaveReal(retro_memory, this * 6 + 1, .save, .y)
call SaveReal(retro_memory, this * 6 + 2, .save, .z)
static if RECORD_FACING then
set .ang = GetUnitFacing(.subject)
call SaveReal(retro_memory, this * 6 + 3, .save, .ang)
endif
static if RECORD_HP then
set .hp = GetWidgetLife(.subject)
call SaveReal(retro_memory, this * 6 + 4, .save, .hp)
endif
static if RECORD_MP then
set .mp = GetUnitState(.subject, UNIT_STATE_MANA)
call SaveReal(retro_memory, this * 6 + 5, .save, .mp)
endif
if (.save >= RETRO_ERASER) then
call RemoveSavedReal(retro_memory, this * 6 + 0, .save - RETRO_ERASER)
call RemoveSavedReal(retro_memory, this * 6 + 1, .save - RETRO_ERASER)
call RemoveSavedReal(retro_memory, this * 6 + 2, .save - RETRO_ERASER)
static if RECORD_FACING then
call RemoveSavedReal(retro_memory, this * 6 + 3, .save - RETRO_ERASER)
endif
static if RECORD_HP then
call RemoveSavedReal(retro_memory, this * 6 + 4, .save - RETRO_ERASER)
endif
static if RECORD_MP then
call RemoveSavedReal(retro_memory, this * 6 + 5, .save - RETRO_ERASER)
endif
endif
set this = .next
endloop
endif
// Fire things;
call TriggerEvaluate(retro_launch)
endmethod
private static method onInit takes nothing returns nothing
call TimerStart(CreateTimer(), RETRO_TIMEOUT, true, function retro.periodic)
endmethod
static retro removing
private static method remove takes unit exitSubject returns nothing
local retro this = retro.create(exitSubject)
if .subject == exitSubject then
set .prev.next = .next
set .next.prev = .prev
call FlushChildHashtable(retro_memory, this * 6 + 0)
call FlushChildHashtable(retro_memory, this * 6 + 1)
call FlushChildHashtable(retro_memory, this * 6 + 2)
static if RECORD_FACING then
call FlushChildHashtable(retro_memory, this * 6 + 3)
endif
static if RECORD_HP then
call FlushChildHashtable(retro_memory, this * 6 + 4)
endif
static if RECORD_MP then
call FlushChildHashtable(retro_memory, this * 6 + 5)
endif
// Fire things;
set retro.removing = this
call TriggerEvaluate(retro_expire)
set .subject = null
endif
endmethod
private static method add takes unit newSubject returns nothing
local retro this = retro.create(newSubject)
if this != 0 then
set .subject = newSubject
set .save = 0
set retro(0).next.prev = this
set this.next = retro(0).next
set retro(0).next = this
set this.prev = retro(0)
endif
endmethod
implement RetroInitializer
endstruct
private module RetroInitializer
private static method onInit takes nothing returns nothing
call OnUnitIndexed(retro.add)
call OnUnitDeindexed(retro.remove)
call OnUnitLoad(retro.remove)
call OnUnitUnload(retro.add)
call OnUnitDeath(retro.remove)
call OnUnitReincarnationFinish(retro.add)
endmethod
endmodule
native UnitAlive takes unit id returns boolean
module RETRO /* May only be implemented in <extends array> structs */
boolean noLoops /* To toggle a method on/off that gets called <every single> loop */
boolean noExtras /* To toggle the extra methods on/off (called for one instance per loop) */
private thistype prev
private thistype next
private boolean active
private boolean reverse
private boolean captured
private real timeLeft
private real timePend
private integer iSave
static if RECORD_FACING then
method operator ang takes nothing returns real // Delegate ang from retro
return retro(this).ang
endmethod
method operator ang= takes real r returns nothing
set retro(this).ang = r
endmethod
endif
static if RECORD_HP then
boolean restoreGoodHP
boolean restoreBadHP
boolean restoreAnyHP
method operator hp takes nothing returns real // Delegate hp from retro
return retro(this).hp
endmethod
method operator hp= takes real r returns nothing
set retro(this).hp = r
endmethod
endif
static if RECORD_MP then
boolean restoreGoodMP
boolean restoreBadMP
boolean restoreAnyMP
method operator mp takes nothing returns real // Delegate mp from retro
return retro(this).mp
endmethod
method operator mp= takes real r returns nothing
set retro(this).mp = r
endmethod
endif
method operator x takes nothing returns real // Delegate x
return retro(this).x
endmethod
method operator x= takes real r returns nothing
set retro(this).x = r
endmethod
method operator y takes nothing returns real // Delegate y
return retro(this).y
endmethod
method operator y= takes real r returns nothing
set retro(this).y = r
endmethod
method operator z takes nothing returns real // Delegate z
return retro(this).z
endmethod
method operator z= takes real r returns nothing
set retro(this).z = r
endmethod
method operator subject takes nothing returns unit // Readonly subject
return retro(this).subject
endmethod
method deallocate takes nothing returns nothing
if .active then
set .active = false
set .reverse = false
set .captured = false
set .prev.next = .next
set .next.prev = .prev
static if BREAKS_CHANNEL then
call SetUnitPathing(.subject, true)
endif
static if thistype.onFinish.exists then
call .onFinish()
else
call BJDebugMsg("No onFinish found")
endif
endif
endmethod
method initiate takes real duration returns nothing
set .iSave = retro(this).save
set .timeLeft = .timeLeft + duration
set .reverse = true
static if BREAKS_CHANNEL then
call SetUnitPathing(.subject, false)
endif
endmethod /* <duration> is the time the unit will be in reverse */
method capture takes real duration returns nothing
set .timeLeft = .timeLeft + duration
set .captured = true
if .reverse then
set .reverse = false
static if BREAKS_CHANNEL then
call SetUnitPathing(.subject, true)
endif
endif
endmethod /* <duration> is the time the unit will be in forward. Reverse is 1/4 of that */
method collapse takes nothing returns nothing
if not .reverse and .captured then
set .timeLeft = (.timePend / 4)
set .iSave = retro(this).save
set .timePend = 0.00
set .captured = false
set .reverse = true
static if thistype.onStart.exists then
call .onStart()
endif
static if BREAKS_CHANNEL then
call SetUnitPathing(.subject, false)
endif
endif
endmethod
private static method handler takes nothing returns boolean
local thistype array choices
local thistype this = thistype(0).next
local thistype rand = 0
local real r
loop
exitwhen (this == 0)
if .reverse then
set .x = LoadReal(retro_memory, this * 6 + 0, .iSave)
set .y = LoadReal(retro_memory, this * 6 + 1, .iSave)
set .z = LoadReal(retro_memory, this * 6 + 2, .iSave)
static if BREAKS_CHANNEL then
call SetUnitPosition(.subject, .x, .y)
else
call SetUnitX(.subject, .x)
call SetUnitY(.subject, .y)
endif
call SetUnitFlyHeight(.subject, .z, 0.00)
static if RECORD_FACING then
set .ang = LoadReal(retro_memory, this * 6 + 3, .iSave)
call SetUnitFacing(.subject, .ang)
endif
static if RECORD_HP then
set r = GetWidgetLife(.subject)
set .hp = LoadReal(retro_memory, this * 6 + 4, .iSave)
if .restoreAnyHP or (.restoreGoodHP and r < .hp - 1) or (.restoreBadHP and r > .hp + 1) then
call SetWidgetLife(.subject, .hp)
endif
endif
static if RECORD_MP then
set r = GetUnitState(.subject, UNIT_STATE_MANA)
set .mp = LoadReal(retro_memory, this * 6 + 5, .iSave)
if .restoreAnyMP or (.restoreGoodMP and r < .mp + 1) or (.restoreBadMP and r > .mp + 1) then
call SetUnitState(.subject, UNIT_STATE_MANA, .mp)
endif
endif
set .iSave = .iSave - 1
set .timeLeft = .timeLeft - RETRO_TIMEOUT
if (.timeLeft <= 0.00 or .iSave <= 0) then
call .deallocate()
endif
elseif .captured then
set .timePend = .timePend + RETRO_TIMEOUT
if (.timePend >= .timeLeft) then
call .collapse()
endif
endif
static if thistype.onLoop.exists then
if not .noLoops then
call .onLoop()
endif
endif
if not .noExtras then
set rand:choices = this
set rand = rand + 1
endif
set this = .next
endloop
if (rand > 0) then
set rand = choices[GetRandomInt(0, rand - 1)]
if rand.reverse then
static if thistype.onLoopReverse.exists then
call rand.onLoopReverse()
endif
else
static if thistype.onLoopForward.exists then
call rand.onLoopForward()
endif
endif
endif
return false
endmethod
static method allocate takes unit theSubject, boolean wantLoop, boolean wantLoopEx returns thistype
local thistype this = retro.create(theSubject)
if this == 0 or retro(this).save == 0 then
return 0
elseif .active then
call .deallocate()
endif
set .active = true
set .noLoops = not wantLoop
set .noExtras = not wantLoopEx
set .timeLeft = 0.00
set .timePend = 0.00
static if RECORD_HP then
set .restoreGoodHP = false
set .restoreBadHP = false
set .restoreAnyHP = false
endif
static if RECORD_MP then
set .restoreGoodMP = false
set .restoreBadMP = false
set .restoreAnyMP = false
endif
set thistype(0).next.prev = this
set this.next = thistype(0).next
set thistype(0).next = this
set this.prev = thistype(0)
return this
endmethod
private static method onRemoval takes nothing returns boolean
if thistype(retro.removing).active then
call thistype(retro.removing).deallocate()
endif
return false
endmethod
private static method onInit takes nothing returns nothing
call TriggerAddCondition(retro_launch, Condition(function thistype.handler)) // Enables the handler event
call TriggerAddCondition(retro_expire, Condition(function thistype.onRemoval)) // Enables the onRemoval event
endmethod
endmodule
module RetroTimerModule
private static method onInit takes nothing returns nothing
call TriggerAddCondition(retro_launch, Condition(function thistype.handler)) // Basically for RetroFade
endmethod
endmodule
endlibrary
//TESH.scrollpos=42
//TESH.alwaysfold=0
//! zinc
library RetroFade {
/*
* The current fade-functionality is very primitive at the moment. If you have dynamic fog in your
* map that changes sometimes, I don't recommend this library for you.
*/
constant real
DURATION = 2.00 ,
MAX_DISTANCE = 1000.0 , /* Players whose cameras are out of this range will not see the fog fade */
FLICKER_MAGNITUDE = 300.0 ; /* Factors between Z-End + this & Z-End - this */
constant boolean
FLICKER = true ;
real fog[];
integer units[];
boolean paused = true, want = false, to = true;
constant integer Fz = 18, FZ = 19, Fd = 20, Fr = 21, Fg = 22, Fb = 23;
function onInit() {
integer i; /* Your fog settings Retro's fog settings
Fog type | | Allowed values
¯¯¯¯¯¯¯¯ V V ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
| Z-Start */ fog[0] = 3000.00 ; fog[6] = 1250.0 ; /* 0.00 to ????
---------------------------------------------------------------------------------------
| Z-End */ fog[1] = 5000.00 ; fog[7] = 3250.00 ; /* 0.00 to ????
---------------------------------------------------------------------------------------
| Density */ fog[2] = 0.50 ; fog[8] = 0.1 ; /* 0.00 to 1.00
---------------------------------------------------------------------------------------
| Red */ fog[3] = 1.00 ; fog[9] = 0.35 ; /* 0.00 to 1.00
---------------------------------------------------------------------------------------
| Green */ fog[4] = 1.00 ; fog[10] = 0.35 ; /* 0.00 to 1.00
---------------------------------------------------------------------------------------
| Blue */ fog[5] = 1.00 ; fog[11] = 0.00 ; /* 0.00 to 1.00
---------------------------------------------------------------------------------------
*/
for (i = 0; i < 24; i+= 1) i:units = 0;
for (i = 0; i < 6; i+= 1) {
if (i:fog==fog[i + 6]) {
if (i:fog < 0.999) i:fog+= 0.001;
else i:fog-= 0.001;
}
fog[i + 12] = (i:fog - fog[i + 6])/(DURATION / RETRO_TIMEOUT);
fog[i + 18] = i:fog;
}
}
public struct retrofade [] {
private static method handler()-> boolean {
integer i = 18;
if (!want && paused)
return false;
static if (FLICKER) {
if (GetRandomReal(0.0, 2.0)<=RETRO_TIMEOUT)
SetTerrainFogEx(0, Fz:fog, GetRandomReal(FZ:fog - FLICKER_MAGNITUDE, FZ:fog + FLICKER_MAGNITUDE), Fd:fog, Fr:fog, Fg:fog, Fb:fog);
}
if (paused)
return false;
if (to) while (i < 24) { /* Fade into retro-fog */
if ( i:fog <= fog[i - 12]) {
i:fog = fog[i - 12]; paused = true; break;
} else i:fog -= fog[i - 6]; i+= 1;
}
else while (i < 24) { /* Fade into normal-fog */
if ( i:fog >= fog[i - 18]) {
i:fog = fog[i - 18]; paused = true; break;
} else i:fog += fog[i - 6]; i+= 1;
}
SetTerrainFogEx (0, Fz:fog, FZ:fog, Fd:fog, Fr:fog, Fg:fog, Fb:fog);
return false;
}
module RetroTimerModule;
static method CamCheck (real centerX, real centerY) -> boolean {
real cam_x= GetCameraTargetPositionX(), cam_y= GetCameraTargetPositionY();
return(cam_x >= centerX - MAX_DISTANCE && cam_y >= centerY - MAX_DISTANCE &&
cam_x <= centerX + MAX_DISTANCE && cam_y <= centerY + MAX_DISTANCE );
}
static method yes (player whichPlayer, real x, real y) {
integer id = GetPlayerId(whichPlayer);
if (GetLocalPlayer() != whichPlayer || whichPlayer == null || id > 11)
return;
if (CamCheck(x, y)) {
to = true;
want = true;
if (paused)
paused = false;
}
units[id]+= 1;
}
static method no (player whichPlayer) {
integer id = GetPlayerId(whichPlayer);
if (GetLocalPlayer() != whichPlayer || whichPlayer==null || id > 11)
return;
units[id]-= 1;
if (units[id] <= 0) {
units[id] = 0;
to = false;
want = false;
if (paused)
paused = false;
}
}
}
}
//! endzinc
library TimeWarp requires Retro, Projectile, optional Knockback, optional RetroFade
//*********************************************************************************
// ___ ___ _ _ ___ _ _ _ ___ ___
// / / /| / /__ | / /__/ /__/ /__/
// / _/_ / |/| /__ |/|/ / / / | /
// Created by Bribe
//************************************************************
globals
private constant integer SPELL_ID = 'A003' /* The rawcode for the Time Travel spell */
private constant integer EXTRA_ID = 'A001' /* An ability that collapses the caster's portals */
private constant integer DUMMY_ID = 'n000' /* This should use the DUMMY.mdx model from XE */
//* >> Knockback Values;
private constant real KB_DURATION = 1.125 /* Duration is in seconds */
private constant real KB_MIN_DISTANCE = 225.0 /* Value in WC3 gridspace */
private constant real KB_MAX_DISTANCE = 525.0
//* >> TimeWarp Values;
private constant real BASE_DURATION = 3.00 /* Base value at level 1 */
private constant real LVL_INC_DURATION = 1.15 /* Level increment value */
private constant real BASE_DAMAGE = 10.0
private constant real LVL_INC_DAMAGE = 4.00
private constant real BASE_AOE = 150.0
private constant real LVL_INC_AOE = 40.0
private group array casterGroup
/*
* If you have the xebasic library in your map, remove these two lines so they won't conflict:
*/ constant integer XE_DUMMY_UNITID = DUMMY_ID
constant real XE_MAX_COLLISION_SIZE = 60.00
endglobals
static if LIBRARY_RetroFade then
globals
private sound callback = CreateMIDISound("FlashBack1Second", 12700, 12700)
endglobals
endif
native UnitAlive takes unit whichUnit returns boolean
static if LIBRARY_Knockback then
struct retroKB extends knockback
effect attach
static constant string fx = "Abilities\\Spells\\Orc\\AncestralSpirit\\AncestralSpiritCaster.mdl"
private method onDestroy takes nothing returns nothing
call DestroyEffect(this.attach)
endmethod
private static method onInit takes nothing returns nothing
call Preload(fx)
endmethod
endstruct
endif
struct retroproj extends projectile
static constant string launchSkin = "Abilities\\Spells\\Undead\\Possession\\PossessionMissile.mdl"
static constant string attackSkin = "Abilities\\Spells\\Undead\\DarkSummoning\\DarkSummonMissile.mdl"
static constant string simpleSkin = "Abilities\\Spells\\NightElf\\Barkskin\\BarkSkinTarget.mdl"
static constant string simpImpact = "Abilities\\Spells\\Items\\AIil\\AIilTarget.mdl"
static constant string painImpact = "Abilities\\Spells\\Demon\\DemonBoltImpact\\DemonBoltImpact.mdl"
private static method onInit takes nothing returns nothing
call Preload(launchSkin)
call Preload(attackSkin)
call Preload(simpleSkin)
call Preload(simpImpact)
call Preload(painImpact)
endmethod
effect skin
group units
boolean primary = true
real damage
real Time
vector carryVec
timewarp carryWarp
private method onFinish takes nothing returns nothing
if .primary then
if UnitAlive(.target) then
call timewarp.Initiate(this)
endif
call .carryVec.destroy()
else
set .carryWarp.go = true
if UnitAlive(.target) and not .carryWarp.to then
call DestroyEffect(AddSpecialEffectTarget(painImpact, .target, "overhead"))
call UnitDamageTarget(.carryWarp.caster, .target, .carryWarp.damage, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, WEAPON_TYPE_WHOKNOWS)
else
call DestroyEffect(AddSpecialEffect(simpImpact, .x, .y))
endif
endif
endmethod
endstruct
struct timewarp extends array
unit caster
unit oriUnit
group units
player casterOwner
player victimOwner
effect attach
effect gloomy
effect spooky
boolean go
boolean to
vector oriVec
real damage
static constant string finalFX = "Abilities\\Spells\\Orc\\MirrorImage\\MirrorImageDeathCaster.mdl"
static constant string callbackFX1 = "Abilities\\Spells\\Demon\\DarkPortal\\DarkPortalTarget.mdl"
static constant string callbackFX2 = "Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl"
static constant string forwardLoopFX = "Abilities\\Weapons\\FlyingMachine\\FlyingMachineImpact.mdl"
static constant string reverseLoopFX1 = "Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl"
static constant string reverseLoopFX2 = "Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl"
static constant string caughtFX = "Abilities\\Spells\\Items\\AIso\\AIsoTarget.mdl"
static constant string gloomyFX = "Abilities\\Spells\\Human\\Banish\\BanishTarget.mdl"
static constant string spookyFX = "Abilities\\Spells\\NightElf\\shadowstrike\\shadowstrike.mdl"
static constant string attachFX = "Abilities\\Spells\\Undead\\Possession\\PossessionTarget.mdl"
static constant string epicenterFX = "Objects\\Spawnmodels\\Undead\\UCancelDeath\\UCancelDeath.mdl"
static constant string recallFXCast = "Abilities\\Spells\\Items\\TomeOfRetraining\\TomeOfRetrainingCaster.mdl"
static constant string recallFXTarg = "Abilities\\Spells\\Human\\MarkOfChaos\\MarkOfChaosTarget.mdl"
private static method PreloadStrings takes nothing returns nothing
call Preload(finalFX)
call Preload(callbackFX1)
call Preload(callbackFX2)
call Preload(forwardLoopFX)
call Preload(reverseLoopFX1)
call Preload(reverseLoopFX2)
call Preload(caughtFX)
call Preload(gloomyFX)
call Preload(spookyFX)
call Preload(attachFX)
call Preload(epicenterFX)
call Preload(recallFXCast)
call Preload(recallFXTarg)
endmethod
private method onFinish takes nothing returns nothing
call DestroyEffect(AddSpecialEffect(finalFX, .x, .y))
call DestroyEffect(.spooky)
call BlzSetSpecialEffectZ(.gloomy, -50000.00) //The effect doesn't disappear immediately, so bury it
call DestroyEffect(.gloomy)
call DestroyEffect(.attach)
call GroupRemoveUnit(.units, .subject)
call SetUnitTimeScale(.subject, 1.00)
if .oriVec > 0 then
call .oriVec.destroy()
set .oriVec = 0
endif
static if LIBRARY_RetroFade then
call retrofade.no(.casterOwner)
call retrofade.no(.victimOwner)
endif
endmethod
private method onStart takes nothing returns nothing
call DestroyEffect(AddSpecialEffectTarget(callbackFX1, .subject, "origin"))
call DestroyEffect(AddSpecialEffectTarget(callbackFX2, .subject, "origin"))
call SetUnitTimeScale(.subject, 4.00)
set .restoreBadHP = true
//set .restoreBadMP = true <- would throw a syntax error because I have RESTORE_MP set to <false>
static if LIBRARY_RetroFade then
if retrofade.CamCheck(.x, .y) then // Only plays the sound if a player's camera is near.
call StartSound(callback)
endif
endif
endmethod
private method onLoopForward takes nothing returns nothing
call DestroyEffect(AddSpecialEffectTarget(forwardLoopFX, .subject, "overhead"))
endmethod
private method onLoopReverse takes nothing returns nothing
call DestroyEffect(AddSpecialEffectTarget(reverseLoopFX1, .subject, "origin"))
call DestroyEffect(AddSpecialEffectTarget(reverseLoopFX2, .subject, "origin"))
endmethod
static vector regVec // This gets <vector.create> during map init.
static method operator bigVec takes nothing returns vector
return 8191
endmethod
private method onLoop takes nothing returns nothing
local real x2
local real y2
local retroproj pro
// Will not launch until the other missile has expired.
// Check the origin vector to make sure it's not a dud.
if .go and .oriVec!=0 then
/* Check if the distance is far enough */
set x2 = .x - .oriVec.x
set y2 = .y - .oriVec.y
if ((x2*x2 + y2*y2) > 0xF000) then
/* Yet another important check */
if UnitAlive(.subject) then
/* bigVec = Location(.x, .y) */
call bigVec.getTerrainPoint(.x, .y)
/* Add flyheight and offset to vector.Z */
set bigVec.z = bigVec.z + this.z + 90.0
if .to then
/* If flying toward the subject unit */
set .to = false
set pro = retroproj.create(AddSpecialEffect(retroproj.attackSkin, .oriVec.x, .oriVec.y), Atan2(.y - .oriVec.y, .x - .oriVec.x))
set pro.target = .subject
/* Control the dimensions and launch-properties of the projectile */
call BlzSetSpecialEffectScale(pro.toUnit, 0.65)
call pro.doLaunch(.oriVec, bigVec, 900.0, 0.10)
else
/* If gliding towards the origin point */
set .to = true
set pro = retroproj.create(AddSpecialEffect(retroproj.simpleSkin, .x, .y), Atan2(.oriVec.y - .y, .oriVec.x - .x))
set pro.source = .oriUnit
/* Control the dimensions and launch-properties of the projectile */
call BlzSetSpecialEffectScale(pro.toUnit, 0.85)
call pro.doLaunch(bigVec, .oriVec, 300.0, 0.10)
endif
set .go = false
set pro.primary = false
set pro.carryWarp = this
endif
endif
endif
endmethod
implement RETRO
private static effect tempFX = null
private method createFX takes string fx, real height returns effect
set tempFX = AddSpecialEffect(fx, .oriVec.x, .oriVec.y)
call BlzSetSpecialEffectYaw(tempFX, Atan2(.oriVec.y - .y, .oriVec.x - .x))
call BlzSetSpecialEffectScale(tempFX, 0.80)
call BlzSetSpecialEffectZ(tempFX, height + .z)
return tempFX
endmethod
static method Initiate takes retroproj h returns nothing
local real dist
local real x
local real y
local thistype this = allocate(h.target, true, true)
if this == 0 then /* The allocate method will return 0 for stationary units (buildings, wards) */
return
endif
set .caster = h.source
set .units = h.units
set .damage = h.damage
static if LIBRARY_RetroFade then
set .casterOwner = GetOwningPlayer(.caster)
set .victimOwner = GetOwningPlayer(.subject)
call retrofade.yes(.casterOwner, .x, .y)
call retrofade.yes(.victimOwner, .x, .y)
endif
if .oriVec == 0 then
set .oriVec = vector.createTerrainPoint(.x, .y)
set .oriVec.z = .oriVec.z + this.z + 60.00
endif
set .go = true
set .to = false
call GroupAddUnit(.units, .subject)
call .capture(h.Time)
call DestroyEffect(.createFX(caughtFX, 0.00))
set .gloomy = .createFX(gloomyFX, 40.00)
set .spooky = .createFX(spookyFX, 80.00)
set .attach = .createFX(attachFX, 80.00)
static if LIBRARY_Knockback then
set x = .x - h.carryVec.x
set y = .y - h.carryVec.y
set dist = KB_MAX_DISTANCE - (SquareRoot(x*x + y*y) / 2.0)
if dist <= KB_MIN_DISTANCE then
set dist = KB_MIN_DISTANCE
endif
set retroKB.create(.subject, Atan2(y, x), dist, KB_DURATION).attach = AddSpecialEffectTarget(retroKB.fx, h.target, "chest")
endif
endmethod
static method enumAOE takes nothing returns boolean
local retroproj p
local effect e
local unit u = GetFilterUnit()
if UnitAlive(u) and IsUnitEnemy(u, thistype(0).casterOwner) and thistype(GetUnitId(u)).subject == u then
call bigVec.getTerrainPoint(GetUnitX(u), GetUnitY(u))
set bigVec.z = bigVec.z + 60.0
set e = AddSpecialEffect(retroproj.launchSkin, regVec.x, regVec.y)
set p = retroproj.create(e, Atan2(bigVec.y - regVec.y, bigVec.x - regVec.x))
set e = null
set p.target = u
set p.source = thistype(0).caster
set p.units = thistype(0).units
set p.damage = thistype(0).damage
set p.Time = retroproj(0).Time
set p.carryVec = vector.create(regVec.x, regVec.y, regVec.z)
call p.doLaunch(regVec, bigVec, 900.0, 0.10)
endif
set u = null
return false
endmethod
static method forceCollapse takes nothing returns boolean
call thistype(GetUnitId(GetEnumUnit())).collapse()
call DestroyEffect(AddSpecialEffectTarget(recallFXTarg, GetEnumUnit(), "origin"))
return false
endmethod
static method onCast takes nothing returns boolean
local real factor
local integer id = GetSpellAbilityId()
if (id == SPELL_ID) then
set thistype(0).caster = GetTriggerUnit()
set thistype(0).casterOwner = GetTriggerPlayer()
set thistype(0).units = casterGroup[GetUnitId(thistype(0).caster)]
call regVec.getTerrainPoint(GetSpellTargetX(), GetSpellTargetY())
set regVec.z = regVec.z + 60.00
call DestroyEffect(AddSpecialEffect(epicenterFX, regVec.x, regVec.y))
set factor = GetUnitAbilityLevel(thistype(0).caster, SPELL_ID) - 1.00
set thistype(0).damage = BASE_DAMAGE + LVL_INC_DAMAGE * factor
set retroproj(0).Time = BASE_DURATION + LVL_INC_DURATION * factor
static if LIBRARY_GroupUtils then
if GetRandomInt(1, 10) == 1 then
call GroupRefresh(thistype(0).units)
endif
call GroupEnumUnitsInArea(ENUM_GROUP, regVec.x, regVec.y, BASE_AOE + LVL_INC_AOE * factor, Filter(function thistype.enumAOE))
else
call GroupEnumUnitsInRange(bj_lastCreatedGroup, regVec.x, regVec.y, BASE_AOE + LVL_INC_AOE * factor, Filter(function thistype.enumAOE))
endif
elseif (id == EXTRA_ID) then
call ForGroup(casterGroup[GetUnitId(GetTriggerUnit())], function thistype.forceCollapse)
call DestroyEffect(AddSpecialEffectTarget(recallFXCast, GetTriggerUnit(), "origin"))
endif
return false
endmethod
static method learnSkill takes nothing returns boolean
if GetLearnedSkill() == SPELL_ID and GetLearnedSkillLevel() == 1 then
call UnitAddAbility(GetTriggerUnit(), EXTRA_ID)
static if LIBRARY_Recycle then
set casterGroup[GetUnitId(GetTriggerUnit())] = Group.get()
elseif LIBRARY_GroupUtils then
set casterGroup[GetUnitId(GetTriggerUnit())] = NewGroup()
else
set casterGroup[GetUnitId(GetTriggerUnit())] = CreateGroup()
endif
endif
return false
endmethod
static method cleanup takes unit caster returns nothing
local integer id
static if LIBRARY_AIDS then
set id = AIDS_GetDecayingIndex()
else
set id = GetUnitId(caster)
endif
if casterGroup[id] != null then
static if LIBRARY_Recycle then
call Group.release(casterGroup[id])
elseif LIBRARY_GroupUtils then
call ReleaseGroup(casterGroup[id])
else
call GroupClear(casterGroup[id])
call DestroyGroup(casterGroup[id])
endif
set casterGroup[id] = null
endif
endmethod
static if LIBRARY_AIDS then
private static method AIDS_OnDeallocate takes nothing returns boolean
call cleanup(null)
return false
endmethod
endif
private static method onInit takes nothing returns nothing
local trigger t
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_HERO_SKILL)
call TriggerAddCondition(t, Condition(function thistype.learnSkill))
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.onCast))
set regVec = vector.create(0.0, 0.0, 0.0)
call PreloadStrings()
static if LIBRARY_AIDS then
call AIDS_RegisterOnDeallocate(Filter(function thistype.AIDS_OnDeallocate))
else
call OnUnitDeindexed(thistype.cleanup)
endif
endmethod
endstruct
endlibrary
library_once xebasic
/* AutoIndex and GroupUtils will interpret this to set some default values */
endlibrary
library RetroExample requires Retro
private struct example extends array
// Retro structs must <extend array>
effect attach // This effect gets attached to the unit for the duration of the spell.
method onFinish takes nothing returns nothing
call DestroyEffect(.attach)
endmethod // <onFinish> is the <onDestroy> method. You don't need to call <deallocate> from it.
/*
* <onLoopReverse> or <onLoopForward> methods are called only once per cycle of RETRO_TIMEOUT,
* so they help to moderate high numbers of special effects to keep the frames-per-second low.
*/
method onLoopReverse takes nothing returns nothing
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\StrongDrink\\BrewmasterMissile.mdl", .x, .y))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Other\\Transmute\\GoldBottleMissile.mdl", .x, .y))
endmethod
implement RETRO // The module is below <onFinish> and <onLoopReverse> while above <allocate>
static method Selection takes nothing returns boolean
local thistype this
// <allocate> and <deallocate> are provided by the RETRO module.
set this = allocate(GetFilterUnit(), false, true)
// <allocate> will return <0> if a unit is invalid; I advise checking that before any continuation;
if this > 0 and GetHeroProperName(.subject) != "Master of Time" then
/*
* <initiate> immediately launches a unit back through time for the
* specified duration. This won't do anything for units created in
* the same instant; they haven't had time to record some movement.
*/
call .initiate(2.00)
// The unit you're manipulating is referenced as <.subject>
set .attach = AddSpecialEffectTarget("Abilities\\Spells\\Other\\Drain\\ManaDrainTarget.mdl", .subject, "overhead")
// <.restoreGoodHP> will revive the unit if its former HP was greater.
set .restoreGoodHP = true
/*
* <.restoreGoodMP> will do the same, but for MP. Un-commenting the line will provide a syntax
* error, as I have the Retro constant boolean RECORD_MP set to <false>, preventing the variable
* from even being compiled.
*/
//set .restoreGoodMP = true
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Other\\Monsoon\\MonsoonBoltTarget.mdl", .subject, "origin"))
endif
return false
endmethod
static method onInit takes nothing returns nothing
local integer i = 0
local trigger t = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SELECTED, Filter(function thistype.Selection))
exitwhen i == 11
set i = i + 1
endloop
endmethod // A normal initialization method.
/*
* Note the <onLoop>, <onLoopForward> and <onStart> methods are nowhere in sight. They are optional,
* and so are <onLoopReverse> and <onFinish>
*
* Final Notes
* ¯¯¯¯¯¯¯¯¯¯¯
* ~ You should only implement an event-method if you have a use for it.
* ~ You don't have to call <destroy> on a RETRO-implemented struct unless you wish to call your onFinish method.
*/
endstruct
endlibrary
//TESH.scrollpos=646
//TESH.alwaysfold=0
library AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoIndex is a very simple script to utilize. Just call GetUnitId(unit)
// to get get the unique value assigned to a particular unit. The GetUnitId
// function is extremely fast because it inlines directly to a GetUnitUserData
// call. AutoIndex automatically assigns an ID to each unit as it enters the
// map, and instantly frees that ID as the unit leaves the map. Detection of
// leaving units is accomplished in constant time without a periodic scan.
//
// AutoIndex uses UnitUserData by default. If something else in your map
// would conflict with that, you can set the UseUnitUserData configuration
// constant to false, and a hashtable will be used instead. Note that hash-
// tables are about 60% slower.
//
// If you turn on debug mode, AutoIndex will be able to display several
// helpful error messages. The following issues will be detected:
// -Passing a removed or decayed unit to GetUnitId
// -Code outside of AutoIndex has overwritten a unit's UserData value.
// -GetUnitId was used on a filtered unit (a unit you don't want indexed).
//
// AutoIndex provides events upon indexing or deindexing units. This
// effectively allows you to notice when units enter or leave the game. Also
// included are the AutoData, AutoCreate, and AutoDestroy modules, which allow
// you to fully utilize AutoIndex's enter/leave detection capabilities in
// conjunction with your structs.
//
//===========================================================================
// How to install AutoIndex:
//===========================
//
// 1.) Copy and paste this script into your map.
// 2.) Save it to allow the ObjectMerger macro to generate the "Leave Detect"
// ability for you. Close and re-open the map. After that, disable the macro
// to prevent the delay while saving.
//
//===========================================================================
// How to use AutoIndex:
//=======================
//
// So you can get a unique integer for each unit, but how do you use that to
// attach data to a unit? GetUnitId will always return a number in the range of
// 1-8190. This means it can be used as an array index, as demonstrated below:
/*
globals
integer array IntegerData
real array RealData
SomeStruct array SomeStructData
englobals
function Example takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local integer id = GetUnitId(u)
//You now have a unique index for the unit, so you can
//attach or retrieve data about the unit using arrays.
set IntegerData[id] = 5
set RealData[id] = 25.0
set SomeStructData[id] = SomeStruct.create()
//If you have access to the same unit in another function, you can
//retrieve the data by using GetUnitId() and reading the arrays.
endfunction
*/
// The UnitFilter function in the configuration section is provided so that
// you can make AutoIndex completely ignore certain unit-types. Ignored units
// won't be indexed or fire indexed/deindexed events. You may want to filter out
// dummy casters or system-private units, especially ones that use UnitUserData
// internally. xe dummy units are automatically filtered.
//
//===========================================================================
// How to use OnUnitIndexed / OnUnitDeindexed:
//=============================================
//
// AutoIndex will fire the OnUnitIndexed event when a unit enters the map,
// and the OnUnitDeindexed event when a unit leaves the map. Functions used
// as events must take a unit and return nothing. An example is given below:
/*
function UnitEntersMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" entered the map.")
endfunction //Using GetUnitId() during Indexed events works fine...
function UnitLeavesMap takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" with ID "+I2S(GetUnitId(u))+" left the map.")
endfunction //So does using GetUnitId() during Deindexed events.
function Init takes nothing returns nothing
call OnUnitIndexed(UnitEntersMap)
call OnUnitDeindexed(UnitLeavesMap)
endfunction
*/
// If you call OnUnitIndexed during map initialization, every existing
// unit will be considered as entering the map. This saves you from the need
// to manually enumerate preplaced units (or units created by initialization
// code that ran before OnUnitIndexed was called).
//
// OnUnitDeindexed runs while a unit still exists, which means you can
// still do things such as destroy special effects attached to the unit.
// The unit will cease to exist immediately after the event is over.
//
//===========================================================================
// AutoIndex API:
//================
//
// GetUnitId(unit) -> integer
// This function returns a unique ID in the range of 1-8190 for the
// specified unit. Returns 0 if a null unit was passed. This function
// inlines directly to GetUnitUserData or LoadInteger if debug mode
// is disabled. If debug mode is enabled, this function will print
// an error message when passed a decayed or filtered unit.
//
// IsUnitIndexed(unit) -> boolean
// This function returns a boolean indicating whether the specified
// unit has been indexed. The only time this will return false is
// for units you have filtered using the UnitFilter function, or
// for xe dummy units. You can use this function to easily detect
// dummy units and avoid performing certain actions on them.
//
// OnUnitIndexed(IndexFunc)
// This function accepts an IndexFunc, which must take a unit and
// return nothing. The IndexFunc will be fired instantly whenever
// a unit enters the map. You may use GetUnitId on the unit. When
// you call this function during map initialization, every existing
// unit will be considered as entering the map.
//
// OnUnitDeindexed(IndexFunc)
// Same as above, but runs whenever a unit is leaving the map. When
// this event runs, the unit still exists, but it will cease to exist
// as soon as the event ends. You may use GetUnitId on the unit.
//
//===========================================================================
// How to use AutoData:
//======================
//
// The AutoData module allows you to associate one or more instances
// of the implementing struct with units, as well as iterate through all
// of the instances associated with each unit.
//
// This association is accomplished through the "me" instance member,
// which the module will place in the implementing struct. Whichever unit
// you assign to "me" becomes the owner of that instance. You may change
// ownership by reassigning "me" to another unit at any time, or you may
// make the instance unowned by assigning "me" to null.
//
// AutoData implements the static method operator [] in your struct
// to allow you to access instances from their owning units. For example,
// you may type: local StructName s = StructName[u]. If u has been set
// to own an instance of StructName, s will be set to that instance.
//
// So, what happens if you assign the same owning unit to multiple
// instances? You may use 2D array syntax to access instances assigned to
// the same unit: local StructName s = StructName[u][n], where u is the
// owning unit, and n is the index beginning with 0 for each unit. You
// can access the size of a unit's instance list (i.e. the number of
// instances belonging to the unit) by using the .size instance member.
/*
struct Example
implement AutoData
static method create takes unit u returns Example
local Example this = allocate()
set me = u //Assigning the "me" member from AutoData.
return this
endmethod
endstruct
function Test takes nothing returns nothing
local unit u = CreateUnit(Player(0), 'hpea', 0., 0., 0.)
local Example e1 = Example.create(u)
local Example e2 = Example.create(u)
local Example e3 = Example.create(u)
local Example e
call BJDebugMsg(I2S(Example[u].size)) //Prints 3 because u owns e1, e2, and e3.
set e = Example[u][GetRandomInt(0, Example[u].size - 1)] //Random instance belonging to u.
set e = Example[u] //This is the fastest way to iterate the instances belonging
loop //to a specific unit, starting with the first instance.
exitwhen e == 0 //e will be assigned to 0 when no instances remain.
call BJDebugMsg(I2S(e)) //Prints the values of e1, e2, e3.
set e = e[e.index + 1] //"e.index" refers to the e's position in u's instance list.
endloop //Thus, index + 1 is next, and index - 1 is previous.
endfunction //This trick allows you to avoid a local counter.
*/
// AutoData restrictions:
// -You may not implement AutoData in any struct which has already
// declared static or non-static method operator [].
// -AutoData will conflict with anything named "me", "size", or
// "index" in the implementing struct.
// -AutoData may not be implemented in structs that extend array.
// -You may not declare your own destroy method. (This restriction
// can be dropped as soon as JassHelper supports module onDestroy).
//
// AutoData information:
// -You do not need to null the "me" member when destroying an
// instance. That is done for you automatically during destroy().
// (But if you use deallocate(), you must null "me" manually.)
// -StructName[u] and StructName[u][0] refer to the same instance,
// which is the first instance that was associated with unit u.
// -StructName[u][StructName[u].size - 1] refers to the instance that
// was most recently associated with unit u.
// -Instances keep their relative order in the list when one is removed.
//
//===========================================================================
// How to use AutoCreate:
//=======================
//
// The AutoCreate module allows you to automatically create instances
// of the implementing struct for units as they enter the game. AutoCreate
// automatically implements AutoData into your struct. Any time an instance
// is automatically created for a unit, that instance's "me" member will be
// assigned to the entering unit.
//
// AutoCreate restrictions:
// -All of the same restrictions as AutoData.
// -If your struct's allocate() method takes parameters (i.e. the parent
// type's create method takes parameters), you must declare a create
// method and pass those extra parameters to allocate yourself.
//
// AutoCreate information:
// -You may optionally declare the createFilter method, which specifies
// which units should recieve an instance as they enter the game. If
// you do not declare it, all entering units will recieve an instance.
// -You may optionally declare the onCreate method, which will run when
// AutoCreate automatically creates an instance. (This is just a stand-
// in until JassHelper supports the onCreate method.)
// -You may declare your own create method, but it must take a single
// unit parameter (the entering unit) if you do so.
/*
struct Example
private static method createFilter takes unit u returns boolean
return GetUnitTypeId(u) == 'hfoo' //Created only for Footmen.
endmethod
private method onCreate takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" entered the game!")
endmethod
implement AutoCreate
endstruct
*/
//===========================================================================
// How to use AutoDestroy:
//=========================
//
// The AutoDestroy module allows you to automatically destroy instances
// of the implementing struct when their "me" unit leaves the game. AutoDestroy
// automatically implements AutoData into your struct. You must assign a unit
// to the "me" member of an instance for this module to have any effect.
//
// AutoDestroy restrictions:
// -All of the same restrictions as AutoData.
//
// AutoDestroy information:
// -If you also implement AutoCreate in the same struct, remember that it
// assigns the "me" unit automatically. That means you can have fully
// automatic creation and destruction.
/*
struct Example
static method create takes unit u returns Example
local Example this = allocate()
set me = u //You should assign a unit to "me",
return this //otherwise AutoDestroy does nothing.
endmethod //Not necessary if using AutoCreate.
private method onDestroy takes nothing returns nothing
call BJDebugMsg(GetUnitName(me)+" left the game!")
endmethod
implement AutoDestroy
endstruct
*/
//===========================================================================
// Configuration:
//================
// ! external ObjectMerger w3a Adef lvdt anam "Leave Detect" aart "" arac 0
//Save your map with this Object Merger call enabled, then close and reopen your
//map. Disable it by removing the exclamation to remove the delay while saving.
globals
private constant integer LeaveDetectAbilityID = 'lvdt'
//This rawcode must match the parameter after "Adef" in the
//ObjectMerger macro above. You may change both if you want.
private constant boolean UseUnitUserData = true
//If this is set to true, UnitUserData will be used. You should only set
//this to false if something else in your map already uses UnitUserData.
//A hashtable will be used instead, but it is about 60% slower.
private constant boolean SafeMode = true
//This is set to true by default so that GetUnitId() will ALWAYS work.
//If if this is set to false, GetUnitId() may fail to work in a very
//rare circumstance: creating a unit that has a default-on autocast
//ability, and using GetUnitId() on that unit as it enters the game,
//within a trigger that detects any order. Set this to false for a
//performance boost only if you think you can avoid this issue.
private constant boolean AutoDataFastMode = true
//If this is set to true, AutoData will utilize one hashtable per time
//it is implemented. If this is set to false, all AutoDatas will share
//a single hashtable, but iterating through the instances belonging to
//a unit will become about 12.5% slower. Your map will break if you
//use more than 255 hashtables simultaneously. Only set this to false
//if you suspect you will run out of hashtable instances.
endglobals
private function UnitFilter takes unit u returns boolean
return GetUnitAbilityLevel(u, 'Aloc') == 0
endfunction
//Make this function return false for any unit-types you want to ignore.
//Ignored units won't be indexed or fire OnUnitIndexed/OnUnitDeindexed
//events. The unit parameter "u" to refers to the unit being filtered.
//Do not filter out xe dummy units; they are automatically filtered.
//===========================================================================
// AutoData / AutoCreate / AutoDestroy modules:
//==============================================
function interface AutoCreator takes unit u returns nothing
function interface AutoDestroyer takes unit u returns nothing
globals
hashtable AutoData = null //If AutoDataFastMode is disabled, this hashtable will be
endglobals //initialized and shared between all AutoData implementations.
module AutoData
private static hashtable ht
private static thistype array data
private static integer array listsize
private static key typeid //Good thing keys exist to identify each implementing struct.
private unit meunit
private integer id
readonly integer index //The user can avoid using a local counter because this is accessable.
static method operator [] takes unit u returns thistype
return data[GetUnitId(u)]
endmethod //This is as fast as retrieving an instance from a unit gets.
method operator [] takes integer index returns thistype
static if AutoDataFastMode then //If fast mode is enabled...
return LoadInteger(ht, id, index)
else //Each instance has its own hashtable to associate unit and index.
return LoadInteger(AutoData, id, index*8190+typeid)
endif //Otherwise, simulate a 3D array associating unit, struct-type ID, and index.
endmethod //Somehow, this version is 12.5% slower just because of the math.
private method setIndex takes integer index, thistype data returns nothing
static if AutoDataFastMode then //Too bad you can't have a module-private operator []=.
call SaveInteger(ht, id, index, data)
else
call SaveInteger(AutoData, id, index*8190+typeid, data)
endif
endmethod
private method remove takes nothing returns nothing
if meunit == null then //If the struct doesn't have an owner...
return //Nothing needs to be done.
endif
loop
exitwhen index == listsize[id] //The last value gets overwritten by 0.
call setIndex(index, this[index + 1]) //Shift each element down by one.
set this[index].index = index //Update the shifted instance's index.
set index = index + 1
endloop
set listsize[id] = listsize[id] - 1
set data[id] = this[0] //Ensure thistype[u] returns the same value as thistype[u][0].
set meunit = null
endmethod
private method add takes unit u returns nothing
if meunit != null then //If the struct has an owner...
call remove() //remove it first.
endif
set meunit = u
set id = GetUnitId(u) //Cache GetUnitId for slight performance boost.
if data[id] == 0 then //If this is the first instance for this unit...
set data[id] = this //Update the value that thistype[u] returns.
endif
set index = listsize[id] //Remember the index for removal.
call setIndex(index, this) //Add to the array.
set listsize[id] = index + 1
endmethod
method operator me takes nothing returns unit
return meunit
endmethod
method operator me= takes unit u returns nothing
if u != null then //If assigning "me" a non-null value...
call add(u) //Add this instance to that unit's array.
else //If assigning "me" a null value...
call remove() //Remove this instance from that unit's array.
endif
endmethod
method operator size takes nothing returns integer
return listsize[id]
endmethod
method destroy takes nothing returns nothing
call deallocate()
call remove() //This makes removal automatic when an instance is destroyed.
endmethod
private static method onInit takes nothing returns nothing
static if AutoDataFastMode then //If fast mode is enabled...
set ht = InitHashtable() //Initialize one hashtable per instance.
else //If fast mode is disabled...
if AutoData == null then //If the hashtable hasn't been initialized yet...
set AutoData = InitHashtable() //Initialize the shared hashtable.
endif
endif
endmethod
endmodule
module AutoCreate
implement AutoData //AutoData is necessary for AutoCreate.
private static method creator takes unit u returns nothing
local thistype this
local boolean b = true //Assume that the instance will be created.
static if thistype.createFilter.exists then //If createFilter exists...
set b = createFilter(u) //evaluate it and update b.
endif
if b then //If the instance should be created...
static if thistype.create.exists then //If the create method exists...
set this = create(u) //Create the instance, passing the entering unit.
else //If the create method doesn't exist...
set this = allocate() //Just allocate the instance.
endif
set me = u //Assign the instance's owner as the entering unit.
static if thistype.onCreate.exists then //If onCreate exists...
call onCreate() //Call it, because JassHelper should do this anyway.
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoCreate(thistype.creator)
endmethod //During module initialization, pass the creator function to AutoIndex.
endmodule
module AutoDestroy
implement AutoData //AutoData is necessary for AutoDestroy.
static method destroyer takes unit u returns nothing
loop
exitwhen thistype[u] == 0
call thistype[u].destroy()
endloop
endmethod //Destroy each instance owned by the unit until none are left.
private static method onInit takes nothing returns nothing
call AutoIndex.addAutoDestroy(thistype.destroyer)
endmethod //During module initialization, pass the destroyer function to AutoIndex.
endmodule
//===========================================================================
// AutoIndex struct:
//===================
function interface IndexFunc takes unit u returns nothing
hook RemoveUnit AutoIndex.hook_RemoveUnit
hook ReplaceUnitBJ AutoIndex.hook_ReplaceUnitBJ
debug hook SetUnitUserData AutoIndex.hook_SetUnitUserData
private keyword getIndex
private keyword getIndexDebug
private keyword isUnitIndexed
private keyword onUnitIndexed
private keyword onUnitDeindexed
struct AutoIndex
private static trigger enter = CreateTrigger()
private static trigger order = CreateTrigger()
private static trigger creepdeath = CreateTrigger()
private static group preplaced = CreateGroup()
private static timer allowdecay = CreateTimer()
private static hashtable ht
private static boolean array dead
private static boolean array summoned
private static boolean array animated
private static boolean array nodecay
private static boolean array removing
private static IndexFunc array indexfuncs
private static integer indexfuncs_n = -1
private static IndexFunc array deindexfuncs
private static integer deindexfuncs_n = -1
private static IndexFunc indexfunc
private static AutoCreator array creators
private static integer creators_n = -1
private static AutoDestroyer array destroyers
private static integer destroyers_n = -1
private static unit array allowdecayunit
private static integer allowdecay_n = -1
private static boolean duringinit = true
private static boolean array altered
private static unit array idunit
//===========================================================================
static method getIndex takes unit u returns integer
static if UseUnitUserData then
return GetUnitUserData(u)
else
return LoadInteger(ht, 0, GetHandleId(u))
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method getIndexDebug takes unit u returns integer
if u == null then
return 0
elseif GetUnitTypeId(u) == 0 then
call BJDebugMsg("AutoIndex error: Removed or decayed unit passed to GetUnitId.")
elseif idunit[getIndex(u)] != u and GetIssuedOrderId() != 852056 then
call BJDebugMsg("AutoIndex error: "+GetUnitName(u)+" is a filtered unit.")
endif
return getIndex(u)
endmethod //If debug mode is enabled, use the getIndex method that shows errors.
private static method setIndex takes unit u, integer index returns nothing
static if UseUnitUserData then
call SetUnitUserData(u, index)
else
call SaveInteger(ht, 0, GetHandleId(u), index)
endif
endmethod //Resolves to an inlinable one-liner after the static if.
static method isUnitIndexed takes unit u returns boolean
return u != null and idunit[getIndex(u)] == u
endmethod
static method isUnitAnimateDead takes unit u returns boolean
return animated[getIndex(u)]
endmethod //Don't use this; use IsUnitAnimateDead from AutoEvents instead.
//===========================================================================
private static method onUnitIndexed_sub takes nothing returns nothing
call indexfunc.evaluate(GetEnumUnit())
endmethod
static method onUnitIndexed takes IndexFunc func returns nothing
set indexfuncs_n = indexfuncs_n + 1
set indexfuncs[indexfuncs_n] = func
if duringinit then //During initialization, evaluate the indexfunc for every preplaced unit.
set indexfunc = func
call ForGroup(preplaced, function AutoIndex.onUnitIndexed_sub)
endif
endmethod
static method onUnitDeindexed takes IndexFunc func returns nothing
set deindexfuncs_n = deindexfuncs_n + 1
set deindexfuncs[deindexfuncs_n] = func
endmethod
static method addAutoCreate takes AutoCreator func returns nothing
set creators_n = creators_n + 1
set creators[creators_n] = func
endmethod
static method addAutoDestroy takes AutoDestroyer func returns nothing
set destroyers_n = destroyers_n + 1
set destroyers[destroyers_n] = func
endmethod
//===========================================================================
private static method hook_RemoveUnit takes unit whichUnit returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever RemoveUnit is called and sets a flag.
private static method hook_ReplaceUnitBJ takes unit whichUnit, integer newUnitId, integer unitStateMethod returns nothing
set removing[getIndex(whichUnit)] = true
endmethod //Intercepts whenever ReplaceUnitBJ is called and sets a flag.
private static method hook_SetUnitUserData takes unit whichUnit, integer data returns nothing
static if UseUnitUserData then
if idunit[getIndex(whichUnit)] == whichUnit then
if getIndex(whichUnit) == data then
call BJDebugMsg("AutoIndex error: Code outside AutoIndex attempted to alter "+GetUnitName(whichUnit)+"'s index.")
else
call BJDebugMsg("AutoIndex error: Code outside AutoIndex altered "+GetUnitName(whichUnit)+"'s index.")
if idunit[data] != null then
call BJDebugMsg("AutoIndex error: "+GetUnitName(whichUnit)+" and "+GetUnitName(idunit[data])+" now have the same index.")
endif
set altered[data] = true
endif
endif
endif //In debug mode, intercepts whenever SetUnitUserData is used on an indexed unit.
endmethod //Displays an error message if outside code tries to alter a unit's index.
//===========================================================================
private static method allowDecay takes nothing returns nothing
local integer n = allowdecay_n
loop
exitwhen n < 0
set nodecay[getIndex(allowdecayunit[n])] = false
set allowdecayunit[n] = null
set n = n - 1
endloop
set allowdecay_n = -1
endmethod //Iterate through all the units in the stack and allow them to decay again.
private static method detectStatus takes nothing returns boolean
local unit u = GetTriggerUnit()
local integer index = getIndex(u)
local integer n
if idunit[index] == u then //Ignore non-indexed units.
if not IsUnitType(u, UNIT_TYPE_DEAD) then
if dead[index] then //The unit was dead, but now it's alive.
set dead[index] = false //The unit has been resurrected.
//! runtextmacro optional RunAutoEvent("Resurrect")
//If AutoEvents is in the map, run the resurrection events.
if IsUnitType(u, UNIT_TYPE_SUMMONED) and not summoned[index] then
set summoned[index] = true //If the unit gained the summoned flag,
set animated[index] = true //it's been raised with Animate Dead.
//! runtextmacro optional RunAutoEvent("AnimateDead")
//If AutoEvents is in the map, run the Animate Dead events.
endif
endif
else
if not removing[index] and not dead[index] and not animated[index] then
set dead[index] = true //The unit was alive, but now it's dead.
set nodecay[index] = true //A dead unit can't decay for at least 0. seconds.
set allowdecay_n = allowdecay_n + 1 //Add the unit to a stack. After the timer
set allowdecayunit[allowdecay_n] = u //expires, allow the unit to decay again.
call TimerStart(allowdecay, 0., false, function AutoIndex.allowDecay)
//! runtextmacro optional RunAutoEvent("Death")
//If AutoEvents is in the map, run the Death events.
elseif removing[index] or (dead[index] and not nodecay[index]) or (not dead[index] and animated[index]) then
//If .nodecay was false and the unit is dead and was previously dead, the unit decayed.
//If .animated was true and the unit is dead, the unit died and exploded.
//If .removing was true, the unit is being removed or replaced.
set n = deindexfuncs_n
loop //Run the OnUnitDeindexed events.
exitwhen n < 0
call deindexfuncs[n].evaluate(u)
set n = n - 1
endloop
set n = destroyers_n
loop //Destroy AutoDestroy structs for the leaving unit.
exitwhen n < 0
call destroyers[n].evaluate(u)
set n = n - 1
endloop
call AutoIndex(index).destroy() //Free the index by destroying the AutoIndex struct.
set idunit[index] = null //Null this unit reference to prevent a leak.
endif
endif
endif
set u = null
return false
endmethod
//===========================================================================
private static method unitEntersMap takes unit u returns nothing
local integer index
local integer n = 0
if getIndex(u) != 0 then
return //Don't index a unit that already has an ID.
endif
static if LIBRARY_xebasic then
if GetUnitTypeId(u) == XE_DUMMY_UNITID then
return //Don't index xe dummy units.
endif
endif
if not UnitFilter(u) then
return //Don't index units that fail the unit filter.
endif
set index = create()
call setIndex(u, index) //Assign an index to the entering unit.
call UnitAddAbility(u, LeaveDetectAbilityID) //Add the leave detect ability to the entering unit.
call UnitMakeAbilityPermanent(u, true, LeaveDetectAbilityID) //Prevent it from disappearing on morph.
set dead[index] = IsUnitType(u, UNIT_TYPE_DEAD) //Reset all of the flags for the entering unit.
set summoned[index] = IsUnitType(u, UNIT_TYPE_SUMMONED) //Each of these flags are necessary to detect
set animated[index] = false //when a unit leaves the map.
set nodecay[index] = false
set removing[index] = false
debug set altered[index] = false //In debug mode, this flag tracks wheter a unit's index was altered.
set idunit[index] = u //Attach the unit that is supposed to have this index to the index.
if duringinit then //If a unit enters the map during initialization...
call GroupAddUnit(preplaced, u) //Add the unit to the preplaced units group. This ensures that
endif //all units are noticed by OnUnitIndexed during initialization.
loop //Create AutoCreate structs for the entering unit.
exitwhen n > creators_n
call creators[n].evaluate(u)
set n = n + 1
endloop
set n = 0
loop //Run the OnUnitIndexed events.
exitwhen n > indexfuncs_n
call indexfuncs[n].evaluate(u)
set n = n + 1
endloop
endmethod
private static method onIssuedOrder takes nothing returns boolean
static if SafeMode then //If SafeMode is enabled, perform this extra check.
if getIndex(GetTriggerUnit()) == 0 then //If the unit doesn't already have
call unitEntersMap(GetTriggerUnit()) //an index, then assign it one.
endif
endif
return GetIssuedOrderId() == 852056 //If the order is Undefend, allow detectStatus to run.
endmethod
private static method initEnteringUnit takes nothing returns boolean
call unitEntersMap(GetFilterUnit())
return false
endmethod
//===========================================================================
private static method afterInit takes nothing returns nothing
set duringinit = false //Initialization is over; set a flag.
call DestroyTimer(GetExpiredTimer()) //Destroy the timer.
call GroupClear(preplaced) //The preplaced units group is
call DestroyGroup(preplaced) //no longer needed, so clean it.
set preplaced = null
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local group g = CreateGroup()
local integer i = bj_MAX_PLAYER_SLOTS
static if not UseUnitUserData then
set ht = InitHashtable() //Only create a hashtable if it will be used.
endif
loop
exitwhen i < 0
call SetPlayerAbilityAvailable(Player(i), LeaveDetectAbilityID, false)
//Make the LeaveDetect ability unavailable so that it doesn't show up on the command card of every unit.
call TriggerRegisterPlayerUnitEvent(order, Player(i), EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
//Register the "EVENT_PLAYER_UNIT_ISSUED_ORDER" event for each player.
call GroupEnumUnitsOfPlayer(g, Player(i), function AutoIndex.initEnteringUnit)
//Enum every non-filtered unit on the map during initialization and assign it a unique
//index. By using GroupEnumUnitsOfPlayer, even units with Locust can be detected.
set i = i - 1
endloop
call TriggerAddCondition(order, And(function AutoIndex.onIssuedOrder, function AutoIndex.detectStatus))
//The detectStatus method will fire every time a non-filtered unit recieves an undefend order.
//And() is used here to avoid using a trigger action, which starts a new thread and is slower.
call TriggerRegisterPlayerUnitEvent(creepdeath, Player(12), EVENT_PLAYER_UNIT_DEATH, null)
call TriggerAddCondition(creepdeath, function AutoIndex.detectStatus)
//The detectStatus method must also fire when a neutral hostile creep dies, in case it was
//sleeping. Sleeping creeps don't fire undefend orders on non-damaging deaths.
call RegionAddRect(maparea, bounds) //GetWorldBounds() includes the shaded boundry areas.
call TriggerRegisterEnterRegion(enter, maparea, function AutoIndex.initEnteringUnit)
//The filter function of an EnterRegion trigger runs instantly when a unit is created.
call TimerStart(CreateTimer(), 0., false, function AutoIndex.afterInit)
//After any time elapses, perform after-initialization actions.
call GroupClear(g)
call DestroyGroup(g)
call RemoveRect(bounds)
set g = null
set bounds = null
endmethod
endstruct
//===========================================================================
// User functions:
//=================
function GetUnitId takes unit u returns integer
static if DEBUG_MODE then //If debug mode is enabled...
return AutoIndex.getIndexDebug(u) //call the debug version of GetUnitId.
else //If debug mode is disabled...
return AutoIndex.getIndex(u) //call the normal, inlinable version.
endif
endfunction
function IsUnitIndexed takes unit u returns boolean
return AutoIndex.isUnitIndexed(u)
endfunction
function OnUnitIndexed takes IndexFunc func returns nothing
call AutoIndex.onUnitIndexed(func)
endfunction
function OnUnitDeindexed takes IndexFunc func returns nothing
call AutoIndex.onUnitDeindexed(func)
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library AutoEvents requires AutoIndex
//===========================================================================
// Information:
//==============
//
// AutoEvents is an add-on library for AutoIndex. It gives you events that
// detect the following things: when units resurrect, when units are raised with
// Animate Dead, when units begin reincarnating, when units finish reincarnating,
// when transports load units, and when transports unload units. It also provides
// other useful functions: you can check if a unit is currently raised with Ani-
// mate Dead, get the transport carrying a unit, get the number of a units in a
// transport, get the passenger in a specific slot of a transport, and enumerate
// through all of the units in a transport.
//
//===========================================================================
// How to use AutoEvents:
//========================
//
// You can use the events in this library to run specific functions when
// an event occurs. Unit-related events require a function matching the Stat-
// usEvent function interface, and transport-related events require a function
// matching the TransportEvent function interface:
//
// function interface AutoEvent takes unit u returns nothing
// function interface TransportEvent takes unit transport, unit passenger returns nothing
//
// The following examples use the AutoEvent function interface:
/*
function UnitDies takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" has died.")
endfunction
function UnitResurrects takes unit u returns nothing
call BJDebugMsg(GetUnitName(u)+" has been resurrected.")
endfunction
function Init takes nothing returns nothing
call OnUnitDeath(UnitDies)
call OnUnitResurrect(UnitResurrects)
endfunction
*/
// And the following examples use the TransportEvents function interface:
/*
function UnitLoads takes unit transport, unit passenger returns nothing
call BJDebugMsg(GetUnitName(transport)+" loaded "+GetUnitName(passenger))
endfunction
function UnitUnloads takes unit transport, unit passenger returns nothing
call BJDebugMsg(GetUnitName(transport)+" unloaded "+GetUnitName(passenger))
endfunction
function Init takes nothing returns nothing
call OnUnitLoad(UnitLoads)
call OnUnitUnload(UnitUnloads)
endfunction
*/
// Here is an example of using ForPassengers to enumerate each unit in
// a transport and heal them for 100 life:
/*
function HealPassenger takes unit transport, unit passenger returns nothing
call SetWidgetLife(passenger, GetWidgetLife(passenger) + 100.)
endfunction
function HealAllPassengers takes unit transport returns nothing
call ForPassengers(transport, HealPassenger)
endfunction
*/
// GetPassengerBySlot provides an alternative way to enumerate the
// units within a transport. (The following example would heal a unit
// that occupies multiple slots in the transport only one time, since
// GetPassengerBySlot assumes that each unit occupies only one slot.)
/*
function HealAllPassengers takes unit transport returns nothing
local integer slot = 1 //Start at slot 1.
local unit passenger
loop
set passenger = GetPassengerBySlot(transport, slot)
exitwhen passenger == null
call SetWidgetLife(passenger, GetWidgetLife(passenger) + 100.)
set slot = slot + 1
endloop
endfunction
*/
//===========================================================================
// AutoEvents API:
//=================
//
// OnUnitDeath(AutoEvent)
// This event runs when any unit dies. It fires after the unit is dead, but
// before any death triggers fire.
//
// OnUnitResurrect(AutoEvent)
// This event runs when any unit is resurrected. It also fires when units
// are raised with Animate Dead or Reincarnation, as those are forms of
// resurrection as well.
//
// OnUnitAnimateDead(AutoEvent)
// This event runs when any unit is raised with Animate Dead. It fires after
// the resurrection event.
//
// IsUnitAnimateDead(unit) -> boolean
// This function returns a boolean that indicates if the specified unit
// has been raised with Animate Dead.
//
// OnUnitReincarnateStart(AutoEvent)
// This event runs when any unit begins reincarnating. The OnUnitDeath event
// will run first.
//
// OnUnitReincarnateEnd(AutoEvent)
// This event runs when any unit finishes reincarnating. The OnUnitResurrect
// event will occur immediately after.
//
// OnUnitLoad(TransportEvent)
// This event runs when any transport loads a passenger.
//
// OnUnitUnload(TransportEvent)
// This event runs when any transport unloads a passenger.
//
// GetUnitTransport(unit)
// Returns the transport that a unit is loaded in. Returns null if the
// unit is not riding in any transport.
//
// CountPassengers(transport) -> integer
// Returns the number of passengers in the specified transport.
//
// GetPassengerBySlot(transport, slot) -> unit
// Returns the passenger in the given slot of the specified transport.
// However, if a unit takes more than one transport slot, it will only be
// treated as occupying one transport slot.
//
// ForPassengers(transport, TransportEvent)
// This function runs a TransportEvent immediately for each passenger in
// the specified transport.
//
//===========================================================================
function interface AutoEvent takes unit u returns nothing
//! textmacro RunAutoEvent takes EVENT
set n = 0
loop
exitwhen n > $EVENT$funcs_n
call $EVENT$funcs[n].evaluate(u)
set n = n + 1
endloop
//! endtextmacro
//Injecting this textmacro into AutoIndex will cause the events to actually run.
//! textmacro AutoEvent takes EVENT, EVENTTYPE
globals
$EVENTTYPE$ array $EVENT$funcs
integer $EVENT$funcs_n = -1
endglobals
function OnUnit$EVENT$ takes $EVENTTYPE$ func returns nothing
set $EVENT$funcs_n = $EVENT$funcs_n + 1
set $EVENT$funcs[$EVENT$funcs_n] = func
endfunction
//! endtextmacro
//Instantiate the function to register events of each type.
//! runtextmacro AutoEvent("Death", "AutoEvent")
//! runtextmacro AutoEvent("Resurrect", "AutoEvent")
//! runtextmacro AutoEvent("AnimateDead", "AutoEvent")
//===========================================================================
//The code below this point adds Reincarnation support to AutoEvents.
//Credit to ToukoAozaki for the idea behind this detection method.
//! runtextmacro AutoEvent("ReincarnationStart", "AutoEvent")
//! runtextmacro AutoEvent("ReincarnationFinish", "AutoEvent")
//Create registration functions for reincarnation start and stop events.
globals
private timer ReincarnateTimer = CreateTimer()
private boolean array Reincarnated
private unit array Reincarnating
private integer Reincarnating_N = -1
endglobals
private function OnResurrect takes unit u returns nothing
local integer index = GetUnitId(u)
local integer n
if Reincarnated[index] then
set Reincarnated[index] = false
//If a resurrecting unit is flagged as reincarnating,
//it's time to run the ReincarnationFinish event.
//! runtextmacro RunAutoEvent("ReincarnationFinish")
endif
endfunction
private function ReincarnateCheck takes nothing returns nothing
local integer n = Reincarnating_N
local unit u
loop
exitwhen n < 0
set u = Reincarnating[n]
if GetUnitTypeId(u) != 0 and Reincarnated[GetUnitId(u)] then
//If the unit is still flagged as reincarnating, it means DeathDetect didn't run.
//The unit is actually reincarnating, so run the ReincarnationStart event.
//! runtextmacro RunAutoEvent("ReincarnationStart")
endif
set Reincarnating[n] = null
set n = n - 1
endloop
set Reincarnating_N = -1
set u = null
endfunction
private function OnDeath takes unit u returns nothing
set Reincarnated[GetUnitId(u)] = true
//Assume any unit that dies is going to reincarnate, unless this
//flag is set to false later by the DeathDetect function.
set Reincarnating_N = Reincarnating_N + 1 //Add the dying unit to a stack and
set Reincarnating[Reincarnating_N] = u //check the flag 0. seconds later.
call TimerStart(ReincarnateTimer, 0., false, function ReincarnateCheck)
endfunction
private function DeathDetect takes nothing returns boolean
set Reincarnated[GetUnitId(GetTriggerUnit())] = false
return false //Set the Reincarnated flag to false if the unit will not reincarnate.
endfunction
private function OnEnter takes unit u returns nothing
set Reincarnated[GetUnitId(u)] = false
//When a unit enters the map, initialize its Reincarnated flag to false.
endfunction
private struct ReincarnationInit extends array
private static method onInit takes nothing returns nothing
local trigger deathdetect = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(deathdetect, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(deathdetect, function DeathDetect)
//This trigger runs 0. seconds after OnUnitDeath events,
//but does not fire if the unit is going to Reincarnate.
call OnUnitIndexed(OnEnter)
call OnUnitDeath(OnDeath)
call OnUnitResurrect(OnResurrect)
endmethod
endstruct
//===========================================================================
// All of the remaining code deals with transports.
function interface TransportEvent takes unit transport, unit passenger returns nothing
//! runtextmacro AutoEvent("Load", "TransportEvent")
//! runtextmacro AutoEvent("Unload", "TransportEvent")
//Create registration functions for load and unload events.
//! textmacro RunTransportEvent takes EVENT
set n = 0
loop
exitwhen n > $EVENT$funcs_n
call $EVENT$funcs[n].evaluate(transport, passenger)
set n = n + 1
endloop
//! endtextmacro
//The above textmacro is used to run the Load/Unload events in the Transport struct below.
//===========================================================================
//A transport struct is created and attached to any unit detected loading another unit.
//It keeps track of the units within a transport and updates when they load or unload.
private keyword getUnitTransport
private keyword countPassengers
private keyword getPassengerBySlot
private keyword forPassengers
struct Transport
private static unit array loadedin
private static Transport array transports
private static integer array loadedindex
private static group array groups
private static integer groups_n = -1
private static real MaxX
private static real MaxY
private unit array loaded[10] //Transports can only carry 10 units.
private integer loaded_n = -1
//===========================================================================
static method getUnitTransport takes unit u returns unit
return loadedin[GetUnitId(u)]
endmethod
static method countPassengers takes unit transport returns integer
return transports[GetUnitId(transport)].loaded_n + 1
endmethod
static method getPassengerBySlot takes unit transport, integer slot returns unit
if slot < 1 or slot > 10 then
return null
endif
return transports[GetUnitId(transport)].loaded[slot - 1]
endmethod
static method forPassengers takes unit transport, TransportEvent func returns nothing
local Transport this = transports[GetUnitId(transport)]
local integer n = 0
if loaded_n == -1 then
return //Return if transport has no units loaded inside.
endif
loop
exitwhen n > loaded_n
call func.evaluate(transport, loaded[n])
//Loop through each passenger and call the TransportEvent func on it.
set n = n + 1
endloop
endmethod
//===========================================================================
static method loadUnit takes nothing returns boolean
local unit transport = GetTransportUnit()
local unit passenger = GetTriggerUnit()
local Transport this = transports[GetUnitId(transport)]
local integer n
if this == 0 then //If this is the first unit loaded by this transport...
set this = allocate() //allocate a Transport struct,
set transports[GetUnitId(transport)] = this //and attach it to the transport.
endif
set loaded_n = loaded_n + 1 //Increment the passenger counter.
set loaded[loaded_n] = passenger //Put the passenger in the unit array.
set loadedindex[GetUnitId(passenger)] = loaded_n //Attach the index to the passenger.
set loadedin[GetUnitId(passenger)] = transport //Attach the transport struct to the transport.
//! runtextmacro RunTransportEvent("Load") //Run the OnUnitLoad events.
call SetUnitX(passenger, MaxX) //Move the passenger to the edge of the map so that
call SetUnitY(passenger, MaxY) //unloading will trigger a "unit enters region" event.
set transport = null
set passenger = null
return false
endmethod
static method unloadUnit takes unit passenger returns nothing
local unit transport = getUnitTransport(passenger) //Get the transport unit.
local Transport this = transports[GetUnitId(transport)] //Get the transport struct.
local integer n = loadedindex[GetUnitId(passenger)] //Get the passenger's index.
loop
exitwhen n == loaded_n
set loaded[n] = loaded[n + 1]
set loadedindex[GetUnitId(loaded[n])] = n
set n = n + 1 //Starting from the position of the removed unit,
endloop //shift everything down by one and update the index.
set loaded[n] = null
set loaded_n = loaded_n - 1 //Decrement the passenger counter.
set loadedin[GetUnitId(passenger)] = null //Null the unloaded unit's transport.
//! runtextmacro RunTransportEvent("Unload") //Run the OnUnitUnload events.
if loaded_n == -1 then //If the transport is now empty...
call destroy() //Destroy the transport struct.
set transports[GetUnitId(transport)] = 0 //Disassociate it from the unit.
endif
set transport = null
endmethod
//===========================================================================
private static method unitEntersMap takes nothing returns boolean
if getUnitTransport(GetFilterUnit()) != null then //If the entering unit is in a transport...
call unloadUnit(GetFilterUnit()) //The unit was unloaded.
endif
return false
endmethod
private static method unitDies takes nothing returns boolean
if getUnitTransport(GetTriggerUnit()) != null then //If the dying unit is in a transport...
call unloadUnit(GetTriggerUnit()) //Unload the unit from its transport.
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local region maparea = CreateRegion()
local rect bounds = GetWorldBounds()
local trigger unload = CreateTrigger()
local trigger load = CreateTrigger()
local trigger death = CreateTrigger()
call RegionAddRect(maparea, bounds)
call TriggerRegisterEnterRegion(unload, maparea, function Transport.unitEntersMap) //When a unit enters the map area,
call TriggerRegisterAnyUnitEventBJ(load, EVENT_PLAYER_UNIT_LOADED) //it may have been unloaded.
call TriggerAddCondition(load, function Transport.loadUnit) //When a unit loads a unit, run the loadUnit method.
call TriggerRegisterAnyUnitEventBJ(death, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(death, function Transport.unitDies) //Detect when a unit dies in order to unload it.
call OnUnitDeindexed(Transport.unloadUnit) //When a unit leaves the game, unload it from its transport.
set Transport(0).loaded_n = -1 //Initialize this to -1 to make CountUnitsInTransport work properly.
set MaxX = GetRectMaxX(bounds) //Record the coordinates of a corner of the map so
set MaxY = GetRectMaxY(bounds) //that loaded units can be moved to that location.
call RemoveRect(bounds)
set bounds = null
endmethod
endstruct
//===========================================================================
// User functions:
//=================
function IsUnitAnimateDead takes unit u returns boolean
return AutoIndex.isUnitAnimateDead(u)
endfunction
function GetUnitTransport takes unit u returns unit
return Transport.getUnitTransport(u)
endfunction
function CountPassengers takes unit transport returns integer
return Transport.countPassengers(transport)
endfunction
function GetPassengerBySlot takes unit transport, integer slot returns unit
return Transport.getPassengerBySlot(transport, slot)
endfunction
function ForPassengers takes unit transport, TransportEvent func returns nothing
call Transport.forPassengers(transport, func)
endfunction
endlibrary
library Vector
//*****************************************************************
//* VECTOR LIBRARY
//*
//* written by: Anitarf
//*
//* The library contains a struct named vector, which represents a
//* point in 3D space. As such, it has three real members, one for
//* each coordinate: x, y, z. It also has the following methods:
//*
//* static method create takes real x, real y, real z returns vector
//* Creates a new vector with the given coordinates.
//*
//* method getLength takes nothing returns real
//* Returns the length of the vector it is called on.
//*
//* static method sum takes vector augend, vector addend returns vector
//* Returns the sum of two vectors as a new vector.
//*
//* method add takes vector addend returns nothing
//* Similar to sum, except that it doesn't create a new vector for the result,
//* but changes the vector it is called on by adding the "added" to it.
//*
//* static method difference takes vector minuend, vector subtrahend returns vector
//* Returns the difference between two vectors as a new vector.
//*
//* method subtract takes vector subtrahend returns nothing
//* Similar to difference, except that it doesn't create a new vector for the result,
//* but changes the vector it is called on by subtracting the "subtrahend" from it.
//*
//* method scale takes real factor returns nothing
//* Scales the vector it is called on by the given factor.
//*
//* method setLength takes real length returns nothing
//* Sets the length of the vector it is called on to the given value, maintaining it's orientation.
//*
//* static method dotProduct takes vector a, vector b returns real
//* Calculates the dot product (also called scalar product) of two vectors.
//*
//* static method crossProduct takes vector a, vector b returns vector
//* Calculates the cross product (also called vector product) of two vectors
//* and returns it as a new vector.
//*
//* static method tripleProductScalar takes vector a, vector b, vector c returns real
//* Calculates the triple scalar product of three vectors.
//*
//* static method tripleProductVector takes vector a, vector b, vector c returns vector
//* Calculates the triple vector product of three vectors and returns it as a new vector.
//*
//*
//* static method projectionVector takes vector projected, vector direction returns vector
//* Calculates the projection of the vector "projected" onto the vector "direction"
//* and returns it as a new vector.
//* Returns null if the vector direction has a length of 0.
//*
//* method projectVector takes vector direction returns nothing
//* Projects the vector it is called on onto the vector "direction".
//* Does nothing if the vector "direction" has a length of 0.
//*
//* static method projectionPlane takes vector projected, vector normal returns vector
//* Calculates the projection of the vector projected onto a plane defined by
//* it's normal vector and returns it as a new vector.
//* Returns null if the vector "normal" has a length of 0.
//*
//* method projectPlane takes vector normal returns nothing
//* Projects the vector it is called on onto a plane defined by it's normal vector.
//* Does nothing if the vector "normal" has a length of 0.
//*
//* static method getAngle takes vector a, vector b returns real
//* Returns the angle between two vectors, in radians, returns a value between 0 and pi.
//* Returns 0.0 if any of the vectors are 0 units long.
//*
//* method rotate takes vector axis, real angle returns nothing
//* Rotates the vector it is called on around the axis defined by the vector "axis"
//* by the given angle, which should be input in radians.
//* Does nothing if axis is 0 units long.
//*
//*
//* static method createTerrainPoint takes real x, real y returns vector
//* Creates a vector to the given terrain coordinate, taking it's z height into account.
//*
//* method getTerrainPoint takes real x, real y returns nothing
//* Sets the vector it is called on to the given terrain coordinate, taking it's z height into account.
//*
//* static method createTerrainNormal takes real x, real y, real sampleRadius returns vector
//* Creates the normal vector of the terrain at given coordinates. "sampleRadius" defines
//* how far apart the reference points will be, if they are further apart, the result will
//* be an impression of smoother terrain; normaly the value should be between 0 and 128.
//*
//* method getTerrainNormal takes real x, real y, real sampleRadius returns nothing
//* Sets the vector it is called on to the normal of the terrain at given coordinates.
//*
//*
//* method isInCylinder takes vector cylinderOrigin, vector cylinderHeight, real cylinderRadius returns boolean
//* Determines if a point is within a given cylinder. The cylinder's origin vector points
//* to the center of one of the two paralel circular sides, and the height vector points
//* from the origin point to the center of the other of the two paralel circular sides.
//* Returns false if the point is not in the cylinder or if the vector cylinderHeight is 0 units long.
//*
//* method isInCone takes vector coneOrigin, vector coneHeight, real coneRadius returns boolean
//* Determines if a point is within a given cone. The cone's origin vector points to the
//* center of the circular side, and the height vector points from the origin point to
//* the tip of the cone.
//* Returns false if the point is not in the cylinder or if the vector coneHeight is 0 units long.
//*
//* method isInSphere takes vector sphereOrigin, real sphereRadius returns boolean
//* Determines if a point is within a give sphere. The sphere's origin vector points to the
//* center of the sphere.
//* Returns false if the point is not in the sphere.
//*****************************************************************
struct vector
real x
real y
real z
static method create takes real x, real y, real z returns vector
local vector v = vector.allocate()
set v.x=x
set v.y=y
set v.z=z
return v
endmethod
method getLength takes nothing returns real
return SquareRoot(.x*.x + .y*.y + .z*.z)
endmethod
static method sum takes vector augend, vector addend returns vector
local vector v = vector.allocate()
set v.x = augend.x+addend.x
set v.y = augend.y+addend.y
set v.z = augend.z+addend.z
return v
endmethod
method add takes vector addend returns nothing
set this.x=this.x+addend.x
set this.y=this.y+addend.y
set this.z=this.z+addend.z
endmethod
static method difference takes vector minuend, vector subtrahend returns vector
local vector v = vector.allocate()
set v.x = minuend.x-subtrahend.x
set v.y = minuend.y-subtrahend.y
set v.z = minuend.z-subtrahend.z
return v
endmethod
method subtract takes vector subtrahend returns nothing
set this.x=this.x-subtrahend.x
set this.y=this.y-subtrahend.y
set this.z=this.z-subtrahend.z
endmethod
method scale takes real factor returns nothing
set this.x=this.x*factor
set this.y=this.y*factor
set this.z=this.z*factor
endmethod
method setLength takes real length returns nothing
local real l = SquareRoot(.x*.x + .y*.y + .z*.z)
if l == 0.0 then
debug call BJDebugMsg("Attempted to set the length of a vector with no length!")
return
endif
set l = length/l
set this.x = this.x*l
set this.y = this.y*l
set this.z = this.z*l
endmethod
static method dotProduct takes vector a, vector b returns real
return (a.x*b.x+a.y*b.y+a.z*b.z)
endmethod
static method crossProduct takes vector a, vector b returns vector
local vector v = vector.allocate()
set v.x = a.y*b.z - a.z*b.y
set v.y = a.z*b.x - a.x*b.z
set v.z = a.x*b.y - a.y*b.x
return v
endmethod
static method tripleProductScalar takes vector a, vector b, vector c returns real
return ((a.y*b.z - a.z*b.y)*c.x+(a.z*b.x - a.x*b.z)*c.y+(a.x*b.y - a.y*b.x)*c.z)
endmethod
static method tripleProductVector takes vector a, vector b, vector c returns vector
local vector v = vector.allocate()
local real n = a.x*c.x+a.y*c.y+a.z*c.z
local real m = a.x*b.x+a.y*b.y+a.z*b.z
set v.x = b.x*n-c.x*m
set v.y = b.y*n-c.y*m
set v.z = b.z*n-c.z*m
return v
endmethod
// ================================================================
static method projectionVector takes vector projected, vector direction returns vector
local vector v = vector.allocate()
local real l = direction.x*direction.x+direction.y*direction.y+direction.z*direction.z
if l == 0.0 then
call v.destroy()
debug call BJDebugMsg("Attempted to project onto a vector with no length!")
return vector(0)
endif
set l = (projected.x*direction.x+projected.y*direction.y+projected.z*direction.z) / l
set v.x = direction.x*l
set v.y = direction.y*l
set v.z = direction.z*l
return v
endmethod
method projectVector takes vector direction returns nothing
local real l = direction.x*direction.x+direction.y*direction.y+direction.z*direction.z
if l == 0.0 then
debug call BJDebugMsg("Attempted to project onto a vector with no length!")
return
endif
set l = (this.x*direction.x+this.y*direction.y+this.z*direction.z) / l
set this.x = direction.x*l
set this.y = direction.y*l
set this.z = direction.z*l
endmethod
static method projectionPlane takes vector projected, vector normal returns vector
local vector v = vector.allocate()
local real l = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z
if l == 0.0 then
call v.destroy()
debug call BJDebugMsg("Attempted to project onto an undefined plane!")
return vector(0)
endif
set l = (projected.x*normal.x+projected.y*normal.y+projected.z*normal.z) / l
set v.x = projected.x - normal.x*l
set v.y = projected.y - normal.y*l
set v.z = projected.z - normal.z*l
return v
endmethod
method projectPlane takes vector normal returns nothing
local real l = normal.x*normal.x+normal.y*normal.y+normal.z*normal.z
if l == 0.0 then
debug call BJDebugMsg("Attempted to project onto an undefined plane!")
return
endif
set l = (this.x*normal.x+this.y*normal.y+this.z*normal.z) / l
set this.x = this.x - normal.x*l
set this.y = this.y - normal.y*l
set this.z = this.z - normal.z*l
endmethod
static method getAngle takes vector a, vector b returns real
local real l = SquareRoot(a.x*a.x + a.y*a.y + a.z*a.z)*SquareRoot(b.x*b.x + b.y*b.y + b.z*b.z)
if l == 0 then
debug call BJDebugMsg("Attempted to get angle between vectors with no length!")
return 0.0
endif
return Acos((a.x*b.x+a.y*b.y+a.z*b.z)/l) //angle is returned in radians
endmethod
method rotate takes vector axis, real angle returns nothing //angle is taken in radians
local real xx
local real xy
local real xz
local real yx
local real yy
local real yz
local real zx
local real zy
local real zz
local real al = axis.x*axis.x+axis.y*axis.y+axis.z*axis.z //axis length^2
local real f
local real c = Cos(angle)
local real s = Sin(angle)
if al == 0.0 then
debug call BJDebugMsg("Attempted to project onto a vector with no length!")
return
endif
set f = (this.x*axis.x+this.y*axis.y+this.z*axis.z) / al
set zx = axis.x*f
set zy = axis.y*f
set zz = axis.z*f //axis component of rotated vector
set xx = this.x-zx
set xy = this.y-zy
set xz = this.z-zz //component of vector perpendicular to axis
set al = SquareRoot(al)
set yx = (axis.y*xz - axis.z*xy)/al
set yy = (axis.z*xx - axis.x*xz)/al //y same length as x by using cross product and dividing with axis length
set yz = (axis.x*xy - axis.y*xx)/al //x,y - coordinate system in which we rotate
set this.x=xx*c+yx*s+zx
set this.y=xy*c+yy*s+zy
set this.z=xz*c+yz*s+zz
endmethod
// ================================================================
private static location loc = Location(0.0,0.0)
static method createTerrainPoint takes real x, real y returns vector
local vector v = vector.allocate()
call MoveLocation(vector.loc,x,y)
set v.x=x
set v.y=y
set v.z=GetLocationZ(loc)
return v
endmethod
method getTerrainPoint takes real x, real y returns nothing
call MoveLocation(vector.loc,x,y)
set this.x=x
set this.y=y
set this.z=GetLocationZ(loc)
endmethod
static method createTerrainNormal takes real x, real y, real sampleRadius returns vector
local vector v = vector.allocate()
local real zx
local real zy
call MoveLocation(vector.loc, x-sampleRadius, y)
set zx=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x+sampleRadius, y)
set zx=zx-GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y-sampleRadius)
set zy=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y+sampleRadius)
set zy=zy-GetLocationZ(vector.loc)
set sampleRadius=2*sampleRadius
set v.x = zx*sampleRadius
set v.y = zy*sampleRadius
set v.z = sampleRadius*sampleRadius
return v
endmethod
method getTerrainNormal takes real x, real y, real sampleRadius returns nothing
local real zx
local real zy
call MoveLocation(vector.loc, x-sampleRadius, y)
set zx=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x+sampleRadius, y)
set zx=zx-GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y-sampleRadius)
set zy=GetLocationZ(vector.loc)
call MoveLocation(vector.loc, x, y+sampleRadius)
set zy=zy-GetLocationZ(vector.loc)
set sampleRadius=2*sampleRadius
set this.x = zx*sampleRadius
set this.y = zy*sampleRadius
set this.z = sampleRadius*sampleRadius
endmethod
// ================================================================
method isInCylinder takes vector cylinderOrigin, vector cylinderHeight, real cylinderRadius returns boolean
local real l
local real x = this.x-cylinderOrigin.x
local real y = this.y-cylinderOrigin.y
local real z = this.z-cylinderOrigin.z
if x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z < 0.0 then //point below cylinder
return false
endif
set x = x-cylinderHeight.x
set y = y-cylinderHeight.y
set z = z-cylinderHeight.z
if x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z > 0.0 then //point above cylinder
return false
endif
set l = cylinderHeight.x*cylinderHeight.x+cylinderHeight.y*cylinderHeight.y+cylinderHeight.z*cylinderHeight.z
if l == 0.0 then
debug call BJDebugMsg("Cylinder with no height!")
return false
endif
set l = (x*cylinderHeight.x+y*cylinderHeight.y+z*cylinderHeight.z) / l
set x = x - cylinderHeight.x*l
set y = y - cylinderHeight.y*l
set z = z - cylinderHeight.z*l
if x*x+y*y+z*z > cylinderRadius*cylinderRadius then //point outside cylinder
return false
endif
return true
endmethod
method isInCone takes vector coneOrigin, vector coneHeight, real coneRadius returns boolean
local real l
local real x = this.x-coneOrigin.x
local real y = this.y-coneOrigin.y
local real z = this.z-coneOrigin.z
if x*coneHeight.x+y*coneHeight.y+z*coneHeight.z < 0.0 then //point below cone
return false
endif
set l = coneHeight.x*coneHeight.x+coneHeight.y*coneHeight.y+coneHeight.z*coneHeight.z
if l == 0.0 then
debug call BJDebugMsg("cone with no height!")
return false
endif
set l = (x*coneHeight.x+y*coneHeight.y+z*coneHeight.z) / l
set x = x - coneHeight.x*l
set y = y - coneHeight.y*l
set z = z - coneHeight.z*l
if SquareRoot(x*x+y*y+z*z) > coneRadius*(1.0-l) then //point outside cone
return false
endif
return true
endmethod
method isInSphere takes vector sphereOrigin, real sphereRadius returns boolean
if sphereRadius*sphereRadius < ((this.x-sphereOrigin.x)*(this.x-sphereOrigin.x)+(this.y-sphereOrigin.y)*(this.y-sphereOrigin.y)+(this.z-sphereOrigin.z)*(this.z-sphereOrigin.z)) then
return false
endif
return true
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Projectile requires Vector
//*********************************************************************************************************
//*
//* WarCraft III: Custom Projectiles Library
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Author: Berbanog / TheKid
//* Version: 1.7
//*
//* Requirements:
//* • JassHelper 0.A.2.B
//* • Vector library by Anitarf
//*
//* To Know:
//* • Pitch rotation only works with the dummy model that is included in the imports to this
//* map. It is Vexorian/Infrane's with many animations for different pitch angles.
//* • Projectile groups are limited by the number of projectiles that they can contain.
//*
//* Description:
//* • Duplicates the cosmetics of an in-game projectile and includes functionality dedicated
//* to the manipulation of projectiles.
//*
//* Changelog:
//* • 1.2 a) Added pause functionality to timescale (zero timescale means frozen or paused).
//* b) Fixed test-map problem.
//*
//* • 1.3 a) Added functionality for .toRemove, which acts like .toKill except instead of
//* having the projectile-unit killed it is removed. They can both be used simultaneously.
//* b) Added function GetTotalProjectiles which is basically just a debugging tool.
//*
//* • 1.4 a) Added more functionality to projectile groups. They now include the .add and
//* .remove methods as well as the .inGroup boolean which takes a projectile parameter.
//* b) Added the isValidTargetUnit interface method that is used to filter the unit
//* that is collected by the .onUnitCollision interface method.
//* c) The name of the library has been changed from Projectiles to Projectile. I can't
//* seem to remember whether this update was made in 1.3 or 1.4, but it is for my own
//* programming grammar standards.
//*
//* • 1.5 a) Fixed more issues with timescale. It should now be working 100% correctly.
//* b) Added the .removeIndex to the method API of projectile groups. This makes removing
//* projectiles faster in certain situations.
//* c) Added operators for projectile position; .x, .y, and .z.
//* d) Added a hashtable to dynamic storage for use with projectile groups, hopefully
//* this second method of reference will improve the functionality of these groups.
//* e) Added destructable collision, as well as a .isValidTargetDest method filter for
//* destructables that trigger the collision.
//*
//* • 1.6 a) Fixed the z-arc when the projectile is following a target so that the projectile
//* does not simply move directly towards the target once it moves (ugly).
//* b) Fixed a timescale issue on projectiles that have moving targets that would cause
//* the projectile to be destroyed earlier if it's time-scale was manipulated.
//*
//* • 1.6b a) Fixed test-map references to imported code so that they actually contain the
//* reqiored cpde for testing.
//* b) Optimized the destructible collision by removing the code and executing all
//* necessary actions in the boolexpr filter.
//*
//* • 1.6c a) Optimized code further. Eliminated unnecessary function calls.
//* - Replaced repeated Filter call with a static reference.
//* - Eliminated a square root call in setTargetPos() method.
//* - Eliminated a square root call in loop iteration on target seeking projectiles.
//*
//* • 1.6d a) Included method operators for retrieving the coordinates of the target vector,
//* which will allow some more accurate enumeration of nearby units for something like
//* an area of effect spell.
//* b) Fixed a logic error that caused the rotation functionality to cease when it should
//* not: (x2 != 0 and y2 != 0 and z2 != 0) to (x2 != 0 or y2 != 0 or z2 != 0)
//* c) Fixed a logic error that doesn't seem to affect any functionality associated with
//* the pitch animation calculation.
//*
//* • 1.7 a) Removed the height check when executing a unit-collision response to allow more
//* exact duplication of Warcraft III mechanics.
//* b) Replaced a "SquareRoot" call with another mathematical derivation of the same value.
//* c) Cleaned up a few unnecessary libraries, such as "LinkedList" from the test-map.
//*
//* If the user needs to detect spherical collision (as was done before) they can
//* compare the projectile/unit Z values. This only improves functionality, it will
//* not restrict the user in any way.
//*
//*
//*
//*********************************************************************************************************
globals
//************************
//* Configuration
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Includes default definitions of projectile settings in addition to other values that are used
//* within the system but rely on a value in order to function.
//*
//* >> Medihv's crow-form ability which allows non-flying units to have their fly-height modified;
public constant integer FLY_ABIL_ID = 'Amrf'
//*
//* >> Projectile enumeration constants;
public constant integer PROJ_ENUM_COUNT_MAX = 64
//*
//* >> Default constants; these can be changed per instance of each projectile.
public constant real PROJ_COLLISION_DEFAULT = 70.00
public constant real PROJ_COLLISION_MAX = 200.00
//*
public constant real PROJ_MOTION_REF = 0.03 //* This identifies the amount of time (in seconds) that lapses
//* between each iteration that the projectiles are "updated";
//* >> Active feature defaults;
private constant boolean default__targetFollow = true
private constant boolean default__unitCollision = true //* The unit-collision feature of projectiles is on by default.
private constant boolean default__destCollision = true //* The destructible-collision feature of the projectile is on
//* by default.
private constant boolean default__faceRotation = true //* Whether or not the unit's facing will be updated to match the
//* direction of the velocity vector.
private constant boolean default__facePitch = true //* Whether or not a unit will have its pitch-rotation adjusted
//* when the projectile is updated. Only guaranteed to work with
//* the dummy.mdx included in the imports (by Vexorian).
private constant boolean default__toExpire = true //* By default, projectiles are flagged to be destroyed after their
//* determined path unless otherwise specified. If it is necessary
//* to manipulate projectiles after their determined lifespan, then
//* this should be set to false.
private constant boolean default__toKill = true //* Once the projectile expires, the unit associated with the
//* projectile will in turn be killed.
private constant boolean default__toRemove = false //* Once the projectile expires, the unit associated with the
//* projectile will in turn be removed.
//*
//*
//*********************************************************************************************************
endglobals
interface projectileinterface
//***************************
//* Projectile Interface
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Includes the names for methods (and their parameters) that users can declare in child-structures to
//* acquire reference to a projectile on different occurances that prompt a response.
//*
//* >> Responses associated with time-line "events";
method onStart takes nothing returns nothing defaults nothing
method onFinish takes nothing returns nothing defaults nothing
//*
//* >> Responses associated with physical surrounding occurances;
method onGround takes nothing returns nothing defaults nothing
//*
//* >> Responses associated with widget collision;
method onUnitCollision takes unit u returns nothing defaults nothing
method onDestCollision takes destructable d returns nothing defaults nothing
//*
//* >> The unit collision event is used to filter units that trigger
//* the onUnitCollision method event response.
method isValidTargetUnit takes unit u returns boolean defaults true
method isValidTargetDest takes destructable d returns boolean defaults true
//*
//*
//*********************************************************************************************************
endinterface
globals
//********************
//* Dynamic Storage
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* In order to optimize the Projectiles efficiency, certain global variables are required to make special
//* calculations and operations; such as GetLocationZ().
//*
private projectile projRef
private location loc = Location(0, 0)
private group grp = CreateGroup()
private rect rct
//*
//* Hashtable Storage
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
private hashtable table = InitHashtable()
//*
//* -------------------------------------------------------------------------------------------------------
private integer totalProjectiles = 0
//* -------------------------------------------------------------------------------------------------------
//*
//*********************************************************************************************************
endglobals
function GetTotalProjectiles takes nothing returns integer
// ----
// This function is for debugging purposes. It returns the total
// amount of projectiles created that have not yet been destroyed.
//
return totalProjectiles
endfunction
struct projectilegroup
//************************
//* Projectile Group
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//* Enumeration is a big part of WarCraft III, and it would be hard to really utilize projectiles properly
//* without having some method of enumeration. At this point this structure is pretty basic, including
//* only a method for adding a projectile to the group (can only be done this way for security).
//*
//* Update 1.5 >> Some values are easier to retrieve if the projectiles are added to a
//* hashtable, so they are added to both a linear array as well as a hashtable.
//*
readonly projectile array at [PROJ_ENUM_COUNT_MAX]
readonly integer max = 0
//*
method onDestroy takes nothing returns nothing
call FlushChildHashtable(table, this)
endmethod
//* ----
//* Group Methods & Operators
//* =======================================================================================================
//*
method add takes projectile p returns boolean
if .max < PROJ_ENUM_COUNT_MAX then
set .at[.max] = p
set .max = .max + 1
call SaveBoolean(table, this, integer(p), true)
return true
endif
return false
endmethod
method remove takes projectile p returns boolean
local integer i=.max-1
call RemoveSavedBoolean(table, this, integer(p))
loop
exitwhen(i < 0)
if (.at[i]==p) then
set .max = .max-1
set .at[i] = .at[.max]
return true
endif
set i=i-1
endloop
return false // If the projectile was not in the group, it will return false;
endmethod
method removeIndex takes integer index returns boolean
call RemoveSavedBoolean(table, this, .at[index])
if (index < .max) then // Remove index can be used on an index of the
set .max = .max - 1 // projectile group to save the trouble of
set .at[index] = .at[.max] // looping through the group until the indexed
return true // projectile is found.
endif
return false
endmethod // If the index is within the bounds of the group it will return true;
//*
//*
method inGroup takes projectile p returns boolean
return HaveSavedBoolean(table, this, p)
endmethod
//*
//*
//*********************************************************************************************************
endstruct
struct projectile extends projectileinterface
//*******************************************
//* Projectile
//* ¯¯¯¯¯¯¯¯¯¯
//* Primary data-structure that encapsullates the projectile functionality. Contains methods for creating,
//* destroying, and manipulating certain aspects of the projectile motion.
//*
//* #######################################################################################################
//*
readonly effect toUnit = null
private unit priv_target = null //* Both .priv_target and .priv_source can be referenced publicly by using the
private unit priv_source = null //* method operators without the "priv_" prefix.
//*
readonly real speed //* In order to launch a projectile, a "speed" and "arc" input is necessary. These
readonly real arc //* values are stored in readonly-scope variables so that they can be referenced
//* externally.
private real priv_timescale = 1.00
private real priv_collision = PROJ_COLLISION_DEFAULT
//*
//* These are used to maintain necessary projectile checks. They are also used to determine the features
//* that should be included in each specific projectile, such as "activeUnitCollision".
//*
private boolean active = false //* This is automatically updated depending on whether the projectile
//* has been launched.
public boolean activeTargetFollow = default__targetFollow
public boolean activePitch = default__facePitch
public boolean activeRotation = default__faceRotation //* Whether the unit's facing will be updated to match the 2D direction
//* of the projectile's velocity vector.
public boolean activeUnitCollision = default__unitCollision
public boolean activeDestCollision = default__destCollision
//*
//* In order to prevent multiple responses of certain projectile events, the "state" of the projectile is
//* stored in a private-scope variable. The events that are included in this are:
//* • method onGround
//* • method onFinish
//*
private boolean priv_ground = false
private boolean priv_finish = false
//*
//* Other private status checks:
private boolean priv_follow = false
private boolean priv_unitfollow = false
private boolean priv_paused = false
//*
//* Projectile motion is dictated by 4 vectors: position, velocity, acceleration, and target-position. If
//* these values are manipulated they can be used to achieve certain effects.
//*
private vector posVec // These are all created when the projectile is created. All
private vector velVec // values are 0 until the projectile has been launched except
private vector accVec // for the position (posVec) of the projectile.
private vector tarVec
private vector strVec // The starting point of a projectile motion is given when the
//* // projectile is launched, and used for arc-motion reference.
//*
//* Projectiles are updated in a stack; and require certain data on-hand in order to properly update and
//* manage the stack.
//*
private integer priv_index
private static integer priv_stackDex = 0 //* The amount of projectiles currently in the stack.
//*
private static thistype array priv_stack
private static timer priv_stackLoop = CreateTimer()
private static constant real priv_stackLoopRef = PROJ_MOTION_REF
//*
//* To make the enumeration process slightly faster, use a reference variable to store the filter so it
//* doesn't have to be referenced via function.
private static boolexpr priv_filterFunc = null
//*
private real priv_timeleft
private boolean priv_killnext = false
//*
public boolean toDestroy = default__toExpire //* Once the projectile finishes it's launch process, these
public boolean toKill = default__toKill //* flags will determine whether special procedure is taken,
public boolean toRemove = default__toRemove //* such as destroying the projectile once it completes its
//* //* path, or killing the projectile-unit once it is destroyed.
//*
//* #######################################################################################################
//*
//* Position
//* =======================================================================================================
method operator x takes nothing returns real
return .posVec.x
endmethod
method operator y takes nothing returns real
return .posVec.y
endmethod
method operator z takes nothing returns real
return .posVec.z
endmethod
//*
//* Collision
//* =======================================================================================================
method operator collision takes nothing returns real
return .priv_collision
endmethod
method operator collision= takes real r returns nothing
if (r>PROJ_COLLISION_MAX) then
set r=PROJ_COLLISION_MAX
elseif (r<0) then
set r=0
endif
set .priv_collision=r
endmethod
//*
//* Source
//* =======================================================================================================
method operator source takes nothing returns unit
return .priv_source
endmethod
method operator source= takes unit who returns nothing
if (.priv_source==null) then
set .priv_source=who
endif
endmethod
//*
//* Target
//* =======================================================================================================
method operator target takes nothing returns unit
return .priv_target
endmethod
method operator target= takes unit who returns nothing
if (.priv_target==null) then
set .priv_target=who
set .priv_unitfollow=true
endif
endmethod
//*
method operator targetX takes nothing returns real
return tarVec.x
endmethod
method operator targetY takes nothing returns real
return tarVec.y
endmethod
method operator targetZ takes nothing returns real
return tarVec.z
endmethod
//*
//* Timescale
//* =======================================================================================================
method operator timescale takes nothing returns real
if (.priv_paused) then
return 0.00
endif
return .priv_timescale
endmethod
method operator timescale= takes real r returns nothing // Time left = 10s Time scale = 1.0
if (r == 0) then // Time left = 20s Time scale = 0.5 (1.0 - 0.5) = 0.5
set .priv_paused = true // Time left = 20s Time scale = 1.0 (0.5 - 0.5) = 0.0
else
set .priv_paused = false
set .priv_timescale = r
endif
endmethod
//*
//* Target Position
//* =======================================================================================================
method setTargetPos takes real x, real y, real z returns boolean //* The projectile-target-pos can be manipulated many times.
local real xA
local real yA
local vector v = .velVec
if (.tarVec.x==x and .tarVec.y==y and .tarVec.z==z) then
return false
endif
set .tarVec.x = x
set .tarVec.y = y
set .tarVec.z = z
set xA = .posVec.x
set yA = .posVec.y
set xA = (x-xA)*(x-xA) + (y-yA)*(y-yA)
set yA = (v.x*v.x + v.y*v.y)/(thistype.priv_stackLoopRef*thistype.priv_stackLoopRef)
set .priv_timeleft = SquareRoot(xA/yA)
set .priv_follow = true // This must be flagged to notify the "projectile"
return true // that the target has been changed.
endmethod
//*
//*
method kill takes nothing returns nothing
set .priv_killnext = true
endmethod
private method onDestroy takes nothing returns nothing
set thistype.priv_stackDex = thistype.priv_stackDex - 1
set thistype.priv_stack[.priv_index] = thistype.priv_stack[thistype.priv_stackDex]
set thistype.priv_stack[.priv_index].priv_index = .priv_index
set totalProjectiles = totalProjectiles - 1
call .posVec.destroy()
call .velVec.destroy()
call .accVec.destroy()
call .tarVec.destroy()
call .strVec.destroy()
call DestroyEffect(.toUnit)
if (thistype.priv_stackDex == 0) then
call PauseTimer(thistype.priv_stackLoop)
endif
endmethod
//*
//* Projectile Enumeration
//* =======================================================================================================
static method enumNearby takes projectilegroup g, real x, real y, real z, real radius returns nothing
local integer i=0
local real x2
local real y2
local real z2
loop
exitwhen (i==.priv_stackDex)
set x2 = .priv_stack[i].posVec.x
set y2 = .priv_stack[i].posVec.y
set z2 = .priv_stack[i].posVec.z
if ((x2-x)*(x2-x)+(y2-y)*(y2-y)+(z2-z)*(z2-z)) <= (radius*radius) then
call g.add(.priv_stack[i])
endif
set i=i+1
endloop
endmethod
//*
//* Launch Projectile **
//* =======================================================================================================
method doLaunch takes vector start, vector finish, real speed, real arc returns boolean
local real d
local real a
local real r
local unit u
if (start == 0) or (finish == 0) or (.active) then
return false
//There are rules to launching a projectile. If any of these rules are broken, the method will
//return false. A projectile cannot be launched if it has already been launched.
endif
set .posVec.x = start.x
set .posVec.y = start.y
set .posVec.z = start.z
set .tarVec.x = finish.x
set .tarVec.y = finish.y
set .tarVec.z = finish.z
set .strVec.x = start.x
set .strVec.y = start.y
set .strVec.z = start.z
set .speed = speed
set .arc = arc
set d = SquareRoot((finish.x-start.x)*(finish.x-start.x) + (finish.y-start.y)*(finish.y-start.y))
set a = Atan2(finish.y-start.y, finish.x-start.x)
if (d == 0.00) or (speed == 0.00) then
set .posVec.x = .tarVec.x
set .posVec.y = .tarVec.y
set .posVec.z = .tarVec.z
set .priv_timeleft = 0.00
else
set .priv_timeleft = d/speed
set r = thistype.priv_stackLoopRef
set .accVec.z = (-8*arc*speed*speed/d)
set .velVec.x = speed*Cos(a)
set .velVec.y = speed*Sin(a)
set .velVec.z = (-.accVec.z * (d/speed)/2 + (finish.z-start.z)/(d/speed))
set .accVec.z = .accVec.z *r*r
set .velVec.x = .velVec.x *r
set .velVec.y = .velVec.y *r
set .velVec.z = .velVec.z *r
endif
call MoveLocation(loc, posVec.x, posVec.y)
call BlzSetSpecialEffectPosition(.toUnit, posVec.x, posVec.y, posVec.z - GetLocationZ(loc))
call onStart()
set active = true
set priv_finish = false
return true
endmethod
//*
//*
private static method doLoopDestFilter takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real x = GetWidgetX(d)
local real y = GetWidgetY(d)
if ((projRef.x-x)*(projRef.x-x) + (projRef.y-y)*(projRef.y-y)) <= projRef.priv_collision*projRef.priv_collision then
if projRef.isValidTargetDest(d) then
call projRef.onDestCollision(d)
endif
endif //destructibles are picked if their x/y coordinates fall within a
set d=null //circular area around the projectile.
return false
endmethod
//*
private static method doLoopEnum takes nothing returns boolean
local unit filt = GetFilterUnit()
local real xA = GetUnitX(filt)
local real yA = GetUnitY(filt)
local real xB = projRef.posVec.x
local real yB = projRef.posVec.y
if (((xA-xB)*(xA-xB) + (yA-yB)*(yA-yB)) <= projRef.collision*projRef.collision) then
// Unit collision is filtered using the interface method 'isValidTargetUnit';
if (projRef.isValidTargetUnit(filt)) then
call projRef.onUnitCollision(filt)
endif
endif
set filt = null
return false
endmethod
private static method doLoop takes nothing returns nothing
local integer i = .priv_stackDex - 1
local thistype dat
local vector vA
local vector vB
local vector vC
local real x
local real y
local real z
local real x2
local real y2
local real z2
local real e
local effect f
loop
exitwhen (i < 0)
set dat = .priv_stack[i]
if (dat != 0) then
//-----------------------------------------------------------------------------------------------------------
// Vector Updates
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//
if (dat.active) then
set e = dat.priv_timescale
// If .priv_paused then set e = 0.00;
if (dat.priv_paused) then
set e = 0.00
endif
set vA = dat.velVec
set vB = dat.accVec
set vA.x = vA.x + vB.x *e
set vA.y = vA.y + vB.y *e
set vA.z = vA.z + vB.z *e
set vB = dat.posVec
set vB.x = vB.x + vA.x *e
set vB.y = vB.y + vA.y *e
set vB.z = vB.z + vA.z *e
else
set vA = dat.velVec
set vB = dat.posVec
endif
set f = dat.toUnit
set x = vB.x
set y = vB.y
set z = vB.z
call MoveLocation(loc, x, y)
call BlzSetSpecialEffectPosition(f, dat.posVec.x, dat.posVec.y, dat.posVec.z - GetLocationZ(loc))
//-----------------------------------------------------------------------------------------------------------
// Projectile Rotation
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Projectiles also have the feature of pitch and rotation values, if those features are enabled for
// the specific projectile. While pitch-rotation has specific requirements, rotation does not.
//
set x2 = vA.x
set y2 = vA.y
set z2 = vA.z
if (dat.active) and (x2!=0 or y2!=0 or z2!=0) then
if dat.activePitch then
call BlzSetSpecialEffectPitch(f, Atan2(z2, dat.speed*priv_stackLoopRef) + bj_PI*0.50)
// This requires the dummy.mdx file that is included in xepreload. I should really make
// xepreload a requirement (perhaps optional requirement) of this system.
endif
if dat.activeRotation then
call BlzSetSpecialEffectYaw(f, Atan2(y2, x2))
// There are no special requirements for units to have their rotation updated to match their
// velocity, only that rotation be enabled.
endif
endif
//-----------------------------------------------------------------------------------------------------------
// Terrain Collision
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//
if (z <= GetLocationZ(loc)) then //This detects whether or not a projectile's trajectory has
if not (dat.priv_ground) then //taken it beneath the terrain. The "onGround" method will
set dat.priv_ground = true //only fire once when the projectile flies underground, and
call dat.onGround() //will not repeat on every iteration after until the projectile
endif //has re-surfaced above the terrain.
else
if (dat.priv_ground) then
set dat.priv_ground = false
endif
endif
//-----------------------------------------------------------------------------------------------------------
// Projectile Collision Events
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
//
if (dat.activeUnitCollision) then // -- Unit Collision --
set projRef = dat
call GroupEnumUnitsInRange(grp, x, y, dat.collision, priv_filterFunc)
//priv_filterFunc always returns false so there is no need to monitor/clear this group at any time,
//it should always be empty.
endif
if (dat.activeDestCollision) then // -- Destructable Collision --
set projRef = dat
call SetRect(rct, 0, 0, dat.priv_collision, dat.priv_collision)
call MoveRectTo(rct, x, y)
call EnumDestructablesInRect(rct, Filter(function thistype.doLoopDestFilter), null)
endif
//-----------------------------------------------------------------------------------------------------------
// Target Tracking
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// Target tracking (specifically, units) requires an "active" boolean config value in order for each
// specific projectile to allow tracking, that is, following a moving target.
//
// There are two variations to this, typically when you want to track a unit (which is common) and when
// you simply want to track the vector target (which could possibly be moved using setTargetPos().
//
if (dat.active) and (dat.activeTargetFollow) then
if (dat.priv_unitfollow) and (dat.target!=null) then
//Unit-follow means the target-vector will be updated to match the 3D position of the given
//unit-target.
set x = GetUnitX(dat.target)
set y = GetUnitY(dat.target)
set z = BlzGetUnitZ(dat.target)
call dat.setTargetPos(x, y, z)
endif
if (dat.priv_follow) then
//If this runs, then the target has moved; in which case certain components need to be adjusted
//to compensate for the change.
set vC = dat.tarVec
set x = Atan2(vC.y-vB.y, vC.x-vB.x) //Angle to target
set y = SquareRoot((vC.x-vB.x)*(vC.x-vB.x) + (vC.y-vB.y)*(vC.y-vB.y)) //Distance to target
set z2 = vC.z-vB.z //Height difference
set vA.x = dat.speed * Cos(x) * priv_stackLoopRef
set vA.y = dat.speed * Sin(x) * priv_stackLoopRef
set vB = dat.strVec
set x = SquareRoot((vB.x-vC.x)*(vB.x-vC.x) + (vB.y-vC.y)*(vB.y-vC.y)) //Total distance
set dat.accVec.z = -8*dat.arc*dat.speed*dat.speed/x
set vA.z = -dat.accVec.z * y/(dat.speed*2) + dat.speed*z2/y
set dat.accVec.z = dat.accVec.z *priv_stackLoopRef*priv_stackLoopRef
set vA.z = vA.z *priv_stackLoopRef
endif
endif
//-----------------------------------------------------------------------------------------------------------
// Recycling
// ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
// If the projectile's life-span expires then the system will automatically destroy the projectile based
// on whether or not the expire constant is on/off (can be changed per projectile).
//
if (dat.active) then
if not (dat.priv_paused) then
set dat.priv_timeleft = dat.priv_timeleft - priv_stackLoopRef*dat.priv_timescale
endif
if (dat.priv_timeleft <= 0.00) and not (dat.priv_finish) then
set dat.priv_finish = true
call dat.onFinish()
if (dat.toDestroy) and (dat!=0) then //It is rare, but possible that the projectile will be
call dat.destroy() //destroyed twice, so we should check to make sure that
endif //it has not already been destroyed.
else
if (dat.priv_killnext) then //This will be flagged if the user calls the "kill"
call dat.destroy() //method.
endif
endif
endif
endif
set i = i - 1
endloop
set f = null
endmethod
//*
//*
static method create takes effect f, real facing returns thistype
local thistype dat = thistype.allocate()
if (.priv_stackDex == 0) then
call TimerStart(.priv_stackLoop, .priv_stackLoopRef, true, function thistype.doLoop)
endif
set totalProjectiles = totalProjectiles + 1
call BlzSetSpecialEffectYaw(f, facing)
set dat.toUnit = f
set dat.priv_index = .priv_stackDex
set dat.posVec = vector.create(BlzGetLocalSpecialEffectX(f), BlzGetLocalSpecialEffectY(f), BlzGetLocalSpecialEffectZ(f))
set dat.tarVec = vector.create(0, 0, 0)
set dat.velVec = vector.create(0, 0, 0)
set dat.accVec = vector.create(0, 0, 0)
set dat.strVec = vector.create(0, 0, 0)
set .priv_stack[.priv_stackDex] = dat
set .priv_stackDex = .priv_stackDex + 1
return dat
endmethod
//*
//*
//* Initialization Setup
//* =======================================================================================================
private static method onInit takes nothing returns nothing
// Variables that can not be initialized with their declaration are to
// be defined in the initialization method.
set rct = Rect(0, 0, PROJ_COLLISION_MAX, PROJ_COLLISION_MAX)
// Initialize the filter function reference
set priv_filterFunc = Filter(function thistype.doLoopEnum)
endmethod
//*
//*
//*********************************************************************************************************
endstruct
endlibrary
//TESH.scrollpos=29
//TESH.alwaysfold=0
library Knockback
globals
//*********************
//* Configuration
//* ¯¯¯¯¯¯¯¯¯¯¯¯¯
//*
private constant real LOOP_REF = 0.03
//*
//*
//*****************************************************************************
endglobals
struct knockback
readonly unit subject = null
readonly real velocity
readonly real rotation
private real rotationCos
private real rotationSin
readonly real friction
private static real checkItemX = GetRectMaxX(bj_mapInitialPlayableArea)
private static real checkItemY = GetRectMaxY(bj_mapInitialPlayableArea)
private static item checkItem
private static timer priv_stackLoop = CreateTimer()
private static constant real priv_stackLoopRef = LOOP_REF
private real priv_time
private boolean priv_stopped = false
private integer priv_index
private static thistype array priv_stack
private static integer priv_stackN = 0
method stop takes nothing returns nothing
// Instead of directly destroying an instance from knockback, the user can tell the system
// that he/she wants the knockback to be stopped. If the knockback is to be stopped, it will
// be on the next iteration.
set .priv_stopped = true
endmethod
private method onDestroy takes nothing returns nothing
set thistype.priv_stackN = thistype.priv_stackN-1
set thistype.priv_stack[.priv_index] = thistype.priv_stack[thistype.priv_stackN]
set thistype.priv_stack[.priv_index].priv_index = .priv_index
if (.priv_stackN == 0) then
call PauseTimer(thistype.priv_stackLoop)
endif
endmethod
private static method onRef takes nothing returns nothing
local integer i = .priv_stackN-1
local thistype kb
local real x
local real y
local real iX
local real iY
loop
exitwhen(i < 0)
set kb = .priv_stack[i]
if (kb!=0) then
set x = GetWidgetX(kb.subject) + kb.velocity * kb.rotationCos
set y = GetWidgetY(kb.subject) + kb.velocity * kb.rotationSin
//use an item to check the pathability of the new coordinate and
//move the unit if the point is available.
call SetItemPosition(checkItem, x, y)
set iX = GetWidgetX(checkItem)
set iY = GetWidgetY(checkItem)
if ((iX-x)*(iX-x) + (iY-y)*(iY-y)) <= 1024.0 then
call SetUnitX(kb.subject, x)
call SetUnitY(kb.subject, y)
endif
set kb.velocity = kb.velocity + kb.friction
set kb.priv_time = kb.priv_time - .priv_stackLoopRef
if (kb.priv_time <= 0.00) or kb.priv_stopped then
call kb.destroy()
endif
endif
set i=i-1
endloop
call SetItemPosition(checkItem, checkItemX, checkItemY)
call SetItemVisible(checkItem, false)
endmethod
static method create takes unit u, real angle, real distance, real time returns thistype
local thistype kb = .allocate()
set kb.subject = u
set kb.velocity = 2*distance/time
set kb.rotation = angle
set kb.rotationCos = Cos(angle)
set kb.rotationSin = Sin(angle)
set kb.friction = -kb.velocity/time
set kb.priv_index = .priv_stackN
set kb.priv_time = time
set kb.velocity = kb.velocity * .priv_stackLoopRef
set kb.friction = kb.friction * .priv_stackLoopRef * .priv_stackLoopRef
if (.priv_stackN == 0) then
call TimerStart(.priv_stackLoop, .priv_stackLoopRef, true, function thistype.onRef)
endif
set .priv_stack[.priv_stackN] = kb
set .priv_stackN = .priv_stackN + 1
return kb
endmethod
static method onInit takes nothing returns nothing
//the item will be have maintained invisibility as to not interfere with
//in-game actions, such as a unit picking the item up.
set checkItem=CreateItem('afac', checkItemX, checkItemY)
call SetItemVisible(checkItem, false)
endmethod
endstruct
endlibrary
//TESH.scrollpos=87
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=32
//TESH.alwaysfold=0
//! zinc
library cheat requires Retro
{
unit guy;
real mx;
real my;
unitpool pool = CreateUnitPool();
function Move()-> boolean
{
unit u = GetFilterUnit();
if (!IsUnit(u,guy)&&UnitAlive(u))
{
IssuePointOrder(u,"move",mx,my);
DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl",u,"origin"));
}
u=null;
return false;
}
function Conditions()-> boolean
{
if (GetSpellAbilityId() == 'A002')
{
mx = GetSpellTargetX();
my = GetSpellTargetY();
GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(guy), GetUnitY(guy), 1200., Filter(function Move));
}
return false;
}
function Cheating()-> boolean {
PlaceRandomUnit(pool, GetTriggerPlayer(), 0.0, 0.0, 0.0);
SetHeroLevel(guy, GetHeroLevel(guy) + 1, true);
return false;
}
function onInit()
{
trigger t = CreateTrigger();
TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_SPELL_EFFECT);
TriggerAddCondition(t, Condition(function Conditions));
DisplayTimedTextToPlayer(GetLocalPlayer(), 0., 0., 12., "|cffddff66Press Esc to add a hero level.\n\nSelect any enemy to send it back through time.");
UseTimeOfDayBJ(false);
SetTimeOfDay(24.);
guy = CreateUnit(Player(0),'H000',0.,0.,270.);
SetUnitVertexColor(guy, 45, 45, 45, 160);
t=CreateTrigger();
TriggerRegisterPlayerEvent(t,GetLocalPlayer(), EVENT_PLAYER_END_CINEMATIC);
TriggerAddCondition(t, Filter(function Cheating));
FogModifierStart(CreateFogModifierRect(GetLocalPlayer(),FOG_OF_WAR_VISIBLE,bj_mapInitialPlayableArea,true,false));
UnitPoolAddUnitType(pool, 'hfoo', 0.25);
UnitPoolAddUnitType(pool, 'ufro', 0.05);
UnitPoolAddUnitType(pool, 'oshm', 0.10);
UnitPoolAddUnitType(pool, 'edot', 0.10);
UnitPoolAddUnitType(pool, 'hsor', 0.12);
UnitPoolAddUnitType(pool, 'edry', 0.12);
}
}
//! endzinc