Name | Type | is_array | initial_value |
DemoMap_Hero1 | unit | No | |
DemoMap_Hero2 | unit | No | |
LS_AreaDamage | real | Yes | |
LS_AreaDamageBase | real | No | |
LS_AreaDamagePerLevel | real | No | |
LS_AttackType | attacktype | No | |
LS_Caster | unit | Yes | |
LS_CasterLocation | location | No | |
LS_CollisionSize | real | Yes | |
LS_CollisionSizeBase | real | No | |
LS_CollisionSizePerLevel | real | No | |
LS_Damage | real | Yes | |
LS_DamageBase | real | No | |
LS_DamagePerLevel | real | No | |
LS_DamageType | damagetype | No | |
LS_DestroyFlag | boolean | Yes | |
LS_DistanceTravelled | real | Yes | |
LS_Dummy | unit | Yes | |
LS_DummySfx | effect | Yes | |
LS_DummyUnitType | unitcode | No | |
LS_HeadingAngle | real | No | |
LS_ImpactRadius | real | Yes | |
LS_ImpactRadiusBase | real | No | |
LS_ImpactRadiusPerLevel | real | No | |
LS_ImpactSfxAttachPoint | string | No | |
LS_ImpactSfxModel | string | No | |
LS_Level | real | No | |
LS_MapMaxX | real | No | |
LS_MapMaxY | real | No | |
LS_MapMinX | real | No | |
LS_MapMinY | real | No | |
LS_MaxDistance | real | Yes | |
LS_MaxDistanceBase | real | No | |
LS_MaxDistancePerLevel | real | No | |
LS_MissileHeightBase | real | No | |
LS_MissileHeightPerLevel | real | No | |
LS_MissileOffset | real | No | |
LS_MissileSfxAttachPoint | string | No | |
LS_MissileSfxModel | string | No | |
LS_NewX | real | No | |
LS_NewY | real | No | |
LS_SpeedCos | real | Yes | |
LS_SpeedSin | real | Yes | |
LS_TempDestructable | destructable | No | |
LS_TempGroup | group | No | |
LS_TempInt | integer | No | |
LS_TempLocation | location | No | |
LS_TempUnit | unit | No | |
LS_Timer | timer | No | |
LS_TravelSpeed | real | Yes | |
LS_TravelSpeedBase | real | No | |
LS_TravelSpeedPerLevel | real | No | |
SPELL__Ability | abilcode | No | |
SPELL__AllyFilterFlag | boolean | No | |
SPELL__DeadFilterFlag | boolean | No | |
SPELL__DUMMY_PLAYER | player | No | |
SPELL__EnemyFilterFlag | boolean | No | |
SPELL__EnumCount | integer | No | |
SPELL__EnumedTargets | unit | Yes | |
SPELL__EnumerateTargetsInRange | trigger | No | |
SPELL__EnumRange | real | No | |
SPELL__Event | real | No | |
SPELL__EVENT_CAST | integer | No | |
SPELL__EVENT_CHANNEL | integer | No | |
SPELL__EVENT_EFFECT | integer | No | |
SPELL__EVENT_ENDCAST | integer | No | |
SPELL__EVENT_FINISH | integer | No | |
SPELL__EventType | integer | No | |
SPELL__ExitPeriodic | boolean | No | |
SPELL__FlyingFilterFlag | boolean | No | |
SPELL__FRAME_RATE | real | No | |
SPELL__HeroFilterFlag | boolean | No | |
SPELL__Index | integer | No | |
SPELL__InitializationEvent | real | No | |
SPELL__InvokeEvent | trigger | No | |
SPELL__Level | integer | No | |
SPELL__LivingFilterFlag | boolean | No | |
SPELL__MagicImmuneFilterFlag | boolean | No | |
SPELL__MechanicalFilterFlag | boolean | No | |
SPELL__NonHeroFilterFlag | boolean | No | |
SPELL__OnEndTrigger | trigger | No | |
SPELL__OnPeriodTrigger | trigger | No | |
SPELL__OnStartTrigger | trigger | No | |
SPELL__ORDER_NO_TARGET | integer | No | |
SPELL__ORDER_POINT_TARGET | integer | No | |
SPELL__ORDER_SINGLE_TARGET | integer | No | |
SPELL__OrderType | integer | No | |
SPELL__OverrideParams | trigger | No | |
SPELL__PERIOD | real | No | |
SPELL__RealLevel | real | No | |
SPELL__RegisterHandler | trigger | No | |
SPELL__StructureFilterFlag | boolean | No | |
SPELL__TargetDest | destructable | No | |
SPELL__TargetItem | item | No | |
SPELL__TargetPoint | location | No | |
SPELL__TargetUnit | unit | No | |
SPELL__TriggerPlayer | player | No | |
SPELL__TriggerUnit | unit | No | |
TempLoc | location | No | |
TempReal | real | No | |
TempUnit | unit | No |
library EnergyMorph /*
*/uses /*
*/PolymorphicEnergy /* https://www.hiveworkshop.com/threads/328348/
*/EnergyStack /*
*/
public struct Default extends array
/*
* Ability used to activate the energy stored in the energy spheres
*/
static constant integer SPELL_ABILITY_ID = 'Poly'
/*
* Spell event type
*/
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
/*
* Model of special effect created when an energy sphere is popped and activated
*/
static constant string ENERGY_ACTIVATE_START_MODEL = ""
/*
* Model of the activated energy travelling from the activated energy sphere into
* the activating unit (caster)
*/
static constant string ENERGY_ACTIVATED_MODEL = EnergyStack_Default.ENERGY_PROXY_MODEL
/*
* Model of the trail attached to the activated energy
*/
static constant string ENERGY_ACTIVATED_TRAIL_MODEL = EnergyStack_Default.ENERGY_PROXY_TRAIL_MODEL
/*
* Model of special effect created when an active energy arrives at the activating
* unit (caster)
*/
static constant string ENERGY_ACTIVATE_COMPLETE_MODEL = ""
static constant string ENERGY_ACTIVATE_COMPLETE_ATTACHPOINT = "origin"
/*
* The number of energy spheres to be activated upon using the active ability
*/
static constant method energySpheresActivationCount takes integer level returns integer
return 1 + 0*level
endmethod
/*
* The interval between the sequence of activation of energy spheres in cases where
* EnergySpheresActivationCount(level) > 1
*/
static constant method energySpheresActivationInterval takes integer level returns real
return 0.75 - 0.00*level
endmethod
/*
* The speed of travel of the activated energy from its sphere's orbit location to
* the position of the activating unit (caster)
*/
static constant method activatedEnergyTravelSpeed takes integer level returns real
return 600.00 + 0.00*level
endmethod
implement EnergyMorphConfiguration
endstruct
endlibrary
library EnergyStack /*
*/uses /*
*/PolymorphicEnergy /* https://www.hiveworkshop.com/threads/328348/
*/SpecialEffect /* https://www.hiveworkshop.com/threads/325954/
*/
private function DealDamage takes unit source, unit target, real amount, attacktype at, damagetype dt, weapontype wt returns nothing
if amount > 0.00 then
call UnitDamageTarget(source, target, amount, true, false, at, dt, wt)
elseif amount < 0.00 then
call SetWidgetLife(target, GetWidgetLife(target) - amount + 0.001)
call UnitDamageTarget(source, target, 0.001, true, false, at, dt, wt)
endif
endfunction
public struct Default extends array
/*===============================================================================================*/
/* STATIC CONFIGURATION */
/*===============================================================================================*/
/*
* The created energy spheres are based on the level of this ability
*/
static constant integer SPELL_ABILITY_ID = 'Poly'
/*
* Model for the energy spheres orbiting the unit with the spell
*/
static constant string ENERGY_SPHERES_MODEL = "Abilities\\Weapons\\WitchDoctorMissile\\WitchDoctorMissile.mdl"
/*
* Model for the energy spheres when inactive
*/
static constant string EMPTY_ENERGY_SPHERES_MODEL = "Units\\Human\\HeroBloodElf\\BloodElfBall.mdl"
/*
* Model for the trails of the orbiting energy spheres
*/
static constant string ENERGY_SPHERES_TRAIL_MODEL = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"
/*
* Energy proxy model
*/
static constant string ENERGY_PROXY_MODEL = "Abilities\\Weapons\\SpiritOfVengeanceMissile\\SpiritOfVengeanceMissile.mdl"
/*
* Energy proxy trail model when travelling
*/
static constant string ENERGY_PROXY_TRAIL_MODEL = "Abilities\\Weapons\\DragonHawkMissile\\DragonHawkMissile.mdl"
/*
* Special effect model attached to the casting unit when its casted ability is blocked by
* the passive effect of this spell
*/
static constant string ENERGY_TRANSFER_DRAIN_MODEL = "Abilities\\Spells\\Human\\DispelMagic\\DispelMagicTarget.mdl"
/*
* Model of special effect when the energy proxy imparts its energy to an energy sphere
*/
static constant string ENERGY_TRANSFER_ABSORB_MODEL = "Abilities\\Spells\\Human\\Invisibility\\InvisibilityTarget.mdl"
/*
* Attributes of the energy spheres' DPS to units within radius
*/
static constant attacktype DPS_ATTACK_TYPE = ATTACK_TYPE_HERO
static constant damagetype DPS_DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
static constant weapontype DPS_WEAPON_TYPE = null
/*===============================================================================================*/
/* DYNAMIC CONFIGURATION */
/*===============================================================================================*/
/*
* The number of energy spheres orbiting the unit
*/
static constant method energySpheresCapacity takes integer level returns integer
return 1 + 1*level
endmethod
/*
* The radius of spell cast detection
*/
static constant method energyDetectionRadius takes integer level returns real
return 2000.00 + 0.00*level
endmethod
/*
* The radius of the energy spheres' orbit
*/
static constant method energySpheresOrbitRadius takes integer level returns real
return 216.00 + 0.00*level
endmethod
/*
* Height of the energy spheres' orbit relative to the ground
*/
static constant method energySpheresOrbitHeight takes integer level returns real
return 54.00 + 0.00*level
endmethod
/*
* The tangential speed of the energy spheres' orbiting motion
* (<NEGATIVE> values means clockwise orbit and <POSITIVE> means counter-clockwise orbit)
*/
static constant method energySpheresOrbitSpeed takes integer level returns real
return -(360.00 + 0.00*level)
endmethod
/*
* The size scaling of the active energy spheres
*/
static constant method energySpheresScale takes integer level returns real
return 1.00 + 0.00*level
endmethod
/*
* The size scaling of the inactive energy spheres
*/
static constant method emptyEnergySpheresScale takes integer level returns real
return 1.00 + 0.00*level
endmethod
/*
* The distance of the energy proxy creation of the position of the energy source
*/
static constant method energyProxySourceOffset takes integer level returns real
return 0.00 + 0.00*level
endmethod
/*
* The height of the created energy proxy relative to the ground
*/
static constant method energyProxyHeight takes integer level returns real
return energySpheresOrbitHeight(level)
endmethod
/*
* The size scaling of the created energy proxy
*/
static constant method energyProxyScale takes integer level returns real
return energySpheresScale(level)
endmethod
static constant method energyProxyTrailScale takes integer level returns real
return energyProxyScale(level)
endmethod
/*
* The speed at which the energy proxy travels from the energy source to the attracting energy sphere
*/
static constant method energyProxyTransferSpeed takes integer level returns real
return 432.00 + 0.00*level
endmethod
/*===============================================================================================*/
/* BONUS CONFIGURATION */
/* These configurations is used by the operations in the callback methods methods. */
/*===============================================================================================*/
/*
* The chance of blocking ang absorbing the targeting spell (Default value is 100%)
*/
static constant method energyAbsorptionChance takes integer level returns real
return 1.00 + 0.00*level
endmethod
/*
* The radius around an energy spheres that takes damage thru time
*/
static constant method energySpheresDpsRadius takes integer level returns real
return (108.00 + 0.00*level)*energySpheresScale(level)
endmethod
/*
* The damage through time dealt to enemies within the radius around an occupied energy sphere
*/
static constant method energySpheresRadiusEnemyDps takes integer level, real currentHp, real maxHp returns real
return (54.00 + 0.00*level) + (0.00 + 0.00*level)*currentHp + (0.00 + 0.00*level)*maxHp
endmethod
/*
* The damage through time dealt to allies within the radius around an occupied energy sphere
*/
static constant method energySpheresRadiusAlliedDps takes integer level, real currentHp, real maxHp returns real
return -((54.00 + 0.00*level) + (0.00 + 0.00*level)*currentHp + (0.00 + 0.00*level)*maxHp)
endmethod
/*
* The damage thru time dealt by an occupied energy sphere to its owner
* (Multiple energy spheres accumulate their damage to their owner)
*/
static constant method energySpheresSelfDps takes integer level, real currentHp, real maxHp returns real
return -(54.00 + 0.00*level) + (0.00 + 0.00*level)*currentHp + (0.04 + 0.01*level)*maxHp
endmethod
/*
* The damage through time dealt to enemies within the radius around an empty energy sphere
*/
static constant method emptyEnergySpheresRadiusEnemyDps takes integer level, real currentHp, real maxHp returns real
return (0.00 + 0.00*level) + (0.00 + 0.00*level)*currentHp + (0.00 + 0.00*level)*maxHp
endmethod
/*
* The damage through time dealt to allies within the radius around an empty energy sphere
*/
static constant method emptyEnergySpheresRadiusAlliedDps takes integer level, real currentHp, real maxHp returns real
return -(54.00 + 0.00*level) - (0.00 + 0.00*level)*currentHp - (0.00 + 0.00*level)*maxHp
endmethod
/*
* The damage thru time dealt by an empty energy sphere to its owner
* (Multiple energy spheres accumulates their damage to their owner)
*/
static constant method emptyEnergySpheresSelfDps takes integer level, real currentHp, real maxHp returns real
return -(54.00 + 0.00*level) - (0.00 + 0.00*level)*currentHp - (0.00 + 0.00*level)*maxHp
endmethod
/*
* Filter expression for damaging units within radius of Energy Sphere per period
*/
static method energySpheresDpsFilter takes integer level, unit owner, unit picked returns boolean
return not IsUnitType(picked, UNIT_TYPE_STRUCTURE)
endmethod
/*===============================================================================================*/
/* EXTENSION CONFIGURATION */
/* These are callback methods that run in certain phases of the spell process. */
/*===============================================================================================*/
/*
* What happens when an Energy Sphere is reserved for spell energy absorpsion
*/
static method onEnergySphereReserved takes unit owner, SpecialEffect sphere, integer abilId, integer abilLevel, integer eventType returns nothing
call SpellCancelEventHandlers(true) // This certain feature crashes on Reforged (see SpellEvent library). Remove if needed.
endmethod
/*
* What happens when an Energy Sphere absorbs energy from a spell
*/
static method onEnergySphereOccupied takes unit owner, SpecialEffect sphere, integer abilId, integer abilLevel, integer eventType returns nothing
endmethod
/*
* What happens when an Energy Sphere is activated by its wielder
*/
static method onEnergySphereActivated takes unit owner, SpecialEffect sphere, integer abilId, integer abilLevel, integer eventType returns nothing
endmethod
/*
* What happens for each Energy Sphere each period
*/
static method onEnergySpherePeriod takes unit owner, SpecialEffect sphere, integer sphereState returns nothing
local boolean occupied = sphereState == 2 // 0 - vacant, 1 - reserved, 2 - occupied
local player p = GetOwningPlayer(owner)
local integer level = GetUnitAbilityLevel(owner, SPELL_ABILITY_ID)
local real radius = energySpheresDpsRadius(level)
local real size
local real dx
local real dy
local real dz
local real ds
local unit u
call GroupEnumUnitsInRange(PolymorphicEnergy_enumGroup, sphere.x, sphere.y, radius + 197.00, null)
loop
set u = FirstOfGroup(PolymorphicEnergy_enumGroup)
exitwhen u == null
call GroupRemoveUnit(PolymorphicEnergy_enumGroup, u)
if UnitAlive(u) and u != owner and energySpheresDpsFilter(level, owner, u) then
set dx = GetUnitX(u) - sphere.x
set dy = GetUnitY(u) - sphere.y
set dz = PolymorphicEnergy_GetUnitZ(u) - sphere.z
set ds = radius + BlzGetUnitCollisionSize(u)
if dx*dx + dy*dy + dz*dz <= ds*ds then
if IsUnitEnemy(u, p) then
if occupied then
call DealDamage(owner, u, energySpheresRadiusEnemyDps(level, GetWidgetLife(u), GetUnitState(u, UNIT_STATE_MAX_LIFE))*PolymorphicEnergy_PERIODIC_TIMEOUT, DPS_ATTACK_TYPE, DPS_DAMAGE_TYPE, DPS_WEAPON_TYPE)
else
call DealDamage(owner, u, emptyEnergySpheresRadiusEnemyDps(level, GetWidgetLife(u), GetUnitState(u, UNIT_STATE_MAX_LIFE))*PolymorphicEnergy_PERIODIC_TIMEOUT, DPS_ATTACK_TYPE, DPS_DAMAGE_TYPE, DPS_WEAPON_TYPE)
endif
elseif occupied then
call DealDamage(owner, u, energySpheresRadiusAlliedDps(level, GetWidgetLife(u), GetUnitState(u, UNIT_STATE_MAX_LIFE))*PolymorphicEnergy_PERIODIC_TIMEOUT, DPS_ATTACK_TYPE, DPS_DAMAGE_TYPE, DPS_WEAPON_TYPE)
else
call DealDamage(owner, u, emptyEnergySpheresRadiusAlliedDps(level, GetWidgetLife(u), GetUnitState(u, UNIT_STATE_MAX_LIFE))*PolymorphicEnergy_PERIODIC_TIMEOUT, DPS_ATTACK_TYPE, DPS_DAMAGE_TYPE, DPS_WEAPON_TYPE)
endif
endif
endif
endloop
if occupied then
call DealDamage(owner, owner, energySpheresSelfDps(level, GetWidgetLife(owner), GetUnitState(owner, UNIT_STATE_MAX_LIFE))*PolymorphicEnergy_PERIODIC_TIMEOUT, DPS_ATTACK_TYPE, DPS_DAMAGE_TYPE, DPS_WEAPON_TYPE)
else
call DealDamage(owner, owner, emptyEnergySpheresSelfDps(level, GetWidgetLife(owner), GetUnitState(owner, UNIT_STATE_MAX_LIFE))*PolymorphicEnergy_PERIODIC_TIMEOUT, DPS_ATTACK_TYPE, DPS_DAMAGE_TYPE, DPS_WEAPON_TYPE)
endif
endmethod
/*
* Filters the types of abilities (alongside other conditions) that can activate this spell
*/
static method onAbilityAbsorbFilter takes integer abilId, integer level returns boolean
local real dx = GetUnitX(Spell.triggerUnit) - GetUnitX(Spell.targetUnit)
local real dy = GetUnitY(Spell.triggerUnit) - GetUnitY(Spell.targetUnit)
local real dz = PolymorphicEnergy_GetUnitZ(Spell.triggerUnit) - PolymorphicEnergy_GetUnitZ(Spell.targetUnit)
local real dr = energyDetectionRadius(GetUnitAbilityLevel(Spell.targetUnit, SPELL_ABILITY_ID))
/*
* Other Example:
* return not (abilId == 'XXX0' or abilId == 'XXX1' or abilId == 'XXX2')
*/
return dx*dx + dy*dy + dz*dz <= dr*dr and GetRandomReal(0.00, 1.00) <= energyAbsorptionChance(level)
endmethod
implement EnergyStackConfiguration
endstruct
endlibrary
library PolymorphicEnergy /* v1.0.0 by AGD | https://www.hiveworkshop.com/threads/328348/ | Patches 1.31+
*/uses /*
*/SpellFramework /* https://www.hiveworkshop.com/threads/325448/
*/Missile /* https://www.hiveworkshop.com/threads/325956/
*/SpecialEffect /* https://www.hiveworkshop.com/threads/325954/
*/UnitDex /* https://www.hiveworkshop.com/threads/248209/
*/Table /* https://www.hiveworkshop.com/threads/188084/
*/PeriodicListTraversal /* (Included in this Bundle)
*/optional ResourcePreloader /* https://www.hiveworkshop.com/threads/287358/
|-----------------|
| SPELL MECHANICS |
|-----------------|
It is better if you try the spell first in-game before reading its mechanics below, to
give you a better context especially on the terms used.
Definitions:
> HERO - the wielder/learner of this spell
Phases (In sequence):
1. Energy Stack Creation:
- Upon learning the 'passive' ability, the HERO will acquire its <energy stack>.
The stack will contain vacant <energy spheres> that will orbit around the HERO.
2. Energy Absorption:
- When an enemy unit targets the HERO with a single-target spell, the spell will
be blocked if the HERO's <energy stack> is not full.
- When a spell is blocked, it is transformed into potential energy and is
extracted from the enemy unit by the HERO.
3. Energy Transfer:
- The energy will be transferred to the HERO in the form of an <energy proxy>
that travels towards one of the HERO's vacant <energy spheres>.
4. Energy Storing:
- Upon contact between the <energy proxy> and the vacant <energy sphere>, the
<energy proxy> gives off its energy into the vacant <energy sphere>, changing
its state from being 'vacant' to 'charged'/'occupied'. Once all <energy spheres>
belonging to an <energy stack> are charged, that <energy stack> is considered
'full'.
5. Energy Spheres Orbit:
- Charged <energy spheres> orbiting the HERO deals damage through time to units
within their radius.
- The HERO also takes damage equal to percentage of its max HP over time, for
each charged <energy spheres> in its <energy stack> (the damage taken by the
HERO is multiplied by the number of charged <energy spheres>).
6. Energy Activation:
- When the HERO uses the activation ability, a charged <energy sphere> is popped
from its <energy stack>. The energy will be extracted from the <energy sphere>
(changing its state to vacant) and will travel towards the HERO, again, in the
form of an <energy proxy>.
7. Energy Consumption:
- Finally, when the <energy proxy> contacts the HERO, the contained potential
energy is extracted and the HERO will morph that energy to whatever is its
original form and sends it towards a target.
*/
/*===============================================================================================*/
/* GLOBAL CONFIGURATION */
/*===============================================================================================*/
globals
/*
* Timer timeout of the spell's periodic operations
*/
public constant real PERIODIC_TIMEOUT = 1.00/32.00
/*
* Timer timeout for keeping track of the unit's activation ability level
* since the spell has a passive effect
*/
public constant real PASSIVE_ABILITY_UPDATE_PERIOD = 0.10
/*
* OrderID for "stop"
*/
public constant integer STOP_ORDER_ID = 851972
/*
* Death animation duration for special effects
*/
public constant real SFX_DEATH_TIME = 2.16
endglobals
/*===============================================================================================*/
/* END OF GLOBAL CONFIGURATION */
/*===============================================================================================*/
native UnitAlive takes unit u returns boolean
private keyword PolymorphicEnergy
private keyword EnergySphere
private keyword EnergyStack
private keyword EnergyStackList
globals
public group enumGroup = CreateGroup()
private trigger evaluator = CreateTrigger()
private location loc = Location(0.00, 0.00)
private integer tempLevel
private EnergySphere tempSphere
private integer tempState
private boolean fixedRotationAxes
private EnergyStackList array energyStackList
endglobals
public function GetUnitZ takes unit u returns real
call MoveLocation(loc, GetUnitX(u), GetUnitY(u))
return GetLocationZ(loc) + GetUnitFlyHeight(u)
endfunction
private function Evaluate takes boolexpr expr returns boolean
call TriggerClearConditions(evaluator)
call TriggerAddCondition(evaluator, expr)
return TriggerEvaluate(evaluator)
endfunction
private function DestroyTimerEx takes timer t returns nothing
call PauseTimer(t)
call DestroyTimer(t)
endfunction
/*
* Node stack for the whole library
*/
private struct Node extends array
implement Alloc
endstruct
/*
* Spell configuration data
*/
private struct Config extends array
string energyActivateStartModel
string energyActivatedModel
string energyActivatedTrailModel
string energyActivateCompleteModel
string energyActivateCompleteAttachPoint
integer energySpheresActivationCount
real energySpheresActivationInterval
real activatedEnergyTravelSpeed
string energySpheresModel
string emptyEnergySpheresModel
string energySpheresTrailModel
string energyProxyModel
string energyProxyTrailModel
string energyTransferDrainModel
string energyTransferAbsorbModel
integer energySpheresCapacity
real energySpheresOrbitRadius
real energySpheresOrbitHeight
real energySpheresOrbitSpeed
real energySpheresScale
real emptyEnergySpheresScale
real energyProxySourceOffset
real energyProxyHeight
real energyProxyScale
real energyProxyTrailScale
real energyProxyTransferSpeed
boolexpr onEnergyAbsorbFilter
boolexpr onEnergySphereReserved
boolexpr onEnergySphereOccupied
boolexpr onEnergySphereActivated
boolexpr onEnergySpherePeriod
endstruct
/*
* Structure for activated energy spheres
*/
private struct ActiveEnergy extends array
Spell spell
integer eventType
integer level
unit activator
unit target
readonly Config configNode
private string completionModel
method operator missile takes nothing returns Missile
return this
endmethod
private static method onRemove takes thistype node returns boolean
set node.activator = null
set node.target = null
return true
endmethod
private static method onFinish takes thistype node returns boolean
call DestroyEffect(AddSpecialEffectTarget(node.configNode.energyActivateCompleteModel, node.activator, node.configNode.energyActivateCompleteAttachPoint))
call node.spell.invokeSingleTargetEvent(node.eventType, node.level, node.activator, node.target)
return true
endmethod
private static method onPeriod takes thistype node returns boolean
return not UnitAlive(node.activator)
endmethod
implement MissileStruct
static method push takes thistype node returns nothing
set node.configNode = node
set node.missile.target = node.activator
set node.missile.speed = node.configNode.activatedEnergyTravelSpeed
set node.missile.model = node.configNode.energyActivateStartModel
set node.missile.model = node.configNode.energyActivatedModel
call node.missile.effect.addModel(node.configNode.energyActivatedTrailModel)
call launch(node.missile)
endmethod
endstruct
/*
* Structure for an energy sphere
*/
private struct EnergySphere extends array
Spell spell
integer eventType
integer level
readonly boolean occupied
readonly SpecialEffect effect
readonly real orbitRadius
readonly real speed
readonly real theta
readonly real height
method operator energyStack takes nothing returns EnergyStack
return this
endmethod
method updatePosition takes real centerX, real centerY, real centerZ, real theta returns nothing
local real facing
local real x = centerX + this.orbitRadius*Cos(theta)
local real y = centerY + this.orbitRadius*Sin(theta)
if this.speed > 0.00 then
set facing = theta + bj_PI*0.50
elseif this.speed < 0.00 then
set facing = theta - bj_PI*0.50
else
set facing = theta
endif
set this.theta = theta
call this.effect.move(x, y, centerZ)
call this.effect.setOrientation(facing, this.effect.pitch, this.effect.roll)
endmethod
method updateConfiguration takes Config configNode returns nothing
call BlzSetSpecialEffectScale(this.effect.getHandle(configNode.energySpheresModel), configNode.energySpheresScale)
call BlzSetSpecialEffectScale(this.effect.getHandle(configNode.energySpheresTrailModel), configNode.energySpheresScale)
set this.speed = configNode.energySpheresOrbitSpeed
set this.orbitRadius = configNode.energySpheresOrbitRadius
endmethod
method store takes Spell spell, integer eventType, integer level returns nothing
local EnergyStack stack = this.energyStack.head
local Config configNode = EnergyStackList(stack).configNode
set stack.charges = stack.charges + 1
set this.occupied = true
set this.spell = spell
set this.eventType = eventType
set this.level = level
call BlzSetSpecialEffectScale(this.effect.addModel(configNode.energySpheresModel), configNode.energySpheresScale)
call BlzSetSpecialEffectScale(this.effect.addModel(configNode.energySpheresTrailModel), configNode.energySpheresScale)
set tempSphere = this
call Evaluate(configNode.onEnergySphereOccupied)
endmethod
method activate takes PolymorphicEnergy spell returns nothing
local EnergyStack stack = this.energyStack.head
local ActiveEnergy energyNode = Missile.createXYZ(this.effect.x, this.effect.y, this.effect.height, GetUnitX(this.energyStack.unit), GetUnitY(this.energyStack.unit), GetUnitFlyHeight(this.energyStack.unit))
set energyNode.target = spell.target
set energyNode.activator = this.energyStack.unit
set energyNode.spell = this.spell
set energyNode.eventType = this.eventType
set energyNode.level = this.level
set this.occupied = false
set stack.charges = stack.charges - 1
call SpellCloner_LoadSpellConfiguration(PolymorphicEnergy.typeid, energyNode, spell.configId)
call ActiveEnergy.push(energyNode)
set tempSphere = this
call Evaluate(stack.configNode.onEnergySphereActivated)
endmethod
static method create takes nothing returns thistype
local thistype node = Node.allocate()
set node.effect = SpecialEffect.create(0.00, 0.00, 0.00)
return node
endmethod
method destroy takes nothing returns nothing
if this.occupied then
set this.occupied = false
set this.energyStack.head.charges = this.energyStack.head.charges - 1
endif
call this.effect.kill(SFX_DEATH_TIME, true)
set this.effect = 0
call Node(this).deallocate()
endmethod
endstruct
/*
* Structure for an energy stack
*/
private struct EnergyStack extends array
static constant real LIST_TRAVERSAL_PERIOD = PERIODIC_TIMEOUT
unit unit
thistype head
integer charges
readonly integer stackCount
readonly integer reservedCount
readonly boolean reserved
method operator configNode takes nothing returns Config
return EnergyStackList(this.head).configNode
endmethod
method operator energySphere takes nothing returns EnergySphere
return this
endmethod
method operator emptyStack takes nothing returns boolean
return this.reservedCount == 0
endmethod
method operator fullStack takes nothing returns boolean
return this.reservedCount == this.stackCount
endmethod
private method onListTraversalPeriod takes thistype node returns boolean
call node.energySphere.updatePosition(GetUnitX(node.unit), GetUnitY(node.unit), GetUnitZ(node.unit) + node.configNode.energySpheresOrbitHeight, ModuloReal(node.energySphere.theta + node.energySphere.speed/node.energySphere.orbitRadius, 2.00*bj_PI))
set tempSphere = node.energySphere
if node.energySphere.occupied then
set tempState = 2
elseif node.reserved then
set tempState = 1
else
set tempState = 0
endif
call Evaluate(node.configNode.onEnergySpherePeriod)
return false
endmethod
private static method onNodeAdded takes thistype node returns nothing
local thistype list = node.head
local Config configNode = node.configNode
local real centerX = GetUnitX(list.unit)
local real centerY = GetUnitY(list.unit)
local real centerZ = GetUnitZ(list.unit)
local integer stackCount = list.stackCount + 1
local real theta
local real deltaT
set list.stackCount = stackCount
if configNode.energySpheresOrbitSpeed < 0.00 then
set deltaT = (2.00*bj_PI)/stackCount
else
set deltaT = (-2.00*bj_PI)/stackCount
endif
if node.prev == list then
set theta = GetUnitFacing(node.unit)*bj_DEGTORAD
else
set theta = list.next.energySphere.theta
endif
call BlzSetSpecialEffectScale(node.energySphere.effect.addModel(configNode.emptyEnergySpheresModel), configNode.emptyEnergySpheresScale)
set node = list.next
loop
exitwhen node == list
call node.energySphere.updateConfiguration(configNode)
call node.energySphere.updatePosition(centerX, centerY, centerZ + configNode.energySpheresOrbitHeight, theta)
set theta = theta + deltaT
set node = node.next
endloop
endmethod
private static method onNodeRemoved takes thistype node returns nothing
set node.head.stackCount = node.head.stackCount - 1
if node.reserved then
set node.head.reservedCount = node.head.reservedCount - 1
endif
set node.reserved = false
call node.energySphere.destroy()
endmethod
private static method allocate takes nothing returns thistype
local thistype node = Node.allocate()
set node.reservedCount = 0
set node.charges = 0
return node
endmethod
private method deallocate takes nothing returns nothing
call Node(this).deallocate()
endmethod
implement PeriodicListTraversal
implement InstantiatedList
method push takes nothing returns thistype
local thistype node = this.next
loop
exitwhen node == this
if not node.reserved then
set node.reserved = true
set this.reservedCount = this.reservedCount + 1
set tempSphere = node.energySphere
call Evaluate(node.configNode.onEnergySphereReserved)
return node
endif
set node = node.next
endloop
return 0
endmethod
method pop takes nothing returns EnergySphere
local thistype node = this.prev
loop
exitwhen node == this
if node.energySphere.occupied then
set node.reserved = false
set this.reservedCount = this.reservedCount - 1
call node.energySphere.effect.killModel(node.configNode.energySpheresModel, SFX_DEATH_TIME)
call node.energySphere.effect.killModel(node.configNode.energySpheresTrailModel, SFX_DEATH_TIME)
return node.energySphere
endif
set node = node.prev
endloop
return 0
endmethod
endstruct
/*
* Structure for the travelling energy proxies
*/
private struct EnergyProxy extends array
Spell spell
integer eventType
integer level
EnergyStack targetNode
Config configNode
method operator missile takes nothing returns Missile
return this
endmethod
private static method onFinish takes thistype node returns boolean
local EnergySphere sphere = node.targetNode.energySphere
call sphere.effect.addModel(node.configNode.energyTransferAbsorbModel)
call sphere.effect.killModel(node.configNode.energyTransferAbsorbModel, SFX_DEATH_TIME)
call sphere.store(node.spell, node.eventType, node.level)
return true
endmethod
private static method onPeriod takes thistype node returns boolean
local SpecialEffect target = node.targetNode.energySphere.effect
if target == 0 or node.targetNode.head.charges == node.targetNode.head.stackCount then
return true
endif
call node.missile.bounce()
call node.missile.impact.move(target.x, target.y, target.height)
return false
endmethod
implement MissileStruct
static method push takes thistype node returns nothing
set node.missile.speed = node.configNode.energyProxyTransferSpeed
set node.missile.model = node.configNode.energyProxyModel
set node.missile.scale = node.configNode.energyProxyScale
call BlzSetSpecialEffectScale(node.missile.effect.addModel(node.configNode.energyProxyTrailModel), node.configNode.energyProxyTrailScale)
call launch(node.missile)
endmethod
endstruct
/*
* List of energy stacks for a specific unit
*/
private struct EnergyStackList extends array
unit unit
integer abilId
integer abilLevel
integer capacity
Config configNode
method operator stack takes nothing returns EnergyStack
return this
endmethod
private static method onInsert takes thistype node returns nothing
set node.configNode = node
endmethod
private static method onRemove takes thistype node returns nothing
set node.abilId = 0
set node.abilLevel = 0
set node.capacity = 0
set node.unit = null
call EnergyStack(node).destroy()
endmethod
private static method allocate takes nothing returns thistype
return Node.allocate()
endmethod
private method deallocate takes nothing returns nothing
call Node(this).deallocate()
endmethod
implement InstantiatedList
method get takes nothing returns EnergyStack
local thistype node = this.next
loop
exitwhen (node.stack.charges > 0) or node == this
set node = node.next
endloop
if node == this then
return 0
endif
return node.stack
endmethod
endstruct
/*
* List of all available energy stack types configured
*/
private struct EnergyStackType extends array
readonly integer abilId
implement StaticListLite
static method register takes thistype configStructId, integer abilId returns nothing
set configStructId.abilId = abilId
call pushBack(configStructId)
endmethod
endstruct
/*
* Struct for monitoring units owning one or more energy stack types
*/
private struct UnitList extends array
static constant real LIST_TRAVERSAL_PERIOD = PASSIVE_ABILITY_UPDATE_PERIOD
private Table table
private method onListTraversalPeriod takes thistype node returns boolean
local integer delta
local EnergyStackType stackType = EnergyStackType.front
local EnergyStackList energyStack
local EnergyStack energyNode
local boolean alive = UnitAlive(GetUnitById(node))
loop
exitwhen stackType == EnergyStackType.head
if alive then
set tempLevel = GetUnitAbilityLevel(GetUnitById(node), stackType.abilId)
else
set tempLevel = 0
endif
set energyStack = node.table[stackType]
if tempLevel == 0 then
if energyStack > 0 then
call EnergyStackList.remove(energyStack)
call node.table.remove(stackType)
endif
else //if tempLevel > 0 then
if energyStack == 0 then
set energyStack = EnergyStack.create()
call energyStackList[node].pushBack(energyStack)
call SpellCloner_LoadSpellConfiguration(PolymorphicEnergy.typeid, energyStack.configNode, stackType)
set delta = energyStack.configNode.energySpheresCapacity
set energyStack.unit = GetUnitById(node)
set energyStack.abilId = stackType.abilId
set energyStack.abilLevel = tempLevel
set energyStack.capacity = delta
set node.table[stackType] = energyStack
elseif tempLevel == energyStack.abilLevel then
set delta = 0
else
call SpellCloner_LoadSpellConfiguration(PolymorphicEnergy.typeid, energyStack.configNode, stackType)
set delta = energyStack.configNode.energySpheresCapacity - energyStack.capacity
set energyStack.abilLevel = tempLevel
set energyStack.capacity = energyStack.configNode.energySpheresCapacity
endif
if delta > 0 then
loop
exitwhen delta == 0
set energyNode = EnergySphere.create()
set energyNode.head = energyStack.stack
set energyNode.unit = GetUnitById(node)
call energyStack.stack.pushBack(energyNode)
set delta = delta - 1
endloop
elseif delta < 0 then
loop
exitwhen delta == 0
call energyStack.stack.popFront()
set delta = delta + 1
endloop
endif
endif
set stackType = stackType.next
endloop
return false
endmethod
private static method onNodeAdded takes thistype node returns nothing
set energyStackList[node] = EnergyStackList.create()
set node.table = Table.create()
endmethod
private static method onNodeRemoved takes thistype node returns nothing
call node.table.destroy()
call energyStackList[node].destroy()
endmethod
implement PeriodicListTraversal
implement StaticListLite
private static method onUnitIndex takes nothing returns nothing
call pushBack(GetIndexedUnitId())
endmethod
private static method onUnitDeindex takes nothing returns nothing
call remove(GetIndexedUnitId())
endmethod
static method onScopeInit takes nothing returns nothing
local code onIndex = function thistype.onUnitIndex
local code onDeindex = function thistype.onUnitDeindex
call OnUnitIndex(onIndex)
call OnUnitDeindex(onDeindex)
endmethod
endstruct
/*
* Main spell struct
*/
private struct PolymorphicEnergy extends array
static real SPELL_PERIOD
readonly integer configId
readonly integer level
readonly unit target
private integer count
private EnergyStackList stackList
method operator configNode takes nothing returns Config
return this
endmethod
implement SpellClonerHeader
private method onSpellEnd takes nothing returns nothing
set this.target = null
call Node(this).deallocate()
endmethod
private method onSpellPeriodic takes nothing returns boolean
local EnergyStack stack = this.stackList.get()
if stack > 0 then
call stack.pop().activate(this)
set this.configNode.energySpheresActivationCount = this.configNode.energySpheresActivationCount - 1
return this.configNode.energySpheresActivationCount == 0
endif
return true
endmethod
private static method onSpellStart takes nothing returns thistype
local thistype node = Node.allocate()
local EnergyStackList stackList = energyStackList[GetUnitId(Spell.triggerUnit)]
local EnergyStack stack
set node.level = Spell.level
set node.target = Spell.targetUnit
set node.configId = node.initSpellConfiguration(Spell.abilityId)
set node.configNode.energySpheresActivationCount = node.configNode.energySpheresActivationCount - 1
if node.configNode.energySpheresActivationCount < 0 then
call node.onSpellEnd()
return 0
endif
set stack = stackList.get()
if stack == 0 then
/*
* Do not reset cooldown nor deduct mana if the energy stack is empty
*/
call IssueImmediateOrderById(Spell.triggerUnit, STOP_ORDER_ID)
else
call stack.pop().activate(node)
if node.configNode.energySpheresActivationCount > 0 then
set node.stackList = stackList
set node.target = Spell.targetUnit
set SPELL_PERIOD = node.configNode.energySpheresActivationInterval
return node
endif
endif
call node.onSpellEnd()
return 0
endmethod
implement SpellEventEx
implement SpellClonerFooter
private static method initEnergyTransfer takes EnergyStack energyNode returns nothing
local Config configNode = energyNode.configNode
local EnergySphere sphere = energyNode.energySphere
local real sourceX = GetUnitX(Spell.triggerUnit)
local real sourceY = GetUnitY(Spell.triggerUnit)
local real a = Atan2(sphere.effect.y - sourceY, sphere.effect.x - sourceX)
local EnergyProxy node = Missile.createXYZ(sourceX + configNode.energyProxySourceOffset*Cos(a), sourceY + configNode.energyProxySourceOffset*Sin(a), configNode.energyProxyHeight, sphere.effect.x, sphere.effect.y, sphere.effect.height)
set node.configNode = configNode
set node.spell = Spell[Spell.abilityId]
set node.eventType = Spell.eventType
set node.level = Spell.level
set node.targetNode = energyNode
call EnergyProxy.push(node)
call DestroyEffect(AddSpecialEffectTarget(configNode.energyTransferDrainModel, Spell.triggerUnit, "origin"))
endmethod
private static method onSpellEvent takes nothing returns nothing
local EnergyStackList stackList = energyStackList[GetUnitId(Spell.targetUnit)]
local EnergyStackList stackNode = stackList.next
local EnergyStack energyNode
loop
exitwhen stackNode == stackList
if not stackNode.stack.fullStack and Evaluate(stackNode.configNode.onEnergyAbsorbFilter) then
set energyNode = stackNode.stack.push()
if energyNode > 0 then
call initEnergyTransfer(energyNode)
return
endif
endif
set stackNode = stackNode.next
endloop
endmethod
implement SpellEventGeneric
endstruct
private struct Initializer extends array
private static method onInit takes nothing returns nothing
call UnitList.onScopeInit()
endmethod
endstruct
/*
* Implement this module to a struct to configure the 'active' part of the spell
*/
module EnergyMorphConfiguration
private static method configHandler takes nothing returns nothing
local PolymorphicEnergy node = SpellCloner.configuredInstance
set node.configNode.energyActivateStartModel = ENERGY_ACTIVATE_START_MODEL
set node.configNode.energyActivatedModel = ENERGY_ACTIVATED_MODEL
set node.configNode.energyActivatedTrailModel = ENERGY_ACTIVATED_TRAIL_MODEL
set node.configNode.energyActivateCompleteModel = ENERGY_ACTIVATE_COMPLETE_MODEL
set node.configNode.energyActivateCompleteAttachPoint = ENERGY_ACTIVATE_COMPLETE_ATTACHPOINT
set node.configNode.energySpheresActivationCount = energySpheresActivationCount(node.level)
set node.configNode.energySpheresActivationInterval = energySpheresActivationInterval(node.level)
set node.configNode.activatedEnergyTravelSpeed = activatedEnergyTravelSpeed(node.level)*Missile_TIMER_TIMEOUT
endmethod
private static method onInit takes nothing returns nothing
call PolymorphicEnergy.create(thistype.typeid, SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.configHandler)
static if LIBRARY_ResourcePreloader then
/*
* Preload special effects
*/
call PreloadEffect(ENERGY_ACTIVATE_START_MODEL)
call PreloadEffect(ENERGY_ACTIVATED_MODEL)
call PreloadEffect(ENERGY_ACTIVATED_TRAIL_MODEL)
call PreloadEffect(ENERGY_ACTIVATE_COMPLETE_MODEL)
endif
endmethod
endmodule
/*
* Implement this module to a struct to configure the 'passive' part of the spell
*/
module EnergyStackConfiguration
private static method onEnergyAbsorbFilterFunc takes nothing returns boolean
return onAbilityAbsorbFilter(Spell.abilityId, Spell.level)
endmethod
private static method onEnergySphereReservedFunc takes nothing returns boolean
call onEnergySphereReserved(tempSphere.energyStack.unit, tempSphere.effect, Spell.abilityId, Spell.level, Spell.eventType)
return false
endmethod
private static method onEnergySphereOccupiedFunc takes nothing returns boolean
call onEnergySphereOccupied(tempSphere.energyStack.unit, tempSphere.effect, tempSphere.spell.abilId, tempSphere.level, tempSphere.eventType)
return false
endmethod
private static method onEnergySphereActivatedFunc takes nothing returns boolean
call onEnergySphereActivated(tempSphere.energyStack.unit, tempSphere.effect, tempSphere.spell.abilId, tempSphere.level, tempSphere.eventType)
return false
endmethod
private static method onEnergySpherePeriodFunc takes nothing returns boolean
call onEnergySpherePeriod(tempSphere.energyStack.unit, tempSphere.effect, tempState)
return false
endmethod
private static method configHandler takes nothing returns nothing
local Config configNode = SpellCloner.configuredInstance
set configNode.energySpheresModel = ENERGY_SPHERES_MODEL
set configNode.emptyEnergySpheresModel = EMPTY_ENERGY_SPHERES_MODEL
set configNode.energySpheresTrailModel = ENERGY_SPHERES_TRAIL_MODEL
set configNode.energyProxyModel = ENERGY_PROXY_MODEL
set configNode.energyProxyTrailModel = ENERGY_PROXY_TRAIL_MODEL
set configNode.energyTransferDrainModel = ENERGY_TRANSFER_DRAIN_MODEL
set configNode.energyTransferAbsorbModel = ENERGY_TRANSFER_ABSORB_MODEL
set configNode.energySpheresCapacity = energySpheresCapacity(tempLevel)
set configNode.energySpheresOrbitRadius = energySpheresOrbitRadius(tempLevel)
set configNode.energySpheresOrbitHeight = energySpheresOrbitHeight(tempLevel)
set configNode.energySpheresOrbitSpeed = energySpheresOrbitSpeed(tempLevel)*PERIODIC_TIMEOUT
set configNode.energySpheresScale = energySpheresScale(tempLevel)
set configNode.emptyEnergySpheresScale = emptyEnergySpheresScale(tempLevel)
set configNode.energyProxySourceOffset = energyProxySourceOffset(tempLevel)
set configNode.energyProxyHeight = energyProxyHeight(tempLevel)
set configNode.energyProxyScale = energyProxyScale(tempLevel)
set configNode.energyProxyTrailScale = energyProxyTrailScale(tempLevel)
set configNode.energyProxyTransferSpeed = energyProxyTransferSpeed(tempLevel)*Missile_TIMER_TIMEOUT
set configNode.onEnergyAbsorbFilter = Filter(function thistype.onEnergyAbsorbFilterFunc)
set configNode.onEnergySphereReserved = Filter(function thistype.onEnergySphereReservedFunc)
set configNode.onEnergySphereOccupied = Filter(function thistype.onEnergySphereOccupiedFunc)
set configNode.onEnergySphereActivated = Filter(function thistype.onEnergySphereActivatedFunc)
set configNode.onEnergySpherePeriod = Filter(function thistype.onEnergySpherePeriodFunc)
endmethod
private static method onInit takes nothing returns nothing
call PolymorphicEnergy.create(thistype.typeid, SPELL_ABILITY_ID, 0, function thistype.configHandler)
call EnergyStackType.register(thistype.typeid, SPELL_ABILITY_ID)
static if LIBRARY_ResourcePreloader then
/*
* Preload special effects
*/
call PreloadEffect(ENERGY_SPHERES_MODEL)
call PreloadEffect(EMPTY_ENERGY_SPHERES_MODEL)
call PreloadEffect(ENERGY_SPHERES_TRAIL_MODEL)
call PreloadEffect(ENERGY_PROXY_MODEL)
call PreloadEffect(ENERGY_PROXY_TRAIL_MODEL)
call PreloadEffect(ENERGY_TRANSFER_DRAIN_MODEL)
call PreloadEffect(ENERGY_TRANSFER_ABSORB_MODEL)
endif
endmethod
endmodule
/*
* In case one wants to combine the configurations of both the active and passive components
* of the spell into one struct
*/
module PolymorphicEnergyConfiguration
implement EnergyMorphConfiguration
implement EnergyStackConfiguration
endmodule
endlibrary
library SpellFramework /* https://www.hiveworkshop.com/threads/325448/
*/uses /*
[FRAMEWORK CORE]
The core of the spell development framework contains three components namely, SpellEvent, SpellEventGUI,
and SpellCloner. SpellEvent and SpellCloner go hand in hand in creating an organized template for spell
development in vJass.
Below are VERY brief overview of the purpose of each component. A more intensive description of each one can
be found on the header of each listed library.
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
SpellEvent handles and automates common spell-related tasks such as spell indexing and event registration.
It further provides useful advanced features such as manual event invocation, event parameters overriding,
and event cancelling. It also provides event response variables/functions that are intuitive to use and
recursion-safe.
*/SpellCloner /* https://www.hiveworkshop.com/threads/324157/
SpellCloner provides a framework for ensuring the maintainability of custom spells. It divides the
custom spell into two sections namely, the spell mechanics, and the spell configuration. This provides
convenience in updating the code for the spell mechanics without touching the configuration section.
The spell configuration section can also contain more than one set of spell configurations - which is
cool.
*/optional SpellEventGUI /*
The SpellEventGUI in itself is a framework specifically made for GUI developers. It is built upon the
SpellEvent library, from which it derives many of its functionalities. It also provides utilities for
other spell-related tasks that are not so convenient in GUI such as filtered unit-group enumeration.
It also automatically handles channeling abilities interruption/finish for the spell developer unlike
in its vJass counterpart where freedom in usage is given more priority over total automation.
[UTILITY COMPONENTS]
These components are included as they are almost always used in spell making.
*/Alloc /* https://www.hiveworkshop.com/threads/324937/
Allocator module - compatible for recent patches with the updated JASS_MAX_ARRAY_SIZE
You can also use any other Alloc if you prefer but be sure to update it to adapt to JASS_MAX_ARRAY_SIZE
*/LinkedList /* https://www.hiveworkshop.com/threads/325635/
Library providing linked-list modules with different variants
[List of External Library Requirements]
Required:
> Table
https://www.hiveworkshop.com/threads/188084/
Optional:
> RegisterPlayerUnitEvent
https://www.hiveworkshop.com/threads/250266/
> ResourcePreloader
https://www.hiveworkshop.com/threads/287358/
> ErrorMessage
https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*/
endlibrary
/*
* An Alias
*/
library SpellDevFramework uses SpellFramework
endlibrary
library SpellEvent /* v3.1.2 https://www.hiveworkshop.com/threads/301895/
*/uses /*
*/Table /* https://www.hiveworkshop.com/threads/188084/
*/LinkedList /* https://www.hiveworkshop.com/threads/325635/
*/optional Alloc /* https://www.hiveworkshop.com/threads/324937/
*/optional RegisterPlayerUnitEvent /* https://www.hiveworkshop.com/threads/250266/
*/optional ResourcePreloader /* https://www.hiveworkshop.com/threads/287358/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
/*
A library that eases and expands the possibilities of custom spells development.
Core Features:
1. Two-phase spell event handlers
2. Manual spell event invocation
3. Event parameters overriding and event cancelling
4. Spell development template
1.) Spell event handlers are grouped into two: generic handlers that runs for every spell, and ability-specific
handlers. All generic handlers run first. Within them, you can do things such as changing the event parameters
(caster, target, etc.) as well as preventing the ability-specific handlers from running. The second phase are
the specific handlers which are for the spell developers to define the mechanics of the spell.
Note: Generic handlers only run for spells that have existing ability-specific handlers. This is because
generic handlers are intended as custom-spell modifier, not an ability handler. If you want to catch an event
that runs for just any ability (including normal OE abilities), you can easily use (in fact, you should) the
blizzard native events instead.
2.) You can invoke a spell event to run and define the parameters manually. This removes the need for dummy
casters in most cases.
3.) As mentioned in no. 1, within the generic event handlers, you can override the event parameters. The change
will only affect the ability-specific handlers. You can also stop the ability-specific handlers from running
(known as event cancelling).
Note: Event cancelling is currently incompatible with Reforged (Crashes the game).
4.) This library provides a framework for the flow of spell through the use of modules. This removes from the
spell developers the additional work of manual spell event registration, spell instance allocation, and other
minute tasks such as storing and looping through each active spell instance.
*/
|=========|
| Credits |
|=========|
/*
- AGD (Author)
- Bribe, Nestharus (SpellEffectEvent concept)
- Anitarf (Original SpellEvent Idea)
*/
|=========|
| Structs |
|=========|
/*
*/struct Spell extends array/*
*/static constant thistype GENERIC /* You can also use this like 'Spell.GENERIC.registerEventHandlers()'
Event Responses:
*/readonly static integer abilityId /*
*/readonly static integer eventType /*
*/readonly static integer orderType /*
*/readonly static integer level /*
*/readonly static player triggerPlayer /*
*/readonly static unit triggerUnit /*
*/readonly static unit targetUnit /* Fixed a mistake in the native event responses where target is set to caster for 'No-target' abilities based on channel (Is set to null instead)
*/readonly static item targetItem /*
*/readonly static destructable targetDest /*
*/readonly static widget target /*
*/readonly static real targetX /* Returns the x-coordinate of the caster if the spell is a 'No-target' ability
*/readonly static real targetY /* Returns the y-coordinate of the caster if the spell is a 'No-target' ability
Fields:
*/readonly integer abilId/*
- Rawcode of the activation ability for the spell
*/boolean handlersDisabled/*
- (Incompatible with Reforged versions - crashes the game)
- Value is always reset to false before running the generic spell handlers
- Set to <true> to increment the internal disabler counter or <false> to decrement counter
- If counter > 0, the ability-specific handlers won't run
Methods:
*/static method operator [] takes integer abilId returns Spell/*
- Returns a Spell instance based on the given activation-ability rawcode which can be used for event handler registrations
*/method setEventFlag takes integer eventType, boolean flag returns nothing/*
*/method getEventFlag takes integer eventType returns boolean/*
- Disables/Enables certain event types from running for a Spell (These flags are <true> by default)
*/method invokeNoTargetEvent takes integer eventType, integer level, unit caster returns nothing/*
*/method invokePointTargetEvent takes integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
*/method invokeSingleTargetEvent takes integer eventType, integer level, unit caster, widget target returns nothing/*
- Manually invokes a spell event
*/static method overrideNoTargetParams takes integer level, unit caster returns nothing/*
*/static method overridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing/*
*/static method overrideSingleTargetParams takes integer level, unit caster, widget target returns nothing/*
- Overrides the values of the event response variables (Only effective when called inside a generic event handler)
- The values are only overriden in the ability-specific spell event handlers
*/method registerEventHandler takes integer eventType, code handler returns nothing/*
*/method unregisterEventHandler takes integer eventType, code handler returns nothing/*
*/method clearEventHandlers takes integer eventType returns nothing/*
*/method clearHandlers takes nothing returns nothing/*
- Manages ability-specific spell event handlers
*/static method registerGenericEventHandler takes integer eventType, code handler returns nothing/*
*/static method unregisterGenericEventHandler takes integer eventType, code handler returns nothing/*
*/static method clearGenericEventHandlers takes integer eventType returns nothing/*
*/static method clearGenericHandlers takes nothing returns nothing/*
- Manages generic spell event handlers
*/
|===========|
| Variables |
|===========|
/*
Spell Event Types
*/constant integer EVENT_SPELL_CAST/*
*/constant integer EVENT_SPELL_CHANNEL/*
*/constant integer EVENT_SPELL_EFFECT/*
*/constant integer EVENT_SPELL_ENDCAST/*
*/constant integer EVENT_SPELL_FINISH/*
Spell Order Types
*/constant integer SPELL_ORDER_TYPE_SINGLE_TARGET/*
*/constant integer SPELL_ORDER_TYPE_POINT_TARGET/*
*/constant integer SPELL_ORDER_TYPE_NO_TARGET/*
*/
|===========|
| Functions |
|===========|
/*
Equivalent functions for the methods above
(Event Responses)
*/constant function GetEventSpellAbilityId takes nothing returns integer/*
*/constant function GetEventSpellEventType takes nothing returns integer/*
*/constant function GetEventSpellOrderType takes nothing returns integer/*
*/constant function GetEventSpellLevel takes nothing returns integer/*
*/constant function GetEventSpellPlayer takes nothing returns player/*
*/constant function GetEventSpellCaster takes nothing returns unit/*
*/constant function GetEventSpellTargetUnit takes nothing returns unit/*
*/constant function GetEventSpellTargetItem takes nothing returns item/*
*/constant function GetEventSpellTargetDest takes nothing returns destructable/*
*/constant function GetEventSpellTarget takes nothing returns widget/*
*/constant function GetEventSpellTargetX takes nothing returns real/*
*/constant function GetEventSpellTargetY takes nothing returns real/*
*/function SetSpellEventFlag takes integer abilId, integer eventType, boolean flag returns nothing/*
*/function GetSpellEventFlag takes integer abilId, integer eventType returns boolean/*
*/function SpellCancelEventHandlers takes boolean cancel returns nothing/*
- This function is imcompatible with Reforged versions
*/function SpellInvokeNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing/*
*/function SpellInvokePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing/*
*/function SpellInvokeSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing/*
*/function SpellOverrideNoTargetParams takes integer level, unit caster returns nothing/*
*/function SpellOverridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing/*
*/function SpellOverrideSingleTargetParams takes integer level, unit caster, widget target returns nothing/*
*/function SpellRegisterEventHandler takes integer abilId, integer eventType, code handler returns nothing/*
*/function SpellUnregisterEventHandler takes integer abilId, integer eventType, code handler returns nothing/*
*/function SpellClearEventHandlers takes integer abilId, integer eventType returns nothing/*
*/function SpellClearHandlers takes integer abilId returns nothing/*
*/function SpellRegisterGenericEventHandler takes integer eventType, code handler returns nothing/*
*/function SpellUnregisterGenericEventHandler takes integer eventType, code handler returns nothing/*
*/function SpellClearGenericEventHandlers takes integer eventType returns nothing/*
*/function SpellClearGenericHandlers takes nothing returns nothing/*
*/
|=========|
| Modules |
|=========|
/*
Automates spell event handler registration at map initialization
Modules <SpellEvent> and <SpellEventEx> cannot both be implemented in the same struct
*/module SpellEvent extends LinkedListLite/*
> Uses a single timer (per struct) for all active spell instances. Standard module designed for
periodic spells with high-frequency timeout (<= 0.5 seconds)
Fields:
*/readonly thistype prev/*
*/readonly thistype next/*
- 'Inherited' from LinkedListLite module
- Spell instances links
- Readonly attribute is only effective outside the implementing struct, though users are
also not supposed to change these values from inside the struct.
Public methods:
*/static method registerSpellEvent takes integer abilId, integer eventType returns nothing/*
- Manually registers an ability rawcode to trigger spell events
- Can be used for spells that involve more than one activation ability IDs
Member interfaces:
- Should be declared above the module implementation
*/interface static integer SPELL_ABILITY_ID /* Ability rawcode
*/interface static integer SPELL_EVENT_TYPE /* Spell event type
*/interface static real SPELL_PERIOD /* Spell periodic actions execution period
*/interface method onSpellStart takes nothing returns thistype/*
- Runs right after the spell event fires
- Returning zero or a negative value will not run the periodic operations for that instance
- Returning a negative value will try to find that value's positive node equivalent and
calls onSpellEnd() for that node and removes it from the list.
- You can return a different value or transmute 'this', provided that all your nodes/values
comes from the same node/value stack. The important thing to remember is that all nodes in
the list should be unique. Also remember to always deallocate what you manually allocated.
- The value returned will be added to the list of instances that will run onSpellPeriodic().
*/optional interface method onSpellPeriodic takes nothing returns boolean/*
- Runs periodically after the spell event fires until it returns true
*/optional interface method onSpellEnd takes nothing returns nothing/*
- Runs after method onSpellPeriodic() returns true
- If onSpellPeriodic() is not present, this will be called after onSpellStart() returns a valid instance
*/module SpellEventEx/*
> Uses 1 timer for each active spell instance. A module specifically designed for
periodic spells with low-frequency timeout (> 0.5 seconds) as it does not affect
the accuracy of the first 'tick' of the periodic operations. Here, you always
need to manually allocate/deallocate you spell instances.
Public methods:
*/static method registerSpellEvent takes integer abilId, integer eventType returns nothing/*
- Manually registers a spell rawcode to trigger spell events
- Can be used for spells that involves more than one abilityId
Member interfaces:
- Should be declared above the module implementation
*/interface static integer SPELL_ABILITY_ID /* Ability rawcode
*/interface static integer SPELL_EVENT_TYPE /* Spell event type
*/interface static real SPELL_PERIOD /* Spell periodic actions execution period
*/interface static method onSpellStart takes nothing returns thistype/*
- Runs right after the spell event fires
- User should manually allocate the spell instance and use it as a return value of this method
- Returning zero or a negative value will not run the periodic operations for that instance
- Returning a negative value will try to find that value's positive node equivalent and calls
onSpellEnd() for that node.
*/optional interface method onSpellPeriodic takes nothing returns boolean/*
- Runs periodically after the spell event fires until it returns true
*/optional interface method onSpellEnd takes nothing returns nothing/*
- Runs after method onSpellPeriodic() returns true
- If onSpellPeriodic() is not present, this will be called after onSpellStart() returns a valid instance
- User must manually deallocate the spell instance inside this method
*/module SpellEventGeneric/*
Member interfaces:
- Should be declared above the module implementation
*/optional interface static method onSpellEvent takes nothing returns nothing/*
- Runs on any generic spell event
*/optional interface static method onSpellCast takes nothing returns nothing/*
*/optional interface static method onSpellChannel takes nothing returns nothing/*
*/optional interface static method onSpellEffect takes nothing returns nothing/*
*/optional interface static method onSpellEndcast takes nothing returns nothing/*
*/optional interface static method onSpellFinish takes nothing returns nothing/*
- Runs on certain generic spell events
*///! endnovjass
/*=================================== SYSTEM CODE ===================================*/
globals
constant integer EVENT_SPELL_CAST = 0x1
constant integer EVENT_SPELL_CHANNEL = 0x2
constant integer EVENT_SPELL_EFFECT = 0x4
constant integer EVENT_SPELL_ENDCAST = 0x8
constant integer EVENT_SPELL_FINISH = 0x10
constant integer SPELL_ORDER_TYPE_SINGLE_TARGET = 0x12
constant integer SPELL_ORDER_TYPE_POINT_TARGET = 0x123
constant integer SPELL_ORDER_TYPE_NO_TARGET = 0x1234
endglobals
globals
private integer eventAbilityId = 0
private integer eventEventType = 0
private integer eventOrderType = 0
private integer eventLevel = 0
private player eventTriggerPlayer = null
private unit eventTriggerUnit = null
private unit eventTargetUnit = null
private item eventTargetItem = null
private destructable eventTargetDest = null
private real eventTargetX = 0.00
private real eventTargetY = 0.00
private integer tempOrderType = 0
private integer tempLevel = 0
private player tempTriggerPlayer = null
private unit tempTriggerUnit = null
private widget tempTarget = null
private real tempTargetX = 0.00
private real tempTargetY = 0.00
private boolexpr bridgeExpr
private TableArray table
private integer array eventTypeId
private integer array eventIndex
endglobals
private keyword Init
static if DEBUG_MODE then
private function IsValidEventType takes integer eventType returns boolean
return eventType > 0 and eventType <= (EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
endfunction
private function IsEventSingleFlag takes integer eventType returns boolean
return eventType == EVENT_SPELL_CAST or/*
*/ eventType == EVENT_SPELL_CHANNEL or/*
*/ eventType == EVENT_SPELL_EFFECT or/*
*/ eventType == EVENT_SPELL_ENDCAST or/*
*/ eventType == EVENT_SPELL_FINISH
endfunction
private function AssertError takes boolean condition, string methodName, string structName, integer instance, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, instance, message)
else
if condition then
call BJDebugMsg("|cffff0000[ERROR]|r [Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(instance) + "] : |cffff0000" + message + "|r")
call PauseGame(true)
endif
endif
endfunction
endif
/*===================================================================================*/
private function OnOverrideParams takes nothing returns nothing
set eventOrderType = tempOrderType
set eventLevel = tempLevel
set eventTriggerPlayer = GetOwningPlayer(tempTriggerUnit)
set eventTriggerUnit = tempTriggerUnit
set eventTargetX = tempTargetX
set eventTargetY = tempTargetY
if tempTarget == null then
set eventTargetUnit = null
set eventTargetItem = null
set eventTargetDest = null
else
set table[0].widget[0] = tempTarget
set eventTargetUnit = table[0].unit[0]
set eventTargetItem = table[0].item[0]
set eventTargetDest = table[0].destructable[0]
call table[0].handle.remove(0)
endif
set tempOrderType = 0
set tempLevel = 0
set tempTriggerUnit = null
set tempTargetX = 0.00
set tempTargetY = 0.00
set tempTarget = null
endfunction
/*===================================================================================*/
/*
* One Allocator for the whole library. Yes, it would be unlikely for this system to
* reach JASS_MAX_ARRAY_SIZE instances of allocated nodes at a single time.
*
* Need to use custom Alloc because of the updated value for JASS_MAX_ARRAY_SIZE.
* Credits to MyPad for the allocation algorithm
*/
private struct Node extends array
static if LIBRARY_Alloc then
implement optional Alloc
else
private static thistype array stack
static method allocate takes nothing returns thistype
local thistype node = stack[0]
if stack[node] == 0 then
debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", "thistype", node, "Overflow")
set node = node + 1
set stack[0] = node
else
set stack[0] = stack[node]
set stack[node] = 0
endif
return node
endmethod
method deallocate takes nothing returns nothing
debug call AssertError(this == 0, "deallocate()", "thistype", 0, "Null node")
debug call AssertError(stack[this] > 0, "deallocate()", "thistype", this, "Double-free")
set stack[this] = stack[0]
set stack[0] = this
endmethod
endif
endstruct
private struct ConditionList extends array
triggercondition handle
private static method onRemove takes thistype node returns nothing
set node.handle = null
call Node(node).deallocate()
endmethod
implement List
endstruct
private struct ExprList extends array
boolexpr handle
method operator conditionList takes nothing returns ConditionList
return this
endmethod
private static method onRemove takes thistype node returns nothing
set node.handle = null
call Node(node).deallocate()
endmethod
implement List
method insertExpr takes boolexpr expr returns thistype
local thistype node = Node.allocate()
set node.handle = expr
call insert(this, node)
return node
endmethod
endstruct
private struct Handler extends array
readonly trigger trigger
boolean overrideParams
integer disablerCounter
private integer index
private static ExprList array genericList
private method operator exprList takes nothing returns ExprList
return this
endmethod
/*
* You might think that the process of registering handlers are expensive in performance
* due to constant rebuilding of triggerconditions each time, but setting up proper spell
* handlers are seldom done (often only once per spell) and a large part of them are done
* at map initialization.
*/
method updateHandlers takes nothing returns nothing
local ExprList exprNode = genericList[this.index].next
local ConditionList conditionNode
call TriggerClearConditions(this.trigger)
if exprNode != genericList[this.index].prev then
loop
exitwhen exprNode == genericList[this.index]
set conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call TriggerAddCondition(this.trigger, exprNode.handle)
set conditionNode = conditionNode.next
endloop
set exprNode = exprNode.next
endloop
endif
set exprNode = this.exprList.next
loop
exitwhen exprNode == this.exprList
set conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
set conditionNode.handle = TriggerAddCondition(this.trigger, exprNode.handle)
set conditionNode = conditionNode.next
endloop
set exprNode = exprNode.next
endloop
endmethod
/*
* This method is registered in position after all the generic handlers and before the
* ability-specific handlers. Its position allows it to unlink all the ability-specific
* handlers positioned after it or to change the event parameters, should the user choose to
*/
private static method bridge takes nothing returns nothing
local integer triggerId = GetHandleId(GetTriggeringTrigger())
local thistype node = table[0][triggerId]
local trigger tempTrig
if node.disablerCounter > 0 then
if node.exprList.next != node.exprList then
set tempTrig = node.trigger
set node.trigger = CreateTrigger()
set table[0][GetHandleId(node.trigger)] = node
call node.updateHandlers()
call table[0].remove(triggerId)
call TriggerClearConditions(tempTrig)
call DestroyTrigger(tempTrig)
set tempTrig = null
endif
return
endif
if node.overrideParams and node.exprList.next != node.exprList then
call OnOverrideParams()
endif
endmethod
static method registerGeneric takes integer eventIndex, boolexpr expr returns nothing
local integer exprId = GetHandleId(expr)
local ExprList exprNode = table[genericList[eventIndex]][exprId]
if exprNode == 0 then
set exprNode = genericList[eventIndex].prev.prev.insertExpr(expr)
call ConditionList.makeHead(exprNode.conditionList)
set table[genericList[eventIndex]][exprId] = exprNode
endif
call exprNode.conditionList.pushBack(Node.allocate())
endmethod
static method unregisterGeneric takes integer eventIndex, integer exprId returns nothing
local ExprList exprNode = table[genericList[eventIndex]][exprId]
local ConditionList conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call ConditionList.remove(conditionNode)
set conditionNode = conditionNode.next
endloop
call table[genericList[eventIndex]].remove(exprId)
call exprList.remove(exprNode)
endmethod
static method clearGeneric takes integer eventIndex returns nothing
local ExprList exprNode = genericList[eventIndex].next
loop
exitwhen exprNode == genericList[eventIndex].prev
call unregisterGeneric(eventIndex, GetHandleId(exprNode.handle))
set exprNode = exprNode.next
endloop
endmethod
method register takes boolexpr expr returns nothing
local integer exprId = GetHandleId(expr)
local ExprList exprNode = table[this][exprId]
local ConditionList conditionNode = Node.allocate()
if this.exprList.empty then
set this.trigger = CreateTrigger()
set table[0][GetHandleId(this.trigger)] = this
endif
if exprNode == 0 then
set exprNode = this.exprList.prev.insertExpr(expr)
call ConditionList.makeHead(exprNode.conditionList)
set table[this][exprId] = exprNode
call exprNode.conditionList.pushBack(conditionNode)
call this.updateHandlers()
else
call exprNode.conditionList.pushBack(conditionNode)
if exprNode.next == this.exprList then
set conditionNode.handle = TriggerAddCondition(this.trigger, expr)
else
call this.updateHandlers()
endif
endif
endmethod
method unregister takes integer exprId returns nothing
local ExprList exprNode = table[this][exprId]
local ConditionList conditionNode = exprNode.conditionList.next
loop
exitwhen conditionNode == exprNode.conditionList
call TriggerRemoveCondition(this.trigger, conditionNode.handle)
call ConditionList.remove(conditionNode)
set conditionNode = conditionNode.next
endloop
call ExprList.remove(exprNode)
call table[this].remove(exprId)
if this.exprList.empty then
call table[0].remove(GetHandleId(this.trigger))
call DestroyTrigger(this.trigger)
set this.trigger = null
endif
endmethod
method clear takes nothing returns nothing
local ExprList exprNode = this.exprList.next
loop
exitwhen exprNode == this.exprList
call this.unregister(GetHandleId(exprNode.handle))
set exprNode = exprNode.next
endloop
endmethod
static method create takes integer eventIndex returns thistype
local thistype node = Node.allocate()
call ExprList.makeHead(node)
set node.index = eventIndex
set node.disablerCounter = 0
return node
endmethod
method destroy takes nothing returns nothing
call this.clear()
call Node(this).deallocate()
endmethod
debug static method hasGenericExpr takes integer eventIndex, boolexpr expr returns boolean
debug return table[genericList[eventIndex]][GetHandleId(expr)] != 0
debug endmethod
debug method hasExpr takes boolexpr expr returns boolean
debug return table[this][GetHandleId(expr)] != 0
debug endmethod
method operator enabled= takes boolean flag returns nothing
if flag then
call EnableTrigger(this.trigger)
else
call DisableTrigger(this.trigger)
endif
endmethod
method operator enabled takes nothing returns boolean
return IsTriggerEnabled(this.trigger)
endmethod
private static method initGenericList takes integer eventIndex returns nothing
local ExprList list = create(eventIndex)
local ExprList exprNode = list.insertExpr(bridgeExpr)
call ConditionList.makeHead(exprNode.conditionList)
call exprNode.conditionList.pushBack(Node.allocate())
set genericList[eventIndex] = list
endmethod
static method init takes nothing returns nothing
/*
* This bridge boolexpr executes after all the generic spell handlers
* before transitioning into the ability-specific spell handlers.
* This boolexpr is responsible for disabling the ability-specific handlers
* (if requested) as well as implementing the overriding of the event
* parameters.
*/
local code bridgeFunc = function thistype.bridge
set bridgeExpr = Filter(bridgeFunc)
call initGenericList(eventIndex[EVENT_SPELL_CAST])
call initGenericList(eventIndex[EVENT_SPELL_CHANNEL])
call initGenericList(eventIndex[EVENT_SPELL_EFFECT])
call initGenericList(eventIndex[EVENT_SPELL_ENDCAST])
call initGenericList(eventIndex[EVENT_SPELL_FINISH])
endmethod
endstruct
/*===================================================================================*/
struct Spell extends array
readonly integer abilId
private static integer spellCount = 0
private static Node spellKey
private static Handler array eventHandler
static if not LIBRARY_ResourcePreloader then
private static unit preloadDummy
endif
static constant method operator abilityId takes nothing returns integer
return eventAbilityId
endmethod
static constant method operator eventType takes nothing returns integer
return eventEventType
endmethod
static constant method operator orderType takes nothing returns integer
return eventOrderType
endmethod
static constant method operator level takes nothing returns integer
return eventLevel
endmethod
static constant method operator triggerPlayer takes nothing returns player
return eventTriggerPlayer
endmethod
static constant method operator triggerUnit takes nothing returns unit
return eventTriggerUnit
endmethod
static constant method operator targetUnit takes nothing returns unit
return eventTargetUnit
endmethod
static constant method operator targetItem takes nothing returns item
return eventTargetItem
endmethod
static constant method operator targetDest takes nothing returns destructable
return eventTargetDest
endmethod
static constant method operator target takes nothing returns widget
if eventTargetUnit != null then
return eventTargetUnit
elseif eventTargetItem != null then
return eventTargetItem
elseif eventTargetDest != null then
return eventTargetDest
endif
return null
endmethod
static constant method operator targetX takes nothing returns real
return eventTargetX
endmethod
static constant method operator targetY takes nothing returns real
return eventTargetY
endmethod
static method operator [] takes integer abilId returns thistype
local thistype this = table[spellKey][abilId]
local integer offset
if this == 0 then
debug call AssertError(spellCount > R2I(JASS_MAX_ARRAY_SIZE/5), "Spell[]", "thistype", 0, "Overflow")
static if LIBRARY_ResourcePreloader then
call PreloadAbility(abilId)
else
if UnitAddAbility(preloadDummy, abilId) then
call UnitRemoveAbility(preloadDummy, abilId)
endif
endif
set spellCount = spellCount + 1
set thistype(spellCount).abilId = abilId
set table[spellKey][abilId] = spellCount
set offset = (spellCount - 1)*5
set eventHandler[offset + eventIndex[EVENT_SPELL_CAST]] = Handler.create(eventIndex[EVENT_SPELL_CAST])
set eventHandler[offset + eventIndex[EVENT_SPELL_CHANNEL]] = Handler.create(eventIndex[EVENT_SPELL_CHANNEL])
set eventHandler[offset + eventIndex[EVENT_SPELL_EFFECT]] = Handler.create(eventIndex[EVENT_SPELL_EFFECT])
set eventHandler[offset + eventIndex[EVENT_SPELL_ENDCAST]] = Handler.create(eventIndex[EVENT_SPELL_ENDCAST])
set eventHandler[offset + eventIndex[EVENT_SPELL_FINISH]] = Handler.create(eventIndex[EVENT_SPELL_FINISH])
return spellCount
endif
return this
endmethod
static method operator GENERIC takes nothing returns thistype
return thistype[0]
endmethod
static method registerGenericEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer eventId = 0x10
local integer node
if eventType != 0 then
debug call AssertError(not IsValidEventType(eventType), "registerGenericEventHandler()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call Handler.registerGeneric(eventIndex[eventId], expr)
set node = spellCount
loop
exitwhen node == 0
set node = node - 1
call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
endloop
endif
set eventId = eventId/2
endloop
endif
set expr = null
endmethod
static method unregisterGenericEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer eventId = 0x10
local integer node
if eventType != 0 then
debug call AssertError(not IsValidEventType(eventType), "unregisterGenericEventHandler()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
debug call AssertError(not Handler.hasGenericExpr(eventIndex[eventId], expr), "unregisterGenericEventHandler()", "thistype", 0, "EventType(" + I2S(eventType) + "): Code is not registered")
call Handler.unregisterGeneric(eventIndex[eventId], GetHandleId(expr))
set node = spellCount
loop
exitwhen node == 0
set node = node - 1
call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
endloop
endif
set eventId = eventId/2
endloop
endif
set expr = null
endmethod
static method clearGenericEventHandlers takes integer eventType returns nothing
local integer eventId = 0x10
local integer node
if eventType != 0 then
debug call AssertError(not IsValidEventType(eventType), "clearGenericEventHandlers()", "thistype", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call Handler.clearGeneric(eventIndex[eventId])
set node = spellCount
loop
exitwhen node == 0
set node = node - 1
call eventHandler[node*5 + eventIndex[eventId]].updateHandlers()
endloop
endif
set eventId = eventId/2
endloop
endif
endmethod
static method clearGenericHandlers takes nothing returns nothing
call clearGenericEventHandlers(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
endmethod
method registerEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer offset = (this - 1)*5
local integer eventId = 0x10
if eventType != 0 then
debug call AssertError((this) < 1 or (this) > spellCount, "registerEventHandler()", "thistype", this, "Invalid Spell instance")
debug call AssertError(not IsValidEventType(eventType), "registerEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
if this == GENERIC then
call registerGenericEventHandler(eventType, handler)
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call eventHandler[offset + eventIndex[eventId]].register(expr)
endif
set eventId = eventId/2
endloop
endif
endif
set expr = null
endmethod
method unregisterEventHandler takes integer eventType, code handler returns nothing
local boolexpr expr = Filter(handler)
local integer offset = (this - 1)*5
local integer eventId = 0x10
if eventType != 0 then
debug call AssertError((this) < 1 or (this) > spellCount, "unregisterEventHandler()", "thistype", this, "Invalid Spell instance")
debug call AssertError(not IsValidEventType(eventType), "unregisterEventHandler()", "thistype", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
if this == GENERIC then
call unregisterGenericEventHandler(eventType, handler)
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
debug call AssertError(not eventHandler[offset + eventIndex[eventId]].hasExpr(expr), "registerEventHandler()", "thistype", this, "EventType(" + I2S(eventType) + "): Code is already unregistered")
call eventHandler[offset + eventIndex[eventId]].unregister(GetHandleId(expr))
endif
set eventId = eventId/2
endloop
endif
endif
set expr = null
endmethod
method clearEventHandlers takes integer eventType returns nothing
local integer offset = (this - 1)*5
local integer eventId = 0x10
if eventType != 0 then
debug call AssertError((this) < 1 or (this) > spellCount, "SpellEvent", "clearEventHandlers()", this, "Invalid Spell instance")
debug call AssertError(not IsValidEventType(eventType), "SpellEvent", "clearEventHandlers()", this, "Invalid Spell Event Type (" + I2S(eventType) + ")")
if this == GENERIC then
call clearGenericEventHandlers(eventType)
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
call eventHandler[offset + eventIndex[eventId]].clear()
endif
set eventId = eventId/2
endloop
endif
endif
endmethod
method clearHandlers takes nothing returns nothing
debug call AssertError((this) < 1 or (this) > spellCount, "clearHandlers()", "thistype", this, "Invalid Spell instance")
if this == GENERIC then
call this.clearGenericHandlers()
else
call this.clearEventHandlers(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH)
endif
endmethod
method setEventFlag takes integer eventType, boolean flag returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "setEventFlag()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
set eventHandler[(this - 1)*5 + eventIndex[eventType]].enabled = flag
endmethod
method getEventFlag takes integer eventType returns boolean
debug call AssertError(not IsEventSingleFlag(eventType), "getEventFlag()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
return eventHandler[(this - 1)*5 + eventIndex[eventType]].enabled
endmethod
method operator handlersDisabled= takes boolean disabled returns nothing
local Handler handler
if eventAbilityId == this.abilId then
set handler = eventHandler[(this - 1)*5 + eventIndex[eventEventType]]
if disabled then
set handler.disablerCounter = handler.disablerCounter + 1
else
set handler.disablerCounter = handler.disablerCounter - 1
endif
endif
endmethod
method operator handlersDisabled takes nothing returns boolean
if eventAbilityId != this.abilId then
return false
endif
return eventHandler[(this - 1)*5 + eventIndex[eventEventType]].disablerCounter > 0
endmethod
private static method overrideParams takes integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
if eventAbilityId != 0 then
set Handler(table[0][GetHandleId(GetTriggeringTrigger())]).overrideParams = true
set tempOrderType = orderType
set tempLevel = level
set tempTriggerPlayer = GetOwningPlayer(triggerUnit)
set tempTriggerUnit = triggerUnit
set tempTargetX = targetX
set tempTargetY = targetY
set tempTarget = target
endif
endmethod
static method overrideNoTargetParams takes integer level, unit triggerUnit returns nothing
call overrideParams(SPELL_ORDER_TYPE_NO_TARGET, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
endmethod
static method overridePointTargetParams takes integer level, unit triggerUnit, real targetX, real targetY returns nothing
call overrideParams(SPELL_ORDER_TYPE_POINT_TARGET, level, triggerUnit, null, targetX, targetY)
endmethod
static method overrideSingleTargetParams takes integer level, unit triggerUnit, widget target returns nothing
call overrideParams(SPELL_ORDER_TYPE_SINGLE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
endmethod
private static method setTargetData takes real x, real y, integer orderType returns nothing
set eventTargetX = x
set eventTargetY = y
set eventOrderType = orderType
endmethod
private static method executeEventHandler takes Handler eventHandler, integer currentId, boolean manualInvoke, integer eventFlag, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
local integer disablerCounter = eventHandler.disablerCounter
local boolean overrideParams = eventHandler.overrideParams
local integer prevAbilityId = eventAbilityId
local integer prevEventType = eventEventType
local integer prevOrderType = eventOrderType
local integer prevLevel = eventLevel
local player prevTriggerPlayer = eventTriggerPlayer
local unit prevTriggerUnit = eventTriggerUnit
local real prevTargetX = eventTargetX
local real prevTargetY = eventTargetY
local unit prevTargetUnit = eventTargetUnit
local item prevTargetItem = eventTargetItem
local destructable prevTargetDest = eventTargetDest
local location tempLoc
set eventAbilityId = currentId
if manualInvoke then
call setTargetData(targetX, targetY, orderType)
set eventEventType = eventFlag
set eventLevel = level
set eventTriggerPlayer = GetOwningPlayer(triggerUnit)
set eventTriggerUnit = triggerUnit
set table[0].widget[0] = target
set eventTargetUnit = table[0].unit[0]
set eventTargetItem = table[0].item[0]
set eventTargetDest = table[0].destructable[0]
else
set eventEventType = eventTypeId[GetHandleId(GetTriggerEventId())]
set eventTriggerPlayer = GetTriggerPlayer()
set eventTriggerUnit = GetTriggerUnit()
set eventLevel = GetUnitAbilityLevel(eventTriggerUnit, eventAbilityId)
set eventTargetUnit = GetSpellTargetUnit()
set eventTargetItem = GetSpellTargetItem()
set eventTargetDest = GetSpellTargetDestructable()
if eventTargetUnit != null then
if eventTargetUnit == eventTriggerUnit and/*
*/ not (GetSpellTargetX() != 0.) and/*
*/ not (GetSpellTargetY() != 0.) then
/* Special Case (for no-target spells based on channel) */
call setTargetData(GetUnitX(eventTriggerUnit), GetUnitY(eventTriggerUnit), SPELL_ORDER_TYPE_NO_TARGET)
set eventTargetUnit = null
else
call setTargetData(GetUnitX(eventTargetUnit), GetUnitY(eventTargetUnit), SPELL_ORDER_TYPE_SINGLE_TARGET)
endif
elseif eventTargetItem != null then
call setTargetData(GetWidgetX(eventTargetItem), GetWidgetY(eventTargetItem), SPELL_ORDER_TYPE_SINGLE_TARGET)
elseif eventTargetDest != null then
call setTargetData(GetWidgetX(eventTargetDest), GetWidgetY(eventTargetDest), SPELL_ORDER_TYPE_SINGLE_TARGET)
else
set tempLoc = GetSpellTargetLoc()
if tempLoc == null then
/* Special Case (for some no-target spells) */
call setTargetData(GetUnitX(eventTriggerUnit), GetUnitY(eventTriggerUnit), SPELL_ORDER_TYPE_NO_TARGET)
else
call RemoveLocation(tempLoc)
set tempLoc = null
call setTargetData(GetSpellTargetX(), GetSpellTargetY(), SPELL_ORDER_TYPE_POINT_TARGET)
endif
endif
endif
set eventHandler.disablerCounter = 0
set eventHandler.overrideParams = false
call TriggerEvaluate(eventHandler.trigger)
set eventHandler.disablerCounter = disablerCounter
set eventHandler.overrideParams = overrideParams
call setTargetData(prevTargetX, prevTargetY, prevOrderType)
set eventAbilityId = prevAbilityId
set eventEventType = prevEventType
set eventLevel = prevLevel
set eventTriggerPlayer = prevTriggerPlayer
set eventTriggerUnit = prevTriggerUnit
set eventTargetUnit = prevTargetUnit
set eventTargetItem = prevTargetItem
set eventTargetDest = prevTargetDest
set prevTriggerPlayer = null
set prevTriggerUnit = null
set prevTargetUnit = null
set prevTargetItem = null
set prevTargetDest = null
endmethod
private method invokeEvent takes integer eventType, integer orderType, integer level, unit triggerUnit, widget target, real targetX, real targetY returns nothing
local Handler handler = eventHandler[(this - 1)*5 + eventIndex[eventType]]
if handler != 0 and handler.enabled then
call executeEventHandler(handler, this.abilId, true, eventType, orderType, level, triggerUnit, target, targetX, targetY)
endif
endmethod
method invokeNoTargetEvent takes integer eventType, integer level, unit triggerUnit returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "invokeNoTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
call this.invokeEvent(eventType, SPELL_ORDER_TYPE_NO_TARGET, level, triggerUnit, null, GetUnitX(triggerUnit), GetUnitY(triggerUnit))
endmethod
method invokePointTargetEvent takes integer eventType, integer level, unit triggerUnit, real targetX, real targetY returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "invokePointTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
call this.invokeEvent(eventType, SPELL_ORDER_TYPE_POINT_TARGET, level, triggerUnit, null, targetX, targetY)
endmethod
method invokeSingleTargetEvent takes integer eventType, integer level, unit triggerUnit, widget target returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "invokeSingleTargetEvent()", "thistype", this, "Spell Event Type does not contain a single flag (" + I2S(eventType) + ")")
call this.invokeEvent(eventType, SPELL_ORDER_TYPE_SINGLE_TARGET, level, triggerUnit, target, GetWidgetX(target), GetWidgetY(target))
endmethod
private static method onSpellEvent takes integer eventIndex returns nothing
local integer id = GetSpellAbilityId()
local Handler handler = eventHandler[(table[spellKey][id] - 1)*5 + eventIndex]
if handler != 0 and handler.enabled then
call executeEventHandler(handler, id, false, 0, 0, 0, null, null, 0.00, 0.00)
endif
endmethod
private static method onSpellCast takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_CAST])
endmethod
private static method onSpellChannel takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_CHANNEL])
endmethod
private static method onSpellEffect takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_EFFECT])
endmethod
private static method onSpellEndcast takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_ENDCAST])
endmethod
private static method onSpellFinish takes nothing returns nothing
call onSpellEvent(eventIndex[EVENT_SPELL_FINISH])
endmethod
private static method registerEvent takes playerunitevent whichEvent, code handler returns nothing
static if LIBRARY_RegisterPlayerUnitEvent then
call RegisterAnyPlayerUnitEvent(whichEvent, handler)
else
local trigger t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, whichEvent)
call TriggerAddCondition(t, Filter(handler))
set t = null
endif
endmethod
static if not LIBRARY_ResourcePreloader then
private static method initPreloadDummy takes nothing returns nothing
local rect world = GetWorldBounds()
set preloadDummy = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'hpea', 0.00, 0.00, 0.00)
call SetUnitY(preloadDummy, GetRectMaxY(world) + 1000.00)
call UnitAddAbility(preloadDummy, 'AInv')
call UnitAddAbility(preloadDummy, 'Avul')
call UnitRemoveAbility(preloadDummy, 'Amov')
call RemoveRect(world)
set world = null
endmethod
endif
private static method init takes nothing returns nothing
set spellKey = Node.allocate()
set spellCount = spellCount + 1
set table[spellKey][0] = spellCount
set eventIndex[EVENT_SPELL_CAST] = 1
set eventIndex[EVENT_SPELL_CHANNEL] = 2
set eventIndex[EVENT_SPELL_EFFECT] = 3
set eventIndex[EVENT_SPELL_ENDCAST] = 4
set eventIndex[EVENT_SPELL_FINISH] = 5
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CAST)] = EVENT_SPELL_CAST
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_CHANNEL)] = EVENT_SPELL_CHANNEL
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_EFFECT)] = EVENT_SPELL_EFFECT
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_ENDCAST)] = EVENT_SPELL_ENDCAST
set eventTypeId[GetHandleId(EVENT_PLAYER_UNIT_SPELL_FINISH)] = EVENT_SPELL_FINISH
call registerEvent(EVENT_PLAYER_UNIT_SPELL_CAST, function thistype.onSpellCast)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_CHANNEL, function thistype.onSpellChannel)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onSpellEffect)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_ENDCAST, function thistype.onSpellEndcast)
call registerEvent(EVENT_PLAYER_UNIT_SPELL_FINISH, function thistype.onSpellFinish)
static if not LIBRARY_ResourcePreloader then
call initPreloadDummy()
endif
endmethod
implement Init
endstruct
private module Init
private static method onInit takes nothing returns nothing
set table = TableArray[JASS_MAX_ARRAY_SIZE]
call init()
call Handler.init()
endmethod
endmodule
/*===================================================================================*/
constant function GetEventSpellAbilityId takes nothing returns integer
return Spell.abilityId
endfunction
constant function GetEventSpellEventType takes nothing returns integer
return Spell.eventType
endfunction
constant function GetEventSpellOrderType takes nothing returns integer
return Spell.orderType
endfunction
constant function GetEventSpellLevel takes nothing returns integer
return Spell.level
endfunction
constant function GetEventSpellPlayer takes nothing returns player
return Spell.triggerPlayer
endfunction
constant function GetEventSpellCaster takes nothing returns unit
return Spell.triggerUnit
endfunction
constant function GetEventSpellTargetUnit takes nothing returns unit
return Spell.targetUnit
endfunction
constant function GetEventSpellTargetItem takes nothing returns item
return Spell.targetItem
endfunction
constant function GetEventSpellTargetDest takes nothing returns destructable
return Spell.targetDest
endfunction
constant function GetEventSpellTarget takes nothing returns widget
return Spell.target
endfunction
constant function GetEventSpellTargetX takes nothing returns real
return Spell.targetX
endfunction
constant function GetEventSpellTargetY takes nothing returns real
return Spell.targetY
endfunction
function SetSpellEventFlag takes integer abilId, integer eventType, boolean flag returns nothing
debug call AssertError(not IsEventSingleFlag(eventType), "SetSpellEventFlag()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].setEventFlag(eventType, flag)
endfunction
function GetSpellEventFlag takes integer abilId, integer eventType returns boolean
debug call AssertError(not IsEventSingleFlag(eventType), "GetSpellEventFlag()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
return Spell[abilId].getEventFlag(eventType)
endfunction
function SpellCancelEventHandlers takes boolean cancel returns nothing
set Spell[GetEventSpellAbilityId()].handlersDisabled = cancel
endfunction
function SpellInvokeNoTargetEvent takes integer abilId, integer eventType, integer level, unit caster returns nothing
call Spell[abilId].invokeNoTargetEvent(eventType, level, caster)
endfunction
function SpellInvokePointTargetEvent takes integer abilId, integer eventType, integer level, unit caster, real targetX, real targetY returns nothing
call Spell[abilId].invokePointTargetEvent(eventType, level, caster, targetX, targetY)
endfunction
function SpellInvokeSingleTargetEvent takes integer abilId, integer eventType, integer level, unit caster, widget target returns nothing
call Spell[abilId].invokeSingleTargetEvent(eventType, level, caster, target)
endfunction
function SpellOverrideNoTargetParams takes integer level, unit caster returns nothing
call Spell.overrideNoTargetParams(level, caster)
endfunction
function SpellOverridePointTargetParams takes integer level, unit caster, real targetX, real targetY returns nothing
call Spell.overridePointTargetParams(level, caster, targetX, targetY)
endfunction
function SpellOverrideSingleTargetParams takes integer level, unit caster, widget target returns nothing
call Spell.overrideSingleTargetParams(level, caster, target)
endfunction
function SpellRegisterEventHandler takes integer abilId, integer eventType, code handler returns nothing
debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellRegisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].registerEventHandler(eventType, handler)
endfunction
function SpellUnregisterEventHandler takes integer abilId, integer eventType, code handler returns nothing
debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellUnregisterEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].unregisterEventHandler(eventType, handler)
endfunction
function SpellClearEventHandlers takes integer abilId, integer eventType returns nothing
debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellClearEventHandler()", "", 0, "Spell(" + I2S(abilId) + "): Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell[abilId].clearEventHandlers(eventType)
endfunction
function SpellClearHandlers takes integer abilId returns nothing
call Spell[abilId].clearHandlers()
endfunction
function SpellRegisterGenericEventHandler takes integer eventType, code handler returns nothing
debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellRegisterGenericEventHandler()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell.registerGenericEventHandler(eventType, handler)
endfunction
function SpellUnregisterGenericEventHandler takes integer eventType, code handler returns nothing
debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellUnregisterGenericEventHandler()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell.unregisterGenericEventHandler(eventType, handler)
endfunction
function SpellClearGenericEventHandlers takes integer eventType returns nothing
debug call AssertError(eventType != 0 and not IsValidEventType(eventType), "SpellClearGenericEventHandlers()", "", 0, "Invalid Spell Event Type (" + I2S(eventType) + ")")
call Spell.clearGenericEventHandlers(eventType)
endfunction
function SpellClearGenericHandlers takes nothing returns nothing
call Spell.clearGenericHandlers()
endfunction
/*===================================================================================*/
private function DestroyTimerEx takes timer whichTimer returns nothing
call PauseTimer(whichTimer)
call DestroyTimer(whichTimer)
endfunction
private function TimerStartEx takes integer node, real period, code callback returns integer
local timer t
if node > 0 then
set t = CreateTimer()
set table[0].timer[node] = t
set table[0][GetHandleId(t)] = node
call TimerStart(t, period, true, callback)
set t = null
elseif node < 0 then
set node = -node
if table[0].handle.has(node) then
set t = table[0].timer[node]
call table[0].handle.remove(node)
call table[0].remove(GetHandleId(t))
call DestroyTimerEx(t)
set t = null
return node
endif
endif
return 0
endfunction
private function RegisterSpell takes integer abilId, integer eventType, code onSpellEvent returns nothing
if abilId != 0 then
call SpellRegisterEventHandler(abilId, eventType, onSpellEvent)
endif
endfunction
module SpellEvent
static if thistype.onSpellPeriodic.exists then
implement LinkedListLite
private boolean internal
private static timer periodicTimer
private method deleteNode takes nothing returns nothing
call remove(this)
static if thistype.onSpellEnd.exists then
call this.onSpellEnd()
endif
if this.internal then
set this.internal = false
call Node(this).deallocate()
endif
if thistype(0).next == 0 then
call DestroyTimerEx(periodicTimer)
endif
endmethod
private static method onPeriodic takes nothing returns nothing
local thistype node = thistype(0).next
local thistype next
debug local thistype prev
debug local boolean array traversed
/*
* Additional safety check
*/
if node == 0 then
call DestroyTimerEx(periodicTimer)
return
endif
loop
exitwhen node == 0
debug call AssertError(traversed[node], "[Internal] onSpellPeriodic()", "thistype", node, "[Node Links Bugged] : A node in the list was traversed twice in one traversal")
debug set traversed[node] = true
debug set prev = node.prev
set next = node.next
if node.onSpellPeriodic() then
debug call AssertError(node.next.prev != node, "[Internal onSpellPeriodic()", "thistype", node, "[Node Links Bugged] : Node links are messed up!")
call node.deleteNode()
debug set traversed[node] = false
debug elseif next.prev == prev then
debug set traversed[node] = false
endif
set node = next
endloop
endmethod
private static method onSpellEvent takes nothing returns nothing
local thistype node = Node.allocate()
local boolean prevEmpty = thistype(0).next == 0
local thistype used = node.onSpellStart()
/*
* Add the new node into the list
*/
if used == node then
debug call AssertError(used.next.prev == used, "[Internal] onSpellStart()", "thistype", used, "[Node Already Exists] : Make sure your nodes are all from the same stack..")
set used.internal = true
call insert(thistype(0).prev, used)
/*
* If the user returned a different node than the one he was given,
* deallocate the earlier node and replace it with the new node
* from the user.
*/
else
call Node(node).deallocate()
if used > 0 then
debug call AssertError(used.next.prev == used, "[Internal] onSpellStart()", "thistype", used, "[Node Already Exists] : Make sure your nodes are all from the same stack..")
call insert(thistype(0).prev, used)
elseif used < 0 then
set used = -used
if used.next.prev == used then
call used.deleteNode()
endif
endif
endif
/*
* We need to use this kind of check in case the user returned 0
* but manually added some node in the list inside onSpellStart()
*/
if prevEmpty and thistype(0).next != 0 then
set periodicTimer = CreateTimer()
call TimerStart(periodicTimer, SPELL_PERIOD, true, function thistype.onPeriodic)
endif
endmethod
else
/*
* If no periodic operations
*/
private static method onSpellEvent takes nothing returns nothing
local thistype node = Node.allocate()
static if thistype.onSpellEnd.exists then
local thistype used = node.onSpellStart()
if used > 0 then
call used.onSpellEnd()
endif
else
call node.onSpellStart()
endif
call Node(node).deallocate()
endmethod
endif
private static method onInit takes nothing returns nothing
call RegisterSpell(SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
static if thistype.onSpellEventModuleInit.exists then
call onSpellEventModuleInit()
endif
endmethod
static method registerSpellEvent takes integer abilId, integer eventType returns nothing
call SpellRegisterEventHandler(abilId, eventType, function thistype.onSpellEvent)
endmethod
endmodule
module SpellEventEx
static if thistype.onSpellPeriodic.exists then
private static method onPeriodic takes nothing returns nothing
local thistype node = table[0][GetHandleId(GetExpiredTimer())]
if node.onSpellPeriodic() then
static if thistype.onSpellEnd.exists then
call node.onSpellEnd()
endif
call TimerStartEx(-node, 0.00, null)
endif
endmethod
endif
private static method onSpellEvent takes nothing returns nothing
static if thistype.onSpellPeriodic.exists then
static if thistype.onSpellEnd.exists then
local thistype node = TimerStartEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
if node > 0 then
call node.onSpellEnd()
endif
else
call TimerStartEx(onSpellStart(), SPELL_PERIOD, function thistype.onPeriodic)
endif
elseif thistype.onSpellEnd.exists then
local thistype node = onSpellStart()
if node > 0 then
call node.onSpellEnd()
endif
else
call onSpellStart()
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterSpell(SPELL_ABILITY_ID, SPELL_EVENT_TYPE, function thistype.onSpellEvent)
static if thistype.onSpellEventModuleInit.exists then
call onSpellEventModuleInit()
endif
endmethod
static method registerSpellEvent takes integer abilId, integer eventType returns nothing
call SpellRegisterEventHandler(abilId, eventType, function thistype.onSpellEvent)
endmethod
endmodule
module SpellEventGeneric
private static method onSpellResponse takes nothing returns nothing
static if thistype.onSpellEvent.exists then
call onSpellEvent()
endif
static if thistype.onSpellCast.exists then
if GetEventSpellEventType() == EVENT_SPELL_CAST then
call onSpellCast()
endif
endif
static if thistype.onSpellChannel.exists then
if GetEventSpellEventType() == EVENT_SPELL_CHANNEL then
call onSpellChannel()
endif
endif
static if thistype.onSpellEffect.exists then
if GetEventSpellEventType() == EVENT_SPELL_EFFECT then
call onSpellEffect()
endif
endif
static if thistype.onSpellEndcast.exists then
if GetEventSpellEventType() == EVENT_SPELL_ENDCAST then
call onSpellEndcast()
endif
endif
static if thistype.onSpellFinish.exists then
if GetEventSpellEventType() == EVENT_SPELL_FINISH then
call onSpellFinish()
endif
endif
endmethod
private static method onInit takes nothing returns nothing
call SpellRegisterGenericEventHandler(EVENT_SPELL_CAST + EVENT_SPELL_CHANNEL + EVENT_SPELL_EFFECT + EVENT_SPELL_ENDCAST + EVENT_SPELL_FINISH, function thistype.onSpellResponse)
static if thistype.onSpellEventGenericModuleInit.exists then
call onSpellEventGenericModuleInit()
endif
endmethod
endmodule
endlibrary
library SpellEventGUI /* v1.0.3
*/uses /*
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/Table /* https://www.hiveworkshop.com/threads/188084/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
|=========|
| CREDITS |
|=========|
/*
- AGD (Author)
- Bribe (SpellSystem GUI, some features of which I adapted into this GUI Plugin)
*///! endnovjass
/*=============================================================================================*/
native UnitAlive takes unit u returns boolean
globals
private constant integer STOP_ORDER_ID = 851972
private integer array nodeAbilityId
private integer array nodeEventType
private integer array nodeOrderType
private integer array nodeLevel
private player array nodeTriggerPlayer
private unit array nodeTriggerUnit
private real array nodeTargetX
private real array nodeTargetY
private location array nodeTargetPoint
private unit array nodeTargetUnit
private item array nodeTargetItem
private destructable array nodeTargetDest
private integer array filterInstance
private integer array eventIndex
private location staticLocation = Location(0.00, 0.00)
private group enumGroup = CreateGroup()
private integer prevEnumCount = 0
endglobals
private function ResolveFilterFlag takes boolean state, boolean allowTrue, boolean allowFalse returns boolean
return (not state or allowTrue) and (state or allowFalse)
endfunction
private function GetTarget takes nothing returns widget
if udg_SPELL__TargetUnit != null then
return udg_SPELL__TargetUnit
elseif udg_SPELL__TargetItem != null then
return udg_SPELL__TargetItem
elseif udg_SPELL__TargetDest != null then
return udg_SPELL__TargetDest
endif
return null
endfunction
private function GetLevel takes nothing returns integer
if udg_SPELL__Level == 0 then
return R2I(udg_SPELL__RealLevel)
endif
return udg_SPELL__Level
endfunction
private function InitEventParams takes integer abilityId, integer eventType, integer orderType, integer level, player triggerPlayer, unit triggerUnit, real targetX, real targetY, location targetPoint, unit targetUnit, item targetItem, destructable targetDest returns nothing
call MoveLocation(targetPoint, targetX, targetY)
set udg_SPELL__Ability = abilityId
set udg_SPELL__EventType = eventType
set udg_SPELL__OrderType = orderType
set udg_SPELL__Level = level
set udg_SPELL__RealLevel = level
set udg_SPELL__TriggerPlayer = triggerPlayer
set udg_SPELL__TriggerUnit = triggerUnit
set udg_SPELL__TargetPoint = targetPoint
set udg_SPELL__TargetUnit = targetUnit
set udg_SPELL__TargetItem = targetItem
set udg_SPELL__TargetDest = targetDest
endfunction
private function LoadEventParams takes integer node returns nothing
call InitEventParams(/*
*/nodeAbilityId[node], /*
*/nodeEventType[node], /*
*/nodeOrderType[node], /*
*/nodeLevel[node], /*
*/nodeTriggerPlayer[node], /*
*/nodeTriggerUnit[node], /*
*/nodeTargetX[node], /*
*/nodeTargetY[node], /*
*/nodeTargetPoint[node], /*
*/nodeTargetUnit[node], /*
*/nodeTargetItem[node], /*
*/nodeTargetDest[node])
endfunction
private function SaveEventParams takes integer node returns nothing
set nodeAbilityId[node] = udg_SPELL__Ability
set nodeEventType[node] = udg_SPELL__EventType
set nodeOrderType[node] = udg_SPELL__OrderType
set nodeLevel[node] = udg_SPELL__Level
set nodeTriggerPlayer[node] = udg_SPELL__TriggerPlayer
set nodeTriggerUnit[node] = udg_SPELL__TriggerUnit
set nodeTargetX[node] = GetLocationX(udg_SPELL__TargetPoint)
set nodeTargetY[node] = GetLocationY(udg_SPELL__TargetPoint)
set nodeTargetPoint[node] = udg_SPELL__TargetPoint
set nodeTargetUnit[node] = udg_SPELL__TargetUnit
set nodeTargetItem[node] = udg_SPELL__TargetItem
set nodeTargetDest[node] = udg_SPELL__TargetDest
endfunction
private function EvaluateHandler takes trigger handlerTrigger returns nothing
if IsTriggerEnabled(handlerTrigger) and TriggerEvaluate(handlerTrigger) then
call TriggerExecute(handlerTrigger)
endif
endfunction
private struct HandlerList extends array
readonly thistype current
readonly thistype prev
readonly thistype next
readonly trigger onStartTrigger
readonly trigger onPeriodTrigger
readonly trigger onEndTrigger
boolean enemyFlag
boolean allyFlag
boolean deadFlag
boolean livingFlag
boolean magicImmuneFlag
boolean mechanicalFlag
boolean structureFlag
boolean flyingFlag
boolean heroFlag
boolean nonHeroFlag
private static thistype node = 0
method nextNode takes nothing returns nothing
set this.current = this.current.next
endmethod
static method create takes nothing returns thistype
set node = node + 1
set node.prev = node
set node.next = node
set node.current = node
return node
endmethod
method pushBack takes trigger onStart, trigger onPeriod, trigger onEnd returns thistype
local thistype next = this.next
set node = node + 1
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
set node.onStartTrigger = onStart
set node.onPeriodTrigger = onPeriod
set node.onEndTrigger = onEnd
return node
endmethod
endstruct
private struct SpellEventGUI extends array
/*
* Default spell period: value will be set by the GUI configuration
*/
private static real SPELL_PERIOD
private static constant integer SPELL_ABILITY_ID = 0
private static constant integer SPELL_EVENT_TYPE = 0
private HandlerList spellHandler
private static HandlerList array handlerList
private static key table
private method onSpellStart takes nothing returns thistype
local integer casterId
local thistype prevNode = udg_SPELL__Index
local HandlerList list = handlerList[Spell[GetEventSpellAbilityId()]*5 + eventIndex[GetEventSpellEventType()]]
call list.nextNode()
call SaveEventParams(prevNode)
call InitEventParams(/*
*/GetEventSpellAbilityId(), /*
*/GetEventSpellEventType(), /*
*/GetEventSpellOrderType(), /*
*/GetEventSpellLevel(), /*
*/GetEventSpellPlayer(), /*
*/GetEventSpellCaster(), /*
*/GetEventSpellTargetX(), /*
*/GetEventSpellTargetY(), /*
*/staticLocation, /*
*/GetEventSpellTargetUnit(), /*
*/GetEventSpellTargetItem(), /*
*/GetEventSpellTargetDest())
set udg_SPELL__Index = this
call EvaluateHandler(list.current.onStartTrigger)
set this = udg_SPELL__Index
set udg_SPELL__Index = prevNode
if this > 0 then
set this.spellHandler = list.current
call SaveEventParams(this)
endif
if list.current.next == list then
call list.nextNode()
endif
if GetEventSpellEventType() == EVENT_SPELL_CHANNEL then
set Table(table)[GetHandleId(GetEventSpellCaster())] = this
elseif GetEventSpellEventType() == EVENT_SPELL_ENDCAST then
set casterId = GetHandleId(GetEventSpellCaster())
set prevNode = Table(table)[casterId]
call Table(table).remove(casterId)
if prevNode != 0 then
call LoadEventParams(prevNode)
return -prevNode
endif
endif
call LoadEventParams(prevNode)
return this
endmethod
private method onSpellPeriodic takes nothing returns boolean
local boolean exit
local boolean prevExitFlag = udg_SPELL__ExitPeriodic
local thistype prevNode = udg_SPELL__Index
call SaveEventParams(prevNode)
call LoadEventParams(this)
set udg_SPELL__ExitPeriodic = false
set udg_SPELL__Index = this
call EvaluateHandler(this.spellHandler.onPeriodTrigger)
set exit = udg_SPELL__ExitPeriodic
set udg_SPELL__ExitPeriodic = prevExitFlag
set udg_SPELL__Index = prevNode
call LoadEventParams(prevNode)
if exit then
if nodeEventType[this] != EVENT_SPELL_CHANNEL then
return true
endif
call IssueImmediateOrderById(nodeTriggerUnit[this], STOP_ORDER_ID)
endif
return false
endmethod
private method onSpellEnd takes nothing returns nothing
local thistype prevNode = udg_SPELL__Index
call SaveEventParams(prevNode)
call LoadEventParams(this)
set udg_SPELL__Index = this
call EvaluateHandler(this.spellHandler.onEndTrigger)
set udg_SPELL__Index = prevNode
set nodeTargetPoint[this] = null
set nodeTargetUnit[this] = null
set nodeTargetItem[this] = null
set nodeTargetDest[this] = null
set nodeTriggerUnit[this] = null
set this.spellHandler = 0
call LoadEventParams(prevNode)
endmethod
implement SpellEvent
private static method registerSpell takes nothing returns nothing
local Spell spell = Spell[udg_SPELL__Ability]
local integer eventType = udg_SPELL__EventType
local integer eventId = 0x10
local integer index
local HandlerList node
if eventType == EVENT_SPELL_CHANNEL then
set eventType = eventType + EVENT_SPELL_ENDCAST
endif
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
set index = spell*5 + eventIndex[eventId]
if handlerList[index] == 0 then
set handlerList[index] = HandlerList.create()
endif
set node = handlerList[index].pushBack(udg_SPELL__OnStartTrigger, udg_SPELL__OnPeriodTrigger, udg_SPELL__OnEndTrigger)
set node.enemyFlag = udg_SPELL__EnemyFilterFlag
set node.allyFlag = udg_SPELL__AllyFilterFlag
set node.deadFlag = udg_SPELL__DeadFilterFlag
set node.livingFlag = udg_SPELL__LivingFilterFlag
set node.magicImmuneFlag = udg_SPELL__MagicImmuneFilterFlag
set node.mechanicalFlag = udg_SPELL__MechanicalFilterFlag
set node.structureFlag = udg_SPELL__StructureFilterFlag
set node.flyingFlag = udg_SPELL__FlyingFilterFlag
set node.heroFlag = udg_SPELL__HeroFilterFlag
set node.nonHeroFlag = udg_SPELL__NonHeroFilterFlag
call registerSpellEvent(udg_SPELL__Ability, eventId)
endif
set eventId = eventId/2
endloop
endmethod
private static method onSpellInvoke takes nothing returns nothing
local widget target
if udg_SPELL__OrderType == udg_SPELL__ORDER_NO_TARGET then
call SpellInvokeNoTargetEvent(udg_SPELL__Ability, udg_SPELL__EventType, GetLevel(), udg_SPELL__TriggerUnit)
elseif udg_SPELL__OrderType == udg_SPELL__ORDER_POINT_TARGET then
call SpellInvokePointTargetEvent(udg_SPELL__Ability, udg_SPELL__EventType, GetLevel(), udg_SPELL__TriggerUnit, GetLocationX(udg_SPELL__TargetPoint), GetLocationY(udg_SPELL__TargetPoint))
elseif udg_SPELL__OrderType == udg_SPELL__ORDER_SINGLE_TARGET then
set target = GetTarget()
if target != null then
call SpellInvokeSingleTargetEvent(udg_SPELL__Ability, udg_SPELL__EventType, GetLevel(), udg_SPELL__TriggerUnit, target)
set target = null
endif
endif
endmethod
private static method onOverrideParams takes nothing returns nothing
local widget target
if udg_SPELL__OrderType == udg_SPELL__ORDER_NO_TARGET then
call SpellOverrideNoTargetParams(GetLevel(), udg_SPELL__TriggerUnit)
elseif udg_SPELL__OrderType == udg_SPELL__ORDER_POINT_TARGET then
call SpellOverridePointTargetParams(GetLevel(), udg_SPELL__TriggerUnit, GetLocationX(udg_SPELL__TargetPoint), GetLocationY(udg_SPELL__TargetPoint))
elseif udg_SPELL__OrderType == udg_SPELL__ORDER_SINGLE_TARGET then
set target = GetTarget()
if target != null then
call SpellOverrideSingleTargetParams(GetLevel(), udg_SPELL__TriggerUnit, target)
set target = null
endif
endif
endmethod
private static method onEnumTargets takes nothing returns nothing
local HandlerList node = thistype(udg_SPELL__Index).spellHandler
local integer count = 0
local integer currentCount
local unit u
if node.structureFlag then
call GroupEnumUnitsInRangeOfLoc(enumGroup, udg_SPELL__TargetPoint, udg_SPELL__EnumRange + 197.00, null)
else
call GroupEnumUnitsInRangeOfLoc(enumGroup, udg_SPELL__TargetPoint, udg_SPELL__EnumRange + 64.00, null)
endif
loop
set u = FirstOfGroup(enumGroup)
exitwhen u == null
call GroupRemoveUnit(enumGroup, u)
if (ResolveFilterFlag(IsUnitEnemy(u, udg_SPELL__TriggerPlayer), node.enemyFlag, node.allyFlag)) and/*
*/ (ResolveFilterFlag(IsUnitType(u, UNIT_TYPE_HERO), node.heroFlag, node.nonHeroFlag)) and/*
*/ (ResolveFilterFlag(UnitAlive(u), node.livingFlag, node.deadFlag)) and/*
*/ (node.magicImmuneFlag or not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE)) and/*
*/ (node.mechanicalFlag or not IsUnitType(u, UNIT_TYPE_MECHANICAL)) and/*
*/ (node.structureFlag or not IsUnitType(u, UNIT_TYPE_STRUCTURE)) and/*
*/ (node.flyingFlag or not IsUnitType(u, UNIT_TYPE_FLYING)) then
set count = count + 1
set udg_SPELL__EnumedTargets[count] = u
if count == udg_SPELL__EnumCount then
exitwhen true
endif
endif
endloop
set udg_SPELL__EnumCount = count
if count < prevEnumCount then
/*
* Free unused references
*/
loop
exitwhen count == prevEnumCount
set count = count + 1
set udg_SPELL__EnumedTargets[count] = null
endloop
endif
set prevEnumCount = udg_SPELL__EnumCount
endmethod
private static method onSpellEvent takes nothing returns nothing
local thistype prevNode = udg_SPELL__Index
local integer abilityId = udg_SPELL__Ability
local integer eventType = udg_SPELL__EventType
local integer orderType = udg_SPELL__OrderType
local integer level = udg_SPELL__Level
local player triggerPlayer = udg_SPELL__TriggerPlayer
local unit triggerUnit = udg_SPELL__TriggerUnit
local location targetPoint = udg_SPELL__TargetPoint
local unit targetUnit = udg_SPELL__TargetUnit
local item targetItem = udg_SPELL__TargetItem
local destructable targetDest = udg_SPELL__TargetDest
local real targetX = GetLocationX(udg_SPELL__TargetPoint)
local real targetY = GetLocationY(udg_SPELL__TargetPoint)
call InitEventParams(/*
*/GetEventSpellAbilityId(), /*
*/GetEventSpellEventType(), /*
*/GetEventSpellOrderType(), /*
*/GetEventSpellLevel(), /*
*/GetEventSpellPlayer(), /*
*/GetEventSpellCaster(), /*
*/GetEventSpellTargetX(), /*
*/GetEventSpellTargetY(), /*
*/staticLocation, /*
*/GetEventSpellTargetUnit(), /*
*/GetEventSpellTargetItem(), /*
*/GetEventSpellTargetDest())
set udg_SPELL__Index = 0
set udg_SPELL__Event = 0.00
set udg_SPELL__Event = I2R(eventIndex[GetEventSpellEventType()])
set udg_SPELL__Event = 0.00
set udg_SPELL__Index = prevNode
call InitEventParams(/*
*/abilityId, /*
*/eventType, /*
*/orderType, /*
*/level, /*
*/triggerPlayer, /*
*/triggerUnit, /*
*/targetX, /*
*/targetY, /*
*/targetPoint, /*
*/targetUnit, /*
*/targetItem, /*
*/targetDest)
set triggerUnit = null
set targetPoint = null
set targetUnit = null
set targetItem = null
set targetDest = null
endmethod
implement SpellEventGeneric
/*=================================================================================*/
static method initTriggers takes nothing returns nothing
set udg_SPELL__RegisterHandler = CreateTrigger()
call TriggerAddAction(udg_SPELL__RegisterHandler, function thistype.registerSpell)
set udg_SPELL__InvokeEvent = CreateTrigger()
call TriggerAddAction(udg_SPELL__InvokeEvent, function thistype.onSpellInvoke)
set udg_SPELL__OverrideParams = CreateTrigger()
call TriggerAddAction(udg_SPELL__OverrideParams, function thistype.onOverrideParams)
set udg_SPELL__EnumerateTargetsInRange = CreateTrigger()
call TriggerAddAction(udg_SPELL__EnumerateTargetsInRange, function thistype.onEnumTargets)
endmethod
static method initConfiguration takes nothing returns nothing
set SPELL_PERIOD = 1.00/udg_SPELL__FRAME_RATE
set udg_SPELL__PERIOD = SPELL_PERIOD
set udg_SPELL__EVENT_CAST = EVENT_SPELL_CAST
set udg_SPELL__EVENT_CHANNEL = EVENT_SPELL_CHANNEL
set udg_SPELL__EVENT_EFFECT = EVENT_SPELL_EFFECT
set udg_SPELL__EVENT_ENDCAST = EVENT_SPELL_EFFECT
set udg_SPELL__EVENT_FINISH = EVENT_SPELL_FINISH
set udg_SPELL__ORDER_NO_TARGET = SPELL_ORDER_TYPE_NO_TARGET
set udg_SPELL__ORDER_POINT_TARGET = SPELL_ORDER_TYPE_POINT_TARGET
set udg_SPELL__ORDER_SINGLE_TARGET = SPELL_ORDER_TYPE_SINGLE_TARGET
set eventIndex[EVENT_SPELL_CAST] = 1
set eventIndex[EVENT_SPELL_CHANNEL] = 2
set eventIndex[EVENT_SPELL_EFFECT] = 3
set eventIndex[EVENT_SPELL_ENDCAST] = 4
set eventIndex[EVENT_SPELL_FINISH] = 5
endmethod
endstruct
static if DEBUG_MODE and LIBRARY_ErrorMessage then
private function OnRemoveLocation takes location whichLocation returns nothing
call ThrowError(whichLocation == staticLocation, SCOPE_PREFIX, "native RemoveLocation()", "", 0, "Destroyed a system-generated handle")
endfunction
hook RemoveLocation OnRemoveLocation
endif
public function Initialize takes nothing returns nothing
call SpellEventGUI.initTriggers()
call SpellEventGUI.initConfiguration()
set udg_SPELL__InitializationEvent = 1.00
set udg_SPELL__InitializationEvent = 0.00
endfunction
endlibrary
library SpellCloner /* v2.2.0 https://www.hiveworkshop.com/threads/324157/
*/uses /*
*/SpellEvent /* https://www.hiveworkshop.com/threads/301895/
*/Table /* https://www.hiveworkshop.com/threads/188084/
*///! novjass
/*
CREDITS:
- AGD (Author)
- JAKEZINC (Feedbacks and suggestions, which helped bring the system into its current form)
*/
|=====|
| API |
|=====|
readonly static SpellCloner.configuredInstance/*
- Use this variable inside the configuration function to refer to the spell
instance being configured
*/module SpellClonerHeader/*
- Implement this module at the top of your spell struct
*/static method hasActivationAbility takes integer abilId returns boolean/*
*/static method hasConfiguration takes integer configStructId returns boolean/*
- Only call this from inside a spell event handler (generic or specific)
- Could be useful for especially in generic handlers to see if the casted
ability activates a certain type of spell, as cloned spell can have many
activation abilities
*/method initSpellConfiguration takes integer abilId returns integer/*
- Call this method at the top of you onSpellStart() method to initialize the
correct local configuration of your spell instance based on the activation
ability id
- Returns the struct type id of the struct containing the configuration
*/method loadSpellConfiguration takes integer configStructId returns nothing/*
- Call this method with the value returned by initSpellConfiguration() as the
parameter
- Like initSpellConfiguration(), loads the correct local configuration of the
spell, but based on the typeid of the configuration struct
*/module SpellClonerFooter/*
- Implement this module at the bottom of your spell struct, below your SpellEvent implementation
*/static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
- Creates a new local configuration instance for the spell (Return value is obsolete)
*/module SpellCloner/*
- A supplement to using both SpellClonerHeader and SpellClonerFotter. Implement this
module at the bottom of your spell struct, no need to implement the SpellEvent module
*/interface method onClonedSpellStart takes nothing returns thistype/*
*/interface method onClonedSpellPeriodic takes nothing returns boolean/*
*/interface method onClonedSpellEnd takes nothing returns nothing/*
- Supplement to the onSpellStart(), onSpellPeriodic(), and onSpellEnd() methods from
SpellEvent module
- All these interface methods follow the same rules as their SpellEvent interface methods
counterpart
- You no longer need to call this.initSpellConfiguration(ABIL_ID) on onClonedSpellStart()
to run your configuration function, this is already done internally by the system.
*/static method hasActivationAbility takes integer abilId returns boolean/*
*/static method hasConfiguration takes integer configStructId returns boolean/*
- Already defined above (see SpellClonerHeader module)
*/static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype/*
- Creates a new local configuration instance for the spell (Return value is obsolete)
*///! endnovjass
globals
private trigger evaluator = CreateTrigger()
private integer array eventIndex
private integer configuredSpellInstance = 0
private TableArray table
private TableArray configStructNode
endglobals
private module Init
private static method onInit takes nothing returns nothing
set table = TableArray[JASS_MAX_ARRAY_SIZE]
set configStructNode = TableArray[JASS_MAX_ARRAY_SIZE]
set eventIndex[EVENT_SPELL_CAST] = 1
set eventIndex[EVENT_SPELL_CHANNEL] = 2
set eventIndex[EVENT_SPELL_EFFECT] = 3
set eventIndex[EVENT_SPELL_ENDCAST] = 4
set eventIndex[EVENT_SPELL_FINISH] = 5
endmethod
endmodule
struct SpellCloner extends array
static method operator configuredInstance takes nothing returns thistype
return configuredSpellInstance
endmethod
implement Init
endstruct
private struct SpellConfigList extends array
thistype current
readonly thistype prev
readonly thistype next
readonly integer structId
readonly boolexpr configExpr
private static thistype node = 0
method evaluateExpr takes integer spellInstance returns nothing
local integer prevInstance = configuredSpellInstance
set configuredSpellInstance = spellInstance
call TriggerAddCondition(evaluator, this.configExpr)
call TriggerEvaluate(evaluator)
call TriggerClearConditions(evaluator)
set configuredSpellInstance = prevInstance
endmethod
method insert takes integer id, boolexpr expr returns thistype
local thistype next = this.next
set node = node + 1
set node.structId = id
set node.configExpr = expr
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
return node
endmethod
static method create takes nothing returns thistype
set node = node + 1
set node.prev = node
set node.next = node
set node.current = node
return node
endmethod
endstruct
public function HasActivationAbility takes integer spellStructId, integer abilId returns boolean
if GetEventSpellEventType() == 0 then
return table[spellStructId*5 + 1].has(-abilId)
endif
return table[spellStructId*5 + eventIndex[GetEventSpellEventType()]].has(abilId)
endfunction
public function HasConfiguration takes integer spellStructId, integer configStructId returns boolean
if GetEventSpellEventType() == 0 then
return configStructNode[spellStructId*5 + 1].has(-configStructId)
endif
return configStructNode[spellStructId*5 + eventIndex[GetEventSpellEventType()]].has(configStructId)
endfunction
public function InitSpellConfiguration takes integer spellStructId, integer spellInstance, integer abilId returns integer
local integer configStructId
local SpellConfigList configList
if GetEventSpellEventType() == 0 then
set configList = table[spellStructId*5 + 1][-abilId]
else
set configList = table[spellStructId*5 + eventIndex[GetEventSpellEventType()]][abilId]
endif
set configList.current = configList.current.next
set configStructId = configList.current.structId
call configList.current.evaluateExpr(spellInstance)
if configList.current.next == configList then
set configList.current = configList
endif
return configStructId
endfunction
public function LoadSpellConfiguration takes integer spellStructId, integer spellInstance, integer configStructId returns nothing
if GetEventSpellEventType() == 0 then
call SpellConfigList(configStructNode[spellStructId*5 + 1][-configStructId]).evaluateExpr(spellInstance)
else
call SpellConfigList(configStructNode[spellStructId*5 + eventIndex[GetEventSpellEventType()]][configStructId]).evaluateExpr(spellInstance)
endif
endfunction
public function CloneSpell takes integer spellStructId, integer configStructId, integer abilId, integer eventType, code configFunc returns nothing
local SpellConfigList configList
local integer eventId = 0x10
local integer key
if eventType == 0 then
set key = spellStructId*5 + 1
if configStructNode[key][-configStructId] == 0 then
set configList = table[key][-abilId]
if configList == 0 then
set configList = SpellConfigList.create()
set table[key][-abilId] = configList
endif
set configStructNode[key][-configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
endif
else
loop
exitwhen eventId == 0
if eventType >= eventId then
set eventType = eventType - eventId
set key = spellStructId*5 + eventIndex[eventId]
if configStructNode[key][configStructId] == 0 then
set configList = table[key][abilId]
if configList == 0 then
set configList = SpellConfigList.create()
set table[key][abilId] = configList
endif
set configStructNode[key][configStructId] = configList.prev.insert(configStructId, Filter(configFunc))
endif
endif
set eventId = eventId/2
endloop
endif
endfunction
private module SpellClonerCommonHeader
static constant integer SPELL_ABILITY_ID = 0
static constant integer SPELL_EVENT_TYPE = 0
static method hasActivationAbility takes integer abilId returns boolean
return HasActivationAbility(thistype.typeid, abilId)
endmethod
static method hasConfiguration takes integer configStructId returns boolean
return HasConfiguration(thistype.typeid, configStructId)
endmethod
endmodule
module SpellClonerHeader
implement SpellClonerCommonHeader
method initSpellConfiguration takes integer abilId returns integer
return InitSpellConfiguration(thistype.typeid, this, abilId)
endmethod
method loadSpellConfiguration takes integer configStructId returns nothing
call LoadSpellConfiguration(thistype.typeid, this, configStructId)
endmethod
endmodule
module SpellClonerFooter
static method create takes integer configStructId, integer abilId, integer spellEventType, code configurationFunc returns thistype
call CloneSpell(thistype.typeid, configStructId, abilId, spellEventType, configurationFunc)
call registerSpellEvent(abilId, spellEventType)
return 0
endmethod
endmodule
module SpellCloner
implement SpellClonerCommonHeader
method onSpellStart takes nothing returns thistype
local integer configStructId = InitSpellConfiguration(thistype.typeid, this, GetEventSpellAbilityId())
local thistype node = this.onClonedSpellStart()
if node > 0 then
if node != this then
call SpellConfigList(configStructNode[thistype.typeid][configStructId]).evaluateExpr(node)
endif
return node
endif
return 0
endmethod
method onSpellPeriodic takes nothing returns boolean
return this.onClonedSpellPeriodic()
endmethod
method onSpellEnd takes nothing returns nothing
call this.onClonedSpellEnd()
endmethod
implement SpellEvent
implement SpellClonerFooter
endmodule
endlibrary
library Alloc /* v1.1.0 https://www.hiveworkshop.com/threads/324937/
*/uses /*
*/Table /* https://www.hiveworkshop.com/threads/188084/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
/*
Written by AGD, based on MyPad's allocation algorithm
A allocator module using a single global indexed stack. Allocated values are
within the JASS_MAX_ARRAY_SIZE. No more need to worry about code bloats behind
the module implementation as it generates the least code possible (6 lines of
code in non-DEBUG_MODE), nor does it use an initialization function. This system
also only uses ONE variable (for the whole map) for the hashtable.
*/
|-----|
| API |
|-----|
/*
*/module GlobalAlloc/*
- Uses a single stack globally
*/module Alloc/*
- Uses a unique stack per struct
*/debug readonly boolean allocated/* Is node allocated?
*/static method allocate takes nothing returns thistype/*
*/method deallocate takes nothing returns nothing/*
*///! endnovjass
/*===========================================================================*/
globals
private key stack
endglobals
static if DEBUG_MODE then
private function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
else
if condition then
call BJDebugMsg("[Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
endif
endif
endfunction
public function IsAllocated takes integer typeId, integer node returns boolean
return node > 0 and Table(stack)[typeId*JASS_MAX_ARRAY_SIZE + node] == 0
endfunction
endif
public function Allocate takes integer typeId returns integer
local integer offset = typeId*JASS_MAX_ARRAY_SIZE
local integer node = Table(stack)[offset]
local integer stackNext = Table(stack)[offset + node]
debug call AssertError(typeId < 0, "allocate()", Table(stack).string[-typeId], 0, "Invalid struct ID (" + I2S(typeId) + ")")
if stackNext == 0 then
debug call AssertError(node == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", Table(stack).string[-typeId], node, "Overflow")
set node = node + 1
set Table(stack)[offset] = node
else
set Table(stack)[offset] = stackNext
set Table(stack)[offset + node] = 0
endif
return node
endfunction
public function Deallocate takes integer typeId, integer node returns nothing
local integer offset = typeId*JASS_MAX_ARRAY_SIZE
debug call AssertError(node == 0, "deallocate()", Table(stack).string[-typeId], 0, "Null node")
debug call AssertError(Table(stack)[offset + node] > 0, "deallocate()", Table(stack).string[-typeId], node, "Double-free")
set Table(stack)[offset + node] = Table(stack)[offset]
set Table(stack)[offset] = node
endfunction
module Alloc
debug method operator allocated takes nothing returns boolean
debug return IsAllocated(thistype.typeid, this)
debug endmethod
static method allocate takes nothing returns thistype
return Allocate(thistype.typeid)
endmethod
method deallocate takes nothing returns nothing
call Deallocate(thistype.typeid, this)
endmethod
debug private static method onInit takes nothing returns nothing
debug set Table(stack).string[-thistype.typeid] = "thistype"
debug endmethod
endmodule
module GlobalAlloc
debug method operator allocated takes nothing returns boolean
debug return IsAllocated(0, this)
debug endmethod
static method allocate takes nothing returns thistype
debug call AssertError(Table(stack)[0] == (JASS_MAX_ARRAY_SIZE - 1), "allocate()", "thistype", JASS_MAX_ARRAY_SIZE - 1, "Overflow")
return Allocate(0)
endmethod
method deallocate takes nothing returns nothing
debug call AssertError(this == 0, "deallocate()", "thistype", 0, "Null node")
debug call AssertError(Table(stack)[this] > 0, "deallocate()", "thistype", this, "Double-free")
call Deallocate(0, this)
endmethod
endmodule
endlibrary
library LinkedList /* v1.3.0 https://www.hiveworkshop.com/threads/linkedlist-modules.325635/
*/uses /*
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
/*
Author:
- AGD
Credits:
- Nestharus, Dirac, Bribe
> For their scripts and discussions which I used as reference
Pros:
- Feature-rich (Can be)
- Modular
- Safety-oriented (On DEBUG_MODE, but not 100% fool-proof ofcourse)
- Flexible (Does not enforce a built-in allocator - allows user to choose between a custom Alloc
or the default vjass allocator, or neither)
- Extensible (Provides interfaces)
Note:
If you are using using Dirac's 'LinkedListModule' library, you need to replace its contents with
the compatibility lib provided alongside this library for all to work seamlessly.
*/
|-----|
| API |
|-----|
/*
Note: All the fields except from 'prev' and 'next' are actually operators, so you might want to
avoid using them from the interface methods that would be declared above them.
=====================================================================================================
List Fields Modules (For those who want to write or inline the core linked-list operations themselves)
*/module LinkedListFields/*
*/readonly thistype prev/*
*/readonly thistype next/*
*/module StaticListFields extends LinkedListFields/*
*/readonly static constant thistype sentinel/*
*/readonly static thistype front/*
*/readonly static thistype back/*
*/readonly static boolean empty/*
*/module ListFields extends LinkedListFields/*
*/readonly thistype front/*
*/readonly thistype back/*
*/readonly boolean empty/*
=====================================================================================================
Lite List Modules (Should be enough for most cases)
*/module LinkedListLite extends LinkedListFields/*
*/optional interface static method onInsert takes thistype node returns nothing/*
*/optional interface static method onRemove takes thistype node returns nothing/*
*/optional interface method onTraverse takes thistype node returns boolean/*
*/static method insert takes thistype prev, thistype node returns nothing/*
*/static method remove takes thistype node returns nothing/*
*/method traverseForwards takes nothing returns nothing/*
*/method traverseBackwards takes nothing returns nothing/*
- Only present if onTraverse() is also present
*/module StaticListLite extends StaticListFields, LinkedListLite/*
*/static method pushFront takes thistype node returns nothing/*
*/static method popFront takes nothing returns thistype/*
*/static method pushBack takes thistype node returns nothing/*
*/static method popBack takes nothing returns thistype/*
*/module ListLite extends ListFields, LinkedListLite/*
*/method pushFront takes thistype node returns nothing/*
*/method popFront takes nothing returns thistype/*
*/method pushBack takes thistype node returns nothing/*
*/method popBack takes nothing returns thistype/*
*/module InstantiatedListLite extends ListLite/*
*/interface static method allocate takes nothing returns thistype/*
*/interface method deallocate takes nothing returns nothing/*
*/optional interface method onConstruct takes nothing returns nothing/*
*/optional interface method onDestruct takes nothing returns nothing/*
*/static method create takes nothing returns thistype/*
*/method destroy takes nothing returns nothing/*
=====================================================================================================
Standard List Modules
*/module LinkedList extends LinkedListLite/*
*/static method isLinked takes thistype node returns boolean/*
*/module StaticList extends StaticListLite, LinkedList/*
*/static method clear takes nothing returns nothing/*
*/static method flush takes nothing returns nothing/*
*/module List extends ListLite, LinkedList/*
*/static method makeHead takes thistype node returns nothing/*
*/method clear takes nothing returns nothing/*
*/method flush takes nothing returns nothing/*
*/module InstantiatedList extends InstantiatedListLite, List/*
=====================================================================================================
Feature-rich List Modules (For those who somehow need exotic linked-list operations)
*/module LinkedListEx extends LinkedList/*
*/static method move takes thistype prev, thistype node returns nothing/*
*/static method swap takes thistype nodeA, thistype nodeB returns nothing/*
*/module StaticListEx extends StaticList, LinkedListEx/*
*/static method contains takes thistype node returns boolean/*
*/static method getSize takes nothing returns integer/*
*/static method rotateLeft takes nothing returns nothing/*
*/static method rotateRight takes nothing returns nothing/*
*/module ListEx extends List, LinkedListEx/*
*/method contains takes thistype node returns boolean/*
*/method getSize takes nothing returns integer/*
*/method rotateLeft takes nothing returns nothing/*
*/method rotateRight takes nothing returns nothing/*
*/module InstantiatedListEx extends InstantiatedList, ListEx/*
*///! endnovjass
/*========================================= CONFIGURATION ===========================================
* Only affects DEBUG_MODE
* If false, throws warnings instead (Errors pauses the game (Or stops the thread) while warnings do not)
*/
globals
private constant boolean THROW_ERRORS = true
endglobals
/*====================================== END OF CONFIGURATION =====================================*/
static if DEBUG_MODE then
public function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
static if THROW_ERRORS then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
else
call ThrowWarning(condition, SCOPE_PREFIX, methodName, structName, node, message)
endif
else
if condition then
static if THROW_ERRORS then
call BJDebugMsg("|cffff0000[ERROR]|r [Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
call PauseGame(true)
else
call BJDebugMsg("|cffffcc00[WARNING]|r [Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffffcc00" + message + "|r")
endif
endif
endif
endfunction
endif
private module LinkedListUtils
method p_clear takes nothing returns nothing
set this.next.prev = 0
set this.prev.next = 0
set this.prev = this
set this.next = this
endmethod
method p_flush takes nothing returns nothing
local thistype node = this.prev
loop
exitwhen node == this
call remove(node)
set node = node.prev
endloop
endmethod
endmodule
private module LinkedListUtilsEx
implement LinkedListUtils
method p_contains takes thistype toFind returns boolean
local thistype node = this.next
loop
exitwhen node == this
if node == toFind then
return true
endif
set node = node.next
endloop
return false
endmethod
method p_getSize takes nothing returns integer
local integer count = 0
local thistype node = this.next
loop
exitwhen node == this
set count = count + 1
set node = node.next
endloop
return count
endmethod
endmodule
private module LinkedListLiteBase
implement LinkedListFields
debug method p_isEmptyHead takes nothing returns boolean
debug return this == this.next and this == this.prev
debug endmethod
static method p_insert takes thistype this, thistype node returns nothing
local thistype next = this.next
set node.prev = this
set node.next = next
set next.prev = node
set this.next = node
endmethod
static method p_remove takes thistype node returns nothing
set node.next.prev = node.prev
set node.prev.next = node.next
endmethod
static method insert takes thistype this, thistype node returns nothing
debug call AssertError(node == 0, "insert()", "thistype", 0, "Cannot insert null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "insert()", "thistype", 0, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call p_insert(this, node)
static if thistype.onInsert.exists then
call onInsert(node)
endif
endmethod
static method remove takes thistype node returns nothing
debug call AssertError(node == 0, "remove()", "thistype", 0, "Cannot remove null node")
debug call AssertError(node.next.prev != node and node.prev.next != node, "remove()", "thistype", 0, "Invalid node [" + I2S(node) + "]")
static if thistype.onRemove.exists then
call onRemove(node)
endif
call p_remove(node)
endmethod
static if thistype.onTraverse.exists then
method p_traverse takes boolean forward returns nothing
local thistype node
local thistype next
debug local thistype prev
debug local boolean array traversed
if forward then
set node = this.next
loop
exitwhen node == this or node.prev.next != node
debug call AssertError(traversed[node], "traverseForwards()", "thistype", this, "A node was traversed twice in a single traversal")
debug set traversed[node] = true
debug set prev = node.prev
set next = node.next
if this.onTraverse(node) then
call remove(node)
debug set traversed[node] = false
debug elseif next.prev == prev then
debug set traversed[node] = false
endif
set node = next
endloop
else
set node = this.prev
loop
exitwhen node == this or node.next.prev != node
debug call AssertError(traversed[node], "traverseBackwards()", "thistype", this, "A node was traversed twice in a single traversal")
debug set traversed[node] = true
debug set prev = node.next
set next = node.prev
if this.onTraverse(node) then
call remove(node)
debug set traversed[node] = false
debug elseif next.prev == prev then
debug set traversed[node] = false
endif
set node = next
endloop
endif
endmethod
method traverseForwards takes nothing returns nothing
call this.p_traverse(true)
endmethod
method traverseBackwards takes nothing returns nothing
call this.p_traverse(false)
endmethod
endif
endmodule
private module LinkedListBase
implement LinkedListLiteBase
static method isLinked takes thistype node returns boolean
return node.next.prev == node or node.prev.next == node
endmethod
endmodule
module LinkedListFields
readonly thistype prev
readonly thistype next
endmodule
module LinkedListLite
implement LinkedListLiteBase
implement optional LinkedListLiteModuleCompatibility // For API compatibility with Dirac's 'LinkedListModule' library
endmodule
module LinkedList
implement LinkedListBase
implement optional LinkedListModuleCompatibility // For API compatibility with Dirac's 'LinkedListModule' library
endmodule
module LinkedListEx
implement LinkedListBase
static method p_move takes thistype prev, thistype node returns nothing
call p_remove(node)
call p_insert(prev, node)
endmethod
static method move takes thistype prev, thistype node returns nothing
debug call AssertError(not isLinked(node), "move()", "thistype", 0, "Cannot use unlinked node [" + I2S(node) + "]")
call p_move(prev, node)
endmethod
static method swap takes thistype this, thistype node returns nothing
local thistype thisPrev = this.prev
local thistype thisNext = this.next
debug call AssertError(this == 0, "swap()", "thistype", 0, "Cannot swap null node")
debug call AssertError(node == 0, "swap()", "thistype", 0, "Cannot swap null node")
debug call AssertError(not isLinked(this), "swap()", "thistype", 0, "Cannot use unlinked node [" + I2S(this) + "]")
debug call AssertError(not isLinked(node), "swap()", "thistype", 0, "Cannot use unlinked node [" + I2S(node) + "]")
call p_move(node, this)
if thisNext != node then
call p_move(thisPrev, node)
endif
endmethod
endmodule
module StaticListFields
implement LinkedListFields
static constant method operator head takes nothing returns thistype
return 0
endmethod
static method operator back takes nothing returns thistype
return head.prev
endmethod
static method operator front takes nothing returns thistype
return head.next
endmethod
static method operator empty takes nothing returns boolean
return front == head
endmethod
endmodule
module StaticListLite
implement StaticListFields
implement LinkedListLiteBase
static method pushFront takes thistype node returns nothing
debug call AssertError(node == 0, "pushFront()", "thistype", 0, "Cannot use null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushFront()", "thistype", 0, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(head, node)
endmethod
static method popFront takes nothing returns thistype
local thistype node = front
debug call AssertError(node.prev != head, "popFront()", "thistype", 0, "Invalid list")
call remove(node)
return node
endmethod
static method pushBack takes thistype node returns nothing
debug call AssertError(node == 0, "pushBack()", "thistype", 0, "Cannot use null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushBack()", "thistype", 0, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(back, node)
endmethod
static method popBack takes nothing returns thistype
local thistype node = back
debug call AssertError(node.next != head, "popBack()", "thistype", 0, "Invalid list")
call remove(node)
return node
endmethod
endmodule
module StaticList
implement StaticListLite
implement LinkedListBase
implement LinkedListUtils
static method clear takes nothing returns nothing
call head.p_clear()
endmethod
static method flush takes nothing returns nothing
call head.p_flush()
endmethod
endmodule
module StaticListEx
implement StaticList
implement LinkedListEx
implement LinkedListUtilsEx
static method contains takes thistype node returns boolean
return head.p_contains(node)
endmethod
static method getSize takes nothing returns integer
return head.p_getSize()
endmethod
static method rotateLeft takes nothing returns nothing
call p_move(back, front)
endmethod
static method rotateRight takes nothing returns nothing
call p_move(head, back)
endmethod
endmodule
module ListFields
implement LinkedListFields
method operator back takes nothing returns thistype
return this.prev
endmethod
method operator front takes nothing returns thistype
return this.next
endmethod
method operator empty takes nothing returns boolean
return this.next == this
endmethod
endmodule
module ListLite
implement ListFields
implement LinkedListLiteBase
method pushFront takes thistype node returns nothing
debug call AssertError(this == 0, "pushFront()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "pushFront()", "thistype", this, "Invalid list")
debug call AssertError(node == 0, "pushFront()", "thistype", this, "Cannot insert null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushFront()", "thistype", this, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(this, node)
endmethod
method popFront takes nothing returns thistype
local thistype node = this.next
debug call AssertError(this == 0, "popFront()", "thistype", 0, "Null list")
debug call AssertError(node.prev != this, "popFront()", "thistype", this, "Invalid list")
call remove(node)
return node
endmethod
method pushBack takes thistype node returns nothing
debug call AssertError(this == 0, "pushBack()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "pushBack()", "thistype", this, "Invalid list")
debug call AssertError(node == 0, "pushBack()", "thistype", this, "Cannot insert null node")
debug call AssertError(not node.p_isEmptyHead() and (node.next.prev == node or node.prev.next == node), "pushBack()", "thistype", this, "Already linked node [" + I2S(node) + "]: prev = " + I2S(node.prev) + " ; next = " + I2S(node.next))
call insert(this.prev, node)
endmethod
method popBack takes nothing returns thistype
local thistype node = this.prev
debug call AssertError(this == 0, "popBack()", "thistype", 0, "Null list")
debug call AssertError(node.next != this, "pushFront()", "thistype", this, "Invalid list")
call remove(node)
return node
endmethod
endmodule
module List
implement ListLite
implement LinkedListBase
implement LinkedListUtils
static method makeHead takes thistype node returns nothing
set node.prev = node
set node.next = node
endmethod
method clear takes nothing returns nothing
debug call AssertError(this == 0, "clear()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "clear()", "thistype", this, "Invalid list")
call this.p_clear()
endmethod
method flush takes nothing returns nothing
debug call AssertError(this == 0, "flush()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "flush()", "thistype", this, "Invalid list")
call this.p_flush()
endmethod
endmodule
module ListEx
implement List
implement LinkedListEx
implement LinkedListUtilsEx
method contains takes thistype node returns boolean
debug call AssertError(this == 0, "contains()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "contains()", "thistype", this, "Invalid list")
return this.p_contains(node)
endmethod
method getSize takes nothing returns integer
debug call AssertError(this == 0, "getSize()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "getSize()", "thistype", this, "Invalid list")
return this.p_getSize()
endmethod
method rotateLeft takes nothing returns nothing
debug call AssertError(this == 0, "rotateLeft()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "rotateLeft()", "thistype", this, "Invalid list")
call p_move(this.back, this.front)
endmethod
method rotateRight takes nothing returns nothing
debug call AssertError(this == 0, "rotateRight()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev == this, "rotateRight()", "thistype", this, "Invalid list")
call p_move(this, this.back)
endmethod
endmodule
module InstantiatedListLite
implement ListLite
debug private boolean valid
static method create takes nothing returns thistype
local thistype node = allocate()
set node.prev = node
set node.next = node
debug set node.valid = true
static if thistype.onConstruct.exists then
call node.onConstruct()
endif
return node
endmethod
method destroy takes nothing returns nothing
debug call AssertError(this == 0, "destroy()", "thistype", 0, "Null list")
debug call AssertError(this.next.prev != this, "destroy()", "thistype", this, "Invalid list")
debug call AssertError(not this.valid, "destroy()", "thistype", this, "Double-free")
debug set this.valid = false
static if thistype.flush.exists then
call this.flush()
endif
static if thistype.onDestruct.exists then
call this.onDestruct()
endif
debug set this.prev = 0
debug set this.next = 0
call this.deallocate()
endmethod
endmodule
module InstantiatedList
implement List
implement InstantiatedListLite
endmodule
module InstantiatedListEx
implement ListEx
implement InstantiatedList
endmodule
endlibrary
//! novjass
|-------------------|
| API Documentation |
|-------------------|
/*
v1.3.0
LinkedList library
If you are looking for a comprehensive listing of API available for each module, see the header of the
library itself. What you can find here is the description of ALL avaiable API.
The library supports 4 different implementations of linked-list:
- Free lists, where you have freedom in linking and dealing with nodes
without being restricted by the concept of a 'head node' or a 'container node'
- Static Lists, which turns a struct into a single list, with the 'head node'
representing the list itself
- Non-static Lists, which allows you to have multiple lists within a struct
- Instantiated Lists, similar to non-static lists but comes with its own methods
for creating and destroying a list (requires allocate() and deallocate() in the
implementing struct)
Note: In all these kind of lists, all nodes should be unique together with the head nodes.
Lists can also be circular or linear.
|-----------------|
| STATIC LIST API |
|-----------------|
Interfaces:
*/optional interface static method onInsert takes thistype node returns nothing/*
*/optional interface static method onRemove takes thistype node returns nothing/*
- onInsert() is called after a node is inserted everytime insert(), pushFront(),
or pushBack() is called
- onRemove() is called before a node is removed everytime remove(), popFront(),
or popBack() is called
- <node> is the node being inserted/removed
*/optional interface method onTraverse takes thistype node returns boolean/*
- Runs in response to traverseForwards()/traverseBackwards() calls
- <this> is the head node
- <node> is the currently traversed node
- Returning <true> removes <node> from the list <this>
Fields:
*/readonly thistype prev/*
*/readonly thistype next/*
*/readonly static thistype front/*
*/readonly static thistype back/*
*/static constant thistype head/*
*/readonly static boolean empty/*
- <front>, <back>, <head>, and <empty> are method operators
Methods:
*/static method isLinked takes thistype node returns boolean/*
- Checks if a node is currently belongs to a list
*/static method move takes thistype prev, thistype node returns nothing/*
- Moves <node> next to <prev>
- Only works if <node> is already linked
*/static method swap takes thistype nodeA, thistype nodeB returns nothing/*
- Swaps the placement of two nodes
*/static method contains takes thistype node returns boolean/*
- Checks if <head> contains the given node
*/static method getSize takes nothing returns integer/*
- Gets the size of the list <head>
- Time complexity: O(n)
*/static method traverseForwards takes nothing returns nothing/*
*/static method traverseBackwards takes nothing returns nothing/*
- Traverses a list forwards/backwards and calls onTraverse() for
each node in the list
*/static method rotateLeft takes nothing returns nothing/*
*/static method rotateRight takes nothing returns nothing/*
*/static method insert takes thistype prev, thistype node returns nothing/*
*/static method remove takes thistype node returns nothing/*
*/static method pushFront takes thistype node returns nothing/*
- Inlines to insert() if not on DEBUG_MODE
*/static method popFront takes nothing returns thistype/*
*/static method pushBack takes thistype node returns nothing/*
- Inlines to insert() if not on DEBUG_MODE
*/static method popBack takes nothing returns thistype/*
*/static method clear takes nothing returns nothing/*
- Does not call remove() for any node, but only unlinks them from the list
*/static method flush takes nothing returns nothing/*
- Calls remove() for each node on the list starting from the front to the back node
|---------------------|
| NON-STATIC LIST API |
|---------------------|
*/optional interface static method onInsert takes thistype node returns nothing/*
*/optional interface static method onRemove takes thistype node returns nothing/*
- onInsert() is called after a node is inserted everytime insert(), pushFront(),
or pushBack() is called
- onRemove() is called before a node is removed everytime remove(), popFront(),
or popBack() is called
- <node> is the node being inserted/removed
*/optional interface method onConstruct takes nothing returns nothing/*
*/optional interface method onDestruct takes nothing returns nothing/*
- This methods will be called when calling create()/destroy()
- <this> refers to the list to be created/destroyed
*/interface static method allocate takes nothing returns thistype/*
- The value returned by this method will be the value returned by create()
*/interface method deallocate takes nothing returns nothing/*
- This method will be called when calling destroy()
*/optional interface method onTraverse takes thistype node returns boolean/*
- Runs in response to traverseForwards()/traverseBackwards() calls
- <this> is the head node
- <node> is the currently traversed node
- Returning <true> removes <node> from the list <this>
Fields:
*/readonly thistype prev/*
*/readonly thistype next/*
*/readonly thistype front/*
*/readonly thistype back/*
*/readonly boolean empty/*
- <front>, <back>, and <empty> are method operators
Methods:
*/static method isLinked takes thistype node returns boolean/*
- Checks if a node is currently belongs to a list
*/static method move takes thistype prev, thistype node returns nothing/*
- Moves <node> next to <prev>
- Only works if <node> is already linked
*/static method swap takes thistype nodeA, thistype nodeB returns nothing/*
- Swaps the placement of two nodes
*/method contains takes thistype node returns boolean/*
- Checks if <head> contains the given node
*/method getSize takes nothing returns integer/*
- Gets the size of the list <head>
- Time complexity: O(n)
*/method traverseForwards takes nothing returns nothing/*
*/method traverseBackwards takes nothing returns nothing/*
- traverses a list forwards/backwards and calls onTraverse() for
each node in the list
*/method rotateLeft takes nothing returns nothing/*
*/method rotateRight takes nothing returns nothing/*
*/static method makeHead takes thistype node returns nothing/*
- Turns a node into a list
*/static method insert takes thistype prev, thistype node returns nothing/*
*/static method remove takes thistype node returns nothing/*
*/method pushFront takes thistype node returns nothing/*
- Inlines to insert() if not on DEBUG_MODE
*/method popFront takes nothing returns thistype/*
*/method pushBack takes thistype node returns nothing/*
- Inlines to insert() if not on DEBUG_MODE
*/method popBack takes nothing returns thistype/*
*/method clear takes nothing returns nothing/*
- Does not call remove() for any node, but only unlinks them from the list
*/method flush takes nothing returns nothing/*
- Calls remove() for each node on the list starting from the front to the back node
*/static method create takes nothing returns thistype/*
- Creates a new list
*/method destroy takes nothing returns nothing/*
- Destroys the list (Also calls flush internally for InstantiatedList and InstantiatedListEx)
*///! endnovjass
/*
* For compatibility with Dirac's LinkedListModule
*
* Note:
* No changes needed to be made in your old code that's using Dirac's 'LinkedListModule'.
* Just replace his 'LinkedListModule' library in your map with this one.
*/
library LinkedListModule
module LinkedListModuleCompatibility
implement LinkedListLiteModuleCompatibility
static method createNode takes nothing returns thistype
local thistype node = allocate()
//! runtextmacro LINKED_LIST_HEAD("node")
return node
endmethod
method insertNode takes thistype node returns nothing
call insert(this, node)
endmethod
method removeNode takes nothing returns nothing
call remove(this)
endmethod
method clearNode takes nothing returns nothing
//! runtextmacro LINKED_LIST_CLEAR("this")
endmethod
method flushNode takes nothing returns nothing
//! runtextmacro LINKED_LIST_FLUSH("this")
endmethod
endmodule
module LinkedListLiteModuleCompatibility
private static integer instanceCount = 0
private static thistype _base = JASS_MAX_ARRAY_SIZE - 3
boolean head
static method operator base takes nothing returns thistype
if not _base.head then
set _base.prev = _base
set _base.next = _base
set _base.head = true
endif
return _base
endmethod
static method allocate takes nothing returns thistype
local thistype this = thistype(base + 1).prev
if this == 0 then
debug if instanceCount == base then
debug call BJDebugMsg("[Linked List]: Overflow")
debug return 0
debug endif
set instanceCount = instanceCount + 1
return instanceCount
endif
set thistype(base + 1).prev = this.prev
return this
endmethod
method deallocate takes nothing returns nothing
set this.prev = thistype(base + 1).prev
set thistype(base + 1).prev = this
set this.head = false
endmethod
endmodule
//! textmacro LINKED_LIST_HEAD takes node
set $node$.next = $node$
set $node$.prev = $node$
set $node$.head = true
//! endtextmacro
//! textmacro LINKED_LIST_CLEAR takes node
if $node$!=$node$.next then
set $node$.next.prev = thistype(base + 1).prev
set thistype(base + 1).prev = $node$.prev
set $node$.next = $node$
set $node$.prev = $node$
endif
//! endtextmacro
//! textmacro LINKED_LIST_FLUSH takes node
set $node$.next.prev = thistype(base + 1).prev
set thistype(base + 1).prev = $node$
set $node$.head = false
//! endtextmacro
//! textmacro LINKED_LIST_INSERT takes node, toInsert
set $node$.prev.next = $toInsert$
set $toInsert$.prev = $node$.prev
set $node$.prev = $toInsert$
set $toInsert$.next = $node$
//! endtextmacro
//! textmacro LINKED_LIST_REMOVE takes node
set $node$.prev.next = $node$.next
set $node$.next.prev = $node$.prev
//! endtextmacro
//! textmacro LINKED_LIST_MERGE takes nodeA, nodeB
set $nodeA$.next.prev = $nodeB$.prev
set $nodeB$.prev.next = $nodeA$.next
set $nodeA$.next = $nodeB$
set $nodeB$.prev = $nodeA$
//! endtextmacro
endlibrary
library PeriodicListTraversal /* v1.0.0 by AGD |
*/uses /*
*/LinkedList /*
*/Table /*
*/Alloc /*
*///! novjass
|-----|
| API |
|-----|
module PeriodicListTraversal extends LinkedListLite
interface static constant real LIST_TRAVERSAL_PERIOD
interface static method onNodeAdded takes thistype node returns nothing defaults nothing
interface static method onNodeRemoved takes thistype node returns nothing defaults nothing/*
- These methods will be called when a node is added/removed from a list
*/interface method onListTraversalFilter takes nothing returns boolean defaults true/*
- Runs every <LIST_TRAVERSAL_PERIOD> seconds for each list/head node
*/interface method onListTraversalPeriod takes thistype node returns boolean defaults false/*
- Runs every <LIST_TRAVERSAL_PERIOD> seconds for each node excluding the head/list node
- ONLY runs when '<this>.onListTraversalFilter()' returns <true>
- <this> is the head node
- For static lists, <this> is usually 0
- <node> is removed from the list when this method returns true
*///! endnovjass
globals
private key table
endglobals
private keyword HeadNode
private struct Node extends array
implement Alloc
endstruct
private struct PeriodList extends array
readonly trigger trigger
private timer timer
private real period
private integer counter
static method operator [] takes real period returns thistype
local thistype node = Table(table)[-R2I(period*1000000)]
if node == 0 then
set node = Node.allocate()
set node.prev = node
set node.next = node
set node.period = period
set node.timer = CreateTimer()
set node.trigger = CreateTrigger()
set Table(table)[-R2I(period*1000000)] = node
endif
return node
endmethod
private static method onPeriod takes nothing returns nothing
call TriggerEvaluate(thistype[TimerGetTimeout(GetExpiredTimer())].trigger)
endmethod
implement LinkedListLite
private static method onUpdateCallbacks takes nothing returns nothing
local thistype this = thistype[TimerGetTimeout(GetExpiredTimer())]
local thistype node = this.next
call TriggerRemoveCondition(this.trigger, HeadNode(this).condition)
set HeadNode(this).condition = null
loop
exitwhen node == this
set this.counter = this.counter - 1
call TriggerRemoveCondition(this.trigger, HeadNode(node).condition)
set HeadNode(node).condition = null
set node = node.next
endloop
if this.counter == 0 then
call PauseTimer(this.timer)
endif
endmethod
method startPeriodic takes HeadNode headNode returns nothing
set headNode.condition = TriggerAddCondition(this.trigger, headNode.expression)
if this.counter == 0 then
call TimerStart(this.timer, this.period, true, function thistype.onPeriod)
endif
set this.counter = this.counter + 1
endmethod
method stopPeriodic takes HeadNode headNode returns nothing
if this.next == this then
set HeadNode(this).condition = TriggerAddCondition(this.trigger, Filter(function thistype.onUpdateCallbacks))
endif
call insert(this, headNode)
endmethod
endstruct
private struct HeadNode extends array
triggercondition condition
readonly boolexpr expression
readonly integer data
readonly PeriodList periodList
readonly static thistype array list
static method create takes integer typeid, real period, code callback returns thistype
local thistype node = Node.allocate()
set node.prev = node
set node.next = node
set node.periodList = PeriodList[period]
set node.expression = Filter(callback)
set list[typeid] = node
return node
endmethod
implement LinkedListLite
method pushBack takes thistype data returns nothing
local thistype node = Node.allocate()
set node.data = data
set Table(table)[this*JASS_MAX_ARRAY_SIZE + data] = node
if this.next == this then
call this.periodList.startPeriodic(this)
endif
call insert(this.prev, node)
endmethod
method pop takes thistype data returns nothing
local integer key = this*JASS_MAX_ARRAY_SIZE + data
local thistype node = Table(table)[key]
call remove(node)
if this.next == this then
call this.periodList.stopPeriodic(this)
endif
call Table(table).remove(key)
call Node(node).deallocate()
endmethod
endstruct
module PeriodicListTraversal
method onTraverse takes thistype node returns boolean
static if thistype.onListTraversalPeriod.exists then
return this.onListTraversalPeriod(node)
else
return false
endif
endmethod
static method onInsert takes thistype node returns nothing
static if thistype.onNodeAdded.exists then
call onNodeAdded(node)
endif
if node != node.prev and node.prev == node.next then
call HeadNode.list[thistype.typeid].pushBack(node.prev)
endif
endmethod
static method onRemove takes thistype node returns nothing
static if thistype.onNodeRemoved.exists then
call onNodeRemoved(node)
endif
if node != node.prev and node.prev == node.next then
call HeadNode.list[thistype.typeid].pop(node.prev)
endif
endmethod
implement LinkedListLite
private static method onPeriod takes nothing returns nothing
local HeadNode headList = HeadNode.list[thistype.typeid]
local HeadNode headNode = headList.next
loop
exitwhen headNode == headList
static if thistype.onListTraversalFilter.exists and thistype.onListTraversalPeriod.exists then
if thistype(headNode.data).onListTraversalFilter() then
call thistype(headNode.data).traverseForwards()
endif
elseif thistype.onListTraversalFilter.exists then
call thistype(headNode.data).onListTraversalFilter()
elseif thistype.onListTraversalPeriod.exists then
call thistype(headNode.data).traverseForwards()
endif
set headNode = headNode.next
endloop
endmethod
private static method onInit takes nothing returns nothing
call HeadNode.create(thistype.typeid, LIST_TRAVERSAL_PERIOD, function thistype.onPeriod)
endmethod
endmodule
endlibrary
library Table /* made by Bribe, special thanks to Vexorian & Nestharus, version 4.1.0.1.
One map, one hashtable. Welcome to NewTable 4.1.0.1
This newest iteration of Table introduces the new HashTable struct.
You can now instantiate HashTables which enables the use of large
parent and large child keys, just like a standard hashtable. Previously,
the user would have to instantiate a Table to do this on their own which -
while doable - is something the user should not have to do if I can add it
to this resource myself (especially if they are inexperienced).
This library was originally called NewTable so it didn't conflict with
the API of Table by Vexorian. However, the damage is done and it's too
late to change the library name now. To help with damage control, I
have provided an extension library called TableBC, which bridges all
the functionality of Vexorian's Table except for 2-D string arrays &
the ".flush(integer)" method. I use ".flush()" to flush a child hash-
table, because I wanted the API in NewTable to reflect the API of real
hashtables (I thought this would be more intuitive).
API
------------
struct Table
| static method create takes nothing returns Table
| create a new Table
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush all stored values inside of it
|
| method remove takes integer key returns nothing
| remove the value at index "key"
|
| method operator []= takes integer key, $TYPE$ value returns nothing
| assign "value" to index "key"
|
| method operator [] takes integer key returns $TYPE$
| load the value at index "key"
|
| method has takes integer key returns boolean
| whether or not the key was assigned
|
----------------
struct TableArray
| static method operator [] takes integer array_size returns TableArray
| create a new array of Tables of size "array_size"
|
| method destroy takes nothing returns nothing
| destroy it
|
| method flush takes nothing returns nothing
| flush and destroy it
|
| method operator size takes nothing returns integer
| returns the size of the TableArray
|
| method operator [] takes integer key returns Table
| returns a Table accessible exclusively to index "key"
*/
globals
private integer less = 0 //Index generation for TableArrays (below 0).
private integer more = 8190 //Index generation for Tables.
//Configure it if you use more than 8190 "key" variables in your map (this will never happen though).
private hashtable ht = InitHashtable()
private key sizeK
private key listK
endglobals
private struct dex extends array
static method operator size takes nothing returns Table
return sizeK
endmethod
static method operator list takes nothing returns Table
return listK
endmethod
endstruct
private struct handles extends array
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private struct agents extends array
method operator []= takes integer key, agent value returns nothing
call SaveAgentHandle(ht, this, key, value)
endmethod
endstruct
//! textmacro NEW_ARRAY_BASIC takes SUPER, FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSaved$SUPER$(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSaved$SUPER$(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//! textmacro NEW_ARRAY takes FUNC, TYPE
private struct $TYPE$s extends array
method operator [] takes integer key returns $TYPE$
return Load$FUNC$Handle(ht, this, key)
endmethod
method operator []= takes integer key, $TYPE$ value returns nothing
call Save$FUNC$Handle(ht, this, key, value)
endmethod
method has takes integer key returns boolean
return HaveSavedHandle(ht, this, key)
endmethod
method remove takes integer key returns nothing
call RemoveSavedHandle(ht, this, key)
endmethod
endstruct
private module $TYPE$m
method operator $TYPE$ takes nothing returns $TYPE$s
return this
endmethod
endmodule
//! endtextmacro
//Run these textmacros to include the entire hashtable API as wrappers.
//Don't be intimidated by the number of macros - Vexorian's map optimizer is
//supposed to kill functions which inline (all of these functions inline).
//! runtextmacro NEW_ARRAY_BASIC("Real", "Real", "real")
//! runtextmacro NEW_ARRAY_BASIC("Boolean", "Boolean", "boolean")
//! runtextmacro NEW_ARRAY_BASIC("String", "Str", "string")
//New textmacro to allow table.integer[] syntax for compatibility with textmacros that might desire it.
//! runtextmacro NEW_ARRAY_BASIC("Integer", "Integer", "integer")
//! runtextmacro NEW_ARRAY("Player", "player")
//! runtextmacro NEW_ARRAY("Widget", "widget")
//! runtextmacro NEW_ARRAY("Destructable", "destructable")
//! runtextmacro NEW_ARRAY("Item", "item")
//! runtextmacro NEW_ARRAY("Unit", "unit")
//! runtextmacro NEW_ARRAY("Ability", "ability")
//! runtextmacro NEW_ARRAY("Timer", "timer")
//! runtextmacro NEW_ARRAY("Trigger", "trigger")
//! runtextmacro NEW_ARRAY("TriggerCondition", "triggercondition")
//! runtextmacro NEW_ARRAY("TriggerAction", "triggeraction")
//! runtextmacro NEW_ARRAY("TriggerEvent", "event")
//! runtextmacro NEW_ARRAY("Force", "force")
//! runtextmacro NEW_ARRAY("Group", "group")
//! runtextmacro NEW_ARRAY("Location", "location")
//! runtextmacro NEW_ARRAY("Rect", "rect")
//! runtextmacro NEW_ARRAY("BooleanExpr", "boolexpr")
//! runtextmacro NEW_ARRAY("Sound", "sound")
//! runtextmacro NEW_ARRAY("Effect", "effect")
//! runtextmacro NEW_ARRAY("UnitPool", "unitpool")
//! runtextmacro NEW_ARRAY("ItemPool", "itempool")
//! runtextmacro NEW_ARRAY("Quest", "quest")
//! runtextmacro NEW_ARRAY("QuestItem", "questitem")
//! runtextmacro NEW_ARRAY("DefeatCondition", "defeatcondition")
//! runtextmacro NEW_ARRAY("TimerDialog", "timerdialog")
//! runtextmacro NEW_ARRAY("Leaderboard", "leaderboard")
//! runtextmacro NEW_ARRAY("Multiboard", "multiboard")
//! runtextmacro NEW_ARRAY("MultiboardItem", "multiboarditem")
//! runtextmacro NEW_ARRAY("Trackable", "trackable")
//! runtextmacro NEW_ARRAY("Dialog", "dialog")
//! runtextmacro NEW_ARRAY("Button", "button")
//! runtextmacro NEW_ARRAY("TextTag", "texttag")
//! runtextmacro NEW_ARRAY("Lightning", "lightning")
//! runtextmacro NEW_ARRAY("Image", "image")
//! runtextmacro NEW_ARRAY("Ubersplat", "ubersplat")
//! runtextmacro NEW_ARRAY("Region", "region")
//! runtextmacro NEW_ARRAY("FogState", "fogstate")
//! runtextmacro NEW_ARRAY("FogModifier", "fogmodifier")
//! runtextmacro NEW_ARRAY("Hashtable", "hashtable")
struct Table extends array
// Implement modules for intuitive syntax (tb.handle; tb.unit; etc.)
implement realm
implement integerm
implement booleanm
implement stringm
implement playerm
implement widgetm
implement destructablem
implement itemm
implement unitm
implement abilitym
implement timerm
implement triggerm
implement triggerconditionm
implement triggeractionm
implement eventm
implement forcem
implement groupm
implement locationm
implement rectm
implement boolexprm
implement soundm
implement effectm
implement unitpoolm
implement itempoolm
implement questm
implement questitemm
implement defeatconditionm
implement timerdialogm
implement leaderboardm
implement multiboardm
implement multiboarditemm
implement trackablem
implement dialogm
implement buttonm
implement texttagm
implement lightningm
implement imagem
implement ubersplatm
implement regionm
implement fogstatem
implement fogmodifierm
implement hashtablem
method operator handle takes nothing returns handles
return this
endmethod
method operator agent takes nothing returns agents
return this
endmethod
//set this = tb[GetSpellAbilityId()]
method operator [] takes integer key returns Table
return LoadInteger(ht, this, key) //return this.integer[key]
endmethod
//set tb[389034] = 8192
method operator []= takes integer key, Table tb returns nothing
call SaveInteger(ht, this, key, tb) //set this.integer[key] = tb
endmethod
//set b = tb.has(2493223)
method has takes integer key returns boolean
return HaveSavedInteger(ht, this, key) //return this.integer.has(key)
endmethod
//call tb.remove(294080)
method remove takes integer key returns nothing
call RemoveSavedInteger(ht, this, key) //call this.integer.remove(key)
endmethod
//Remove all data from a Table instance
method flush takes nothing returns nothing
call FlushChildHashtable(ht, this)
endmethod
//local Table tb = Table.create()
static method create takes nothing returns Table
local Table this = dex.list[0]
if this == 0 then
set this = more + 1
set more = this
else
set dex.list[0] = dex.list[this]
call dex.list.remove(this) //Clear hashed memory
endif
debug set dex.list[this] = -1
return this
endmethod
// Removes all data from a Table instance and recycles its index.
//
// call tb.destroy()
//
method destroy takes nothing returns nothing
debug if dex.list[this] != -1 then
debug call BJDebugMsg("Table Error: Tried to double-free instance: " + I2S(this))
debug return
debug endif
call this.flush()
set dex.list[this] = dex.list[0]
set dex.list[0] = this
endmethod
//! runtextmacro optional TABLE_BC_METHODS()
endstruct
//! runtextmacro optional TABLE_BC_STRUCTS()
struct TableArray extends array
//Returns a new TableArray to do your bidding. Simply use:
//
// local TableArray ta = TableArray[array_size]
//
static method operator [] takes integer array_size returns TableArray
local Table tb = dex.size[array_size] //Get the unique recycle list for this array size
local TableArray this = tb[0] //The last-destroyed TableArray that had this array size
debug if array_size <= 0 then
debug call BJDebugMsg("TypeError: Invalid specified TableArray size: " + I2S(array_size))
debug return 0
debug endif
if this == 0 then
set this = less - array_size
set less = this
else
set tb[0] = tb[this] //Set the last destroyed to the last-last destroyed
call tb.remove(this) //Clear hashed memory
endif
set dex.size[this] = array_size //This remembers the array size
return this
endmethod
//Returns the size of the TableArray
method operator size takes nothing returns integer
return dex.size[this]
endmethod
//This magic method enables two-dimensional[array][syntax] for Tables,
//similar to the two-dimensional utility provided by hashtables them-
//selves.
//
//ta[integer a].unit[integer b] = unit u
//ta[integer a][integer c] = integer d
//
//Inline-friendly when not running in debug mode
//
method operator [] takes integer key returns Table
static if DEBUG_MODE then
local integer i = this.size
if i == 0 then
call BJDebugMsg("IndexError: Tried to get key from invalid TableArray instance: " + I2S(this))
return 0
elseif key < 0 or key >= i then
call BJDebugMsg("IndexError: Tried to get key [" + I2S(key) + "] from outside TableArray bounds: " + I2S(i))
return 0
endif
endif
return this + key
endmethod
//Destroys a TableArray without flushing it; I assume you call .flush()
//if you want it flushed too. This is a public method so that you don't
//have to loop through all TableArray indices to flush them if you don't
//need to (ie. if you were flushing all child-keys as you used them).
//
method destroy takes nothing returns nothing
local Table tb = dex.size[this.size]
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to destroy an invalid TableArray: " + I2S(this))
debug return
debug endif
if tb == 0 then
//Create a Table to index recycled instances with their array size
set tb = Table.create()
set dex.size[this.size] = tb
endif
call dex.size.remove(this) //Clear the array size from hash memory
set tb[this] = tb[0]
set tb[0] = this
endmethod
private static Table tempTable
private static integer tempEnd
//Avoids hitting the op limit
private static method clean takes nothing returns nothing
local Table tb = .tempTable
local integer end = tb + 0x1000
if end < .tempEnd then
set .tempTable = end
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
else
set end = .tempEnd
endif
loop
call tb.flush()
set tb = tb + 1
exitwhen tb == end
endloop
endmethod
//Flushes the TableArray and also destroys it. Doesn't get any more
//similar to the FlushParentHashtable native than this.
//
method flush takes nothing returns nothing
debug if this.size == 0 then
debug call BJDebugMsg("TypeError: Tried to flush an invalid TableArray instance: " + I2S(this))
debug return
debug endif
set .tempTable = this
set .tempEnd = this + this.size
call ForForce(bj_FORCE_PLAYER[0], function thistype.clean)
call this.destroy()
endmethod
endstruct
//NEW: Added in Table 4.0. A fairly simple struct but allows you to do more
//than that which was previously possible.
struct HashTable extends array
//Enables myHash[parentKey][childKey] syntax.
//Basically, it creates a Table in the place of the parent key if
//it didn't already get created earlier.
method operator [] takes integer index returns Table
local Table t = Table(this)[index]
if t == 0 then
set t = Table.create()
set Table(this)[index] = t //whoops! Forgot that line. I'm out of practice!
endif
return t
endmethod
//You need to call this on each parent key that you used if you
//intend to destroy the HashTable or simply no longer need that key.
method remove takes integer index returns nothing
local Table t = Table(this)[index]
if t != 0 then
call t.destroy()
call Table(this).remove(index)
endif
endmethod
//Added in version 4.1
method has takes integer index returns boolean
return Table(this).has(index)
endmethod
//HashTables are just fancy Table indices.
method destroy takes nothing returns nothing
call Table(this).destroy()
endmethod
//Like I said above...
static method create takes nothing returns thistype
return Table.create()
endmethod
endstruct
endlibrary
library UnitDex uses optional WorldBounds, optional GroupUtils
/***************************************************************
*
* v1.2.2.0, by TriggerHappy
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex assigns every unit an unique integer. It attempts to make that number within the
* maximum array limit so you can associate it with one.
* _________________________________________________________________________
* 1. Installation
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Copy the script to your map, save it, then restart the editor and comment out the line below.
*/
// external ObjectMerger w3a Adef uDex anam "Detect Leave" ansf "(UnitDex)" aart "" acat "" arac 0
/* _________________________________________________________________________
* 2. Configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
private module UnitDexConfig
// The raw code of the leave detection ability.
static constant integer DETECT_LEAVE_ABILITY = 'uDex'
// Allow debug messages (debug mode must also be on)
static constant boolean ALLOW_DEBUGGING = true
// Uncomment the lines below to define a global filter for units being indexed
/*static method onFilter takes unit u returns boolean
return true
endmethod*/
endmodule
/* _________________________________________________________________________
* 3. Function API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* Every function inlines.
*
* function GetUnitId takes unit whichUnit returns integer
* function GetUnitById takes integer index returns unit
*
* function UnitDexEnable takes boolean flag returns nothing
* function UnitDexRemove takes unit u, boolean runEvents returns boolean
*
* function IsUnitIndexed takes unit u returns boolean
* function IsIndexingEnabled takes nothing returns boolean
*
* function GetIndexedUnit takes nothing returns unit
* function GetIndexedUnitId takes nothing returns integer
*
* function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns indexevent
* function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
* function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
*
* function OnUnitIndex takes code func returns triggercondition
* function OnUnitDeidex takes code func returns triggercondition
* _________________________________________________________________________
* 4. Struct API
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* UnitDex.Enabled = false // toggle the indexer
* UnitDex.Initialized // returns true if the preload timer has finished
* UnitDex.Count // returns the amount of units indexed
* UnitDex.Unit[0] // access the UnitDex array directly
* UnitDex.Group // a unit group containing every indexed unit (for enumeration)
* UnitDex.LastIndex // returns the last indexed unit's id
* _________________________________________________________________________
* 5. Public Variables
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* These are to be used with the "eventtype" argument of the event API:
*
* constant integer EVENT_UNIT_INDEX = 0
* constant integer EVENT_UNIT_DEINDEX = 1
* _________________________________________________________________________
* 6. Examples
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Associate a unit with a variable
*
* set UnitFlag[GetUnitId(yourUnit)] = true
*
* 2. Allocate a struct instance using a units assigned ID
*
* local somestruct data = GetUnitId(yourUnit)
*
* 3. Detect when a unit leaves the map
*
* function Exit takes nothing returns nothing
* call BJDebugMsg("The unit " + GetUnitName(GetIndexedUnit()) + " has left the map.")
* endfunction
*
* call OnUnitDeindex(function Exit)
* // or
* call RegisterUnitIndexEvent(Filter(function Exit), EVENT_UNIT_DEINDEX)
*
*
* _________________________________________________________________________
* 7. How it works
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* 1. Enumerate all preplaced units
* 2. TriggerRegisterEnterRegion native to detect when a unit enters the map
* 3. Assign each unit that enters the map a unique integer
* 4. Give every unit an ability based off of defend. There is no native to accurately
* detect when a unit leaves the map but when a unit dies or is removed from the game
* they are issued the "undefend" order
* 5. Catch the "undefend" order and remove unit from the queue
* _________________________________________________________________________
* 8. Notes
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* - This system is compatable with GUI because it utilizes UnitUserData (custom values for units).
* - The object merger line should be commented out after saving and restarting.
*
* -http://www.hiveworkshop.com/forums/submissions-414/unitdex-lightweight-unit-indexer-248209/
*
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
// Event types
constant integer EVENT_UNIT_INDEX = 0
constant integer EVENT_UNIT_DEINDEX = 1
// System variables
private trigger array IndexTrig
private integer Index = 0
private real E=-1
private boolexpr FilterEnter
endglobals
function GetUnitId takes unit whichUnit returns integer
return GetUnitUserData(whichUnit)
endfunction
function GetUnitById takes integer index returns unit
return UnitDex.Unit[index]
endfunction
function GetIndexedUnit takes nothing returns unit
return UnitDex.Unit[UnitDex.LastIndex]
endfunction
function GetIndexedUnitId takes nothing returns integer
return UnitDex.LastIndex
endfunction
function IsUnitIndexed takes unit u returns boolean
return (GetUnitById(GetUnitId(u)) != null)
endfunction
function UnitDexEnable takes boolean flag returns nothing
set UnitDex.Enabled = flag
endfunction
function IsIndexingEnabled takes nothing returns boolean
return UnitDex.Enabled
endfunction
function RegisterUnitIndexEvent takes boolexpr func, integer eventtype returns triggercondition
return TriggerAddCondition(IndexTrig[eventtype], func)
endfunction
function RemoveUnitIndexEvent takes triggercondition c, integer eventtype returns nothing
call TriggerRemoveCondition(IndexTrig[eventtype], c)
endfunction
function TriggerRegisterUnitIndexEvent takes trigger t, integer eventtype returns nothing
call TriggerRegisterVariableEvent(t, SCOPE_PRIVATE + "E", EQUAL, eventtype)
endfunction
function OnUnitIndex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_INDEX], Filter(func))
endfunction
function OnUnitDeindex takes code func returns triggercondition
return TriggerAddCondition(IndexTrig[EVENT_UNIT_DEINDEX], Filter(func))
endfunction
/****************************************************************/
private keyword UnitDexCore
struct UnitDex extends array
static boolean Enabled = true
readonly static integer LastIndex
readonly static boolean Initialized=false
readonly static group Group=CreateGroup()
readonly static unit array Unit
readonly static integer Count = 0
readonly static integer array List
implement UnitDexConfig
private static integer Counter = 0
implement UnitDexCore
endstruct
function UnitDexRemove takes unit u, boolean runEvents returns boolean
return UnitDex.Remove(u, runEvents)
endfunction
/****************************************************************/
private module UnitDexCore
static method Remove takes unit u, boolean runEvents returns boolean
local integer i
if (IsUnitIndexed(u)) then
set i = GetUnitId(u)
set UnitDex.List[i] = Index
set Index = i
call GroupRemoveUnit(UnitDex.Group, u)
call SetUnitUserData(u, 0)
if (runEvents) then
set UnitDex.LastIndex = i
set E = EVENT_UNIT_DEINDEX
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
set E = -1
endif
set UnitDex.Unit[i] = null
set UnitDex.Count = UnitDex.Count - 1
return true
endif
return false
endmethod
private static method onGameStart takes nothing returns nothing
local integer i = 1
loop
exitwhen i > Counter
set LastIndex = i
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
set E = EVENT_UNIT_INDEX
set E = -1
set i = i + 1
endloop
set LastIndex = Counter
set Initialized = true
set FilterEnter = null
call DestroyTimer(GetExpiredTimer())
endmethod
private static method onEnter takes nothing returns boolean
local unit u = GetFilterUnit()
local integer i = GetUnitId(u)
local integer t = Index
if (i == 0 and thistype.Enabled) then
// If a filter was defined pass the unit through it.
static if (thistype.onFilter.exists) then
if (not thistype.onFilter(u)) then
set u = null
return false // check failed
endif
endif
// Handle debugging
static if (thistype.DEBUG_MODE and thistype.ALLOW_DEBUGGING) then
if (t == 0 and Counter+1 >= JASS_MAX_ARRAY_SIZE) then
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, "UnitDex: Maximum number of units reached!")
set u = null
return false
endif
endif
// Add to group of indexed units
call GroupAddUnit(thistype.Group, u)
// Give unit the leave detection ability
call UnitAddAbility(u, thistype.DETECT_LEAVE_ABILITY)
call UnitMakeAbilityPermanent(u, true, thistype.DETECT_LEAVE_ABILITY)
// Allocate index
if (Index != 0) then
set Index = List[t]
else
set Counter = Counter + 1
set t = Counter
endif
set List[t] = -1
set LastIndex = t
set thistype.Unit[t] = u
set Count = Count + 1
// Store the index
call SetUnitUserData(u, t)
if (thistype.Initialized) then
// Execute custom events registered with RegisterUnitIndexEvent
call TriggerEvaluate(IndexTrig[EVENT_UNIT_INDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_INDEX
// Reset so the event can occur again
set E = -1
endif
endif
set u = null
return false
endmethod
private static method onLeave takes nothing returns boolean
local unit u
local integer i
// Check if order is undefend.
if (thistype.Enabled and GetIssuedOrderId() == 852056) then
set u = GetTriggerUnit()
// If unit was killed (not removed) then don't continue
if (GetUnitAbilityLevel(u, thistype.DETECT_LEAVE_ABILITY) != 0) then
set u = null
return false
endif
set i = GetUnitId(u)
// If unit has been indexed then deindex it
if (i > 0 and i <= Counter and u == GetUnitById(i)) then
// Recycle the index
set List[i] = Index
set Index = i
set LastIndex = i
// Remove to group of indexed units
call GroupRemoveUnit(thistype.Group, u)
// Execute custom events without any associated triggers
call TriggerEvaluate(IndexTrig[EVENT_UNIT_DEINDEX])
// Handle TriggerRegisterUnitIndexEvent
set E = EVENT_UNIT_DEINDEX
// Remove entry
call SetUnitUserData(u, 0)
set thistype.Unit[i] = null
// Decrement unit count
set Count = Count - 1
// Reset so the event can occur again
set E = -1
endif
set u = null
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
local player p
local unit u
static if (not LIBRARY_WorldBounds) then // Check if WorldBounts exists, if not then define the necessary vars
local region reg = CreateRegion() // If WorldBounds wasn't found, create the region manually
local rect world = GetWorldBounds()
endif
static if (not LIBRARY_GroupUtils) then // Check if GroupUtils exists so we can use it's enumeration group.
local group ENUM_GROUP = CreateGroup() // If not, create the group.
endif
set FilterEnter = Filter(function thistype.onEnter)
// Begin to index units when they enter the map
static if (LIBRARY_WorldBounds) then
call TriggerRegisterEnterRegion(CreateTrigger(), WorldBounds.worldRegion, FilterEnter)
else
call RegionAddRect(reg, world)
call TriggerRegisterEnterRegion(CreateTrigger(), reg, FilterEnter)
call RemoveRect(world)
set world = null
endif
call TriggerAddCondition(t, Filter(function thistype.onLeave))
set IndexTrig[EVENT_UNIT_INDEX] = CreateTrigger()
set IndexTrig[EVENT_UNIT_DEINDEX] = CreateTrigger()
loop
set p = Player(i)
// Detect "undefend"
call TriggerRegisterPlayerUnitEvent(t, p, EVENT_PLAYER_UNIT_ISSUED_ORDER, null)
// Hide the detect ability from players
call SetPlayerAbilityAvailable(p, thistype.DETECT_LEAVE_ABILITY, false)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
// Index preplaced units
set i = 0
loop
call GroupEnumUnitsOfPlayer(ENUM_GROUP, Player(i), FilterEnter)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
static if (not LIBRARY_GroupUtils) then
call DestroyGroup(ENUM_GROUP)
set ENUM_GROUP = null
endif
set LastIndex = Counter
// run init triggers
call TimerStart(CreateTimer(), 0.00, false, function thistype.onGameStart)
endmethod
endmodule
endlibrary
library Missile /* version 3.2.0 (Update for patches 1.31+)
*************************************************************************************
*
* Resource link:
* https://www.hiveworkshop.com/threads/missile.325956/
*
* Legacy Resource Link (for patches 1.30.x and below):
* https://www.hiveworkshop.com/threads/missile.265370/
*
*************************************************************************************
*
* Original Resource by BPower, updated by AGD
*
* Credits to Dirac, emjlr3, AceHart, Bribe, Wietlol, Nestharus,
* Maghteridon96, Vexorian, Zwiebelchen, and Chopinski
*
*************************************************************************************
*
* Creating custom projectiles in Warcraft III.
*
* Philosophy:
* I want that feature --> Compiler writes that code into your map script.
* I don't want that --> Compiler ignores that code completely.
*
* 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
*
*************************************************************************************
*
* */ requires /*
*
* */ Table /* https://www.hiveworkshop.com/threads/188084/
* */ SpecialEffect /* https://www.hiveworkshop.com/threads/325954/ | Should use atleast v1.1.0
* */ LinkedList /* https://www.hiveworkshop.com/threads/325635/
*
*************************************************************************************
*
* */ optional Alloc /* https://www.hiveworkshop.com/threads/324937/
* */ optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*
************************************************************************************
*
* 1. Import instruction
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
* • Import the required libraries.
* • Copy Missile into to your map.
*
* 2. Global configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
globals
/**
* Missiles are moved periodically. An interval of 1./32. is recommended.
* • Too short timeout intervals may cause performance issues.
* • Too large timeout intervals may look fishy.
*/
public constant real TIMER_TIMEOUT = 1./32.
/**
* 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 required.
* • 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 required 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
/**
* DELAYED_MISSILE_DEATH_ANIMATION_TIME is the delay in seconds
* to accomodate for the sfx death duration of destroyed Missiles
*/
private constant real DELAYED_MISSILE_DEATH_ANIMATION_TIME = 2.16
endglobals
/*
* 3. Function configuration
* ¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯¯
*/
/**
* GetUnitHeight(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.
*/
public function GetUnitHeight takes unit whichUnit returns real
return 108.// Other example: return LoadReal(hash, GetHandleId(whichUnit), KEY_UNIT_HEIGHT)
endfunction
/**
* Same as GetUnitHeight, but for destructables.
* Using occluder height is an idea of mine. Of course you can use your own values.
*/
public function GetDestHeight takes destructable d returns real
return GetDestructableOccluderHeight(d)// Other example: return 72.
endfunction
/**
* Again it's up to you to figure out a fictional item height.
*/
public function GetItemHeight takes item i returns real
return 16.
endfunction
public function GetDestCollisionSize takes destructable d returns real
return 43.2
endfunction
public function GetItemCollisionSize takes item i returns real
return 18.
endfunction
/**
* 4. API
* ¯¯¯¯¯¯
*/
//! novjass
// Custom type Missile for your projectile needs.
struct Missile extends array
// Constants:
// ==========
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 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 the x-y-z axes
// Fields you can only read:
// =========================
readonly SpecialEffect effect
readonly boolean allocated
// 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
// • For multiple effects manipulate "this.effect" directly instead.
method operator scale= takes real value returns nothing
method operator scale takes nothing returns real
// • Set and read the scaling of 'this.effect'.
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.
method operator spin= takes real rps returns nothing
method operator spin takes nothing returns real
// • Spin in rad/sec
// 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 MissileLaunch // ( optional )
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.
static method onItem takes Missile missile, item hit returns boolean
// • Runs for items in collision range of a missile.
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.
// • 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 real MAXIMUM_COLLISION_SIZE
public constant boolean USE_COLLISION_Z_FILTER
readonly static constant real HIT_BOX
// Functions:
// ==========
//
public function GetLocZ takes real x, real y returns real
public function GetUnitHeight takes unit u returns real
public function GetDestHeight takes destructable d returns real
public function GetItemHeight takes item i returns real
public function GetDestCollisionSize takes destructable d returns real
public function GetItemCollisionSize takes item i returns real
//========================================================================
// Missile system. Make changes carefully.
//========================================================================
//! endnovjass
// 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!
private keyword Init
globals
// Core constant handle variables of Missile.
private constant trigger CORE = CreateTrigger()
private constant trigger MOVE = 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()
// For starting and stopping the timer.
private integer active = 0
// Arrays for data structure.
private integer array instances
private Missile array missileList
private boolexpr array expression
private triggercondition array condition
private unit tempUnit
private item tempItem
private destructable tempDest
private TableArray table
endglobals
public function GetLocZ takes real x, real y returns real
call MoveLocation(LOC, x, y)
return GetLocationZ(LOC)
endfunction
/*===============================================================================================*/
/*
* One allocator for the whole library
*/
private struct Node extends array
static if LIBRARY_Alloc then
implement optional Alloc
else
/*
* Credits to MyPad for the allocation algorithm
*/
private static thistype array stack
static method allocate takes nothing returns thistype
local thistype node = stack[0]
if stack[node] == 0 then
static if LIBRARY_ErrorMessage then
debug call ThrowError(node == (JASS_MAX_ARRAY_SIZE - 1), "Missile", "allocate()", "thistype", node, "Overflow")
endif
set node = node + 1
set stack[0] = node
else
set stack[0] = stack[node]
set stack[node] = 0
endif
return node
endmethod
method deallocate takes nothing returns nothing
static if LIBRARY_ErrorMessage then
debug call ThrowError(this == 0, "Missile", "deallocate()", "thistype", 0, "Null node")
debug call ThrowError(stack[this] > 0, "Missile", "deallocate()", "thistype", this, "Double-free")
endif
set stack[this] = stack[0]
set stack[0] = this
endmethod
endif
endstruct
// Simple trigonometry.
struct MissilePosition extends array
readonly real x
readonly real y
readonly real z
readonly real angle
readonly real distance
readonly real square
readonly real slope
readonly real alpha
// Creates an origin - impact link.
private thistype ref
private static method math takes thistype a, thistype b returns nothing
local real dx
local real dy
loop
set dx = b.x - a.x
set dy = b.y - a.y
set dx = dx*dx + dy*dy
set dy = SquareRoot(dx)
exitwhen dx != 0. and dy != 0.
set b.x = b.x + .01
set b.z = b.z - GetLocZ(b.x -.01, b.y) + GetLocZ(b.x, b.y)
endloop
set a.square = dx
set a.distance = dy
set a.angle = Atan2(b.y - a.y, b.x - a.x)
set a.slope = (b.z - a.z)/dy
set a.alpha = Atan(a.slope)
// Set b.
if b.ref == a then
set b.angle = a.angle + bj_PI
set b.distance = dy
set b.slope = -a.slope
set b.alpha = -a.alpha
set b.square = dx
endif
endmethod
static method link takes thistype a, thistype b returns nothing
set a.ref = b
set b.ref = a
call math(a, b)
endmethod
method move takes real toX, real toY, real toZ returns nothing
set this.x = toX
set this.y = toY
set this.z = toZ + GetLocZ(toX, toY)
if this.ref != this then
call math(this, this.ref)
endif
endmethod
static method create takes real x, real y, real z returns MissilePosition
local thistype this = Node.allocate()
set this.ref = this
call this.move(x, y, z)
return this
endmethod
method destroy takes nothing returns nothing
call Node(this).deallocate()
endmethod
endstruct
struct MissileList extends array
method operator missile takes nothing returns Missile
return this
endmethod
implement StaticList
endstruct
struct Missile extends array
// 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.)
// Checks for double launching. Throws an error message.
debug boolean launched
// 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 posX / posY for cool mathematics.
private real posX
private real posY
private real dist// distance
// Readonly members:
// =================
//
// Prevents a double free case.
readonly boolean allocated
readonly SpecialEffect effect
// 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 angle// Current angle
readonly real prevX
readonly real prevY
readonly real prevZ
// Collision detection type. ( Evaluated new in each loop )
readonly integer collisionType// Current collision type ( circular or rectangle )
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 length 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.
real collision // Collision size in plane x / y.
private static method onInsert takes thistype node returns nothing
call MissileList.pushBack(node)
endmethod
private static method onRemove takes thistype node returns nothing
call MissileList.remove(node)
endmethod
implement List
method operator x takes nothing returns real
return this.effect.x
endmethod
method operator y takes nothing returns real
return this.effect.y
endmethod
method operator z takes nothing returns real
return this.effect.height
endmethod
private string path
method operator model= takes string file returns nothing
call this.effect.kill(DELAYED_MISSILE_DEATH_ANIMATION_TIME, false)
// null and ""
if StringLength(file) > 0 then
call BlzSetSpecialEffectScale(this.effect.addModel(file), this.scaling)
set this.path = file
else
set this.path = null
endif
endmethod
method operator model takes nothing returns string
return this.path
endmethod
readonly real open
// Enables curved movement for your missile.
// Remember that the result of Tan(PI/2) is infinity.
method operator curve= takes real value returns nothing
set this.open = Tan(value)*this.origin.distance/4
endmethod
method operator curve takes nothing returns real
return Atan(4*this.open/this.origin.distance)
endmethod
readonly real height
// Enables arcing movement for your missile.
method operator arc= takes real value returns nothing
set this.height = Tan(value)*this.origin.distance/4
endmethod
method operator arc takes nothing returns real
return Atan(4*this.height/this.origin.distance)
endmethod
private real spinRate
private real currentRoll
method operator spin= takes real value returns nothing
set this.spinRate = value*TIMER_TIMEOUT
endmethod
method operator spin takes nothing returns real
return this.spinRate/TIMER_TIMEOUT
endmethod
private real scaling
method operator scale= takes real value returns nothing
call this.effect.resetIterator()
loop
exitwhen this.effect.moveIterator()
call BlzSetSpecialEffectScale(this.effect.currentHandle(), value)
endloop
set this.scaling = value
endmethod
method operator scale takes nothing returns real
return this.scaling
endmethod
method bounce takes nothing returns nothing
call this.origin.move(this.x, this.y, this.z)
set this.dist = 0.
endmethod
method deflect takes real tx, real ty returns nothing
local real a = 2.*Atan2(ty - this.y, tx - this.x) + bj_PI - this.angle
call this.impact.move(x + (origin.distance - this.dist)*Cos(a), this.y + (this.origin.distance - this.dist)*Sin(a), this.impact.z)
call this.bounce()
endmethod
method deflectEx takes real tx, real ty, real tz returns nothing
call this.impact.move(this.impact.x, this.impact.y, tz)
call this.deflect(tx, ty)
endmethod
method flightTime2Speed takes real duration returns nothing
set this.speed = RMaxBJ(0.00000001, (this.origin.distance - this.dist)*Missile_TIMER_TIMEOUT/RMaxBJ(0.00000001, duration))
endmethod
method setMovementSpeed takes real value returns nothing
set this.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 this.wantDestroy = true
endmethod
// Instantly destroys a missile.
method terminate takes nothing returns nothing
if this.allocated then
set this.allocated = false
call this.impact.destroy()
call this.origin.destroy()
call table[this].flush()
set this.source = null
set this.target = null
set this.owner = null
call this.effect.kill(DELAYED_MISSILE_DEATH_ANIMATION_TIME, true)
call Node(this).deallocate()
call remove(this)
endif
endmethod
// Runs in createEx.
private method resetMembers takes nothing returns nothing
set this.path = null
set this.scaling = 1.
set this.speed = 0.
set this.acceleration = 0.
set this.spinRate = 0.
set this.distance = 0.
set this.dist = 0.
set this.height = 0.
set this.turn = 0.
set this.open = 0.
set this.collision = 0.
set this.collisionType = 0
set this.stackSize = 0
set this.wantDestroy = false
set this.recycle = false
endmethod
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)
local thistype this = Node.allocate()
call this.resetMembers()
set this.effect = SpecialEffect.create(x, y, z + GetLocZ(x, y))
set this.origin = MissilePosition.create(x, y, z)
set this.impact = MissilePosition.create(impactX, impactY, impactZ)
call MissilePosition.link(this.origin, this.impact)
set this.currentRoll = 0.
set this.posX = x
set this.posY = y
set this.angle = this.origin.angle
set this.allocated = true
debug set this.launched = false
return this
endmethod
static method createXYZ takes real x, real y, real z, real impactX, real impactY, real impactZ returns thistype
local real dx = impactX - x
local real dy = impactY - y
local real dz = impactZ - z
return create(x, y, z, Atan2(dy, dx), SquareRoot(dx*dx + dy*dy), impactZ)
endmethod
// Missile motion takes place every Missile_TIMER_TIMEOUT
// before accessing each active struct.
static Missile temp = 0
static method move takes nothing returns boolean
local integer loops = 0 // Current iteration.
local integer limit = 150 // Set iteration border per trigger evaluation to avoid hitting the operation limit.
local thistype this = thistype.temp
local MissilePosition p
local real a
local real d
local real roll
local unit u
local real newX
local real newY
local real newZ
local real prevAbsZ
local real vel
local real point
loop
exitwhen this == MissileList.head or loops == limit
set p = this.origin
// Save previous, respectively current missile position.
set this.prevX = this.x
set this.prevY = this.y
set this.prevZ = this.z
set prevAbsZ = this.effect.z
// Evaluate the collision type.
set vel = this.speed
set this.speed = vel + this.acceleration
if vel < this.collision*Missile_COLLISION_ACCURACY_FACTOR then
set this.collisionType = Missile_COLLISION_TYPE_CIRCLE
else
set this.collisionType = Missile_COLLISION_TYPE_RECTANGLE
endif
// Update missile guidance to its intended target.
set u = this.target
if u != null then
if 0 == GetUnitTypeId(u) then
set this.target = null
else
call p.move(this.prevX, this.prevY, this.prevZ)
call this.impact.move(GetUnitX(u), GetUnitY(u), GetUnitFlyHeight(u) + GetUnitHeight(u)*Missile.HIT_BOX)
set this.dist = 0
set this.height = 0
set this.curve = 0
endif
endif
set a = p.angle
// Update the missile facing angle depending on the turn ratio.
if 0. != this.turn and Cos(this.angle - a) < Cos(this.turn) then
if 0. > Sin(a - this.angle) then
set this.angle = this.angle - this.turn
else
set this.angle = this.angle + this.turn
endif
else
set this.angle = a
endif
// Update the missile position on the parabola.
set d = p.distance// origin - impact distance.
set this.recycle = this.dist + vel >= d
if this.recycle then
set point = d
set this.distance = this.distance + d - this.dist
set newX = this.impact.x
set newY = this.impact.y
set newZ = this.impact.z
set this.posX = newX
set this.posY = newY
else
set this.distance = this.distance + vel
set point = this.dist + vel
set newX = this.posX + vel*Cos(this.angle)
set newY = this.posY + vel*Sin(this.angle)
set this.posX = newX
set this.posY = newY
// Update point(x/y) if a curving trajectory is defined.
if 0. != this.open and u == null then
set vel = 4*this.open*point*(d - point)/p.square
set a = this.angle + bj_PI/2
set newX = newX + vel*Cos(a)
set newY = newY + vel*Sin(a)
if vel < 0.00 then
set a = Atan2(newY - this.prevY, newX - this.prevX) + bj_PI
else
set a = Atan2(newY - this.prevY, newX - this.prevX)
endif
else
set a = this.angle
endif
// Update pos z if an arc or height is set.
if 0. == this.height and 0. == p.alpha then
set newZ = p.z
else
set newZ = p.z + p.slope*point
if 0. != this.height and u == null then
set newZ = newZ + (4*this.height*point*(d - point)/p.square)
endif
endif
endif
set this.dist = point
set this.terrainZ = GetLocZ(newX, newY)
if this.open < 0.00 then
set roll = Atan2(this.open, this.height) + bj_PI
else
set roll = Atan2(this.open, this.height)
endif
set this.currentRoll = this.currentRoll - this.spinRate
// Set missile position and orientation
call this.effect.move(newX, newY, newZ)
call this.effect.setOrientation(a, -Atan2(newZ - prevAbsZ, vel), this.currentRoll - roll)
set loops = loops + 1
set this = MissileList(this).next
endloop
set u = null
set thistype.temp = this
return this == MissileList.head
endmethod
// Widget collision API:
// =====================
//
// Runs automatically on widget collision.
method hitWidget takes widget w returns nothing
if w != null then
set table[this].widget[GetHandleId(w)] = w
endif
endmethod
// All widget which have been hit return true.
method hasHitWidget takes widget w returns boolean
return table[this].handle.has(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
if w != null then
call table[this].handle.remove(GetHandleId(w))
endif
endmethod
// Flushes a missile's memory for collision. ( All widgets can be hit again )
method flushHitWidgets takes nothing returns nothing
call table[this].flush()
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 Table t
if w != null then
set t = table[this]
if not t.has(id) then
set t[id] = stackSize
set t[stackSize] = id
set stackSize = stackSize + 1
endif
set t.real[id] = seconds
endif
endmethod
method updateStack takes nothing returns nothing
local integer dex = 0
local integer id
local real time
local Table t
loop
exitwhen dex == stackSize
set t = table[this]
set id = t[dex]
set time = t.real[id] - Missile_TIMER_TIMEOUT
if time <= 0. or not t.handle.has(id) then
set stackSize = stackSize - 1
set id = t[stackSize]
set t[dex] = id
set t[id] = dex
// Enables hit.
call t.handle.remove(id)
// Remove data from stack.
call t.real.remove(id)
call t.remove(id)
call t.remove(stackSize)
else
set t.real[id] = time
set dex = dex + 1
endif
endloop
endmethod
// Widget collision code:
// ======================
//
private static boolean circle = true
//
// 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 isWidgetInRange takes real x, real y, real ws, real ms returns boolean
set x = x - this.x
set y = y - this.y
set ws = ws + ms
return x*x + y*y <= ws*ws
endmethod
private method isWidgetInRect takes real x, real y, real ws, real ms returns boolean
local real dx = this.x - this.prevX
local real dy = this.y - this.prevY
local real s = (dx*(x - this.prevX) + dy*(y - this.prevY))/(dx*dx + dy*dy)
if s < 0. then
set s = 0.
elseif s > 1 then
set s = 1.
endif
set dx = (this.prevX + s*dx) - x
set dy = (this.prevY + s*dy) - y
set ws = ws + ms
return dx*dx + dy*dy <= ws*ws
endmethod
private method isUnitInRect takes real x, real y returns boolean
local real dx = this.x - this.prevX
local real dy = this.y - this.prevY
local real s = (dx*(x - this.prevX) + dy*(y - this.prevY))/(dx*dx + dy*dy)
if s < 0. then
set s = 0.
elseif s > 1 then
set s = 1.
endif
return IsUnitInRangeXY(tempUnit, this.prevX + s*dx, this.prevY + s*dy, this.collision)
endmethod
static if Missile_USE_COLLISION_Z_FILTER then
private method checkZCollision takes real z, real wz returns boolean
set z = z - this.terrainZ
return z + wz >= this.z - this.collision and z <= this.z + this.collision
endmethod
endif
private method isWidgetInCollision takes widget w, real ws, real wz, real ms returns boolean
local real x = GetWidgetX(w)
local real y = GetWidgetY(w)
static if Missile_USE_COLLISION_Z_FILTER then
if circle then
return this.checkZCollision(GetLocZ(x, y), wz) and this.isWidgetInRange(x, y, ws, ms)
endif
return this.checkZCollision(GetLocZ(x, y), wz) and this.isWidgetInRect(x, y, ws, ms)
else
if circle then
return this.isWidgetInRange(x, y, ws, ms)
endif
return this.isWidgetInRect(x, y, ws, ms)
endif
endmethod
private method isUnitInCollision takes nothing returns boolean
static if Missile_USE_COLLISION_Z_FILTER then
local real x = GetUnitX(tempUnit)
local real y = GetUnitY(tempUnit)
if circle then
return this.checkZCollision(GetUnitFlyHeight(tempUnit) + GetLocZ(x, y), GetUnitHeight(tempUnit)) and IsUnitInRangeXY(tempUnit, this.x, this.y, this.collision)
endif
return this.checkZCollision(GetUnitFlyHeight(tempUnit) + GetLocZ(x, y), GetUnitHeight(tempUnit)) and this.isUnitInRect(x, y)
else
if circle then
return IsUnitInRangeXY(tempUnit, this.x, this.y, this.collision)
endif
return this.isUnitInRect(GetUnitX(tempUnit), GetUnitY(tempUnit))
endif
endmethod
//
// Runs for every enumerated destructable.
// • Directly filters out already hit destructables.
// • Distance formula based on the Pythagorean theorem.
//
static method destFilter takes nothing returns boolean
if temp.allocated then
set tempDest = GetFilterDestructable()
if not temp.hasHitWidget(tempDest) and temp.isWidgetInCollision(tempDest, GetDestCollisionSize(tempDest), GetDestHeight(tempDest), temp.collision) then
set table[temp].destructable[GetHandleId(tempDest)] = tempDest
return true
endif
endif
return false
endmethod
//
// Runs for every enumerated item.
// • Directly filters out already hit items.
// • Distance formula based on the Pythagorean theorem.
// • Items have a fix collision size of 16.
//
static method itemFilter takes nothing returns boolean
if temp.allocated then
set tempItem = GetFilterItem()
if not temp.hasHitWidget(tempItem) and temp.isWidgetInCollision(tempItem, GetItemCollisionSize(tempItem), GetItemHeight(tempItem), RMaxBJ(temp.collision, 16.)) then
set table[temp].item[GetHandleId(tempItem)] = tempItem
return true
endif
endif
return false
endmethod
//
// Runs for every enumerated units.
// • Filters out units which are not in collision range in plane x / y.
//
static method unitFilter takes nothing returns boolean
if temp.allocated then
set tempUnit = GetFilterUnit()
if not temp.hasHitWidget(tempUnit) and temp.isUnitInCollision() then
set table[temp].unit[GetHandleId(tempUnit)] = tempUnit
return true
endif
endif
return false
endmethod
/*
* Proper rect preparation.
*/
private method prepareRectRectangle takes nothing returns nothing
local real x1 = RMinBJ(this.prevX, this.x)
local real y1 = RMinBJ(this.prevY, this.y)
local real d = this.collision + Missile_MAXIMUM_COLLISION_SIZE
call SetRect(RECT, x1 - d, y1 - d, this.prevX + this.x - x1 + d, this.prevY + this.y - y1 + d)
endmethod
private method prepareRect takes nothing returns nothing
local real d = this.collision + Missile_MAXIMUM_COLLISION_SIZE
set circle = this.collisionType == Missile_COLLISION_TYPE_CIRCLE
if circle then
call SetRect(RECT, this.x - d, this.y - d, this.x + d, this.y + d)
else
call this.prepareRectRectangle()
endif
set thistype.temp = this
endmethod
/*
* 5.) API for the MissileStruct iteration.
*/
method checkUnitCollision takes code filter returns nothing
if this.allocated and this.collision > 0. then
set thistype.temp = this
set circle = this.collisionType == Missile_COLLISION_TYPE_CIRCLE
if circle then
call GroupEnumUnitsInRange(GROUP, this.x, this.y, this.collision + Missile_MAXIMUM_COLLISION_SIZE, Filter(filter))
else
call this.prepareRectRectangle()
call GroupEnumUnitsInRect(GROUP, RECT, Filter(filter))
endif
endif
endmethod
method checkDestCollision takes code filter returns nothing
if this.allocated and this.collision > 0. then
call this.prepareRect()
call EnumDestructablesInRect(RECT, Filter(filter), null)
endif
endmethod
method checkItemCollision takes code filter returns nothing
if this.allocated and this.collision > 0. then
call this.prepareRect()
call EnumItemsInRect(RECT, Filter(filter), null)
endif
endmethod
private static method onScopeInit takes nothing returns nothing
static if LIBRARY_ErrorMessage then
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!")
endif
call TriggerAddCondition(MOVE, Condition(function thistype.move))
set table = TableArray[JASS_MAX_ARRAY_SIZE]
endmethod
implement Init
endstruct
// Boolean expressions per struct:
// ===============================
//
// 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
local Missile node = Node.allocate()
call Missile.makeHead(node) // Make a circular list
set missileList[structId] = node
endfunction
// Core:
// =====
//
// Fires every Missile_TIMER_TIMEOUT.
private function Fire takes nothing returns nothing
local integer i = 0
// Move all launched missiles.
set Missile.temp = MissileList.front
loop
exitwhen TriggerEvaluate(MOVE)
exitwhen i == 60// Moved over 8910 missiles, something buggy happened.
set i = i + 1 // This case is impossible, hence never happens. But if I'm wrong, which also never happens
endloop // then the map 100% crashes. i == 66 will prevent the game crash and Missile will start to
// to debug itself over the next iterations.
// Access all structs using module MissileStruct.
static if DEBUG_MODE and LIBRARY_ErrorMesssage then
if not TriggerEvaluate(CORE) then
call ThrowWarning(true, "Missile", "Fire", "op limit", 0, /*
*/"You just ran into a op limit!
The op limit protection of Missile is insufficient for your map.
The code of the missile module can't run in one thread and must be splitted.
If unsure make a post in the official 'The Hive Workshop' forum thread of Missile!")
endif
else
call TriggerEvaluate(CORE)
endif
endfunction
// Conditionally starts the timer.
private function StartPeriodic takes integer structId returns nothing
if 0 == instances[structId] then
if 0 == active then
call TimerStart(TMR, Missile_TIMER_TIMEOUT, true, function Fire)
endif
set active = active + 1
set condition[structId] = TriggerAddCondition(CORE, expression[structId])
endif
set instances[structId] = instances[structId] + 1
endfunction
// Conditionally stops the timer in the next callback.
private function StopPeriodic takes integer structId returns nothing
if condition[structId] != null then
set instances[structId] = instances[structId] - 1
if 0 == instances[structId] then
call TriggerRemoveCondition(CORE, condition[structId])
set condition[structId] = null
set active = active - 1
if 0 == active then
call PauseTimer(TMR)
endif
endif
endif
endfunction
// Modules:
// ========
//
// You want to place module MissileLaunch at the very top of your struct.
module MissileLaunch
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 missileList[thistype.typeid].pushBack(missile)
call StartPeriodic(thistype.typeid)
endmethod
endmodule
module MissileTerminate
// Called from missileIterate. "P" to avoid code collision.
static method missileTerminateP takes Missile node returns nothing
if node.allocated then
static if thistype.onRemove.exists then
call thistype.onRemove(node)
endif
call node.terminate()
call StopPeriodic(thistype.typeid)
endif
endmethod
endmodule
// Allows you to inject missile in certain stages of the motion process.
module MissileAction
static if thistype.onCollide.exists then
private static method missileActionUnit takes nothing returns nothing
if Missile.unitFilter() and thistype.onCollide(Missile.temp, tempUnit) then
call missileTerminateP(Missile.temp)
endif
endmethod
endif
static if thistype.onItem.exists then
private static method missileActionItem takes nothing returns nothing
if Missile.itemFilter() and thistype.onItem(Missile.temp, tempItem) then
call missileTerminateP(Missile.temp)
endif
endmethod
endif
static if thistype.onDestructable.exists then
private static method missileActionDest takes nothing returns nothing
if Missile.destFilter() and thistype.onDestructable(Missile.temp, tempDest) then
call missileTerminateP(Missile.temp)
endif
endmethod
endif
// Runs every Missile_TIMER_TIMEOUT for this struct.
static method missileIterateP takes nothing returns boolean
local Missile node = missileList[thistype.typeid].front
loop
exitwhen node == missileList[thistype.typeid]
if node.wantDestroy then
call missileTerminateP(node)
else
if node.stackSize > 0 then
call node.updateStack()
endif
// Runs unit collision.
static if thistype.onCollide.exists then
call node.checkUnitCollision(function thistype.missileActionUnit)
endif
// Runs destructable collision.
static if thistype.onDestructable.exists then
call node.checkDestCollision(function thistype.missileActionDest)
endif
// Runs item collision.
static if thistype.onItem.exists then
call node.checkItemCollision(function thistype.missileActionItem)
endif
// Runs when the destination is reached.
if node.recycle and node.allocated then
static if thistype.onFinish.exists then
if thistype.onFinish(node) then
call missileTerminateP(node)
endif
else
call missileTerminateP(node)
endif
endif
// Runs on terrain collision.
static if thistype.onTerrain.exists then
if node.allocated and 0. > node.z and thistype.onTerrain(node) then
call missileTerminateP(node)
endif
endif
// Runs every Missile_TIMER_TIMEOUT.
static if thistype.onPeriod.exists then
if node.allocated and thistype.onPeriod(node) then
call missileTerminateP(node)
endif
endif
endif
set node = node.next
endloop
static if DEBUG_MODE and LIBRARY_ErrorMessage then
return true
else
return false
endif
endmethod
endmodule
module MissileStruct
implement MissileLaunch
implement MissileTerminate
implement MissileAction
private static method onInit takes nothing returns nothing
call MissileCreateCollection(thistype.typeid)
call MissileCreateExpression(thistype.typeid, function thistype.missileIterateP)
endmethod
endmodule
private module Init
private static method onInit takes nothing returns nothing
call Missile.onScopeInit()
endmethod
endmodule
// The end!
endlibrary
library SpecialEffect /* v1.3.1 by AGD | https://www.hiveworkshop.com/threads/specialeffect.325954/ | Patches 1.31+
*/uses /*
*/LinkedList /* https://www.hiveworkshop.com/threads/325635/ | Should use atleast v1.3.0
*/optional Table /* https://www.hiveworkshop.com/threads/188084/
*/optional Alloc /* https://www.hiveworkshop.com/threads/324937/
*/optional ErrorMessage /* https://github.com/nestharus/JASS/blob/master/jass/Systems/ErrorMessage/main.j
*///! novjass
|-----|
| API |
|-----|
/*
*/struct SpecialEffect extends array/*
*/readonly real x /*
*/readonly real y /*
*/readonly real z /* Absolute height
*/readonly real height/* Height relative to the ground
*/readonly real yaw /*
*/readonly real pitch /*
*/readonly real roll /*
*/method currentHandle takes nothing returns effect/*
- Current <effect> handle pointed by the iterator
*/method resetIterator takes nothing returns nothing/*
*/method moveIterator takes nothing returns boolean/*
- Use these iterator methods together with 'currentHandle()' if you need to traverse
each <effect> handle
- moveIterator() returns true when it moves past the last element of the list
(resetIterator() is automatically called when this happens)
- Sample Usage:
local SpecialEffect sfx = this.specialEffect
loop
exitwhen sfx.moveIterator()
call BlzSetSpecialEffectAlpha(sfx.currentHandle(), 0xAA)
endloop
*/method killHandle takes effect effectHandle, real deathTime returns nothing/*
- Kills a specific <effect> handle, playing its death animation
*/method killModel takes string model, real deathTime returns nothing/*
- Kills all <effect> handles matching the specific model, playing their death animation
*/method kill takes real deathTime, boolean wantDestroy returns nothing/*
- Kills all models, playing their death animation and destroys this SpecialEffect
instance afterwards if <wantDestroy> is true
*/method addModel takes string model returns effect/*
- You can add multiple similar models
*/method removeHandle takes effect effectHandle returns nothing/*
- Removes a specific <effect> handle if found
*/method removeModel takes string model returns nothing/*
- Removes all <effect> handles matching the given model
*/method clearModels takes nothing returns nothing/*
- Removes all <effect> handles
Note:
removeHandle(), removeModel(), and clearModels() instantly vanishes the attached
models without playing their death animation
*/method getHandle takes string model returns effect/*
- For random access (Uses linear search actually)
- Returns the first instance of <effect> handle matching the given model
*/method move takes real x, real y, real z returns nothing/*
*/method moveRelative takes real x, real y, real height returns nothing/*
- Keeps the offset between the actual position of the <effect> handles and the
origin coordinates of this SpecialEffect instance
*/method setPosition takes real x, real y, real z returns nothing/*
*/method setPositionRelative takes real x, real y, real height returns nothing/*
- Sets the actual position of all <effect> handles
*/method setOrientation takes real yaw, real pitch, real roll returns nothing/*
*/method setVisibility takes player whichPlayer, boolean visible returns nothing/*
- Toggles the visibility flag locally for a player
*/static method create takes real x, real y, real z returns SpecialEffect/*
*/static method createRelative takes real x, real y, real height returns SpecialEffect/*
- Constructors
*/method destroy takes nothing returns nothing/*
- Calls clearModels() and destroys this SpecialEffect instance
- Dying instances are also immediately vanished by this method
*///! endnovjass
globals
/*
* Special effects that need to be instantly vanished are moved to this coordinates before destroyed
*/
private constant real HIDDEN_PLACE_X = 0.00
private constant real HIDDEN_PLACE_Y = 10000.00
private location loc = Location(0.00, 0.00)
endglobals
/*========================================== SYSTEM CODE ==========================================*/
static if DEBUG_MODE then
private function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
else
if condition then
call BJDebugMsg("[Library: " + SCOPE_PREFIX + "] [Struct: " + structName + "] [Method: " + methodName + "] [Instance: " + I2S(node) + "] : |cffff0000" + message + "|r")
endif
endif
endfunction
endif
public function GetTerrainZ takes real x, real y returns real
call MoveLocation(loc, x, y)
return GetLocationZ(loc)
endfunction
/*
* Allocator for the whole lib
*/
private struct Node extends array
static if LIBRARY_Alloc then
implement optional Alloc
else
private static thistype array stack
debug method operator allocated takes nothing returns boolean
debug return this > 0 and stack[this] == 0
debug endmethod
/*
* Credits to MyPad for the algorithm
*/
static method allocate takes nothing returns thistype
local thistype node = stack[0]
if stack[node] == 0 then
debug call AssertError(node == JASS_MAX_ARRAY_SIZE - 2, "allocate()", "thistype", node, "Overflow")
set node = node + 1
set stack[0] = node
else
set stack[0] = stack[node]
set stack[node] = 0
endif
return node
endmethod
method deallocate takes nothing returns nothing
debug call AssertError(not this.allocated, "deallocate()", "thistype", this, "Double-free")
set stack[this] = stack[0]
set stack[0] = this
endmethod
endif
endstruct
static if DEBUG_MODE then
private function AssertNodeValidity takes Node node, string methodName, string objectName returns nothing
static if Node.allocated.exists then
debug call AssertError(not node.allocated, methodName, objectName, node, "Invalid node")
endif
endfunction
endif
private function VanishEffect takes effect e returns nothing
call BlzSetSpecialEffectX(e, HIDDEN_PLACE_X)
call BlzSetSpecialEffectY(e, HIDDEN_PLACE_Y)
call DestroyEffect(e)
endfunction
/*
* List of <effect>s
*/
private struct EffectHandle extends array
readonly effect effect
SpecialEffect parent
string model
real dx
real dy
real dz
private static method onInsert takes thistype node returns nothing
local SpecialEffect parent = node.parent
set node.effect = AddSpecialEffect(node.model, HIDDEN_PLACE_X, HIDDEN_PLACE_Y)
call BlzSetSpecialEffectPosition(node.effect, parent.x, parent.y, parent.z)
call BlzSetSpecialEffectOrientation(node.effect, parent.yaw, parent.pitch, parent.roll)
endmethod
private static method onRemove takes thistype node returns nothing
call VanishEffect(node.effect)
set node.effect = null
set node.model = null
call Node(node).deallocate()
endmethod
private static method allocate takes nothing returns thistype
return Node.allocate()
endmethod
private method deallocate takes nothing returns nothing
call Node(this).deallocate()
endmethod
implement InstantiatedList
implement LinkedListEx
method getHandleIndex takes effect e returns thistype
local thistype node = this.next
loop
exitwhen node == this or node.effect == e
endloop
return node
endmethod
method getModelIndex takes string model returns thistype
local thistype node = this.next
loop
exitwhen node == this or node.model == model
set node = node.next
endloop
return node
endmethod
endstruct
private struct DelayedCleanupList extends array
EffectHandle effectHandle
private static method onRemove takes thistype node returns nothing
if EffectHandle.isLinked(node.effectHandle) then
call EffectHandle.remove(node.effectHandle)
endif
call Node(node).deallocate()
endmethod
implement List
endstruct
/*
* SpecialEffect is a cluster of <effect>s that can easily be controlled as a single object
*/
struct SpecialEffect extends array
readonly real x
readonly real y
readonly real z
readonly real height
readonly real yaw
readonly real pitch
readonly real roll
private boolean hidden
private boolean wantDestroy
private EffectHandle current
private EffectHandle killed
private thistype instance
static if LIBRARY_Table then
private static key table
else
private static hashtable table = InitHashtable()
endif
private method startTimer takes real timeout, code callback returns nothing
local timer t = CreateTimer()
static if LIBRARY_Table then
set Table(table)[GetHandleId(t)] = this
else
call SaveInteger(table, 0, GetHandleId(t), this)
endif
call TimerStart(t, timeout, false, callback)
set t = null
endmethod
private static method releaseTimer takes nothing returns thistype
local timer t = GetExpiredTimer()
local integer id = GetHandleId(t)
static if LIBRARY_Table then
local thistype node = Table(table)[id]
call Table(table).remove(id)
else
local thistype node = LoadInteger(table, 0, id)
call RemoveSavedInteger(table, 0, id)
endif
call DestroyTimer(t)
set t = null
return node
endmethod
private method operator handle takes nothing returns EffectHandle
return this
endmethod
method currentHandle takes nothing returns effect
return this.current.effect
endmethod
method resetIterator takes nothing returns nothing
set this.current = this.handle
endmethod
method moveIterator takes nothing returns boolean
set this.current = this.current.next
return this.current == this.handle
endmethod
private static method onSetOrientation takes EffectHandle list, real yaw, real pitch, real roll returns nothing
local EffectHandle node = list.next
loop
exitwhen node == list
call BlzSetSpecialEffectOrientation(node.effect, yaw, pitch, roll)
set node = node.next
endloop
endmethod
private static method onSetPosition takes EffectHandle list, real x, real y, real z returns nothing
local EffectHandle node = list.next
loop
exitwhen node == list
call BlzSetSpecialEffectPosition(node.effect, x, y, z)
set node = node.next
endloop
endmethod
private static method onMove takes EffectHandle list, real dx, real dy, real dz returns nothing
local EffectHandle node = list.next
loop
exitwhen node == list
call BlzSetSpecialEffectPosition(node.effect, BlzGetLocalSpecialEffectX(node.effect) + dx, BlzGetLocalSpecialEffectY(node.effect) + dy, BlzGetLocalSpecialEffectZ(node.effect) + dz)
set node = node.next
endloop
endmethod
private method onSetVisibility takes EffectHandle list, player whichPlayer, boolean visible returns nothing
local EffectHandle node = list.next
loop
exitwhen node == list
if visible then
call BlzSetSpecialEffectPosition(node.effect, this.x + node.dx, this.y + node.dy, this.z + node.dz)
else
set node.dx = BlzGetLocalSpecialEffectX(node.effect) - this.x
set node.dy = BlzGetLocalSpecialEffectY(node.effect) - this.y
set node.dz = BlzGetLocalSpecialEffectZ(node.effect) - this.z
call BlzSetSpecialEffectX(node.effect, HIDDEN_PLACE_X)
call BlzSetSpecialEffectY(node.effect, HIDDEN_PLACE_Y)
endif
set node = node.next
endloop
endmethod
private method updatePosition takes real x, real y, real z, real height returns nothing
set this.x = x
set this.y = y
set this.z = z
set this.height = height
endmethod
method setOrientation takes real yaw, real pitch, real roll returns nothing
debug call AssertNodeValidity(this, "setOrientation()", "thistype")
call onSetOrientation(this.handle, yaw, pitch, roll)
call onSetOrientation(this.killed, yaw, pitch, roll)
set this.yaw = yaw
set this.pitch = pitch
set this.roll = roll
endmethod
private method setPositionEx takes real x, real y, real z, real height returns nothing
call onSetPosition(this.handle, x, y, z)
call onSetPosition(this.killed, x, y, z)
call this.updatePosition(x, y, z, height)
endmethod
method setPosition takes real x, real y, real z returns nothing
debug call AssertNodeValidity(this, "setPosition()", "thistype")
call this.setPositionEx(x, y, z, z - GetTerrainZ(x, y))
endmethod
method setPositionRelative takes real x, real y, real height returns nothing
debug call AssertNodeValidity(this, "setPositionRelative()", "thistype")
call this.setPositionEx(x, y, height + GetTerrainZ(x, y), height)
endmethod
private method moveEx takes real x, real y, real z, real height returns nothing
call onMove(this.handle, x - this.x, y - this.y, z - this.z)
call onMove(this.killed, x - this.x, y - this.y, z - this.z)
call this.updatePosition(x, y, z, height)
endmethod
method move takes real x, real y, real z returns nothing
debug call AssertNodeValidity(this, "move()", "thistype")
call this.moveEx(x, y, z, z - GetTerrainZ(x, y))
endmethod
method moveRelative takes real x, real y, real height returns nothing
debug call AssertNodeValidity(this, "moveRelative()", "thistype")
call this.moveEx(x, y, height + GetTerrainZ(x, y), height)
endmethod
method setVisibility takes player whichPlayer, boolean visible returns nothing
debug call AssertNodeValidity(this, "setVisibility()", "thistype")
if whichPlayer == GetLocalPlayer() and this.hidden == visible then
call this.onSetVisibility(this.handle, whichPlayer, visible)
call this.onSetVisibility(this.killed, whichPlayer, visible)
set this.hidden = not visible
endif
endmethod
method getHandle takes string model returns effect
debug call AssertNodeValidity(this, "getHandle()", "thistype")
return this.handle.getModelIndex(model).effect
endmethod
method addModel takes string model returns effect
local EffectHandle node = Node.allocate()
debug call AssertNodeValidity(this, "addModel()", "thistype")
set node.parent = this.handle
set node.model = model
call EffectHandle.insert(this.handle.getModelIndex(model).prev, node)
return node.effect
endmethod
method removeHandle takes effect e returns nothing
local EffectHandle node = this.handle.next
debug call AssertNodeValidity(this, "removeHandle()", "thistype")
loop
exitwhen node == this.handle
if node.effect == e then
call EffectHandle.remove(node)
exitwhen true
endif
set node = node.next
endloop
endmethod
method removeModel takes string model returns nothing
local EffectHandle node = this.handle.getModelIndex(model)
debug call AssertNodeValidity(this, "removeModel()", "thistype")
if node != this.handle then
loop
exitwhen node.model != model
call EffectHandle.remove(node)
set node = node.next
endloop
endif
endmethod
method clearModels takes nothing returns nothing
debug call AssertNodeValidity(this, "clearModels()", "thistype")
call this.killed.flush()
call this.handle.flush()
endmethod
private static method createEx takes real x, real y, real z, real height returns thistype
local thistype node = EffectHandle.create()
set node.killed = EffectHandle.create()
set node.yaw = 0
set node.pitch = 0
set node.roll = 0
set node.hidden = false
call node.updatePosition(x, y, z, height)
call node.resetIterator()
return node
endmethod
static method create takes real x, real y, real z returns thistype
return createEx(x, y, z, z - GetTerrainZ(x, y))
endmethod
static method createRelative takes real x, real y, real height returns thistype
return createEx(x, y, height + GetTerrainZ(x, y), height)
endmethod
method destroy takes nothing returns nothing
debug call AssertNodeValidity(this, "destroy()", "thistype")
call this.killed.destroy()
call this.handle.destroy()
endmethod
private static method onExpireKill takes nothing returns nothing
local thistype node = releaseTimer()
if EffectHandle.isLinked(node.instance) then
call EffectHandle.remove(node.instance)
endif
call Node(node).deallocate()
endmethod
private static method onExpireKillRange takes nothing returns nothing
local DelayedCleanupList list = releaseTimer()
local thistype node = thistype(list).instance
set thistype(list).instance = 0
if node.wantDestroy then
set node.wantDestroy = false
call node.destroy()
endif
call list.flush()
call Node(list).deallocate()
endmethod
private method killHandleRange takes string model, real deathTime, boolean wantDestroy returns nothing
local DelayedCleanupList list
local DelayedCleanupList temp
local thistype node
local thistype next
local boolean resetIter = false
if not this.handle.empty then
set this.wantDestroy = wantDestroy
if model == null then
set node = this.handle.next
else
set node = this.handle.getModelIndex(model)
endif
if node != this.handle then
set list = Node.allocate()
set thistype(list).instance = this
call DelayedCleanupList.makeHead(list)
loop
exitwhen node == this.handle or (node.handle.model != model and model != null)
set next = node.handle.next
if node.handle == this.current then
set resetIter = true
endif
set temp = Node.allocate()
set temp.effectHandle = node
call list.pushBack(temp)
call EffectHandle.move(this.killed.prev, node)
call BlzPlaySpecialEffect(node.handle.effect, ANIM_TYPE_DEATH)
set node = next
endloop
if resetIter then
set this.current = node.handle.prev
endif
call thistype(list).startTimer(deathTime, function thistype.onExpireKillRange)
endif
elseif wantDestroy then
call this.destroy()
endif
endmethod
method killHandle takes effect e, real deathTime returns nothing
local thistype node
local thistype instance
debug call AssertNodeValidity(this, "killHandle()", "thistype")
if not this.handle.empty then
set instance = this.handle.getHandleIndex(e)
if instance != this.handle then
set node = Node.allocate()
set node.instance = instance
if instance.handle == this.current then
set this.current = this.current.prev
endif
call EffectHandle.move(this.killed.prev, instance.handle)
call BlzPlaySpecialEffect(instance.handle.effect, ANIM_TYPE_DEATH)
call node.startTimer(deathTime, function thistype.onExpireKill)
endif
endif
endmethod
method killModel takes string model, real deathTime returns nothing
debug call AssertNodeValidity(this, "killModel()", "thistype")
call this.killHandleRange(model, deathTime, false)
endmethod
method kill takes real deathTime, boolean wantDestroy returns nothing
debug call AssertNodeValidity(this, "kill()", "thistype")
call this.killHandleRange(null, deathTime, wantDestroy)
endmethod
endstruct
endlibrary
library ErrorMessage /* v1.0.2.0
*************************************************************************************
*
* Issue Compliant Error Messages
*
************************************************************************************
*
* function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
* - In the event of an error the game will be permanently paused
*
* function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
*
************************************************************************************/
private struct Fields extends array
static constant string COLOR_RED = "|cffff0000"
static constant string COLOR_YELLOW = "|cffffff00"
static string lastError = null
endstruct
private function Pause takes nothing returns nothing
call PauseGame(true)
endfunction
private function ThrowMessage takes string libraryName, string functionName, string objectName, integer objectInstance, string description, string errorType, string color returns nothing
local string str
local string color_braces = "|cff66FF99"
local string orange = "|cffff6600"
set str = "->\n-> " + color_braces + "{|r " + "Library" + color_braces + "(" + orange + libraryName + color_braces + ")"
if (objectName != null) then
if (objectInstance != 0) then
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + " (|rinstance = " + orange + I2S(objectInstance) + color_braces + ") )" + "|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
else
set str = str + "|r.Object" + color_braces + "(" + orange + objectName + color_braces + ")|r." + "Method" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
else
set str = str + "|r." + "Function" + color_braces + "(" + orange + functionName + color_braces + ")"
endif
set str = str + color_braces + " }|r " + "has thrown an exception of type " + color_braces + "(" + color + errorType + color_braces + ")|r."
set Fields.lastError = str + "\n->\n" + "-> " + color + description + "|r\n->"
endfunction
function ThrowError takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Error", Fields.COLOR_RED)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
call TimerStart(CreateTimer(), 0, true, function Pause)
set objectInstance = 1/0
endif
endfunction
function ThrowWarning takes boolean expression, string libraryName, string functionName, string objectName, integer objectInstance, string description returns nothing
if (Fields.lastError != null) then
set objectInstance = 1/0
endif
if (expression) then
call ThrowMessage(libraryName, functionName, objectName, objectInstance, description, "Warning", Fields.COLOR_YELLOW)
call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60000,Fields.lastError)
set Fields.lastError = null
endif
endfunction
endlibrary
library ResourcePreloader /* v1.5.0 https://www.hiveworkshop.com/threads/287358/
*/uses /*
*/optional BJObjectId /* http://www.hiveworkshop.com/threads/287128/
*/optional Table /* http://www.hiveworkshop.com/threads/188084/
*/optional UnitRecycler /* http://www.hiveworkshop.com/threads/286701/
*///! novjass
[CREDITS]
/*
AGD - Author
IcemanBo - for suggesting further improvements
Silvenon - for the sound preloading method
JAKEZINC - Suggestions
*/
|-----|
| API |
|-----|
function PreloadUnit takes integer rawcode returns nothing/*
- Assigns a certain type of unit to be preloaded
*/function PreloadItem takes integer rawcode returns nothing/*
- Assigns a certain type of item to be preloaded
*/function PreloadAbility takes integer rawcode returns nothing/*
- Assigns a certain type of ability to be preloaded
*/function PreloadEffect takes string modelPath returns nothing/*
- Assigns a certain type of effect to be preloaded
*/function PreloadSound takes string soundPath returns nothing/*
- Assigns a certain type of sound to be preloaded
The following functions requires the BJObjectId library:
*/function PreloadUnitEx takes integer start, integer end returns nothing/*
- Assigns a range of units to be preloaded
*/function PreloadItemEx takes integer start, integer end returns nothing/*
- Assigns a range of items to be preloaded
*/function PreloadAbilityEx takes integer start, integer end returns nothing/*
- Assigns a range of abilities to be preloaded
*///! endnovjass
/*
* Configuration
*/
globals
/*
* Preload dummy unit type id
*/
public integer PRELOAD_UNIT_TYPE_ID = 'uloc'
/*
* Owner of the preload dummy
*/
public player PRELOAD_UNIT_OWNER = Player(PLAYER_NEUTRAL_PASSIVE)
/*
* Dummy unit's y-coordinate will be positioned at (Max Y of WorldBounds) + this value
*/
public real PRELOAD_UNIT_Y_BOUNDS_EXTENSION = 0.00
endglobals
/*========================================================================================================*/
/* Do not try to change below this line if you're not so sure on what you're doing. */
/*========================================================================================================*/
private keyword S
/*============================================== TextMacros ==============================================*/
//! textmacro PRELOAD_TYPE takes NAME, ARG, TYPE, INDEX, I
function Preload$NAME$ takes $ARG$ what returns nothing
static if LIBRARY_Table then
if S.tb[$I$].boolean[$INDEX$] then
return
endif
set S.tb[$I$].boolean[$INDEX$] = true
call Do$NAME$Preload(what)
else
if LoadBoolean(S.tb, $I$, $INDEX$) then
return
endif
call SaveBoolean(S.tb, $I$, $INDEX$, true)
call Do$NAME$Preload(what)
endif
endfunction
//! endtextmacro
//! textmacro RANGED_PRELOAD_TYPE takes NAME
function Preload$NAME$Ex takes integer start, integer end returns nothing
local boolean forward = start < end
loop
call Preload$NAME$(start)
exitwhen start == end
if forward then
set start = BJObjectId(start).plus_1()
exitwhen start > end
else
set start = BJObjectId(start).minus_1()
exitwhen start < end
endif
endloop
endfunction
//! endtextmacro
/*========================================================================================================*/
private function DoUnitPreload takes integer id returns nothing
static if LIBRARY_UnitRecycler then
call RecycleUnitEx(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), id, 0, 0, 270))
else
call RemoveUnit(CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), id, 0, 0, 0))
endif
endfunction
private function DoItemPreload takes integer id returns nothing
call RemoveItem(UnitAddItemById(S.dummy, id))
endfunction
private function DoAbilityPreload takes integer id returns boolean
return UnitAddAbility(S.dummy, id) and UnitRemoveAbility(S.dummy, id)
endfunction
private function DoEffectPreload takes string path returns nothing
call DestroyEffect(AddSpecialEffect(path, GetUnitX(S.dummy), GetUnitY(S.dummy)))
endfunction
private function DoSoundPreload takes string path returns nothing
local sound s = CreateSound(path, false, false, false, 10, 10, "")
call SetSoundVolume(s, 0)
call StartSound(s)
call KillSoundWhenDone(s)
set s = null
endfunction
//! runtextmacro PRELOAD_TYPE("Unit", "integer", "unit", "what", "0")
//! runtextmacro PRELOAD_TYPE("Item", "integer", "item", "what", "1")
//! runtextmacro PRELOAD_TYPE("Ability", "integer", "ability", "what", "2")
//! runtextmacro PRELOAD_TYPE("Effect", "string", "effect", "StringHash(what)", "3")
//! runtextmacro PRELOAD_TYPE("Sound", "string", "sound", "StringHash(what)", "4")
static if LIBRARY_BJObjectId then
//! runtextmacro RANGED_PRELOAD_TYPE("Unit")
//! runtextmacro RANGED_PRELOAD_TYPE("Item")
//! runtextmacro RANGED_PRELOAD_TYPE("Ability")
endif
/*========================================================================================================*/
private module Init
private static method onInit takes nothing returns nothing
local rect world = GetWorldBounds()
static if LIBRARY_Table then
set tb = TableArray[5]
endif
set dummy = CreateUnit(PRELOAD_UNIT_OWNER, PRELOAD_UNIT_TYPE_ID, 0, 0, 0)
call SetUnitY(dummy, GetRectMaxY(world) + PRELOAD_UNIT_Y_BOUNDS_EXTENSION)
call UnitAddAbility(dummy, 'AInv')
call UnitAddAbility(dummy, 'Avul')
call UnitRemoveAbility(dummy, 'Amov')
call RemoveRect(world)
set world = null
endmethod
endmodule
private struct S extends array
static if LIBRARY_Table then
static TableArray tb
else
static hashtable tb = InitHashtable()
endif
static unit dummy
implement Init
endstruct
endlibrary
/*****************************************************************************
*
* RegisterPlayerUnitEvent v1.0.3.2
* by Bannar
*
* Register version of TriggerRegisterPlayerUnitEvent.
*
* Special thanks to Magtheridon96, Bribe, azlier and BBQ for the original library version.
*
******************************************************************************
*
* Requirements:
*
* RegisterNativeEvent by Bannar
* hiveworkshop.com/threads/snippet-registerevent-pack.250266/
*
******************************************************************************
*
* Functions:
*
* function GetAnyPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
* Retrieves trigger handle for playerunitevent whichEvent.
*
* function GetPlayerUnitEventTrigger takes player whichPlayer, playerunitevent whichEvent returns trigger
* Retrieves trigger handle for playerunitevent whichEvent specific to player whichPlayer.
*
* function RegisterAnyPlayerUnitEvent takes playerunitevent whichEvent, code func returns nothing
* Registers generic playerunitevent whichEvent adding code func as callback.
*
* function RegisterPlayerUnitEvent takes player whichPlayer, playerunitevent whichEvent, code func returns nothing
* Registers playerunitevent whichEvent for player whichPlayer adding code func as callback.
*
*****************************************************************************/
library RegisterPlayerUnitEvent requires RegisterNativeEvent
function GetAnyPlayerUnitEventTrigger takes playerunitevent whichEvent returns trigger
return GetNativeEventTrigger(GetHandleId(whichEvent))
endfunction
function GetPlayerUnitEventTrigger takes player whichPlayer, playerunitevent whichEvent returns trigger
return GetIndexNativeEventTrigger(GetPlayerId(whichPlayer), GetHandleId(whichEvent))
endfunction
function RegisterAnyPlayerUnitEvent takes playerunitevent whichEvent, code func returns nothing
local integer eventId = GetHandleId(whichEvent)
local integer index = 0
local trigger t = null
if RegisterNativeEventTrigger(bj_MAX_PLAYER_SLOTS, eventId) then
set t = GetNativeEventTrigger(eventId)
loop
call TriggerRegisterPlayerUnitEvent(t, Player(index), whichEvent, null)
set index = index + 1
exitwhen index == bj_MAX_PLAYER_SLOTS
endloop
set t = null
endif
call RegisterNativeEvent(eventId, func)
endfunction
function RegisterPlayerUnitEvent takes player whichPlayer, playerunitevent whichEvent, code func returns nothing
local integer playerId = GetPlayerId(whichPlayer)
local integer eventId = GetHandleId(whichEvent)
if RegisterNativeEventTrigger(playerId, eventId) then
call TriggerRegisterPlayerUnitEvent(GetIndexNativeEventTrigger(playerId, eventId), whichPlayer, whichEvent, null)
endif
call RegisterIndexNativeEvent(playerId, eventId, func)
endfunction
endlibrary
/*****************************************************************************
*
* RegisterNativeEvent v1.1.1.3
* by Bannar
*
* Storage of trigger handles for native events.
*
******************************************************************************
*
* Optional requirements:
*
* Table by Bribe
* hiveworkshop.com/threads/snippet-new-table.188084/
*
******************************************************************************
*
* Important:
*
* Avoid using TriggerSleepAction within functions registered.
* Destroy native event trigger on your own responsibility.
*
******************************************************************************
*
* Core:
*
* function IsNativeEventRegistered takes integer whichIndex, integer whichEvent returns boolean
* Whether index whichIndex has already been attached to event whichEvent.
*
* function RegisterNativeEventTrigger takes integer whichIndex, integer eventId returns boolean
* Registers whichIndex within whichEvent scope and assigns new trigger handle for it.
*
* function GetIndexNativeEventTrigger takes integer whichIndex, integer whichEvent returns trigger
* Retrieves trigger handle for event whichEvent specific to provided index whichIndex.
*
* function GetNativeEventTrigger takes integer whichEvent returns trigger
* Retrieves trigger handle for event whichEvent.
*
*
* Custom events:
*
* function CreateNativeEvent takes nothing returns integer
* Returns unique id for new event and registers it with RegisterNativeEvent.
*
* function RegisterIndexNativeEvent takes integer whichIndex, integer whichEvent, code func returns nothing
* Registers new event handler func for event whichEvent specific to index whichIndex.
*
* function RegisterNativeEvent takes integer whichEvent, code func returns nothing
* Registers new event handler func for specified event whichEvent.
*
*****************************************************************************/
library RegisterNativeEvent uses optional Table
globals
private integer eventIndex = 500 // 0-499 reserved for Blizzard native events
endglobals
private module NativeEventInit
private static method onInit takes nothing returns nothing
static if LIBRARY_Table then
set table = TableArray[0x2000]
endif
endmethod
endmodule
private struct NativeEvent extends array
static if LIBRARY_Table then
static TableArray table
else
static hashtable table = InitHashtable()
endif
implement NativeEventInit
endstruct
function IsNativeEventRegistered takes integer whichIndex, integer whichEvent returns boolean
static if LIBRARY_Table then
return NativeEvent.table[whichEvent].trigger.has(whichIndex)
else
return HaveSavedHandle(NativeEvent.table, whichEvent, whichIndex)
endif
endfunction
function RegisterNativeEventTrigger takes integer whichIndex, integer whichEvent returns boolean
if not IsNativeEventRegistered(whichIndex, whichEvent) then
static if LIBRARY_Table then
set NativeEvent.table[whichEvent].trigger[whichIndex] = CreateTrigger()
else
call SaveTriggerHandle(NativeEvent.table, whichEvent, whichIndex, CreateTrigger())
endif
return true
endif
return false
endfunction
function GetIndexNativeEventTrigger takes integer whichIndex, integer whichEvent returns trigger
static if LIBRARY_Table then
return NativeEvent.table[whichEvent].trigger[whichIndex]
else
return LoadTriggerHandle(NativeEvent.table, whichEvent, whichIndex)
endif
endfunction
function GetNativeEventTrigger takes integer whichEvent returns trigger
return GetIndexNativeEventTrigger(bj_MAX_PLAYER_SLOTS, whichEvent)
endfunction
function CreateNativeEvent takes nothing returns integer
local integer eventId = eventIndex
call RegisterNativeEventTrigger(bj_MAX_PLAYER_SLOTS, eventId)
set eventIndex = eventIndex + 1
return eventId
endfunction
function RegisterIndexNativeEvent takes integer whichIndex, integer whichEvent, code func returns nothing
call RegisterNativeEventTrigger(whichIndex, whichEvent)
call TriggerAddCondition(GetIndexNativeEventTrigger(whichIndex, whichEvent), Condition(func))
endfunction
function RegisterNativeEvent takes integer whichEvent, code func returns nothing
call RegisterIndexNativeEvent(bj_MAX_PLAYER_SLOTS, whichEvent, func)
endfunction
endlibrary
library StormBolt /*
*/uses /*
*/SpellFramework /*
*/Missile /*
*/
private module Configuration
static constant integer SPELL_ABILITY_ID = 'Strm'
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
static constant real MISSILE_HEIGHT = 60.00
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
static constant weapontype WEAPON_TYPE = null
static constant method boltDamage takes integer level returns real
return 75.00 + 25.00*level
endmethod
static method onMissileConfigure takes Missile missile returns nothing
set missile.speed = (400.00 + 0.00*Spell.level)*Missile_TIMER_TIMEOUT
set missile.acceleration = (0.00 + 0.00*Spell.level)*Missile_TIMER_TIMEOUT
call missile.effect.addModel("Abilities\\Spells\\Human\\StormBolt\\StormBoltMissile.mdl")
endmethod
endmodule
private struct StormBolt extends array
implement Configuration
private real damage
private method operator missile takes nothing returns Missile
return this
endmethod
private static method onFinish takes thistype node returns boolean
call UnitDamageTarget(node.missile.source, node.missile.target, node.damage, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
return true
endmethod
implement MissileStruct
private method onSpellStart takes nothing returns thistype
set this = Missile.createXYZ(GetUnitX(Spell.triggerUnit), GetUnitY(Spell.triggerUnit), MISSILE_HEIGHT, Spell.targetX, Spell.targetY, MISSILE_HEIGHT)
set this.missile.source = Spell.triggerUnit
set this.missile.target = Spell.targetUnit
set this.damage = boltDamage(Spell.level)
call onMissileConfigure(this.missile)
call launch(this.missile)
return 0
endmethod
implement SpellEvent
endstruct
endlibrary
library TerrainPathability initializer Init
//******************************************************************************
//* BY: Rising_Dusk
//*
//* This script can be used to detect the type of pathing at a specific point.
//* It is valuable to do it this way because the IsTerrainPathable is very
//* counterintuitive and returns in odd ways and aren't always as you would
//* expect. This library, however, facilitates detecting those things reliably
//* and easily.
//*
//******************************************************************************
//*
//* > function IsTerrainDeepWater takes real x, real y returns boolean
//* > function IsTerrainShallowWater takes real x, real y returns boolean
//* > function IsTerrainLand takes real x, real y returns boolean
//* > function IsTerrainPlatform takes real x, real y returns boolean
//* > function IsTerrainWalkable takes real x, real y returns boolean
//*
//* These functions return true if the given point is of the type specified
//* in the function's name and false if it is not. For the IsTerrainWalkable
//* function, the MAX_RANGE constant below is the maximum deviation range from
//* the supplied coordinates that will still return true.
//*
//* The IsTerrainPlatform works for any preplaced walkable destructable. It will
//* return true over bridges, destructable ramps, elevators, and invisible
//* platforms. Walkable destructables created at runtime do not create the same
//* pathing hole as preplaced ones do, so this will return false for them. All
//* other functions except IsTerrainWalkable return false for platforms, because
//* the platform itself erases their pathing when the map is saved.
//*
//* After calling IsTerrainWalkable(x, y), the following two global variables
//* gain meaning. They return the X and Y coordinates of the nearest walkable
//* point to the specified coordinates. These will only deviate from the
//* IsTerrainWalkable function arguments if the function returned false.
//*
//* Variables that can be used from the library:
//* [real] TerrainPathability_X
//* [real] TerrainPathability_Y
//*
globals
private constant real MAX_RANGE = 10.
private constant integer DUMMY_ITEM_ID = 'wolg'
endglobals
globals
private item Item = null
private rect Find = null
private item array Hid
private integer HidMax = 0
public real X = 0.
public real Y = 0.
endglobals
function IsTerrainDeepWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
function IsTerrainShallowWater takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
function IsTerrainLand takes real x, real y returns boolean
return IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY)
endfunction
function IsTerrainPlatform takes real x, real y returns boolean
return not IsTerrainPathable(x, y, PATHING_TYPE_FLOATABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY) and not IsTerrainPathable(x, y, PATHING_TYPE_BUILDABILITY)
endfunction
private function HideItem takes nothing returns nothing
if IsItemVisible(GetEnumItem()) then
set Hid[HidMax] = GetEnumItem()
call SetItemVisible(Hid[HidMax], false)
set HidMax = HidMax + 1
endif
endfunction
function IsTerrainWalkable takes real x, real y returns boolean
//Hide any items in the area to avoid conflicts with our item
call MoveRectTo(Find, x, y)
call EnumItemsInRect(Find ,null, function HideItem)
//Try to move the test item and get its coords
call SetItemPosition(Item, x, y) //Unhides the item
set X = GetItemX(Item)
set Y = GetItemY(Item)
static if LIBRARY_IsTerrainWalkable then
//This is for compatibility with the IsTerrainWalkable library
set IsTerrainWalkable_X = X
set IsTerrainWalkable_Y = Y
endif
call SetItemVisible(Item, false)//Hide it again
//Unhide any items hidden at the start
loop
exitwhen HidMax <= 0
set HidMax = HidMax - 1
call SetItemVisible(Hid[HidMax], true)
set Hid[HidMax] = null
endloop
//Return walkability
return (X-x)*(X-x)+(Y-y)*(Y-y) <= MAX_RANGE*MAX_RANGE and not IsTerrainPathable(x, y, PATHING_TYPE_WALKABILITY)
endfunction
private function Init takes nothing returns nothing
set Find = Rect(0., 0., 128., 128.)
set Item = CreateItem(DUMMY_ITEM_ID, 0, 0)
call SetItemVisible(Item, false)
endfunction
endlibrary
library IsDestructableTree uses optional UnitIndexer /* v1.3.1
*************************************************************************************
*
* Detect whether a destructable is a tree or not.
*
***************************************************************************
*
* Credits
*
* To PitzerMike
* -----------------------
*
* for IsDestructableTree
*
*************************************************************************************
*
* Functions
*
* function IsDestructableTree takes destructable d returns boolean
*
* function IsDestructableAlive takes destructable d returns boolean
*
* function IsDestructableDead takes destructable d returns boolean
*
* function IsTreeAlive takes destructable tree returns boolean
* - May only return true for trees.
*
* function KillTree takes destructable tree returns boolean
* - May only kill trees.
*
*/
globals
private constant integer HARVESTER_UNIT_ID = 'hpea'//* human peasant
private constant integer HARVEST_ABILITY = 'Ahrl'//* ghoul harvest
private constant integer HARVEST_ORDER_ID = 0xD0032//* harvest order ( 852018 )
private constant player NEUTRAL_PLAYER = Player(PLAYER_NEUTRAL_PASSIVE)
private unit harvester = null
endglobals
function IsDestructableTree takes destructable d returns boolean
//* 851973 is the order id for stunned, it will interrupt the preceding harvest order.
return (IssueTargetOrderById(harvester, HARVEST_ORDER_ID, d)) and (IssueImmediateOrderById(harvester, 851973))
endfunction
function IsDestructableDead takes destructable d returns boolean
return (GetWidgetLife(d) <= 0.405)
endfunction
function IsDestructableAlive takes destructable d returns boolean
return (GetWidgetLife(d) > .405)
endfunction
function IsTreeAlive takes destructable tree returns boolean
return IsDestructableAlive(tree) and IsDestructableTree(tree)
endfunction
function KillTree takes destructable tree returns boolean
if (IsTreeAlive(tree)) then
call KillDestructable(tree)
return true
endif
return false
endfunction
private function Init takes nothing returns nothing
static if LIBRARY_UnitIndexer then//* You may adapt this to your own indexer.
set UnitIndexer.enabled = false
endif
set harvester = CreateUnit(NEUTRAL_PLAYER, HARVESTER_UNIT_ID, 0, 0, 0)
static if LIBRARY_UnitIndexer then
set UnitIndexer.enabled = true
endif
call UnitAddAbility(harvester, HARVEST_ABILITY)
call UnitAddAbility(harvester, 'Aloc')
call ShowUnit(harvester, false)
endfunction
//* Seriously?
private module Inits
private static method onInit takes nothing returns nothing
call Init()
endmethod
endmodule
private struct I extends array
implement Inits
endstruct
endlibrary
library MapBounds /* v1.1.0
Based on Nestharus's WorldBounds
*///! novjass
|=====|
| API |
|=====|
struct MapBounds extends array // Refers to initial playable map bounds
struct WorldBounds extends array // Refers to world bounds
readonly static real centerX
readonly static real centerY
readonly static real minX
readonly static real minY
readonly static real maxX
readonly static real maxY
readonly static rect rect
readonly static region region
static method getArea takes nothing returns real
static method getRandomX takes nothing returns real
static method getRandomY takes nothing returns real/*
- Returns a random coordinate inside the rect
*/static method getBoundedXWithMargin takes real x, real margin returns real
static method getBoundedYWithMargin takes real y, real margin returns real/*
*/static method getBoundedX takes real x returns real
static method getBoundedY takes real y returns real/*
- Returns a coordinate that is inside the bounds
*/static method containsX takes real x returns boolean
static method containsY takes real y returns boolean/*
- Checks if the bound contains the input coordinate
*/
//! endnovjass
private module CommonMembers
readonly static real centerX
readonly static real centerY
readonly static real minX
readonly static real minY
readonly static real maxX
readonly static real maxY
readonly static rect rect
readonly static region region
static method getArea takes nothing returns real
return (maxX - minX)*(maxY - minY)
endmethod
static method getRandomX takes nothing returns real
return GetRandomReal(minX, maxX)
endmethod
static method getRandomY takes nothing returns real
return GetRandomReal(minY, maxY)
endmethod
static method getBoundedXWithMargin takes real x, real margin returns real
if x < (minX + margin) then
return minX + margin
elseif x > (maxX - margin) then
return maxX - margin
endif
return x
endmethod
static method getBoundedYWithMargin takes real y, real margin returns real
if y < (minY + margin) then
return minY + margin
elseif y > (maxY - margin) then
return maxY - margin
endif
return y
endmethod
static method getBoundedX takes real x returns real
return getBoundedXWithMargin(x, 0.00)
endmethod
static method getBoundedY takes real y returns real
return getBoundedYWithMargin(y, 0.00)
endmethod
static method containsX takes real x returns boolean
return getBoundedX(x) == x
endmethod
static method containsY takes real y returns boolean
return getBoundedY(y) == y
endmethod
private static method onInit takes nothing returns nothing
set region = CreateRegion()
set rect = getRect()
set minX = GetRectMinX(rect)
set minY = GetRectMinY(rect)
set maxX = GetRectMaxX(rect)
set maxY = GetRectMaxY(rect)
set centerX = (minX + maxX)/2.00
set centerY = (minY + maxY)/2.00
call RegionAddRect(region, rect)
endmethod
endmodule
struct MapBounds extends array
private static method getRect takes nothing returns rect
return bj_mapInitialPlayableArea
endmethod
implement CommonMembers
endstruct
struct WorldBounds extends array
private static method getRect takes nothing returns rect
return GetWorldBounds()
endmethod
implement CommonMembers
endstruct
endlibrary
library GetUnitTypeCollisionSize uses Table
globals
private key t
endglobals
/*
* Credits to Nestharus
* https://github.com/nestharus/JASS/blob/master/jass/Systems/GetUnitCollision/script.j
*/
private function C takes integer unitId returns real
local unit u = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), unitId, 0.00, 0.00, 0.00)
local real x = GetUnitX(u)
local real y = GetUnitY(u)
local real l = 0
local real h = 300
local real m = 150
loop
if (IsUnitInRangeXY(u, x+m, y, 0)) then
set l = m
else
set h = m
endif
set m = (l+h)/2
exitwhen l+.01 > h
endloop
set m = R2I((m + .005)*100)/100.
set Table(t).real[unitId] = m
call RemoveUnit(u)
set u = null
return m
endfunction
function GetUnitTypeCollisionSize takes integer unitId returns real
if (Table(t).real.has(unitId)) then
return Table(t).real[unitId]
endif
return C(unitId)
endfunction
endlibrary
library IPool requires Table, Alloc
/*
IPool 3.0.0.0 by Bribe
Special thanks to Pyrogasm on wc3c.net for the original Pools resource, and to
Rising_Dusk for popularizing it.
Quick Intro of IPool:
Do you want a random integer from a multiple-choice list instead of a number
between x and y? Do you want one of those choices to have a lower or higher chance
to be picked? How about each item has its own odds of being picked?
IPool is similar to the native types itempool and unitpool, however returns
integers instead of handles. Integer-based pools can be used for all sorts of
things, ranging from randomized creep drops, spawning systems, random abilities
being cast (a la traps), random instances of a atruct, etc.
Object-Types of IPool:
IPool - Fast .getItem() method, slow add/remove. Uses a Table index formatted
as an array to get a random item without searching.
SubPool - Fast add/remove, slower .getItem method. Uses an O(n) search to
get a random item.
Main IPool API:
IPool.create()->IPool
returns a new IPool for your needs
iPoolInstance.flush()
reset all values of the IPool to default settings
iPoolInstance.destroy()
use this if you are totally done with that IPool
iPoolInstance.add(integer value, integer weight)
add any integer to the pool. The weight must be greater than 0 to have a
chance of being picked.
iPoolInstance.remove(integer value)
that value will no longer have a chance if you use this method on it!
iPoolInstance.getItem()->integer
returns one of the integers you added. Has higher chance to pick an item
with higher weight
Main SubPool API:
SubPool.create(integer totalWeight)->SubPool
The totalWeight must be equal to or greater than the sum of all items you
will add to the SubPool. Each added item has a chance of itsWeight/
totalWeight.
The rest of the main API is the same as IPool. Only keep in mind that
subPoolInstance.getItem() will return 0 in many cases due to the improbability
of an integer being picked.
Secondary API for both structs is included as follows:
poolInstance.contains(integer value)->boolean
Was the value added to the pool?
poolInstance.copy()->pool
Returns a new pool with the same properties as the pool you want copied.
debug poolInstance.print()
Useful for identifying what objects are in the pool during tests.
poolInstance.lock/unlock()
SubPools can associate with another subPool and/or an IPool. If you
have a single nested pool you wish to not get auto-destroyed, simply
call pool.lock(). pool.unlock() reverses it. If you do not use these, all
nested pools will auto-destruct when the containing SubPool is destroyed.
API for getting/setting the weight of an already-added value to a pool:
iPoolInstance.add(integer value, integer additionalWeight)
You can add more weight to a value in IPool, but the only way to reduce
it is to completely null it using the .remove(integer value) method. This
is to avoid the cumbersome giant that was the original IPool.
subPoolInstance[integer value].weight = integer newWeight
Setting a SubPool value's weight can be done arbitrarily.
iPoolInstance.weightOf(integer value)->integer
&
subPoolInstance[integer value].weight
Just in case you lost track of how much weight you assigned to a value.
API for nesting pools:
subPoolInstance.subPool = SubPool.create()
The SubPool member can be get and set arbitrarily. It cannot be the same
SubPool as the SubPool that's trying to nest it, as that would cause
recursion errors.
If the SubPool was not able to pick an integer, it will default to the
next nested SubPool within itself.
subPoolInstance.pool = IPool.create()
The .pool member can be get and set arbitrarily. If the SubPool and any
nested SubPools could not pick an integer, this pool will be the last resort.
*/
private module Init
private static method onInit takes nothing returns nothing
set .tar = TableArray[JASS_MAX_ARRAY_SIZE]
endmethod
endmodule
private struct data extends array
static TableArray tar
integer int
integer locks
integer weight
implement Alloc
implement Init
endstruct
private function Create takes nothing returns data
local data this = data.allocate()
set this.locks = 0
return this
endfunction
private function Destroy takes data this returns boolean
if this.locks == -1 or this == 0 then
debug call BJDebugMsg("IPool Error: Attempt to double-free instance!")
return false
endif
set this.locks = -1
call this.deallocate()
return true
endfunction
private function Lock takes data i returns nothing
if i.locks == -1 then
debug call BJDebugMsg("IPool Error: Attempt to lock a destroyed instance!")
return
endif
set i.locks = i.locks + 1
endfunction
private function Unlock takes data i returns boolean
if i.locks == -1 then
debug call BJDebugMsg("IPool Error: Attempt to unlock destroyed instance!")
return false
endif
set i.locks = i.locks - 1
return i.locks == 0
endfunction
struct IPool extends array
method operator weight takes nothing returns integer
return data(this).weight
endmethod
private method operator weight= takes integer lbs returns nothing
set data(this).weight = lbs
endmethod
private method operator table takes nothing returns Table
return data(this).int
endmethod
private method operator table= takes Table t returns nothing
set data(this).int = t
endmethod
static method create takes nothing returns thistype
local thistype this = Create()
set this.table = Table.create()
return this
endmethod
method flush takes nothing returns nothing
call this.table.flush()
call data.tar[this].flush()
set this.weight = 0
endmethod
method destroy takes nothing returns nothing
if Destroy(this) then
call this.table.destroy()
call data.tar[this].flush()
set this.weight = 0
endif
endmethod
method lock takes nothing returns nothing
call Lock(this)
endmethod
method unlock takes nothing returns nothing
if Unlock(this) then
call this.destroy()
endif
endmethod
//One-liner method to get a random item from the pool based on weight
method getItem takes nothing returns integer
return this.table[GetRandomInt(0, this.weight -1)]
endmethod
method weightOf takes integer value returns integer
return data.tar[this][value]
endmethod
method chanceOf takes integer value returns real //returns between 0. and 1.
return this.weightOf(value) / (this.weight + 0.) //don't divide by 0 here or else!
endmethod
method contains takes integer value returns boolean
return data.tar[this].has(value)
endmethod
method add takes integer value, integer lbs returns nothing
local Table tb = this.table
local integer i = this.weight
if lbs < 1 then
debug call BJDebugMsg("IPool Error: Tried to add value with invalid weight!")
return
endif
set data.tar[this][value] = data.tar[this][value] + lbs
set lbs = i + lbs
set this.weight = lbs //Important
loop
exitwhen i == lbs
set tb[i] = value //treat this.table as an array
set i = i + 1
endloop
endmethod
method remove takes integer value returns nothing
local Table tb = this.table
local Table new
local integer i = this.weight
local integer n = 0
local integer val
if not this.contains(value) then
debug call BJDebugMsg("IPool Error: Attempt to remove un-added instance!")
return
endif
set new = Table.create()
set this.table = new
loop
set i = i - 1
set val = tb[i]
if val != value then
set new[n] = val //write to the new Table without gaps
set n = n + 1
endif
exitwhen i == 0
endloop
set this.weight = n //lower pool weight
call tb.destroy() //abandon old Table instance
call data.tar[this].remove(value) //clear the value's weight now that it's gone
endmethod
method copy takes nothing returns thistype
local thistype new = .create()
local integer i = this.weight
local Table tt = this.table
local Table nt = new.table
local Table dt = data.tar[new]
local integer val
if i == 0 then
debug call BJDebugMsg("IPool Error: Attempt to copy invalid instance!")
call new.destroy()
return 0
endif
set new.weight = i
loop
set i = i - 1
exitwhen i == 0
set val = tt[i]
set nt[i] = val
set dt[val] = dt[val] + 1
endloop
return new
endmethod
static if DEBUG_MODE then
method print takes nothing returns nothing //print the array of the pool
local string s = "IPool: |cffffcc33Weight: "
local integer i = this.weight
local Table t = this.table
set s = s + I2S(i) + "; Indices: "
loop
set i = i - 1
exitwhen i <= 0
set s = s + "[" + I2S(t[i]) + "]"
endloop
call BJDebugMsg(s + "|r")
endmethod
endif
endstruct
//New struct to handle deliberately-rare chances
struct SubPool extends array
private IPool iPool //for association if you want it
private thistype nest //you can nest IPoolMinis for poolception
//you can change a value's weight via subpoolinstance[value].weight = blah
//you can also change the entire pool's weight via subpoolinstance.weight = blah.
method operator weight takes nothing returns integer
return data(this).weight
endmethod
method operator weight= takes integer lbs returns nothing
set data(this).weight = lbs
endmethod
private method operator value takes nothing returns integer
return data(this).int
endmethod
private method operator value= takes integer val returns nothing
set data(this).int = val
endmethod
private thistype next
private thistype prev
method operator pool takes nothing returns IPool
return this.iPool
endmethod
method operator pool= takes IPool ip returns nothing
if ip != 0 then
call ip.lock()
endif
if this.iPool != 0 then
call this.iPool.unlock()
endif
set this.iPool = ip
endmethod
static method create takes integer totalWeight returns thistype
local thistype this = Create()
set this.next = this
set this.prev = this //I'm my own best friend
set this.weight = totalWeight
return this
endmethod
method destroy takes nothing returns nothing
local thistype curr = this
if this.next == -1 then
debug call BJDebugMsg("SubPool Error: Attempt to double-free!")
return
endif
loop
set curr = curr.next
call Destroy(curr) //destroy all the things
exitwhen curr == this
endloop
set this.next = -1
set this.pool = 0
if this.nest != 0 then
if data(this.nest).locks == 1 then
call this.nest.destroy()
else
call Unlock(this.nest)
endif
set this.nest = 0
endif
call data.tar[this].flush()
endmethod
method lock takes nothing returns nothing
call Lock(this)
endmethod
method unlock takes nothing returns nothing
if Unlock(this) then
call this.destroy()
endif
endmethod
method operator subPool takes nothing returns thistype //need to return thistype and not IPool :P
return this.nest
endmethod
method operator subPool= takes thistype ip returns nothing
if this == ip then
debug call BJDebugMsg("SubPool Error: Don't set a subPool within itself. Use the .copy() method, instead.")
return
endif
if ip != 0 then
call ip.lock()
endif
if this.nest != 0 then
call this.nest.unlock()
endif
set this.nest = ip
endmethod
method add takes integer val, integer lbs returns nothing
local thistype new
if lbs <= 0 then
debug call BJDebugMsg("SubPool Error: Don't add a value without weight")
return
endif
if data.tar[this].has(val) then
set new = data.tar[this][val]
set new.weight = new.weight + lbs
return
endif
set new = Create()
set new.prev = this.prev
set this.prev.next = new
set this.prev = new
set new.next = this
set new.value = val
set new.weight = lbs
set data.tar[this][val] = new
endmethod
method contains takes integer val returns boolean
return data.tar[this].has(val)
endmethod
method operator [] takes integer val returns thistype
return data.tar[this][val]
endmethod
method operator []= takes integer val, integer newWeight returns nothing
set this[val].weight = newWeight
endmethod
method remove takes integer val returns nothing
local thistype node = this[val]
if Destroy(node) then
set node.prev.next = node.next
set node.next.prev = node.prev
call data.tar[this].remove(val)
debug else
debug call BJDebugMsg("SubPool Error: Attempt to remove non-added value")
endif
endmethod
method getItem takes nothing returns integer
local thistype curr = this
local integer i = GetRandomInt(1, this.weight)
loop
set curr = curr.next
set i = i - curr.weight
exitwhen i <= 0
endloop
if curr == this then
if this.nest != 0 then
set i = this.nest.getItem()
else
set i = 0
endif
if i == 0 and this.pool != 0 then //if no low-probability item could be found...
set i = this.pool.getItem() //pick a random int from main pool
endif
else
set i = curr.value
endif
return i
endmethod
method copy takes nothing returns thistype
local thistype new = .create(this.weight)
local thistype curr = this
set new.pool = this.iPool
set new.subPool = this.nest
loop
set curr = curr.next
exitwhen curr == this
call new.add(curr.value, curr.weight)
endloop
return new
endmethod
static if DEBUG_MODE then
method print takes nothing returns nothing
local thistype curr = this
local string s = "SubPool: |cffffcc33Instance: " + I2S(this) + ", TotalWeight: " + I2S(this.weight) + ", Indices: "
if curr.next == this then
call BJDebugMsg("SubPool is empty!")
//return
endif
loop
set curr = curr.next
exitwhen curr == this
set s = s + "[" + I2S(curr.value) + "]<" + I2S(curr.weight) + ">"
endloop
call BJDebugMsg(s + "|r")
if this.nest != 0 then
call this.nest.print()
endif
if this.iPool != 0 then
call this.iPool.print()
endif
endmethod
endif
endstruct
endlibrary
library DestructableHider initializer init
/*
by Zwiebelchen v1.3
Destructables create an enormous amount of overhead on warcraft III maps, almost the same as units, especially walkable destructables.
Thus, a large amount of destructables creates a huge drop of FPS in the game, even on fast machines, due to the poor engine of WC3.
This effect is fairly noticable at an amount of even less than 1000 destructables, which is reached very fast when using invisible platforms.
Warcraft III automaticly hides units outside the screen to save performance, however it does not do so for destructables, for unknown reasons.
The purpose of this fully automatic system is to hide all those destructables, that are currently not viewed anyway, to save a lot of processing time.
To do that, the entire map is splitted into tiles of an editable size and all destructables within those tiles are stored into a table, to allow fast access.
When a tile is viewed, all destructables on adjacent tiles will also be shown, so that moving the camera doesnt create ugly popup effects when the center of the view is on the edge of a tile.
However, there are some rules you need to consider, in order to make your map work without desyncs in multiplayer:
- never hide destructables units or players can interact with (attackable, selectable or destructable)
- hiding destructables that block pathing is safe, as hiding the destructable will not change its pathing
- hiding destructables that need to be enumed is safe; hidden destructables can be enumerated
- hiding walkable platforms is also safe, as long as you dont get a location Z for a location placed on that destructable globally; this is not 100% safe anyway, as
the returned value of GetLocationZ() is dependant on the render state of the destructable
API:
private function filt returns boolean
- add custom filter code for the automatic enumeration of destructables on map init inside
- if all destructables should be added to the system, let it return true
- example:
private function filt returns boolean
return GetDestructableMaxLife(GetFilterDestructable()) == 1
endfunction
-> automaticly adds all destructables on the map with a maximum life of 1 on map init to the system
Optional:
public function register takes destructable returns nothing
- adds a destructable to the system, also hides/shows the destructable depending on the position of the camera
public function unregister takes destructable returns nothing
- removes a destructable from the system, also unhides the destructable in case it was hidden
*/
globals
//==== CONFIGURABLES ====
private constant real INTERVAL = 0.1 //Update interval in seconds.
//[in multiplayer, the camera positions will only get updated every 0.05-0.1 seconds, so setting it to a lower value than 0.05 makes no sense]
//[update frequency can be much higher in single player mode!]
private constant integer DRAW_DISTANCE = 512 //the radius around the camera target in which the tiles are considered visible; should be about the same as sight radius (not diameter) of the camera; for 3d cams, use the FarZ value
//Use multiples of 1024 for maximum efficiency on square division. Recommended value: 5120
private constant integer TILE_RESOLUTION = 4 //amount of tiles spread over DRAW_DISTANCE
//- higher resolution = more overhead to incrementing loop variables, but less amounts of destructables checked when moving the camera
//- lower resolution = less overhead to incrementing loop variables, but higher amounts of destructables checked when moving the camera
//-> Recommended value: 8-12
//==== END OF CONFIGURABLES ====
private hashtable hash = InitHashtable()
private integer columns = 0
private integer rows = 0
private integer lastrow = 0
private integer lastcolumn = 0
private integer lastid = 0
private real mapMinX = 0
private real mapMinY = 0
private constant integer TILESIZE = DRAW_DISTANCE/TILE_RESOLUTION
endglobals
private function filt takes nothing returns boolean
//Add code for the enum filter of the automatic registration of destructables on map init
//example:
//return GetDestructableMaxLife(GetFilterDestructable()) == 1
//-> automaticly adds all destructables on the map with a maximum life of 1 on map init to the system
return true
endfunction
public function register takes destructable d returns nothing
local integer id = R2I((GetDestructableY(d)-mapMinY)/TILESIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILESIZE)
local integer count = LoadInteger(hash, id, 0)+1
call SaveInteger(hash, id, 0, count)
call SaveDestructableHandle(hash, id, count, d)
call ShowDestructable(d, LoadBoolean(hash, id, -1)) //match visibility state
call SaveInteger(hash, GetHandleId(d), 0, count) //store the list position for fast lookup
endfunction
public function unregister takes destructable d returns nothing
local integer id = R2I((GetDestructableY(d)-mapMinY)/TILESIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILESIZE)
local integer count = LoadInteger(hash, id, 0)
local integer a = LoadInteger(hash, GetHandleId(d), 0)
local destructable temp
if a < count then //move the last in list up to this slot
set temp = LoadDestructableHandle(hash, id, count)
call SaveDestructableHandle(hash, id, a, temp)
call SaveInteger(hash, GetHandleId(temp), 0, a) //update list position
set temp = null
endif
call RemoveSavedHandle(hash, id, count) //clean up the deserted slot
call SaveInteger(hash, id, 0, count-1)
call FlushChildHashtable(hash, GetHandleId(d)) //clean up list position
call ShowDestructable(d, true) //make sure its shown again in case it was hidden
endfunction
private function autoregister takes nothing returns nothing
local destructable d = GetEnumDestructable()
local integer id = R2I((GetDestructableY(d)-mapMinY)/TILESIZE)*columns + R2I((GetDestructableX(d)-mapMinX)/TILESIZE)
local integer count = LoadInteger(hash, id, 0)+1
call SaveInteger(hash, id, 0, count)
call SaveDestructableHandle(hash, id, count, d)
call ShowDestructable(d, false) //initially hide everything
call SaveInteger(hash, GetHandleId(d), 0, count) //store the list position for fast lookup
set d = null
endfunction
private function EnumGrid takes integer x1, integer x2, integer y1, integer y2, boolean show returns nothing
local integer a = x1
local integer b
local integer j
local integer id
local integer count
loop
set b = y1
exitwhen a > x2
loop
exitwhen b > y2
set id = b*columns+a
call SaveBoolean(hash, id, -1, show)
set count = LoadInteger(hash, id, 0)
set j = 0
loop
exitwhen j >= count
set j = j + 1
call ShowDestructable(LoadDestructableHandle(hash, id, j), show)
endloop
set b = b + 1
endloop
set a = a + 1
endloop
endfunction
private function ChangeTiles takes integer r, integer c, integer lr, integer lc returns nothing
local integer AminX = c-TILE_RESOLUTION
local integer AmaxX = c+TILE_RESOLUTION
local integer AminY = r-TILE_RESOLUTION
local integer AmaxY = r+TILE_RESOLUTION
local integer BminX = lc-TILE_RESOLUTION
local integer BmaxX = lc+TILE_RESOLUTION
local integer BminY = lr-TILE_RESOLUTION
local integer BmaxY = lr+TILE_RESOLUTION
//border safety:
if AminX < 0 then
set AminX = 0
endif
if AminY < 0 then
set AminY = 0
endif
if BminX < 0 then
set BminX = 0
endif
if BminY < 0 then
set BminY = 0
endif
if AmaxX >= columns then
set AmaxX = columns-1
endif
if AmaxY >= rows then
set AmaxX = rows-1
endif
if BmaxX >= columns then
set BmaxX = columns-1
endif
if BmaxY >= rows then
set BmaxX = rows-1
endif
if BmaxX < AminX or AmaxX < BminX or BmaxY < AminY or AmaxY < BminY then
call EnumGrid(AminX, AmaxX, AminY, AmaxY, true)
call EnumGrid(BminX, BmaxX, BminY, BmaxY, false)
else
if c >= lc then
if c != lc then
call EnumGrid(BmaxX+1, AmaxX, AminY, AmaxY, true)
call EnumGrid(BminX, AminX-1, BminY, BmaxY, false)
endif
if AminY < BminY then
call EnumGrid(AminX, BmaxX, AmaxY+1, BmaxY, false)
call EnumGrid(AminX, BmaxX, AminY, BminY-1, true)
elseif BminY < AminY then
call EnumGrid(AminX, BmaxX, BmaxY+1, AmaxY, true)
call EnumGrid(AminX, BmaxX, BminY, AminY-1, false)
endif
else
call EnumGrid(AminX, BminX-1, AminY, AmaxY, true)
call EnumGrid(AmaxX+1, BmaxX, BminY, BmaxY, false)
if AminY < BminY then
call EnumGrid(BminX, AmaxX, AminY, BminY-1, true)
call EnumGrid(BminX, AmaxX, AmaxY+1, BmaxY, false)
elseif BminY < AminY then
call EnumGrid(BminX, AmaxX, BminY, AminY-1, false)
call EnumGrid(BminX, AmaxX, BmaxY+1, AmaxY, true)
endif
endif
endif
endfunction
private function periodic takes nothing returns nothing
local integer row = R2I((GetCameraTargetPositionY()-mapMinY)/TILESIZE)
local integer column = R2I((GetCameraTargetPositionX()-mapMinX)/TILESIZE)
local integer id = row*columns + column
if id == lastid then //only check for tiles if the camera has left the last tile
return
endif
call ChangeTiles(row, column, lastrow, lastcolumn)
set lastrow = row
set lastcolumn = column
set lastid = id
endfunction
private function init takes nothing returns nothing
set mapMinX = GetRectMinX(bj_mapInitialPlayableArea)
set mapMinY = GetRectMinY(bj_mapInitialPlayableArea)
set lastrow = R2I((GetCameraTargetPositionY()-mapMinY)/TILESIZE)
set lastcolumn = R2I((GetCameraTargetPositionX()-mapMinX)/TILESIZE)
set rows = R2I((GetRectMaxY(bj_mapInitialPlayableArea)-mapMinY)/TILESIZE)+1
set columns = R2I((GetRectMaxX(bj_mapInitialPlayableArea)-mapMinX)/TILESIZE)+1
if lastcolumn <= columns/2 then //to make sure the game starts with a full make-visible enum of all destructables on screen
set lastcolumn = columns-1
else
set lastcolumn = 0
endif
if lastrow <= rows/2 then
set lastrow = rows-1
else
set lastrow = 0
endif
set lastid = lastrow*columns + lastcolumn
call EnumDestructablesInRect(bj_mapInitialPlayableArea, Filter(function filt), function autoregister)
call TimerStart(CreateTimer(), INTERVAL, true, function periodic)
call periodic() //to make sure the destructables on screen after the map loading process finishes are initially shown
endfunction
endlibrary
library TerrainGenerator uses MapBounds
struct TerrainGenerator extends array
static integer background = 0
private static integer array layerId
private static real array probability
private static boolean array needsPreviousLayer
private static integer layerCount = 0
private static integer currentLayer = 0
static method addLayer takes integer terrainId, real prob, boolean needsPrevLayer returns nothing
set layerCount = layerCount + 1
set needsPreviousLayer[layerCount] = needsPrevLayer
set probability[layerCount] = prob
set layerId[layerCount] = terrainId
endmethod
private static method initBackground takes nothing returns nothing
if background != 0 then
call SetTerrainType(WorldBounds.centerX, WorldBounds.centerY, background, -1, 40, 1)
endif
endmethod
private static method initLayer takes integer i returns nothing
local boolean needsPrevLayer = needsPreviousLayer[i]
local real y = WorldBounds.minY + 64.00
local real x
loop
exitwhen y >= WorldBounds.maxY
set x = WorldBounds.minX + 64.00
loop
exitwhen x >= WorldBounds.maxX
if GetRandomReal(0.00, 1.00) < probability[i] and/*
*/ ((not needsPrevLayer) or i == 1 or GetTerrainType(x, y) == layerId[i - 1]) then
call SetTerrainType(x, y, layerId[i], -1, 1, 1)
endif
set x = x + 128.00
endloop
set y = y + 128.00
endloop
endmethod
private static method onLayerInit takes nothing returns nothing
call initLayer(currentLayer)
endmethod
static method generate takes nothing returns nothing
call initBackground()
set currentLayer = 0
loop
exitwhen currentLayer == layerCount
set currentLayer = currentLayer + 1
call ForForce(bj_FORCE_PLAYER[0], function thistype.onLayerInit)
endloop
endmethod
endstruct
endlibrary
library UnitCampsGenerator /*
*/uses /*
*/Alloc /*
*/LinkedList /*
*/IPool /*
*/Table /*
*/MapBounds /*
*/TerrainPathability /*
*/GetUnitTypeCollisionSize /*
*/optional ErrorMessage /*
*/
globals
public constant real CAMP_SPACING_MIN = 800.00
endglobals
private keyword UnitList
globals
UnitCamp array ENUMED_UNIT_CAMPS
private IPool array unitIdPool
private real array unitCampInstanceX
private real array unitCampInstanceY
private real array unitCampInstanceRadius
private real array unitCampInstanceFacing
private UnitList array unitCampInstanceList
private player array unitCampInstancePlayer
private TableArray weightTable
private TableArray limitTable
private trigger evaluator = CreateTrigger()
private rect globalRect = Rect(0.00, 0.00, 0.00, 0.00)
private real tempRadius
private UnitCamp tempCamp
private integer tempData
private boolexpr tempFilter
endglobals
static if DEBUG_MODE then
private function AssertError takes boolean condition, string methodName, string structName, integer node, string message returns nothing
static if LIBRARY_ErrorMessage then
call ThrowError(condition, SCOPE_PREFIX, methodName, structName, node, message)
endif
endfunction
endif
private module Init
private static method onInit takes nothing returns nothing
call onScopeInit()
endmethod
endmodule
private function GetRandomMapX takes nothing returns real
return MapBounds.getRandomX()
endfunction
private function GetRandomMapY takes nothing returns real
return MapBounds.getRandomY()
endfunction
function EnumUnitCampsInRange takes real x, real y, real radius returns nothing
local real dx
local real dy
local UnitCampInstance node
local UnitCampInstance next = 0
local integer i = UnitCampsGenerator.unitCampCount
loop
exitwhen i == 0
set node = UnitCampsGenerator.unitCamp[i]
set dx = unitCampInstanceX[node] - x
set dy = unitCampInstanceY[node] - y
if dx*dx + dy*dy <= (radius + unitCampInstanceRadius[node])*(radius + unitCampInstanceRadius[node]) then
set ENUMED_UNIT_CAMPS[next] = node
set next = ENUMED_UNIT_CAMPS[next]
endif
set i = i - 1
endloop
set ENUMED_UNIT_CAMPS[next] = 0
endfunction
private struct Node extends array
implement Alloc
endstruct
private struct UnitList extends array
unit unit
private static method onRemove takes thistype node returns nothing
call RemoveUnit(node.unit)
set node.unit = null
call Node(node).deallocate()
endmethod
implement List
endstruct
private struct UnitPoolList extends array
boolean removed
readonly integer unitId
private static key tk
implement StaticList
static method contains takes integer unitId returns boolean
return Table(tk).has(unitId)
endmethod
static method getNode takes integer unitId returns thistype
return Table(tk)[unitId]
endmethod
static method emplace takes integer unitId returns nothing
local thistype node = Node.allocate()
call pushBack(node)
set node.unitId = unitId
set Table(tk)[unitId] = node
endmethod
static method delete takes integer unitId returns nothing
call Table(tk).remove(unitId)
call remove(Table(tk)[unitId])
endmethod
endstruct
struct UnitCamp extends array
player player
integer sizeMin
integer sizeMax
real radius
real facingDeviationMax
real unitSpacingMin
static method operator [] takes thistype node returns thistype
local thistype this = Node.allocate()
set this.player = node.player
set this.sizeMin = node.sizeMin
set this.sizeMax = node.sizeMax
set this.radius = node.radius
set this.facingDeviationMax = node.facingDeviationMax
set this.unitSpacingMin = node.unitSpacingMin
set unitIdPool[this] = unitIdPool[node].copy()
return this
endmethod
method registerToUnitPool takes integer unitId, integer weight, integer limit returns thistype
debug call AssertError(weight < 1, "registerToUnitPool()", "thistype", this, "Invalid Pool Item Weight (" + I2S(weight) + ")")
if not weightTable[this].has(unitId) then
call unitIdPool[this].add(unitId, weight)
set weightTable[this][unitId] = weight
set limitTable[this][unitId] = limit
set limitTable[this][-unitId] = limit
endif
return this
endmethod
method unregisterFromUnitPool takes integer unitId returns thistype
if weightTable[this].has(unitId) then
call unitIdPool[this].remove(unitId)
call weightTable[this].remove(unitId)
call weightTable[this].remove(-unitId)
call UnitPoolList.delete(unitId)
endif
return this
endmethod
method clearPool takes nothing returns thistype
call unitIdPool[this].flush()
call weightTable[this].flush()
call limitTable[this].flush()
return this
endmethod
static method create takes nothing returns thistype
local thistype node = Node.allocate()
set unitIdPool[node] = IPool.create()
set node.player = Player(PLAYER_NEUTRAL_AGGRESSIVE)
return node
endmethod
method destroy takes nothing returns nothing
call Node(this.clearPool()).deallocate()
call unitIdPool[this].destroy()
endmethod
endstruct
struct UnitCampInstance extends array
method operator x takes nothing returns real
return unitCampInstanceX[this]
endmethod
method operator y takes nothing returns real
return unitCampInstanceY[this]
endmethod
method operator radius takes nothing returns real
return unitCampInstanceRadius[this]
endmethod
method operator facing takes nothing returns real
return unitCampInstanceFacing[this]
endmethod
method operator player takes nothing returns player
return unitCampInstancePlayer[this]
endmethod
method destroy takes nothing returns nothing
call UnitList(this).flush()
endmethod
endstruct
private struct CampPoolList extends array
boolean removed
implement StaticList
endstruct
struct UnitCampsGenerator extends array
readonly static UnitCampInstance array unitCamp
readonly static integer unitCampCount = 0
readonly static real filterX = 0.00
readonly static real filterY = 0.00
readonly static UnitCamp filterCamp = 0
private static IPool unitCampPool = 0
private static unit array mapCampUnit
private static integer mapCampUnitCount = 0
private static method getPool takes nothing returns IPool
if unitCampPool == 0 then
set unitCampPool = IPool.create()
endif
return unitCampPool
endmethod
static method registerToUnitCampPool takes UnitCamp camp, integer weight, integer limit returns thistype
debug call AssertError(weight < 1, "registerToUnitCampPool()", "thistype", 0, "Invalid Pool Item Weight (" + I2S(weight) + ")")
if not weightTable[0].has(camp) then
call getPool().add(camp, weight)
set weightTable[0][camp] = weight
set limitTable[0][camp] = limit
set limitTable[0][-camp] = limit
endif
return 0
endmethod
static method unregisterFromUnitCampPool takes UnitCamp camp returns thistype
if weightTable[0].has(camp) then
call getPool().remove(camp)
call weightTable[0].remove(camp)
call limitTable[0].remove(camp)
call limitTable[0].remove(-camp)
endif
return 0
endmethod
static method clearUnitCampPool takes nothing returns thistype
call getPool().flush()
call weightTable[0].flush()
call limitTable[0].flush()
return 0
endmethod
private static method clearDestInRadius takes nothing returns boolean
local destructable d = GetFilterDestructable()
local real dx = GetWidgetX(d) - GetRectCenterX(globalRect)
local real dy = GetWidgetY(d) - GetRectCenterY(globalRect)
if dx*dx + dy*dy < tempRadius*tempRadius then
call RemoveDestructable(d)
endif
set d = null
return false
endmethod
private static method generateCamp takes UnitCamp camp, real x, real y, real facing returns UnitCampInstance
local integer i = GetRandomInt(camp.sizeMin, camp.sizeMax)
local integer limit
local integer unitId
local real collisionSize
local real r
local real a
local UnitCampInstance node
local UnitList temp
local UnitList unitNode
local UnitPoolList poolNode
local boolean exit
if i == 0 then
return 0
endif
set node = Node.allocate()
call UnitList.insert(node, node) // Make a circular list
set unitCampInstanceX[node] = x
set unitCampInstanceY[node] = y
set unitCampInstanceRadius[node] = camp.radius
set unitCampInstanceFacing[node] = facing
set tempRadius = camp.radius
call SetRect(globalRect, x - camp.radius, y - camp.radius, x + camp.radius, y + camp.radius)
call EnumDestructablesInRect(globalRect, Filter(function thistype.clearDestInRadius), null)
loop
exitwhen i == 0
set mapCampUnitCount = mapCampUnitCount + 1
set unitId = unitIdPool[camp].getItem()
if not UnitPoolList.contains(unitId) then
call UnitPoolList.emplace(unitId)
endif
if limitTable[camp][unitId] > 0 then
set limit = limitTable[camp][-unitId] - 1
set limitTable[camp][-unitId] = limit
if limit == 0 then
call unitIdPool[camp].remove(unitId)
set UnitPoolList.getNode(unitId).removed = true
endif
endif
set collisionSize = GetUnitTypeCollisionSize(unitId)
loop
set a = GetRandomReal(0.00, 2.00*bj_PI)
set r = GetRandomReal(0.00, camp.radius)
call IsTerrainWalkable(x + r*Cos(a), y + r*Sin(a))
set exit = true
set temp = UnitList(node).next
loop
exitwhen temp == node
if IsUnitInRangeXY(temp.unit, TerrainPathability_X, TerrainPathability_Y, camp.unitSpacingMin + collisionSize) then
set exit = false
exitwhen true
endif
set temp = temp.next
endloop
exitwhen exit
endloop
set mapCampUnit[mapCampUnitCount] = CreateUnit(camp.player, unitId, TerrainPathability_X, TerrainPathability_Y, GetRandomReal(facing - camp.facingDeviationMax, facing + camp.facingDeviationMax)*bj_RADTODEG)
set unitNode = Node.allocate()
set unitNode.unit = mapCampUnit[mapCampUnitCount]
call UnitList(node).pushBack(unitNode)
set i = i - 1
endloop
set poolNode = UnitPoolList.head.next
loop
exitwhen poolNode == UnitPoolList.head
if poolNode.removed then
set poolNode.removed = false
call unitIdPool[camp].add(poolNode.unitId, weightTable[camp][poolNode.unitId])
endif
set limitTable[camp][-poolNode.unitId] = limitTable[camp][poolNode.unitId]
call UnitPoolList.remove(poolNode)
set poolNode = poolNode.next
endloop
return node
endmethod
private static method initCampGeneration takes nothing returns nothing
set tempData = generateCamp(tempCamp, TerrainPathability_X, TerrainPathability_Y, GetRandomReal(0.00, 2.00*bj_PI))
endmethod
private static method initIterativeGeneration takes nothing returns nothing
local IPool pool = getPool()
local UnitCamp camp = pool.getItem()
local UnitCampInstance node
local integer limit
local real x
local real y
local real r = camp.radius
if not CampPoolList.isLinked(camp) then
call CampPoolList.pushBack(camp)
endif
if limitTable[0][camp] > 0 then
set limit = limitTable[0][-camp] - 1
set limitTable[0][-camp] = limit
if limit == 0 then
call pool.remove(camp)
set CampPoolList(camp).removed = true
endif
endif
loop
set x = MapBounds.getBoundedXWithMargin(GetRandomMapX(), r)
set y = MapBounds.getBoundedYWithMargin(GetRandomMapY(), r)
call IsTerrainWalkable(x, y)
call EnumUnitCampsInRange(TerrainPathability_X, TerrainPathability_Y, r + CAMP_SPACING_MIN)
if ENUMED_UNIT_CAMPS[0] == 0 then
if tempFilter == null then
exitwhen true
else
set filterX = TerrainPathability_X
set filterY = TerrainPathability_Y
set filterCamp = camp
exitwhen TriggerEvaluate(evaluator)
endif
endif
endloop
set filterX = 0.00
set filterY = 0.00
set filterCamp = 0
set tempCamp = camp
call ForForce(bj_FORCE_PLAYER[0], function thistype.initCampGeneration)
set node = tempData
if node > 0 then
set unitCampCount = unitCampCount + 1
set unitCamp[unitCampCount] = node
endif
endmethod
static method generate takes integer count, boolexpr filter returns thistype
local IPool pool = getPool()
local CampPoolList poolNode
set tempFilter = filter
call TriggerClearConditions(evaluator)
call TriggerAddCondition(evaluator, filter)
loop
exitwhen count == 0
call ForForce(bj_FORCE_PLAYER[0], function thistype.initIterativeGeneration)
set count = count - 1
endloop
set poolNode = CampPoolList.head.next
loop
exitwhen poolNode == CampPoolList.head
if poolNode.removed then
set poolNode.removed = false
call pool.add(poolNode, weightTable[0][poolNode])
endif
set limitTable[0][-poolNode] = limitTable[0][poolNode]
call CampPoolList.remove(poolNode)
set poolNode = poolNode.next
endloop
return 0
endmethod
endstruct
private struct Initializer extends array
private static method onScopeInit takes nothing returns nothing
set weightTable = TableArray[JASS_MAX_ARRAY_SIZE]
set limitTable = TableArray[JASS_MAX_ARRAY_SIZE]
endmethod
implement Init
endstruct
endlibrary
library DestructablesGenerator /*
*/uses /*
*/MapBounds /*
*/UnitCampsGenerator /*
*/
struct DestructablesGenerator extends array
readonly static integer array clusterDestId
readonly static integer array clusterSizeMin
readonly static integer array clusterSizeMax
readonly static real array clusterRadiusBase
readonly static real array clusterRadiusPerSize
readonly static real array clusterClearance
readonly static integer array clusterCountMin
readonly static integer array clusterCountMax
readonly static boolexpr array clusterPlacementFilter
readonly static real filterX
readonly static real filterY
readonly static real filterRadius
readonly static integer filterSize
readonly static integer filterLayer
readonly static integer currentLayer = 0
private static integer layerCount = 0
private static group enumGroup = CreateGroup()
private static trigger evaluator = CreateTrigger()
static method addLayer takes integer destructableId, integer sizeMin, integer sizeMax, real radiusBase, real radiusPerSize, real clearance, integer countMin, integer countMax, boolexpr filter returns nothing
set layerCount = layerCount + 1
set clusterDestId[layerCount] = destructableId
set clusterSizeMin[layerCount] = sizeMin
set clusterSizeMax[layerCount] = sizeMax
set clusterRadiusBase[layerCount] = radiusBase
set clusterRadiusPerSize[layerCount] = radiusPerSize
set clusterClearance[layerCount] = clearance
set clusterCountMin[layerCount] = countMin
set clusterCountMax[layerCount] = countMax
set clusterPlacementFilter[layerCount] = filter
endmethod
private static method checkUnitProximity takes nothing returns boolean
return IsUnitInRangeXY(GetFilterUnit(), filterX, filterY, filterRadius)
endmethod
private static method initLayer takes nothing returns nothing
local real r
local real a
local real x
local real y
local integer i = GetRandomInt(clusterSizeMin[currentLayer], clusterSizeMax[currentLayer])
local real radius = clusterRadiusBase[currentLayer] + i*clusterRadiusPerSize[currentLayer]
call TriggerClearConditions(evaluator)
call TriggerAddCondition(evaluator, clusterPlacementFilter[currentLayer])
loop
set x = WorldBounds.getBoundedXWithMargin(WorldBounds.getRandomX(), radius)
set y = WorldBounds.getBoundedYWithMargin(WorldBounds.getRandomY(), radius)
call EnumUnitCampsInRange(x, y, radius + clusterClearance[currentLayer])
if ENUMED_UNIT_CAMPS[0] == 0 then
set filterX = x
set filterY = y
set filterRadius = radius + clusterClearance[currentLayer]
call GroupEnumUnitsInRange(enumGroup, x, y, filterRadius + 200.00, Filter(function thistype.checkUnitProximity))
if FirstOfGroup(enumGroup) == null then
if clusterPlacementFilter[currentLayer] == null then
exitwhen true
else
set filterSize = i
set filterLayer = currentLayer
exitwhen TriggerEvaluate(evaluator)
endif
endif
endif
endloop
set filterX = 0.00
set filterY = 0.00
set filterRadius = 0.00
set filterSize = 0
set filterLayer = 0
loop
exitwhen i == 0
set a = GetRandomReal(0.00, 2.00*bj_PI)
set r = GetRandomReal(0.00, radius)
call CreateDestructable(clusterDestId[currentLayer], x + r*Cos(a), y + r*Sin(a), GetRandomReal(0, 360), 1.00, GetRandomInt(0, 3))
set i = i - 1
endloop
endmethod
private static method onLayerInit takes nothing returns nothing
local integer clusterCount = GetRandomInt(clusterCountMin[currentLayer], clusterCountMax[currentLayer])
loop
exitwhen clusterCount == 0
call ForForce(bj_FORCE_PLAYER[0], function thistype.initLayer)
set clusterCount = clusterCount - 1
endloop
endmethod
static method generate takes nothing returns nothing
set currentLayer = 0
loop
exitwhen currentLayer == layerCount
set currentLayer = currentLayer + 1
call onLayerInit()
endloop
endmethod
endstruct
endlibrary
library MapSettingGenerator /*
*/uses /*
*/TerrainGenerator /*
*/DestructablesGenerator /*
*/UnitCampsGenerator /*
Inspiration for creating my own terrain generator:
https://www.hiveworkshop.com/threads/terrain-generation.154168/
*/
struct MapSettingGenerator extends array
static method operator Terrain takes nothing returns TerrainGenerator
return 0
endmethod
static method operator Destructables takes nothing returns DestructablesGenerator
return 0
endmethod
static method operator UnitCamps takes nothing returns UnitCampsGenerator
return 0
endmethod
endstruct
endlibrary
scope MapInit initializer OnInit
globals
private constant integer UNIT_CAMP_COUNT_MIN = 12
private constant integer UNIT_CAMP_COUNT_MAX = 20
private constant real CENTER_STAGE_RADIUS = 1000.00
private string a = "A"
private string b = "B"
private string c = "C"
private real array heroSpawnX
private real array heroSpawnY
private real array heroSpawnFacing
endglobals
private function InitTerrain takes nothing returns nothing
set MapSettingGenerator.Terrain.background = 'Adrd'
call MapSettingGenerator.Terrain.addLayer('Adrg', 0.6, true)
call MapSettingGenerator.Terrain.addLayer('Alvd', 0.3, true)
call MapSettingGenerator.Terrain.addLayer('Avin', 0.15, true)
call MapSettingGenerator.Terrain.generate()
set a = null
endfunction
private function DestructablePlacementFilter takes nothing returns boolean
local real dx = MapSettingGenerator.Destructables.filterX - WorldBounds.centerX
local real dy = MapSettingGenerator.Destructables.filterY - WorldBounds.centerY
local real r = CENTER_STAGE_RADIUS + MapSettingGenerator.Destructables.filterRadius
return dx*dx + dy*dy > r*r
endfunction
private function InitDestructables takes nothing returns nothing
call MapSettingGenerator.Destructables.addLayer('FTtw', 2, 12, 0.00, 50.00, 35.00, 180, 240, Filter(function DestructablePlacementFilter))
call MapSettingGenerator.Destructables.generate()
set b = null
endfunction
private function UnitCampsLocationFilter takes nothing returns boolean
local real dx = MapSettingGenerator.UnitCamps.filterX - MapBounds.centerX
local real dy = MapSettingGenerator.UnitCamps.filterY - MapBounds.centerY
local real r = CENTER_STAGE_RADIUS + MapSettingGenerator.UnitCamps.filterCamp.radius
return dx*dx + dy*dy > r*r
endfunction
private function InitUnitCamps takes nothing returns nothing
/*
* Human camps
*/
local UnitCamp camp = UnitCamp.create()
set camp.sizeMin = 4
set camp.sizeMax = 8
set camp.radius = 450.00
set camp.facingDeviationMax = bj_PI*0.125
set camp.unitSpacingMin = 50.00
call camp/*
*/.registerToUnitPool('hpea', 1, 0)/*
*/.registerToUnitPool('hfoo', 1, 0)/*
*/.registerToUnitPool('hrif', 1, 0)/*
*/.registerToUnitPool('hmpr', 1, 0)/*
*/.registerToUnitPool('hhou', 1, 2)/*
*/.registerToUnitPool('hwtw', 1, 1)
call MapSettingGenerator.UnitCamps.registerToUnitCampPool(camp, 5, 0)
/*
* Orc camps
*/
set camp = UnitCamp.create()
set camp.sizeMin = 3
set camp.sizeMax = 6
set camp.radius = 350.00
set camp.facingDeviationMax = bj_PI*0.125
set camp.unitSpacingMin = 65.00
call camp/*
*/.registerToUnitPool('oshm', 1, 0)/*
*/.registerToUnitPool('ogru', 1, 0)/*
*/.registerToUnitPool('ocat', 1, 2)/*
*/.registerToUnitPool('Obla', 1, 1)/*
*/.registerToUnitPool('otto', 1, 1)/*
*/.registerToUnitPool('ogre', 1, 1)
call MapSettingGenerator.UnitCamps.registerToUnitCampPool(camp, 3, 0)
/*
* Troll camps
*/
set camp = UnitCamp.create()
set camp.sizeMin = 3
set camp.sizeMax = 6
set camp.radius = 350.00
set camp.facingDeviationMax = bj_PI*0.125
set camp.unitSpacingMin = 50.00
call camp/*
*/.registerToUnitPool('ndtr', 1, 0)/*
*/.registerToUnitPool('ndtp', 1, 0)/*
*/.registerToUnitPool('ndtt', 1, 0)/*
*/.registerToUnitPool('ndtb', 1, 0)/*
*/.registerToUnitPool('ndth', 1, 0)/*
*/.registerToUnitPool('ndtw', 1, 0)
call MapSettingGenerator.UnitCamps.registerToUnitCampPool(camp, 2, 0)
/*
* Critters
*/
set camp = UnitCamp.create()
set camp.sizeMin = 1
set camp.sizeMax = 1
set camp.radius = 0.00
set camp.facingDeviationMax = 0.00
call camp/*
*/.registerToUnitPool('nalb', 1, 0)/*
*/.registerToUnitPool('nech', 1, 0)/*
*/.registerToUnitPool('nfro', 1, 0)/*
*/.registerToUnitPool('nsea', 1, 0)
call MapSettingGenerator.UnitCamps.registerToUnitCampPool(camp, 1, 4)
/*
* Neutral buildings
*/
set camp = UnitCamp.create()
set camp.sizeMin = 1
set camp.sizeMax = 1
set camp.radius = 125.00
set camp.facingDeviationMax = 0.00
call camp.registerToUnitPool('ntav', 1, 0)
call MapSettingGenerator.UnitCamps.registerToUnitCampPool(camp, 1, 2)
/*
* Initialize camps generation
*/
call MapSettingGenerator.UnitCamps.generate(GetRandomInt(UNIT_CAMP_COUNT_MIN, UNIT_CAMP_COUNT_MAX), Filter(function UnitCampsLocationFilter))
set c = null
endfunction
private function OnRevive takes nothing returns boolean
call ReviveHero(udg_DemoMap_Hero1, heroSpawnX[0], heroSpawnY[0], true)
call SetUnitFacingTimed(udg_DemoMap_Hero1, heroSpawnFacing[0], 0.00)
call ReviveHero(udg_DemoMap_Hero2, heroSpawnX[1], heroSpawnY[1], true)
call SetUnitFacingTimed(udg_DemoMap_Hero2, heroSpawnFacing[1], 0.00)
return false
endfunction
private function InitDemoHeroes takes nothing returns nothing
local trigger t = CreateTrigger()
call TriggerRegisterPlayerEvent(t, Player(0), EVENT_PLAYER_END_CINEMATIC)
call TriggerAddCondition(t, Filter(function OnRevive))
set heroSpawnX[0] = 0.00
set heroSpawnY[0] = 0.00
set heroSpawnFacing[0] = 90.00
set heroSpawnX[1] = 0.00
set heroSpawnY[1] = 400.00
set heroSpawnFacing[1] = 270.00
set udg_DemoMap_Hero1 = CreateUnit(Player(0), 'Hero', 0.00, 0.00, 90.00)
set udg_DemoMap_Hero2 = CreateUnit(Player(0), 'Demo', 0.00, 400, 270.00)
call SelectUnit(udg_DemoMap_Hero1, true)
call SetHeroLevel(udg_DemoMap_Hero1, 10, false)
call SetHeroLevel(udg_DemoMap_Hero2, 10, false)
endfunction
private function OnTimerExpire takes nothing returns nothing
if a == "A" then
call BJDebugMsg("|cffff0000[ERROR]:|r Terrain generator thread crashed!")
endif
if b == "B" then
call BJDebugMsg("|cffff0000[ERROR]:|r Destructables generator thread crashed!")
endif
if c == "C" then
call BJDebugMsg("|cffff0000[ERROR]:|r Unit camps generator thread crashed!")
endif
call BJDebugMsg("|cffffcc00If you don't like the randomness of the generated map, simply restart the game.|r\n")
call BJDebugMsg("Press [ESC] to cast a spell without actually using an ability.")
call DestroyTimer(GetExpiredTimer())
endfunction
private function OnInit takes nothing returns nothing
call TimerStart(CreateTimer(), 0, false, function OnTimerExpire)
call InitDemoHeroes()
call FogEnable(false)
call FogMaskEnable(false)
call PanCameraToTimed(heroSpawnX[0], heroSpawnY[0], 0.00)
call ForForce(bj_FORCE_PLAYER[0], function InitTerrain)
call ForForce(bj_FORCE_PLAYER[0], function InitUnitCamps)
call ForForce(bj_FORCE_PLAYER[0], function InitDestructables)
endfunction
endscope