| 07-29-2009, 03:58 PM | #1 |
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 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. JASS://=========================================================================== // // // GetSourceUnit() // Made by Cheezeman // // This library lets you store and retrive the source unit of a // TriggerRegisterUnitInRange() event. // It was constructed because Blizzard never made such a native // // Uses: // - TriggerRegisterUnitInRangeSource( trigger whichTrigger, unit whichUnit, real Range, boolexpr filter ) returns event // Same as TriggerRegisterUnitInRange, but saves the source // Common use is [set source = GetSourceUnit( GetTriggeringTrigger() )] // // - GetSourceUnit( trigger whichTrigger ) returns unit // Returns the source unit // // - ClearSource( trigger whichTrigger ) // Just for nulling the source/clean up // // //=========================================================================== library InRangeWithSource globals private unit array InRangeSource[40800] endglobals private function H2I takes handle h returns integer return h return 0 endfunction private function GetHandleId takes handle h returns integer return H2I( h ) - 0x100000 endfunction //=================================================================== function TriggerRegisterUnitInRangeSource takes trigger whichTrigger, unit whichUnit, real range, boolexpr filter returns event local integer trigger_id = GetHandleId( whichTrigger ) set InRangeSource[trigger_id] = whichUnit return TriggerRegisterUnitInRange( whichTrigger, whichUnit, range, filter ) endfunction function GetSourceUnit takes trigger whichTrigger returns unit local integer trigger_id = GetHandleId( whichTrigger ) return InRangeSource[trigger_id] endfunction function ClearSource takes trigger whichTrigger returns nothing local integer trigger_id = GetHandleId( whichTrigger ) set InRangeSource[trigger_id] = null endfunction //=================================================================== endlibrary |
| 07-29-2009, 08:07 PM | #2 |
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 |
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 | |
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: 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: 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:
|
| 07-31-2009, 12:21 PM | #5 |
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 | |
Quote:
|
| 07-31-2009, 01:17 PM | #7 |
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 | ||
Quote:
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:
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 |
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 JASS:scope ComesInRangeDetection initializer Init requires TimedLoop, GroupUtils, BoolexprUtils globals private constant integer SpellId = 'A000' private constant real AoE = 200 private constant string Effect = "Abilities\\Spells\\Other\\GeneralAuraTarget\\GeneralAuraTarget.mdl" endglobals private struct OurSpell real x real y private method onTimedLoop takes nothing returns boolean //This is the loop local unit u call GroupClear(ENUM_GROUP) call GroupEnumUnitsInRange(ENUM_GROUP, .x, .y, AoE, BOOLEXPR_TRUE) set u = FirstOfGroup(ENUM_GROUP) if u != null then call DestroyEffect( AddSpecialEffectTarget( Effect, u, "origin" ) ) set u = null return TimedLoop_STOP endif return TimedLoop_CONTINUE endmethod implement TimedLoop //Here we "import" the TimedLoop library, so we can easily use ".startTimedLoop()" which will run the "onTimedLoop" function static method create takes location taget returns OurSpell local OurSpell this = OurSpell.allocate() set .x = GetLocationX(target) set .y = GetLocationY(target) call .startTimedLoop() //Here we start the loop return this endmethod endstruct private function OnSpellEffect takes nothing returns boolean if GetSpellAbilityId() == SpellId then call OurSpell.create(GetSpellTargetLoc()) endif return false endfunction private function Init takes nothing returns nothing local trigger t = CreateTrigger() call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition(t, Condition(function OnSpellEffect)) endfunction endscope I hope this simple code speaks for itself without a lot of documentation |
| 07-31-2009, 04:56 PM | #10 |
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 |
@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 |
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: 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 |
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. 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 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: 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 |
