//TESH.scrollpos=0
//TESH.alwaysfold=0
Name | Type | is_array | initial_value |
//TESH.scrollpos=195
//TESH.alwaysfold=0
scope BreathOfFire//version 1.4 by kawas11
/******************************************************************************************************
*
* Requires: Missle, DelFX, TimerUtils, and IsUnitChanneling
* Optional: SpellEffectEvent, IsDestructableTree (if you want to check if destructables are trees)
*
*
*
* How to import:
* - Copy BoF trigger
* - Copy Libraries folder
* - Copy Universal Dummy
* - Copy Breath of Fire Ability
*
* Changelog:
* v1.0
* - First Release
* v1.1
* - Tooltip correction
* - Removed unused globals
* - Shorter code
* - Tree filter (IsDestructableTree)
* - onCast now returns boolean
* v1.2
* - Now using KillTree function from IsDestructableTree
* - Improved documentation
* - Structured trigger folders
* 1.3
* - Now based on channel ability
* - Removed unused tree_rect
* - Updated missle library
* - No longer requires CTL, now using TimerUtils
* - Fixed tooltip
* 1.4
* - method Destroy -> method destroy
* - Caster's animation speed correction
* - Fireballs will be now always removed in onRemove method independant of the user setting
*
*
*********************************************************************************************************/
/**************************************************************************************
* *
* CONFIGURATION *
* *
**************************************************************************************/
native UnitAlive takes unit id returns boolean
globals
//Breath of Fire ability rawcode
private constant integer SPELL_ID = 'A000'
//Cast time - time needed for animation
private constant real CAST_TIME = 1.00
//Caster animation speed in percent
private constant real CASTER_ANIMATION_SPEED = 60
//Total spell channel time (exclude CAST_TIME)
private constant real CHANNEL_TIME = 5.00
//Every X timer ticks (0.031250) spawn new fireball
private constant integer FIRE_CD = 3
//Attachment point for damage effect
private constant string DAMAGE_EFFECT_ATTACH = "chest"
//Damage effect
private constant string DAMAGE_EFFECT = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
//Fireballs remove effect
private constant string REMOVE_EFFECT = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
//Fireballs spawn effect
private constant string SPAWN_EFFECT = "Abilities\\Weapons\\FireBallMissile\\FireBallMissile.mdl"
//Fireballs model
private constant string DUMMY_MODEL = "Abilities\\Weapons\\RedDragonBreath\\RedDragonMissile.mdl"
//Fireballs Scale in percent
private constant real DUMMY_SCALE = 120
//Fireballs height
private constant real DUMMY_HEIGHT = 70
//Fireballs move speed every 0.031250
private constant real DUMMY_SPEED = 25
//Fireballs collision size - used for units and destructables
private constant real DUMMY_COLISION = 75
//Distance between caster and fireballs spawn location
private constant real DUMMY_CREATE_DIST = 85
//Set minimum angle difference of caster's facing
private constant real MIN_ANGLE = 15
//Set maximum angle difference of caster's facing
private constant real MAX_ANGLE = 15
//Remove fireballs when collide with units and trees?
private constant boolean REMOVE = true
//Remove fireballs when collide with other destructables (not trees)?
private constant boolean REMOVE2 = true
//Destroy trees?
private constant boolean TREE = true
//Damage and attack type
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
private constant real INTERVAL = 0.031250
endglobals
//Max distance traveled by fireballs
private constant function GetDistance takes integer level returns real
return 800.00 + 100.00 * level
endfunction
//Damage per fireball
private constant function GetDamage takes integer level returns real
return 5.00 + 5.00 * level
endfunction
//Which units should/shouldn't be damaged?
private function DamageFilter takes unit target, player owner returns boolean
return (not IsUnitType(target, UNIT_TYPE_MAGIC_IMMUNE)) and (not IsUnitType(target, UNIT_TYPE_STRUCTURE)) and (not IsUnitType(target, UNIT_TYPE_FLYING)) and (UnitAlive(target)) and (IsUnitEnemy(target, owner))
endfunction
/**************************************************************************************
* *
* CONFIGURATION END *
* *
**************************************************************************************/
private struct BoF
unit caster
player owner
integer cast
integer fireCD
real castTime
real distance
real targetX
real targetY
real channelTime
real damage
timer t
method destroy takes nothing returns nothing
call ReleaseTimer(t)
call SetUnitTimeScale(caster, 1)
set t = null
set caster = null
set owner = null
call deallocate()
endmethod
static method onLoop takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real casterX
local real casterY
local real createX
local real createY
local real angle
local Missile m
//Cast time
if castTime > 0 then
set castTime = castTime - INTERVAL
else
//No reason to set caster's animation sped multiple times
if cast == 1 then
call SetUnitTimeScale(caster, 0)
set cast = 0
endif
//Cooldown between creating new fireballs
if FIRE_CD == fireCD then
set casterX = GetUnitX(caster)
set casterY = GetUnitY(caster)
set angle = Atan2(targetY - casterY, targetX - casterX) + (GetRandomReal(-MIN_ANGLE, MAX_ANGLE) * bj_DEGTORAD)
set createX = casterX + DUMMY_CREATE_DIST * Cos(angle)
set createY = casterY + DUMMY_CREATE_DIST * Sin(angle)
//Spawn effect
call CreateDelayedEffectZ(SPAWN_EFFECT, createX, createY, DUMMY_HEIGHT, 0, 0)
//Fireball create and launch it
set m = Missile.create(createX, createY, DUMMY_HEIGHT, angle, distance, DUMMY_HEIGHT)
set m.source = caster
set m.owner = owner
set m.speed = DUMMY_SPEED
set m.damage = damage
set m.collision = DUMMY_COLISION
set m.scale = DUMMY_SCALE * 0.01
set m.model = DUMMY_MODEL
call thistype.launch(m)
//Reseting fireballs create cooldown
set fireCD = 0
endif
set fireCD = fireCD + 1
set channelTime = channelTime - INTERVAL
endif
if channelTime <= 0 or not IsUnitChanneling(caster) then
call destroy()
endif
endmethod
static method onCollide takes Missile missile, unit hit returns boolean
if DamageFilter(hit, missile.owner) then
call UnitDamageTarget(missile.source, hit, missile.damage, false, false, ATTACK_TYPE, DAMAGE_TYPE, null)
call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT, hit, DAMAGE_EFFECT_ATTACH))
endif
return REMOVE
endmethod
static method onRemove takes Missile missile returns boolean
call DestroyEffect(AddSpecialEffectTarget(REMOVE_EFFECT, missile.dummy, "origin"))
return true
endmethod
static method onDestructable takes Missile missile, destructable hit returns boolean
static if LIBRARY_IsDestructableTree and TREE then
call KillTree(hit)
endif
return REMOVE2
endmethod
implement MissileStruct
static method onCast takes nothing returns boolean
local BoF data = BoF.create()
local integer level
set data.t = NewTimerEx(data)
set data.caster = GetTriggerUnit()
set data.owner = GetTriggerPlayer()
set level = GetUnitAbilityLevel(data.caster, SPELL_ID)
set data.targetX = GetSpellTargetX()
set data.targetY = GetSpellTargetY()
set data.channelTime = CHANNEL_TIME
set data.castTime = CAST_TIME
set data.distance = GetDistance(level)
set data.damage = GetDamage(level)
set data.cast = 1
set data.fireCD = FIRE_CD
call SetUnitTimeScale(data.caster, CASTER_ANIMATION_SPEED * 0.01)
call TimerStart(data.t, INTERVAL, true, function BoF.onLoop)
return false
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method condition takes nothing returns boolean
return (GetSpellAbilityId() == SPELL_ID) and thistype.onCast()
endmethod
endif
private static method onInit takes nothing returns nothing
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_ID, function thistype.onCast)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.condition))
set t = null
endif
//Preloading effects to prevent first use lag
call Preload(DUMMY_MODEL)
call Preload(SPAWN_EFFECT)
call Preload(REMOVE_EFFECT)
call Preload(DAMAGE_EFFECT)
endmethod
endstruct
endscope
//TESH.scrollpos=0
//TESH.alwaysfold=0
library BoundSentinel initializer init
//*************************************************
//* BoundSentinel
//* -------------
//* Don't leave your units unsupervised, naughty
//* them may try to get out of the map bounds and
//* crash your game.
//*
//* To implement, just get a vJass compiler and
//* copy this library/trigger to your map.
//*
//*************************************************
//==================================================
globals
// High enough so the unit is no longer visible, low enough so the
// game doesn't crash...
//
// I think you need 0.0 or soemthing negative prior to patch 1.22
//
private constant real EXTRA = 500.0
endglobals
//=========================================================================================
globals
private real maxx
private real maxy
private real minx
private real miny
endglobals
//=======================================================================
private function dis takes nothing returns nothing
local unit u=GetTriggerUnit()
local real x=GetUnitX(u)
local real y=GetUnitY(u)
if(x>maxx) then
set x=maxx
elseif(x<minx) then
set x=minx
endif
if(y>maxy) then
set y=maxy
elseif(y<miny) then
set y=miny
endif
call SetUnitX(u,x)
call SetUnitY(u,y)
set u=null
endfunction
private function init takes nothing returns nothing
local trigger t=CreateTrigger()
local region r=CreateRegion()
local rect rc
set minx=GetCameraBoundMinX() - EXTRA
set miny=GetCameraBoundMinY() - EXTRA
set maxx=GetCameraBoundMaxX() + EXTRA
set maxy=GetCameraBoundMaxY() + EXTRA
set rc=Rect(minx,miny,maxx,maxy)
call RegionAddRect(r, rc)
call RemoveRect(rc)
call TriggerRegisterLeaveRegion(t,r, null)
call TriggerAddAction(t, function dis)
//this is not necessary but I'll do it anyway:
set t=null
set r=null
set rc=null
endfunction
endlibrary
//TESH.scrollpos=81
//TESH.alwaysfold=0
library Missile /* v2.0.2
*************************************************************************************
*
* 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.
*
* Do the setup at:
*
* 1.) Import instruction
* 2.) Global configuration
* 3.) Function configuration
*
* Credits to Dirac, emjlr3, AceHart, Bribe, Nestharus, Maghteridon96, Vexorian and Zwiebelchen.
*
*************************************************************************************
*
* */ requires /*
*
* - Missile requires nothing
*
*************************************************************************************
*
* Optional requirements listed can reduce overall code generation,
* add safety mechanisms, decrease overhead and optimize handle management.
* For a better overview I put them into blocks.
*
* I recommend to use at least one per block in your map.
*
* a). For best debug results: ( Useful )
* */ optional ErrorMessage /* github.com/nestharus/JASS/tree/master/jass/Systems/ErrorMessage
*
* b). Fatal error protection ( Case: unit out moves of world bounds )
* - WorldBounds is safer than BoundSentinel.
* - WorldBounds adds more overhead than BoundSentinel.
* */ optional WorldBounds /* githubusercontent.com/nestharus/JASS/master/jass/Systems/WorldBounds/script.j
* */ optional BoundSentinel /* wc3c.net/showthread.php?t=102576
*
* c). Handle recycling ( Speed gain, memory management )
* - uses MissileRecylcer > Dummy > xedummy.
* */ optional MissileRecycler /* hiveworkshop.com/forums/jass-resources-412/system-missilerecycler-206086/
* */ optional Dummy /* github.com/nestharus/JASS/blob/master/jass/Systems/Dummy/Dummy.w3x
* */ optional xedummy /* wc3c.net/showthread.php?t=101150
*
* d). Misc ( Absolutely not needed, just listed to avoid an onIndex event )
* */ optional UnitIndexer /* github.com/nestharus/JASS/tree/master/jass/Systems/Unit%20Indexer
*
************************************************************************************
*
* 1. Import instruction
* —————————————————————
* • Copy Missile into to your map.
* • You need a dummy unit, using Vexorians "dummy.mdx".
* This unit must use the locust and crow form ability. ( Aloc & Amrf )
* ————
* 2. Global configuration
* ———————————————————————
* Seven constants to setup!
*/
globals
/**
* Missiles are moved periodically. 1./32. is recommended.
* • Too short timeout values may cause performance issues.
* • Too large timeout values may look fishy.
*/
public constant real TIMER_TIMEOUT = 1./32.
/**
* Owner of all Missile dummies. Should be a neutral player in your map.
*/
public constant player NEUTRAL_PASSIVE = Player(15)
/**
* Raw code of the dummy unit. Object Editor ( F6 )
* • Must be correct, otherwise missiles can neither be recycled nor destroyed.
* • Units of other type ids will not be thrown into the recycler bin.
*/
public constant integer DUMMY_UNIT_ID = 'h000'
/**
* The maximum collision size used in your map. If unsure use 197. ( Town hall collision )
* • Applies for all types of widgets.
* • A precise value can improve Missile's performance,
* since smaller values enumerate less widgtes per loop per missile.
*/
public constant real MAXIMUM_COLLISION_SIZE = 197.
/**
* Collision types for missiles. ( Documentation only )
* Missile decides internally each loop which type of collision is used.
* • Uses circular collision dectection for speed < collision. ( Good accuracy, best performance )
* • Uses rectangle collision for speed >= collision. ( Best accuracy, normal performance )
*/
public constant integer COLLISION_TYPE_CIRCLE = 0
public constant integer COLLISION_TYPE_RECTANGLE = 1
/**
* Determine when rectangle collision is used to detect nearby widgets.
* • The smaller the factor the earlier rectangle collision is used. ( by default 1. )
* • Formula: speed >= collision*Missile_COLLISION_ACCURACY_FACTOR
*/
public constant real COLLISION_ACCURACY_FACTOR = 1.
// Optional toogles:
// —————————————————
// Set booleans listed below to "true" and the compiler will write
// the feature into your map. Otherwise this code is completly ignored.
// • Yay, I want that --> "true"
// • Naah that's useless for me --> "false"
/**
* USE_COLLISION_Z_FILTER enables z axis checks for widget collision. ( Adds minimal overhead )
* Use it when you need:
* • Missiles flying over or under widgets.
* • Determine between flying and walking units.
*/
public constant boolean USE_COLLISION_Z_FILTER = true
/**
* WRITE_DELAYED_MISSILE_RECYCLING enables a delayed dummy recycling system. ( Very recommended )
* Use it if:
* • You use a dummy recycling library like MissileRecycler, Dummy or xedummy.
* • You want to properly display death animations of effects added to missiles.
*/
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING = true
/**
* DELAYED_MISSILE_DEATH_ANIMATION_TIME is the delay in seconds
* Missile holds back a dummy, before recycling it.
* • The time does not have to be precise.
* • Requires WRITE_DELAYED_MISSILE_RECYCLING = true
*/
private constant real DELAYED_MISSILE_DEATH_ANIMATION_TIME = 2.
/**
* USE_DESTRUCTABLE_FILTER and USE_ITEM_FILTER are redundant constants from previous Missile versions.
* They do nothing, but remain for backwards compatibilty.
* From Missile version 1.5 on any widget collision is always enabled.
*/
public constant boolean USE_DESTRUCTABLE_FILTER = true
public constant boolean USE_ITEM_FILTER = true
endglobals
/*
* 3. Function configuration
* —————————————————————————
* Four functions to setup!
*/
/**
* GetUnitBodySize(unit) returns a fictional value for z - axis collision.
* You have two options:
* • One constant value shared over all units.
* • Dynamic values based on handle id, type id, unit user data, scaling or other parameters.
*/
function GetUnitBodySize takes unit whichUnit returns real
return 100.// Other example: return LoadReal(hash, GetHandleId(whichUnit), KEY_UNIT_BODY_SIZE)
endfunction
/**
* Same as GetUnitBodySize, but for destructables.
* Using occluder height is an idea of mine. Of course you can use your own values.
*/
function GetDestructableHeight takes destructable d returns real
return GetDestructableOccluderHeight(d)// Other example: return 100.
endfunction
/**
* Same as GetUnitBodySize, but for items.
* Again it is up to you to figure out a fictional item height.
*/
function GetItemHeight takes item i returns real
return 20.
endfunction
/**
* Unit indexers and missiles ( Only if you don't use a dummy recycling library )
* ——————————————————————————
* It is most likely intended that projectiles don't run through a unit indexing snippet.
* ToogleUnitIndexer runs:
* • Directly before a dummy is created.
* • Directly after dummy unit creation.
*
* Please return the previous setup of your indexing tool ( enabled, disabled ),
* so Missile can properly reset it to the original state.
*/
private function ToogleUnitIndexer takes boolean enable returns boolean
local boolean prev = true//UnitIndexer.enabled
// set UnitIndexer.enabled = enable
return prev
endfunction
/**
* 4. API
* ——————
*/
//! novjass ( Disables the compiler until the next endnovjass )
// Custom type Missile for your projectile needs.
struct Missile extends array
// Constants:
// ==========
//
readonly static constant string ORIGIN = "origin"
// • Attach point name for fxs on dummies.
readonly static constant real HIT_BOX = (2./3.)
// • Fictional hit box for homing missiles.
// while 0 would be the toe and 1 the head of a unit.
// Available creators:
// ===================
//
static method createEx takes unit missileDummy, real impactX, real impactY, real impactZ returns Missile
// • Core creator method.
// • May launches any unit.
// • Units of type Missile_DUMMY_UNIT_ID get recycled in the end.
static method create takes real x, real y, real z, real angleInRadians, real distanceToTravel, real endZ returns Missile
// • Converts arguments to fit into createEx, then calls createEx.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns Missile
// • Converts arguments to fit into createEx, then calls createEx.
// Available destructors:
// ======================
//
return true
// • Core destructor.
// • Returning true in any of the interface methods of the MissileStruct module
// will destroy that instance instantly.
method destroy takes nothing returns nothing
// • Destroys the missile during the next timer callback.
method terminate takes nothing returns nothing
// • Destroys the missile instantly.
// Fields you can set and read directly:
// =====================================
//
unit source
unit target // For homing missiles.
real distance // Distance traveled.
player owner // Pseudo owner for faster onCollide evaluation. The proper dummy owner remains PLAYER_NEUTRAL_PASSIVE.
real speed // Vector lenght for missile movement in plane x / y. ( DOES NOT TAKE THE TIMER TIMEOUT IN ACCOUNT )
real acceleration
real damage
real turn // Set a turn rate for missiles.
integer data // For data transfer set and read data.
boolean recycle // Is automatically set to true, when a Missile reaches it's destination.
boolean wantDestroy // Set wantDestroy to true, to destroy a missile during the next timer callback.
// Neither collision nor collisionZ accept values below zero.
real collision // Collision size in plane x / y.
real collisionZ // Collision size in z - axis.
// Fields you can only read:
// =========================
//
readonly boolean allocated
readonly unit dummy// The dummy unit of this missile.
// Position members for you needs.
readonly MissilePosition origin// Grants access to readonly members of MissilePosition,
readonly MissilePosition impact// which are "x", "y", "z", "angle", "distance", "slope" and the pitch angle "alpha".
// Furthermore method origin.move(x, y, z) and impact.move(x, y, z).
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly real angle// Current angle in radians.
// Method operators for set and read:
// ==================================
//
method operator model= takes string modelFile returns nothing
method operator model takes nothing returns string
// • Adds an effect handle on a missile dummy to it's "origin".
// • You can read the file path.
// • For multiple effects access "this.dummy" in your struct.
method operator scale= takes real value returns nothing
method operator scale takes nothing returns real
// • Set and read the scaling of the dummy unit.
method operator curve= takes real value returns nothing
method operator curve takes nothing returns real
// • Enables curved movement for your missile. ( Pass in radians, NOT degrees )
// • Do not pass in PI/2.
method operator arc= takes real value returns nothing
method operator arc takes nothing returns real
// • Enables arcing movement for your missile. ( Pass in radians, NOT degrees )
// • Do not pass in PI/2.
// Methods for missile movement:
// =============================
//
method bounce takes nothing returns nothing
// • Moves the MissilePosition "origin" to the current missile coordinates.
// • Resets the distance traveled to 0.
method deflect takes real tx, real ty returns nothing
// • Deflect the missile towards tx, ty. Then calls bounce.
method deflectEx takes real tx, real ty, real tz returns nothing
// • Deflect the missile towards tx, ty, tz. Then calls bounce.
method flightTime2Speed takes real duration returns nothing
// • Converts a fly time to a vector lenght for member "speed".
// • Does not take acceleration into account. ( Disclaimer )
method setMovementSpeed takes real value returns nothing
// • Converts Warcraft III movement speed to a vector lenght for member "speed".
// Methods for missile collision: ( all are hashtable entries )
// ==============================
// By default a widget can only be hit once per missile.
//
method hitWidget takes widget w returns nothing
// • Saves a widget in the memory as hit by this instance.
method hasHitWidget takes widget w returns boolean
// • Returns true, if "w" has been hit by this instance.
method removeHitWidget takes widget w returns nothing
// • Removes a widget from this missile's memory for widget collision. ( Can hit "w" again )
method flushHitWidgets takes nothing returns nothing
// • Flushes a missile's memory for widget collision. ( All widgets can be hit again )
method enableHitAfter takes widget w, real seconds returns nothing
// • Automatically calls removeHitWidget(w) after "seconds" time. ( Can hit "w" again after given time )
// Module MissileStruct:
// =====================
//
module MissileStruct
// • Enables the entire missile interface for that struct.
// Interface in structs: ( Must implement module MissileStruct )
// =====================
//
// • Write one, many or all of the static method below into your struct.
// • Missiles launched in this struct will run those methods, when their events fire.
//
// • All of those static methods must return a boolean.
// a) return true --> destroys the missile instance instantly.
// b) return false --> keep on flying.
// Available static method:
// ========================
//
static method onCollide takes Missile missile, unit hit returns boolean
// • Runs for units in collision range of a missile.
static method onDestructable takes Missile missile, destructable hit returns boolean
// • Runs for destructables in collision range of a missile.
// • Runs after onDestructableFilter ( if a filter is declared )
static method onItem takes Missile missile, item hit returns boolean
// • Runs for items in collision range of a missile.
// • Runs after onItemFilter ( if a filter is declared )
static method onDestructableFilter takes nothing returns boolean
// • Runs before onDestructable as filter function.
// • Runs for destructables in collision range + Missile_MAX_COLLISION_RANGE.
// • Get the filter destructable via "GetFilterDestructable()"
// • Designed only for improving code read-ability.
static method onDestructableFilter takes nothing returns boolean
// • Runs before onItem as filter function.
// • Runs for items in collision range + Missile_MAX_COLLISION_RANGE.
// • Get the filter item via "GetFilterItem()"
// • Designed only for improving code read-ability.
static method onTerrain takes Missile missile returns boolean
// • Runs when a missile collides with the terrain. ( Ground and cliffs )
static method onFinish takes Missile missile returns boolean
// • Runs only when a missile reaches it's destination.
// • However does not run, if a Missile is destroyed in another method.
static method onPeriod takes Missile missile returns boolean
// • Runs every Missile_TIMER_TIMEOUT seconds.
static method onRemove takes Missile missile returns boolean
// • Runs when a missile is destroyed.
// • Unlike onFinish, onRemove will runs ALWAYS when a missile is destroyed!!!
//
// For onRemove the returned boolean has a unique meaning:
// • Return true will recycle this missile delayed. ( Only if WRITE_DELAYED_MISSILE_RECYCLING = true )
// • Return false will recycle this missile right away.
static method launch takes Missile toLaunch returns nothing
// • Well ... Launches this Missile.
// • Missile "toLaunch" will behave as declared in the struct. ( static methods from above )
// Misc: ( From the global setup )
// =====
//
// Constants:
// ==========
//
public constant real TIMER_TIMEOUT
public constant player NEUTRAL_PASSIVE
public constant integer DUMMY_UNIT_ID
public constant real MAXIMUM_COLLISION_SIZE
public constant boolean USE_COLLISION_Z_FILTER
public constant boolean WRITE_DELAYED_MISSILE_RECYCLING
public constant boolean USE_DESTRUCTABLE_FILTER
public constant boolean USE_ITEM_FILTER
// Functions:
// ==========
//
public function GetLocZ takes real x, real y returns real
function GetUnitBodySize takes unit whichUnit returns real
function GetDestructableHeight takes destructable d returns real
function GetItemHeight takes item i returns real
//========================================================================
// Missile system. Make changes carefully.
//========================================================================
//! endnovjass ( Enables the compiler )
// Hello and welcome to Missile.
// I wrote a guideline for every piece of code inside Missile, so you
// can easily understand how the it gets compiled and evaluated.
//
// Let's go!
globals
// Core constant handle variables of Missile.
private constant trigger CORE = CreateTrigger()
private constant timer TMR = CreateTimer()
private constant location LOC = Location(0., 0.)
private constant rect RECT = Rect(0., 0., 0., 0.)
private constant group GROUP = CreateGroup()
private constant hashtable HASH = InitHashtable()
// For starting and stopping the timer.
private integer active = 0
// Arrays for data structure.
private integer array instances
private Missile array missileStack
private boolexpr array expression
private triggercondition array condition
private integer array remove
// Arrays for widgets filter functions in structs.
private boolexpr array destFilter
private boolexpr array itemFilter
// Rectangle filter functions in Missile.
private boolexpr unitFilter
endglobals
public function GetLocZ takes real x, real y returns real
call MoveLocation(LOC, x, y)
return GetLocationZ(LOC)
endfunction
// For WRITE_DELAYED_MISSILE_RECYCLING = true Missile will hold back dummy for DELAYED_MISSILE_DEATH_ANIMATION_TIME,
// until the are recylced. ( Placed in a static if )
//
//! runtextmacro optional WRITE_MISSILE_RECYCLE_BIN("WRITE_DELAYED_MISSILE_RECYCLING", "DELAYED_MISSILE_DEATH_ANIMATION_TIME")
// The code of this macro boxes a missiles position and does the required trigonometry.
//
//! runtextmacro WRITE_MISSILE_POSITION_CODE()
// Missiles structure works like a linked list with the folling methods:
// allocateCollection(), allocateNode(), insertEnd(node) and remove()
//
private keyword MissileStructure
struct Missile extends array
implement MissileStructure
// Constants:
// ==========
//
// Attach point name for effects created by model=.
readonly static constant string ORIGIN = "origin"
// Set a ficitional 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.)
// DEBUG_MODE only members:
// ========================
//
// Checks for double launching. Throws an error message.
debug boolean launched
// Private members:
// ================
//
// The position of a missile using curve= does not
// match the position used by Missile's trigonometry.
// Therefore we need this member two times.
// Readable x / y / z for your needs and xPrivate / yPrivate for cool mathematics.
private real xPrivate
private real yPrivate
private real xPrev
private real yPrev
// Readonly members:
// =================
//
// Prevents a double free case.
readonly boolean allocated
// The dummy unit.
readonly unit dummy
// Position members for your needs.
readonly MissilePosition origin// Grants access to readonly members of MissilePosition,
readonly MissilePosition impact// which are "x", "y", "z", "angle", "distance", "slope" and "alpha".
readonly real terrainZ
readonly real x
readonly real y
readonly real z
readonly real angle// Current angle
// Collision detection type. ( Evaluated new in each loop )
readonly integer collisionType// Current collision type ( circular or rectangle )
// Members you can set:
// ====================
//
unit source
unit target // For homing missiles.
real distance // Distance traveled.
player owner // Pseudo owner for faster onCollide evaluation. The proper dummy owner is PLAYER_NEUTRAL_PASSIVE.
real speed // Vector lenght for missile movement in plane x / y.
real acceleration
real damage
integer data // For data transfer set and read data.
boolean recycle // Is set to true, when a Missile reaches it's destination.
real turn // Sets a turn rate for a missile.
// Members which are monitored: ( DEBUG_MODE )
// ============================
//
static if not DEBUG_MODE then
real collision // Collision size in plane x / y.
real collisionZ // Collision size in z - axis.
else
// Check for collision < 0.
debug private real collisionDebug
method operator collision= takes real value returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((value < 0), "Missile", "collision=", "collision", this, "Found collision below zero [" + R2S(value) + "]!")
endif
debug set collisionDebug = value
endmethod
method operator collision takes nothing returns real
debug return collisionDebug
endmethod
// Check for collisionZ < 0.
debug private real collisionZDebug
method operator collisionZ= takes real value returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError((value < 0), "Missile", "collisionZ=", "collisionZ", this, "found collisionZ below zero [" + R2S(value) + "]!")
endif
debug set collisionZDebug = value
endmethod
method operator collisionZ takes nothing returns real
debug return collisionZDebug
endmethod
endif
// Operator overloading:
// =====================
//
// Effect handle on a missile dummy. For multiple effect attaching, access "dummy" in your struct.
private effect sfx
private string path
method operator model= takes string modelFile returns nothing
if (sfx != null) then
call DestroyEffect(sfx)
endif
//
set path = modelFile
set sfx = AddSpecialEffectTarget(modelFile, dummy, ORIGIN)
endmethod
method operator model takes nothing returns string
return path
endmethod
real open
// Enables curved movement for your missile. PI/2 should be avoided.
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 as return value.
endif
return Atan(open/origin.distance)
endmethod
real height
// Enables arcing movement for your missile. PI/2 should be avoided.
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 as return value.
endif
return Atan(4.*height/origin.distance)
endmethod
private real scaleValue
method operator scale= takes real value returns nothing
call SetUnitScale(dummy, value, 1., 1.)
set scaleValue = value
endmethod
method operator scale takes nothing returns real
return scaleValue
endmethod
// Methods:
// ========
//
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
// Please do not pass in 0.
method flightTime2Speed takes real duration returns nothing
set speed = RMaxBJ(0.00000001, (origin.distance - distance)*Missile_TIMER_TIMEOUT/duration)
endmethod
method setMovementSpeed takes real value returns nothing
set speed = value*Missile_TIMER_TIMEOUT
endmethod
boolean wantDestroy// For "true" a missile is destroyed on the next timer callback. 100% safe.
method destroy takes nothing returns nothing
set wantDestroy = true
endmethod
// Instantly destroys a missile.
method terminate takes nothing returns nothing
set allocated = false
call remove()// Throws an error for invalid nodes.
call impact.destroy()
call origin.destroy()
//
call DestroyEffect(sfx)
call FlushChildHashtable(HASH, this)
if (GetUnitTypeId(dummy) == Missile_DUMMY_UNIT_ID) then
// MissileRecycler > Dummy > xe.
static if LIBRARY_MissileRecycler then
call RecycleMissile(dummy)
elseif LIBRARY_Dummy and Dummy.create.exists then
call Dummy[dummy].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(dummy)
else
call RemoveUnit(dummy)
endif
endif
//
set sfx = null
set source = null
set target = null
set dummy = null
set owner = null
endmethod
// Runs in createEx.
//! textmacro MISSILE_RESET_ALL_MEMBERS
set path = null
set speed = 0.
set acceleration = 0.
set distance = 0.
set height = 0.
set turn = 0.
set open = 0.
set collision = 0.
set collisionZ = 0.
set collisionType = 0
set scaleValue = 1.
set wantDestroy = false
set recycle = false
//! endtextmacro
// Launches a dummy of your choice.
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 allocated = true
//
//! runtextmacro MISSILE_RESET_ALL_MEMBERS()
//
set origin = MissilePosition.create(originX, originY, originZ)
set impact = MissilePosition.create(impactX, impactY, impactZ)
call MissilePosition.link(origin, impact)
set xPrivate = originX
set yPrivate = originY
set x = originX
set y = originY
set z = originZ
set angle = origin.angle
set dummy = missileDummy
set stackSize = 0
call SetUnitFlyHeight(missileDummy, originZ, 0.)
//
static if LIBRARY_ErrorMessage then
debug call ThrowError((GetUnitTypeId(missileDummy) == 0), "Missile", "createEx", "missileDummy", this, "Invalid missile dummy unit (null)!")
endif
debug set launched = false
return this
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 z, real face returns unit
local boolean prev = ToogleUnitIndexer(false)
set bj_lastCreatedUnit = CreateUnit(Missile_NEUTRAL_PASSIVE, Missile_DUMMY_UNIT_ID , x, y, face)
call ToogleUnitIndexer(prev)
call SetUnitX(bj_lastCreatedUnit, x)
call SetUnitY(bj_lastCreatedUnit, y)
call UnitAddAbility(bj_lastCreatedUnit, 'Amrf')
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0.)
call PauseUnit(bj_lastCreatedUnit, true)
return bj_lastCreatedUnit
endmethod
endif
// MissileRecylcer > Dummy > xe > Missile.
//! textmacro MISSILE_GET_DUMMY_FROM_LIBRARY
static if LIBRARY_MissileRecycler then
return createEx(GetRecycledMissile(x, y, z, angle*bj_RADTODEG), impactX, impactY, impactZ)
elseif LIBRARY_Dummy and Dummy.create.exists then
local Dummy dummy = Dummy.create(x, y, angle*bj_RADTODEG)
call SetUnitFlyHeight(dummy.unit, z, 0.)
return createEx(dummy.unit, impactX, impactY, impactZ)
elseif LIBRARY_xedummy and xedummy.new.exists then
set bj_lastCreatedUnit = xedummy.new(Missile_NEUTRAL_PASSIVE, x, y, angle*bj_RADTODEG)
call SetUnitFlyHeight(bj_lastCreatedUnit, z, 0.)
return createEx(bj_lastCreatedUnit, impactX, impactY, impactZ)
else
return createEx(Missile.newMissileUnit(x, y, z, angle*bj_RADTODEG), impactX, impactY, impactZ)
endif
//! endtextmacro
// Wrapper to createEx.
static method create takes real x, real y, real z, real angle, real distance, real impactZ returns thistype
local real impactX = x + distance*Cos(angle)
local real impactY = y + distance*Sin(angle)
// Get Dummy.
//! runtextmacro MISSILE_GET_DUMMY_FROM_LIBRARY()
endmethod
// Wrapper to createEx.
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns thistype
local real angle = Atan2(impactY - y, impactX - x)
// Get Dummy.
//! runtextmacro MISSILE_GET_DUMMY_FROM_LIBRARY()
endmethod
// Called from each active struct every Missile_TIMER_TIMEOUT seconds.
// In this method performance matters.
// • Locals used for faster multiple lookups.
// • Short variable names.
method move takes nothing returns nothing
local MissilePosition loc
local real a
local real d
local real s
local real v
local unit u
local real h
local real tx
local real ty
local real dex
loop
exitwhen (0 == this)
set loc = origin
set h = height
// Missile target.
set u = 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)
//
// Uses a fictional hit box of GetUnitBodySize()*HIT_BOX for the z offset.
call origin.move(x, y, z)
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 a = loc.angle
set target = null
endif
else
set a = loc.angle
endif
set u = dummy
// 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
// Update position members.
set v = speed
set d = loc.distance
//call BJDebugMsg(R2S(d))
if (distance + v > d) then
set s = d + 0.000001
else
set s = distance + v
endif
set distance = s
//
set tx = xPrivate + v*Cos(angle)
set ty = yPrivate + v*Sin(angle)
set speed = v + acceleration
set xPrivate = tx
set yPrivate = ty
// Detect the collision type.
if (v < collision*Missile_COLLISION_ACCURACY_FACTOR) then
set collisionType = Missile_COLLISION_TYPE_CIRCLE
else
set collisionType = Missile_COLLISION_TYPE_RECTANGLE
endif
// 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 arc.
call MoveLocation(LOC, tx, ty)
set terrainZ = GetLocationZ(LOC)
if (h != 0) or (loc.slope != 0) then
set z = loc.z - terrainZ + loc.slope*s
set dex = loc.alpha
if (h != 0) then
set z = z + (4*h*s*(d - s)/(d*d))
set dex = dex - Atan(((4*h)*(2*s - d))/(d*d))*bj_RADTODEG
endif
if (GetUnitTypeId(u) == Missile_DUMMY_UNIT_ID) then
call SetUnitAnimationByIndex(u, R2I(dex + 90.5))
endif
else
set z = loc.z - terrainZ
endif
// Missile position.
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
call SetUnitFlyHeight(u, z, 0.)
// Update missile x / y ( readonly )
set xPrev = x
set yPrev = y
set x = tx
set y = ty
// Check for destination reached.
if (s >= d) then
set recycle = true
endif
set this = next
endloop
set u = null
endmethod
// Widget collision API:
// =====================
//
// Runs automatically on widget collision.
method hitWidget takes widget w returns nothing
call SaveWidgetHandle(HASH, this, GetHandleId(w), w)
endmethod
// All widget which have been hit return true.
method hasHitWidget takes widget w returns boolean
return HaveSavedHandle(HASH, this, GetHandleId(w))
endmethod
// Removes a widget from the missile's memory of hit widgets. ( This widget can be hit again )
method removeHitWidget takes widget w returns nothing
call RemoveSavedHandle(HASH, this, GetHandleId(w))
endmethod
// Flushes a missile's memory for collision. ( All widgets can be hit again )
method flushHitWidgets takes nothing returns nothing
call FlushChildHashtable(HASH, this)
endmethod
// Tells missile to call removeHitWidget(w) after "seconds" time.
// Does not apply to widgets, which are already hit by this missile.
readonly integer stackSize
method enableHitAfter takes widget w, real seconds returns nothing
local integer id = GetHandleId(w)
local integer dex
if (id == 0) then
return
endif
if HaveSavedInteger(HASH, this, id) then
set dex = LoadInteger(HASH, this, id)
else
set dex = stackSize
set stackSize = stackSize + 1
call SaveInteger(HASH, this, id, dex)
call SaveInteger(HASH, this, dex, id)
endif
call SaveReal(HASH, this, id, seconds)// Set time.
endmethod
method updateStack takes nothing returns nothing
local integer dex = 0
local integer id
local real time
loop
exitwhen (dex == stackSize)
set id = LoadInteger(HASH, this, dex)
set time = LoadReal(HASH, this, id) - Missile_TIMER_TIMEOUT
if (time <= 0.) or not HaveSavedHandle(HASH, this, id) then
set stackSize = stackSize - 1
set id = LoadInteger(HASH, this, stackSize)
call SaveInteger(HASH, this, dex, id)
call SaveInteger(HASH, this, id, dex)
// Enables hit.
call RemoveSavedHandle(HASH, this, id)
// Remove data from stack.
call RemoveSavedReal(HASH, this, id)
call RemoveSavedInteger(HASH, this, id)
call RemoveSavedInteger(HASH, this, stackSize)
set dex = dex - 1
else
call SaveReal(HASH, this, id, time)
endif
set dex = dex + 1
endloop
endmethod
// Widget collision code:
// ======================
//
// Transfer data to action functions.
private static Missile temp = 0
private static boolean circle = true
//
// 1.) Rectangle collision for fast moving missiles with small collision radius.
//
// Runs for destructables and items in a rectangle.
// Checks if widget w is in collision range of a missile.
// Is not precise in z - axis collision.
private method checkWidgetRectangle takes widget w, real height returns boolean
local real wx = GetWidgetX(w)
local real wy = GetWidgetY(w)
local real wz = Missile_GetLocZ(wx, wy) - terrainZ
local real dx = x - xPrev
local real dy = y - yPrev
local real s = (dx*(wx - xPrev) + dy*(wy - yPrev))/(dx*dx + dy*dy)
static if Missile_USE_COLLISION_Z_FILTER then
local real cz = collisionZ
else
local real cz = collision
endif //
if (s < 0) then
set s = 0.
elseif (s > 1) then
set s = 1.
endif
set dx = (xPrev + s*dx) - wx
set dy = (yPrev + s*dy) - wy
set s = dx*dx + dy*dy
//call BJDebugMsg(R2S(s))
if (s < bj_enumDestructableRadius) and (wz + height >= z - cz) and (wz <= z + cz) then
set bj_enumDestructableRadius = s
return true
endif
return false
endmethod
//
// 2.) Circular collision detection for all other missiles.
//
// Runs for destructables and items in a circle.
// Z - axis collision is precise.
private method checkWidgetCircle takes widget w, real height returns boolean
local real wx = GetWidgetX(w)
local real wy = GetWidgetY(w)
local real wz = Missile_GetLocZ(wx, wy) - terrainZ
local real d = (x - wx)*(x - wx) + (y - wy)*(y - wy)
static if Missile_USE_COLLISION_Z_FILTER then
local real cz = collisionZ
else
local real cz = collision
endif //
if (d < bj_enumDestructableRadius) and (wz + height >= z - cz) and (wz <= z + cz) then
set bj_enumDestructableRadius = d
return true
endif
return false
endmethod
//
// 3.) Action functions inside the widget enumeration thread.
//
// Runs for every enumerated destructable.
// • Searches the closest available destructables
// • Distance formula based on the Pythagorean theorem.
//
private static method enumDests takes nothing returns nothing
local destructable d = GetEnumDestructable()
if HaveSavedHandle(HASH, temp, GetHandleId(d)) then
set d = null
return
endif
if (circle) then
if temp.checkWidgetCircle(d, GetDestructableHeight(d)) then
set bj_destRandomCurrentPick = d
endif
// Runs rectangle collision check.
elseif temp.checkWidgetRectangle(d, GetDestructableHeight(d)) then
set bj_destRandomCurrentPick = d
endif
set d = null
endmethod
//
// Runs for every enumerated item.
// • Searches the closest available item.
// • Distance formula based on the Pythagorean theorem.
//
private static method enumItems takes nothing returns nothing
local item i = GetEnumItem()
if HaveSavedHandle(HASH, temp, GetHandleId(i)) then
set i = null
return
endif
if (circle) then
if temp.checkWidgetCircle(i, GetItemHeight(i)) then
set bj_itemRandomCurrentPick = i
endif
// Runs rectangle collision check.
elseif temp.checkWidgetRectangle(i, GetItemHeight(i)) then
set bj_itemRandomCurrentPick = i
endif
set i = null
endmethod
//
// 4.) Filter function for rectangle unit collision.
//
// Runs for every enumerated units.
// • Filters out units which are not in collision range in plane x / y.
//
private static method filterUnits takes nothing returns boolean
local thistype this = thistype.temp
//
local unit u = GetFilterUnit()
local real dx
local real dy
local real s
local boolean is
// Missile knows this unit already.
if HaveSavedBoolean(HASH, this, GetHandleId(u)) then
set u = null
return false
endif
//
set dx = x - xPrev
set dy = y - yPrev
set s = (dx*(GetUnitX(u) - xPrev) + dy*(GetUnitY(u)- yPrev))/(dx*dx + dy*dy)
if(s < 0) then
set s = 0.
elseif(s > 1) then
set s = 1.
endif
set is = IsUnitInRangeXY(u, xPrev + s*dx, yPrev + s*dy, collision)
set u = null
return is
endmethod
//
// 5.) Proper rect preparation.
//
// For rectangle.
private method prepareRectRectangle takes nothing returns nothing
local real x1 = xPrev
local real y1 = yPrev
local real x2 = x
local real y2 = y
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
// What is min, what is max ...
if(x1 < x2) then
if( y1 < y2) then
call SetRect(RECT, x1 - d, y1 - d, x2 + d, y2 + d)
else
call SetRect(RECT, x1 - d, y2 - d, x2 + d, y1 + d)
endif
else
if( y1 < y2) then
call SetRect(RECT, x2 - d, y1 - d, x1 + d, y2 + d)
else
call SetRect(RECT, x2 - d, y2 - d, x1 + d, y1 + d)
endif
endif
endmethod
//
// For circular.
private method prepareRectCircle takes nothing returns nothing
local real d = collision + Missile_MAXIMUM_COLLISION_SIZE
call SetRect(RECT, x - d, y - d, x + d, y + d)
endmethod
//
// 5.) API for the MissileStruct iteration.
//
method groupEnumUnitsRectangle takes nothing returns nothing
call prepareRectRectangle()
set thistype.temp = this
call GroupEnumUnitsInRect(GROUP, RECT, unitFilter)
endmethod
//
// Prepares destructable enumeration, then runs enumDests.
method checkDestCollision takes boolexpr expr returns nothing
set circle = (collisionType == Missile_COLLISION_TYPE_CIRCLE)
if (circle) then
call prepareRectCircle()
else
call prepareRectRectangle()
endif
//
set thistype.temp = this
set bj_destRandomCurrentPick = null
set bj_enumDestructableRadius = collision*collision + 1.
call EnumDestructablesInRect(RECT, expr, function thistype.enumDests)
endmethod
//
// Prepares item enumeration, then runs enumItems.
method checkItemCollision takes boolexpr expr returns nothing
set circle = (collisionType == Missile_COLLISION_TYPE_CIRCLE)
if (circle) then
call prepareRectCircle()
else
call prepareRectRectangle()
endif
//
set thistype.temp = this
set bj_itemRandomCurrentPick = null
set bj_enumDestructableRadius = collision*collision + 1.
call EnumItemsInRect(RECT, expr, function thistype.enumItems)
endmethod
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.
private static method onInit takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug local boolean prev = ToogleUnitIndexer(false)
debug local unit dummy = CreateUnit(Missile_NEUTRAL_PASSIVE, Missile_DUMMY_UNIT_ID, 0, 0, 0)
debug call ToogleUnitIndexer(prev)
//
debug call ThrowError((GetUnitTypeId(dummy) != Missile_DUMMY_UNIT_ID), "Missile", "DEBUG_MISSILE", "typeid", 0, "Global setup for public integer DUMMY_UNIT_ID is incorrect! This map currently can't use Missile!")
debug call ThrowError((Missile_MAXIMUM_COLLISION_SIZE < 0), "Missile", "DEBUG_MISSILE", "collision", 0, "Global setup for public real MAXIMUM_COLLISION_SIZE is incorrect, below zero! This map currently can't use Missile!")
debug call RemoveUnit(dummy)
debug set dummy = null
endif
//
set unitFilter = Filter(function thistype.filterUnits)
endmethod
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 ( full 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 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 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 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 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 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 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 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
// Boolean expressions per struct:
// ===============================
//
// Filter function for item enumeration. ( Runs only if a filter is declared in the struct )
private function MissileCreateItemFilter takes integer structId, code func returns nothing
set itemFilter[structId] = Filter(func)
endfunction
// Filter function for destructable enumeration. ( Runs only if a filter is declared in the struct )
private function MissileCreateDestructableFilter takes integer structId, code func returns nothing
set destFilter[structId] = Filter(func)
endfunction
// Condition function for the core trigger evaluation. ( Runs for all struct using module MissileStruct )
private function MissileCreateExpression takes integer structId, code func returns nothing
set expression[structId] = Condition(func)
endfunction
// Creates a collection for a struct. ( Runs for all struct using module MissileStruct )
private function MissileCreateCollection takes integer structId returns nothing
set missileStack[structId] = Missile.allocateCollection()
endfunction
// Core:
// =====
//
// Fires every Missile_TIMER_TIMEOUT.
private function Fire takes nothing returns nothing
local integer i = remove[0]
set remove[0] = 0
loop
exitwhen i == 0
if (instances[i] == 0) and (condition[i] != null) then
call TriggerRemoveCondition(CORE, condition[i])
set condition[i] = null
set active = active - 1
endif
set i = remove[i]
endloop
if (active == 0) then
call PauseTimer(TMR)
else
call TriggerEvaluate(CORE)
endif
endfunction
// Conditionally starts the timer.
private function StartPeriodic takes integer structId returns nothing
if (instances[structId] == 0) then
if (active == 0) then
call TimerStart(TMR, Missile_TIMER_TIMEOUT, true, function Fire)
endif
if (condition[structId] == null) then
set condition[structId] = TriggerAddCondition(CORE, expression[structId])
set active = active + 1
endif
endif
set instances[structId] = instances[structId] + 1
endfunction
// Conditionally stops the timer.
private function StopPeriodic takes integer structId returns nothing
set instances[structId] = instances[structId] - 1
if (instances[structId] == 0) then
set remove[structId] = remove[0]
set remove[0] = structId
endif
endfunction
// Module:
// =======
//
module MissileStruct
// Runs check during compile time.
static if DEBUG_MODE then
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
// Called from missileIterate. "P" to avoid code collision.
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
// Runs every Missile_TIMER_TIMEOUT for this struct.
private static method missileIterateP takes nothing returns boolean
local Missile this = missileStack[thistype.typeid].first
local Missile node
local real collideZ
local boolean b
local unit u
call this.move()// Moves all missiles of this collection.
loop
exitwhen (0 == this)
set node = this.next// The linked list should not lose the next node.
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
if (this.stackSize > 0) then
call this.updateStack()
endif
// Runs unit collision.
static if thistype.onCollide.exists then
if (this.allocated) and (0 != this.collision) then
set b = (this.collisionType == Missile_COLLISION_TYPE_RECTANGLE)
if b then
call this.groupEnumUnitsRectangle()
else
call GroupEnumUnitsInRange(GROUP, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, null)
endif
loop
set u = FirstOfGroup(GROUP)
exitwhen u == null
call GroupRemoveUnit(GROUP, u)
if not HaveSavedHandle(HASH, this, GetHandleId(u)) then
if ((IsUnitInRange(u, this.dummy, this.collision)) or (b)) then
// Eventually run z collision checks.
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
// Mark as hit.
call SaveUnitHandle(HASH, this, GetHandleId(u), u)
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
endif
else
// Runs unit collision without z collision checks.
call SaveUnitHandle(HASH, this, GetHandleId(u), u)
if thistype.onCollide(this, u) then
call thistype.missileTerminateP(this)
exitwhen true
endif
endif
endif
endif
endloop
endif
endif
// Runs destructable collision.
static if thistype.onDestructable.exists then
// Check if the missile is not terminated.
if (this.allocated) and (0 != this.collision) then
call this.checkDestCollision(destFilter[thistype.typeid])
if (bj_destRandomCurrentPick != null) then
call SaveDestructableHandle(HASH, this, GetHandleId(bj_destRandomCurrentPick), bj_destRandomCurrentPick)
if thistype.onDestructable(this, bj_destRandomCurrentPick) then
call missileTerminateP(this)
endif
endif
endif
endif
// Runs item collision.
static if thistype.onItem.exists then
// Check if the missile is not terminated.
if (this.allocated) and (0 != this.collision) then
call this.checkItemCollision(itemFilter[thistype.typeid])
if (bj_itemRandomCurrentPick != null) then
call SaveItemHandle(HASH, this, GetHandleId(bj_itemRandomCurrentPick), bj_itemRandomCurrentPick)
if thistype.onItem(this, bj_itemRandomCurrentPick) then
call missileTerminateP(this)
endif
endif
endif
endif
// Runs when the destination is reached.
if (this.recycle) and (this.allocated) then
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
// Runs on terrian collision.
static if thistype.onTerrain.exists then
if (this.allocated) and (this.z < 0) and thistype.onTerrain(this) then
call missileTerminateP(this)
endif
endif
// Runs every Missile_TIMER_TIMEOUT.
static if thistype.onPeriod.exists then
if (this.allocated) and thistype.onPeriod(this) then
call missileTerminateP(this)
endif
endif
endif
set this = node
endloop
set u = null
return false
endmethod
// Places the launcher in your struct.
static method launch takes Missile missile returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(missile.launched, "thistype", "launch", "missile.launched", missile, "This missile was already launched before!")
endif
debug set missile.launched = true
//
call missileStack[thistype.typeid].insertEnd(missile)
call StartPeriodic(thistype.typeid)
endmethod
// Runs onInit. Creates the collection, trigger condition and widget filters.
private static method onInit takes nothing returns nothing
call MissileCreateCollection(thistype.typeid)
call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)
static if thistype.onDestructableFilter.exists and thistype.onDestructable.exists then
call MissileCreateDestructableFilter(thistype.typeid, function thistype.onDestructableFilter)
endif
static if thistype.onItemFilter.exists and thistype.onItem.exists then
call MissileCreateItemFilter(thistype.typeid, function thistype.onItemFilter)
endif
endmethod
endmodule
// Missile position:
// =================
//
// Simple trigonometry.
//! textmacro WRITE_MISSILE_POSITION_CODE
struct MissilePosition extends array
private static integer array recycler
private static integer alloc = 0
// Readonly members you can access.
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real slope
readonly real alpha
// Creates an origin - impact link.
private thistype ref
private static method math takes thistype a, thistype b returns boolean
// Set a.
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// For distance == 0 the thread crashes!
set a.x = a.x + .0001
return false// Runs the thread again.
endif
set a.alpha = Atan(a.slope)*bj_RADTODEG
// Set b.
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
set x = toX
set y = toY
set z = toZ + Missile_GetLocZ(toX, toY)
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
// Delayed dummy recycling:
// ========================
//
// Ensures proper fx death animations.
//! textmacro WRITE_MISSILE_RECYCLE_BIN takes DO_THIS, AFTER_TIME
static if $DO_THIS$ then
private struct RecycleBin extends array
private static constant timer t = CreateTimer()
private static integer max = 0
private static unit array dummy
private static real array time
private static method onPeriodic takes nothing returns nothing
local integer dex = 0
loop
exitwhen dex == thistype.max
set thistype.time[dex] = thistype.time[dex] - 1
if (0 >= thistype.time[dex]) then
static if LIBRARY_MissileRecycler then
call RecycleMissile(thistype.dummy[dex])
elseif Dummy.create.exists and LIBRARY_Dummy then
call Dummy[thistype.dummy[dex]].destroy()
elseif LIBRARY_xedummy and xedummy.release.exists then
call xedummy.release(thistype.dummy[dex])
else
call RemoveUnit(thistype.dummy[dex])
endif
set thistype.dummy[dex] = null
set thistype.max = thistype.max - 1
set thistype.dummy[dex] = thistype.dummy[thistype.max]
set thistype.time[dex] = thistype.time[thistype.max]
set thistype.dummy[thistype.max] = null
set dex = dex - 1
if (0 == thistype.max) then
call PauseTimer(thistype.t)
endif
endif
set dex = dex + 1
endloop
endmethod
static method recycle takes unit toRecycle returns nothing
if (0 == thistype.max) then
call TimerStart(thistype.t, 1., true, function thistype.onPeriodic)
endif
set thistype.dummy[max] = toRecycle
set thistype.time[max] = $AFTER_TIME$ + TimerGetRemaining(thistype.t)
set thistype.max = thistype.max + 1
endmethod
endstruct
endif
//! endtextmacro
// The end!
endlibrary
//TESH.scrollpos=21
//TESH.alwaysfold=0
library GroupUtils initializer Init requires optional xebasic
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This library is a combination of several features relevant to groups. First
//* and foremost, it contains a group stack that you can access dynamic groups
//* from. It also provides means to refresh groups and clear any shadow
//* references within them. The included boolexprs are there for backwards
//* compatibility with maps that happen to use them. Since the 1.24c patch,
//* null boolexprs used in GroupEnumUnits* calls no longer leak, so there is no
//* performance gain to using the BOOLEXPR_TRUE constant.
//*
//* Instead of creating/destroying groups, we have moved on to recycling them.
//* NewGroup pulls a group from the stack and ReleaseGroup adds it back. Always
//* remember to call ReleaseGroup on a group when you are done using it. If you
//* fail to do so enough times, the stack will overflow and no longer work.
//*
//* GroupRefresh cleans a group of any shadow references which may be clogging
//* its hashtable. If you remove a unit from the game who is a member of a unit
//* group, it will 'effectively' remove the unit from the group, but leave a
//* shadow in its place. Calling GroupRefresh on a group will clean up any
//* shadow references that may exist within it. It is only worth doing this on
//* groups that you plan to have around for awhile.
//*
//* Constants that can be used from the library:
//* [group] ENUM_GROUP As you might expect, this group is good for
//* when you need a group just for enumeration.
//* [boolexpr] BOOLEXPR_TRUE This is a true boolexpr, which is important
//* because a 'null' boolexpr in enumeration
//* calls results in a leak. Use this instead.
//* [boolexpr] BOOLEXPR_FALSE This exists mostly for completeness.
//*
//* This library also includes a simple implementation of a group enumeration
//* call that factors collision of units in a given area of effect. This is
//* particularly useful because GroupEnumUnitsInRange doesn't factor collision.
//*
//* In your map, you can just replace all instances of GroupEnumUnitsInRange
//* with GroupEnumUnitsInArea with identical arguments and your spells will
//* consider all units colliding with the area of effect. After calling this
//* function as you would normally call GroupEnumUnitsInRange, you are free to
//* do anything with the group that you would normally do.
//*
//* If you don't use xebasic in your map, you may edit the MAX_COLLISION_SIZE
//* variable below and the library will use that as the added radius to check.
//* If you use xebasic, however, the script will automatically use xe's
//* collision size variable.
//*
//* You are also able to use GroupUnitsInArea. This function returns all units
//* within the area, no matter what they are, which can be convenient for those
//* instances where you actually want that.
//*
//* Example usage:
//* local group MyGroup = NewGroup()
//* call GroupRefresh(MyGroup)
//* call ReleaseGroup(MyGroup)
//* call GroupEnumUnitsInArea(ENUM_GROUP, x, y, 350., BOOLEXPR_TRUE)
//* call GroupUnitsInArea(ENUM_GROUP, x, y, 350.)
//*
globals
//If you don't have xebasic in your map, this value will be used instead.
//This value corresponds to the max collision size of a unit in your map.
private constant real MAX_COLLISION_SIZE = 197.
//If you are insane and don't care about any of the protection involved in
//this library, but want this script to be really fast, set this to true.
private constant boolean LESS_SAFETY = false
endglobals
globals
//* Constants that are available to the user
group ENUM_GROUP = CreateGroup()
boolexpr BOOLEXPR_TRUE = null
boolexpr BOOLEXPR_FALSE = null
endglobals
globals
//* Hashtable for debug purposes
private hashtable ht = InitHashtable()
//* Temporary references for GroupRefresh
private boolean Flag = false
private group Refr = null
//* Arrays and counter for the group stack
private group array Groups
private integer Count = 0
//* Variables for use with the GroupUnitsInArea function
private real X = 0.
private real Y = 0.
private real R = 0.
private hashtable H = InitHashtable()
endglobals
private function HookDestroyGroup takes group g returns nothing
if g == ENUM_GROUP then
call BJDebugMsg(SCOPE_PREFIX+"Warning: ENUM_GROUP destroyed")
endif
endfunction
debug hook DestroyGroup HookDestroyGroup
private function AddEx takes nothing returns nothing
if Flag then
call GroupClear(Refr)
set Flag = false
endif
call GroupAddUnit(Refr, GetEnumUnit())
endfunction
function GroupRefresh takes group g returns nothing
set Flag = true
set Refr = g
call ForGroup(Refr, function AddEx)
if Flag then
call GroupClear(g)
endif
endfunction
function NewGroup takes nothing returns group
if Count == 0 then
set Groups[0] = CreateGroup()
else
set Count = Count - 1
endif
static if not LESS_SAFETY then
call SaveInteger(ht, 0, GetHandleId(Groups[Count]), 1)
endif
return Groups[Count]
endfunction
function ReleaseGroup takes group g returns boolean
local integer id = GetHandleId(g)
static if LESS_SAFETY then
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
else
if g == null then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Null groups cannot be released")
return false
elseif not HaveSavedInteger(ht, 0, id) then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Group not part of stack")
return false
elseif LoadInteger(ht, 0, id) == 2 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Groups cannot be multiply released")
return false
elseif Count == 8191 then
debug call BJDebugMsg(SCOPE_PREFIX+"Error: Max groups achieved, destroying group")
call DestroyGroup(g)
return false
endif
call SaveInteger(ht, 0, id, 2)
endif
call GroupClear(g)
set Groups[Count] = g
set Count = Count + 1
return true
endfunction
private function Filter takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), X, Y, R)
endfunction
private function HookDestroyBoolExpr takes boolexpr b returns nothing
local integer bid = GetHandleId(b)
if HaveSavedHandle(H, 0, bid) then
//Clear the saved boolexpr
call DestroyBoolExpr(LoadBooleanExprHandle(H, 0, bid))
call RemoveSavedHandle(H, 0, bid)
endif
endfunction
hook DestroyBoolExpr HookDestroyBoolExpr
private constant function GetRadius takes real radius returns real
static if LIBRARY_xebasic then
return radius+XE_MAX_COLLISION_SIZE
else
return radius+MAX_COLLISION_SIZE
endif
endfunction
function GroupEnumUnitsInArea takes group whichGroup, real x, real y, real radius, boolexpr filter returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
local integer bid = 0
//Set variables to new values
set X = x
set Y = y
set R = radius
if filter == null then
//Adjusts for null boolexprs passed to the function
set filter = Condition(function Filter)
else
//Check for a saved boolexpr
set bid = GetHandleId(filter)
if HaveSavedHandle(H, 0, bid) then
//Set the filter to use to the saved one
set filter = LoadBooleanExprHandle(H, 0, bid)
else
//Create a new And() boolexpr for this filter
set filter = And(Condition(function Filter), filter)
call SaveBooleanExprHandle(H, 0, bid, filter)
endif
endif
//Enumerate, if they want to use the boolexpr, this lets them
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), filter)
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
function GroupUnitsInArea takes group whichGroup, real x, real y, real radius returns nothing
local real prevX = X
local real prevY = Y
local real prevR = R
//Set variables to new values
set X = x
set Y = y
set R = radius
//Enumerate
call GroupEnumUnitsInRange(whichGroup, x, y, GetRadius(radius), Condition(function Filter))
//Give back original settings so nested enumerations work
set X = prevX
set Y = prevY
set R = prevR
endfunction
private function True takes nothing returns boolean
return true
endfunction
private function False takes nothing returns boolean
return false
endfunction
private function Init takes nothing returns nothing
set BOOLEXPR_TRUE = Condition(function True)
set BOOLEXPR_FALSE = Condition(function False)
endfunction
endlibrary
//TESH.scrollpos=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=0
//TESH.alwaysfold=0
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
// RegisterSpellEffectEvent(integer abil, code onCast)
//
// Requires
// --------
// RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
//
// Optional
// --------
// Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
//============================================================================
private module M
static if LIBRARY_Table then
static Table tb
else
static hashtable ht = InitHashtable()
endif
static method onCast takes nothing returns nothing
static if LIBRARY_Table then
call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
else
call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
endif
endmethod
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set .tb = Table.create()
endif
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
endmethod
endmodule
//============================================================================
private struct S extends array
implement M
endstruct
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
static if LIBRARY_Table then
if not S.tb.handle.has(abil) then
set S.tb.trigger[abil] = CreateTrigger()
endif
call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
else
if not HaveSavedHandle(S.ht, 0, abil) then
call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
endif
call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
endif
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/**************************************************************
*
* RegisterPlayerUnitEvent
* v5.1.0.1
* By Magtheridon96
*
* I would like to give a special thanks to Bribe, azlier
* and BBQ for improving this library. For modularity, it only
* supports player unit events.
*
* Functions passed to RegisterPlayerUnitEvent must either
* return a boolean (false) or nothing. (Which is a Pro)
*
* Warning:
* --------
*
* - Don't use TriggerSleepAction inside registered code.
* - Don't destroy a trigger unless you really know what you're doing.
*
* API:
* ----
*
* - function RegisterPlayerUnitEvent takes playerunitevent whichEvent, code whichFunction returns nothing
* - Registers code that will execute when an event fires.
* - function RegisterPlayerUnitEventForPlayer takes playerunitevent whichEvent, code whichFunction, player whichPlayer returns nothing
* - Registers code that will execute when an event fires for a certain player.
* - function GetPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* - Returns the trigger corresponding to ALL functions of a playerunitevent.
*
**************************************************************/
library RegisterPlayerUnitEvent // Special Thanks to Bribe and azlier
globals
private trigger array t
endglobals
function RegisterPlayerUnitEvent takes playerunitevent p, code c returns nothing
local integer i = GetHandleId(p)
local integer k = 15
if t[i] == null then
set t[i] = CreateTrigger()
loop
call TriggerRegisterPlayerUnitEvent(t[i], Player(k), p, null)
exitwhen k == 0
set k = k - 1
endloop
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function RegisterPlayerUnitEventForPlayer takes playerunitevent p, code c, player pl returns nothing
local integer i = 16 * GetHandleId(p) + GetPlayerId(pl)
if t[i] == null then
set t[i] = CreateTrigger()
call TriggerRegisterPlayerUnitEvent(t[i], pl, p, null)
endif
call TriggerAddCondition(t[i], Filter(c))
endfunction
function GetPlayerUnitEventTrigger takes playerunitevent p returns trigger
return t[GetHandleId(p)]
endfunction
endlibrary
//TESH.scrollpos=0
//TESH.alwaysfold=0
/**************************************
*
* IsUnitChanneling
* v2.1.0.0
* By Magtheridon96
*
* - Tells whether a unit is channeling or not.
*
* Requirements:
* -------------
*
* - RegisterPlayerUnitEvent by Magtheridon96
* - hiveworkshop.com/forums/jass-resources-412/snippet-registerplayerunitevent-203338/
*
* Optional:
* ---------
*
* - UnitIndexer by Nestharus
* - hiveworkshop.com/forums/jass-resources-412/system-unit-indexer-172090/
* - Table by Bribe
* - hiveworkshop.com/forums/jass-resources-412/snippet-new-table-188084/
*
* API:
* ----
*
* - function IsUnitChanneling takes unit whichUnit returns boolean
* - Tells whether a unit is channeling or not.
*
* - function IsUnitChannelingById takes integer unitIndex returns boolean
* - Tells whether a unit is channeling or not given the unit index.
* (This function is only available if you have UnitIndexer)
*
**************************************/
library IsUnitChanneling requires optional UnitIndexer, optional Table, RegisterPlayerUnitEvent
private struct OnChannel extends array
static if LIBRARY_UnitIndexer then
static boolean array channeling
else
static if LIBRARY_Table then
static key k
static Table channeling = k
else
static hashtable hash = InitHashtable()
endif
endif
private static method onEvent takes nothing returns nothing
static if LIBRARY_UnitIndexer then
local integer id = GetUnitUserData(GetTriggerUnit())
set channeling[id] = not channeling[id]
else
static if LIBRARY_Table then
local integer id = GetHandleId(GetTriggerUnit())
set channeling.boolean[id] = not channeling.boolean[id]
else
local integer id = GetHandleId(GetTriggerUnit())
call SaveBoolean(hash, 0, id, not LoadBoolean(hash, 0, id))
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onEvent)
call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onEvent)
endmethod
endstruct
static if LIBRARY_UnitIndexer then
function IsUnitChannelingById takes integer id returns boolean
return OnChannel.channeling[id]
endfunction
endif
function IsUnitChanneling takes unit u returns boolean
static if LIBRARY_UnitIndexer then
return OnChannel.channeling[GetUnitUserData(u)]
else
static if LIBRARY_Table then
return OnChannel.channeling.boolean[GetHandleId(u)]
else
return LoadBoolean(OnChannel.hash, 0, GetHandleId(u))
endif
endif
endfunction
endlibrary
//TESH.scrollpos=-1
//TESH.alwaysfold=0
// API:
//
// >>CreateDelayedEffect(whichEffect, X, Y, Delay, Timeout)
// - whichEffect is of type string and contains the path of the
// effect-to-be-spawned
// - X and Y indicate where the effect should be spawned
// - Delay is of type real and indicates how long to wait before spawning the
// effect
// - Timeout is of type real and indicates how long to wait before destroying
// the effect after it has been created
//
// >>CreateDelayedEffectZ(whichEffect, X, Y, Z, Delay, Timeout)
// - whichEffect: see above
// - X, Y and Z indicate where to spawn the effect
// - Delay: see above
// - Timeout: see above
//
// >>CreateDelayedEffectTarget(whichEffect, Target, AttachmentPoint, Delay, Timeout)
// - whichEffect: see above
// - Target is of type widget and indicates on which widget the effect should
// be spawned
// - AttachmentPoint is of type string and holds the attachment point where
// the effect should be spawned on target widget
// - Delay: see above
// - Timeout: see above
//
// CREDITS:
// - Vexorian (JassHelper; TimerUtils)
// - Anitarf (Suggestions)
// - KaTTaNa (AddSpecialEffectZ function @ wc3jass.com)
// - PitzerMike (JassNewGenPack)
// - Pipedream (Grimoire)
// - SFilip (TESH)
library DelFX uses TimerUtils
private struct DELFX
private effect fx
private string path
private boolean target
private widget tar
private string attpt
private real x
private real y
private real z
private timer t
private real timeout
private method onDestroy takes nothing returns nothing
if .fx!=null then
call DestroyEffect(.fx)
set .fx=null
endif
set .tar=null
call ReleaseTimer(.t)
endmethod
private static method Release takes nothing returns nothing
call DELFX.destroy(GetTimerData(GetExpiredTimer()))
endmethod
private static method Callback takes nothing returns nothing
local DELFX s=GetTimerData(GetExpiredTimer())
local destructable d
debug if s.fx==null then
if s.target then
set s.fx=AddSpecialEffectTarget(s.path, s.tar, s.attpt)
elseif s.z==0 then
set s.fx=AddSpecialEffect(s.path, s.x, s.y)
else
set d=CreateDestructableZ('OTip', s.x, s.y, s.z, 0,1.,0)
set s.fx=AddSpecialEffect(s.path, s.x, s.y)
call RemoveDestructable(d)
set d=null
endif
call TimerStart(s.t, s.timeout, false, function DELFX.Release)
debug else
debug call BJDebugMsg("DELFX["+I2S(s)+"].Callback: Effect already spawned!")
debug endif
endmethod
static method Create takes string path, boolean target, widget tar, string attpt, real x, real y, real z, real delay, real timeout returns DELFX
local DELFX s=DELFX.allocate()
set s.t=NewTimer()
call SetTimerData(s.t, s)
set s.path=path
set s.target=target
set s.tar=tar
set s.attpt=attpt
set s.x=x
set s.y=y
set s.z=z
set s.timeout=timeout
call TimerStart(s.t, delay, false, function DELFX.Callback)
return s
endmethod
endstruct
// The functions below have been explained above.
function CreateDelayedEffect takes string path, real x, real y, real delay, real timeout returns nothing
call DELFX.Create(path, false, null, "", x, y, 0, delay, timeout)
endfunction
function CreateDelayedEffectZ takes string path, real x, real y, real z, real delay, real timeout returns nothing
call DELFX.Create(path, false, null, "", x, y, z, delay, timeout)
endfunction
function CreateDelayedEffectTarget takes string path, widget target, string attachmentpoint, real delay, real timeout returns nothing
call DELFX.Create(path, true, target, attachmentpoint, 0, 0, 0, delay, timeout)
endfunction
endlibrary
//TESH.scrollpos=-1
//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=35
//TESH.alwaysfold=0
library IsDestructableTree uses optional UnitIndexer /* v1.3.1
*************************************************************************************
*
* Detect whether a destructable is a tree or not.
*
***************************************************************************
*
* Credits
*
* To PitzerMike
* -----------------------
*
* for IsDestructableTree
*
*************************************************************************************
*
* Functions
*
* function IsDestructableTree takes destructable d returns boolean
*
* function IsDestructableAlive takes destructable d returns boolean
*
* function IsDestructableDead takes destructable d returns boolean
*
* function IsTreeAlive takes destructable tree returns boolean
* - May only return true for trees.
*
* function KillTree takes destructable tree returns boolean
* - May only kill trees.
*
*/
globals
private constant integer HARVESTER_UNIT_ID = 'hpea'//* human peasant
private constant integer HARVEST_ABILITY = 'Ahrl'//* ghoul harvest
private constant integer HARVEST_ORDER_ID = 0xD0032//* harvest order ( 852018 )
private constant player NEUTRAL_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
private unit harvester = null
endglobals
function IsDestructableTree takes destructable d returns boolean
//* 851973 is the order id for stunned, it will interrupt the preceding harvest order.
return (IssueTargetOrderById(harvester, HARVEST_ORDER_ID, d)) and (IssueImmediateOrderById(harvester, 851973))
endfunction
function IsDestructableDead takes destructable d returns boolean
return (GetWidgetLife(d) <= 0.405)
endfunction
function IsDestructableAlive takes destructable d returns boolean
return (GetWidgetLife(d) > .405)
endfunction
function IsTreeAlive takes destructable tree returns boolean
return IsDestructableAlive(tree) and IsDestructableTree(tree)
endfunction
function KillTree takes destructable tree returns boolean
if (IsTreeAlive(tree)) then
call KillDestructable(tree)
return true
endif
return false
endfunction
private function Init takes nothing returns nothing
static if LIBRARY_UnitIndexer then//* You may adapt this to your own indexer.
set UnitIndexer.enabled = false
endif
set harvester = CreateUnit(NEUTRAL_PLAYER, HARVESTER_UNIT_ID, 0, 0, 0)
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = true
endif
call UnitAddAbility(harvester, HARVEST_ABILITY)
call UnitAddAbility(harvester, 'Aloc')
call ShowUnit(harvester, false)
endfunction
//* Seriously?
private module Inits
private static method onInit takes nothing returns nothing
call Init()
endmethod
endmodule
private struct I extends array
implement Inits
endstruct
endlibrary