[WIP] FSInv

the Immortal

I know, I know...
Reaction score
51
A quick explanation: An early alpha of a full-screen inventory that will eventually support number of modules (socketing, randomstats, vendors, etc.), and has easy-to-use interface for the average JASS-er (sad, can't make such a thing in GUI, ynow...)

Some details: I felt we were missing a thing like that, as, afaik, there exist only two systems: Toadcop's (which uses gc and with its 13k lines of code is really.. hard to read / understand and kinda too complicated for the avg user) and the original FSGUI (which is outdated, uses gc again, and has many disadvantages, as well). Plus none of these uses OOP/vJASS syntax, which makes things much easier to implement n use.. so a month or two ago I began making (really casually) that system.

Once again, it's an early version, releasing it.. well.. just so you know a thing like that is being made, idk ^^.
A brief TODO is included in the testmap. No sfx-es, no vendor items or such shit - just two heroes that are given two items which they can equip (but not drop for ex) freely.

And yeh, everything is actually written in Zinc, except the config parts. Here's the code so far:
JASS:
 //! zinc
library FSInv requires SimError, Hexxer
{
/* -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- *
 * --------------------------- Full Screen Inventory v0.A   -------------------------- *
 * --------------------------------------------------------------------- by Ixtreon -- *
 * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- *
 *                                                                                     *
 *   Effectively replaces the Warcraft III item system, taking use of an extended,     *
 *   full screen inventory system, using vJASS' structs to organize everything in a    *
 *   user-friendly (although still requiring vJASS knowledge) interface. Features:     *
 *    - Backpack system allowing heroes to carry unused items. Size limited only by    *
 *      the screen size (can make it over 100-150 items total).                        *
 *    - Dynamic equip and item system, allowing limiting different items to specific   *
 *      slots and changing all data for items and equipment slots during runtime       *
 *    - Different modules attachable to it:  /TODO/                                    *
 *          Vendor - Full Screen preview and selling/buying of items                   *
 *          Socket - Allows socketed items and jewel-type items (runes, gems, w/e)     *
 *          RandomStats - Different items have different 'bonus' stats (D2-like)       *
 *                                                                                     *
 *   Well, it has its (minor) cons aswell so lemme list them, too:                     *
 *    - Every item must be created in the Trigger Editor*. Item Editor has almost no   *
 *      usage here**.                                                                  *
 *    - Having to add a doodad for every different item icon.                          *
 *    - Requires a 1x1 square on your map with flat terrain and no objects on it.      *
 *      (OMG GAMEBREAKING!! END OF THE WORLD <img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" class="smilie smilie--sprite smilie--sprite12" alt="o_O" title="Er... what?    o_O" loading="lazy" data-shortname="o_O" />)                                      *
 *    - Restriction to up to 40 equipment slot types and 200 items per one ***.        *
 *    - Restriction for units using inventory: data can be saved only while            *
 *      [units on the map using FSInv] * [total backpack slots] &lt; ~8100 ***            *
 *    - Slight delay due to using trackables (unfixable; hardcoded wc3 engine).        *
 *    - Non-pleasing effect if the user begins to frantically scroll with his mouse D: *
 *        *   - API and technical shiet in the trigger named &#039;API&#039; (logic, eh?)        *
 *        **  - Read the instructions for more info                                    *
 *        *** - Give me a VERY good reason and I might make a hashtable version, which *
 *              although very slightly slower, will have no limits.                    *
 *              Though these numbers should be damn enough for every map out here...   *
 *                                                                                     *
 *   I&#039;d say nothing -that- hard or groundbreaking... =P                               *
 *   Example usage in the triggers in the &#039;Examples&#039; category.                         *
 *   A step-by-step guide describing how to setup the system in the trigger comment    *
 *   Configuration part follows                                                        *
 *                                                                                     *
 *   For bug reports, feedback and all that shiet:                                     *
 *       write in the topic @ TheHelper.net                                            *
 *                                                                                     *
 *   Njoy!                                                            Ixtreon          *
 *                                                            (a.k.a. the Immortal)    *
 * -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- */
 
 
 // --------------------- \\
 // --- CONFIGURATION --- \\
 // --------------------- \\
    
        //The inventory ability ID
        constant integer AbilityID = &#039;A000&#039;;
        
        //The coordinates of the bottom-left corner of the mini-rectangle used for the inventory.
        //As mentioned, it must be flat ground with no objects on it (no units/doodads/items etc.!)
        //like in the middle of an unreachable forest, or maybe atop some hills *shrugs*
        //(required rect with size ~45x85 &#039;range&#039;)
        constant real MapPosition_X = 3000;
        constant real MapPosition_Y = 420;
        
        //The size of the backpack module
        constant integer Backpack_Rows = 6;
        constant integer Backpack_Columns = 6;
        //WARNING: Until new version of vJASS is released, fixing a damn bug
        //you MUST scroll down and manually edit the value of BACKPACK_SLOTS_TOTAL
        //a sample of the bug is in the trigger BugExample :x
        
        //The position of the backpack module; range [0;1], where:
        //(0;0) is the bottom-left corner of the FSInv screen
        //(1;1) is the top-right corner of the same FSInv screen
        constant real Backpack_Pos_X = 0.70;
        constant real Backpack_Pos_Y = 0.15;
        
        //TODO: Implement it
        //Same but for the usable items hotbar
        constant real Consumables_Pos_X = 0.50;
        constant real Consumables_Pos_Y = 0.95;
        
        //Same but for the tooltip window
        constant real Tooltip_Pos_X = 0.69;
        constant real Tooltip_Pos_Y = 0.67;

        //The number of equip slots your map will use
        //Please remember that:
        //Total units having inventory * max(&lt;number of equipment slots&gt;, &lt;number of backpack slots&gt;) &lt; ~8190
        //(if that seems small to you, once again, convince me, and I might make a hashtable version w/o restrictions)
        constant integer EQUIPMENT_SLOTS_TOTAL = 5;
        
        //Path for the Bazez.mdl file
        constant string TRACKABLE_PATH = &quot;war3mapImported\\Bazez.mdl&quot;;
        
        //ID of the &#039;no item&#039; destructable
        constant integer ITEM_NONE_DESTRID = &#039;BI01&#039;;
        
        //Whether to pause unit while browsing its inventory /recommended/
        constant boolean PauseUnits = true;
    
 // ---------------------------------------- \\
 // ---- Do not modify below this point ---- \\
 // -- ...unless you know what ye&#039;r doing -- \\
 // ---------------------------------------- \\
    
        timer CameraTimer = CreateTimer();
        hashtable ht = InitHashtable();
        destructable dBackground;

        //player properties
        fogmodifier  PVar_FogModifier[12];
        rect         PVar_CamBounds[12];
        unit         PVar_CurrentUnit[12];
        boolean      PVar_InvOpen[12];
        real         PVar_CamX[12];
        real         PVar_CamY[12];
        boolean      PVar_InitObj[12];


        constant real INV_WIDTH = 85;
        constant real INV_HEIGHT = 45;
        constant real INV_CENTER_X = MapPosition_X + INV_WIDTH / 2;
        constant real INV_CENTER_Y = MapPosition_Y + INV_HEIGHT / 2;
        constant integer BACKPACK_SLOTS_TOTAL = 36;//Backpack_Rows * Backpack_Columns

        //constants for hashtable keys.. no, I won&#039;t use vJASS keys, I prefer writing &#039;em on my own
        constant integer    HTABLE_EQUIPSLOTS = 0,
                            HTABLE_ITEMS = 1,
                            HTABLE_UNITINV = 2,
                            HTABLE_SLOTOBJS = 3;
    
    //equipment slots
    public struct EquipSlot
    {
        public
        {
            static integer count = 0; 
        
            integer thiscount = 0;
            Item thisArray[200];   //number of items per type. I assume no more than 200 item types for -each slot-, with 40 max slots
        
            string Description;
            real X;
            real Y;
            integer index;
        }
        private string Namez;
        
        public static method Create(string pName, string pDescription, real pX, real pY)
        {
            count += 1;
            thistype(count).Description = pDescription;
            thistype(count).Namez = pName;
            thistype(count).X = pX;
            thistype(count).Y = pY;
            thistype(count).index = count;
            SaveInteger(ht, HTABLE_EQUIPSLOTS, StringHash(pName), count);
        }
        
        method operator Name() -&gt; string
        {
            return Namez;
        }
        
        method operator Name=(string s)
        {
            SaveInteger(ht, HTABLE_EQUIPSLOTS, StringHash(s), index);
            Namez = s;
        }
        
        //(ArrayGet + HashtableGet) is faster than (NUMSLOTS * ArrayGet)
        static method operator[](string s) -&gt; thistype
        {
            return thistype(LoadInteger(ht, HTABLE_EQUIPSLOTS, StringHash(s)));
        }
        
        //ex: Item.Random(EquipSlot[&quot;head&quot;])
        method Random (EquipSlot es) -&gt; Item
        {
            return thisArray[GetRandomInt(0, thiscount)];
        }
    }
    
    public struct Item
    {
        static integer count = 0;
        
        string Description;
        string Color;

        EquipSlot Slot;
        integer index,
                DestructableId,
                LumberCost,
                GoldCost;


        integer Life = 0,
                Mana = 0,
                Strength = 0,
                Agility = 0,
                Intelligence = 0,
                Damage = 0,
                Armor = 0,
                AtkSpeed = 0;
        
        integer PassiveAbilities[10];
        
        //TODO: Implement &#039;em
        //integer itemlevel
        //integer Charges
        //boolean MustBeEquipped  //...to give bonuses
        //boolean CannotBePawned
        //boolean CannotBeDropped
        
        private string Namez;
        
        method operator Name() -&gt; string
        {
            return Namez;
        }
        
        method operator Name= (string s)
        {
            //RemoveSavedInteger(ht, HTABLE_ITEMS, StringHash(.Namez));     // - computation time, plus ability to access item via its old name isnt a minus
            Namez = s;
            SaveInteger(ht, HTABLE_ITEMS, StringHash(s), index);
        }
        
        static method Create(string pName, string pDescription, EquipSlot pSlot, integer pDestructableId)
        {
            count += 1;
            thistype(count).Namez = pName;
            thistype(count).Description = pDescription;
            thistype(count).DestructableId = pDestructableId;

            thistype(count).Slot = pSlot;
            thistype(count).index = count;
            
            thistype(count).Color = &quot;ffffff&quot;;
            SaveInteger(ht, HTABLE_ITEMS, StringHash(pName), count);
            pSlot.thisArray[pSlot.thiscount] = thistype(count);
            pSlot.thiscount = pSlot.thiscount + 1;
        }
        
        static method CreateEx(string pName, string pDescription, EquipSlot pSlot, integer pDestructableId, integer pGoldCost, integer pLumberCost, integer pHP, integer pMana, integer pStr, integer pAgi, integer pInt, integer pAS, integer pDmg, integer pArmor)
        {
            count += 1;
            thistype(count).Namez = pName;
            thistype(count).Description = pDescription;
            thistype(count).DestructableId = pDestructableId;
            thistype(count).GoldCost = pGoldCost;
            thistype(count).LumberCost = pLumberCost;
            
            thistype(count).Life = pHP;
            thistype(count).Mana = pMana;
            thistype(count).Strength = pStr;
            thistype(count).Agility = pAgi;
            thistype(count).Intelligence = pInt;
            thistype(count).Damage = pDmg;
            thistype(count).Armor = pArmor;
            thistype(count).AtkSpeed = pAS;
            
            thistype(count).Color = &quot;ffffff&quot;;
            thistype(count).Slot = pSlot;
            thistype(count).index = count;
            SaveInteger(ht, HTABLE_ITEMS, StringHash(pName), count);
            pSlot.thisArray[pSlot.thiscount] = thistype(count);
            pSlot.thiscount += 1;
        }
        
        //(ArrayGet + HashtableGet) is faster than (NUMSLOTS * ArrayGet)
        //(main reason for overloading &#039;Name&#039; set/get)
        //ex: Item[&quot;Shiny Breastplate&quot;]
        static method operator[] (string s) -&gt; thistype
        {
            return thistype(LoadInteger(ht, HTABLE_ITEMS, StringHash(s)));
        }
        
        method Bonuses (unit u, boolean add)
        {
            integer i;
            if (add)
                i = 1;
            else
                i = -1;
            
            Bonus_Life<u> = Bonus_Life<u> + i * Life / Bonus_Life.DELTA;
            Bonus_Mana<u> = Bonus_Mana<u> + i * Mana / Bonus_Mana.DELTA;
            Bonus_Armor<u> = Bonus_Armor<u> + i * Armor / Bonus_Armor.DELTA;
            Bonus_Damage<u> = Bonus_Damage<u> + i * Damage / Bonus_Damage.DELTA;
            Bonus_Str<u> = Bonus_Str<u> + i * Strength / Bonus_Str.DELTA;
            Bonus_Agi<u> = Bonus_Agi<u> + i * Agility / Bonus_Agi.DELTA;
            Bonus_Int<u> = Bonus_Int<u> + i * Intelligence / Bonus_Int.DELTA;
            Bonus_AttackSpeed<u> = Bonus_AttackSpeed<u> + i * AtkSpeed / Bonus_AttackSpeed.DELTA;
        }
    }
    
    struct TooltipWindow    //wanna-be class
    {
        static destructable GoldIcon;
        static texttag GoldText = CreateTextTag();
        static destructable LumberIcon;
        static texttag LumberText = CreateTextTag();
        static texttag HeaderText = CreateTextTag();
        static texttag BodyText = CreateTextTag();
        
        static constant real TOOLTIP_WIDTH = 0.25;
        static constant real TOOLTIP_HEIGHT = 0.20;
        static integer arr[];
        
        static method GetX(real i) -&gt; real
        {
            return INV_CENTER_X + INV_WIDTH * (Tooltip_Pos_X - 0.5) + INV_WIDTH * i * TOOLTIP_WIDTH;
        }
        
        static method GetY(real i) -&gt; real
        {
            return INV_CENTER_Y + INV_HEIGHT * (Tooltip_Pos_Y - 0.5) + INV_HEIGHT * i * TOOLTIP_HEIGHT;
        }
        
        static method SetText(ItemSlotObj obj) //string header, string body, string color, integer gold, integer lumber, integer p)
        {
            if (GetPlayerId(GetLocalPlayer()) == obj.Owner)
            {
                if(obj.CurItem == 0)
                {
                    SetTextTagColor(HeaderText, 127, 127, 127, 255);
                    if(obj.eq)
                    {
                        SetTextTagColor(BodyText, 127, 127, 127, 255);
                
                        SetTextTagText(HeaderText, EquipSlotObj(obj).EQSlot.Name, 0.022);
                        SetTextTagText(BodyText, EquipSlotObj(obj).EQSlot.Description, 0.022);
                    }
                    else
                    {
                        SetTextTagText(HeaderText, &quot;No item&quot;, 0.022);
                        SetTextTagText(BodyText, &quot;&quot;, 0.022);
                    }
                }
                else
                {
                    SetTextTagColor(HeaderText, HexTo255(SubString(obj.CurItem.Color, 0, 2)), HexTo255(SubString(obj.CurItem.Color, 2, 4)), HexTo255(SubString(obj.CurItem.Color, 4, 6)), 255);
                    SetTextTagColor(BodyText, 255, 255, 255, 255);
                    
                    SetTextTagText(HeaderText, obj.CurItem.Name, 0.022);
                    SetTextTagText(BodyText, obj.CurItem.Description, 0.022);
                }
                
                if (obj.CurItem.GoldCost == 0)
                {
                    ShowDestructable(GoldIcon, false);
                    SetTextTagVisibility(GoldText, false);
                }
                else
                {
                    ShowDestructable(GoldIcon, true);
                    SetTextTagVisibility(GoldText, true);
                    SetTextTagText(GoldText, I2S(obj.CurItem.GoldCost), 0.022);
                }
                
                if (obj.CurItem.LumberCost == 0)
                {
                    ShowDestructable(LumberIcon, false);
                    SetTextTagVisibility(LumberText, false);
                }
                else
                {
                    ShowDestructable(LumberIcon, true);
                    SetTextTagVisibility(LumberText, true);
                    SetTextTagText(LumberText, I2S(obj.CurItem.LumberCost), 0.022);
                }
            }
        }
        
        static method HideText(integer p)
        {
            if (GetLocalPlayer() == Player(p))
            {
                SetTextTagText(HeaderText, &quot;&quot;, 0.022);
                SetTextTagText(BodyText, &quot;&quot;, 0.022);
                SetTextTagText(LumberText, &quot;&quot;, 0.022);
                SetTextTagText(GoldText, &quot;&quot;, 0.022);
                ShowDestructable(GoldIcon, false);
                ShowDestructable(LumberIcon, false);
            }
        }
        
        static method Initialize()
        {
            SetTextTagPos(HeaderText, GetX(0), GetY(0.9), 0);
            SetTextTagPos(BodyText, GetX(0), GetY(0), 0);
            SetTextTagPos(GoldText, GetX(0.45), GetY(0.7), 0);
            SetTextTagPos(LumberText, GetX(0.8), GetY(0.7), 0);
            GoldIcon = CreateDestructable(&#039;B001&#039;, GetX(0.35), GetY(0.72), 0, 1, 0);
            LumberIcon = CreateDestructable(&#039;B002&#039;, GetX(0.7), GetY(0.72), 0, 1, 0);
            ShowDestructable(GoldIcon, false);
            ShowDestructable(LumberIcon, false);
        }
    }
    
    struct ItemSlotObj
    {
        static thistype Picked[12];
        
        trackable tkMain;
        destructable dMain;
        Item CurItem;
        boolean eq = false;
        integer Owner;
        trigger OnPointTrigger = CreateTrigger();
        trigger OnClickTrigger = CreateTrigger();
        //TODO: implement
        //destructable dBorder
        
        method operator Item= (Item it)
        {
            real x, y;            
            if (eq)
            {
                x = EquipSlotObj.GetX(EquipSlotObj(this).EQSlot);
                y = EquipSlotObj.GetY(EquipSlotObj(this).EQSlot);
            }
            else
            {
                x = BackpackSlotObj.GetX(BackpackSlotObj(this).id);
                y = BackpackSlotObj.GetY(BackpackSlotObj(this).id);
            }
            
            if (CurItem != it || (CurItem == it &amp;&amp; it == 0))
            {
                CurItem = it;
                if (dMain != null)
                    RemoveDestructable(dMain);
                if (it != 0)
                    dMain = CreateDestructable(it.DestructableId, x, y, 0, 1, 0);
                else
                    dMain = CreateDestructable(ITEM_NONE_DESTRID, x, y, 0, 1, 0);
                if (GetLocalPlayer() != Player(Owner))
                    ShowDestructable(dMain, false);
            }
        }
        
        method operator Item() -&gt; Item
        {
            return CurItem;
        }
        
        static method OnPoint() -&gt; boolean
        {
            thistype obj = LoadInteger(ht, HTABLE_SLOTOBJS, GetHandleId(GetTriggeringTrigger()));
            if (obj.CurItem == 0)
                if (obj.eq)
                    TooltipWindow.SetText(obj);
                else
                    TooltipWindow.SetText(obj);
            else
            {
                TooltipWindow.SetText(obj);
            }
            return false;
        }

        static method OnClick() -&gt; boolean
        {
            integer t;
            thistype obj = LoadInteger(ht, HTABLE_SLOTOBJS, GetHandleId(GetTriggeringTrigger()));
            debug ClearTextMessages();
            if (Picked[obj.Owner] == 0)
            {
                if (obj.CurItem != 0)
                {
                    debug BJDebugMsg(&quot;selected item &quot; + obj.CurItem.Name);
                    Picked[obj.Owner] = obj;
                    //add sfx
                }
            }
            else if (Picked[obj.Owner] == obj)
            {
                debug BJDebugMsg(&quot;unselected item&quot;);
                Picked[obj.Owner] = 0;
                //remove sfx
            }
            else if (Picked[obj.Owner].eq)
            {
                if (obj.eq)
                    if (EquipSlotObj(obj).EQSlot.Name == EquipSlotObj(Picked[obj.Owner]).EQSlot.Name)
                    {
                        TooltipWindow.SetText(Picked[obj.Owner]);
                        t = obj.CurItem;
                        obj.Item = Picked[obj.Owner].CurItem;
                        Picked[obj.Owner].Item = t;
                        debug BJDebugMsg(&quot;swapped / moved eq to eq &quot; + Picked[obj.Owner].CurItem.Name);
                        Picked[obj.Owner] = 0;
                    }
                    else
                        SimError(Player(obj.Owner), &quot;Invalid slot&quot;);
                else if (obj.CurItem == 0)
                {
                    TooltipWindow.SetText(Picked[obj.Owner]);
                    obj.Item = Picked[obj.Owner].CurItem;
                    Picked[obj.Owner].CurItem.Bonuses(PVar_CurrentUnit[obj.Owner], false);
                    Picked[obj.Owner].Item = 0;
                    debug BJDebugMsg(&quot;unequipped&quot;);
                    Picked[obj.Owner] = 0;
                    //remove sfx
                }
                else if (obj.CurItem.Slot.Name == Picked[obj.Owner].CurItem.Slot.Name)
                {
                    TooltipWindow.SetText(Picked[obj.Owner]);
                    Picked[obj.Owner].CurItem.Bonuses(PVar_CurrentUnit[obj.Owner], false);
                    obj.CurItem.Bonuses(PVar_CurrentUnit[obj.Owner], true);
                    t = obj.CurItem;
                    obj.Item = Picked[obj.Owner].CurItem;
                    Picked[obj.Owner].Item = t;
                    debug BJDebugMsg(&quot;swapped&quot;);
                    Picked[obj.Owner] = 0;
                    //remove sfx
                }
                else
                    SimError(Player(obj.Owner), &quot;The target item cannot go in that slot&quot;);
            }
            else if (obj.eq)
            {
                if (EquipSlotObj(obj).EQSlot.Name == Picked[obj.Owner].CurItem.Slot.Name)
                {
                    TooltipWindow.SetText(Picked[obj.Owner]);
                    Picked[obj.Owner].CurItem.Bonuses(PVar_CurrentUnit[obj.Owner], true);
                    obj.CurItem.Bonuses(PVar_CurrentUnit[obj.Owner], false);
                    t = obj.CurItem;
                    obj.Item = Picked[obj.Owner].CurItem;
                    Picked[obj.Owner].Item = t;
                    debug BJDebugMsg(&quot;swapped / moved&quot;);
                    Picked[obj.Owner] = 0;
                    //remove sfx
                }
                else
                    SimError(Player(obj.Owner), &quot;Invalid slot&quot;);
            }
            else
            {
                TooltipWindow.SetText(Picked[obj.Owner]);
                t = obj.CurItem;
                obj.Item = Picked[obj.Owner].CurItem;
                Picked[obj.Owner].Item = t;
                debug BJDebugMsg(&quot;swapped / moved backpack to backpack &quot; + Picked[obj.Owner].CurItem.Name);
                Picked[obj.Owner] = 0;
                //remove sfx
            }
            return false;
        }
    }
    
    //equip slots
    struct EquipSlotObj extends ItemSlotObj
    {
        public static thistype Array[EQUIPMENT_SLOTS_TOTAL][12];
        
        public EquipSlot EQSlot;
        
        static method GetX(EquipSlot eq) -&gt; real
        {
            return INV_CENTER_X + INV_WIDTH * (eq.X - 0.5);
        }
        
        static method GetY(EquipSlot eq) -&gt; real
        {
            return INV_CENTER_Y + INV_HEIGHT * (eq.Y - 0.5);
        }
        
        static method create (EquipSlot es, integer pid) -&gt; thistype
        {
            thistype t = thistype.allocate();
            t.EQSlot = es;
            t.Owner = pid;
            t.tkMain = CreateTrackable(TRACKABLE_PATH, GetX(es), GetY(es), 0);
            t.eq = true;
            
            TriggerRegisterTrackableTrackEvent(t.OnPointTrigger, t.tkMain);
            TriggerAddCondition(t.OnPointTrigger, Condition(function ItemSlotObj.OnPoint));
            SaveInteger(ht, HTABLE_SLOTOBJS, GetHandleId(t.OnPointTrigger), t);
            
            TriggerRegisterTrackableHitEvent(t.OnClickTrigger, t.tkMain);
            TriggerAddCondition(t.OnClickTrigger, Condition(function ItemSlotObj.OnClick));
            SaveInteger(ht, HTABLE_SLOTOBJS, GetHandleId(t.OnClickTrigger), t);
            return t;
        }
        
        static method InitForPlayer(integer pid)
        {
            integer i = 0;
            for(0 &lt;= i &lt; EQUIPMENT_SLOTS_TOTAL)
                thistype.Array<i>[pid] = thistype.create(EquipSlot(i+1), pid);
        }
    }
    
    //backpack slots 
    struct BackpackSlotObj extends ItemSlotObj
    {
        static thistype Array[BACKPACK_SLOTS_TOTAL][12];
        
        integer id;
        
        static method GetX(integer id) -&gt; real
        {
            return INV_CENTER_X + INV_WIDTH * (Backpack_Pos_X - 0.5) + 3.75 * (id - (id / Backpack_Columns) * Backpack_Columns);
        }
        
        static method GetY(integer id) -&gt; real
        {
            return INV_CENTER_Y + INV_HEIGHT * (Backpack_Pos_Y - 0.5) + 3.75 * (id / Backpack_Columns);
        }
        
        static method create (integer i, integer pid) -&gt; thistype
        {
            thistype t = thistype.allocate();
            t.id = i;
            t.Owner = pid;
            t.tkMain = CreateTrackable(TRACKABLE_PATH, thistype.GetX(i), thistype.GetY(i), 0);
            TriggerRegisterTrackableTrackEvent(t.OnPointTrigger, t.tkMain);
            TriggerAddCondition(t.OnPointTrigger, Condition(function ItemSlotObj.OnPoint));
            SaveInteger(ht, HTABLE_SLOTOBJS, GetHandleId(t.OnPointTrigger), t);
            
            TriggerRegisterTrackableHitEvent(t.OnClickTrigger, t.tkMain);
            TriggerAddCondition(t.OnClickTrigger, Condition(function ItemSlotObj.OnClick));
            SaveInteger(ht, HTABLE_SLOTOBJS, GetHandleId(t.OnClickTrigger), t);
            return t;
        }
        
        static method InitForPlayer (integer pid)
        {
            integer i = 0;
            for(0 &lt;= i &lt; BACKPACK_SLOTS_TOTAL)
                thistype.Array<i>[pid] = thistype.create(i, pid);
        }
    }
    
    public struct Inventory
    {
        //struct responsible for saving every unit&#039;s inventory
        //and all inventory-related functions
        public
        {
            Item EquipSlots[EQUIPMENT_SLOTS_TOTAL];
            Item BackpackSlots[BACKPACK_SLOTS_TOTAL];
        }
        unit Unit;
        
        //u:Inventory
        static method operator[] (unit u) -&gt; thistype
        {
            thistype ret = LoadInteger(ht, HTABLE_UNITINV, GetHandleId(u));
            if (ret.Unit != u)   //unit has died or unit doesn&#039;t have inv
            {
                if (ret != 0)    //old unit with that id has died
                    ret.destroy(); //no fucking idea if that&#039;s how it should be done -.-
                ret = thistype.allocate();
                ret.Unit = u;
                SaveInteger(ht, HTABLE_UNITINV, GetHandleId(u), ret);
                debug BJDebugMsg(&quot;DEBUG: adding 2 sample items to inv[&quot; + I2S(ret) + &quot;]. To edit - Line ~660 in WE&quot;);
                debug ret.BackpackSlots[0] = Item[&quot;Shiny Breastplate&quot;];
                debug ret.BackpackSlots[1] = Item[&quot;Tarnished Breastplate&quot;];
            }
            return ret;
        }
        //u:Inventory.GetBackpackItem(0, 0).Name
        method GetBackpackItem(integer row, integer column) -&gt; Item
        {
            return BackpackSlots[Backpack_Rows * column + row];
        }
        
        //u:Inventory.GetEquippedItem(EquipSlot[&quot;head&quot;]).Name
        method GetEquippedItem(EquipSlot eq) -&gt; Item
        {
            return EquipSlots[eq - 1];
        }
        
        //u:Inventory.HasItem(Item[&quot;Breastplate&quot;], true, true)
        method HasItem (Item it, boolean inv, boolean eq) -&gt; boolean
        {
            integer i;
            if (inv)
                for(0 &lt;= i &lt; BACKPACK_SLOTS_TOTAL)
                    if (BackpackSlots<i> == it)
                        return true;
            if (eq &amp;&amp; EquipSlots[it.Slot] == it)
                return true;
            return false;
        }
        
        //u:Inventory.RemoveItem(Item[&quot;Breastplate&quot;], true)
        method DeleteItem (Item it, boolean all)
        {
            integer i;
            if(EquipSlots[it.Slot - 1] == it)
            {
                EquipSlots[it.Slot - 1] = 0;
                if (!all) return;
            }
            else
                for(0 &lt;= i &lt; BACKPACK_SLOTS_TOTAL)
                    if(BackpackSlots<i> == it)
                    {
                        BackpackSlots<i> = 0;
                        if (!all) return;
                    }
        }
        
        //u:Inventory.AddItem(Item[&quot;Breastplate&quot;], true)
        method AddItem(Item it, boolean drop)
        {
            integer i;
            if(EquipSlots[it.Slot - 1] == 0)
            {
                EquipSlots[it.Slot - 1] = it;
            }
            else
            {
                for(0 &lt;= i &lt; BACKPACK_SLOTS_TOTAL)
                    if(BackpackSlots<i> == 0)
                    {
                        BackpackSlots<i> = it;
                        return;
                    }
                //create item on the ground
            }
        }
    }
    
    //END structs
    
    function OnLeaveInventory()
    {
        player p = GetTriggerPlayer();
        integer pn = GetPlayerId(p), i;
        Inventory inv;
        
        if (GetLocalPlayer() == p)
        {
            ShowDestructable(dBackground, false);
            SetCameraBoundsToRect(PVar_CamBounds[pn]);   //cba inlining it!...
            ResetToGameCamera(0);
            SetCameraPosition(PVar_CamX[pn], PVar_CamY[pn]);
        }
        DestroyFogModifier(PVar_FogModifier[pn]);
        PVar_InvOpen[pn] = false;
        PauseUnit(PVar_CurrentUnit[pn], false);
        TooltipWindow.HideText(pn);
        ItemSlotObj.Picked[pn] = 0;
        //save manipulated unit&#039;s items
        inv = PVar_CurrentUnit[pn]:Inventory;
        
        for(0 &lt;= i &lt; EQUIPMENT_SLOTS_TOTAL)
            inv.EquipSlots<i> = EquipSlotObj.Array<i>[pn].Item;
        
        for(0 &lt;= i &lt; BACKPACK_SLOTS_TOTAL)
            inv.BackpackSlots<i> = BackpackSlotObj.Array<i>[pn].Item;
        
        p = null;
    }
    
    function OnEnterInventory()
    {
        player p = GetOwningPlayer(GetTriggerUnit());
        integer pn = GetPlayerId(p), i;
        real x, y;
        Inventory inv;
        PVar_CurrentUnit[pn] = GetTriggerUnit();
        //stop pause unit
        IssueImmediateOrder(PVar_CurrentUnit[pn], &quot;stop&quot;);
        PauseUnit(PVar_CurrentUnit[pn], PauseUnits);
        //create visibility modifier
        PVar_FogModifier[pn] = CreateFogModifierRect(p, FOG_OF_WAR_VISIBLE, Rect(MapPosition_X, MapPosition_Y, MapPosition_X + INV_WIDTH, MapPosition_Y + INV_HEIGHT), false, false);
        FogModifierStart(PVar_FogModifier[pn]);
        //set vars
        PVar_InvOpen[pn] = true;
        PVar_CamX[pn] = GetCameraTargetPositionX();
        PVar_CamY[pn] = GetCameraTargetPositionY();
        PVar_CamBounds[pn] = Rect(GetCameraBoundMinX(), GetCameraBoundMinY(), GetCameraBoundMaxX(), GetCameraBoundMaxY());
        //set camera / background
        if (GetLocalPlayer() == p)
        {
            SetCameraPosition(INV_CENTER_X,INV_CENTER_Y);
            SetCameraBounds(INV_CENTER_X,INV_CENTER_Y,INV_CENTER_X,INV_CENTER_Y,INV_CENTER_X,INV_CENTER_Y,INV_CENTER_X,INV_CENTER_Y);
            ShowDestructable(dBackground, true);
        }
        //init destructablez / trackablez 1st time
        if (!PVar_InitObj[pn])
        {
            BackpackSlotObj.InitForPlayer(pn);
            EquipSlotObj.InitForPlayer(pn);
            PVar_InitObj[pn] = true;
        }
        //load items from uinv struct
        inv = PVar_CurrentUnit[pn]:Inventory;
        for(0 &lt;= i &lt; EQUIPMENT_SLOTS_TOTAL)
            EquipSlotObj.Array<i>[pn].Item = inv.EquipSlots<i>;
        
        for(0 &lt;= i &lt; BACKPACK_SLOTS_TOTAL)
            BackpackSlotObj.Array<i>[pn].Item = inv.BackpackSlots<i>;

        p = null;
    }
    
    function CameraTimed()
    {
        if (PVar_InvOpen[GetPlayerId(GetLocalPlayer())])
        {
            //if (GetCameraField(CAMERA_FIELD_TARGET_DISTANCE) != 135)  //
            //{
                SetCameraField(CAMERA_FIELD_ANGLE_OF_ATTACK, 270, 0);
                SetCameraField(CAMERA_FIELD_ROTATION, 90, 0);
                SetCameraField(CAMERA_FIELD_FARZ, 5000, 0);
                SetCameraField(CAMERA_FIELD_TARGET_DISTANCE, 135, 0);
                SetCameraField(CAMERA_FIELD_FIELD_OF_VIEW, 44, 0);
            //}
        }
    }
    
    function onInit()
    {
        integer i = 0, k = 0;
        trigger t = CreateTrigger(), t2 = CreateTrigger();
        TimerStart(CameraTimer, 0.025, true, function CameraTimed);
        dBackground = CreateDestructable(&#039;BX00&#039;, INV_CENTER_X, INV_CENTER_Y, 270., 1., 0);
        ShowDestructable(dBackground, false);
        for(0 &lt;= i &lt; 12)
        {
            TriggerRegisterPlayerEvent(t, Player(i), EVENT_PLAYER_END_CINEMATIC);
            TriggerRegisterPlayerUnitEvent(t2, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null);
        }
        SetTerrainPathable(INV_CENTER_X, INV_CENTER_Y, PATHING_TYPE_ANY, false);
        TriggerAddCondition(t2, Condition(
                                            function() -&gt; boolean 
                                            { 
                                                if(GetSpellAbilityId() == AbilityID)
                                                    OnEnterInventory();
                                                return false;
                                            }
                                          ));
        TriggerAddCondition(t, Condition(
                                            function() -&gt; boolean 
                                            { 
                                                if(PVar_InvOpen[GetPlayerId(GetTriggerPlayer())])
                                                    OnLeaveInventory();
                                                return false;
                                            }
                                         ));        
        TooltipWindow.Initialize();
        
        t = null;
        t2 = null;
    }
}
//! endzinc</i></i></i></i></i></i></i></i></i></i></i></i></i></i></i></u></u></u></u></u></u></u></u></u></u></u></u></u></u></u></u>
(~850 lines so far, be warned)

And here's the sample FSInvObjects (the interface for equipment slots/items):
JASS:
library FSInvObjects initializer Init_Vars requires FSInv
    public function Init_Vars takes nothing returns nothing
        // DESCRIPTION: Initializes a slot for equipping items with given parameters
        // SYNTAX:      EquipSlot.Create(SLOT_NAME, SLOT_DESCRIPTION, SCREEN_X, SCREEN_Y)
        //              SREEN_X and SCREEN_Y follow the same rules as backpack positioning, ranging [0;1]
        // WARNING:     You should only create and modify slots in this trigger. 
        //              EquipSlots with same names are treated as same slots
        // EXAMPLES:
        call EquipSlot.Create(&quot;Head&quot;, &quot;Headpieces from steel, leather or cloth are often made to protect the upper part of one&#039;s body.. the head&quot;, 0.3, 0.8)
        call EquipSlot.Create(&quot;Chest&quot;, &quot;Armor pieces made from steel, metal rings, leather, or even simple clothing are worn on the chest&quot;, 0.3, 0.7)
        call EquipSlot.Create(&quot;Chest&quot;, &quot;Armor pieces made from steel, metal rings, leather, or even simple clothing are worn on the chest&quot;, 0.4, 0.7)
        //    having 2 chest slots just cuz Im too lazy to make two rings or something like that to test double slots. 
        //    yes, it doesn&#039;t make any sense =D
        call EquipSlot.Create(&quot;Waist&quot;, &quot;Different pants - different belts!&quot;, 0.3, 0.6)
        call EquipSlot.Create(&quot;Legs&quot;, &quot;Panties - so others don&#039;t get frightened by your butt!&quot;, 0.3, 0.5)
        call EquipSlot.Create(&quot;Feet&quot;, &quot;You don&#039;t want to walk over these dead monsters without shoes, do you?&quot;, 0.3, 0.4)
        
        //DESCRIPTION: Creates an item with given name, description, equipment slot, gold/lumber cost, doodad file, stats modifiers
        //SYNTAX:      Item.Create(NAME, DESCRIPTION, SLOT, GOLD_COST, LUMBER_COST, DOODAD_FILE, HEALTH_MOD, MANA_MOD, STR_MOD, AGI_MOD, INT_MOD, ATK_SPEED_MOD, DAMAGE_MOD, ARMOR_MOD)
        //EXTRA:       In DESCRIPTION or NAME                              %n%,   %s%,    %g%,      %l%,      %hp%,     %mp%,     %str%,   %agi%,   %int%,      %as%,        %dmg%       %arm%
        //             are substituted with the values of correspondingly  NAME, SLOT, GOLD_COST, LUMBER_COST, HEALTH_MOD, MANA_MOD, STR_MOD, AGI_MOD, INT_MOD, ATK_SPEED_MOD, DAMAGE_MOD, ARMOR_MOD 
        call Item.CreateEx(&quot;Shiny Breastplate&quot;, &quot;A brightly glowing, lightweight piece of armor\n+10 Armor\n+5 Strength&quot;, EquipSlot[&quot;Chest&quot;], &#039;BI07&#039;, 2500, 1000, 500, 0, 5, 0, 0, 0, 0, 10)
        call Item.CreateEx(&quot;Tarnished Breastplate&quot;, &quot;A piece of armor seen many battles\n+5 Armor\n+5 Strength&quot;, EquipSlot[&quot;Chest&quot;], &#039;BI08&#039;, 1500, 500, 0, 0, 5, 0, 0, 0, 0, 5)
        
        //DESCRIPTION: Adds/Removes a passive ability from an item eventually disabling the abil for the player
        //             (wrapper for &lt;item&gt;.PassiveAbilities[&lt;num&gt;] = &lt;abil&gt;)
        //SYNTAX:      Item.AddPassive(ABIL_ID, DISABLE_FLAG)
        //SYNTAX:      Item.RemovePassive(ABIL_ID)
        //WARNING:     The ability will be visible unless it&#039;s based off spellbook, contains the wanted passive and is disabled. 
        //EXAMPLES:
        
    endfunction
endlibrary


Some other examples for the user syntax:
JASS:

.
    local Inventory inv = u:Inventory    //a pointer to unit&#039;s inventory
    if u2:Inventory.HasItem(Item[&quot;Shiny Breastplate&quot;]) then    //if u2 has item &quot;Shiny Breastplate&quot; in inventory...
        call inv.AddItem(Item[&quot;Shiny Breastplate&quot;], true)    //create that item in u&#039;s inventory and drop it on the ground if its inventory is full
    endif
    call inv.GetBackpackItem(0, 0).Name)    //returns the name of the item at backpack slot (0,0)
    inv.DeleteItem(Item[&quot;Shiny Breastplate&quot;], true)    //removes all instances of the &quot;Shiny Breastplate&quot; item from u&#039;s inventory. 
    //... and so on

Pfft, at last, would love comments and opinions. Would also like to hear any bugs / optimizations you find, although the map is still untested in multiplayer (it WILL be compatible, just atm it might not be :Р) and though I see some places where code can be significantly optimized / improved.

And, finally, the poor testmap (yes, the miniature inventory can be seen once you access it, but it's supposed to be put on a looonely place!!):
 

Attachments

  • FS_Inv.w3x
    64.4 KB · Views: 249

Viikuna

No Marlo no game.
Reaction score
265
I find FSGUIs method nicer, even though it requires some camera system to work properly.

Why?

Because this stuff is kinda only useful in RPG maps, most of which have somekind of camera system already.


But yea, this is pretty cool too.
 
Reaction score
341
I find FSGUIs method nicer, even though it requires some camera system to work properly.

Doesn't require a cam sys AFAIK.

The bag system from wc3c does though (if that's what you were thinking of).

Anyways, the interface is decent, I suppose your going to add borders and things to the inventory.

Also, none of the items showed up.
 

Tom_Kazansky

--- wraith it ! ---
Reaction score
157
the origninal FSGUI is really awesome (at that time) and it's not too hard to learn, however, now I prefer Toadcop's :D.

Toadcop's system takes less space then the original FSGUI.
But avg users can use resources from Toadcop's system to minimize the "restriction areas" :) (maybe they aren't needed - no restriction areas)

You should use Multiboard to display the tooltips of the items instead of floating text because there are items with long tooltip so the tooltip could screw the interface up.
 

the Immortal

I know, I know...
Reaction score
51
First thanks for the replies, and sorry for being away fo' that much.. simply wasn't able to.

And second, the idea behind this is to be a fusion between FSInv and Toadcop's system (name btw?) which is modular, easily modifiable, fast, and as lightweight as possible. As you see it is pretty far from this at its current state. But for now I'd simply want to see if peepl are interested and maybe even feedback on coding (if someone has the time to look at it) as I (eventually) upgrade it /since I have free time so rarely these days/

Atm working on ground items, eventually switching item display to multiboard and cleaning up interface.
Also having probs with trackables not firing events ~_~ but it b fine I say!

bumpity!
 
General chit-chat
Help Users
  • No one is chatting at the moment.
  • Ghan Ghan:
    Still lurking
    +3
  • The Helper The Helper:
    I am great and it is fantastic to see you my friend!
    +1
  • The Helper The Helper:
    If you are new to the site please check out the Recipe and Food Forum https://www.thehelper.net/forums/recipes-and-food.220/
  • Monovertex Monovertex:
    How come you're so into recipes lately? Never saw this much interest in this topic in the old days of TH.net
  • Monovertex Monovertex:
    Hmm, how do I change my signature?
  • tom_mai78101 tom_mai78101:
    Signatures can be edit in your account profile. As for the old stuffs, I'm thinking it's because Blizzard is now under Microsoft, and because of Microsoft Xbox going the way it is, it's dreadful.
  • The Helper The Helper:
    I am not big on the recipes I am just promoting them - I use the site as a practice place promoting stuff
    +2
  • Monovertex Monovertex:
    @tom_mai78101 I must be blind. If I go on my profile I don't see any area to edit the signature; If I go to account details (settings) I don't see any signature area either.
  • The Helper The Helper:
    You can get there if you click the bell icon (alerts) and choose preferences from the bottom, signature will be in the menu on the left there https://www.thehelper.net/account/preferences
  • The Helper The Helper:
    I think I need to split the Sci/Tech news forum into 2 one for Science and one for Tech but I am hating all the moving of posts I would have to do
  • The Helper The Helper:
    What is up Old Mountain Shadow?
  • The Helper The Helper:
    Happy Thursday!
    +1
  • Varine Varine:
    Crazy how much 3d printing has come in the last few years. Sad that it's not as easily modifiable though
  • Varine Varine:
    I bought an Ender 3 during the pandemic and tinkered with it all the time. Just bought a Sovol, not as easy. I'm trying to make it use a different nozzle because I have a fuck ton of Volcanos, and they use what is basically a modified volcano that is just a smidge longer, and almost every part on this thing needs to be redone to make it work
  • Varine Varine:
    Luckily I have a 3d printer for that, I guess. But it's ridiculous. The regular volcanos are 21mm, these Sovol versions are about 23.5mm
  • Varine Varine:
    So, 2.5mm longer. But the thing that measures the bed is about 1.5mm above the nozzle, so if I swap it with a volcano then I'm 1mm behind it. So cool, new bracket to swap that, but THEN the fan shroud to direct air at the part is ALSO going to be .5mm to low, and so I need to redo that, but by doing that it is a little bit off where it should be blowing and it's throwing it at the heating block instead of the part, and fuck man
  • Varine Varine:
    I didn't realize they designed this entire thing to NOT be modded. I would have just got a fucking Bambu if I knew that, the whole point was I could fuck with this. And no one else makes shit for Sovol so I have to go through them, and they have... interesting pricing models. So I have a new extruder altogether that I'm taking apart and going to just design a whole new one to use my nozzles. Dumb design.
  • Varine Varine:
    Can't just buy a new heatblock, you need to get a whole hotend - so block, heater cartridge, thermistor, heatbreak, and nozzle. And they put this fucking paste in there so I can't take the thermistor or cartridge out with any ease, that's 30 dollars. Or you can get the whole extrudor with the direct driver AND that heatblock for like 50, but you still can't get any of it to come apart
  • Varine Varine:
    Partsbuilt has individual parts I found but they're expensive. I think I can get bits swapped around and make this work with generic shit though
  • Ghan Ghan:
    Heard Houston got hit pretty bad by storms last night. Hope all is well with TH.
  • The Helper The Helper:
    Power back on finally - all is good here no damage
    +2
  • V-SNES V-SNES:
    Happy Friday!
    +1
  • The Helper The Helper:
    New recipe is another summer dessert Berry and Peach Cheesecake - https://www.thehelper.net/threads/recipe-berry-and-peach-cheesecake.194169/

      The Helper Discord

      Members online

      No members online now.

      Affiliates

      Hive Workshop NUON Dome World Editor Tutorials

      Network Sponsors

      Apex Steel Pipe - Buys and sells Steel Pipe.
      Top