HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

Spell Help. Again.

07-29-2009, 03:58 PM#1
Cheezeman
Hello mates.
I've been working on a spell lately. The basic idea of it is that when an enemy unit gets in range of a dummy unit (which is created on cast), it'll have a special effect created on them.
Yes I plan to extend this later, but right now I just want the basics to work.

I'll probably run into more issues soon, but right now I'm having this problem that the event TriggerRegisterUnitInRange() never fires, even if it's just any unit at all. I've tried several cases, including removing filters etc. but nothing seems to work.
Can someone help me?

By the way, coding is horrible I know, but my knowledge is limited.
There's no clean up what so ever and it's because I will add it once it's done.

List of stuff that has been checked:
- Actions run when cast

Here's the code
Collapse JASS:
scope SomeSpell initializer Initialize
//===========================================================================
//=============================== SETUP =====================================
//===========================================================================

    globals
        private constant integer    SPELL_ID        = 'A000'
        private constant integer    DUMMY_ID        = 'h000'
        private constant integer    EFFECT_DUMMY_ID = 'h003'
        private constant real       INTERVAL        = 1.
    endglobals

    private constant function Area takes integer level returns real
        return 100. + ( 100. * level )
    endfunction
    
    private constant function Duration takes integer level returns real
        return 10. + level //This isn't used yet
    endfunction
    
    //====================== Special settings ============================
    //========= These settings are for 'advanced' Jass/vJass users =======
    
    globals
        private constant integer MAX_HANDLE_COUNT   = 40800
    endglobals

    private function AbilityPreload takes nothing returns nothing
        set bj_lastCreatedUnit = CreateUnit( Player(15), DUMMY_ID, 0, 0, 0)
        call UnitAddAbility( bj_lastCreatedUnit, SPELL_ID )
        call KillUnit( bj_lastCreatedUnit )
    endfunction
    
    private function AllowedTargets takes unit caster, unit target returns boolean
        return IsUnitEnemy( target, GetOwningPlayer( caster ) )
    endfunction

//===========================================================================
//============================= END SETUP ===================================
//================ Don't mess with the code below this line =================
//===========================================================================
    globals
        private unit                TemporareCaster
        private boolexpr            DummyFilter
    
        private trigger     array   Trigger[MAX_HANDLE_COUNT]
        private unit        array   Caster[MAX_HANDLE_COUNT]
        private unit        array   Target[MAX_HANDLE_COUNT]
        private real        array   SpellX[MAX_HANDLE_COUNT]
        private real        array   SpellY[MAX_HANDLE_COUNT]
    endglobals
    
    private function TargetsCaller takes nothing returns boolean
        return true //AllowedTargets( GetSourceUnit( GetTriggeringTrigger() ), GetTriggerUnit() )
    endfunction
    
    private function H2I takes handle h returns integer
        return h
        return 0
    endfunction
    private function GetHandleId takes handle u returns integer
        return H2I(u) - 0x100000
    endfunction
    
    private function AntiLeakFilter takes nothing returns boolean
        return true
    endfunction
    
    private function Conditions takes nothing returns boolean
        return GetSpellAbilityId() == SPELL_ID
    endfunction
//===========================================================================
    
    private function CreateEffect takes nothing returns nothing
//Nevermind this function, it's never activated (yet)
        local timer t = GetExpiredTimer()
        local integer dummy_id = GetTimerData( t )
        local trigger trig = Trigger[dummy_id]
        local unit dummy = GetSourceUnit( trig )
        local unit target = Target[dummy_id]
        local unit caster = Caster[dummy_id]
        local integer level = GetUnitAbilityLevel( caster, SPELL_ID )
    
        if IsUnitInRangeXY( target, SpellX[dummy_id], SpellY[dummy_id], Area( level ) ) then
            call DestroyEffect( AddSpecialEffectTarget( "Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl", target, "origin" ) )
        else
            //Release timer and clean up
        endif
    endfunction
    
    private function StartTimerForUnit takes nothing returns nothing
        local unit dummy = GetSourceUnit( GetTriggeringTrigger() )
        local integer dummy_id = GetHandleId( dummy )
        local timer t = NewTimer()

        call BJDebugMsg( "Spell has reached timer" ) //Never displayed
        set Target[dummy_id] = GetTriggerUnit()
        call SetTimerData( t, dummy_id )
        call TimerStart( t, INTERVAL, true, function CreateEffect )
    endfunction
    
    private function Actions takes nothing returns nothing
        local unit caster = GetTriggerUnit()
        local location loc = GetSpellTargetLoc()
        local real spell_x = GetLocationX( loc )
        local real spell_y = GetLocationX( loc )
        local unit dummy = CreateUnit( GetOwningPlayer( caster ), EFFECT_DUMMY_ID, spell_x, spell_y, 0. )
        local integer dummy_id = GetHandleId( dummy )
        local integer level = GetUnitAbilityLevel( caster, SPELL_ID )
        local boolexpr cond_filter = Condition( function TargetsCaller )

        set Caster[dummy_id] = caster
        set Trigger[dummy_id] = CreateTrigger()
        set SpellX[dummy_id] = spell_x
        set SpellY[dummy_id] = spell_y

        call TriggerRegisterUnitInRangeSource( Trigger[dummy_id], dummy, Area( level ), DummyFilter )
//I made this function in a library (located below). I've tried with TriggerRegisterUnitInRange but it gives me the same result.
//I also replaced DummyFilter with null but nothing happens
//SpellX, SpellY and Area are all correct (checked)
        call TriggerAddCondition( Trigger[dummy_id], cond_filter ) //Returns true no matter what
        call TriggerAddAction( Trigger[dummy_id], function StartTimerForUnit )
    endfunction

//===========================================================================
    private function Initialize takes nothing returns nothing
        local trigger t = CreateTrigger()
        local boolexpr cond_filter = Condition( function Conditions )
        local integer ForLoop = 0
        
        set DummyFilter = Condition( function AntiLeakFilter )
        call AbilityPreload()
        
        loop
            call TriggerRegisterPlayerUnitEvent( t, Player(ForLoop), ConvertPlayerUnitEvent(274), DummyFilter )
            set ForLoop = ForLoop + 1
            exitwhen ForLoop > 15
        endloop
        call TriggerAddCondition( t, cond_filter )
        call TriggerAddAction( t, function Actions )
        
        call DestroyBoolExpr( cond_filter )
        set cond_filter = null
    endfunction
endscope

Here's the library I talked about.
The spell uses TimerUtils and GroupUtils aswell.
Expand JASS:
07-29-2009, 08:07 PM#2
Hans_Maulwurf
People here will tell you that dynamic triggers are evil and you should use a timer and groupenum or such instead…
And you REALLY should start using structs instead of all these arrays with H2I

However, does "Actions" run? Check if a DebugMsg after call TriggerRegisterUnitInRangeSource shows. If yes, does the DebugMsg in "StartTimerForUnit" show when you comment out the locals and its the first line in that function.

call DestroyBoolExpr( cond_filter ) Does this make sense? When you destroy the BoolExpr it doesn't exists anymore when the trigger checks it. So this would be like passing null in the first place? Somebody correct/enlighten me here pls.
07-29-2009, 08:42 PM#3
Cheezeman
Yes Actions run.
I think you're right there, I thought I was cleaning while doing that but maybe it's null now... Still, it shouldn't matter, cause StartTimer isn't ran anyway

by the way, thanks for the non-aggressive comments.
07-30-2009, 09:44 PM#4
Themerion
Some constructive critique:

I'd go for code readability here. I don't think you win anything particular in performance (I also think the null leak is not worth paying attention to), and you can shorten code by reusing what already exists:

Collapse JASS:
set DummyFilter = Condition( function AntiLeakFilter )
        
loop
    call TriggerRegisterPlayerUnitEvent( t, Player(ForLoop), ConvertPlayerUnitEvent(274), DummyFilter )
    set ForLoop = ForLoop + 1
    exitwhen ForLoop > 15
endloop

//===================================//
// SWAP FOR THE FUNCTION YOU TOOK IT FROM 
//===================================//

call TriggerRegisterAnyUnitEventBJ(t,EVENT_PLAYER_UNIT_?)

Also, you shouldn't destroy the BoolExpr. Just make it a one line addition:

Collapse JASS:
local boolexpr cond_filter = Condition( function Conditions )

call TriggerAddCondition( t, cond_filter )

call DestroyBoolExpr( cond_filter )
set cond_filter = null

//=========================
// MUCH SHORTER. AYE? :)
//=========================

call TriggerAddCondition( t, Condition( function Conditions ) )

Quote:
People here will tell you that dynamic triggers are evil and you should use a timer and groupenum or such instead…
Nobody's going to debug that for you, because you shouldn't do it that way. If you like, I can explain how to do it with a timer, GroupEnum, and structs instead.
07-31-2009, 12:21 PM#5
Cheezeman
Before I start replying I'd like to notify you that I don't start nulling variables untill the spell is done (it saves some work).

--

ConvertPlayerUnitEvent(274) is the same as EVENT_PLAYER_UNIT_SPELL_EFFECT, check it out in the function list (I wanted to avoid a 'BJ').
And I'm using the loop because I'm not sure if the null boolexpr filter leaks or not, but that's for another thread.

Oh and that dumb condition filter. As Hans_Maulwurf mentioned I'm nulling it, so the trigger will refer to nothing when checking the spell. I thought I changed it, but apparently not.
Still, this doesn't solve my wierd issue; StartTimer doesn't run.
07-31-2009, 12:34 PM#6
Anitarf
Quote:
Originally Posted by Cheezeman
ConvertPlayerUnitEvent(274) is the same as EVENT_PLAYER_UNIT_SPELL_EFFECT, check it out in the function list (I wanted to avoid a 'BJ').
Why? You use plenty of other constant global variables, why not use this one? Why would it being a blizzard variable make it behave differently from the ones you use?
07-31-2009, 01:17 PM#7
Cheezeman
Duh, nothing really, I was only curious if it worked, and it did.

Back to topic:
I think it's a problem with the TriggerRegisterUnitInRange() because I've had that problem before, maybe they can't handle variables for it?
That wouldn't make much sence since the 'normal' use of this uses gg_trg_ variables...

Really, I should stop this spell. I feel like I'm eating with my eyes. I should learn structs before I try this again.
07-31-2009, 02:27 PM#8
Themerion
Quote:
Really, I should stop this spell. I feel like I'm eating with my eyes. I should learn structs before I try this again.

Or, the people here help you update this thing (so that it uses structs). I'm fairly sure very few are willing to try with TriggerRegisterUnitInRange, though.

Quote:
And I'm using the loop because I'm not sure if the null boolexpr filter leaks or not, but that's for another thread.

Even if it does, it's a very insignificant one time leak. As stated above. Go for readability and use the BJ.

For your problems, how far does the function actually go? Add more BJDebug-messages. So we know where it stops. Also do some BJDebugMsg( I2S( handle_id ) ) so you can see if they are the same.
07-31-2009, 03:00 PM#9
Hans_Maulwurf
I wrote a simple scope for you. It creates an effect on the first unit that comes within range of the target spell location (what you are trying, if i got you right). But using a struct and TimedLoop + GroupEnum. Note that it's very modular (what is good and everything should be imo). Don't get confused by that. Just copypasta the required librarys (or create BOOLEXPR_TRUE and ENUM_GROUP yourself if you are too lazy…)
IMPORTANT NOTE: I just wrote that in gedit and didn't test it, because.. well i hate rebooting to Windows
But even if there are some typos or small bugs, this should give you an idea how to solve such a task without dynamic triggers and learn how to use structs
Expand JASS:

I hope this simple code speaks for itself without a lot of documentation
07-31-2009, 04:56 PM#10
jwallstone
Wait a minute, if all you're trying to do is to put a special effect on every unit within range of a dummy, just give it an aura with a buff that has that effect. That's all you have to do. What's with all the complicated triggers to achieve something the game already does?

EDIT: If you want the effect to last for a certain duration even after they leave the region, just use something like Disease Cloud or Phoenix Fire to attach the effect, with 0 damage of course.
07-31-2009, 10:24 PM#11
Cheezeman
@Themerion
I'm sure they will code something like that, but since I don't have my own map at the moment I'm here to learn, not to achieve. Which makes me wanna be there at the initial design so I won't confuse myself into the wonders of programming.

And that loop thingy, yes 16 leaks isn't much of an issue, I agree.

@Hans_Maulwurf
I really appreciate your help, I really do, but frankly, this doesn't explain anything at all.
There are certain things I do not understand with structs/methods etc;
- When you call .startTimedLoop, this starts some timer which call onTimedLoop (am I right?)
How?
- Can you create your own variable types? Like "OurSpell"?

There are of course more things which I'm uncertain off, but that'll be it for now.

@jwallstone
Of course, I do plan to make more than just a special effect, and this isn't possible without a looping timer.



Edit: I just rememberd that I'll be gone from now on until end of next week, so there won't be much of a discussion. I'll be sure to read your answers though, and I hope someone has an idea of why StartTimer isn't run... gah it kills me!
08-01-2009, 05:24 AM#12
Pyrogasm
Well, to answer your question about .startTimedLoop(), you should look at the TimedLoop Module thread. Modules very similar to TextMacroes, and so by adding in the implement TimedLoop line, you a new method to the struct (startTimedLoop).

TimedLoop is useful for making a lot of code look much less ugly when you want to loop struct instances on a very fast periodic timer. In your case, UnitInRange just isn't going to work because it would require dynamic triggers, so the solution is to run a fast periodic timer and check to see if there are units in range of the dummy and simulate the event yourself.

The code he wrote calls .startTimedLoop() which is a function added by the TimedLoop module, and yes it starts a timer that calls .onTimedLoop.

As for "OurSpell", yes you can define your own objects. A struct is a type of object, so any and all structs you make are types you can use for globals, locals, even other struct members.

For example:
Collapse JASS:
struct Point
    real X
    real Y
endstruct

//I can then do this:
globals
    private Point MyPointObject
    public Point array MorePoints
endglobals

I would suggest you read this to help understand structs in a spellmaking context more fully.
08-01-2009, 01:06 PM#13
Hans_Maulwurf
Like Pyro said, all the work is done by the TimedLoop library

The line implement TimedLoop "imports" this library and allows you to use call .startTimedLoop(). This will start a timer which will run the function "OnTimedLoop" for you. It will run as long as you return TimedLoop_CONTINUE and will stop when you return TimedLoop_STOP. Everything is done fully automatically for you.

And since we called the struct OurSpell, we can use this like a variable.
Collapse JASS:
struct Point
    real x
    real y
endstruct

function UseAStruct takes nothing returns nothing
    local Point SomePoint = Point.create()
    set SomePoint.x = 100
    set SomePoint.y = 200
    call SomePoint.detroy()
endfunction
See how "SomePoint" is a instance of a package of all the variables defined in the struct.
You can not just only use variables within a struct, but also methods. Default methods are .create() and .destory(). However you can also modify them. The default .create only does .allocate(). But like this you can make your own version:
Collapse JASS:
struct Point
    real x
    real y

    static method create takes real x, real y returns Point
        local Point this = OurSpell.allocate()

        set this.x = x
        set this.y = y

        return this
    endmethod
endstruct

function UseAStruct takes nothing returns nothing
    local Point SomePoint = Point.create(100, 200)

    call SomePoint.detroy()
endfunction