• 🏆 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!

Basic Coding Help - for Blessed Hammer Ability (i downloaded)

Status
Not open for further replies.
Level 9
Joined
Sep 5, 2015
Messages
369
I am really interested in learning more about triggers and novice level coding finally so im trying to learn as much as I can as i build this map, and after finding this ability i feel it is fairly simple (compared to other stuff i've seen on here...(>o.o<)) and would make a good learning stepping stone for me
p.s. first time ive posted triggers/script so forgive me if this is inefficient or a wrong way of posting

(QUESTION)
I downloaded blessed hammer ability (--> Blessed Hammer 1.11 ) And it runs fine
but I want to change some features about it:
the base damage it does, -> (i got a good idea how this works)
changing the scaling with damage based on amount of strength, -> (can't figure out how to scale it up per level:
level 1=2. level2=2.2. level3= 2.35. level4= 2.5 etc)
and removing the bonus damage to undead units -> (i got a good idea how this works)
the hitbox of the hammers, -> (i got NO clue how to even look for this one in the code)

just to make sure I dont screw up the entire spell and potentially break my map I would like some confirmation on what i think i need to do. I don't know really anything about coding but with my limited experience, for example, i can see that-> private constant real Hammer_Damage_Range = 128.0 might potentially be the "hitbox" of the hammers because they seem about as big as that. but i also think thats the distance inbetween the hammers themself? also there is code->
set damage = 25.0 + 25.0*h.abi_lvl
set damage = damage + 2.0 * GetHeroStr(h.caster, /include_bonuses:/ true)
if IsUnitType(target, UNIT_TYPE_UNDEAD) then
// evil beware!
set damage = 2.0 * damage
endif

which gives bonus damage (2 x str level) and i assume if i were to change that to say 1? it would be an easy adjust? also i also see set damage = 25.0+25.0*h.abi lvl (the first level is 50 damage so i assume that line would change the base damage, example, 50 + 25*h.abi lvl would make the first level deal 75 damage. 25*h.abi lvl simply means 25 x the ability level correct? so at level 4 it should be 200? this works when ive tried it so im running with it. but again i dont want it to like crash later on or something? lol

Thanks,
code below and link again: Blessed Hammer 1.11


vJASS:
library Hammer initializer init

globals
    private constant real Tau = 6.283185

    private constant integer Ability_Id = 'A000'
    private constant integer Dummy_Id = 'e000'

    private constant real Dt = 1.0 / 32.0
    private constant real Hammer_Duration = 6.0 // in seconds
    private constant real Hammer_Rot_Speed = Tau / 1.65 // in radians per second
    private constant real Hammer_Outward_Speed = 112.0 // in units per second
    private constant real Hammer_Damage_Range = 128.0
    private constant real Hammer_Damage_Again_Delay = 0.85

    private constant string Hammer_On_Hit_Effect_Model = "Abilities\\Weapons\\GryphonRiderMissile\\GryphonRiderMissile.mdl"
    private constant real Hammer_Fly_Height = 96.0
endglobals

private struct Hammer
    Hammer prev
    Hammer next
    integer abi_lvl
    unit du
    unit caster
    player caster_owner
    real start_pos_x
    real start_pos_y
    real pos_x
    real pos_y
    real outward_radius
    real rot_dir
    integer duration // in ticks per second
endstruct

native UnitAlive takes unit u returns boolean

private function target_allowed takes unit target, Hammer h returns boolean
    return UnitAlive(target) and IsUnitEnemy(target, h.caster_owner)
endfunction

private function hammer_damage_target takes Hammer h, unit target returns nothing
    local boolean melee_attack = false
    local boolean range_attack = true
    local attacktype attack_type = ATTACK_TYPE_NORMAL // spell
    local damagetype damage_type = DAMAGE_TYPE_UNIVERSAL // ignore armor value
    local weapontype weapon_type = WEAPON_TYPE_WHOKNOWS
    local real damage

    set damage = 25.0 + 25.0*h.abi_lvl
    set damage = damage + 2.0 * GetHeroStr(h.caster, /[I]include_bonuses:[/I]/ true)
    if IsUnitType(target, UNIT_TYPE_UNDEAD) then
        // evil beware!
        set damage = 2.0 * damage
    endif

    call UnitDamageTarget(h.caster, target, damage, melee_attack, range_attack, attack_type, damage_type, weapon_type)
endfunction

// These should suffice, but we should probably use a dummy recycling library instead.
//
globals
    private unit dummy_spawn_result
endglobals
private function dummy_spawn takes player caster_owner, real start_pos_x, real start_pos_y returns unit
    set dummy_spawn_result = CreateUnit(caster_owner, Dummy_Id, start_pos_x, start_pos_y, 0.0)
    call SetUnitFlyHeight(dummy_spawn_result, Hammer_Fly_Height, 0.0)
    return dummy_spawn_result
endfunction
private function dummy_remove takes unit dummy returns nothing
    call RemoveUnit(dummy)
endfunction


// We need to save the time when a unit was last damaged by a hammer.
//
globals
    private hashtable ht = InitHashtable()
endglobals
private function unit_get_last_time_damaged_by_hammer takes unit u, Hammer hammer returns real
    return LoadReal(ht, integer(hammer), GetHandleId(u))
endfunction
private function unit_set_last_time_damaged_by_hammer takes unit u, Hammer hammer, real value returns nothing
    call SaveReal(ht, integer(hammer), GetHandleId(u), value)
endfunction
private function hammer_clear_units_last_damaged_times takes Hammer hammer returns nothing
    call FlushChildHashtable(ht, integer(hammer))
endfunction

private function hammer_spawn takes unit caster returns Hammer
    local Hammer h = Hammer.create() // Hammer.allocate()
    local real start_pos_x = GetUnitX(caster)
    local real start_pos_y = GetUnitY(caster)

    set h.abi_lvl = GetUnitAbilityLevel(caster, Ability_Id)
    set h.caster = caster
    set h.caster_owner = GetOwningPlayer(caster)
    set h.du = dummy_spawn(h.caster_owner, start_pos_x, start_pos_y)
    set h.start_pos_x = start_pos_x
    set h.start_pos_y = start_pos_y
    set h.pos_x = start_pos_x
    set h.pos_y = start_pos_y
    set h.outward_radius = 0.0
    set h.rot_dir = 0.0
    set h.duration = R2I(Hammer_Duration * (1.0 / Dt))

    return h
endfunction

private function hammer_remove takes Hammer h returns nothing
    set h.caster = null
    call dummy_remove(h.du)
    call hammer_clear_units_last_damaged_times(h)
    call h.destroy() // h.deallocate()
endfunction

globals
    private real now = 0.0
endglobals
private function update_now takes nothing returns nothing
    set now = now + 0.03125 // 1.0 / 32.0
endfunction

globals
    private group targets = CreateGroup()
endglobals
private function deal_damage_in_range takes Hammer h, real range returns nothing
    local unit u
    local real t

    call GroupEnumUnitsInRange(targets, h.pos_x, h.pos_y, 192.0 + Hammer_Damage_Range, /[I]filter:[/I]/ null)
    call GroupRemoveUnit(targets, h.caster)
    loop
        set u = FirstOfGroup(targets)
        exitwhen u == null
        call GroupRemoveUnit(targets, u)

        if IsUnitInRangeXY(u, h.pos_x, h.pos_y, Hammer_Damage_Range) and target_allowed(u, h) then
            set t = unit_get_last_time_damaged_by_hammer(u, h)
             if now - t >= Hammer_Damage_Again_Delay then
                call unit_set_last_time_damaged_by_hammer(u, h, now)
                call hammer_damage_target(h, u)

                if UnitAlive(u) then
                    call DestroyEffect(AddSpecialEffectTarget(Hammer_On_Hit_Effect_Model, u, "head"))
                else
                    call DestroyEffect(AddSpecialEffect(Hammer_On_Hit_Effect_Model, GetUnitX(u), GetUnitY(u)))
                endif
            endif
        endif
    endloop
endfunction

globals
    // the sentinel node of the doubly linked list of all hammers
    private Hammer hammers = Hammer(0)
    // implicitly we have
    // hammers.prev = hammers
    // hammers.next = hammers

    private timer ticker = CreateTimer()
endglobals

private function hammer_do_every_tick takes Hammer h returns nothing
    set h.pos_x = h.start_pos_x + Dt * h.outward_radius * Cos(h.rot_dir)
    set h.pos_y = h.start_pos_y + Dt * h.outward_radius * Sin(h.rot_dir)
    set h.rot_dir = h.rot_dir + Dt * Hammer_Rot_Speed
    set h.outward_radius = h.outward_radius + Hammer_Outward_Speed

    call SetUnitX(h.du, h.pos_x)
    call SetUnitY(h.du, h.pos_y)
    call SetUnitFacing(h.du, h.rot_dir * 57.295779 + 90.0)

    call deal_damage_in_range(h, Hammer_Damage_Range)
endfunction

private function hammers_do_every_tick takes nothing returns nothing
    local Hammer h
    local Hammer next

    set h = hammers.next
    if h == hammers then
        call PauseTimer(ticker)
        return
    endif

    loop
        exitwhen h == hammers
        if h.duration == 0 then
            set next = h.next
            set h.prev.next = h.next
            set h.next.prev = h.prev
            call hammer_remove(h)
            set h = next
        else
            set h.duration = h.duration - 1
            call hammer_do_every_tick(h)
            set h = h.next
        endif
    endloop
endfunction

private function on_spell_effect takes nothing returns nothing
    local Hammer h

    if Ability_Id != GetSpellAbilityId() then
        return
    endif

    set h = hammer_spawn(GetTriggerUnit())

    set h.prev = hammers.prev
    set h.next = hammers
    set h.prev.next = h
    set h.next.prev = h

    call hammer_do_every_tick(h)
    if h.prev == hammers then
        call TimerStart(ticker, Dt, true, function hammers_do_every_tick)
    endif
endfunction

private function init takes nothing returns nothing
    local trigger t
    call TimerStart(CreateTimer(), Dt, true, function update_now)

    // should probably use a "better" on ability spell effect dispatch mechanism
    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
    call TriggerAddAction(t, function on_spell_effect)
endfunction

endlibrary
 
Last edited by a moderator:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Enclose all of that code like so:
[*code=vJass]
put the code here
[*/code]"
But remove the *

Then we can read it properly:
vJASS:
function ThisIsAnExample takes nothing returns nothing
endfunction

For the damage, it does: 25 + (25 * ability level)
So if the ability had 4 levels it would deal 50/75/100/125 damage.

That value is then increased by (2 * Hero Strength) so if the Hero had 10 Strength then it will deal an additional 20 damage on top of the previous calculation.

Lastly, if the target is an Undead unit, the final damage calculation is doubled.

Remember your order of operations (PEMDAS), multiplication happens before addition.

If you wanted to scale the Strength bonus per level you can do something like this:
vJASS:
set damage = damage + (1.5 + (0.5 * h.abi_lvl)) * GetHeroStr(h.caster, true)
This would result in 2.0/2.5/3.0/3.5 * Strength.

If the scaling isn't consistent, like how you described it in your example: 2/2.2/2.35/2.5
You can create a Real array and reference that instead:
vJASS:
private constant real Hammer_Damage_Again_Delay = 0.85
private real array Str_Multiplier
Then initialize it in the init function:
vJASS:
private function init takes nothing returns nothing
local trigger t
call TimerStart(CreateTimer(), Dt, true, function update_now)

// should probably use a "better" on ability spell effect dispatch mechanism
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t, function on_spell_effect)

// initialize our strength multiplier array
set Str_Multiplier[1] = 2.0
set Str_Multiplier[2] = 2.2
set Str_Multiplier[3] = 2.35
set Str_Multiplier[4] = 2.5
endfunction
Now you can reference Str_Multiplier[ability level] to get the value you want:
vJASS:
set damage = damage + Str_Multiplier[h.abi_lvl] * GetHeroStr(h.caster, true)
 
Last edited:
Level 9
Joined
Sep 5, 2015
Messages
369
Enclose all of that code like so:
[*code=vJass]
put the code here
[*/code]"
But remove the *

Then we can read it properly:
vJASS:
function ThisIsAnExample takes nothing returns nothing
endfunction

For the damage, it does: 25 + (25 * ability level)
So if the ability had 4 levels it would deal 50/75/100/125 damage.

That value is then increased by (2 * Hero Strength) so if the Hero had 10 Strength then it will deal an additional 20 damage on top of the previous calculation.

Lastly, if the target is an Undead unit, the final damage calculation is doubled.

Remember your order of operations (PEMDAS), multiplication happens before addition.

If you wanted to scale the Strength bonus per level you can do something like this:
vJASS:
set damage = damage + (1.5 + (0.5 * h.abi_lvl)) * GetHeroStr(h.caster, true)
This would result in 2.0/2.5/3.0/3.5 * Strength.

If the scaling isn't consistent, like how you described it in your example: 2/2.2/2.35/2.5
You can create a Real array and reference that instead:
vJASS:
private constant real Hammer_Damage_Again_Delay = 0.85
private real array Str_Multiplier
Then initialize it in the init function:
vJASS:
private function init takes nothing returns nothing
local trigger t
call TimerStart(CreateTimer(), Dt, true, function update_now)

// should probably use a "better" on ability spell effect dispatch mechanism
set t = CreateTrigger()
call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
call TriggerAddAction(t, function on_spell_effect)

// initialize our strength multiplier array
set Str_Multiplier[1] = 2.0
set Str_Multiplier[2] = 2.2
set Str_Multiplier[3] = 2.35
set Str_Multiplier[4] = 2.5
endfunction
Now you can reference Str_Multiplier[ability level] to get the value you want:
vJASS:
set damage = damage + Str_Multiplier[h.abi_lvl] * GetHeroStr(h.caster, true)
hey sorry i havent replied i seen this when it was posted just really busy around christmas and havent had a chance to study it. ive figured out most things with this spell as well other than how to adjust the hitbox BUT i think that that would be the dummy itself? the invisible dummy unit? just with how warcraft 3 works and probably how this spell is coded i dont think i could. but i will check this out when i get a chance brother i appreciate the reply

p.s. im interested (and confused lol but again havent looked just replying quick) in the real array you mentioned? because that does seem to be a problem when scaling the damage, it's like i would have to come up with an advanced math equation to develop the numbers i want otherwise it would be standard: 50>100>150>200 etc? brb
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
Hammer_Damage_Range is the Area of Effect of the hammers. Units have a Collision Size (Hitbox) but that's rarely if not ever used in calculations since there's no native way to get a unit's Collision Size.

This is what's used to get nearby units:
  • Unit Group - Pick every unit in (Units within 128.00 of SomePoint and do (Actions)
    • Loop - Actions
      • -------- Damage them and stuff --------
So the code picks units within Hammer_Damage_Range (128.00) of each hammer and if they're valid targets it damages them. It also does some other things to make sure that the units can only get damaged once every Hammer_Damage_Again_Delay (0.85) seconds. Of course you can modify these values to make the spell work the way you'd like.

Regarding the damage calculations, Arithmetic for consistent scaling like 50/100/150/200 is pretty simple and so is 50/75/100/125..
But when you want to do something inconsistent like 50/65/100/200 it makes sense to use an Array.
 
Last edited:
Level 9
Joined
Sep 5, 2015
Messages
369
Hammer_Damage_Range is the Area of Effect of the hammers. Units have a Collision Size (Hitbox) but that's rarely if not ever used in calculations since there's no native way to get a unit's Collision Size.

This is what's used to get nearby units:
  • Unit Group - Pick every unit in (Units within 128.00 of SomePoint and do (Actions)
    • Loop - Actions
      • -------- Damage them and stuff --------
So the code picks units within Hammer_Damage_Range (128.00) of each hammer and if they're valid targets it damages them. It also does some other things to make sure that the units can only get damaged once every Hammer_Damage_Again_Delay (0.85) seconds. Of course you can modify these values to make the spell work the way you'd like.

Regarding the damage calculations, Arithmetic for consistent scaling like 50/100/150/200 is pretty simple. Even 50/75/100/125 is simple.
But when you want to do like 50/65/100/200 it makes sense to use an Array.
thank you i read this real quick (have had no f***ing time to figure this more advanced stuff out (talking about first comment i got from the other guy that was talking more about trigger work and stuff i still want to read)

but i changed this real quick tested it and it works. thanks brotha. coding is cool lol i wish i could learn it but it seems so hard
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,535
You're technically coding when you make triggers in the trigger editor. You're just using GUI (graphical user interface) which makes things easier to understand. You can even convert your triggers to code to see what each Event/Condition/Action looks like in code form.

For example here's a GUI action converted into it's code form:

GUI form:
  • Unit - Kill (Triggering unit)
Code form (jass):
vJASS:
call KillUnit(GetTriggerUnit())
In Jass code (the default coding language wc3 uses), you need to write "call" before you use functions. You then need to provide arguments in the parameters of these functions, aka inside the parenthesis. So the function KillUnit() requires that you provide a unit to kill inside of it's (parenthesis). In this case I provided the (Triggering unit) which is called GetTriggerUnit() in code form.

The good news is that there's tools to make it a lot easier to learn and use these programming languages.
This extension is slightly outdated but it's a great way to start writing vJass code: vjass support extension for Visual Studio Code
Tools like this make it so you don't need to memorize every single thing. They have Autocomplete and something called Intellisense which helps you find what you're looking for and understand how to use it. For example, if you type the letter "U", every function that refers to Units will pop-up and you can scroll through them. Their names are often self-explanatory like KillUnit() so it's really not that difficult once you get the hang of it.

Note that vJass is an updated version of Jass which simply adds features to Jass. Blizzard adopted vJass and made it the norm so you're going to be using vJass. Just think of them as the same thing as the names are often used interchangeably.

There's an even better programming language that Wc3 uses which is called Lua. This was added in patch 1.31 in preparation for Reforged. With Lua you'll be using the same functions like KillUnit() but some of the minor syntax rules change like you no longer need to write "call" before your functions and overall Lua is a nice improvement over vJass. You can even program in Typescript or C# and then convert them to Lua, so Lua opens up the door to many programming languages.
 
Last edited:
Status
Not open for further replies.
Top