Name | Type | is_array | initial_value |
i | integer | No | |
loopA | integer | No | |
MH_Ability | abilcode | Yes | |
MH_Animation_Indexes | integer | Yes | |
MH_Attach_Point | string | No | |
MH_Booleans | boolean | Yes | |
MH_Caster_Execute | group | No | |
MH_Caster_Stop | group | No | |
MH_Dummy_Type | unitcode | No | |
MH_Effect | string | Yes | |
MH_Effect_Create | effect | No | |
MH_Hash | hashtable | No | |
MH_i1 | integer | No | |
MH_i2 | integer | No | |
MH_Images | group | No | |
MH_r1 | real | No | |
MH_r2 | real | No | |
MH_r3 | real | No | |
MH_r4 | real | No | |
MH_r5 | real | No | |
MH_Reals | real | Yes | |
MH_u1 | unit | No | |
MH_u2 | unit | No | |
MH_u3 | unit | No |
//TESH.scrollpos=0
//TESH.alwaysfold=0
Make sure you have a tick in the following box:
File -> Preferences -> Automatically create unknown variables...
Copy Lightning Speed Laceration ability to your map.
Copy LSL Dummy ability to your map.
Copy Disable Attack ability to your map.
Copy the dummy paladin to your map.
Copy the LSL trigger folder to your map.
In Init Multihit trigger, set
MH_Abilities[0] = Lightning Speed Laceration
MH_Abilities[1] = LSL Dummy
MH_Abilities[0] = Disable Attack
In MH Begin Attack and MH Start Effect triggers some <Trigger - Turn on> are grayed out. Right click them and select "Enable function".
Add the ability to your hero and it is ready to be used.
//TESH.scrollpos=0
//TESH.alwaysfold=0
//==================================================================================//
// //
// LIGHTNING SPEED LACERATION by Maker v1.00 //
// //
// The caster strikes the target several times at lightning-like speed //
// causing severe blunt force trauma and stun. //
// //
// Requires JNGP and TimerUtils //
// //
// How to import: //
// 1. Copy the ability //
// 2. Copy the dummy ability //
// 3. Create a copy of your unit and make it a dummy unit. //
// You can take a look at the paladin dummy in this map to see //
// which data field you need to edit. //
// 4. Set ABILID, DUMABILID and DUMMYID to correct raw codes. //
// You can view raw codes in object editor by clicking Ctrl + D. //
// 5. Edit animation indexes in SetAnim function. //
// You can use the Animation Test trigger in Test folder to //
// check animation indexes. //
// //
//==================================================================================//
scope LSL
globals
// Ability raw code
private constant integer ABILID = 'A003'
// Dummy stun ability raw code
private constant integer DUMABILID = 'A002'
// Dummy unit raw code
private constant integer DUMMYID = 'H000'
private constant attacktype ATT = ATTACK_TYPE_NORMAL
private constant damagetype DAM = DAMAGE_TYPE_NORMAL
// Affects the sound when damage is applied
private constant weapontype WEA = WEAPON_TYPE_METAL_HEAVY_BASH
// Delay between starting the hit animation and applying damage
private constant real DMGDELAY = 0.25
// Delay between applying damage and removing dummies
private constant real DEATHDELAY = 0.50
// Delay of dummy creation between creating copies
private constant real CREATEDELAY = 0.15
// How fast the animation plays
private constant real TIMESCALE = 1.50
// Dummy unit transparency. 0 = invisible, 255 = fully visible
private constant integer ALPHA = 127
// Attached to the the attackers
private constant string WPNEFF = "Abilities\\Weapons\\FaerieDragonMissile\\FaerieDragonMissile.mdl"
// Attached on the target
private constant string HITEFF = "Abilities\\Weapons\\GyroCopter\\GyroCopterImpact.mdl"
// Attach point of WPNEFF
private constant string HITATT = "weapon"
// Attach point of HITEFF
private constant string DMGATT = "chest"
private integer ANIMCOUNT
private integer array ANIMS
endglobals
// Configure the attack animation indexes an how many of the exist
private function SetAnims takes nothing returns nothing
set ANIMS[1] = 4
set ANIMS[2] = 5
set ANIMCOUNT = 2
endfunction
// How many times the ability hits
private constant function GetHits takes integer lvl returns integer
return 2 + lvl
endfunction
// Damage per hit
private constant function GetDamage takes integer lvl returns real
return 15. + lvl * 15.
endfunction
// Handles hit instances
private struct Hit
unit hit
unit caster
unit target
real dmg
real dmgdelay
real dur
timer tim
effect eff
boolean b
static method remove takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
if .b then
call RemoveUnit(.hit)
else
call SetUnitTimeScale(.hit, TIMESCALE)
endif
call DestroyEffect(.eff)
call ReleaseTimer(t)
call .destroy()
set t = null
endmethod
static method damage takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
call UnitDamageTarget(.caster, .target, .dmg, true, false, ATT, DAM, WEA)
call DestroyEffect(AddSpecialEffectTarget(HITEFF, .target, DMGATT))
call TimerStart(.tim, DEATHDELAY, false, function thistype.remove)
set t = null
endmethod
static method onHit takes boolean b, integer ind, unit caster, unit tar, real dmg, real x, real y returns nothing
local thistype this = thistype.allocate()
if b then
set .hit = CreateUnit(Player(15), DUMMYID, x, y, GetUnitFacing(caster))
call UnitApplyTimedLife(.hit, 1, 2)
call SetUnitColor(.hit, GetPlayerColor(GetOwningPlayer(caster)))
call SetUnitVertexColor(.hit, 255, 255, 255, ALPHA)
call SetUnitAnimationByIndex(.hit, ind)
else
set .hit = caster
endif
set .b = b
set .caster = caster
set .target = tar
set .dmg = dmg
set .tim = NewTimer()
call SetUnitTimeScale(.hit, TIMESCALE)
set .eff = AddSpecialEffectTarget(WPNEFF, .hit, HITATT)
call SetTimerData(.tim, this)
call TimerStart(.tim, DMGDELAY, false, function thistype.damage)
endmethod
endstruct
private struct LSL
real x
real y
real dmg
integer lvl
integer loops
integer maxLoops
unit caster
unit target
integer anim
timer tim
static method periodic takes nothing returns nothing
local timer t = GetExpiredTimer()
local thistype this = GetTimerData(t)
if not(IsUnitType(.caster, UNIT_TYPE_DEAD) or GetUnitTypeId(.caster) == 0) /*
*/ and not(IsUnitType(.target, UNIT_TYPE_DEAD) or GetUnitTypeId(.target) == 0) /*
*/ and .loops != maxLoops /*
*/ and .x == GetUnitX(.caster) /*
*/ and .y == GetUnitY(.caster) then
if .loops == 0 then
set bj_lastCreatedUnit = CreateUnit(Player(15), DUMMYID, .x, .y, GetUnitFacing(.caster))
call UnitApplyTimedLife(bj_lastCreatedUnit, 1, 1)
call SetUnitAbilityLevel(bj_lastCreatedUnit, DUMABILID, .lvl)
call IssueTargetOrder(bj_lastCreatedUnit, "thunderbolt", .target)
call ShowUnit(bj_lastCreatedUnit, false)
call Hit.onHit(false, .anim, .caster, .target, .dmg, .x, .y)
else
call Hit.onHit(true, .anim, .caster, .target, .dmg, .x, .y)
endif
set .loops = .loops + 1
else
call ReleaseTimer(t)
call .destroy()
endif
set t = null
endmethod
static method onCast takes nothing returns nothing
local thistype this = thistype.allocate()
set .lvl = GetUnitAbilityLevel(GetTriggerUnit(), ABILID)
set .tim = NewTimer()
set .loops = 0
set .maxLoops = GetHits(.lvl)
set .dmg = GetDamage(.lvl)
set .caster = GetTriggerUnit()
set .target = GetSpellTargetUnit()
set .x = GetUnitX(.caster)
set .y = GetUnitY(.caster)
set .anim = ANIMS[GetRandomInt(1, ANIMCOUNT)]
call SetTimerData(.tim, this)
call SetUnitAnimationByIndex(.caster, .anim)
call TimerStart(.tim, CREATEDELAY, true, function thistype.periodic)
endmethod
static method condition takes nothing returns boolean
if GetSpellAbilityId() == ABILID then
call thistype.onCast()
endif
return false
endmethod
private static method onInit takes nothing returns nothing
local trigger t = CreateTrigger()
call SetAnims()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddCondition(t, Condition(function thistype.condition))
set t = null
endmethod
endstruct
endscope
//TESH.scrollpos=180
//TESH.alwaysfold=0
library TimerUtils initializer init
//*********************************************************************
//* TimerUtils (red+blue+orange flavors for 1.24b+)
//* ----------
//*
//* To implement it , create a custom text trigger called TimerUtils
//* and paste the contents of this script there.
//*
//* To copy from a map to another, copy the trigger holding this
//* library to your map.
//*
//* (requires vJass) More scripts: htt://www.wc3c.net
//*
//* For your timer needs:
//* * Attaching
//* * Recycling (with double-free protection)
//*
//* set t=NewTimer() : Get a timer (alternative to CreateTimer)
//* ReleaseTimer(t) : Relese a timer (alt to DestroyTimer)
//* SetTimerData(t,2) : Attach value 2 to timer
//* GetTimerData(t) : Get the timer's value.
//* You can assume a timer's value is 0
//* after NewTimer.
//*
//* Multi-flavor:
//* Set USE_HASH_TABLE to true if you don't want to complicate your life.
//*
//* If you like speed and giberish try learning about the other flavors.
//*
//********************************************************************
//================================================================
globals
//How to tweak timer utils:
// USE_HASH_TABLE = true (new blue)
// * SAFEST
// * SLOWEST (though hash tables are kind of fast)
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = true (orange)
// * kinda safe (except there is a limit in the number of timers)
// * ALMOST FAST
//
// USE_HASH_TABLE = false, USE_FLEXIBLE_OFFSET = false (red)
// * THE FASTEST (though is only faster than the previous method
// after using the optimizer on the map)
// * THE LEAST SAFE ( you may have to tweak OFSSET manually for it to
// work)
//
private constant boolean USE_HASH_TABLE = false
private constant boolean USE_FLEXIBLE_OFFSET = true
private constant integer OFFSET = 0x100000
private integer VOFFSET = OFFSET
//Timers to preload at map init:
private constant integer QUANTITY = 256
//Changing this to something big will allow you to keep recycling
// timers even when there are already AN INCREDIBLE AMOUNT of timers in
// the stack. But it will make things far slower so that's probably a bad idea...
private constant integer ARRAY_SIZE = 8190
endglobals
//==================================================================================================
globals
private integer array data[ARRAY_SIZE]
private hashtable ht
endglobals
//It is dependent on jasshelper's recent inlining optimization in order to perform correctly.
function SetTimerData takes timer t, integer value returns nothing
static if(USE_HASH_TABLE) then
// new blue
call SaveInteger(ht,0,GetHandleId(t), value)
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-VOFFSET]=value
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
set data[GetHandleId(t)-OFFSET]=value
endif
endfunction
function GetTimerData takes timer t returns integer
static if(USE_HASH_TABLE) then
// new blue
return LoadInteger(ht,0,GetHandleId(t) )
elseif (USE_FLEXIBLE_OFFSET) then
// orange
static if (DEBUG_MODE) then
if(GetHandleId(t)-VOFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-VOFFSET]
else
// new red
static if (DEBUG_MODE) then
if(GetHandleId(t)-OFFSET<0) then
call BJDebugMsg("SetTimerData: Wrong handle id, only use SetTimerData on timers created by NewTimer")
endif
endif
return data[GetHandleId(t)-OFFSET]
endif
endfunction
//==========================================================================================
globals
private timer array tT[ARRAY_SIZE]
private integer tN = 0
private constant integer HELD=0x28829022
//use a totally random number here, the more improbable someone uses it, the better.
endglobals
//==========================================================================================
function NewTimer takes nothing returns timer
if (tN==0) then
//If this happens then the QUANTITY rule has already been broken, try to fix the
// issue, else fail.
debug call BJDebugMsg("NewTimer: Warning, Exceeding TimerUtils_QUANTITY, make sure all timers are getting recycled correctly")
static if( not USE_HASH_TABLE) then
debug call BJDebugMsg("In case of errors, please increase it accordingly, or set TimerUtils_USE_HASH_TABLE to true")
set tT[0]=CreateTimer()
static if( USE_FLEXIBLE_OFFSET) then
if (GetHandleId(tT[0])-VOFFSET<0) or (GetHandleId(tT[0])-VOFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
else
if (GetHandleId(tT[0])-OFFSET<0) or (GetHandleId(tT[0])-OFFSET>=ARRAY_SIZE) then
//all right, couldn't fix it
call BJDebugMsg("NewTimer: Unable to allocate a timer, you should probably set TimerUtils_USE_HASH_TABLE to true or fix timer leaks.")
return null
endif
endif
endif
else
set tN=tN-1
endif
call SetTimerData(tT[tN],0)
return tT[tN]
endfunction
//==========================================================================================
function ReleaseTimer takes timer t returns nothing
if(t==null) then
debug call BJDebugMsg("Warning: attempt to release a null timer")
return
endif
if (tN==ARRAY_SIZE) then
debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!")
//stack is full, the map already has much more troubles than the chance of bug
call DestroyTimer(t)
else
call PauseTimer(t)
if(GetTimerData(t)==HELD) then
debug call BJDebugMsg("Warning: ReleaseTimer: Double free!")
return
endif
call SetTimerData(t,HELD)
set tT[tN]=t
set tN=tN+1
endif
endfunction
private function init takes nothing returns nothing
local integer i=0
local integer o=-1
local boolean oops = false
static if( USE_HASH_TABLE ) then
set ht = InitHashtable()
loop
exitwhen(i==QUANTITY)
set tT[i]=CreateTimer()
call SetTimerData(tT[i], HELD)
set i=i+1
endloop
set tN = QUANTITY
else
loop
set i=0
loop
exitwhen (i==QUANTITY)
set tT[i] = CreateTimer()
if(i==0) then
set VOFFSET = GetHandleId(tT[i])
static if(USE_FLEXIBLE_OFFSET) then
set o=VOFFSET
else
set o=OFFSET
endif
endif
if (GetHandleId(tT[i])-o>=ARRAY_SIZE) then
exitwhen true
endif
if (GetHandleId(tT[i])-o>=0) then
set i=i+1
endif
endloop
set tN = i
exitwhen(tN == QUANTITY)
set oops = true
exitwhen not USE_FLEXIBLE_OFFSET
debug call BJDebugMsg("TimerUtils_init: Failed a initialization attempt, will try again")
endloop
if(oops) then
static if ( USE_FLEXIBLE_OFFSET) then
debug call BJDebugMsg("The problem has been fixed.")
//If this message doesn't appear then there is so much
//handle id fragmentation that it was impossible to preload
//so many timers and the thread crashed! Therefore this
//debug message is useful.
elseif(DEBUG_MODE) then
call BJDebugMsg("There were problems and the new timer limit is "+I2S(i))
call BJDebugMsg("This is a rare ocurrence, if the timer limit is too low:")
call BJDebugMsg("a) Change USE_FLEXIBLE_OFFSET to true (reduces performance a little)")
call BJDebugMsg("b) or try changing OFFSET to "+I2S(VOFFSET) )
endif
endif
endif
endfunction
endlibrary