Page 1 of 1

[1.10] shift clicking items

Posted: Sun Feb 01, 2015 2:32 pm
by weapon-x
hi all, so i was playing mods again, median xvc, started anew...

one of the things that i am having a hard time doing, is storing items,
runes, etc. on my stash... so i thought... "if only i can shift+click these items pretty much like how we add items to the belt, then i will not have a hard time"

:mrgreen: code hunt time

in [v1.10] we have known functions:

Code: Select all

D2Common.#10720 *(D2UnitStrc* pItem, int nPage)    \\Set item page
the above function works, but it doesn't care if your stash is empty or not,
if there is no enough room in your stash, the item vanishes into thin air...

is there any known function that checks if there is still room for the item inside the a specific page?
spent few hours doing some trial and errors but i cant seem to find this elusive stuff... :-|

Re: [1.10] shift clicking items

Posted: Sun Feb 01, 2015 3:01 pm
by kingpin
You can check my function I did for DA.

Code: Select all

void DASpawnItem(D2Game* ptGame, Unit* ptUnit, DWORD itemCode, DWORD ilvl, DWORD quality, DWORD page, BYTE CubeClientMsg, BYTE unidItem, BOOL nBeltInv/* = FALSE*/, BOOL notSellable /* = FALSE*/, BOOL nPlayerName /* = FALSE*/)
{
	int dwDummy = 0;
	DWORD unitLevel = 0;
	Unit* ptItem = NULL;
	DWORD maxDurability = 0;
	BOOL nInvItem = 0;
	int Offsets[2];
	DRLGRoom* ptRoom = NULL;

	if (ptGame == NULL){ // Check if ptGame == NULL
		log_message("DASpawnItem Func: ptGame == %d", ptGame);
		return;
	}

	if (ptUnit == NULL){ // Check if ptUnit == NULL
		log_message("DASpawnItem Func: ptUnit == %d", ptUnit);
		return;
	}

	//ItemMod ModData;
	

	D2ItemsTXT* ptItemsTXT = D2GetItemTXTFromCode(itemCode, &dwDummy);

	if (ptItemsTXT != NULL)
	{
		unitLevel = D2GetUnitLevel(ptUnit, 0);

		if (ilvl == 0) ilvl = unitLevel;

		ptItem = D2CreateItem_Wrapper(ptUnit, D2GetItemIDfromCode(itemCode), ptGame, 4, quality, 0, 1, ilvl, 0, 0, 0);
		
		if (ptItem == NULL){ // Check if ptItem == NULL
			log_message("DASpawnItem Func: ptItem == %d", ptItem);
			return;
		}

		quality = D2GetQuality(ptItem);

		if ((quality == ITEMQUALITY_SET || quality == ITEMQUALITY_UNIQUE) && unidItem == 1) D2SetFlags(ptItem, ITEMFLAG_UNIDENTIFIED, 0);

		if (CubeClientMsg == 1)
		{
			switch(quality)
			{
				case ITEMQUALITY_CRACKED: D2ClientMsgTop(D2GetStringFromIndex(53694), D2_Orange);
				break;
				case ITEMQUALITY_NORMAL: D2ClientMsgTop(D2GetStringFromIndex(53695), D2_Orange);
				break; 
				case ITEMQUALITY_MONSTER: D2ClientMsgTop(D2GetStringFromIndex(53696), D2_Orange);
				break;
				case ITEMQUALITY_MAGIC: D2ClientMsgTop(D2GetStringFromIndex(53697), D2_Orange);
				break;
				case ITEMQUALITY_SET: D2ClientMsgTop(D2GetStringFromIndex(53693), D2_Orange);
				break;
				case ITEMQUALITY_RARE: D2ClientMsgTop(D2GetStringFromIndex(53698), D2_Orange);
				break;
				case ITEMQUALITY_UNIQUE: D2ClientMsgTop(D2GetStringFromIndex(53692), D2_Orange);	
				break;
			}

		}

		maxDurability = D2GetMaxDurability(ptItem);
			
		if (maxDurability > 0) D2SetUnitStat(ptItem, STATS_DURABILITY, maxDurability, 0);


		if (notSellable == TRUE){
			D2SetFlags(ptItem,ITEMFLAG_NONSELLABLE,1); // 4096 set non sellable flag
			D2SetFlags(ptItem,ITEMFLAG_FROMPLAYER,1); // 16777216
		}

		int i = 0;

		// add player name

		if (nPlayerName == TRUE)
		{
			D2SetPlayerIDonItem(ptItem,ptUnit->nUnitUnid);
			D2PlayerData* playerdata = D2GetPlayerData(ptUnit);
		
			if (playerdata)
			{
				for (i=0; i<=15; i++)
					ptItem->pItemData->IName[i] = playerdata->CharName[i];
			}
		}


		BOOL beltable = D2Beltable(ptItem);
		BOOL check = FALSE;
		WORD OffsetY = 0;
		WORD OffsetX = 0;

		if (nBeltInv == TRUE && beltable != FALSE)
		{
			check = D2GameAddItemtoBelt(ptUnit->ptGame, ptUnit, ptItem->nUnitUnid, ptUnit->hPath->targetx, 1, &dwDummy);

			if (check == FALSE){
				D2SetPage(ptItem, PAGE_INVENTORY);
				OffsetY = D2GetOffsetYFromUnit(ptItem);
				OffsetX = D2GetOffsetXFromUnit(ptItem);

				nInvItem = D2PlaceItem(__FILE__, __LINE__, ptUnit->ptGame, ptUnit, ptItem->nUnitUnid, OffsetX, OffsetY, 1, 1, 0);

				if (nInvItem == 0)
				{
					D2GetMapOffsetsFromUnit(ptUnit, Offsets);
					ptRoom = D2GetRoomFromUnit(ptUnit);

					D2ServerDropUnitOnGround(ptGame, 0, ptItem, ptRoom, Offsets[0], Offsets[1]);
				}
			}
		}else{
			D2SetPage(ptItem, page);
			
			nInvItem = D2PlaceItem(__FILE__, __LINE__, ptGame, ptUnit, ptItem->nUnitUnid, 0, 0, 1, 1, 0);

			if (nInvItem == 0)
			{
				D2GetMapOffsetsFromUnit(ptUnit, Offsets);
				ptRoom = D2GetRoomFromUnit(ptUnit);

				D2ServerDropUnitOnGround(ptGame, 0, ptItem, ptRoom, Offsets[0], Offsets[1]);
			}
		}
		
	}
}

the most important part of code is here:

Code: Select all

			nInvItem = D2PlaceItem(__FILE__, __LINE__, ptGame, ptUnit, ptItem->nUnitUnid, 0, 0, 1, 1, 0);

			if (nInvItem == 0)
			{
				D2GetMapOffsetsFromUnit(ptUnit, Offsets);
				ptRoom = D2GetRoomFromUnit(ptUnit);

				D2ServerDropUnitOnGround(ptGame, 0, ptItem, ptRoom, Offsets[0], Offsets[1]);
			}
If it fails to place the item it will drop it on ground instead.

Re: [1.10] shift clicking items

Posted: Mon Feb 02, 2015 12:20 pm
by weapon-x
sir, aren't functions supposed to have return values?

Code: Select all

int Offsets[2];
D2GetMapOffsetsFromUnit(ptUnit, Offsets);

or should it be

Code: Select all

int Offsets[2] = D2GetMapOffsetsFromUnit(ptUnit, Offsets);
havent tried it yet, but i do tried using

D2PlaceItem(__FILE__, __LINE__, ptGame, ptUnit, ptItem->nUnitUnid, 0, 0, 1, 1, 0)

and ended-up crashing with c000005 error

Re: [1.10] shift clicking items

Posted: Mon Feb 02, 2015 12:57 pm
by Necrolis
weapon-x" wrote:sir, aren't functions supposed to have return values?

Code: Select all

int Offsets[2];
D2GetMapOffsetsFromUnit(ptUnit, Offsets);

or should it be

Code: Select all

int Offsets[2] = D2GetMapOffsetsFromUnit(ptUnit, Offsets);
It has return values, "Offests" passes the pointer to that start of the array, the function uses the pointer to fill the array. The second form isn't valid in C or C++.
That function has the definition

Code: Select all

void __stdcall D2GetUnitOffsets(D2UnitStrc* pUnit, int* pOffsets);
weapon-x" wrote: havent tried it yet, but i do tried using

D2PlaceItem(__FILE__, __LINE__, ptGame, ptUnit, ptItem->nUnitUnid, 0, 0, 1, 1, 0)

and ended-up crashing with c000005 error
There is a lot of setup require before you can actually put an item into an inventory; you need to explicitly tell the game where to place the item (both the x/y position, the node page and the storage page).

Re: [1.10] shift clicking items

Posted: Tue Feb 03, 2015 3:11 am
by weapon-x
Necrolis" wrote:
weapon-x" wrote:sir, aren't functions supposed to have return values?

Code: Select all

int Offsets[2];
D2GetMapOffsetsFromUnit(ptUnit, Offsets);

or should it be

Code: Select all

int Offsets[2] = D2GetMapOffsetsFromUnit(ptUnit, Offsets);
It has return values, "Offests" passes the pointer to that start of the array, the function uses the pointer to fill the array. The second form isn't valid in C or C++.
That function has the definition

Code: Select all

void __stdcall D2GetUnitOffsets(D2UnitStrc* pUnit, int* pOffsets);
weapon-x" wrote: havent tried it yet, but i do tried using

D2PlaceItem(__FILE__, __LINE__, ptGame, ptUnit, ptItem->nUnitUnid, 0, 0, 1, 1, 0)

and ended-up crashing with c000005 error
There is a lot of setup require before you can actually put an item into an inventory; you need to explicitly tell the game where to place the item (both the x/y position, the node page and the storage page).
ow, perhaps i should try again, maybe i missed something :)

will post an update if successful

edit:
[small findings]
D2DropItemInGround is kinda buggy... lets say i have an item in my inventory and used a targeting misc item (new pspell) that uses the d2dropiteminground, the item is dropped, but the cell used by the item inside the player's inventory becomes occupied, empty but unuseable... when you pickup the item again, the unusable cells become useable again... :mrgreen:

perhaps i missed a client update function or something like that...

Re: [1.10] shift clicking items

Posted: Tue Feb 03, 2015 10:48 am
by Necrolis
weapon-x" wrote:edit:
[small findings]
D2DropItemInGround is kinda buggy... lets say i have an item in my inventory and used a targeting misc item (new pspell) that uses the d2dropiteminground, the item is dropped, but the cell used by the item inside the player's inventory becomes occupied, empty but unuseable... when you pickup the item again, the unusable cells become useable again... :mrgreen:

perhaps i missed a client update function or something like that...
You have to ensure the item is set to the correct mode:

Code: Select all

			if(!D2PlaceItem(__FILE__,__LINE__,pGame,pPlayer,pItem->dwGUID,0,0,1,1,0))
			{
				D2CoordStrc pSource, pDest;
				UNITS_SetMode(pItem,ITEMMODE_ON_GROUND);
				UNITS_GetPosition(pPlayer,&pSource);
				DRLGRoom* pRoom = ITEMS_FindSpawnPosition(DUNGEON_GetRoom(pPlayer),&pSource,&pDest,1);
				ITEMS_ServerDrop(pGame,NULL,pItem,pRoom,pDest.nXpos,pDest.nYpos);
			}
Removing an item also requires a call to update the inv and the unit:

Code: Select all

ITEMS_RemovalUpdate(pGame,pEquippedItem,pMerc,FALSE,FALSE);
		ITEMS_UpdateTrade(pMerc->pInventory,pEquippedItem);
		UNITS_RefreshInventory(pMerc,TRUE);
		D2UpdateInventoryItems(pGame,pMerc,0);
The last two are required, the other two depend on whether the item was equiped and a few other factors.

Re: [1.10] shift clicking items

Posted: Mon Jun 20, 2016 1:47 am
by weapon-x
Necrolis" wrote:
weapon-x" wrote:edit:
[small findings]
D2DropItemInGround is kinda buggy... lets say i have an item in my inventory and used a targeting misc item (new pspell) that uses the d2dropiteminground, the item is dropped, but the cell used by the item inside the player's inventory becomes occupied, empty but unuseable... when you pickup the item again, the unusable cells become useable again... :mrgreen:

perhaps i missed a client update function or something like that...
You have to ensure the item is set to the correct mode:

Code: Select all

			if(!D2PlaceItem(__FILE__,__LINE__,pGame,pPlayer,pItem->dwGUID,0,0,1,1,0))
			{
				D2CoordStrc pSource, pDest;
				UNITS_SetMode(pItem,ITEMMODE_ON_GROUND);
				UNITS_GetPosition(pPlayer,&pSource);
				DRLGRoom* pRoom = ITEMS_FindSpawnPosition(DUNGEON_GetRoom(pPlayer),&pSource,&pDest,1);
				ITEMS_ServerDrop(pGame,NULL,pItem,pRoom,pDest.nXpos,pDest.nYpos);
			}
Removing an item also requires a call to update the inv and the unit:

Code: Select all

ITEMS_RemovalUpdate(pGame,pEquippedItem,pMerc,FALSE,FALSE);
		ITEMS_UpdateTrade(pMerc->pInventory,pEquippedItem);
		UNITS_RefreshInventory(pMerc,TRUE);
		D2UpdateInventoryItems(pGame,pMerc,0);
The last two are required, the other two depend on whether the item was equiped and a few other factors.
sorry to bump this old post of mine, but i am having a trouble with this again *this is my gajillionth attempt to pull this off

here is the code i used:

Code: Select all


D2COMMON_SetUnitMode(pItem, IMODE_STORED);
D2COMMON_SetPageofItem(pItem, ITEMLOC_STASH);

D2COMMON_AddToContainer(pPlayer->pInventory, pItem);
D2COMMON_SetBeginFlag(pPlayer, TRUE);	

D2GAME_UpdateInventoryItems(pGame, pPlayer, 0);
the above code works a bit, but would require the player to save and exit first >.< i guess i do lack these functions which i could not seemed to find or have any idea to start where to search for:

Code: Select all

ITEMS_RemovalUpdate(pGame,pEquippedItem,pMerc,FALSE,FALSE);
ITEMS_UpdateTrade(pMerc->pInventory,pEquippedItem);
UNITS_RefreshInventory(pMerc,TRUE); //is this the same as SetBeginFlag?
my end goal would be to move player inventory items to the player stash and or cube...

also i think the code i wrote doesn't check the stash inventory grids (*i could not seemed to understand how to do it) :-|
and i presumed it would be important to move the item to a specific inventory grids, so as to prevent glitches...

any help will be greatly appreciated ;)

Re: [1.10] shift clicking items

Posted: Mon Jun 20, 2016 4:45 am
by Nefarius
You're doing this the hard way.

You only need 3 functions to do this. You need the two top level functions for taking and adding items to inventory pages, both are in D2Game.DLL (Take=6FC68730, Put=6FC69B00 both 1.11b) and D2Common.10003 (1.11b) to check for space in the stash page before trying to put things there. To get the correct inventory index for the stash page you call D2Common.10301 (1.11b) in vanilla.

Now, unfortunately Blizzard's version of 6FC68730 doesn't give you control over if it attaches items to the cursor or not so I whipped up a quick version with none of the unnecessary behavior in it, you will have to figure out what all these functions are in 1.10 as I don't have any 1.10 info on hand.

Code: Select all

static BOOL DEMO_PRV_TakeItemFromInventory(D2Game* pGame, D2Unit* pUnit, D2Unit* pItem)
{
	D2Inventory* hInventory;
	if ((hInventory = pUnit->hInventory) == NULL)
		return FALSE;
	
	if (!UNIT_IS_ITEM(pItem) || pItem->eMode != ITEMMODE_INVENTORY)
		return FALSE;
	
	D2ItemData* pItemData;
	ASSERT(pItemData = pItem->pItemData);
	BYTE ePage = pItemData->ePage;
	
	ASSERT(D2RemoveItemFromInventory(hInventory, pItem) == pItem);

	if (ePage == INVENTORY_PAGE_PLAYER_INVENTORY)
		SITEM_AddOrRemoveBookCharges(pUnit, pItem, FALSE, TRUE);

	SITEM_RemoveItemStatsFromUnit(pUnit, pItem, FALSE, TRUE);
	if (ITEMS_CanActivateCharm(pItem, pUnit))
		SITEM_UpdateEquipment(pGame, pUnit, FALSE);

	pItemData->ePreviousPage = ePage;
	pItemData->ePage = INVENTORY_PAGE_NONE;
	pItem->dwUnitFlags &= ~UNITFLAG_SELECTABLE;
	UNITS_SetMode(pItem, ITEMMODE_CURSOR);
	
	pItemData->dwItemFlags |= ITEMFLAG_REMOVED;
					
	if (SITEM_DoSocketsHaveContent(pItem))
		pItemData->dwItemFlags |= ITEMFLAG_RELOAD;
	
	pItemData->dwItemFlags &= ~ITEMFLAG_DISABLED;
	pItemData->dwCMDFlags |= ITEMCMD_INVENTORY_TAKE_ITEM;
	D2AddItemToInventoryUpdateList(hInventory, pItem);
	D2SetInventoryUpdateMessage(pUnit, TRUE);
	return TRUE;
}

Once you have that routine complete you just need to do this. This demo function moves all inventory items to the stash.

Code: Select all

static void DEMO_PRV_MoveEntireInventoryToStash(D2Game* pGame, D2Unit* pPlayer)
{
	D2ItemData* pItemData;
	D2Unit* pItem = INV_INL_GetItemListByGrid(pPlayer->hInventory, INVENTORY_GRID_DEFAULT);
	
	while (pItemData = ITEMS_INL_GetIterator(pItem))
	{
		D2Unit* pNext = pItemData->pNextItemInPage;
		
		int nX;
		int nY;
		
		if (INV_INL_GetPosForItem(pPlayer->hInventory, pItem, INV_GetInventoryType(pPlayer, INVENTORY_PAGE_PLAYER_STASH, pGame->bExpansion), INVENTORY_PAGE_PLAYER_STASH, nX, nY))
		{
			if (DEMO_PRV_TakeItemFromInventory(pGame, pPlayer, pItem))
				ASSERT(SITEM_PutItemInInventory(pGame, pPlayer, pItem->dwID, INVENTORY_PAGE_PLAYER_STASH, nX, nY, FALSE, TRUE, NULL, NULL));
		}
		
		pItem = pNext;
	}
}
I've verified that this works as intended so assuming you rebuild it correctly it should work for you. The only other caveat is that Blizzard's version of SITEM_PutItemInInventory expects you to mess up the item data and set the page prior to calling it, my version takes that as an extra parameter (but I think that much will already have been discussed earlier in this thread).

Re: [1.10] shift clicking items

Posted: Mon Jun 20, 2016 7:27 am
by weapon-x
Nefarius" wrote:You're doing this the hard way.

You only need 3 functions to do this. You need the two top level functions for taking and adding items to inventory pages, both are in D2Game.DLL (Take=6FC68730, Put=6FC69B00 both 1.11b) and D2Common.10003 (1.11b) to check for space in the stash page before trying to put things there. To get the correct inventory index for the stash page you call D2Common.10301 (1.11b) in vanilla.

Now, unfortunately Blizzard's version of 6FC68730 doesn't give you control over if it attaches items to the cursor or not so I whipped up a quick version with none of the unnecessary behavior in it, you will have to figure out what all these functions are in 1.10 as I don't have any 1.10 info on hand.

Code: Select all

static BOOL DEMO_PRV_TakeItemFromInventory(D2Game* pGame, D2Unit* pUnit, D2Unit* pItem)
{
	D2Inventory* hInventory;
	if ((hInventory = pUnit->hInventory) == NULL)
		return FALSE;
	
	if (!UNIT_IS_ITEM(pItem) || pItem->eMode != ITEMMODE_INVENTORY)
		return FALSE;
	
	D2ItemData* pItemData;
	ASSERT(pItemData = pItem->pItemData);
	BYTE ePage = pItemData->ePage;
	
	ASSERT(D2RemoveItemFromInventory(hInventory, pItem) == pItem);

	if (ePage == INVENTORY_PAGE_PLAYER_INVENTORY)
		SITEM_AddOrRemoveBookCharges(pUnit, pItem, FALSE, TRUE);

	SITEM_RemoveItemStatsFromUnit(pUnit, pItem, FALSE, TRUE);
	if (ITEMS_CanActivateCharm(pItem, pUnit))
		SITEM_UpdateEquipment(pGame, pUnit, FALSE);

	pItemData->ePreviousPage = ePage;
	pItemData->ePage = INVENTORY_PAGE_NONE;
	pItem->dwUnitFlags &= ~UNITFLAG_SELECTABLE;
	UNITS_SetMode(pItem, ITEMMODE_CURSOR);
	
	pItemData->dwItemFlags |= ITEMFLAG_REMOVED;
					
	if (SITEM_DoSocketsHaveContent(pItem))
		pItemData->dwItemFlags |= ITEMFLAG_RELOAD;
	
	pItemData->dwItemFlags &= ~ITEMFLAG_DISABLED;
	pItemData->dwCMDFlags |= ITEMCMD_INVENTORY_TAKE_ITEM;
	D2AddItemToInventoryUpdateList(hInventory, pItem);
	D2SetInventoryUpdateMessage(pUnit, TRUE);
	return TRUE;
}

Once you have that routine complete you just need to do this. This demo function moves all inventory items to the stash.

Code: Select all

static void DEMO_PRV_MoveEntireInventoryToStash(D2Game* pGame, D2Unit* pPlayer)
{
	D2ItemData* pItemData;
	D2Unit* pItem = INV_INL_GetItemListByGrid(pPlayer->hInventory, INVENTORY_GRID_DEFAULT);
	
	while (pItemData = ITEMS_INL_GetIterator(pItem))
	{
		D2Unit* pNext = pItemData->pNextItemInPage;
		
		int nX;
		int nY;
		
		if (INV_INL_GetPosForItem(pPlayer->hInventory, pItem, INV_GetInventoryType(pPlayer, INVENTORY_PAGE_PLAYER_STASH, pGame->bExpansion), INVENTORY_PAGE_PLAYER_STASH, nX, nY))
		{
			if (DEMO_PRV_TakeItemFromInventory(pGame, pPlayer, pItem))
				ASSERT(SITEM_PutItemInInventory(pGame, pPlayer, pItem->dwID, INVENTORY_PAGE_PLAYER_STASH, nX, nY, FALSE, TRUE, NULL, NULL));
		}
		
		pItem = pNext;
	}
}
I've verified that this works as intended so assuming you rebuild it correctly it should work for you. The only other caveat is that Blizzard's version of SITEM_PutItemInInventory expects you to mess up the item data and set the page prior to calling it, my version takes that as an extra parameter (but I think that much will already have been discussed earlier in this thread).
thank you sir, i will do my best to find the functions you used in 1.10 :)

Cheers !

Re: [1.10] shift clicking items

Posted: Tue Jan 15, 2019 10:21 pm
by prawn
Weapon-x did you ever manage to move player inventory to stash for 1.10?

Re: [1.10] shift clicking items

Posted: Thu Jun 20, 2019 6:24 am
by weapon-x
prawn wrote:
Tue Jan 15, 2019 10:21 pm
Weapon-x did you ever manage to move player inventory to stash for 1.10?
sorry for this super ultra late reply, but yes... i managed to pull this out, inventory to stash, and stash to inventory, if i remember right i was working on equipping items (inventory to equipment slot) before i was occupied with work stuff