Name | Type | is_array | initial_value |
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
HOW TO IMPORT AERIAL AID? (No need to copy object editor data one by one)
1. Copy the 'Spells Folder' trigger folder containing 'Aerial Aid' and 'Aerial Aid Object Merger'.
2. Configure 'Aerial Aid Object Merger' for generated object editor data. Make sure the rawcodes
are not already used.
3. Save the map, it should take time in "Executing external commands". If there are errors, the
likely cause is you edited something (in the lua script) not meant to be edited.
4. Close the map, then re-open it. You should be able to see new Object Editor Data. Edit the
Aerial Aid ability by checking 'Visible' in Data - Options and setting 'Point Target' as
Target Type. Edit the Deploy Paratroopers ability by checking 'Visible' in Data - Options.
5. Disable or delete 'Aerial Aid Object Merger'
6. Customize the new object editor data such as icon, button position, models, damage, etc.
Then configure the 'Aerial Aid' spell, make sure the rawcodes matches.
7. Test if working properly.
*/
//TESH.scrollpos=0
//TESH.alwaysfold=0
scope AerialAid/*
*************************** Aerial Aid v1.01 *****************************
by Flux
SPELL DESCRIPTION:
Calls for an air support containing goblin paratrooper soldiers. The Plane
is equipped with 2/3/4 guns which fires at enemy units in its field of sight
targeting heroes and units with lower hitpoints. Upon activation, 'Aerial Aid'
is replaced by the sub-skill 'Deploy Paratroopers' which commands your backup
soldiers to parachute from the Plane.
REQUIRES:
(nothing)
OPTIONALLY REQUIRES:
- BonusMod and SetUnitMaxState
If not found, it will generate it's own object editor data to manipulate the
stats per level.
Recommended if your other spells uses BonusMod or SetUnitMaxState as optional
requirement, avoiding redundant functions and object editor abilities.
- DummyRecycler/MissileRecycler
If not found, it will create new units for all the dummy units needed
everytime the spell is cast. Dummy units are used as Parachute, Visual Effects
and Bullet Projectile.
Strongly recommended because this spell creates a lot of units.
- SpellEffectEvent
If not found, it will create another trigger with 'A unit Starts the effect of an
ability' event.
Strongly recommended for maps with huge number of spells.
- Table
If not found, this spell will require 1 hashtable. Hashtable is limited to 256
per map.
Recommended if you used a lot of hashtable in your map to avoid reaching the
hashtable limit.
- WorldBounds
If not found, this spell will create 4 more variables for the map boundary parameters.
Recommended if your other spells uses WorldBounds as optional requirement, avoiding
redundant variables.
---------------------------------- CREDITS: --------------------------------------------
OPTIONAL SYSTEMS:
Bribe - MissileRecycler, SpellEffectEvent, Table
Earth-Fury - BonusMod, SetUnitMaxState
Flux - DummyRecycler
Magtheridon96 - RegisterPlayerUnitEvent
Nestharus - WorldBounds
MODELS:
Anitarf, iNfraNe, Vexorian - Attachable Dummy Model with Pitch Animation
WILL THE ALMIGHTY - Goblin Scout, Goblin Soldier
NatDis - Dwarven Air Force DF 35 Fighter
Fingolfin - Parachute
Grey Knight - Bullet
Tranquil - Snipe Target Effect (edited)
ICONS:
Sin'dorei300 - BTNGoblinRifleman, BTNGoblinFireworker
Marcos DAB - BTNGoblinHeroAviator
SKINS:
Erkki2 - Blue Laser
-----------------------------------------------------------------------------------------*/
native UnitAlive takes unit u returns boolean
//====================================================================================
//============================== CONFIGURATION =======================================
//====================================================================================
globals
//Rawcodes
private constant integer SPELL_ID = 'Aaid'
private constant integer SPELL_ID_DEPLOY = 'Adpy'
private constant integer TOGGLE_ID = 'AAtg'
//Dummy Unit Properties
private constant integer DUMMY_ID = 'dumi'
private constant player DUMMY_OWNER = Player(14)
//Summoned Units
private constant integer PARATROOPER_AIR_ID = 'pair'
private constant integer PLANE_ID = 'plne'
private constant integer TRANSFORM_ID = 'AaTF'
//Periodic Timing of the Spell (Plane and Paratrooper timing)
private constant real TIMEOUT = 0.03125
//Visual Effects
private constant string PARACHUTE_MODEL = "war3mapImported\\Parachute.mdl"
private constant string TARGET_MODEL = "war3mapImported\\Target.mdl"
private constant string TARGET_ATTACHMENT = "overhead"
private constant string LASER_CODE = "LASR" //Laser string code as configured in LightningData.slk
private constant real LASER_DISTANCE = 50 //Max Distance of straight laser lightning in the curve
private constant string BULLET_MODEL = "war3mapImported\\Bullet.mdl"
//Attack and Damage types of Plane Gun
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
//Preload abilities at Map Loading Screen to avoid lag spike the first time it is cast?
private constant boolean PRELOAD = true
//************************************************************************************
//**************************** PLANE CONFIGURATION ***********************************
//************************************************************************************
private constant real PLANE_HEIGHT_INITIAL = 1200
private constant real PLANE_HEIGHT = 600
private constant real PLANE_FALL_SPEED = 700
//************************************************************************************
//***************************** GUN CONFIGURATION ************************************
//************************************************************************************
//When the absolute z difference between the plane and target is PLANE_HEIGHT, the
//plane can't hit units under this range.
//When the absolute z difference between the plane and target is 0, the
//plane can hit units directly in front of it even at close range.
private constant real MIN_HIT_DISTANCE = 500
//Conic Radius for the Plane Gun (degrees)
private constant real PLANE_SIGHT_ANGLE = 40
//Plane Gun will prioritize targeting hero
private constant boolean PRIORITIZE_HERO = true
//Speed of Gun bullets (displacement per second)
private constant real BULLET_SPEED = 3200
//Is the BULLET_SPEED considered a 3D speed? If false, it will be considered as a 2D speed
//meaning the horizontal (ground) speed is constant.
private constant boolean BULLET_SPEED_3D = true
//An optional different TIMEOUT for the Bullet projectiles just in case
private constant real BULLET_TIMEOUT = TIMEOUT
//Collision Size of Bullet
private constant real BULLET_COLLISION = 110
//Gun Field of Sight visible to enemy? Recommended to be set to false
private constant boolean VISIBLE_LASER = false
//************************************************************************************
//************************ PARATROOPER CONFIGURATION *********************************
//************************************************************************************
//When the Paratrooper's height is below GND_HEIGHT, it is now targeted
//as a ground unit
private constant real GND_HEIGHT = 100
//Does the Paratroopers have a Timed Life?
private constant boolean APPLY_TIMED_LIFE = false
//Is the Paratrooper vulnerable while on air?
private constant boolean CAN_BE_HIT = true
//When the current height of the Paratrooper is less than PARACHUTE_ACTIVATE, it will
//activate its parachute
private constant real PARACHUTE_ACTIVATE = 400
//How fast Paratroopers accelerates when their parachute is not yet used (speed per second)
private constant real PARATROOPER_GRAVITY = 150
//How fast Paratroopers will fall upon using their parachute (displacement per second)
private constant real PARACHUTE_FALL_SPEED = 180
//When the Paratrooper hits the ground at this z-speed (displacement per second), it will die!
private constant real SPEED_WITH_DAMAGE = 400
//The lower the values, the greater the friction (for calculation simplicity)
private constant real DRAG_COEFFICIENT = 0.3
endglobals
//************************************************************************************
//*************************** PLANE CONFIGURATION ************************************
//************************************************************************************
//Plane Hitpoints per level
private function PlaneLife takes integer lvl returns real
return 200.0 + 100.0*lvl //300, 400, 500
endfunction
//Plane Bonus armor per level.
private function PlaneArmor takes integer lvl returns integer
return 1 + 1*lvl //2, 3, 4
endfunction
//How fast the Plane moves (displacement per second)
private function PlaneSpeed takes integer lvl returns real
return 350.0 + 0.0*lvl
endfunction
//How long the Plane will last
private function Duration takes integer lvl returns real
return 8.0 + 0.0*lvl //0.0*lvl exist to make the function inline
endfunction
//************************************************************************************
//***************************** GUN CONFIGURATION ************************************
//************************************************************************************
//Search radius for gun target
private function GunRadius takes integer lvl returns real
return 1400 + 0.0*lvl
endfunction
//How fast the gun fires
private function GunFiringRate takes integer lvl returns real
return 0.1 + 0.0*lvl //Every 0.1 second, each Plane gun will fire
endfunction
//Damage per gun bullet
private function GunDamage takes integer lvl returns real
return 4.0 + 1.0*lvl //5, 6, 7
endfunction
//Number of Plane's gun per level
private function GunCount takes integer lvl returns integer
return 1 + lvl //2, 3, 4
endfunction
//What gets hit by the Gun
private function TargetFilter takes unit u, player owner returns boolean
return UnitAlive(u) and IsUnitEnemy(u, owner)
endfunction
//Position of Guns with reference to the Plane's center when the Plane is facing 0 degrees.
//gunNumber starts at 1
private function GunOffsetX takes integer gunNumber, integer totalGunNumber returns real
return 0. + 0*gunNumber + 0*totalGunNumber //All guns will belong to the same x-axis of the Plane
endfunction
private function GunOffsetY takes integer gunNumber, integer totalGunNumber returns real
if totalGunNumber == 2 then
return -150. + gunNumber*100 //-50, 50
elseif totalGunNumber == 3 then
return -100. + gunNumber*50 //-50, 0, 50
elseif totalGunNumber == 4 then
return -100. + gunNumber*40 //-60, -20, 20, 60
endif
return 0.
endfunction
//************************************************************************************
//************************** PARATROOPER CONFIGURATION *******************************
//************************************************************************************
//Paratrooper Hitpoints per level
private function ParatrooperLife takes integer lvl returns real
return 100.0 + 50.0*lvl //150, 200, 250
endfunction
//Paratrooper Bonus Damage per level (See also Attack Cooldown of Paratrooper to balance)
private function ParatrooperAttackDamage takes integer lvl returns integer
return 2 + lvl //3, 4, 5
endfunction
//Paratrooper Bonus Armor per level
private function ParatrooperArmor takes integer lvl returns integer
return lvl //1, 2, 3
endfunction
//Paratrooper Spawnrate (second)
private function SpawnRate takes integer lvl returns real
return 1.0 - 0.2*lvl //Every 0.8/0.6/0.4 second, a Paratrooper will spawn
endfunction
//The maximum number of paratroopers the spell can spawn
private function MaxSpawnCount takes integer lvl returns integer
return 3 + lvl //4, 5, 6
endfunction
static if APPLY_TIMED_LIFE then
//Only applicable if APPLY_TIMED_LIFE = true
//Determines how long each Paratrooper last.
private function TimedLife takes integer lvl returns real
return 7.0 + 1.0*lvl
endfunction
endif
//====================================================================================
//============================ END CONFIGURATION =====================================
//====================================================================================
static if not LIBRARY_BonusMod then
//For the static if to work, the variables must be a static member of a struct
private struct S1 extends array
static constant integer BONUS_ARMOR = 0
static constant integer BONUS_DAMAGE = 1
static integer array pow2
static integer array bonusAbility
endstruct
private function UnitSetBonus2 takes unit u, integer bonusType, integer amount returns boolean
local integer i = 7
loop
if amount >= S1.pow2[i] then
call UnitAddAbility(u, S1.bonusAbility[bonusType*8 + i])
call UnitMakeAbilityPermanent(u, true, S1.bonusAbility[bonusType*7 + i])
set amount = amount - S1.pow2[i]
endif
set i = i - 1
exitwhen i < 0 or amount == 0
endloop
return true
endfunction
private function BonusMod_Init takes nothing returns nothing
local integer i = 0
static if PRELOAD then
local unit u = CreateUnit(DUMMY_OWNER, PARATROOPER_AIR_ID, 0, 0, 0)
endif
// Bonus Mod - Armor abilitys
set S1.bonusAbility[i + 0] = 'ZxA0' // +1
set S1.bonusAbility[i + 1] = 'ZxA1' // +2
set S1.bonusAbility[i + 2] = 'ZxA2' // +4
set S1.bonusAbility[i + 3] = 'ZxA3' // +8
set S1.bonusAbility[i + 4] = 'ZxA4' // +16
set S1.bonusAbility[i + 5] = 'ZxA5' // +32
set S1.bonusAbility[i + 6] = 'ZxA6' // +64
set S1.bonusAbility[i + 7] = 'ZxA7' // +128
// Bonus Mod - Damage abilitys
set i = 8
set S1.bonusAbility[i + 0] = 'ZxB0' // +1
set S1.bonusAbility[i + 1] = 'ZxB1' // +2
set S1.bonusAbility[i + 2] = 'ZxB2' // +4
set S1.bonusAbility[i + 3] = 'ZxB3' // +8
set S1.bonusAbility[i + 4] = 'ZxB4' // +16
set S1.bonusAbility[i + 5] = 'ZxB5' // +32
set S1.bonusAbility[i + 6] = 'ZxB6' // +64
set S1.bonusAbility[i + 7] = 'ZxB7' // +128
//Initialize cached power of 2
set S1.pow2[0] = 1
set i = 1
loop
set S1.pow2[i] = 2*S1.pow2[i - 1]
set i = i + 1
exitwhen i == 8
endloop
//Preload
static if PRELOAD then
set i = 0
loop
exitwhen i > 15
call UnitAddAbility(u, S1.bonusAbility[i])
set i = i + 1
endloop
call RemoveUnit(u)
set u = null
endif
endfunction
endif
static if not LIBRARY_SetUnitMaxState then
private struct S2 extends array
static integer array pow2
endstruct
private function SetUnitMaxState2 takes unit u, unitstate state, real newValue returns nothing
local integer newVal = R2I(newValue)
local integer i = 8
local integer offset
set newVal = newVal - R2I(GetUnitState(u, state))
if newVal > 0 then
set offset = 11
elseif newVal < 0 then
set offset = 2
set newVal = -newVal
else
return
endif
loop
exitwhen newVal == 0 or i < 0
if newVal >= S2.pow2[i] then
call UnitAddAbility(u, 'Zx01')
call SetUnitAbilityLevel(u, 'Zx01', offset + i)
call UnitRemoveAbility(u, 'Zx01')
set newVal = newVal - S2.pow2[i]
else
set i = i - 1
endif
endloop
endfunction
private function SetUnitMaxState_Init takes nothing returns nothing
local integer i = 1
static if PRELOAD then
local unit u = CreateUnit(DUMMY_OWNER, PARATROOPER_AIR_ID, 0, 0, 0)
endif
set S2.pow2[0] = 1
loop
set S2.pow2[i] = 2*S2.pow2[i - 1]
set i = i + 1
exitwhen i == 11
endloop
static if PRELOAD then
call UnitAddAbility(u, 'Zx01')
call RemoveUnit(u)
set u = null
endif
endfunction
endif
static if not LIBRARY_WorldBounds then
//Uses a private struct WorldBounds2
private struct WorldBounds2 extends array
readonly static real maxX
readonly static real maxY
readonly static real minX
readonly static real minY
private static method onInit takes nothing returns nothing
local rect map = GetWorldBounds()
set thistype.maxX = GetRectMaxX(map)
set thistype.maxY = GetRectMaxY(map)
set thistype.minX = GetRectMinX(map)
set thistype.minY = GetRectMinY(map)
call RemoveRect(map)
set map = null
endmethod
endstruct
endif
globals
private location l = Location(0, 0)
private real playMinX
private real playMaxX
private real playMinY
private real playMaxY
endglobals
private struct Paratrooper
private unit soldier
private unit parachute
private effect parachuteModel
private boolean activated
private boolean transformed
private integer damage //bonus damage is lost upon transformation
//that's why this needs to be stored
//Movement
private real z
private real dz
private thistype next
private thistype prev
private static timer t = CreateTimer()
//constants to avoid repeating calculations
//constants to fill in the TIMEOUT
private static constant real GRAVITY = -PARATROOPER_GRAVITY*TIMEOUT
private static constant real SPEED_DAMAGE = SPEED_WITH_DAMAGE*TIMEOUT
private static constant real TERMINAL_VELOCITY = -PARACHUTE_FALL_SPEED*TIMEOUT
static if not LIBRARY_BonusMod then
private static constant integer BONUS_ARMOR = 0
private static constant integer BONUS_DAMAGE = 1
endif
//Calls when the soldier landed
private method destroy takes nothing returns nothing
//Remove from List
set .prev.next = .next
set .next.prev = .prev
if thistype(0).next == 0 then
call PauseTimer(t)
endif
//Remove Parachute
call DestroyEffect(.parachuteModel)
static if LIBRARY_DummyRecycler then
call DummyAddRecycleTimer(.parachute, 3.0)
elseif LIBRARY_MissileRecycler then
call RecycleMissile(.parachute)
else
call UnitApplyTimedLife(.parachute, 'BTLF', 3.0)
endif
set .parachuteModel = null
set .soldier = null
set .parachute = null
call .deallocate()
endmethod
//! textmacro AERIAL_AID_PARATROOPER_UPDATE
set .z = .z + .dz
if .activated then
if .dz < TERMINAL_VELOCITY then
//Apply drag (air-resistance)
set .dz = .dz*DRAG_COEFFICIENT
if .dz > TERMINAL_VELOCITY then
set .dz = TERMINAL_VELOCITY
endif
else
//increase speed due to gravity until it reaches terminal velocity
set .dz = .dz + GRAVITY
endif
call SetUnitX(.parachute, GetUnitX(.soldier))
call SetUnitY(.parachute, GetUnitY(.soldier))
else
set .dz = .dz + GRAVITY
if .z < PARACHUTE_ACTIVATE then
set .activated = true
//Create the Parachute
static if LIBRARY_DummyRecycler then
set .parachute = GetRecycledDummy(GetUnitX(.soldier), GetUnitY(.soldier), .z, GetRandomReal(0, 360))
elseif LIBRARY_MissileRecycler then
set .parachute = GetRecycledMissile(GetUnitX(.soldier), GetUnitY(.soldier), .z, GetRandomReal(0, 360))
else
set .parachute = CreateUnit(DUMMY_OWNER, DUMMY_ID, GetUnitX(.soldier), GetUnitY(.soldier), GetRandomReal(0, 360))
call SetUnitFlyHeight(.parachute, .z, 0)
endif
set .parachuteModel = AddSpecialEffectTarget(PARACHUTE_MODEL, .parachute, "origin")
endif
endif
if .z <= 0 then
call SetUnitFlyHeight(.parachute, .z, 0)
//For realism effect
if .dz < -SPEED_DAMAGE then
call KillUnit(.soldier)
endif
call .destroy()
else
if .activated then
call SetUnitFlyHeight(.parachute, .z, 0)
endif
if .z < GND_HEIGHT and not .transformed then
call UnitAddAbility(.soldier, TRANSFORM_ID)
call UnitRemoveAbility(.soldier, TRANSFORM_ID)
set .transformed = true
static if LIBRARY_BonusMod then
call UnitSetBonus(.soldier, BONUS_DAMAGE, .damage)
else
call UnitSetBonus2(.soldier, BONUS_DAMAGE, .damage)
endif
endif
call SetUnitFlyHeight(.soldier, .z, 0)
endif
//! endtextmacro
private static method pickAll takes nothing returns nothing
local thistype this = thistype(0).next
local real x
local real y
loop
exitwhen this == 0
//Using a textmacro to save a function call per paratrooper instance
//! runtextmacro AERIAL_AID_PARATROOPER_UPDATE()
set this = .next
endloop
endmethod
static method create takes player owner, integer lvl, real x, real y, real z returns thistype
local thistype this = .allocate()
local real spd = 0
//Paratrooper Initialization
set .transformed = false
set .activated = false
set .z = z
set .dz = 0
//Create the soldier
set .soldier = CreateUnit(owner, PARATROOPER_AIR_ID, x, y, GetRandomReal(0, 360))
static if APPLY_TIMED_LIFE then
call UnitApplyTimedLife(.soldier, 'BTLF', TimedLife(lvl))
endif
call MoveLocation(l, x, y)
call SetUnitFlyHeight(.soldier, .z - GetLocationZ(l), 0)
static if not CAN_BE_HIT then
call UnitAddAbility(.soldier, 'Avul')
endif
//Stats
set .damage = ParatrooperAttackDamage(lvl)
static if LIBRARY_BonusMod then
call UnitSetBonus(.soldier, BONUS_DAMAGE, .damage)
call UnitSetBonus(.soldier, BONUS_ARMOR, ParatrooperArmor(lvl))
else
call UnitSetBonus2(.soldier, BONUS_DAMAGE, .damage)
call UnitSetBonus2(.soldier, BONUS_ARMOR, ParatrooperArmor(lvl))
endif
static if LIBRARY_SetUnitMaxState then
call SetUnitMaxState(.soldier, UNIT_STATE_MAX_LIFE, ParatrooperLife(lvl))
else
call SetUnitMaxState2(.soldier, UNIT_STATE_MAX_LIFE, ParatrooperLife(lvl))
endif
//Linked-list
set .next = 0
set .prev = thistype(0).prev
set .next.prev = this
set .prev.next = this
if .prev == 0 then
call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
endif
return this
endmethod
endstruct
private struct Bullet
private player owner
private unit source
private unit u
private effect bulletModel
private real damage
private boolean lineCollision
private real x
private real y
private real z
private real dx
private real dy
private real dz
private real dxy
private real dxyz
private thistype next
private thistype prev
private static timer t = CreateTimer()
private static group g = CreateGroup()
method destroy takes nothing returns nothing
//Remove from List
set .prev.next = .next
set .next.prev = .prev
if thistype(0).next == 0 then
call PauseTimer(t)
endif
//Clean handles
static if LIBRARY_DummyRecycler then
call DummyAddRecycleTimer(.u, 1.0)
elseif LIBRARY_MissileRecycler then
call RecycleMissile(.u)
else
call UnitApplyTimedLife(.u, 'BTLF', 1.0)
endif
call DestroyEffect(.bulletModel)
set .u = null
set .source = null
set .bulletModel = null
call .deallocate()
endmethod
private static constant real BULLET_COLLISION2 = BULLET_COLLISION*BULLET_COLLISION
//! textmacro AERIAL_AID_BULLET_LOCALS
local real x
local real y
local real z
local real dx
local real dy
local real dz
local real height
local real dist
local real checkDist
local unit u
local unit hit
local boolean withinBound
//line data for line collision
local real xPrev
local real yPrev
local real zPrev
local real radius
local real dxL
local real dyL
local real dzL
local real projection
//! endtextmacro
//! textmacro AERIAL_AID_BULLET_UPDATE
if .lineCollision then
//Store last position
set xPrev = .x
set yPrev = .y
set zPrev = .z
set radius = 0.5*.dxy
endif
//Initially no one is hit
set hit = null
//Update position
set .x = .x + .dx
set .y = .y + .dy
set .z = .z + .dz
call MoveLocation(l, .x, .y)
set height = .z - GetLocationZ(l)
static if LIBRARY_WorldBounds then
set withinBound = .x > WorldBounds.minX and .x < WorldBounds.maxX and .y > WorldBounds.minY and .y < WorldBounds.maxY
else
set withinBound = .x > WorldBounds2.minX and .x < WorldBounds2.maxX and .y > WorldBounds2.minY and .y < WorldBounds2.maxY
endif
if withinBound then
call SetUnitX(.u, .x)
call SetUnitY(.u, .y)
call SetUnitFlyHeight(.u, height, 0)
endif
//Check Collision
if .lineCollision then //when bullet moves to fast
set dxL = .x - xPrev
set dyL = .y - yPrev
set dzL = .z - zPrev
set checkDist = (.dxyz + BULLET_COLLISION)*(.dxyz + BULLET_COLLISION)
call GroupEnumUnitsInRange(g, xPrev + 0.5*.dx, yPrev + 0.5*.dy, radius + 2*BULLET_COLLISION, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
set x = GetUnitX(u)
set y = GetUnitY(u)
call MoveLocation(l, x, y)
set z = GetLocationZ(l) + GetUnitFlyHeight(u)
set dx = x - xPrev
set dy = y - yPrev
set dz = z - zPrev
//Scalar Projection of vector (xPrev -> x, yPrev -> y) on line (xPrev -> x, yPrev -> y)
set projection = (dx*dxL + dy*dyL + dz*dzL)/(.dxyz)
set dist = dx*dx + dy*dy + dz*dz //distance of target to line point1
//dist - projection*projection = 3D distance of the picked unit to the line
if dist - projection*projection <= BULLET_COLLISION2 then
if TargetFilter(u, .owner) then
if dist < checkDist then //if it is closer to the line point1
set checkDist = dist
set hit = u
endif
endif
endif
endloop
else
set checkDist = BULLET_COLLISION2
call GroupEnumUnitsInRange(g, .x, .y, BULLET_COLLISION, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
set x = GetUnitX(u)
set y = GetUnitY(u)
call MoveLocation(l, x, y)
set z = GetLocationZ(l) + GetUnitFlyHeight(u)
set dx = x - .x
set dy = y - .y
set dz = z - .z
//Check if it collides in 3D
set dist = dx*dx + dy*dy + dz*dz //distance of target to bullet
if dist <= checkDist then
if TargetFilter(u, .owner) then
set checkDist = dist
set hit = u
endif
endif
endloop
endif
if hit != null then
call UnitDamageTarget(.source, hit, .damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call .destroy()
set hit = null
elseif height < 0 or height > 1500 or not withinBound then
call .destroy()
endif
//! endtextmacro
private static method pickAll takes nothing returns nothing
local thistype this = thistype(0).next
//! runtextmacro AERIAL_AID_BULLET_LOCALS()
loop
exitwhen this == 0
//Use textmacro to save a function call per bullet instance
//! runtextmacro AERIAL_AID_BULLET_UPDATE()
set this = .next
endloop
endmethod
static method create takes real x1, real y1, real z1, real x2, real y2, real z2, unit source, player owner, real damage returns thistype
local thistype this = .allocate()
local real dx = x2 - x1
local real dy = y2 - y1
local real dz = z2 - z1
local real facing = Atan2(dy, dx)
local real pitch = Atan(dz/SquareRoot(dx*dx + dy*dy))
static if BULLET_SPEED_3D then
set .dxy = BULLET_SPEED*BULLET_TIMEOUT*Cos(pitch)
else
set .dxy = BULLET_SPEED*BULLET_TIMEOUT
endif
//Initialize positions and speeds
set .x = x1
set .y = y1
set .z = z1
set .dx = dxy*Cos(facing)
set .dy = dxy*Sin(facing)
static if BULLET_SPEED_3D then
set .dz = BULLET_SPEED*BULLET_TIMEOUT*Sin(pitch)
set .dxyz = BULLET_SPEED*BULLET_TIMEOUT
else
set .dz = BULLET_SPEED*BULLET_TIMEOUT*Tan(pitch)
if pitch == 0 then
set .dxyz = .dxy
else
set .dxyz = .dz/Sin(pitch)
endif
endif
set .lineCollision = dxy > BULLET_COLLISION
//Initialize bullet interact properties
set .source = source
set .owner = owner
set .damage = damage
//Create Bullet and set appropriate angle
//Safety precaution, make sure bullet is not outside bounds
static if LIBRARY_WorldBounds then
if x1 > WorldBounds.maxX then
set x1 = WorldBounds.maxX
elseif x1 < WorldBounds.minX then
set x1 = WorldBounds.minX
endif
if y1 > WorldBounds.maxY then
set y1 = WorldBounds.maxY
elseif y1 < WorldBounds.minY then
set y1 = WorldBounds.minY
endif
else
if x1 > WorldBounds2.maxX then
set x1 = WorldBounds2.maxX
elseif x1 < WorldBounds2.minX then
set x1 = WorldBounds2.minX
endif
if y1 > WorldBounds2.maxY then
set y1 = WorldBounds2.maxY
elseif y1 < WorldBounds2.minY then
set y1 = WorldBounds2.minY
endif
endif
call MoveLocation(l, x1, y1)
static if LIBRARY_DummyRecycler then
set .u = GetRecycledDummy(x1, y1, z1 - GetLocationZ(l), facing*bj_RADTODEG)
elseif LIBRARY_MissileRecycler then
set .u = GetRecycledMissile(x1, y1, z1 - GetLocationZ(l), facing*bj_RADTODEG)
else
set .u = CreateUnit(DUMMY_OWNER, DUMMY_ID, x1, y1, facing*bj_RADTODEG)
call SetUnitFlyHeight(.u, z1 - GetLocationZ(l), 0)
endif
set .bulletModel = AddSpecialEffectTarget(BULLET_MODEL, .u, "origin")
call SetUnitAnimationByIndex(.u, R2I(pitch*bj_RADTODEG + 90.5))
//Linked-list
set .next = 0
set .prev = thistype(0).prev
set .next.prev = this
set .prev.next = this
if .prev == 0 then
call TimerStart(t, BULLET_TIMEOUT, true, function thistype.pickAll)
endif
return this
endmethod
endstruct
private struct Gun
private effect targetSfx
readonly unit target
readonly real dx
readonly real dy
readonly thistype next
readonly thistype prev
method destroy takes nothing returns nothing
//Remove from List
set .prev.next = .next
set .next.prev = .prev
if .target != null then
call DestroyEffect(.targetSfx)
endif
set .targetSfx = null
set .target = null
call .deallocate()
endmethod
method update takes unit newTarget returns nothing
if newTarget != .target then
if .target != null then
call DestroyEffect(.targetSfx)
endif
if newTarget != null then
set .targetSfx = AddSpecialEffectTarget(TARGET_MODEL, newTarget, TARGET_ATTACHMENT)
endif
endif
set .target = newTarget
endmethod
static method create takes thistype head, real dx, real dy returns thistype
local thistype this = .allocate()
set .dx = dx
set .dy = dy
set .target = null
set .next = head.next
set .prev = head
set .next.prev = this
set .prev.next = this
return this
endmethod
static method head takes nothing returns thistype
local thistype this = .allocate()
set .next = 0
set .prev = 0
return this
endmethod
endstruct
//The self-sorting list, using an Insertion Sort algorithm
private struct List
private real value
readonly unit unit
readonly thistype next
readonly thistype prev
method destroy takes nothing returns nothing
//Remove List
set .prev.next = .next
set .next.prev = .prev
set .value = 0
set .next = 0
set .unit = null
call .deallocate()
endmethod
static method empty takes nothing returns nothing
local thistype this = thistype(0).next
loop
exitwhen this == 0
call .destroy()
set this = thistype(0).next
endloop
endmethod
static method add takes unit u, real hp, integer count returns nothing
local thistype node = thistype(0).next //head node is thistype(0)
local boolean insert = true
local thistype this
static if PRIORITIZE_HERO then
local boolean isHero = IsUnitType(u, UNIT_TYPE_HERO)
endif
//Insert in the current list
loop
exitwhen node == 0
static if PRIORITIZE_HERO then
if isHero then
exitwhen hp < node.value or not IsUnitType(node.unit, UNIT_TYPE_HERO)
else
exitwhen hp < node.value and not IsUnitType(node.unit, UNIT_TYPE_HERO)
endif
else
exitwhen hp < node.value
endif
set count = count - 1
//If it will not get included in the count anyway, end the insertion and do not insert it in the list
if count == 0 then
set insert = false
exitwhen true
endif
set node = node.next
endloop
//Insert 'this' before 'node'
if insert then
set this = .allocate()
set .unit = u
set .value = hp
set .next = node
set .prev = node.prev
set .next.prev = this
set .prev.next = this
endif
endmethod
endstruct
private struct Effect
private lightning laser
private real x1
private real y1
private real x2
private real y2
private thistype next
private thistype prev
private method destroy takes nothing returns nothing
//Remove from List
set .prev.next = .next
set .next.prev = .prev
//Clean ups
call DestroyLightning(.laser)
set .laser = null
call .deallocate()
endmethod
method destroyAll takes nothing returns nothing
local thistype node = .next
loop
exitwhen node == 0
call node.destroy()
set node = node.next
endloop
call .destroy()
endmethod
//Used on head instances
method updateHead takes real dx, real dy returns nothing
local thistype node = .next
loop
exitwhen node == 0
set node.x1 = node.x1 + dx
set node.y1 = node.y1 + dy
set node.x2 = node.x2 + dx
set node.y2 = node.y2 + dy
call MoveLightningEx(node.laser, false, node.x1, node.y1, 0, node.x2, node.y2, 0)
set node = node.next
endloop
endmethod
static method create takes thistype head, real x1, real y1, real x2, real y2, player p returns thistype
local thistype this = .allocate()
set .laser = AddLightning(LASER_CODE, false, x1, y1, x2, y2)
static if not VISIBLE_LASER then
if IsPlayerEnemy(GetLocalPlayer(), p) then
call SetLightningColor(.laser, 0, 0, 0, 0)
endif
endif
set .x1 = x1
set .y1 = y1
set .x2 = x2
set .y2 = y2
set .next = head.next
set .prev = head
set .next.prev = this
set .prev.next = this
return this
endmethod
private static constant real PLANE_SIGHT_HALF_ANGLE_RAD = 0.5*PLANE_SIGHT_ANGLE*bj_DEGTORAD
static method head takes real x, real y, real angle, real radius, player owner returns thistype
local thistype this = .allocate()
local real dx = Cos(angle + PLANE_SIGHT_HALF_ANGLE_RAD)
local real dy = Sin(angle + PLANE_SIGHT_HALF_ANGLE_RAD)
local real limit = angle + PLANE_SIGHT_HALF_ANGLE_RAD
local real tempAngle
local real tempX
local real tempY
set .next = 0
set .prev = 0
//Create AREA_SFX radius
call Effect.create(this, x + MIN_HIT_DISTANCE*dx, y + MIN_HIT_DISTANCE*dy, x + radius*dx, y + radius*dy, owner)
set dx = Cos(angle - PLANE_SIGHT_HALF_ANGLE_RAD)
set dy = Sin(angle - PLANE_SIGHT_HALF_ANGLE_RAD)
call Effect.create(this, x + MIN_HIT_DISTANCE*dx, y + MIN_HIT_DISTANCE*dy, x + radius*dx, y + radius*dy, owner)
//Create AREA_SFX curve
//! textmacro AERIAL_AID_AREA_SFX_CURVE takes RADIUS
set tempAngle = angle - PLANE_SIGHT_HALF_ANGLE_RAD
set tempX = x + $RADIUS$*Cos(tempAngle)
set tempY = y + $RADIUS$*Sin(tempAngle)
loop
set tempAngle = tempAngle + LASER_DISTANCE/$RADIUS$
exitwhen tempAngle > limit
set dx = x + $RADIUS$*Cos(tempAngle)
set dy = y + $RADIUS$*Sin(tempAngle)
call Effect.create(this, tempX, tempY, dx, dy, owner)
set tempX = dx
set tempY = dy
endloop
call Effect.create(this, tempX, tempY, x + $RADIUS$*Cos(limit), y + $RADIUS$*Sin(limit), owner)
//! endtextmacro
//! runtextmacro AERIAL_AID_AREA_SFX_CURVE("MIN_HIT_DISTANCE")
//! runtextmacro AERIAL_AID_AREA_SFX_CURVE("radius")
return this
endmethod
endstruct
private struct Plane
//Spell Mechanics
private unit caster
private unit plane
private player owner
private integer lvl
private boolean toggled
private boolean outside
private real duration
//Gun-related
private Gun gunHead
private real radius
private integer gunCount
private real gunDamage
private real gunFireTime
private real gunCtr //counter for gun firing rate
//Paratrooper-related
private real soldierSpawnRate
private real soldierCtr //counter for paratrooper deployment
private boolean deploy
private integer spawnCtr //number of paratrooper deployed
//Visual Effect related
private Effect sfxHead
//Movement
private real x //Contains the location of the Plane, the Plane is not meant
private real y //to be moved by Moving effects such as Swap, Hook, Knockback, etc.
private real z
private real angle
private real dx
private real dy
private real dz
private thistype next
private thistype prev
private static timer t = CreateTimer()
private static group g = CreateGroup()
static if LIBRARY_Table then
private static Table tb
private static Table tbCount
else
private static hashtable hash = InitHashtable()
endif
static if not LIBRARY_BonusMod then
private static constant integer BONUS_ARMOR = 0
endif
private method destroy takes nothing returns nothing
local integer id = GetHandleId(.caster)
//Remove from List
set .prev.next = .next
set .next.prev = .prev
if thistype(0).next == 0 then
call PauseTimer(t)
endif
//Table Clean-up
static if LIBRARY_Table then
set tbCount[id] = tbCount[id] - 1
if tbCount[id] == 0 then
call tb.remove(id)
call tbCount.remove(id)
call UnitRemoveAbility(.caster, TOGGLE_ID)
call UnitRemoveAbility(.caster, SPELL_ID_DEPLOY)
endif
else
call SaveInteger(hash, id, 1, LoadInteger(hash, id, 1) - 1)
if LoadInteger(hash, id, 1) == 0 then
call FlushChildHashtable(hash, id)
call UnitRemoveAbility(.caster, TOGGLE_ID)
call UnitRemoveAbility(.caster, SPELL_ID_DEPLOY)
endif
endif
//Destroy Handles
if UnitAlive(.plane) then
call RemoveUnit(.plane)
endif
//For perfect clean-up
set .caster = null
set .plane = null
//Recycle Index
call .deallocate()
endmethod
//Avoid recalculating them everytime
private static constant real PLANE_SIGHT_HALF_ANGLE_RAD = 0.5*PLANE_SIGHT_ANGLE*bj_DEGTORAD
private static constant real SLOPE_CHECK = MIN_HIT_DISTANCE/PLANE_HEIGHT
private method update takes nothing returns nothing
local Gun gun = .gunHead.next
local boolean withinPlay //within Playable Area
local List list
local unit u
local real x
local real y
local real z
local real dx
local real dy
local real angle
local real diff
local real height
//Movement
set .x = .x + .dx
set .y = .y + .dy
//Safety check
set withinPlay = .x < playMaxX and .x > playMinX and .y < playMaxY and .y > playMinY
if withinPlay then
call SetUnitX(.plane, .x)
call SetUnitY(.plane, .y)
endif
//Hide/Unhide the Plane when leaving/entering playable map area
if .outside then
if withinPlay then
set .outside = false
call ShowUnit(.plane, true)
endif
else
if not withinPlay then
set .outside = true
call ShowUnit(.plane, false)
endif
endif
if .duration > 0 and UnitAlive(.plane) then
//Change Plane Height
if .z > PLANE_HEIGHT then
set .z = .z + .dz
if .z < PLANE_HEIGHT then
set .z = PLANE_HEIGHT
endif
call SetUnitFlyHeight(.plane, .z, 0)
endif
//Will only do some action if it has stable height
if .z == PLANE_HEIGHT then
//Create SFX Laser Border when the Plane is active, it will only happen once
if .sfxHead == 0 then
set .sfxHead = Effect.head(.x, .y, .angle, .radius, .owner)
else
//Update SFX
call .sfxHead.updateHead(.dx, .dy)
endif
set .duration = .duration - TIMEOUT
//Targeting
call GroupEnumUnitsInRange(g, .x, .y, .radius, null)
call MoveLocation(l, .x, .y)
set z = GetLocationZ(l) + PLANE_HEIGHT
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if TargetFilter(u, .owner) then
set x = GetUnitX(u)
set y = GetUnitY(u)
set dx = x - .x
set dy = y - .y
set angle = Atan2(dy, dx)
set diff = RAbsBJ(angle - .angle)
//Check if it is within range of gun angle
if RMinBJ(diff, 2*bj_PI - diff) <= PLANE_SIGHT_HALF_ANGLE_RAD then
//Then check if it is within range of gun pitch
call MoveLocation(l, x, y)
//GetUnitFlyHeight(u) + GetLocationZ(l) = absolute z of the target
//recycle variable 'real angle' as the check distance
set angle = SLOPE_CHECK*(z - GetUnitFlyHeight(u) - GetLocationZ(l)) //recycled real-type variable
if dx*dx + dy*dy >= angle*angle then
call List.add(u, GetWidgetLife(u), .gunCount)
endif
endif
endif
endloop
//Update Gun Targets
set list = List(0).next
loop
exitwhen gun == 0
call gun.update(list.unit)
set list = list.next
//If there are more guns than targets, go back to the first target so that all guns are active
if list == 0 then
set list = List(0).next
endif
set gun = gun.next
endloop
//Empty the list
call List.empty()
//Are the guns ready to fire?
set .gunCtr = .gunCtr + TIMEOUT
if .gunCtr >= .gunFireTime then
set .gunCtr = .gunCtr - .gunFireTime
set gun = .gunHead.next
loop
exitwhen gun == 0 or gun.target == null
set x = GetUnitX(gun.target)
set y = GetUnitY(gun.target)
call MoveLocation(l, x, y)
call Bullet.create(.x + gun.dx, .y + gun.dy, z, x, y, GetUnitFlyHeight(gun.target) + GetLocationZ(l), .plane, .owner, .gunDamage)
set gun = gun.next
endloop
endif
//Are paratroopers commanded to deploy?
if .deploy and .spawnCtr < MaxSpawnCount(.lvl) then
set .soldierCtr = .soldierCtr + TIMEOUT
if .soldierCtr >= .soldierSpawnRate then
set .soldierCtr = .soldierCtr - .soldierSpawnRate
if withinPlay then
call Paratrooper.create(.owner, .lvl, .x, .y, .z)
set .spawnCtr = .spawnCtr + 1
endif
endif
endif
endif
else
//Destroy Guns
if .gunHead != 0 then
loop
exitwhen gun == 0
call gun.destroy()
set gun = gun.next
endloop
call .gunHead.destroy()
set .gunHead = 0
endif
//Destroy Effects
if .sfxHead != 0 then
call .sfxHead.destroyAll()
set .sfxHead = 0
endif
//Make the plane rise again until it is no longer seen in the screen
set .z = .z - .dz
if .z > PLANE_HEIGHT_INITIAL or not UnitAlive(.plane) then
call .destroy()
else
call SetUnitFlyHeight(.plane, .z, 0)
endif
endif
endmethod
private static method pickAll takes nothing returns nothing
local thistype this = thistype(0).next
loop
exitwhen this == 0
call .update()
set this = .next
endloop
endmethod
//Delayed toggle
private static method toggle takes nothing returns nothing
local timer delay = GetExpiredTimer()
static if LIBRARY_Table then
local thistype this = tb[GetHandleId(delay)]
else
local thistype this = LoadInteger(hash, GetHandleId(delay), 0)
endif
set .toggled = true
call UnitAddAbility(.caster, TOGGLE_ID)
call UnitAddAbility(.caster, SPELL_ID_DEPLOY)
call DestroyTimer(delay)
set delay = null
endmethod
private static method onCast takes nothing returns nothing
local thistype this = .allocate()
local timer delay = CreateTimer()
local integer i = 1
local integer id
local real x
local real y
local real cos
local real sin
local real spd
local real gunX
local real gunY
//Spell Data and Initializations
set .caster = GetTriggerUnit()
set .owner = GetTriggerPlayer()
set .deploy = false
set .sfxHead = 0
set .lvl = GetUnitAbilityLevel(.caster, SPELL_ID)
set .spawnCtr = 0
set .gunHead = Gun.head()
set .gunCtr = 0
set .gunFireTime = GunFiringRate(.lvl)
set .gunDamage = GunDamage(.lvl)
set .soldierCtr = 0
set .soldierSpawnRate = SpawnRate(.lvl)
set .duration = Duration(.lvl)
set .radius = GunRadius(.lvl)
set x = GetSpellTargetX()
set y = GetSpellTargetY()
set .angle = Atan2(y - GetUnitY(.caster), x - GetUnitX(.caster))
set cos = Cos(.angle)
set sin = Sin(.angle)
set .gunCount = GunCount(.lvl)
loop
exitwhen i > .gunCount
set gunX = GunOffsetX(i, .gunCount)
set gunY = GunOffsetY(i, .gunCount)
call Gun.create(.gunHead, gunX*cos - gunY*sin, gunY*cos + gunX*sin)
set i = i + 1
endloop
//Movement Data
set spd = PlaneSpeed(.lvl)*TIMEOUT
set .dx = spd*cos
set .dy = spd*sin
set .dz = -PLANE_FALL_SPEED*TIMEOUT
//Calculate the spawn loc based on duration and target location with an offset for the rise/fall of the plane
set .x = x - (0.5*.duration + (PLANE_HEIGHT_INITIAL - PLANE_HEIGHT)/PLANE_FALL_SPEED)*PlaneSpeed(.lvl)*cos
set .y = y - (0.5*.duration + (PLANE_HEIGHT_INITIAL - PLANE_HEIGHT)/PLANE_FALL_SPEED)*PlaneSpeed(.lvl)*sin
set .z = PLANE_HEIGHT_INITIAL
if .x > playMaxX or .x < playMinX or .y > playMaxY or .y < playMinY then
//Safety precaution, make sure plane is not outside bounds to prevent fatal error
//recycle 'x' and 'y' as the spawn loc, because .x and .y must not be overwritten
set x = .x
set y = .y
static if LIBRARY_WorldBounds then
if x > WorldBounds.maxX then
set x = WorldBounds.maxX
elseif x < WorldBounds.minX then
set x = WorldBounds.minX
endif
if y > WorldBounds.maxY then
set y = WorldBounds.maxY
elseif y < WorldBounds.minY then
set y = WorldBounds.minY
endif
else
if x > WorldBounds2.maxX then
set x = WorldBounds2.maxX
elseif x < WorldBounds2.minX then
set x = WorldBounds2.minX
endif
if y > WorldBounds2.maxY then
set y = WorldBounds2.maxY
elseif y < WorldBounds2.minY then
set y = WorldBounds2.minY
endif
endif
set .outside = true
set .plane = CreateUnit(.owner, PLANE_ID, x, y, .angle*bj_RADTODEG)
call ShowUnit(.plane, false)
else
set .outside = false
set .plane = CreateUnit(.owner, PLANE_ID, .x, .y, .angle*bj_RADTODEG)
endif
call SetUnitFlyHeight(.plane, .z, 0)
static if not CONTROLLABLE then
call SetUnitTurnSpeed(.plane, 0.0)
endif
//Stats
static if LIBRARY_BonusMod then
call UnitSetBonus(.plane, BONUS_ARMOR, PlaneArmor(lvl))
else
call UnitSetBonus2(.plane, BONUS_ARMOR, PlaneArmor(lvl))
endif
static if LIBRARY_SetUnitMaxState then
call SetUnitMaxState(.plane, UNIT_STATE_MAX_LIFE, PlaneLife(lvl))
else
call SetUnitMaxState2(.plane, UNIT_STATE_MAX_LIFE, PlaneLife(lvl))
endif
//Store caster handle id for onDeploy data retrieval
set id = GetHandleId(.caster)
static if LIBRARY_Table then
set tb[id] = this
set tbCount[id] = tbCount[id] + 1
set tb[GetHandleId(delay)] = this
else
call SaveInteger(hash, id, 0, this)
call SaveInteger(hash, id, 1, LoadInteger(hash, id, 1) + 1)
call SaveInteger(hash, GetHandleId(delay), 0, this)
endif
//Toggle Ability
call TimerStart(delay, 0., false, function thistype.toggle)
//Linked-list
set .next = 0
set .prev = thistype(0).prev
set .next.prev = this
set .prev.next = this
if .prev == 0 then
call TimerStart(t, TIMEOUT, true, function thistype.pickAll)
endif
endmethod
private static method onDeploy takes nothing returns nothing
static if LIBRARY_Table then
local thistype this = tb[GetHandleId(GetTriggerUnit())]
else
local thistype this = LoadInteger(hash, GetHandleId(GetTriggerUnit()), 0)
endif
set .deploy = true
//Toggle Ability
set .toggled = false
call UnitRemoveAbility(.caster, TOGGLE_ID)
call UnitRemoveAbility(.caster, SPELL_ID_DEPLOY)
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method castCondition takes nothing returns boolean
local integer spell = GetSpellAbilityId()
if spell == SPELL_ID then
call thistype.onCast()
elseif spell == SPELL_ID_DEPLOY then
call thistype.onDeploy()
endif
return false
endmethod
endif
private static method onInit takes nothing returns nothing
local integer i = 0
static if PRELOAD then
local unit u1 = CreateUnit(DUMMY_OWNER, PARATROOPER_AIR_ID, 0, 0, 0)
local unit u2 = CreateUnit(DUMMY_OWNER, PLANE_ID, 0, 0, 0)
endif
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
call RegisterSpellEffectEvent(SPELL_ID_DEPLOY, function thistype.onDeploy)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.castCondition))
endif
static if LIBRARY_Table then
set tb = Table.create()
set tbCount = Table.create()
endif
static if not LIBRARY_BonusMod then
call BonusMod_Init()
endif
static if not LIBRARY_SetUnitMaxState then
call SetUnitMaxState_Init()
endif
//For toggling from Aerial Aid -> Deploy Paratroopers and vice versa
loop
exitwhen i == bj_MAX_PLAYER_SLOTS
call SetPlayerAbilityAvailable(Player(i), TOGGLE_ID, false)
set i = i + 1
endloop
//Playable Map bounds for non-locust units
set playMinX = GetRectMinX(bj_mapInitialPlayableArea)
set playMaxX = GetRectMaxX(bj_mapInitialPlayableArea)
set playMinY = GetRectMinY(bj_mapInitialPlayableArea)
set playMaxY = GetRectMaxY(bj_mapInitialPlayableArea)
static if PRELOAD then
call UnitAddAbility(u1, TRANSFORM_ID)
call UnitAddAbility(u2, SPELL_ID_DEPLOY)
call UnitAddAbility(u2, SPELL_ID)
call RemoveUnit(u1)
call RemoveUnit(u2)
set u1 = null
set u2 = null
endif
endmethod
endstruct
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
//! externalblock extension=lua ObjectMerger $FILENAME$
//====================================================================================
//============================== CONFIGURATION =======================================
//====================================================================================
//Configure these rawcodes here if you want
//Abilities
//! i local spellId = "Aaid"
//! i local deployId = "Adpy"
//! i local toggleId = "AAtg"
//! i local transform = "AaTF"
//Unit-Type
//! i local plane = "plne"
//! i local troopAir = "pair"
//! i local troopGround = "pgnd"
//====================================================================================
//============================ END CONFIGURATION =====================================
//====================================================================================
//Create new units
//! i setobjecttype("units")
//Plane
//! i createobject("hgyr",plane)
//! i makechange(current,"uabi","ACmi")
//! i makechange(current,"umxr","0.0")
//! i makechange(current,"umxp","0.0")
//! i makechange(current,"uico","ReplaceableTextures\\CommandButtons\\BTNGoblinHeroAviator.blp")
//! i makechange(current,"umdl","war3mapImported\\DFA_D35.mdl")
//! i makechange(current,"usca","1.5")
//! i makechange(current,"umvs","1")
//! i makechange(current,"umvr","0.0")
//! i makechange(current,"uaen","0")
//! i makechange(current,"ufoo","0")
//! i makechange(current,"urac","creeps")
//! i makechange(current,"usid","1600")
//! i makechange(current,"usin","1600")
//! i makechange(current,"upgr","")
//! i makechange(current,"unam","Goblin Plane")
//Paratrooper (Flying)
//! i createobject("hspt",troopAir)
//! i makechange(current,"udaa","")
//! i makechange(current,"uabi","")
//! i makechange(current,"umdl","war3mapImported\\GoblinSoldier.mdl")
//! i makechange(current,"uico","ReplaceableTextures\\CommandButtons\\BTNGoblinFireworker.blp")
//! i makechange(current,"usca","1.0")
//! i makechange(current,"udp1","0.1")
//! i makechange(current,"ua1c","0.1")
//! i makechange(current,"ua1b","0")
//! i makechange(current,"ua1s","1")
//! i makechange(current,"ua1m","war3mapImported\\Bullet.mdl")
//! i makechange(current,"ua1z","2000")
//! i makechange(current,"ua1r","700")
//! i makechange(current,"ua1g","air,debris,ground,item,structure,ward")
//! i makechange(current,"utar","air")
//! i makechange(current,"umvs","1")
//! i makechange(current,"umvt","fly")
//! i makechange(current,"ufoo","0")
//! i makechange(current,"umpm","0")
//! i makechange(current,"urac","creeps")
//! i makechange(current,"ureq","")
//! i makechange(current,"upgr","")
//! i makechange(current,"unam","Paratrooper")
//! i makechange(current,"unsf"," (Flying)")
//Paratrooper (Ground)
//! i createobject("hspt",troopGround)
//! i makechange(current,"udaa","")
//! i makechange(current,"uabi","")
//! i makechange(current,"umdl","war3mapImported\\GoblinSoldier.mdl")
//! i makechange(current,"uico","ReplaceableTextures\\CommandButtons\\BTNGoblinFireworker.blp")
//! i makechange(current,"usca","1.0")
//! i makechange(current,"udp1","0.1")
//! i makechange(current,"ua1c","0.1")
//! i makechange(current,"ua1b","0")
//! i makechange(current,"ua1s","1")
//! i makechange(current,"ua1m","war3mapImported\\Bullet.mdl")
//! i makechange(current,"ua1z","2000")
//! i makechange(current,"ua1r","700")
//! i makechange(current,"ua1g","air,debris,ground,item,structure,ward")
//! i makechange(current,"utar","ground")
//! i makechange(current,"ufoo","0")
//! i makechange(current,"umpm","0")
//! i makechange(current,"urac","creeps")
//! i makechange(current,"ureq","")
//! i makechange(current,"upgr","")
//! i makechange(current,"unam","Paratrooper")
//! i makechange(current,"unsf"," (Ground)")
//Create new abilities
//! i setobjecttype("abilities")
//Aerial Aid
//! i createobject("ANcl",spellId)
//! i makechange(current,"abpx","0")
//! i makechange(current,"abpy","2")
//! i makechange(current,"acat","")
//! i makechange(current, "aart", "ReplaceableTextures\\CommandButtons\\BTNGoblinHeroAviator.blp")
//! i makechange(current, "arar", "ReplaceableTextures\\CommandButtons\\BTNGoblinHeroAviator.blp")
//! i makechange(current,"aeat","")
//! i makechange(current,"atat","")
//! i makechange(current,"arhk","Q")
//! i makechange(current,"ahky","Q")
//! i makechange(current, "aret","Learn Aerial Aid [|cffffcc00Q|r] - [|cffffcc00Level %d|r]")
//! i makechange(current,"arut","Calls for an air support containing goblin paratrooper soldiers. The Plane is equipped with guns which fires at enemy units in its field of sight targeting heroes and units with lower hitpoints. Upon activation, 'Aerial Aid' is replaced by the sub-skill 'Deploy Paratroopers' which commands your backup soldiers to parachute from the Plane.|n|n|cffffcc00Level 1|r|n Plane: HP: 300 / Damage: 5 / Armor: +2 / Guns: 2|n Paratrooper: HP: 150 / Damage: 3 / Armor: +1 / Qty: 4|n|cffffcc00Level 2|r|n Plane: HP: 400 / Damage: 6 / Armor: +3 / Guns: 3|n Paratrooper: HP: 200 / Damage: 4 / Armor: +2 / Qty: 5|n|cffffcc00Level 3|r|n Plane: HP: 500 / Damage: 7 / Armor: +4 / Guns: 4|n Paratrooper: HP: 250 / Damage: 5 / Armor: +3 / Qty: 6|n|n|cffc3dbffCooldown: 50/45/40|r")
//! i makechange(current,"anam","Aerial Aid")
//! i makechange(current,"Ncl6","1","flare")
//! i makechange(current,"Ncl6","2","flare")
//! i makechange(current,"Ncl6","3","flare")
//! i makechange(current,"Ncl5","1","0")
//! i makechange(current,"Ncl5","2","0")
//! i makechange(current,"Ncl5","3","0")
//! i makechange(current,"Ncl1","1","0.3")
//! i makechange(current,"Ncl1","2","0.3")
//! i makechange(current,"Ncl1","3","0.3")
//! i makechange(current,"aran","1","1500")
//! i makechange(current,"aran","2","1500")
//! i makechange(current,"aran","3","1500")
//! i makechange(current,"acdn","1","50")
//! i makechange(current,"acdn","2","45")
//! i makechange(current,"acdn","3","40")
//! i makechange(current,"amcs","1","100")
//! i makechange(current,"amcs","2","100")
//! i makechange(current,"amcs","3","100")
//! i makechange(current,"atp1","1","Aerial Aid [|cffffcc00Q|r] - [|cffffcc00Level 1|r]")
//! i makechange(current,"atp1","2","Aerial Aid [|cffffcc00Q|r] - [|cffffcc00Level 2|r]")
//! i makechange(current,"atp1","3","Aerial Aid [|cffffcc00Q|r] - [|cffffcc00Level 3|r]")
//! i makechange(current,"aub1","1","Calls for an air support containing goblin paratrooper soldiers. The Plane is equipped with 2 guns which fires at enemy units in its field of sight targeting heroes and units with lower hitpoints. Upon activation, 'Aerial Aid' is replaced by the sub-skill 'Deploy Paratroopers' which commands your backup soldiers to parachute from the Plane.|n|n|cffffcc00Plane|r: HP: 300 / Damage: 5 / Armor: +2 / Guns: 2|n|cffffcc00Paratrooper|r: HP: 150 / Damage: 3 / Armor: +1 / Qty: 4|n|n|cffc3dbffCooldown: 50|r")
//! i makechange(current,"aub1","2","Calls for an air support containing goblin paratrooper soldiers. The Plane is equipped with 3 guns which fires at enemy units in its field of sight targeting heroes and units with lower hitpoints. Upon activation, 'Aerial Aid' is replaced by the sub-skill 'Deploy Paratroopers' which commands your backup soldiers to parachute from the Plane.|n|n|cffffcc00Plane|r: HP: 400 / Damage: 6 / Armor: +3 / Guns: 3|n|cffffcc00Paratrooper|r: HP: 200 / Damage: 4 / Armor: +2 / Qty: 5|n|n|cffc3dbffCooldown: 45|r")
//! i makechange(current,"aub1","3","Calls for an air support containing goblin paratrooper soldiers. The Plane is equipped with 4 guns which fires at enemy units in its field of sight targeting heroes and units with lower hitpoints. Upon activation, 'Aerial Aid' is replaced by the sub-skill 'Deploy Paratroopers' which commands your backup soldiers to parachute from the Plane.|n|n|cffffcc00Plane|r: HP: 500 / Damage: 7 / Armor: +4 / Guns: 4|n|cffffcc00Paratrooper|r: HP: 250 / Damage: 5 / Armor: +3 / Qty: 6|n|n|cffc3dbffCooldown: 40|r")
//Deploy Paratroopers
//! i createobject("ANcl",deployId)
//! i makechange(current,"abpx","0")
//! i makechange(current,"abpy","2")
//! i makechange(current,"acat","")
//! i makechange(current,"aart","ReplaceableTextures\\CommandButtons\\BTNUnLoad.blp")
//! i makechange(current,"aeat","")
//! i makechange(current,"atat","")
//! i makechange(current,"aher","0")
//! i makechange(current,"arhk","Q")
//! i makechange(current,"ahky","Q")
//! i makechange(current, "alev", "1")
//! i makechange(current,"anam","Deploy Paratroopers")
//! i makechange(current,"Ncl6","1","channel")
//! i makechange(current,"Ncl5","1","0")
//! i makechange(current,"Ncl1","1","0.3")
//! i makechange(current,"atp1","1","Deploy Paratroopers [|cffffcc00Q|r]")
//! i makechange(current,"aub1","1","Command your Paratroopers to parachute from the Plane.")
//Toggle Ability
//! i createobject("ANeg",toggleId)
//! i makechange(current,"Neg3","1",spellId .. "," .. toggleId)
//! i makechange(current,"Neg4","1","")
//! i makechange(current,"Neg5","1","")
//! i makechange(current,"Neg6","1","")
//! i makechange(current,"Neg2","1","0")
//! i makechange(current,"Neg1","1","0")
//! i makechange(current,"alev","1")
//! i makechange(current,"anam","Aerial Aid Toggle")
//Transform (Fly -> Ground)
//! i createobject("Abrf",transform)
//! i makechange(current,"anam","Paratrooper (Fly -> Ground)")
//! i makechange(current,"Eme1","1",troopGround)
//! i makechange(current,"Emeu","1",troopAir)
//! i makechange(current,"adur","1","0")
//! i makechange(current,"amcs","1","0")
//! i makechange(current,"achd","0")
//! i makechange(current,"arac",race)
//! endexternalblock
//Create Armor Manipulating and Damage Manipulating Object Editor Data
static if not LIBRARY_BonusMod then
//! textmacro ArmorAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AId1 $RAWCODE$ Idef 1 $VALUE$ anam "BonusMod - Armor ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNHumanArmorUpOne.blp
//! endtextmacro
//! textmacro DamageAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AItg $RAWCODE$ Iatt 1 $VALUE$ anam "BonusMod - Damage ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNSteelMelee.blp
//! endtextmacro
//! runtextmacro ArmorAbility("ZxA0", "+0001", "1")
//! runtextmacro ArmorAbility("ZxA1", "+0002", "2")
//! runtextmacro ArmorAbility("ZxA2", "+0004", "4")
//! runtextmacro ArmorAbility("ZxA3", "+0008", "8")
//! runtextmacro ArmorAbility("ZxA4", "+0016", "16")
//! runtextmacro ArmorAbility("ZxA5", "+0032", "32")
//! runtextmacro ArmorAbility("ZxA6", "+0064", "64")
//! runtextmacro ArmorAbility("ZxA7", "+0128", "128")
//! runtextmacro DamageAbility("ZxB0", "+0001", "1")
//! runtextmacro DamageAbility("ZxB1", "+0002", "2")
//! runtextmacro DamageAbility("ZxB2", "+0004", "4")
//! runtextmacro DamageAbility("ZxB3", "+0008", "8")
//! runtextmacro DamageAbility("ZxB4", "+0016", "16")
//! runtextmacro DamageAbility("ZxB5", "+0032", "32")
//! runtextmacro DamageAbility("ZxB6", "+0064", "64")
//! runtextmacro DamageAbility("ZxB7", "+0128", "128")
endif
//Create HP Manipulating Object Editor Data
static if not LIBRARY_SetUnitMaxState then
//! external ObjectMerger w3a AIlf Zx01 alev 19 aite 0 Ilif 1 0 Ilif 2 1 Ilif 3 2 Ilif 4 4 Ilif 5 8 Ilif 6 16 Ilif 7 32 Ilif 8 64 Ilif 9 128 Ilif 10 256 Ilif 11 -1 Ilif 12 -2 Ilif 13 -4 Ilif 14 -8 Ilif 15 -16 Ilif 16 -32 Ilif 17 -64 Ilif 18 -128 Ilif 19 -256 anam "SetUnitMaxState - Life" ansf "" aart ReplaceableTextures\CommandButtons\BTNHealthStone.blp
endif
//TESH.scrollpos=0
//TESH.alwaysfold=0
/*
CHANGELOG:
v1.00 - [23 May 2016]
- Initial Release
v1.01 - [29 May 2016]
- Generated structs and functions when WorldBounds, BonusMod and SetUnitMaxState is not present
is now private, avoiding redeclaration when other spells uses these systems as optional
requirement but those other spells uses a non-private structs and functions as replacement.
- Included the Dummy.mdx in the credit list.
*/
//TESH.scrollpos=0
//TESH.alwaysfold=0
// DummyRecycler v1.22
// by Flux
//
// A system that recycles dummy units while considering their facing angle.
// It can be used as attachment units or as dummy caster.
//
// Why is recycling a unit important?
// Because creating a unit is is one of the slowest function in the game
// and it will always leave a permanent tiny bit of memory (0.04 KB).
//
// Features:
//
// -- Dummy Sharing
// When a Dummy List gets low on unit count, it will borrow Dummy Units
// from the Dummy List with the highest unit count. The transfer is not
// instant because the shared Dummy Unit has to turn to the appropriate
// angle of its new Dummy List before it can be recycled.
// See Dummy_BorrowRequest.
//
// -- Self-balancing recycling algorithm
// Recycled Dummy Units will be thrown to the List having the least number
// of Dummy Units.
//
// -- Recycling least used
// Allows recycling a Dummy from the Dummy List with the highest
// unit count. It is useful when the facing angle of the Dummy Unit
// does not matter.
// See GetRecycledDummyAnyAngle.
//
// -- Self-adaptation
// When there are no free Dummy Units from a Dummy List, it will end up creating
// a new unit instead but that unit will be permanently added as a Dummy
// Unit to be recycled increasing the overall total Dummy Unit count.
//
// -- Count control
// Allows limiting the overall number of Dummy Units.
// See Dummy_MaxCount.
//
// -- Delayed Recycle
// Allows recycling Dummy Units after some delay to allocate time for the
// death animation of Special Effects to be seen.
// See DummyAddRecycleTimer.
//
//
//--------------------
// CREDITS
//--------------------
// Bribe - for the MissileRecycler (vJASS) where I got this concept from
// http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
// Vexorian - for the Attachable and Pitch Animation Model (dummy.mdx)
// http://www.wc3c.net/showthread.php?t=101150
// Maker and IcemanBo - for the unit permanent 0.04 KB memory leak of units.
// http://www.hiveworkshop.com/forums/trigger-gui-editor-tutorials-279/memory-leaks-263410/
// Nestharus - for the data structure.
// http://www.hiveworkshop.com/forums/2809461-post7.html
//
// ******************************************************************
// ***************************** API: *******************************
// ******************************************************************
//
// function GetRecycledDummy takes real x, real y, real z, real facing returns unit
// - Retrieve an unused Dummy Unit from the List.
// - The equivalent of CreateUnit
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false)
//
// function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
// - Use this function if the facing angle of the Dummy doesn't matter to you.
// - It will return a unit from the list having the highest number of unused Dummy Units.
// - To use as a Dummy Caster, follow it with PauseUnit(dummy, false)
//
// function RecycleDummy takes unit u returns nothing
// - Recycle the Dummy unit for it to be used again later.
// - The equivalent of RemoveUnit.
//
// function DummyAddRecycleTimer takes unit u, real time returns nothing
// - Recycle the Dummy unit after a certain time.
// - Use this to allocate time for the the death animation of an effect attached to the
// Dummy Unit to finish..
// - The equivalent of UnitApplyTimedLife.
// =============================================================== //
// ====================== CONFIGURATION ========================== //
// =============================================================== //
//The rawcode of the Dummy Unit
constant function Dummy_Rawcode takes nothing returns integer
return 'dumi'
endfunction
//The owner of the Dummy Unit
constant function Dummy_Owner takes nothing returns player
return Player(14)
endfunction
//The number of indexed angle. The higher the value the:
// - Lesser the turning time for the Dummy Units.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 10 (Max difference of 18 degrees)
constant function Dummy_Angles takes nothing returns integer
return 10
endfunction
//The number of Dummy units per Dummy_Angle. The higher the value the:
// - Higher the number of units that can be recycled per angle, when
// no more units are in queue, the system will resort to use CreateUnit.
// - Higher the total number of Dummy Units created at Map Initialization.
// Recommended Value: 3 to 5 (for less overhead in Map Loading Screen)
constant function Dummy_StoredUnits takes nothing returns integer
return 3
endfunction
//The maximum number of Dummy units that can exist. When the system resort
//to using CreateUnit, the unit will be permanently added to the Dummy
//List. To avoid spamming Dummy Units and having too much free Dummy
//Units to allocate, the maximum number of Dummy Units is capped.
// Recommended Value: 80 to 120
constant function Dummy_MaxCount takes nothing returns integer
return 100
endfunction
//When a certain angle have less than BORROW_REQUEST units in its list,
//it will start to borrow Dummy Units from the list with the highest
//Dummy Unit count.
// Recommended Value: Half of maximum Dummy_StoredUnits
constant function Dummy_BorrowRequest takes nothing returns integer
return 3
endfunction
//It will only return a Dummy if the current dummy is close
//to it's appropriate facing angle. This is to avoid returning
//a Dummy which is still turning to face it's list angle.
constant function Dummy_AngleTolerance takes nothing returns real
return 10.
endfunction
//An additional option to automatically hide recycled dummy units in the
//corner of the map camera bounds
constant function Dummy_HideOnMapCorner takes nothing returns boolean
return true
endfunction
// =============================================================== //
// ==================== END CONFIGURATION ======================== //
// =============================================================== //
function Dummy_GetHead takes integer facing returns integer
if facing < 0 or facing >= 360 then
set facing = facing - (facing/360)*360
if facing < 0 then
set facing = facing + 360
endif
endif
return R2I((facing*Dummy_Angles()/360.0))
endfunction
function GetRecycledDummy takes real x, real y, real z, real facing returns unit
local integer head = Dummy_GetHead(R2I(facing + 180.0/Dummy_Angles()))
local integer this = udg_Dummy_Next[head]
local integer countHead
//If there are Dummy Units in the Queue List already facing the appropriate angle
if this != head and RAbsBJ(GetUnitFacing(udg_Dummy_Unit[this]) - udg_Dummy_Angle[head]) <= Dummy_AngleTolerance() then
//Remove from the Queue List
set udg_Dummy_Next[udg_Dummy_Prev[this]] = udg_Dummy_Next[this]
set udg_Dummy_Prev[udg_Dummy_Next[this]] = udg_Dummy_Prev[this]
//For double free protection
set udg_Dummy_Next[this] = -1
//Unit Properties
set bj_lastCreatedUnit = udg_Dummy_Unit[this]
call PauseUnit(bj_lastCreatedUnit, true)
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call SetUnitFacing(bj_lastCreatedUnit, facing)
call SetUnitVertexColor(bj_lastCreatedUnit, 255, 255, 255, 255)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
//------------------------------------------------
// Comment out resets you don't need
//------------------------------------------------
call SetUnitScale(bj_lastCreatedUnit, 1, 0, 0)
call SetUnitAnimationByIndex(bj_lastCreatedUnit, 90)
//Update Count and Bounds
set udg_Dummy_Count[head] = udg_Dummy_Count[head] - 1
//------------------------------------------------
// Unit Sharing
//------------------------------------------------
if udg_Dummy_Count[head] < Dummy_BorrowRequest() and udg_Dummy_Count[udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]] > udg_Dummy_Count[head] then
set udg_Dummy_Count[head] = udg_Dummy_Count[head] + 1
//Take an instance from the UpperBound list
set this = udg_Dummy_Next[udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]]
call SetUnitFacing(udg_Dummy_Unit[this], udg_Dummy_Angle[head])
//Remove
set udg_Dummy_Next[udg_Dummy_Prev[this]] = udg_Dummy_Next[this]
set udg_Dummy_Prev[udg_Dummy_Next[this]] = udg_Dummy_Prev[this]
//Add to the Current List
set udg_Dummy_Next[this] = head
set udg_Dummy_Prev[this] = udg_Dummy_Prev[head]
set udg_Dummy_Next[udg_Dummy_Prev[this]] = this
set udg_Dummy_Prev[udg_Dummy_Next[this]] = this
set head = udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]
set udg_Dummy_Count[head] = udg_Dummy_Count[head] - 1
endif
//---------------------------
//Update Count Lists
//---------------------------
//Remove from the current Count List
set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = udg_Dummy_CountNext[head]
set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = udg_Dummy_CountPrev[head]
//Add to the new Count List
set countHead = udg_Dummy_CountHead[udg_Dummy_Count[head]]
set udg_Dummy_CountNext[head] = countHead
set udg_Dummy_CountPrev[head] = udg_Dummy_CountPrev[countHead]
set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = head
set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set countHead = udg_Dummy_CountHead[udg_Dummy_Upper]
if udg_Dummy_CountNext[countHead] == countHead then
set udg_Dummy_Upper = udg_Dummy_Upper - 1
endif
if udg_Dummy_Count[head] < udg_Dummy_Lower then
set udg_Dummy_Lower = udg_Dummy_Count[head]
endif
else
set bj_lastCreatedUnit = CreateUnit(Dummy_Owner(), Dummy_Rawcode(), x, y, facing)
call PauseUnit(bj_lastCreatedUnit, true)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0)
if udg_Dummy_UnitCount < Dummy_MaxCount() then
set this = udg_Dummy_LastInstance
set udg_Dummy_Next[this] = -1
set udg_Dummy_Unit[this] = bj_lastCreatedUnit
call SaveInteger(udg_Dummy_Hashtable, GetHandleId(bj_lastCreatedUnit), 0, this)
set udg_Dummy_LastInstance = udg_Dummy_LastInstance + 1
endif
set udg_Dummy_UnitCount = udg_Dummy_UnitCount + 1
endif
return bj_lastCreatedUnit
endfunction
function GetRecycledDummyAnyAngle takes real x, real y, real z returns unit
return GetRecycledDummy(x, y, z, udg_Dummy_Angle[udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Upper]]])
endfunction
function RecycleDummy takes unit u returns nothing
local integer this = LoadInteger(udg_Dummy_Hashtable, GetHandleId(u), 0)
local integer head
local integer countHead
//If the unit is a legit Dummy Unit
if this > 0 and udg_Dummy_Next[this] == -1 then
//Find where to insert based on the list having the least number of units
set head = udg_Dummy_CountNext[udg_Dummy_CountHead[udg_Dummy_Lower]]
set udg_Dummy_Next[this] = head
set udg_Dummy_Prev[this] = udg_Dummy_Prev[head]
set udg_Dummy_Next[udg_Dummy_Prev[this]] = this
set udg_Dummy_Prev[udg_Dummy_Next[this]] = this
//Update Status
call SetUnitFacing(u, udg_Dummy_Angle[head])
call PauseUnit(u, true)
call SetUnitOwner(u, Dummy_Owner(), false)
if Dummy_HideOnMapCorner() then
call SetUnitX(u, udg_Dummy_X)
call SetUnitY(u, udg_Dummy_Y)
else
call SetUnitVertexColor(u, 0, 0, 0, 0)
endif
set udg_Dummy_Count[head] = udg_Dummy_Count[head] + 1
//---------------------------
// Update Count Lists
//---------------------------
//Remove
set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = udg_Dummy_CountNext[head]
set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = udg_Dummy_CountPrev[head]
//Add to the new Count List
set countHead = udg_Dummy_CountHead[udg_Dummy_Count[head]]
set udg_Dummy_CountNext[head] = countHead
set udg_Dummy_CountPrev[head] = udg_Dummy_CountPrev[countHead]
set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = head
set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = head
//---------------------------
// Update Bounds
//---------------------------
set countHead = udg_Dummy_CountHead[udg_Dummy_Lower]
if udg_Dummy_CountNext[countHead] == countHead then
set udg_Dummy_Lower = udg_Dummy_Lower + 1
endif
if udg_Dummy_Count[head] > udg_Dummy_Upper then
set udg_Dummy_Upper = udg_Dummy_Count[head]
endif
elseif this == 0 then
//User tries to recycle an invalid unit, remove the unit instead
call RemoveUnit(u)
endif
endfunction
function Dummy_TimerExpires takes nothing returns nothing
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
call RecycleDummy(LoadUnitHandle(udg_Dummy_Hashtable, id, 0))
call FlushChildHashtable(udg_Dummy_Hashtable, id)
call DestroyTimer(t)
set t = null
endfunction
function DummyAddRecycleTimer takes unit u, real time returns nothing
local timer t = CreateTimer()
call SaveUnitHandle(udg_Dummy_Hashtable, GetHandleId(t), 0, u)
call TimerStart(t, time, false, function Dummy_TimerExpires)
set t = null
endfunction
function InitTrig_DummyRecycler_JASS takes nothing returns nothing
local integer id = Dummy_Rawcode()
local player owner = Dummy_Owner()
local integer unitCount = Dummy_StoredUnits()
local real add = 360.0/Dummy_Angles()
local real angle = 0
local integer this = Dummy_Angles()
local integer head = 0
local integer countHead = JASS_MAX_ARRAY_SIZE - 1 //avoid allocation collision
local integer i = R2I(Dummy_MaxCount()/Dummy_Angles() + 0.5)
local rect r
set udg_Dummy_Hashtable = InitHashtable()
set udg_Dummy_Upper = unitCount
set udg_Dummy_Lower = unitCount
//Create Map Bounds
if Dummy_HideOnMapCorner() then
set r = GetWorldBounds()
set udg_Dummy_X = GetRectMaxX(r)
set udg_Dummy_Y = GetRectMaxY(r)
call RemoveRect(r)
set r = null
endif
//Initialize countHeads
loop
exitwhen i < 0
set udg_Dummy_CountNext[countHead] = countHead
set udg_Dummy_CountPrev[countHead] = countHead
set udg_Dummy_CountHead[i] = countHead
set countHead = countHead - 1
set i = i - 1
endloop
set countHead = udg_Dummy_CountHead[unitCount] //All heads will be inserted here initially
//Create the Dummy units
loop
exitwhen angle >= 360
//Initialize head
set udg_Dummy_Next[head] = head
set udg_Dummy_Prev[head] = head
set udg_Dummy_Count[head] = unitCount
set udg_Dummy_Angle[head] = angle
//Insert head in the Count List
set udg_Dummy_CountNext[head] = countHead
set udg_Dummy_CountPrev[head] = udg_Dummy_CountPrev[countHead]
set udg_Dummy_CountNext[udg_Dummy_CountPrev[head]] = head
set udg_Dummy_CountPrev[udg_Dummy_CountNext[head]] = head
set i = 0
loop
exitwhen i >= unitCount
//Queued Linked List
set udg_Dummy_Next[this] = head
set udg_Dummy_Prev[this] = udg_Dummy_Prev[head]
set udg_Dummy_Next[udg_Dummy_Prev[this]] = this
set udg_Dummy_Prev[udg_Dummy_Next[this]] = this
//The actual unit
set udg_Dummy_Unit[this] = CreateUnit(owner, id, udg_Dummy_X, udg_Dummy_Y, angle)
call PauseUnit(udg_Dummy_Unit[this], true)
call SaveInteger(udg_Dummy_Hashtable, GetHandleId(udg_Dummy_Unit[this]), 0, this)
set this = this + 1
set i = i + 1
endloop
set head = head + 1
set angle = angle + add
endloop
set udg_Dummy_LastInstance = this
set udg_Dummy_UnitCount = Dummy_Angles()*unitCount
endfunction
// runtextmacro DUMMY_DEBUG_TOOLS_JASS()
//TESH.scrollpos=177
//TESH.alwaysfold=0
library MissileRecycler initializer PreInit requires optional UnitIndexer, optional UnitDex, optional UnitIndexerGUI /*
MissileRecycler v 1.4.0.1
=========================================================================
Credits:
-------------------------------------------------------------------------
Written by Bribe
Vexorian, Anitarf and iNfraNe for the dummy.mdx model file
Nestharus for the Queue data structure and for finding that paused units
consume very few CPU resources.
=========================================================================
Introduction:
-------------------------------------------------------------------------
Recycling dummy units is important because the CreateUnit call is one of,
if not the, most processor-intensive native in the entire game. Creating
just a couple dozen dummy units in a single thread causes a visible frame
glitch for that instant. The overhead is even higher if you are using a
Unit Indexing library in the map which causes some extra evaluations per
new unit.
There are also reports of removed units leaving a little trail of RAM
surplus in their wake. I have not been able to reproduce this so I don't
know how serious it is.
I was motivated to create this system because removed units might be un-
safe in very large numbers and CreateUnit is a very heavy process.
The thing that makes this system different than others is the fact that
it considers the facing angle of the dummies being recycled, which I have
never seen another system even attempt before this. Since then,
MissileRecycler has inspired Anitarf to update XE with the same angle-retaining
capability and Nestharus has created Dummy. Considering the facing angle is
important because it takes 0.73 seconds for the unit to turn around,
which - when overlooked - looks especially weird if you are creating a unit-trail
or if you are shooting arrow-shaped objects as projectiles. For fireball effects or
effects that generally don't depend on facing angle, this system would be
a bit wasteful.
With default settings and the worst-case-scenario, it will take 0.09 seconds for
the projectile to turn to the angle you need. This is 1/8 of the normal worst case
scenario if you weren't recycling dummies considering facing. On average, it takes
roughly 0.045 seconds to turn to the angle you need (which is not noticable).
However, I have made this completely configurable and you are
able to change the values to whatever needs you have.
=========================================================================
Calibration Guide:
-------------------------------------------------------------------------
The thing that surprised me the most about this system was, no matter how
complex it turned out, it became very configurable. So I should let you
know what the constants do so you know if/how much you want to modify.
constant real DEATH_TIME = 2.0 //seconds
- Should not be less than the maximum time a death animation needs to play.
Should not be lower than .73 to ensure enough time to turn.
Should not be too high otherwise the dummies will take too long to recycle.
constant integer ANG_N = 8
- How many different angles are recognized by the system. Don't do
360 different angles because then you're going to have thousands of dummy
units stored and that's ridiculous, the game lags enough at 1000 units.
Increasing ANG_N increases realism but decreases the chance that a dummy
unit will be available to be recycled. I don't recommend making this any
lower, and the max I'd recommend would be 16.
constant integer ANG_STORAGE_MAX = 12
- How many dummy units are stored per angle. This limit is important
because you might have a spike at one point in the game where many units
are created, which could result in too high of a dummy population.
In general, I advise that the product of ANG_N x ANG_STORAGE_MAX does
not exceed 100 or 200. More than that is excessive, but you can
hypothetically have it up to 8190 if Warcraft 3's memory management
were better.
Preloads ANG_N x ANG_STORAGE_MAX dummy units. Preloading dummies is
useful as it dumps a lot of CreateUnit calls in initialization where you
won't see a frame glitch. In the 1.4 update, preloading is done 0 seconds
into the game to ensure any Indexers have already initialized.
private function ToggleIndexer takes boolean flag returns nothing
- Put what you need in here to disable/enable any indexer in your
map. if flag is true, enable indexer. If false, disable.
=========================================================================
API Guide:
-------------------------------------------------------------------------
You obviously need some functions so you can get a recycled dummy unit or
recycle it. Therefore I provide these:
function GetRecycledMissile
takes real x, real y, real z, real facing
returns unit
Returns a new dummy unit that acts as a projectile missile. The args
are simply the last three arguments you'd use for a CreateUnit call,
with the addition of a z parameter to represent the flying height -
it isn't the absolute z but relative to the ground because it uses
SetUnitFlyHeight on that value directly.
function RecycleMissile
takes unit u
returns nothing
When you are done with that dummy unit, recycle it via this function.
This function is pretty intelligent and resets that unit's animation
and its facing angle so you don't have to.
*/
//=======================================================================
// Save the map, then delete the exclaimation mark in the following line.
// Make sure that you don't have an object in your map with the rawcode
// 'dumi' and also configure the model path (war3mapImported\dummy.mdl)
// to the dummy.mdx model created by Vexorian.
///! external ObjectMerger w3u ewsp dumi unam "Missile Dummy" ubui "" uhom 1 ucol 0.01 umvt "None" umvr 1.00 utar "" uspa "" umdl "war3mapImported\dummy.mdl" umxr 0.00 umxp 0.00 ushr 0 uerd 0.00 udtm 0.00 ucbs 0.00 uble 0.00 uabi "Aloc,Amrf"
//Thanks to Vexorian that Optimizer 5.0 no longer kills natives
native UnitAlive takes unit id returns boolean
globals
//-------------------------------------------------------------------
// You must configure the dummy unit with the one created from the
// ObjectMerger statement above.
//
private constant integer DUMMY_ID = 'dumi' //The rawcode of the dummy unit.
private player OWNER = Player(15) //The owner of the dummy unit.
private constant integer ANG_N = 10 //# of indexed angles. Higher value increases realism but decreases recycle frequency.
private constant integer ANG_STORAGE_MAX = 10 //Max dummies per indexed angle. I recommend lowering this if you increase ANG_N.
private constant real DEATH_TIME = 2. //Allow the special effect on
//the unit to complete its "death" animation in this timeframe. Must
//be higher than 0.74 seconds to allow the unit time to turn. This
//number should not be lower than the maximum death-animation time of
//your missile-units' effect attachments, just to be safe.
endglobals
private function ToggleIndexer takes boolean flag returns nothing
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = flag
elseif LIBRARY_UnitIndexerGUI then
set udg_UnitIndexerEnabled = flag
elseif LIBRARY_UnitDex then
set UnitDex.Enabled = flag
endif
endfunction
globals
private constant integer ANG_VAL = 360 / ANG_N //Generate angle value from ANG_N.
private constant integer ANG_MID = ANG_VAL / 2 //The middle value of angle value.
//Misc vars
private unit array stack //Recycled dummy units.
private real array timeStamp //Prevents early recycling of units.
private integer array queueNext
private integer array queueLast
private integer recycle = 0
private timer gameTime = CreateTimer() //Used for visual continuity.
private integer array queueStack
private integer queueStackN = 0 //Used to avoid searching the queues.
endglobals
static if DEBUG_MODE then
private function Print takes string s returns nothing
//Un-comment this next line if you want to know how the system works:
//call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 999, "[MissileRecycler] " + s)
endfunction
endif
//=======================================================================
// Get a recycled dummy missile unit. If there are no recycled dummies
// that are already facing the angle you need, it creates a new dummy for
// you.
//
function GetRecycledMissile takes real x, real y, real z, real facing returns unit
local integer i = ModuloInteger(R2I(facing), 360) / ANG_VAL
local integer this = queueNext[i]
local unit u
if this != 0 and TimerGetElapsed(gameTime) >= timeStamp[this] then
call BJDebugMsg("Recycled at " + I2S(recycleCount))
set recycleCount = recycleCount + 1
//Dequeue this
set queueNext[i] = queueNext[this]
if queueNext[i] == 0 then
set queueLast[i] = i
endif
//Recycle this index
set queueLast[this] = recycle
set recycle = this
//Add queue index to available stack
set queueStack[queueStackN] = i
set queueStackN = queueStackN + 1
//Old unit will return as new
set u = stack[this]
call SetUnitFacing(u, facing)
call SetUnitUserData(u, 0)
//Reset the dummy's properties.
call SetUnitVertexColor(u, 255, 255, 255, 255)
call SetUnitAnimationByIndex(u, 90)
call SetUnitScale(u, 1, 0, 0)
//call PauseUnit(u, false) -- you can disable "resets" that you don't need to worry about.
debug call Print("Recycling")
else
debug call Print("Creating new")
call ToggleIndexer(false)
set u = CreateUnit(OWNER, DUMMY_ID, x, y, facing)
call ToggleIndexer(true)
call PauseUnit(u, true)
call BJDebugMsg("Created new at " + I2S(createCount))
set createCount = createCount + 1
endif
call SetUnitX(u, x)
call SetUnitY(u, y)
call SetUnitFlyHeight(u, z, 0)
set bj_lastCreatedUnit = u
set u = null
return bj_lastCreatedUnit
endfunction
//=======================================================================
// You should recycle the dummy missile unit when its job is done.
//
function RecycleMissile takes unit u returns nothing
local integer i
local integer this = recycle
if GetUnitTypeId(u) == DUMMY_ID and UnitAlive(u) and GetUnitUserData(u) != -1 then
if queueStackN == 0 then
debug call Print("Stack is full - removing surplus unit")
call UnitApplyTimedLife(u, 'BTLF', DEATH_TIME)
return
endif
//Recycle this
set recycle = queueLast[this]
//Index the dummy unit to an available facing angle.
//Get the last vacant angle index.
set queueStackN = queueStackN - 1
set i = queueStack[queueStackN]
//Enqueue this
set queueNext[queueLast[i]] = this
set queueLast[i] = this
set queueNext[this] = 0
//Allow a time barrier for the effect to destroy/turn to complete.
set timeStamp[this] = TimerGetElapsed(gameTime) + DEATH_TIME
set stack[this] = u
call SetUnitFacing(u, i * ANG_VAL + ANG_MID)
//Prevent double-free of this unit.
call SetUnitUserData(u, -1)
debug else
debug call BJDebugMsg("[MissileRecycler] Error: Attempt to recycle invalid unit.")
endif
endfunction
//=======================================================================
// I didn't need this function after all
//
function RecycleMissileDelayed takes unit u, real r returns nothing
call RecycleMissile(u)
endfunction
//=======================================================================
// Map the dummy units to their facing angles (map below is if ANG_N is
// 4 and ANG_STORAGE_MAX is 3).
//
// angle[0] (0) - [4] [5] [6]
// angle[1] (90) - [7] [8] [9]
// angle[2] (180) - [10][11][12]
// angle[3] (270) - [13][14][15]
//
private function Init takes nothing returns nothing
local integer end
local integer i = ANG_N
local integer n = i
local integer angle
local real x = GetRectMaxX(bj_mapInitialPlayableArea)
local real y = GetRectMaxY(bj_mapInitialPlayableArea)
local unit u
call ToggleIndexer(false)
loop
set i = i - 1
set queueNext[i] = n
set angle = i * ANG_VAL + ANG_MID
set end = n + ANG_STORAGE_MAX
set queueLast[i] = end - 1
loop
set queueNext[n] = n + 1
set u = CreateUnit(OWNER, DUMMY_ID, x, y, angle)
set stack[n] = u
call PauseUnit(u, true)
call SetUnitUserData(u, -1)
set n = n + 1
exitwhen n == end
endloop
set queueNext[n - 1] = 0
exitwhen i == 0
endloop
call ToggleIndexer(true)
call TimerStart(gameTime, 1000000., false, null)
set u = null
endfunction
private function PreInit takes nothing returns nothing
static if LIBRARY_UnitIndexerGUI then
call OnUnitIndexerInitialized(function Init)
else
call Init()
endif
endfunction
endlibrary
//TESH.scrollpos=16
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=1
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
/*
============= Why this is important? =================
1. Does not generate 16 events per spell.
2. This uses one trigger evaluation instead of one for each
individual spell (some maps have quite a few). This helps keep
framerate high and fluid when a spell is cast.
*/
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary
//TESH.scrollpos=21
//TESH.alwaysfold=0
library WorldBounds /* v2.0.0.0
************************************************************************************
*
* struct WorldBounds extends array
*
* Fields
* -------------------------
*
* readonly static integer maxX
* readonly static integer maxY
* readonly static integer minX
* readonly static integer minY
*
* readonly static integer centerX
* readonly static integer centerY
*
* readonly static rect world
* readonly static region worldRegion
*
************************************************************************************/
private module WorldBoundInit
private static method onInit takes nothing returns nothing
set world=GetWorldBounds()
set maxX = R2I(GetRectMaxX(world))
set maxY = R2I(GetRectMaxY(world))
set minX = R2I(GetRectMinX(world))
set minY = R2I(GetRectMinY(world))
set centerX = R2I((maxX + minX)/2)
set centerY = R2I((minY + maxY)/2)
set worldRegion = CreateRegion()
call RegionAddRect(worldRegion, world)
endmethod
endmodule
struct WorldBounds extends array
readonly static integer maxX
readonly static integer maxY
readonly static integer minX
readonly static integer minY
readonly static integer centerX
readonly static integer centerY
readonly static rect world
readonly static region worldRegion
implement WorldBoundInit
endstruct
endlibrary
//TESH.scrollpos=335
//TESH.alwaysfold=0
//////////////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ Bonus Mod
//@=======================================================================================
//@ Credits:
//@---------------------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@ Based on the work of:
//@ weaaddar
//@
//@ If you use this system, please credit all of the people mentioned above in your map.
//@=======================================================================================
//@ Bonus Mod Readme
//@---------------------------------------------------------------------------------------
//@
//@ BonusMod is a system for adding bonuses to a single unit. For example, you may wish
//@ to add a +40 damage bonus, or a -3 armor 'bonus'. Bonus mod works by adding abilitys
//@ to a unit which effect the particular stat by a power of two. By combining diffrent
//@ powers of two, you can reach any number between 0 and 2^(n+1) - 1, where n is the
//@ largest power of 2 used. Bonus mod can also apply negative bonuses, by adding an
//@ ability which has a 'bonus' of -2^(n+1), where again, n is the maximum power of 2.
//@ With the negative bonus, you can add anywhere between 1 and 2^(n+1)-1 of a bonus. This
//@ gives bonus mod a range of bonuses between -2^(n+1) and 2^(n+1)-1. By default, n is
//@ set at 11, giving us a range of bonuses between -4096 and +4095.
//@
//@---------------------------------------------------------------------------------------
//@ Adding Bonus Mod to your map:
//@
//@ Copy this library in to a trigger named "BonusMod" in your map.
//@
//@ After the script is copied, the hard part begins. You will have to transfer all of the
//@ bonus abilitys found in this map to yours. However, this is really easy to do if you
//@ are using the JASS NewGen editor. (Which you will have to be anyway, considering this
//@ system is written in vJASS.) Included with this library are macros for the Object
//@ Merger included in NewGen. Simply copy the Object Merger script included with this
//@ system in to your map in its own trigger. Save your map. (Saving will take a while.
//@ Up to 5 min if you have a slow computer.) Close your map, and reopen it. Disable the
//@ trigger you copied the ObjectMerger script in to.
//@ Your map now has all the abilitys it needs!
//@
//@---------------------------------------------------------------------------------------
//@ Functions:
//@
//@ boolean UnitSetBonus(unit <target unit>, integer <bonus type>, integer <bonus ammount>)
//@
//@ This function clears any previously applied bonus on <target unit>, setting the
//@ unit's bonus for <bonus type> to <bonus ammount>. <bonus type> should be one of the
//@ integer type constants below. This function will return false if the desired bonus is
//@ not a valid bonus type, or out of the range of bonuses that can be applied.
//@
//@ integer UnitGetBonus(unit <target unit>, integer <bonus type>)
//@
//@ Returns the bonus ammount of <bonus type> currently applied to <target unit>.
//@
//@ boolean UnitAddBonus(unit <target unit>, integer <bonus type>, integer <bonus ammount>)
//@
//@ This function will add <bonus ammount> to the bonus of type <bonus type> on the
//@ unit <target unit>. <bonus ammount> can be a negative value. This function will return
//@ false if the new bonus will be out of the range which bonus mod can apply.
//@
//@ nothing UnitClearBonus(unit <target unit>, integer <bonus type>)
//@
//@ This function will effectively set the bonus of type <bonus type> for the unit
//@ <target unit> to 0. It is advised you use this function over UnitSetBonus(..., ..., 0)
//@
//@---------------------------------------------------------------------------------------
//@ Variables:
//@
//@ BonusMod_MaxBonus
//@ The maximum bonus that Bonus Mod can apply
//@ BonusMod_MinBonus
//@ The minimum bonus that Bonus Mod can apply
//@---------------------------------------------------------------------------------------
//@ Increasing the Range of Bonuses:
//@
//@ By default, bonus mod uses 13 abilitys per bonus type. This gives each bonus type a
//@ range of -4096 to +4095. To increase this range, you will have to create one new
//@ ability for each ability, for each power of two you increase bonus mod by. You will
//@ also have to edit the negative bonus ability to apply a bonus of -2^(n+1), where n is
//@ the largest power of two you will be using for positive bonuses. You will need to edit
//@ the ABILITY_COUNT constant found below to reflect the new total number of abilitys
//@ each individual bonus will use. You will also have to add the abilitys to the function
//@ InitializeAbilitys. Note that the number in the array index indicates which power of
//@ 2 is held there. So, for instance, set BonusAbilitys[i + 15] would hold an ability
//@ which changes the relivent stat by 32768. (2^15 = 32768) The last ability in the array
//@ must apply a negative bonus.
//@
//@ Here is an example of the bonus BONUS_ARMOR using 15 abilitys instead of 12:
//@
//@ set i = BONUS_ARMOR * ABILITY_COUNT
//@ set BonusAbilitys[i + 0] = 'ZxA0' // +1
//@ set BonusAbilitys[i + 1] = 'ZxA1' // +2
//@ set BonusAbilitys[i + 2] = 'ZxA2' // +4
//@ set BonusAbilitys[i + 3] = 'ZxA3' // +8
//@ set BonusAbilitys[i + 4] = 'ZxA4' // +16
//@ set BonusAbilitys[i + 5] = 'ZxA5' // +32
//@ set BonusAbilitys[i + 6] = 'ZxA6' // +64
//@ set BonusAbilitys[i + 7] = 'ZxA7' // +128
//@ set BonusAbilitys[i + 8] = 'ZxA8' // +256
//@ set BonusAbilitys[i + 9] = 'ZxA9' // +512
//@ set BonusAbilitys[i + 10] = 'ZxAa' // +1024
//@ set BonusAbilitys[i + 11] = 'ZxAb' // +2048
//@ set BonusAbilitys[i + 12] = 'ZxAc' // +4096
//@ set BonusAbilitys[i + 13] = 'ZxAd' // +8192
//@ set BonusAbilitys[i + 14] = 'ZxAe' // +16384
//@ set BonusAbilitys[i + 15] = 'ZxAf' // -32768
//@
//@---------------------------------------------------------------------------------------
//@ Adding and Removing Bonus Types:
//@
//@ Removing a bonus type is simple. First, delete it from the list of constants found
//@ below. Make sure the constants are numberd 0, 1, 2, 3, etc. without any gaps. Change
//@ the BONUS_TYPES constant to reflect the new number of bonuses. You must then remove
//@ the lines of array initialization for the bonus you removed from the
//@ InitializeAbilitys function. You can then delete the abilitys for that bonus type, and
//@ you are then done removing a bonus type.
//@
//@ Adding a bonus type is done in much the same way. Add a constant for it to the list of
//@ constants below, ensuring they are numberd 0, 1, 2, 3 etc. withour any gaps. Change
//@ the BONUS_TYPES constant to reflect the new number of bonuses. You must then create
//@ all the needed abilitys for the new bonus type. Ensure the bonus they each apply is a
//@ power of 2, as with the already included bonuses. See the section Increasing the Range
//@ of Bonuses for more information. After all the abilitys are added, you must add the
//@ needed lines to the InitializeAbilitys function. The existing lines should be a clear
//@ enogh example.
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//////////////////////////////////////////////////////////////////////////////////////////
library BonusMod initializer Initialize
globals
//========================================================================================
// Bonus Type Constants
//========================================================================================
constant integer BONUS_ARMOR = 0 // Armor Bonus
constant integer BONUS_DAMAGE = 1 // Damage Bonus
constant integer BONUS_SIGHT_RANGE = 2 // Sight Range Bonus
constant integer BONUS_MANA_REGEN = 3 // Mana Regeneration Bonus (A % value)
constant integer BONUS_LIFE_REGEN = 4 // Life Regeneration Bonus (An absolute value)
constant integer BONUS_HERO_STR = 5 // Strength Bonus
constant integer BONUS_HERO_AGI = 6 // Agility Bonus
constant integer BONUS_HERO_INT = 7 // Intelligence Bonus
// The number of bonus type constants above:
constant integer BONUS_TYPES = 8
//========================================================================================
// Other Configuration
//========================================================================================
// The number of abilitys used per bonus type:
private constant integer ABILITY_COUNT = 13
// Note: Setting the following to false will decrease loading time, but will cause a
// small ammount of lag when a bonus is first applied. (Especially a negative bonus)
// If set to true, all BonusMod abilitys will be preloaded:
private constant boolean PRELOAD_ABILITYS = true
// Only applies if PRELOAD_ABILITYS is set to true.
// The unit type used to preload abilitys on:
private constant integer PRELOAD_DUMMY_UNIT = 'hpea'
endglobals
//========================================================================================
// Ability Initialization
//----------------------------------------------------------------------------------------
// The following function is used to define the rawcodes for all the abilitys bonus mod
// uses. If you use the text macros included with BonusMod, and if you do not wish to add,
// remove, or change the range of bonuses, you will not have to edit the following.
//
// Note that if your map already has abilitys with rawcodes that begin with Zx followed by
// an upper-case letter, the ObjectMerger macros included with this library will not work
// and you will have to edit the lines below. However, you could use the find and replace
// feature in JASS NewGen's Trigger Editor Syntax Highlighter to replace all occurances of
// Zx both here and in the ObjectMerger macros to ease configuration.
//========================================================================================
private keyword BonusAbilitys
private function InitializeAbilitys takes nothing returns nothing
local integer i
// Bonus Mod - Armor abilitys
set i = BONUS_ARMOR * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxA0' // +1
set BonusAbilitys[i + 1] = 'ZxA1' // +2
set BonusAbilitys[i + 2] = 'ZxA2' // +4
set BonusAbilitys[i + 3] = 'ZxA3' // +8
set BonusAbilitys[i + 4] = 'ZxA4' // +16
set BonusAbilitys[i + 5] = 'ZxA5' // +32
set BonusAbilitys[i + 6] = 'ZxA6' // +64
set BonusAbilitys[i + 7] = 'ZxA7' // +128
set BonusAbilitys[i + 8] = 'ZxA8' // +256
set BonusAbilitys[i + 9] = 'ZxA9' // +512
set BonusAbilitys[i + 10] = 'ZxAa' // +1024
set BonusAbilitys[i + 11] = 'ZxAb' // +2048
set BonusAbilitys[i + 12] = 'ZxAc' // -4096
// Bonus Mod - Damage abilitys
set i = BONUS_DAMAGE * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxB0' // +1
set BonusAbilitys[i + 1] = 'ZxB1' // +2
set BonusAbilitys[i + 2] = 'ZxB2' // +4
set BonusAbilitys[i + 3] = 'ZxB3' // +8
set BonusAbilitys[i + 4] = 'ZxB4' // +16
set BonusAbilitys[i + 5] = 'ZxB5' // +32
set BonusAbilitys[i + 6] = 'ZxB6' // +64
set BonusAbilitys[i + 7] = 'ZxB7' // +128
set BonusAbilitys[i + 8] = 'ZxB8' // +256
set BonusAbilitys[i + 9] = 'ZxB9' // +512
set BonusAbilitys[i + 10] = 'ZxBa' // +1024
set BonusAbilitys[i + 11] = 'ZxBb' // +2048
set BonusAbilitys[i + 12] = 'ZxBc' // -4096
// Bonus Mod - Sight Range abilitys
set i = BONUS_SIGHT_RANGE * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxC0' // +1
set BonusAbilitys[i + 1] = 'ZxC1' // +2
set BonusAbilitys[i + 2] = 'ZxC2' // +4
set BonusAbilitys[i + 3] = 'ZxC3' // +8
set BonusAbilitys[i + 4] = 'ZxC4' // +16
set BonusAbilitys[i + 5] = 'ZxC5' // +32
set BonusAbilitys[i + 6] = 'ZxC6' // +64
set BonusAbilitys[i + 7] = 'ZxC7' // +128
set BonusAbilitys[i + 8] = 'ZxC8' // +256
set BonusAbilitys[i + 9] = 'ZxC9' // +512
set BonusAbilitys[i + 10] = 'ZxCa' // +1024
set BonusAbilitys[i + 11] = 'ZxCb' // +2048
set BonusAbilitys[i + 12] = 'ZxCc' // -4096
// Bonus Mod - Mana Regen abilitys
set i = BONUS_MANA_REGEN * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxD0' // +1
set BonusAbilitys[i + 1] = 'ZxD1' // +2
set BonusAbilitys[i + 2] = 'ZxD2' // +4
set BonusAbilitys[i + 3] = 'ZxD3' // +8
set BonusAbilitys[i + 4] = 'ZxD4' // +16
set BonusAbilitys[i + 5] = 'ZxD5' // +32
set BonusAbilitys[i + 6] = 'ZxD6' // +64
set BonusAbilitys[i + 7] = 'ZxD7' // +128
set BonusAbilitys[i + 8] = 'ZxD8' // +256
set BonusAbilitys[i + 9] = 'ZxD9' // +512
set BonusAbilitys[i + 10] = 'ZxDa' // +1024
set BonusAbilitys[i + 11] = 'ZxDb' // +2048
set BonusAbilitys[i + 12] = 'ZxDc' // -4096
// Bonus Mod - Life Regen abilitys
set i = BONUS_LIFE_REGEN * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxE0' // +1
set BonusAbilitys[i + 1] = 'ZxE1' // +2
set BonusAbilitys[i + 2] = 'ZxE2' // +4
set BonusAbilitys[i + 3] = 'ZxE3' // +8
set BonusAbilitys[i + 4] = 'ZxE4' // +16
set BonusAbilitys[i + 5] = 'ZxE5' // +32
set BonusAbilitys[i + 6] = 'ZxE6' // +64
set BonusAbilitys[i + 7] = 'ZxE7' // +128
set BonusAbilitys[i + 8] = 'ZxE8' // +256
set BonusAbilitys[i + 9] = 'ZxE9' // +512
set BonusAbilitys[i + 10] = 'ZxEa' // +1024
set BonusAbilitys[i + 11] = 'ZxEb' // +2048
set BonusAbilitys[i + 12] = 'ZxEc' // -4096
// Bonus Mod - Hero STR abilitys
set i = BONUS_HERO_STR * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxF0' // +1
set BonusAbilitys[i + 1] = 'ZxF1' // +2
set BonusAbilitys[i + 2] = 'ZxF2' // +4
set BonusAbilitys[i + 3] = 'ZxF3' // +8
set BonusAbilitys[i + 4] = 'ZxF4' // +16
set BonusAbilitys[i + 5] = 'ZxF5' // +32
set BonusAbilitys[i + 6] = 'ZxF6' // +64
set BonusAbilitys[i + 7] = 'ZxF7' // +128
set BonusAbilitys[i + 8] = 'ZxF8' // +256
set BonusAbilitys[i + 9] = 'ZxF9' // +512
set BonusAbilitys[i + 10] = 'ZxFa' // +1024
set BonusAbilitys[i + 11] = 'ZxFb' // +2048
set BonusAbilitys[i + 12] = 'ZxFc' // -4096
// Bonus Mod - Hero AGI abilitys
set i = BONUS_HERO_AGI * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxG0' // +1
set BonusAbilitys[i + 1] = 'ZxG1' // +2
set BonusAbilitys[i + 2] = 'ZxG2' // +4
set BonusAbilitys[i + 3] = 'ZxG3' // +8
set BonusAbilitys[i + 4] = 'ZxG4' // +16
set BonusAbilitys[i + 5] = 'ZxG5' // +32
set BonusAbilitys[i + 6] = 'ZxG6' // +64
set BonusAbilitys[i + 7] = 'ZxG7' // +128
set BonusAbilitys[i + 8] = 'ZxG8' // +256
set BonusAbilitys[i + 9] = 'ZxG9' // +512
set BonusAbilitys[i + 10] = 'ZxGa' // +1024
set BonusAbilitys[i + 11] = 'ZxGb' // +2048
set BonusAbilitys[i + 12] = 'ZxGc' // -4096
// Bonus Mod - Hero INT abilitys
set i = BONUS_HERO_INT * ABILITY_COUNT
set BonusAbilitys[i + 0] = 'ZxH0' // +1
set BonusAbilitys[i + 1] = 'ZxH1' // +2
set BonusAbilitys[i + 2] = 'ZxH2' // +4
set BonusAbilitys[i + 3] = 'ZxH3' // +8
set BonusAbilitys[i + 4] = 'ZxH4' // +16
set BonusAbilitys[i + 5] = 'ZxH5' // +32
set BonusAbilitys[i + 6] = 'ZxH6' // +64
set BonusAbilitys[i + 7] = 'ZxH7' // +128
set BonusAbilitys[i + 8] = 'ZxH8' // +256
set BonusAbilitys[i + 9] = 'ZxH9' // +512
set BonusAbilitys[i + 10] = 'ZxHa' // +1024
set BonusAbilitys[i + 11] = 'ZxHb' // +2048
set BonusAbilitys[i + 12] = 'ZxHc' // -4096
endfunction
//========================================================================================
// System Code
//----------------------------------------------------------------------------------------
// Do not edit below this line unless you wish to change the way the system works.
//========================================================================================
globals
// Contains all abilitys in a two-dimensional structure:
private integer array BonusAbilitys
// Precomputed powers of two to avoid speed and rounding issues with Pow():
private integer array PowersOf2
// Range constants (Read only please):
public integer MaxBonus
public integer MinBonus
endglobals
function UnitClearBonus takes unit u, integer bonusType returns nothing
local integer i = 0
loop
call UnitRemoveAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + i])
set i = i + 1
exitwhen i == ABILITY_COUNT - 2
endloop
endfunction
function UnitSetBonus takes unit u, integer bonusType, integer ammount returns boolean
local integer i = ABILITY_COUNT - 2
if ammount < MinBonus or ammount > MaxBonus then
debug call BJDebugMsg("BonusSystem Error: Bonus too high or low (" + I2S(ammount) + ")")
return false
elseif bonusType < 0 or bonusType >= BONUS_TYPES then
debug call BJDebugMsg("BonusSystem Error: Invalid bonus type (" + I2S(bonusType) + ")")
return false
endif
if ammount < 0 then
set ammount = MaxBonus + ammount + 1
call UnitAddAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1])
call UnitMakeAbilityPermanent(u, true, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1])
else
call UnitRemoveAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1])
endif
loop
if ammount >= PowersOf2[i] then
call UnitAddAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + i])
call UnitMakeAbilityPermanent(u, true, BonusAbilitys[bonusType * ABILITY_COUNT + i])
set ammount = ammount - PowersOf2[i]
else
call UnitRemoveAbility(u, BonusAbilitys[bonusType * ABILITY_COUNT + i])
endif
set i = i - 1
exitwhen i < 0
endloop
return true
endfunction
function UnitGetBonus takes unit u, integer bonusType returns integer
local integer i = 0
local integer ammount = 0
if GetUnitAbilityLevel(u, BonusAbilitys[bonusType * ABILITY_COUNT + ABILITY_COUNT - 1]) > 0 then
set ammount = MinBonus
endif
loop
if GetUnitAbilityLevel(u, BonusAbilitys[bonusType * ABILITY_COUNT + i]) > 0 then
set ammount = ammount + PowersOf2[i]
endif
set i = i + 1
exitwhen i == ABILITY_COUNT - 2
endloop
return ammount
endfunction
function UnitAddBonus takes unit u, integer bonusType, integer ammount returns boolean
return UnitSetBonus(u, bonusType, UnitGetBonus(u, bonusType) + ammount)
endfunction
private function Initialize takes nothing returns nothing
local integer i = 1
local unit u
set PowersOf2[0] = 1
loop
set PowersOf2[i] = PowersOf2[i - 1] * 2
set i = i + 1
exitwhen i == ABILITY_COUNT
endloop
set MaxBonus = PowersOf2[ABILITY_COUNT - 1] - 1
set MinBonus = -PowersOf2[ABILITY_COUNT - 1]
call InitializeAbilitys()
if PRELOAD_ABILITYS then
set u = CreateUnit(Player(15), PRELOAD_DUMMY_UNIT, 0, 0, 0)
set i = 0
loop
exitwhen i == BONUS_TYPES * ABILITY_COUNT
call UnitAddAbility(u, BonusAbilitys[i])
set i = i + 1
endloop
call RemoveUnit(u)
endif
endfunction
endlibrary
//TESH.scrollpos=10
//TESH.alwaysfold=0
// About these macros:
//
// The first paramiter is the rawcode for the ability. The convention used here by default is:
// Zx followed by an uppercase letter, unique to the bonus, followed by a 0-9-a-z representing
// the base of two the ability applys.
//
// The second paramiter is the display value of the ability. This is so that they all line up
// neatly in the object editor.
//
// The third paramiter is the actual value the bonus ability applys.
//
// Note that you should copy this in to its own trigger, save once, close the map and re-open
// it in the World Editor, then disable the trigger you copied this in to. If you do not
// disable the trigger, saving will take an insane ammount of time every time you save. If you
// make any changes below, you can simply enable the trigger again, save again, then disable
// the trigger again.
// BonusMod - Armor
//============================================================================================
//! textmacro ArmorAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AId1 $RAWCODE$ Idef 1 $VALUE$ anam "BonusMod - Armor ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNHumanArmorUpOne.blp
//! endtextmacro
//! runtextmacro ArmorAbility("ZxA0", "+0001", "1")
//! runtextmacro ArmorAbility("ZxA1", "+0002", "2")
//! runtextmacro ArmorAbility("ZxA2", "+0004", "4")
//! runtextmacro ArmorAbility("ZxA3", "+0008", "8")
//! runtextmacro ArmorAbility("ZxA4", "+0016", "16")
//! runtextmacro ArmorAbility("ZxA5", "+0032", "32")
//! runtextmacro ArmorAbility("ZxA6", "+0064", "64")
//! runtextmacro ArmorAbility("ZxA7", "+0128", "128")
//! runtextmacro ArmorAbility("ZxA8", "+0256", "256")
//! runtextmacro ArmorAbility("ZxA9", "+0512", "512")
//! runtextmacro ArmorAbility("ZxAa", "+1024", "1024")
//! runtextmacro ArmorAbility("ZxAb", "+2048", "2048")
//! runtextmacro ArmorAbility("ZxAc", "-4096", "-4096")
// BonusMod - Damage
//============================================================================================
//! textmacro DamageAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AItg $RAWCODE$ Iatt 1 $VALUE$ anam "BonusMod - Damage ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNSteelMelee.blp
//! endtextmacro
//! runtextmacro DamageAbility("ZxB0", "+0001", "1")
//! runtextmacro DamageAbility("ZxB1", "+0002", "2")
//! runtextmacro DamageAbility("ZxB2", "+0004", "4")
//! runtextmacro DamageAbility("ZxB3", "+0008", "8")
//! runtextmacro DamageAbility("ZxB4", "+0016", "16")
//! runtextmacro DamageAbility("ZxB5", "+0032", "32")
//! runtextmacro DamageAbility("ZxB6", "+0064", "64")
//! runtextmacro DamageAbility("ZxB7", "+0128", "128")
//! runtextmacro DamageAbility("ZxB8", "+0256", "256")
//! runtextmacro DamageAbility("ZxB9", "+0512", "512")
//! runtextmacro DamageAbility("ZxBa", "+1024", "1024")
//! runtextmacro DamageAbility("ZxBb", "+2048", "2048")
//! runtextmacro DamageAbility("ZxBc", "-4096", "-4096")
// BonusMod - Sight Range
//============================================================================================
//! textmacro SightRangeAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIsi $RAWCODE$ Isib 1 $VALUE$ anam "BonusMod - Sight Range ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNTelescope.blp
//! endtextmacro
//! runtextmacro SightRangeAbility("ZxC0", "+0001", "1")
//! runtextmacro SightRangeAbility("ZxC1", "+0002", "2")
//! runtextmacro SightRangeAbility("ZxC2", "+0004", "4")
//! runtextmacro SightRangeAbility("ZxC3", "+0008", "8")
//! runtextmacro SightRangeAbility("ZxC4", "+0016", "16")
//! runtextmacro SightRangeAbility("ZxC5", "+0032", "32")
//! runtextmacro SightRangeAbility("ZxC6", "+0064", "64")
//! runtextmacro SightRangeAbility("ZxC7", "+0128", "128")
//! runtextmacro SightRangeAbility("ZxC8", "+0256", "256")
//! runtextmacro SightRangeAbility("ZxC9", "+0512", "512")
//! runtextmacro SightRangeAbility("ZxCa", "+1024", "1024")
//! runtextmacro SightRangeAbility("ZxCb", "+2048", "2048")
//! runtextmacro SightRangeAbility("ZxCc", "-4096", "-4096")
// BonusMod - Mana Regeneration
//============================================================================================
//! textmacro ManaRegenAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIrm $RAWCODE$ Imrp 1 $VALUE$ anam "BonusMod - Mana Regen ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNSobiMask.blp
//! endtextmacro
//! runtextmacro ManaRegenAbility("ZxD0", "+0001", "0.01")
//! runtextmacro ManaRegenAbility("ZxD1", "+0002", "0.02")
//! runtextmacro ManaRegenAbility("ZxD2", "+0004", "0.04")
//! runtextmacro ManaRegenAbility("ZxD3", "+0008", "0.08")
//! runtextmacro ManaRegenAbility("ZxD4", "+0016", "0.16")
//! runtextmacro ManaRegenAbility("ZxD5", "+0032", "0.32")
//! runtextmacro ManaRegenAbility("ZxD6", "+0064", "0.64")
//! runtextmacro ManaRegenAbility("ZxD7", "+0128", "1.28")
//! runtextmacro ManaRegenAbility("ZxD8", "+0256", "2.56")
//! runtextmacro ManaRegenAbility("ZxD9", "+0512", "5.12")
//! runtextmacro ManaRegenAbility("ZxDa", "+1024", "10.24")
//! runtextmacro ManaRegenAbility("ZxDb", "+2048", "20.48")
//! runtextmacro ManaRegenAbility("ZxDc", "-4096", "-40.96")
// BonusMod - Life Regenration
//============================================================================================
//! textmacro LifeRegenAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a Arel $RAWCODE$ Ihpr 1 $VALUE$ anam "BonusMod - Life Regen ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNRingSkull.blp
//! endtextmacro
//! runtextmacro LifeRegenAbility("ZxE0", "+0001", "1")
//! runtextmacro LifeRegenAbility("ZxE1", "+0002", "2")
//! runtextmacro LifeRegenAbility("ZxE2", "+0004", "4")
//! runtextmacro LifeRegenAbility("ZxE3", "+0008", "8")
//! runtextmacro LifeRegenAbility("ZxE4", "+0016", "16")
//! runtextmacro LifeRegenAbility("ZxE5", "+0032", "32")
//! runtextmacro LifeRegenAbility("ZxE6", "+0064", "64")
//! runtextmacro LifeRegenAbility("ZxE7", "+0128", "128")
//! runtextmacro LifeRegenAbility("ZxE8", "+0256", "256")
//! runtextmacro LifeRegenAbility("ZxE9", "+0512", "512")
//! runtextmacro LifeRegenAbility("ZxEa", "+1024", "1024")
//! runtextmacro LifeRegenAbility("ZxEb", "+2048", "2048")
//! runtextmacro LifeRegenAbility("ZxEc", "-4096", "-4096")
// BonusMod - Strength
//============================================================================================
//! textmacro HeroStrAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIa1 $RAWCODE$ Iagi 1 0 Iint 1 0 Istr 1 $VALUE$ anam "BonusMod - Hero STR ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNGoldRing.blp
//! endtextmacro
//! runtextmacro HeroStrAbility("ZxF0", "+0001", "1")
//! runtextmacro HeroStrAbility("ZxF1", "+0002", "2")
//! runtextmacro HeroStrAbility("ZxF2", "+0004", "4")
//! runtextmacro HeroStrAbility("ZxF3", "+0008", "8")
//! runtextmacro HeroStrAbility("ZxF4", "+0016", "16")
//! runtextmacro HeroStrAbility("ZxF5", "+0032", "32")
//! runtextmacro HeroStrAbility("ZxF6", "+0064", "64")
//! runtextmacro HeroStrAbility("ZxF7", "+0128", "128")
//! runtextmacro HeroStrAbility("ZxF8", "+0256", "256")
//! runtextmacro HeroStrAbility("ZxF9", "+0512", "512")
//! runtextmacro HeroStrAbility("ZxFa", "+1024", "1024")
//! runtextmacro HeroStrAbility("ZxFb", "+2048", "2048")
//! runtextmacro HeroStrAbility("ZxFc", "-4096", "-4096")
// BonusMod - Agility
//============================================================================================
//! textmacro HeroAgiAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIa1 $RAWCODE$ Iagi 1 $VALUE$ Iint 1 0 Istr 1 0 anam "BonusMod - Hero AGI ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNGoldRing.blp
//! endtextmacro
//! runtextmacro HeroAgiAbility("ZxG0", "+0001", "1")
//! runtextmacro HeroAgiAbility("ZxG1", "+0002", "2")
//! runtextmacro HeroAgiAbility("ZxG2", "+0004", "4")
//! runtextmacro HeroAgiAbility("ZxG3", "+0008", "8")
//! runtextmacro HeroAgiAbility("ZxG4", "+0016", "16")
//! runtextmacro HeroAgiAbility("ZxG5", "+0032", "32")
//! runtextmacro HeroAgiAbility("ZxG6", "+0064", "64")
//! runtextmacro HeroAgiAbility("ZxG7", "+0128", "128")
//! runtextmacro HeroAgiAbility("ZxG8", "+0256", "256")
//! runtextmacro HeroAgiAbility("ZxG9", "+0512", "512")
//! runtextmacro HeroAgiAbility("ZxGa", "+1024", "1024")
//! runtextmacro HeroAgiAbility("ZxGb", "+2048", "2048")
//! runtextmacro HeroAgiAbility("ZxGc", "-4096", "-4096")
// BonusMod - Intelligence
//============================================================================================
//! textmacro HeroIntAbility takes RAWCODE, DISPLAYVALUE, VALUE
//! external ObjectMerger w3a AIa1 $RAWCODE$ Iagi 1 0 Iint 1 $VALUE$ Istr 1 0 anam "BonusMod - Hero INT ($DISPLAYVALUE$)" aite 0 ansf "" aart ReplaceableTextures\CommandButtons\BTNGoldRing.blp
//! endtextmacro
//! runtextmacro HeroIntAbility("ZxH0", "+0001", "1")
//! runtextmacro HeroIntAbility("ZxH1", "+0002", "2")
//! runtextmacro HeroIntAbility("ZxH2", "+0004", "4")
//! runtextmacro HeroIntAbility("ZxH3", "+0008", "8")
//! runtextmacro HeroIntAbility("ZxH4", "+0016", "16")
//! runtextmacro HeroIntAbility("ZxH5", "+0032", "32")
//! runtextmacro HeroIntAbility("ZxH6", "+0064", "64")
//! runtextmacro HeroIntAbility("ZxH7", "+0128", "128")
//! runtextmacro HeroIntAbility("ZxH8", "+0256", "256")
//! runtextmacro HeroIntAbility("ZxH9", "+0512", "512")
//! runtextmacro HeroIntAbility("ZxHa", "+1024", "1024")
//! runtextmacro HeroIntAbility("ZxHb", "+2048", "2048")
//! runtextmacro HeroIntAbility("ZxHc", "-4096", "-4096")
// Don't let a //! external command be the last line in a trigger!
//TESH.scrollpos=35
//TESH.alwaysfold=0
//////////////////////////////////////////////////////////////////////////////////////////
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//@ SetUnitMaxState
//@=======================================================================================
//@ Credits:
//@---------------------------------------------------------------------------------------
//@ Written by:
//@ Earth-Fury
//@ Based on the work of:
//@ Blade.dk
//@
//@ If you use this system, please credit all of the people mentioned above in your map.
//@=======================================================================================
//@ SetUnitMaxState Readme
//@---------------------------------------------------------------------------------------
//@
//@ SetUnitMaxState() is a function origionally written by Blade.dk. It takes advantage of
//@ a bug which was introduced in one of the patches: Bonus life and mana abilitys will
//@ only ever add the bonus ammount for level 1. However, when removed, they will remove
//@ the ammount they should have added at their current level. This allows you to change a
//@ units maximum life and mana, without adding a perminent ability to the unit.
//@
//@---------------------------------------------------------------------------------------
//@ Adding SetUnitMaxState to your map:
//@
//@ Simply copy this library in to a trigger which has been converted to custom text.
//@ After that, you must copy over the abilitys. This is made easy by the ObjectMerger in
//@ JASS NewGen. Distributed with this system are //! external calls to the ObjectMerger.
//@ Simply copy both of them in to your map, save your map, close and reopen your map in
//@ the editor, and remove the external calls. (Or otherwise disable them. Removing the !
//@ after the // works.)
//@
//@---------------------------------------------------------------------------------------
//@ Using SetUnitMaxState:
//@
//@ nothing SetUnitMaxState(unit <target>, unitstate <state>, real <new value>)
//@
//@ This function changes <target>'s unitstate <state> to be eqal to <new value>. Note
//@ that the only valid unitstates this function will use are UNIT_STATE_MAX_MAN and
//@ UNIT_STATE_MAX_LIFE. Use SetUnitState() to change other unitstates.
//@
//@ nothing AddUnitMaxState(unit <target>, unitstate <state>, real <add value>)
//@
//@ This function adds <add value> to <target>'s <state> unitstate. <add value> can be
//@ less than 0, making this function reduce the specified unitstate. This function will
//@ only work with the unitstates UNIT_STATE_MAX_LIFE and UNIT_STATE_MAX_MANA.
//@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
//////////////////////////////////////////////////////////////////////////////////////////
library SetUnitMaxState initializer Initialize
globals
//========================================================================================
// Configuration
//========================================================================================
// The rawcode of the life ability:
private constant integer MAX_STATE_LIFE_ABILITY = 'Zx01'
// The rawcode of the mana ability:
private constant integer MAX_STATE_MANA_ABILITY = 'Zx00'
// The maximum power of two the abilitys use:
private constant integer MAX_STATE_MAX_POWER = 8
endglobals
//========================================================================================
// System Code
//----------------------------------------------------------------------------------------
// Do not edit below this line unless you wish to change the way the system works.
//========================================================================================
globals
private integer array PowersOf2
endglobals
function SetUnitMaxState takes unit u, unitstate state, real newValue returns nothing
local integer stateAbility
local integer newVal = R2I(newValue)
local integer i = MAX_STATE_MAX_POWER
local integer offset
if state == UNIT_STATE_MAX_LIFE then
set stateAbility = MAX_STATE_LIFE_ABILITY
elseif state == UNIT_STATE_MAX_MANA then
set stateAbility = MAX_STATE_MANA_ABILITY
else
debug call BJDebugMsg("SetUnitMaxState Error: Invalid unitstate")
return
endif
set newVal = newVal - R2I(GetUnitState(u, state))
if newVal > 0 then
set offset = MAX_STATE_MAX_POWER + 3
elseif newVal < 0 then
set offset = 2
set newVal = -newVal
else
return
endif
loop
exitwhen newVal == 0 or i < 0
if newVal >= PowersOf2[i] then
call UnitAddAbility(u, stateAbility)
call SetUnitAbilityLevel(u, stateAbility, offset + i)
call UnitRemoveAbility(u, stateAbility)
set newVal = newVal - PowersOf2[i]
else
set i = i - 1
endif
endloop
endfunction
function AddUnitMaxState takes unit u, unitstate state, real addValue returns nothing
call SetUnitMaxState(u, state, GetUnitState(u, state) + addValue)
endfunction
private function Initialize takes nothing returns nothing
local integer i = 1
set PowersOf2[0] = 1
loop
set PowersOf2[i] = PowersOf2[i - 1] * 2
set i = i + 1
exitwhen i == MAX_STATE_MAX_POWER + 3
endloop
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
// The Mana ability:
//! external ObjectMerger w3a AImz Zx00 alev 19 aite 0 Iman 1 0 Iman 2 1 Iman 3 2 Iman 4 4 Iman 5 8 Iman 6 16 Iman 7 32 Iman 8 64 Iman 9 128 Iman 10 256 Iman 11 -1 Iman 12 -2 Iman 13 -4 Iman 14 -8 Iman 15 -16 Iman 16 -32 Iman 17 -64 Iman 18 -128 Iman 19 -256 anam "SetUnitMaxState - Mana" ansf "" aart ReplaceableTextures\CommandButtons\BTNManaStone.blp
// The Life ability:
//! external ObjectMerger w3a AIlf Zx01 alev 19 aite 0 Ilif 1 0 Ilif 2 1 Ilif 3 2 Ilif 4 4 Ilif 5 8 Ilif 6 16 Ilif 7 32 Ilif 8 64 Ilif 9 128 Ilif 10 256 Ilif 11 -1 Ilif 12 -2 Ilif 13 -4 Ilif 14 -8 Ilif 15 -16 Ilif 16 -32 Ilif 17 -64 Ilif 18 -128 Ilif 19 -256 anam "SetUnitMaxState - Life" ansf "" aart ReplaceableTextures\CommandButtons\BTNHealthStone.blp
// Don't let a //! external command be the last line in a trigger!
//TESH.scrollpos=30
//TESH.alwaysfold=0
library MinigameInitialize initializer Init
globals
unit playerUnit
private integer sec = 0
private integer min = 0
private dialog d
private button miniGame
private button freeWorld
endglobals
private function TimeDisplay takes nothing returns nothing
set sec = sec + 1
if sec == 60 then
set sec = 0
set min = min + 1
call SetPlayerState(Player(0), PLAYER_STATE_RESOURCE_LUMBER, min)
endif
call SetPlayerState(Player(0), PLAYER_STATE_RESOURCE_FOOD_USED, sec)
endfunction
private function ButtonClicked takes nothing returns boolean
local button b = GetClickedButton()
if b == miniGame then
call TriggerEvaluate(planeSpawn)
call TriggerEvaluate(creepSpawn)
call TriggerEvaluate(itemSpawn)
call TriggerEvaluate(expTrigger)
call TriggerEvaluate(arenaTrigger)
elseif b == freeWorld then
call SetHeroLevel(playerUnit, 10, true)
call TriggerEvaluate(freeWorldTrigger)
endif
call DialogDestroy(d)
return false
endfunction
private function CreateDialog takes nothing returns boolean
local trigger t = CreateTrigger()
set d = DialogCreate()
call DialogSetMessage(d, "Select Mini-game:")
set miniGame = DialogAddButton(d, "Survival", 0)
set freeWorld = DialogAddButton(d, "Free world", 0)
call TriggerRegisterDialogEvent(t, d)
call TriggerAddCondition(t, Condition(function ButtonClicked))
call DialogDisplay(Player(0), d, true)
//Timing
call TimerStart(CreateTimer(), 1.000000, true, function TimeDisplay)
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
local rect r = Rect(-1000, -1000, 1000, 1000)
local integer i = 1
local integer j
local fogmodifier fg = CreateFogModifierRect(Player(0), FOG_OF_WAR_VISIBLE, r, true, true)
call FogModifierStart(fg)
call RemoveRect(r)
set fg = null
set r = null
set playerUnit = gg_unit_Nsnp_0001
loop
exitwhen i > 4
set j = 1
loop
exitwhen j > 4
if i != j then
call SetPlayerAlliance(Player(i), Player(j), ALLIANCE_PASSIVE, true)
endif
set j = j + 1
endloop
set i = i + 1
endloop
//Create Dialog
call TriggerRegisterTimerEvent(t, 0.0, false)
call TriggerAddCondition(t, Condition(function CreateDialog))
call SetPlayerState(Player(0), PLAYER_STATE_RESOURCE_LUMBER, 0)
call SetPlayerState(Player(0), PLAYER_STATE_RESOURCE_FOOD_USED, 0)
endfunction
endlibrary
//TESH.scrollpos=34
//TESH.alwaysfold=0
library PlaneSpawn
native UnitAlive takes unit u returns boolean
globals
trigger planeSpawn
endglobals
private struct Summoner extends array
private unit u
private timer t
private integer wave
private real waveInterval
private real deathTime
private real hp
private real max
private real percentage
private boolean changeTime
private boolean ready
private unit plane
private thistype next
private thistype prev
private static constant integer SPELL_ID = 'Aaid'
private static constant integer TOGGLE_ID = 'AAtg'
private static constant integer PLANE_ID = 'plne'
private static constant real DISTANCE = 1000
private static constant real DURATION = 8
private static integer counter = 0
private static hashtable hash = InitHashtable()
private static timer periodic = CreateTimer()
private method checkHp takes nothing returns nothing
if GetUnitTypeId(.plane) == 0 then //Detect if remove
set .percentage = .hp/.max
//Remove from List
set .prev.next = .next
set .next.prev = .prev
if thistype(0).next == 0 then
call PauseTimer(periodic)
endif
else
if UnitAlive(.plane) then
set .hp = GetWidgetLife(.plane)
else
set .ready = false
set .percentage = 1
call AddHeroXP(playerUnit, 150, true)
call DisplayTimedTextToPlayer(Player(0), 0, 0, 5, "Plane destroyed will not appear in the next wave")
//Remove from List
set .prev.next = .next
set .next.prev = .prev
if thistype(0).next == 0 then
call PauseTimer(periodic)
endif
endif
endif
endmethod
private static method pickAll takes nothing returns nothing
local thistype this = thistype(0).next
loop
exitwhen this == 0
call .checkHp()
set this = .next
endloop
endmethod
private static method enter takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this
if GetUnitTypeId(u) == PLANE_ID then
set this = GetPlayerId(GetOwningPlayer(u))
if integer(this) > 0 and integer(this) < 5 then
set .max = GetUnitState(u, UNIT_STATE_MAX_LIFE)
call SetWidgetLife(u, .max*.percentage)
set .plane = u
//Linked-list
set .next = 0
set .prev = thistype(0).prev
set .next.prev = this
set .prev.next = this
if .prev == 0 then
call TimerStart(periodic, 0.03125, true, function thistype.pickAll)
endif
endif
endif
set u = null
endmethod
private static method toggle takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = LoadInteger(hash, GetHandleId(t), 0)
call UnitRemoveAbility(.u, TOGGLE_ID)
call UnitResetCooldown(.u)
call DestroyTimer(t)
set t = null
endmethod
private static method onSummon takes nothing returns nothing
local thistype this = LoadInteger(hash, GetHandleId(GetExpiredTimer()), 0)
local real angle = GetRandomReal(-bj_PI, bj_PI)
local real x2 = GetUnitX(playerUnit)
local real y2 = GetUnitY(playerUnit)
local real x1 = x2 + DISTANCE*Cos(angle)
local real y1 = y2 + DISTANCE*Sin(angle)
local integer mod = .wave - (.wave/10)*10
local timer tempTimer = CreateTimer()
local real percentHp
local thistype new
if .changeTime then
set .changeTime = false
call TimerStart(.t, waveInterval, true, function thistype.onSummon)
endif
call SetUnitState(.u, UNIT_STATE_MANA, 999999)
if .ready then
//Little heal
if .percentage != 1 then
set .percentage = .percentage + 0.05 //+5% of max life
endif
call SetUnitX(.u, x1)
call SetUnitY(.u, y1)
call PingMinimapEx(x1, y1, 5, 255, 0, 0, true)
call IssuePointOrder(.u, "flare", x2, y2)
//Remove the toggle after a very quick delay
call SaveInteger(hash, GetHandleId(tempTimer), 0, this)
call TimerStart(tempTimer, 1.0, false, function thistype.toggle)
else
set .ready = true
endif
if mod == 3 or mod == 6 then
call SelectHeroSkill(.u, SPELL_ID)
endif
if mod == 0 then
call SetHeroLevel(.u, 1, false)
call UnitRemoveAbility(.u, SPELL_ID)
call UnitAddAbility(.u, SPELL_ID)
call SelectHeroSkill(.u, SPELL_ID)
if this == 1 and .wave <= 40 then
set new = thistype.create()
call DisplayTimedTextToPlayer(Player(0), 0, 0, 5, "A new enemy Plane has arrived")
set new.wave = .wave
endif
endif
if this == 1 then
call DisplayTimedTextToPlayer(Player(0), 0, 0, 5, "Plane Wave " + I2S(.wave))
endif
if .wave == 50 then
call DisplayTimedTextToPlayer(Player(0), 0, 0, 5, "Last Plane Wave")
call PauseTimer(GetExpiredTimer())
endif
set .wave = .wave + 1
endmethod
private static method create takes nothing returns thistype
local thistype this = counter + 1
set counter = counter + 1
//Initialize summoner unit
set .u = CreateUnit(Player(this), 'Nsnp', 750, 750, 0)
call SelectHeroSkill(.u, SPELL_ID)
call SetHeroLevel(.u, 10, false)
call UnitAddAbility(.u, 'Aloc')
call SetUnitVertexColor(.u, 0, 0, 0, 0)
call UnitRemoveAbility(.u, 'Aatk')
//Initialize Timing
set .t = CreateTimer()
call SaveInteger(hash, GetHandleId(.t), 0, this)
set .waveInterval = 15
set .wave = 1
set .deathTime = 15
set .changeTime = true
set .percentage = 1
set .ready = true
call TimerStart(.t, 0, false, function thistype.onSummon)
return this
endmethod
private static method controlledInit takes nothing returns boolean
local trigger t = CreateTrigger()
local region reg = CreateRegion()
//For detecting incoming enemy planes
call RegionAddRect(reg, bj_mapInitialPlayableArea)
call TriggerRegisterEnterRegion(t, reg, null)
call TriggerAddAction(t, function thistype.enter)
call thistype.create()
return false
endmethod
private static method onInit takes nothing returns nothing
set planeSpawn = CreateTrigger()
call TriggerAddCondition(planeSpawn, Condition(function thistype.controlledInit))
endmethod
endstruct
endlibrary
//TESH.scrollpos=67
//TESH.alwaysfold=0
library CreepSpawn initializer OnInit
globals
trigger creepSpawn
private hashtable hash = InitHashtable()
private integer array unitId
private integer wave
private constant real DISTANCE = 2000
endglobals
private function SummonCreeps takes nothing returns nothing
local integer i = 1
local integer j = 1
local unit u
local real angle = GetRandomReal(-bj_PI, bj_PI)
local real x = DISTANCE*Cos(angle)
local real y = DISTANCE*Sin(angle)
loop
exitwhen i > 4
set j = LoadInteger(hash, wave, unitId[i])
loop
exitwhen j <= 0
set u = CreateUnit(Player(1), unitId[i], x, y, 0)
call IssuePointOrder(u, "attack", 0, 0)
set j = j - 1
endloop
set i = i + 1
endloop
call DisplayTimedTextToPlayer(Player(0), 0, 0, 5, "Creep Wave " + I2S(wave))
set wave = wave + 1
set u = null
endfunction
private function ControlledInit takes nothing returns boolean
call TimerStart(CreateTimer(), 60, true, function SummonCreeps)
set wave = 1
//Initialize Per Wave Quantities
set unitId[1] = 'hfoo'
set unitId[2] = 'hrif'
set unitId[3] = 'hkni'
set unitId[4] = 'hsor'
//call SaveInteger(hash, waveNumber, unitType, quantity)
//WAVE1
call SaveInteger(hash, 1, 'hfoo', 3)
call SaveInteger(hash, 1, 'hrif', 0)
call SaveInteger(hash, 1, 'hkni', 0)
call SaveInteger(hash, 1, 'hsor', 0)
//WAVE2
call SaveInteger(hash, 2, 'hfoo', 3)
call SaveInteger(hash, 2, 'hrif', 1)
call SaveInteger(hash, 2, 'hkni', 0)
call SaveInteger(hash, 2, 'hsor', 0)
//WAVE3
call SaveInteger(hash, 3, 'hfoo', 4)
call SaveInteger(hash, 3, 'hrif', 2)
call SaveInteger(hash, 3, 'hkni', 0)
call SaveInteger(hash, 3, 'hsor', 0)
//WAVE4
call SaveInteger(hash, 4, 'hfoo', 3)
call SaveInteger(hash, 4, 'hrif', 2)
call SaveInteger(hash, 4, 'hkni', 1)
call SaveInteger(hash, 4, 'hsor', 0)
//WAVE5
call SaveInteger(hash, 5, 'hfoo', 3)
call SaveInteger(hash, 5, 'hrif', 3)
call SaveInteger(hash, 5, 'hkni', 0)
call SaveInteger(hash, 5, 'hsor', 2)
//WAVE6
call SaveInteger(hash, 6, 'hfoo', 5)
call SaveInteger(hash, 6, 'hrif', 3)
call SaveInteger(hash, 6, 'hkni', 2)
call SaveInteger(hash, 6, 'hsor', 2)
//WAVE7
call SaveInteger(hash, 7, 'hfoo', 5)
call SaveInteger(hash, 7, 'hrif', 5)
call SaveInteger(hash, 7, 'hkni', 4)
call SaveInteger(hash, 7, 'hsor', 3)
//WAVE8
call SaveInteger(hash, 8, 'hfoo', 0)
call SaveInteger(hash, 8, 'hrif', 12)
call SaveInteger(hash, 8, 'hkni', 0)
call SaveInteger(hash, 8, 'hsor', 8)
//WAVE9
call SaveInteger(hash, 9, 'hfoo', 8)
call SaveInteger(hash, 9, 'hrif', 8)
call SaveInteger(hash, 9, 'hkni', 7)
call SaveInteger(hash, 9, 'hsor', 7)
//WAVE10
call SaveInteger(hash, 10, 'hfoo', 10)
call SaveInteger(hash, 10, 'hrif', 10)
call SaveInteger(hash, 10, 'hkni', 10)
call SaveInteger(hash, 10, 'hsor', 10)
return false
endfunction
private function OnInit takes nothing returns nothing
set creepSpawn = CreateTrigger()
call TriggerAddCondition(creepSpawn, Condition(function ControlledInit))
endfunction
endlibrary
//TESH.scrollpos=8
//TESH.alwaysfold=0
library ItemSpawn
globals
trigger itemSpawn
endglobals
struct ItemSpawner extends array
private static integer counter = 0
private static integer array itemId
private static method spawnItem takes nothing returns nothing
call CreateItem(itemId[counter], 0, 0)
set counter = counter + 1
if counter > 7 then
set counter = 0
endif
endmethod
private static method controlledInit takes nothing returns boolean
set itemId[0] = 'hslv' //Healing Salve
set itemId[1] = 'rej6' //Greater Scroll of Replenishment
set itemId[2] = 'rres' //Rune of Restoration
set itemId[3] = 'pdiv' //Potion of Divinity
set itemId[4] = 'mnst' //Manastone
set itemId[5] = 'sres' //Scroll of Restoration
set itemId[6] = 'fgrd' //Red Drake Egg
set itemId[7] = 'srrc' //Scroll of Ressurection
call TimerStart(CreateTimer(), 45, true, function thistype.spawnItem)
return false
endmethod
private static method onInit takes nothing returns nothing
set itemSpawn = CreateTrigger()
call TriggerAddCondition(itemSpawn, Condition(function thistype.controlledInit))
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library PeriodicExp
globals
trigger expTrigger
endglobals
struct Experience extends array
private static integer amount
private static method spawnItem takes nothing returns nothing
call AddHeroXP(playerUnit, amount, true)
set amount = amount + 5
endmethod
private static method controlledInit takes nothing returns boolean
set amount = 25
call TimerStart(CreateTimer(), 20, true, function thistype.spawnItem)
return false
endmethod
private static method onInit takes nothing returns nothing
set expTrigger = CreateTrigger()
call TriggerAddCondition(expTrigger, Condition(function thistype.controlledInit))
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library Arena
globals
trigger arenaTrigger
endglobals
private struct Arena extends array
private static group g = CreateGroup()
private static constant real AREA_SIZE = 500
private static real amount = 1.5
private static method check takes nothing returns nothing
local real x
local real y
local unit u
call GroupEnumUnitsInRect(g, bj_mapInitialPlayableArea, null)
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
if GetOwningPlayer(u) == Player(0) and not IsUnitType(u, UNIT_TYPE_MECHANICAL) then
set x = GetUnitX(u)
set y = GetUnitY(u)
if x > AREA_SIZE or x < -AREA_SIZE or y > AREA_SIZE or y < -AREA_SIZE then
call UnitDamageTarget(u, u, amount, true, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
endif
endloop
endmethod
private static method increase1 takes nothing returns nothing
set amount = amount + 1.0
call DestroyTimer(GetExpiredTimer())
endmethod
private static method increase2 takes nothing returns nothing
set amount = amount + 1.5
call DestroyTimer(GetExpiredTimer())
endmethod
private static method controlledInit takes nothing returns boolean
call AddLightningEx("MBUR", false, -AREA_SIZE, -AREA_SIZE, 0, AREA_SIZE, -AREA_SIZE, 0)
call AddLightningEx("MBUR", false, AREA_SIZE, -AREA_SIZE, 0, AREA_SIZE, AREA_SIZE, 0)
call AddLightningEx("MBUR", false, AREA_SIZE, AREA_SIZE, 0, -AREA_SIZE, AREA_SIZE, 0)
call AddLightningEx("MBUR", false, -AREA_SIZE, AREA_SIZE, 0, -AREA_SIZE, -AREA_SIZE, 0)
call TimerStart(CreateTimer(), 0.1, true, function thistype.check)
call TimerStart(CreateTimer(), 120, false, function thistype.increase1)
call TimerStart(CreateTimer(), 300, false, function thistype.increase2)
return false
endmethod
private static method onInit takes nothing returns nothing
set arenaTrigger = CreateTrigger()
call TriggerAddCondition(arenaTrigger, Condition(function thistype.controlledInit))
endmethod
endstruct
endlibrary
//TESH.scrollpos=5
//TESH.alwaysfold=0
library FreeWorld
globals
trigger freeWorldTrigger
endglobals
struct FreeWorld extends array
private static method getRandomId takes nothing returns integer
local real rnd = GetRandomReal(0.00, 1.00)
if rnd < 0.3 then
return 'hfoo'
elseif rnd < 0.45 then
return 'hrif'
elseif rnd < 0.6 then
return 'hkni'
elseif rnd < 0.75 then
return 'hsor'
elseif rnd < 0.8 then
return 'hgyr'
elseif rnd < 0.85 then
return 'hgry'
elseif rnd < 0.9 then
return 'hdhw'
elseif rnd < 0.95 then
return 'Hpal'
endif
return 'Hamg'
endmethod
private static method controlledInit takes nothing returns boolean
local integer i = 0
local integer j = 0
local real angle = 0
local integer rnd
local integer id
local real distance
local real x
local real y
loop
exitwhen i > 14
set j = 0
set rnd = GetRandomInt(2, 7)
set distance = GetRandomReal(1500, 3000)
set x = distance*Cos(angle)
set y = distance*Sin(angle)
loop
exitwhen j > rnd
call CreateUnit(Player(1), getRandomId(), x + GetRandomReal(-300, 300), y + GetRandomReal(-300, 300), GetRandomReal(0, 360))
set j = j + 1
endloop
set i = i + 1
set angle = angle + GetRandomReal(15, 25)*bj_DEGTORAD
endloop
return false
endmethod
private static method onInit takes nothing returns nothing
set freeWorldTrigger = CreateTrigger()
call TriggerAddCondition(freeWorldTrigger, Condition(function thistype.controlledInit))
endmethod
endstruct
endlibrary