Warcraft III: MapsModelsSkinsIconsSpellsToolsTutorials
WC3 JASS: Code SnippetsJASS and vJASS Spells and SystemsJass Tutorials
Chat @Discord

Recent Posts

Pages: [1] 2 3 ... 10
Jass Tutorials / Trackables (Mouse detection)
« Last post by BlizzMod on July 09, 2017, 01:51:10 AM »
Tutorial originally made by wonderpriest inspired in Kattana original tutorial.


A trackable is just something like a doodad, let's say. But it is different as it can't be selected, it can't be moved, it can't die, and it can't be eliminated. It doesn't even have to be seen if you don't want it to.
The point of a trackable is to harness the mouse detection for usage in triggers. Mouse detection just means the game registers when your mouse pointer moves over an object, or clicks it.

As I get more questions, I will answer them here so you don't have to sift through the posts.
Code: jass  [Select]
  1. Q. Do trackables work in multiplayer maps?
  2. A. Yes, they work fine. As for tracking which player triggered the trackable, you would need a handler system for that.

Although Blizzard gave us these functions to use mouse detection, it doesn't seem they really cared to finsh the code, because you can not retrieve a triggering player from a trackable event. Nor can a trackable be owned by a player.
The usage of trackables is very limited, because you can not manipulate anything about a trackable once you create it, or before. This means you can not move, scale, color, or make any other kind of modification. You just create it, and that's it.
Also, one of the reasons not many people know of trackables, is because they require JASS to fully use. You can create and manipulate trackables through the Custom Script action, but there is no event in GUI that uses trackables.
Fortunately, the functions are not very hard to understand and require little, if any, JASS knowledge.

I wanted to bring these trackables to attention, because even many JASSers seem to not know what they are. Trackables are a powerful tool that can be used to make better interactive systems, and even things like mini-games.

To use trackables, first you must create them. They can not be pre-placed in a map, so you must make them through triggers. They are created somewhat like units are created, only you use a model string to determine the model of the trackable, not an id or name.
This is actually very useful, because you can use any valid model path. This includes units, buildings, doodads...anything with a model path. If you use a unit or buildiing model, the trackable will appear looking like a neutral hostile unit, with no team colors.
Trackables do play animations. If you want a trackable to be invisible, you just insert a void string argument (""). Here is the function to create a trackable:

Code: jass  [Select]
  1. CreateTrackable(string modelpath, real x, real y, real facing)
  3. modelpath is the path of the model you would like to use. Use "" for an invisable trackable. Remember to use \\ instead of \.
  4. real x and real y are the coordinates of the location to create the trackable
  5. facing is the angle that the trackable faces (remember this cannot be changed after creation)

The events for trackables are as follows:

Code: jass  [Select]
  1. TriggerRegisterTrackableHitEvent(trigger whichTrigger, trackable t)
  2. TriggerRegisterTrackableTrackEvent(trigger whichTrigger, trackable t)
  4. trackable is a handle that you use like any other handle (unit, location...).

TriggerRegisterTrackableHitEvent registers when a player clicks on a trackable. TriggerRegisterTrackableTrackEvent registers when a player moves his mouse across a trackable.
You may notice that this same system is in place with normal units in Warcraft III. When you click a unit, you select it. When you pass over a unit with your mouse, a bar shows it's health.
Trackable events work like any other event, but you also need to specify a trackable for the event to register.

A Trigger:
This trigger will create a trackable, and when someone passes over it, a message will be sent.
Code: jass  [Select]
  1. function Massage takes nothing returns nothing
  2.     call DisplayTextToForce(GetPlayersAll(),"Peasant: OOh, that feels nice.") //Displays the pleasure of the peasant when we rub him with our mouse
  3. endfunction
  5. //===========================================================================
  6. function InitTrig_zomg takes nothing returns nothing
  7.  local trigger t=CreateTrigger() //Creates the trigger
  8.  local trackable tr=CreateTrackable("units\\human\\Peasant\\Peasant.mdl",0,0,-90) //Creates our trackable, with the peasant model
  9.     call TriggerRegisterTrackableTrackEvent(t,tr) //Registers when someone passes over tr with their mouse
  10.     call TriggerAddAction(t,function Massage) //Calls our actions function
  11. endfunction

You may notice I display the message to all players. This is because there is no 'GetTriggeringPlayer' for a trackable event, which is probably the biggest limitation of trackables. But, there is a 'GetTriggeringTrackable'. Which opens up possibilites to fix the missing TriggeringPlayer problem. (With 'GetTriggeringTrackable', you may be able to stimulate a Triggering Player, maybe by using the Handle Vars or another system)

The Result:

ADD: Trackable Workarounds (credits to KaTTana)
Using Handle Vars With Trackables
KaTTana wrote this part better than I can, so here it is.
You can find KaTTana's Trackables tutorial here: Link
You can find KaTTana's Handle Var turorial here: Link

A Part of KaTTana's Tutorial:
"We don't have any natives for getting the (x,y) coordinates of a trackable, but we can handle that ourselves with the Local Handle Variables.
Here is a custom API for extended use of trackables.

Code: jass  [Select]
  1. // ===========================
  2. //   Trackable API
  4. function GetTrackableX takes trackable tc returns real
  5.     return GetHandleReal(tc, "x")
  6. endfunction
  7. function GetTrackableY takes trackable tc returns real
  8.     return GetHandleReal(tc, "y")
  9. endfunction
  10. function GetTrackableFacing takes trackable tc returns real
  11.     return GetHandleReal(tc, "facing")
  12. endfunction
  13. function GetTrackablePath takes trackable tc returns string
  14.     return GetHandleString(tc, "path")
  15. endfunction
  17. function NewTrackable takes string path, real x, real y, real facing returns trackable
  18.     local trackable tc = CreateTrackable(path, x, y, facing)
  19.     call SetHandleReal(tc, "x", x)
  20.     call SetHandleReal(tc, "y", y)
  21.     call SetHandleReal(tc, "facing", facing)
  22.     call SetHandleString(tc, "path", path)
  23.     return tc
  24. endfunction

Trackables in Multiplayer
Previously, the tutorial stated that trackables didn't work in multiplayer, but that was wrong. They work fine, and they don't desynchronize the game.
However, there is no way to determine which player triggered the event on a trackable. We can work around this by creating a trackable for each player - each can only be triggered by one player.

Code: jass  [Select]
  1.     // t1 and t2 are visually the same trackable, but in fact they only work for one player each
  2.     local trackable t1 // Player 1's trackable
  3.     local trackable t2 // Player 2's trackable
  4.     local string peasant = "units\\human\\Peasant\\Peasant.mdl"
  5.     local string invisible = ""
  6.     local string path = invisible
  8.     if ( GetLocalPlayer() == Player(0) ) then
  9.         set path = peasant
  10.     endif
  11.     set t1 = CreateTrackable(path, -500, 0, 0)
  13.     set path = invisible
  14.     if ( GetLocalPlayer() == Player(1) ) then
  15.         set path = peasant
  16.     endif
  17.     set t2 = CreateTrackable(path, -500, 0, 0)
  19.     call SetHandleInt(t1, "player", 0) // Store which player "owns" this trackable
  20.     call SetHandleInt(t2, "player", 1) // Same for player 2
  22.     // Add events to register track/hit on t1 and t2...

After this, you can determine the player triggering a trackable by reading the local integer named "player" on the triggering trackable.

We can extend to NewTrackable if you like:
Code: jass  [Select]
  1. function GetTrackableOwner takes trackable t returns player
  2.     return Player(GetHandleInt(t, "player"))
  3. endfunction
  5. function NewTrackable takes string path, real x, real y, real facing, player owner returns trackable
  6.     local trackable tc
  7.     local string invisible = ""
  8.     if GetLocalPlayer() != owner then
  9.         set path = invisible
  10.     endif
  11.     set tc = CreateTrackable(path, x, y, facing)
  12.     call SetHandleReal(tc, "x", x)
  13.     call SetHandleReal(tc, "y", y)
  14.     call SetHandleReal(tc, "facing", facing)
  15.     call SetHandleString(tc, "path", path)
  16.     call SetHandleInt(tc, "player", GetPlayerId(owner))
  17.     return tc
  18. endfunction

Giving height to Trackables
Although the natives do not allow it, a simple workaround enables us to create trackables at a given height above ground.
Code: jass  [Select]
  1. function CreateTrackableZ takes string path, real x, real y, real z, real face returns trackable
  2.     local destructable d = CreateDestructableZ( 'OTip', x, y, z, 0.00, 1, 0 )
  3.     local trackable tr = CreateTrackable( path, x, y, face )
  4.     call RemoveDestructable( d )
  5.     set d = null
  6.     return tr
  7. endfunction
It works by creating an Invisible Platform at the given height, and then creating the trackable on top of that. After removing the platform, the trackable stays in place."

Once again, that was part of KaTTana's trackable tutorial, which you can find here: Link

That's it for the Trackables tutorial, thanks for reading, and hope you have fun using these in your map!

Questions, comments, and suggestions are welcome, and appreciated!

ADD: If anyone finds this confusing, or wants a demo map to look at, just say iit and I will make a demo map using all of the Trackable functions.
Jass Tutorials / Creating simple AIs for Hero Arena-type maps
« Last post by BlizzMod on July 09, 2017, 01:42:59 AM »
Developed by Blade.dk

This tutorial will help you add a simple, but cool, AI to hero arena type maps.

The AI you’ll learn how to make here will not be perfect. The one we will create here will attack other heroes, pick up items, learn and use spells, but it won’t be as effective as a human player.

However, when you’ve learned the basics you should be able to improve it yourself.


JASS Knowledge – This tutorial uses JASS examples, and JASS-only features, so you will need to know JASS to follow this tutorial and make the AI. Technically it can be done in the GUI, but I won’t recommend that due to memory leaks, tons of unneeded code, and because using return bug and game cache based systems isn’t possible in the GUI. If you don’t know JASS, check the JASS tutorials at The JASS Vault and Wc3Campaigns.
You also need to know what a rawcode are, if you don’t, just search the different Warcraft 3 sites to find out.
A game cache and return bug based system – This can be KaTTaNa’s Local Handle Variables, Vexorian’s CSCache module (a part of the Caster System) or any other similar system.
In this tutorial I’ll use the CSCache module.
This map – A small map I’ve created that shows a simple AI like the one we’ll make in action. It is important that you have this map, as the tutorial often refers to it.

- The AI we’ll make here is not as good as a human player, but better than nothing. And when you’ve learned the basics, you should be able to improve it yourself.
- A lot of numbers (player numbers, for example) starts from 1 in the GUI, but while they starts from 0 in JASS. As this is a JASS tutorial, they start from 0 here.
- You don’t have to do the things exactly like I do them; I do it in my way, but if your way is better or you just feel more comfortable with it, do it your own way. I’m not perfect, and this tutorial isn’t perfect either, but hopefully it will help somebody.
- You could use the AI from my demo map without creating your own (if you do so, give me credit), but I suggest making your own, as maps can be very different, and because you should be able to learn more from making it yourself.
- The demo map is probably not perfectly unbugged, and it isn’t the funniest map either. Remember that it was just a map I quickly created to show a simple AI, if you want to see a better map with a good AI, take a look at Azeroth’s Arcane Arena.
- I would like to specially thank Vexorian for encouraging me to make my first AI for my map, giving me tips on how to do it, and for showing me his AI, which helped me improve mine. Thanks!


First create a new trigger with the “Player - Player 1 (Red) leaves the game” event. Convert it to JASS. We need that trigger to register when a player leaves the game, so we can start the AI for that player. At the moment it will only register when player 0 leaves the game, so we’ll use a loop to make it register when any player from 0-11 leaves instead.

We want the AI to use abilities. This may sound hard, but it isn’t. We just have to make the Heroes learn the abilities, and they’ll cast them themselves.

NOTE: The situation where a computer-controlled hero will cast a spell is always the same situation as where it would cast the spell it’s based on. So if you have a custom spell based on Silence, it will cast it in situations where it would cast it in melee games. NEVER base your spells of the “Channel” ability, as the AI never will use them. Changing the OrderString field on a spell in the Object Editor does nothing, it will still be the same as on the base spell.

To know which spells the heroes have, we create a game cache to store it in.

In the example map my trigger creates a gamecache at map initialization and saves it in the udg_GameCache global variable. Note that the cache HAS to be initialized before we starts using it, so I will do that in the first InitTrig function of my map.

In my map I create a function called “SetupSkills”. In the AI trigger’s InitTrig function I use the ExecuteFunc native (read more about that native here) to execute that function in another thread. This is to prevent the map initialization thread from getting too long, and crash.

My SetupSkills function looks like this:

Code: jass  [Select]
  1.  function SetupSkills takes nothing returns nothing
  2.     local string h // Create a local string variable
  3. // Paladin // Here we’ll initialise the Paladin’s skills, repeat this for all other heroes
  4.     set h = UnitId2String('Hpal') // Store the returned value of UnitId2String(‘Hpal’) in the local
  5.     call StoreInteger(udg_GameCache, h, "BaseSkill1", 'AHhb') // One of his base skills is Holy Light, store it as “BaseSkill1”
  6.     call StoreInteger(udg_GameCache, h, "BaseSkill2", 'AHds') // Store Divine Shield as “BaseSkill2”
  7.     call StoreInteger(udg_GameCache, h, "BaseSkill3", 'AHad') // Store Devotion Aura as “BaseSkill3”
  8.     call StoreInteger(udg_GameCache, h, "UltimateSkill", 'AHre') // Store Resurrection as his “UltimateSkill”
  9. // Repeat for each Hero.
  10. endfunction

Here’s my InitTrig_AI function:

Code: jass  [Select]
  1.  function InitTrig_AI takes nothing returns nothing
  2.     local integer i = 0
  3.     set gg_trg_AI = CreateTrigger(  )
  4.     loop
  5.         exitwhen i > 11
  6.         call TriggerRegisterPlayerEventLeave( gg_trg_AI, Player(i) )
  7.         set i = i + 1
  8.     endloop
  9.     call TriggerAddAction( gg_trg_AI, function PlayerLeaves )
  10.     call ExecuteFunc("SetupSkills")
  11. endfunction

Starting the AI for a hero

To control the AI I will use a timer. I create a function called “StartAI” that takes a single unit argument: the hero (check the function in the example map). The function just creates a timer, "attaches" the hero to it, and starts it (just make the expiration function now, we will out some actions into it later, but you need the function and endfunction lines to prevent getting compile errors).

This is the empty AILoop function and the StartAI function from the example map:

Code: jass  [Select]
  1. function AILoop takes nothing returns nothing
  2. endfunction
  4. function StartAI takes unit hero returns nothing
  5.     local timer m = CreateTimer()
  6.     call AttachObject(m, "hero", hero)
  7.     call TimerStart(m, 0, false, function AILoop)
  8.     set m = null
  9. endfunction

Note that I’m starting it as a “one-shot” timer, by using false as the 'periodic' boolean value (we’ll get back to that later).

Now just make your hero selection system call that function when a computer controlled player chooses a hero, and go to the function that is executed when a player leaves the game. Check if the player has a hero, if he/she has one, call the function that starts the AI on that hero. Example:

Code: jass  [Select]
  1. function PlayerLeaves takes nothing returns nothing
  2.     local player p = GetTriggerPlayer()
  3.     call DisplayTextToForce(bj_FORCE_ALL_PLAYERS, GetPlayerName(p)+" has left the game.")
  4.     if udg_Hero[GetPlayerId(p)] != null then
  5.         call StartAI(udg_Hero[GetPlayerId(p)])
  6.     endif
  7.     set p = null
  8. endfunction

NOTE: This will make the AI take control of a leaving player's hero, this is not needed, if you want to do something else when a player leaves.

Making the AI do something

Whenever the timer expires there are some things we want it to do:
  •    If the hero is dead, wait until he/she/it is revived.
  •    If the hero is about to die, order him/her/it to move to the fountain at the map center.
  •    If the hero has a fine amount of health, check if an enemy is close. If true, order the Hero to attack it, else check for items close to the hero, if any, issue a smart order so the Hero will pick the up. if there isn’t any, just order the hero to patrol to a random point in the arena.
  •    If the hero is alive and has any unused skill points, learn a skill.

We’ll start with declaring all the variables.  Notice the real variable 'e' in my function, it defines how long time will elapse before the timer expires again, so we can wait shorter time if the hero is dead, or longer time if he/she/it is attacking. That variable is initialized with the value 5.

Declare the local variables:

Code: jass  [Select]
  1. function AILoop takes nothing returns nothing
  2.     local string a = GetAttachmentTable(GetExpiredTimer())
  3.     local unit h = GetTableUnit(a, "hero")
  4.     local rect i
  5.     local location r
  6.     local real x = GetUnitX(h)
  7.     local real y = GetUnitY(h)
  8.     local group g
  9.     local boolexpr b
  10.     local boolexpr be
  11.     local unit f
  12.     local string o = OrderId2String(GetUnitCurrentOrder(h))
  13.     local real l = GetUnitState(h, UNIT_STATE_LIFE)
  14.     local real e = 5

We start with checking if the hero is dead, if he/she/it is, set the real variable to 1.5 (because waiting 5 seconds after revival is too long time, we don’t want that).

The hero’s life ('l' is 0, just set e to 1.5 to make the timer check more frequently for the hero’s revival.

Code: jass  [Select]
  1.     if l <= 0 then
  2.         set e = 1.5
  3.     endif

Next I check if the hero’s life is below 20% of it's max life. If it is low, order the hero to move to fountain and set the variable 'e' to 3.

The hero’s life is less than 20% of max life, so order the hero to move to the position of the fountain:

Code: jass  [Select]
  1.     if l < GetUnitState(h, UNIT_STATE_MAX_LIFE)/5 then
  2.         call IssuePointOrder(h, "move", GetUnitX(gg_unit_nfoh_0001), GetUnitY(gg_unit_nfoh_0001))
  3.         set e = 3

If the hero isn’t weak, check if he/she/it has a common order (to prevent it from interrupting channel spells). If it is a standard order, we check if any enemies are within a radius of 500. If true, simply issue an attack order (don’t change the 'e' variable, 5 seconds is fine in this situation).

Code: jass  [Select]
  1.  function AIFilterEnemyConditions takes nothing returns boolean
  2.     return GetUnitState(GetFilterUnit(), UNIT_STATE_LIFE) > 0 and IsPlayerEnemy(GetOwningPlayer(GetFilterUnit()), GetOwningPlayer(GetAttachedUnit(GetExpiredTimer(), "hero")))
  3. endfunction
  5.     else
  6.         if ((o == "smart") or (o == "attack") or (o == "patrol") or (o == "move") or (o == "stop") or (o == "hold") or (o == null)) then
  7.             set g = CreateGroup()
  8.             set b = Condition(function AIFilterEnemyConditions)
  9.             call GroupEnumUnitsInRange(g, x, y, 500, b)
  10.             set f = FirstOfGroup(g)
  11.             if f == null then
  12.             else
  13.                 call IssueTargetOrder(h, "attack", f)
  14.             endif
  15.             call DestroyGroup(g)
  16.             call DestroyBoolExpr(b)
  17.         endif

If no enemies are found, check for items. If an item is found, check if it’s a powerup. If it isn’t, check if the hero has any empty inventory slots, and order the hero to pick it up.

Code: jass  [Select]
  1.  function AISetItem takes nothing returns nothing
  2.     set bj_lastRemovedItem=GetEnumItem()
  3. endfunction
  5. function AIItemFilter takes nothing returns boolean
  6. endfunction
  8. function AIHasEmptyInventorySlot takes unit u returns boolean
  9.     return UnitItemInSlot(u, 0) == null or UnitItemInSlot(u, 1) == null or UnitItemInSlot(u, 2) == null or UnitItemInSlot(u, 3) == null or UnitItemInSlot(u, 4) == null or UnitItemInSlot(u, 5) == null
  10. endfunction
  12.             if f == null then
  13.                 set i = Rect(x-800, y-800, x+800, y+800)
  14.                 set be = Condition(function AIItemFilter)
  15.                 set bj_lastRemovedItem=null
  16.                 call EnumItemsInRect(i, be, function AISetItem)
  17.                 if bj_lastRemovedItem != null and (GetItemType(bj_lastRemovedItem) == ITEM_TYPE_POWERUP or AIHasEmptyInventorySlot(h)) then
  18.                     call IssueTargetOrder(h, "smart", bj_lastRemovedItem)
  19.                 else
  20.                 endif
  21.                 call RemoveRect(i)
  22.                 call DestroyBoolExpr(be)

If the hero has items in all slots, or no items existed, order him/her/it to patrol to a random location in the map, to find new targets.

Code: jass  [Select]
  1.                 else
  2.                     set r = GetRandomLocInRect(bj_mapInitialPlayableArea)
  3.                     call IssuePointOrderLoc(h, "patrol", r)
  4.                     call RemoveLocation(r)

Now let’s check if the hero has any unused skill points (keep this separated from the attack/item pickup/patrol block).

If he/she/it has, call a function that learns a skill to the hero. In my example I’ve used a function that stores the number it has taught the hero an ability, to keep a special pattern in the ability learning:

Code: jass  [Select]
  1. function AILearnSkill takes unit h, string a returns nothing
  2.     local integer i = GetTableInt(a, "LearnSkillOrder")+1
  3.     if i == 1 or i == 4 or i == 8 then
  4.         call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill1"))
  5.     elseif i == 2 or i == 5 or i == 9 then
  6.         call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill2"))
  7.     elseif i == 3 or i == 7 or i == 10 then
  8.         call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "BaseSkill3"))
  9.     elseif i == 6 then
  10.         call SelectHeroSkill(h, GetStoredInteger(udg_GameCache, UnitId2String(GetUnitTypeId(h)), "UltimateSkill"))
  11.     endif
  12.     call SetTableInt(a, "LearnSkillOrder", i)
  13. endfunction
  15.     if GetHeroSkillPoints(h) > 0 and l > 0 then
  16.         call AILearnSkill(h, a)
  17.     endif

Now simply make the timer expire again after 'e' seconds:

Code: jass  [Select]
  1.     call TimerStart(GetExpiredTimer(), e, true, function AILoop)

Last we need to set the local variables to null:

Code: jass  [Select]
  1.     set h = null
  2.     set i = null
  3.     set r = null
  4.     set g = null
  5.     set b = null
  6.     set f = null
  7.     set be = null

Final notes

This is the basics of it, it can be way better, but this should help you get started. Feel free to ask questions here or pm me.

It shouldn’t be complicated at all, but if you have just checked the tutorial it can be so. The map was made to give a better demonstration, so please check it.

When you have finished making your simple AI, try to add one or more of the following things to imrove it:

-   Try to make it find the weakest enemy close.
-   Try to make different AI players work together on killing a specific unit.
-   When most battles becomes centered about the fountain, make heroes run away from it when they’re fleeing.
-   Make the AI post text messages that varies depending on the situation (for example, an AI player can say “Die, sucker!” before killing you).

I hope this will help somebody!

Codes & Snippets / Re: BoolexprUtils
« Last post by moyack on November 08, 2016, 09:15:32 AM »
simple, but effective ;)

Good job!
Codes & Snippets / BoolexprUtils
« Last post by AGD on November 08, 2016, 08:04:14 AM »
True and False BooleanExpressions

Credits goes to Vexorian for the first idea.

Vjass version
Code: jass  [Select]
  1. library BoolexprUtils
  3.     globals
  4.         boolexpr BOOLEXPR_TRUE
  5.         boolexpr BOOLEXPR_FALSE
  6.     endglobals
  8.     private module Init
  10.         private static method filterTrue takes nothing returns boolean
  11.             return true
  12.         endmethod
  14.         private static method filterFalse takes nothing returns boolean
  15.             return false
  16.         endmethod
  18.         private static method onInit takes nothing returns nothing
  19.             set BOOLEXPR_TRUE = Filter(function thistype.filterTrue)
  20.             set BOOLEXPR_TRUE = Filter(function thistype.filterFalse)
  21.         endmethod
  23.     endmodule
  25.     private struct S extends array
  26.         implement Init
  27.     endstruct
  29. endlibrary

Zinc version
Code: jass  [Select]
  1. //! zinc
  2. library BoolexprUtils {
  4.     public boolexpr BOOLEXPR_TRUE, BOOLEXPR_FALSE;
  6.     module Init {
  7.         static method onInit() {
  8.             BOOLEXPR_TRUE = Filter(function() -> boolean {return true;});
  9.             BOOLEXPR_FALSE = Filter(function() -> boolean {return false;});
  10.         }
  11.     }
  13.     struct S extends array {module Init;}
  15. }
  16. //! endzinc
Codes & Snippets / Re: UnitRecycler
« Last post by AGD on November 05, 2016, 09:25:50 AM »
Oh, nice to know that you already have conceived the concept a long time ago =D Though the two resources have a bit different approach.
Btw, mine does not account for summoned units and structures yet.
Codes & Snippets / Re: UnitRecycler
« Last post by moyack on November 02, 2016, 10:51:45 AM »
Nice!!! a unit recycler system :)

This remembers an old one I did with Damage detection integrated. Here's the link: http://blizzardmodding.info/4529/system-unit-recycler-simple-damage-detection-system/

One thing to know: is this system capable to manage buildings and summoned units?
Codes & Snippets / UnitRecycler
« Last post by AGD on November 02, 2016, 03:36:36 AM »
A useful library which allows you to recycle units (even dead ones, but they must leave a corpse), avoiding yet another permanent 0.04kb memory leak for each future CreateUnit() call.

Code: jass  [Select]
  1. library UnitRecycler /* v1.3b
  4.     |=============|
  5.     | Author: AGD |
  6.     |=============|
  8.     */requires /*
  10.     */ReviveUnit                        /*  http://www.hiveworkshop.com/threads/snippet-reviveunit.186696/
  11.     */UnitDex                           /*  http://www.hiveworkshop.com/threads/system-unitdex-unit-indexer.248209/
  12.     */optional Table                    /*  http://www.hiveworkshop.com/threads/snippet-new-table.188084/
  13.     */optional TimerUtils               /*  http://www.wc3c.net/showthread.php?t=101322
  14.     */optional RegisterPlayerUnitEvent  /*  http://www.hiveworkshop.com/threads/snippet-registerevent-pack.250266/
  16.     This system is important because CreateUnit() is one of the most processor-intensive function in
  17.     the game and there are reports that even after they are removed, they still leave some bit of memory
  18.     consumption (0.04 KB) on the RAM. Therefore it would be very helpful if you can minimize unit
  19.     creation or so. This system also allows you to recycle dead units to avoid permanent 0.04 KB memory
  20.     leak for each future CreateUnit() call.                                                                 */
  22. //! novjass
  24.     [Credits]
  25.         Aniki - For suggesting ideas on further improvements
  28.     |-----|
  29.     | API |
  30.     |-----|
  32.         function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit/*
  33.             - Returns unit of specified ID from the stock of recycled units. If there's none in the stock that
  34.               matched the specified unit's rawcode, it will create a new unit instead
  35.             - Returns null if the rawcode's unit-type is a hero or non-existent
  37.       */function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit/*
  38.             - Works similar to GetRecycledUnit() except that if the input rawcode's unit-type is a hero, it will
  39.               be created via CreateUnit() instead
  40.             - You can use this as an alternative to CreateUnit()
  42.       */function RecycleUnit takes unit u returns boolean/*
  43.             - Recycles the specified unit and returns a boolean value depending on the success of the operation
  44.             - Does nothing to hero units
  46.       */function RecycleUnitEx takes unit u returns boolean/*
  47.             - Works similar to RecycleUnit() except that if <u> is not recyclable, it will be removed via
  48.               RemoveUnit() instead
  49.             - You can use this as an alternative to RemoveUnit()
  51.       */function RecycleUnitDelayed takes unit u, real delay returns nothing/*
  52.             - Recycles the specified unit after <delay> seconds
  54.       */function RecycleUnitDelayedEx takes unit u, real delay returns nothing/*
  55.             - Works similar to RecycleUnitDelayed() except that it calls RecycleUnitEx() instead of RecycleUnit()
  57.       */function UnitAddToStock takes integer rawCode returns boolean/*
  58.             - Creates a unit of type ID and adds it to the stock of recycled units then returns a boolean value
  59.               depending on the success of the operation
  61. *///! endnovjass
  66.     globals
  68. /*      The owner of the stocked/recycled units
  69. */      private constant player OWNER               = Player(15)
  71. /*      Determines if dead units will be automatically recycled
  72.         after a delay designated by the <constant function
  73.         DeathTime below>
  74. */      private constant boolean AUTO_RECYCLE_DEAD  = true
  76. /*      Error debug message prefix
  77. */      private constant string ERROR_PREFIX        = "|CFFFF0000Operation Failed: "
  79.     endglobals
  81.     /* The delay before dead units will be recycled in case AUTO_RECYCLE_DEAD == true */
  82.     static if AUTO_RECYCLE_DEAD then
  83.         private constant function DeathTime takes unit u returns real
  84.             /*if <condition> then
  85.                   return someValue
  86.               elseif <condition> then
  87.                   return someValue
  88.               endif                 */
  89.             return 8.00
  90.         endfunction
  91.     endif
  93.     /* When recycling a unit back to the stock, these resets will be applied to the
  94.        unit. You can add more actions to this or you can delete this textmacro if you
  95.        don't need it.                                                                       */
  96.         //! textmacro_once UNIT_RECYCLER_RESET
  97.             call SetUnitScale(u, 1, 0, 0)
  98.             call SetUnitVertexColor(u, 255, 255, 255, 255)
  99.             call SetUnitFlyHeight(u, GetUnitDefaultFlyHeight(u), 0)
  100.         //! endtextmacro
  105.     /*==== Do not do changes below this line if you're not so sure on what you're doing ====*/
  106.     native UnitAlive takes unit u returns boolean
  108.     globals
  109.         private keyword S
  110.         private integer count = 0
  111.         private real unitCampX
  112.         private real unitCampY
  113.         private integer array stack
  114.         private boolean array stacked
  115.     endglobals
  117.     private function GetIndex takes integer rawCode returns integer
  118.         static if LIBRARY_Table then
  119.             local integer i = S.table.integer[rawCode]
  120.             if i == 0 then
  121.                 set count = count + 1
  122.                 set S.table.integer[rawCode] = count
  123.                 set i = count
  124.             endif
  125.         else
  126.             local integer i = LoadInteger(S.hash, -1, rawCode)
  127.             if i == 0 then
  128.                 set count = count + 1
  129.                 call SaveInteger(S.hash, -1, rawCode, count)
  130.                 set i = count
  131.             endif
  132.         endif
  133.         return i
  134.     endfunction
  136.     static if DEBUG_MODE then
  137.         private function Debug takes string msg returns nothing
  138.             call DisplayTimedTextToPlayer(GetLocalPlayer(), 0, 0, 60, "|CFFFFCC00[Unit Recycler]|R " + msg)
  139.         endfunction
  140.     endif
  142.     function GetRecycledUnit takes player owner, integer rawCode, real x, real y, real facing returns unit
  143.         local integer i
  144.         if not IsHeroUnitId(rawCode) then
  145.             set i = GetIndex(rawCode)
  146.             if stack[i] == 0 then
  147.                 set bj_lastCreatedUnit = CreateUnit(owner, rawCode, x, y, facing)
  148.                 debug call Debug(GetUnitName(bj_lastCreatedUnit) + " stock is empty, creating new " + GetUnitName(bj_lastCreatedUnit))
  149.             else
  150.                 static if LIBRARY_Table then
  151.                     set bj_lastCreatedUnit = S.hash[i].unit[stack[i]]
  152.                 else
  153.                     set bj_lastCreatedUnit = LoadUnitHandle(S.hash, i, stack[i])
  154.                 endif
  155.                 set stacked[GetUnitId(bj_lastCreatedUnit)] = false
  156.                 call PauseUnit(bj_lastCreatedUnit, false)
  157.                 call SetUnitOwner(bj_lastCreatedUnit, owner, true)
  158.                 call SetUnitPosition(bj_lastCreatedUnit, x, y)
  159.                 call SetUnitFacing(bj_lastCreatedUnit, facing)
  160.                 set stack[i] = stack[i] - 1
  161.                 debug call Debug("Retrieving " + GetUnitName(bj_lastCreatedUnit) + " from stock")
  162.             endif
  163.             debug if bj_lastCreatedUnit == null then
  164.                 debug call Debug(ERROR_PREFIX + "Specified unit-type does not exist")
  165.             debug endif
  166.         else
  167.             debug call Debug(ERROR_PREFIX + "Attemp to retrieve a hero unit")
  168.             return null
  169.         endif
  170.         return bj_lastCreatedUnit
  171.     endfunction
  173.     function GetRecycledUnitEx takes player owner, integer rawCode, real x, real y, real facing returns unit
  174.         if not IsHeroUnitId(rawCode) then
  175.             return GetRecycledUnit(owner, rawCode, x, y, facing)
  176.         endif
  177.         debug call Debug("Cannot retrieve a hero unit, creating new unit")
  178.         return CreateUnit(owner, rawCode, x, y, facing)
  179.     endfunction
  181.     function RecycleUnit takes unit u returns boolean
  182.         local integer rawCode = GetUnitTypeId(u)
  183.         local integer uDex = GetUnitId(u)
  184.         local integer i
  185.         if not IsHeroUnitId(rawCode) and not stacked[uDex] and u != null then
  186.             set i = GetIndex(rawCode)
  187.             if not UnitAlive(u) and not ReviveUnit(u) then
  188.                 debug call Debug(ERROR_PREFIX + "Unable to recycle unit: Unable to revive dead unit")
  189.                 return false
  190.             endif
  191.             set stacked[uDex] = true
  192.             call PauseUnit(u, true)
  193.             call SetUnitOwner(u, OWNER, true)
  194.             call SetUnitX(u, unitCampX)
  195.             call SetUnitY(u, unitCampY)
  196.             call SetUnitFacing(u, 270)
  197.             call SetWidgetLife(u, GetUnitState(u, UNIT_STATE_MAX_LIFE))
  198.             call SetUnitState(u, UNIT_STATE_MANA, GetUnitState(u, UNIT_STATE_MAX_MANA))
  199.             //! runtextmacro optional UNIT_RECYCLER_RESET()
  200.             set stack[i] = stack[i] + 1
  201.             static if LIBRARY_Table then
  202.                 set S.hash[i].unit[stack[i]] = u
  203.             else
  204.                 call SaveUnitHandle(S.hash, i, stack[i], u)
  205.             endif
  206.             debug call Debug("Successfully recycled " + GetUnitName(u))
  207.             return true
  208.         debug else
  209.             debug if stacked[uDex] then
  210.                 debug call Debug(ERROR_PREFIX + "Attempt to recycle an already recycled unit")
  211.             debug elseif u == null then
  212.                 debug call Debug(ERROR_PREFIX + "Attempt to recycle a null unit")
  213.             debug else
  214.                 debug call Debug(ERROR_PREFIX + "Attempt to recycle a hero unit")
  215.             debug endif
  216.         endif
  217.         return false
  218.     endfunction
  220.     function RecycleUnitEx takes unit u returns boolean
  221.         if not RecycleUnit(u) then
  222.             call RemoveUnit(u)
  223.             debug call Debug("Cannot recycle the specified unit, removing unit")
  224.             return false
  225.         endif
  226.         return true
  227.     endfunction
  229.     //! textmacro DELAYED_RECYCLE_TYPE takes EX
  230.     private function RecycleTimer$EX$ takes nothing returns nothing
  231.         local timer t = GetExpiredTimer()
  232.         static if LIBRARY_TimerUtils then
  233.             call RecycleUnit$EX$(GetUnitById(GetTimerData(t)))
  234.             call ReleaseTimer(t)
  235.         else
  236.             local integer key = GetHandleId(t)
  237.             static if LIBRARY_Table then
  238.                 call RecycleUnit$EX$(S.hash[0].unit[key])
  239.                 call S.hash[0].remove(key)
  240.             else
  241.                 call RecycleUnit$EX$(LoadUnitHandle(S.hash, 0, key))
  242.                 call RemoveSavedHandle(S.hash, 0, key)
  243.             endif
  244.             call DestroyTimer(t)
  245.         endif
  246.         set t = null
  247.     endfunction
  249.     function RecycleUnitDelayed$EX$ takes unit u, real delay returns nothing
  250.         static if LIBRARY_TimerUtils then
  251.             call TimerStart(NewTimerEx(GetUnitId(u)), delay, false, function RecycleTimer$EX$)
  252.         else
  253.             local timer t = CreateTimer()
  254.             static if LIBRARY_Table then
  255.                 set S.hash[0].unit[GetHandleId(t)] = u
  256.             else
  257.                 call SaveUnitHandle(S.hash, 0, GetHandleId(t), u)
  258.             endif
  259.             call TimerStart(t, delay, false, function RecycleTimer$EX$)
  260.             set t = null
  261.         endif
  262.     endfunction
  263.     //! endtextmacro
  265.     //! runtextmacro DELAYED_RECYCLE_TYPE("")
  266.     //! runtextmacro DELAYED_RECYCLE_TYPE("Ex")
  268.     function UnitAddToStock takes integer rawCode returns boolean
  269.         local unit u
  270.         local integer i
  271.         if not IsHeroUnitId(rawCode) then
  272.             set u = CreateUnit(OWNER, rawCode, unitCampX, unitCampY, 270)
  273.             if u != null then
  274.                 set i = GetIndex(rawCode)
  275.                 call SetUnitX(u, unitCampX)
  276.                 call SetUnitY(u, unitCampY)
  277.                 call PauseUnit(u, true)
  278.                 set stacked[GetUnitId(u)] = true
  279.                 set stack[i] = stack[i] + 1
  280.                 static if LIBRARY_Table then
  281.                     set S.hash[i].unit[stack[i]] = u
  282.                 else
  283.                     call SaveUnitHandle(S.hash, i, stack[i], u)
  284.                 endif
  285.                 debug call Debug("Adding " + GetUnitName(u) + " to stock")
  286.                 return true
  287.             debug else
  288.                 debug call Debug(ERROR_PREFIX + "Attemp to stock a null unit")
  289.             endif
  290.             set u = null
  291.         debug else
  292.             debug call Debug(ERROR_PREFIX + "Attemp to stock a hero unit")
  293.         endif
  294.         return false
  295.     endfunction
  297.     static if AUTO_RECYCLE_DEAD then
  298.         private function OnDeath takes nothing returns nothing
  299.             local unit u = GetTriggerUnit()
  300.             if not IsUnitType(u, UNIT_TYPE_HERO) and not IsUnitType(u, UNIT_TYPE_STRUCTURE) then
  301.                 call RecycleUnitDelayed(u, DeathTime(u))
  302.             endif
  303.             set u = null
  304.         endfunction
  305.     endif
  307.     private module Init
  309.         static if LIBRARY_Table then
  310.             static TableArray hash
  311.             static Table table
  312.         else
  313.             static hashtable hash = InitHashtable()
  314.         endif
  316.         private static method onInit takes nothing returns nothing
  317.             local rect bounds = GetWorldBounds()
  318.             static if AUTO_RECYCLE_DEAD then
  319.                 static if LIBRARY_RegisterPlayerUnitEvent then
  320.                     static if RPUE_VERSION_NEW then
  321.                         call RegisterAnyPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
  322.                     else
  323.                         call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_DEATH, function OnDeath)
  324.                     endif
  325.                 else
  326.                     local trigger t = CreateTrigger()
  327.                     local code c = function OnDeath
  328.                     local integer i = 16
  329.                     loop
  330.                         set i = i - 1
  331.                         call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_DEATH, null)
  332.                         exitwhen i == 0
  333.                     endloop
  334.                     call TriggerAddCondition(t, Filter(c))
  335.                 endif
  336.             endif
  337.             static if LIBRARY_Table then
  338.                 set hash = TableArray[0x2000]
  339.                 set table = Table.create()
  340.             endif
  341.             // Hides recycled units at the top of the map beyond reach of the camera
  342.             set unitCampX = 0.00
  343.             set unitCampY = GetRectMaxY(bounds) + 1000.00
  344.             call RemoveRect(bounds)
  345.             set bounds = null
  346.         endmethod
  348.     endmodule
  350.     private struct S extends array
  351.         implement Init
  352.     endstruct
  355. endlibrary
Codes & Snippets / Re: Resource Preloader
« Last post by AGD on October 28, 2016, 01:03:52 AM »

- Made Table optional
- Added UnitRecycler as an optional requirement
- Upon calling PreloadUnit(), if the unit is not a hero and UnitRecycler is found, the unit will be added to the unit stock instead. Otherwise, it goes with normal preloading.
- dummy unit's movement is disabled to prevent possible game crash.
- You can now preload at any time during the game instead of only during the map initialization
- Significantly optimized the code
- Removed the unnecessary custom function for checking preload duplicates
- Added Table to the library requirements
- Preloading does not anymore happen in a single phase at the GUI Map Initialization
- Resources are now preloaded at the instant you call the preload function
- Other changes
Codes & Snippets / Re: Random Iteration
« Last post by moyack on September 04, 2016, 10:22:47 AM »
I just did some small aesthetics for your post. Interesting snippet :)
Codes & Snippets / Random Iteration
« Last post by AGD on September 04, 2016, 09:16:22 AM »
This snippet is a replacement to the BJ function GetRandomSubGroup with the advantage of being able to directly enumerate random number of units in a group instead of creating another subgroup of an already existing group. Notice that this is not the same as GroupEnumUnitsInRangeCounted in which case the enumerated units are not random but is based on which units are picked first.

Code: jass  [Select]
  1. library RandomIteration
  3.     globals
  4.         private group tempGroup = CreateGroup()
  5.         private integer unitCount
  6.     endglobals
  8.     private function EnumUnits takes nothing returns boolean
  9.         call GroupAddUnit(tempGroup, GetFilterUnit())
  10.         set unitCount = unitCount + 1
  11.         return false
  12.     endfunction
  14.     function EnumRandomUnitsInRangeCounted takes group g, real x, real y, real radius, integer limit returns nothing
  15.         local real chance
  16.         local unit u
  17.         call GroupEnumUnitsInRange(g, x, y, radius, Filter(function EnumUnits))
  18.         set chance = I2R(limit)/I2R(unitCount)
  19.         loop
  20.             set u = FirstOfGroup(tempGroup)
  21.             exitwhen u == null or limit == 0
  22.             call GroupRemoveUnit(tempGroup, u)
  23.             if GetRandomReal(0, 1) <= chance then
  24.                 call GroupAddUnit(g, u)
  25.                 set limit = limit - 1
  26.             endif
  27.         endloop
  28.         set u = null
  29.     endfunction
  31.     function EnumRandomUnitsInRectCounted takes group g, rect r, integer limit returns nothing
  32.         local real chance
  33.         local unit u
  34.         call GroupEnumUnitsInRect(g, r, Filter(function EnumUnits))
  35.         set chance = I2R(limit)/I2R(unitCount)
  36.         loop
  37.             set u = FirstOfGroup(tempGroup)
  38.             exitwhen u == null or limit == 0
  39.             call GroupRemoveUnit(tempGroup, u)
  40.             if GetRandomReal(0, 1) <= chance then
  41.                 call GroupAddUnit(g, u)
  42.                 set limit = limit - 1
  43.             endif
  44.         endloop
  45.         set u = null
  46.     endfunction
  48. endlibrary
Pages: [1] 2 3 ... 10
Blizzard Modding Information Center Starcraft II Modding Information Center Wacraft III Modding Information Center WC3JASS.com - The JASS Vault Chronicles of Darkness - A Warcraft III mod Jetcraft - A Starcraft II mod Troll Smash - A Warcraft III Arena
  Mod DB - Change the Game Power of Corruption - A Warcraft III altered melee map Chaos Realm - The world of Game modders and wc3 addicts Follow us on Facebook!!