//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
BS_Caster | unit | No | |
BS_Player | player | No | |
BS_Target | unit | No | |
CF_Ability | abilcode | No | |
CF_Attacktype | attacktype | No | |
CF_BurnAttach | string | No | |
CF_BurnEff | string | No | |
CF_Caster | unit | Yes | |
CF_CEff | string | No | |
CF_CurrentIndex | integer | No | |
CF_Damage | real | Yes | |
CF_Damagetype | damagetype | No | |
CF_Gap | real | No | |
CF_HitAttach | string | No | |
CF_HitEff | string | No | |
CF_Level | integer | Yes | |
CF_LineEff | string | No | |
CF_Loop | integer | Yes | |
CF_MaxEff | integer | No | |
CF_MaxIndex | integer | No | |
CF_Neutral | player | No | |
CF_PeriodicDamage | real | Yes | |
CF_Target | unit | Yes | |
CF_Waves | integer | Yes |
//TESH.scrollpos=452
//TESH.alwaysfold=0
library Missile /* v1.4
*************************************************************************************
*
* Creating custom projectiles in Warcraft III
*
* Major goal:
* No unessary external requirements.
* Implements code optional.
*
* Philosophy:
* I want that feature --> Compiler writes that code into your map script.
* I don't want that --> Compiler ignores that code completly.
*
* Important:
* Take yourself 2 minutes time to setup Missile correctly.
* Otherwise I can't guarantee, that Missile works the way you want.
* Once the setup is done, you can check out some examples and Missile will be easy
* to use for everyone. I promise it. Be aware that Missile does not compile
* when Cohadar's JassHelper is checked. Please use Vexorian's JassHelper instead.
*
* Do the setup at:
*
* 1.) Import instruction
* 2.) Configuration
*
* Credits to Dirac, emjlr3, AceHart
*
*************************************************************************************
*
* */ requires /*
*
* - Missile requires nothing
*
*************************************************************************************
*
* Optional requirements listed can reduce overall code generation,
* add safety mechanims, decrease overhead and optimize handle management.
* For a better overview I put them into blocks.
*
* I recommend to use one per block in your map.
*
* a) For best debug results:
* */ optional ErrorMessage /* [url]https://github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage[/url]
*
* b) Fatal error protection ( Case: unit out of world bounds ):
* - WorldBounds adds more overhead than BoundSentinel.
* */ optional WorldBounds /* [url]https://raw.githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j[/url]
* */ optional BoundSentinel /* [url]http://www.wc3c.net/showthread.php?t=102576[/url]
*
* c) Dummy recycling ( Speed gain, memory management ):
* - Uses MissileRecylcer over Dummy.
* - Due to positive user feedbacks I highly recommend to use MissileRecycler.
* */ optional MissileRecycler /* [url]http://www.hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/[/url]
* */ optional Dummy /* [url]https://github.com/nestharus/JASS/blob/master/jass/Systems/Dummy/Dummy.w3x[/url]
* */ optional xedummy /* [url]http://www.wc3c.net/showthread.php?t=101150[/url]
*
* d) Group handle recycling ( Speed gain )
* */ optional GroupUtils /* [url]http://www.wc3c.net/showthread.php?t=104464[/url]
*
* e) Misc ( Absolutely not needed, just listed to avoid an onIndex event. )
* */ optional UnitIndexer /* [url]https://github.com/nestharus/JASS/tree/master/jass/Systems/Unit%20Indexer[/url]
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* You need a dummy unit, using Vexorians "dummy.mdx".
* If you are using water terrain in your map, the movement type has to be "Hover".
* This unit must use the locust and crow form ability. (Aloc & Amrf)
* ¯¯¯¯
* Copy Missile into to your map.
* Libraries listed as optional may improve Missile,
* however aren't mandatory to have.
*
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Eight constants and two functions to setup and Missile is ready to use!
*/
globals
/**
* Missiles are moved periodically. 1/32 is recommended.
* - Small values may cause performance issues.
* - Large values may look fishy.
*/
public constant real TIMER_TIMEOUT = 0.031250000
/**
* Owner of all Missile dummies. Should be a neutral player in your map.
*/
public constant player NEUTRAL_PASSIVE = Player(PLAYER_NEUTRAL_PASSIVE)
/**
* Raw code of the dummy unit. Object Editor (F6)
* Must be correct, otherwise you will experience lags as game time goes by.
* Units having other type ids will not be thrown into the recycler bin.
* In DEBUG_MODE you'll recieve an error, if the setup is incorrect.
*/
public constant integer DUMMY_UNIT_ID = 'h000'
/**
* The maximum collision size used in your map. If unsure use 197.
* Applies for units and destructables.
* A precise value can improve Missile's performance,
* since smaller values enumerate less widgtes per loop per missile.
* In DEBUG_MODE you'll recieve an error, if the setup is incorrect.
*/
public constant real MAXIMUM_COLLISION_SIZE = 197.
// Set booleans listed below to "true" and the compiler will write
// the feature into your map. Otherwise this code is completly ignored.
// o Yay, I want that --> "true"
// o Naah that's useless for me --> "false"
/**
* Set USE_DESTRUCTABLE_FILTER to "true" to:
* - Enable destructable collision for Missiles.
*/
public constant boolean USE_DESTRUCTABLE_FILTER = true
/**
* Enables z axis collision for Missiles. Normally it is only in x and y axis.
* This toggle will add overhead! Use when you need:
* - Missiles fly over or under widgets.
* - Determine between flying and walking units.
*/
public constant boolean USE_COLLISION_Z_FILTER = true
/**
* Set WRITE_DELAYED_MISSILE_RECYCLING to "true", to write a delayed dummy
* recycling system into library Missile. Very recommended in two cases:
*
* - You use a dummy recycling library like Dummy, xedummy.
* - You want to display death animations of the effect added to your missiles.
* - You DON'T need this, when using MissileRecycler!!!
*/
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING = true
/**
* Relates to WRITE_DELAYED_MISSILE_RECYCLING. It's the approximated
* death animation time for every effect you use on Missiles. Doesn't have to be precise.
*/
private constant real DELAYED_MISSILE_DEATH_ANIMATION_TIME = 2.
endglobals
/**
* Only required when USE_COLLSION_Z_FILTER = true.
* To detect z collision, every unit needs a body size value.
* How you reference these values is up to you: (Hashtable + HandleId/UnitTypeId, constant value for all units...)
* In the example I placed a constant value (100) for all units.
*
* If you don't want to use this feature, then return 0.
*/
function GetUnitBodySize takes unit whichUnit returns real
return 100. // LoadReal(hash, GetHandleId(whichUnit), KEY_UNIT_BODY_SIZE)<- Example
endfunction
/**
* Only required when USE_COLLSION_Z_FILTER = true.
* Same as GetUnitBodySize, but for destructables.
*/
function GetDestructableHeight takes destructable whichDestructable returns real
return GetDestructableOccluderHeight(whichDestructable)
endfunction
/**
* 2. API
* ¯¯¯¯¯¯
* struct Missile extends array
*
* Methods to create a new Missile instance:
*
* static method create takes real x, real y, real z, real angleInRadians, real distanceToTravel, real endZ returns Missile
* - Basic creator functions of Missile
*
* static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns Missile
* - Converts arguments to fit into create(), then calls create.
*
* static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns Missile
* - May launch any unit. Be aware that units of type Missile_DUMMY_UNIT_ID get recycled in the end.
*
*
* Members you can configurate:
*
* - source
* - target
* - real speed
* - real acceleration
* - real turn
* - real open --> use curve=
* - real height --> use arc=
* - string model
* - integer data
* - real collision
* - real collisionZ ( only available, if USE_COLLISION_Z_FILTER = true )
*
* Methods and operator overloading:
*
* method operator arc= takes real value returns nothing
* method operator curve= takes real value returns nothing
* - Only use radians in both methods!!
*
* method operator arc takes nothing returns real
* method operator curve takes nothing returns real
*
* method operator scale= takes real value returns nothing
* method operator scale takes nothing returns real
*
* method bounce takes nothing returns nothing
* method deflect takes real x, real y returns nothing
* method deflectEx takes real x, real y, real z returns nothing
*
* method destroy takes nothing returns nothing
* - deallocates a Missile.
*
* Missile offers various "optional" static methods, which can be written into
* any struct you implement the MissileStruct module in.
*
* Important:
*
* All of them return a boolean, which determines the future of that Missile instance.
* - return true --> destroy the Missile.
* - return false --> keep the Missile flying.
*
* Available static methods:
*
* static method onCollide takes Missile missile, unit hit returns boolean
* - Runs every time the missile collides with a unit.
*
*
* static method onPeriod takes Missile missile returns boolean
* - Runs every Missile_TIMER_TIMEOUT seconds.
*
*
* static method onFinish takes Missile missile returns boolean
* - Runs whenever the missile finishes it's course.
* - Runs before the missile is destroyed.
* - Does not run, if a Missile is destroyed by another method.
*
*
* static method onRemove takes Missile missile returns boolean
* - Runs whenever the missile is deallocated.
* - Return true will recycle a Missile delayed ( only if WRITE_DELAYED_MISSILE_RECYCLING = true )
* - Return false will recycle a Missile right away.
* - Runs always, if declared!!
*
*
* static method onDestructableFilter takes nothing returns boolean
* - Runs before onDestructable.
* - May create a better filter for enumerated destructables.
* - Only method which has no impact on the Missile instance.
*
*
* static method onDestructable takes Missile missile, destructable hit returns boolean
* - Runs every time the missile collides with a destructable.
* - Can use onDestructableFilter ( optional )
* - Runs after onDestructableFilter ( if filter is declared )
*
*
* module MissileStruct
*
* static method launch takes Missile toLaunch returns nothing
* - Well ... launches a Missile.
* - Example: call Fireball.launch( toLaunch )
*
************************************************************************************************/
// Hello and welcome to Missile.
// I wrote a guideline for every piece of code inside Missile, so you
// can easily understand how the code gets compiled and evaluated.
//
// Let's go!
// If you set WRITE_DELAYED_MISSILE_RECYCLING from the configs to true, the compiler will write
// a recycler bin, doing delayed dummy unit recycling, into Missle.
// If set to false, no code is created here as the code is ignored via "static if".
//
// The code of the macro is located at the bottom of Missile, as it would hurt read-ability if placed here.
//! runtextmacro WRITE_MISSILE_RECYCLE_BIN("WRITE_DELAYED_MISSILE_RECYCLING", "DELAYED_MISSILE_DEATH_ANIMATION_TIME")
public function GetLocZ takes real x, real y returns real
call MoveLocation(MissilePosition.loc, x, y)
return GetLocationZ(MissilePosition.loc)
endfunction
// This macro boxes a missiles positions and does the required trigonometry.
//
// The code of the macro is located at the bottom of Missile, as it would hurt read-ability if placed here.
//! runtextmacro WRITE_MISSILE_POSITION_CODE()
// keywords are replaced directives for a scope.
// Missiles structure works like a list with the folling methods:
// allocateCollection(), allocateNode(), insertEnd(node) and remove()
//
// MissileStructure is implemented as a module
// The code is located at the bottom of Missile, as it would also hurt read-ability if placed here.
private keyword MissileStructure
struct Missile extends array
implement MissileStructure
// I included enormous debug safety to Missile,
// in order to eradicate wrong user usage.
// You should do this with every system you write.
//
// Set MORE_SAFETY to false, once major mistakes are
// purged from your code. It will create a more realistic
// performance to a non DEBUG_MODE code, because many
// error checks are then excluded via "static if".
//
// Placed here on purpose, to ensure that less experienced users
// recieve the full range of debug features. Basically they will
// never find this constant boolean :)!
debug static constant boolean MORE_SAFETY = false
// launched prevents double launching! Very logical. DEBUG_MODE only.
debug boolean launched
// Effect attach point name. Read-only for shared usage in your map.
// Of course only if you read until here :)!
readonly static constant string ORIGIN = "origin"
// Defines a default unit hit box in range of 0 to 1,
// while 0 is the toe and 1 the head of a unit.
readonly static constant real HIT_BOX = (2./3.)
// Please do not use this group from somewhere else,
// as it may conflict with Missiles code execution.
// Rather go with bj_lastCreatedGroup.
readonly static group enumGroup = CreateGroup()
// Prevents a double free case.
readonly boolean allocated
// Useful struct members. Names chosen to be self-explaining.
private effect sfx
private string path
private real missileScale
readonly real angle// current angle
// For curved missiles the position of a missile is not
// equal to the x/y required by Missiles trigonometry.
// Therefore we need this member two times.
// x/y/z for your needs and xPrivate/yPrivate for cool mathematics.
private real xPrivate
private real yPrivate
readonly MissilePosition origin
readonly MissilePosition impact
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly unit dummy
readonly group unitsHit
// wantDestroy deallocates an instance on the next
// timer callback. 100% safe to use.
boolean wantDestroy
// recycle is set to true, when a missile reached
// it's destination. Then it may be destroyed.
boolean recycle
// distance traveled.
real distance
// Members for your needs.
// No operator overloading, expect in a few cases.
unit target
unit source
// As the dummy always belongs to the neutral player.
// You can add a pseudo owner for faster onCollide evaluation.
// No monitoring or nulling included. Use carefully.
player owner
// I really don't know how wc3 works with negative collision values. (bug free???)
// So I decided to monitor collision and eventually collisionZ.
static if DEBUG_MODE and LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
// operators allow us to check for invalid arguments like value < 0
debug private real collisionDebug
method operator collision= takes real value returns nothing
debug call ThrowError((value < 0), "Missile", "collision=", "collision", this, "Found Collision Below Zero [" + R2S(value) + "]!")
debug set collisionDebug = value
endmethod
method operator collision takes nothing returns real
debug return collisionDebug
endmethod
// Same applies for collisionZ
static if Missile_USE_COLLISION_Z_FILTER then
debug private real collisionZDebug
method operator collisionZ= takes real value returns nothing
debug call ThrowError((value < 0), "Missile", "collisionZ=", "collisionZ", this, "Found CollisionZ Below Zero [" + R2S(value) + "]!")
debug set collisionZDebug = value
endmethod
method operator collisionZ takes nothing returns real
debug return collisionZDebug
endmethod
endif
else
// Without debug mode we give a shit on monitoring!
real collision
static if Missile_USE_COLLISION_Z_FILTER then
real collisionZ
endif
endif
// Speed determines the lenght of our vector for Missile movement.
// Non positive values and zero are invalid. So let's monitor again.
static if DEBUG_MODE and LIBRARY_ErrorMessage then
debug private real speedDebug
method operator speed= takes real value returns nothing
debug call ThrowError((value <= 0), "Missile", "speed=", "speed", this, "Speed Values Below Or Equal To Zero Are Not Allowed [" + R2S(value) + "]!")
set speedDebug = value
endmethod
method operator speed takes nothing returns real
return speedDebug
endmethod
else
real speed
endif
real height
real turn
real open
real damage
real acceleration
// You can attach any data to a missile.
integer data
// Dummy is readonly so you can add more than one model.
method operator model= takes string modelFile returns nothing
if (sfx != null) then
call DestroyEffect(sfx)
endif
set path = modelFile
set sfx = AddSpecialEffectTarget(modelFile, dummy, Missile.ORIGIN)
endmethod
method operator model takes nothing returns string
return path
endmethod
// Simple trigonometry, some values will freak out the missile movement.
method operator curve= takes real value returns nothing
set open = Tan(value)*origin.distance
endmethod
method operator curve takes nothing returns real
if (origin.distance == 0) then
return 0.// It's a limit of sequence and crashes the thread. Intuitively chosen 0.
endif
return Atan(open/origin.distance)
endmethod
// Simple trigonometry, some values will freak out the missile movement.
method operator arc= takes real value returns nothing
set height = Tan(value)*origin.distance/4
endmethod
method operator arc takes nothing returns real
if (origin.distance == 0) then
return 0.// It's a limit of sequence and crashes the thread. Intuitively chosen 0.
endif
return Atan(4*height/origin.distance)
endmethod
method operator scale= takes real value returns nothing
call SetUnitScale(dummy, value, 1., 1.)
set missileScale = value
endmethod
method operator scale takes nothing returns real
return missileScale
endmethod
method bounce takes nothing returns nothing
call origin.move(xPrivate, yPrivate, z)
set distance = 0
endmethod
method deflect takes real tx, real ty returns nothing
local real a = 2*Atan2(ty - yPrivate, tx - xPrivate) + bj_PI - angle
call impact.move(xPrivate + (origin.distance - distance)*Cos(a), yPrivate + (origin.distance - distance)*Sin(a), impact.z)
call bounce()
endmethod
method deflectEx takes real tx, real ty, real tz returns nothing
call impact.move(impact.x, impact.y, tz)
call deflect(tx, ty)
endmethod
method flightTime2Speed takes real duration returns nothing
set speed = RMaxBJ(0.00000001, (origin.distance - distance)*Missile_TIMER_TIMEOUT/duration)
endmethod
// I would expect more from a destroy method :)!
method destroy takes nothing returns nothing
set wantDestroy = true
endmethod
// As user you shouldn't have access to .remove(), so you can use .terminate() instead.
method terminate takes nothing returns nothing
set allocated = false
call DestroyEffect(sfx)
static if LIBARARY_GroupUtils then
call ReleaseGroup(unitsHit)
else
call DestroyGroup(unitsHit)
endif
if (GetUnitTypeId(dummy) == Missile_DUMMY_UNIT_ID) then
static if LIBRARY_MissileRecycler then
call RecycleMissile(dummy)
elseif Dummy.create.exists and LIBRARY_Dummy then
call Dummy[dummy].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(dummy)
else
call RemoveUnit(dummy)
endif
endif
set recycle = false
set sfx = null
set unitsHit = null
set source = null
set target = null
set dummy = null
call impact.destroy()
call origin.destroy()
call remove()
endmethod
// Freaky static if ensures these libraries really don't exist.
static if not LIBRARY_MissileRecycler and not LIBRARY_Dummy and not Dummy.create.exists and not LIBRARY_xe_dummy and not xe_dummy.new.exists then
private static method newMissileUnit takes real x, real y, real face returns unit
// Just in case you have UnitIndexer.
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = false
endif
set bj_lastCreatedUnit = CreateUnit(Missile_NEUTRAL_PASSIVE, Missile_DUMMY_UNIT_ID , x, y, face)
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call PauseUnit(bj_lastCreatedUnit, true)
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = true
endif
return bj_lastCreatedUnit
endmethod
endif
static method create takes real originX, real originY, real originZ, real angleP, real distanceP, real impactZ returns thistype
local thistype this = thistype.allocateNode()
set origin = MissilePosition.create(originX, originY, originZ)
set impact = MissilePosition.create(originX + distanceP*Cos(angleP), originY + distanceP*Sin(angleP), impactZ)
call MissilePosition.link(origin, impact)
set collision = 0
static if Missile_USE_COLLISION_Z_FILTER then
set collisionZ = 0
endif
set acceleration = 0
set height = 0
set turn = 0
set open = 0
set recycle = false
set wantDestroy = false
set path = ""
set xPrivate = originX
set yPrivate = originY
set z = origin.z
set angle = angleP
set distance = 0
static if LIBRARY_MissileRecycler then
set dummy = GetRecycledMissile(originX, originY, originZ, angleP*bj_RADTODEG)
elseif LIBRARY_Dummy and Dummy.create.exists then
set dummy = Dummy.create(originX, originY, angleP*bj_RADTODEG).unit
elseif LIBRARY_xedummy and xedummy.new.exists then
set dummy = xedummy.new(Missile_NEUTRAL_PASSIVE, originX, originY, angleP*bj_RADTODEG)
else
set dummy = Missile.newMissileUnit(originX, originY, angleP*bj_RADTODEG)
endif
set allocated = true
static if LIBRARY_GroupUtils then
set unitsHit = NewGroup()
else
set unitsHit = CreateGroup()
endif
call MoveLocation(MissilePosition.loc, originX, originY)
call SetUnitFlyHeight(dummy, origin.z - GetLocationZ(MissilePosition.loc), 0.)
debug set launched = false
return this
endmethod
// Currently a private method. In future updates it will be public again and similiar
// to create(), but you can pass in any existing unit, instead poping a dummy unit from the stack.
static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns thistype
local thistype this = thistype.allocateNode()
local real originX = GetUnitX(missileDummy)
local real originY = GetUnitY(missileDummy)
local real originZ = GetUnitFlyHeight(missileDummy)
set origin = MissilePosition.create(originX, originY, originZ)
set impact = MissilePosition.create(impactX, impactY, impactZ)
call MissilePosition.link(origin, impact)
static if DEBUG_MODE and LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError((GetUnitTypeId(missileDummy) == 0), "Missile", "createEx", "missileDummy", this, "Invalid Missile Dummy Unit (null)!")
endif
set collision = 0
static if Missile_USE_COLLISION_Z_FILTER then
set collisionZ = 0
endif
set acceleration = 0
set height = 0
set turn = 0
set open = 0
set recycle = false
set wantDestroy = false
set path = ""
set xPrivate = originX
set yPrivate = originY
set z = originZ
set angle = origin.angle
set distance = 0
set dummy = missileDummy
set allocated = true
static if LIBRARY_GroupUtils then
set unitsHit = NewGroup()
else
set unitsHit = CreateGroup()
endif
call MoveLocation(MissilePosition.loc, originX, originY)
call SetUnitFlyHeight(missileDummy, origin.z - GetLocationZ(MissilePosition.loc), 0.)
debug set launched = false
return this
endmethod
// This one is new. I think it is useful.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns thistype
local real angleP = Atan2(impactY - y, impactX - x)
local real distance = SquareRoot((x - impactX)*(x - impactX) + (y - impactY)*(y - impactY))
return thistype.create(x, y, z, angleP, distance, impactZ)
endmethod
// Periodic loop every 1/32 seconds. Runs once per active missile struct.
method move takes nothing returns nothing
local MissilePosition loc
local real a
local real d
local real s
local unit u
local real h
local real tx
local real ty
local real index
loop
exitwhen (this == 0)
set loc = origin
set u = target
set h = height
// Missile target.
if (u != null) then
if (0 != GetUnitTypeId(u)) then
set tx = GetUnitX(u)
set ty = GetUnitY(u)
set a = Atan2(ty - yPrivate, tx - xPrivate)
// Does not hit the toe, but hits the chest.
// Why so? Because of HIT_BOX and GetUnitBodySize(u)
call impact.move(tx, ty, GetUnitFlyHeight(u) + GetUnitBodySize(u)*Missile.HIT_BOX)
set distance = loc.distance - SquareRoot((tx - xPrivate)*(tx - xPrivate) + (ty - yPrivate)*(ty - yPrivate))
else
set target = null
endif
else
set a = loc.angle
endif
// Missile turn
if (0 != turn) and (Cos(angle - a) < Cos(turn)) then
if (Sin(a - angle) >= 0) then
set angle = angle + turn
else
set angle = angle - turn
endif
else
set angle = a
endif
set d = loc.distance
set s = distance + speed
set distance = s
set u = dummy
set tx = xPrivate + speed*Cos(angle)
set ty = yPrivate + speed*Sin(angle)
set speed = speed + acceleration
set xPrivate = tx
set yPrivate = ty
// Missile curve
if (open != 0) then
set a = 4*open*s*(d - s)/(d*d)
set tx = tx + a*Cos(angle + 1.57)
set ty = ty + a*Sin(angle + 1.57)
call SetUnitFacing(u, (angle + Atan(-((4*open)*(2*s - d))/(d*d)))*bj_RADTODEG)
else
call SetUnitFacing(u, angle*bj_RADTODEG)
endif
// Missile x/y
set x = tx
set y = ty
static if not LIBRARY_BoundSentinel and LIBRARY_WorldBounds then
if (tx>WorldBounds.maxX) or (tx<WorldBounds.minX) or (ty>WorldBounds.maxY) or (ty<WorldBounds.minY) then
set wantDestroy = true
else
call SetUnitX(u, tx)
call SetUnitY(u, ty)
endif
else
call SetUnitX(u, tx)
call SetUnitY(u, ty)
endif
// Missile z
call MoveLocation(MissilePosition.loc, tx, ty)
set terrainZ = GetLocationZ(MissilePosition.loc)
if (h != 0) or (loc.slope != 0) then
set z = loc.z - terrainZ + loc.slope*s
set index = loc.alpha
if (h != 0) then
set z = z + (4*h*s*(d - s)/(d*d))
set index = index - Atan(((4*h)*(2*s - d))/(d*d))*bj_RADTODEG
endif
call SetUnitAnimationByIndex(u, R2I(index + 90.5))
else
set z = loc.z - terrainZ
endif
call SetUnitFlyHeight(u, z, 0)
if (s >= d) then
set recycle = true
endif
set this = next
endloop
set u = null
endmethod
// Optional code stays optional.
static if Missile_USE_DESTRUCTABLE_FILTER then
readonly static rect enumRect = Rect(0., 0., 0., 0.)
static Missile temp = 0
static real tempDist = 0
// After several test it turned out, that the square used in EnumDestructablesInRect leaves out
// destructables which are within an appropriate range of the projectile.
// Sadly there is no IsDestructableInRange native!!!
static method actionFuncDest takes nothing returns nothing
local destructable d = GetEnumDestructable()
local real xd = GetDestructableX(d)
local real yd = GetDestructableY(d)
local real dist = (temp.x - xd)*(temp.x - xd) + (temp.y - yd)*(temp.y - yd)
static if Missile_USE_COLLISION_Z_FILTER then
local real collideZ = Missile_GetLocZ(xd, yd) - temp.terrainZ
if (dist <= bj_enumDestructableRadius) and (collideZ + GetDestructableHeight(d) >= temp.z - temp.collisionZ) and (collideZ <= temp.z + temp.collisionZ) then
if (dist < Missile.tempDist) then
set Missile.tempDist = dist
set bj_destRandomCurrentPick = d
endif
endif
else
if (dist <= bj_enumDestructableRadius) then
if (dist < Missile.tempDist) then
set Missile.tempDist = dist
set bj_destRandomCurrentPick = d
endif
endif
endif
set d = null
endmethod
endif
static if Missile_WRITE_DELAYED_MISSILE_RECYCLING then
method nullBefore takes nothing returns nothing
set dummy = null
endmethod
endif
// Does not check for 'Aloc' and 'Amrf' as they could be customized.
static if Missile.MORE_SAFETY and DEBUG_MODE and LIBRARY_ErrorMessage then
private static method onInit takes nothing returns nothing
static if LIBRARY_UnitIndexer then
debug set UnitIndexer.enabled = false
endif
debug set bj_lastCreatedUnit = CreateUnit(Missile_NEUTRAL_PASSIVE, Missile_DUMMY_UNIT_ID, 0, 0, 0)
debug call ThrowError((GetUnitTypeId(bj_lastCreatedUnit) != Missile_DUMMY_UNIT_ID), "Missile", "DEBUG_MISSILE", "typeid", 0, "Missile Setup For DUMMY_UNIT_ID Is Incorrect! Map Can't Use Missile!")
debug call ThrowError((Missile_MAXIMUM_COLLISION_SIZE < 0), "Missile", "DEBUG_MISSILE", "collision", 0, "Missile Setup For MAXIMUM_COLLISION_SIZE Is Incorrect, Below Zero! Map Can't Use Missile!")
debug call RemoveUnit(bj_lastCreatedUnit)
debug set bj_lastCreatedUnit = null
static if LIBRARY_UnitIndexer then
debug set UnitIndexer.enabled = true
endif
endmethod
endif
endstruct
// You made it to the end of Missile, but we are not finished.
// Do you remember about the data structure, the delayed recycler
// and of course our interface module "MissileStruct"
//
// This comes now!
// Debug code taken from List (credits to Nestharus)
private module MissileStructure
private static thistype collectionCount = 0
private static thistype nodeCount = 0
static if LIBRARY_ErrorMessage then
debug private boolean isNode
debug private boolean isCollection
endif
private thistype _list
method operator list takes nothing returns thistype
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(this == 0, "MissileStructure", "list", "thistype", this, "Attempted To Read Null Node.")
debug call ThrowError(not isNode, "MissileStructure", "list", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _list
endmethod
private thistype _next
method operator next takes nothing returns thistype
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(this == 0, "MissileStructure", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "MissileStructure", "next", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _next
endmethod
private thistype _prev
method operator prev takes nothing returns thistype
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(this == 0, "MissileStructure", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "MissileStructure", "prev", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _prev
endmethod
private thistype _first
method operator first takes nothing returns thistype
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(this == 0, "MissileStructure", "first", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "MissileStructure", "first", "thistype", this, "Attempted To Read Invalid List.")
endif
return _first
endmethod
private thistype _last
method operator last takes nothing returns thistype
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(this == 0, "MissileStructure", "last", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "MissileStructure", "last", "thistype", this, "Attempted To Read Invalid List.")
endif
return _last
endmethod
static method allocateCollection takes nothing returns thistype
local thistype this = thistype(0)._first
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(collectionCount == 8191, "MissileStructure", "allocateCollection", "thistype", 0, "Overflow.")
endif
set this = collectionCount + 1
set collectionCount = this
else
set thistype(0)._first = _first
endif
static if LIBRARY_ErrorMessage then
debug set isCollection = true
endif
set _first = 0
return this
endmethod
static method allocateNode takes nothing returns thistype
local thistype this = thistype(0)._next
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(nodeCount == 8191, "MissileStructure", "allocateNode", "thistype", 0, "Overflow.")
endif
set this = nodeCount + 1
set nodeCount = this
else
set thistype(0)._next = _next
endif
return this
endmethod
method insertEnd takes thistype node returns thistype
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(this == 0, "MissileStructure", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
debug call ThrowError(not isCollection, "MissileStructure", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
debug set node.isNode = true
endif
set node._list = this
if (_first == 0) then
set _first = node
set _last = node
set node._prev = 0
else
set _last._next = node
set node._prev = _last
set _last = node
endif
set node._next = 0
return node
endmethod
method remove takes nothing returns nothing
local thistype node = this
set this = node._list
static if LIBRARY_ErrorMessage and Missile.MORE_SAFETY then
debug call ThrowError(node == 0, "MissileStructure", "remove", "thistype", this, "Attempted To Remove Null Node.")
debug call ThrowError(not node.isNode, "MissileStructure", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
debug set node.isNode = false
endif
set node._list = 0
if (0 == node._prev) then
set _first = node._next
else
set node._prev._next = node._next
endif
if (0 == node._next) then
set _last = node._prev
else
set node._next._prev = node._prev
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
endmethod
endmodule
// Can private as the MissileStruct module is written here.
globals
private trigger core = CreateTrigger()
private timer clock = CreateTimer()
private integer active = 0
private integer array instances
private Missile array missileStack
private boolexpr array expression
private triggercondition array condition
endglobals
private function Fire takes nothing returns nothing
call TriggerEvaluate(core)
endfunction
private function MissileCreateExpression takes integer structId, code func returns nothing
set expression[structId] = Condition(func)
endfunction
// Start timer if not already running.
private function StartPeriodic takes integer structId returns nothing
if (instances[structId] == 0) then
set condition[structId] = TriggerAddCondition(core, expression[structId])
if (active == 0) then
call TimerStart(clock, Missile_TIMER_TIMEOUT, true, function Fire)
endif
set active = active + 1
endif
set instances[structId] = instances[structId] + 1
endfunction
// And stops it.
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if (instances[structId] == 0) then
set active = active - 1
if (active == 0) then
call PauseTimer(clock)
endif
call TriggerRemoveCondition(core, condition[structId])
set condition[structId] = null
endif
endfunction
// Now the popular MissileStruct.
// How is it working? Let's see!
//
// P in the end of all methods hopefully prevents
// any conflicting code.
module MissileStruct
// Why not run some debug checks first.
static if DEBUG_MODE then
static if not Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructable.exists then
Error Message from library Missile in struct thistype !
thistype.onDestructable is a reserved name for Missile, once you implemented MissileStruct.
Either you forgot to set USE_DESTRUCTABLE_FILTER = true in library Missile or re-name that method.
endif
static if not Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructableFilter.exists then
Error Message from library Missile in struct thistype !
thistype.onDestructableFilter is a reserved name for Missile, once you implemented MissileStruct.
Either you forget to set USE_DESTRUCTABLE_FILTER = true in library Missile or re-name that method.
endif
static if Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructableFilter.exists and not thistype.onDestructable.exists then
Error Message from library Missile in struct thistype !
thistype.onDestructableFilter only be used, when thistype.onDestructable is also declared.
endif
static if thistype.onMissile.exists then
Error Message from library Missile in struct thistype !
thistype.onMissile is a reserved name for Missile, once you implemented MissileStruct.
thistype.onMissile is currently not supported by library Missile.
Please delete or re-name that method.
endif
endif
static if Missile_USE_DESTRUCTABLE_FILTER and thistype.onDestructableFilter.exists then
// Stupid name chosen, to be unique in that struct ... hopefully.
private static boolexpr destConditionP = null
endif
// Stupid name ensures not to be called accidently.
private static method missileTerminateP takes Missile node returns nothing
static if thistype.onRemove.exists then
static if Missile_WRITE_DELAYED_MISSILE_RECYCLING and RecycleBin.recycle.exists then
if thistype.onRemove(node) and (GetUnitTypeId(node.dummy) == Missile_DUMMY_UNIT_ID) then
call RecycleBin.recycle(node.dummy)
call node.nullBefore()
endif
else
call thistype.onRemove(node)
endif
endif
call node.terminate()
call StopPeriodic(thistype.typeid)
endmethod
// Returns boolean to be compile-able with Cohadars JassHelper.
private static method missileIterateP takes nothing returns boolean
local Missile this = missileStack[.typeid].first
local Missile node
local real collideZ
local unit u
// Move the whole stack.
call this.move()
loop
exitwhen this == 0
set node = this.next
if (this.wantDestroy) then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
endif
else
call thistype.missileTerminateP(this)
endif
else
static if thistype.onCollide.exists then
if (0 != this.collision) then
call GroupEnumUnitsInRange(Missile.enumGroup, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, null)
loop
set u = FirstOfGroup(Missile.enumGroup)
exitwhen u == null
call GroupRemoveUnit(Missile.enumGroup, u)
if (u != this.target) and IsUnitInRange(u, this.dummy, this.collision) then
static if Missile_USE_COLLISION_Z_FILTER then
set collideZ = Missile_GetLocZ(GetUnitX(u), GetUnitY(u)) + GetUnitFlyHeight(u) - this.terrainZ
if (collideZ + GetUnitBodySize(u) >= this.z - this.collisionZ) and (collideZ <= this.z + this.collisionZ) then
if not (IsUnitInGroup(u, this.unitsHit)) then
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
call GroupAddUnit(this.unitsHit, u)
endif
endif
else
if not (IsUnitInGroup(u, this.unitsHit)) then
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
call GroupAddUnit(this.unitsHit, u)
endif
endif
endif
endloop
endif
endif
static if thistype.onDestructable.exists then
// Check if the missile is not terminated.
if (this.allocated) and (0 != this.collision) then
set bj_enumDestructableRadius = this.collision + Missile_MAXIMUM_COLLISION_SIZE
set bj_destRandomCurrentPick = null
call SetRect(Missile.enumRect, this.x - bj_enumDestructableRadius, this.y - bj_enumDestructableRadius, this.x + bj_enumDestructableRadius, this.y + bj_enumDestructableRadius)
set bj_enumDestructableRadius = this.collision*this.collision
set Missile.temp = this
set Missile.tempDist = bj_enumDestructableRadius + 1
static if thistype.onDestructableFilter.exists then
call EnumDestructablesInRect(Missile.enumRect, thistype.destConditionP, function Missile.actionFuncDest)
else
call EnumDestructablesInRect(Missile.enumRect, null, function Missile.actionFuncDest)
endif
if (bj_destRandomCurrentPick != null) and thistype.onDestructable(this, bj_destRandomCurrentPick) then
call missileTerminateP(this)
endif
endif
endif
if (this.recycle) and (this.allocated) then
static if thistype.onCollide.exists then
static if thistype.onFinish.exists then
if (this.target == null) then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
elseif thistype.onCollide(this, this.target) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
else
if (this.target == null) then
call thistype.missileTerminateP(this)
elseif thistype.onCollide(this, this.target) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
endif
else
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call thistype.missileTerminateP(this)
else
set this.recycle = false
endif
else
call thistype.missileTerminateP(this)
endif
endif
endif
static if thistype.onTerrain.exists then
if (this.allocated) and (this.z < 0) and thistype.onTerrain(this) then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call missileTerminateP(this)
endif
else
call missileTerminateP(this)
endif
endif
endif
// Runs last
static if thistype.onPeriod.exists then
if (this.allocated) and thistype.onPeriod(this) then
static if thistype.onFinish.exists then
if thistype.onFinish(this) then
call missileTerminateP(this)
endif
else
call missileTerminateP(this)
endif
endif
endif
endif
set this = node
endloop
set u = null
return false
endmethod
static method launch takes Missile missile returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(missile.launched, "thistype", "launch", "missile.launched", missile, "Missile Was Already Launched Before!")
endif
call missileStack[thistype.typeid].insertEnd(missile)
call StartPeriodic(thistype.typeid)
debug set missile.launched = true
endmethod
private static method onInit takes nothing returns nothing
set missileStack[thistype.typeid] = Missile.allocateCollection()
call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)
static if thistype.onDestructableFilter.exists then
set thistype.destConditionP = Condition(function thistype.onDestructableFilter)
endif
endmethod
endmodule
//! textmacro WRITE_MISSILE_POSITION_CODE
struct MissilePosition extends array
private static integer array recycler
private static integer alloc = 0
readonly static location loc = Location(0., 0.)
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real slope
readonly real alpha
private thistype ref
private static method math takes thistype a, thistype b returns boolean
set a.angle = Atan2(b.y - a.y, b.x - a.x)
set a.distance = SquareRoot((b.x - a.x)*(b.x - a.x) + (b.y - a.y)*(b.y - a.y))
if (a.distance != 0) then
set a.slope = (b.z - a.z)/a.distance
else// Error! I decide we do this math again!
set a.x = a.x + .0001
return false
endif
set a.alpha = Atan(a.slope)*bj_RADTODEG
set b.angle = a.angle + bj_PI
set b.distance = a.distance
set b.slope = -a.slope
set b.alpha = -a.alpha
return true
endmethod
static method link takes thistype a, thistype b returns nothing
set a.ref = b
set b.ref = a
loop
exitwhen math(a, b)
endloop
endmethod
method move takes real toX, real toY, real toZ returns nothing
call MoveLocation(MissilePosition.loc, toX, toY)
set x = toX
set y = toY
set z = toZ + GetLocationZ(MissilePosition.loc)
if (ref != this) then
loop
exitwhen math(this, ref)
endloop
endif
endmethod
method destroy takes nothing returns nothing
set recycler[this] = recycler[0]
set recycler[0] = this
endmethod
static method create takes real x, real y, real z returns MissilePosition
local thistype this = recycler[0]
if (0 == this) then
set alloc = alloc + 1
set this = alloc
else
set recycler[0] = recycler[this]
endif
set ref = this
call move(x, y, z)
return this
endmethod
endstruct
//! endtextmacro
//! textmacro WRITE_MISSILE_RECYCLE_BIN takes DO_THIS, AFTER_TIME
static if $DO_THIS$ then
// In order to play a proper death animation,
// RecycleBin to recycles missiles after
// DEFAULT_DEATH_ANIMATION_TIME
private struct RecycleBin extends array
private static timer t = CreateTimer()
private static integer alloc = 0
private static unit array dummy
private static real array time
private static method handlerFunc takes nothing returns nothing
local integer index = 0
loop
exitwhen index == thistype.alloc
set thistype.time[index] = thistype.time[index] - 1
if (0 >= thistype.time[index]) then
static if LIBRARY_MissileRecycler then
call RecycleMissile(thistype.dummy[index])
elseif Dummy.create.exists and LIBRARY_Dummy then
call Dummy[thistype.dummy[index]].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(thistype.dummy[index])
else
call RemoveUnit(thistype.dummy[index])
endif
set thistype.dummy[index] = null
set thistype.alloc = thistype.alloc - 1
set thistype.dummy[index] = thistype.dummy[thistype.alloc]
set thistype.time[index] = thistype.time[thistype.alloc]
set thistype.dummy[thistype.alloc] = null
set index = index - 1
if (0 == thistype.alloc) then
call PauseTimer(thistype.t)
endif
endif
set index = index + 1
endloop
endmethod
static method recycle takes unit toRecycle returns nothing
if (0 == thistype.alloc) then
call TimerStart(thistype.t, 1., true, function thistype.handlerFunc)
endif
set thistype.dummy[alloc] = toRecycle
set thistype.time[alloc] = $AFTER_TIME$ + TimerGetRemaining(thistype.t)
set thistype.alloc = thistype.alloc + 1
endmethod
endstruct
endif
//! endtextmacro
// The end!
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+) 2.0
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* set t=NewTimerEx(x) : Get a timer (alternative to CreateTimer), call
//* Initialize timer data as x, instead of 0.
//*
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = true
private constant boolean USE_FLEXIBLE_OFFSET = false
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
private boolean didinit = false
endglobals
private keyword init
//==========================================================================================
// I needed to decide between duplicating code ignoring the "Once and only once" rule
// and using the ugly textmacros. I guess textmacros won.
//
//! textmacro TIMERUTIS_PRIVATE_NewTimerCommon takes VALUE
// On second thought, no.
//! endtextmacro
function NewTimerEx takes integer value returns timer
if (tN==0) then
if (not didinit) then
//This extra if shouldn't represent a major performance drawback
//because QUANTITY rule is not supposed to be broken every day.
call init.evaluate()
set tN = tN - 1
else
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
set tT[0]=CreateTimer()
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],value)
return tT[tN]
endfunction
function NewTimer takes nothing returns timer
return NewTimerEx(0)
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
if ( didinit ) then
return
else
set didinit = true
endif
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
library_once WorldBounds /* v2.0.0.0
************************************************************************************
*
* 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
*
************************************************************************************/
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=21
//TESH.alwaysfold=0
library ErrorMessage /* v1.0.1.4
*************************************************************************************
*
* Issue Compliant Error Messages
*
************************************************************************************
*
* debug function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
* - In the event of an error the game will be permanently paused
*
* debug function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
*
************************************************************************************/
static if DEBUG_MODE then
private struct Fields extends array
static constant string COLOR_RED = "|cffff0000"
static constant string COLOR_YELLOW = "|cffffff00"
static string lastError = null
endstruct
private function Pause takes nothing returns nothing
call PauseGame(true)
endfunction
private function ThrowMessage takes string libraryName, string functionName, string objectName, integer objectInstance, string description, string errorType, string color returns nothing
local string str
local string color_braces = "|cff66FF99"
local string orange = "|cffff6600"
set str = "->\n-> " + color_braces + "{|r " + "Library" + color_braces + "(" + orange + libraryName + color_braces + ")"
if (objectName != null) then
if (objectInstance > 0) then
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + " (|rinstance = " + orange + I2S(objectInstance) + color_braces + ") )" + "|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
else
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + ")|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
else
set str = str + "|r." + "Function" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
set str = str + color_braces + " }|r " + "has thrown an exception of type " + color_braces + "(" + color + errorType + color_braces + ")|r."
set Fields.lastError = str + "\n->\n" + "-> " + color + description + "|r\n->"
endfunction
function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Error", Fields.COLOR_RED)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
call TimerStart(CreateTimer(), 0, true, function Pause)
set objectInstance = 1/0
endif
endfunction
function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Warning", Fields.COLOR_YELLOW)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
set Fields.lastError = null
endif
endfunction
endif
endlibrary
//TESH.scrollpos=319
//TESH.alwaysfold=0
library Hydra /* v4.2
*************************************************************************************
*
* Summons a many-headed dragon, attacking nearby enemies.
*
*************************************************************************************
*
* To Vexorian
* -----------------------
*
* For TimerUtils, Hydra, cool vJass features and JassHelper
*
* To Nestharus
* -----------------------
*
* For ErrorMessage, properly debugging data structures.
*
*************************************************************************************
*
* */ uses /*
*
* */ Missile /* http://www.hiveworkshop.com/forums/jass-resources-412/missile-265370/
* */ TimerUtils /* http://www.wc3c.net/showthread.php?t=101322
* */ optional ErrorMessage /* http://github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the Hydra script and the required libraries into your map.
* The FireSnake.mdx model is very recommended as Hydra Head model.
*
* 2. API
* ¯¯¯¯¯¯
* struct Hydra
*
* Creator/Destructor:
*
* static method create takes unit whichUnit, integer unitId, real x, real y, real z, real face, spawnInterval returns Hydra
* method destroy takes nothing returns nothing
*
* Mandatory:
*
* method operator attackAnimationTime= takes real value returns nothing
* - duration of the attack animation for the hydra unit (object editor).
* - Normally it's half of the animation time displayed in the object editor,
* as the second half is the backswing animation time.
*
* method operator attackSpeed= takes real value returns nothing
* - attack speed of one single hydra head.
*
* method operator duration= takes real time returns nothing
* - sets the life time of this Hydra instance, once it is summoned.
* - time values <= 0, will make the Hydra permanent, until you set
* a new duration or use th destructor method: this.destroy().
*
* method summon takes nothing returns nothing
* - summons this instance
*
* real attackPeriod ( pause between two heads )
* real headHeight ( is also missile z offset )
* real attackRange
* real bodysize ( by default 48.00 )
* real birthAnimationTime ( timeout until the head will start to shoot. )
* integer heads ( total amount of heads )
* boolean hasArcOrCurve ( MUST be set, otherwise the Missile fly incorrect! )
*
* Optional:
*
* method addHead takes real x, real y, real z, real face returns boolean
* - summons a new head to the Hydra.
* - only works if the Hydra is at least one head already exists.
* - does not reset the duration.
*
* method operator scale= takes real value returns nothing
* - sets the scale of each hydra units. Runs for the entire hydra head list.
* - can be updated at any time.
*
* method operator scale takes nothing returns real
* - gets the current scaling of each head.
*
* method operator offset= takes real value returns nothing
* - by default this value is 0.
* - Each missile can be create with an offset towards the shooting unit.
* - offset is automatically affect by the scale operator. Ergo scaling, also scales the offset.
*
* method optimize takes nothing returns nothing
* - optimizes attack period and animation, based on the attack speed.
*
* Fields:
*
* readonly unit source
* readonly player owner
* readonly integer typeId
* readonly real posX
* readonly real posY
* readonly real posZ
*
* Safety:
* readonly boolean summoned
*
* Extending structs available stub methods:
*
* public stub method onSummonHead takes unit head returns nothing
* - runs each time a new head is summoned
*
* public stub method onRemoveHead takes unit head returns nothing
* - runs each time a head is removed.
*
* public stub method onTargetFilter takes unit target, player owner returns boolean
* - runs each time a target filter is applied
* by default: return UnitAlive(target) and IsUnitEnemy(target, owner)
*
*
* public stub method onFire takes unit shooter, unit target, Missile missile returns nothing
* - runs each time a new missile is created.
* by default: call missile.terminate()
*
*/
// Hydra Code. Changes may causes errors.
native UnitAlive takes unit id returns boolean
// Toogles unit indexer systems in the best way possible.
// Considers the previous library setup ( enabled or not )
private function ToogleUnitIndexer takes boolean enable returns boolean
local boolean prevSetup = true
static if LIBRARY_UnitIndexer then
set prevSetup = UnitIndexer.enabled
set UnitIndexer.enabled = enable
elseif LIBRARY_UnitIndexerGUI then
set prevSetup = udg_UnitIndexerEnabled
set udg_UnitIndexerEnabled = enable
elseif LIBRARY_UnitDex then
set prevSetup = UnitDex.Enabled
set UnitDex.Enabled = enable
endif
return prevSetup
endfunction
// Data structure; complexity: List
private keyword HydraStructure
private struct HydraHead extends array
implement HydraStructure
unit unit
unit target
real x// Hydra position.
real y
real z//
real unitZ
real lastShot // Hydra uses a timer stamp, stores elapsed time on lastShot.
integer number// Head number.
real aimX
real aimY
real aimZ
real aimAngle
endstruct
globals
// Constants required in Hydra.
private constant real TWO_PI = bj_PI*2
private constant real DEFAULT_BODYSIZE = 48.
private constant integer LOCUST = 'Aloc'
private constant integer TIMED_LIFE = 'BTLF'
private constant integer CROW_FORM = 'Amrf'
private constant string ATTACK = "attack "
private constant string BIRTH = "birth "
private constant string STAND = "stand "
private constant timer STAMP = CreateTimer()
// Available animationSuffixes. Global for ever unit type.
private string array animationSuffix
// Tracks how many Hydra instances are allocated
private integer allocCount = 0
endglobals
struct Hydra
//grp for a better target evaluation.
private static group enu = CreateGroup()
private static group grp = CreateGroup()
private HydraHead list
private HydraHead currentHead
private HydraHead nextShootingHead
private timer shoot
private timer lifeTimer
readonly unit source
readonly player owner
readonly integer typeId
readonly boolean summoned
readonly real posX
readonly real posY
readonly real posZ
private real angle
private real timeScale
private real interval
real headHeight// is also the missile offset.
real attackSpeed
real attackRange
real bodysize
real birthAnimationTime
integer heads
boolean hasArcOrCurve
// Available stub methods.
public stub method onSummonHead takes unit head returns nothing
endmethod
public stub method onRemoveHead takes unit head returns nothing
endmethod
public stub method onTargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner)
endmethod
public stub method onFire takes unit shooter, unit target, Missile missile returns nothing
call missile.terminate()
endmethod
private real attackPeriod_p
private real animationTime_p
private real animationTimeOriginal
method operator attackAnimationTime= takes real value returns nothing
set animationTimeOriginal = value
set animationTime_p = value
if (0. != attackPeriod_p and attackPeriod_p < value) then
set timeScale = value/attackPeriod_p
set animationTime_p = attackPeriod_p
endif
endmethod
method operator attackPeriod= takes real value returns nothing
set attackPeriod_p = value
if (value < animationTime_p and animationTimeOriginal != 0.) then
set timeScale = animationTimeOriginal/value
set animationTime_p = value
endif
endmethod
private real duration_p
method operator duration= takes real time returns nothing
if (0. <= time) then
call PauseTimer(lifeTimer)
elseif (summoned) and (list.size != 0) then
call TimerStart(lifeTimer, time, false, function thistype.killHead)
endif
set duration_p = time
endmethod
private real scale_p
method operator scale= takes real value returns nothing
local HydraHead node
if (summoned) then
set node = list.first
loop
exitwhen node == 0
call SetUnitScale(node.unit, value, 0, 0)
set node.unitZ = node.z + headHeight*value
set node = node.next
endloop
endif
set offset_p = offset_p*value
set scale_p = value
endmethod
method operator scale takes nothing returns real
return scale_p
endmethod
private real offset_p
method operator offset= takes real value returns nothing
set offset_p = value*scale
endmethod
method optimize takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((heads <= 0), "Hydra", "optimize", "heads", this, "Can't use optimize() before heads are set above 0!")
debug call ThrowError((attackPeriod_p <= 0), "Hydra", "optimize", "attackPeriod", this, "Can't use optimize() before attackPeriod is set!")
debug call ThrowError((attackSpeed <= 0), "Hydra", "optimize", "attackSpeed", this, "Can't use optimize() before attackSpeed is set!")
debug call ThrowError((animationTimeOriginal <= 0), "Hydra", "optimize", "animationTime", this, "Can't use optimize() before attackAnimationTime is set!")
endif
set attackPeriod = (attackSpeed/heads)
endmethod
method destroy takes nothing returns nothing
local HydraHead head = list.first
call ReleaseTimer(lifeTimer)
call ReleaseTimer(shoot)
loop
exitwhen (0 == head)
if (UnitAlive(head.unit)) then
call onRemoveHead(head.unit)
call UnitApplyTimedLife(head.unit, TIMED_LIFE, 0.001)
endif
set head.unit = null
set head.target = null
set head = head.next
endloop
call list.destroy()
set owner = null
set shoot = null
set lifeTimer = null
set source = null
set summoned = false
set allocCount = allocCount - 1
if (allocCount == 0) then
call PauseTimer(STAMP)
endif
call deallocate()
endmethod
// It took some time until I found the proper Z offset calculation for non arcing missiles.
// Warcraft III terrain surface says no-no to simple geometry and congruent triangles.
// It is like this:
// 1. head.unitZ == GetUnitFlyHeight(head) + headHeight*scale
// 2. this.z == GetUnitFlyHeight(head.target) + GetUnitBodySize(head.target)*Missile.HIT_BOX
// 3. dZ == LocZ(impactX, impactY) - head.UnitZ + LocZ(originX, originY)
// 4. endZ == head.unitZ + dZ*(maxDistance/distance) - LocZ(maxDistanceImpactX, maxDistanceImpactY)
private static method fire takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = thistype(GetTimerData(t))
local HydraHead head = currentHead
local real sin = Sin(head.aimAngle)
local real cos = Cos(head.aimAngle)
local real ox = head.x + offset_p*cos
local real oy = head.y + offset_p*sin
local real a = attackPeriod_p - animationTime_p
local real d
local real dZ
local real endZ
local Missile missile
if (hasArcOrCurve) then
set missile = Missile.createXYZ(ox, oy, head.unitZ, head.aimX, head.aimY, head.aimZ)
else
set d = RMaxBJ(SquareRoot((ox - head.aimX)*(ox - head.aimX) + (oy - head.aimY)*(oy - head.aimY)), 1.)
set dZ = (Missile_GetLocZ(head.aimX, head.aimY) + head.aimZ) - (head.unitZ + Missile_GetLocZ(ox, oy))
set endZ = head.unitZ + (dZ*(attackRange/d) - Missile_GetLocZ(ox + attackRange*cos, oy + attackRange*sin))
set missile = Missile.create(ox, oy, head.unitZ, head.aimAngle, attackRange, endZ)
endif
set missile.source = source
set missile.owner = owner
set missile.data = this
// Fire stub method onFire
call onFire(head.unit, head.target, missile)
static if LIBRARY_ErrorMessage then
debug if (hasArcOrCurve) and ((not (missile.arc != 0.)) and (not (missile.curve != 0.))) then
debug call ThrowWarning(true, "Hydra", "fire", "hasArcOrCurve", this, "Don't set it to true, when you don't use arced or curved missiles!")
debug elseif not (hasArcOrCurve) and (((missile.arc != 0.) or (missile.curve != 0.))) then
debug call ThrowWarning(true, "Hydra", "fire", "hasArcOrCurve", this, "Arc & Curve settings are only valid if hasArcOrCurve is set to true")
debug endif
endif
if (1. != timeScale) then
call SetUnitTimeScale(head.unit, 1.)
endif
call QueueUnitAnimation(currentHead.unit ,STAND + animationSuffix[head.number])
if (0. > a) then
set a = 0.
endif
call TimerStart(t, a, false, function thistype.attemptShoot)
set t = null
endmethod
/*
* Concept:
* - Each head has an individual target, if possible.
* - The closest unit is the best target, since moving units are hard to hit.
* - The old target has the highest priority, unless the last two missiles failed.
* Only missiles, which hit walls or cliffs are evaluated as fails.
* - Returns true will initialize the shooting process.
*/
private method evaluateTarget takes HydraHead node returns boolean
local real dist // Distance,
local real ndist// new distance
local real bdist// and best new distance.
local unit u
local unit new = null // This is the new target,
local unit best = null // the best new target
local unit old = node.target// and the old target.
local real x
local real y
local real ox
local real oy
local HydraHead temp
// First check the previous target.
// Evaluates stub method onTargetFilter
if (IsUnitInRange(old, node.unit, attackRange)) and (onTargetFilter(old, owner)) then
set old = null
return true
else
set node.target = null// Clear the previous target.
call GroupClear(thistype.grp)
// Units targeted by the other heads of this instance, have a lower priority.
set temp = list.first
loop
exitwhen (0 == temp)
if (temp.target != null) then
call GroupAddUnit(thistype.grp, temp.target)
endif
set temp = temp.next
endloop
set ox = node.x
set oy = node.y
call GroupEnumUnitsInRange(thistype.enu, ox, oy, attackRange + Missile_MAXIMUM_COLLISION_SIZE, null)
// The old target probably has an high error quote,
// therefore it gets the lowest priority.
call GroupRemoveUnit(thistype.enu, old)
loop
set u = FirstOfGroup(thistype.enu)
exitwhen u == null
call GroupRemoveUnit(thistype.enu, u)
if IsUnitInRange(u, node.unit, attackRange) and onTargetFilter(u, owner) then
set x = GetUnitX(u)
set y = GetUnitY(u)
set dist = ((x - ox)*(x - ox) + (y - oy)*(y - oy))//screw squareroot
if (new == null) or (dist < ndist) then
set ndist = dist
set new = u
endif
if not (IsUnitInGroup(u, thistype.grp)) then
if (best == null) or (dist < bdist) then
set best = u
set bdist = dist
endif
endif
endif
endloop
if (best != null) then
set node.target = best
set best = null
elseif (new != null) then
set node.target = new
elseif (onTargetFilter(old, owner) and IsUnitInRange(old, node.unit, attackRange)) then
set node.target = old
endif
set new = null
set old = null
endif
return (node.target != null)
endmethod
private static method attemptShoot takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = thistype(GetTimerData(t))
local real elapsed = TimerGetElapsed(STAMP)
local HydraHead head = nextShootingHead
local real timeout
if (head.lastShot + attackSpeed <= elapsed) then
if (evaluateTarget(head)) then
set nextShootingHead = head.next
if (nextShootingHead == 0) then
set nextShootingHead = list.first
endif
// Taking aim, before the shoot animation starts.
set head.aimX = GetUnitX(head.target)
set head.aimY = GetUnitY(head.target)
set head.aimZ = GetUnitFlyHeight(head.target) + GetUnitBodySize(head.target)*Missile.HIT_BOX
set head.aimAngle = Atan2(head.aimY - head.y, head.aimX - head.x)
set currentHead = head
set head.lastShot = elapsed
// Initiate the shooting process.
call SetUnitFacing(head.unit, head.aimAngle*bj_RADTODEG)
if (1. != timeScale) then
call SetUnitTimeScale(head.unit, timeScale)
endif
call SetUnitAnimation(head.unit, ATTACK + animationSuffix[head.number])
call TimerStart(t, animationTime_p, false, function thistype.fire)
elseif (list.size > 1) then
// This head can't shoot, we pass in a timeout and
// move to the next head.
set head.lastShot = elapsed
set head = head.next
if (head == 0) then
set head = list.first
endif
set nextShootingHead = head
set timeout = head.lastShot + attackSpeed - elapsed
if (timeout < 0) then
set timeout = 0.
elseif (timeout > attackPeriod_p) then
set timeout = attackPeriod_p
endif
call TimerStart(t, timeout, false, function thistype.attemptShoot)
else
call TimerStart(t, attackPeriod_p, false, function thistype.attemptShoot)
endif
else
call TimerStart(t, RMaxBJ(0., head.lastShot + attackSpeed - elapsed), false, function thistype.attemptShoot)
endif
set t = null
endmethod
private static method killHead takes nothing returns nothing
local thistype this = thistype(GetTimerData(GetExpiredTimer()))
local HydraHead head = list.first
local HydraHead node = head.next
local real time = RMaxBJ(.0001, head.lastShot + (animationTime_p*2.) - TimerGetElapsed(STAMP))
// In case this head is still in the shoot animation foreward or backwards,
// we show that shoot animation before killing it.
call UnitApplyTimedLife(head.unit, TIMED_LIFE, time)
// Evaluate stub method onRemoveHead
call this.onRemoveHead(head.unit)
set head.unit = null
call head.remove()
if (0 == list.first) then
call destroy()
else
if (nextShootingHead == head) then
if (node == 0) then
set nextShootingHead = list.first
else
set nextShootingHead = node
endif
endif
call TimerStart(lifeTimer, interval, false, function thistype.killHead)
endif
endmethod
// Create a new head without firing off an onIndex event.
private method addHeadEx takes real x, real y, real z, real face returns nothing
local HydraHead node = list.enqueue()
local string suffix = animationSuffix[list.size]
local boolean prev = ToogleUnitIndexer(false)
local unit head = CreateUnit(owner, typeId, x, y, face)
call ToogleUnitIndexer(prev)
call SetUnitFlyHeight(head, z, 0.)
if (1. != scale_p) then
call SetUnitScale(head, scale_p, 1., 1.)
endif
call UnitAddAbility(head, LOCUST)
call UnitAddAbility(head, CROW_FORM)
call SetUnitAnimation(head, BIRTH + suffix)
call QueueUnitAnimation(head, STAND + suffix)
set node.unit = head
set node.target = null
set node.lastShot = TimerGetElapsed(STAMP) - attackSpeed + birthAnimationTime
set node.x = GetUnitX(head)
set node.y = GetUnitY(head)
set node.z = z
set node.unitZ = z + headHeight*scale_p
set node.number = list.size
// Evaluate stub method onSummonHead.
call this.onSummonHead(head)
set head = null
endmethod
method addHead takes real x, real y, real z, real face returns boolean
if (summoned) and (list.size > 0) then
call addHeadEx(x, y, z, face)
return true
endif
return false
endmethod
// Periodically called until all Hydra heads are created.
private static method spawn takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = thistype(GetTimerData(t))
local real a = angle*bj_DEGTORAD + list.size*(TWO_PI/heads)
call addHeadEx((bodysize*scale_p)*Cos(a) + posX, (bodysize*scale_p)*Sin(a) + posY, posZ, a*bj_RADTODEG)
if (list.size == 1) then
set nextShootingHead = list.first
call TimerStart(t, interval, true, function thistype.spawn)
endif
if (list.size >= heads) then
call ReleaseTimer(t)
endif
set t = null
endmethod
method summon takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((summoned), "Hydra", "summon", "summoned", this, "This Hydra is already summoned!")
debug call ThrowError((typeId == 0), "Hydra", "summon", "unitId", this, "Passed in Hydra unit type id is invalid ( 0 )!")
debug call ThrowError((heads <= 0), "Hydra", "summon", "heads", this, "Passed in value is invalid ( <= 0 )!")
debug call ThrowError((attackRange <= 0), "Hydra", "summon", "attackRange", this, "Passed in value is invalid ( <= 0 )!")
debug call ThrowError((attackPeriod_p <= 0), "Hydra", "summon", "attackPeriod", this, "Passed in value is invalid ( <= 0 )!")
debug call ThrowError((animationTimeOriginal <= 0), "Hydra", "summon", "attackAnimationTime", this, "Passed in value is invalid ( <= 0 )!")
debug call ThrowWarning((headHeight < 0), "Hydra", "summon", "headHeight", this, "Passed in value is invalid ( below 0 )!")
debug call ThrowWarning((GetUnitTypeId(source) == 0), "Hydra", "summon", "GetUnitTypeId(source)", this, "Passed in unit is invalid ( null )!")
endif
if not (summoned) then
set summoned = true
call TimerStart(NewTimerEx(this), 0., false, function thistype.spawn)
call TimerStart(shoot, birthAnimationTime, false, function thistype.attemptShoot)
if (duration_p > 0.) then
call TimerStart(lifeTimer, duration_p, false, function thistype.killHead)
endif
endif
endmethod
static method create takes unit whichUnit, integer unitId, real x, real y, real z, real face, real spawnInterval returns Hydra
local thistype this = thistype.allocate()
set list = HydraHead.create()
set shoot = NewTimerEx(this)
set lifeTimer = NewTimerEx(this)
set owner = GetOwningPlayer(whichUnit)
// Set members.
set source = whichUnit
set typeId = unitId
set posX = x
set posY = y
set posZ = z
set angle = face
set scale_p = 1.
set timeScale = 1.
set attackPeriod_p = 0.
set offset_p = 0.
set duration_p = 0.
set animationTimeOriginal = 0.
set birthAnimationTime = 0.
set bodysize = DEFAULT_BODYSIZE
set interval = spawnInterval
set hasArcOrCurve = false
if (allocCount == 0) then
call TimerStart(STAMP, 604800, false, null)
endif
set allocCount = allocCount + 1
// Debug safety for method summon.
debug set heads = 0
debug set attackSpeed = 0.
debug set headHeight = -1.
debug set attackRange = 0.
return this
endmethod
private static method onInit takes nothing returns nothing
set animationSuffix[0] = "first"
set animationSuffix[1] = "second"
set animationSuffix[2] = "third"
set animationSuffix[3] = "fourth"
set animationSuffix[4] = "fifth"
endmethod
endstruct
// Credits to Nestharus for a properly working
// debug algorithm in list modules.
private module HydraStructure
private static thistype collectionCount = 0
private static thistype nodeCount = 0
debug private boolean isNode
debug private boolean isCollection
readonly integer size
private thistype _list
method operator list takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "list", "thistype", this, "Attempted To Read Null Node.")
debug call ThrowError(not isNode, "List", "list", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _list
endmethod
private thistype _next
method operator next takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "List", "next", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _next
endmethod
private thistype _prev
method operator prev takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "List", "prev", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _prev
endmethod
private thistype _first
method operator first takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "first", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "List", "first", "thistype", this, "Attempted To Read Invalid List.")
endif
return _first
endmethod
private thistype _last
method operator last takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "last", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "List", "last", "thistype", this, "Attempted To Read Invalid List.")
endif
return _last
endmethod
private static method allocateCollection takes nothing returns thistype
local thistype this = thistype(0)._first
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(collectionCount == 8191, "List", "allocateCollection", "thistype", 0, "Overflow.")
endif
set this = collectionCount + 1
set collectionCount = this
else
set thistype(0)._first = _first
endif
return this
endmethod
private static method allocateNode takes nothing returns thistype
local thistype this = thistype(0)._next
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(nodeCount == 8191, "List", "allocateNode", "thistype", 0, "Overflow.")
endif
set this = nodeCount + 1
set nodeCount = this
else
set thistype(0)._next = _next
endif
return this
endmethod
static method create takes nothing returns thistype
local thistype this = allocateCollection()
debug set isCollection = true
set _first = 0
set size = 0
return this
endmethod
method enqueue takes nothing returns thistype
local thistype node = allocateNode()
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
debug call ThrowError(not isCollection, "List", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
endif
debug set node.isNode = true
set node._list = this
if (_first == 0) then
set _first = node
set _last = node
set node._prev = 0
else
set _last._next = node
set node._prev = _last
set _last = node
endif
set node._next = 0
set size = size + 1
return node
endmethod
method remove takes nothing returns nothing
local thistype node = this
set this = node._list
static if LIBRARY_ErrorMessage then
debug call ThrowError(node == 0, "List", "remove", "thistype", this, "Attempted To Remove Null Node.")
debug call ThrowError(not node.isNode, "List", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
endif
debug set node.isNode = false
set node._list = 0
if (0 == node._prev) then
set _first = node._next
else
set node._prev._next = node._next
endif
if (0 == node._next) then
set _last = node._prev
else
set node._next._prev = node._prev
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
set size = size - 1
endmethod
method clear takes nothing returns nothing
debug local thistype node = _first
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "clear", "thistype", this, "Attempted To Clear Null List.")
debug call ThrowError(not isCollection, "List", "clear", "thistype", this, "Attempted To Clear Invalid List.")
endif
static if DEBUG_MODE then
loop
exitwhen node == 0
set node.isNode = false
set node = node._next
endloop
endif
if (_first == 0) then
return
endif
set _last._next = thistype(0)._next
set thistype(0)._next = _first
set _first = 0
set _last = 0
endmethod
method destroy takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "destroy", "thistype", this, "Attempted To Destroy Null List.")
debug call ThrowError(not isCollection, "List", "destroy", "thistype", this, "Attempted To Destroy Invalid List.")
endif
static if DEBUG_MODE then
debug call clear()
debug set isCollection = false
else
if (_first != 0) then
set _last._next = thistype(0)._next
set thistype(0)._next = _first
set _last = 0
endif
endif
set _first = thistype(0)._first
set thistype(0)._first = this
set size = 0
endmethod
endmodule
endlibrary
//TESH.scrollpos=69
//TESH.alwaysfold=0
private module HydraStructure
private static thistype collectionCount = 0
private static thistype nodeCount = 0
debug private boolean isNode
debug private boolean isCollection
readonly integer size
private thistype _list
method operator list takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "list", "thistype", this, "Attempted To Read Null Node.")
debug call ThrowError(not isNode, "List", "list", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _list
endmethod
private thistype _next
method operator next takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "next", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "List", "next", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _next
endmethod
private thistype _prev
method operator prev takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "prev", "thistype", this, "Attempted To Go Out Of Bounds.")
debug call ThrowError(not isNode, "List", "prev", "thistype", this, "Attempted To Read Invalid Node.")
endif
return _prev
endmethod
private thistype _first
method operator first takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "first", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "List", "first", "thistype", this, "Attempted To Read Invalid List.")
endif
return _first
endmethod
private thistype _last
method operator last takes nothing returns thistype
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "last", "thistype", this, "Attempted To Read Null List.")
debug call ThrowError(not isCollection, "List", "last", "thistype", this, "Attempted To Read Invalid List.")
endif
return _last
endmethod
private static method allocateCollection takes nothing returns thistype
local thistype this = thistype(0)._first
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(collectionCount == 8191, "List", "allocateCollection", "thistype", 0, "Overflow.")
endif
set this = collectionCount + 1
set collectionCount = this
else
set thistype(0)._first = _first
endif
return this
endmethod
private static method allocateNode takes nothing returns thistype
local thistype this = thistype(0)._next
if (0 == this) then
static if LIBRARY_ErrorMessage then
debug call ThrowError(nodeCount == 8191, "List", "allocateNode", "thistype", 0, "Overflow.")
endif
set this = nodeCount + 1
set nodeCount = this
else
set thistype(0)._next = _next
endif
return this
endmethod
static method create takes nothing returns thistype
local thistype this = allocateCollection()
debug set isCollection = true
set _first = 0
set size = 0
return this
endmethod
method enqueue takes nothing returns thistype
local thistype node = allocateNode()
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "enqueue", "thistype", this, "Attempted To Enqueue On To Null List.")
debug call ThrowError(not isCollection, "List", "enqueue", "thistype", this, "Attempted To Enqueue On To Invalid List.")
endif
debug set node.isNode = true
set node._list = this
if (_first == 0) then
set _first = node
set _last = node
set node._prev = 0
else
set _last._next = node
set node._prev = _last
set _last = node
endif
set node._next = 0
set size = size + 1
return node
endmethod
method remove takes nothing returns nothing
local thistype node = this
set this = node._list
static if LIBRARY_ErrorMessage then
debug call ThrowError(node == 0, "List", "remove", "thistype", this, "Attempted To Remove Null Node.")
debug call ThrowError(not node.isNode, "List", "remove", "thistype", this, "Attempted To Remove Invalid Node (" + I2S(node) + ").")
endif
debug set node.isNode = false
set node._list = 0
if (0 == node._prev) then
set _first = node._next
else
set node._prev._next = node._next
endif
if (0 == node._next) then
set _last = node._prev
else
set node._next._prev = node._prev
endif
set node._next = thistype(0)._next
set thistype(0)._next = node
set size = size - 1
endmethod
method clear takes nothing returns nothing
debug local thistype node = _first
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "clear", "thistype", this, "Attempted To Clear Null List.")
debug call ThrowError(not isCollection, "List", "clear", "thistype", this, "Attempted To Clear Invalid List.")
endif
static if DEBUG_MODE then
loop
exitwhen node == 0
set node.isNode = false
set node = node._next
endloop
endif
if (_first == 0) then
return
endif
set _last._next = thistype(0)._next
set thistype(0)._next = _first
set _first = 0
set _last = 0
endmethod
method destroy takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "List", "destroy", "thistype", this, "Attempted To Destroy Null List.")
debug call ThrowError(not isCollection, "List", "destroy", "thistype", this, "Attempted To Destroy Invalid List.")
endif
static if DEBUG_MODE then
debug call clear()
debug set isCollection = false
else
if (_first != 0) then
set _last._next = thistype(0)._next
set thistype(0)._next = _first
set _last = 0
endif
endif
set _first = thistype(0)._first
set thistype(0)._first = this
set size = 0
endmethod
endmodule
//TESH.scrollpos=106
//TESH.alwaysfold=0
library HydraTemplate uses Hydra
//******************************************************************************
//*
//* A guide to individual Hydra structs
//* ===================================
//*
//* First and foremost Hydra uses the vJass feature "Extending structs".
//* - http://www.hiveworkshop.com/forums/tutorial-submission-283/vjass-meet-vjass-extending-structs-267719/#post2706474
//*
//* Which means that each of your custom Hydra-types will extend Hydra. Example: FireHydra extends Hydra
//* That makes customization very flexible, as Hydra will evaluate several stub methods
//* in the respective child struct like the target filter or the missile setup.
//*
//* The projectiles fired by hydra units are outsourced to library Missile,
//* as consequence you have full access granted to the Missile API, which
//* can be individual per child struct. This means projectiles fired by
//* i.e. FireHydra can have a different behaviour than those fired by an i.e. IceHydra
//* If you haven't done it already, please do a proper setup for the globals in library Missile
//*
//* struct Hydra has a boolean member named hasArcOrCurve. You have to set it per Hydra instance you create
//* to false or true. Set it to true, if you wish to setup curve= or arc= for that custom Hydra.
//* Don't forget it, because otherwise the projectiles fired in the struct will go crazy.
//* in DEBUG_MODE you will recieve a warning, if you forget to set hasArcOrCurve.
//*
//* Below you will find an example struct, how a custum Hydra could look like
//* For more examples check out IceHydra, FriendlyHydra, EasterEgg, ... in this map.
//*
//*
//********************************************************************************
//=================================================================================
//* Declare a new custom hydra type which extends Hydra
//* Here I have chosen the struct name AcidHydra.
struct AcidHydra extends Hydra
//*
//* Acces the API of library Missile ( for detailed information read library Missile ).
//* As already mentioned above, you have granted full access to the API of library Missile.
//* You can declare various static method which will be called if a Missile launched from this struct,
//* meets the condition for the respective static method.
//*
//* Available static methods are:
//*
//* static method onCollide takes Missile missile, unit hit returns boolean
//* - Runs every time the missile collides with a unit.
//*
//* static method onPeriod takes Missile missile returns boolean
//* - Runs every Missile_TIMER_TIMEOUT seconds.
//*
//* static method onFinish takes Missile missile returns boolean
//* - Runs whenever the missile finishes it's course.
//* - Runs before the missile is destroyed.
//* - Does not run, if a Missile is destroyed by another method.
//*
//* static method onRemove takes Missile missile returns boolean
//* - Runs whenever the missile is deallocated.
//* - Return true will recycle a Missile delayed ( only if WRITE_DELAYED_MISSILE_RECYCLING = true )
//* - Return false will recycle a Missile right away.
//* - Runs always, if declared!!
//*
//* static method onDestructableFilter takes nothing returns boolean
//* - Runs before onDestructable.
//* - May create a better filter for enumerated destructables.
//* - Only method which has no impact on the Missile instance.
//*
//* static method onDestructable takes Missile missile, destructable hit returns boolean
//* - Runs every time the missile collides with a destructable.
//* - Can use onDestructableFilter ( optional )
//* - Runs after onDestructableFilter ( if filter is declared )
//*
//* Hint: missile.data is always the respective Hydra instance.
//*
//* Let's see some of the static methods I declared for this struct.
private static method onRemove takes Missile missile returns boolean
return true
endmethod
//* Will kill the missile, when it collides i.e. with a wall.
private static method onTerrain takes Missile missile returns boolean
return true
endmethod
//* Will kill the missile when it deals damage.
//* You can read missile members, like missile.source, missile.damage, ....
private static method onCollide takes Missile missile, unit hit returns boolean
if UnitAlive(hit) and IsUnitEnemy(hit, missile.owner) then
return UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
return false
endmethod
//* module MissileStruct is required, that a hydra unit can fire missiles at all.
//* The best place for it is above the Hydra stub methods and below the Missile API.
//* In this position the JassHelper will not generate extra pseudo-code duplicates.
implement MissileStruct
//* Access the Hydra API: ( for detailed information read library Hydra. )
//* As mentioned above you can customize a couple of stub methods located
//* in library Hydra, our parent struct. A declaration of method onFire
//* is required, that a Hydra type of this struct can fire missiles at all.
//*
//* Available stub methods are:
//*
//* public stub method onSummonHead takes unit head returns nothing
//* - runs each time a new head is summoned.
//*
//* public stub method onRemoveHead takes unit head returns nothing
//* - runs each time a head dies.
//*
//* public stub method onTargetFilter takes unit target, player owner returns boolean
//* - customize a target filter. As you can see each different Hydra type can have it's own filter.
//*
//* public stub method onFire takes unit shooter, unit target, Missile missile returns nothing
//* - customize the missile datas here. ( damage, model, arc, curve, ... )
//* - from this method you have to launch the missile via thistype.launch(missile)
//*
//* Let's see some example code:
//* Here we have access to the shooting head and the already created missile instance.
//* missile.source, missile.owner are already set and don't need further changes.
//* missile.data is already set to this AcidHydra instance, you can override it if required.
private method onFire takes unit shooter, unit target, Missile missile returns nothing
set missile.model = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
set missile.scale = 1.
set missile.damage = GetRandomReal(50, 150)
set missile.collision = 32.
set missile.collisionZ = 15.
set missile.speed = 27.
//* You have to launch the Missile here. It will tell library Missile, that it's
//* a missile fired from excacly this struct.
call thistype.launch(missile)
endmethod
//* Another stub method, which runs each time a new head of type AcidHydra is created
private method onSummonHead takes unit summoned returns nothing
//* call BJDebugMsg("summoned")
endmethod
//* Here you can customize, which units can the AcidHydra target.
//* Example: Just enemies structure type units are valid targets.
private method onTargetFilter takes unit target, player owner returns boolean
return IsUnitType(target, UNIT_TYPE_STRUCTURE) and IsUnitEnemy(target, owner) and UnitAlive(target)
endmethod
endstruct
//* Here you will learn how to create a AcidHydra instance.
//* I choose a spell as example:
private function OnCast takes nothing returns boolean
//* Declare a new Acidydra local.
local AcidHydra hydra
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real z = 0.
local real face = GetRandomReal(0., 2*bj_PI)*bj_RADTODEG
//* This is the spawn interval between each heads.
//* CreateHead - wait interval - CreatHead - wait interval - ...
local real interval = .23
if (GetSpellAbilityId() == 'xxxx') then
//* static method create takes unit whichUnit, integer unitid, real x, real y, z, real angle, spawnInterval returns Hydra
set hydra = AcidHydra.create(GetTriggerUnit(), 'hfoo', x, y, z, face, interval)
//* Customize hydra members.
//* Each member has a default setting, which is done in static method create.
//* You will recieve an error message, if you forget to setup a mandatory member.
set hydra.heads = 3 // By default 0.
set hydra.attackAnimationTime = .5 // Read it in the object editor and divide it with 1/2. ( the second half is the backswing )
set hydra.attackSpeed = 3. // By default 0.
set hydra.attackPeriod = .25 // By default 0.
set hydra.duration = 12 // By default 0.
set hydra.attackRange = 950. // By default 0.
set hydra.headHeight = 55. // By default 0.
set hydra.scale = 1. // By default 1.
set hydra.hasArcOrCurve = false // By default false
set hydra.birthAnimationTime = 0. // By default 0.
// *Summon it.
call hydra.summon()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
if UnitAddAbility(DemoMap_HERO, 'xxxx') then
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function OnCast))
endif
endfunction
endscope
//TESH.scrollpos=47
//TESH.alwaysfold=0
scope FireHydra initializer Init
globals
private constant integer HYDRA_ABILITY = 'A002'
private constant integer HYDRA_UNIT_ID = 'o002'
endglobals
struct FireHydra extends Hydra
private static method onRemove takes Missile missile returns boolean
return true
endmethod
private static method onTerrain takes Missile missile returns boolean
return true
endmethod
private static method onCollide takes Missile missile, unit hit returns boolean
if UnitAlive(hit) and IsUnitEnemy(hit, missile.owner) then
return UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
return false
endmethod
implement MissileStruct
method onFire takes unit shooter, unit target, Missile missile returns nothing
set missile.model = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
set missile.scale = 1.
set missile.damage = GetRandomReal(50, 150)
set missile.collision = 32.
set missile.collisionZ = 15.
set missile.speed = 27.
call thistype.launch(missile)
endmethod
method onTargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner)
endmethod
method onRemoveHead takes unit head returns nothing
endmethod
method onSummonHead takes unit head returns nothing
endmethod
endstruct
private function OnCast takes nothing returns boolean
local FireHydra hydra
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real z = 0.
local real face = GetRandomReal(0., 2*bj_PI)*bj_RADTODEG
local real interval = .23
if (GetSpellAbilityId() == HYDRA_ABILITY) then
set hydra = FireHydra.create(GetTriggerUnit(), HYDRA_UNIT_ID, x, y, z, face, interval)
set hydra.heads = 3
set hydra.attackAnimationTime = .5
set hydra.attackSpeed = 3.
set hydra.attackPeriod = .25
set hydra.duration = 12
set hydra.attackRange = 950.
set hydra.headHeight = 55.
set hydra.scale = 1.
set hydra.hasArcOrCurve = false
set hydra.birthAnimationTime = 1.
call hydra.summon()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
if UnitAddAbility(DemoMap_HERO, HYDRA_ABILITY) then
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function OnCast))
endif
endfunction
endscope
//TESH.scrollpos=45
//TESH.alwaysfold=0
library IceHydra initializer Init uses Hydra
globals
private constant integer HYDRA_ABILITY = 'A000'
private constant integer HYDRA_UNIT_ID = 'o000'
endglobals
struct IceHydra extends Hydra
private static method onRemove takes Missile missile returns boolean
return true
endmethod
private static method onTerrain takes Missile missile returns boolean
return true
endmethod
private static method onCollide takes Missile missile, unit hit returns boolean
if UnitAlive(hit) and IsUnitEnemy(hit, missile.owner) then
if IsUnitType(hit, UNIT_TYPE_STRUCTURE) then
return UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
return UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
return false
endmethod
implement MissileStruct
method onFire takes unit shooter, unit target, Missile missile returns nothing
set missile.model = "Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl"
set missile.scale = 0.8
set missile.damage = GetRandomReal(15, 30)
set missile.collision = 32.
set missile.collisionZ = 15.
set missile.speed = 35.
call thistype.launch(missile)
endmethod
method onTargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner)
endmethod
method onRemoveHead takes unit head returns nothing
endmethod
method onSummonHead takes unit head returns nothing
endmethod
endstruct
private function OnCast takes nothing returns boolean
local IceHydra hydra
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real z = 0.
local real face = GetRandomReal(0., 2*bj_PI)*bj_RADTODEG
local real interval = 1.523
if (GetSpellAbilityId() == HYDRA_ABILITY) then
set hydra = IceHydra.create(GetTriggerUnit(), HYDRA_UNIT_ID, x, y, z, face, interval)
set hydra.headHeight = 55.
set hydra.scale = 1.2
set hydra.attackAnimationTime = .5
set hydra.attackPeriod = .2
set hydra.attackSpeed = .6
set hydra.duration = 10.
set hydra.attackRange = 1200.
set hydra.heads = 3
set hydra.offset = 8.
set hydra.hasArcOrCurve = false
set hydra.birthAnimationTime = 1.
//call hydra.optimize()
call hydra.summon()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
if UnitAddAbility(DemoMap_HERO, HYDRA_ABILITY) then
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function OnCast))
endif
endfunction
endlibrary
//TESH.scrollpos=45
//TESH.alwaysfold=0
scope FriendlyHydra initializer Init
globals
private constant integer HYDRA_ABILITY = 'A001'
private constant integer HYDRA_UNIT_ID = 'o002'
endglobals
struct FriendlyHydra extends Hydra
private static method onRemove takes Missile missile returns boolean
return true
endmethod
private static method onTerrain takes Missile missile returns boolean
return true
endmethod
private static method onCollide takes Missile missile, unit hit returns boolean
if (UnitAlive(hit) and IsUnitAlly(hit, missile.owner)) then
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\HolyBolt\\HolyBoltSpecialArt.mdl", hit, "origin"))
call SetWidgetLife(hit, GetWidgetLife(hit) + missile.damage)
return true
endif
return false
endmethod
implement MissileStruct
method onFire takes unit shooter, unit target, Missile missile returns nothing
set missile.model = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"
set missile.scale = 1.2
set missile.damage = GetRandomReal(25., 40.)
set missile.collision = 32.
set missile.collisionZ = 15.
set missile.speed = 8.
set missile.arc = (bj_PI/2.5)// You can only use arc/curve when hasArcOrCurve is set to true.
set missile.target = target
call thistype.launch(missile)
endmethod
method onTargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitAlly(target, owner) and GetWidgetLife(target) != GetUnitState(target, UNIT_STATE_MAX_LIFE)
endmethod
method onRemoveHead takes unit head returns nothing
endmethod
method onSummonHead takes unit head returns nothing
endmethod
endstruct
private function OnCast takes nothing returns boolean
local FriendlyHydra hydra
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real z = 0.
local real face = GetRandomReal(0., 2*bj_PI)*bj_RADTODEG
local real interval = .23
if (GetSpellAbilityId() == HYDRA_ABILITY) then
set hydra = FriendlyHydra.create(GetTriggerUnit(), HYDRA_UNIT_ID, x, y, z, face, interval)
set hydra.headHeight = 55.
set hydra.scale = 1.2
set hydra.attackAnimationTime = .5
set hydra.attackPeriod = 1.
set hydra.attackSpeed = 3.
set hydra.duration = 20.
set hydra.attackRange = 600.
set hydra.heads = 3
set hydra.offset = 8.
set hydra.hasArcOrCurve = true
set hydra.birthAnimationTime = 1.
call hydra.optimize()
call hydra.summon()
endif
return false
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
if UnitAddAbility(DemoMap_HERO, HYDRA_ABILITY) then
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function OnCast))
endif
endfunction
endscope
//TESH.scrollpos=2
//TESH.alwaysfold=0
scope EasterEgg initializer Init
globals
private constant integer HYDRA_ABILITY = 'A004'
private constant integer HYDRA_UNIT_ID = 'o001'
private integer array count
private boolean paused = false
endglobals
private function PauseSound takes nothing returns nothing
set paused = true
call TriggerSleepAction(1.)
set paused = false
endfunction
private function CoolSound takes string path, player p, boolean forceSound returns nothing
local sound s
if not forceSound and paused then
return
endif
call ExecuteFunc(PauseSound.name)
set s = CreateSound(path, false, false, false, 10, 10, "CombatSoundsEAX")
if GetLocalPlayer() != p then
call SetSoundVolume(s, 0)
endif
call StartSound(s)
call KillSoundWhenDone(s)
set s = null
endfunction
struct HammerTime extends Hydra
private static method onDestructable takes Missile missile, destructable hit returns boolean
return true
endmethod
private static method onRemove takes Missile missile returns boolean
return true
endmethod
private static method onTerrain takes Missile missile returns boolean
return true
endmethod
private static method onCollide takes Missile missile, unit hit returns boolean
if UnitAlive(hit) and IsUnitEnemy(hit, missile.owner) then
call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
if not UnitAlive(hit) and (GetUnitTypeId(hit) != 0) then
call CoolSound("Units\\Human\\Muradin\\MuradinYesAttack3.wav", missile.owner, false)
if (IsUnitType(hit, UNIT_TYPE_GROUND)) then
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\Thunderclap\\ThunderClapCaster.mdl", GetUnitX(hit), GetUnitY(hit)))
endif
endif
return true
endif
return false
endmethod
implement MissileStruct
method onFire takes unit shooter, unit target, Missile missile returns nothing
set missile.model = "Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl"
set missile.damage = GetRandomReal(150, 550)
set missile.collision = 36.*scale
set missile.collisionZ = 15.*scale
set missile.speed = 10.
set missile.scale = scale
set count[this] = count[this] + 1
if count[this] == 6 then
call DestroyEffect(AddSpecialEffectTarget("Abilities\\Spells\\Human\\Avatar\\AvatarCaster.mdl", shooter, "origin"))
set attackSpeed = attackSpeed/2.
set scale = scale*2
call SetUnitVertexColor(shooter, 150, 150, 150, 200)
endif
call thistype.launch(missile)
endmethod
method onTargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner)
endmethod
method onRemoveHead takes unit head returns nothing
call RemoveUnit(head)
endmethod
method onSummonHead takes unit head returns nothing
call CoolSound("Units\\Human\\Muradin\\MuradinYes4.wav", this.owner, true)
set count[this] = 0
endmethod
endstruct
private function OnCast takes nothing returns boolean
local HammerTime hydra
local real x = GetSpellTargetX()
local real y = GetSpellTargetY()
local real z = GetRandomReal(0, 600)
local real face = GetRandomReal(0., 2*bj_PI)*bj_RADTODEG
local real interval = .23
if (GetSpellAbilityId() == HYDRA_ABILITY) then
set hydra = HammerTime.create(GetTriggerUnit(), HYDRA_UNIT_ID, x, y, z, face, interval)
set hydra.headHeight = 45.
set hydra.scale = .76
set hydra.attackAnimationTime = .5
set hydra.attackPeriod = .2
set hydra.attackSpeed = 1.
set hydra.duration = 10.
set hydra.attackRange = 750.
set hydra.heads = 1
set hydra.offset = 8.
set hydra.hasArcOrCurve = false
call hydra.summon()
endif
return false
endfunction
private function Pre takes nothing returns nothing
call CoolSound("Units\\Human\\Muradin\\MuradinYes4.wav", null, true)
call CoolSound("Units\\Human\\Muradin\\MuradinYesAttack3.wav", null, true)
call DestroyTimer(GetExpiredTimer())
endfunction
private function Init takes nothing returns nothing
local trigger t = CreateTrigger()
if UnitAddAbility(DemoMap_HERO, HYDRA_ABILITY) then
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function OnCast))
endif
call TimerStart(CreateTimer(), 1., false, function Pre)
endfunction
endscope
//TESH.scrollpos=44
//TESH.alwaysfold=0
scope HillGuard initializer Init
globals
private unit REVENANT = null
private boolean READY = true
private Guard GUARDIAN = 0
endglobals
// This is the big revenant on the hill. Right corner of the map.
private function cb takes nothing returns nothing
call ReleaseTimer(GetExpiredTimer())
set READY = true
endfunction
struct Guard extends Hydra
private static method onRemove takes Missile missile returns boolean
return true
endmethod
private static method onTerrain takes Missile missile returns boolean
return true
endmethod
private static method onCollide takes Missile missile, unit hit returns boolean
if UnitAlive(hit) and IsUnitEnemy(hit, missile.owner) then
return UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_NORMAL, null)
endif
return false
endmethod
implement MissileStruct
method onTargetFilter takes unit target, player owner returns boolean
return UnitAlive(target) and IsUnitEnemy(target, owner)
endmethod
private method onFire takes unit shooter, unit target, Missile missile returns nothing
set missile.model = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
set missile.scale = 1.
set missile.damage = GetRandomReal(50, 150)
set missile.collision = 32.
set missile.collisionZ = 15.
set missile.speed = 27.
set missile.arc = bj_PI/3
set missile.curve = GetRandomReal(-bj_PI/3, bj_PI/3)
call thistype.launch(missile)
endmethod
endstruct
private function enter takes nothing returns boolean
local unit u = GetTriggerUnit()
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local player p = GetOwningPlayer(u)
if Player(0) == p and READY then
if (GUARDIAN != 0) and GUARDIAN.summoned then
call GUARDIAN.destroy()
set GUARDIAN = 0
endif
set GUARDIAN = Guard.create(REVENANT, 'o002', x, y, 0, 225., 0.23)
set GUARDIAN.headHeight = 55.
set GUARDIAN.scale = 1.0
set GUARDIAN.attackAnimationTime = .5
set GUARDIAN.attackSpeed = 3.2
set GUARDIAN.attackPeriod = .5
set GUARDIAN.duration = 0.00
set GUARDIAN.attackRange = 750.
set GUARDIAN.heads = 5
set GUARDIAN.hasArcOrCurve = true
set GUARDIAN.birthAnimationTime = 1.
call GUARDIAN.summon()
//----------------------------
call SetUnitFacing(REVENANT, Atan2(y - GetUnitY(REVENANT), x - GetUnitX(REVENANT))*bj_RADTODEG)
call SetUnitAnimation(REVENANT, "spell")
call QueueUnitAnimation(REVENANT, "stand")
set READY = false
call TimerStart(NewTimer(), 4.00, false, function cb)
endif
set p = null
set u = null
return false
endfunction
private function Init takes nothing returns nothing
local region r = CreateRegion()
local trigger t = CreateTrigger()
local rect c = Rect(64, -2624, 384, -2336)
call RegionAddRect(r, c)
call TriggerRegisterEnterRegion(t, r, null)
call TriggerAddCondition(t, Filter(function enter))
set t = null
set r = null
set c = null
set REVENANT = CreateUnit(Player(PLAYER_NEUTRAL_AGGRESSIVE), 'nlrv', 127., -3072., 0)
call UnitAddAbility(REVENANT, 'Aloc')
call UnitAddAbility(REVENANT, 'Abun')
endfunction
endscope
//TESH.scrollpos=24
//TESH.alwaysfold=0
library DemoMap
globals
private real X = -1500
private real Y = -2280
private real X0 = 1177
private real Y0 = 1370
public unit HERO
endglobals
private struct preload extends array
static method sfx takes nothing returns nothing
call RemoveUnit(CreateUnit(Player(15), 'o002', 0, 0, 0))
call RemoveUnit(CreateUnit(Player(15), 'o000', 0, 0, 0))
call RemoveUnit(CreateUnit(Player(15), 'o001', 0, 0, 0))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl", 0, 0))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl", 0, 0))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\ChimaeraAcidMissile\\ChimaeraAcidMissile.mdl", 0, 0))
call DestroyEffect(AddSpecialEffect("Abilities\\Weapons\\FrostWyrmMissile\\FrostWyrmMissile.mdl", 0, 0))
call DestroyEffect(AddSpecialEffect("Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl", 0, 0))
endmethod
endstruct
private struct xxx extends array
private static method spawn takes nothing returns nothing
local integer i = 0
local unit u
loop
exitwhen i == 2
set u = CreateUnit(Player(1), 'hfoo', X, Y, 0)
call IssuePointOrder(u, "attack", X0, Y0)
set u = CreateUnit(Player(1), 'ehpr', X, Y, 0)
call IssuePointOrder(u, "attack", X0, Y0)
set u = CreateUnit(Player(2), 'ugho', X0, Y0, 0)
call IssuePointOrder(u, "attack", X, Y)
set u = CreateUnit(Player(2), 'ugar', X0, Y0, 0)
call IssuePointOrder(u, "attack", X, Y)
set i = i + 1
endloop
set u = null
endmethod
private static method revive takes nothing returns boolean
local player p = GetTriggerPlayer()
local integer id = GetUnitTypeId(GetTriggerUnit())
local unit u
if p == Player(1) and GetUnitAbilityLevel(GetTriggerUnit(), 'Aloc') == 0 then
set u = CreateUnit(p, id, X, Y, 0)
call IssuePointOrder(u, "attack", X0, Y0)
elseif p == Player(2)and GetUnitAbilityLevel(GetTriggerUnit(), 'Aloc') == 0 then
set u = CreateUnit(p, id, X0, Y0, 0)
call IssuePointOrder(u, "attack", X, Y)
elseif GetTriggerUnit() == HERO then
call ReviveHero(HERO, 0,0, true)
endif
set u = null
set p = null
return false
endmethod
private static method reset takes nothing returns boolean
call ClearTextMessages()
call UnitResetCooldown(HERO)
call SetUnitState(HERO, UNIT_STATE_MANA, GetUnitState(HERO, UNIT_STATE_MAX_MANA))
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DEATH)
call TriggerAddCondition(t, Condition( function thistype.revive))
call thistype.spawn()
set t = CreateTrigger()
set HERO = CreateUnit(Player(0), 'Uktl', 0,0,0)
call SetHeroLevel(HERO, 12, true)
call FogModifierStart(CreateFogModifierRect(Player(0), FOG_OF_WAR_VISIBLE, WorldBounds.world, false, false))
call TriggerRegisterPlayerEventEndCinematic(t, Player(0))
call TriggerAddCondition(t, Condition(function thistype.reset))
call BJDebugMsg("Press |cffdaa520ESC|r to reset cooldowns and refresh mana")
set t = null
call preload.sfx()
call AddSpecialEffectTarget("UI\\Feedback\\SelectionCircleEnemy\\SelectionCircleEnemy.mdx", HERO, "origin")
endmethod
endstruct
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0