From 4b35f427bc4a9dda563cae2e509574c886ffbbcc Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 19:56:08 -0500 Subject: [PATCH 01/14] wip new storage system --- src/game/client/ms/clplayer.cpp | 6 +- src/game/client/ui/ms/vgui_storage.cpp | 149 +++++-------- src/game/client/ui/ms/vgui_storage.h | 1 - src/game/client/ui/ms/vgui_storemainwin.cpp | 8 + src/game/server/character/mscharacterheader.h | 8 +- src/game/server/character/sv_character.cpp | 51 ++--- src/game/server/client.cpp | 122 +++++------ src/game/server/monsters/npcscript.cpp | 58 +----- src/game/server/player/player.cpp | 196 +++++++++++++++--- src/game/server/player/player.h | 33 ++- src/game/server/player/playershared.cpp | 24 +-- src/game/shared/ms/global.cpp | 1 + src/game/shared/weapons/genericitem.cpp | 89 ++++---- src/game/shared/weapons/genericitem.h | 4 +- 14 files changed, 399 insertions(+), 351 deletions(-) diff --git a/src/game/client/ms/clplayer.cpp b/src/game/client/ms/clplayer.cpp index 54ffa438a..c32957d74 100644 --- a/src/game/client/ms/clplayer.cpp +++ b/src/game/client/ms/clplayer.cpp @@ -56,7 +56,7 @@ int playerBodyArray[16]; //MiB MAR2010_12 Armor Fix FINAL //Master Sword player functionality void ShowWeaponDesc(CGenericItem* pItem); -void Storage_Show(const char* DisplayName, const char* StorageName, float flFeeRatio); +void Storage_Show(const char* DisplayName); void UpdateActiveMenus(); /* @@ -877,9 +877,7 @@ int __MsgFunc_Item(const char* pszName, int iSize, void* pbuf) case 5: { msstring DisplayName = READ_STRING(); - msstring StorageName = READ_STRING(); - float flFeeRatio = READ_FLOAT(); - Storage_Show(DisplayName, StorageName, flFeeRatio); + Storage_Show(DisplayName); bDoInvUpdate = true; } break; diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index f2d1b594f..cbb083ce2 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -74,23 +74,6 @@ CStoragePanel::CStoragePanel(Panel *pParent) : CStorePanel() //Update void CStoragePanel::AddInventoryItems() { - //Find the storage on the player - storage_t *pStorage = NULL; - for (int s = 0; s < player.m_Storages.size(); s++) - if (player.m_Storages[s].Name == player.m_CurrentStorage.StorageName) - { - pStorage = &player.m_Storages[s]; - break; - } - - if (!pStorage) - { - //If the storage doesn't exist, create it real quick. This should never happen. - storage_t Storage; - Storage.Name = player.m_CurrentStorage.StorageName; - pStorage = player.Storage_CreateAccount(Storage); - } - //Add the 'storage' container gearitem_t GearItem; GearItem.Name = "Storage"; @@ -99,10 +82,22 @@ void CStoragePanel::AddInventoryItems() VGUI_Inv_GearItem *pGearItemButton = m_GearPanel->AddGearItem(GearItem); //Add the storage items - storage_t &Storage = *pStorage; - for (int i = 0; i < Storage.Items.size(); i++) + for (int i = 0; i < player.m_Storage.size(); i++) { - containeritem_t vNewItem = containeritem_t(Storage.Items[i]); + genericitem_full_t &Item = player.m_Storage[i]; + containeritem_t vNewItem = containeritem_t(Item); + + //Groupable items are stacked by quantity, everything else by count + int Amt = FBitSet(Item.Properties, ITEM_GROUPABLE) ? Item.Quantity : Item.Count; + vNewItem.Quantity = Amt; + if (Amt > 1) + { + //The item's name comes from the global item, which doesn't know the amount. Name it the way SPEECH::ItemName does + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(Item.Name, true); + if (pGlobalItem) + vNewItem.setFullName(msstring() + Amt + " " + pGlobalItem->DisplayName() + "s"); + } + pGearItemButton->m_ItemContainer->AddItem(vNewItem); } @@ -168,7 +163,7 @@ bool CStoragePanel::GearItemClicked(ulong ID) for (int i = 0; i < m_SelectedItems.size(); i++) { - if (checkValid(m_SelectedItems[i])) //Check it against the bank mask (MiB Feb2008a) + if (!AddOrRemove || checkValid(m_SelectedItems[i])) //Check it against the bank mask (MiB Feb2008a) { //Send the command if (AddOrRemove) @@ -188,52 +183,15 @@ bool CStoragePanel::GearItemClicked(ulong ID) //MiB FEB2008a bool CStoragePanel::checkValid(containeritem_t &item) { - msstring name = item.Name; - if (name.starts_with("pack_")) - return false; - if (name.starts_with("sheath_")) - return false; - if (name.starts_with("mana_")) - return false; - if (name.starts_with("health_")) - return false; - if (name.starts_with("proj_")) - return false; - - return true; + //The server checks this too + return CBasePlayer::Storage_CanStoreItem(item.Name.c_str(), item.Properties); } -//Item selected -int CStoragePanel::ItemRetrievalCost(containeritem_t &Item) -{ - return int(Item.Value * player.m_CurrentStorage.flFeeRatio); -} void CStoragePanel::ItemHighlighted(void *pData) { CStorePanel::ItemHighlighted(pData); - if (m_LastGearItem != 1) - { - m_InfoPanel->m_SaleText->setVisible(false); - m_InfoPanel->setVisible(true); - return; - } - - VGUI_ItemButton &ItemButton = *(VGUI_ItemButton *)pData; - if (ItemButton.m_Highlighted) - { - int Value = ItemRetrievalCost(ItemButton.m_Data); - if (Value) - { - char cTemp[256]; - _snprintf(cTemp, sizeof(cTemp), Localized("#STORAGE_ITEM_COST"), Value ); - m_InfoPanel->m_SaleText->setText(cTemp); - } - else - m_InfoPanel->m_SaleText->setText(Localized("#STORAGE_ITEM_FREE")); - m_InfoPanel->m_SaleText->setVisible(true); - } - m_InfoPanel->m_Scroll->validate(); + m_InfoPanel->m_SaleText->setVisible(false); m_InfoPanel->setVisible(true); } @@ -244,8 +202,6 @@ void CStoragePanel::ItemSelectChanged(ulong ID, bool fSelected) m_SaleLabel->setVisible(true); - int Valuetotal = 0; - for (int g = 0; g < m_GearPanel->GearItemButtonTotal; g++) { VGUI_Inv_GearItem &GearItem = *m_GearPanel->GearItemButtons[g]; @@ -256,16 +212,13 @@ void CStoragePanel::ItemSelectChanged(ulong ID, bool fSelected) continue; m_SelectedItems.add(ItemButton.m_Data); - - if (m_LastGearItem == 1) - Valuetotal += ItemRetrievalCost(ItemButton.m_Data); } } if (m_SelectedItems.size()) { if (m_LastGearItem == 1) - m_SaleLabel->setText(msstring("Removing ") + (int)m_SelectedItems.size() + " items from storage for " + Valuetotal + " gold"); + m_SaleLabel->setText(msstring("Removing ") + (int)m_SelectedItems.size() + " items from storage"); else m_SaleLabel->setText(msstring("Moving ") + (int)m_SelectedItems.size() + " items into storage"); m_SaleLabel->setVisible(true); @@ -311,48 +264,60 @@ void Storage_Update() //Item msg void Storage_ItemReset() { - player.m_Storages.clear(); + player.m_Storage.clear(); Storage_Update(); } +//Items were added to my storage void Storage_ItemMsg() { - msstring StorageName = READ_STRING(); - storage_t *pStorage = NULL; + int Count = READ_BYTE(); + for (int i = 0; i < Count; i++) + { + genericitem_full_t Item; + bool Valid = ReadGenericItem(Item); + Item.Count = READ_SHORT(); //Always read, so the next item can still be read + if (Valid) + player.m_Storage.add(Item); + } + + Storage_Update(); +} - for (int s = 0; s < player.m_Storages.size(); s++) - if (player.m_Storages[s].Name == StorageName) +//An item was removed from my storage +void Storage_ItemRemoveMsg() +{ + ulong ID = READ_LONG(); + for (int i = 0; i < player.m_Storage.size(); i++) + if (player.m_Storage[i].ID == ID) { - pStorage = &player.m_Storages[s]; + player.m_Storage.erase(i); break; } - if (!pStorage) - { - storage_t Storage; - Storage.Name = StorageName; - pStorage = player.Storage_CreateAccount(Storage); - } - - bool AddItem = READ_BYTE() ? true : false; - - if (AddItem) - { - CGenericItem *pItem = ReadGenericItem(true); - if (pItem) - pStorage->Items.add(genericitem_full_t(pItem)); + Storage_Update(); +} - pItem->SUB_Remove(); - } +//The amount of an item in my storage changed +void Storage_ItemUpdateMsg() +{ + ulong ID = READ_LONG(); + int Quantity = READ_SHORT(); + int Count = READ_SHORT(); + for (int i = 0; i < player.m_Storage.size(); i++) + if (player.m_Storage[i].ID == ID) + { + player.m_Storage[i].Quantity = Quantity; + player.m_Storage[i].Count = Count; + break; + } Storage_Update(); } -void Storage_Show(const char* DisplayName, const char* StorageName, float flFeeRatio) +void Storage_Show(const char* DisplayName) { player.m_CurrentStorage.Active = true; player.m_CurrentStorage.DisplayName = DisplayName; - player.m_CurrentStorage.StorageName = StorageName; - player.m_CurrentStorage.flFeeRatio = flFeeRatio; ShowVGUIMenu(MENU_STORAGE); } \ No newline at end of file diff --git a/src/game/client/ui/ms/vgui_storage.h b/src/game/client/ui/ms/vgui_storage.h index d53afeff9..43af88864 100644 --- a/src/game/client/ui/ms/vgui_storage.h +++ b/src/game/client/ui/ms/vgui_storage.h @@ -12,7 +12,6 @@ class CStoragePanel : public CStorePanel void AddInventoryItems(); void Update(); void Close(); - int ItemRetrievalCost(containeritem_t &Item); //Item callbacks void ItemSelectChanged(ulong ID, bool fSelected); diff --git a/src/game/client/ui/ms/vgui_storemainwin.cpp b/src/game/client/ui/ms/vgui_storemainwin.cpp index 86c7cecbf..5f6d684a5 100644 --- a/src/game/client/ui/ms/vgui_storemainwin.cpp +++ b/src/game/client/ui/ms/vgui_storemainwin.cpp @@ -95,6 +95,8 @@ void MsgFunc_Store() void Update_StoreBuy(); void Storage_ItemReset(); void Storage_ItemMsg(); +void Storage_ItemRemoveMsg(); +void Storage_ItemUpdateMsg(); int __MsgFunc_StoreItem(const char *pszName, int iSize, void *pbuf) { @@ -143,6 +145,12 @@ int __MsgFunc_StoreItem(const char *pszName, int iSize, void *pbuf) case 2: Storage_ItemMsg(); break; + case 3: + Storage_ItemRemoveMsg(); + break; + case 4: + Storage_ItemUpdateMsg(); + break; } return 0; } diff --git a/src/game/server/character/mscharacterheader.h b/src/game/server/character/mscharacterheader.h index edc93c07c..7c3406ded 100644 --- a/src/game/server/character/mscharacterheader.h +++ b/src/game/server/character/mscharacterheader.h @@ -88,10 +88,11 @@ enum charfield_e CF_SKILLS = 33, //[ stat: [ substat: [value, exp], ... ], ... ] CF_SPELLS = 34, //[str, ...] CF_ITEMS = 35, //[item, ...] - CF_STORAGES = 36, //[storage, ...] + CF_STORAGES = 36, //RETIRED - [storage, ...] named storages. Still read, to move their items into CF_STORAGE CF_COMPANIONS = 37, //[companion, ...] CF_QUESTS = 38, //[ [name, data], ... ] CF_QUICKSLOTS = 39, //[ nil | [type, id], ... ] + CF_STORAGE = 40, //[item, ...] - items in storage }; //Item keys @@ -106,12 +107,13 @@ enum charitemfield_e CI_MAXQUALITY = 6, //int - perishable/drinkable only CI_QUANTITY = 7, //int - groupable only CI_CONTENTS = 8, //[item, ...] - containers only + CI_COUNT = 9, //int - storage only: identical items stacked in this entry (absent = 1) }; -//Storage keys +//Storage keys - only used to read the retired CF_STORAGES enum charstoragefield_e { - CS_NAME = 0, //str + CS_NAME = 0, //str - no longer read CS_ITEMS = 1, //[item, ...] }; diff --git a/src/game/server/character/sv_character.cpp b/src/game/server/character/sv_character.cpp index 7ce4c5238..1654fb1cc 100644 --- a/src/game/server/character/sv_character.cpp +++ b/src/game/server/character/sv_character.cpp @@ -257,6 +257,7 @@ static void ReadItemListPack(const msgpack::object &Obj, mslist Items; msgpack::object_map Fields = PackMap(Storages.ptr[s]); for (uint32_t f = 0; f < Fields.size; f++) + if (PackReadInt(Fields.ptr[f].key, -1) == CS_ITEMS) + ReadItemListPack(Fields.ptr[f].val, Items); + + for (int i = 0; i < Items.size(); i++) { - const msgpack::object &FieldValue = Fields.ptr[f].val; - switch (PackReadInt(Fields.ptr[f].key, -1)) - { - case CS_NAME: Storage.Name = PackReadMsStr(FieldValue); break; - case CS_ITEMS: ReadItemListPack(FieldValue, Storage.Items); break; - } + Items[i].ID = m_Storage.size(); //The IDs were only unique within each named storage + m_Storage.add(Items[i]); } - m_Storages.add(Storage); } break; } @@ -553,7 +556,7 @@ bool chardata_t::ReadDataLegacy(void *pData, ulong Size) ReadSkills1(DataID, m_File); ReadSpells1(DataID, m_File); ReadItems1(DataID, m_File); - ReadStorageItems1(DataID, m_File); + SkipStorageItems1(DataID, m_File); ReadCompanions1(DataID, m_File); SkipHelpTips1(DataID, m_File); ReadQuests1(DataID, m_File); @@ -693,31 +696,25 @@ void chardata_t::ReadItems1(byte DataID, CPlayer_DataBuffer &m_File) } } -void chardata_t::ReadStorageItems1(byte DataID, CPlayer_DataBuffer &m_File) +void chardata_t::SkipStorageItems1(byte DataID, CPlayer_DataBuffer &m_File) { if (DataID == CHARDATA_STORAGE1) { - //Read storage items + //Storage from legacy saves is no longer loaded, but the chunk still has to be read past short Storages = 0; m_File.ReadShort(Storages); //[SHORT] for (int i = 0; i < Storages; i++) { - storage_t Storage; - m_File.ReadString(Storage.Name, MSSTRING_SIZE); //[X STRINGS] + m_File.ReadString(cTemp, MSSTRING_SIZE); //[X STRINGS] short Items = 0; m_File.ReadShort(Items); //[X SHORTS] for (int i = 0; i < Items; i++) { genericitem_full_t Item; - if (!ReadItem1(DataID, m_File, Item)) //[Y ITEMS] - continue; - - Storage.Items.add(Item); + ReadItem1(DataID, m_File, Item); //[Y ITEMS] } - - m_Storages.add(Storage); } } } @@ -1006,6 +1003,9 @@ static bool PackItem(charpack_t &Out, genericitem_full_t &Item) if (FBitSet(Item.Properties, ITEM_GROUPABLE)) Fields.Field(CI_QUANTITY, Item.Quantity); + if (Item.Count > 1) + Fields.Field(CI_COUNT, Item.Count); + if (FBitSet(Item.Properties, ITEM_CONTAINER)) { Fields.Key(CI_CONTENTS); @@ -1104,18 +1104,11 @@ static void PackChar(charpack_t &Out, CBasePlayer *pPlayer, savedata_t &Data) } //Storage items - Root.Key(CF_STORAGES); - Out.Pk.pack_array(pPlayer->m_Storages.size()); - for (int s = 0; s < pPlayer->m_Storages.size(); s++) + Root.Key(CF_STORAGE); { - storage_t &Storage = pPlayer->m_Storages[s]; - - CPackScope StorageFields(Out, true); - StorageFields.FieldStr(CS_NAME, Storage.Name); - StorageFields.Key(CS_ITEMS); CPackScope Items(Out, false); - for (int i = 0; i < Storage.Items.size(); i++) - if (PackItem(Out, Storage.Items[i])) + for (int i = 0; i < pPlayer->m_Storage.size(); i++) + if (PackItem(Out, pPlayer->m_Storage[i])) Items.Added(); } diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index ef1a6f904..dcaf9ccfc 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -938,90 +938,74 @@ void ClientCommand2(edict_t *pEntity) if (pPlayer->m_CurrentStorage.Active) { - storage_t *pStorage = pPlayer->Storage_GetStorage(pPlayer->m_CurrentStorage.StorageName); - if (pStorage) + if (!_stricmp(CMD_ARGV(1), "add")) { - if (!_stricmp(CMD_ARGV(1), "add")) + CGenericItem *pItem = MSUtil_GetItemByID(atol(CMD_ARGV(2)), pPlayer); + if (pItem && (pItem == pPlayer->PlayerHands || !CBasePlayer::Storage_CanStoreItem(pItem->ItemName.c_str(), pItem->MSProperties()))) + pPlayer->SendEventMsg(HUDEVENT_UNABLE, msstring("You can't put ") + SPEECH::ItemName(pItem) + " in storage"); + else if (pItem) { - CGenericItem *pItem = MSUtil_GetItemByID(atol(CMD_ARGV(2)), pPlayer); - if (pItem) + genericitem_full_t Item(pItem); + if (pPlayer->Storage_AddItem(Item)) { - //Determine a unique ID for the item in storage - genericitem_full_t Item(pItem); - for (unsigned int ID = 0; ID < SHRT_MAX; ID++) - { - bool Duplicate = false; - for (int i = 0; i < pStorage->Items.size(); i++) - if (pStorage->Items[i].ID == ID) - { - Duplicate = true; - break; - } - if (Duplicate) - continue; - Item.ID = ID; - break; - } - - pStorage->Items.add(Item); - pPlayer->Storage_Send(); pPlayer->RemoveItem(pItem); pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You put ") + SPEECH::ItemName(pItem) + " in storage"); pItem->SUB_Remove(); } + else + pPlayer->SendEventMsg(HUDEVENT_UNABLE, "Your storage is full"); } - else if (!_stricmp(CMD_ARGV(1), "remove")) - { - if (CMD_ARGC() < 4) - return; - - int ItemID = atoi(CMD_ARGV(2)); - genericitem_t *pStorageItem = NULL; - int StorageItemIdx = 0; - for (unsigned int i = 0; i < pStorage->Items.size(); i++) - if (pStorage->Items[i].ID == ItemID) - { - pStorageItem = &pStorage->Items[i]; - StorageItemIdx = i; - break; - } - - if (pStorageItem) + } + else if (!_stricmp(CMD_ARGV(1), "remove")) + { + if (CMD_ARGC() < 4) + return; + + int ItemID = atoi(CMD_ARGV(2)); + genericitem_full_t *pStorageItem = NULL; + int StorageItemIdx = 0; + for (unsigned int i = 0; i < pPlayer->m_Storage.size(); i++) + if (pPlayer->m_Storage[i].ID == ItemID) { - CGenericItem *pItem = *pStorageItem; - int Cost = int(pItem->m_Value * pPlayer->m_CurrentStorage.flFeeRatio); - - if (!Cost || pPlayer->m_Gold > Cost) //If no retrieval fee or I have enough to pay it... - { - int ContainerID = atol(CMD_ARGV(3)); - bool Success = false; + pStorageItem = &pPlayer->m_Storage[i]; + StorageItemIdx = i; + break; + } - if (!ContainerID) - Success = pItem->GiveTo(pPlayer, true, true); - else + if (pStorageItem) + { + //Create it as a new item, without the location and hand it had when it was stored + CGenericItem *pItem = pStorageItem->genericitem_t::operator CGenericItem *(); + if (pItem) + { + //If it's a container, put back the items that were inside it + if (FBitSet(pItem->MSProperties(), ITEM_CONTAINER)) + for (int i = 0; i < pStorageItem->ContainerItems.size(); i++) { - CGenericItem *pContainer = MSUtil_GetItemByID(ContainerID, pPlayer); - if (pContainer) - Success = pPlayer->PutInPack(pItem, pContainer); + CGenericItem *pContainedItem = pStorageItem->ContainerItems[i]; + if (pContainedItem && !pItem->Container_AddItem(pContainedItem)) + pContainedItem->SUB_Remove(); } - if (Success) - { - pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + SPEECH::ItemName(pItem) + " from storage"); - if (Cost) - pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("Retrieval fee: ") + Cost + " Gold"); - else - pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("Retrieval fee: None")); - - pPlayer->GiveGold(-Cost, false); - pStorage->Items.erase(StorageItemIdx); - pPlayer->Storage_Send(); - } - else - pItem->SUB_Remove(); + int ContainerID = atol(CMD_ARGV(3)); + bool Success = false; + + if (!ContainerID) + Success = pItem->GiveTo(pPlayer, true, true); + else + { + CGenericItem *pContainer = MSUtil_GetItemByID(ContainerID, pPlayer); + if (pContainer) + Success = pPlayer->PutInPack(pItem, pContainer); + } + + if (Success) + { + pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + SPEECH::ItemName(pItem) + " from storage"); + pPlayer->Storage_RemoveItem(StorageItemIdx); } else - pPlayer->SendEventMsg(HUDEVENT_UNABLE, msstring("You can't afford the retrieval fee of ") + Cost + " Gold"); + pItem->SUB_Remove(); } } } diff --git a/src/game/server/monsters/npcscript.cpp b/src/game/server/monsters/npcscript.cpp index 4476a4128..4acf3023d 100644 --- a/src/game/server/monsters/npcscript.cpp +++ b/src/game/server/monsters/npcscript.cpp @@ -1388,61 +1388,17 @@ bool CMSMonster::Script_ExecuteCmd(CScript *Script, SCRIPT_EVENT &Event, scriptc //************************** STORAGE ******************************* else if (Cmd.Name() == "storage") { - if (Params.size() >= 3) + //trade target displayname cb_prefix + //Opens the target player's storage. The player has to stay near me to keep using it + if (Params.size() >= 3 && Params[0] == "trade") { - const char* StorageName = Params[1]; - CBaseEntity *pTarget = RetrieveEntity(Params[2]); + CBaseEntity *pTarget = RetrieveEntity(Params[1]); if (pTarget && pTarget->IsPlayer()) { - CBasePlayer *pPlayer = (CBasePlayer *)pTarget; - - //openaccount storagename target cb_prefix - if (Params[0] == "openaccount") - { - msstring Callback = Params.size() >= 4 ? Params[3].str() : "game_openaccount"; - - if (pPlayer->Storage_GetStorage(StorageName)) - CallScriptEvent(Callback + "_exists"); - else - { - storage_t Storage; - Storage.Name = StorageName; - - if (pPlayer->Storage_CreateAccount(Storage)) - CallScriptEvent(Callback + "_success"); - else - CallScriptEvent(Callback + "_failed"); - } - } - //checkaccount storagename target cb_prefix - else if (Params[0] == "checkaccount") - { - msstring Callback = Params.size() >= 4 ? Params[3].c_str() : "game_checkaccount"; + msstring Callback = Params.size() >= 4 ? Params[3].c_str() : "game_storage_trade"; - if (pPlayer->Storage_GetStorage(StorageName)) - CallScriptEvent(Callback + "_success"); - else - CallScriptEvent(Callback + "_failed"); - } - //trade storagename target feeratio displayname cb_prefix - else if (Params[0] == "trade") - { - if (Params.size() >= 5) - { - msstring Callback = Params.size() >= 6 ? Params[5].c_str() : "game_storage_trade"; - - if (pPlayer->Storage_GetStorage(StorageName)) - { - entityinfo_t newEnt = entityinfo_t(this); - pPlayer->Storage_Open(Params[4], StorageName, atof(Params[3]), newEnt); - CallScriptEvent(Callback + "_success"); - } - else - CallScriptEvent(Callback + "_noaccount"); - } - else - ERROR_MISSING_PARMS; - } + ((CBasePlayer *)pTarget)->Storage_Open(Params[2], this); + CallScriptEvent(Callback + "_success"); } } else diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index c75ff60f0..56367c278 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -1519,6 +1519,7 @@ void CBasePlayer::PreThink(void) return; // intermission or finale Trade(); //Trade - Do this early on + Storage_CheckTeller(); ItemPreFrame(); WaterMove(); @@ -3815,6 +3816,9 @@ void CBasePlayer::UpdateClientData(void) if (bShouldSaveChar) SaveChar(); + //Send new storage items + Storage_UpdateClient(); + //Active Hand if (m_CurrentHand != m_ClientCurrentHand) { @@ -6470,8 +6474,7 @@ bool CBasePlayer::RestoreAllServer(void *pData, ulong Size) } //Read storage items - m_Storages = Data.m_Storages; - Storage_Send(); //Send all the items in storage + m_Storage = Data.m_Storage; //Sent to the client when the storage is opened //Read Companions @@ -6678,55 +6681,188 @@ bool CBasePlayer::IsInAttackStance() return false; } -void CBasePlayer::Storage_Open(const char* pszDisplayName, const char* pszStorageName, float flFeeRatio, entityinfo_t &Entity) +#define STORAGE_TELLER_RANGE 128 //Same range as CMSMonster::Trade +#define STORAGE_ITEMS_PER_MSG 7 //2 + (7 * 25 bytes max per item) = 177, which fits in MAX_USERMESSAGE_SIZE + +void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller) { m_CurrentStorage.Active = true; - m_CurrentStorage.StorageName = pszStorageName; m_CurrentStorage.DisplayName = pszDisplayName; - m_CurrentStorage.flFeeRatio = flFeeRatio; - m_CurrentStorage.ReqTeller = Entity.pvPrivData ? true : false; - if (m_CurrentStorage.ReqTeller) - m_CurrentStorage.Teller = Entity; + m_CurrentStorage.Teller = pTeller; + m_CurrentStorage.ClientItems = 0; MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_ITEM], NULL, pev); WRITE_BYTE(5); WRITE_STRING_LIMIT(m_CurrentStorage.DisplayName, 90); - WRITE_STRING_LIMIT(m_CurrentStorage.StorageName, 90); - WRITE_FLOAT(m_CurrentStorage.flFeeRatio); + MESSAGE_END(); + + //The client only knows my storage items while it has the storage open. Storage_UpdateClient sends them + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); + WRITE_BYTE(1); //reset storage items MESSAGE_END(); } -void CBasePlayer::Storage_Send() +void CBasePlayer::Storage_Close() { + m_CurrentStorage.Active = false; + + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_VGUIMENU], NULL, pev); + WRITE_BYTE(0); + MESSAGE_END(); +} +void CBasePlayer::Storage_CheckTeller() +{ + //I have to stay near the entity that opened my storage + if (!m_CurrentStorage.Active) + return; + + CBaseEntity *pTeller = m_CurrentStorage.Teller.Entity(); + if (pTeller && pTeller->pev->deadflag == DEAD_NO && + (pTeller->Center() - Center()).Length() <= STORAGE_TELLER_RANGE) + return; + + Storage_Close(); +} +void CBasePlayer::Storage_UpdateClient() +{ + //Send the storage items that the client doesn't know about yet. + //One message per frame, so that a large storage doesn't overflow the client + if (!m_CurrentStorage.Active) + return; + + int Count = V_min((int)m_Storage.size() - m_CurrentStorage.ClientItems, STORAGE_ITEMS_PER_MSG); + if (Count <= 0) + return; + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); - WRITE_BYTE(1); //reset storage items + WRITE_BYTE(2); //add storage items + WRITE_BYTE(Count); + for (int i = 0; i < Count; i++) + { + genericitem_full_t &Item = m_Storage[m_CurrentStorage.ClientItems + i]; + SendGenericItem(this, Item, false); + WRITE_SHORT(Item.Count); + } MESSAGE_END(); - //Send all storage items. - for (int s = 0; s < m_Storages.size(); s++) + m_CurrentStorage.ClientItems += Count; +} +//How many more of Item can stack onto Cur, an item already in storage. 0 if they aren't identical. +//Groupable items stack by quantity, like they do in a pack. Everything else stacks by count. +//Containers don't stack, because of the items inside them +static int Storage_StackRoom(genericitem_full_t &Cur, genericitem_full_t &Item) +{ + if (FBitSet(Item.Properties, ITEM_CONTAINER) || Cur.Name != Item.Name || Cur.Properties != Item.Properties) + return 0; + + if (FBitSet(Item.Properties, ITEM_GROUPABLE)) + return V_max(NUM_MAX_STACK - (int)Cur.Quantity, 0); + + if (Cur.Quality != Item.Quality || Cur.MaxQuality != Item.MaxQuality) + return 0; + + return V_max(NUM_MAX_STACK - (int)Cur.Count, 0); +} +bool CBasePlayer::Storage_AddItem(genericitem_full_t &Item) +{ + //Adds an item to my storage, stacking it with identical items. + //Returns false if the storage is full + bool Groupable = FBitSet(Item.Properties, ITEM_GROUPABLE) ? true : false; + + //A full storage can still take an item that stacks completely onto the items already there. + //Check before changing anything, so that a stack is never left half stored + if ((int)m_Storage.size() >= STORAGE_MAX_ITEMS) { - if (!m_Storages[s].Items.size()) + int Room = 0; + for (int i = 0; i < m_Storage.size(); i++) + Room += Storage_StackRoom(m_Storage[i], Item); + + if (Room < (Groupable ? (int)Item.Quantity : 1)) + return false; + } + + for (int i = 0; i < m_Storage.size(); i++) + { + genericitem_full_t &Cur = m_Storage[i]; + int Room = Storage_StackRoom(Cur, Item); + if (Room <= 0) + continue; + + if (Groupable) { - //If the storage has no items, just send the storage name, indicating no items - MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); - WRITE_BYTE(2); //storage item - WRITE_STRING_LIMIT(m_Storages[s].Name, WRITE_STRING_MAX); - WRITE_BYTE(0); //No items - MESSAGE_END(); + int Amt = V_min((int)Item.Quantity, Room); + if (Amt <= 0) + continue; + + Cur.Quantity += Amt; + Item.Quantity -= Amt; + Storage_SendItemUpdate(i); + if (!Item.Quantity) + return true; } else { - //If the storage has items, send a full message - for (int i = 0; i < m_Storages[s].Items.size(); i++) + Cur.Count++; + Storage_SendItemUpdate(i); + return true; + } + } + + //Determine a unique ID for the item in storage + for (unsigned int ID = 0; ID < SHRT_MAX; ID++) + { + bool Duplicate = false; + for (int i = 0; i < m_Storage.size(); i++) + if (m_Storage[i].ID == ID) { - MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); - WRITE_BYTE(2); //storage item - WRITE_STRING_LIMIT(m_Storages[s].Name, WRITE_STRING_MAX); - WRITE_BYTE(1); //Add this item to the storage - SendGenericItem(this, m_Storages[s].Items[i], false); - MESSAGE_END(); + Duplicate = true; + break; } - } + if (Duplicate) + continue; + Item.ID = ID; + break; + } + + Item.Count = 1; + m_Storage.add(Item); //Storage_UpdateClient sends it + return true; +} +void CBasePlayer::Storage_SendItemUpdate(int Idx) +{ + //The amount of an item in my storage changed. + //Items past ClientItems haven't been sent yet, so they get sent with the new amount + if (Idx >= m_CurrentStorage.ClientItems) + return; + + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); + WRITE_BYTE(4); //update storage item + WRITE_LONG(m_Storage[Idx].ID); + WRITE_SHORT(m_Storage[Idx].Quantity); + WRITE_SHORT(m_Storage[Idx].Count); + MESSAGE_END(); +} +void CBasePlayer::Storage_RemoveItem(int Idx) +{ + //Removes one item from my storage. If it was stacked by count, the rest of the stack stays + if (m_Storage[Idx].Count > 1) + { + m_Storage[Idx].Count--; + Storage_SendItemUpdate(Idx); + return; } + + //Items past ClientItems haven't been sent yet, so the client has nothing to remove + if (Idx < m_CurrentStorage.ClientItems) + { + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); + WRITE_BYTE(3); //remove storage item + WRITE_LONG(m_Storage[Idx].ID); + MESSAGE_END(); + + m_CurrentStorage.ClientItems--; + } + + m_Storage.erase(Idx); } void CBasePlayer::SaveChar() diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index a3112948a..2f3c8f5a1 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -29,6 +29,7 @@ // was 1500, but there shouldn't be any issues with increasing. #define NUM_MAX_STACK 2500 //stack is stored as either a unsigned short, so that's the actual max it can be. +#define STORAGE_MAX_ITEMS 100 //Max entries in a player's storage. Stacked items share an entry enum sbar_data { @@ -307,21 +308,15 @@ struct genericitem_full_t : genericitem_t byte Hand; float Spell_TimePrepare; byte Spell_CastSuccess; + unsigned short Count; //Storage only: identical items stacked in this entry. Groupable items stack by Quantity instead mslist ContainerItems; }; -struct storage_t -{ - msstring Name; - mslist Items; -}; struct storageaccess_t { bool Active; - bool ReqTeller; //Whether we should keep checking to see if the player is out of range of the teller or if the teller has died msstring DisplayName; - msstring StorageName; - float flFeeRatio; - entityinfo_t Teller; + entityinfo_t Teller; //The entity that opened my storage. I must stay near it to keep using it + int ClientItems; //Number of items in my storage that the client knows about }; //Companion @@ -365,7 +360,7 @@ struct chardata_t : savedata_t statlist m_Stats; //All stats. Natural stats and Skill-based stats std::vector m_Spells; //All known spells mslist m_Items; //All carried items - mslist m_Storages; //Storage places where I have items + mslist m_Storage; //All items in storage mslist m_Companions; //Companions mslist m_Quests; //All the quests I've completed fixed_vector m_QuickSlots; //All the quickslots (based on the item IDs of the last save). Extras in a save are dropped @@ -382,7 +377,7 @@ struct chardata_t : savedata_t void ReadSpells1(byte DataID, CPlayer_DataBuffer &m_File); void ReadItems1(byte DataID, CPlayer_DataBuffer &m_File); bool ReadItem1(byte DataID, CPlayer_DataBuffer &Data, genericitem_full_t &outItem); - void ReadStorageItems1(byte DataID, CPlayer_DataBuffer &m_File); + void SkipStorageItems1(byte DataID, CPlayer_DataBuffer &m_File); void ReadCompanions1(byte DataID, CPlayer_DataBuffer &m_File); void SkipHelpTips1(byte DataID, CPlayer_DataBuffer &m_File); void ReadQuests1(byte DataID, CPlayer_DataBuffer &m_File); @@ -435,8 +430,8 @@ class CBasePlayer : public CMSMonster float m_TimeCanVote; bool m_fClientInitiated; //Client sent this command to the server. Make sure to cache any item changes so they don't get sent back to client bool m_fInTownArea; - mslist m_Storages; //Storage places where I have items - storageaccess_t m_CurrentStorage; //Currently accessing this storage + mslist m_Storage; //Items I have in storage + storageaccess_t m_CurrentStorage; //Set while I have my storage open bool m_Initialized; //Whether stats were created, script loaded, etc float m_TimeNextSave; bool m_CanJoin; //Whether I have any characters on this server that can join the game @@ -533,8 +528,13 @@ class CBasePlayer : public CMSMonster bool IsInAttackStance(); const char *DisplayName(); bool CanDamage(CBaseEntity *pOther); //Can I damage this entity? - void Storage_Open(const char* pszDisplayName, const char* pszStorageName, float flFeeRatio, entityinfo_t &Entity); - void Storage_Send(); + void Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller); + void Storage_Close(); + void Storage_CheckTeller(); + void Storage_UpdateClient(); + bool Storage_AddItem(genericitem_full_t &Item); + void Storage_RemoveItem(int Idx); + void Storage_SendItemUpdate(int Idx); std::tuple LearnSkill(int iStat, int iStatType, int EnemySkillLevel); bool LearnSkill(int iStat, int EnemySkillLevel); void SetQuest(bool SetData, const char* Name, const char* Data); @@ -628,8 +628,7 @@ class CBasePlayer : public CMSMonster const char *GetFullTitle(); //Calculate title with skill Ex. "23 berserker" void SetKeys(); void BlockButton(int Button); - storage_t *Storage_CreateAccount(storage_t &Storage); - storage_t *Storage_GetStorage(const char* pszStorageName); + static bool Storage_CanStoreItem(const char* pszItemName, int Properties); //Whether this kind of item is allowed in storage void InitialSpawn(void); bool Script_SetupEvent(CScript *Script, SCRIPT_EVENT &Event); const char *GetPartyName(); diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index 68582679c..a8a932b26 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1319,7 +1319,7 @@ void CBasePlayer::Deactivate() m_CharInfo[i].Destroy(); m_EntInfo.clear(); - m_Storages.clear(); + m_Storage.clear(); m_Quests.clear(); m_Maps.clear(); m_WearPositions.clear(); @@ -1393,21 +1393,19 @@ void CBasePlayer::LearnSpell(const char* pszSpellScript, bool fVerbose) #endif } -storage_t* CBasePlayer::Storage_GetStorage(const char* pszStorageName) +bool CBasePlayer::Storage_CanStoreItem(const char* pszItemName, int Properties) { - for (int s = 0; s < m_Storages.size(); s++) - if (m_Storages[s].Name == pszStorageName) - return &m_Storages[s]; + //Spells aren't saved with the character + if (FBitSet(Properties, ITEM_SPELL)) + return false; - return NULL; -} -storage_t* CBasePlayer::Storage_CreateAccount(storage_t& Storage) -{ - storage_t* pExistingStorage = Storage_GetStorage(Storage.Name); - if (pExistingStorage) - return pExistingStorage; + //MiB FEB2008a - the bank mask + const char* pszBlocked[] = {"pack_", "sheath_", "mana_", "health_", "proj_"}; + for (unsigned int i = 0; i < sizeof(pszBlocked) / sizeof(pszBlocked[0]); i++) + if (!strncmp(pszItemName, pszBlocked[i], strlen(pszBlocked[i]))) + return false; - return &m_Storages.add(Storage); + return true; } bool CBasePlayer::Script_SetupEvent(CScript* Script, SCRIPT_EVENT& Event) diff --git a/src/game/shared/ms/global.cpp b/src/game/shared/ms/global.cpp index 34ce7aa07..c53d54540 100644 --- a/src/game/shared/ms/global.cpp +++ b/src/game/shared/ms/global.cpp @@ -303,6 +303,7 @@ genericitem_full_t::genericitem_full_t(CGenericItem *pItem) : genericitem_t(pIte Location = pItem->m_Location; Spell_TimePrepare = pItem->Spell_TimePrepare; Spell_CastSuccess = pItem->Spell_CastSuccess; + Count = 1; if (FBitSet(pItem->MSProperties(), ITEM_CONTAINER)) for (int i = 0; i < pItem->Container_ItemCount(); i++) ContainerItems.add(pItem->Container_GetItem(i)); diff --git a/src/game/shared/weapons/genericitem.cpp b/src/game/shared/weapons/genericitem.cpp index 005ca9389..56baa2a63 100644 --- a/src/game/shared/weapons/genericitem.cpp +++ b/src/game/shared/weapons/genericitem.cpp @@ -2246,11 +2246,13 @@ CGenericItem* MSUtil_GetItemByID(ulong m_iId) } void SendGenericItem(CBasePlayer* pPlayer, CGenericItem* pItem, bool fNewMessage) { - //genericitem_full_t newItem = const_cast(*pItem); - //SendGenericItem(pPlayer, pItem, fNewMessage); - - // { genericitem_full_t Item(pItem); + SendGenericItem(pPlayer, Item, fNewMessage); +} + +//Writes at most 23 bytes per item. Storage batches these, so update STORAGE_ITEMS_PER_MSG if this grows +void SendGenericItem(CBasePlayer* pPlayer, genericitem_full_t& Item, bool fNewMessage) +{ if (fNewMessage) { MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_ITEM], NULL, pPlayer->pev); @@ -2284,43 +2286,6 @@ void SendGenericItem(CBasePlayer* pPlayer, CGenericItem* pItem, bool fNewMessage if (fNewMessage) MESSAGE_END(); } - -//MIB MAR2012 anti-overlow (see previous archives for original) -// void SendGenericItem(CBasePlayer* pPlayer, genericitem_full_t Item, bool fNewMessage) -// { -// if (fNewMessage) -// { -// MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_ITEM], NULL, pPlayer->pev); -// WRITE_BYTE(0); -// } -// WRITE_LONG(Item.ID); -// //WRITE_STRING( Item.Name ); -// int iList = -1; -// WRITE_LONG(CGenericItemMgr::LookUpItemIdx(Item.Name)); // MiB MAR2012_10 - Item index rather than script name -// WRITE_SHORT(Item.Location); -// WRITE_BYTE(Item.Hand); -// WRITE_SHORT(Item.Properties); -// if (FBitSet(Item.Properties, ITEM_GROUPABLE)) -// { -// WRITE_SHORT(Item.Quantity); -// } -// if (FBitSet(Item.Properties, ITEM_DRINKABLE)) -// { -// WRITE_BYTE(Item.Quality); -// } -// if (FBitSet(Item.Properties, ITEM_PERISHABLE)) -// { -// WRITE_SHORT(Item.Quality); -// WRITE_SHORT(Item.MaxQuality); -// } -// if (FBitSet(Item.Properties, ITEM_SPELL)) -// { -// WRITE_COORD(Item.Spell_TimePrepare); -// WRITE_BYTE(Item.Spell_CastSuccess); -// } -// if (fNewMessage) -// MESSAGE_END(); -// } #endif CGenericItem* MSUtil_GetItemByID(ulong m_iId, CMSMonster* pOwner) @@ -2391,6 +2356,48 @@ CGenericItem* ReadGenericItem(bool fAllowCreateNew) << ")\r\n";*/ return pItem; } +bool ReadGenericItem(genericitem_full_t& outItem) +{ + //Always read every field, even for a bad item, so the next item in the same message can still be read + outItem.ID = READ_LONG(); + int idx = READ_LONG(); + outItem.Location = READ_SHORT(); + outItem.Hand = READ_BYTE(); + outItem.Properties = (unsigned short)READ_SHORT(); + outItem.Quantity = 1; + outItem.Quality = outItem.MaxQuality = 0; + outItem.Weight = 0; + outItem.Spell_TimePrepare = 0; + outItem.Spell_CastSuccess = 0; + outItem.Count = 1; + if (FBitSet(outItem.Properties, ITEM_GROUPABLE)) + { + outItem.Quantity = READ_SHORT(); + } + if (FBitSet(outItem.Properties, ITEM_DRINKABLE)) + { + outItem.Quality = READ_BYTE(); + } + if (FBitSet(outItem.Properties, ITEM_PERISHABLE)) + { + outItem.Quality = READ_SHORT(); + outItem.MaxQuality = READ_SHORT(); + } + if (FBitSet(outItem.Properties, ITEM_SPELL)) + { + outItem.Spell_TimePrepare = READ_COORD(); + outItem.Spell_CastSuccess = READ_BYTE(); + } + + if (idx < 0 || idx >= CGenericItemMgr::ItemCount()) + { + MS_ERROR("ReadGenericItem: Bad item index %i ID: %i", idx, (int)outItem.ID); + return false; + } + + outItem.Name = CGenericItemMgr::Item(idx)->Name; + return true; +} #endif //splayviewanim diff --git a/src/game/shared/weapons/genericitem.h b/src/game/shared/weapons/genericitem.h index 5cd6b3507..e93d40a6e 100644 --- a/src/game/shared/weapons/genericitem.h +++ b/src/game/shared/weapons/genericitem.h @@ -431,11 +431,13 @@ class CGenericItem : public CBasePlayerItem bool bDropAttempted = false; int iDropTickCounter = 0; }; +struct genericitem_full_t; #ifdef VALVE_DLL void SendGenericItem(CBasePlayer *pPlayer, CGenericItem *pItem, bool fNewMessage); -//void SendGenericItem(CBasePlayer *pPlayer, CGenericItem *pItem, bool fNewMessage); +void SendGenericItem(CBasePlayer *pPlayer, genericitem_full_t &Item, bool fNewMessage); //Sends an item that only exists as data (storage) #else CGenericItem *ReadGenericItem(bool fAllowCreateNew); +bool ReadGenericItem(genericitem_full_t &outItem); //Reads an item as data, without creating it (storage) #endif void BeamEffect(Vector vStart, Vector vEnd, int sprite, int startframe, From d3be883919a4c191ed1c99ab31f956efd6048666 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:08:58 -0500 Subject: [PATCH 02/14] more storage work --- src/game/client/ui/ms/vgui_storage.cpp | 46 +++++----- src/game/server/character/mscharacterheader.h | 20 ++--- src/game/server/character/sv_character.cpp | 54 ++++++----- src/game/server/client.cpp | 27 +++--- src/game/server/player/player.cpp | 84 +++++++---------- src/game/server/player/player.h | 17 ++-- src/game/server/player/playershared.cpp | 4 +- src/game/shared/ms/global.cpp | 1 - src/game/shared/weapons/genericitem.cpp | 89 +++++++++---------- src/game/shared/weapons/genericitem.h | 4 +- 10 files changed, 172 insertions(+), 174 deletions(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index cbb083ce2..7ca5744e4 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -84,19 +84,19 @@ void CStoragePanel::AddInventoryItems() //Add the storage items for (int i = 0; i < player.m_Storage.size(); i++) { - genericitem_full_t &Item = player.m_Storage[i]; - containeritem_t vNewItem = containeritem_t(Item); + storageitem_t &Item = player.m_Storage[i]; - //Groupable items are stacked by quantity, everything else by count - int Amt = FBitSet(Item.Properties, ITEM_GROUPABLE) ? Item.Quantity : Item.Count; - vNewItem.Quantity = Amt; - if (Amt > 1) - { - //The item's name comes from the global item, which doesn't know the amount. Name it the way SPEECH::ItemName does - CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(Item.Name, true); - if (pGlobalItem) - vNewItem.setFullName(msstring() + Amt + " " + pGlobalItem->DisplayName() + "s"); - } + //Storage only knows the item's name, so the rest comes from the global item + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(Item.Name, true); + if (!pGlobalItem) + continue; + + containeritem_t vNewItem = containeritem_t(pGlobalItem); + vNewItem.ID = Item.ID; + vNewItem.Quantity = Item.Quantity; + vNewItem.Quality = Item.Quality; + if (Item.Quantity > 1) + vNewItem.setFullName(msstring() + (int)Item.Quantity + " " + pGlobalItem->DisplayName() + "s"); //Named the way SPEECH::ItemName does pGearItemButton->m_ItemContainer->AddItem(vNewItem); } @@ -274,11 +274,19 @@ void Storage_ItemMsg() int Count = READ_BYTE(); for (int i = 0; i < Count; i++) { - genericitem_full_t Item; - bool Valid = ReadGenericItem(Item); - Item.Count = READ_SHORT(); //Always read, so the next item can still be read - if (Valid) - player.m_Storage.add(Item); + storageitem_t Item; + Item.ID = READ_LONG(); + int idx = READ_LONG(); + Item.Quantity = READ_SHORT(); + Item.Quality = READ_SHORT(); + Item.MaxQuality = READ_SHORT(); + + //Everything is read before the item is checked, so the next item can still be read + if (idx < 0 || idx >= CGenericItemMgr::ItemCount()) + continue; + + Item.Name = CGenericItemMgr::Item(idx)->Name; + player.m_Storage.add(Item); } Storage_Update(); @@ -298,17 +306,15 @@ void Storage_ItemRemoveMsg() Storage_Update(); } -//The amount of an item in my storage changed +//The quantity of an item in my storage changed void Storage_ItemUpdateMsg() { ulong ID = READ_LONG(); int Quantity = READ_SHORT(); - int Count = READ_SHORT(); for (int i = 0; i < player.m_Storage.size(); i++) if (player.m_Storage[i].ID == ID) { player.m_Storage[i].Quantity = Quantity; - player.m_Storage[i].Count = Count; break; } diff --git a/src/game/server/character/mscharacterheader.h b/src/game/server/character/mscharacterheader.h index 7c3406ded..ad1110841 100644 --- a/src/game/server/character/mscharacterheader.h +++ b/src/game/server/character/mscharacterheader.h @@ -88,11 +88,10 @@ enum charfield_e CF_SKILLS = 33, //[ stat: [ substat: [value, exp], ... ], ... ] CF_SPELLS = 34, //[str, ...] CF_ITEMS = 35, //[item, ...] - CF_STORAGES = 36, //RETIRED - [storage, ...] named storages. Still read, to move their items into CF_STORAGE - CF_COMPANIONS = 37, //[companion, ...] - CF_QUESTS = 38, //[ [name, data], ... ] - CF_QUICKSLOTS = 39, //[ nil | [type, id], ... ] - CF_STORAGE = 40, //[item, ...] - items in storage + CF_COMPANIONS = 36, //[companion, ...] + CF_QUESTS = 37, //[ [name, data], ... ] + CF_QUICKSLOTS = 38, //[ nil | [type, id], ... ] + CF_STORAGE = 39, //[storageitem, ...] }; //Item keys @@ -107,14 +106,15 @@ enum charitemfield_e CI_MAXQUALITY = 6, //int - perishable/drinkable only CI_QUANTITY = 7, //int - groupable only CI_CONTENTS = 8, //[item, ...] - containers only - CI_COUNT = 9, //int - storage only: identical items stacked in this entry (absent = 1) }; -//Storage keys - only used to read the retired CF_STORAGES -enum charstoragefield_e +//Storage item keys +enum charstorageitemfield_e { - CS_NAME = 0, //str - no longer read - CS_ITEMS = 1, //[item, ...] + CSI_NAME = 0, //str - item script name + CSI_QUANTITY = 1, //int - how many. For a groupable item, the size of the stack + CSI_QUALITY = 2, //int + CSI_MAXQUALITY = 3, //int }; //Companion keys diff --git a/src/game/server/character/sv_character.cpp b/src/game/server/character/sv_character.cpp index 1654fb1cc..6205b1aa2 100644 --- a/src/game/server/character/sv_character.cpp +++ b/src/game/server/character/sv_character.cpp @@ -257,7 +257,6 @@ static void ReadItemListPack(const msgpack::object &Obj, mslist Items; - msgpack::object_map Fields = PackMap(Storages.ptr[s]); + storageitem_t Item; + Item.Quantity = Item.Quality = Item.MaxQuality = 0; + + msgpack::object_map Fields = PackMap(Items.ptr[i]); for (uint32_t f = 0; f < Fields.size; f++) - if (PackReadInt(Fields.ptr[f].key, -1) == CS_ITEMS) - ReadItemListPack(Fields.ptr[f].val, Items); + { + const msgpack::object &FieldValue = Fields.ptr[f].val; + switch (PackReadInt(Fields.ptr[f].key, -1)) + { + case CSI_NAME: Item.Name = PackReadMsStr(FieldValue); break; + case CSI_QUANTITY: Item.Quantity = (unsigned short)PackReadInt(FieldValue); break; + case CSI_QUALITY: Item.Quality = (unsigned short)PackReadInt(FieldValue); break; + case CSI_MAXQUALITY: Item.MaxQuality = (unsigned short)PackReadInt(FieldValue); break; + } + } - for (int i = 0; i < Items.size(); i++) + if (!Item.Name.len() || !Item.Quantity) { - Items[i].ID = m_Storage.size(); //The IDs were only unique within each named storage - m_Storage.add(Items[i]); + MS_ERROR("ReadCharData: Bad storage item, skipping..."); + continue; } + + ApplyItemAlias(Item.Name); + Item.ID = m_Storage.size(); //IDs aren't saved. They only have to be unique + m_Storage.add(Item); } break; } @@ -1003,9 +1013,6 @@ static bool PackItem(charpack_t &Out, genericitem_full_t &Item) if (FBitSet(Item.Properties, ITEM_GROUPABLE)) Fields.Field(CI_QUANTITY, Item.Quantity); - if (Item.Count > 1) - Fields.Field(CI_COUNT, Item.Count); - if (FBitSet(Item.Properties, ITEM_CONTAINER)) { Fields.Key(CI_CONTENTS); @@ -1105,11 +1112,16 @@ static void PackChar(charpack_t &Out, CBasePlayer *pPlayer, savedata_t &Data) //Storage items Root.Key(CF_STORAGE); + Out.Pk.pack_array(pPlayer->m_Storage.size()); + for (int i = 0; i < pPlayer->m_Storage.size(); i++) { - CPackScope Items(Out, false); - for (int i = 0; i < pPlayer->m_Storage.size(); i++) - if (PackItem(Out, pPlayer->m_Storage[i])) - Items.Added(); + storageitem_t &Item = pPlayer->m_Storage[i]; + + CPackScope Fields(Out, true); + Fields.FieldStr(CSI_NAME, Item.Name); + Fields.Field(CSI_QUANTITY, Item.Quantity); + Fields.Field(CSI_QUALITY, Item.Quality); + Fields.Field(CSI_MAXQUALITY, Item.MaxQuality); } //Companions - save any variables that start with "companion.save." diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index dcaf9ccfc..5426a223b 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -945,8 +945,7 @@ void ClientCommand2(edict_t *pEntity) pPlayer->SendEventMsg(HUDEVENT_UNABLE, msstring("You can't put ") + SPEECH::ItemName(pItem) + " in storage"); else if (pItem) { - genericitem_full_t Item(pItem); - if (pPlayer->Storage_AddItem(Item)) + if (pPlayer->Storage_AddItem(pItem)) { pPlayer->RemoveItem(pItem); pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You put ") + SPEECH::ItemName(pItem) + " in storage"); @@ -962,7 +961,7 @@ void ClientCommand2(edict_t *pEntity) return; int ItemID = atoi(CMD_ARGV(2)); - genericitem_full_t *pStorageItem = NULL; + storageitem_t *pStorageItem = NULL; int StorageItemIdx = 0; for (unsigned int i = 0; i < pPlayer->m_Storage.size(); i++) if (pPlayer->m_Storage[i].ID == ItemID) @@ -974,18 +973,18 @@ void ClientCommand2(edict_t *pEntity) if (pStorageItem) { - //Create it as a new item, without the location and hand it had when it was stored - CGenericItem *pItem = pStorageItem->genericitem_t::operator CGenericItem *(); + CGenericItem *pItem = NewGenericItem(pStorageItem->Name); if (pItem) { - //If it's a container, put back the items that were inside it - if (FBitSet(pItem->MSProperties(), ITEM_CONTAINER)) - for (int i = 0; i < pStorageItem->ContainerItems.size(); i++) - { - CGenericItem *pContainedItem = pStorageItem->ContainerItems[i]; - if (pContainedItem && !pItem->Container_AddItem(pContainedItem)) - pContainedItem->SUB_Remove(); - } + //A groupable item comes out as the whole stack. Anything else comes out one at a time + int Amt = 1; + if (FBitSet(pItem->MSProperties(), ITEM_GROUPABLE)) + { + Amt = pStorageItem->Quantity; + pItem->iQuantity = Amt; + } + pItem->Quality = pStorageItem->Quality; + pItem->MaxQuality = pStorageItem->MaxQuality; int ContainerID = atol(CMD_ARGV(3)); bool Success = false; @@ -1002,7 +1001,7 @@ void ClientCommand2(edict_t *pEntity) if (Success) { pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + SPEECH::ItemName(pItem) + " from storage"); - pPlayer->Storage_RemoveItem(StorageItemIdx); + pPlayer->Storage_RemoveItem(StorageItemIdx, Amt); } else pItem->SUB_Remove(); diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 56367c278..560cb291c 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6682,7 +6682,7 @@ bool CBasePlayer::IsInAttackStance() return false; } #define STORAGE_TELLER_RANGE 128 //Same range as CMSMonster::Trade -#define STORAGE_ITEMS_PER_MSG 7 //2 + (7 * 25 bytes max per item) = 177, which fits in MAX_USERMESSAGE_SIZE +#define STORAGE_ITEMS_PER_MSG 13 //2 + (13 * 14 bytes per item) = 184, which fits in MAX_USERMESSAGE_SIZE void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller) { @@ -6738,35 +6738,34 @@ void CBasePlayer::Storage_UpdateClient() WRITE_BYTE(Count); for (int i = 0; i < Count; i++) { - genericitem_full_t &Item = m_Storage[m_CurrentStorage.ClientItems + i]; - SendGenericItem(this, Item, false); - WRITE_SHORT(Item.Count); + storageitem_t &Item = m_Storage[m_CurrentStorage.ClientItems + i]; + WRITE_LONG(Item.ID); + WRITE_LONG(CGenericItemMgr::LookUpItemIdx(Item.Name)); + WRITE_SHORT(Item.Quantity); + WRITE_SHORT(Item.Quality); + WRITE_SHORT(Item.MaxQuality); } MESSAGE_END(); m_CurrentStorage.ClientItems += Count; } -//How many more of Item can stack onto Cur, an item already in storage. 0 if they aren't identical. -//Groupable items stack by quantity, like they do in a pack. Everything else stacks by count. -//Containers don't stack, because of the items inside them -static int Storage_StackRoom(genericitem_full_t &Cur, genericitem_full_t &Item) +//How many more of Item can stack onto Cur, an item already in storage. 0 if they aren't identical +static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) { - if (FBitSet(Item.Properties, ITEM_CONTAINER) || Cur.Name != Item.Name || Cur.Properties != Item.Properties) + if (Cur.Name != Item.Name || Cur.Quality != Item.Quality || Cur.MaxQuality != Item.MaxQuality) return 0; - if (FBitSet(Item.Properties, ITEM_GROUPABLE)) - return V_max(NUM_MAX_STACK - (int)Cur.Quantity, 0); - - if (Cur.Quality != Item.Quality || Cur.MaxQuality != Item.MaxQuality) - return 0; - - return V_max(NUM_MAX_STACK - (int)Cur.Count, 0); + return V_max(NUM_MAX_STACK - (int)Cur.Quantity, 0); } -bool CBasePlayer::Storage_AddItem(genericitem_full_t &Item) +bool CBasePlayer::Storage_AddItem(CGenericItem *pItem) { //Adds an item to my storage, stacking it with identical items. //Returns false if the storage is full - bool Groupable = FBitSet(Item.Properties, ITEM_GROUPABLE) ? true : false; + storageitem_t Item; + Item.Name = pItem->ItemName; + Item.Quantity = FBitSet(pItem->MSProperties(), ITEM_GROUPABLE) ? V_max(pItem->iQuantity, 1) : 1; + Item.Quality = pItem->Quality; + Item.MaxQuality = pItem->MaxQuality; //A full storage can still take an item that stacks completely onto the items already there. //Check before changing anything, so that a stack is never left half stored @@ -6776,38 +6775,25 @@ bool CBasePlayer::Storage_AddItem(genericitem_full_t &Item) for (int i = 0; i < m_Storage.size(); i++) Room += Storage_StackRoom(m_Storage[i], Item); - if (Room < (Groupable ? (int)Item.Quantity : 1)) + if (Room < (int)Item.Quantity) return false; } - for (int i = 0; i < m_Storage.size(); i++) + for (int i = 0; i < m_Storage.size() && Item.Quantity; i++) { - genericitem_full_t &Cur = m_Storage[i]; - int Room = Storage_StackRoom(Cur, Item); - if (Room <= 0) + int Amt = V_min((int)Item.Quantity, Storage_StackRoom(m_Storage[i], Item)); + if (Amt <= 0) continue; - if (Groupable) - { - int Amt = V_min((int)Item.Quantity, Room); - if (Amt <= 0) - continue; - - Cur.Quantity += Amt; - Item.Quantity -= Amt; - Storage_SendItemUpdate(i); - if (!Item.Quantity) - return true; - } - else - { - Cur.Count++; - Storage_SendItemUpdate(i); - return true; - } + m_Storage[i].Quantity += Amt; + Item.Quantity -= Amt; + Storage_SendItemUpdate(i); } - //Determine a unique ID for the item in storage + if (!Item.Quantity) + return true; + + //What's left becomes a new item in storage. Determine a unique ID for it for (unsigned int ID = 0; ID < SHRT_MAX; ID++) { bool Duplicate = false; @@ -6823,14 +6809,13 @@ bool CBasePlayer::Storage_AddItem(genericitem_full_t &Item) break; } - Item.Count = 1; m_Storage.add(Item); //Storage_UpdateClient sends it return true; } void CBasePlayer::Storage_SendItemUpdate(int Idx) { - //The amount of an item in my storage changed. - //Items past ClientItems haven't been sent yet, so they get sent with the new amount + //The quantity of an item in my storage changed. + //Items past ClientItems haven't been sent yet, so they get sent with the new quantity if (Idx >= m_CurrentStorage.ClientItems) return; @@ -6838,15 +6823,14 @@ void CBasePlayer::Storage_SendItemUpdate(int Idx) WRITE_BYTE(4); //update storage item WRITE_LONG(m_Storage[Idx].ID); WRITE_SHORT(m_Storage[Idx].Quantity); - WRITE_SHORT(m_Storage[Idx].Count); MESSAGE_END(); } -void CBasePlayer::Storage_RemoveItem(int Idx) +void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) { - //Removes one item from my storage. If it was stacked by count, the rest of the stack stays - if (m_Storage[Idx].Count > 1) + //Takes Amt of an item out of my storage. The item stays in storage until its quantity runs out + if (m_Storage[Idx].Quantity > Amt) { - m_Storage[Idx].Count--; + m_Storage[Idx].Quantity -= Amt; Storage_SendItemUpdate(Idx); return; } diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index 2f3c8f5a1..eae242d9e 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -308,9 +308,16 @@ struct genericitem_full_t : genericitem_t byte Hand; float Spell_TimePrepare; byte Spell_CastSuccess; - unsigned short Count; //Storage only: identical items stacked in this entry. Groupable items stack by Quantity instead mslist ContainerItems; }; +//An item in storage. Only what's needed to create the item again is kept +struct storageitem_t +{ + ulong ID; //Unique within the storage. Not saved + msstring Name; //Item script name + unsigned short Quantity; //How many. For a groupable item, the size of the stack + unsigned short Quality, MaxQuality; +}; struct storageaccess_t { bool Active; @@ -360,7 +367,7 @@ struct chardata_t : savedata_t statlist m_Stats; //All stats. Natural stats and Skill-based stats std::vector m_Spells; //All known spells mslist m_Items; //All carried items - mslist m_Storage; //All items in storage + mslist m_Storage; //All items in storage mslist m_Companions; //Companions mslist m_Quests; //All the quests I've completed fixed_vector m_QuickSlots; //All the quickslots (based on the item IDs of the last save). Extras in a save are dropped @@ -430,7 +437,7 @@ class CBasePlayer : public CMSMonster float m_TimeCanVote; bool m_fClientInitiated; //Client sent this command to the server. Make sure to cache any item changes so they don't get sent back to client bool m_fInTownArea; - mslist m_Storage; //Items I have in storage + mslist m_Storage; //Items I have in storage storageaccess_t m_CurrentStorage; //Set while I have my storage open bool m_Initialized; //Whether stats were created, script loaded, etc float m_TimeNextSave; @@ -532,8 +539,8 @@ class CBasePlayer : public CMSMonster void Storage_Close(); void Storage_CheckTeller(); void Storage_UpdateClient(); - bool Storage_AddItem(genericitem_full_t &Item); - void Storage_RemoveItem(int Idx); + bool Storage_AddItem(CGenericItem *pItem); + void Storage_RemoveItem(int Idx, int Amt); void Storage_SendItemUpdate(int Idx); std::tuple LearnSkill(int iStat, int iStatType, int EnemySkillLevel); bool LearnSkill(int iStat, int EnemySkillLevel); diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index a8a932b26..3cc3ffbe0 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1395,8 +1395,8 @@ void CBasePlayer::LearnSpell(const char* pszSpellScript, bool fVerbose) bool CBasePlayer::Storage_CanStoreItem(const char* pszItemName, int Properties) { - //Spells aren't saved with the character - if (FBitSet(Properties, ITEM_SPELL)) + //Spells aren't saved with the character, and storage doesn't keep what's inside a container + if (FBitSet(Properties, ITEM_SPELL | ITEM_CONTAINER)) return false; //MiB FEB2008a - the bank mask diff --git a/src/game/shared/ms/global.cpp b/src/game/shared/ms/global.cpp index c53d54540..34ce7aa07 100644 --- a/src/game/shared/ms/global.cpp +++ b/src/game/shared/ms/global.cpp @@ -303,7 +303,6 @@ genericitem_full_t::genericitem_full_t(CGenericItem *pItem) : genericitem_t(pIte Location = pItem->m_Location; Spell_TimePrepare = pItem->Spell_TimePrepare; Spell_CastSuccess = pItem->Spell_CastSuccess; - Count = 1; if (FBitSet(pItem->MSProperties(), ITEM_CONTAINER)) for (int i = 0; i < pItem->Container_ItemCount(); i++) ContainerItems.add(pItem->Container_GetItem(i)); diff --git a/src/game/shared/weapons/genericitem.cpp b/src/game/shared/weapons/genericitem.cpp index 56baa2a63..005ca9389 100644 --- a/src/game/shared/weapons/genericitem.cpp +++ b/src/game/shared/weapons/genericitem.cpp @@ -2246,13 +2246,11 @@ CGenericItem* MSUtil_GetItemByID(ulong m_iId) } void SendGenericItem(CBasePlayer* pPlayer, CGenericItem* pItem, bool fNewMessage) { - genericitem_full_t Item(pItem); - SendGenericItem(pPlayer, Item, fNewMessage); -} + //genericitem_full_t newItem = const_cast(*pItem); + //SendGenericItem(pPlayer, pItem, fNewMessage); -//Writes at most 23 bytes per item. Storage batches these, so update STORAGE_ITEMS_PER_MSG if this grows -void SendGenericItem(CBasePlayer* pPlayer, genericitem_full_t& Item, bool fNewMessage) -{ + // { + genericitem_full_t Item(pItem); if (fNewMessage) { MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_ITEM], NULL, pPlayer->pev); @@ -2286,6 +2284,43 @@ void SendGenericItem(CBasePlayer* pPlayer, genericitem_full_t& Item, bool fNewMe if (fNewMessage) MESSAGE_END(); } + +//MIB MAR2012 anti-overlow (see previous archives for original) +// void SendGenericItem(CBasePlayer* pPlayer, genericitem_full_t Item, bool fNewMessage) +// { +// if (fNewMessage) +// { +// MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_ITEM], NULL, pPlayer->pev); +// WRITE_BYTE(0); +// } +// WRITE_LONG(Item.ID); +// //WRITE_STRING( Item.Name ); +// int iList = -1; +// WRITE_LONG(CGenericItemMgr::LookUpItemIdx(Item.Name)); // MiB MAR2012_10 - Item index rather than script name +// WRITE_SHORT(Item.Location); +// WRITE_BYTE(Item.Hand); +// WRITE_SHORT(Item.Properties); +// if (FBitSet(Item.Properties, ITEM_GROUPABLE)) +// { +// WRITE_SHORT(Item.Quantity); +// } +// if (FBitSet(Item.Properties, ITEM_DRINKABLE)) +// { +// WRITE_BYTE(Item.Quality); +// } +// if (FBitSet(Item.Properties, ITEM_PERISHABLE)) +// { +// WRITE_SHORT(Item.Quality); +// WRITE_SHORT(Item.MaxQuality); +// } +// if (FBitSet(Item.Properties, ITEM_SPELL)) +// { +// WRITE_COORD(Item.Spell_TimePrepare); +// WRITE_BYTE(Item.Spell_CastSuccess); +// } +// if (fNewMessage) +// MESSAGE_END(); +// } #endif CGenericItem* MSUtil_GetItemByID(ulong m_iId, CMSMonster* pOwner) @@ -2356,48 +2391,6 @@ CGenericItem* ReadGenericItem(bool fAllowCreateNew) << ")\r\n";*/ return pItem; } -bool ReadGenericItem(genericitem_full_t& outItem) -{ - //Always read every field, even for a bad item, so the next item in the same message can still be read - outItem.ID = READ_LONG(); - int idx = READ_LONG(); - outItem.Location = READ_SHORT(); - outItem.Hand = READ_BYTE(); - outItem.Properties = (unsigned short)READ_SHORT(); - outItem.Quantity = 1; - outItem.Quality = outItem.MaxQuality = 0; - outItem.Weight = 0; - outItem.Spell_TimePrepare = 0; - outItem.Spell_CastSuccess = 0; - outItem.Count = 1; - if (FBitSet(outItem.Properties, ITEM_GROUPABLE)) - { - outItem.Quantity = READ_SHORT(); - } - if (FBitSet(outItem.Properties, ITEM_DRINKABLE)) - { - outItem.Quality = READ_BYTE(); - } - if (FBitSet(outItem.Properties, ITEM_PERISHABLE)) - { - outItem.Quality = READ_SHORT(); - outItem.MaxQuality = READ_SHORT(); - } - if (FBitSet(outItem.Properties, ITEM_SPELL)) - { - outItem.Spell_TimePrepare = READ_COORD(); - outItem.Spell_CastSuccess = READ_BYTE(); - } - - if (idx < 0 || idx >= CGenericItemMgr::ItemCount()) - { - MS_ERROR("ReadGenericItem: Bad item index %i ID: %i", idx, (int)outItem.ID); - return false; - } - - outItem.Name = CGenericItemMgr::Item(idx)->Name; - return true; -} #endif //splayviewanim diff --git a/src/game/shared/weapons/genericitem.h b/src/game/shared/weapons/genericitem.h index e93d40a6e..5cd6b3507 100644 --- a/src/game/shared/weapons/genericitem.h +++ b/src/game/shared/weapons/genericitem.h @@ -431,13 +431,11 @@ class CGenericItem : public CBasePlayerItem bool bDropAttempted = false; int iDropTickCounter = 0; }; -struct genericitem_full_t; #ifdef VALVE_DLL void SendGenericItem(CBasePlayer *pPlayer, CGenericItem *pItem, bool fNewMessage); -void SendGenericItem(CBasePlayer *pPlayer, genericitem_full_t &Item, bool fNewMessage); //Sends an item that only exists as data (storage) +//void SendGenericItem(CBasePlayer *pPlayer, CGenericItem *pItem, bool fNewMessage); #else CGenericItem *ReadGenericItem(bool fAllowCreateNew); -bool ReadGenericItem(genericitem_full_t &outItem); //Reads an item as data, without creating it (storage) #endif void BeamEffect(Vector vStart, Vector vEnd, int sprite, int startframe, From 57a0c07e0c2bca8473e1dd0c5a0c3b14b4a49662 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 20:58:32 -0500 Subject: [PATCH 03/14] make storage work better with scripts --- src/game/server/client.cpp | 2 +- .../server/gamerules/multiplay_gamerules.cpp | 1 + src/game/server/monsters/npcscript.cpp | 9 ++++-- src/game/server/player/player.cpp | 29 +++++++++++++++++-- src/game/server/player/player.h | 4 ++- src/game/server/player/playershared.cpp | 2 +- 6 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index 5426a223b..5d088bf51 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -931,7 +931,7 @@ void ClientCommand2(edict_t *pEntity) return; if (!_stricmp(CMD_ARGV(1), "stop")) - pPlayer->m_CurrentStorage.Active = false; + pPlayer->Storage_End(); if (CMD_ARGC() < 3) return; diff --git a/src/game/server/gamerules/multiplay_gamerules.cpp b/src/game/server/gamerules/multiplay_gamerules.cpp index 31cafb45f..4b563df43 100644 --- a/src/game/server/gamerules/multiplay_gamerules.cpp +++ b/src/game/server/gamerules/multiplay_gamerules.cpp @@ -437,6 +437,7 @@ void CHalfLifeMultiplay::ClientDisconnected( edict_t *pClient ) if( pPlayer && pPlayer->IsPlayer() ) { pPlayer->SaveChar(); // Save data on disconnect! TODO: should this count for non-FN as well? + pPlayer->Storage_End(); // Tell the entity that opened this player's storage that they're done with it FireTargets( "game_playerleave", pPlayer, pPlayer, USE_TOGGLE, 0 ); diff --git a/src/game/server/monsters/npcscript.cpp b/src/game/server/monsters/npcscript.cpp index 4acf3023d..889d06242 100644 --- a/src/game/server/monsters/npcscript.cpp +++ b/src/game/server/monsters/npcscript.cpp @@ -1390,6 +1390,7 @@ bool CMSMonster::Script_ExecuteCmd(CScript *Script, SCRIPT_EVENT &Event, scriptc { //trade target displayname cb_prefix //Opens the target player's storage. The player has to stay near me to keep using it + //Calls cb_prefix_success now, and cb_prefix_done when the player is finished. Both get the player as PARAM1 if (Params.size() >= 3 && Params[0] == "trade") { CBaseEntity *pTarget = RetrieveEntity(Params[1]); @@ -1397,8 +1398,12 @@ bool CMSMonster::Script_ExecuteCmd(CScript *Script, SCRIPT_EVENT &Event, scriptc { msstring Callback = Params.size() >= 4 ? Params[3].c_str() : "game_storage_trade"; - ((CBasePlayer *)pTarget)->Storage_Open(Params[2], this); - CallScriptEvent(Callback + "_success"); + ((CBasePlayer *)pTarget)->Storage_Open(Params[2], this, Callback); + + static msstringlist Parameters; + Parameters.clearitems(); + Parameters.add(EntToString(pTarget)); + CallScriptEvent(Callback + "_success", &Parameters); } } else diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 560cb291c..453fdd5be 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6684,11 +6684,16 @@ bool CBasePlayer::IsInAttackStance() #define STORAGE_TELLER_RANGE 128 //Same range as CMSMonster::Trade #define STORAGE_ITEMS_PER_MSG 13 //2 + (13 * 14 bytes per item) = 184, which fits in MAX_USERMESSAGE_SIZE -void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller) +void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller, const char* pszCallback) { + //Copied first, because ending the last use of my storage runs a script event + msstring DisplayName = pszDisplayName, Callback = pszCallback; + Storage_End(); //In case I still have it open through another entity + m_CurrentStorage.Active = true; - m_CurrentStorage.DisplayName = pszDisplayName; + m_CurrentStorage.DisplayName = DisplayName; m_CurrentStorage.Teller = pTeller; + m_CurrentStorage.Callback = Callback; m_CurrentStorage.ClientItems = 0; MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_ITEM], NULL, pev); @@ -6701,10 +6706,28 @@ void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller) WRITE_BYTE(1); //reset storage items MESSAGE_END(); } -void CBasePlayer::Storage_Close() +void CBasePlayer::Storage_End() { + //I'm done using my storage. Tell the entity that opened it + if (!m_CurrentStorage.Active) + return; + m_CurrentStorage.Active = false; + CBaseEntity *pTeller = m_CurrentStorage.Teller.Entity(); + IScripted *pScripted = pTeller ? pTeller->GetScripted() : NULL; + if (pScripted) + { + msstringlist Parameters; + Parameters.add(EntToString(this)); + pScripted->CallScriptEvent(m_CurrentStorage.Callback + "_done", &Parameters); + } +} +void CBasePlayer::Storage_Close() +{ + //Makes the client close the storage too + Storage_End(); + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_VGUIMENU], NULL, pev); WRITE_BYTE(0); MESSAGE_END(); diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index eae242d9e..aceff131b 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -323,6 +323,7 @@ struct storageaccess_t bool Active; msstring DisplayName; entityinfo_t Teller; //The entity that opened my storage. I must stay near it to keep using it + msstring Callback; //Prefix of the script events called on the teller int ClientItems; //Number of items in my storage that the client knows about }; @@ -535,7 +536,8 @@ class CBasePlayer : public CMSMonster bool IsInAttackStance(); const char *DisplayName(); bool CanDamage(CBaseEntity *pOther); //Can I damage this entity? - void Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller); + void Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller, const char* pszCallback); + void Storage_End(); void Storage_Close(); void Storage_CheckTeller(); void Storage_UpdateClient(); diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index 3cc3ffbe0..3e48c579e 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1400,7 +1400,7 @@ bool CBasePlayer::Storage_CanStoreItem(const char* pszItemName, int Properties) return false; //MiB FEB2008a - the bank mask - const char* pszBlocked[] = {"pack_", "sheath_", "mana_", "health_", "proj_"}; + const char* pszBlocked[] = {"pack_", "sheath_"}; for (unsigned int i = 0; i < sizeof(pszBlocked) / sizeof(pszBlocked[0]); i++) if (!strncmp(pszItemName, pszBlocked[i], strlen(pszBlocked[i]))) return false; From 94f0401d62f9420a523b436825a702707c878cc9 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:12:49 -0500 Subject: [PATCH 04/14] add double click to take item out of storage --- src/game/client/ui/ms/vgui_storage.cpp | 13 +++++++++++++ src/game/client/ui/ms/vgui_storage.h | 1 + src/game/server/client.cpp | 7 ++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index 7ca5744e4..ad9b4d260 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -180,6 +180,19 @@ bool CStoragePanel::GearItemClicked(ulong ID) return true; } +void CStoragePanel::ItemDoubleclicked(ulong ID) +{ + //Double clicking an item in storage takes it out and puts it in my hands + if (m_LastGearItem != 1) + return; + + UnSelectAllItems(); + m_InfoPanel->setVisible(false); + + msstring Command = msstring("storage remove ") + (int)ID + " hands\n"; + gEngfuncs.pfnClientCmd(Command); +} + //MiB FEB2008a bool CStoragePanel::checkValid(containeritem_t &item) { diff --git a/src/game/client/ui/ms/vgui_storage.h b/src/game/client/ui/ms/vgui_storage.h index 43af88864..478cd60a6 100644 --- a/src/game/client/ui/ms/vgui_storage.h +++ b/src/game/client/ui/ms/vgui_storage.h @@ -18,6 +18,7 @@ class CStoragePanel : public CStorePanel void ItemHighlighted(void *pData); void GearItemSelected(ulong ID); bool GearItemClicked(ulong ID); + void ItemDoubleclicked(ulong ID); bool checkValid(containeritem_t &Item); //MiB Feb2008a static const char* Text_Subtitle_Storage; diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index 5d088bf51..fe4056964 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -987,9 +987,12 @@ void ClientCommand2(edict_t *pEntity) pItem->MaxQuality = pStorageItem->MaxQuality; int ContainerID = atol(CMD_ARGV(3)); + bool ToHands = !_stricmp(CMD_ARGV(3), "hands"); //Sent when an item in storage is double clicked bool Success = false; - if (!ContainerID) + if (ToHands) + Success = pItem->GiveTo(pPlayer, false, true, true); //Only into my hands. What I'm holding is put away to make room + else if (!ContainerID) Success = pItem->GiveTo(pPlayer, true, true); else { @@ -1002,6 +1005,8 @@ void ClientCommand2(edict_t *pEntity) { pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + SPEECH::ItemName(pItem) + " from storage"); pPlayer->Storage_RemoveItem(StorageItemIdx, Amt); + if (ToHands) + pPlayer->Storage_Close(); //Like the inventory, taking an item into my hands closes the storage } else pItem->SUB_Remove(); From 3c10beb101ad5d62d7e2cef652240bd38e6caf5a Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:53:08 -0500 Subject: [PATCH 05/14] convert old storage items to new one --- src/game/server/monsters/npcscript.cpp | 10 ++++++++ src/game/server/player/player.cpp | 32 ++++++++++++++++++++++++-- src/game/server/player/player.h | 2 ++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/game/server/monsters/npcscript.cpp b/src/game/server/monsters/npcscript.cpp index 889d06242..605e7768e 100644 --- a/src/game/server/monsters/npcscript.cpp +++ b/src/game/server/monsters/npcscript.cpp @@ -1406,6 +1406,16 @@ bool CMSMonster::Script_ExecuteCmd(CScript *Script, SCRIPT_EVENT &Event, scriptc CallScriptEvent(Callback + "_success", &Parameters); } } + //add target itemname quantity quality + else if (Params.size() >= 3 && Params[0] == "add") + { + int Added = 0; + CBaseEntity *pTarget = RetrieveEntity(Params[1]); + if (pTarget && pTarget->IsPlayer()) + Added = ((CBasePlayer *)pTarget)->Storage_AddItems(Params[2], Params.size() >= 4 ? atoi(Params[3]) : 1, Params.size() >= 5 ? atoi(Params[4]) : -1); + + Event.SetVar("game.ret.storage.added", UTIL_VarArgs("%i", Added)); + } else ERROR_MISSING_PARMS; } diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 453fdd5be..f53f8e163 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6713,6 +6713,7 @@ void CBasePlayer::Storage_End() return; m_CurrentStorage.Active = false; + m_CurrentStorage.ClientItems = 0; //The client forgets my storage items when it closes CBaseEntity *pTeller = m_CurrentStorage.Teller.Entity(); IScripted *pScripted = pTeller ? pTeller->GetScripted() : NULL; @@ -6782,14 +6783,41 @@ static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) } bool CBasePlayer::Storage_AddItem(CGenericItem *pItem) { - //Adds an item to my storage, stacking it with identical items. - //Returns false if the storage is full storageitem_t Item; Item.Name = pItem->ItemName; Item.Quantity = FBitSet(pItem->MSProperties(), ITEM_GROUPABLE) ? V_max(pItem->iQuantity, 1) : 1; Item.Quality = pItem->Quality; Item.MaxQuality = pItem->MaxQuality; + return Storage_AddItem(Item); +} +int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Quality) +{ + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(pszItemName, true); + if (!pGlobalItem || !Storage_CanStoreItem(pGlobalItem->ItemName.c_str(), pGlobalItem->MSProperties())) + return 0; + + int Added = 0; + while (Added < Quantity) + { + //One stack at a time, so that a large amount gets split over several items in storage + storageitem_t Item; + Item.Name = pGlobalItem->ItemName; + Item.Quantity = V_min(Quantity - Added, NUM_MAX_STACK); + Item.Quality = Quality >= 0 ? Quality : pGlobalItem->Quality; + Item.MaxQuality = pGlobalItem->MaxQuality; + + int Amt = Item.Quantity; //Storage_AddItem changes the quantity + if (!Storage_AddItem(Item)) + break; + + Added += Amt; + } + + return Added; +} +bool CBasePlayer::Storage_AddItem(storageitem_t &Item) +{ //A full storage can still take an item that stacks completely onto the items already there. //Check before changing anything, so that a stack is never left half stored if ((int)m_Storage.size() >= STORAGE_MAX_ITEMS) diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index aceff131b..9f544c1d3 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -542,6 +542,8 @@ class CBasePlayer : public CMSMonster void Storage_CheckTeller(); void Storage_UpdateClient(); bool Storage_AddItem(CGenericItem *pItem); + bool Storage_AddItem(storageitem_t &Item); + int Storage_AddItems(const char* pszItemName, int Quantity, int Quality); void Storage_RemoveItem(int Idx, int Amt); void Storage_SendItemUpdate(int Idx); std::tuple LearnSkill(int iStat, int iStatType, int EnemySkillLevel); From 9d6e98f79af6caa6519b528696d20a883bfdc531 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 21:55:38 -0500 Subject: [PATCH 06/14] fix chest UI override storage UI --- src/game/client/ui/vgui_teamfortressviewport.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/game/client/ui/vgui_teamfortressviewport.cpp b/src/game/client/ui/vgui_teamfortressviewport.cpp index 40a0a250b..648a7d470 100644 --- a/src/game/client/ui/vgui_teamfortressviewport.cpp +++ b/src/game/client/ui/vgui_teamfortressviewport.cpp @@ -1526,7 +1526,7 @@ CMenuPanel *TeamFortressViewport::ShowStoreMenu(int iStoreType) return NULL; //Special circumstance: inventory items for packs not being held (STORE_INV) - if (CStorePanel::iStoreBuyFlags & STORE_INV) + if (iStoreType != MENU_STORAGE && (CStorePanel::iStoreBuyFlags & STORE_INV)) iStoreType = MENU_STOREBUY; switch (iStoreType) From 23ccfb7192a2111e34329907799ba6d3361d7f24 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Sun, 11 Oct 2026 22:13:44 -0500 Subject: [PATCH 07/14] add limit to items transferred cause of overflow --- src/game/client/ui/ms/vgui_storage.cpp | 17 ++++++++-- src/game/client/ui/ms/vgui_storage.h | 1 + src/game/server/client.cpp | 8 +++-- src/game/server/player/player.cpp | 44 ++++++++++++++++++++++++++ src/game/server/player/player.h | 8 +++++ 5 files changed, 74 insertions(+), 4 deletions(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index ad9b4d260..84e786fc7 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -193,6 +193,13 @@ void CStoragePanel::ItemDoubleclicked(ulong ID) gEngfuncs.pfnClientCmd(Command); } +bool CStoragePanel::ItemClicked(void *pData) +{ + //Only so many items can be moved at once, so don't let more than that be selected + VGUI_ItemButton &ItemButton = *(VGUI_ItemButton *)pData; + return !ItemButton.m_Selected && (int)m_SelectedItems.size() >= STORAGE_MAX_TRANSFER; +} + //MiB FEB2008a bool CStoragePanel::checkValid(containeritem_t &item) { @@ -230,10 +237,16 @@ void CStoragePanel::ItemSelectChanged(ulong ID, bool fSelected) if (m_SelectedItems.size()) { + msstring Text; if (m_LastGearItem == 1) - m_SaleLabel->setText(msstring("Removing ") + (int)m_SelectedItems.size() + " items from storage"); + Text = msstring("Removing ") + (int)m_SelectedItems.size() + " items from storage"; else - m_SaleLabel->setText(msstring("Moving ") + (int)m_SelectedItems.size() + " items into storage"); + Text = msstring("Moving ") + (int)m_SelectedItems.size() + " items into storage"; + + if ((int)m_SelectedItems.size() >= STORAGE_MAX_TRANSFER) + Text += " (max)"; + + m_SaleLabel->setText(Text); m_SaleLabel->setVisible(true); } else diff --git a/src/game/client/ui/ms/vgui_storage.h b/src/game/client/ui/ms/vgui_storage.h index 478cd60a6..99905a07b 100644 --- a/src/game/client/ui/ms/vgui_storage.h +++ b/src/game/client/ui/ms/vgui_storage.h @@ -19,6 +19,7 @@ class CStoragePanel : public CStorePanel void GearItemSelected(ulong ID); bool GearItemClicked(ulong ID); void ItemDoubleclicked(ulong ID); + bool ItemClicked(void *pData); bool checkValid(containeritem_t &Item); //MiB Feb2008a static const char* Text_Subtitle_Storage; diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index fe4056964..1264b7263 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -938,6 +938,10 @@ void ClientCommand2(edict_t *pEntity) if (pPlayer->m_CurrentStorage.Active) { + //The storage panel doesn't let more than this be moved at once + if (!pPlayer->Storage_CountTransfer()) + return; + if (!_stricmp(CMD_ARGV(1), "add")) { CGenericItem *pItem = MSUtil_GetItemByID(atol(CMD_ARGV(2)), pPlayer); @@ -948,7 +952,7 @@ void ClientCommand2(edict_t *pEntity) if (pPlayer->Storage_AddItem(pItem)) { pPlayer->RemoveItem(pItem); - pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You put ") + SPEECH::ItemName(pItem) + " in storage"); + pPlayer->Storage_Moved(SPEECH::ItemName(pItem), true); pItem->SUB_Remove(); } else @@ -1003,7 +1007,7 @@ void ClientCommand2(edict_t *pEntity) if (Success) { - pPlayer->SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + SPEECH::ItemName(pItem) + " from storage"); + pPlayer->Storage_Moved(SPEECH::ItemName(pItem), false); pPlayer->Storage_RemoveItem(StorageItemIdx, Amt); if (ToHands) pPlayer->Storage_Close(); //Like the inventory, taking an item into my hands closes the storage diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index f53f8e163..4f92f37ee 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6746,8 +6746,52 @@ void CBasePlayer::Storage_CheckTeller() Storage_Close(); } +bool CBasePlayer::Storage_CountTransfer() +{ + //Counts an item I'm trying to move into or out of storage. + //Returns false once STORAGE_MAX_TRANSFER have been moved this frame + if (m_CurrentStorage.TransferTime != gpGlobals->time) + { + m_CurrentStorage.TransferTime = gpGlobals->time; + m_CurrentStorage.Transfers = 0; + } + + return ++m_CurrentStorage.Transfers <= STORAGE_MAX_TRANSFER; +} +void CBasePlayer::Storage_Moved(const char* pszItemName, bool In) +{ + //An item was moved into or out of my storage. Storage_SendMoved tells me about it + if (In) + { + m_CurrentStorage.MovedIn++; + m_CurrentStorage.MovedInName = pszItemName; + } + else + { + m_CurrentStorage.MovedOut++; + m_CurrentStorage.MovedOutName = pszItemName; + } +} +void CBasePlayer::Storage_SendMoved() +{ + //One message for all of the items moved this frame, instead of one for each item + if (m_CurrentStorage.MovedIn == 1) + SendEventMsg(HUDEVENT_NORMAL, msstring("You put ") + m_CurrentStorage.MovedInName + " in storage"); + else if (m_CurrentStorage.MovedIn > 1) + SendEventMsg(HUDEVENT_NORMAL, msstring("You put ") + m_CurrentStorage.MovedIn + " items in storage"); + + if (m_CurrentStorage.MovedOut == 1) + SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + m_CurrentStorage.MovedOutName + " from storage"); + else if (m_CurrentStorage.MovedOut > 1) + SendEventMsg(HUDEVENT_NORMAL, msstring("You take ") + m_CurrentStorage.MovedOut + " items from storage"); + + m_CurrentStorage.MovedIn = m_CurrentStorage.MovedOut = 0; +} void CBasePlayer::Storage_UpdateClient() { + //Done even when the storage isn't open. Taking an item into my hands closes it + Storage_SendMoved(); + //Send the storage items that the client doesn't know about yet. //One message per frame, so that a large storage doesn't overflow the client if (!m_CurrentStorage.Active) diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index 9f544c1d3..02548370e 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -30,6 +30,7 @@ // was 1500, but there shouldn't be any issues with increasing. #define NUM_MAX_STACK 2500 //stack is stored as either a unsigned short, so that's the actual max it can be. #define STORAGE_MAX_ITEMS 100 //Max entries in a player's storage. Stacked items share an entry +#define STORAGE_MAX_TRANSFER 20 //Max items moved into or out of storage at once. Each one sends the client messages, and too many overflow it enum sbar_data { @@ -325,6 +326,10 @@ struct storageaccess_t entityinfo_t Teller; //The entity that opened my storage. I must stay near it to keep using it msstring Callback; //Prefix of the script events called on the teller int ClientItems; //Number of items in my storage that the client knows about + float TransferTime; //The frame that Transfers was counted in + int Transfers; //Number of items I tried to move into or out of storage that frame + int MovedIn, MovedOut; //Items moved into and out of storage that I haven't been told about yet + msstring MovedInName, MovedOutName; //The last item moved each way }; //Companion @@ -541,6 +546,9 @@ class CBasePlayer : public CMSMonster void Storage_Close(); void Storage_CheckTeller(); void Storage_UpdateClient(); + bool Storage_CountTransfer(); + void Storage_Moved(const char* pszItemName, bool In); + void Storage_SendMoved(); bool Storage_AddItem(CGenericItem *pItem); bool Storage_AddItem(storageitem_t &Item); int Storage_AddItems(const char* pszItemName, int Quantity, int Quality); From c75346cee0031e9a48a369b732b972272d3ee3a3 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 12:55:49 -0500 Subject: [PATCH 08/14] make non groupable items not stack --- src/game/server/player/player.cpp | 21 ++++++++++++++------- src/game/server/player/player.h | 8 ++++---- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 4f92f37ee..0f9d8c7ca 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6827,13 +6827,15 @@ static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) } bool CBasePlayer::Storage_AddItem(CGenericItem *pItem) { + bool Groupable = FBitSet(pItem->MSProperties(), ITEM_GROUPABLE) ? true : false; + storageitem_t Item; Item.Name = pItem->ItemName; - Item.Quantity = FBitSet(pItem->MSProperties(), ITEM_GROUPABLE) ? V_max(pItem->iQuantity, 1) : 1; + Item.Quantity = Groupable ? V_max(pItem->iQuantity, 1) : 1; Item.Quality = pItem->Quality; Item.MaxQuality = pItem->MaxQuality; - return Storage_AddItem(Item); + return Storage_AddItem(Item, Groupable); } int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Quality) { @@ -6841,18 +6843,19 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua if (!pGlobalItem || !Storage_CanStoreItem(pGlobalItem->ItemName.c_str(), pGlobalItem->MSProperties())) return 0; + bool Groupable = FBitSet(pGlobalItem->MSProperties(), ITEM_GROUPABLE) ? true : false; + int Added = 0; while (Added < Quantity) { - //One stack at a time, so that a large amount gets split over several items in storage storageitem_t Item; Item.Name = pGlobalItem->ItemName; - Item.Quantity = V_min(Quantity - Added, NUM_MAX_STACK); + Item.Quantity = Groupable ? V_min(Quantity - Added, NUM_MAX_STACK) : 1; Item.Quality = Quality >= 0 ? Quality : pGlobalItem->Quality; Item.MaxQuality = pGlobalItem->MaxQuality; int Amt = Item.Quantity; //Storage_AddItem changes the quantity - if (!Storage_AddItem(Item)) + if (!Storage_AddItem(Item, Groupable)) break; Added += Amt; @@ -6860,12 +6863,16 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua return Added; } -bool CBasePlayer::Storage_AddItem(storageitem_t &Item) +bool CBasePlayer::Storage_AddItem(storageitem_t &Item, bool Groupable) { + //Only groupable items stack. Anything else always becomes a new item in storage. //A full storage can still take an item that stacks completely onto the items already there. //Check before changing anything, so that a stack is never left half stored if ((int)m_Storage.size() >= STORAGE_MAX_ITEMS) { + if (!Groupable) + return false; + int Room = 0; for (int i = 0; i < m_Storage.size(); i++) Room += Storage_StackRoom(m_Storage[i], Item); @@ -6874,7 +6881,7 @@ bool CBasePlayer::Storage_AddItem(storageitem_t &Item) return false; } - for (int i = 0; i < m_Storage.size() && Item.Quantity; i++) + for (int i = 0; Groupable && i < m_Storage.size() && Item.Quantity; i++) { int Amt = V_min((int)Item.Quantity, Storage_StackRoom(m_Storage[i], Item)); if (Amt <= 0) diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index 02548370e..a80bcebbe 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -29,8 +29,8 @@ // was 1500, but there shouldn't be any issues with increasing. #define NUM_MAX_STACK 2500 //stack is stored as either a unsigned short, so that's the actual max it can be. -#define STORAGE_MAX_ITEMS 100 //Max entries in a player's storage. Stacked items share an entry -#define STORAGE_MAX_TRANSFER 20 //Max items moved into or out of storage at once. Each one sends the client messages, and too many overflow it +#define STORAGE_MAX_ITEMS 300 +#define STORAGE_MAX_TRANSFER 20 enum sbar_data { @@ -316,7 +316,7 @@ struct storageitem_t { ulong ID; //Unique within the storage. Not saved msstring Name; //Item script name - unsigned short Quantity; //How many. For a groupable item, the size of the stack + unsigned short Quantity; //For a groupable item, the size of the stack. Otherwise 1 unsigned short Quality, MaxQuality; }; struct storageaccess_t @@ -550,7 +550,7 @@ class CBasePlayer : public CMSMonster void Storage_Moved(const char* pszItemName, bool In); void Storage_SendMoved(); bool Storage_AddItem(CGenericItem *pItem); - bool Storage_AddItem(storageitem_t &Item); + bool Storage_AddItem(storageitem_t &Item, bool Groupable); int Storage_AddItems(const char* pszItemName, int Quantity, int Quality); void Storage_RemoveItem(int Idx, int Amt); void Storage_SendItemUpdate(int Idx); From 7d18848dec652c630b5801f35fee9cfe396b973f Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:29:56 -0500 Subject: [PATCH 09/14] add amount to title --- src/game/client/ui/ms/vgui_storage.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index 84e786fc7..842951038 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -106,6 +106,7 @@ void CStoragePanel::AddInventoryItems() return; } + void CStoragePanel::Update() { if (!player.m_CurrentStorage.Active) @@ -117,7 +118,7 @@ void CStoragePanel::Update() CStorePanel::Update(); - m_pTitle->setText(player.m_CurrentStorage.DisplayName); + m_pTitle->setText(player.m_CurrentStorage.DisplayName + " (" + (int)player.m_Storage.size() + "/" + STORAGE_MAX_ITEMS + ")"); } void CStoragePanel::GearItemSelected(ulong ID) From 3b04e27e33a44b20826565b5741db419f32bbcb5 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 13:42:07 -0500 Subject: [PATCH 10/14] make items stack, but still take slots --- src/game/client/ui/ms/vgui_storage.cpp | 2 +- src/game/server/player/player.cpp | 22 +++++++++++++++------- src/game/server/player/player.h | 3 ++- src/game/server/player/playershared.cpp | 16 ++++++++++++++++ 4 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index 842951038..2370548d5 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -118,7 +118,7 @@ void CStoragePanel::Update() CStorePanel::Update(); - m_pTitle->setText(player.m_CurrentStorage.DisplayName + " (" + (int)player.m_Storage.size() + "/" + STORAGE_MAX_ITEMS + ")"); + m_pTitle->setText(player.m_CurrentStorage.DisplayName + " (" + player.Storage_ItemCount() + "/" + STORAGE_MAX_ITEMS + ")"); } void CStoragePanel::GearItemSelected(ulong ID) diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 0f9d8c7ca..6032d288d 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6845,12 +6845,16 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua bool Groupable = FBitSet(pGlobalItem->MSProperties(), ITEM_GROUPABLE) ? true : false; + //Only add as many as there's room for, instead of none at all + if (!Groupable) + Quantity = V_min(Quantity, STORAGE_MAX_ITEMS - Storage_ItemCount()); + int Added = 0; while (Added < Quantity) { storageitem_t Item; Item.Name = pGlobalItem->ItemName; - Item.Quantity = Groupable ? V_min(Quantity - Added, NUM_MAX_STACK) : 1; + Item.Quantity = V_min(Quantity - Added, NUM_MAX_STACK); Item.Quality = Quality >= 0 ? Quality : pGlobalItem->Quality; Item.MaxQuality = pGlobalItem->MaxQuality; @@ -6865,14 +6869,18 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua } bool CBasePlayer::Storage_AddItem(storageitem_t &Item, bool Groupable) { - //Only groupable items stack. Anything else always becomes a new item in storage. - //A full storage can still take an item that stacks completely onto the items already there. + //Identical items stack, but only a stack of groupable items counts once towards the max. + //Anything else counts for each item in the stack, so it needs room even when it stacks. //Check before changing anything, so that a stack is never left half stored - if ((int)m_Storage.size() >= STORAGE_MAX_ITEMS) + int Count = Storage_ItemCount(); + if (!Groupable) { - if (!Groupable) + if (Count + (int)Item.Quantity > STORAGE_MAX_ITEMS) return false; - + } + else if (Count >= STORAGE_MAX_ITEMS) + { + //A full storage can still take a groupable item that stacks completely onto the items already there int Room = 0; for (int i = 0; i < m_Storage.size(); i++) Room += Storage_StackRoom(m_Storage[i], Item); @@ -6881,7 +6889,7 @@ bool CBasePlayer::Storage_AddItem(storageitem_t &Item, bool Groupable) return false; } - for (int i = 0; Groupable && i < m_Storage.size() && Item.Quantity; i++) + for (int i = 0; i < m_Storage.size() && Item.Quantity; i++) { int Amt = V_min((int)Item.Quantity, Storage_StackRoom(m_Storage[i], Item)); if (Amt <= 0) diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index a80bcebbe..effbaa007 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -316,7 +316,7 @@ struct storageitem_t { ulong ID; //Unique within the storage. Not saved msstring Name; //Item script name - unsigned short Quantity; //For a groupable item, the size of the stack. Otherwise 1 + unsigned short Quantity; //How many are in the stack unsigned short Quality, MaxQuality; }; struct storageaccess_t @@ -648,6 +648,7 @@ class CBasePlayer : public CMSMonster void SetKeys(); void BlockButton(int Button); static bool Storage_CanStoreItem(const char* pszItemName, int Properties); //Whether this kind of item is allowed in storage + int Storage_ItemCount(); //How much of STORAGE_MAX_ITEMS is used void InitialSpawn(void); bool Script_SetupEvent(CScript *Script, SCRIPT_EVENT &Event); const char *GetPartyName(); diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index 3e48c579e..86617f3de 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1408,6 +1408,22 @@ bool CBasePlayer::Storage_CanStoreItem(const char* pszItemName, int Properties) return true; } +int CBasePlayer::Storage_ItemCount() +{ + //How much of STORAGE_MAX_ITEMS is used. A stack of groupable items only counts once + int Count = 0; + for (int i = 0; i < m_Storage.size(); i++) + { + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(m_Storage[i].Name, true); + if (pGlobalItem && FBitSet(pGlobalItem->MSProperties(), ITEM_GROUPABLE)) + Count++; + else + Count += m_Storage[i].Quantity; + } + + return Count; +} + bool CBasePlayer::Script_SetupEvent(CScript* Script, SCRIPT_EVENT& Event) { //SetScriptVar( "game.monster.current_anim.uselegs", strlen(m_szAnimLegs) > 0 ); From b121e436ac3cd6d7dff29ef2666be002a4b4ac51 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 14:39:27 -0500 Subject: [PATCH 11/14] potion stacking --- src/game/client/ui/ms/vgui_container.cpp | 5 +- src/game/client/ui/ms/vgui_mscontrols.cpp | 8 ++ src/game/client/ui/ms/vgui_storage.cpp | 8 +- src/game/server/client.cpp | 22 +++- src/game/server/player/player.cpp | 121 +++++++++++++++++++--- src/game/server/player/player.h | 9 +- src/game/server/player/playershared.cpp | 5 + 7 files changed, 159 insertions(+), 19 deletions(-) diff --git a/src/game/client/ui/ms/vgui_container.cpp b/src/game/client/ui/ms/vgui_container.cpp index d5c9350a4..778602883 100644 --- a/src/game/client/ui/ms/vgui_container.cpp +++ b/src/game/client/ui/ms/vgui_container.cpp @@ -336,8 +336,9 @@ void VGUI_ItemInfoPanel::Update(containeritem_t &Item) m_Name->setText(Item.getFullName()); m_Quantity->setText(Localized("#ITEMINFO_QUANTITY"), Item.Quantity); - m_Quality->setText(Localized("#ITEMINFO_QUALITY"), Item.Quality); - m_Quality->setVisible(false); + //Only items with a quality have a max quality + m_Quality->setText("Quality: %i/%i", (int)Item.Quality, (int)Item.MaxQuality); + m_Quality->setVisible(Item.MaxQuality > 0); char weight[50]; _snprintf(weight, sizeof(weight), "Weight: %.2f", Item.Weight); diff --git a/src/game/client/ui/ms/vgui_mscontrols.cpp b/src/game/client/ui/ms/vgui_mscontrols.cpp index 39dba64c5..e7dbc9568 100644 --- a/src/game/client/ui/ms/vgui_mscontrols.cpp +++ b/src/game/client/ui/ms/vgui_mscontrols.cpp @@ -275,6 +275,14 @@ void containeritem_t::init( CGenericItem *pItem ) Properties = pItem->MSProperties( ); Quantity = FBitSet( Properties, ITEM_GROUPABLE ) ? pItem->iQuantity : 1; Quality = pItem->Quality; + MaxQuality = pItem->MaxQuality; + if( !MaxQuality && FBitSet( Properties, ITEM_DRINKABLE ) ) + { + //Drinkables don't have a max quality. A new one is full + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName( pItem->ItemName, true ); + if( pGlobalItem ) + MaxQuality = pGlobalItem->Quality; + } Value = pItem->m_Value; Desc = pItem->DisplayDesc; bForceFull = false; diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index 2370548d5..346f88863 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -95,6 +95,10 @@ void CStoragePanel::AddInventoryItems() vNewItem.ID = Item.ID; vNewItem.Quantity = Item.Quantity; vNewItem.Quality = Item.Quality; + if (CBasePlayer::Storage_CombinesSwigs(Item.Name.c_str(), vNewItem.Properties)) + vNewItem.MaxQuality = STORAGE_MAX_SWIGS; //Shown as how full it is, instead of against one potion + else if (Item.MaxQuality) + vNewItem.MaxQuality = Item.MaxQuality; if (Item.Quantity > 1) vNewItem.setFullName(msstring() + (int)Item.Quantity + " " + pGlobalItem->DisplayName() + "s"); //Named the way SPEECH::ItemName does @@ -333,15 +337,17 @@ void Storage_ItemRemoveMsg() Storage_Update(); } -//The quantity of an item in my storage changed +//The quantity or quality of an item in my storage changed void Storage_ItemUpdateMsg() { ulong ID = READ_LONG(); int Quantity = READ_SHORT(); + int Quality = READ_SHORT(); for (int i = 0; i < player.m_Storage.size(); i++) if (player.m_Storage[i].ID == ID) { player.m_Storage[i].Quantity = Quantity; + player.m_Storage[i].Quality = Quality; break; } diff --git a/src/game/server/client.cpp b/src/game/server/client.cpp index 1264b7263..8480c6c13 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -987,8 +987,21 @@ void ClientCommand2(edict_t *pEntity) Amt = pStorageItem->Quantity; pItem->iQuantity = Amt; } - pItem->Quality = pStorageItem->Quality; - pItem->MaxQuality = pStorageItem->MaxQuality; + + //A mana potion comes out full, or with whatever swigs are left in storage + bool Swigs = CBasePlayer::Storage_CombinesSwigs(pStorageItem->Name.c_str(), pItem->MSProperties()); + int SwigAmt = pStorageItem->Quality; + if (Swigs) + { + if (pItem->Quality > 0) + SwigAmt = V_min(SwigAmt, (int)pItem->Quality); + pItem->Quality = SwigAmt; + } + else + { + pItem->Quality = pStorageItem->Quality; + pItem->MaxQuality = pStorageItem->MaxQuality; + } int ContainerID = atol(CMD_ARGV(3)); bool ToHands = !_stricmp(CMD_ARGV(3), "hands"); //Sent when an item in storage is double clicked @@ -1008,7 +1021,10 @@ void ClientCommand2(edict_t *pEntity) if (Success) { pPlayer->Storage_Moved(SPEECH::ItemName(pItem), false); - pPlayer->Storage_RemoveItem(StorageItemIdx, Amt); + if (Swigs) + pPlayer->Storage_RemoveSwigs(StorageItemIdx, SwigAmt); + else + pPlayer->Storage_RemoveItem(StorageItemIdx, Amt); if (ToHands) pPlayer->Storage_Close(); //Like the inventory, taking an item into my hands closes the storage } diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 6032d288d..7621f3e03 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6475,6 +6475,7 @@ bool CBasePlayer::RestoreAllServer(void *pData, ulong Size) //Read storage items m_Storage = Data.m_Storage; //Sent to the client when the storage is opened + Storage_CombineSwigs(); //Read Companions @@ -6835,7 +6836,7 @@ bool CBasePlayer::Storage_AddItem(CGenericItem *pItem) Item.Quality = pItem->Quality; Item.MaxQuality = pItem->MaxQuality; - return Storage_AddItem(Item, Groupable); + return Storage_AddItem(Item, pItem->MSProperties()); } int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Quality) { @@ -6843,10 +6844,19 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua if (!pGlobalItem || !Storage_CanStoreItem(pGlobalItem->ItemName.c_str(), pGlobalItem->MSProperties())) return 0; - bool Groupable = FBitSet(pGlobalItem->MSProperties(), ITEM_GROUPABLE) ? true : false; + int Properties = pGlobalItem->MSProperties(); + if (Quality < 0) + Quality = pGlobalItem->Quality; //Only add as many as there's room for, instead of none at all - if (!Groupable) + if (Storage_CombinesSwigs(pGlobalItem->ItemName.c_str(), Properties)) + { + if (Quality <= 0) + return 0; + + Quantity = V_min(Quantity, Storage_SwigRoom(pGlobalItem->ItemName.c_str()) / Quality); + } + else if (!FBitSet(Properties, ITEM_GROUPABLE)) Quantity = V_min(Quantity, STORAGE_MAX_ITEMS - Storage_ItemCount()); int Added = 0; @@ -6855,11 +6865,11 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua storageitem_t Item; Item.Name = pGlobalItem->ItemName; Item.Quantity = V_min(Quantity - Added, NUM_MAX_STACK); - Item.Quality = Quality >= 0 ? Quality : pGlobalItem->Quality; + Item.Quality = Quality; Item.MaxQuality = pGlobalItem->MaxQuality; int Amt = Item.Quantity; //Storage_AddItem changes the quantity - if (!Storage_AddItem(Item, Groupable)) + if (!Storage_AddItem(Item, Properties)) break; Added += Amt; @@ -6867,8 +6877,78 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua return Added; } -bool CBasePlayer::Storage_AddItem(storageitem_t &Item, bool Groupable) +int CBasePlayer::Storage_SwigRoom(const char* pszItemName) +{ + //How many more swigs of a mana potion fit in my storage. + //The ones already in storage get filled first, and then each new one holds up to STORAGE_MAX_SWIGS + int Room = V_max(STORAGE_MAX_ITEMS - Storage_ItemCount(), 0) * STORAGE_MAX_SWIGS; + for (int i = 0; i < m_Storage.size(); i++) + if (m_Storage[i].Name == pszItemName) + Room += V_max(STORAGE_MAX_SWIGS - (int)m_Storage[i].Quality, 0); + + return Room; +} +void CBasePlayer::Storage_PutSwigs(const char* pszItemName, int Swigs) +{ + //Adds the swigs of a mana potion to my storage. Doesn't check for room, Storage_SwigRoom does + for (int i = 0; i < m_Storage.size() && Swigs > 0; i++) + { + if (m_Storage[i].Name != pszItemName) + continue; + + int Amt = V_min(Swigs, STORAGE_MAX_SWIGS - (int)m_Storage[i].Quality); + if (Amt <= 0) + continue; + + m_Storage[i].Quality += Amt; + Swigs -= Amt; + Storage_SendItemUpdate(i); + } + + while (Swigs > 0) + { + storageitem_t Item; + Item.Name = pszItemName; + Item.Quantity = 1; + Item.Quality = V_min(Swigs, STORAGE_MAX_SWIGS); + Item.MaxQuality = 0; + + Swigs -= Item.Quality; + Storage_NewItem(Item); + } +} +void CBasePlayer::Storage_CombineSwigs() +{ + //Storage saved before mana potions were kept by their swigs has them stacked like any other item. + //Take them all out and put their swigs back in. Nothing is lost, even if it goes over STORAGE_MAX_ITEMS + mslist Potions; + for (int i = (int)m_Storage.size() - 1; i >= 0; i--) + { + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(m_Storage[i].Name, true); + if (!pGlobalItem || !Storage_CombinesSwigs(pGlobalItem->ItemName.c_str(), pGlobalItem->MSProperties())) + continue; + + Potions.add(m_Storage[i]); + m_Storage.erase(i); + } + + for (int i = 0; i < Potions.size(); i++) + Storage_PutSwigs(Potions[i].Name, (int)Potions[i].Quantity * (int)Potions[i].Quality); +} +bool CBasePlayer::Storage_AddItem(storageitem_t &Item, int Properties) { + if (Storage_CombinesSwigs(Item.Name.c_str(), Properties)) + { + int Swigs = (int)Item.Quantity * (int)Item.Quality; + if (Swigs > Storage_SwigRoom(Item.Name)) + return false; + + Storage_PutSwigs(Item.Name, Swigs); + return true; + } + + bool Groupable = FBitSet(Properties, ITEM_GROUPABLE) ? true : false; + //Identical items stack, but only a stack of groupable items counts once towards the max. //Anything else counts for each item in the stack, so it needs room even when it stacks. //Check before changing anything, so that a stack is never left half stored @@ -6900,10 +6980,15 @@ bool CBasePlayer::Storage_AddItem(storageitem_t &Item, bool Groupable) Storage_SendItemUpdate(i); } - if (!Item.Quantity) - return true; + //What's left becomes a new item in storage + if (Item.Quantity) + Storage_NewItem(Item); - //What's left becomes a new item in storage. Determine a unique ID for it + return true; +} +void CBasePlayer::Storage_NewItem(storageitem_t &Item) +{ + //Determine a unique ID for it for (unsigned int ID = 0; ID < SHRT_MAX; ID++) { bool Duplicate = false; @@ -6920,12 +7005,11 @@ bool CBasePlayer::Storage_AddItem(storageitem_t &Item, bool Groupable) } m_Storage.add(Item); //Storage_UpdateClient sends it - return true; } void CBasePlayer::Storage_SendItemUpdate(int Idx) { - //The quantity of an item in my storage changed. - //Items past ClientItems haven't been sent yet, so they get sent with the new quantity + //The quantity or quality of an item in my storage changed. + //Items past ClientItems haven't been sent yet, so they get sent with the new amounts if (Idx >= m_CurrentStorage.ClientItems) return; @@ -6933,6 +7017,7 @@ void CBasePlayer::Storage_SendItemUpdate(int Idx) WRITE_BYTE(4); //update storage item WRITE_LONG(m_Storage[Idx].ID); WRITE_SHORT(m_Storage[Idx].Quantity); + WRITE_SHORT(m_Storage[Idx].Quality); MESSAGE_END(); } void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) @@ -6958,6 +7043,18 @@ void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) m_Storage.erase(Idx); } +void CBasePlayer::Storage_RemoveSwigs(int Idx, int Amt) +{ + //Takes Amt swigs out of a mana potion in my storage. It stays in storage until its swigs run out + if (m_Storage[Idx].Quality > Amt) + { + m_Storage[Idx].Quality -= Amt; + Storage_SendItemUpdate(Idx); + return; + } + + Storage_RemoveItem(Idx, m_Storage[Idx].Quantity); +} void CBasePlayer::SaveChar() { diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index effbaa007..c245f6f1d 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -31,6 +31,7 @@ #define NUM_MAX_STACK 2500 //stack is stored as either a unsigned short, so that's the actual max it can be. #define STORAGE_MAX_ITEMS 300 #define STORAGE_MAX_TRANSFER 20 +#define STORAGE_MAX_SWIGS 30 enum sbar_data { @@ -550,10 +551,15 @@ class CBasePlayer : public CMSMonster void Storage_Moved(const char* pszItemName, bool In); void Storage_SendMoved(); bool Storage_AddItem(CGenericItem *pItem); - bool Storage_AddItem(storageitem_t &Item, bool Groupable); + bool Storage_AddItem(storageitem_t &Item, int Properties); int Storage_AddItems(const char* pszItemName, int Quantity, int Quality); + void Storage_NewItem(storageitem_t &Item); void Storage_RemoveItem(int Idx, int Amt); void Storage_SendItemUpdate(int Idx); + int Storage_SwigRoom(const char* pszItemName); + void Storage_PutSwigs(const char* pszItemName, int Swigs); + void Storage_RemoveSwigs(int Idx, int Amt); + void Storage_CombineSwigs(); std::tuple LearnSkill(int iStat, int iStatType, int EnemySkillLevel); bool LearnSkill(int iStat, int EnemySkillLevel); void SetQuest(bool SetData, const char* Name, const char* Data); @@ -648,6 +654,7 @@ class CBasePlayer : public CMSMonster void SetKeys(); void BlockButton(int Button); static bool Storage_CanStoreItem(const char* pszItemName, int Properties); //Whether this kind of item is allowed in storage + static bool Storage_CombinesSwigs(const char* pszItemName, int Properties); //Whether this kind of item is kept in storage by its swigs int Storage_ItemCount(); //How much of STORAGE_MAX_ITEMS is used void InitialSpawn(void); bool Script_SetupEvent(CScript *Script, SCRIPT_EVENT &Event); diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index 86617f3de..c7ca75146 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1408,6 +1408,11 @@ bool CBasePlayer::Storage_CanStoreItem(const char* pszItemName, int Properties) return true; } +bool CBasePlayer::Storage_CombinesSwigs(const char* pszItemName, int Properties) +{ + return FBitSet(Properties, ITEM_DRINKABLE) && !strncmp(pszItemName, "mana_", 5); +} + int CBasePlayer::Storage_ItemCount() { //How much of STORAGE_MAX_ITEMS is used. A stack of groupable items only counts once From 47d648182007bbd9b13599bbefa7366d84d7d52d Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:13:36 -0500 Subject: [PATCH 12/14] custom storage amounts --- src/game/client/ui/ms/vgui_storage.cpp | 13 +++-- src/game/client/ui/ms/vgui_storemainwin.cpp | 4 ++ src/game/server/character/mscharacterheader.h | 2 + src/game/server/character/sv_character.cpp | 3 ++ src/game/server/monsters/npcscript.cpp | 8 +++ src/game/server/player/player.cpp | 49 ++++++++++--------- src/game/server/player/player.h | 7 ++- src/game/server/player/playershared.cpp | 9 +++- src/game/shared/ms/scriptcmds.cpp | 6 +++ 9 files changed, 71 insertions(+), 30 deletions(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index 346f88863..8f6352656 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -122,7 +122,7 @@ void CStoragePanel::Update() CStorePanel::Update(); - m_pTitle->setText(player.m_CurrentStorage.DisplayName + " (" + player.Storage_ItemCount() + "/" + STORAGE_MAX_ITEMS + ")"); + m_pTitle->setText(player.m_CurrentStorage.DisplayName + " (" + player.Storage_ItemCount() + "/" + player.Storage_MaxItems() + ")"); } void CStoragePanel::GearItemSelected(ulong ID) @@ -299,7 +299,6 @@ void Storage_ItemReset() Storage_Update(); } -//Items were added to my storage void Storage_ItemMsg() { int Count = READ_BYTE(); @@ -312,7 +311,6 @@ void Storage_ItemMsg() Item.Quality = READ_SHORT(); Item.MaxQuality = READ_SHORT(); - //Everything is read before the item is checked, so the next item can still be read if (idx < 0 || idx >= CGenericItemMgr::ItemCount()) continue; @@ -323,7 +321,6 @@ void Storage_ItemMsg() Storage_Update(); } -//An item was removed from my storage void Storage_ItemRemoveMsg() { ulong ID = READ_LONG(); @@ -337,7 +334,6 @@ void Storage_ItemRemoveMsg() Storage_Update(); } -//The quantity or quality of an item in my storage changed void Storage_ItemUpdateMsg() { ulong ID = READ_LONG(); @@ -354,6 +350,13 @@ void Storage_ItemUpdateMsg() Storage_Update(); } +void Storage_MaxMsg() +{ + player.m_StorageMax = READ_SHORT(); + + Storage_Update(); +} + void Storage_Show(const char* DisplayName) { player.m_CurrentStorage.Active = true; diff --git a/src/game/client/ui/ms/vgui_storemainwin.cpp b/src/game/client/ui/ms/vgui_storemainwin.cpp index 5f6d684a5..56bbae832 100644 --- a/src/game/client/ui/ms/vgui_storemainwin.cpp +++ b/src/game/client/ui/ms/vgui_storemainwin.cpp @@ -97,6 +97,7 @@ void Storage_ItemReset(); void Storage_ItemMsg(); void Storage_ItemRemoveMsg(); void Storage_ItemUpdateMsg(); +void Storage_MaxMsg(); int __MsgFunc_StoreItem(const char *pszName, int iSize, void *pbuf) { @@ -151,6 +152,9 @@ int __MsgFunc_StoreItem(const char *pszName, int iSize, void *pbuf) case 4: Storage_ItemUpdateMsg(); break; + case 5: + Storage_MaxMsg(); + break; } return 0; } diff --git a/src/game/server/character/mscharacterheader.h b/src/game/server/character/mscharacterheader.h index ad1110841..b4f270b95 100644 --- a/src/game/server/character/mscharacterheader.h +++ b/src/game/server/character/mscharacterheader.h @@ -41,6 +41,7 @@ struct savedata_legacy_t struct savedata_t : savedata_legacy_t { short Body = 0; + short StorageMax = 0; }; // @@ -92,6 +93,7 @@ enum charfield_e CF_QUESTS = 37, //[ [name, data], ... ] CF_QUICKSLOTS = 38, //[ nil | [type, id], ... ] CF_STORAGE = 39, //[storageitem, ...] + CF_STORAGEMAX = 40, //int }; //Item keys diff --git a/src/game/server/character/sv_character.cpp b/src/game/server/character/sv_character.cpp index 6205b1aa2..89e4d7f46 100644 --- a/src/game/server/character/sv_character.cpp +++ b/src/game/server/character/sv_character.cpp @@ -377,6 +377,7 @@ bool chardata_t::ReadDataPack(const char *pData, size_t Size) case CF_PLAYERKILLS: PlayerKills = (short)PackReadInt(Value); break; case CF_TIMEFORGETKILL: TimeWaitedToForgetKill = PackReadFloat(Value); break; case CF_BODY: Body = (short)PackReadInt(Value); break; + case CF_STORAGEMAX: StorageMax = (int)PackReadInt(Value); break; //Sections case CF_VISITEDMAPS: ReadStringListPack(Value, m_VisitedMaps); break; @@ -1064,6 +1065,7 @@ static void PackChar(charpack_t &Out, CBasePlayer *pPlayer, savedata_t &Data) Root.Field(CF_PLAYERKILLS, Data.PlayerKills); Root.Field(CF_TIMEFORGETKILL, Data.TimeWaitedToForgetKill); Root.Field(CF_BODY, Data.Body); + Root.Field(CF_STORAGEMAX, Data.StorageMax); //Maps visited Root.Key(CF_VISITEDMAPS); @@ -1264,6 +1266,7 @@ void MSChar_Interface::SaveChar(CBasePlayer *pPlayer, savedata_t *pData) Data.PlayerKills = pPlayer->m_PlayersKilled; Data.TimeWaitedToForgetKill = pPlayer->m_TimeWaitedToForgetKill; Data.Body = pPlayer->m_CharInfo[pPlayer->m_CharacterNum].Body; + Data.StorageMax = pPlayer->m_StorageMax; //#ifdef VALVE_DLL strncpy(Data.SteamID, GETPLAYERAUTHID(pPlayer->edict()), 32); //#else diff --git a/src/game/server/monsters/npcscript.cpp b/src/game/server/monsters/npcscript.cpp index 605e7768e..04d3e365c 100644 --- a/src/game/server/monsters/npcscript.cpp +++ b/src/game/server/monsters/npcscript.cpp @@ -1416,6 +1416,14 @@ bool CMSMonster::Script_ExecuteCmd(CScript *Script, SCRIPT_EVENT &Event, scriptc Event.SetVar("game.ret.storage.added", UTIL_VarArgs("%i", Added)); } + //setmax target amount + //Sets how many items the target player's storage holds, up to STORAGE_MAX_ITEMS. $get(target,storagemax) returns it + else if (Params.size() >= 3 && Params[0] == "setmax") + { + CBaseEntity *pTarget = RetrieveEntity(Params[1]); + if (pTarget && pTarget->IsPlayer()) + ((CBasePlayer *)pTarget)->Storage_SetMax(atoi(Params[2])); + } else ERROR_MISSING_PARMS; } diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 7621f3e03..989e6cade 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6475,6 +6475,7 @@ bool CBasePlayer::RestoreAllServer(void *pData, ulong Size) //Read storage items m_Storage = Data.m_Storage; //Sent to the client when the storage is opened + m_StorageMax = Data.StorageMax; Storage_CombineSwigs(); //Read Companions @@ -6702,19 +6703,34 @@ void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller, WRITE_STRING_LIMIT(m_CurrentStorage.DisplayName, 90); MESSAGE_END(); - //The client only knows my storage items while it has the storage open. Storage_UpdateClient sends them + //The client only knows my storage items while it has the storage open. MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); WRITE_BYTE(1); //reset storage items MESSAGE_END(); + + Storage_SendMax(); +} +void CBasePlayer::Storage_SetMax(int Max) +{ + m_StorageMax = V_max(V_min(Max, STORAGE_MAX_ITEMS), 1); + + if (m_CurrentStorage.Active) + Storage_SendMax(); +} +void CBasePlayer::Storage_SendMax() +{ + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); + WRITE_BYTE(5); //storage max + WRITE_SHORT(Storage_MaxItems()); + MESSAGE_END(); } void CBasePlayer::Storage_End() { - //I'm done using my storage. Tell the entity that opened it if (!m_CurrentStorage.Active) return; m_CurrentStorage.Active = false; - m_CurrentStorage.ClientItems = 0; //The client forgets my storage items when it closes + m_CurrentStorage.ClientItems = 0; CBaseEntity *pTeller = m_CurrentStorage.Teller.Entity(); IScripted *pScripted = pTeller ? pTeller->GetScripted() : NULL; @@ -6727,7 +6743,6 @@ void CBasePlayer::Storage_End() } void CBasePlayer::Storage_Close() { - //Makes the client close the storage too Storage_End(); MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_VGUIMENU], NULL, pev); @@ -6736,7 +6751,6 @@ void CBasePlayer::Storage_Close() } void CBasePlayer::Storage_CheckTeller() { - //I have to stay near the entity that opened my storage if (!m_CurrentStorage.Active) return; @@ -6749,8 +6763,6 @@ void CBasePlayer::Storage_CheckTeller() } bool CBasePlayer::Storage_CountTransfer() { - //Counts an item I'm trying to move into or out of storage. - //Returns false once STORAGE_MAX_TRANSFER have been moved this frame if (m_CurrentStorage.TransferTime != gpGlobals->time) { m_CurrentStorage.TransferTime = gpGlobals->time; @@ -6761,7 +6773,6 @@ bool CBasePlayer::Storage_CountTransfer() } void CBasePlayer::Storage_Moved(const char* pszItemName, bool In) { - //An item was moved into or out of my storage. Storage_SendMoved tells me about it if (In) { m_CurrentStorage.MovedIn++; @@ -6775,7 +6786,6 @@ void CBasePlayer::Storage_Moved(const char* pszItemName, bool In) } void CBasePlayer::Storage_SendMoved() { - //One message for all of the items moved this frame, instead of one for each item if (m_CurrentStorage.MovedIn == 1) SendEventMsg(HUDEVENT_NORMAL, msstring("You put ") + m_CurrentStorage.MovedInName + " in storage"); else if (m_CurrentStorage.MovedIn > 1) @@ -6790,7 +6800,6 @@ void CBasePlayer::Storage_SendMoved() } void CBasePlayer::Storage_UpdateClient() { - //Done even when the storage isn't open. Taking an item into my hands closes it Storage_SendMoved(); //Send the storage items that the client doesn't know about yet. @@ -6818,7 +6827,7 @@ void CBasePlayer::Storage_UpdateClient() m_CurrentStorage.ClientItems += Count; } -//How many more of Item can stack onto Cur, an item already in storage. 0 if they aren't identical +//How many more of Item can stack onto Cur, an item already in storage. 0 if they aren't identical static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) { if (Cur.Name != Item.Name || Cur.Quality != Item.Quality || Cur.MaxQuality != Item.MaxQuality) @@ -6848,7 +6857,6 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua if (Quality < 0) Quality = pGlobalItem->Quality; - //Only add as many as there's room for, instead of none at all if (Storage_CombinesSwigs(pGlobalItem->ItemName.c_str(), Properties)) { if (Quality <= 0) @@ -6857,7 +6865,7 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua Quantity = V_min(Quantity, Storage_SwigRoom(pGlobalItem->ItemName.c_str()) / Quality); } else if (!FBitSet(Properties, ITEM_GROUPABLE)) - Quantity = V_min(Quantity, STORAGE_MAX_ITEMS - Storage_ItemCount()); + Quantity = V_min(Quantity, Storage_MaxItems() - Storage_ItemCount()); int Added = 0; while (Added < Quantity) @@ -6881,7 +6889,7 @@ int CBasePlayer::Storage_SwigRoom(const char* pszItemName) { //How many more swigs of a mana potion fit in my storage. //The ones already in storage get filled first, and then each new one holds up to STORAGE_MAX_SWIGS - int Room = V_max(STORAGE_MAX_ITEMS - Storage_ItemCount(), 0) * STORAGE_MAX_SWIGS; + int Room = V_max(Storage_MaxItems() - Storage_ItemCount(), 0) * STORAGE_MAX_SWIGS; for (int i = 0; i < m_Storage.size(); i++) if (m_Storage[i].Name == pszItemName) Room += V_max(STORAGE_MAX_SWIGS - (int)m_Storage[i].Quality, 0); @@ -6919,8 +6927,6 @@ void CBasePlayer::Storage_PutSwigs(const char* pszItemName, int Swigs) } void CBasePlayer::Storage_CombineSwigs() { - //Storage saved before mana potions were kept by their swigs has them stacked like any other item. - //Take them all out and put their swigs back in. Nothing is lost, even if it goes over STORAGE_MAX_ITEMS mslist Potions; for (int i = (int)m_Storage.size() - 1; i >= 0; i--) { @@ -6952,13 +6958,13 @@ bool CBasePlayer::Storage_AddItem(storageitem_t &Item, int Properties) //Identical items stack, but only a stack of groupable items counts once towards the max. //Anything else counts for each item in the stack, so it needs room even when it stacks. //Check before changing anything, so that a stack is never left half stored - int Count = Storage_ItemCount(); + int Count = Storage_ItemCount(), Max = Storage_MaxItems(); if (!Groupable) { - if (Count + (int)Item.Quantity > STORAGE_MAX_ITEMS) + if (Count + (int)Item.Quantity > Max) return false; } - else if (Count >= STORAGE_MAX_ITEMS) + else if (Count >= Max) { //A full storage can still take a groupable item that stacks completely onto the items already there int Room = 0; @@ -7008,8 +7014,6 @@ void CBasePlayer::Storage_NewItem(storageitem_t &Item) } void CBasePlayer::Storage_SendItemUpdate(int Idx) { - //The quantity or quality of an item in my storage changed. - //Items past ClientItems haven't been sent yet, so they get sent with the new amounts if (Idx >= m_CurrentStorage.ClientItems) return; @@ -7022,7 +7026,6 @@ void CBasePlayer::Storage_SendItemUpdate(int Idx) } void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) { - //Takes Amt of an item out of my storage. The item stays in storage until its quantity runs out if (m_Storage[Idx].Quantity > Amt) { m_Storage[Idx].Quantity -= Amt; @@ -7045,7 +7048,7 @@ void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) } void CBasePlayer::Storage_RemoveSwigs(int Idx, int Amt) { - //Takes Amt swigs out of a mana potion in my storage. It stays in storage until its swigs run out + //Takes Amt swigs out of a mana potion in my storage. It stays in storage until its swigs run out if (m_Storage[Idx].Quality > Amt) { m_Storage[Idx].Quality -= Amt; diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index c245f6f1d..8f31d7ec1 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -30,6 +30,7 @@ // was 1500, but there shouldn't be any issues with increasing. #define NUM_MAX_STACK 2500 //stack is stored as either a unsigned short, so that's the actual max it can be. #define STORAGE_MAX_ITEMS 300 +#define STORAGE_DEFAULT_ITEMS 100 #define STORAGE_MAX_TRANSFER 20 #define STORAGE_MAX_SWIGS 30 @@ -445,6 +446,7 @@ class CBasePlayer : public CMSMonster bool m_fClientInitiated; //Client sent this command to the server. Make sure to cache any item changes so they don't get sent back to client bool m_fInTownArea; mslist m_Storage; //Items I have in storage + int m_StorageMax; //How many items my storage holds. 0 until it's set, which gives STORAGE_DEFAULT_ITEMS storageaccess_t m_CurrentStorage; //Set while I have my storage open bool m_Initialized; //Whether stats were created, script loaded, etc float m_TimeNextSave; @@ -560,6 +562,8 @@ class CBasePlayer : public CMSMonster void Storage_PutSwigs(const char* pszItemName, int Swigs); void Storage_RemoveSwigs(int Idx, int Amt); void Storage_CombineSwigs(); + void Storage_SetMax(int Max); + void Storage_SendMax(); std::tuple LearnSkill(int iStat, int iStatType, int EnemySkillLevel); bool LearnSkill(int iStat, int EnemySkillLevel); void SetQuest(bool SetData, const char* Name, const char* Data); @@ -655,7 +659,8 @@ class CBasePlayer : public CMSMonster void BlockButton(int Button); static bool Storage_CanStoreItem(const char* pszItemName, int Properties); //Whether this kind of item is allowed in storage static bool Storage_CombinesSwigs(const char* pszItemName, int Properties); //Whether this kind of item is kept in storage by its swigs - int Storage_ItemCount(); //How much of STORAGE_MAX_ITEMS is used + int Storage_ItemCount(); //How much of Storage_MaxItems is used + int Storage_MaxItems(); //How many items my storage holds void InitialSpawn(void); bool Script_SetupEvent(CScript *Script, SCRIPT_EVENT &Event); const char *GetPartyName(); diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index c7ca75146..e9369ffba 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1415,7 +1415,7 @@ bool CBasePlayer::Storage_CombinesSwigs(const char* pszItemName, int Properties) int CBasePlayer::Storage_ItemCount() { - //How much of STORAGE_MAX_ITEMS is used. A stack of groupable items only counts once + //How much of Storage_MaxItems is used. A stack of groupable items only counts once int Count = 0; for (int i = 0; i < m_Storage.size(); i++) { @@ -1429,6 +1429,13 @@ int CBasePlayer::Storage_ItemCount() return Count; } +int CBasePlayer::Storage_MaxItems() +{ + //Each player has their own max, but it's never more than STORAGE_MAX_ITEMS + int Max = m_StorageMax > 0 ? m_StorageMax : STORAGE_DEFAULT_ITEMS; + return V_min(Max, STORAGE_MAX_ITEMS); +} + bool CBasePlayer::Script_SetupEvent(CScript* Script, SCRIPT_EVENT& Event) { //SetScriptVar( "game.monster.current_anim.uselegs", strlen(m_szAnimLegs) > 0 ); diff --git a/src/game/shared/ms/scriptcmds.cpp b/src/game/shared/ms/scriptcmds.cpp index d78a8fdf0..970fc55dc 100644 --- a/src/game/shared/ms/scriptcmds.cpp +++ b/src/game/shared/ms/scriptcmds.cpp @@ -1280,6 +1280,12 @@ const char* CBaseEntity::GetProp(CBaseEntity *pTarget, msstring &FullParams, mss msstring msPlayerGoldCount = STRING(iPlayerGoldCount); return msPlayerGoldCount; } + else if (Prop == "storagemax") + { + //How many items the player's storage holds + if (pTarget->IsPlayer()) RETURN_INT(((CBasePlayer *)pTarget)->Storage_MaxItems()) + return "0"; + } else if (Prop == "isbot") { //Thothie JUN2007a - check if bot From 1033fef07d7972181baafd927d572935cd2736f4 Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:39:42 -0500 Subject: [PATCH 13/14] formatting --- src/game/server/player/player.cpp | 22 +++++++++++++++++++++- src/game/server/player/playershared.cpp | 3 +-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 989e6cade..846a5a8d7 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6683,9 +6683,9 @@ bool CBasePlayer::IsInAttackStance() return false; } + #define STORAGE_TELLER_RANGE 128 //Same range as CMSMonster::Trade #define STORAGE_ITEMS_PER_MSG 13 //2 + (13 * 14 bytes per item) = 184, which fits in MAX_USERMESSAGE_SIZE - void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller, const char* pszCallback) { //Copied first, because ending the last use of my storage runs a script event @@ -6710,6 +6710,7 @@ void CBasePlayer::Storage_Open(const char* pszDisplayName, CBaseEntity *pTeller, Storage_SendMax(); } + void CBasePlayer::Storage_SetMax(int Max) { m_StorageMax = V_max(V_min(Max, STORAGE_MAX_ITEMS), 1); @@ -6717,6 +6718,7 @@ void CBasePlayer::Storage_SetMax(int Max) if (m_CurrentStorage.Active) Storage_SendMax(); } + void CBasePlayer::Storage_SendMax() { MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); @@ -6724,6 +6726,7 @@ void CBasePlayer::Storage_SendMax() WRITE_SHORT(Storage_MaxItems()); MESSAGE_END(); } + void CBasePlayer::Storage_End() { if (!m_CurrentStorage.Active) @@ -6741,6 +6744,7 @@ void CBasePlayer::Storage_End() pScripted->CallScriptEvent(m_CurrentStorage.Callback + "_done", &Parameters); } } + void CBasePlayer::Storage_Close() { Storage_End(); @@ -6749,6 +6753,7 @@ void CBasePlayer::Storage_Close() WRITE_BYTE(0); MESSAGE_END(); } + void CBasePlayer::Storage_CheckTeller() { if (!m_CurrentStorage.Active) @@ -6761,6 +6766,7 @@ void CBasePlayer::Storage_CheckTeller() Storage_Close(); } + bool CBasePlayer::Storage_CountTransfer() { if (m_CurrentStorage.TransferTime != gpGlobals->time) @@ -6771,6 +6777,7 @@ bool CBasePlayer::Storage_CountTransfer() return ++m_CurrentStorage.Transfers <= STORAGE_MAX_TRANSFER; } + void CBasePlayer::Storage_Moved(const char* pszItemName, bool In) { if (In) @@ -6784,6 +6791,7 @@ void CBasePlayer::Storage_Moved(const char* pszItemName, bool In) m_CurrentStorage.MovedOutName = pszItemName; } } + void CBasePlayer::Storage_SendMoved() { if (m_CurrentStorage.MovedIn == 1) @@ -6798,6 +6806,7 @@ void CBasePlayer::Storage_SendMoved() m_CurrentStorage.MovedIn = m_CurrentStorage.MovedOut = 0; } + void CBasePlayer::Storage_UpdateClient() { Storage_SendMoved(); @@ -6827,6 +6836,7 @@ void CBasePlayer::Storage_UpdateClient() m_CurrentStorage.ClientItems += Count; } + //How many more of Item can stack onto Cur, an item already in storage. 0 if they aren't identical static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) { @@ -6835,6 +6845,7 @@ static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) return V_max(NUM_MAX_STACK - (int)Cur.Quantity, 0); } + bool CBasePlayer::Storage_AddItem(CGenericItem *pItem) { bool Groupable = FBitSet(pItem->MSProperties(), ITEM_GROUPABLE) ? true : false; @@ -6847,6 +6858,7 @@ bool CBasePlayer::Storage_AddItem(CGenericItem *pItem) return Storage_AddItem(Item, pItem->MSProperties()); } + int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Quality) { CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(pszItemName, true); @@ -6885,6 +6897,7 @@ int CBasePlayer::Storage_AddItems(const char* pszItemName, int Quantity, int Qua return Added; } + int CBasePlayer::Storage_SwigRoom(const char* pszItemName) { //How many more swigs of a mana potion fit in my storage. @@ -6896,6 +6909,7 @@ int CBasePlayer::Storage_SwigRoom(const char* pszItemName) return Room; } + void CBasePlayer::Storage_PutSwigs(const char* pszItemName, int Swigs) { //Adds the swigs of a mana potion to my storage. Doesn't check for room, Storage_SwigRoom does @@ -6925,6 +6939,7 @@ void CBasePlayer::Storage_PutSwigs(const char* pszItemName, int Swigs) Storage_NewItem(Item); } } + void CBasePlayer::Storage_CombineSwigs() { mslist Potions; @@ -6941,6 +6956,7 @@ void CBasePlayer::Storage_CombineSwigs() for (int i = 0; i < Potions.size(); i++) Storage_PutSwigs(Potions[i].Name, (int)Potions[i].Quantity * (int)Potions[i].Quality); } + bool CBasePlayer::Storage_AddItem(storageitem_t &Item, int Properties) { if (Storage_CombinesSwigs(Item.Name.c_str(), Properties)) @@ -6992,6 +7008,7 @@ bool CBasePlayer::Storage_AddItem(storageitem_t &Item, int Properties) return true; } + void CBasePlayer::Storage_NewItem(storageitem_t &Item) { //Determine a unique ID for it @@ -7012,6 +7029,7 @@ void CBasePlayer::Storage_NewItem(storageitem_t &Item) m_Storage.add(Item); //Storage_UpdateClient sends it } + void CBasePlayer::Storage_SendItemUpdate(int Idx) { if (Idx >= m_CurrentStorage.ClientItems) @@ -7024,6 +7042,7 @@ void CBasePlayer::Storage_SendItemUpdate(int Idx) WRITE_SHORT(m_Storage[Idx].Quality); MESSAGE_END(); } + void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) { if (m_Storage[Idx].Quantity > Amt) @@ -7046,6 +7065,7 @@ void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) m_Storage.erase(Idx); } + void CBasePlayer::Storage_RemoveSwigs(int Idx, int Amt) { //Takes Amt swigs out of a mana potion in my storage. It stays in storage until its swigs run out diff --git a/src/game/server/player/playershared.cpp b/src/game/server/player/playershared.cpp index e9369ffba..9313e8e27 100644 --- a/src/game/server/player/playershared.cpp +++ b/src/game/server/player/playershared.cpp @@ -1415,7 +1415,7 @@ bool CBasePlayer::Storage_CombinesSwigs(const char* pszItemName, int Properties) int CBasePlayer::Storage_ItemCount() { - //How much of Storage_MaxItems is used. A stack of groupable items only counts once + //How much of Storage_MaxItems is used. A stack of groupable items only counts once int Count = 0; for (int i = 0; i < m_Storage.size(); i++) { @@ -1431,7 +1431,6 @@ int CBasePlayer::Storage_ItemCount() int CBasePlayer::Storage_MaxItems() { - //Each player has their own max, but it's never more than STORAGE_MAX_ITEMS int Max = m_StorageMax > 0 ? m_StorageMax : STORAGE_DEFAULT_ITEMS; return V_min(Max, STORAGE_MAX_ITEMS); } From 556167e586b104ab0e712da7500b4f86a30840dd Mon Sep 17 00:00:00 2001 From: Saint Wish <6036821+SaintWish@users.noreply.github.com> Date: Mon, 12 Oct 2026 15:44:54 -0500 Subject: [PATCH 14/14] use std vector --- src/game/client/ui/ms/vgui_storage.cpp | 4 ++-- src/game/server/character/sv_character.cpp | 2 +- src/game/server/player/player.cpp | 10 +++++----- src/game/server/player/player.h | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/game/client/ui/ms/vgui_storage.cpp b/src/game/client/ui/ms/vgui_storage.cpp index 8f6352656..de44b0cc4 100644 --- a/src/game/client/ui/ms/vgui_storage.cpp +++ b/src/game/client/ui/ms/vgui_storage.cpp @@ -315,7 +315,7 @@ void Storage_ItemMsg() continue; Item.Name = CGenericItemMgr::Item(idx)->Name; - player.m_Storage.add(Item); + player.m_Storage.push_back(Item); } Storage_Update(); @@ -327,7 +327,7 @@ void Storage_ItemRemoveMsg() for (int i = 0; i < player.m_Storage.size(); i++) if (player.m_Storage[i].ID == ID) { - player.m_Storage.erase(i); + player.m_Storage.erase(player.m_Storage.begin() + i); break; } diff --git a/src/game/server/character/sv_character.cpp b/src/game/server/character/sv_character.cpp index 89e4d7f46..7e16dab0e 100644 --- a/src/game/server/character/sv_character.cpp +++ b/src/game/server/character/sv_character.cpp @@ -443,7 +443,7 @@ bool chardata_t::ReadDataPack(const char *pData, size_t Size) ApplyItemAlias(Item.Name); Item.ID = m_Storage.size(); //IDs aren't saved. They only have to be unique - m_Storage.add(Item); + m_Storage.push_back(Item); } break; } diff --git a/src/game/server/player/player.cpp b/src/game/server/player/player.cpp index 846a5a8d7..1be6917c2 100644 --- a/src/game/server/player/player.cpp +++ b/src/game/server/player/player.cpp @@ -6942,15 +6942,15 @@ void CBasePlayer::Storage_PutSwigs(const char* pszItemName, int Swigs) void CBasePlayer::Storage_CombineSwigs() { - mslist Potions; + std::vector Potions; for (int i = (int)m_Storage.size() - 1; i >= 0; i--) { CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(m_Storage[i].Name, true); if (!pGlobalItem || !Storage_CombinesSwigs(pGlobalItem->ItemName.c_str(), pGlobalItem->MSProperties())) continue; - Potions.add(m_Storage[i]); - m_Storage.erase(i); + Potions.push_back(m_Storage[i]); + m_Storage.erase(m_Storage.begin() + i); } for (int i = 0; i < Potions.size(); i++) @@ -7027,7 +7027,7 @@ void CBasePlayer::Storage_NewItem(storageitem_t &Item) break; } - m_Storage.add(Item); //Storage_UpdateClient sends it + m_Storage.push_back(Item); //Storage_UpdateClient sends it } void CBasePlayer::Storage_SendItemUpdate(int Idx) @@ -7063,7 +7063,7 @@ void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) m_CurrentStorage.ClientItems--; } - m_Storage.erase(Idx); + m_Storage.erase(m_Storage.begin() + Idx); } void CBasePlayer::Storage_RemoveSwigs(int Idx, int Amt) diff --git a/src/game/server/player/player.h b/src/game/server/player/player.h index 8f31d7ec1..2949cf595 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -375,7 +375,7 @@ struct chardata_t : savedata_t statlist m_Stats; //All stats. Natural stats and Skill-based stats std::vector m_Spells; //All known spells mslist m_Items; //All carried items - mslist m_Storage; //All items in storage + std::vector m_Storage; //All items in storage mslist m_Companions; //Companions mslist m_Quests; //All the quests I've completed fixed_vector m_QuickSlots; //All the quickslots (based on the item IDs of the last save). Extras in a save are dropped @@ -445,7 +445,7 @@ class CBasePlayer : public CMSMonster float m_TimeCanVote; bool m_fClientInitiated; //Client sent this command to the server. Make sure to cache any item changes so they don't get sent back to client bool m_fInTownArea; - mslist m_Storage; //Items I have in storage + std::vector m_Storage; //Items I have in storage int m_StorageMax; //How many items my storage holds. 0 until it's set, which gives STORAGE_DEFAULT_ITEMS storageaccess_t m_CurrentStorage; //Set while I have my storage open bool m_Initialized; //Whether stats were created, script loaded, etc