HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

modules=new and better textmacro?

04-04-2011, 03:46 AM#1
SanKakU
* Modules are superior to text macros in every imaginable way, the syntax is good and readable and there are no name conflict risks.

that was said in the introduction to itemdex...a script that Vexorian submitted that i am now going to use in the latest version of my map. i don't need any unit tracking but a minor item tracking system seems like it's efficient and useful...and easy.

but i had a major question what did he mean about modules? i've seen them but don't understand them yet...can someone make a quick explanation of how you would convert a textmacro into a module? or is that not really how it works? or what?
04-04-2011, 09:28 AM#2
Bribe
Modules are textmacros with no parameters but with the following attributes:
  1. They inherit from both the library they were declared in as well as the struct & the library they are implemented in.
  2. They can declare their own private components to protect data from being accessed by the struct.
  3. They can have their own initializers. (private static method onInit)
04-04-2011, 06:20 PM#3
SanKakU
Quote:
Originally Posted by Bribe
Modules are textmacros with no parameters but with the following attributes:
  1. They inherit from both the library they were declared in as well as the struct & the library they are implemented in.
  2. They can declare their own private components to protect data from being accessed by the struct.
  3. They can have their own initializers. (private static method onInit)
that's nice but i feel like you're quoting some resource, like i've heard that one before.
let me quote myself like you've heard this one before.
can someone make a quick explanation of how you would convert a textmacro into a module?
perhaps i should provide the example, huh?
i have many textmacro sets like this in libraries:
Code:
//! textmacro itemstacking takes itemA, itemB
private function f takes nothing returns boolean
local unit mu = GetManipulatingUnit()
local integer invi = GetInventoryHero(mu, $itemB$)
local item itm = null
if invi !=6 then
set itm=GetItemHero(mu,$itemB$)
call SetItemCharges(itm,GetItemCharges(itm)+1)
set mu=null
return false 
else
call UnitAddItemById(mu,$itemB$)
set mu=null
return false
endif
endfunction
private function I takes nothing returns nothing
call GT_AddItemAcquiredAction(function f, $itemA$)
endfunction
endscope
//! endtextmacro
and then i have a bunch of these somewhere else:
Code:
scope stacka initializer I
//! runtextmacro itemstacking("'desc'","'I007'")
scope stackb initializer I
//! runtextmacro itemstacking("'fgsk'","'I008'")
so if module is better than textmacro, someone show me how to get rid of my textmacro sets and use the modules.
04-05-2011, 02:38 AM#4
PurgeandFire111
Despite modules being considered a "high-level textmacro", they have different purposes than a textmacro.

Textmacros can be used on anything and implemented anywhere. Modules, on the other hand, can only be implemented into structs.

The reasons to use a module are pretty much the ones stated by Bribe.

If you really want to convert that textmacro into a module, you can always brute force it.
Collapse JASS:
module ItemStacking
    private static method f takes nothing returns boolean
        local unit mu      = GetManipulatingUnit()
        local integer invi = GetInventoryHero(mu, thistype.itemB)
        local item itm     
        if invi != 6 then
            set itm = GetItemHero(mu, thistype.itemB)
            call SetItemCharges(itm, GetItemCharges(itm)+1)
            set mu  = null
            set itm = null
            return false
        endif
        call UnitAddItemById(mu, thistype.itemB)
        set mu = null
        return false
    endmethod
    
    static method onInit takes nothing returns nothing
        call GT_AddItemAcquiredAction(function thistype.f, thistype.itemA)
    endmethod
endmodule

That makes the requirement of static integer arrays from the implemented struct. So it would end up looking like this:
Collapse JASS:
struct A
    static constant integer itemA = 'desc'
    static constant integer itemB = 'I007'
    implement ItemStacking
endstruct

However, that ends up creating problems:
  1. You can only have 1 implementation per struct if you require global creation in that manner.
  2. It requires global creation, which is bad compared to the textmacros which require no global creation. (unless you use Vex's optimizer to inline constants or something)

In terms of flexibility, textmacros end up winning the race. However, each has their own benefits, otherwise they would not have been created. ;)
04-05-2011, 09:31 AM#5
Anitarf
Although they are similar in function, textmacros and modules are used for different purposes.

I find modules to be more readable and have nicer implementation syntax than textmacros, so I feel that any code that you want users to copy to their libraries should come in modules. For example, grim001 uses modules in his AutoIndex and I use a module for my ABuffStruct.

On the other hand, when copying code within a library, textmacros tend to be more useful. When you replicate code within the same library, you usually want each copy to be different in some way and the textmacro parameters allow you to do this easily while modules are not as flexible. For example, I used a textmacro in SpellEvent to replicate event response code for all five of the spell events.

However, in general I use a coding style that requires very little use of textmacros. For example, I would write your code in such a way that it wouldn't need to be copied for each stackable item; instead, I would use a hashtable to link source items to results and have one function run whenever any item is acquired, check the hastable to see if that item was registered as a source and if so, create/increase the charges of the other item.
04-05-2011, 08:11 PM#6
SanKakU
oh ya purge... wouldn't the 2 non native functions getitemhero and getinventoryhero need to be present in the module? i didn't post that part of the code? sorry.

would i be correct to suppose i should make them methods and put them above the other method there?

at any rate, it would appear the point you and previous poster were trying to make was that module
was being praised in itemdex for accomplishing something that was quite different from what my textmacro scopes are doing.

now anitarf seems to have an idea about hashtables. i would like to see him show me what he means. but maybe i better post the rest of my code...btw, i was noticing cjass was having difficulty compiling my textmacro, so i had to update my textmacro. (quote from DotaMaster666: cJass display syntax error because it process textmacros like #define instruction - you cannot use in textmacros or defines unclossed library or scope (and also closed, but unopened, in cJass you may use private define, so it must process scope and library before define and textmacro).) i will post the textmacro and the entire trigger that i have been working on, even though much of it may be irrelevant.

now maybe i can post it and anitarf, perhaps after i do that you could show me what you meant. as it is, i don't know use of hashtables, either. they make no sense to me.

bear in mind that some stuff in that trigger might be unused in one map and used in another, since i am currently developing 2+ maps. also, in case you didn't already know it, the stacking is taking advantage of powerups in order to stack items even if the inventory is full. i'm in the process of remaking the code since i got rid of aids and other stuff and so some things might not be optimal right now.
Code:
//! runtextmacro itemswitching("'mcri'","'I00B'","a")
//! runtextmacro ammostacking("'I00D'","'mcri'","3","b")
//! runtextmacro ammostacking("'rnec'","'I00E'","4","c")
//! runtextmacro ammostacking("'I00F'","'pgma'","2","d")
//! runtextmacro itemstacking("'desc'","'I007'","a")
//! runtextmacro itemstacking("'fgsk'","'I008'","b")
//! runtextmacro itemstacking("'fgfh'","'I009'","c")
//! runtextmacro itemstacking("'pclr'","'I005'","d")
//! runtextmacro itemstacking("'pman'","'I006'","e")
//! runtextmacro itemstacking("'tgxp'","'I002'","f")
//! runtextmacro itemstacking("'pams'","'I004'","g")
//! runtextmacro itemstacking("'moon'","'I003'","h")
//! runtextmacro itemstacking("'I00C'","'whwd'","i")
Code:
//compilation of itemtriggers composed by sankaku
//dubbed item management 
scope powerupSENTINEL initializer I
private function F takes nothing returns nothing
if GetWidgetLife(GetManipulatedItem()) == 0 then
call RemoveItem(GetManipulatedItem())
endif     
endfunction
private function I takes nothing returns nothing
local trigger T = CreateTrigger(  )
call TriggerRegisterAnyUnitEventBJ( T, EVENT_PLAYER_UNIT_PICKUP_ITEM )
call TriggerAddAction( T, function F)
endfunction
endscope
scope mergeitems initializer I
private function f takes nothing returns boolean
call AddItemCharges(GetManipulatingUnit(),GetManipulatedItem())
return false
endfunction
private function I takes nothing returns nothing
local trigger t = CreateTrigger(  )
//! runtextmacro CHASER_PUE("t","EVENT_PLAYER_UNIT_PICKUP_ITEM")
call TriggerAddCondition( t, Condition(function f) )
endfunction
endscope
//=====================================================================================
//HOW TO USE:
//=====================================================================================
//
//=====================================================================================
//MAKE YOUR OWN TRIGGER, CALL IT WHATEVER YOU LIKE...AND MAKE YOUR OWN TEXTMACRO SCOPES
//=====================================================================================
//
//=====================================================================================
//CREDITS
//=====================================================================================
//RDZ's Incredible Item Stacking System of Doom - by Rao Dao Zao
//=====================================================================================
//GTrigger Event System - by Jesus4Lyf
//=====================================================================================
//Itemdex - Vexorian
//=====================================================================================

library StackItems uses GT//and Itemdex

function GetIndexItemCopy takes unit u, integer i, integer in returns integer
local integer index
local item indexItem
set index = 0
loop
set indexItem = UnitItemInSlot(u, index)
if (indexItem != null) and (GetItemTypeId(indexItem) == i) and (GetItemId(indexItem)!=in) then
return index
else
set index=index+1
endif
exitwhen index>5
endloop
return 6
endfunction

function GetInventoryHero takes unit u, integer i returns integer
local integer index
local item indexItem
set index = 0
loop
set indexItem = UnitItemInSlot(u, index)
if (indexItem != null) and (GetItemTypeId(indexItem) == i) then
return index
else
set index=index+1
endif
exitwhen index>5
endloop
return 6
endfunction

function GetItemHero takes unit whichUnit, integer itemId returns item
local integer index = GetInventoryHero(whichUnit, itemId)
if (index == 6) then
return null
else
return UnitItemInSlot(whichUnit, index)
endif
endfunction

function AddItemCharges takes unit x, item y returns boolean
local integer w = GetIndexItemCopy(x, GetItemTypeId(y),GetItemId(y))
local item z = null
if GetItemType(y) !=ConvertItemType(1) then
return false
endif
if w !=6 then
set z=UnitItemInSlot(x,w)
call SetItemCharges(y,GetItemCharges(z)+GetItemCharges(y))
call RemoveItem(z)
set z=null
return false 
else
return false
endif
endfunction
//the following is for if you want to add the AUTOMATIC stacking.
//this is useful for if you have units that expect to have full inventories already.
//! textmacro ammostacking takes itemA, itemB, count, type
scope ammo$type$ initializer I
private function f takes nothing returns boolean
local unit mu = GetManipulatingUnit()
local integer invi = GetInventoryHero(mu, $itemB$)
local item itm = null
if invi !=6 then
set itm=GetItemHero(mu,$itemB$)
call SetItemCharges(itm,GetItemCharges(itm)+$count$)
set mu=null
return false 
else
call UnitAddItemById(mu,$itemB$)
set mu=null
return false
endif
set mu=null
return false
endfunction
private function I takes nothing returns nothing
call GT_AddItemAcquiredAction(function f, $itemA$)
endfunction
endscope
//! endtextmacro
//this textmacro uses GTrigger by Jesus4Lyf, download it at thehelper.net
//! textmacro itemstacking takes itemA, itemB, type
scope stack$type$ initializer I
private function f takes nothing returns boolean
local unit mu = GetManipulatingUnit()
local integer invi = GetInventoryHero(mu, $itemB$)
local item itm = null
if invi !=6 then
set itm=GetItemHero(mu,$itemB$)
call SetItemCharges(itm,GetItemCharges(itm)+1)
set mu=null
return false 
else
call UnitAddItemById(mu,$itemB$)
set mu=null
return false
endif
endfunction
private function I takes nothing returns nothing
call GT_AddItemAcquiredAction(function f, $itemA$)
endfunction
endscope
//! endtextmacro
//remember to make your own textmacro scopes in another trigger, or if you prefer, at the
//bottom of this trigger
//for example:

//! textmacro DisplayToRecipe takes itemA, itemB, type
scope display$type$ initializer I
private function f takes nothing returns boolean
call UnitAddItemById(GetManipulatingUnit(),$itemB$)
return false
endfunction
private function I takes nothing returns nothing
call GT_AddItemAcquiredAction(function f, $itemA$)
endfunction
endscope
//! endtextmacro

/*the following is a textmacro that exchanges one item for another, think of it like
in dota when a hero is ranged or melee, or you have a bear or syllabear item versions...
well it is kinda like that.  you can make additional textmacro scopes for different unit
types easily by replacing the UNIT_TYPE_HERO with something else, like in the dota
example of melee or ranged... UNIT_TYPE_MELEE_ATTACKER, in this case, we are checking
to make sure that the heroes get the itemA, but if a different unit picks up the itemA,
that unit gets itemB instead.  you can of course name your textmacro whatever you want,
we can of course, make the next one be called itemswitchhero and make a second one
called itemswitchmelee*/

//! textmacro itemswitching takes itemA, itemB, type
scope switch$type$ initializer I
private function f takes nothing returns boolean
local unit mu = GetManipulatingUnit()
if IsUnitType(mu, UNIT_TYPE_HERO) then
set mu=null
return false
else
call UnitRemoveItem(mu,GetManipulatedItem())
call UnitAddItemById(mu,$itemB$)
set mu=null
return false
endif
endfunction
private function I takes nothing returns nothing
call GT_AddItemAcquiredAction(function f, $itemA$)
endfunction
endscope
//! endtextmacro
endlibrary
library Itemdex initializer init
//**************************************************************
//* Itemdex
//* -------
//* I need this for many things, something that indexes items
//* Problems of item indexing:
//*  a) Only way to detect item death is with a dynamic trigger
//*  b) No known way to detect an item removal event. Besides
//*  expecting the user to tell you.
//*  c) Runes leave some kind of ghost item with 0.0 life that
//*  still got all the data of the item , including its type.
//*
//* ...So, I use a loop to do index garbage collection,
//* see you in hell. Luckily it is even rarer to think of 8190
//* items than to think of 8190 units so it should work ok...
//*
//* ItemUserData Version//comments added for table version creation
//*
//***************************************************************
 globals
private constant real RECYCLE_INTERVAL = 45.0//2.5
private constant integer RECYCLE_COUNT= 72//10
// Smaller RECYCLE_INTERVAL / Bigger RECYCLE_COUNT equal faster
//  detection of removed items and worse performance.
//
// "Every RECYCLE_INTERVAL seconds RECYCLE_COUNT indexes are
//  reviewed looking for removed/dead ones to recycle"
 endglobals
private struct index
item attachedto
endstruct
 globals
private index   array activeIndexes
private integer indexN = 0
private integer DataInteger
 endglobals
function GetItemId takes item it returns integer
 local index d
//sankaku adds the if then to avoid indexing blank inventory slots...
 if it==null then
 return 0
 endif
 //iftablethenset d = index( HandleTable(DataInteger)[it] )
set d = index( GetItemUserData(it) )
if(d==0) then
set d = index.create()
set d.attachedto = it
//iftablethenset HandleTable(DataInteger)[it] = integer(d)
call SetItemUserData(it, integer(d) )
set activeIndexes[indexN] = d
set indexN = indexN + 1
endif
return integer(d)
endfunction
// Requirements to use ItemdexModule:
// : create takes no arguments
// : no custom .destroy method
//
module Itemdex
private static thistype array V
private item attachedto = null
method operator thatItem takes nothing returns item
return this.attachedto
endmethod
private static method get takes nothing returns nothing
//remind me to add .name to structs
endmethod
static method operator [] takes item it returns thistype
 local integer d = GetItemId(it)
if( (.V[d]!=0) and (.V[d].attachedto != it) ) then
call .V[d].destroy()
set  .V[d] = 0
endif
if ( .V[d] == 0) then
set .V[d] = thistype.create()
set .V[d].attachedto = it
endif
return .V[d]
endmethod
method destroy takes nothing returns nothing
 local integer d
if ( this.attachedto != null) then
set d = GetItemId( this.attachedto)
set .V[d] = 0
call deallocate()
set this.attachedto = null
else
call deallocate()
endif
endmethod
method release takes nothing returns nothing
call destroy()
endmethod
endmodule
globals
private integer lastindex = 0
endglobals
private function recycleLoop takes nothing returns nothing
 local integer start
 local integer i
 local integer j
if(indexN>0) then
//set start = lastindex % indexN
set start = lastindex - indexN * (lastindex/indexN)
set i=start
set j=0
loop
exitwhen (j>=RECYCLE_COUNT)
if (GetItemTypeId(activeIndexes[i].attachedto) == 0) or (GetWidgetLife(activeIndexes[i].attachedto) == 0.0) then
set activeIndexes[i].attachedto = null
call activeIndexes[i].destroy()
set indexN = indexN-1
set activeIndexes[i] = activeIndexes[ indexN ]
endif
set i = i +1
exitwhen (indexN==0)
if(i>=indexN) then
set i=0
endif
if(start>=indexN) then
set start = 0
endif
exitwhen(i==start)
set j=j+1
endloop
set lastindex = i
endif
endfunction
private function init takes nothing returns nothing
 local timer T= CreateTimer()
 //iftablethenset DataInteger = integer(HandleTable.create())//noexclusion
 call TimerStart(T, RECYCLE_INTERVAL, true, function recycleLoop)
 set T=null   
endfunction
endlibrary
off topic but did anyone notice that one library is using another here but somehow the requirement keyword isn't necessary? i'm not sure why my code bypassed the need to type needs/uses/requires but i wonder why more coders don't do what i just did. it makes me think requirement keyword is but a mere formality if you can code things a certain way.
04-06-2011, 10:25 AM#7
Anitarf
Quote:
Originally Posted by SanKakU
now maybe i can post it and anitarf, perhaps after i do that you could show me what you meant. as it is, i don't know use of hashtables, either. they make no sense to me.

bear in mind that some stuff in that trigger might be unused in one map and used in another, since i am currently developing 2+ maps. also, in case you didn't already know it, the stacking is taking advantage of powerups in order to stack items even if the inventory is full. i'm in the process of remaking the code since i got rid of aids and other stuff and so some things might not be optimal right now.
Yeah, I thought that was what you were doing based on the code. I did also notice some problems: apparently, the powerups can only have one charge? What happens if you drop a stack of items, does it get replaced by a powerup? If that's the case, multiple charges per pickup should be supported (if powerups can't have charges, then that data needs to be attached some other way). Also, if the unit does not have the item yet, then you create it for the unit, but you should first check if the unit has any empty inventory slots.

I'll disregard that for now, though, and just demonstrate the use of a hashtable; I'll just copy the rest of the code, even if I think it could be done better. Hashtables are data structures that work like two dimensional arrays. They don't have the 8190 size limit of arrays, so we can use very large numbers as indexes; for example, we can use item rawcodes, which allows us to use an item rawcode to store and retrieve another item rawcode. Since I only need a one dimensional array, I'll be using Table which uses a single hashtable to make any number of Tables, which are like vJass dynamic arrays only without a size limit.

Collapse JASS:
globals
    private Table tab
endglobals

function NewItemStack takes integer powerup, integer stack returns nothing
    set tab[powerup] = stack
endfunction

private function AnyItemAcquired takes nothing returns boolean
    local integer itemA = GetItemTypeId( GetManipulatedItem() )
    local integer itemB = tab[itemA]
    local unit mu
    local integer invi
    local item itm = null

    if itemB==0 then
        return false // The acquired item does not have a stackable item linked to it, do nothing.
    endif
    set mu = GetManipulatingUnit()
    set invi = GetInventoryHero(mu, itemB)

    if invi !=6 then
        set itm=GetItemHero(mu,itemB)
        call SetItemCharges(itm,GetItemCharges(itm)+1)
        set itm=null
        set mu=null
        return false 
    else
        call UnitAddItemById(mu,itemB)
        set mu=null
        return false
    endif
endfunction
private function I takes nothing returns nothing
    set tab=Table.create()
    call GT_AddItemAcquiredAction(function AnyItemAcquired, 0)
    //I assume using 0 makes this work for all item types, if not then change this.
endfunction
endscope


Quote:
off topic but did anyone notice that one library is using another here but somehow the requirement keyword isn't necessary? i'm not sure why my code bypassed the need to type needs/uses/requires but i wonder why more coders don't do what i just did. it makes me think requirement keyword is but a mere formality if you can code things a certain way.
It depends on the order in which your code is compiled. If you have your code organized in multiple "triggers", the editor may mess up their order. If a function you are calling ends up below the place where you call it from, then you'll get a compile error. We use requirements to avoid this, one of the first things JassHelper does when compiling the map script is to move libraries around so that required libraries always come before the libraries requiring them. Because of that, it is highly recommended to use requirements even if the map script can be compiled without them.
04-07-2011, 03:01 AM#8
SanKakU
some interesting questions you have there, anitarf...as for dropped items...that's where the ammostacking comes into play. i was originally using a system at thehelper to merge items and then i saw itemdex and thought to use it instead.

and yeah...the regular stacking must be charges of 1 only because any higher is irrelevant. that's why i made up the ammostacking idea. which works just fine...but like i say you cannot be picking up the item if your inventory is full.

so anyhow the data is attached manually for how much "ammo" is sold...
the only thing really different from the itemdex version of my stackitems system and the old version as far as ammo stacking is concerned is that the merged items move around(merged items are different from stacked items, as stacked items require powerups)...but that might be something that i'm too lazy to fix. at any rate, i don't think that part matters much. actually if it's not for that you can't merge the items if you have a full inventory, i would just abolish the powerups altogether.

as for the off-topic part...maybe it's a simple side effect of deleting a lot of libraries...that and putting multiple libraries in the same triggers frequently.

EDIT: it doesn't appear table is so easily used...i suppose you left out a lot of code. at any rate...i'll just ignore hastables for the moment, i guess.
04-07-2011, 08:28 AM#9
Anitarf
Table is easily used, I included all the code. It is possible you are calling NewItemStack before the Table or Table's hashtable are created (for example, if you are calling NewItemStack from a module initializer). For the first, make sure function I runs before any functions that call NewItemStack, so if you make any such calls from module initializers then you need to also call function I from a module initializer. For the second, you need to fix Table (it's a simple fix, just delete its initialization function and change the global declaration to this: private hashtable ht = InitHashtable()).
04-07-2011, 09:55 PM#10
SanKakU
Quote:
Originally Posted by Anitarf
Table is easily used, I included all the code. It is possible you are calling NewItemStack before the Table or Table's hashtable are created (for example, if you are calling NewItemStack from a module initializer). For the first, make sure function I runs before any functions that call NewItemStack, so if you make any such calls from module initializers then you need to also call function I from a module initializer. For the second, you need to fix Table (it's a simple fix, just delete its initialization function and change the global declaration to this: private hashtable ht = InitHashtable()).
i don't have time to check right now as i'm on another computer, but if i understood you right, table needed to be modified which doesn't sound very appealing i guess...but i'm unsure. anyway it's possible that cjass messed up the order of the declarations. anyway the reason i know things went to hell was not that i got as far as buying items. units were not generated on the map...so when i loaded up the map, i could do nothing. i didn't actually call cheat iseedeadpeople to see what if anything was generated, though.