I have been trying to create a spell, but I encountered a problem with transferring data and with the damage system, so I thought someone might help me.
For the spell I use the following libraries:
Description: on cast, the spell selects an enemy unit in front of the caster, prioritising the closest hero unit. If the target's hp is below a certain %, after a delay, the caster will leave a stamp from the attacking angle on the target. Otherwise, the spell will cause 0 damage and will display "miss". Overall, there are 6 possible angles to leave a stamp. The effect of the stamp moves with the target, attached to a certain angle. On the second stamp at the same angle, the caster instakills the target (if the hp is still below a certain %, otherwise, the attack misses again).
I encountered two problems:
(1) I can't transfer the stamp's integer along with the id of the class to the timer
(2) OnDamageAfter does not work for me for some reason. I want to use it to show "miss", hide the target and show a death effect
Any other suggestions are welcome.
Here is my code:
For the spell I use the following libraries:
- Nes DDS nestharus/JASS
- Spell framework Spell Framework [vJASS + GUI] v2.1.0
- Nes Unit indexer nestharus/JASS
- Table
- Buff System (by Flux) [vJASS] - Buff System
Description: on cast, the spell selects an enemy unit in front of the caster, prioritising the closest hero unit. If the target's hp is below a certain %, after a delay, the caster will leave a stamp from the attacking angle on the target. Otherwise, the spell will cause 0 damage and will display "miss". Overall, there are 6 possible angles to leave a stamp. The effect of the stamp moves with the target, attached to a certain angle. On the second stamp at the same angle, the caster instakills the target (if the hp is still below a certain %, otherwise, the attack misses again).
I encountered two problems:
(1) I can't transfer the stamp's integer along with the id of the class to the timer
(2) OnDamageAfter does not work for me for some reason. I want to use it to show "miss", hide the target and show a death effect
Any other suggestions are welcome.
Here is my code:
JASS:
library Suzumebachi /*
[I]/ uses /[/I]
[I]/ SpellFramework /[/I]
*/ BaseFunctions
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* SPELL CONFIGURATION *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
private module SpellConfiguration
static constant integer SPELL_ABILITY_ID = 'A00G'
static constant integer SPELL_EVENT_TYPE = EVENT_SPELL_EFFECT
static constant real SFX_DEATH_TIME = 1.50
static constant integer STACKS_GAIN_ON_CD = 1
static constant real SPELL_RANGE = 64
static constant real SPELL_AOE = 300
static constant attacktype ATTACK_TYPE = ATTACK_TYPE_MAGIC
static constant damagetype DAMAGE_TYPE = DAMAGE_TYPE_MAGIC
static constant weapontype WEAPON_TYPE = WEAPON_TYPE_WHOKNOWS
//Suz
static constant string SUZ_EFFECT_ID = "Abilities\\Weapons\\LordofFlameMissile\\LordofFlameMissile.mdl"
static constant string SUZ_DEATH_EFFECT_ID = "Abilities\\Spells\\Undead\\AnimateDead\\AnimateDeadTarget.mdl"
static constant real SUZ_DELAY = 0.5
static constant real SUZ_EFFECT_DIST = 100
static constant real SUZ_EFFECT_DEF_SCALE = 1.0
static constant real SUZ_EFFECT_KILL_SCALE = 2.0
static constant real SUZ_Z = 100
//Rangle of angles per side
static constant real ANGLE_FRONT = 210.0
static constant real ANGLE_SIDE_1 = 150.0
static constant real ANGLE_SIDE_2 = 90.0
static constant real ANGLE_BACK = 30.0
static constant real ANGLE_SIDE_3 = 330.0
static constant real ANGLE_SIDE_4 = 270.0
static constant integer INT_ANGLE_FRONT = 1
static constant integer INT_ANGLE_SIDE_1 = 2
static constant integer INT_ANGLE_SIDE_2 = 3
static constant integer INT_ANGLE_BACK = 4
static constant integer INT_ANGLE_SIDE_3 = 5
static constant integer INT_ANGLE_SIDE_4 = 6
static method Dmg takes integer level, unit caster returns real
return 50. + 0. * level + 0.1 * GetHeroStr(caster, true)
endmethod
static method szbDuration takes integer level returns real
return 5.0 + 0.0 * level
endmethod
static method FrontMaxHp takes unit caster returns real
return 50 + 0.1 * GetHeroStr(caster, true)
endmethod
static method SideMaxHp takes unit caster returns real
return 0.10 + 0.1 * GetHeroStr(caster, true)
endmethod
static method BackMaxHp takes unit caster returns real
return 0.25 + 0.1 * GetHeroStr(caster, true)
endmethod
static method StackCdTime takes integer level returns integer
return 3
endmethod
static method MaxStacks takes integer level returns integer
return 3 + level
endmethod
static method TargetsFilter takes unit target, unit caster returns boolean
return GetWidgetLife(target) > 0.405 and /*
[I]/ IsUnitEnemy(target, GetOwningPlayer(caster)) and /[/I]
[I]/ (IsUnitVisible(target, GetOwningPlayer(caster))) and /[/I]
[I]/ not IsUnitType(target, UNIT_TYPE_STRUCTURE) and /[/I]
*/ GetUnitAbilityLevel(GetFilterUnit(), 'Aloc') == 0
endmethod
endmodule
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* END OF SPELL CONFIGURATION *
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* == == == == == == == == == == == == = SPELL CODE == == == == == == == == == == == == = */
//Buff
struct SuzBuff extends Buff
private static constant integer RAWCODE = 'A016'
private static constant integer DISPEL_TYPE = BUFF_NONE
private static constant integer STACK_TYPE = BUFF_STACK_NONE
implement SpellConfiguration
private timer timer
private boolean array scaleChange[6]
private integer targetId
integer suzNum
static HashTable suzStacks
static HashTable suzEffect
//static HashTable suzDelayTimer
method effectMove takes integer suzNum returns nothing
local real tX = GetUnitX(this.target)
local real tY = GetUnitY(this.target)
local real effectAngle360 = GetUnitFacing(this.target) - 60 * (suzNum - 1)
local real moveX = GetXWithOffset(tX, SUZ_EFFECT_DIST, effectAngle360)
local real moveY = GetYWithOffset(tY, SUZ_EFFECT_DIST, effectAngle360)
local real tPercentHp = GetUnitStatePercentEx(this.target, UNIT_STATE_LIFE, UNIT_STATE_MAX_LIFE)
call SetEffectX(thistype.suzEffect[targetId].effect[suzNum], moveX)
call SetEffectY(thistype.suzEffect[targetId].effect[suzNum], moveY)
call SetEffectFacing(thistype.suzEffect[targetId].effect[suzNum], effectAngle360)
if (tPercentHp > thistype.suzStacks[targetId].real[suzNum] and this.scaleChange[suzNum]) then
call SetEffectScale(thistype.suzEffect[targetId].effect[suzNum], SUZ_EFFECT_DEF_SCALE)
set this.scaleChange[suzNum] = false
return
endif
if (tPercentHp <= thistype.suzStacks[targetId].real[suzNum] and not this.scaleChange[suzNum]) then
call SetEffectScale(thistype.suzEffect[targetId].effect[suzNum], SUZ_EFFECT_KILL_SCALE)
set this.scaleChange[suzNum] = true
return
endif
endmethod
static method onPeriod takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local integer tempInt = 1
loop
exitwhen tempInt > 6
if this.suzStacks[this.targetId].boolean[tempInt] then
call effectMove(tempInt)
endif
set tempInt = tempInt + 1
endloop
endmethod
static method onStampEnd takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local integer targetId = UnitIndex[this.target]
call DestroyEffect(thistype.suzEffect[targetId].effect[this.suzNum])
set thistype.suzStacks[targetId].boolean[this.suzNum] = false
call ReleaseTimer(thistype.suzStacks[targetId].timer[this.suzNum])
set thistype.suzStacks[targetId].timer[this.suzNum] = null
set thistype.suzEffect[targetId].effect[this.suzNum] = null
endmethod
static method onExpire takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
local real tX
local real tY
local real effectAngle360
local real moveX
local real moveY
local integer targetId
if GetWidgetLife(this.target) < 0.405 then
call ReleaseTimer(GetExpiredTimer())
return
endif
set tX = GetUnitX(this.target)
set tY = GetUnitY(this.target)
set effectAngle360 = GetUnitFacing(this.target) - 60 * (this.suzNum - 1)
set moveX = GetXWithOffset(tX, SUZ_EFFECT_DIST, effectAngle360)
set moveY = GetYWithOffset(tY, SUZ_EFFECT_DIST, effectAngle360)
set targetId = UnitIndex[this.target]
set thistype.suzStacks[targetId].boolean[this.suzNum] = true
set thistype.suzEffect[targetId].effect[this.suzNum] = AddSpecialEffect(SUZ_EFFECT_ID, moveX, moveY)
call SetEffectFacing(thistype.suzEffect[targetId].effect[this.suzNum], effectAngle360)
call SetEffectScale(thistype.suzEffect[targetId].effect[this.suzNum], SUZ_EFFECT_KILL_SCALE)
call SetEffectZ(thistype.suzEffect[targetId].effect[this.suzNum], SUZ_Z)
set this.scaleChange[this.suzNum] = true
set thistype.suzStacks[targetId].timer[this.suzNum] = NewTimerEx(this)
call TimerStart(thistype.suzStacks[targetId].timer[this.suzNum], (this.duration - SUZ_DELAY), false, function thistype.onStampEnd)
// debug call BJDebugMsg("[On Expire] this: " + I2S(this))
if this.timer == null then
set this.timer = NewTimerEx(this)
call TimerStart(this.timer, FPS, true, function thistype.onPeriod)
endif
call ReleaseTimer(GetExpiredTimer())
endmethod
method onRemove takes nothing returns nothing
//local integer sourceId = GetUnitId(source)
//ocal integer targetId = GetUnitId(target)
//call DestroyEffect(this.suzEffect)
//set SuzBuff.suzStacks[INT_ANGLE_FRONT].boolean[targetId] = true
local integer tempInt = 1
loop
exitwhen tempInt > 6
if this.suzStacks[this.targetId].boolean[tempInt] then
if GetWidgetLife(this.target) < 0.405 and thistype.suzStacks[this.targetId].timer[this.suzNum] != null then
call DestroyEffect(thistype.suzEffect[this.targetId].effect[this.suzNum])
call ReleaseTimer(thistype.suzStacks[this.targetId].timer[this.suzNum])
set thistype.suzStacks[this.targetId].timer[this.suzNum] = null
set thistype.suzEffect[this.targetId].effect[this.suzNum] = null
endif
endif
set tempInt = tempInt + 1
endloop
call ReleaseTimer(this.timer)
call thistype.suzStacks.remove(targetId)
call thistype.suzEffect.remove(targetId)
set this.timer = null
endmethod
method onApply takes nothing returns nothing
set this.targetId = UnitIndex[this.target]
call TimerStart(NewTimerEx(this), SUZ_DELAY, false, function thistype.onExpire)
endmethod
static method onInit takes nothing returns nothing
set thistype.suzStacks = HashTable.create()
set thistype.suzEffect = HashTable.create()
endmethod
implement BuffApply
endstruct
//Spell
globals
private group eG = CreateGroup()
private group hG = CreateGroup()
private group nonHG = CreateGroup()
endglobals
private struct Suzumebachi extends array
implement SpellConfiguration
private integer stacks
private integer maxStacks
private boolean isStackTimerOn
private timer t
private boolean hit
private static method onSpellStart takes nothing returns thistype
local unit closestUnit
local real cX
local real cY
local real targetX
local real targetY
local real angle
local unit u
local thistype this = GetUnitId(Spell.triggerUnit)
local real dmg
if (this.stacks == 0) then
debug call BJDebugMsg("[Suzumebachi] Not Enough Stacks. Stacks: " + I2S(this.stacks) )
return 0
endif
set this.stacks = this.stacks - 1
call SetCustomUnitAbilityCharges(Spell.triggerUnit, SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Suzumebachi] Number of Stacks Left " + I2S(this.stacks))
if not this.isStackTimerOn then
set this.t = NewTimerEx(this)
call TimerStart(this.t, StackCdTime(Spell.level), true, function thistype.StacksCd)
set this.isStackTimerOn = true
endif
set cX = GetUnitX(Spell.triggerUnit)
set cY = GetUnitY(Spell.triggerUnit)
set angle = GetAngle(cX, Spell.targetX, cY, Spell.targetY)
set targetX = GetXWithOffset(cX, SPELL_RANGE, angle)
set targetY = GetYWithOffset(cY, SPELL_RANGE, angle)
set dmg = Dmg(Spell.level, Spell.triggerUnit)
call GroupEnumUnitsInRange(eG, targetX, targetY, SPELL_AOE, null)
loop
set u = FirstOfGroup(eG)
exitwhen u == null
if (TargetsFilter(u, Spell.triggerUnit) and IsUnitType(u, UNIT_TYPE_HERO)) then
call GroupAddUnit(hG, u)
elseif (TargetsFilter(u, Spell.triggerUnit) and not IsUnitType(u, UNIT_TYPE_HERO)) then
call GroupAddUnit(nonHG, u)
endif
call GroupRemoveUnit(eG, u)
endloop
call GroupClear(eG)
if GetGroupUnitCount(hG) > 0 then
set closestUnit = GetClosestUnitInGroup(targetX, targetY, hG)
call thistype[UnitIndex[closestUnit]].enableDamageEventLocal()
call UnitDamageTarget(Spell.triggerUnit, closestUnit, dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
debug call BJDebugMsg("[Suzumebachi] A hero got damaged")
set closestUnit = null
elseif GetGroupUnitCount(nonHG) > 0 then
set closestUnit = GetClosestUnitInGroup(targetX, targetY, nonHG)
call thistype[UnitIndex[closestUnit]].enableDamageEventLocal()
call UnitDamageTarget(Spell.triggerUnit, closestUnit, dmg, false, false, ATTACK_TYPE, DAMAGE_TYPE, WEAPON_TYPE )
debug call BJDebugMsg("[Suzumebachi] Non hero unit got damaged")
set closestUnit = null
endif
call GroupClear(hG)
call GroupClear(nonHG)
return this
endmethod
private method onSpellEnd takes nothing returns nothing
if this.stacks == this.maxStacks then
set this.isStackTimerOn = false
call ReleaseTimer(this.t)
set this.t = null
elseif this.stacks == 0 then
call StartUnitAbilityCooldown(GetUnitById(this), SPELL_ABILITY_ID, TimerGetRemaining(this.t))
endif
endmethod
implement SpellEventEx
private static method onDamage takes nothing returns nothing
local real cFacing = GetUnitFacing(source)
local real tFacing = GetUnitFacing(target)
local real angleDiff = AngleDiff(cFacing, tFacing)
local integer spellLevel = GetUnitAbilityLevel(source, SPELL_ABILITY_ID)
local real tPercentHp = GetUnitStatePercentEx(target, UNIT_STATE_LIFE, UNIT_STATE_MAX_LIFE)
local real hpShotPercent
local SuzBuff szb
set thistype[sourceId].hit = false
debug call BJDebugMsg("[Suzumebachi] AngleDiff: " + R2S(angleDiff))
//Front Stamp
if (angleDiff <= ANGLE_FRONT and angleDiff > ANGLE_SIDE_1) then
set hpShotPercent = FrontMaxHp(source)
debug call BJDebugMsg("[Suzumebachi] hpShotPerecent: " + R2S(hpShotPercent))
debug call BJDebugMsg("[Suzumebachi] tPercentHp: " + R2S(tPercentHp))
if tPercentHp > hpShotPercent then
set damage = 0
return
endif
if not SuzBuff.suzStacks[targetId].boolean[INT_ANGLE_FRONT] then
debug call BJDebugMsg("[Suzumebachi] Buff Aplied ")
set szb = SuzBuff.add(source, target)
set szb.duration = szbDuration(spellLevel) + SUZ_DELAY
set szb.suzNum = INT_ANGLE_FRONT
set szb.suzStacks[targetId].real[INT_ANGLE_FRONT] = hpShotPercent
set thistype[sourceId].hit = true
return
endif
set thistype[sourceId].hit = true
set damage = 999999
return
endif
if (angleDiff <= ANGLE_SIDE_1 and angleDiff > ANGLE_SIDE_2) then
set hpShotPercent = FrontMaxHp(source)
debug call BJDebugMsg("[Suzumebachi] hpShotPerecent: " + R2S(hpShotPercent))
debug call BJDebugMsg("[Suzumebachi] tPercentHp: " + R2S(tPercentHp))
if tPercentHp > hpShotPercent then
set damage = 0
return
endif
if not SuzBuff.suzStacks[targetId].boolean[INT_ANGLE_FRONT] then
debug call BJDebugMsg("[Suzumebachi] Buff Aplied ")
set szb = SuzBuff.add(source, target)
set szb.duration = szbDuration(spellLevel) + SUZ_DELAY
set szb.suzNum = INT_ANGLE_SIDE_1
set szb.suzStacks[targetId].real[INT_ANGLE_SIDE_1] = hpShotPercent
set thistype[sourceId].hit = true
return
endif
set thistype[sourceId].hit = true
set damage = 999999
return
endif
//call thistype[sourceId].disableDamageEventLocalOutgoing()
endmethod
private static method onDamageAfter takes nothing returns nothing
local thistype this = sourceId
local texttag textTag
local string toDisplay
local real tX
local real tY
debug call BJDebugMsg("On Damage After WORKS ALOHA")
/*
if (disableDamageEventLocal()) then
return
endif
set tX = GetUnitX(target)
set tY = GetUnitY(target)
if this.hit then
call ShowUnit(target, true)
call DestroyEffect(AddSpecialEffect(SUZ_DEATH_EFFECT_ID, tX, tY))
return
endif
set textTag = CreateTextTag()
set toDisplay = "missed"
call SetTextTagPos(textTag, tX - 32, tY, 16)
call SetTextTagText(textTag, toDisplay, 10*.0023)
call SetTextTagVisibility(textTag, true)
call SetTextTagPermanent(textTag, false)
call SetTextTagVelocity(textTag, 0, .03)
call SetTextTagLifespan(textTag, 3)
set textTag = null
set toDisplay = null
call thistype[sourceId].enableDamageEventLocalOutgoing()
*/
endmethod
implement DDS
//Stacks Mechanics
static method StacksCd takes nothing returns nothing
local thistype this = GetTimerData(GetExpiredTimer())
if this.stacks < this.maxStacks then
set this.stacks = this.stacks + STACKS_GAIN_ON_CD
call SetCustomUnitAbilityCharges(GetUnitById(this), SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Suzumebachi] CD Finished: 1 Shunpo Stack Added")
else
debug call BJDebugMsg("[Suzumebachi] CD Finished: 1 Shunpo Stack Added")
set this.isStackTimerOn = false
call ReleaseTimer(this.t)
set this.t = null
endif
endmethod
private static method GiveShunpoStacks takes nothing returns nothing
local unit learner
local thistype this
if GetLearnedSkill() == SPELL_ABILITY_ID then
set learner = GetTriggerUnit()
set this = GetUnitId(learner)
set this.maxStacks = MaxStacks(GetUnitAbilityLevel(learner, SPELL_ABILITY_ID))
if GetUnitAbilityLevel(learner, SPELL_ABILITY_ID) == 1 then
call SetUnitAbilityCastpoint(learner, SPELL_ABILITY_ID, 0.3)
call InitCustomAbilityAddressChargesHook( GetUnitAbility(learner, SPELL_ABILITY_ID))
call EnableCustomUnitAbilityCharges(learner, SPELL_ABILITY_ID)
debug call BJDebugMsg("[Suzumebachi] Setup Finished")
set this.stacks = this.maxStacks
else
set this.stacks = this.stacks + 1
endif
call SetCustomUnitAbilityCharges(learner, SPELL_ABILITY_ID, this.stacks)
debug call BJDebugMsg("[Suzumebachi] Stacks " + I2S(this.stacks))
debug call BJDebugMsg("[Suzumebachi] Max Stacks " + I2S(this.maxStacks))
endif
endmethod
private static method onInit takes nothing returns nothing
call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_HERO_SKILL, function thistype.GiveShunpoStacks)
endmethod
endstruct
endlibrary