• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

Reversal Stance

"The caster assumes a counter stance for several seconds. If an enemy hero attacks the caster duing this time, the damage will be blocked and the caster will counter with a powerful shockwave."
Contents

Reversal Stance (Map)

Reviews
Antares
There's some issue with the animation getting cancelled prematurely if you get hit multiple times while it's still playing. Maybe you can improve that. A short explanation of how the special effect in the Channel ability works in your...
  • You have a mix of tab and eight-space indents in your code, which messes up its formatting in anything other than vanilla World Editor.
  • The animation for the counter action doesn't play when the SINGLE_COUNTER is set to true, because the hero will usually attack immediately after finishing the spell, overwriting the animation.
  • The config is spread throughout the code. If you make the CounterCondition into a regular function, you can put it at the top of the script below the globals and have the config separated from the rest of the script. You could also make the damage formula into a function that you put into the config as well.
  • The cooldown for the spell doesn't start when SINGLE_COUNTER is set to true and the first attack lands shortly after casting the spell.
  • Listing the dependencies in your description would be nice.
  • The spellcast could have a better visual representation. There's currently no spell effect or sound when you cast it. If the user is supposed to do that via the Channel ability, preset something that looks nice.
  • I don't quite understand why "Disable other abilities" is enabled in the Channel spell.

This could be a really cool spell, but it needs some bugs fixed first before it can be approved. Some more eye-candy would also be in order.

Awaiting Update
 
  • You have a mix of tab and eight-space indents in your code, which messes up its formatting in anything other than vanilla World Editor.
  • The animation for the counter action doesn't play when the SINGLE_COUNTER is set to true, because the hero will usually attack immediately after finishing the spell, overwriting the animation.
  • The config is spread throughout the code. If you make the CounterCondition into a regular function, you can put it at the top of the script below the globals and have the config separated from the rest of the script. You could also make the damage formula into a function that you put into the config as well.
  • The cooldown for the spell doesn't start when SINGLE_COUNTER is set to true and the first attack lands shortly after casting the spell.
  • Listing the dependencies in your description would be nice.
  • The spellcast could have a better visual representation. There's currently no spell effect or sound when you cast it. If the user is supposed to do that via the Channel ability, preset something that looks nice.
  • I don't quite understand why "Disable other abilities" is enabled in the Channel spell.

This could be a really cool spell, but it needs some bugs fixed first before it can be approved. Some more eye-candy would also be in order.

Awaiting Update
Updated based on review
 
The animation works now on SINGLE_COUNTER, but you're starting the timer that calls onTimerExpire only if SINGLE_COUNTER is enabled, so now the animation won't play if it isn't.

I fixed the indentation for you. Now it's all tabs and should work in any editor:

JASS:
scope ReversalStance
	globals
		private constant integer AID = 'A000' //Rawcode of the reversal stance ability
		private constant integer OID = 852600 //Order ID while reversal stance is active
		private constant real DISTANCE = 1500. //Distance the shockwave travels
		private constant real MISSILE_COLLISION = 200. //Collision radius of the shockwave
		private constant real MISSILE_FX_SCALE = 4.0 //Scale of the shockwave model
		private constant string COUNTER_FX_PATH = "Abilities\\Spells\\Human\\Defend\\DefendCaster.mdl" //Effect to play when an attack is countered
		private constant string MISSILE_FX_PATH = "Abilities\\Weapons\\GargoyleMissile\\GargoyleMissile.mdl" //Shockwave model
		private constant string ANIMATION = "attack slam" //Animation to play when an attack is countered
		private constant boolean SINGLE_COUNTER = true //Should the stance stop after countering an attack?
		private constant boolean FACE_ATTACKER = true //Should the caster turn to face the attacker when the counter occurs?
		private constant real ANIM_DURATION = 2.0 //Duration to play the counter animation
	endglobals
private struct Missile extends Missiles
	private timer timer
	private unit caster
	private boolean animresetphase

	private static method CounterCondition takes nothing returns boolean
		//Adjust your counter condition here. Freel free to modify
		if not udg_IsDamageAttack or GetUnitAbilityLevel(udg_DamageEventTarget, AID) == 0 or GetUnitCurrentOrder(udg_DamageEventTarget)!=OID then
			return false
		endif
		if IsUnitPaused(udg_DamageEventTarget) then
			return false
		endif
		return IsUnitType(udg_DamageEventSource,UNIT_TYPE_HERO)
	endmethod

	private static method DamageFormula takes nothing returns real
		return udg_DamageEventAmount * 3 //configure your damage formula here
	endmethod

	method onFinish takes nothing returns boolean	
		return true
	endmethod

	method onHit takes unit hit returns boolean
		if not IsUnitType(hit,UNIT_TYPE_STRUCTURE) and hit != .source and IsUnitEnemy(hit, .owner) and not BlzIsUnitInvulnerable(hit) and UnitAlive(hit) then
		call UnitDamageTarget(.source, hit, damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
		endif
		return false
	endmethod

	private static method onTimerExpire takes nothing returns nothing
		local thistype this = GetTimerData(GetExpiredTimer())
		if not animresetphase then
			set animresetphase = true
			call BlzUnitDisableAbility(caster,AID,false,false)
				call SetUnitAnimation(caster,ANIMATION)
			call TimerStart(timer, ANIM_DURATION, false, function thistype.onTimerExpire)
		else
			call ResetUnitAnimation(caster) 
			call ReleaseTimer(timer)
			call deallocate()
			set caster = null
			set timer  = null
		endif
	endmethod

	static method CounterAction takes nothing returns nothing
		local unit	 c = udg_DamageEventTarget
		local unit	 t = udg_DamageEventSource
		local real	 x = GetUnitX(c)
		local real	 y = GetUnitY(c)
		local real	 z = GetUnitFlyHeight(c) + 50
		local real 	 angle = AngleBetweenPointsReal(GetUnitX(c),GetUnitY(c),GetUnitX(t),GetUnitY(t))
		local real tx = x + DISTANCE * Cos(angle * bj_DEGTORAD)
		local real ty = y + DISTANCE * Sin(angle * bj_DEGTORAD)
		local thistype this = thistype.create(x, y, z, tx, ty, z)

		set animresetphase = false

		set source	= c
		set model	 = MISSILE_FX_PATH
		set speed	 = 2000
		set arc	   = 10
		set collision = MISSILE_COLLISION
		set owner = GetOwningPlayer(c)
		set scale= MISSILE_FX_SCALE
		set damage=DamageFormula()
		call launch()
		call DestroyEffect(AddSpecialEffectTarget(COUNTER_FX_PATH,c,"origin"))

		if BlzGetUnitAbilityCooldownRemaining(c,AID) <= 0 then
		call BlzStartUnitAbilityCooldown(c, AID, BlzGetUnitAbilityCooldown(c,AID,GetUnitAbilityLevel(c,AID)-1))
		endif

		static if FACE_ATTACKER then	
		call SetUnitFacing(c,angle)
		endif

		static if SINGLE_COUNTER then
		set .caster = c
		call BlzUnitDisableAbility(c,AID,true,false)
		set timer  = NewTimerEx(this)
		call TimerStart(timer, 0.125, false, function thistype.onTimerExpire)
		endif

		set udg_DamageEventAmount = 0.0
		set udg_DamageEventWeaponT=udg_WEAPON_TYPE_NONE
		set udg_DamageEventArmorT=udg_ARMOR_TYPE_NONE
		set c = null
		set t = null
	endmethod

	private static method onInit takes nothing returns nothing
		local trigger t= CreateTrigger()
		call TriggerRegisterVariableEvent(t, "udg_PreDamageEvent", EQUAL, 1.00)
		call TriggerAddCondition(t,Condition(function thistype.CounterCondition))
		call TriggerAddAction(t,function thistype.CounterAction)
		set t=null
	endmethod
endstruct
endscope
 
The animation works now on SINGLE_COUNTER, but you're starting the timer that calls onTimerExpire only if SINGLE_COUNTER is enabled, so now the animation won't play if it isn't.

I fixed the indentation for you. Now it's all tabs and should work in any editor:

JASS:
scope ReversalStance
    globals
        private constant integer AID = 'A000' //Rawcode of the reversal stance ability
        private constant integer OID = 852600 //Order ID while reversal stance is active
        private constant real DISTANCE = 1500. //Distance the shockwave travels
        private constant real MISSILE_COLLISION = 200. //Collision radius of the shockwave
        private constant real MISSILE_FX_SCALE = 4.0 //Scale of the shockwave model
        private constant string COUNTER_FX_PATH = "Abilities\\Spells\\Human\\Defend\\DefendCaster.mdl" //Effect to play when an attack is countered
        private constant string MISSILE_FX_PATH = "Abilities\\Weapons\\GargoyleMissile\\GargoyleMissile.mdl" //Shockwave model
        private constant string ANIMATION = "attack slam" //Animation to play when an attack is countered
        private constant boolean SINGLE_COUNTER = true //Should the stance stop after countering an attack?
        private constant boolean FACE_ATTACKER = true //Should the caster turn to face the attacker when the counter occurs?
        private constant real ANIM_DURATION = 2.0 //Duration to play the counter animation
    endglobals
private struct Missile extends Missiles
    private timer timer
    private unit caster
    private boolean animresetphase

    private static method CounterCondition takes nothing returns boolean
        //Adjust your counter condition here. Freel free to modify
        if not udg_IsDamageAttack or GetUnitAbilityLevel(udg_DamageEventTarget, AID) == 0 or GetUnitCurrentOrder(udg_DamageEventTarget)!=OID then
            return false
        endif
        if IsUnitPaused(udg_DamageEventTarget) then
            return false
        endif
        return IsUnitType(udg_DamageEventSource,UNIT_TYPE_HERO)
    endmethod

    private static method DamageFormula takes nothing returns real
        return udg_DamageEventAmount * 3 //configure your damage formula here
    endmethod

    method onFinish takes nothing returns boolean   
        return true
    endmethod

    method onHit takes unit hit returns boolean
        if not IsUnitType(hit,UNIT_TYPE_STRUCTURE) and hit != .source and IsUnitEnemy(hit, .owner) and not BlzIsUnitInvulnerable(hit) and UnitAlive(hit) then
        call UnitDamageTarget(.source, hit, damage, true, false, ATTACK_TYPE_CHAOS, DAMAGE_TYPE_UNIVERSAL, null)
        endif
        return false
    endmethod

    private static method onTimerExpire takes nothing returns nothing
        local thistype this = GetTimerData(GetExpiredTimer())
        if not animresetphase then
            set animresetphase = true
            call BlzUnitDisableAbility(caster,AID,false,false)
                call SetUnitAnimation(caster,ANIMATION)
            call TimerStart(timer, ANIM_DURATION, false, function thistype.onTimerExpire)
        else
            call ResetUnitAnimation(caster)
            call ReleaseTimer(timer)
            call deallocate()
            set caster = null
            set timer  = null
        endif
    endmethod

    static method CounterAction takes nothing returns nothing
        local unit     c = udg_DamageEventTarget
        local unit     t = udg_DamageEventSource
        local real     x = GetUnitX(c)
        local real     y = GetUnitY(c)
        local real     z = GetUnitFlyHeight(c) + 50
        local real      angle = AngleBetweenPointsReal(GetUnitX(c),GetUnitY(c),GetUnitX(t),GetUnitY(t))
        local real tx = x + DISTANCE * Cos(angle * bj_DEGTORAD)
        local real ty = y + DISTANCE * Sin(angle * bj_DEGTORAD)
        local thistype this = thistype.create(x, y, z, tx, ty, z)

        set animresetphase = false

        set source    = c
        set model     = MISSILE_FX_PATH
        set speed     = 2000
        set arc       = 10
        set collision = MISSILE_COLLISION
        set owner = GetOwningPlayer(c)
        set scale= MISSILE_FX_SCALE
        set damage=DamageFormula()
        call launch()
        call DestroyEffect(AddSpecialEffectTarget(COUNTER_FX_PATH,c,"origin"))

        if BlzGetUnitAbilityCooldownRemaining(c,AID) <= 0 then
        call BlzStartUnitAbilityCooldown(c, AID, BlzGetUnitAbilityCooldown(c,AID,GetUnitAbilityLevel(c,AID)-1))
        endif

        static if FACE_ATTACKER then   
        call SetUnitFacing(c,angle)
        endif

        static if SINGLE_COUNTER then
        set .caster = c
        call BlzUnitDisableAbility(c,AID,true,false)
        set timer  = NewTimerEx(this)
        call TimerStart(timer, 0.125, false, function thistype.onTimerExpire)
        endif

        set udg_DamageEventAmount = 0.0
        set udg_DamageEventWeaponT=udg_WEAPON_TYPE_NONE
        set udg_DamageEventArmorT=udg_ARMOR_TYPE_NONE
        set c = null
        set t = null
    endmethod

    private static method onInit takes nothing returns nothing
        local trigger t= CreateTrigger()
        call TriggerRegisterVariableEvent(t, "udg_PreDamageEvent", EQUAL, 1.00)
        call TriggerAddCondition(t,Condition(function thistype.CounterCondition))
        call TriggerAddAction(t,function thistype.CounterAction)
        set t=null
    endmethod
endstruct
endscope
Fixed again
 
There's some issue with the animation getting cancelled prematurely if you get hit multiple times while it's still playing. Maybe you can improve that. A short explanation of how the special effect in the Channel ability works in your documentation/description would be nice, since that's quite unintuitive with the Art Duration field. No reason to keep this pending though.

Approved
 
Top