• 🏆 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!
  • 🏆 Hive's 6th HD Modeling Contest: Mechanical is now open! Design and model a mechanical creature, mechanized animal, a futuristic robotic being, or anything else your imagination can tinker with! 📅 Submissions close on June 30, 2024. Don't miss this opportunity to let your creativity shine! Enter now and show us your mechanical masterpiece! 🔗 Click here to enter!

[vJASS] Help Make This Spell MUI

Status
Not open for further replies.
Level 17
Joined
Feb 11, 2011
Messages
1,860
Hello guys,

I would like to know how to make this spell MUI.
JASS:
scope FrostNova initializer InitTrigger

    globals
        private unit TriggerUnit = null
        private unit TargetUnit = null
        private unit FilterUnit = null
        private group g = CreateGroup()
    endglobals
    
    private function Group_Conditions takes nothing returns boolean
       set FilterUnit = GetFilterUnit()
       if (IsUnit(FilterUnit, TargetUnit) == false) and (IsPlayerEnemy(GetOwningPlayer(TriggerUnit), GetOwningPlayer(FilterUnit)) == true) and (IsUnitType(FilterUnit, UNIT_TYPE_DEAD) == false) then
            set bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(TriggerUnit), 'h003', GetUnitX(TargetUnit), GetUnitY(TargetUnit), bj_UNIT_FACING)
            call UnitAddAbility(bj_lastCreatedUnit, 'A02T')
            call RemoveGuardPosition(bj_lastCreatedUnit)
            call SetUnitAbilityLevel(bj_lastCreatedUnit, 'A02T', GetUnitAbilityLevel(TriggerUnit, 'A02G'))
            call IssueTargetOrder(bj_lastCreatedUnit, "thunderbolt", FilterUnit)
            call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', 2)
            call GroupRemoveUnit(g, FilterUnit)
        endif
        return false
    endfunction
        
    private function Timer_Actions takes nothing returns nothing
        call GroupEnumUnitsInRange(g, GetUnitX(TargetUnit), GetUnitY(TargetUnit), 200, Condition(function Group_Conditions))
    endfunction
    
    private function Actions takes nothing returns nothing
        local timer t = CreateTimer()
        call TimerStart(t, 0.5, false, function Timer_Actions)
        set t = null
    endfunction
    
    private function Conditions takes nothing returns boolean
        if (GetSpellAbilityId() == 'A02G') then
            set TriggerUnit = GetTriggerUnit()
            set TargetUnit = GetSpellTargetUnit()
            call Actions()
        endif
        return false
    endfunction
    
    private function InitTrigger takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function Conditions))
        set t = null
    endfunction

endscope
Frost Nova
Blasts a target with ice, damaging it. After 0.5 seconds, the ice explodes, sending shards to all enemies within 200 range, stunning them for 3 seconds and dealing minor damage. Original target does not get hit by an ice shard.

Level 1: 75 target damage; 65 ice shard damage
Level 2: 125 target damage; 105 ice shard damage
Level 3: 175 target damage; 145 ice shard damage
Level 4: 225 target damage; 185 ice shard damage
Level 5: 275 target damage; 225 ice shard damage
This is based off the Frost Nova spell. Only the targeted unit is damaged directly by the nova. After 0.5 seconds, all enemies within 200 range of the target are targeted by a frost bolt.

Thanks for any help.
 
Level 14
Joined
Nov 18, 2007
Messages
1,084
The reason this spell is not MUI is because it uses global variables to store the caster and target unit.

The easiest way for you to make this MUI would be to use a struct and a timer attachment system, like TimerUtils.

For now, you could think of structs as a new variable type that can hold information you specify.
Declare the struct before function Actions. Your struct would have a unit caster member and a unit target member to store those values instead of using a global.
JASS:
struct dat
    unit caster
    unit target
endstruct
Now, you would create an instance of the struct whenever you cast the spell, and set those members to their respective values. Then, using TimerUtils, you would get a new timer and store the struct instance to the timer.
JASS:
    // I'm coding everything in Conditions for the sake of briefer code
    private function Conditions takes nothing returns boolean
        local dat d
        local timer tim
        if (GetSpellAbilityId() == 'A02G') then
            set d = dat.create() // This is the syntax for creating an instance of your struct.
            set d.caster = GetTriggerUnit()
            set d.target = GetSpellTargetUnit()

            set tim = NewTimer() // This is how you get a timer from TimerUtils.
            call SetTimerData(tim, d) // This stores the struct instance to the timer.
            call TimerStart(tim, 0.5, false, function Timer_Actions)
            set tim = null // Not completely necessary since the timers are recycled but for good practice
        endif
        return false
    endfunction
Then in Timer_Actions, you would retrieve the struct instance from the timer with local dat d = GetTimerData(GetExpiredTimer()). Your caster and target members would then be available to use in that function. After you do your group enumeration, remember to destroy d afterwards with d.destroy()and call ReleaseTimer on the timer to recycle it.

Note that it would be easier to use a FirstOfGroup loop for the group enumeration as you wouldn't need to use global variables.

I'd also recommend taking a look at this.
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Okay, thanks man! Much appreiciated. I will take a look at those things and let you know how I progress.

EDIT: Is it okay to use locals in a condition function? It will declare the locals every time a spell is cast...
 
Last edited:
Level 17
Joined
Feb 11, 2011
Messages
1,860
Update: I'm getting an error message "Undefined function Actions". I know I'm doing something silly wrong. What is it? Can you also help me to optimize this (release timer, destroy instances etc.)?
JASS:
scope FrostNova

    struct dat
        unit Caster = null
        unit Target = null

        private method Actions takes nothing returns nothing
            call KillUnit(this.Caster)
        endmethod

        static method altcreat takes unit u, unit targ returns dat
            local dat r = dat.create()
            local timer tim = NewTimer()
            set r.Caster = u
            set r.Target = targ
            call SetTimerData(tim, r)
            call TimerStart(tim, 0.5, false, function Actions)
            call r.destroy()
         return r
        endmethod

    endstruct

    function FrostNova takes nothing returns nothing
        local dat e = dat.altcreat(GetTriggerUnit(), GetSpellTargetUnit())
        call e.destroy()
    endfunction

endscope
 
Level 4
Joined
Mar 27, 2008
Messages
112
Try this:
JASS:
scope FrostNova

    private struct dat //making this private as no other function will probably use this, otherwise remove the private (the private is so you can create multiple structs named dat for each spell no point making up names for each spell for the struct of it right;))
        unit Caster = null
        unit Target = null

        private static method Actions takes nothing returns nothing //making it static every method called through another function (like TimerStart) has to be static
            local dat d = GetTimerData(GetExpiredTimer()) //retrieving the data attached tot he timer
            call KillUnit(d.Caster)
            call d.destroy() //destroying the struct instance so it can be used again
        endmethod

        static method altcreat takes unit u, unit targ returns dat
            local dat r = dat.create()
            local timer tim = NewTimer()
            set r.Caster = u
            set r.Target = targ
            call SetTimerData(tim, r)
            call TimerStart(tim, 0.5, false, function dat.Actions) //you can also use thistype.Actions, thistype refers to the structs name
            //call r.destroy() you were destroying it too early before even returning it that means you basicly return 0, and the data could not be retrieved in the actions function
         return r
        endmethod

    endstruct

    function FrostNova takes nothing returns nothing
        local dat e = dat.altcreat(GetTriggerUnit(), GetSpellTargetUnit())
        call e.destroy()
    endfunction

endscope
 
Level 17
Joined
Feb 11, 2011
Messages
1,860
Okay, thanks. I have made the complete spell now. Would you mind checking it for areas where it can be improved? Thanks!
JASS:
scope FrostNova initializer InitTrigger
    
    globals
        private group G = CreateGroup()
    endglobals
    
    private struct Data
        unit caster
        unit target

        static method create takes unit c, unit t returns Data
            local Data D = Data.allocate() 
            set D.caster = c
            set D.target = t
            return D
        endmethod
    endstruct

    private function Group_Conditions takes nothing returns boolean
        return (IsUnitEnemy(GetFilterUnit(), GetOwningPlayer(GetTriggerUnit())) == true) and (IsUnitType(GetFilterUnit(), UNIT_TYPE_DEAD) == false)
    endfunction
    
    private function Timer_Actions takes nothing returns nothing
        local timer t = GetExpiredTimer()
        local Data D = Data(GetTimerData(t))
        local real x = GetUnitX(D.target)
        local real y = GetUnitY(D.target)
        local unit u = null
        set G = CreateGroup()
        call GroupEnumUnitsInRange(G, x, y, 250, Condition(function Group_Conditions))
        loop
            set u = FirstOfGroup(G)
            exitwhen u == null
            if (u != D.target) then
                set bj_lastCreatedUnit = CreateUnit(GetOwningPlayer(D.caster), 'h001', x, y, 0)
                call UnitAddAbility(bj_lastCreatedUnit, 'ACcb')
                call IssueTargetOrder(bj_lastCreatedUnit, "thunderbolt", u)
                call UnitApplyTimedLife(bj_lastCreatedUnit, 'BTLF', 5)
            endif
            call GroupRemoveUnit(G, u)
        endloop
        call ReleaseTimer(t)
        set t = null
        call D.destroy()
    endfunction

    private function Actions takes nothing returns nothing
        local Data D = Data.create(GetTriggerUnit(), GetSpellTargetUnit())
        local timer t = NewTimer()
        call SetTimerData(t, integer(D))
        call TimerStart(t, 0.5, true, function Timer_Actions)
        set t = null
    endfunction

    private function Conditions takes nothing returns boolean
        if (GetSpellAbilityId() == 'AUfn') then
            call Actions()
        endif
        return false
    endfunction

    private function InitTrigger takes nothing returns nothing
        local trigger t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT)
        call TriggerAddCondition(t, Condition(function Conditions))
    endfunction
    
endscope
 
Last edited:
Level 14
Joined
Nov 18, 2007
Messages
1,084
  • You only need to create group G once.
  • == true is not necessary. This is more nitpicky but == false can be replaced with not in front on the condition.
  • You don't need the filter for your group enumeration. Just do the check inside the FirstOfGroup loop.
  • If you have the proper dummy settings, you only need to create one dummy unit and make it issue the stun ability on all the units for the group enumeration.
 
Status
Not open for further replies.
Top