The most recent version of this spell requires just about every single system around. This update is much lighter, with very minimal script requirements, built in objectmerger calls, uses the optimized struct array timer method and contains a fix for the WC3 unit facing bug. Enjoy.
Import Difficulty: Low
Units Affected: Ground/Air
Target Type: Unit/Point - Enemy, Friend, Ground, Terrain, Structure
Spell Info:
Launches a bloody hook at a unit or location. The hook will snag the first target it encounters, deal damage and drag the victim back to the caster.
Requirements:
UnitAlive native
ObjectMerger Hotfix
Would also be a good idea to make use of Bound Sentinel
Updates:
Suggestions and requests are welcome.
FYI: I have managed to get this up to about 20 instances before lag w/o the facing bug fix, but only about 5 with it.
Pudge Wars (like) Meat Hook v1.00
Import Difficulty: Low
Units Affected: Ground/Air
Target Type: Unit/Point - Enemy, Friend, Ground, Terrain, Structure
Spell Info:
Launches a bloody hook at a unit or location. The hook will snag the first target it encounters, deal damage and drag the victim back to the caster.
Requirements:
UnitAlive native
ObjectMerger Hotfix
Would also be a good idea to make use of Bound Sentinel
JASS:
scope MeatHook initializer Init
//*********************************************************************
//* Meat Hook v1.00
//* by: emjlr3
//* ----------
//*
//* Requirements:
//* *A Unit/Point target ability
//* *The Chain Link dummy unit
//* *UnitAlive native, found in the custom script section of this map
//* *A copy of this trigger
//* *Objectmerger hotfix (<a href="http://www.wc3c.net/showpost.php?p=1050064&postcount=7" target="_blank" class="link link--external" rel="nofollow ugc noopener">http://www.wc3c.net/showpost.php?p=1050064&postcount=7</a>)
//* This is required for correct channel based ability mergers
//*
//* (requires vJass) More abilities at <a href="http://www.thehelper.net/forums" class="link link--internal">http://www.thehelper.net/forums</a>
//*
//* Important:
//* *There is maximum limit of 100 links/cast. Make sure your Links
//* function never returns a value greater then 100. As such, there
//* is also an instance limit of 81. If this limitation does become
//* a problem, let me know.
//* *Object merger calls for ability and dummy unit supplied. Use
//* them if you want, but they are not required. If you use the ability
//* merger call, make sure you first have the aforementioned hotfix.
//* *FACINGBUG can be used to remove the inherent WC3 facing bug, which
//* causes non-instant facing upates. Setting this to true alleviates
//* this issue, but makes for a more hardware intensive spell.
//* *SFX strings can be set to "" for no effect
//*
//********************************************************************
//====CONFIGURABLES====\\
//! external ObjectMerger w3u ushd link unam "Chain Link" unsf "(Meat Hook)" uico "ReplaceableTextures\CommandButtons\BTNImpale.blp" umdl "Abilities\Weapons\WardenMissile\WardenMissile" usca 2.0 umvt fly ucol 0.0 ufoo 0 usid 0 usin 0 uabi Aloc urac other utar invulnerable uacq 0.0 ushu "" uhom 1
//! external ObjectMerger w3a ANcl meat anam "Meat Hook" aret "Learn Mea|cffffcc00t|r Hook - [|cffffcc00Level %d|r]" arhk "T" ahky "T" acat "" aeat "" atat "" aart "ReplaceableTextures\CommandButtons\BTNImpale.blp" arar "ReplaceableTextures\CommandButtons\BTNImpale.blp" arut "Launches a bloody hook at a unit or location. The hook will snag the first target encountered, dealing damage then dragging the victim back to the Butcher." atp1 1 "Mea|cffffcc00t|r Hook - [|cffffcc00Level 1|r]" aub1 1 "Fires a meat hook at a unit or location. The hook will snag the first target encountered." alev 1 aani "attack" Ncl6 1 channel Ncl1 1 .2 atar 1 enemies,friend,ground,terrain,structure Ncl2 1 3 Ncl3 1 5
globals
private integer ABIL = 039;meat039; // Meat Hook ability rawcode
private string ATTACH = "chest" // Attachement point for effects
private attacktype ATTACK = ATTACK_TYPE_CHAOS // Ability damage attacktype
private damagetype DAMAGE = DAMAGE_TYPE_UNIVERSAL // Ability damage damagetype
private boolean DISCARD = true // Discard target if it dies during retract, allowing another target to be hit
private real DISTANCE = 40.0 // Distance between links
private boolean FACINGBUG = false // Correct facing bug
private boolean GRAPPLE = false // Grapple on hit
private string HITSFX = "Objects\\Spawnmodels\\Human\\HumanBlood\\HeroBloodElfBlood.mdl" // Effect created on target when hit
private boolean KILLTREES = true // Destroy destructables in path of links
private string KILLSFX = "Units\\Undead\\Abomination\\AbominationExplosion.mdl" // Effect created on target if hit kills
private integer LINK = 039;link039; // Chain Link unit rawcode
private real RADIUS = 100.0 // Hook collision radius
private boolean REFLECT = true // Reflect off structures
private string SLIDESFX = "Abilities\\Weapons\\AncientProtectorMissile\\AncientProtectorMissile.mdl" // Effect created on target during retract
private real TIMEOUT = 0.033 // Periodic timeout interval
endglobals
private function Damage takes integer lvl returns real
return 250. // Damage/lvl
endfunction
private function Links takes integer lvl returns integer
return 30 // Links/lvl, must be <100
endfunction
private function Bool takes unit target returns boolean
return true // Custom target-boolean function
endfunction
private function onHit takes unit caster, unit target returns nothing
return // Custom target-on-hit function
endfunction
//====NO TOUCHING!!!!====\\
private keyword data
globals
private data I
private unit T
private group G=CreateGroup()
private rect R
private data array D
private timer Tim=CreateTimer()
private integer C=0
endglobals
private struct data
unit cast
unit targ=null
unit array links[101]
integer linkstotal=0
integer linksmin=0
player p
boolean hit=false
boolean extend=true
boolean grapple=false
real ang
real distance=0.
real maxdistance
method onDestroy takes nothing returns nothing
local integer i=1
// Remove any left over links
loop
exitwhen i>.linkstotal
if UnitAlive(.links<i>) then
call RemoveUnit(.links<i>)
endif
set i=i+1
endloop
// Update target pathing
if .hit and .targ!=null and not .grapple then
call SetUnitPathing(.targ,true)
endif
if .grapple then
call SetUnitPathing(.cast,true)
endif
endmethod
static method ValidDestructables takes nothing returns boolean
if GetWidgetLife(GetFilterDestructable())>.405 then
call KillDestructable(GetFilterDestructable())
endif
return false
endmethod
static method Filt takes nothing returns boolean
local data this=I
local real theta
local real tangent
local real deflection
set T=GetFilterUnit()
// Hit a structure
if IsUnitType(T,UNIT_TYPE_STRUCTURE)==true and UnitAlive(T) then
call onHit(.cast,T)
if GRAPPLE and not .hit then
set .targ=T
set .hit=not .hit
set .grapple=not .grapple
call SetUnitPathing(.cast,false)
elseif REFLECT then
set theta=Atan2(GetUnitY(T)-GetUnitY(.links[1]),GetUnitX(T)-GetUnitX(.links[1]))
if theta>.ang then
set tangent=theta+3.14159/2
else
set tangent=theta-3.14159/2
endif
set deflection=theta-tangent
set tangent=ModuloReal(tangent+3.14159, 6.28318)
set .ang=tangent+deflection
endif
// Hit a unit
elseif Bool(T) and IsUnitType(T,UNIT_TYPE_STRUCTURE)==false and UnitAlive(T) and not .hit and T!=.cast then
call onHit(.cast,T)
set .targ=T
set .hit=not .hit
if IsUnitEnemy(T,.p) then
call UnitDamageTarget(.cast,T,Damage(GetUnitAbilityLevel(.cast,ABIL)),false,false,ATTACK,DAMAGE,null)
if not UnitAlive(T) then
call DestroyEffect(AddSpecialEffectTarget(KILLSFX,T,ATTACH))
else
call DestroyEffect(AddSpecialEffectTarget(HITSFX,T,ATTACH))
endif
endif
if UnitAlive(T) then
if GRAPPLE then
set .grapple = not.grapple
else
call SetUnitPathing(T,false)
endif
endif
endif
return false
endmethod
static method Effects takes nothing returns nothing
local data this
local real cx
local real cy
local real x
local real y
local unit h
local unit last
local real lang
local integer i
local integer c=1
loop
exitwhen c>C
set this=D[c]
set cx=GetUnitX(.cast)
set cy=GetUnitY(.cast)
// Update target status
if DISCARD then
if (not UnitAlive(.targ) or .targ==null) and .hit then
call SetUnitPathing(.targ,true)
set .hit=not .hit
set .targ=null
endif
endif
// Filter through available targets
if .linkstotal>0 then
call GroupClear(G)
set I=this
call GroupEnumUnitsInRange(G,GetUnitX(.links[1]),GetUnitY(.links[1]),RADIUS,Condition(function data.Filt))
endif
if .extend then
// Create head
if .linkstotal==0 then
set .links[1]=CreateUnit(.p,LINK,cx+DISTANCE*Cos(.ang),cy+DISTANCE*Sin(.ang),.ang*bj_RADTODEG)
else
// Link creation
set .links[.linkstotal+1]=CreateUnit(.p,LINK,cx,cy,Atan2(GetUnitY(.links[.linkstotal])-cy,GetUnitX(.links[.linkstotal])-cx)*bj_RADTODEG)
endif
set .linkstotal=.linkstotal+1
// Links extend
set i=1
set lang=.ang
loop
exitwhen i>.linkstotal
set h=.links<i>
set x=GetUnitX(h)
set y=GetUnitY(h)
if i>1 then
set lang=Atan2(GetUnitY(last)-y,GetUnitX(last)-x)
endif
if FACINGBUG then
set .links<i>=CreateUnit(.p,LINK,x+DISTANCE*Cos(lang),y+DISTANCE*Sin(lang),lang*bj_RADTODEG)
call RemoveUnit(h)
else
call SetUnitX(h,x+DISTANCE*Cos(lang))
call SetUnitY(h,y+DISTANCE*Sin(lang))
call SetUnitFacing(h,lang*bj_RADTODEG)
endif
if KILLTREES then
call SetRect(R,GetUnitX(.links<i>)-RADIUS,GetUnitY(.links<i>)-RADIUS,GetUnitX(.links<i>)+RADIUS,GetUnitY(.links<i>)+RADIUS)
call EnumDestructablesInRect(R,Condition(function data.ValidDestructables),null)
endif
set last=.links<i>
set i=i+1
endloop
set .distance=.distance+DISTANCE
if .distance>.maxdistance or .hit then
set .extend=not .extend
endif
else
// All done
if .linksmin>=.linkstotal then
set D[c]=D[C]
set C=C-1
set c=c-1
call .destroy()
return
else
if .hit and not .grapple then
call SetUnitX(.targ,GetUnitX(.links[1]))
call SetUnitY(.targ,GetUnitY(.links[1]))
if not .grapple then
call DestroyEffect(AddSpecialEffectTarget(SLIDESFX,.targ,"origin"))
endif
elseif .grapple then
call DestroyEffect(AddSpecialEffectTarget(SLIDESFX,.cast,"origin"))
endif
// Links retract
if GRAPPLE and .grapple then
// Grapple
call SetUnitX(.links[1],GetUnitX(.targ))
call SetUnitY(.links[1],GetUnitY(.targ))
set i=2
set last=.links[1]
loop
exitwhen i>.linkstotal
set h=.links<i>
set x=GetUnitX(h)
set y=GetUnitY(h)
if h==.links[.linkstotal-linksmin] then
call ShowUnit(h,false)
call SetUnitX(h,99999.)
call SetUnitY(h,99999.)
call RemoveUnit(h)
set lang=Atan2(GetUnitY(last)-GetUnitY(.cast),GetUnitX(last)-GetUnitX(.cast))
call SetUnitX(.cast,GetUnitX(last)-DISTANCE*Cos(lang))
call SetUnitY(.cast,GetUnitY(last)-DISTANCE*Sin(lang))
call SetUnitFacing(.cast,lang*bj_RADTODEG)
else
set lang=Atan2(GetUnitY(last)-y,GetUnitX(last)-x)
if FACINGBUG then
set .links<i>=CreateUnit(.p,LINK,GetUnitX(last)-DISTANCE*Cos(lang),GetUnitY(last)-DISTANCE*Sin(lang),lang*bj_RADTODEG)
call RemoveUnit(h)
else
call SetUnitX(h,GetUnitX(last)-DISTANCE*Cos(lang))
call SetUnitY(h,GetUnitY(last)-DISTANCE*Sin(lang))
call SetUnitFacing(h,lang*bj_RADTODEG)
endif
if KILLTREES then
call SetRect(R,GetUnitX(.links<i>)-RADIUS,GetUnitY(.links<i>)-RADIUS,GetUnitX(.links<i>)+RADIUS,GetUnitY(.links<i>)+RADIUS)
call EnumDestructablesInRect(R,Condition(function data.ValidDestructables),null)
endif
endif
set last=.links<i>
set i=i+1
endloop
else
// Normal retract
set i=.linkstotal-.linksmin
set last=.cast
loop
exitwhen i==0
set h=.links<i>
set x=GetUnitX(h)
set y=GetUnitY(h)
if h==.links[.linkstotal-linksmin] then
call ShowUnit(h,false)
call SetUnitX(h,cx)
call SetUnitY(h,cy)
else
set lang=Atan2(GetUnitY(last)-y,GetUnitX(last)-x)
if FACINGBUG then
set .links<i>=CreateUnit(.p,LINK,GetUnitX(last)-DISTANCE*Cos(lang),GetUnitY(last)-DISTANCE*Sin(lang),(lang*bj_RADTODEG)+180.)
call RemoveUnit(h)
else
call SetUnitX(h,GetUnitX(last)-DISTANCE*Cos(lang))
call SetUnitY(h,GetUnitY(last)-DISTANCE*Sin(lang))
call SetUnitFacing(h,(lang*bj_RADTODEG)+180.)
endif
if KILLTREES then
call SetRect(R,GetUnitX(.links<i>)-RADIUS,GetUnitY(.links<i>)-RADIUS,GetUnitX(.links<i>)+RADIUS,GetUnitY(.links<i>)+RADIUS)
call EnumDestructablesInRect(R,Condition(function data.ValidDestructables),null)
endif
endif
set last=.links<i>
set i=i-1
endloop
endif
set .linksmin=.linksmin+1
endif
endif
set c=c+1
endloop
if C==0 then
call PauseTimer(Tim)
endif
endmethod
method Begin takes nothing returns nothing
set .cast=GetTriggerUnit()
set .p=GetOwningPlayer(.cast)
set .ang=Atan2(GetSpellTargetY()-GetUnitY(.cast), GetSpellTargetX()-GetUnitX(.cast))
set .maxdistance=Links(GetUnitAbilityLevel(.cast,ABIL))*DISTANCE
set C=C+1
set D[C]=this
if C==1 then
call TimerStart(Tim,TIMEOUT,true,function data.Effects)
endif
endmethod
endstruct
private function Conditions takes nothing returns boolean
local data d
if GetSpellAbilityId()==ABIL then
set d=data.create()
call d.Begin()
endif
return false
endfunction
//===========================================================================
private function Init takes nothing returns nothing
local trigger trig=CreateTrigger( )
call TriggerRegisterAnyUnitEventBJ(trig,EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(trig,Condition(function Conditions))
call RemoveUnit(CreateUnit(Player(15),LINK,0.,0.,0.))
call Preload(HITSFX)
call Preload(KILLSFX)
if KILLTREES then
set R=Rect(0.,0.,0.,0.)
endif
endfunction
endscope</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>
Updates:
1.00
- Initial release
Suggestions and requests are welcome.
FYI: I have managed to get this up to about 20 instances before lag w/o the facing bug fix, but only about 5 with it.
-emjlr3