- Joined
- Feb 9, 2009
- Messages
- 1,800
Wow I had fun with that point defense one!
![]() | Moonerang | Launch a boulder at target area. Upon reaching the area the boulder ignites and will track the mouse cursor for a short duration. Units hit by the initial shot are stunned, and those hit by the tracking shot have their movement and attack speeds slowed. Each impact deals damage, and units can only be affected by each phase once. |
![]() | Point Defense | Spawn defense drones that encircle the caster. Drones launch themselves to impact at the mouse cursor location periodically. Each drone can travel a maximum distance, and those closest to the target are launched first. Enemies in the impact radius take damage per drone and are briefly blinded, causing them to miss most of their attacks. |
library MouseUtils
/*
-------------------
MouseUtils
- MyPad
1.0.2.2
-------------------
----------------------------------------------------------------------------
A simple snippet that allows one to
conveniently use the mouse natives
as they were meant to be...
-------------------
| API |
-------------------
struct UserMouse extends array
static method operator [] (player p) -> thistype
- Returns the player's id + 1
static method getCurEventType() -> integer
- Returns the custom event that got executed.
method operator player -> player
- Returns Player(this - 1)
readonly real mouseX
readonly real mouseY
- Returns the current mouse coordinates.
readonly method operator isMouseClicked -> boolean
- Determines whether any mouse key has been clicked,
and will return true on the first mouse key.
method isMouseButtonClicked(mousebuttontype mouseButton)
- Returns true if the mouse button hasn't been
released yet.
method setMousePos(real x, y) (introduced in 1.0.2.2)
- Sets the mouse position for a given player.
static method registerCode(code c, integer ev) -> triggercondition
- Lets code run upon the execution of a certain event.
- Returns a triggercondition that can be removed later.
static method unregisterCallback(triggercondition trgHndl, integer ev)
- Removes a generated triggercondition from the trigger.
functions:
GetPlayerMouseX(player p) -> real
GetPlayerMouseY(player p) -> real
- Returns the coordinates of the mouse of the player.
OnMouseEvent(code func, integer eventId) -> triggercondition
- See UserMouse.registerCode
GetMouseEventType() -> integer
- See UserMouse.getCurEventType
UnregisterMouseCallback(triggercondition t, integer eventId)
- See UserMouse.unregisterCallback
SetUserMousePos(player p, real x, real y)
- See UserMouse.setMousePos
Unique Global Constants:
IMPL_LOCK (Introduced in v.1.0.2.2)
- Enables or disables the lock option
-------------------
| Credits |
-------------------
- Pyrogasm for pointing out a comparison logic flaw
in operator isMouseClicked.
- Illidan(Evil)X for the useful enum handles that
grant more functionality to this snippet.
- TriggerHappy for the suggestion to include
associated events and callbacks to this snippet.
- Quilnez for pointing out a bug related to the
method isMouseButtonClicked not working as intended
in certain situations.
----------------------------------------------------------------------------
*/
// Arbitrary constants
globals
constant integer EVENT_MOUSE_UP = 1024
constant integer EVENT_MOUSE_DOWN = 2048
constant integer EVENT_MOUSE_MOVE = 3072
private constant boolean IMPL_LOCK = false
endglobals
private module Init
private static method onInit takes nothing returns nothing
call thistype.init()
endmethod
endmodule
struct UserMouse extends array
static if IMPL_LOCK then
// Determines the minimum interval that a mouse move event detector
// will be deactivated. (Globally-based)
// You can configure it to any amount you like.
private static constant real INTERVAL = 0.031250000
// Determines how many times a mouse move event detector can fire
// before being deactivated. (locally-based)
// You can configure this to any integer value. (Preferably positive)
private static constant integer MOUSE_COUNT_MAX = 16
// Determines the amount to be deducted from mouseEventCount
// per INTERVAL. Runs independently of resetTimer
private static constant integer MOUSE_COUNT_LOSS = 8
private static constant boolean IS_INSTANT = INTERVAL <= 0.
endif
private static integer currentEventType = 0
private static integer updateCount = 0
private static trigger stateDetector = null
static if IMPL_LOCK and not IS_INSTANT then
private static timer resetTimer = null
endif
private static trigger array evTrigger
private static integer array mouseButtonStack
static if IMPL_LOCK and not IS_INSTANT then
private integer mouseEventCount
private timer mouseEventReductor
endif
private thistype next
private thistype prev
private thistype resetNext
private thistype resetPrev
private trigger posDetector
private integer mouseClickCount
readonly real mouseX
readonly real mouseY
// Converts the enum type mousebuttontype into an integer
private static method toIndex takes mousebuttontype mouseButton returns integer
return GetHandleId(mouseButton)
endmethod
static method getCurEventType takes nothing returns integer
return currentEventType
endmethod
static method operator [] takes player p returns thistype
if thistype(GetPlayerId(p) + 1).posDetector != null then
return GetPlayerId(p) + 1
endif
return 0
endmethod
method operator player takes nothing returns player
return Player(this - 1)
endmethod
method operator isMouseClicked takes nothing returns boolean
return .mouseClickCount > 0
endmethod
method isMouseButtonClicked takes mousebuttontype mouseButton returns boolean
return UserMouse.mouseButtonStack[(this - 1)*3 + UserMouse.toIndex(mouseButton)] > 0
endmethod
method setMousePos takes integer x, integer y returns nothing
if GetLocalPlayer() == this.player then
call BlzSetMousePos(x, y)
endif
endmethod
static if IMPL_LOCK then
private static method getMouseEventReductor takes timer t returns thistype
local thistype this = thistype(0).next
loop
exitwhen this.mouseEventReductor == t or this == 0
set this = this.next
endloop
return this
endmethod
private static method onMouseUpdateListener takes nothing returns nothing
local thistype this = thistype(0).resetNext
set updateCount = 0
loop
exitwhen this == 0
set updateCount = updateCount + 1
set this.mouseEventCount = 0
call EnableTrigger(this.posDetector)
set this.resetNext.resetPrev = this.resetPrev
set this.resetPrev.resetNext = this.resetNext
set this = this.resetNext
endloop
if updateCount > 0 then
static if not IS_INSTANT then
call TimerStart(resetTimer, INTERVAL, false, function thistype.onMouseUpdateListener)
else
call onMouseUpdateListener()
endif
else
static if not IS_INSTANT then
call TimerStart(resetTimer, 0.00, false, null)
call PauseTimer(resetTimer)
endif
endif
endmethod
private static method onMouseReductListener takes nothing returns nothing
local thistype this = getMouseEventReductor(GetExpiredTimer())
if this.mouseEventCount <= 0 then
call PauseTimer(this.mouseEventReductor)
else
set this.mouseEventCount = IMaxBJ(this.mouseEventCount - MOUSE_COUNT_LOSS, 0)
call TimerStart(this.mouseEventReductor, INTERVAL, false, function thistype.onMouseReductListener)
endif
endmethod
endif
private static method onMouseUpOrDown takes nothing returns nothing
local thistype this = thistype[GetTriggerPlayer()]
local integer index = (this - 1)*3 + UserMouse.toIndex(BlzGetTriggerPlayerMouseButton())
local boolean releaseFlag = false
if GetTriggerEventId() == EVENT_PLAYER_MOUSE_DOWN then
set this.mouseClickCount = IMinBJ(this.mouseClickCount + 1, 3)
set releaseFlag = UserMouse.mouseButtonStack[index] <= 0
set UserMouse.mouseButtonStack[index] = IMinBJ(UserMouse.mouseButtonStack[index] + 1, 1)
if releaseFlag then
set currentEventType = EVENT_MOUSE_DOWN
call TriggerEvaluate(evTrigger[EVENT_MOUSE_DOWN])
endif
else
set this.mouseClickCount = IMaxBJ(this.mouseClickCount - 1, 0)
set releaseFlag = UserMouse.mouseButtonStack[index] > 0
set UserMouse.mouseButtonStack[index] = IMaxBJ(UserMouse.mouseButtonStack[index] - 1, 0)
if releaseFlag then
set currentEventType = EVENT_MOUSE_UP
call TriggerEvaluate(evTrigger[EVENT_MOUSE_UP])
endif
endif
endmethod
private static method onMouseMove takes nothing returns nothing
local thistype this = thistype[GetTriggerPlayer()]
local boolean started = false
set this.mouseX = BlzGetTriggerPlayerMouseX()
set this.mouseY = BlzGetTriggerPlayerMouseY()
static if IMPL_LOCK then
set this.mouseEventCount = this.mouseEventCount + 1
if this.mouseEventCount <= 1 then
call TimerStart(this.mouseEventReductor, INTERVAL, false, function thistype.onMouseReductListener)
endif
endif
set currentEventType = EVENT_MOUSE_MOVE
call TriggerEvaluate(evTrigger[EVENT_MOUSE_MOVE])
static if IMPL_LOCK then
if this.mouseEventCount >= thistype.MOUSE_COUNT_MAX then
call DisableTrigger(this.posDetector)
if thistype(0).resetNext == 0 then
static if not IS_INSTANT then
call TimerStart(resetTimer, INTERVAL, false, function thistype.onMouseUpdateListener)
// Mouse event reductor should be paused
else
set started = true
endif
call PauseTimer(this.mouseEventReductor)
endif
set this.resetNext = 0
set this.resetPrev = this.resetNext.resetPrev
set this.resetPrev.resetNext = this
set this.resetNext.resetPrev = this
if started then
call onMouseUpdateListener()
endif
endif
endif
endmethod
private static method init takes nothing returns nothing
local thistype this = 1
local player p = this.player
static if IMPL_LOCK and not IS_INSTANT then
set resetTimer = CreateTimer()
endif
set stateDetector = CreateTrigger()
set evTrigger[EVENT_MOUSE_UP] = CreateTrigger()
set evTrigger[EVENT_MOUSE_DOWN] = CreateTrigger()
set evTrigger[EVENT_MOUSE_MOVE] = CreateTrigger()
call TriggerAddCondition( stateDetector, Condition(function thistype.onMouseUpOrDown))
loop
exitwhen integer(this) > bj_MAX_PLAYER_SLOTS
if GetPlayerController(p) == MAP_CONTROL_USER and GetPlayerSlotState(p) == PLAYER_SLOT_STATE_PLAYING then
set this.next = 0
set this.prev = thistype(0).prev
set thistype(0).prev.next = this
set thistype(0).prev = this
set this.posDetector = CreateTrigger()
static if IMPL_LOCK and not IS_INSTANT then
set this.mouseEventReductor = CreateTimer()
endif
call TriggerRegisterPlayerEvent( this.posDetector, p, EVENT_PLAYER_MOUSE_MOVE )
call TriggerAddCondition( this.posDetector, Condition(function thistype.onMouseMove))
call TriggerRegisterPlayerEvent( stateDetector, p, EVENT_PLAYER_MOUSE_UP )
call TriggerRegisterPlayerEvent( stateDetector, p, EVENT_PLAYER_MOUSE_DOWN )
endif
set this = this + 1
set p = this.player
endloop
endmethod
static method registerCode takes code handlerFunc, integer eventId returns triggercondition
return TriggerAddCondition(evTrigger[eventId], Condition(handlerFunc))
endmethod
static method unregisterCallback takes triggercondition whichHandler, integer eventId returns nothing
call TriggerRemoveCondition(evTrigger[eventId], whichHandler)
endmethod
implement Init
endstruct
function GetPlayerMouseX takes player p returns real
return UserMouse.mouseX
endfunction
function GetPlayerMouseY takes player p returns real
return UserMouse.mouseY
endfunction
function OnMouseEvent takes code func, integer eventId returns triggercondition
return UserMouse.registerCode(func, eventId)
endfunction
function GetMouseEventType takes nothing returns integer
return UserMouse.getCurEventType()
endfunction
function UnregisterMouseCallback takes triggercondition whichHandler, integer eventId returns nothing
call UserMouse.unregisterCallback(whichHandler, eventId)
endfunction
function SetUserMousePos takes player p, integer x, integer y returns nothing
call UserMouse.setMousePos(x, y)
endfunction
endlibrary
library RemoveEffect
//Destroying an effect always plays its death animation and any associated sounds.
//This library fixes it in a simple way: move the effect to a location where nobody can see or hear it.
//By Pyrogasm, additional help by Bribe
//v1.1
globals
private real SAFE_X = -3900.
private real SAFE_Y = 3900.
private real SAFE_Z = -1000.
private real TIMESCALE = 10. //doesn't really matter what this is but we set it to > 0 so the animations actually finish
endglobals
function RemoveEffect takes effect e returns nothing
call BlzSetSpecialEffectAlpha(e, 255)
call BlzSetSpecialEffectZ(e, SAFE_Z)
call BlzSetSpecialEffectPosition(e, SAFE_X, SAFE_Y, SAFE_Z)
call BlzSetSpecialEffectTimeScale(e, TIMESCALE)
call DestroyEffect(e)
endfunction
endlibrary
library LocalZ
//Can be substituted with any other library that does the same thing
//or a Blz native if Blizzard adds something like that
globals
private location l = Location(0.,0.)
endglobals
function GetLocalZ takes real x, real y returns real
call MoveLocation(l, x, y)
return GetLocationZ(l)
endfunction
endlibrary
library Moonerang requires MouseUtils, RemoveEffect, LocalZ
//=============================================================================================================================
// Moonerang v1.2
// by Pyrogasm
//
// Launch a boulder at target area. Upon reaching the area the boulder ignites and will track the mouse cursor for a short duration.
//
// Spell behavior is highly configurable, but by default:
// Units hit by the initial shot are stunned, and those hit by the tracking shot have their movement and attack speeds slowed.
// Each impact deals damage, and units can only be affected by each phase once.
//
// Dependencies:
// - MouseUtils by MyPad: https://www.hiveworkshop.com/threads/snippet-mouse-utility.310523/
// - RemoveEffect by Pyrogasm: https://www.hiveworkshop.com/threads/small-code-snippets.40758/page-45#post-3361522
// - LocalZ - any library that contains a function to get the Z height of X/Y coordinates
//=============================================================================================================================
globals
//Configuration globals, muck with these as much as you want
public constant integer SPELL_ID = 'Moon'
public constant integer DUMMY_ID = 'dmmy'
private constant real TIMER_TICK = 0.03
private constant real MAX_COLLISION = 128.00 //this value is added to GroupEnum calls before checking with IsUnitInRangeXY() so collision is properly accounted for
//set this to the max collision size a unit has in your map
//For all effect variables:
//1A = primary pre-flip
//1B = primary post-flip
//2A = secondary pre-flip
//2B = secondary post-flip
private constant real EFFECT_H = 125.
private constant string EFFECT_1A = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl"
private constant string EFFECT_1B = "Abilities\\Spells\\Other\\Volcano\\VolcanoMissile.mdl"
private constant string EFFECT_2A = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareTarget.mdl"
private constant string EFFECT_2B = "Abilities\\Spells\\Human\\ManaFlare\\ManaFlareTarget.mdl"
private constant real E1A_SCALE = 1.5
private constant real E1B_SCALE = 1.5
private constant real E2A_SCALE = 1.5
private constant real E2B_SCALE = 1.5
private constant real E1A_TSCALE = 0. //timescale, aka animation speed
private constant real E1B_TSCALE = 0.
private constant real E2A_TSCALE = 0.
private constant real E2B_TSCALE = 0.
private constant integer E1A_COL_R = 255
private constant integer E1A_COL_G = 255
private constant integer E1A_COL_B = 255
private constant integer E1B_COL_R = 255
private constant integer E1B_COL_G = 255
private constant integer E1B_COL_B = 255
private constant integer E2A_COL_R = 255
private constant integer E2A_COL_G = 255
private constant integer E2A_COL_B = 255
private constant integer E2B_COL_R = 255
private constant integer E2B_COL_G = 255
private constant integer E2B_COL_B = 255
private constant real ORBIT_PERIOD = 1.5 //this is only used in the line below
private constant real ORBIT_ANGLE_INC = -2*bj_PI/ORBIT_PERIOD*TIMER_TICK
private constant real ORBIT_OFFSET = 70.
private constant real LAUNCH_DECEL = 900.
private constant real LAUNCH_ENDVEL = 250.
private constant real FOLLOW_DURATION = 1.5
private constant real FOLLOW_ACCEL = 2400.
private constant real FOLLOW_MAX_VEL = 800.
endglobals
globals
//Spell globals, probably don't change these but you can reference the public ones
public keyword SpellData
private SpellData array GSD
private integer COUNT = 0
private integer CURR = 0
public timer SpellTimer = CreateTimer()
public trigger CastTrigger = CreateTrigger()
endglobals
//Need this for checks in the spellBehavior module
native UnitAlive takes unit u returns boolean
public module spellBehavior
// the following member variables are defined in the struct below for you to use
// they do not need to be destroyed or removed, that is handled already:
//
// unit DUMMY <-- can cast instantly and is moved to (.cx, .cy) every timer step
// add abilities to it with .prepDummy(abilCode) (level is set automatically)
// group HIT_GROUP <-- units already hit by this instance of the spell, you are free to clear this as necessary, but you must add units manually
// static group SEARCH_GROUP <-- a group you can use to search for targets via GroupEnumUnitsIn...
// integer l <-- level of the spell when it was cast
// unit c <-- caster
// player p <-- owner of caster
// real cx, cy, cz <-- aboslute coordinates of the center of the boulder
//
// look at the SpellData struct below for additional members if you want more information
// you may end an insance early by calling .endInstance()
// you may add your own instance or static members here:
static integer CRIPPLE_ABIL = 'Mcpl'
static string CRIPPLE_ORDER = "cripple"
static integer STUN_ABIL = 'Mstn'
static string STUN_ORDER = "firebolt"
static real RADIUS = 70.
static real DMG = 60.
static method onInitEx takes nothing returns nothing
//called on map init, so you can set up anything you need to here (most likely arrays or some global dummies)
endmethod
method onStart takes nothing returns nothing
//called when the spell starts
call this.prepDummy(STUN_ABIL)
call this.prepDummy(CRIPPLE_ABIL)
endmethod
method onPeriodic takes boolean afterFlip returns nothing
local unit u
call GroupEnumUnitsInRange(SEARCH_GROUP, this.cx, this.cy, RADIUS+MAX_COLLISION, null)
loop
set u = FirstOfGroup(SEARCH_GROUP)
exitwhen u == null
call GroupRemoveUnit(SEARCH_GROUP, u)
if IsUnitInRangeXY(u, this.cx, this.cy, RADIUS) and /*
*/ IsUnitEnemy(u, this.p) and UnitAlive(u) and /*
*/ not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ not BlzIsUnitInvulnerable(u) and /*
*/ not IsUnitInGroup(u, this.HIT_GROUP) then
if afterFlip then
call UnitDamageTarget(this.c, u, DMG, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FIRE, WEAPON_TYPE_WHOKNOWS)
call IssueTargetOrder(this.DUMMY, CRIPPLE_ORDER, u)
else
call UnitDamageTarget(this.c, u, DMG, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FORCE, WEAPON_TYPE_WHOKNOWS)
call IssueTargetOrder(this.DUMMY, STUN_ORDER, u)
endif
call GroupAddUnit(this.HIT_GROUP, u)
endif
endloop
set u = null
endmethod
method onFlip takes nothing returns nothing
//called when the boulder switches from linear movement to mouse cursor tracking movement
call GroupClear(this.HIT_GROUP)
endmethod
method onEnd takes nothing returns nothing
//called when the spell ends
endmethod
endmodule
public struct SpellData
effect e1 = null
effect e2 = null
unit c = null
player p = null
integer l = 0
real vx = 0.
real vy = 0.
real ax = 0.
real ay = 0.
real tx = 0.
real ty = 0.
real cx = 0.
real cy = 0.
real cz = 0.
real a = 0.
real e2a = 0.
integer d = 0
boolean b = false
unit DUMMY = null
group HIT_GROUP = CreateGroup()
static group SEARCH_GROUP = CreateGroup()
method prepDummy takes integer abil returns nothing
call UnitAddAbility(this.DUMMY, abil)
call SetUnitAbilityLevel(this.DUMMY, abil, this.l)
endmethod
implement spellBehavior
method endInstance takes nothing returns nothing
call this.onEnd()
call RemoveUnit(this.DUMMY)
call DestroyGroup(this.HIT_GROUP)
call BlzSetSpecialEffectTimeScale(this.e1, 1.)
call BlzSetSpecialEffectTimeScale(this.e2, 1.)
call DestroyEffect(this.e1)
call DestroyEffect(this.e2)
set GSD[CURR] = GSD[COUNT-1]
set COUNT = COUNT-1
set CURR = CURR-1
if COUNT == 0 then
call PauseTimer(SpellTimer)
endif
call this.destroy()
endmethod
static method periodic takes nothing returns nothing
local SpellData sd
set CURR = 0
loop
exitwhen CURR >= COUNT
set sd = GSD[CURR]
set sd.e2a = sd.e2a + ORBIT_ANGLE_INC
call BlzSetSpecialEffectYaw(sd.e1, sd.e2a)
call BlzSetSpecialEffectYaw(sd.e2, sd.e2a+bj_PI)
if not sd.b then
set sd.cx = sd.cx + sd.vx*TIMER_TICK
set sd.cy = sd.cy + sd.vy*TIMER_TICK
set sd.cz = GetLocalZ(sd.cx, sd.cy)+EFFECT_H
set sd.vx = sd.vx + sd.ax*TIMER_TICK
set sd.vy = sd.vy + sd.ay*TIMER_TICK
call SetUnitX(sd.DUMMY, sd.cx)
call SetUnitY(sd.DUMMY, sd.cy)
call sd.onPeriodic(false)
set sd.d = sd.d-1
if sd.d <= 0 then
set sd.b = true
set sd.d = R2I(FOLLOW_DURATION/TIMER_TICK)
call RemoveEffect(sd.e1)
call RemoveEffect(sd.e2)
set sd.e1 = AddSpecialEffect(EFFECT_1B, sd.cx, sd.cy)
call BlzSetSpecialEffectZ(sd.e1, sd.cz)
call BlzSetSpecialEffectScale(sd.e1, E1B_SCALE)
call BlzSetSpecialEffectTimeScale(sd.e1, E1B_TSCALE)
call BlzSetSpecialEffectYaw(sd.e1, sd.e2a)
call BlzSetSpecialEffectColor(sd.e1, E1B_COL_R, E1B_COL_G, E1B_COL_B)
set sd.e2 = AddSpecialEffect(EFFECT_2B, sd.cx, sd.cy)
call BlzSetSpecialEffectZ(sd.e2, sd.cz)
call BlzSetSpecialEffectScale(sd.e2, E2B_SCALE)
call BlzSetSpecialEffectTimeScale(sd.e2, E2B_TSCALE)
call BlzSetSpecialEffectYaw(sd.e2, bj_PI)
call BlzSetSpecialEffectColor(sd.e2, E2B_COL_R, E2B_COL_G, E2B_COL_B)
call BlzSetSpecialEffectTime(sd.e2, 1.)
call sd.onFlip()
endif
call BlzSetSpecialEffectPosition(sd.e1, sd.cx, sd.cy, sd.cz)
call BlzSetSpecialEffectPosition(sd.e2, sd.cx + ORBIT_OFFSET*Cos(sd.e2a), sd.cy + ORBIT_OFFSET*Sin(sd.e2a), sd.cz)
else
set sd.a = GetUnitFacing(sd.c)*bj_DEGTORAD
set sd.tx = UserMouse[sd.p].mouseX
set sd.ty = UserMouse[sd.p].mouseY
set sd.a = Atan2(sd.ty-sd.cy, sd.tx-sd.cx)
set sd.ax = FOLLOW_ACCEL*Cos(sd.a)
set sd.ay = FOLLOW_ACCEL*Sin(sd.a)
set sd.vx = sd.vx + sd.ax*TIMER_TICK
set sd.vy = sd.vy + sd.ay*TIMER_TICK
if sd.vx*sd.vx + sd.vy*sd.vy > FOLLOW_MAX_VEL*FOLLOW_MAX_VEL then
set sd.a = Atan2(sd.vy, sd.vx)
set sd.vx = FOLLOW_MAX_VEL*Cos(sd.a)
set sd.vy = FOLLOW_MAX_VEL*Sin(sd.a)
endif
set sd.cx = sd.cx + sd.vx*TIMER_TICK
set sd.cy = sd.cy + sd.vy*TIMER_TICK
set sd.cz = GetLocalZ(sd.cx, sd.cy)+EFFECT_H
call BlzSetSpecialEffectPosition(sd.e1, sd.cx, sd.cy, sd.cz)
call BlzSetSpecialEffectPosition(sd.e2, sd.cx + ORBIT_OFFSET*Cos(sd.e2a), sd.cy + ORBIT_OFFSET*Sin(sd.e2a), sd.cz)
call SetUnitX(sd.DUMMY, sd.cx)
call SetUnitY(sd.DUMMY, sd.cy)
call sd.onPeriodic(true)
set sd.d = sd.d-1
if sd.d <= 0 then
call sd.endInstance()
endif
endif
set CURR = CURR+1
endloop
endmethod
static method create takes unit c, real x, real y returns thistype
local thistype sd = thistype.allocate()
local real v
local real dx
local real dy
set sd.c = c
set sd.p = GetOwningPlayer(sd.c)
set sd.l = GetUnitAbilityLevel(sd.c, SPELL_ID)
set sd.cx = GetUnitX(sd.c)
set sd.cy = GetUnitY(sd.c)
set sd.cz = GetLocalZ(sd.cx, sd.cy) + EFFECT_H
set sd.tx = x
set sd.ty = y
set dx = sd.tx-sd.cx
set dy = sd.ty-sd.cy
set sd.a = Atan2(dy,dx)
set v = SquareRoot(LAUNCH_ENDVEL*LAUNCH_ENDVEL + 2*LAUNCH_DECEL*SquareRoot(dx*dx + dy*dy))
set sd.vx = v*Cos(sd.a)
set sd.vy = v*Sin(sd.a)
set sd.ax = -1*LAUNCH_DECEL*Cos(sd.a)
set sd.ay = -1*LAUNCH_DECEL*Sin(sd.a)
set sd.d = R2I((v-LAUNCH_ENDVEL)/LAUNCH_DECEL/TIMER_TICK)
set sd.e1 = AddSpecialEffect(EFFECT_1A, sd.cx, sd.cy)
call BlzSetSpecialEffectZ(sd.e1, sd.cz)
call BlzSetSpecialEffectScale(sd.e1, E1A_SCALE)
call BlzSetSpecialEffectTimeScale(sd.e1, E1A_TSCALE)
call BlzSetSpecialEffectYaw(sd.e1, 0.)
call BlzSetSpecialEffectColor(sd.e1, E1A_COL_R, E1A_COL_G, E1A_COL_B)
set sd.e2 = AddSpecialEffect(EFFECT_2A, sd.cx+ORBIT_OFFSET, sd.cy)
call BlzSetSpecialEffectZ(sd.e2, sd.cz)
call BlzSetSpecialEffectScale(sd.e2, E2A_SCALE)
call BlzSetSpecialEffectTimeScale(sd.e2, E2A_TSCALE)
call BlzSetSpecialEffectYaw(sd.e2, bj_PI)
call BlzSetSpecialEffectColor(sd.e2, E2A_COL_R, E2A_COL_G, E2A_COL_B)
call BlzSetSpecialEffectTime(sd.e2, 1.)
set sd.DUMMY = CreateUnit(sd.p, DUMMY_ID, sd.cx, sd.cy, 0.)
if COUNT == 0 then
call TimerStart(SpellTimer, TIMER_TICK, true, function thistype.periodic)
endif
set GSD[COUNT] = sd
set COUNT = COUNT+1
return sd
endmethod
static method onCast takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call SpellData.create(GetTriggerUnit(), GetSpellTargetX(), GetSpellTargetY())
call GSD[COUNT-1].onStart()
endif
return false
endmethod
static method onInit takes nothing returns nothing
call TriggerRegisterAnyUnitEventBJ(CastTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(CastTrigger, Condition(function thistype.onCast))
call thistype.onInitEx()
endmethod
endstruct
endlibrary
library PointDefense requires MouseUtils, RemoveEffect, LocalZ
//=============================================================================================================================
// PointDefense v1.2
// by Pyrogasm
//
// Spawn defense drones that encircle the caster. Drones launch themselves to impact at the mouse cursor location periodically.
// Each drone can travel a maximum distance, and those closest to the target are launched first.
//
// Spell behavior is highly configurable, but by default:
// Enemies in the impact radius take damage per drone and are briefly blinded, causing them to miss most of their attacks.
//
// Dependencies:
// - MouseUtils by MyPad: https://www.hiveworkshop.com/threads/snippet-mouse-utility.310523/
// - RemoveEffect by Pyrogasm: https://www.hiveworkshop.com/threads/small-code-snippets.40758/page-45#post-3361522
// - LocalZ - any library that contains a function to get the Z height of X/Y coordinates
//=============================================================================================================================
globals
public constant integer SPELL_ID = 'Pdfs'
public constant integer DUMMY_ID = 'dmmy'
private constant real TIMER_TICK = 0.03
private constant real MAX_COLLISION = 128.00 //this value is added to GroupEnum calls before checking with IsUnitInRangeXY() so collision is properly accounted for
//set this to the max collision size a unit has in your map
private constant integer MAX_DEFENDERS = 24 //used in array member sizing
private constant real ORBIT_RADIUS = 170.
private constant real ORBIT_PERIOD = 10. //this is only used in the line below
private constant real ORBIT_ANGLEINC = 2*bj_PI/ORBIT_PERIOD*TIMER_TICK
private constant real ORBIT_DELAY = 0.51
private constant real FIRE_PERIOD = 0.24
private constant real FIRE_IMPACT_H = 0.
private constant real FIRE_MAX_DIST = 800.
private constant real FIRE_MAX_VEL = 1500.
private constant real FIRE_START_VEL = 0.
private constant real FIRE_ACCEL = 3000.
//For all effect variables:
//1A = drone pre-launch
//1B = drone post-launch
//2A = impact fx
private constant real EFFECT_H = 150.
private constant string EFFECT_1A = "Abilities\\Weapons\\WitchDoctorMissile\\WitchDoctorMissile.mdl"
private constant string EFFECT_1B = "Abilities\\Weapons\\WitchDoctorMissile\\WitchDoctorMissile.mdl"
private constant string EFFECT_2A = "Abilities\\Spells\\NightElf\\Blink\\BlinkCaster.mdl" // Human\\ThunderClap\\ThunderClapCaster.mdl"
private constant real E1A_SCALE = 0.5
private constant real E1A_TSCALE = 1. //timescale, aka animation speed
private constant real E1B_SCALE = 1.
private constant real E1B_TSCALE = 1.
private constant real E2A_SCALE = 0.6
private constant real E2A_TSCALE = 2.
private constant integer E1A_COL_R = 255
private constant integer E1A_COL_G = 255
private constant integer E1A_COL_B = 255
private constant integer E1B_COL_R = 255
private constant integer E1B_COL_G = 70
private constant integer E1B_COL_B = 70
private constant integer E2A_COL_R = 255
private constant integer E2A_COL_G = 0
private constant integer E2A_COL_B = 100
endglobals
globals
public keyword SpellData
private keyword defender
private SpellData array GSD
private integer COUNT = 0
private integer CURR = 0
public timer SpellTimer = CreateTimer()
public trigger CastTrigger = CreateTrigger()
endglobals
//Need this for checks in the spellBehavior module
native UnitAlive takes unit u returns boolean
public module spellBehavior
// the following variables are defined in the struct below for you to use
// they do not need to be destroyed or removed, that is handled already:
//
// unit DUMMY <-- can cast instantly and is moved to (.cx, .cy) every timer step
// add abilities to it with .prepDummy(abilCode) (level is set automatically)
// group HIT_GROUP <-- units already hit by this instance of the spell, you are free to clear this as necessary, but you must add units manually
// static group SEARCH_GROUP <-- a group you can use to search for targets via GroupEnumUnitsIn...
// integer l <-- level of the spell when it was cast
// unit c <-- caster
// player p <-- owner of caster
//
// look at the SpellData struct below for additional members if you want more information
// you may end an insance early by calling .endInstance()
// you may add your own instance or static members here:
static integer SECONDARY_ID = 'Pdcs'
static string SECONDARY_ORDER = "curse"
static real RADIUS = 100.
static real DMG = 20.
method numDefenders takes nothing returns integer
//called on spell cast, determines the number of defenders to create
return GetRandomInt(5,15)
endmethod
static method onInitEx takes nothing returns nothing
//called on map init, so you can set up anything you need to here (most likely arrays or some global dummies)
endmethod
method onStart takes nothing returns nothing
//called when the spell is cast
call this.prepDummy(SECONDARY_ID)
endmethod
method onPeriodic takes nothing returns nothing
//called periodically while spell is running
endmethod
method onLaunch takes defender def, real tx, real ty, real th returns nothing
//called when defender def is launched
//can use members def.x, def.y, def.z for current location (def.h for height) and the tx, ty, th parameters for targeted location
endmethod
method onImpact takes defender def returns nothing
//can use members def.x, def.y, def.z for current location
local unit u
local effect e
set e = AddSpecialEffect(EFFECT_2A, def.x, def.y)
call BlzSetSpecialEffectColor(e, E2A_COL_R, E2A_COL_G, E2A_COL_B)
call BlzSetSpecialEffectScale(e, E2A_SCALE)
call BlzSetSpecialEffectTimeScale(e, E2A_TSCALE)
call DestroyEffect(e)
call GroupEnumUnitsInRange(SEARCH_GROUP, def.x, def.y, RADIUS+MAX_COLLISION, null)
loop
set u = FirstOfGroup(SEARCH_GROUP)
exitwhen u == null
call GroupRemoveUnit(SEARCH_GROUP, u)
if IsUnitInRangeXY(u, def.x, def.y, RADIUS) and /*
*/ IsUnitEnemy(u, this.p) and UnitAlive(u) and /*
*/ not IsUnitType(u, UNIT_TYPE_MAGIC_IMMUNE) and /*
*/ not BlzIsUnitInvulnerable(u) then
call UnitDamageTarget(this.c, u, DMG, false, false, ATTACK_TYPE_NORMAL, DAMAGE_TYPE_FORCE, WEAPON_TYPE_WHOKNOWS)
call IssueTargetOrder(this.DUMMY, SECONDARY_ORDER, u)
endif
endloop
set u = null
endmethod
method onEnd takes nothing returns nothing
//called when the spell ends
endmethod
endmodule
private struct defender
SpellData sd = 0
boolean f = false
boolean i = false
effect e = null
integer n = 0
integer d = 0
real x = 0.
real y = 0.
real z = 0.
real h = 0.
real v = 0
real cosA = 0.
real sinA = 0.
real dz = 0.
method move takes nothing returns nothing
if not this.f then
set this.x = this.sd.cx + this.sd.orbitR*Cos(this.sd.defAng*this.n+this.sd.defTheta)
set this.y = this.sd.cy + this.sd.orbitR*Sin(this.sd.defAng*this.n+this.sd.defTheta)
set this.z = BlzGetUnitZ(this.sd.c)+this.h
else
set this.x = this.x + this.v*this.cosA*TIMER_TICK
set this.y = this.y + this.v*this.sinA*TIMER_TICK
set this.h = this.h - this.dz
set this.z = GetLocalZ(this.x, this.y) + this.h
set this.v = this.v + FIRE_ACCEL*TIMER_TICK
if this.v > FIRE_MAX_VEL then
set this.v = FIRE_MAX_VEL
endif
set this.d = this.d - 1
endif
call BlzSetSpecialEffectPosition(this.e, this.x, this.y, this.z)
if this.d <= 0 then
call this.sd.onImpact(this)
call DestroyEffect(this.e)
set this.sd.d = this.sd.d - 1
set this.i = true
endif
endmethod
method fireAt takes real x, real y, real h returns nothing
local real a = Atan2(y-this.y, x-this.x)
local real t1
local real t2
local real qa
local real qb
local real qc
local real d
call RemoveEffect(this.e)
set this.e = AddSpecialEffect(EFFECT_1B, this.x, this.y)
call BlzSetSpecialEffectZ(this.e, this.z)
call BlzSetSpecialEffectScale(this.e, E1B_SCALE)
call BlzSetSpecialEffectTimeScale(this.e, E1B_TSCALE)
call BlzSetSpecialEffectColor(this.e, E1B_COL_R, E1B_COL_G, E1B_COL_B)
call this.sd.onLaunch(this, x, y, h)
set this.f = true
set this.cosA = Cos(a)
set this.sinA = Sin(a)
set this.v = FIRE_START_VEL
set d = SquareRoot((x-this.x)*(x-this.x) + (y-this.y)*(y-this.y))
set qa = FIRE_ACCEL/2.
set qb = FIRE_START_VEL
set qc = -1.*d
set t1 = (-1.*qb + SquareRoot(qb*qb - 4.*qa*qc))/(2.*qa)
set t2 = 0.
if this.v+FIRE_ACCEL*t1 > FIRE_MAX_VEL then
set t1 = (FIRE_MAX_VEL-this.v)/FIRE_ACCEL
set t2 = (d - (FIRE_MAX_VEL-this.v)/2.*t1)/FIRE_MAX_VEL
endif
set this.d = R2I((t1+t2)/TIMER_TICK)
set this.dz = ((this.h - h)/this.d)
endmethod
static method create takes SpellData sd, integer n returns thistype
local thistype def = thistype.allocate()
set def.sd = sd
set def.n = n
set def.f = false
set def.i = false
set def.x = sd.cx + sd.orbitR*Cos(sd.defAng*n+sd.defTheta)
set def.y = sd.cy + sd.orbitR*Sin(sd.defAng*n+sd.defTheta)
set def.h = EFFECT_H
set def.z = BlzGetUnitZ(sd.c)+def.h
set def.d = 1 //Simplest solution I guess...
set def.e = AddSpecialEffect(EFFECT_1A, def.x, def.y)
call BlzSetSpecialEffectZ(def.e, def.z)
call BlzSetSpecialEffectScale(def.e, E1A_SCALE)
call BlzSetSpecialEffectTimeScale(def.e, E1A_TSCALE)
call BlzSetSpecialEffectColor(def.e, E1A_COL_R, E1A_COL_G, E1A_COL_B)
return def
endmethod
endstruct
public struct SpellData
static group SEARCH_GROUP = CreateGroup()
group HIT_GROUP = CreateGroup()
unit DUMMY = null
unit c = null
player p = null
integer l = 0
integer d = 0
integer fire = 0
real cx = 0.
real cy = 0.
defender array defs[MAX_DEFENDERS]
integer defCount = 0
real defAng = 0.
real defTheta = 0.
integer phase = 0
real orbitA = 0.
real orbitV = 0.
real orbitR = 0.
method prepDummy takes integer abil returns nothing
call UnitAddAbility(this.DUMMY, abil)
call SetUnitAbilityLevel(this.DUMMY, abil, this.l)
endmethod
method endInstance takes nothing returns nothing
local integer i = 0
call this.onEnd()
call RemoveUnit(this.DUMMY)
call DestroyGroup(this.HIT_GROUP)
loop
call this.defs[i].destroy()
set i = i+1
exitwhen i >= this.defCount
endloop
set GSD[CURR] = GSD[COUNT-1]
set COUNT = COUNT-1
set CURR = CURR-1
if COUNT == 0 then
call PauseTimer(SpellTimer)
endif
call this.destroy()
endmethod
implement spellBehavior
method fireDefender takes real x, real y, real z returns nothing
local integer i = 0
local real dx
local real dy
local real d2
local integer closest = -1
local real d2Closest = (FIRE_MAX_DIST+ORBIT_RADIUS)*(FIRE_MAX_DIST+ORBIT_RADIUS)
loop
if not this.defs[i].f then
set dx = this.defs[i].x - x
set dy = this.defs[i].y - y
set d2 = dx*dx + dy*dy
if d2 <= d2Closest then
set closest = i
set d2Closest = d2
endif
endif
set i = i+1
exitwhen i >= this.defCount
endloop
if closest >= 0 then
call this.defs[closest].fireAt(x, y, z)
endif
endmethod
method updateDefenders takes nothing returns nothing
local integer i = 0
loop
if not this.defs[i].i then
call this.defs[i].move()
endif
set i = i+1
exitwhen i >= this.defCount
endloop
endmethod
static method periodic takes nothing returns nothing
local SpellData sd
local integer i
local real mx
local real my
local real a
set CURR = 0
loop
exitwhen CURR >= COUNT
set sd = GSD[CURR]
set sd.cx = GetUnitX(sd.c)
set sd.cy = GetUnitY(sd.c)
call SetUnitX(sd.DUMMY, sd.cx)
call SetUnitY(sd.DUMMY, sd.cy)
set sd.defTheta = sd.defTheta + ORBIT_ANGLEINC
if sd.phase == 0 then
set sd.orbitR = sd.orbitR + sd.orbitV*TIMER_TICK
set sd.orbitV = sd.orbitV - sd.orbitA*TIMER_TICK
call sd.updateDefenders()
set sd.d = sd.d-1
if sd.d <= 0 then
set sd.phase = sd.phase+1
set sd.d = sd.defCount
endif
elseif sd.phase == 1 then
call sd.updateDefenders()
call sd.onPeriodic()
set sd.fire = sd.fire-1
if sd.fire <= 0 then
set mx = UserMouse[sd.p].mouseX
set my = UserMouse[sd.p].mouseY
if (mx-sd.cx)*(mx-sd.cx) + (my-sd.cy)*(my-sd.cy) > FIRE_MAX_DIST*FIRE_MAX_DIST then
set a = Atan2(my-sd.cy, mx-sd.cx)
set mx = sd.cx + FIRE_MAX_DIST*Cos(a)
set my = sd.cy + FIRE_MAX_DIST*Sin(a)
endif
call sd.fireDefender(mx, my, FIRE_IMPACT_H)
set sd.fire = R2I(FIRE_PERIOD/TIMER_TICK)
endif
if sd.d <= 0 then
call sd.endInstance()
endif
endif
set CURR = CURR+1
endloop
endmethod
static method create takes unit c returns thistype
local thistype sd = thistype.allocate()
local integer i = 0
set sd.c = c
set sd.cx = GetUnitX(sd.c)
set sd.cy = GetUnitY(sd.c)
set sd.p = GetOwningPlayer(sd.c)
set sd.l = GetUnitAbilityLevel(sd.c, SPELL_ID)
set sd.d = R2I(ORBIT_DELAY/TIMER_TICK)
set sd.defCount = sd.numDefenders()
set sd.defAng = 2*bj_PI/sd.defCount
set sd.DUMMY = CreateUnit(sd.p, DUMMY_ID, sd.cx, sd.cy, 0.)
set sd.orbitR = 0.
set sd.orbitV = 2.*ORBIT_RADIUS/ORBIT_DELAY
set sd.orbitA = 2./ORBIT_DELAY*(sd.orbitV - ORBIT_RADIUS/ORBIT_DELAY)
loop
set sd.defs[i] = defender.create(sd, i)
set i = i+1
exitwhen i >= sd.defCount
endloop
if COUNT == 0 then
call TimerStart(SpellTimer, TIMER_TICK, true, function thistype.periodic)
endif
set GSD[COUNT] = sd
set COUNT = COUNT+1
return sd
endmethod
static method onCast takes nothing returns boolean
if GetSpellAbilityId() == SPELL_ID then
call SpellData.create(GetTriggerUnit())
call GSD[COUNT-1].onStart()
endif
return false
endmethod
static method onInit takes nothing returns nothing
call TriggerRegisterAnyUnitEventBJ(CastTrigger, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(CastTrigger, Condition(function thistype.onCast))
call thistype.onInitEx()
endmethod
endstruct
endlibrary