• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[Solved] Child struct for each Item-type

Status
Not open for further replies.
Level 4
Joined
Dec 24, 2010
Messages
67
Hey guys. How do I call a specific child struct create() method for an Item-type?
I'm bad at explaining so I'll just put the triggers here:

JASS:
struct Item
    stub method onReceive ...
    stub method onDrop ...
 
    method receive takes nothing returns nothing
        call this.onReceive()
    endmethod
    method drop takes nothing returns nothing
        call this.onDrop()
    endmethod
endstruct

struct BootsOfSpeed extends Item
    method onReceive takes nothing returns nothing
        call UnitAddAbility(...)
    endmethod
    method onDrop takes nothing returns nothing
        call UnitRemoveAbility(...)
    endmethod
endstruct

struct Yay
    static method bam takes integer id returns nothing
        local item t = CreateItem(id, ...)
     
        //This where the problem starts
        if id == 'wtvr' then
            call NAME_OF_SOME_CHILD_STRUCT.create(t)
        elseif id == 'lick' then
            call ANOTHER_CHILD.create(t)
        endif
        //I want to create specific object of struct type for each item-type created.
    endmethod
endstruct

I hope that's clear enough.
Right now, using IFs is the only way I can think of. Is there a cleaner way to do this? Cuz I have liek hundreds of itemz.
 
Level 13
Joined
Nov 7, 2014
Messages
571
Is there a cleaner way to do this?

In my opinion this would be clearer:

JASS:
struct Item
    integer id
    item it // SetItemUserData(it, <an-instance-of-this-struct>)

    method on_pickup takes unit u returns nothing
        local integer id = this.id

        if id == 'wtvr' then
            // whenever a unit picks up a war traveler boots

        elseif id == 'etc.' then
            // something else

        endif
    endmethod

    method on_drop takes unit u returns nothing
        local integer id = this.id

        if id == 'wtvr' then
            // no more "war traveling" for u

        elseif id == 'etc.' then
            // ...
        endif
    endmethod

endstruct

But then again I don't really like OOP.
 
Level 4
Joined
Dec 24, 2010
Messages
67
Nope. I have hundreds of items, and probably more to come. With that said, your example is actually my current situation. Thus I want to clean it.
 

Deleted member 219079

D

Deleted member 219079

You could take reference from Bribe's SpellEffectEvent:
JASS:
//============================================================================
// SpellEffectEvent
// - Version 1.1.0.0
//
// API
// ---
//     RegisterSpellEffectEvent(integer abil, code onCast)
// 
// Requires
// --------
//     RegisterPlayerUnitEvent: hiveworkshop.com/forums/showthread.php?t=203338
// 
// Optional
// --------
//     Table: hiveworkshop.com/forums/showthread.php?t=188084
//
library SpellEffectEvent requires RegisterPlayerUnitEvent, optional Table
 
//============================================================================
private module M
    
    static if LIBRARY_Table then
        static Table tb
    else
        static hashtable ht = InitHashtable()
    endif
    
    static method onCast takes nothing returns nothing
        static if LIBRARY_Table then
            call TriggerEvaluate(.tb.trigger[GetSpellAbilityId()])
        else
            call TriggerEvaluate(LoadTriggerHandle(.ht, 0, GetSpellAbilityId()))
        endif
    endmethod
 
    private static method onInit takes nothing returns nothing
        static if LIBRARY_Table then
            set .tb = Table.create()
        endif
        call RegisterPlayerUnitEvent(EVENT_PLAYER_UNIT_SPELL_EFFECT, function thistype.onCast)
    endmethod
endmodule
 
//============================================================================
private struct S extends array
    implement M
endstruct
 
//============================================================================
function RegisterSpellEffectEvent takes integer abil, code onCast returns nothing
    static if LIBRARY_Table then
        if not S.tb.handle.has(abil) then
            set S.tb.trigger[abil] = CreateTrigger()
        endif
        call TriggerAddCondition(S.tb.trigger[abil], Filter(onCast))
    else
        if not HaveSavedHandle(S.ht, 0, abil) then
            call SaveTriggerHandle(S.ht, 0, abil, CreateTrigger())
        endif
        call TriggerAddCondition(LoadTriggerHandle(S.ht, 0, abil), Filter(onCast))
    endif
endfunction
 
endlibrary
 
Level 13
Joined
Nov 7, 2014
Messages
571
Nope. I have hundreds of items, and probably more to come. With that said, your example is actually my current situation. Thus I want to clean it.
If statements can have ~127 branches after that you can just use a second if statement, so it doesn't matter how many items you have, if statements would scale.
Also I don't know how moving some code from "under" an if statement into its own type of struct would result in an improvement, but it's your script...

If you really want to go with that route I guess you can use a hashtable to map from item-ids to struct instances:
JASS:
globals
    hashtable ht = InitHashtable()
    Item items
endglobals
function items_init takes nothing returns nothing
    set items[1] = BootsOfSpeed.create(...)
    call SaveInteger(ht, 0, 'wtvr', items[1])

    set items[2] = Etc.create(...)
    call SaveInteger(ht, 0, 'etc.', items[2])
endfunction

function drop_item_or_something takes nothing returns nothing
    local Item it = LoadInteger(ht, 0, id)
    call it.on_drop()
endfunction

Note that using a struct per type of item and using stub methods would result in a lot of duplicated code from jasshelper (because of how those are implemented) and trigger evaluations.
So if I were you I would use a single struct and a whole lot of if statements. (would also try to parameterize some of the items, say claws of attack +3|+6|+7, or something, I don't really know what you are doing so...)
 
Level 4
Joined
Dec 24, 2010
Messages
67
@jondrean - Thanks. I'll do that for reference.

@Aniki - I see. Almost all of the info you gave are new to me so I find your post really helpful. Never thought of using hashtable, I might actually do that if I don't go for IF statements now.
What I'm doing is exactly as my example above. Just calling onReceive() & onDrop() methods for different effects (adding ability and such) of items.
Anyway, I think that answers my question. Thanks a lot, you earn my respect.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
if statements would scale.
They would scale badly unless arranged into a binary search tree (good luck with that).

Is there a cleaner way to do this?
Use a data fed system.

In such a system there are two kinds of struct, Item and ItemType. Item struct is created for every single item and manages state for the item. ItemType struct is created for every single item type and manages data associated with the item type. Item has a many to one relationship with ItemType.

In the given example, the Boots of Speed item added some ability to the unit on pickup and removed that ability on drop. This would be implemented by creating an ItemType for boots of speed that contains an ability (or list of abilities) which are to be added or removed on pickup via methods such as "Pickup" or "Drop". Other item types which operate similar to Boots of Speed will be different instances of ItemType. For items which operate differently than Boots of Speed (eg a wisp that follows you) the ItemType class can have various flags or other features added to it to control behaviour. One may be tempted to make ItemType abstract and extend it based on different item functionality, which would also work, but this generally not recommended for maintainability, flexibility and in vJASS it might result in bloated JASS.

Here is a rough method and member template to give some ideas.

JASS:
struct ItemType
    // object editor item type
    private integer Type
    // structure that can add or remove 1 or more abilities
    private AbilityListType AddAbilites
    // other data can go here, such as flags for certain functionality or references to other objects to use if they exist

    // a constructor that can initialize the above

    public method Pickup takes unit u returns nothing
        // item ability functionality
        if AddAbilities != null then
            AddAbilities.Add(u)
        endif
        // other functionality goes here
    endmethod

    // like pickup except opposite
    public method Drop takes unit u returns nothing

    // getter for the object editor item type
    public method GetItemTypeId takes nothing returns integer
endstruct

struct Item
    // reference to interact able item this object represents
    private item ItemRef
    // the type of this item
    private ItemType Type

    // a constructor which takes ItemType and a unit which creates a real item on a unit
    // a constructor which takes ItemType and x/y coordinates which creates a real item at the point

    // methods, possibly static, which deal with the interact able items to call the ItemType Pickup and Drop methods
endstruct

function init takes nothing returns nothing
    local AbilityListType someabilities
    local ItemType itemtyperef1
    local ItemType itemtyperef2
    local item  itemref

    // initialize item type data
    set someabilities = AbilityListType.create(...)
    set itemtyperef1 = ItemType.create('wtvr', someabilities)
    set someabilities = AbilityListType.create(...)
    set itemtyperef2 = ItemType.create('lick', someabilities)

    // create some items as an example
    set itemref = Item.create(itemtyperef1, 0, 0)
    set itemref = Item.create(itemtyperef2, udg_AwesomeHero)
endfunction

The functionality is all direct so no algorithmic lookup is needed. A hasthable to map interact able item objects to Item instances may be needed.

For human friendly referencing of ItemType each instance could be assigned a unique string, and a hashtable used to map the string to instances where parent key is the string hash and child key is used to form a list in case of collisions. A sensible naming convention should be used which may or may not feature spaces depending on preference. In such a case creating an Item could look like the following...
JASS:
    ItemType.create("Boots of Speed", 'wtvr', someabilities)
    ItemType.create("Banana of Doom", 'lick', someabilities)
    ItemType.create("Smartphone of Cheating", '1337', someabilities)

    local item  itemref1 = Item.create(ItemType_GetItemTypeByName("Boots of Speed"), 0, 0)
    // a convenience constructor could be made
    local item  itemref2 = Item.create("Boots of Speed", 0, 0)
    local item  itemref3 = Item.create("Banana of Doom", 0, 0)
    local item  itemref4 = Item.create("Smartphone of Cheating", 0, 0)
 
Last edited:
Level 4
Joined
Dec 24, 2010
Messages
67
First of, thank you. I am amazed by the effort you put there.

For items which operate differently than Boots of Speed (eg a wisp that follows you) the ItemType class can have various flags or other features added to it to control behaviour.
Exactly. That's what I'm trying to do.

So.. should I save/register the data of, for example BootsOfSpeed inside the struct ItemType? I mean the trigger/function-call. I'm sorry if it sounded dumb, but I don't entirely get the example to be honest, just started vJass-ing a few days ago. Mostly the ItemType struct. What does the "AbilityListType" do? And does it mean I won't have to deal with Child/Parent thingy?

Edit:
Oh never mind. I must have misread it or sumthing. I got it working, somehow. Now I just have to familiarize myself with it. Thanks again Dr, I'll come back when there's something I wanna ask.
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
What does the "AbilityListType" do?
It is a list of abilities to add or remove when the item is picked up. How it is implemented is up to you, and could even be a single ability so not need a complex type.
And does it mean I won't have to deal with Child/Parent thingy?
Unless you go the abstract ItemType approach then every ItemType will be the same struct/class.

The point of all this is to make coding, maintenance and readability easy. Creating an item with
JASS:
Item.create("Boots of Speed", x, y)
is far easier than creating an item using the native, applying hooks to it and other stuff.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
There are ofcourse many ways to do it "correctly".
One that you almost had:
JASS:
struct Item
   
    private item ref //referenced item
    //add some other fields like current holder/current position
   
    public static method parse takes item i returns thistype
        return GetItemUserData(i)
    endmethod
   
    public static method create takes integer typeId, real x, real y returns thistype
        local thistype this = .allocate()
        set .ref = CreateItem(typeId, x, y)
        call SetItemUserData(.ref, this)
        return this
    endmethod
   
endstruct

struct SwordOfWietlol extends Item
   
    private static constant integer TYPE_ID = 'I000'
   
    public static method create takes real x, real y returns thistype
        return .allocate(.TYPE_ID, x, y)
    endmethod
   
    method onReceive takes nothing returns nothing
        call UnitAddAbility(...)
    endmethod
    method onDrop takes nothing returns nothing
        call UnitRemoveAbility(...)
    endmethod
   
endstruct

// ...
local Item i = SwordOfWietlol.create(x, y)
local SwordOfWietlol i = SwordOfWietlol.create(x, y)
The downside is that you cant really do ".getItemInSlot(x).doSomething()" because then you have a variable of type "Item" and not of the actual itemtype.
You would have to use stub methods and stuff like that to make it work, but you would also need stub methods to load stuff like TYPE_ID.
And stub methods are almost always trigger calls, so you dont want to unnecesarily use them.
And you ofcourse need a struct for each item type.


This is another way.
JASS:
struct ItemType
   
    readonly integer rawcode
    readonly string name
    readonly stuff whatever
   
    //could be public
    private static method create takes integer rawcode, string name, stuff whatever returns thistype
        local thistype this = .allocate()
        set .rawcode = rawcode
        set .name = name
        set .whatever = whatever
        return this
    endmethod
   
    readonly static thistype SWORD_OF_WIETLOL
    private static method onInit takes nothing returns nothing
        set .SWORD_OF_WIETLOL = .create('I000', "Sword Of Wietlol", whatever)
    endmethod
   
endstruct

struct Item
   
    readonly ItemType itemType
    private item ref //referenced item
    //add some other fields like current holder/current position
   
    public static method parse takes item i returns thistype
        return GetItemUserData(i)
    endmethod
   
    public static method create takes ItemType itemType, real x, real y returns thistype
        local thistype this = .allocate()
        set .ref = CreateItem(typeId, x, y)
        call SetItemUserData(.ref, this)
        set .itemType = itemType
        return this
    endmethod
   
endstruct

// ...

local Item i = Item.create(ItemType.SWORD_OF_WIETLOL, x, y)
In this one, you do have access to the actual item type constants, but you do have to initialize them.
Be aware that other struct onInits might be before the initialization of the item types.
In this one, you still need either stub methods or a massive switch case.


I ussually use both of them.
I use the "extends Item" for stub methods, and the other for item type data (which includes stats and special effect data etc).
 
Level 4
Joined
Dec 24, 2010
Messages
67
@Dr Super Good - I understand. It makes complete sense now, thanks.

@Wietlol - About the first example.
I have no way to know what type of struct to create for each item-type. Example:
JASS:
// In my map the only way to get an item is to buy from shops.
// What I'm doing is, once the player bought an item, the item
// is instantly hid/removed and be replaced by another item.
// Kinda like buying a refillable EmptyBottle and receiving
// BottleFullOfWater instead.

static method ItemSold takes nothing returns nothing
    local item t = GetSoldItem() //just imagine i removed/hid this
    local integer id = GetUnitTypeId(t)
    local Item T
 
    //the problem
    set T = IMPOSSIBLE.create(0, 0) //this
    //I was hoping to get an answer how to do like:
    //set T = Item(id).create(0, 0)  <-sumthin like this
    //just in case it's not clear, I was talking about GETTING a specific struct type
    //that extends an Item through the item-type id.
    //Well that's not exactly it, but I don't really know how else to explain it.
 
    //It's impossible so I have no choice but to use if statements again
    if id == 'I000' then
        set T = SwordOfWietlol.create(0, 0)
    endif
    // I just want to avoid doing this
    // It's unnecessary long considering I have hundreds of items
endmethod
Basically, I don't see how I can use this method if not spamming If statement, or switch. Or maybe I'm just misunderstanding it completely.

I like the second example better, but:
In this one, you still need either stub methods or a massive switch case.
I don't see why I would need them. Can you please elaborate?
And do I also make a trigger member for every ItemType? You know, to call their unique onReceive() and onDrop() method uh... I don't think I'm doing it right.
 
Last edited:
Level 13
Joined
Nov 7, 2014
Messages
571
I guess you can also do something like this and avoid having to dispatch on the object editor item id that much:

JASS:
library Item initializer init requires /*
    */ BonusMod, /* http://www.wc3c.net/showthread.php?t=107940
    */ Movespeed /* http://www.hiveworkshop.com/threads/movespeed.287539/
    */

globals
    private integer array abilities
    private integer abilities_count = 0
endglobals

private function abilities_init_4 takes integer a1, integer a2, integer a3, integer a4 returns nothing
    set abilities_count = abilities_count + 1
    set abilities[abilities_count] = a1

    set abilities_count = abilities_count + 1
    set abilities[abilities_count] = a2

    set abilities_count = abilities_count + 1
    set abilities[abilities_count] = a3

    set abilities_count = abilities_count + 1
    set abilities[abilities_count] = a4
endfunction

private function init takes nothing returns nothing
    call abilities_init_4('AHbz', 'AHwe', 'AHab', 'AHmt')
    call abilities_init_4('AHtb', 'AHtc', 'AHbh', 'AHav')
    call abilities_init_4('AHhb', 'AHds', 'AHad', 'AHre')
    call abilities_init_4('AHfs', 'AHbn', 'AHdr', 'AHpx')

    call abilities_init_4('AOwk', 'AOmi', 'AOcr', 'AOww')
    call abilities_init_4('AOcl', 'AOfs', 'AOsf', 'AOeq')
    call abilities_init_4('AOsh', 'AOws', 'AOae', 'AOre')
    call abilities_init_4('AOhw', 'AOhx', 'AOsw', 'AOvd')

    call abilities_init_4('AUdc', 'AUdp', 'AUau', 'AUan')
    call abilities_init_4('AUcs', 'AUsl', 'AUav', 'AUin')
    call abilities_init_4('AUfn', 'AUfu', 'AUdr', 'AUdd')
    call abilities_init_4('AUim', 'AUts', 'AUcb', 'AUls')

    call abilities_init_4('AEmb', 'AEim', 'AEev', 'AEme')
    call abilities_init_4('AEer', 'AEfn', 'AEah', 'AEtq')
    call abilities_init_4('AEst', 'AHfa', 'AEar', 'AEsf')
    call abilities_init_4('AEfk', 'AEbl', 'AEsh', 'AEsv')

    call abilities_init_4('ANsg', 'ANsq', 'ANsw', 'ANst')
    call abilities_init_4('ANbf', 'ANdh', 'ANdb', 'ANef')
    call abilities_init_4('ANfl', 'ANms', 'ANfa', 'ANto')
    call abilities_init_4('ANsi', 'ANdr', 'ANba', 'ANch')
    call abilities_init_4('ANrf', 'ANht', 'ANca', 'ANdo')
    call abilities_init_4('ANsy', 'ANcs', 'ANeg', 'ANrg')
    call abilities_init_4('ANhs', 'ANab', 'ANcr', 'ANtm')
    call abilities_init_4('ANso', 'ANia', 'ANlm', 'ANvc')
endfunction

struct Item
    string name

    integer ability_id = 0
    integer ability_level = 0

    integer dmg = 0
    integer arm = 0

    integer str = 0
    integer agi = 0
    integer int = 0

    real ms_perc = 0.0
    integer ms_flat = 0
    Movespeed ms
    integer as = 0

    integer life_reg = 0
    integer mana_perc_reg = 0

    static method create takes string name returns thistype
        local thistype this = allocate()
        set this.name = name
        return this
    endmethod

    method ability takes integer id, integer level returns thistype
        set this.ability_id = abilities[id]
        set this.ability_level = level
        return this
    endmethod

    method damage takes integer v returns thistype
        set this.dmg = v
        return this
    endmethod
    method armor takes integer v returns thistype
        set this.arm = v
        return this
    endmethod

    method strength takes integer v returns thistype
        set this.str = v
        return this
    endmethod
    method agility takes integer v returns thistype
        set this.agi = v
        return this
    endmethod
    method intelligence takes integer v returns thistype
        set this.int = v
        return this
    endmethod

    method movespeed takes real perc, integer flat returns thistype
        set this.ms_perc = perc
        set this.ms_flat = flat
        return this
    endmethod
    method attackspeed takes integer as returns thistype
        set this.as = as
        return this
    endmethod

    method life_regen takes integer v returns thistype
        set this.life_reg = v
        return this
    endmethod
    method mana_perc_regen takes integer v returns thistype
        set this.mana_perc_reg = v
        return this
    endmethod

    method on_pickup takes unit u returns nothing
        local boolean is_hero = IsHeroUnitId(GetUnitTypeId(u))

        if this.ability_id != 0 then
            call UnitAddAbility(u, this.ability_id)
            call SetUnitAbilityLevel(u, this.ability_id, this.ability_level)
            call UnitMakeAbilityPermanent(u, /*permanent:*/ true, this.ability_id)
        endif

        if this.dmg != 0 then
            call AddUnitBonus(u, BONUS_DAMAGE, this.dmg)
        endif
        if this.arm != 0 then
            call AddUnitBonus(u, BONUS_ARMOR, this.arm)
        endif

        if is_hero then
            if this.str != 0 then
                call AddUnitBonus(u, BONUS_STRENGTH, this.str)
            endif
            if this.agi != 0 then
                call AddUnitBonus(u, BONUS_AGILITY, this.agi)
            endif
            if this.int != 0 then
                call AddUnitBonus(u, BONUS_INTELLIGENCE, this.int)
            endif
        endif

        if this.ms_flat != 0 or this.ms_perc != 0.0 then
            set this.ms = Movespeed.create(u, this.ms_perc, this.ms_flat)
        endif
        if this.as != 0 then
            call AddUnitBonus(u, BONUS_ATTACK_SPEED, this.as)
        endif

        if this.life_reg != 0 then
            call AddUnitBonus(u, BONUS_LIFE_REGEN, this.life_reg)
        endif
        if this.mana_perc_reg != 0 then
            call AddUnitBonus(u, BONUS_MANA_REGEN_PERCENT, this.mana_perc_reg)
        endif
    endmethod

    method on_drop takes unit u returns nothing
        local boolean is_hero = IsHeroUnitId(GetUnitTypeId(u))

        if this.ability_id != 0 then
            call UnitRemoveAbility(u, this.ability_id)
        endif

        if this.dmg != 0 then
            call AddUnitBonus(u, BONUS_DAMAGE, -this.dmg)
        endif
        if this.arm != 0 then
            call AddUnitBonus(u, BONUS_ARMOR, -this.arm)
        endif

        if is_hero then
            if this.str != 0 then
                call AddUnitBonus(u, BONUS_STRENGTH, -this.str)
            endif
            if this.agi != 0 then
                call AddUnitBonus(u, BONUS_AGILITY, -this.agi)
            endif
            if this.int != 0 then
                call AddUnitBonus(u, BONUS_INTELLIGENCE, -this.int)
            endif
        endif

        if this.ms_flat != 0 or this.ms_perc != 0.0 then
            call this.ms.destroy()
        endif
        if this.as != 0 then
            call AddUnitBonus(u, BONUS_ATTACK_SPEED, -this.as)
        endif

        if this.life_reg != 0 then
            call AddUnitBonus(u, BONUS_LIFE_REGEN, -this.life_reg)
        endif
        if this.mana_perc_reg != 0 then
            call AddUnitBonus(u, BONUS_MANA_REGEN_PERCENT, -this.mana_perc_reg)
        endif
    endmethod
endstruct

endlibrary


library UseItem initializer init requires Item
globals
    Item array items
endglobals

private function items_init takes nothing returns nothing
    set items[1] = Item.create("Item 1").ability(1, 1).damage(2).armor(3)
    set items[2] = Item.create("Item 2").ability(2, 2).strength(3).agility(4).intelligence(5)
    set items[3] = Item.create("Item 3").ability(3, 3).movespeed(0.25, 0).attackspeed(20)
    set items[4] = Item.create("Item 4").ability(4, 1).life_regen(2).mana_perc_regen(50)
    set items[5] = Item.create("Item 5").ability(5, 2).damage(5).strength(10).movespeed(-0.10, 0).life_regen(4)
    set items[6] = Item.create("Item 6").ability(6, 3).life_regen(2).mana_perc_regen(50)
    // etc.
endfunction

function unit_add_item takes unit u, integer item_id, Item it returns nothing
    local item it_handle = UnitAddItemById(u, item_id)
    call SetItemUserData(it_handle, it)
    call it.on_pickup(u)
    set it_handle = null
endfunction

function on_item_pickup takes nothing returns nothing
    local Item it = GetItemUserData(GetManipulatedItem())
    if it != 0 then
        call dd("picking up Item(" + I2S(it) + ")")
        call it.on_pickup(GetTriggerUnit())
    endif
endfunction

function on_item_drop takes nothing returns nothing
    local Item it = GetItemUserData(GetManipulatedItem())
    if it != 0 then
        call dd("dropping Item(" + I2S(it) + ")")
        call it.on_drop(GetTriggerUnit())
    endif
endfunction

private function init takes nothing returns nothing
    local trigger t

    call items_init()

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
    call TriggerAddAction(t, function on_item_pickup)

    set t = CreateTrigger()
    call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DROP_ITEM)
    call TriggerAddAction(t, function on_item_drop)
endfunction

endlibrary
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
I don't see why I would need them. Can you please elaborate?
Well, you still want to have your onPickup, onDrop, onSell, onActivate, onWhatever methods so you still have to make those as abstract (stub) methods in an extendable struct.
JASS:
struct ItemType
  
    private static constant hashtable TABLE = InitHashtable()
    public static method parse takes integer rawcode returns thistype
        return LoadInteger(.TABLE, rawcode, 0)
    endmethod
    public method register takes integer rawcode returns nothing
        call SaveInteger(.TABLE, rawcode, 0, this)
    endmethod
  
    public stub method onPickup takes Item i, unit holder returns nothing
    endmethod
    public stub method onDrop takes Item i, unit holder returns nothing
    endmethod
    public stub method onActivate takes Item i, unit holder returns nothing
    endmethod
  
    readonly integer rawcode
  
    public static method create takes integer rawcode returns thistype
        local thistype this = .allocate()
        call .register(rawcode)
        set .rawcode = rawcode
        return this
    endmethod
  
    private static method onPickupHandler takes nothing returns boolean
        local Item i = Item.parse(GetManipulatedItem())
        local unit u = GetTriggerUnit()
      
        call Item.itemType.onPickup(i, u)
      
        set u = null
        return false
    endmethod
  
    private static method onDropHandler takes nothing returns boolean
        local Item i = Item.parse(GetManipulatedItem())
        local unit u = GetTriggerUnit()
      
        call Item.itemType.onDrop(i, u)
      
        set u = null
        return false
    endmethod
  
    readonly static thistype SWORD_OF_WIETLOL
    private static method onInit takes nothing returns nothing
        local trigger t
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_PICKUP_ITEM)
        call TriggerAddCondition(t, Condition(function thistype.onPickupHandler))
        set t = CreateTrigger()
        call TriggerRegisterAnyUnitEventBJ(t, EVENT_PLAYER_UNIT_DROP_ITEM)
        call TriggerAddCondition(t, Condition(function thistype.onDropHandler))
      
      
        set .SWORD_OF_WIETLOL = SwordOfWietlol.create('I000')
    endmethod
  
endstruct

struct SwordOfWietlol extends ItemType
  
    public method onPickup takes Item i, unit holder returns nothing
        // <holder> as acquired <i>, which is of type SwordOfWietlol.
    endmethod
  
endstruct

struct Item
  
    private item ref
    readonly ItemType itemType
   
    public static method create takes ItemType itemType, real x, real y returns thistype
        local thistype this = .allocate()
        set .ref = CreateItem(itemType.rawcode, x, y)
        call SetItemUserData(.ref, this)
        set .itemType = itemType
        return this
    endmethod
   
    private static method createEx takes item i returns thistype
        local thistype this = .allocate()
        set .ref = i
        call SetItemUserData(.ref, this)
        set .itemType = ItemType.parse(GetItemTypeId(.ref))
        return this
    endmethod
  
    public static method parse takes item i returns thistype
        local Item i = GetItemUserData(i)
      
        if i == 0 then
            return .createEx(i)
        endif
            return i
    endmethod
  
endstruct

The drawback of data structures like these is that they dont work cooperatively with WC3 data structures.
That is why I for example do not use WC3 standard features like selling items, training units, constructing buildings, casting spells, dealing damage, etc, etc, etc.
Everything of those have to be triggered as well to make it work smoothly.
For example this line:
local item t = GetSoldItem() //just imagine i removed/hid this
I instead have this line:
local Item i = Item.getSoldItem() //struct instance

And for lines like these:
local unit u = GetTriggerUnit()
I have:
local Unit u = Unit.getTriggerUnit()

For everything that I code via these wrapper structs, I simply tell myself to never use the handle data type any more outside of the wrapper struct.
So for "Item", I would never use the data type "item" any more.
All functions and stuff should go via the "Item" struct.
Same for Unit, Gamer (Player is reserved for function name.), Timer, Group (with some efficiency exceptions), Sound, Effect, etc.
It is sometimes hard to do, but it is worth in the end.
On top of which, the syntax is imho much better.
 
Level 4
Joined
Dec 24, 2010
Messages
67
@Aniki - That's not the point, lol. Anyway, that system is on the right track. It helped me on some ways.

@Wietlol - Wow, thanks. I understand now. I can't believe I learned so much from this thread. I was kinda already expecting vague and short answers (like I experienced from certain sites).

I have just one more question that piques my interest.
JASS:
local Item i = Item.getSoldItem() //struct instance
Same for Unit, Gamer, Timer, Group, etc. How do you apply this? I'm assuming you use handles in some way, set the members, and return the data?
I like your way, I think my map coding will be a lot more flexible and readable if I learned it. Care to share?
 
Last edited:

Dr Super Good

Spell Reviewer
Level 64
Joined
Jan 18, 2005
Messages
27,198
How do you apply this?
Create a custom event system. The Item struct gives functionality that then wraps the standard event so that as soon as a standard item is sold, it then wraps it in an Item and fires bound triggers like a normal event. The static method getSoldItem then uses global state based on the currently executing trigger and makes the API contract that it is always called before any functions that can fire events. Most of the standard ones attach some sort of thread state so even when the thread is interrupted the correct value is returned. However simulating such a system would add a lot of overhead and likely not be worth it.
 
Level 24
Joined
Aug 1, 2013
Messages
4,657
The actual overhead that you create (or at least in my case) is extremely minimal.
Only the Group wrapper (which is extremely heavy for some reason... probably wrong order of functions resulting in massive thread executions) is removed from high efficiency scripts such as missile intervals (movement of many units on a low interval).

In many cases, you dont really need to do some serious stuff to get the struct instance.
In most cases, like units and items, you can attach an integer value to each unit/item to reference the struct.
In other cases, you could just place the struct instance in a hashtable under the handle id parent key and a constant child key.
In some cases, you might have to do a security check to ensure that the handle has a struct instance containing it.
For example, the .parse() method of the Item struct in my last example creates a new instance of the struct and binds the already existing item to that instance.

In this case, doing something like "Item.getSoldItem()" would be equal to "Item.parse(GetSoldItem())".
However, I just prefer to make the code more readable so I make simple functions such as
JASS:
public static method getSoldItem takes nothing returns thistype
    return .parse(GetSoldItem())
endmethod
Yes, it is pretty much the same as all those BJ/Simple/Swapped functions from the Blizzard.j
However, it doesnt really give any noticable difference if they are not called in high frequency loops.
 
Level 4
Joined
Dec 24, 2010
Messages
67
Oh, ok. But it seems worth it learning though. I might need it someday, or even now, as I find it really tempting.
Well that's pretty much it. I'll take this myself from here, learn this and that by trial and error. Many thanks again guys.
And I think I'll stick around, this site is great!
 
Status
Not open for further replies.
Top