scope GummyLeash
/*
Gummy Leash v2.5
Descriptions:
Summons a mythical orb upon the target point.
Nearby enemy units will be captured by an
elastical energy stream which will keep them
from moving further away from the orb.
Requires:
- GummyLink
External Dependencies:
(Required)
- CTL
- UnitIndexer
- WorldBounds
(Optional)
- GetClosestWidget
- SpellEffectEvent
How to import:
- Install and configure all dependencies properly
- Copy GummyLeash trigger group into your map
- Import all necessary stuffs (OE & import data) to your map
- Configure the spell
Credits:
- IsTerrainWalkable by Anitarf and Vexorian
- CTL by Nestharus
- AutoFly by Nestharus
- WorldBounds by Nestharus
- UnitIndexer by Nestharus
- GetClosestWidget by Bannar
- SpellEffectEvent by Bribe
- UnitZ by Garfield1337
- OrbDragonX.mdx by Frankster
- BTNManaBreathe by D.ee
- LaserBlue.blp by Erkki2
Link:
hiveworkshop.com/forums/spells-569/gummy-leash-v1-0-a-257717/
*/
// CONFIGURATIONS
// Native declaration
native UnitAlive takes unit id returns boolean
globals
// Summoned unit's rawcode
private constant integer WARD_ID = 'h000'
// Main ability's rawcode
private constant integer MAIN_ID = 'A000'
// Adjust z offset of the lightning
// As example, if you use a floating model for the ward
// then you can adjust the lightning's z offset here
private constant real Z_OFFSET = 75.0
// Lightning effect string
private constant string LIGHTNING_EFFECT = "BLUE"
// Allow ward to detect for new targets during lifespan
private constant boolean ALLOW_NEW_TARGET = true
// Simulate 3D effect for GummyLink
private constant boolean ENABLE_3D = true
// Add timed life bar to the ward
private constant boolean ADD_TIMED_LIFE = true
private constant integer TIMED_LIFE = 'BTLF'
// Maximum possible target
// Set this to target count at max ability level
private constant integer MAXIMUM_TARGET = 5
// Attached sfx on victims
private constant string TARGET_SFX = "war3mapImported\\GummyLeash.mdx"
private constant string TARGET_SFX_PT = "chest"
// Add buff effect (stun) to targets
private constant boolean ADD_BUFF = true
// Damage configuration
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_NORMAL
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
endglobals
// Just skip these buff datas if ADD_BUFF is false
private module BuffData
// Dummy unit's rawcode
static constant integer DUMMY_ID = 'h001'
// Buff ability rawcode
static constant integer SPELL_ID = 'A001'
// Buff rawcode
static constant integer BUFF_ID = 'B000'
// Stun order id
static constant integer BUFF_ORDER = 852127 // stomp
// The higher the safer but just leave it
static constant real BUFF_SAFETY = 64.0
endmodule
// Maximum targets per ward
private constant function count takes integer level returns integer
return 2 + level
endfunction
// Ward's lifespan
private constant function duration takes integer level returns real
return 3.0 + 2.0 * level
endfunction
// Apply damage interval
private constant function delay takes integer level returns real
return 0.0
endfunction
// Dealt damage amount
private constant function damage takes integer level returns real
return 0.0
endfunction
// Detect AoE
private constant function aoe takes integer level returns real
return 600.0
endfunction
// Drag power of the link (when shrinking)
private constant function power takes integer level returns real
return 3.2
endfunction
// Drag power of the link (when stretching)
private constant function elasticity takes integer level returns real
return 3.2
endfunction
// Configure desired targets
// By default, it's unable to targets structures and magic immune
private function targets takes unit u, player p returns boolean
return UnitAlive(u) and IsUnitEnemy(u, p) and not IsUnitType(u, UNIT_TYPE_MECHANICAL)
endfunction
// This function is called whenever a unit takes damage from this spell
private function onDamage takes unit caster, unit target returns nothing
endfunction
// This function is called whenever a unit is released by this spell
private function onRelease takes unit caster, unit target returns nothing
endfunction
// =============================================================================
private struct LeashBuff extends array
unit target
boolean dispose
effect sfx
static unit Caster
static group Group = CreateGroup()
static boolean array Bool
static thistype array Index
static if ADD_BUFF then
implement BuffData
endif
method remove takes nothing returns nothing
set .dispose = true
endmethod
implement CTLExpire
if .dispose or not UnitAlive(.target) then
set Bool[GetUnitUserData(.target)] = false
static if ADD_BUFF then
call UnitRemoveAbility(.target, BUFF_ID)
endif
call DestroyEffect(.sfx)
call destroy()
set .sfx = null
set .target = null
endif
implement CTLEnd
static method apply takes unit target returns thistype
local thistype this = 0
local integer dex = GetUnitUserData(target)
local real x
local real y
if Bool[dex] then
set this = Index[dex]
else
if not(IsUnitType(target, UNIT_TYPE_STRUCTURE)) then
set this = create()
set .target = target
set .dispose = false
set .sfx = AddSpecialEffectTarget(TARGET_SFX, .target, TARGET_SFX_PT)
set Bool[dex] = true
set Index[dex] = this
static if ADD_BUFF then
set x = GetUnitX(target)
set y = GetUnitY(target)
// If unit don't have the buff yet
if GetUnitAbilityLevel(target, BUFF_ID) == 0 then
call SetUnitX(Caster, x)
call SetUnitY(Caster, y)
call IssueImmediateOrderById(Caster, BUFF_ORDER)
call SetUnitPosition(Caster, WorldBounds.maxX, WorldBounds.maxY)
endif
// Remove unwanted buff
call GroupEnumUnitsInRange(Group, x, y, BUFF_SAFETY, null)
loop
set target = FirstOfGroup(Group)
exitwhen target == null
call GroupRemoveUnit(Group, target)
if GetUnitAbilityLevel(target, BUFF_ID) > 0 and not Bool[GetUnitUserData(target)] then
call UnitRemoveAbility(target, BUFF_ID)
endif
endloop
endif
debug else
debug call BJDebugMsg("Error occured: failed to attach buff")
endif
endif
return this
endmethod
private static method onInit takes nothing returns nothing
static if ADD_BUFF then
set Caster = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), DUMMY_ID, WorldBounds.maxX, WorldBounds.maxY, 0)
call UnitAddAbility(Caster, SPELL_ID)
endif
endmethod
endstruct
// Container for targets per instance
private struct LeashTargets
unit array target[MAXIMUM_TARGET]
GummyLink array link[MAXIMUM_TARGET]
LeashBuff array buff[MAXIMUM_TARGET]
endstruct
private struct GummyLeash extends array
real x
real y
real dur
real aoe
real pow
real elstc
real dly
real dlyx
real dmg
integer ct
integer ctx
player p
unit caster
unit ward
LeashTargets t
static group Group = CreateGroup()
static player TempPlayer
static thistype TempDex
static boolean array Bool
// Target filtration, made to be compatible for GetClosestUnit
static method filter takes nothing returns boolean
return targets(GetFilterUnit(), TempPlayer)
endmethod
implement CTL
local unit u
local integer i
local integer dex
implement CTLExpire
if .dur > 0.03125 and UnitAlive(.ward) then
set .dur = .dur - 0.03125
set .dly = .dly - 0.03215
set i = 0
loop
exitwhen i >= .ct
if UnitAlive(.t.target[i]) then
if .dly <= 0 and .dmg > 0 then
call onDamage(.caster, .t.target[i])
call UnitDamageTarget(.caster, .t.target[i], .dmg, true, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
endif
else
call onRelease(.caster, .t.target[i])
set Bool[GetUnitUserData(.t.target[i])] = false
call .t.link[i].remove()
// Deindex
set .ct = .ct - 1
set .t.target[i] = .t.target[.ct]
set .t.link[i] = .t.link[.ct]
set .t.buff[i] = .t.buff[.ct]
set .t.target[.ct] = null
set i = i - 1
endif
set i = i + 1
endloop
if .dly <= 0 then
set .dly = .dlyx
endif
static if ALLOW_NEW_TARGET then
if .ct < .ctx then
set TempPlayer = .p
call GroupEnumUnitsInRange(Group, .x, .y, .aoe, function thistype.filter)
loop
exitwhen .ct >= .ctx
static if LIBRARY_GetClosestWidget then
set u = GetClosestUnitInGroup(.x, .y, Group)
else
set u = FirstOfGroup(Group)
endif
exitwhen u == null
call GroupRemoveUnit(Group, u)
set dex = GetUnitUserData(u)
if not Bool[dex] then
set .t.buff[.ct] = LeashBuff.apply(u)
// If successfully buffed
if .t.buff[.ct] != 0 then
set .t.link[.ct] = GummyLink.connect(.ward, u, Z_OFFSET, .pow, .elstc, LIGHTNING_EFFECT, ENABLE_3D)
// If successfully connected
if .t.link[.ct] != 0 then
set Bool[dex] = true
set .t.target[.ct] = u
set .ct = .ct + 1
endif
endif
endif
endloop
set u = null
endif
endif
else
set i = 0
loop
exitwhen i >= .ct
call onRelease(.caster, .t.target[i])
set Bool[GetUnitUserData(.t.target[i])] = false
call .t.link[i].remove()
call .t.buff[i].remove()
set .t.target[i] = null
set i = i + 1
endloop
call KillUnit(.ward)
call .t.destroy()
call destroy()
set .ward = null
set .caster = null
endif
implement CTLEnd
static method onCast takes nothing returns boolean
local thistype this = create()
local integer level
local integer dex
local unit fog
set .caster = GetTriggerUnit()
set .p = GetTriggerPlayer()
set .ward = CreateUnit(.p, WARD_ID, GetSpellTargetX(), GetSpellTargetY(), bj_UNIT_FACING)
set .x = GetUnitX(.ward)
set .y = GetUnitY(.ward)
set .ct = 0
set level = GetUnitAbilityLevel(.caster, MAIN_ID)
set .aoe = aoe(level)
set .dur = duration(level)
set .elstc = elasticity(level)
set .pow = power(level)
set .ctx = count(level)
set .dlyx = delay(level)
set .dmg = damage(level)
set .dly = .dlyx
set .t = LeashTargets.create()
static if ADD_TIMED_LIFE then
call UnitApplyTimedLife(.ward, TIMED_LIFE, .dur)
endif
set TempPlayer = .p
call GroupEnumUnitsInRange(Group, .x, .y, .aoe, function thistype.filter)
loop
exitwhen .ct >= .ctx
static if LIBRARY_GetClosestWidget then
set fog = GetClosestUnitInGroup(.x, .y, Group)
else
set fog = FirstOfGroup(Group)
endif
exitwhen fog == null
call GroupRemoveUnit(Group, fog)
set dex = GetUnitUserData(fog)
if not Bool[dex] then
set .t.buff[.ct] = LeashBuff.apply(fog)
// If successfully buffed
if .t.buff[.ct] != 0 then
set .t.link[.ct] = GummyLink.connect(.ward, fog, Z_OFFSET, .pow, .elstc, LIGHTNING_EFFECT, ENABLE_3D)
// If successfully connected
if .t.link[.ct] != 0 then
set Bool[dex] = true
set .t.target[.ct] = fog
set .ct = .ct + 1
endif
endif
endif
endloop
set fog = null
return false
endmethod
static if not LIBRARY_SpellEffectEvent then
private static method checkId takes nothing returns boolean
if GetSpellAbilityId() == MAIN_ID then
call thistype.onCast()
endif
return false
endmethod
endif
static method onInit takes nothing returns nothing
static if LIBRARY_SpellEffectEvent then
call RegisterSpellEffectEvent(MAIN_ID, function thistype.onCast)
else
set gg_trg_GummyLeash = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(gg_trg_GummyLeash, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(gg_trg_GummyLeash, Condition(function thistype.checkId))
endif
endmethod
endstruct
endscope