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

[Trigger] Drop gold when damaged

Status
Not open for further replies.
Level 8
Joined
Jun 13, 2010
Messages
344
I want to make an event where a Gold Chest drops a Gold Coin (item) on the floor for every 5 % it loses of its maximum hp.

I do however want to avoid using the DDS if possible.

I was thinking about using an array string with up to 20. For it I could make a periodic trigger checking what hp it is at, if it is less than value set string[integer] = done.

I do need some help to arrange this though, if I am to make it work properly.

If you do have other suggestions however, which seem to work better and are more easy I would gladly like to hear them.

Thank you for reading.
 
Last edited:
Level 39
Joined
Feb 27, 2007
Messages
5,013
Do you mean if a single attack does 5% or more of its max health, or every time it loses 5% total something occurs? Can the unit heal? Healing makes this more complicated.
I do however want to avoid using the damage detection system if possible.
Why? You'll probably need a DDS for something else in your map at some point, and this desired effect is much simpler to do with a DDS than without it.
 
Level 8
Joined
Jun 13, 2010
Messages
344
Do you mean if a single attack does 5% or more of its max health, or every time it loses 5% total something occurs? Can the unit heal? Healing makes this more complicated.

No. Whenever the target unit loses 5 % hp, it drops a gold coin on the floor. That's just a matter to control how many gold coins it may drop in total.

Why? You'll probably need a DDS for something else in your map at some point, and this desired effect is much simpler to do with a DDS than without it.

I want the map to be as transferable to the Reforged as it can be. I have never used DDS or anything else outside of WE3 except custom models and would for this matter want to keep everything out.

I'll try rewrite the main thread.
 
Level 9
Joined
Jul 30, 2018
Messages
445
I want the map to be as transferable to the Reforged as it can be. I have never used DDS or anything else outside of WE3 except custom models and would for this matter want to keep everything out.

DDS won't be a problem in Reforged. DDS's are done in JASS code and they aren't "outside of WE". They are basically just already-made triggers that you copy to your map.
 
Level 8
Joined
Jul 10, 2018
Messages
383
you can make a difficult trigger

When a unit health becomes Equal to 300
it drops a coin
when a unit health becomes Equal to 250
it drops a coin
when a unit health becomes Equal to 200
it drops a coin
and so on
 
Level 39
Joined
Feb 27, 2007
Messages
5,013
you can make a difficult trigger ...
This is not a solution at all. If a unit at 305 health takes 10 damage it will end up with 295 health and never trigger an "equal to 300" event. If you instead use "less than or equal to" events then the event for <= 300 life will trigger when the unit hits 250 life, 200 life, 150 life, etc. In fact it will also trigger when the unit hits 299 life and 275 life. See my point? With 5 different triggers that you disable appropriately you could accomplish this but it's really not viable to make 5 triggers per unit you want this to work for.
 
You can loop over a list of tracked units ...
... then get current/max HP of each unit and attach them to the unit. In the next iteration you already will have new current/max HP data which the unit currently has and you also can access the "old" current/max HP the ones that we attached to the unit before. So you now easily can calculate the difference and then just overwrite again the attached current/max HP with the new ones, for being correct for the next iteration.
 
Level 18
Joined
Nov 21, 2012
Messages
835
Here's my solution, (it requires UnitEvent by Bribe)
JASS:
//Life Event by ZibiTheWand3r3r
//requires Unit Event by Bribe
globals
    constant integer        HP_CHANGE_STEP=50 // at least 50hp change will trigger an event
    constant integer        HP_PERCENT_CHANGE_STEP=2 // at least 2% change will trigger an event

    trigger array           g_trgLifeTrack
   
    integer array               g_hpForUp
    integer array               g_hpForDown   
    integer array               g_prevHP
    integer array               g_prevEventHP
   
    integer array               g_hpPercentForUp
    integer array               g_hpPercentForDown   
    integer array               g_prevHPPercent
    integer array               g_prevEventHPPercent

endglobals
//------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------
function R2IEx takes real r returns integer //rounded
    return R2I(r+0.5)
endfunction

function GetDisplayedHP takes real life returns integer// displayed on UI
    local integer hp=R2I(life)//cuts decimals   
    if life - (I2R(hp)) > 0.00 then
        return (hp+1)
    endif
    return hp
endfunction
/*
function GetUnitSpeedChange takes integer percentLife returns real//1..100
    if percentLife>90 then
        return 0.00       
    endif
    return I2R(percentLife-90)
     //local real oldMove=GetUnitSpeedChange(g_prevLifePercent[id])
    //local real newMove=GetUnitSpeedChange(lifePercent)
endfunction
*/
function RunUnitLifeEvent takes unit u, integer currentLife, integer prevLife returns nothing
    set udg_UnitLife_Unit=u
    set udg_UnitLife_Current=currentLife
    set udg_UnitLife_Prev=prevLife
    set udg_UnitLife_Event=0.00
    set udg_UnitLife_Event=1.00
    set udg_UnitLife_Event=0.00
endfunction

function RunUnitLifePercentEvent takes unit u, integer currentLife, integer prevLife returns nothing
    set udg_UnitLifePercent_Unit=u
    set udg_UnitLifePercent_Current=currentLife
    set udg_UnitLifePercent_Prev=prevLife
    set udg_UnitLifePercent_Event=0.00
    set udg_UnitLifePercent_Event=1.00
    set udg_UnitLifePercent_Event=0.00
endfunction
//------------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------------
function Trig_HP_Changed takes nothing returns boolean
    local unit u=GetTriggerUnit()
    local integer id=GetUnitUserData(u)
    local real life=GetWidgetLife(u)
    local integer ui_life = GetDisplayedHP(life)
    local integer lifePercent=R2IEx((ui_life/GetUnitState(u, UNIT_STATE_MAX_LIFE))*100)// 1..100
    local boolean currentChangeUp = (ui_life>g_prevHP[id])
   
    //by value
    if ui_life <= g_hpForDown[id] then
        call RunUnitLifeEvent(u, ui_life, g_prevEventHP[id])
        set g_hpForUp[id]=ui_life+HP_CHANGE_STEP
        set g_hpForDown[id]=ui_life-HP_CHANGE_STEP
        set g_prevEventHP[id]=ui_life
    elseif ui_life >= g_hpForUp[id] then
        call RunUnitLifeEvent(u, ui_life, g_prevEventHP[id])
        set g_hpForUp[id]=ui_life+HP_CHANGE_STEP
        set g_hpForDown[id]=ui_life-HP_CHANGE_STEP
        set g_prevEventHP[id]=ui_life
    endif
     
    if currentChangeUp then   
        if ui_life-HP_CHANGE_STEP > g_hpForDown[id] then//if new down-level is greater then old one
            set g_hpForDown[id]=ui_life-HP_CHANGE_STEP //change down-level
            //call Msg("Down level: "+I2S(g_hpForDown[id]))
        endif
    else
        if ui_life+HP_CHANGE_STEP < g_hpForUp[id] then
            set g_hpForUp[id]=ui_life+HP_CHANGE_STEP
            //call Msg("UP level: "+I2S(g_hpForUp[id]))
        endif
    endif
   
    //percent:---------------------------------------------------------------------------------
    //call Msg(I2S(lifePercent)+" / "+I2S(g_prevEventHPPercent[id]))
    if lifePercent <= g_hpPercentForDown[id] then
        //call BJDebugMsg("maxhp:" +I2S(BlzGetUnitMaxHP(u))+", life:"+I2S(ui_life))
        call RunUnitLifePercentEvent(u, lifePercent, g_prevEventHPPercent[id])
        set g_hpPercentForUp[id]=lifePercent+HP_PERCENT_CHANGE_STEP
        set g_hpPercentForDown[id]=lifePercent-HP_PERCENT_CHANGE_STEP
        set g_prevEventHPPercent[id]=lifePercent
    elseif lifePercent >= g_hpPercentForUp[id] then
        //call BJDebugMsg("maxhp:" +I2S(BlzGetUnitMaxHP(u))+", life:"+I2S(ui_life))
        call RunUnitLifePercentEvent(u, lifePercent, g_prevEventHPPercent[id])
        set g_hpPercentForUp[id]=lifePercent+HP_PERCENT_CHANGE_STEP
        set g_hpPercentForDown[id]=lifePercent-HP_PERCENT_CHANGE_STEP
        set g_prevEventHPPercent[id]=lifePercent
    endif
   
    if currentChangeUp then   
        if lifePercent-HP_PERCENT_CHANGE_STEP > g_hpPercentForDown[id] then//if new down-level is greater then old one
            set g_hpPercentForDown[id]=lifePercent-HP_PERCENT_CHANGE_STEP //change down-level
            //call Msg("Down level: "+I2S(g_hpPercentForDown[id])+"%")
        endif
    else
        if lifePercent+HP_PERCENT_CHANGE_STEP < g_hpPercentForUp[id] then
            set g_hpPercentForUp[id]=lifePercent+HP_PERCENT_CHANGE_STEP
            //call Msg("UP level: "+I2S(g_hpPercentForUp[id])+"%")
        endif
    endif
   
    //recreate trigger each time unit's life changed
    set g_prevHPPercent[id]=lifePercent
    set g_prevHP[id]=ui_life
    call DestroyTrigger(g_trgLifeTrack[id])
    set g_trgLifeTrack[id]=CreateTrigger()
    call TriggerRegisterUnitStateEvent(g_trgLifeTrack[id], u, UNIT_STATE_LIFE, LESS_THAN, life-1)
    call TriggerRegisterUnitStateEvent(g_trgLifeTrack[id], u, UNIT_STATE_LIFE, GREATER_THAN, life+1)
    call TriggerAddCondition(g_trgLifeTrack[id], Condition(function Trig_HP_Changed))
    //---
    set u=null
    return false
endfunction
//------------------------------------------------------------------------------------------
//------------------------------------------------------------------------------------------
function Trig_LifeEventEnters takes nothing returns nothing
    local integer id=udg_UDex
    local unit u=udg_UDexUnits[udg_UDex]
    local real life=GetWidgetLife(u)
    local integer ui_life = GetDisplayedHP(GetWidgetLife(u))
   
    set g_prevHP[id]=ui_life
    set g_hpForUp[id]=ui_life+HP_CHANGE_STEP
    set g_hpForDown[id]=ui_life-HP_CHANGE_STEP
    set g_prevEventHP[id]=ui_life
   
    set g_prevHPPercent[id]=R2IEx((ui_life/GetUnitState(u, UNIT_STATE_MAX_LIFE))*100)
    set g_hpPercentForUp[id]=g_prevHPPercent[id] + HP_PERCENT_CHANGE_STEP
    set g_hpPercentForDown[id]=g_prevHPPercent[id] - HP_PERCENT_CHANGE_STEP   
    set g_prevEventHPPercent[id]=g_prevHPPercent[id]
   
    set g_trgLifeTrack[id]=CreateTrigger()
    call TriggerRegisterUnitStateEvent(g_trgLifeTrack[id], u, UNIT_STATE_LIFE, LESS_THAN, life-1)
    call TriggerRegisterUnitStateEvent(g_trgLifeTrack[id], u, UNIT_STATE_LIFE, GREATER_THAN, life+1)

    call TriggerAddCondition(g_trgLifeTrack[id], Condition(function Trig_HP_Changed))
   
    set u=null
endfunction

function Trig_LifeEventRemoved takes nothing returns nothing
    local integer id=udg_UDex
    call DestroyTrigger(g_trgLifeTrack[id])
    set g_trgLifeTrack[id]=null
endfunction

//==============================================================
function InitTrig_LifeEvent takes nothing returns nothing
    local trigger t=CreateTrigger()
   
    call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 1.00)
    call TriggerAddAction(t, function Trig_LifeEventEnters)
    set t=CreateTrigger()
    call TriggerRegisterVariableEvent(t, "udg_UnitIndexEvent", EQUAL, 2.00)
    call TriggerAddAction(t, function Trig_LifeEventRemoved)
   
endfunction
 

Attachments

  • WoundedUnits.w3x
    74 KB · Views: 27
A solution I came up with for this goldChest behaviour. It generates UnitState Life below events for an wanted unit after all goldcoins droped or the unit is dead the trigger and the events will selfdestruct. ZiBitheWand3r3r's solution is less specific making it more useful in more situations but also much bigger.
Mine is quite inflexible in the amount of gold droped.
JASS:
constant function GoldCoinAmount takes unit u returns integer
    return 20 //if one wants goldChests with different gold count one could use ifs here
endfunction
function DropGold takes nothing returns boolean
    call CreateItem('gold', GetUnitX(GetTriggerUnit()), GetUnitY(GetTriggerUnit()))
    if GetTriggerEvalCount(GetTriggeringTrigger()) >= GoldCoinAmount(GetTriggerUnit()) or GetWidgetLife(GetTriggerUnit()) <= 0.45 then //all goldcoins droped or dead/removed
        call DestroyTrigger(GetTriggeringTrigger())
    endif
    return false
endfunction

function RegisterGoldChest takes unit u returns nothing
    local integer goldCoins = GoldCoinAmount(u)
    local real maxLife = GetUnitState(u, UNIT_STATE_MAX_LIFE)
    local real lifeStep = maxLife / goldCoins
    local real life = maxLife
    local trigger trig = CreateTrigger()
    call TriggerAddCondition(trig, Condition(function DropGold))
    loop
        set life = life - lifeStep
        call TriggerRegisterUnitStateEvent(trig, u, UNIT_STATE_LIFE, LESS_THAN_OR_EQUAL, life)
        exitwhen life < 0
    endloop
    set trig = null
endfunction
 
Last edited:
@ZiBitheWand3r3r , it looks like byValue and byPercent can run at same time, so one damage step could fire 2 gold events? For units with a bit higher hp, in fights, they keep getting damaged, and they perma keep HP regen... which might lead to high amounts of trigger re-creates.

@Tasyen , HP regen, max HP change isn't considered. And it looks like further HP- steps may fire the trigger multiple times for one step, because multiple events are registered for lower HPs. (LESS_THAN_OR_EQUAL 100 will fire for 90, for example, and the same event will also run for 80, and for 70, ..., but which again use their own events in addition)
 
Level 18
Joined
Nov 21, 2012
Messages
835
yes there are 2 separate events (by value and by percent)
  • event1
    • Events
      • Game - UnitLifePercent_Event becomes Equal to 1.00
    • Conditions
    • Actions
      • Game - Display to (All players) the text: ((EVENT - + (Name of UnitLifePercent_Unit)) + (, + ((String(UnitLifePercent_Prev)) + (% --> + ((String(UnitLifePercent_Current)) + (% + <Empty String>))))))
  • event2
    • Events
      • Game - UnitLife_Event becomes Equal to 1.00
    • Conditions
    • Actions
      • Game - Display to (All players) the text: ((EVENT -- + (Name of UnitLife_Unit)) + (, + ((String(UnitLife_Prev)) + ( --> + ((String(UnitLife_Current)) + ( + <Empty String>))))))
      • -------- UnitLife_Prev is a value a last event ran with --------
which might lead to high amounts of trigger re-creates
yes, you're right, but when I wrote it I had no other idea how to execute life event.
 

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,542
I would use a DDS and Unit Indexer. Although, that may be outdated with this latest patch. Didn't they just add Damage Detection events as natives to the World Editor in the new PTR patch?

Anyway, using what we have now, here's a working example I created. I imagine you guys can come up with something a lot more efficient than looping 20 times.

  • Setup
    • Events
      • Time - Elapsed game time is 0.00 seconds
    • Conditions
    • Actions
      • Hashtable - Create a hashtable
      • Set TreasureHash = (Last created hashtable)
  • Damage Event
    • Events
      • Game - DamageEvent becomes Equal to 1.00
    • Conditions
    • Actions
      • -------- We store 20 Booleans as false inside the Hashtable, one for each 5% of the Treasure Chest's maximum life. --------
      • -------- Whenever we break past a life threshold (95%, 90%, 85%) we set the respective Boolean for that threshold to True. --------
      • -------- That way we don't spawn Gold Coins for life thresholds that have already been broken (set to True). --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • (Unit-type of DamagedUnit) Equal to Treasure Chest
        • Then - Actions
          • Set UnitIndex = (Custom value of DamagedUnit)
          • For each (Integer A) from 1 to 20, do (Actions)
            • Loop - Actions
              • Set LifeThresholds = (100.00 - (5.00 x (Real((Integer A)))))
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Life of TreasureChest) Less than or equal to LifeThresholds
                  • (Load (Integer A) of UnitIndex from TreasureHash) Equal to False
                • Then - Actions
                  • Hashtable - Save True as (Integer A) of UnitIndex in TreasureHash
                  • Item - Create Gold Coins at (Center of (Playable map area))
                • Else - Actions
        • Else - Actions
Just make sure to clean up the Child of the Hashtable when the Treasure Chest is destroyed.
 
Last edited:
Level 8
Joined
May 21, 2019
Messages
435
No. Whenever the target unit loses 5 % hp, it drops a gold coin on the floor. That's just a matter to control how many gold coins it may drop in total.

Have you considered a simpler approach? How about having it drop 1 coin every time that it takes damage, until 20 coins is dropped, at which point the chest dies?

All you need to do is use the chests Custom Value to track the amount of times that it has dropped a coin.

The trigger would look something like this: (sorry for the pseudocode, I don't have access to my editor right now)
Code:
PSEUDOCODE!

(a unit takes damage)

(Unit type equal to Chest)

(If custom value of chest > 20)
{drop coin;
custom value + 1}
 else
{kill chest}

Then you just have to set the custom value to 0 when the chest is spawned and you're good to go. This trigger can be made in like 1 minute and will work with any level of damage from the attacking units. If you want the HP bar to indicate how much gold is left, simply set the HP of the chest to a percentage based on 100 - (custom value*5). This may even kill the chest when it gets set to 0%, but I am not certain, as I have never done that.

You can exclude spell damage by making it immune to spells. The way this works with just attacks feels somewhat similar to the gold chests in the Blackwater Bay map of HOTS.

Oh, and obviously, you'd need to make the chest really really tough to avoid it taking actual damage, of course.
 
Last edited:
Level 8
Joined
May 21, 2019
Messages
435
A unit is attacked will fire even if the attack doesn't finish. Meaning that you could spam Attack and Stop and create 20 coins without ever landing a single attack.
Yeah, the thought did occur to me after I wrote it... so...
A unit takes damage.
There, is there a problem with that event?
You can use conditions to exclude spells and such, or just make it immune to spells.
 
Last edited:

Uncle

Warcraft Moderator
Level 64
Joined
Aug 10, 2018
Messages
6,542
A unit takes damage doesn't exist for Any unit as far as I'm aware, you can do it for specific units but that means the chest would have to be pre-placed in the map. Unless you're referring to the new natives in the PTR patch. The whole point of using a damage detection system is because we didn't have the natives (until this latest patch, I believe) to properly detect when damage is taken.

Also, your code doesn't do what the guy is asking for. He wants it so it drops a coin for every 5% of maximum hp lost. Your code drops a coin for every 1 attack, regardless of damage dealt.

If the chest has 100 hp, and I hit it for 10 damage with my footman it will drop 2 coins.
Then if I hit the chest for 20 damage with my knight it will drops 4 coins.
Every 5 damage = 1 coin dropped in this case.
 
Last edited:
Level 8
Joined
May 21, 2019
Messages
435
A unit takes damage doesn't exist for Any unit as far as I'm aware, you can do it for specific units but that means the chest would have to be pre-placed in the map. Unless you're referring to the new natives in the PTR patch. The whole point of using a damage detection system is because we didn't have the natives (until this latest patch, I believe) to properly detect when damage is taken.

Also, your code doesn't do what the guy is asking for. He wants it so it drops a coin for every 5% of maximum hp lost. Your code drops a coin for every 1 attack, regardless of damage dealt.

If the chest has 100 hp, and I hit it for 10 damage with my footman it will drop 2 coins.
Then if I hit the chest for 20 damage with my knight it will drops 4 coins.
Every 5 damage = 1 coin dropped in this case.

I have done DPS meter systems in maps using unit groups. Quite simply, you just add the unit to the group as it is created, then track the event based on a unit taking damage in that group. This is an entirely "vanilla" approach to doing damage detection systems. No idea why it isn't more common.

Either way, my code doesn't do explicitly what he asked for, but it does do what I think he is trying to accomplish anyway, based on a portion of what he wrote in a message, namely, controlling the gold output of the chest. I think it's dangerous design to base it on hp% as that would make the chest slow in the early game, and fast in the later game, assuming that the map has some form of power accumulation.
 
Status
Not open for further replies.
Top