- Joined
- May 11, 2008
- Messages
- 1,198
update: using rain of fire as base ability seems to resolve the issue
this spell razor gale. i like it tons. but i had a fun idea for making a double of a hero go around doing the things the hero did.
basically everything the hero does, the double will do. movement, attack, spell casting, all automatic. but razor gale does not get casted. can you figure out why and how to fix it?
warden spins around jumping past units, damaging them, that's what happens. it's all fine and dandy except when i want my dummy unit to cast the spell at the exact same time. i haven't tried adding a delay to the automatic action to see if that would help. maybe it would...regardless it's odd because other spells including channeling based custom spells work.
this spell razor gale. i like it tons. but i had a fun idea for making a double of a hero go around doing the things the hero did.
basically everything the hero does, the double will do. movement, attack, spell casting, all automatic. but razor gale does not get casted. can you figure out why and how to fix it?
warden spins around jumping past units, damaging them, that's what happens. it's all fine and dandy except when i want my dummy unit to cast the spell at the exact same time. i haven't tried adding a delay to the automatic action to see if that would help. maybe it would...regardless it's odd because other spells including channeling based custom spells work.
JASS:
scope RazorGalefudo initializer Init
// ****************************************************************
// spell - Razor Gale
//
// written by: Anitarf
// requires: -xebasic
// -xedamage
// -TimerUtils
//
// -a point and/or unit target channeling triggerer ability
//
// description: The casting unit glides towards a target point as
// long as it continues channeling or until it
// reaches the target point or max distance and
// deals damage to enemy units along the way.
//
// technical: The spell is designed specifically for the Warden
// model. It also disables spell sounds for the
// duration of the spell because the sound attached to
// the Warden's spell slam animation is too annoying
// otherwise. If a custom warden model is used with
// this sound removed, then the sound disabling
// feature of this spell may be turned off.
//
// ****************************************************************
globals
//Spell settings
private constant integer SPELL_ABILITY = 'ARz2' //the spell ability
private constant real SPELL_PERIOD = XE_ANIMATION_PERIOD //the speed of the periodic timer that moves the caster
private constant integer DAMAGE_PERIOD_FACTOR = 3 //optimization option, every how many periods are new targets affected
//Animation options
private constant real ANIM_SPEED_START = 1.2 //the animation speed at the start of the spell
private constant real ANIM_SPEED_END = 0.9 //the animation speed at the end of the spell
private constant boolean DISABLE_SPELL_SOUND = false //may be set to false with a custom warden model
endglobals
//spell stats
private constant function GlideSpeed takes integer level returns real
return 750.0 //the distance that the caster travles per second
endfunction
private constant function GlideMaxDistance takes integer level returns real
return 1000.0 //what is the furthest the caster can glide
endfunction
private constant function GlideMinDistance takes integer level returns real
return 1000.0 //if the target point is closer than this, the caster will still glide this far
endfunction
private constant function Damage takes integer level returns real
return 250.0*level
endfunction
private constant function DamageRadius takes integer level returns real
return 160.0
endfunction
private function DamageOptions takes xedamage spellDamage returns nothing
//useful read: [url]http://www.wc3campaigns.net/showpost.php?p=1030046&postcount=19[/url]
set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL
set spellDamage.atype=PureWarriorAttack
set spellDamage.tag=0 //the tag attached to the damage by xedamage
set spellDamage.exception=UNIT_TYPE_STRUCTURE //deal no damage to structures
endfunction
// END OF CALIBRATION SECTION
// ================================================================
//Sliding and damage
private keyword instance
globals
private xedamage xed
private timer slide
private instance array instances
private integer instanceCount = 0
private group tempg = CreateGroup()
private boolexpr tempbx
private real tempx
private real tempy
private instance tempsi
endglobals
private function Targets takes nothing returns boolean
local unit u=GetFilterUnit()
if not(IsUnitInGroup(u, tempsi.affected)) and IsUnitInRangeXY(u, tempx, tempy, DamageRadius(tempsi.level)) then
call GroupAddUnit(tempsi.affected, u)
set u=null
return true
endif
set u=null
return false
endfunction
private function Periodic takes nothing returns nothing
local integer i=0
loop
exitwhen i>=instanceCount
set tempsi = instances[i]
set tempx = GetUnitX(tempsi.caster)+tempsi.dx
set tempy = GetUnitY(tempsi.caster)+tempsi.dy
set tempsi.time=tempsi.time-SPELL_PERIOD
if tempsi.time>0.0 and IsTerrainWalkable(tempx,tempy) then
call SetUnitX(tempsi.caster, tempx)
call SetUnitY(tempsi.caster, tempy)
if tempsi.whenToDamage <= 0 then
set tempsi.whenToDamage=DAMAGE_PERIOD_FACTOR
call GroupEnumUnitsInRange(tempg,tempx,tempy,DamageRadius(tempsi.level) + XE_MAX_COLLISION_SIZE , tempbx)
call xed.damageGroup(tempsi.caster, tempg, Damage(tempsi.level)) //empties the group
endif
set tempsi.whenToDamage=tempsi.whenToDamage-1
set i=i+1
else //if instance is on the list it should still be active so it's safe to call finish
call tempsi.finish()
endif
endloop
endfunction
// ================================================================
//Animations
private function Animation_Finish takes nothing returns nothing
local instance si=instance(GetTimerData(GetExpiredTimer()))
call SetUnitTimeScale(si.caster, 1.0)
call si.destroy() //this will release the expired timer, so no need to do it here
endfunction
private function Animation_Child takes nothing returns nothing
local instance si=instance(GetTimerData(GetExpiredTimer()))
call SetUnitFlyHeight(si.caster, 0.0, 48.0/0.125*si.timescale)
call ReleaseTimer(GetExpiredTimer())
endfunction
private function Animation takes nothing returns nothing
local instance si=instance(GetTimerData(GetExpiredTimer()))
local timer t
if si.active then //go into next animation cycle
set t = NewTimer()
call SetTimerData(t, integer(si))
call SetUnitTimeScale(si.caster, si.timescale)
call SetUnitAnimationByIndex(si.caster, 6)
call SetUnitFlyHeight(si.caster, 48.0, 48.0/0.125*si.timescale)
call TimerStart(t, 0.125/si.timescale, false, function Animation_Child) //run secondary function in this interval
call TimerStart(GetExpiredTimer(), 0.35/si.timescale, false, function Animation) //run next interval
set si.timescale=si.timescale+si.dtimescale*0.35/si.timescale //how the time scale changes during the interval
else //let the animation finish
call SetUnitTimeScale(si.caster, 1.5)
call TimerStart(GetExpiredTimer(), 0.45, false, function Animation_Finish) //allow animation to finish
endif
endfunction
// ================================================================
//Spell instance
private struct instance
private integer index
private timer t //this timer needs to be on a per-caster basis
real timescale
real dtimescale
boolean active = true
boolean interrupted = false
integer whenToDamage = DAMAGE_PERIOD_FACTOR
unit caster
integer level
real dx
real dy
group affected
real time
static method create takes unit caster, integer level, real targetx, real targety returns instance
local instance si=instance.allocate()
local real distance
local real factor=1.0
call UnitAddAbility(caster, XE_HEIGHT_ENABLER)
call UnitRemoveAbility(caster, XE_HEIGHT_ENABLER)
set si.caster=caster
set si.level=level
set si.dx=targetx-GetUnitX(caster)
set si.dy=targety-GetUnitY(caster)
set distance = SquareRoot(si.dx*si.dx+si.dy*si.dy)+1.0 //to avoid division by zero later
if distance>GlideMaxDistance(level) then
set factor = GlideMaxDistance(level)/distance
elseif distance<GlideMinDistance(level) then
set factor = GlideMinDistance(level)/distance
endif
set si.time = factor*distance/GlideSpeed(level) //duration of the spell
set factor = factor/si.time*SPELL_PERIOD
set si.dx=si.dx*factor //distance traveled per interval
set si.dy=si.dy*factor
set si.timescale=ANIM_SPEED_START
set si.dtimescale=(ANIM_SPEED_END-ANIM_SPEED_START)/(GlideMaxDistance(level)/GlideSpeed(level)) //animation speed change per second
if si.affected == null then //create a group if this is the first time this instance id is used
set si.affected=CreateGroup()
endif
set si.t=NewTimer() //animation timer
call SetTimerData(si.t, integer(si))
call TimerStart(si.t, 0.0, false, function Animation)
//neccessary evil, the spell sounds like crap if the warden's animation sounds are allowed to play
if DISABLE_SPELL_SOUND then
call VolumeGroupSetVolume(SOUND_VOLUMEGROUP_SPELLS, 0.0)
endif
if instanceCount==0 then //slide timer
set slide=NewTimer()
call TimerStart(slide, SPELL_PERIOD, true, function Periodic)
endif
set instances[instanceCount]=si
set si.index=instanceCount
set instanceCount=instanceCount+1
return si
endmethod
static method get takes unit u returns instance
local integer i=0
loop
exitwhen i==instanceCount
if instances[i].caster==u then
return instances[i]
endif
set i=i+1
endloop
return 0
endmethod
method finish takes nothing returns nothing
set this.active=false //spell stopped, wait for animation to finish and then end it
set instanceCount=instanceCount-1
set instances[this.index]=instances[instanceCount]
set instances[instanceCount].index=this.index
if instanceCount==0 then
call ReleaseTimer(slide)
//end of evil
if DISABLE_SPELL_SOUND then
call VolumeGroupReset()
endif
endif
endmethod
method onDestroy takes nothing returns nothing
if not(this.interrupted) then //if the channeling wasn't interrupted by the player...
call IssueImmediateOrder(this.caster, "stop") //...then stop it now
endif
call ReleaseTimer(this.t)
call GroupClear(this.affected) //the next time this instance id is used we won't need to create a group
endmethod
endstruct
// ================================================================
private function SpellEffect takes nothing returns nothing
local integer lvl
local unit u
local location l
if GetSpellAbilityId() == SPELL_ABILITY then
set lvl =GetUnitAbilityLevel(GetTriggerUnit(), SPELL_ABILITY)
set u = GetSpellTargetUnit()
if u == null then
//point-target cast
set l = GetSpellTargetLoc()
call instance.create(GetTriggerUnit(), lvl, GetLocationX(l), GetLocationY(l))
call RemoveLocation(l)
set l = null
else
//unit-target cast
call instance.create(GetTriggerUnit(), lvl, GetUnitX(u), GetUnitY(u))
set u = null
endif
endif
endfunction
private function SpellStop takes nothing returns nothing
local instance si
if GetSpellAbilityId() == SPELL_ABILITY then
set si = instance.get(GetTriggerUnit())
if si != 0 then
call si.finish()
set si.interrupted=true
endif
endif
endfunction
private function Init takes nothing returns nothing
//init spellcast trigger
local trigger tr = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( tr, EVENT_PLAYER_UNIT_SPELL_EFFECT )
call TriggerAddAction( tr, function SpellEffect )
set tr = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ( tr, EVENT_PLAYER_UNIT_SPELL_ENDCAST )
call TriggerAddAction( tr, function SpellStop )
//init damage filter
set tempbx=Condition(function Targets)
//init xedamage
set xed=xedamage.create()
call DamageOptions(xed)
endfunction
endscope
Last edited: