scope DarkMatter
/*
~* Dark Matter *~
version 2.1
Created by: Dalvengyr aka Quilnez
Copyright 2015
This resource is copyright protected.
Don't redistribute without permission.
You are free to edit, use, remake, etc.
but don't remove the copyright notes
above.
*/
/* *\
Configuration
\* */
globals
/* I. General settings */
// Dummy unit's raw code at Object Editor
private constant integer DUMMY_OBJECT_ID = 'h000'
// Main spell's raw code at Object Editor
private constant integer SPELL_OBJECT_ID = 'A000'
// General decay time for special effects
private constant real SFX_DECAY_TIME = 5.0
// General units' hit z offset. Used for 3D collision
private constant real UNITS_HIT_Z_OFFSET = 50.0
/* II. Orb settings
(Orb is an object at the center of spell area)
*/
// Model's file path
private constant string ORB_SFX = "war3mapImported\\BlackHole.mdx"
// Flying height
private constant real ORB_HEIGHT_Z = 0.0
// Scale/size
private constant real ORB_SCALE = 1.0
// How bad range between victims and the orb can affect the absorb power
private constant real ORB_POWER_FACTOR = 0.375
/* III. Swirl settings
(Swirl consists of objects which swirl around the main orb)
*/
// Raw code at Object Editor
private constant integer SWIRL_WAVE_OBJECT_ID = 'h001'
// Animation speed
private constant real SWIRL_TIME_SCALE = 0.1
// Flying height
private constant real SWIRL_HEIGHT_Z = 100.0
// Scale/size
private constant real SWIRL_SCALE = 1.25
/* IV. Particle settings
(Particles are missiles that are periodically released by the orb)
*/
// Model file path
private constant string PARTICLE_SFX = "Abilities\\Weapons\\AvengerMissile\\AvengerMissile.mdl"
// Starting fly height when launched
private constant real PARTICLE_LAUNCH_Z = 100.0
// Scale/size
private constant real PARTICLE_SCALE = 0.2
// Turn rate
private constant real PARTICLE_TURN_SPEED = 0.1
// Max range between particle at target to hit the target
private constant real PARTICLE_HIT_RADIUS = 70.0
// If false, each particle can only hits one target
private constant boolean PARTICLE_PIERCING = false
// Dealt damage setting
private constant attacktype PARTICLE_ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype PARTICLE_DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
endglobals
/* V. Dynamic settings
(Allows you to set dynamic values based on parameters)
*/
// Number of created wave to swirl around the orb
private constant function SwirlCount takes integer level returns integer
return 9
endfunction
// Range/distance from the orb to the created waves
private constant function SwirlRadius takes integer level returns real
return 125.0
endfunction
// Rotation speed of waves, in radians
private constant function SwirlRotation takes integer level returns real
return 0.09
endfunction
// Move speed of each particle
private constant function ParticleSpeed takes integer level returns real
return 5.0
endfunction
// Minimum delay before the orb releases another particle
private constant function ParticleSpawnDelayMin takes integer level returns real
return 0.0
endfunction
// Maximum delay before the orb releases another particle
private constant function ParticleSpawnDelayMax takes integer level returns real
return 0.5
endfunction
// The absorbtion power
private constant function OrbPower takes integer level returns real
return 15.0
endfunction
// Dealt damage when particle hits a target
private constant function Damage takes integer level returns real
return 90.0
endfunction
// Maximum range between the orb and targets to absorb them
private constant function AoE takes integer level returns real
return 400.0
endfunction
// Duration of the spell
private constant function Duration takes integer level returns real
return 15.0
endfunction
// Maximum missile count launched to each target
private constant function MaxCount takes integer level returns integer
return 99
endfunction
// Classifications of targets that can be affected by this spell.
private constant function SpellTargets takes unit target, player caster returns boolean
return IsUnitEnemy(target, caster) and not IsUnitType(target, UNIT_TYPE_STRUCTURE)
endfunction
// You can add any additional effects to the target here
// This function is executed right before the damage is dealt to
// the target
private module DarkMatterDamageEvent
static method onDamage takes unit caster, unit target returns nothing
endmethod
endmodule
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
globals
private constant real INTERVAL = 0.0312500
private location Loc = Location(0, 0)
private group Group = CreateGroup()
private unit Checker = null
endglobals
// Native declaration
native UnitAlive takes unit id returns boolean
// Reset ground unit's position without interrupting orders
private function UnstuckUnit takes unit whichUnit returns nothing
if IsUnitType(whichUnit, UNIT_TYPE_GROUND) then
call SetUnitPosition(Checker, GetUnitX(whichUnit), GetUnitY(whichUnit))
call SetUnitX(whichUnit, GetUnitX(Checker))
call SetUnitY(whichUnit, GetUnitY(Checker))
endif
endfunction
private function GetLocZ takes real x, real y returns real
call MoveLocation(Loc, x, y)
return GetLocationZ(Loc)
endfunction
private function SetUnitZ takes unit u, real z returns nothing
call SetUnitFlyHeight(u, z - GetLocZ(GetUnitX(u), GetUnitY(u)), 0)
endfunction
// Stack used to contain waves
private struct Swirl extends array
unit unit
implement Stack
endstruct
private struct Tornado extends array
Swirl swirl
integer count
real distance
real angle
real rotation
real space
real x
real y
real z
static constant real TAU = 2*bj_PI
method terminate takes nothing returns nothing
local Swirl node = .swirl.first
// Kill all waves
loop
exitwhen node == 0
call KillUnit(node.unit)
set node.unit = null
set node = node.next
endloop
call .swirl.destroy()
call destroy()
endmethod
implement CTL
local real a
local real x2
local real y2
local integer i
local Swirl node
implement CTLExpire
set i = .count
set node = .swirl.first
set .angle = .angle + .rotation
// Iterate update nodes' angles/rotations
loop
exitwhen i <= 0
set a = .angle+.space*i
set x2 = .x+.distance*Cos(a)
set y2 = .y+.distance*Sin(a)
call SetUnitX(node.unit, x2)
call SetUnitY(node.unit, y2)
call SetUnitFacing(node.unit, a*bj_RADTODEG)
call SetUnitZ(node.unit, .z + SWIRL_HEIGHT_Z)
set node = node.next
set i = i - 1
endloop
implement CTLEnd
static method spawn takes player p, integer count, real x, real y, real dist, real rot returns thistype
local thistype this = create()
local integer i = 0
local real a
local real x2
local real y2
local Swirl node
// Allocate stack for waves
set .swirl = Swirl.create()
// Angle difference between each wave
set .space = TAU/count
set .angle = GetRandomReal(-bj_PI, bj_PI)
set .distance = dist
set .rotation = rot
set .count = count
set .x = x
set .y = y
set .z = GetLocZ(x, y) + ORB_HEIGHT_Z
// Create the waves and store into the stack
loop
exitwhen i >= count
// Allocate new node to the stack
set node = .swirl.push()
set a = .angle+.space*i
set x2 = x+dist*Cos(a)
set y2 = y+dist*Sin(a)
set node.unit = CreateUnit(p, SWIRL_WAVE_OBJECT_ID, x2, y2, a*bj_RADTODEG)
call SetUnitScale(node.unit, SWIRL_SCALE, 1, 1)
call SetUnitTimeScale(node.unit, SWIRL_TIME_SCALE)
if SWIRL_HEIGHT_Z > 0 then
static if not LIBRARY_AutoFly then
if UnitAddAbility(node.unit, 'Amrf') and UnitRemoveAbility(node.unit, 'Amrf') then
endif
endif
call SetUnitZ(node.unit, .z + SWIRL_HEIGHT_Z)
endif
set i = i + 1
endloop
return this
endmethod
endstruct
private keyword DarkMatter
// Launch 3D homing missile
private struct SimpleMissile extends array
effect sfx
player owner
group targets
unit caster
unit target
unit missile
real x
real y
real z
real dc
real dx
real damage
real turn
real angle
real speed
method terminate takes nothing returns nothing
// Group is only created if piercing is true
static if PARTICLE_PIERCING then
call DestroyGroup(.targets)
set .targets = null
endif
call DestroyEffect(.sfx)
call UnitApplyTimedLife(.missile, 'BTLF', SFX_DECAY_TIME)
call destroy()
// Remove leaks
set .missile = null
set .target = null
set .caster = null
set .sfx = null
endmethod
implement optional DarkMatterDamageEvent
implement CTL
local real a
local real v
local real h
local real d
local real x
local real y
local unit u
local unit t
local integer rand
implement CTLExpire
set .dc = .dc + .speed
set .x = .x + .speed * Cos(.angle)
set .y = .y + .speed * Sin(.angle)
// Parabola equation
set v = 4 * (.dx-.dc) * (.dc/.dx) + PARTICLE_LAUNCH_Z
// If not supposed to dead yet
if v > 0 and .x < WorldBounds.maxX and .x > WorldBounds.minX and .y < WorldBounds.maxY and .y > WorldBounds.minY and UnitAlive(.caster) then
call SetUnitX(.missile, .x)
call SetUnitY(.missile, .y)
call SetUnitFlyHeight(.missile, v, 0)
// Adjust the angle to face the target
set a = Atan2(GetUnitY(.target)-.y, GetUnitX(.target)-.x)
if Cos(.angle - a) < Cos(.turn) then
if Sin(a - .angle) >= 0 then
set .angle = .angle + .turn
else
set .angle = .angle - .turn
endif
else
set .angle = a
endif
// Determine one random target from all possible targets
static if not PARTICLE_PIERCING then
set t = null
set rand = 0
endif
// Iterates all units around the missile
call GroupEnumUnitsInRange(Group, .x, .y, PARTICLE_HIT_RADIUS, null)
loop
set u = FirstOfGroup(Group)
exitwhen u == null
call GroupRemoveUnit(Group, u)
if UnitAlive(u) and SpellTargets(u, owner) then
set x = GetUnitX(u)
set y = GetUnitY(u)
set h = GetUnitFlyHeight(u) + UNITS_HIT_Z_OFFSET - v
set d = SquareRoot((.x-x)*(.x-x)+(.y-y)*(.y-y))
// 3D spherical collision
if SquareRoot(d*d+h*h) <= PARTICLE_HIT_RADIUS then
static if PARTICLE_PIERCING then
if not IsUnitInGroup(u, .targets) then
static if thistype.onDamage.exists then
call thistype.onDamage(.caster, u)
endif
call UnitDamageTarget(.caster, u, .damage, false, false, PARTICLE_ATTACK_TYPE, PARTICLE_DAMAGE_TYPE, null)
call GroupAddUnit(.targets, u)
endif
else
// Quite effective to obtain random unit from a group
set rand = rand + 1
if GetRandomInt(1, rand) == 1 then
set t = u
endif
endif
endif
endif
endloop
static if not PARTICLE_PIERCING then
// If a target is found
if t != null then
static if thistype.onDamage.exists then
call thistype.onDamage(.caster, t)
endif
call UnitDamageTarget(.caster, t, .damage, false, false, PARTICLE_ATTACK_TYPE, PARTICLE_DAMAGE_TYPE, null)
call terminate()
set t = null
endif
endif
else
call terminate()
endif
implement CTLEnd
static method launch takes DarkMatter source, unit target returns thistype
local thistype this = create()
local real x = GetUnitX(target)
local real y = GetUnitY(target)
set .target = target
set .caster = source.caster
set .owner = source.owner
// Launch from random location nearby the orb
set .x = source.x + GetRandomReal(-source.tornado.distance, source.tornado.distance)
set .y = source.y + GetRandomReal(-source.tornado.distance, source.tornado.distance)
set .angle = GetRandomReal(-bj_PI, bj_PI)
set .missile = CreateUnit(.owner, DUMMY_OBJECT_ID, .x, .y, .angle*bj_RADTODEG)
set .sfx = AddSpecialEffectTarget(PARTICLE_SFX, .missile, "origin")
set .turn = PARTICLE_TURN_SPEED
set .damage = source.damage
set .speed = source.speed
set .dx = source.aoe
set .dc = 0
// Create group if only piercing is true
static if PARTICLE_PIERCING then
set .targets = CreateGroup()
endif
static if not LIBRARY_AutoFly then
if UnitAddAbility(.missile, 'Amrf') and UnitRemoveAbility(.missile, 'Amrf') then
endif
endif
call SetUnitFlyHeight(.missile, PARTICLE_LAUNCH_Z, 0)
call SetUnitScale(.missile, PARTICLE_SCALE, 1, 1)
return this
endmethod
endstruct
// Main spell's struct
private struct DarkMatter extends array
unit orb
unit caster
group targets
Tornado tornado
integer hitx
player owner
effect sfx
real aspd
real aspdo
real aspdx
real speed
real damage
real power
real aoe
real duration
real x
real y
real delay
real delayx
real rotation
real amount
static if LIBRARY_Table then
static TableArray Ht
else
static hashtable Ht
endif
static constant real HP = bj_PI/2
method terminate takes nothing returns nothing
local unit u
// Turn all remaining units' collision back to on
loop
set u = FirstOfGroup(.targets)
exitwhen u == null
call GroupRemoveUnit(.targets, u)
call SetUnitPathing(u, true)
call UnstuckUnit(u)
static if LIBRARY_Table then
call Ht[this].integer.remove(GetHandleId(u))
else
call RemoveSavedInteger(Ht, GetHandleId(u), 0)
endif
endloop
call .tornado.terminate()
call DestroyEffect(.sfx)
call DestroyGroup(.targets)
call UnitApplyTimedLife(.orb, 'BTLF', SFX_DECAY_TIME)
call destroy()
// Remove leaks
set .targets = null
set .caster = null
set .orb = null
set .sfx = null
endmethod
implement CTL
local group g
local boolean b
local integer hand
local integer rand = 0
local unit t = null
local unit u
local real d
local real x
local real y
local real h
local real a
local real pow
implement CTLExpire
if .duration > INTERVAL and UnitAlive(.caster) then
set .duration = .duration - INTERVAL
// Iterate through all units around the orb (within aoe).
call GroupEnumUnitsInRange(Group, .x, .y, .aoe, null)
loop
set u = FirstOfGroup(Group)
exitwhen u == null
call GroupRemoveUnit(Group, u)
if UnitAlive(u) and SpellTargets(u, owner) then
set x = GetUnitX(u)
set y = GetUnitY(u)
set d = SquareRoot((.x-x)*(.x-x)+(.y-y)*(.y-y))
// Power is affected by distance between orb and the target.
set pow = .power - .power*(d/.aoe)*ORB_POWER_FACTOR
// Calculate target angle.
set a = Atan2(.y-y, .x-x)-(HP*(d/.aoe))*.rotation
if not IsUnitInGroup(u, .targets) then
call GroupAddUnit(.targets, u)
call SetUnitPathing(u, false)
endif
if .aspd <= INTERVAL then
static if LIBRARY_Table then
set b = Ht[this].integer[GetHandleId(u)] < .hitx
else
set b = LoadInteger(Ht, this, GetHandleId(u)) < .hitx
endif
if b then
set rand = rand + 1
if GetRandomInt(1, rand) == 1 then
set t = u
endif
endif
endif
if d > pow then
call SetUnitX(u, x + pow * Cos(a))
call SetUnitY(u, y + pow * Sin(a))
else
call SetUnitX(u, .x)
call SetUnitY(u, .y)
endif
endif
endloop
// Iterates all affected units which have
// escaped the spell area and turn their
// collision on.
set g = .targets
set .targets = CreateGroup()
loop
set u = FirstOfGroup(g)
exitwhen u == null
call GroupRemoveUnit(g, u)
set x = GetUnitX(u)
set y = GetUnitY(u)
if SquareRoot((.x-x)*(.x-x)+(.y-y)*(.y-y)) > .aoe then
call SetUnitPathing(u, true)
call UnstuckUnit(u)
static if LIBRARY_Table then
call Ht[this].integer.remove(GetHandleId(u))
else
call RemoveSavedInteger(Ht, this, GetHandleId(u))
endif
else
call GroupAddUnit(.targets, u)
endif
endloop
if .aspd > INTERVAL then
set .aspd = .aspd - INTERVAL
elseif t != null then
// Launch missile if a random target is found
set .aspd = GetRandomReal(.aspdo, .aspdx)
call SimpleMissile.launch(this, t)
set hand = GetHandleId(t)
static if LIBRARY_Table then
set Ht[this].integer[hand] = Ht[this].integer[hand] + 1
else
call SaveInteger(Ht, this, hand, LoadInteger(Ht, this, hand) + 1)
endif
set t = null
endif
// Remove leaks
call DestroyGroup(g)
set g = null
else
call terminate()
endif
implement CTLEnd
static method onCast takes nothing returns nothing
local thistype this = create()
local integer l
set .caster = GetTriggerUnit()
set .owner = GetTriggerPlayer()
set .x = GetSpellTargetX()
set .y = GetSpellTargetY()
set .targets = CreateGroup()
set l = GetUnitAbilityLevel(.caster, SPELL_OBJECT_ID)
set .orb = CreateUnit(.owner, DUMMY_OBJECT_ID, .x, .y, 270)
set .sfx = AddSpecialEffectTarget(ORB_SFX, .orb, "origin")
set .duration = Duration(l)
set .power = OrbPower(l)
set .aoe = AoE(l)
set .rotation = SwirlRotation(l)
set .tornado = Tornado.spawn(.owner, SwirlCount(l), .x, .y, SwirlRadius(l), .rotation)
// Get the rotation direction
set .rotation = .rotation/RAbsBJ(.rotation)
set .speed = ParticleSpeed(l)
set .damage = Damage(l)
set .hitx = MaxCount(l)
set .aspdx = ParticleSpawnDelayMax(l)
set .aspdo = ParticleSpawnDelayMin(l)
set .aspd = GetRandomReal(.aspdo, .aspdx)
call SetUnitScale(.orb, ORB_SCALE, 1, 1)
if ORB_HEIGHT_Z > 0 then
static if not LIBRARY_AutoFly then
if UnitAddAbility(.orb, 'Amrf') and UnitRemoveAbility(.orb, 'Amrf') then
endif
endif
call SetUnitFlyHeight(.orb, ORB_HEIGHT_Z, 0)
endif
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method check takes nothing returns boolean
if GetSpellAbilityId() == SPELL_OBJECT_ID then
call thistype.onCast()
endif
return false
endmethod
endif
static method onInit takes nothing returns nothing
local trigger t
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(SPELL_OBJECT_ID, function thistype.onCast)
else
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.check))
endif
static if LIBRARY_Table then
set Ht = TableArray[0x2000]
else
set Ht = InitHashtable()
endif
set Checker = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), 'uabo', 0, 0, 0)
call PauseUnit(Checker, true)
call ShowUnit(Checker, false)
endmethod
endstruct
endscope