• 🏆 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!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Blood Rage 1.03

This bundle is marked as useful / simple. Simplicity is bliss, low effort and/or may contain minor bugs.
  • Like
Reactions: IcemanBo
This is a Blood Rage ability, inspired by the Stronghold racial ability from Heroes of Might And Magic 5.

Description:
Allows the hero to gain rage points when dealing damage or when nearby unit dies. Rage points will be used to reduce damage taken by the hero. If hero has enough rage points, he will also gain additional bonuses.

Level 1 - Level 1 bonus (+10 damage) enabled at 250 rage points.
Level 2 - Level 2 bonus (+3 hp regen) enabled at 500 rage points.
Level 3 - Level 3 bonus (+50% attack rate) enabled at 750 rage points.

Requires:
Unit Indexer by Bribe
Damage Engine by Bribe

Import instructions:
1. Copy the cheat death ability to your map and create an ability for Blood Rage
2. Check "automatically create unknown variables while pasting trigger data" in map preferences
3. Copy this trigger and required libraries to your map
4. Set DamageBlockingAbility in Damage Engine trigger to the ability copied at 1
5. Set configurables in this trigger
6. To modify rage from in your own triggers, call addRage(unit, amount)

Changelog:
1.03: updated registering units to use unit indexer, added option for multilevel bonus abilities
1.02: added option to delay rage degeneration, replaced bj event registration
1.01: replaced some BJs
1.00: spell uploaded

JASS:
//###########################################################################################################
//# Blood Rage                                                                                              #
//# Created by Erkki2                                                                                       #
//# Required libraries:                                                                                     #
//#     Unit Indexer by Bribe                                                                               #
//#     Damage Engine by Bribe                                                                              #
//#                                                                                                         #
//# Import instructions:                                                                                    #
//#     1. Copy the cheat death ability to your map and create an ability for Blood Rage                    #
//#     2. Check "automatically create unknown variables while pasting trigger data" in map preferences     #
//#     3. Copy this trigger and required libraries to your map                                             #
//#     4. Set DamageBlockingAbility in Damage Engine trigger to the ability copied at 1                    #
//#     5. Set configurables in this trigger                                                                #
//#     6. To modify rage from in your own triggers, call addRage(unit, amount)                             #
//###########################################################################################################

scope BloodRage

//  #################
//  # CONFIGURABLES #
//  #################
    globals
        
        //Raw code of the spell ability
        private constant integer ABIL_CODE = 'A000'
              
        //Iteration interval
        private constant real TIMEOUT = 0.03125
        
        //How high above unit the bar is
        private constant real BAR_HEIGHT = 150.0
        
        //Maximum amount of rage
        private constant real MAX_RAGE = 1000.0
        
        //How many characters ("|") the bar has
        private constant integer BAR_WIDTH = 150
        
        //Setting this to smaller value will move the bar left, and higher value to tight
        private constant real LETTER_WIDTH = 0.4
        
        //Height of the bar
        private constant real BAR_FONT_SIZE = 0.015
        
        //Colour settings for the bar
        private constant integer BAR_RED = 255
        private constant integer BAR_GREEN = 0
        private constant integer BAR_BLUE = 0
        private constant integer BAR_ALPHA = 255
        private constant integer BAR_BACKGROUND_RED = 102
        private constant integer BAR_BACKGROUND_GREEN = 0
        private constant integer BAR_BACKGROUND_BLUE = 0
        private constant integer BAR_BACKGROUND_ALPHA = 255
        
        //Unit will gain (X * damage dealt) rage points when dealing damage
        private constant real ATTACK_RAGE_FACTOR = 0.3
        
        //Unit will gain X rage when an allied unit dies nearby
        private constant real ALLY_DEATH_RAGE_AMOUNT = 50
        
        //Unit will gain X rage when an enemy unit dies nearby
        private constant real ENEMY_DEATH_RAGE_AMOUNT = 30
        
        //Unit will gain rage points when a unit dies within this area
        private constant real RAGE_GAIN_AREA = 800
        
        //The speed at which rage depletes
        private constant real DEGENERATION_PER_SECOND = 30
        
        //If the unit has not gained rage in x seconds, it will begin to degenerate
        private constant real DEGENERATION_DELAY = 10.0
        
        //Damage blocked by rage
        private constant real DAMAGE_REDUCTION_FACTOR = 0.5
        
        //Rage lost per point of blocked damage
        private constant real RAGE_DRAIN_FACTOR = 1

    endglobals
    
    //Rage points required to gain level "level"
    private function levelRageAmount takes integer level returns real
        return level*250.0
    endfunction
    
    //Rage bonus ability for level "level"
    private function rageAbility takes integer level returns integer
        if level == 1 then
            return 'A002'
        elseif level == 2 then
            return 'A003'
        elseif level == 3 then
            return 'A004'
        endif
        return 0
    endfunction
    
    //Level of rage bonus ability for level "level".
    private function rageBonusLevel takes integer level returns integer
        return 1
    endfunction

    //Rage doesn't deregenerate if current rage is below this amount
    private function degenerationTreshold takes unit u returns real
        return 100.0*GetUnitAbilityLevel(u, ABIL_CODE)
    endfunction
    
//  ############################################################
//  # CONFIGURABLES END, Don't touch anything after this line. #
//  ############################################################

    private struct Spell
    
        private thistype next
        private thistype prev
        private static timer iterator = CreateTimer()
        private static integer count = 0
        
        private unit u
        private texttag rageBarTotal
        private texttag rageBarCurrent
        private real currentRage
        private integer rageLevel
        private real degenDelay
        
        private method destroy takes nothing returns nothing
            call this.deallocate()
            set this.next.prev = this.prev
            set this.prev.next = this.next
            call DestroyTextTag(this.rageBarCurrent)
            call DestroyTextTag(this.rageBarTotal)
            set this.rageBarCurrent = null
            set this.rageBarTotal = null
            set this.u = null
            set count = count - 1
        endmethod
        
        private static method periodic takes nothing returns nothing
            local thistype this = thistype(0).next
            local real x
            local real y
            local string text
            local integer i
            local integer treshold
            
            loop
                exitwhen this == 0
                
                //Set bar position to unit's position.
                //Move bar to left so that its center is at the unit.
                set x = GetUnitX(this.u) - BAR_WIDTH*LETTER_WIDTH
                set y = GetUnitY(this.u)
                
                call SetTextTagPos(this.rageBarTotal, x, y, BAR_HEIGHT)
                call SetTextTagPos(this.rageBarCurrent, x, y, BAR_HEIGHT)

                //Set bar to match unit's current rage amount
                set text = ""
                set i = 0
                set treshold = R2I(this.currentRage/MAX_RAGE*I2R(BAR_WIDTH))
                loop
                    exitwhen i >= treshold
                    set text = text + "|"
                    set i = i + 1
                endloop
            
                call SetTextTagText(this.rageBarCurrent, text, BAR_FONT_SIZE)
                
                //If unit is dead, hide the bar, and if unit is alive, show the bar
                call SetTextTagVisibility(rageBarTotal, GetUnitState(this.u, UNIT_STATE_LIFE) > 0)
                call SetTextTagVisibility(rageBarCurrent, GetUnitState(this.u, UNIT_STATE_LIFE) > 0)
                
                //If unit's rage level changes, add/remove bonuses accordingly
                loop
                    if this.currentRage >= levelRageAmount(this.rageLevel + 1) /*
*/                  and GetUnitAbilityLevel(this.u, ABIL_CODE) > this.rageLevel then
                        call UnitAddAbility(this.u, rageAbility(this.rageLevel + 1))
                        call SetUnitAbilityLevel(this.u, rageAbility(this.rageLevel + 1), rageBonusLevel(this.rageLevel))
                        set this.rageLevel = this.rageLevel + 1
                    elseif this.currentRage < levelRageAmount(this.rageLevel) then
                        call UnitRemoveAbility(this.u, rageAbility(this.rageLevel))
                        set this.rageLevel = this.rageLevel - 1
                    else
                        exitwhen true
                    endif
                endloop
                
                //This unit hasn't gained rage in DEGENERATION_DELAY seconds
                if this.degenDelay < TIMEOUT then
                    //Degenerate unit's rage
                    if this.currentRage - DEGENERATION_PER_SECOND > degenerationTreshold(this.u) then
                        set this.currentRage = this.currentRage - DEGENERATION_PER_SECOND*TIMEOUT
                    endif
                else
                    set this.degenDelay = this.degenDelay - TIMEOUT
                endif
                
                set this = this.next
            
            endloop
        
            //if there are no spell instances running, pause the timer
            if count == 0 then
                call PauseTimer(iterator)
            endif
            
        endmethod

        //Registers a unit to have blood rage
        private static method registerUnit takes unit u returns nothing
            local thistype this
            local string text
            local integer i

            set this = thistype.allocate()
            set this.next = 0
            set this.prev = thistype(0).prev
            set thistype(0).prev.next = this
            set thistype(0).prev = this
            set count = count + 1
                
            if count == 1 then
                call TimerStart(iterator, TIMEOUT, true, function thistype.periodic)
            endif
            
            set this.u = u
            set this.currentRage = 0
            set this.rageLevel = 0
            set this.degenDelay = DEGENERATION_DELAY
            
            set text = ""
            set i = 0
            
            loop
                exitwhen i >= BAR_WIDTH
                set text = text + "|"
                set i = i + 1
            endloop
            
            set this.rageBarCurrent = CreateTextTag()
            call SetTextTagText(this.rageBarCurrent, "", BAR_FONT_SIZE)
            call SetTextTagPos(this.rageBarCurrent, GetUnitX(this.u), GetUnitY(this.u), BAR_HEIGHT)
            call SetTextTagColor(this.rageBarCurrent, BAR_RED, BAR_GREEN, BAR_BLUE, BAR_ALPHA)
            
            set this.rageBarTotal = CreateTextTag()
            call SetTextTagText(this.rageBarTotal, text, BAR_FONT_SIZE)
            call SetTextTagPos(this.rageBarTotal, GetUnitX(this.u), GetUnitY(this.u), BAR_HEIGHT)
            call SetTextTagColor(this.rageBarTotal, BAR_BACKGROUND_RED, BAR_BACKGROUND_GREEN, BAR_BACKGROUND_BLUE, BAR_BACKGROUND_ALPHA)
        endmethod
        
        //Registers units that are indexed
        private static method registerUnitsIndexed takes nothing returns boolean
            if GetUnitAbilityLevel(udg_UDexUnits[udg_UDex], ABIL_CODE) > 0 then
                call thistype.registerUnit(udg_UDexUnits[udg_UDex])
            endif
            return false
        endmethod
        
        //Registers a hero that learns blood rage
        private static method registerLearningHero takes nothing returns boolean            
            if GetUnitAbilityLevel(GetTriggerUnit(), ABIL_CODE) == 1 and (GetLearnedSkill() == ABIL_CODE) then
                call thistype.registerUnit(GetTriggerUnit())
            endif
            return false
        endmethod
        
        //Unregisters a unit from blood rage
        private static method deindexUnit takes nothing returns boolean
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                if GetUnitUserData(this.u) == 0 then
                    call this.destroy()
                    return false
                endif
                set this = this.next
            endloop
            return false
        endmethod
        
        //Changes unit's current rage by amount
        private method modifyRage takes real amount returns nothing
            if this.currentRage + amount <= 0 then
                set this.currentRage = 0
            elseif currentRage + amount >= MAX_RAGE then
                set this.currentRage = MAX_RAGE
            else
                set this.currentRage = this.currentRage + amount
                if amount > 0 then
                    set this.degenDelay = DEGENERATION_DELAY
                endif
            endif
        endmethod
        
        //This function allows modifying rage from outside this trigger
        static method addRage takes unit u, real amount returns nothing
            local thistype this = thistype(0).next
            loop
                exitwhen this == 0
                if u == this.u then
                    call this.modifyRage(amount)
                    return
                endif
                set this = this.next
            endloop
        endmethod
        
        //Adds rage when unit deals damage
        private static method addRageAttack takes nothing returns boolean
            local thistype this = thistype(0).next
            if not (GetUnitAbilityLevel(udg_DamageEventSource, ABIL_CODE) == 0) then
                loop
                    exitwhen this == 0
                    if udg_DamageEventSource == this.u then
                        call this.modifyRage(udg_DamageEventAmount*ATTACK_RAGE_FACTOR)
                        return false
                    endif
                    set this = this.next
                endloop
            endif
            return false
        endmethod
        
        //Adds rage when a unit dies nearby
        private static method addRageDeath takes nothing returns boolean
            local real dx
            local real dy
            local unit dying = GetTriggerUnit()
            local thistype this = thistype(0).next
            if GetUnitAbilityLevel(dying, 'Aloc') == 0 then
                loop
                    exitwhen this == 0
                    set dx = GetUnitX(this.u) - GetUnitX(dying)
                    set dy = GetUnitY(this.u) - GetUnitY(dying)
                    if SquareRoot(dx*dx + dy*dy) <= RAGE_GAIN_AREA then
                        if IsUnitAlly(dying, GetOwningPlayer(this.u)) then
                            call this.modifyRage(ALLY_DEATH_RAGE_AMOUNT)
                        else
                            call this.modifyRage(ENEMY_DEATH_RAGE_AMOUNT)
                        endif
                    endif
                    set this = this.next
                endloop
            endif
            return false
        endmethod
    
        //Reduces damage taken by units that have blood rage
        private static method reduceDamage takes nothing returns boolean
            local thistype this = thistype(0).next
            local real reduction
            if not (GetUnitAbilityLevel(udg_DamageEventTarget, ABIL_CODE) == 0) then
                loop
                    exitwhen this == 0
                    if udg_DamageEventTarget == this.u then
                        if this.currentRage > udg_DamageEventAmount*DAMAGE_REDUCTION_FACTOR*RAGE_DRAIN_FACTOR then
                            set reduction = udg_DamageEventAmount*DAMAGE_REDUCTION_FACTOR
                        else
                            set reduction = this.currentRage/RAGE_DRAIN_FACTOR
                        endif
                            set this.currentRage = this.currentRage - reduction*RAGE_DRAIN_FACTOR
                            set udg_DamageEventAmount = udg_DamageEventAmount - reduction
                        return false
                    endif
                    set this = this.next
                endloop
            endif
            return false
        endmethod
    
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            local trigger t2 = CreateTrigger()
            local trigger t3 = CreateTrigger()
            local trigger t4 = CreateTrigger()
            local trigger t5 = CreateTrigger()
            local trigger t6 = CreateTrigger()
            local integer i = 0
            
            call TriggerRegisterVariableEvent( t, "udg_UnitIndexEvent", EQUAL, 1.00 )
            call TriggerAddCondition(t, Condition(function thistype.registerUnitsIndexed))
            call TriggerAddCondition(t2, Condition(function thistype.registerLearningHero))
            call TriggerAddCondition(t3, Condition(function thistype.deindexUnit))
            call TriggerRegisterVariableEvent( t4, "udg_UnitIndexEvent", EQUAL, 2.00 )
            call TriggerRegisterVariableEvent( t4, "udg_DamageEvent", EQUAL, 1.00 )
            call TriggerAddCondition(t4, Condition(function thistype.addRageAttack))
            call TriggerAddCondition(t5, Condition(function thistype.addRageDeath))
            call TriggerRegisterVariableEvent( t6, "udg_DamageModifierEvent", EQUAL, 1.00 )
            call TriggerAddCondition(t6, Condition(function thistype.reduceDamage))
            
            loop
                exitwhen i == 16
                
                call TriggerRegisterPlayerUnitEvent(t2, Player(i),  EVENT_PLAYER_HERO_SKILL, null)
                call TriggerRegisterPlayerUnitEvent(t5, Player(i),  EVENT_PLAYER_UNIT_DEATH, null)
            
                set i = i + 1
            endloop
            
        endmethod
        
    endstruct
    
    //This function can be used to modify rage from outside this trigger
    function addRage takes unit u, real amount returns nothing
        call Spell.addRage(u, amount)
    endfunction

endscope

Keywords:
blood rage, blood, rage, berserk, orc, heroes of might and magic, stronghold, aura, homm
Contents

Blood Rage (Map)

Reviews
12th Dec 2015 IcemanBo: Too long as NeedsFix. Rejected. 16:21, 5th Jan 2015 Maker: Check my reply in this thread. 11:38, 19th Sep 2014 TriggerHappy: This is a vJass spell which requires a GUI Unit Indexer and DDS? Why...

Moderator

M

Moderator

12th Dec 2015
IcemanBo: Too long as NeedsFix. Rejected.

16:21, 5th Jan 2015
Maker: Check my reply in this thread.


11:38, 19th Sep 2014
TriggerHappy:

  • This is a vJass spell which requires a GUI Unit Indexer and DDS? Why?
  • levelRageAmount could be simplified to level*250.
  • The way you're registering preplaced units is wrong and will likely bug. This is why you use a vJass Unit Indexer. The same goes for most of your events.
  • Why does it require 3 abilities for the "Rage Bonus" ability?

Also, take a look at IcemanBo's post.
 

Chaosy

Tutorial Reviewer
Level 40
Joined
Jun 9, 2011
Messages
13,183
I feel like it should be the other way around, rage boosts your skills offensively and not defensively :p

You should mention what kind of bonuses too, or that isn't a part of the actual spell? and is more of something the user should modify?

Either way, I am having a hard time if I should give it 4/5 or 3/5.. I can't judge the code as I am a nawb vjasser.
 
Last edited:
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for your comment! Rage points are used to block damage in homm5, and I tried to make this ability this as similar as possible. Bonuses are passive abilities, so they can be practically anything. On the test map they are

level 1: +10 damage
level 2: +3 hp regen
level 3: +50% attack rate
 
Level 18
Joined
Sep 14, 2012
Messages
3,413
call TriggerRegisterEnterRectSimple(t2, bj_mapInitialPlayableArea)
And
call TriggerRegisterLeaveRectSimple(t4, bj_mapInitialPlayableArea)
Can be replace by their non BJs.
Don't have WE here so can't say much more on it.

Anyway from a quick look, this is a bit messy but the code looks ok.
 
What are those natives called? I only found natives for regions, but not rects.
Just look up BJs in JNGP function list if you want to know what they exactly do.
Also, the event always works with regions. But you also can add rects to a region. So if you add only one rect it is exactly what you want.
I don't have access to blizzard.j :(
If you can upload it and link it to me I'll tell you what to do.
I uploaded it for you. <3 http://jass.sourceforge.net/doc/api/Blizzard_j-functions.shtml
 
Had just a quick look:
  • 3x TriggerRegisterAnyUnitEventBJ is not needed in your Init function.
    You can replace it with one loop and then work with TriggerRegisterPlayerUnitEvent by yourself.
  • No need to null the triggers in end, unless you destroy them.
  • In private static method registerUnitsInit you don't need to null the local enum unit as you already exit loop when it equals null.
  • Only create one global group, that you never destroy, and which you use in each function you would declare a local group.
Could you link to resources it requires?

Probably it make sense, if rage points would decrease after a while, if no nearby ally dies and they are not attacked.

Code looks very good and clean! Gj:)
 
Last edited:
Level 10
Joined
Jun 6, 2007
Messages
392
Thanks for all the feedback!

•This is a vJass spell which requires a GUI Unit Indexer and DDS? Why?

I used GUI indexer and DDS mainly because I'm familiar with them. If anyone can suggest betted vJass alternatives, I'll replace them.

•levelRageAmount could be simplified to level*250.

Done.

•The way you're registering preplaced units is wrong and will likely bug. This is why you use a vJass Unit Indexer. The same goes for most of your events.

I updated the spell to use indexer events when registering units.

•Why does it require 3 abilities for the "Rage Bonus" ability?

In HoMM5 blood rage, all bonuses gained through the rage are different. However, I realised that someone might want it to have only one type of bonuses (e.g. +5, +10 and +15 damage), in which case it would be more convenient to have one bonus ability with multiple levels. I added support for that.
 
Top