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_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 f2d1b594f..de44b0cc4 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,26 @@ 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]); + storageitem_t &Item = player.m_Storage[i]; + + //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 (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 + pGearItemButton->m_ItemContainer->AddItem(vNewItem); } @@ -111,6 +110,7 @@ void CStoragePanel::AddInventoryItems() return; } + void CStoragePanel::Update() { if (!player.m_CurrentStorage.Active) @@ -122,7 +122,7 @@ void CStoragePanel::Update() CStorePanel::Update(); - m_pTitle->setText(player.m_CurrentStorage.DisplayName); + m_pTitle->setText(player.m_CurrentStorage.DisplayName + " (" + player.Storage_ItemCount() + "/" + player.Storage_MaxItems() + ")"); } void CStoragePanel::GearItemSelected(ulong ID) @@ -168,7 +168,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) @@ -185,55 +185,38 @@ bool CStoragePanel::GearItemClicked(ulong ID) return true; } -//MiB FEB2008a -bool CStoragePanel::checkValid(containeritem_t &item) +void CStoragePanel::ItemDoubleclicked(ulong ID) { - 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; + //Double clicking an item in storage takes it out and puts it in my hands + if (m_LastGearItem != 1) + return; - return true; + UnSelectAllItems(); + m_InfoPanel->setVisible(false); + + msstring Command = msstring("storage remove ") + (int)ID + " hands\n"; + gEngfuncs.pfnClientCmd(Command); } -//Item selected -int CStoragePanel::ItemRetrievalCost(containeritem_t &Item) +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) { - return int(Item.Value * player.m_CurrentStorage.flFeeRatio); + //The server checks this too + return CBasePlayer::Storage_CanStoreItem(item.Name.c_str(), item.Properties); } + 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 +227,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,18 +237,21 @@ 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()) { + msstring Text; if (m_LastGearItem == 1) - m_SaleLabel->setText(msstring("Removing ") + (int)m_SelectedItems.size() + " items from storage for " + Valuetotal + " gold"); + 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 @@ -311,48 +295,71 @@ void Storage_Update() //Item msg void Storage_ItemReset() { - player.m_Storages.clear(); + player.m_Storage.clear(); Storage_Update(); } void Storage_ItemMsg() { - msstring StorageName = READ_STRING(); - storage_t *pStorage = NULL; + int Count = READ_BYTE(); + for (int i = 0; i < Count; i++) + { + storageitem_t Item; + Item.ID = READ_LONG(); + int idx = READ_LONG(); + Item.Quantity = READ_SHORT(); + Item.Quality = READ_SHORT(); + Item.MaxQuality = READ_SHORT(); + + if (idx < 0 || idx >= CGenericItemMgr::ItemCount()) + continue; + + Item.Name = CGenericItemMgr::Item(idx)->Name; + player.m_Storage.push_back(Item); + } - for (int s = 0; s < player.m_Storages.size(); s++) - if (player.m_Storages[s].Name == StorageName) + Storage_Update(); +} + +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(player.m_Storage.begin() + i); break; } - if (!pStorage) - { - storage_t Storage; - Storage.Name = StorageName; - pStorage = player.Storage_CreateAccount(Storage); - } + Storage_Update(); +} - bool AddItem = READ_BYTE() ? true : false; +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; + } - if (AddItem) - { - CGenericItem *pItem = ReadGenericItem(true); - if (pItem) - pStorage->Items.add(genericitem_full_t(pItem)); + Storage_Update(); +} - pItem->SUB_Remove(); - } +void Storage_MaxMsg() +{ + player.m_StorageMax = READ_SHORT(); 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..99905a07b 100644 --- a/src/game/client/ui/ms/vgui_storage.h +++ b/src/game/client/ui/ms/vgui_storage.h @@ -12,13 +12,14 @@ class CStoragePanel : public CStorePanel void AddInventoryItems(); void Update(); void Close(); - int ItemRetrievalCost(containeritem_t &Item); //Item callbacks void ItemSelectChanged(ulong ID, bool fSelected); void ItemHighlighted(void *pData); 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/client/ui/ms/vgui_storemainwin.cpp b/src/game/client/ui/ms/vgui_storemainwin.cpp index 86c7cecbf..56bbae832 100644 --- a/src/game/client/ui/ms/vgui_storemainwin.cpp +++ b/src/game/client/ui/ms/vgui_storemainwin.cpp @@ -95,6 +95,9 @@ void MsgFunc_Store() void Update_StoreBuy(); 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) { @@ -143,6 +146,15 @@ 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; + case 5: + Storage_MaxMsg(); + break; } return 0; } 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) diff --git a/src/game/server/character/mscharacterheader.h b/src/game/server/character/mscharacterheader.h index edc93c07c..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; }; // @@ -88,10 +89,11 @@ enum charfield_e CF_SKILLS = 33, //[ stat: [ substat: [value, exp], ... ], ... ] CF_SPELLS = 34, //[str, ...] CF_ITEMS = 35, //[item, ...] - CF_STORAGES = 36, //[storage, ...] - CF_COMPANIONS = 37, //[companion, ...] - CF_QUESTS = 38, //[ [name, data], ... ] - CF_QUICKSLOTS = 39, //[ nil | [type, id], ... ] + CF_COMPANIONS = 36, //[companion, ...] + CF_QUESTS = 37, //[ [name, data], ... ] + CF_QUICKSLOTS = 38, //[ nil | [type, id], ... ] + CF_STORAGE = 39, //[storageitem, ...] + CF_STORAGEMAX = 40, //int }; //Item keys @@ -108,11 +110,13 @@ enum charitemfield_e CI_CONTENTS = 8, //[item, ...] - containers only }; -//Storage keys -enum charstoragefield_e +//Storage item keys +enum charstorageitemfield_e { - CS_NAME = 0, //str - 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 7ce4c5238..7e16dab0e 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; @@ -413,23 +414,36 @@ bool chardata_t::ReadDataPack(const char *pData, size_t Size) break; } - case CF_STORAGES: + case CF_STORAGE: { - msgpack::object_array Storages = PackArray(Value); - for (uint32_t s = 0; s < Storages.size; s++) + msgpack::object_array Items = PackArray(Value); + for (uint32_t i = 0; i < Items.size; i++) { - storage_t Storage; - 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++) { 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; + 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; } } - m_Storages.add(Storage); + + if (!Item.Name.len() || !Item.Quantity) + { + 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.push_back(Item); } break; } @@ -553,7 +567,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 +707,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); } } } @@ -1057,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); @@ -1104,19 +1113,17 @@ 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); + Out.Pk.pack_array(pPlayer->m_Storage.size()); + for (int i = 0; i < pPlayer->m_Storage.size(); i++) { - storage_t &Storage = pPlayer->m_Storages[s]; + storageitem_t &Item = pPlayer->m_Storage[i]; - 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])) - Items.Added(); + 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." @@ -1259,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/client.cpp b/src/game/server/client.cpp index ef1a6f904..8480c6c13 100644 --- a/src/game/server/client.cpp +++ b/src/game/server/client.cpp @@ -931,97 +931,105 @@ 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; if (pPlayer->m_CurrentStorage.Active) { - storage_t *pStorage = pPlayer->Storage_GetStorage(pPlayer->m_CurrentStorage.StorageName); - if (pStorage) + //The storage panel doesn't let more than this be moved at once + if (!pPlayer->Storage_CountTransfer()) + return; + + 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) + if (pPlayer->Storage_AddItem(pItem)) { - //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"); + pPlayer->Storage_Moved(SPEECH::ItemName(pItem), true); pItem->SUB_Remove(); } + else + pPlayer->SendEventMsg(HUDEVENT_UNABLE, "Your storage is full"); } - else if (!_stricmp(CMD_ARGV(1), "remove")) + } + else if (!_stricmp(CMD_ARGV(1), "remove")) + { + if (CMD_ARGC() < 4) + return; + + int ItemID = atoi(CMD_ARGV(2)); + 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) + { + pStorageItem = &pPlayer->m_Storage[i]; + StorageItemIdx = i; + break; + } + + if (pStorageItem) { - 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) + CGenericItem *pItem = NewGenericItem(pStorageItem->Name); + if (pItem) + { + //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)) { - pStorageItem = &pStorage->Items[i]; - StorageItemIdx = i; - break; + Amt = pStorageItem->Quantity; + pItem->iQuantity = Amt; } - if (pStorageItem) - { - CGenericItem *pItem = *pStorageItem; - int Cost = int(pItem->m_Value * pPlayer->m_CurrentStorage.flFeeRatio); + //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 + bool Success = false; - if (!Cost || pPlayer->m_Gold > Cost) //If no retrieval fee or I have enough to pay it... + 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 { - int ContainerID = atol(CMD_ARGV(3)); - bool Success = false; + CGenericItem *pContainer = MSUtil_GetItemByID(ContainerID, pPlayer); + if (pContainer) + Success = pPlayer->PutInPack(pItem, pContainer); + } - 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"); - 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(); - } + if (Success) + { + pPlayer->Storage_Moved(SPEECH::ItemName(pItem), false); + if (Swigs) + pPlayer->Storage_RemoveSwigs(StorageItemIdx, SwigAmt); else - pItem->SUB_Remove(); + pPlayer->Storage_RemoveItem(StorageItemIdx, Amt); + if (ToHands) + pPlayer->Storage_Close(); //Like the inventory, taking an item into my hands closes the storage } 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/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 4476a4128..04d3e365c 100644 --- a/src/game/server/monsters/npcscript.cpp +++ b/src/game/server/monsters/npcscript.cpp @@ -1388,63 +1388,42 @@ 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 + //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") { - 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"; + ((CBasePlayer *)pTarget)->Storage_Open(Params[2], this, Callback); - 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; - } + static msstringlist Parameters; + Parameters.clearitems(); + Parameters.add(EntToString(pTarget)); + 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)); + } + //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 c75ff60f0..1be6917c2 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,9 @@ 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 + m_StorageMax = Data.StorageMax; + Storage_CombineSwigs(); //Read Companions @@ -6678,55 +6683,400 @@ 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 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 + msstring DisplayName = pszDisplayName, Callback = pszCallback; + Storage_End(); //In case I still have it open through another entity + 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.DisplayName = DisplayName; + m_CurrentStorage.Teller = pTeller; + m_CurrentStorage.Callback = Callback; + 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. + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_STOREITEM], NULL, pev); + WRITE_BYTE(1); //reset storage items + MESSAGE_END(); + + Storage_SendMax(); } -void CBasePlayer::Storage_Send() + +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(1); //reset storage items + WRITE_BYTE(5); //storage max + WRITE_SHORT(Storage_MaxItems()); MESSAGE_END(); +} + +void CBasePlayer::Storage_End() +{ + if (!m_CurrentStorage.Active) + return; - //Send all storage items. - for (int s = 0; s < m_Storages.size(); s++) + m_CurrentStorage.Active = false; + m_CurrentStorage.ClientItems = 0; + + CBaseEntity *pTeller = m_CurrentStorage.Teller.Entity(); + IScripted *pScripted = pTeller ? pTeller->GetScripted() : NULL; + if (pScripted) { - if (!m_Storages[s].Items.size()) - { - //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(); - } - else - { - //If the storage has items, send a full message - for (int i = 0; i < m_Storages[s].Items.size(); i++) + msstringlist Parameters; + Parameters.add(EntToString(this)); + pScripted->CallScriptEvent(m_CurrentStorage.Callback + "_done", &Parameters); + } +} + +void CBasePlayer::Storage_Close() +{ + Storage_End(); + + MESSAGE_BEGIN(MSG_ONE, g_netmsg[NETMSG_VGUIMENU], NULL, pev); + WRITE_BYTE(0); + MESSAGE_END(); +} + +void CBasePlayer::Storage_CheckTeller() +{ + 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(); +} + +bool CBasePlayer::Storage_CountTransfer() +{ + 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) +{ + if (In) + { + m_CurrentStorage.MovedIn++; + m_CurrentStorage.MovedInName = pszItemName; + } + else + { + m_CurrentStorage.MovedOut++; + m_CurrentStorage.MovedOutName = pszItemName; + } +} + +void CBasePlayer::Storage_SendMoved() +{ + 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() +{ + 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) + 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(2); //add storage items + WRITE_BYTE(Count); + for (int i = 0; i < Count; i++) + { + 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 +static int Storage_StackRoom(storageitem_t &Cur, storageitem_t &Item) +{ + if (Cur.Name != Item.Name || Cur.Quality != Item.Quality || Cur.MaxQuality != Item.MaxQuality) + return 0; + + 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; + + storageitem_t Item; + Item.Name = pItem->ItemName; + Item.Quantity = Groupable ? V_max(pItem->iQuantity, 1) : 1; + Item.Quality = pItem->Quality; + Item.MaxQuality = pItem->MaxQuality; + + return Storage_AddItem(Item, pItem->MSProperties()); +} + +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 Properties = pGlobalItem->MSProperties(); + if (Quality < 0) + Quality = pGlobalItem->Quality; + + 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_MaxItems() - Storage_ItemCount()); + + int Added = 0; + while (Added < Quantity) + { + storageitem_t Item; + Item.Name = pGlobalItem->ItemName; + Item.Quantity = V_min(Quantity - Added, NUM_MAX_STACK); + Item.Quality = Quality; + Item.MaxQuality = pGlobalItem->MaxQuality; + + int Amt = Item.Quantity; //Storage_AddItem changes the quantity + if (!Storage_AddItem(Item, Properties)) + break; + + Added += Amt; + } + + return Added; +} + +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_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); + + 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() +{ + 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.push_back(m_Storage[i]); + m_Storage.erase(m_Storage.begin() + 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 + int Count = Storage_ItemCount(), Max = Storage_MaxItems(); + if (!Groupable) + { + if (Count + (int)Item.Quantity > Max) + return false; + } + else if (Count >= Max) + { + //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); + + if (Room < (int)Item.Quantity) + return false; + } + + 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) + continue; + + m_Storage[i].Quantity += Amt; + Item.Quantity -= Amt; + Storage_SendItemUpdate(i); + } + + //What's left becomes a new item in storage + if (Item.Quantity) + Storage_NewItem(Item); + + 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; + 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; } + + m_Storage.push_back(Item); //Storage_UpdateClient sends it +} + +void CBasePlayer::Storage_SendItemUpdate(int Idx) +{ + 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].Quality); + MESSAGE_END(); +} + +void CBasePlayer::Storage_RemoveItem(int Idx, int Amt) +{ + if (m_Storage[Idx].Quantity > Amt) + { + m_Storage[Idx].Quantity -= Amt; + 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(m_Storage.begin() + 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 a3112948a..2949cf595 100644 --- a/src/game/server/player/player.h +++ b/src/game/server/player/player.h @@ -29,6 +29,10 @@ // 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 enum sbar_data { @@ -309,19 +313,25 @@ struct genericitem_full_t : genericitem_t byte Spell_CastSuccess; mslist ContainerItems; }; -struct storage_t +//An item in storage. Only what's needed to create the item again is kept +struct storageitem_t { - msstring Name; - mslist Items; + ulong ID; //Unique within the storage. Not saved + msstring Name; //Item script name + unsigned short Quantity; //How many are in the stack + unsigned short Quality, MaxQuality; }; 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 + 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 @@ -365,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_Storages; //Storage places where I have items + 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 @@ -382,7 +392,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 +445,9 @@ 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 + 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 float m_TimeNextSave; bool m_CanJoin; //Whether I have any characters on this server that can join the game @@ -533,8 +544,26 @@ 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, const char* pszCallback); + void Storage_End(); + 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 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(); + 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); @@ -628,8 +657,10 @@ 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 + 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_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 68582679c..9313e8e27 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,46 @@ 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, and storage doesn't keep what's inside a container + if (FBitSet(Properties, ITEM_SPELL | ITEM_CONTAINER)) + return false; + + //MiB FEB2008a - the bank mask + 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; - return NULL; + return true; +} + +bool CBasePlayer::Storage_CombinesSwigs(const char* pszItemName, int Properties) +{ + return FBitSet(Properties, ITEM_DRINKABLE) && !strncmp(pszItemName, "mana_", 5); } -storage_t* CBasePlayer::Storage_CreateAccount(storage_t& Storage) + +int CBasePlayer::Storage_ItemCount() { - storage_t* pExistingStorage = Storage_GetStorage(Storage.Name); - if (pExistingStorage) - return pExistingStorage; + //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++) + { + CGenericItem *pGlobalItem = CGenericItemMgr::GetGlobalGenericItemByName(m_Storage[i].Name, true); + if (pGlobalItem && FBitSet(pGlobalItem->MSProperties(), ITEM_GROUPABLE)) + Count++; + else + Count += m_Storage[i].Quantity; + } - return &m_Storages.add(Storage); + return Count; +} + +int CBasePlayer::Storage_MaxItems() +{ + 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) 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