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

Luck Aura v1.3

Luck Aura v1.3
Once activated, it brings good luck to the caster and his allies, giving a chance to drop random items from kills depending on the level of the spell.

|cffffcc00Level 1|r - 15% of Level 1 items,
|cffffcc00Level 2|r - 20% of Level 1-3 items.
|cffffcc00Level 3|r - 25% of Level 1-4 items.
|cffffcc00Level 4|r - 30% of Level 1-5 items.

JASS:
/*
===Luck Aura v1.3
===By Mckill2009

INSTALLATION:
- Copy the custom dummy unit/buff/abilities to your map
- You MUST input or change the correct rawID's (see below)
- To view rawID, press CTRL+D in the object editor
- Copy ALL the required libraries and the LuckAura trigger to your map (SimError is optional)
- Change the CONFIGURABLES and AOE if needed
- DONE!

REQUIRED LIBRARIES:
- RegisterPlayerUnitEvent by Magtheridon96
- Table by Bribe
- SpellEffectEvent by Bribe
- *SimError (Optional)

FULL DESCRIPTION:
Once activated, it brings good luck to the caster and his allies, giving a chance to drop random items from kills depending on the level of the spell.

|cffffcc00Level 1|r - 15% of Level 1 items,
|cffffcc00Level 2|r - 20% of Level 1-3 items.
|cffffcc00Level 3|r - 25% of Level 1-4 items.
|cffffcc00Level 4|r - 30% of Level 1-5 items.

|cffffcc00Spell lasts 30 seconds.|r
*/

scope LuckAura

globals
    private Table lu //NEVER TOUCH THIS!
    private constant itemtype          ITEMID = ITEM_TYPE_ANY
    private constant real                 AOE = 600 //Buff Effect or range, make sure that this matches the AOE of DUMMY_SPELL_ID
    private constant integer         SPELL_ID = 'A001' //Hero ability rawID
    private constant integer   DUMMY_SPELL_ID = 'A000' //Buff Aura rawID
    private constant integer         DUMMY_ID = 'h000' //Dummy Unit rawID
    private constant integer             BUFF = 'B000' //Buff Aura rawID     
endglobals

//===CONFIGURABLES:
private function GetDuration takes integer i returns real
    return 30. 
endfunction

private function GetChance takes integer i returns integer
    return 10 + i * 5
endfunction
//===END OF CONFIGURABLES

private function EnemyDies takes unit u, unit k returns nothing
    local unit first
    local integer level
    call GroupEnumUnitsInRange(bj_lastCreatedGroup,GetUnitX(k),GetUnitY(k),AOE,null)
    loop
        set first = FirstOfGroup(bj_lastCreatedGroup)
        exitwhen first==null
        if UnitAlive(first) and lu.has(GetHandleId(first)) then
            set level = lu[GetHandleId(first)]
            if GetRandomInt(1,100) <= GetChance(level) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
                if level==1 then
                    call CreateItem(ChooseRandomItemEx(ITEMID, 1), GetUnitX(u), GetUnitY(u))
                else
                    call CreateItem(ChooseRandomItemEx(ITEMID, GetRandomInt(1,level+1)), GetUnitX(u), GetUnitY(u))
                endif
            endif
        endif
        call GroupRemoveUnit(bj_lastCreatedGroup,first)
    endloop
endfunction

private function EnemyDiesCond takes nothing returns nothing
    if (GetUnitAbilityLevel(GetKillingUnit(), BUFF) > 0) and IsUnitEnemy(GetKillingUnit(),GetTriggerPlayer()) then  
        call EnemyDies(GetTriggerUnit(), GetKillingUnit())
    endif
endfunction

private struct Luck
    unit caster
    unit dummy
    real duration
    
    private static timer tim
    private static integer index = 0
    private static integer array indexAR  
    private static thistype DATA
    
    private static method looper takes nothing returns nothing
        local thistype this 
        local integer i = 0
        loop
            set i = i+1
            set this = indexAR[i]
            if .duration > 0 and UnitAlive(.caster) then
                set .duration = .duration - 0.5
                call SetUnitX(.dummy, GetUnitX(.caster))
                call SetUnitY(.dummy, GetUnitY(.caster))
            else
                call KillUnit(.dummy)
                call lu.remove(GetHandleId(.caster))
                set .caster = null
                set .dummy = null
                call .destroy()
                set indexAR[i] = indexAR[index]
                set indexAR[index] = this
                set i = i-1
                set index = index - 1
                if index==0 then
                    call PauseTimer(tim)
                    call DestroyTimer(tim)
                endif     
            endif            
            exitwhen i==index
        endloop
    endmethod
                                             
    private static method cast takes nothing returns nothing
        local thistype this
        local unit u = GetTriggerUnit()
        local timer t
        local integer timerID
        local integer casterID = GetHandleId(u)
        local integer level  
        local player p = GetTriggerPlayer()
        if not lu.has(casterID) then
            set this = allocate()
            set level = GetUnitAbilityLevel(u, SPELL_ID)
            set lu[casterID] = level
            set .caster = GetTriggerUnit()
            set .dummy = CreateUnit(p, DUMMY_ID, GetUnitX(u), GetUnitY(u), 0)            
            set .duration = GetDuration(level)
            if index==0 then
                set tim = CreateTimer()
                call TimerStart(tim, 0.5, true, function thistype.looper) 
            endif
            set index = index + 1
            set indexAR[index] = this
            call UnitAddAbility(dummy, DUMMY_SPELL_ID)
            call SetUnitAbilityLevel(dummy, DUMMY_SPELL_ID, level) 
        else
            call IssueImmediateOrder(u, "stop")
            static if LIBRARY_SimError then
                call SimError(p, "Can't be used yet!")
            endif
        endif
        set u = null
        set p = null
    endmethod
    
    private static method onInit takes nothing returns nothing
        call RegisterSpellEffectEvent(SPELL_ID, function thistype.cast)
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function EnemyDiesCond)
        set lu = Table.create()
    endmethod
endstruct

endscope


v1.3
- Changed to structs
- Uses only 1 timer
- Uses 2 additional libraries

v1.2
- 6 Lines reduced.
- "u" is nulled from FilterThem.
- Ability level reduced to 4, as suggested by Bribe (I dont really understand why).
- Description changed.

v1.1
- Converted to vJass with optimized coding.
- Uses known libraries.
- Description changed.


Keywords:
aura, active, passive, item, enemy, powerup
Contents

Luck Aura (Map)

Reviews
13 Dec 2011 Bribe: Approved.
Level 22
Joined
Nov 14, 2008
Messages
3,256
Nice to see you stepping up your coding knowledge. So here we go.

-Don't use actions, merge them with the conditions.

-Trigger t2 shouldn't have actions either, just add condition and make the actions there.

And remember to return false afterwards.

-Instead of creating your own hashtable, use this: Link to Bribe's New Table

-Please use TimerUtils, you never destroy timers in vJASS.

-These are unneeded. What are you trying to do? Shadow globals?

private integer level
private integer targetID

-No need to use this local or shadowed global in the filter, it's only used once anyway.
set targetID = GetHandleId(u)

-GetOwningPlayer(u) -> GetTriggerPlayer() in the cast trigger.

-In the loop function, the GetUnitX(u), GetUnitY(u) should be stored into locals, saving two additional calls.

-Should be a local integer
set level = LoadInteger(HASH, timerID, 5)// This is a global variable

That's what I could find for now. You should really learn to use the ability to use libraries in vJASS. Will save ya lots of work. Example that could make this even better is to use Axarion's advanced aura module.
 
JASS:
private function LUCK_DEATHS takes nothing returns nothing
    local unit k = GetTriggerUnit()
    local unit u = GetKillingUnit()
    local real x = GetUnitX(k)
    local real y = GetUnitY(k)
    local integer targetID = GetHandleId(u) // This is a global variable
    local integer level = LoadInteger(HASH, targetID, 1) 
    if IsUnitEnemy(u, GetOwningPlayer(k)) and GetRandomInt(1,100) <= LUCK_CHANCE(level) and IsUnitType(k, UNIT_TYPE_STRUCTURE)==false then
        if GetUnitAbilityLevel(u, BUFF) > 0 then
            call CreateItem(ChooseRandomItemEx(ITEMID, level), x, y)
        endif
    endif
    set k = null
    set u = null
endfunction

Since you only used x and y once, it's better to just use the functions :)
You don't need locals if you're only going to use them once :)

JASS:
private function LUCK_FILTER takes nothing returns boolean
    local unit u = GetFilterUnit()
    local integer targetID
    if IsUnitAlly(u, GetOwningPlayer(u)) and GetUnitAbilityLevel(u, BUFF) > 0 then
        set targetID = GetHandleId(u)
        call SaveInteger(HASH, targetID, 1, level) 
    endif
    return true
endfunction

Since it would make no difference, I think it's better to just set the targetID to the handle id
of the unit on local declaration :)

JASS:
private function LUCK_LOOP takes nothing returns nothing
    local timer t = GetExpiredTimer()
    local integer timerID = GetHandleId(t)
    local unit u = LoadUnitHandle(HASH, timerID, 1)
    local unit dummy = LoadUnitHandle(HASH, timerID, 2)
    local real duration = LoadReal(HASH, timerID, 3)
    local real AoE = LoadReal(HASH, timerID, 4)
    local integer casterID = GetHandleId(u)
    set level = LoadInteger(HASH, timerID, 5)// This is a global variable
    if duration >0 and GetWidgetLife(u) >= 0.405 then
        call SetUnitX(dummy, GetUnitX(u))
        call SetUnitY(dummy, GetUnitY(u))
        call SaveReal(HASH, timerID, 3, duration - TIMERSPEED) 
        call GroupEnumUnitsInRange(bj_lastCreatedGroup, GetUnitX(u), GetUnitY(u), AoE, Filter(function LUCK_FILTER))
    else
        call UnitApplyTimedLife(dummy, 'BTLF', 0.01)
        call SetUnitExploded(dummy, true)
        call FlushChildHashtable(HASH, timerID)       
        call FlushChildHashtable(HASH, casterID)
        call PauseTimer(t)
        call DestroyTimer(t)
    endif
    set t = null
    set u = null
    set dummy = null 
endfunction

This would be a good place to declare 2 local real variables for the units
x and y since you're using the functions more than once.

Also, Filter(function LUCK_FILTER))
You don't really need to use the "Filter".
Without it, it would still function similarly :]
Using it in this case is a waste of efficiency :/

JASS:
private function LUCK_CAST takes nothing returns nothing
    local timer t = CreateTimer()
    local unit u = GetTriggerUnit()
    local unit dummy
    local integer timerID = GetHandleId(t)
    local integer casterID = GetHandleId(u)
    local integer level = GetUnitAbilityLevel(u, ABILITY_ID) 
    local real duration = LUCK_DURATION(level)
    local real AoE = LUCK_AOE(level)
    local texttag tag = CreateTextTag()
    if HaveSavedReal(HASH, casterID, 1)==false then
        set dummy = CreateUnit(GetOwningPlayer(u), DUMMY_ID, GetUnitX(u), GetUnitY(u), 0)    
        call UnitAddAbility(dummy, DUMMYABILITY_ID)
        call SetUnitAbilityLevel(dummy, DUMMYABILITY_ID, level) 
        
        //SAVE FOR THE CASTER ID
        call SaveReal(HASH, casterID, 1, duration)
        
        //SAVE FOR THE TIMER ID
        call SaveUnitHandle(HASH, timerID, 1, u)
        call SaveUnitHandle(HASH, timerID, 2, dummy)
        call SaveReal(HASH, timerID, 3, duration)
        call SaveReal(HASH, timerID, 4, AoE)
        call SaveInteger(HASH, timerID, 5, level)
        call TimerStart(t, TIMERSPEED, true, function LUCK_LOOP) 
    else
        call IssueImmediateOrder(u, "stop")
        call SetTextTagPosUnit(tag, u, 0.) 
        call SetTextTagText(tag, "You can't use this yet!", 0.025)
        call SetTextTagPermanent(tag, false)
        call SetTextTagVelocity(tag, 0.03, 0.03)
        call SetTextTagLifespan(tag, 3)
        call SetTextTagFadepoint(tag, 0.01)        
    endif
    set t = null
    set u = null
    set dummy = null
    set tag = null
endfunction

This function looks good :)


I think that almost ANY spell that happens to be efficient and original
deserves a 5/5 :) (In 90% of all cases that is ;P)
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
thanks for all your suggestion guys, however, I just started to make vJASS spells since my JNGP fucked up before...So I may need time to learn libraries and structs...

I really need to use filter since I dont want to pick up every unit, but I think I will change it to "GetUnitAbilityLevel(u, BUFF) > 0" only...

As for merging actions, Ima excercise that next update...

@Marcos DAB
The configurations are there for a reason :)...
 

Bribe

Code Moderator
Level 50
Joined
Sep 26, 2009
Messages
9,464
Currently your system will cause errors - what if RemoveUnit is called? The death event won't fire and you're stuck with unused space in your hashtable that will probably never be clear thus filling up unnecessary memory.

If you use a unit-indexing system, you will not have to worry about this as the indexer takes care of the deindexing portion and the arrays' data will be replaced with other data at some point.
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
Bribe: "FilterThem" needs to null the local unit. However, you'd be better using a FirstOfGroup loop instead of a filter, anyway.

Please fix the first bug mentioned, also please note that abilities shouldn't usually support more than 4 levels because then the object data won't get converted to SLK and the map loading time gets enormous.

Changed most of it however I really dont know why I'd be using FirstOfGroup coz
TimerLoop is meant to Filter 'all' those who gets near the hero, not the FirstOfGroup...
 
Level 29
Joined
Mar 10, 2009
Messages
5,016
you mean something like this?...
I'm surprised, its faster :)...
JASS:
function Actions takes nothing returns nothing
    local unit u
    call GroupEnumUnitsInRange(bj_lastCreatedGroup,0,0,5000,null)  
    loop
        set u = FirstOfGroup(bj_lastCreatedGroup)
        exitwhen u==null
        if not IsUnitType(u, UNIT_TYPE_DEAD) then
            call KillUnit(u)
            set I = I+1
            call BJDebugMsg(I2S(I))
        endif
        call GroupRemoveUnit(bj_lastCreatedGroup, u)
    endloop
endfunction
 
Top