Jump to content
celludriel

[Release] Infinite Inventory

Recommended Posts

Goal

 

Create a script library able to give objects a new Inventory UI that can store an unlimited weight of items but still quantify them.  The current bis arsenal gives you an option to limit the choice of weapons to pick from but they are in fact unlimited to pick.  I needed something that could hold unlimited items not constrained by weight limits but still the items would be quantified.

 

Development cycle

 

Agile Kanban

This allows me to make increments to the library creating quickly a first implementation and adding to it with each iteration.  I monitor progress on a trello scrum board and it will give others the option to join in if they would love to do so.

 

https://trello.com/b/TARfxEdx/infinite-inventory

 

Current backlog

 

Make it possible to register objects to have infinite inventory and show a basic new GUI.  It must be possible to add an item to the inventory, take one or add all.  Filter placeholders should be in place but do not have to be functional yet.

 

Code repository

 

develop: https://github.com/Celludriel/InfiniteInventory/tree/develop

 

Anyone is welcome to help in the development I will consolidate my own questions and others in this thread in the hope to keep things together and make this a successful library

 

Release

 

version: 1.0.2

github: https://github.com/Celludriel/InfiniteInventory/archive/1.0.2.zip

pbo: http://users.telenet.be/sunspot/arma3/InfiniteInventory_102/InfiniteInventory.VR.pbo

  • Like 2

Share this post


Link to post
Share on other sites

I'm currently a bit stuck on opening the dialog.  It looks like I followed the right steps but I must have missed something.  I have the following dialog hpp file and description.ext.  ParentClasses166.hpp contains an export of the current parent classes of arma.

 

class INFINV_Dialog {
	idd = -1;
	onLoad = "";
	onUnload = "";
	class Controls {
		class InfiniteInventoryFrame: RscFrame {
			idc = 1800;
			x = 0 * GUI_GRID_W + GUI_GRID_X;
			y = 0 * GUI_GRID_H + GUI_GRID_Y;
			w = 40 * GUI_GRID_W;
			h = 25 * GUI_GRID_H;
		};
	};
};
enableDebugConsole = 1;

// gui
#include "gui\ParentClasses166.hpp"
#include "gui\InfiniteInventoryDialog.hpp"

 

I'm calling it like this

params ["_container"];

private ["_ok"];

diag_log format ["Opening container: %1", _container];

_ok = createDialog "INFINV_Dialog";
if (!_ok) then {hint "Dialog couldn't be opened!"};

With following output in the client side error file

 9:43:10 Starting mission:
 9:43:10  Mission file: __cur_mp (__CUR_MP)
 9:43:10  Mission world: VR
 9:43:10  Mission directory: mpmissions\__CUR_MP.VR\
 9:43:12 Attempt to override final function - bis_functions_list
 9:43:12 Attempt to override final function - bis_functions_listpreinit
 9:43:12 Attempt to override final function - bis_functions_listpostinit
 9:43:12 Attempt to override final function - bis_functions_listrecompile
 9:43:12 Attempt to override final function - bis_fnc_missiontaskslocal
 9:43:12 Attempt to override final function - bis_fnc_missionconversationslocal
 9:43:12 Attempt to override final function - bis_fnc_missionflow
 9:43:13  Mission id: 4c6ba9194dfc7eddd1930a3eb8647b770738d59e
 9:43:55 "Opening container: testInventory"
 9:43:55 Error loading control mpmissions\__CUR_MP.VR\description.ext/INFINV_Dialog/Controls/InfiniteInventoryFrame/

Is there a way to get a clearer error output then this abstract one ?  Can anyone see what is going wrong exactly ?

Share this post


Link to post
Share on other sites

Well I found the solution not sure it's the preferred one but it worked

 

class INFINV_Dialog {
  idd = 1900;
  movingEnable = false;
  enableSimulation = 1;	
  onLoad = "";
  onUnload = "";
  controlsBackground[] = { "InfiniteInventoryFrame" };
	
  class InfiniteInventoryFrame: RscFrame {
    idc = 1800;
    x = 0 * GUI_GRID_W + GUI_GRID_X;
    y = 0 * GUI_GRID_H + GUI_GRID_Y;
    w = 40 * GUI_GRID_W;
    h = 25 * GUI_GRID_H;
  }; 
};

The dialog pops up and I can get going with my next tasks

Share this post


Link to post
Share on other sites

I'm stuck on opening the inventory , I know the documentation says you can't override it but I wanted to at least give it a shot

 

if(!isServer || hasInterface) exitWith {};

params ["_container"];

diag_log format ["Registering container: %1", _container];

_container setVariable ["INFINV_CONTENTS", []];

_inventoryOverrideFunction = {
	_container = _this select 0;
    diag_log format ["Overriding container opening for: %1", _container];
    player setVariable ["InfInvActiveContainer", _container];

    _openHandlerFunction = {
        _container = player getVariable "InfInvActiveContainer";
        diag_log format ["Overriding open inventory for: %1", _container];
        _container call InfInv_fnc_openInventory;
        true
    };

    _closeHandlerFunction = {
        diag_log format ["Overriding closing inventory"];
        _openHandler = player getVariable "InfInvOpenHandler";
        _closeHandler = player getVariable "InfInvCloseHandler";
        player setVariable ["InfInvOpenHandler", nil];
        player setVariable ["InfInvCloseHandler", nil];
        player setVariable ["InfInvActiveContainer", nil];

        diag_log format ["Removing eventhandlers %1, %2", _openHandler, _closeHandler];
        player removeEventHandler("InventoryOpened", _openHandler);
        player removeEventHandler("InventoryClosed", _closeHandler);
    };

    _openHandler = player addEventHandler ["InventoryOpened", _openHandlerFunction];
    _closeHandler = player addEventHandler ["InventoryClosed", _closeHandlerFunction];

    diag_log format ["Adding eventhandlers %1, %2", _openHandler, _closeHandler];
    player setVariable ["InfInvOpenHandler", _openHandler];
    player setVariable ["InfInvCloseHandler", _closeHandler];

};

[_container, ["ContainerOpened", _inventoryOverrideFunction]] remoteExec ["addEventHandler", 0, true];

 

I tried by adding inventory handlers the second the container opens and removing them when it closes.  Doesn't seem to work though the default inventory still opens when the container inventory is selected.  Is there any way to accomplish it ?  Otherwise I need a way to disable the regular inventory option on a container with inventory and do it with an AddAction command.  

 

I'm open to any suggestion

Share this post


Link to post
Share on other sites

Variables with names beginning with an underscore are local. Local variables exist only in the scope where they are defined. More information: #Local_Variables

  • Like 1

Share this post


Link to post
Share on other sites
32 minutes ago, serena said:

Variables with names beginning with an underscore are local. Local variables exist only in the scope where they are defined. More information: #Local_Variables

 

I understand, but when I set them with 

player setVariable ["InfInvOpenHandler", _openHandler];

that shouldn't matter, then for any of the handlers.  It seems that when you open up a container "InventoryOpened" doesn't get triggered.  Maybe I need to do a waitUntil the regular dialog opens and block it.  I'm not sure, maybe it's just not possible with the current methods.  Maybe you are right and I need to make them global and eventhandlers can't be assigned local , but still seems weird.  I've been experimenting a bit in the meanwhile but without luck yet

Share this post


Link to post
Share on other sites

Problems can appear when trying to access such function remotely or asynchronously

Share this post


Link to post
Share on other sites

Well I gave it a shot without local variables

 

if(!isServer || hasInterface) exitWith {};

params ["_container"];

diag_log format ["Registering container: %1", _container];

_container setVariable ["INFINV_CONTENTS", []];

[_container, ["ContainerOpened", {
	_container = _this select 0;
    diag_log format ["Overriding container opening for: %1", _container];
    player setVariable ["InfInvActiveContainer", _container];

    _openHandler = player addEventHandler ["InventoryOpened", {
        _container = player getVariable "InfInvActiveContainer";
        diag_log format ["Overriding open inventory for: %1", _container];
        _container call InfInv_fnc_openInventory;
        true
    }];
	
    _closeHandler = player addEventHandler ["InventoryClosed", {
        diag_log format ["Overriding closing inventory"];
        _openHandler = player getVariable "InfInvOpenHandler";
        _closeHandler = player getVariable "InfInvCloseHandler";
        player setVariable ["InfInvOpenHandler", nil];
        player setVariable ["InfInvCloseHandler", nil];
        player setVariable ["InfInvActiveContainer", nil];

        diag_log format ["Removing eventhandlers %1, %2", _openHandler, _closeHandler];
        player removeEventHandler("InventoryOpened", _openHandler);
        player removeEventHandler("InventoryClosed", _closeHandler);
    }];

    diag_log format ["Adding eventhandlers %1, %2", _openHandler, _closeHandler];
    player setVariable ["InfInvOpenHandler", _openHandler];
    player setVariable ["InfInvCloseHandler", _closeHandler];

}]] remoteExec ["addEventHandler", 0, true];

Following output in the logs

Client side on open inventory

21:09:11 "Overriding container opening for: testInventory"
21:09:11 "Adding eventhandlers 0, 0"

Server side on registering the container

21:09:06 "Registering container: testInventory"
21:09:09  Mission id: b189d9c208fb04be7ded4e4b6c1512c3923ecf85

As you can see ContainerOpened does not trigger InventoryOpened as I hoped it would.  I feel like strangling BIS on making ContainerOpened not overridable WHY tell me WHY ...grrrr  I also learned I cannot hide the inventory option in the menu so if I go the addAction way I will always have Inventory as well in the action menu ... NOT a happy camper at the moment

 

I also tried the following after a post I reread of Larrow

 

if(!isServer || hasInterface) exitWith {};

params ["_container"];

diag_log format ["Registering container: %1", _container];

_container setVariable ["INFINV_CONTENTS", []];

[_container, ["ContainerOpened", {
	diag_log format ["Finding display 602"];
	
	_display = objNull;
	while { isnull _display } do {
		_display = finddisplay 602;
	};
	
	diag_log format ["Display 602 found"];
	
	closeDialog 2;
	
	diag_log format ["Closing 602"];
	
	_container call InfInv_fnc_openInventory;	

}]] remoteExec ["addEventHandler", 0, true];

Output

21:52:07  Mission id: ffc5932b2a5c8580b26c391788e40475ceac4124
21:52:09 "Finding display 602"
21:52:09 "Display 602 found"
21:52:09 "Closing 602"

I was hoping to ok open the display but then immediatly close it and popup my dialog.  Unfourtunatly this was a nogo either.  I'm not sure it's display 602 that opens on opening a container inventory ?

Share this post


Link to post
Share on other sites

BINGO got it

 

Thanks @Larrow your previous post gave me some insight

if(!isServer || hasInterface) exitWith {};

params ["_container"];

diag_log format ["Registering container: %1", _container];

_container setVariable ["INFINV_CONTENTS", []];

[_container, ["ContainerOpened", {
	_container = _this select 0;
	[_container] spawn {
		disableSerialization;
		
		diag_log format ["Finding display 602"];
		waitUntil { !isNull ( findDisplay 602 ) };
		_display = findDisplay 602;
		
		diag_log format ["Display 602 found"];
		
		closeDialog 2;
		
		diag_log format ["Closing 602"];
		
		_container = _this select 0;
		_container call InfInv_fnc_openInventory;			
	};
}]] remoteExec ["addEventHandler", 0, true];

This made it happen

Share this post


Link to post
Share on other sites

Well I'm stuck again on something I never should be stuck at ! SQF can be so frustrating

 

I'm trying to call the server to get some data back.  Here is my calling script and executing script

 

params ["_display"];

private ["_container", "_contents"];

_container = INFINV_CURRENT_CONTAINER;

diag_log format ["Calling InfInv_fnc_getContainerContents with %1", _container];

_contents = objNull;

_contents = [_container] remoteExecCall ["InfInv_fnc_getContainerContents", 2];

waitUntil { !(isNull _contents) };

diag_log format ["InfInv_fnc_getContainerContents contents %1", _contents];

if(count _contents > 0) then {
    _lb = _display displayCtrl 1500;
    [_lb, _contents] call InfInv_fnc_loadListBoxFromData;
};
if(!isServer || hasInterface) exitWith {};

params ["_container"];

diag_log format ["Getting data from %1", _container];

_retValue = _container getVariable ["INFINV_CONTENTS", []];

diag_log format ["Data %1", _retValue];

_retValue

Now here is my server output and client output

 

Server

18:42:04 "Getting data from testInventory"
18:42:04 "Data [[""FirstAidKit"",10]]"

Client

18:42:04 "Calling InfInv_fnc_getContainerContents with testInventory"
18:42:04 "InfInv_fnc_getContainerContents contents "

As you can see on server side I get the right data but when it is send back it arrives as empty string ... how come ?  I know it can take a while for the command to execute on server, but that is why I put in a waitUntil ?  Is there another way I should approach this ?  remoteExecCall should be the right method isn't it ????

 

I don't get it :(

Share this post


Link to post
Share on other sites

I decided to solve it another way, not my preferred but it works.  I call the server with remoteexec, and the server calls the client with remoteexec.  it works ... 

 

//Calling client script

params ["_display"];

private ["_container", "_contents"];

_container = INFINV_CURRENT_CONTAINER;

diag_log format ["Calling InfInv_fnc_getContainerContents with %1", _container];

[_container, clientOwner, _display] remoteExecCall ["InfInv_fnc_getContainerContents", 2];

//Responding server script

if(!isServer || hasInterface) exitWith {};

params ["_container", "_clientID"];

diag_log format ["Getting data from %1", _container];

_retValue = _container getVariable ["INFINV_CONTENTS", []];

diag_log format ["Data %1", _retValue];

[_retValue] remoteExecCall ["InfInv_fnc_loadInventoryContainer", _clientID];

//Executing client script

params ["_contents"];

private ["_display"];

diag_log format ["InfInv_fnc_getContainerContents contents %1", _contents];

_display = findDisplay 1900;
if(count _contents > 0) then {
    _lb = _display displayCtrl 1500;
    [_lb, _contents] call InfInv_fnc_loadListBoxFromData;
};

 

Share this post


Link to post
Share on other sites
1 hour ago, celludriel said:

I decided to solve it another way, not my preferred but it works.  I call the server with remoteexec, and the server calls the client with remoteexec.  it works ... 

 

It's the way I would do it. Another means would be publicVariable I suppose. Arma does not come with a decent "hey do this and get back to me" command out of the box (nor those it really need to).

The reason you were running into the issue can be found on the BIKI for remoteExecCall:
 

Quote

Return Value: Anything - Nil in case of error. String otherwise. If JIP is not requested this is an empty string, if JIP is requested, it is the JIP ID. See the topic Function for more information.

 

The return value is only relevant for JIP execution / handling. It's not the return value of the code you execute. That return value is simply discarded.

  • Like 2

Share this post


Link to post
Share on other sites

 

<< nm small brainfart >>

 

I was using the same log message on two places seemed like something crazy was going on

Share this post


Link to post
Share on other sites

There is one more script I need to write and to be honest I'm dreading it at the moment.  I'll get it done but just thinking on what all can go wrong ... makes me squirm

 

params ["_item"];

//check if is primary weapon
    //if primary weapon and has attachments put attachments in inventory if they can't fit on the ground
    //remove primary weapon
//check if is secondary weapon
    //if secondary weapon and has attachements put attachments in inventory if they can't fit on the ground
    //remove secondary weapon
//check if is handgun
    //if handgun and has attachements put attachements in inventory if they can't fit on the ground
    //remove handgun
//check if is weapon in inventory
    //if weapon and has attachments put attachments in inventory if they can't fit on the ground
    //remove weapon from inventory
//check if is attachment
    //check if attachment on primary weapon
        //remove attachment from primary weapon
    //check if attachment on secondary weapon
        //remove attachment from secondary weapon
    //check if attachment on handgun
        //remove attachment from handgun
    //check if attachment on any of the carried weapons in inventory
        //remove from carried weapon in inventory
    //attachment is only in inventory, remove it
//regular item delete from inventory

I want to create a function that you send an item string to any item string, and then deletes that item from the players inventory.  The item can be an assigned weapon, an attachment on an assigned weapon, maybe a weapon in the inventory or an attachment in the inventory.  It can be anywhere in the inventory, there could be multiple but I don't care which one get deleted as long as one gets deleted.

 

This one is going to give me headaches ... I just know it

Share this post


Link to post
Share on other sites

Well I got through it , but look at all this code I had to write.  JUST to remove an item somewhere in the players inventory ...  So unreal that a method like this isn't made by BI themselfes.  I'm sure I missed some things and there could still be a bug I haven't encountered.  How hard is it to look at the inventory of a player as a mini database.  What do you need then, items with ID's ! and methods like updateItemWithID and deleteItemWithID ...  but nooooooo a half a dozen methods that all do different things then you expect ... SOOOOOO FRUSTRATING

 

params ["_item"];
diag_log format ["Removing _item: %1", _item];

private ["_deleteDone"];

_deleteDone = false;

_detachWeaponItem = {
    params ["_weaponItem"];
    if(typeName _weaponItem == "ARRAY") then {
        _weaponItem = _weaponItem select 0;
    };

    if(_weaponItem != "") then {
        if(player canAdd _weaponItem) then {
            player addItem _weaponItem;
        } else {
            _ground = "GroundWeaponHolder" createVehicle (position player);
            _ground addItemCargo [_weaponItem, 1];
        };
    };
};

_hasAttachment = {
    params ["_haystack", "_needle"];
    _result = false;
    {
        if(typeName _x == "ARRAY") then {
            if(count _x > 0) then {
                _x = _x select 0;
            } else {
                _x = "";
            };
        };

        if(_needle == _x) exitWith {
            _result = true;
        };
    } forEach _haystack;
    _result
};

//check if is primary weapon
if( _item == (primaryWeapon player) call Bis_fnc_BaseWeapon) exitWith {
    //if primary weapon and has attachments put attachments in inventory if they can't fit on the ground
    _weaponItems = primaryWeaponItems player;
    removeAllPrimaryWeaponItems player;
    {
        [_x] call _detachWeaponItem;
    } forEach _weaponItems;
    //remove primary weapon
    player removeWeapon (primaryWeapon player);
};

//check if is secondary weapon
if( _item == (secondaryWeapon player) call Bis_fnc_BaseWeapon) exitWith {
    //if secondary weapon and has attachements put attachments in inventory if they can't fit on the ground
    _weaponItems = secondaryWeaponItems player;
    {
        player removeSecondaryWeaponItem _x;
    } forEach _weaponItems;

    {
        [_x] call _detachWeaponItem;
    } forEach _weaponItems;
    //remove secondary weapon
    player removeWeapon (secondaryWeapon player);
};

//check if is handgun
if( _item == (handgunWeapon player) call Bis_fnc_BaseWeapon) exitWith {
    //if handgun and has attachements put attachements in inventory if they can't fit on the ground
    _weaponItems = handgunItems player;
    removeAllHandgunItems player;
    {
        [_x] call _detachWeaponItem;
    } forEach _weaponItems;
    //remove handgun
    player removeWeapon (handgunWeapon player);
};

//remove weapon from uniform if present, put attachments in inventory if they can't put on the ground
_uniform = uniformContainer player;
if(!(isNull _uniform)) then {
    _weaponItems = weaponsItems _uniform;
    {
        if(_item == (_x select 0)) exitWith {
            {
                if(_forEachIndex == 0) then {
                    player removeItemFromUniform _x;
                } else {
                    [_x] call _detachWeaponItem;
                };
            } forEach _x;
            _deleteDone = true;
        };
    } forEach _weaponItems;
};

if(_deleteDone) exitWith { true };

//remove weapon from vest if present, put attachments in inventory if they can't put on the ground
_vest = vestContainer player;
if(!(isNull _vest)) then {
    _weaponItems = weaponsItems _vest;
    {
        if(_item == (_x select 0)) exitWith {
            {
                if(_forEachIndex == 0) then {
                    player removeItemFromVest _x;
                } else {
                    [_x] call _detachWeaponItem;
                };
            } forEach _x;
            _deleteDone = true;
        };
    } forEach _weaponItems;
};

if(_deleteDone) exitWith { true };

//remove weapon from backpack if present, put attachments in inventory if they can't put on the ground
_backpack = backpackContainer player;
if(!(isNull _backpack)) then {
    _weaponItems = weaponsItems _backpack;
    {
        if(_item == (_x select 0)) exitWith {
            {
                if(_forEachIndex == 0) then {
                    player removeItemFromBackpack _x;
                } else {
                    [_x] call _detachWeaponItem;
                };
            } forEach _x;
            _deleteDone = true;
        };
    } forEach _weaponItems;
};

if(_deleteDone) exitWith { true };

//check for attachment
//check if attachment on primary weapon
_weaponItems = primaryWeaponItems player + primaryWeaponMagazine player;
if(_weaponItems find _item >= 0) exitWith {
    //remove attachment from primary weapon
    player removePrimaryWeaponItem _item;
};

//check if attachment on secondary weapon
_weaponItems = secondaryWeaponItems player + secondaryWeaponMagazine player;
if(_weaponItems find _item >= 0) exitWith {
    //remove attachment from secondary weapon
    player removeSecondaryWeaponItem _item;
};

//check if attachment on handgun
_weaponItems = handgunItems player + handgunMagazine player;
if(_weaponItems find _item >= 0) exitWith {
    //remove attachment from handgun
    player removeHandgunItem _item;
};

//check if attachment on any of the carried weapons in inventory
_weaponItems = weaponsItems _vest;
_weaponItems = _weaponItems + (weaponsItems _backpack);
_weaponItems = _weaponItems + (weaponsItems _uniform);
{
    diag_log format ["weaponitems %1", _x];
    if([_x, _item] call _hasAttachment) then {
        diag_log format ["Attachment present %1", _x];
        //delete the weapon
        player removeItem (_x select 0);
        {
            //only way is to fake disassembling the weapon and adding each part again except the one we don't want
            if(typeName _x == "ARRAY") then {
                _x = _x select 0;
            };

            if(_x != _item && _x != "") then {
                player addItem _x;
            };
        } forEach _x;
        _deleteDone = true;
    };
} forEach _weaponItems;

if(_deleteDone) exitWith { true };

//item is only in inventory, remove it
//regular item delete from inventory
diag_log format ["Just removing %1", _item];
player removeItem _item;
true

 

Share this post


Link to post
Share on other sites

Version 1.0.0 is released.  I just tested it by myself so I'm not sure if there are multiplayer bugs with regards to concurrency, I sure hope not.

 

Tell me what you think and where I could improve a little.  A little code review would be appreciated as well.  I hope you can use this in any missions you will make.

Share this post


Link to post
Share on other sites

There is always one that skips through if you do your own Q&A :(

 

There was a bug when you took an item from the inventory that  controls stayed locked.  This only occured when ran on a dedicated server or you where not the hosting server.  It was an easy fix and version 1.0.1 is released.  Sorry for the inconvenience.

Share this post


Link to post
Share on other sites

After a play session in my mission where I incorporated Infinite Inventory I stumbled on another bug.  If you had weapons of a complex variety in your backpack and you added them to the inventory they didn't get properly removed from the player inventory.  This caused item duplication, I made release 1.0.2 fixing this bug.  You find the new links in my updated post above

Share this post


Link to post
Share on other sites
On 11-3-2017 at 9:56 AM, celludriel said:

Snip

As someone who has 0 knowledge of scripts.. how do I install this?

Share this post


Link to post
Share on other sites

Please sign in to comment

You will be able to leave a comment after signing in



Sign In Now

×