library ZSlash requires UnitIndexer, SpellEffectEvent, IsTerrainWalkable
//====================================================================================================================================
//
// By Dr.Killer
//
//====================================================================================================================================
//====================================================================================================================================
// Introduction: The hero launches himself forward, slashing enemies along his path and wounding them. Then he proceeds to slash
// more enemies in a Z pattern.
//====================================================================================================================================
//====================================================================================================================================
// Requirements:
//
// - Table (Bribe's version)
// http:// ??
//
// - UnitIndexer
// http://www.hiveworkshop.com/forums/jass-resources-412/system-unit-indexer-172090/
//
// - SpellEffectEvent
// http://www.hiveworkshop.com/forums/jass-resources-412/snippet-spelleffectevent-187193/?highlight=spelleffect
//
// The following libraries are required by the above systems:
//
// - RegisterPlayerUnitEvent
// http://hiveworkshop.com/forums/showthread.php?t=203338
//
// - Event
// http://hiveworkshop.com/forums/submissions-414/snippet-event-186555/
//
// - WorldBounds
// http://hiveworkshop.com/forums/jass-functions-413/snippet-worldbounds-180494/
//====================================================================================================================================
//====================================================================================================================================
// Credits
//
// - Vexorian for TimerUtils, JassHelper and contributions to WC3 modding in general
// - Nestharus for UnitIndexer, Event, WorldBounds :|
// - Magtheridon96 for his awesome MeatMash spell from which I learned a lot (and copied a bit) and RegisterPlayerUnitEvent, also his tutorial
// "efficient and clean vJass spell models", which can be found at: http://www.hiveworkshop.com/forums/jass-ai-scripts-tutorials-280/efficient-clean-vjass-spell-models-216166/
// - Bribe for SpellEffectEvent, Table (New version)
// - Maker for a few suggestions, found the reason of a weird bug and MADE ME use
// linked lists
// - Almia for some other suggestions, such as making more things configurable.
//
//====================================================================================================================================
//====================================================================================================================================
// Important Notice
//
// Feel free to edit, modify, revise or anything that comes to your mind, just remember to credit me as the original creator
// wherever you use this.
//====================================================================================================================================
//=============================================================
// Configuration
//=============================================================
globals
//Editor stuff
private constant integer ABIL_CODE = 'A001'
//Spell stuff
private constant integer TARGET_LIMIT = 0 //Should the maximum number of slashed enemies be limited? if yes, set th limit here,
//otherwise leave it to be 0.
private constant boolean LIMIT_INJURY = true //Should a victim be able to be damaged more than once?
private constant boolean INV = true //Should the caster turn invulnerable during the spell?
private constant boolean PAN = true //Should I pan the camera to hero's location in the end?
private constant real JUMP_DIST = 450. //How far the hero launches himself forward (Affects the size of Z)
private constant real Z_ANGLE = (bj_PI) / 4. //The angle between Z's horizontal and diagonal line. Best looking at Pi/4.
private constant real DUR = 1.5 //How long it takes the hero to reach the end of each line in Z
private constant real INTERVAL = 0.03125 //Interval between hero's movements. A lower amount results in a smooth
//movement but is more demanding.
private constant real D_JUMP_DIST = JUMP_DIST/Sin(Z_ANGLE)
private constant real TRAVEL_1 = JUMP_DIST / (DUR/INTERVAL)
private constant real TRAVEL_2 = D_JUMP_DIST / (DUR/INTERVAL)
private constant real PATH_OFFSET = 32. //Used when the hero gets stuck somewhere in the end of the spell.
//That's how much the hero's moved to find a walkable ground
private constant real RADIUS = 95. //How far from the caster are the targets picked to be slashed
private constant real ADD_ANGLE = bj_PI/36.
private constant real CONE_ANGLE = (bj_PI/6.)
private constant integer RED = 255
private constant integer GREEN = 255
private constant integer BLUE = 255
private constant integer ALPHA = 130
private constant attacktype ATTACK_TYPE = ATTACK_TYPE_HERO
private constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_NORMAL
private constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
private constant string EFFECT_PATH = "Abilities\\Weapons\\PhoenixMissile\\Phoenix_Missile_mini.mdl"
private constant string BLOOD_EFFECT_PATH = "Objects\\Spawnmodels\\Human\\HumanBlood\\HumanBloodFootman.mdl"
private constant string ATTACH_POINT = "chest"
endglobals
//How much damage is dealt upon a slash
private function DamageOnSlash takes integer lvl returns real
return I2R(lvl*100)
endfunction
//Here you can decide what units will be affected by this spell
private function TargetFilter takes unit target, unit caster , integer slashed returns boolean
static if LIMIT_INJURY then
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not (IsUnitType(target, UNIT_TYPE_DEAD) or IsUnitType(target, UNIT_TYPE_MECHANICAL) or IsUnitType(target, UNIT_TYPE_FLYING)) and slashed != 1
else
return IsUnitEnemy(target, GetOwningPlayer(caster)) and not (IsUnitType(target, UNIT_TYPE_DEAD) or IsUnitType(target, UNIT_TYPE_MECHANICAL) or IsUnitType(target, UNIT_TYPE_FLYING))
endif
endfunction
//=============================================================
// End of Configuration
//=============================================================
//=============================================================
// Spell Code
//=============================================================
private function IsLineWalkable takes real x, real y, real ang, real dist, real remainingDist returns boolean
local real nx = x
local real ny = y
local real cos = Cos(ang)
local real sin = Sin(ang)
local boolean break = false
local integer i = R2I(remainingDist/dist) + 2
local integer j = 1
loop
exitwhen j>i or break
set nx = nx+dist*cos
set ny = ny+dist*sin
break = not IsTerrainWalkable(nx,ny)
set j=j+1
endloop
return (not break)
endfunction
private struct Z extends array
private static integer array rn
private static integer ic = 0
private static integer rc = 0
private static integer array next
private static integer array prev
private static unit array hero
private static real array totalTravel
private static integer array travelStage
private static real array angle
private static effect array sfx
private static real array dx
private static real array dy
private static boolean haveTimer = false
private static boolean array break
private static boolean array finished
private static real array aqq
private static Table array injured
private static group tempGroup //Used for unit enumeration
private static timer globalTimer
private method destroy takes nothing returns nothing
local real hero_X = GetUnitX(hero[this])
local real hero_Y = GetUnitY(hero[this])
call SetUnitInvulnerable(hero[this], false)
if GetLocalPlayer() == GetOwningPlayer(hero[this]) then
call SelectUnit(hero[this], true)
if PAN then
call PanCameraTo(hero_X, hero_Y)
endif
endif
call DestroyEffect(sfx[this])
call SetUnitVertexColor(hero[this], 255, 255, 255, 255)
call SetUnitAcquireRange(hero[this], aqq[this])
//Cleanup
set hero[this] = null
set sfx[this] = null
set dx[this] = 0.
set dy[this] = 0.
set aqq[this] = 0.
set break[this] = false
set finished[this] = true
call injured[this].flush()
//Deallocate this instance
set rn[this] = rn[0]
set rn[0] = this
set prev[next[this]] = prev[this]
set next[prev[this]] = next[this]
set rc = rc-1
if rc==0 then
call PauseTimer(globalTimer)
endif
endmethod
private static method onPeriod takes nothing returns nothing
local thistype this = next[0]
local real x
local real y
local real hero_X
local real hero_Y
local real rem
local unit tempUnit
local integer target_id = 0
//Iterate over all spell instances that are active
loop
exitwhen this == 0
set hero_X = GetUnitX(hero[this])
set hero_Y = GetUnitY(hero[this])
if hero[this] != null and not break[this] and not (IsUnitType(hero[this], UNIT_TYPE_DEAD)) then
if travelStage[this] == 1 then //We're running along the first line
//call BJDebugMsg("ic: "+I2S(ic))
if totalTravel[this] < JUMP_DIST then
set x = hero_X + dx[this]
set y = hero_Y + dy[this]
if not IsLineWalkable(hero_X, hero_Y, angle[this], PATH_OFFSET, 150.) then
set break[this] = true
endif
call SetUnitX(hero[this], x)
call SetUnitY(hero[this], y)
set totalTravel[this] = totalTravel[this] + (JUMP_DIST / (DUR/INTERVAL))
if TARGET_LIMIT == 0 then
call GroupEnumUnitsInRange(tempGroup, x, y, RADIUS, null)
else
call GroupEnumUnitsInRangeCounted(tempGroup, x, y, RADIUS, null, TARGET_LIMIT+1)
endif
call GroupRemoveUnit(tempGroup, hero[this])
loop
set tempUnit = FirstOfGroup(tempGroup)
exitwhen tempUnit == null
set target_id = GetUnitId(tempUnit)
if TargetFilter(tempUnit, hero[this], injured[this][target_id]) then
call UnitDamageTarget(hero[this], tempUnit, DamageOnSlash(GetUnitAbilityLevel(hero[this], ABIL_CODE)), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT_PATH, tempUnit, ATTACH_POINT))
set injured[this][target_id] = 1
endif
call GroupRemoveUnit(tempGroup, tempUnit)
endloop
else
set totalTravel[this] = 0.
set travelStage[this] = travelStage[this] + 1
set angle[this] = angle[this] + bj_PI + Z_ANGLE
set dx[this] = TRAVEL_2*Cos(angle[this])
set dy[this] = TRAVEL_2*Sin(angle[this])
//call BJDebugMsg("first ende")
endif
elseif travelStage[this] == 2 then
if totalTravel[this] < D_JUMP_DIST then
set x = hero_X + dx[this]
set y = hero_Y + dy[this]
if not IsLineWalkable(hero_X, hero_Y, angle[this], PATH_OFFSET, 150.) then
set break[this] = true
endif
call SetUnitX(hero[this], x)
call SetUnitY(hero[this], y)
set totalTravel[this] = totalTravel[this] + (D_JUMP_DIST / (DUR/INTERVAL))
if TARGET_LIMIT == 0 then
call GroupEnumUnitsInRange(tempGroup, x, y, RADIUS, null)
else
call GroupEnumUnitsInRangeCounted(tempGroup, x, y, RADIUS, null, TARGET_LIMIT+1)
endif
call GroupRemoveUnit(tempGroup, hero[this])
loop
set tempUnit = FirstOfGroup(tempGroup)
exitwhen tempUnit == null
set target_id = GetUnitId(tempUnit)
if TargetFilter(tempUnit, hero[this], injured[this][target_id]) then
call UnitDamageTarget(hero[this], tempUnit, DamageOnSlash(GetUnitAbilityLevel(hero[this], ABIL_CODE)), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT_PATH, tempUnit, ATTACH_POINT))
set injured[this][target_id] = 1
endif
call GroupRemoveUnit(tempGroup, tempUnit)
endloop
//call BJDebugMsg("this: "+I2S(this))
else
set totalTravel[this] = 0.
set travelStage[this] = travelStage[this] + 1
set angle[this] = angle[this] - bj_PI - Z_ANGLE
set dx[this] = TRAVEL_1*Cos(angle[this])
set dy[this] = TRAVEL_1*Sin(angle[this])
//call BJDebugMsg("second ende")
endif
else
//Third travel stage
if totalTravel[this] < JUMP_DIST then
set x = hero_X + dx[this]
set y = hero_Y + dy[this]
if not IsLineWalkable(hero_X, hero_Y, angle[this], PATH_OFFSET, 150.) then
set break[this] = true
endif
call SetUnitX(hero[this], x)
call SetUnitY(hero[this], y)
set totalTravel[this] = totalTravel[this] + (JUMP_DIST / (DUR/INTERVAL))
if TARGET_LIMIT == 0 then
call GroupEnumUnitsInRange(tempGroup, x, y, RADIUS, null)
else
call GroupEnumUnitsInRangeCounted(tempGroup, x, y, RADIUS, null, TARGET_LIMIT+1)
endif
call GroupRemoveUnit(tempGroup, hero[this])
loop
set tempUnit = FirstOfGroup(tempGroup)
exitwhen tempUnit == null
set target_id = GetUnitId(tempUnit)
if TargetFilter(tempUnit, hero[this], injured[this][target_id]) then
call UnitDamageTarget(hero[this], tempUnit, DamageOnSlash(GetUnitAbilityLevel(hero[this], ABIL_CODE)), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT_PATH, tempUnit, ATTACH_POINT))
set injured[this][target_id] = 1
endif
call GroupRemoveUnit(tempGroup, tempUnit)
endloop
//call BJDebugMsg("this: "+I2S(this))
else
call this.destroy()
endif
endif
elseif not finished[this] then
//Spell has ended
call this.destroy()
endif
set this = next[this]
endloop
//Cleanup Section
set tempUnit = null
endmethod
private static method onCast takes nothing returns nothing
local unit u = GetTriggerUnit()
local thistype this = rn[0]
local real tx = GetSpellTargetX()
local real ty = GetSpellTargetY()
local real cx //These are used for geometric calculations and determining the path of hero
local real cy //They actually help draw my Z
local integer lvl = GetUnitAbilityLevel(u, ABIL_CODE)
local integer target_id = 0
if this == 0 then
set ic = ic + 1
set this = ic
else
set rn[0] = rn[this]
endif
set next[this] = 0
set prev[this] = prev[0]
set next[prev[0]] = this
set prev[0] = this
set rc = rc+1
set injured[this] = Table.create()
set break[this] = false
set finished[this] = false
set hero[this] = u
set cx = GetUnitX(u)
set cy = GetUnitY(u)
set angle[this] = Atan2(ty-cy, tx-cx)
set totalTravel[this] = 0.
set travelStage[this] = 1
set aqq[this] = GetUnitAcquireRange(u)
call SetUnitAcquireRange(u, 1.)
set u = null
set dx[this] = TRAVEL_1*Cos(angle[this])
set dy[this] = TRAVEL_1*Sin(angle[this])
//Apply invunerability
if INV then
call SetUnitInvulnerable(hero[this], true)
endif
//Make hero transparent
call SetUnitVertexColor(hero[this], RED, GREEN, BLUE, ALPHA)
//Apply eyecandy
set sfx[this] = AddSpecialEffectTarget(EFFECT_PATH, hero[this], ATTACH_POINT)
//De-select the hero
if GetLocalPlayer() == GetOwningPlayer(hero[this]) then
call SelectUnit(hero[this], false)
endif
//First damage nearby enemies
if TARGET_LIMIT == 0 then
call GroupEnumUnitsInRange(tempGroup, cx, cy, RADIUS, null)
else
call GroupEnumUnitsInRangeCounted(tempGroup, cx, cy, RADIUS, null, TARGET_LIMIT+1)
endif
call GroupRemoveUnit(tempGroup, hero[this])
loop
set u = FirstOfGroup(tempGroup)
exitwhen u==null
set target_id = GetUnitId(u)
if TargetFilter(u, hero[this], 0) then
call UnitDamageTarget(hero[this], u, DamageOnSlash(GetUnitAbilityLevel(hero[this], ABIL_CODE)), false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE)
call DestroyEffect(AddSpecialEffectTarget(BLOOD_EFFECT_PATH, u, ATTACH_POINT))
set injured[this][target_id] = 1
endif
call GroupRemoveUnit(tempGroup, u)
endloop
//Now we start the Stage 1 timer that moves our hero every INTEVAL seconds along the top most line of Z
if rc == 1 then
call TimerStart(globalTimer, INTERVAL, true, function thistype.onPeriod)
endif
//Cleanup
set u = null
endmethod
private static method onInit takes nothing returns nothing
set globalTimer = CreateTimer()
set tempGroup = CreateGroup()
call RegisterSpellEffectEvent(ABIL_CODE, function thistype.onCast)
endmethod
endstruct
endlibrary