Spell Heroic Guard

Romek

Super Moderator
Reaction score
964
Heroic Guard
By Romek

Code: vJASS
MUI / MPI: MUI
Leaks: None
Description: Makes this unit take damage for the target unit. Damage will be reduced to 100/80/60%.
Lasts 30 seconds.
Icon:

Thanks to Grymlax!

Notes:
I've remade this spell since last time. I said I would over Christmas, but I didn't have time. :D
The old version used a timer per instance, and dynamic triggers.
This uses no dynamic triggers, and 2 timers altogether. :thup:

Screenshot:
HeroicGuard.png


The Code:
JASS:
scope HeroicGuard initializer Init // requires PUI
// ______________________________________________________________________
// +--------------------------------------------------------------------+
// |                    H E R O I C   G U A R D                         |
// |____________________________________________________________________|
// +--------------------------------------------------------------------+
// |                 -== By Romek -- Version 2.2 ==-                    |
// |____________________________________________________________________|
// +--------------------------------------------------------------------+
// |  2.0:                                                              |
// |   - Remade the entire spell so it uses no dynamic triggers         |
// |     and 2 timers                                                   |
// |                                                                    |
// |  2.1:                                                              |
// |   - Fixed a critical error caused by casting the spell on the      |
// |     same unit twice                                                |
// |                                                                    |
// |  2.2:                                                              |
// |   - Made the spell use index 0.  (Uberplayer complained)           |
// |   - Prevented a critical crash which occured when the spell was    |
// |     cast by 2 heroes at eachother.                                 |
// |____________________________________________________________________|
// +--------------------------------------------------------------------+
// |  -== C o n f i g u r a t i o n: ==-                                |
// +--------------------------------------------------------------------+
globals
    
    // The raw code of the ability
    private constant integer ID = 'A000'
    
    // How often the lightning will be moved, the duration will also be accurate to this amount
    private constant real TIMEOUT = 0.03125
        
    // The raw code ot the lightning
    private constant string LIGHTNING = "DRAL"
            
    // How high from the units 'base' the lightning will be.
    private constant real LIGHT_OFFSET = 50.
            
    // The RGBA values of the lightning. 1 = 100%. 0 = 0%
    private constant real L_R = 1. // Red
    private constant real L_G = 1. // Green
    private constant real L_B = 1. // Blue
    private constant real L_A = 0.2 // Alpha
    
    // Should the effects be preloaded?
    private constant boolean PRELOAD = true
        
    // The effect created on the caster when the spell is cast
    private constant string CAST_EFFECT_C = "Abilities\\Spells\\Orc\\Disenchant\\DisenchantSpecialArt.mdl"
    // ...Attached to the casters:
    private constant string CAST_EFFECT_C_ATTACH = "chest"
        
    // The effect created on the target when the spell is cast
    private constant string CAST_EFFECT_T = "Abilities\\Spells\\Orc\\Disenchant\\DisenchantSpecialArt.mdl"     
    // ...Attached to the targets:
    private constant string CAST_EFFECT_T_ATTACH = "chest"
        
    // The effect created on the caster when the target is damaged
    private constant string DAMAGE_EFFECT_C = "Abilities\\Spells\\Orc\\FeralSpirit\\feralspiritdone.mdl"
    // ...Attached to the casters:
    private constant string DAMAGE_EFFECT_C_ATTACH = "origin"
            
    // The effect created on the target when it is damaged
    private constant string DAMAGE_EFFECT_T = "Abilities\\Spells\\Undead\\ReplenishHealth\\ReplenishHealthCasterOverhead.mdl" 
    // ... Attached to the targets:
    private constant string DAMAGE_EFFECT_T_ATTACH = "overhead"
            
    // The raw code of an item ability which gives +999999 life
    private constant integer LIFEBONUS = 'A001'
                    
    // The attack, damage and weapon type of the damage done to the caster when the target is damaged
    private constant attacktype AT = ATTACK_TYPE_CHAOS // Attack type
    private constant damagetype DT = DAMAGE_TYPE_UNIVERSAL // Damage type
    private constant weapontype WT = WEAPON_TYPE_WHOKNOWS // Weapon type
    // Leaving these as the default values will ensure the caster takes the full damage the target did.
    // (Assuming MULTIPLIER returns 1.)
    
    // The message displayed to the owner of the caster when the target is already affected by the spell
    private constant string ERROR = "|cFFFFBF00This unit is already affected by Heroic Guard|r"
    
    // The message displayed when the target is already casting the spell
    private constant string ERRORB = "|cFFFFBF00This unit is currently casting Heroic Guard|r"
endglobals
    
    // What the damage is multiplied by before it is dealt to the caster.
    private constant function MULTIPLIER takes integer level returns real
        return 1 - ((level - 1) * 0.2) // 1, 0.8, 0.6, 0.4
        // Any lower should use '0.1', or IMaxBJ()
    endfunction
    
    // How long the spell lasts. Accurate to TIMEOUT.
    private constant function DURATION takes integer level returns real
        return 30.
    endfunction
    
    // The mana cost of the spell. This is used to restore mana if the target incase the spell fails.
    private constant function MANA takes integer level returns real
        return 150.
    endfunction
    
//  ____________________________________________________________________
// +--------------------------------------------------------------------+
// |        -== E n d   o f    C o n f i g u r a t i o n: ==-           |
// |____________________________________________________________________|
// +--------------------------------------------------------------------+
//            -== D O   N O T   T O U C H   B E L O W ==-              
        
    private keyword Data

    globals
        private trigger Trig = CreateTrigger()
        private timer T = CreateTimer()
        private Data array D
        private integer I = 0
        private group Registered = CreateGroup()
        private group Wanted = CreateGroup()
        private group Casters = CreateGroup()
        private Data array Z
        private timer X = CreateTimer()
        private integer Y = 0
    endglobals
    
    private struct Data
        //! runtextmacro PUI()
        unit caster
        unit target
        integer level
        lightning light
        
        real total
        real ticks = 0.
                
        real damage
        boolean abil
        
        method onDestroy takes nothing returns nothing
            call DestroyLightning(.light)
        endmethod
    endstruct
    
    private function HitEx takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i >= Y
                call SetWidgetLife(Z<i>.target, GetWidgetLife(Z<i>.target) + Z<i>.damage)
                if Z<i>.abil then
                    call UnitRemoveAbility(Z<i>.target, LIFEBONUS)
                    set Z<i>.abil = false
                endif
                set Z<i> = Z[Y]
                set Y = Y - 1
            set i = i + 1
        endloop
        if Y == 0 then
            call PauseTimer(X)
        endif
    endfunction
    
    private function Hit takes nothing returns nothing
        local Data d = Data[GetTriggerUnit()]
        set d.damage = GetEventDamage()
        call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT_T, d.target, DAMAGE_EFFECT_T_ATTACH))
        call DestroyEffect(AddSpecialEffectTarget(DAMAGE_EFFECT_C, d.caster, DAMAGE_EFFECT_C_ATTACH))
        if GetWidgetLife(d.target) - d.damage &lt; 1 then
            call UnitAddAbility(d.target, LIFEBONUS)
            set d.abil = true
        else
            set d.abil = false
        endif
        set Z[Y] = d
        set Y = Y + 1
        if Y == 1 then
            call TimerStart(X, 0., true, function HitEx)
        endif
        call UnitDamageTarget(GetEventDamageSource(), d.caster, d.damage*MULTIPLIER(d.level), false, true, AT, DT, WT)
    endfunction
    
    private function HitCond takes nothing returns boolean
        return IsUnitInGroup(GetTriggerUnit(), Wanted)
    endfunction
    
    private function Expire takes nothing returns nothing
        local integer i = 0
        loop
            exitwhen i &gt;= I
                set D<i>.ticks = D<i>.ticks + TIMEOUT
                if D<i>.ticks &gt;= D<i>.total or IsUnitType(D<i>.caster, UNIT_TYPE_DEAD) then
                    call D<i>.release()
                    call GroupRemoveUnit(Casters, D<i>.caster)
                    call GroupRemoveUnit(Wanted, D<i>.target)
                    set D<i> = D[I ]
                    set I = I - 1
                else
                    call MoveLightningEx(D<i>.light, true, GetUnitX(D<i>.caster), GetUnitY(D<i>.caster), GetUnitFlyHeight(D<i>.caster) + LIGHT_OFFSET, GetUnitX(D<i>.target), GetUnitY(D<i>.target), GetUnitFlyHeight(D<i>.target) + LIGHT_OFFSET)
                endif
            set i = i + 1
        endloop
        if I == 0 then
            call PauseTimer(T)
        endif
    endfunction
    
    private function SpellRestore takes unit u, integer level returns nothing
        call TriggerSleepAction(0.)
        call UnitRemoveAbility(u, ID)
        call UnitAddAbility(u, ID)
        call SetUnitAbilityLevel(u, ID, level)
        call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MANA) + MANA(level))
    endfunction
    
    private function Act takes nothing returns nothing
        local Data d = Data.create()
        
        set d.target = GetSpellTargetUnit()
        set d.caster = GetTriggerUnit()
        set d.level = GetUnitAbilityLevel(d.caster, ID)
        
        if IsUnitInGroup(d.target, Wanted) then
            call DisplayTimedTextToPlayer(GetOwningPlayer(d.caster), 0., 0., 3., ERROR)
            call SpellRestore.execute(d.caster, d.level)
            call d.release()
            return
        elseif IsUnitInGroup(d.target, Casters) then
            call DisplayTimedTextToPlayer(GetOwningPlayer(d.caster), 0., 0., 3., ERRORB)
            call SpellRestore.execute(d.caster, d.level)
            call d.release()
            return
        endif
        
        call GroupAddUnit(Casters, d.caster)
        call DestroyEffect(AddSpecialEffectTarget(CAST_EFFECT_C, d.caster, CAST_EFFECT_C_ATTACH))
        call DestroyEffect(AddSpecialEffectTarget(CAST_EFFECT_T, d.target, CAST_EFFECT_T_ATTACH))
        
        set d.total = DURATION(d.level)
        set d.light = AddLightningEx(LIGHTNING, true, GetUnitX(d.caster), GetUnitY(d.caster), GetUnitFlyHeight(d.caster) + LIGHT_OFFSET, GetUnitX(d.target), GetUnitY(d.target), GetUnitFlyHeight(d.target) + LIGHT_OFFSET)
        call SetLightningColor(d.light, L_R, L_G, L_B, L_A)
        set D[I ] = d
        set I = I + 1
        if I == 1 then
            call TimerStart(T, TIMEOUT, true, function Expire)
        endif

        call GroupAddUnit(Wanted, d.target)
        if not IsUnitInGroup(d.target, Registered) then
            call TriggerRegisterUnitEvent(Trig, d.target, EVENT_UNIT_DAMAGED)
            call GroupAddUnit(Registered, d.target)
        endif
        set Data[d.target] = d
    endfunction
    
    private function Cond takes nothing returns boolean
        return GetSpellAbilityId() == ID
    endfunction

    private function Init takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Filter(function Cond))
        call TriggerAddAction(t, function Act)
        call TriggerAddCondition(Trig, Filter(function HitCond))
        call TriggerAddAction(Trig, function Hit)
        if PRELOAD then
            call DestroyEffect(AddSpecialEffect(CAST_EFFECT_C, 0., 0.))
            call DestroyEffect(AddSpecialEffect(CAST_EFFECT_T, 0., 0.))
            call DestroyEffect(AddSpecialEffect(DAMAGE_EFFECT_C, 0., 0.))
            call DestroyEffect(AddSpecialEffect(DAMAGE_EFFECT_T, 0., 0.))
        endif
    endfunction

endscope</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></i>


Requires PUI

The Map:
 

Attachments

  • [Spell] Heroic Guard.w3x
    51 KB · Views: 484

iPeez

Hot food far all world wide!
Reaction score
165
Awesome :D works great! ^^ Combined with Taunt omfg! +rep!
 

Romek

Super Moderator
Reaction score
964
Thanks iPeez.
Although it would usually be used on a stronger hero with less HP in my opinion, so you essentially get another units Health for X seconds.
Or it could be used to save units :D

Oh, and [ I ] becomes on vB forums. It's awfully annoying. :p
 

Nexor

...
Reaction score
74
hm, nice spell, only bug I found was that casted twice on the same unit caused crit error :)
 

bananaHUNT

You can change this now in User CP
Reaction score
55
Nice spell, very useful! :D
You could maybe make it when targeting on enemy units to let them take 60/80/100% damage for the hero?
 

Romek

Super Moderator
Reaction score
964
> hm, nice spell, only bug I found was that casted twice on the same unit caused crit error
Fixed. v2.1 :)
It now stops the spell casting if the target is already affected. The cooldown is reset, and mana is restored.

> You could maybe make it when targeting on enemy units to let them take 60/80/100% damage for the hero?
That'd be quite a different spell :p

> Im not sure about this ( you need to test it ), but doesnt this return false when unit is still decaying?
I haven't tested this, but it's used often, and I doubt it.
 

Sim

Forum Administrator
Staff member
Reaction score
534
Nice spell

> works great! ^^

Agreed.

Approved!
 

Romek

Super Moderator
Reaction score
964
Thanks for the approval Daxtreme, and the nice comments everyone.

> You're still leaving index 0 unused .
End of the world. :(
I'll change it if this ever needs another update. Otherwise I'll leave it. ^_^
 

Romek

Super Moderator
Reaction score
964
Updated to 2.2.

There was a bug which caused a critical error when 2 heroes casted this on each other, then one of them took damage. The spell now won't cast on a hero that is already casting the spell.
Also uses index 0 now, so cheer up Uber. :D
 

Lehona

New Member
Reaction score
12
You should make the ability force channeling. Otherwise you can cast it on two units at the same time and one lightning won't get destroyed.
 

Lehona

New Member
Reaction score
12
The test map? I just casted it on one unit, press esc and then on the next one. The first lightning was there all the time and didn't get destroyed.
 

Furby

Current occupation: News poster
Reaction score
144
The test map? I just casted it on one unit, press esc and then on the next one. The first lightning was there all the time and didn't get destroyed.

He's right. :) You know you call struct with PUI (and all other attachment systems by unit that means you can cast only one spell at time.

That's why ABC (or some else system which attach structs to handles) would be better for this, even though .. dynamic triggers =/.

Btw, you should add there some maximal range, when the spell will be interrupted and stops when target and caster are out of this range.

Also, there should be way to stop casting spell.. if it isn't channeling ability, it should still have some way to stop from casting, to prevent dying of your hero if you don't want him to die.

EDIT: You should also use [lJASS]//! external ObjectMerger w3a[/lJASS] for that "dummy bonus hp" ability. :p
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Ghan Ghan:
    Howdy
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1

      The Helper Discord

      Members online

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top