| 11-06-2007, 12:06 PM | #1 |
Welcome to the world of ABC. You can get the latest version from here or here. This is a thread for all you people who currently use and abuse ABC, for all you who got sick of gamecache and nightmares with impossible to understand code, for people who are new to vJass and for pros who always want to be better, welcome to the world of clean, bug-free and easy to understand code. I made this thread because I noticed people tend to send me PM's or emails when they have a problem with stuff they make with ABC, and I ended up answering to the same questions multiple times. Private messages cannot be seen by public and cannot benefit many people. From now on if you have a problem with anything ABC related, you can post it in here, also feel free to give answers yourself. In here you will soon find tutorials and demo spells, also links to cool stuff people made with ABC. And note that this is also advanced vJass thread, in here you will find nothing but the top-quality vjass, and there is no vJass question that will go unanswered. Enjoy! //---------------------------------------------------------------------- // LESSONS: //---------------------------------------------------------------------- Lesson no. 1: Destroying Special Effects - example for using ABC Lesson no. 2: Local Triggers - writing of a clean WE independent code Lesson no. 3: Function Interfaces - Conditions vs Actions, Evaluate vs Execute <lesson no. 3 is still in editing phase> |
| 11-06-2007, 12:15 PM | #2 |
Destroying special effects Well this is quite common operation and I got a few questions about that, so this will be out first ABC lesson. Problem: You made a custom spell and it uses some special effect, you want to destroy that effects at the end of spell to prevent memory leaks, but the time when you want to destroy the effect is not always the same. It depends on spell level and stuff like that, and you cannot possibly use waits because than it would not be MUI. Easy Solution: Lets make a function DestroyEffectTimed takes effect fx, real delay returns nothing With this function we could do stuff like: call DestroyEffectTimed(fx, 2.0) or call DestroyEffectTimed(fx, level *0.4) So how exactly do we make a function like that? Well for starters we will need a timer, that timer will after delay seconds call a function that will destroy our effect. Unfortunately in jass timer functions don't have parameters so we will have to attach our effect to a timer, that is where ABC comes in play! But wait ABC is struct attachment system? How do you attach effects with ABC? You don't! We will also need a "wrapper" struct around our effect to be able to pass it to timer. JASS:struct EffectWrapper effect fx endstruct Wait what! That is lame, if I used gamecache I could directly attach effect to timer! Yes you could, and that is one of the reasons gamecache is evil, direct handle attachments with gamecache cause infamous I2H bug. Anything that uses direct handle attachments can be considered bugged. Pro mappers who still use gamecache don't do direct attachments any more, they also make wrapper structs. Mmmkaaay, so lets continue, where were we, we had a DestroyEffectTimed function a struct that is used as a wrapper for an effect, a timer and a timer function: lets see how it all looks together: JASS:struct EffectWrapper effect fx endstruct //============================================================================== function EffectDestroyer takes nothing returns nothing local timer t=GetExpiredTimer() local EffectWrapper ew=GetTimerStructA(t) // ABC call DestroyEffect(ew.fx) call ClearTimerStructA(t) // ABC call ew.destroy() call DestroyTimer(t) set t = null endfunction //============================================================================== function DestroyEffectTimed takes effect fx, real delay returns nothing local timer t=CreateTimer() local EffectWrapper e=EffectWrapper.create() set e.fx=fx call SetTimerStructA(t,e) // ABC call TimerStart(t,delay,false,function EffectDestroyer) endfunction So here it is, in DestroyEffectTimed we create a timer create wrapper struct, insert our effect inside struct, attach the struct to timer with ABC and call a timer. Then when the timer expires it calls EffectDestroyer function we get the timer, get the struct wrapper, destroy the effect inside struct, remove the timer->struct link from ABC destroy the struct and finally destroy and null the timer. Pretty straightforward is it not? Well it is simple and it works, but it has a couple of flows. It can be optimized and made more encapsulated. Pro Solution: First of all directly using Create/Destroy timer functions is a noob way of dealing with timers. Instead of those functions we will be using timer recycling library knows as CSSafety, this library is a part of Vexorian's Caster System. You don't need to use the whole caster system (I for example use only CSSafety), so for convenience here is only the CSSafety trigger: JASS://! library CSSafety //****************************************************************************************** //* //* CSSafety 14.5 //* ———————— //* //* Utilities to make things safer. Currently this simply includes a timer recycling //* Stack. Once you replace CreateTimer with NewTimer and DestroyTimer with ReleaseTimer //* you no longer have to care about setting timers to null nor about timer related issues //* with the handle index stack. //* //****************************************************************************************** //========================================================================================== globals private timer array T private integer N = 0 endglobals //========================================================================================== function NewTimer takes nothing returns timer if (N==0) then return CreateTimer() endif set N=N-1 return T[N] endfunction //========================================================================================== function ReleaseTimer takes timer t returns nothing call PauseTimer(t) if (N==8191) then debug call BJDebugMsg("Warning: Timer stack is full, destroying timer!!") //stack is full, the map already has much more troubles than the chance of bug call DestroyTimer(t) else set T[N]=t set N=N+1 endif endfunction //! endlibrary //Forced by WE function InitTrig_CSSafety takes nothing returns nothing endfunction Ok lets go to the next step. You noticed in easy solution how you create effect, create struct create timer, destroy effect, destroy struct destroy timer .... + ABC calls Just too many create/destroy stuff there, can this be simplified? Of course it can, ... how? We will use create and onDestroy, two great features of structs, to make our struct wrapper more easy to use. Let's go straight to example: JASS:struct EffectWrapper private effect fx // note that fx is now private static method create takes effect fx returns EffectWrapper local EffectWrapper ret = EffectWrapper.allocate() set ret.fx = fx return ret endmethod method onDestroy takes nothing returns nothing call DestroyEffect(.fx) // set .fx = null // <--- You don't need to null struct variables endmethod endstruct Lets see how it affected other parts of the system: JASS://============================================================================== function EffectDestroyer takes nothing returns nothing local timer t=GetExpiredTimer() local EffectWrapper ew=GetTimerStructA(t) // ABC //call DestroyEffect(ew.fx) call ew.destroy() call ClearTimerStructA(t) // ABC call ReleaseTimer(t) // CSSafety // set t = null // We are using CSSafety, no need to null timer variables any more! endfunction //============================================================================== function DestroyEffectTimed takes effect fx, real delay returns nothing local timer t=NewTimer() // CSSafety local EffectWrapper e=EffectWrapper.create(fx) //local EffectWrapper e=EffectWrapper.create() //set e.fx=fx call SetTimerStructA(t,e) // ABC call TimerStart(t,delay,false,function EffectDestroyer) endfunction you don't need to destroy effect directly any more, and you don't need to null timer variable because of CSSafety. On the other hand you did not need to null variables at all, because unlike other systems ABC is resistant to leaks and not-nulling, but that is not the issue here, lets continue... Ok so it is still not good? Cmon we used some heavy shit here what else can you possibly improve? Well for starters here is a place for optimization: This: JASS://============================================================================== function EffectDestroyer takes nothing returns nothing local timer t=GetExpiredTimer() local EffectWrapper ew=GetTimerStructA(t) // ABC call ew.destroy() call ClearTimerStructA(t) // ABC call ReleaseTimer(t) // CSSafety endfunction Can actually be done like this: JASS://============================================================================== function EffectDestroyer takes nothing returns nothing local timer t=GetExpiredTimer() local EffectWrapper ew=ClearTimerStructA(t) // ABC call ew.destroy() call ReleaseTimer(t) // CSSafety endfunction Ok we are almost there, we made our little DestroyEffectTimed function and it is working just like we wanted it to work. But now we want to use it for all our spells that need to destroy effects, not just this one. Time to put this all into a library: JASS:library EffectDestroyer uses ABC, CSSafety //============================================================================== private struct EffectWrapper private effect fx static method create takes effect fx returns EffectWrapper local EffectWrapper ret = EffectWrapper.allocate() set ret.fx = fx return ret endmethod method onDestroy takes nothing returns nothing call DestroyEffect(.fx) endmethod endstruct //============================================================================== private function EffectDestroyer takes nothing returns nothing local timer t=GetExpiredTimer() local EffectWrapper ew=ClearTimerStructA(t) // ABC call ew.destroy() call ReleaseTimer(t) // CSSafety endfunction //============================================================================== // Exported function //============================================================================== function DestroyEffectTimed takes effect fx, real delay returns nothing local timer t=NewTimer() // CSSafety local EffectWrapper e=EffectWrapper.create(fx) call SetTimerStructA(t,e) // ABC call TimerStart(t,delay,false,function EffectDestroyer) endfunction endlibrary Note that we made everything private except DestroyEffectTimed function, that is the only thing other triggers will be able to use. We did this because: 1. Noone really cares how DestroyEffectTimed function works inside. 2. Noone will be able to mess with private stuff so they cannot screw up anything even if they wanted to. This method of hiding stuff from users of a library that they don't need to know/ should not use is in programming jargon called encapsulation. That's all in this lesson folks! |
| 11-06-2007, 01:50 PM | #3 |
To be continue... Right? |
| 11-06-2007, 01:56 PM | #4 | |
Local triggers how and why Ok so you are a jass moderate/pro coder and you just started using vJass and you have seen people use that local trigger trig = CreateTrigger() but you don't really know why. Have no fear, scopes are near. Ok lets assume that you want to make custom jass-triggered animate dead spell, and you want your trigger to be started when units casts a standard animate dead spell. You would naturally start from a trigger editor: Trigger: Then you would convert it into text: JASS:function Trig_CustomAnimate_Conditions takes nothing returns boolean if ( not ( GetSpellAbilityId() == 'AUan' ) ) then return false endif return true endfunction function Trig_CustomAnimate_Actions takes nothing returns nothing endfunction //=========================================================================== function InitTrig_CustomAnimate takes nothing returns nothing set gg_trg_CustomAnimate = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( gg_trg_CustomAnimate, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( gg_trg_CustomAnimate, Condition( function Trig_CustomAnimate_Conditions ) ) call TriggerAddAction( gg_trg_CustomAnimate, function Trig_CustomAnimate_Actions ) endfunction And now all you have to do is fill Trig_CustomAnimate_Actions with your crazzy ass custom code for that spell, right? WRONG! That is not the way of a vJass warriors and is not the way of JESP. I mean just look at that code, it has a bounch of gg_trg stuff it has unnecessary if statements it has raw codes 'AUan' lying around, it is simply asking for trouble. And it is looking uglier that GUI goddamnit. If you are to be called masters of vjass you shall write pretty code or vanish in oblivion. Lets start by converting this: JASS://=========================================================================== function InitTrig_CustomAnimate takes nothing returns nothing set gg_trg_CustomAnimate = CreateTrigger( ) call TriggerRegisterAnyUnitEventBJ( gg_trg_CustomAnimate, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( gg_trg_CustomAnimate, Condition( function Trig_CustomAnimate_Conditions ) ) call TriggerAddAction( gg_trg_CustomAnimate, function Trig_CustomAnimate_Actions ) endfunction To this: JASS://=========================================================================== function InitTrig_CustomAnimate takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( trig, Condition( function Trig_CustomAnimate_Conditions ) ) call TriggerAddAction( trig, function Trig_CustomAnimate_Actions ) endfunction See it already looks prettier. Ok so what did we do here? we created a local trigger instead of default one, and replaced all those ugly gg_trg names with it. But the benefit is not only the beauty of code, the code has become less dependent on WE trigger editor (more on that later) Next step: Lets optimize this ugly biach: JASS:function Trig_CustomAnimate_Conditions takes nothing returns boolean if ( not ( GetSpellAbilityId() == 'AUan' ) ) then return false endif return true endfunction like this: JASS:globals constant integer AID_ANIMATE_DEAD = 'AUan' endglobals function Trig_CustomAnimate_Conditions takes nothing returns boolean return GetSpellAbilityId() == AID_ANIMATE_DEAD endfunction ahh globals, greatest feature of vJass, it is to wc3 mapping what wheel is for science. It is quite obvious now what that condition is doing is it not? Is someone casts animate dead it will return true and our trigger actions will run, straight on the spot. And that global integer AID__ ?? Is it constant? What does that mean? constant means that it can be optimized by wc3optimizer It also means that you cannot change it's value, but you don't want that do anyway, it is a Id of your triggering spell, that should not be changed by code. You should ALWAYS extract all 'XXXX' stuff from your triggers into constant integer global constants. - NO EXCEPTIONS. It is the way to make stuff MUI, it is the way to make stuff JESP it is the way to remove potential errors from your code. Prefix naming convention AID_ = Ability Id UID_ = unit Id and so on.... You don't have to use it, you can give your constants any names you like but you shall make all your constant names in UPPERCASE latters or be called noob forever. Lets see where we at now: JASS:globals constant integer AID_ANIMATE_DEAD = 'AUan' endglobals //=========================================================================== function Trig_CustomAnimate_Conditions takes nothing returns boolean return GetSpellAbilityId() == AID_ANIMATE_DEAD endfunction //=========================================================================== function Trig_CustomAnimate_Actions takes nothing returns nothing endfunction //=========================================================================== function InitTrig_CustomAnimate takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( trig, Condition( function Trig_CustomAnimate_Conditions ) ) call TriggerAddAction( trig, function Trig_CustomAnimate_Actions ) endfunction Ok it looks better than that idiotic GUI generated code, time to add a final blow: scopes Brace yourself: JASS:scope CustomAnimate globals constant integer AID_ANIMATE_DEAD = 'AUan' endglobals //=========================================================================== private function Conditions takes nothing returns boolean return GetSpellAbilityId() == AID_ANIMATE_DEAD endfunction //=========================================================================== private function Actions takes nothing returns nothing endfunction //=========================================================================== public function InitTrig takes nothing returns nothing local trigger trig = CreateTrigger() call TriggerRegisterAnyUnitEventBJ( trig, EVENT_PLAYER_UNIT_SPELL_EFFECT ) call TriggerAddCondition( trig, Condition( function Conditions ) ) call TriggerAddAction( trig, function Actions ) endfunction endscope Wait what! WTF just happened to the code? scope , endscope, public, private ? geeez. First of all notice that code looks fucking awesome, just compare it to the code we started with! Now to explain: scope <TriggerName> should be the first line of code in your trigger (you can put comments before it if you want but nothing else!) endscope should be the last line obviously. Now come the 3 changes to InitTrig, to Conditions and to Actions: this: JASS://=========================================================================== function Trig_CustomAnimate_Conditions takes nothing returns boolean return GetSpellAbilityId() == AID_ANIMATE_DEAD endfunction transformed into this: JASS://=========================================================================== private function Conditions takes nothing returns boolean return GetSpellAbilityId() == AID_ANIMATE_DEAD endfunction Ok what does that mean? First of all notice that second version does not have Trig_CustomAnimate inside the code, that means that your condition is now independent of in witch trigger it is in, it means that you can just copy it into another trigger and it will work straight away! But it is called Conditions for god's sake!!! What if I have two vJass triggers, would not 2 Conditions with same names create errors? No they would not if they have private prefix ! Private stuff are always different. Private means that that condition will be used ONLY inside that trigger, it cannot go outside and mess with other conditions! Same for Actions. But InitTrig is public ? Yes and it should be. It is the entry point of your trigger, it is what all other triggers see. InitTrig functions in vJass should always look like this: public function InitTrig takes nothing returns nothing NO EXCEPTIONS! And finally: This lesson is more explaining how than why things should be done this way. But that is intentional, for why about public and private stuff you should check the jasshelper documentation, no need to rewrite that in here. For all that matters the biggest why here could simply be the astonishing difference in look of GUI generated and vJass code. |
| 11-06-2007, 03:19 PM | #5 |
Hmm, in my way of doing these. I would just wrap everything into the struct and remain that public calling function. Static method could be treat as a call back function. JASS:library EffectDestroyer uses ABC, CSSafety //============================================================================== private struct EffectWrapper private effect fx static method create takes effect fx returns EffectWrapper local EffectWrapper ret = EffectWrapper.allocate() set ret.fx = fx return ret endmethod static method EffectDestroyer takes nothing returns nothing local timer t=GetExpiredTimer() local EffectWrapper ew=ClearTimerStructA(t) // ABC call ew.destroy() call ReleaseTimer(t) // CSSafety endmethod method onDestroy takes nothing returns nothing call DestroyEffect(.fx) endmethod endstruct //============================================================================== // Exported function //============================================================================== function DestroyEffectTimed takes effect fx, real delay returns nothing local timer t=NewTimer() // CSSafety local EffectWrapper e=EffectWrapper.create(fx) call SetTimerStructA(t,e) // ABC call TimerStart(t,delay,false,function EffectDestroyer) endfunction endlibrary |
| 11-06-2007, 03:52 PM | #6 |
Nice one ~Gals~ One objection thou: Indent that static method properly, it looks ugly now. EDIT: See it looks better now :) |
| 11-07-2007, 10:54 AM | #7 | |
>>InitTrig functions in vJass should always look like this: JASS:public function InitTrig takes nothing returns nothing No exceptions? Actually.... JASS:public function InitTrig takes nothing returns nothing JASS:function InitTrig takes nothing returns nothing >>Scope name and the trigger name MUST be the same or else your trigger won't work. Hmm, is this true? I don't really see it anywhere on the manual >>Indent that static method properly, it looks ugly now. Sorry boss. I just did some cut-paste on your code. I didnt really have my JC2 opened and start teh editing. The forum doesn't support TAB encap. IMPORTANT NOTE Quote:
|
| 11-07-2007, 01:18 PM | #8 | |||
Quote:
public keyword in jass does not work the same like in java or C# Quote:
scopes do. Quote:
Please fix that, I promised nothing but top-quality code in here, so when others post code I expect them to be on the level. |
| 11-07-2007, 01:50 PM | #9 |
>>No they are not treated the same. public keyword in jass does not work the same like in java or C# Sorry, I don't know any other programing language other than Jass. I know, I am an idiot. But what's the different? You still need to call it by <ScopeName>_<FunctionName>() >>library names do not have to have the same name as triggers they are in, scopes do. True? Well, what if my trigger has multiple scope? I need to name them exactly as same as my trigger's name?? I don't mean nested scope. It is multiple scope. A small test to prove. An item I made for someone else. >>That is why I have jasscraft on my flash drive. Please fix that, I promised nothing but top-quality code in here, so when others post code I expect them to be on the level. Please understand me, I am running on a 256 ram computer. It lags well (2-5 minutes) once I open my JassCraft. Lags more when I open my NewGen. I know, I know. You would advise me to get some better performance, get me the budget. Could'nt you just edit my post. xD I know, just lazy. Edit - Sorry, the link was not working. Something happened on FileFront. -.- [Delu]Shuriken.w3x |
| 11-07-2007, 02:43 PM | #10 |
For inits, you could do library with an initializer function. JASS:library SomeShit initializer InitSomeShit // code function InitSomeShit takes nothing returns nothing local trigger t = CreateTrigger() call TriggerAddActions... call RegisterPlayerEvent... set t = null endfunction endlibrary |
| 11-07-2007, 03:39 PM | #11 | |
Quote:
You're a liar or a retard. Please note that that statement is not an exclusive or. |
| 11-07-2007, 04:06 PM | #12 | ||
Quote:
Quote:
In java it is forbidden to do it altogether. The reason that scope name has to be the same as trigger name is that once you convert your InitTrig to this form: public function InitTrig takes nothing returns nothing it only works if names match. And the reason for this is that vjass does this: public InitTrig -> InitTrig_ScopeName for trigger initializers unlike for functions where it does: public FuncName -> ScopeName_InitTrig And if scope name does not match trigger name inittrig will not be called on map initialization and your trigger will not be initialized. So basically you "can" have 2 scopes in one trigger, but only one inittrig function will work, this is not a problem by itself, but it usually means that your designed is wrong. Ok so why would anyone convert triggers to InitTrig form when trigger works fine after direct conversion from GUI? Because it: 1. looks lot nicer 2. it is independent of WE generated trigger name. Independence is extremely important here, if you for example wanted to copy someones triggered spell into your map, but want to change parameters and also the name of spell, you would have to find ALL gg_trg uglies, and replace them with new ones, and then find different Trig_name uglies for actions and conditions, not to mention those uglies generated by GUI if statements. On the other hand if you used the clean vJass form, all you have to do after copying the trigger is to change scope name, nothing else :) (It is the easiest way to make JESP spells) @griffein: In my case it is a correct AND statement there. It is not my fault that you cannot write a code like that. :P If I had written clean and optimized on the same line that would be a problem, I don't suppose apologizing is your stile? |
| 11-08-2007, 03:46 AM | #13 |
I, might need an explanation of what interface is... Don't tell me to read the manual, if I understand the manual well, I could be your teacher, Cohadar. |
| 11-08-2007, 08:13 AM | #14 | |
Quote:
Not anymore....... So we should use local triggers just so it looks prettier? I still don't understand why. Btw, nice job man, I'll enjoy this, +rep One question for ~Gals~: I remember your thread on handle vars (on TheHelper), you know vJass, but you don't know how to use handle vars? Some guy you are..... :) |
| 11-08-2007, 12:03 PM | #15 | |
Quote:
Well if you prefer code like this: JASS:function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing if ( Trig_Untitled_Trigger_001_Func001C() ) then if ( Trig_Untitled_Trigger_001_Func001Func001C() ) then call ForGroupBJ( GetUnitsInRectAll(GetPlayableMapRect()), function Trig_Untitled_Trigger_001_Func001Func001Func003A ) else call ForGroupBJ( GetUnitsInRectAll(GetPlayableMapRect()), function Trig_Untitled_Trigger_001_Func001Func001Func002A ) endif else endif endfunction @~Gals~ struct interfaces are one of those features of vjass that we could do without, my estimate is that there is no more than 20 people who use them. I had to use them only once for example and for an extremely non-common problem. I honestly think they are of no use for 95% of mappers. (This does not mean that you should not try and learn them, after all they might be just what you needed for your xyz thing..) function interfaces on the other hand are extremely useful, they are a bit hard to understand at beginning but once you learn them, you really gain some programming power you can use right away. My next tutorial will be about function interfaces, what is their purpose, how to use them and how they work "under the hood" It will take me some time, so don't hold your breath. |
