| 08-24-2009, 11:28 PM | #1 | ||
The abilities in this pack are: Pounce:scope Pounce initializer Init //***************************************************************** //* spell - Pounce //* //* written by: Anitarf //* requires: -IsTerrainWalkable //* -IsUnitSpellResistant //* -xebasic //* -xedamage //* -ABuffStun //* -SpellEvent //* //* -a unit target channeling triggerer ability //* //* recommended: -BoundSentinel //* //* description: The hero rapidly moves towards the target enemy //* unit, stunning and damaging it upon impact and //* knocking it back. The code supports the hero's //* ability to be upgraded once with engineering //* upgrade. //* //* technical: The channeling ability should have a long duration //* set in the object editor because once the spell //* finishes, the code will interrupt the channeling //* anyway. //* //***************************************************************** globals private constant integer SPELL_ABILITY = 'A00A' // The main spell that runs this script when cast. private constant integer UPG_SPELL_ABILITY = 'A00C' // The upgraded main spell, if you don't need this feature simply set it to 0. private constant real UPG_DAMAGE_FACTOR = 2.0 // How much better the spell is when upgraded? private constant real UPG_DURATION_FACTOR = 2.0 private constant real LEAP_SPEED = 2000.0 // Movement speed when closing in on target. private constant real PUSH_SPEED = 1000.0 // Movement speed when pushing the target. private constant real SLOWDOWN_TIME = 0.25 // The time it should take for the caster and target to stop. private constant real HIT_RANGE = 100.0 // How far is the caster from the target's collision circle when it hits. private constant integer HIT_ANIMATION_ID = 0 // The animation the caster plays after it hits the target. endglobals private constant function PushDistance takes integer level returns real return 200.0+100.0*level // The distance the target is pushed after it is hit. endfunction private constant function Damage takes integer level returns real return 25.0+25.0*level // The damage dealt tot he target when it is hit. endfunction private constant function DurationHero takes integer level returns real return 1.0*level // Stun duration for heroes and resistant units. endfunction private constant function DurationUnit takes integer level returns real return 1.0*(1+level) // Stun duration for regular units. 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=ATTACK_TYPE_NORMAL set spellDamage.tag=0 // The tag attached to the damage by xedamage. endfunction // END OF CALIBRATION SECTION // ================================================================ globals private xedamage xed endglobals // Spell instance struct private struct si unit caster unit target integer level boolean impact boolean interrupted boolean upgraded real distance real x real y private integer index private static si array loopList private static integer loopCount=0 private static timer tim static method get takes unit u returns si // Since we can have at most one instance per caster // (this is a channeling spell) and since this is used // very rarely, an O(n) search is fine. local integer i=0 loop exitwhen i==si.loopCount if si.loopList[i].caster==u then return si.loopList[i] endif set i=i+1 endloop return 0 endmethod static method periodic takes nothing returns nothing local integer i=si.loopCount-1 local real tx local real ty local real dx local real dy local real ang local si this loop exitwhen i<0 set this=si.loopList[i] set tx=GetUnitX(.target) set ty=GetUnitY(.target) set dx=tx-.x set dy=ty-.y set ang=Atan2(dy,dx) if .impact then // Spell status is after impact. set dx=PUSH_SPEED*XE_ANIMATION_PERIOD*Cos(ang) set dy=PUSH_SPEED*XE_ANIMATION_PERIOD*Sin(ang) if .interrupted then // Spell is ending. set .distance=.distance-XE_ANIMATION_PERIOD/SLOWDOWN_TIME set dx=dx*.distance // We reuse the distance parameter as a factor for speed set dy=dy*.distance // for slowing down the movement when the spell finishes. if .distance<=0 then // Slowing down has finished. call .destroy() endif else // Spell is still in progress. set .distance=.distance-PUSH_SPEED*XE_ANIMATION_PERIOD if .distance<=0 then // Spell has reached the end of the linear sliding phase. if SLOWDOWN_TIME==0 then // If there's no slowdown then end the spell... call .destroy() else // ...else, begin slowdown. set .distance=1.0 call IssueImmediateOrder(.caster, "stop") set .interrupted=true endif endif endif if IsTerrainWalkable(tx+dx,ty+dy) then // Only keep moving if target is not blocked. call SetUnitX(.target, tx+dx) call SetUnitY(.target, ty+dy) if .interrupted then // If spell was finished then caster should be allowed to move. set .x=GetUnitX(.caster)+dx set .y=GetUnitY(.caster)+dy else set .x=.x+dx set .y=.y+dy endif // Move the caster. call SetUnitX(.caster, .x) call SetUnitY(.caster, .y) endif else // Spell status is before impact. set dx=LEAP_SPEED*XE_ANIMATION_PERIOD*Cos(ang) set dy=LEAP_SPEED*XE_ANIMATION_PERIOD*Sin(ang) if .interrupted then // Spell is ending, modify move speed. set .distance=.distance-XE_ANIMATION_PERIOD/SLOWDOWN_TIME set dx=dx*.distance // We reuse the distance parameter as a factor for speed set dy=dy*.distance // for slowing down the movement when the spell finishes. if .distance<=0 then // Slowing down has finished. call .destroy() endif set .x=GetUnitX(.caster)+dx set .y=GetUnitY(.caster)+dy else set .x=.x+dx set .y=.y+dy endif // Move the caster. call SetUnitX(.caster, .x) call SetUnitY(.caster, .y) if IsUnitInRangeXY(.target, .x,.y, HIT_RANGE) then // Impact. if this.upgraded then set tx=UPG_DAMAGE_FACTOR // Reuse target coordinate variables set ty=UPG_DURATION_FACTOR // as damage and stun multipliers. else set tx=1.0 set ty=1.0 endif call SetUnitAnimationByIndex(.caster, HIT_ANIMATION_ID) call xed.damageTarget(.caster, .target, Damage(.level)*tx) if IsUnitSpellResistant(.target) then call UnitStun(.target, .caster, DurationHero(.level)*ty) else call UnitStun(.target, .caster, DurationUnit(.level)*ty) endif set .impact=true endif endif set i=i-1 endloop endmethod method interrupt takes nothing returns nothing set .interrupted=true set .distance=1.0 // Reuse the distance member as a speed factor. endmethod static method create takes unit caster, integer level, boolean upgraded, unit target returns si local si this=si.allocate() // Setup spell instance data. set .caster=caster set .target=target set .level=level set .upgraded=upgraded set .impact=false set .interrupted=false set .distance=PushDistance(level)-SLOWDOWN_TIME*PUSH_SPEED/2 set .x=GetUnitX(caster) set .y=GetUnitY(caster) // Activate the loop timer if needed. if si.loopCount==0 then call TimerStart(si.tim, XE_ANIMATION_PERIOD, true, function si.periodic) endif // Add the instance to the loop list. set si.loopList[si.loopCount]=this set .index=si.loopCount set si.loopCount=si.loopCount+1 return this endmethod method onDestroy takes nothing returns nothing // Remove the instance from the list. set si.loopCount=si.loopCount-1 set si.loopList[this.index]=si.loopList[si.loopCount] set si.loopList[si.loopCount].index=this.index // If there are no more instances left, pause the timer. if si.loopCount==0 then call PauseTimer(si.tim) endif // If the channeling wasn't interrupted by the player, then stop it now. if not(this.interrupted) then call IssueImmediateOrder(this.caster, "stop") endif endmethod static method onInit takes nothing returns nothing // Create the main loop timer. set si.tim=CreateTimer() endmethod endstruct // ================================================================ private function SpellEffect takes nothing returns nothing local integer lvl = GetUnitAbilityLevel(SpellEvent.CastingUnit, SpellEvent.AbilityId) call si.create(SpellEvent.CastingUnit, lvl, SpellEvent.AbilityId==UPG_SPELL_ABILITY, SpellEvent.TargetUnit) endfunction private function SpellStop takes nothing returns nothing local si s = si.get(SpellEvent.CastingUnit) if s != 0 then // Initiate spell termination. call s.interrupt() endif endfunction private function Init takes nothing returns nothing // Initialize SpellEvent response functions. call RegisterSpellEffectResponse(SPELL_ABILITY, SpellEffect) call RegisterSpellEndCastResponse(SPELL_ABILITY, SpellStop) if UPG_SPELL_ABILITY != 0 then call RegisterSpellEffectResponse(UPG_SPELL_ABILITY, SpellEffect) call RegisterSpellEndCastResponse(UPG_SPELL_ABILITY, SpellStop) endif // Initialize xedamage. set xed=xedamage.create() call DamageOptions(xed) endfunction endscope Firestorm:scope Firestorm initializer Init //***************************************************************** //* spell - Firestorm //* //* written by: Anitarf //* requires: -TimerUtils //* -VectorLib //* -xebasic //* -xefx //* -xedamage //* -SpellEvent //* //* -an instant (no-target) channeling triggerer ability //* (the ability's "Art - Missile Art" is used for the projectile model) //* (the ability's "Art - Area Effect" is used when projectiles are launched) //* //* recommended: -BoundSentinel //* //* description: The hero unleashes a stream of projectiles that //* home in on random targets and deal damage to //* them when they hit. The projectiles move in a //* cool-looking spiraly motion. The code supports //* the hero's ability to be upgraded once with //* engineering upgrade. //* //* technical: The channeling ability should have a long duration //* set in the object editor because once the spell //* finishes, the code will interrupt the channeling //* anyway. //* //* The projectile movement is rather complex so //* having too many projectiles may cause performance //* issues on weaker computers (although having too //* many projectiles can be a problem anyway due to //* limitations of the wc3 graphics engine). //* //***************************************************************** globals private constant integer SPELL_ABILITY = 'A008' // The main spell that runs this script when cast. private constant integer UPG_SPELL_ABILITY = 'A00B' // The upgraded main spell, if you don't need this feature simply set it to 0. private constant real UPG_DAMAGE_FACTOR = 2.0 // How much better the spell is when upgraded? private constant real PROJECTILE_VELOCITY = 800 // The movement speed of the projectile. private constant real PROJECTILE_ROLL_RATE = 12 // Roll speed, radians per second. private constant real PROJECTILE_TURN_RATE = 12 // Max turn speed, radians per second. private constant real PROJECTILE_TURNING_START = 0.4 // The time it takes for the projectile to reach max turnspeed. private constant real PROJECTILE_COLLISION_SIZE = 32 // Size of the projectile. private constant real PROJECTILE_IMPACT_Z = 60 // The height at which the projectile hits the target. private constant real PROJECTILE_TARGET_HITSIZE = 120 // The vertical target size. private constant real PROJECTILE_LAUNCH_X = 0 // The launch offset of the projectile from the casting unit. private constant real PROJECTILE_LAUNCH_Y = 0 private constant real PROJECTILE_LAUNCH_Z = 120 private constant real PROJECTILE_LAUNCH_MINANGLE = -180 // The bounds for the projectile's starting direction, in degrees. private constant real PROJECTILE_LAUNCH_MAXANGLE = 180 private constant real PROJECTILE_LAUNCH_MINPITCH = 15 private constant real PROJECTILE_LAUNCH_MAXPITCH = 85 private constant real PROJECTILE_DURATION = 5.0 // The time it takes for a projectile to time out. private constant real PROJECTILE_ACQUIRE_RANGE = 600 // The radius of the area in which the projectile looks for new targets. private constant boolean PROJECTILE_ACQUIRES_NEW_TARGETS=true // If a projectile's target dies, does it pick a new one? endglobals private constant function SpellDuration takes integer level returns real return 3.0 // The duration of the spell. endfunction private constant function ProjectileCount takes integer level returns integer return 30 // The number of projectiles fired over the duration of the spell. endfunction private constant function Damage takes integer level returns real return 5.0+level*5.0 // Damage per projectile. endfunction private function DamageOptions takes xedamage spellDamage returns nothing // Useful read: [url]http://www.wc3c.net/showpost.php?p=1030046&postcount=19[/url] set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL set spellDamage.atype=ATTACK_TYPE_NORMAL set spellDamage.tag=0 // The tag attached to the damage by xedamage. set spellDamage.exception=UNIT_TYPE_STRUCTURE endfunction // END OF CALIBRATION SECTION // ================================================================ globals private location l = Location(0.0,0.0) endglobals private function GetTerrainZ takes real x, real y returns real call MoveLocation(l, x, y) return GetLocationZ(l) endfunction globals private xedamage xed endglobals // ================================================================ // Projectile struct private struct p private delegate xefx fx vector position vector facing vector right unit caster player owner real damage unit target real duration private integer index private static p array loopList private static integer loopCount=0 private static timer tim private static boolexpr bx private static group g private static integer picks private static p temp private static vector v private static method targetEnum takes nothing returns boolean if xed.allowedTarget(p.temp.caster, GetFilterUnit()) then if GetRandomInt(0, p.picks)==0 then set p.temp.target=GetFilterUnit() endif set p.picks=p.picks+1 endif return false endmethod private method acquireTarget takes nothing returns nothing // Pick a new random target. set p.picks=0 set p.temp=this set p.temp.target=null call GroupEnumUnitsInRange(p.g,p.temp.position.x,p.temp.position.y,PROJECTILE_ACQUIRE_RANGE,p.bx) if p.temp.target==null then // If no target was found then let the projectile time out. set p.temp.duration=PROJECTILE_DURATION endif endmethod private method impact takes nothing returns nothing call xed.damageTarget(.caster, .target, .damage) endmethod private static method periodic takes nothing returns nothing local integer i=p.loopCount-1 local real r loop exitwhen i<0 set p.temp=p.loopList[i] set p.temp.duration=p.temp.duration+XE_ANIMATION_PERIOD // Get turnrate factor. if p.temp.duration<PROJECTILE_TURNING_START then set r=p.temp.duration/PROJECTILE_TURNING_START else set r=1.0 endif // Switch targets if allowed/needed. if PROJECTILE_ACQUIRES_NEW_TARGETS and p.temp.target!=null and GetWidgetLife(p.temp.target)<0.405 then call p.temp.acquireTarget() endif // Projectile maneuvering. if p.temp.target!=null then // Get the vector between projectile and target. set p.v.x=GetUnitX(p.temp.target)-p.temp.position.x set p.v.y=GetUnitY(p.temp.target)-p.temp.position.y set p.v.z=GetTerrainZ(GetUnitX(p.temp.target), GetUnitY(p.temp.target))+GetUnitFlyHeight(p.temp.target)+PROJECTILE_IMPACT_Z-p.temp.position.z // Roll the projectile constantly, that's the way it moves. call p.temp.right.rotate(p.temp.facing, PROJECTILE_ROLL_RATE*XE_ANIMATION_PERIOD) // Turn the projectile based on target position. if vector.tripleProductScalar(p.temp.facing, p.temp.right, p.v)>0 then call p.temp.facing.rotate(p.temp.right, -PROJECTILE_TURN_RATE*r*XE_ANIMATION_PERIOD) else call p.temp.facing.rotate(p.temp.right, PROJECTILE_TURN_RATE*r*XE_ANIMATION_PERIOD) endif endif call p.temp.position.add(p.temp.facing) // Update xefx. set p.temp.x=p.temp.position.x set p.temp.y=p.temp.position.y set p.temp.z=p.temp.position.z-GetTerrainZ(p.temp.position.x, p.temp.position.y) set p.temp.xyangle=Atan2(p.temp.facing.y, p.temp.facing.x) set p.temp.zangle=Atan2(p.temp.facing.z, SquareRoot(p.temp.facing.x*p.temp.facing.x+p.temp.facing.y*p.temp.facing.y)) // Verify impact. if IsUnitInRangeXY(p.temp.target,p.temp.position.x,p.temp.position.y,PROJECTILE_COLLISION_SIZE) and (p.v.z>-PROJECTILE_TARGET_HITSIZE/2 and p.v.z<PROJECTILE_TARGET_HITSIZE/2) then call p.temp.impact() call p.temp.destroy() elseif p.temp.duration>=PROJECTILE_DURATION then call p.temp.destroy() endif set i=i-1 endloop endmethod static method create takes unit caster, real damage, vector position, real facing, real pitch returns p local p this=p.allocate() // Setup projectile data. set .caster=caster set .owner=GetOwningPlayer(caster) set .damage=damage set .duration=0.0 // Initialize xefx. set .fx=xefx.create(position.x,position.y,facing) set .z=position.z-GetTerrainZ(position.x, position.y) set .zangle=pitch set .fxpath=GetAbilityEffectById(SPELL_ABILITY, EFFECT_TYPE_MISSILE, 0) call .flash(GetAbilityEffectById(SPELL_ABILITY, EFFECT_TYPE_AREA_EFFECT, 0)) // Initialize vectors. set .position=vector.create(position.x, position.y, position.z) set .facing=vector.create(PROJECTILE_VELOCITY*XE_ANIMATION_PERIOD*Cos(pitch)*Cos(facing),PROJECTILE_VELOCITY*XE_ANIMATION_PERIOD*Cos(pitch)*Sin(facing),PROJECTILE_VELOCITY*XE_ANIMATION_PERIOD*Sin(pitch)) set .right=vector.create(128.0*Cos(pitch+bj_PI/2)*Cos(facing),128.0*Cos(pitch+bj_PI/2)*Sin(facing),128.0*Sin(pitch+bj_PI/2)) call .right.rotate(.facing, GetRandomReal(0,bj_PI*2)) // Get a random perpendicular vector. // Get a random target. call .acquireTarget() // Activate the loop timer if needed. if p.loopCount==0 then set p.tim=NewTimer() call TimerStart(p.tim, XE_ANIMATION_PERIOD, true, function p.periodic) endif // Add projectile to the loop list. set p.loopList[p.loopCount]=this set .index=p.loopCount set p.loopCount=p.loopCount+1 return this endmethod method onDestroy takes nothing returns nothing // Remove projectile from the list. set p.loopCount=p.loopCount-1 set p.loopList[this.index]=p.loopList[p.loopCount] set p.loopList[p.loopCount].index=this.index // Cleanup projectile vectors and the effect. call .position.destroy() call .facing.destroy() call .right.destroy() call .fx.destroy() // If there are no more projectiles left, pause the timer. if p.loopCount==0 then call ReleaseTimer(p.tim) endif endmethod static method onInit takes nothing returns nothing set p.g=CreateGroup() set p.bx=Condition(function p.targetEnum) set p.v=vector.create(0.0,0.0,0.0) endmethod endstruct // ================================================================ // Spell instance struct private struct si private timer t unit caster integer level integer projectiles boolean upgraded boolean interrupted private integer index private static si array loopList private static integer loopCount=0 private static vector launch static method get takes unit u returns si // Since we can have at most one instance per caster // (this is a channeling spell) and since this is used // very rarely, an O(n) search is fine. local integer i=0 loop exitwhen i==si.loopCount if si.loopList[i].caster==u then return si.loopList[i] endif set i=i+1 endloop return 0 endmethod static method update takes nothing returns nothing local si this=si(GetTimerData(GetExpiredTimer())) // Calculate the launch coordinates. local real a=GetUnitFacing(.caster)*bj_DEGTORAD set si.launch.x=GetUnitX(.caster)+Cos(a)*PROJECTILE_LAUNCH_X-Sin(a)*PROJECTILE_LAUNCH_Y set si.launch.y=GetUnitY(.caster)+Cos(a)*PROJECTILE_LAUNCH_Y+Sin(a)*PROJECTILE_LAUNCH_X set si.launch.z=GetTerrainZ(si.launch.x,si.launch.y)+PROJECTILE_LAUNCH_Z+GetUnitFlyHeight(.caster) // Create the projectile. if .upgraded then call p.create(.caster,Damage(.level)*UPG_DAMAGE_FACTOR, si.launch, GetRandomReal(PROJECTILE_LAUNCH_MINANGLE, PROJECTILE_LAUNCH_MAXANGLE)*bj_DEGTORAD, GetRandomReal(PROJECTILE_LAUNCH_MINPITCH, PROJECTILE_LAUNCH_MAXPITCH)*bj_DEGTORAD) else call p.create(.caster,Damage(.level), si.launch, GetRandomReal(PROJECTILE_LAUNCH_MINANGLE, PROJECTILE_LAUNCH_MAXANGLE)*bj_DEGTORAD, GetRandomReal(PROJECTILE_LAUNCH_MINPITCH, PROJECTILE_LAUNCH_MAXPITCH)*bj_DEGTORAD) endif set .projectiles=.projectiles-1 if .projectiles <=0 then call .destroy() endif endmethod static method create takes unit caster, integer level, boolean upgraded returns si local si this=si.allocate() // Setup spell instance data. set .caster=caster set .level=level set .upgraded=upgraded set .interrupted=false set .projectiles=ProjectileCount(level) // Start the loop timer of the instance. set .t=NewTimer() call SetTimerData(.t, integer(this)) call TimerStart(.t, SpellDuration(level)/ProjectileCount(level), true, function si.update) // Add the instance to the search list. set si.loopList[si.loopCount]=this set .index=si.loopCount set si.loopCount=si.loopCount+1 return this endmethod method onDestroy takes nothing returns nothing // Remove the instance from the list. set si.loopCount=si.loopCount-1 set si.loopList[this.index]=si.loopList[si.loopCount] set si.loopList[si.loopCount].index=this.index // Cleanup the loop timer. call ReleaseTimer(this.t) if not(this.interrupted) then // If the channeling wasn't interrupted by the player, then stop it now. call IssueImmediateOrder(this.caster, "stop") endif endmethod static method onInit takes nothing returns nothing set si.launch=vector.create(0.0,0.0,0.0) endmethod endstruct // ================================================================ private function SpellEffect takes nothing returns nothing local integer lvl = GetUnitAbilityLevel(SpellEvent.CastingUnit, SpellEvent.AbilityId) call si.create(SpellEvent.CastingUnit, lvl, SpellEvent.AbilityId==UPG_SPELL_ABILITY) endfunction private function SpellStop takes nothing returns nothing local si s = si.get(SpellEvent.CastingUnit) if s != 0 then // Terminate the spell. set s.interrupted=true call s.destroy() endif endfunction private function Init takes nothing returns nothing // Initialize SpellEvent response functions. call RegisterSpellEffectResponse(SPELL_ABILITY, SpellEffect) call RegisterSpellEndCastResponse(SPELL_ABILITY, SpellStop) if UPG_SPELL_ABILITY != 0 then call RegisterSpellEffectResponse(UPG_SPELL_ABILITY, SpellEffect) call RegisterSpellEndCastResponse(UPG_SPELL_ABILITY, SpellStop) endif // Initialize xedamage. set xed=xedamage.create() call DamageOptions(xed) endfunction endscope Soul Glaive:scope SoulGlaive initializer Init //***************************************************************** //* spell - Soul Glaive //* //* written by: Anitarf //* requires: -xebasic //* -xedamage //* -xefx //* -SpellEvent //* //* -a unit target triggerer ability //* (the ability's "Art - Missile Art" is used for the projectile model) //* (the ability's first "Art - Target" is used when hitting units without mana) //* (the ability's second "Art - Target" is used when hitting units with mana) //* //* recommended: -BoundSentinel //* //* description: Launches a projectile which approaches the target //* in a curved trajectory and when it hits, it deals //* damage and burns mana on the target and then //* bounces to new targets. //* //***************************************************************** globals private constant integer SPELL_ABILITY = 'A000' // The main spell that runs this script when cast. private constant integer UPG_SPELL_ABILITY = 'A00G' // The upgraded main spell, if you don't need this feature simply set it to 0. private constant real UPG_DAMAGE_FACTOR = 2.0 // How much better the spell is when upgraded? private constant real UPG_MANABURN_FACTOR = 2.0 private constant real PROJECTILE_VELOCITY = 800 // The movement speed of the projectile. private constant real PROJECTILE_COLLISION_SIZE = 48 // Size of the projectile. private constant real PROJECTILE_FACING_TWEAK_FACTOR = 45.0/1000.0 // The angle offset relative to the distance to target. (degrees per distance unit) private constant real PROJECTILE_IMPACT_Z = 60 // The height at which the projectile hits the target. private constant real PROJECTILE_LAUNCH_X = 0 // The launch offset of the projectile from the casting unit. private constant real PROJECTILE_LAUNCH_Y = 30 private constant real PROJECTILE_LAUNCH_Z = 60 private constant boolean CAN_BOUNCE_TO_SAME_TARGET = false // Can the spell bounce to the same target it just hit? private constant boolean CAN_BOUNCE_TO_PREVIOUS_TARGETS = false // Can the spell bounce to a target it had previously hit? // The spell chooses bounce targets based on their distance from the projectile; // among the valid targets in range it always bounces to the nearest one; // however, you can use these modifiers to make specific targets more or less preferable. private constant real DISTANCE_MODIFIER_MANA = -500.0 // Units with mana. private constant real DISTANCE_MODIFIER_SAME = 2000.0 // The unit it's bouncing from (only if CAN_BOUNCE_TO_SAME_TARGET is true). private constant real DISTANCE_MODIFIER_PREVIOUS = 1000.0 // Units it previously bounced from (only if CAN_BOUNCE_TO_PREVIOUS_TARGETS is true). endglobals private constant function TargetsHit takes integer level returns integer return level //number of targets the spell hits, including the first one endfunction private constant function Damage takes integer level returns real return (25.0 + 25.0*level) //how much base damage does the spell do per target endfunction private constant function ManaBurned takes integer level returns real return 50.0 //up to how much mana can the spell burn per target endfunction private constant function DamagePerManaPoint takes integer level returns real return 1.0 //bonus damage per mana point burned endfunction private constant function BounceRange takes integer level returns real return 500.0 //at what range does the spell acquire bounce targets endfunction private function DamageOptions takes xedamage spellDamage returns nothing // Useful read: [url]http://www.wc3c.net/showpost.php?p=1030046&postcount=19[/url] set spellDamage.dtype=DAMAGE_TYPE_UNIVERSAL set spellDamage.atype=ATTACK_TYPE_NORMAL set spellDamage.tag=0 // The tag attached to the damage by xedamage. set spellDamage.exception=UNIT_TYPE_STRUCTURE endfunction // END OF CALIBRATION SECTION // ================================================================ globals private xedamage xed endglobals // PruneGroup fitness function private keyword p private function Fitness takes unit u returns real local real x = GetUnitX(u)-p.temp.x local real y = GetUnitY(u)-p.temp.y local real fit = -SquareRoot(x*x+y*y) // Modify the fitness if the unit has mana. if GetUnitState(u, UNIT_STATE_MAX_MANA)>0.0 then set fit=fit-DISTANCE_MODIFIER_MANA endif // Modify the fitness if the unit was already hit. if u==p.temp.target then set fit=fit-DISTANCE_MODIFIER_SAME elseif IsUnitInGroup(u, p.g) then set fit=fit-DISTANCE_MODIFIER_PREVIOUS endif return fit endfunction // ================================================================ // Projectile struct private struct p private delegate xefx fx unit caster player owner integer level boolean upgraded unit target group affected integer hits private integer index private static p array loopList private static integer loopCount=0 private static timer tim private static boolexpr bx static group g static p temp private static method targetEnum takes nothing returns boolean local unit u=GetFilterUnit() local boolean b=xed.allowedTarget(p.temp.caster, u) and (CAN_BOUNCE_TO_SAME_TARGET or p.temp.target!=u) and (CAN_BOUNCE_TO_PREVIOUS_TARGETS or not(IsUnitInGroup(u, p.temp.affected))) set u=null return b endmethod private method acquireTarget takes nothing returns nothing set p.temp=this // Get all valid targets. call GroupEnumUnitsInRange(p.g,.x,.y,BounceRange(.level),p.bx) // Get the most suitable new target. call PruneGroup(p.g, Fitness, 1, NO_FITNESS_LIMIT) // Mark the current target as hit. call GroupAddUnit(.affected, .target) // Get the new target. set .target=FirstOfGroup(p.g) // Cleanup. call GroupClear(p.g) endmethod private method impact takes nothing returns nothing local real m local real r = 0.0 // Check if the unit should be manaburned. if GetUnitState(.target, UNIT_STATE_MAX_MANA)>0.0 then set m=GetUnitState(.target, UNIT_STATE_MANA) set r=ManaBurned(.level) if .upgraded then set r=r*UPG_MANABURN_FACTOR endif if r>m then set r=m endif call SetUnitState(.target, UNIT_STATE_MANA, m-r) set r=r*DamagePerManaPoint(.level) call DestroyEffect(AddSpecialEffectTarget(GetAbilityEffectById(SPELL_ABILITY, EFFECT_TYPE_TARGET, 1), .target, "origin")) else call DestroyEffect(AddSpecialEffectTarget(GetAbilityEffectById(SPELL_ABILITY, EFFECT_TYPE_TARGET, 0), .target, "origin")) endif // Deal the damage to the unit. if .upgraded then set r=r+(Damage(.level)*UPG_DAMAGE_FACTOR) else set r=r+Damage(.level) endif call xed.damageTarget(.caster, .target, r) endmethod private static method periodic takes nothing returns nothing local integer i=p.loopCount-1 local real dx local real dy local real dz local real angle local real pitch local real dist loop exitwhen i<0 set p.temp=p.loopList[i] // Get positional info relative to the target. set dx=GetUnitX(p.temp.target)-p.temp.x set dy=GetUnitY(p.temp.target)-p.temp.y set dz=GetUnitFlyHeight(p.temp.target)+PROJECTILE_IMPACT_Z-p.temp.z set angle=Atan2(dy,dx) set dist=SquareRoot(dx*dx+dy*dy) set pitch=Atan2(dz,dist) // Get the vertical distance to travel. if dist!=0.0 then set dz=dz*PROJECTILE_VELOCITY*XE_ANIMATION_PERIOD/dist else set dz=0.0 endif // Tweak the horizontal angle. set dist=dist*PROJECTILE_FACING_TWEAK_FACTOR*bj_DEGTORAD if (p.temp.hits/2)*2==p.temp.hits then set angle=angle+dist else set angle=angle-dist endif // Update xefx. set p.temp.x=p.temp.x+PROJECTILE_VELOCITY*XE_ANIMATION_PERIOD*Cos(angle) set p.temp.y=p.temp.y+PROJECTILE_VELOCITY*XE_ANIMATION_PERIOD*Sin(angle) set p.temp.z=p.temp.z+dz set p.temp.xyangle=angle set p.temp.zangle=pitch // Verify impact. if IsUnitInRangeXY(p.temp.target,p.temp.x,p.temp.y,PROJECTILE_COLLISION_SIZE) then call p.temp.impact() set p.temp.hits=p.temp.hits+1 if p.temp.hits>=TargetsHit(p.temp.level) then // If the spell finished then end it. call p.temp.destroy() else // If the spell has more targets to hit then choose the next target. call p.temp.acquireTarget() if p.temp.target==null then // If there is no valid target then abort. call p.temp.destroy() endif endif endif set i=i-1 endloop endmethod static method create takes unit caster, integer level, boolean upgraded, unit target returns p local p this=p.allocate() // Calculate launch coordinames and angle. local real a=GetUnitFacing(.caster)*bj_DEGTORAD local real x=GetUnitX(caster)+Cos(a)*PROJECTILE_LAUNCH_X-Sin(a)*PROJECTILE_LAUNCH_Y local real y=GetUnitY(caster)+Cos(a)*PROJECTILE_LAUNCH_Y+Sin(a)*PROJECTILE_LAUNCH_X local real dx=GetUnitX(target)-x local real dy=GetUnitY(target)-y set a=Atan2(dy,dx)+SquareRoot(dx*dx+dy*dy)*PROJECTILE_FACING_TWEAK_FACTOR*bj_DEGTORAD // Setup spell instance data. set .caster=caster set .owner=GetOwningPlayer(caster) set .target=target set .level=level set .upgraded=upgraded set .hits=0 // Initialize xefx. set .fx=xefx.create(x,y,a) set .z=PROJECTILE_LAUNCH_Z set .fxpath=GetAbilityEffectById(SPELL_ABILITY, EFFECT_TYPE_MISSILE, 0) // Activate the loop timer and create the group if needed. if p.loopCount==0 then call TimerStart(p.tim, XE_ANIMATION_PERIOD, true, function p.periodic) endif if .affected == null then set .affected=CreateGroup() endif // Add the instance to the loop list. set p.loopList[p.loopCount]=this set .index=p.loopCount set p.loopCount=p.loopCount+1 return this endmethod method onDestroy takes nothing returns nothing // Remove the instance from the list. set p.loopCount=p.loopCount-1 set p.loopList[this.index]=p.loopList[p.loopCount] set p.loopList[p.loopCount].index=this.index // Cleanup the spell's effect. call .fx.destroy() call GroupClear(.affected) // If there are no more instances left, pause the timer. if p.loopCount==0 then call PauseTimer(p.tim) endif endmethod static method onInit takes nothing returns nothing set p.tim=CreateTimer() set p.g=CreateGroup() set p.bx=Condition(function p.targetEnum) endmethod endstruct // ================================================================ private function SpellEffect takes nothing returns nothing local integer lvl = GetUnitAbilityLevel(SpellEvent.CastingUnit, SpellEvent.AbilityId) call p.create(SpellEvent.CastingUnit, lvl, SpellEvent.AbilityId==UPG_SPELL_ABILITY, SpellEvent.TargetUnit) endfunction private function Init takes nothing returns nothing // Initialize SpellEvent response function. call RegisterSpellEffectResponse(SPELL_ABILITY, SpellEffect) if UPG_SPELL_ABILITY != 0 then call RegisterSpellEffectResponse(UPG_SPELL_ABILITY, SpellEffect) endif // Initialize xedamage. set xed=xedamage.create() call DamageOptions(xed) endfunction endscope Elusive steed:scope ElusiveSteed initializer Init //***************************************************************** //* passive hero ability - Elusive Steed //* //* written by: Anitarf //* requires: -xepreload //* //* -a passive hero ability //* -a passive unit ability with the same number of levels //* -a spellbook ability that holds the one mentioned above //* //* -optional: an upgraded version of all these abilities //* //* description: When a hero learns this ability, the passive //* ability will be added to the hero as long as he is //* moving. The code supports the hero's ability to be //* upgraded once with engineering upgrade. //* //* technical: The spellbook-based abilities are required to hide //* the passive ability's icon. If your passive ability //* doesn't show an icon by default, you don't need the //* spellbook, however in that case you must set both //* the MOVEMENT_ABILITY and the MOVEMENT_ABILITY_HOLDER //* constants to point to your passive ability. //* //***************************************************************** globals private constant integer PASSIVE_ABILITY = 'A003' // The main ability that the hero learns. private constant integer MOVEMENT_ABILITY = 'A001' // The ability added while moving. private constant integer MOVEMENT_ABILITY_HOLDER = 'A002' // The spellbook holding the movement ability (to hide the icon). private constant integer UPG_PASSIVE_ABILITY = 'A00D' // The upgraded version of the hero ability, private constant integer UPG_MOVEMENT_ABILITY = 'A00E' // and the matching passive ability and it's spellbook, private constant integer UPG_MOVEMENT_ABILITY_HOLDER = 'A00F' // simply set these values to 0 if you don't need this feature. private constant real ABILITY_UPDATE_PERIOD = 0.2 // The rate at which the abilities are updated on the unit. endglobals // END OF CALIBRATION SECTION // ================================================================ //ability instance struct private struct ai unit u real x real y integer level boolean moving boolean upgraded private integer index private static ai array loopList private static integer loopCount=0 private static timer tim static method get takes unit u returns ai // Since we can have at most one instance per caster // (this is a passive ability) and since this is used // very rarely, an O(n) search is fine. local integer i=0 loop exitwhen i==ai.loopCount if ai.loopList[i].u==u then return ai.loopList[i] endif set i=i+1 endloop return 0 endmethod method levelup takes nothing returns nothing set .level=.level+1 if .moving then call SetUnitAbilityLevel(.u, MOVEMENT_ABILITY, .level) call SetUnitAbilityLevel(.u, UPG_MOVEMENT_ABILITY, .level) endif endmethod private static method addAbility takes unit u, integer spellbook, integer abil, integer lvl returns nothing call UnitAddAbility(u, spellbook) call UnitMakeAbilityPermanent(u, true, spellbook) call SetUnitAbilityLevel(u, abil, lvl) endmethod private static method periodic takes nothing returns nothing local integer i=ai.loopCount-1 local boolean b local real x local real y loop exitwhen i<0 set x=GetUnitX(ai.loopList[i].u) set y=GetUnitY(ai.loopList[i].u) set b=(ai.loopList[i].x!=x or ai.loopList[i].y!=y) set ai.loopList[i].x=x set ai.loopList[i].y=y if b!=ai.loopList[i].moving then // Change in movement status. set ai.loopList[i].moving=b if b then // Started moving. if ai.loopList[i].upgraded then // Add upgraded abilities. call ai.addAbility(ai.loopList[i].u, UPG_MOVEMENT_ABILITY_HOLDER, UPG_MOVEMENT_ABILITY, ai.loopList[i].level) else // Add unupgraded abilities. call ai.addAbility(ai.loopList[i].u, MOVEMENT_ABILITY_HOLDER, MOVEMENT_ABILITY, ai.loopList[i].level) endif else // Stopped moving. call UnitRemoveAbility(ai.loopList[i].u, MOVEMENT_ABILITY_HOLDER) call UnitRemoveAbility(ai.loopList[i].u, UPG_MOVEMENT_ABILITY_HOLDER) endif endif set b=GetUnitAbilityLevel(ai.loopList[i].u, UPG_PASSIVE_ABILITY)>0 if not b and GetUnitAbilityLevel(ai.loopList[i].u, PASSIVE_ABILITY)<=0 then // Ability has been removed. call ai.loopList[i].destroy() elseif ai.loopList[i].moving and b!=ai.loopList[i].upgraded then // Change in upgraded status. set ai.loopList[i].upgraded=b if b then // Ability has been upgraded. call UnitRemoveAbility(ai.loopList[i].u, MOVEMENT_ABILITY_HOLDER) call ai.addAbility(ai.loopList[i].u, UPG_MOVEMENT_ABILITY_HOLDER, UPG_MOVEMENT_ABILITY, ai.loopList[i].level) else // Ability has been downgraded. call UnitRemoveAbility(ai.loopList[i].u, UPG_MOVEMENT_ABILITY_HOLDER) call ai.addAbility(ai.loopList[i].u, MOVEMENT_ABILITY_HOLDER, MOVEMENT_ABILITY, ai.loopList[i].level) endif endif set i=i-1 endloop endmethod static method create takes unit u returns ai local ai this=ai.allocate() // Setup ability instance data. set .u=u set .x=GetUnitX(u) set .y=GetUnitY(u) set .level=1 set .moving=false set .upgraded=false // Activate the loop timer if needed. if ai.loopCount==0 then call TimerStart(ai.tim, ABILITY_UPDATE_PERIOD, true, function ai.periodic) endif // Add the instance to the loop list. set ai.loopList[ai.loopCount]=this set .index=ai.loopCount set ai.loopCount=ai.loopCount+1 return this endmethod method onDestroy takes nothing returns nothing // Remove the instance from the list. set ai.loopCount=ai.loopCount-1 set ai.loopList[this.index]=ai.loopList[ai.loopCount] set ai.loopList[ai.loopCount].index=this.index // Remove abilities from the unit if it has them. call UnitRemoveAbility(.u, MOVEMENT_ABILITY_HOLDER) call UnitRemoveAbility(.u, UPG_MOVEMENT_ABILITY_HOLDER) // If there are no more instances left, pause the timer. if ai.loopCount==0 then call PauseTimer(ai.tim) endif endmethod static method onInit takes nothing returns nothing set ai.tim=CreateTimer() endmethod endstruct // ================================================================ private function SpellLearn takes nothing returns boolean local ai a if GetLearnedSkill()==PASSIVE_ABILITY or (GetLearnedSkill()==UPG_PASSIVE_ABILITY and UPG_PASSIVE_ABILITY!=0) then set a=ai.get(GetTriggerUnit()) if a==0 then set a=ai.create(GetTriggerUnit()) else call a.levelup() endif endif return false endfunction private function Init takes nothing returns nothing // Initialize the trigger and hide the ability holders. local trigger t = CreateTrigger() local integer i=0 loop call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_HERO_SKILL, null) call SetPlayerAbilityAvailable(Player(i), MOVEMENT_ABILITY_HOLDER, false) call SetPlayerAbilityAvailable(Player(i), UPG_MOVEMENT_ABILITY_HOLDER, false) set i=i+1 exitwhen i>=12 endloop call TriggerAddCondition( t, Condition( function SpellLearn ) ) // Preload the abilities. call XE_PreloadAbility(MOVEMENT_ABILITY_HOLDER) call XE_PreloadAbility(UPG_MOVEMENT_ABILITY_HOLDER) endfunction endscope Hunting Grounds:scope HuntingGrounds initializer Init //***************************************************************** //* spell - Hunting Grounds //* //* written by: Anitarf //* requires: -TimerUtils //* -xepreload //* -SpellEvent //* //* -a point target triggerer ability //* (the ability's "Art - Area Effect" model is attached to the ward unit) //* -a passive unit ability with the same number of levels //* -a spellbook ability that holds the one mentioned above //* -a ward unit //* //* -optional: an ability to be added to the ward unit //* //* description: As long as the caster remains in the target area, //* he gets a passive ability. In addition, the ward //* unit in the center of the area may be given an //* ability such as an aura. //* //* technical: The spellbook-based abilities are required to hide //* the passive ability's icon. The ability that is //* added to the ward unit must be specified (instead //* of just adding it in the object editor) so that the //* code can match it's level to the spell's level, so //* you don't have to use a different ward unit for //* each level if you don't want to. //* //***************************************************************** globals private constant integer SPELL_ABILITY = 'A004' // The main spell that runs this script when cast. private constant integer AREA_ABILITY = 'A007' // The ability added to the dummy unit. private constant integer CASTER_ABILITY = 'A005' // The ability added to the caster when in range. private constant integer CASTER_ABILITY_HOLDER = 'A006' // The spellbook holding the caster ability (to hide the icon). private constant boolean PRELOAD_CASTER_ABILITY = false // Preloading engineering upgrade-based abilities crashes the game with current version of xepreload, so you can disable it here if you use such an ability. private constant real ABILITY_UPDATE_PERIOD = 0.2 // How often to update the abilities on the unit. endglobals private constant function WardUnit takes integer level returns integer return 'o000' // The unit to be created at the spell's target point. endfunction private constant function AuraRange takes integer level returns real return 1000.0 // The range in which the caster must be to get the caster ability. endfunction private constant function SpellDuration takes integer level returns real return 60.0 // The duration of the spell. endfunction // END OF CALIBRATION SECTION // ================================================================ // Spell caster struct private keyword si private struct sc unit u integer level si first private integer index private static sc array loopList private static integer loopCount=0 private static timer tim static method periodic takes nothing returns nothing local integer i=0 local si s local integer lvl loop exitwhen i>=sc.loopCount set s=sc.loopList[i].first set lvl=0 // Check if the unit is in range of any of it's spell instances. loop if IsUnitInRangeXY(s.caster, s.x,s.y, AuraRange(s.level)) and s.level>lvl then set lvl=s.level endif set s=s.next exitwhen s==0 endloop // Verify if in-range status has changed. if lvl!=sc.loopList[i].level then set sc.loopList[i].level=lvl if lvl>0 then call UnitAddAbility(sc.loopList[i].u, CASTER_ABILITY_HOLDER) call UnitMakeAbilityPermanent(sc.loopList[i].u, true, CASTER_ABILITY_HOLDER) call SetUnitAbilityLevel(sc.loopList[i].u, CASTER_ABILITY, lvl) else call UnitRemoveAbility(sc.loopList[i].u, CASTER_ABILITY_HOLDER) endif endif set i=i+1 endloop endmethod static method get takes unit u returns sc // Since this is used very rarely (only when new // spell instances are created), an O(n) search is fine. local integer i=0 loop exitwhen i==sc.loopCount if sc.loopList[i].u==u then return sc.loopList[i] endif set i=i+1 endloop return 0 endmethod static method create takes unit u returns sc local sc this=sc.allocate() set .u=u set .level=0 // Activate the loop timer if needed. if sc.loopCount==0 then set sc.tim=NewTimer() call TimerStart(sc.tim, ABILITY_UPDATE_PERIOD, true, function sc.periodic) endif // Add the unit to the loop list. set sc.loopList[sc.loopCount]=this set .index=sc.loopCount set sc.loopCount=sc.loopCount+1 return this endmethod method onDestroy takes nothing returns nothing // Remove the unit from the list. set sc.loopCount=sc.loopCount-1 set sc.loopList[this.index]=sc.loopList[sc.loopCount] set sc.loopList[sc.loopCount].index=this.index // If this is the last unit then pause the timer. if sc.loopCount==0 then call ReleaseTimer(sc.tim) endif // Remove the ability if needed. call UnitRemoveAbility(.u, CASTER_ABILITY_HOLDER) endmethod endstruct // ================================================================ // Spell instance struct private struct si unit caster integer level real x real y unit u effect e si next timer expiration static method expire takes nothing returns nothing call si(GetTimerData(GetExpiredTimer())).destroy() endmethod static method create takes unit caster, integer level, real x, real y returns si local sc c=sc.get(caster) local si this=si.allocate() // Setup ability instance data. set .caster=caster set .level=level set .x=x set .y=y // Initialize the ward unit. set .u=CreateUnit(GetOwningPlayer(caster), WardUnit(level), x,y,0.0) call UnitAddAbility(.u, AREA_ABILITY) call SetUnitAbilityLevel(.u, AREA_ABILITY, level) set .e=AddSpecialEffectTarget(GetAbilityEffectById(SPELL_ABILITY, EFFECT_TYPE_AREA_EFFECT, 0), .u, "origin") // Add the spell instance to the caster's linked list. if c==0 then set sc.create(caster).first=this set .next=0 else set .next=c.first set c.first=this endif // Start the spell's expiration timer. set .expiration=NewTimer() call SetTimerData(.expiration, integer(this)) call TimerStart(.expiration, SpellDuration(level), false, function si.expire) return this endmethod method onDestroy takes nothing returns nothing local sc c=sc.get(.caster) local si s=c.first // Recycle the spell's expiration timer. call ReleaseTimer(.expiration) // Cleanup the spell's effect. call DestroyEffect(.e) call KillUnit(.u) // Remove the spell instance from the caster's linked list. if s==this then set c.first=.next if c.first==0 then call c.destroy() endif else loop if s.next==this then set s.next=.next set .next=0 return endif set s=s.next endloop endif endmethod endstruct // ================================================================ private function SpellEffect takes nothing returns nothing local integer lvl = GetUnitAbilityLevel(SpellEvent.CastingUnit, SPELL_ABILITY) call si.create(SpellEvent.CastingUnit, lvl, SpellEvent.TargetX, SpellEvent.TargetY) endfunction private function Init takes nothing returns nothing // Hide the ability holders. local integer i=0 loop call SetPlayerAbilityAvailable(Player(i), CASTER_ABILITY_HOLDER, false) set i=i+1 exitwhen i>=12 endloop // Initialize SpellEvent response function. call RegisterSpellEffectResponse(SPELL_ABILITY, SpellEffect) // Preload the abilities. call XE_PreloadAbility(AREA_ABILITY) if PRELOAD_CASTER_ABILITY then call XE_PreloadAbility(CASTER_ABILITY_HOLDER) endif endfunction endscope The custom model for the hero was never finished, so this map includes a WIP version of it for the sake of completeness. |
| 08-25-2009, 02:41 PM | #2 |
I cannot believe I just spent half an hour looking over all that code. From what I can tell, it looks good and I didn't see any critical flaws. Furthermore, you are practically the paragon of portability and using standard libraries, so no worries there. The spells are also quite cool in and of themselves, and big packs make me happy as well. The only thing I dislike is that because I test maps on BNet, I wish people would add computer enemies to maps so that I can. :p There appears to be a bug with Hunting Grounds and Pounce. It doesn't actually stun for the listed 8s or deal 200 damage, it actually deals only 100 damage and a 4s stun. That should be fixed. You should check the other abilities, too, because I'm convinced none of them were working properly in Hunting Grounds except the evasion when moving (not the movement speed part, there's no way that was 90%) and the Soul Glaive ability. |
| 08-25-2009, 09:01 PM | #3 |
The speed bonus works, but due to the maximum WC3 speed limit it's effect is only noticeable if the unit is also affected by slowing spells. Pounce and Firestorm indeed didn't get the bonus because I completely forgot to set the appropriate struct member in their create methods. The silly mistake has been corrected. |
| 08-25-2009, 09:10 PM | #4 |
Ah yes, I was bouncing into the max MS. Good call. Anyways, after testing, it appears to work happily now and I'd already mentioned previously that the code looks great for all spells. Approved |
| 08-25-2009, 09:16 PM | #5 |
Heh, I just noticed another silly bug with Elusive Steed, it wouldn't work if you learned it while under the effect of Hunting Grounds, fixed that too. The spells should be perfect now! :) |
| 08-25-2009, 09:26 PM | #6 |
Oh, that's a bug I never noticed when testing. Guess that's my fault for auto learning stuff before testing it, hrm. |
